123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- const fs = require('fs')
- const sqlite3 = require('sqlite3')
- const path = require('path')
- const log = require('./util/log')
- const filehash = require('./util/backup_filehash')
- const manifestMBDBParse = require('./util/manifest_mbdb_parse')
- const os = require('os')
- /**
- * Backup3 is the version 4 of the backup library.
- * It focuses on file lookups, and better error handling.
- */
- class Backup {
- /**
- * Create a new backup instance.
- * @param {*} base path to backups folder.. Defaults to '~/Library/Application Support/MobileSync/Backup/'
- * @param {*} id directory name of the backup.
- */
- constructor (base, id) {
- log.verbose(`create backup with base=${base}, id=${id}`)
- id = id || ''
- base = base || ''
- // Very wierd, but unwrap from existing backup instance.
- if (id.constructor === Backup) {
- id = id.id
- log.verbose(`unwrapping backup to id=${id}`)
- }
- this.id = id
- this.base = base
- // Get the path of the folder.
- if (base) {
- this.path = path.join(base, id)
- } else {
- this.path = path.join(os.homedir(), '/Library/Application Support/MobileSync/Backup/', id)
- }
- }
- /**
- * Derive a file's ID from it's filename and domain.
- * @param {string} file the path to the file in the domain
- * @param {string=} domain (optional) the file's domain. Default: HomeDomain
- */
- getFileID (path, domain) {
- return Backup.getFileID(path, domain)
- }
- /**
- * Derive a file's ID from it's filename and domain.
- * @param {string} file the path to the file in the domain
- * @param {string=} domain (optional) the file's domain. Default: HomeDomain
- */
- static getFileID (path, domain) {
- return filehash(path, domain)
- }
- /**
- * Get the on-disk filename of a fileID.
- * You shouldn't really ever need to use the isAbsolute flag at all.
- * By default, it searches both possibile paths.
- *
- * @param {*} fileID the file ID. derive using getFileID()
- * @param {boolean=} isAbsoulte (optional) default: false. should we check other file locations?.
- * @throws Throws an error if no file is found
- */
- getFileName (fileID, isAbsoulte) {
- // Default to non-absolute paths.
- isAbsoulte = isAbsoulte || false
- // Possible file locations for an ID
- let possibilities
- if (isAbsoulte) {
- // We must only check in the root folder of the backup.
- possibilities = [
- path.join(this.path, fileID)
- ]
- } else {
- // Check in both /abcdefghi and /ab/abcdefghi
- possibilities = [
- path.join(this.path, fileID),
- path.join(this.path, fileID.substr(0, 2), fileID)
- ]
- }
- // Return first path that works.
- for (let path of possibilities) {
- log.verbose('trying', path, fs.existsSync(path))
- // Check if the path exists
- if (fs.existsSync(path)) {
- log.verbose('trying', path, '[found]')
- // If it does, return it.
- return path
- }
- }
- // Throw an error.
- throw new Error(`Could not find a file needed for this report. It may not be compatibile with this specific backup or iOS Version.`)
- }
- /// Get the manifest for an sqlite database if available
- getSqliteFileManifest () {
- return new Promise(async (resolve, reject) => {
- this.openDatabase('Manifest.db', true)
- .then(db => {
- db.all('SELECT fileID, domain, relativePath as filename from FILES', async function (err, rows) {
- if (err) reject(err)
-
- resolve(rows)
- })
- })
- .catch(reject)
- })
- }
- /// Get the manifest from the mbdb file
- getMBDBFileManifest () {
- return new Promise((resolve, reject) => {
- let mbdbPath = this.getFileName('Manifest.mbdb', true)
- manifestMBDBParse.process(mbdbPath, resolve, reject)
- })
- }
-
- /// Try to load both of the manifest files
- getManifest () {
- return new Promise(async (resolve, reject) => {
- // Try the new sqlite file database.
- try {
- log.verbose('Trying sqlite manifest...')
- let item = await this.getSqliteFileManifest(this)
- return resolve(item)
- } catch (e) {
- log.verbose('Trying sqlite manifest... [failed]', e)
- }
-
- // Try the mbdb file database
- try {
- log.verbose('Trying mbdb manifest...')
- let item = await this.getMBDBFileManifest(this)
- return resolve(item)
- } catch (e) {
- log.verbose('Trying mbdb manifest...[failed]', e)
- }
-
- reject(new Error('Could not find a manifest.'))
- })
- }
-
- /**
- * Open a database referenced by a fileID
- * It uses getFileName(), so it tries both v2 and v3 paths.
- * @param {string} fileID ihe file id
- * @param {boolean=} isAbsoulte is this an absolute path? default: false.
- * @returns {Promise.<sqlite3.Database>} database instance.
- */
- openDatabase (fileID, isAbsoulte) {
- return new Promise((resolve, reject) => {
- try {
- // Lookup the filename
- let file = this.getFileName(fileID, isAbsoulte)
- // Open as read only
- let db = new sqlite3.Database(file, sqlite3.OPEN_READONLY, (err) => {
- if (err) { return reject(err) }
- if (db != null) {
- resolve(db)
- } else {
- reject(new Error('did not get a database instance.'))
- }
- })
- } catch (e) {
- return reject(e)
- }
- })
- }
- }
- module.exports = Backup
|