backup.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. const fs = require('fs')
  2. const sqlite3 = require('sqlite3')
  3. const path = require('path')
  4. const log = require('./util/log')
  5. const filehash = require('./util/backup_filehash')
  6. /**
  7. * Backup3 is the version 4 of the backup library.
  8. * It focuses on file lookups, and better error handling.
  9. */
  10. class Backup {
  11. /**
  12. * Create a new backup instance.
  13. * @param {*} base path to backups folder.. Defaults to '~/Library/Application Support/MobileSync/Backup/'
  14. * @param {*} id directory name of the backup.
  15. */
  16. constructor (base, id) {
  17. log.verbose(`create backup with base=${base}, id=${id}`)
  18. id = id || ''
  19. base = base || ''
  20. // Very wierd, but unwrap from existing backup instance.
  21. if (id.constructor === Backup) {
  22. id = id.id
  23. log.verbose(`unwrapping backup to id=${id}`)
  24. }
  25. this.id = id
  26. this.base = base
  27. // Get the path of the folder.
  28. if (base) {
  29. this.path = path.join(base, id)
  30. } else {
  31. this.path = path.join(process.env.HOME, '/Library/Application Support/MobileSync/Backup/', id)
  32. }
  33. }
  34. /**
  35. * Derive a file's ID from it's filename and domain.
  36. * @param {string} file the path to the file in the domain
  37. * @param {string=} domain (optional) the file's domain. Default: HomeDomain
  38. */
  39. getFileID (path, domain) {
  40. return Backup.getFileID(path, domain)
  41. }
  42. /**
  43. * Derive a file's ID from it's filename and domain.
  44. * @param {string} file the path to the file in the domain
  45. * @param {string=} domain (optional) the file's domain. Default: HomeDomain
  46. */
  47. static getFileID (path, domain) {
  48. return filehash(path, domain)
  49. }
  50. /**
  51. * Get the on-disk filename of a fileID.
  52. * You shouldn't really ever need to use the isAbsolute flag at all.
  53. * By default, it searches both possibile paths.
  54. *
  55. * @param {*} fileID the file ID. derive using getFileID()
  56. * @param {boolean=} isAbsoulte (optional) default: false. should we check other file locations?.
  57. * @throws Throws an error if no file is found
  58. */
  59. getFileName (fileID, isAbsoulte) {
  60. // Default to non-absolute paths.
  61. isAbsoulte = isAbsoulte || false
  62. // Possible file locations for an ID
  63. let possibilities
  64. if (isAbsoulte) {
  65. // We must only check in the root folder of the backup.
  66. possibilities = [
  67. path.join(this.path, fileID)
  68. ]
  69. } else {
  70. // Check in both /abcdefghi and /ab/abcdefghi
  71. possibilities = [
  72. path.join(this.path, fileID),
  73. path.join(this.path, fileID.substr(0, 2), fileID)
  74. ]
  75. }
  76. // Return first path that works.
  77. for (let path of possibilities) {
  78. log.verbose('trying', path, fs.existsSync(path))
  79. // Check if the path exists
  80. if (fs.existsSync(path)) {
  81. log.verbose('trying', path, '[found]')
  82. // If it does, return it.
  83. return path
  84. }
  85. }
  86. // Throw an error.
  87. throw new Error(`Could not find a file needed for this report. It may not be compatibile with this specific backup or iOS Version.`)
  88. }
  89. /**
  90. * Open a database referenced by a fileID
  91. * It uses getFileName(), so it tries both v2 and v3 paths.
  92. * @param {string} fileID ihe file id
  93. * @param {boolean=} isAbsoulte is this an absolute path? default: false.
  94. * @returns {Promise.<sqlite3.Database>} database instance.
  95. */
  96. openDatabase (fileID, isAbsoulte) {
  97. return new Promise((resolve, reject) => {
  98. try {
  99. // Lookup the filename
  100. let file = this.getFileName(fileID, isAbsoulte)
  101. // Open as read only
  102. let db = new sqlite3.Database(file, sqlite3.OPEN_READONLY, (err) => {
  103. if (err) { return reject(err) }
  104. if (db != null) {
  105. resolve(db)
  106. } else {
  107. reject(new Error('did not get a database instance.'))
  108. }
  109. })
  110. } catch (e) {
  111. return reject(e)
  112. }
  113. })
  114. }
  115. }
  116. module.exports = Backup