files.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. const fs = require('fs-extra')
  2. const path = require('path')
  3. const log = require('../../util/log')
  4. const manifestMBDBParse = require('../../util/manifest_mbdb_parse')
  5. const bplist = require('bplist-parser')
  6. const Mode = require('stat-mode');
  7. module.exports = {
  8. version: 4,
  9. name: 'backup.files',
  10. description: `Gets a backup's file list`,
  11. requiresBackup: true,
  12. // Run on a v3 lib / backup object.
  13. run (lib, { backup, extract, filter }) {
  14. return new Promise(async (resolve, reject) => {
  15. getManifest(backup)
  16. .then(files => {
  17. // Possibly extract objects.
  18. if (extract) {
  19. extractFiles(backup, extract, filter, files)
  20. }
  21. resolve(files)
  22. })
  23. .catch(reject)
  24. })
  25. },
  26. // Available fields.
  27. output: {
  28. id: el => el.fileID,
  29. domain: el => el.domain,
  30. path: el => el.filename,
  31. size: el => el.filelen || 0,
  32. mode: el => new Mode(el).toString()
  33. }
  34. }
  35. /// Get the manifest for an sqlite database if available
  36. function getSqliteFileManifest (backup) {
  37. return new Promise(async (resolve, reject) => {
  38. backup.openDatabase('Manifest.db', true)
  39. .then(db => {
  40. db.all('SELECT fileID, domain, relativePath as filename, file from FILES', async function (err, rows) {
  41. if (err) reject(err)
  42. // Extract binary plist metadata
  43. for (var row of rows) {
  44. let data = bplist.parseBuffer(row.file)[0]
  45. let metadata = data['$objects'][1];
  46. row.filelen = metadata.Size
  47. row.mode = metadata.Mode
  48. }
  49. resolve(rows)
  50. })
  51. })
  52. .catch(reject)
  53. })
  54. }
  55. /// Get the manifest from the mbdb file
  56. function getMBDBFileManifest (backup) {
  57. return new Promise((resolve, reject) => {
  58. let mbdbPath = backup.getFileName('Manifest.mbdb', true)
  59. manifestMBDBParse.process(mbdbPath, resolve, reject)
  60. })
  61. }
  62. /// Try to load both of the manifest files
  63. function getManifest (backup) {
  64. return new Promise(async (resolve, reject) => {
  65. // Try the new sqlite file database.
  66. try {
  67. log.verbose('Trying sqlite manifest...')
  68. let item = await getSqliteFileManifest(backup)
  69. return resolve(item)
  70. } catch (e) {
  71. log.verbose('Trying sqlite manifest... [failed]', e)
  72. }
  73. // Try the mbdb file database
  74. try {
  75. log.verbose('Trying mbdb manifest...')
  76. let item = await getMBDBFileManifest(backup)
  77. return resolve(item)
  78. } catch (e) {
  79. log.verbose('Trying mbdb manifest...[failed]', e)
  80. }
  81. reject(new Error('Could not find a manifest.'))
  82. })
  83. }
  84. /// Filter exclusion check
  85. function isIncludedByFilter (filter, item) {
  86. return filter === 'all' ||
  87. filter === undefined ||
  88. (filter && item.domain.indexOf(filter) > -1) ||
  89. (filter && item.filename.indexOf(filter) > -1)
  90. }
  91. /// Extract files
  92. /// - backup: the backup api object
  93. /// - destination: file system location
  94. /// - filter: contains check filter for files
  95. /// - items: list of files.
  96. function extractFiles (backup, destination, filter, items) {
  97. for (var item of items) {
  98. // Filter by the domain.
  99. // Simple "Contains" Search
  100. if (!isIncludedByFilter(filter, item)) {
  101. // Skip to the next iteration of the loop.
  102. log.action('skipped', item.filename)
  103. continue
  104. }
  105. try {
  106. var stat = new Mode(item)
  107. if (stat.isDirectory()) {
  108. // Created implicitly below
  109. continue
  110. }
  111. if (stat.isSymbolicLink()) {
  112. log.warning('skipping symlink', item.filename)
  113. // FIXME: Restore symlinks
  114. continue
  115. }
  116. let sourceFile = backup.getFileName(item.fileID)
  117. // Only process files that exist.
  118. if (fs.existsSync(sourceFile)) {
  119. log.action('export', item.filename)
  120. // Calculate the output dir.
  121. var outDir = path.join(destination, item.domain, item.filename)
  122. // Create the directory and copy
  123. fs.ensureDirSync(path.dirname(outDir))
  124. fs.copySync(sourceFile, outDir)
  125. // Save output info to the data item.
  126. item.output_dir = outDir
  127. } else {
  128. log.error('not found', sourceFile)
  129. }
  130. } catch (e) {
  131. log.error(item.fileID, item.filename, e.toString())
  132. }
  133. }
  134. }