files.js 3.9 KB

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