68 lines
2.3 KiB
JavaScript
68 lines
2.3 KiB
JavaScript
|
'use strict';
|
||
|
const fs = require('fs');
|
||
|
const path = require('path');
|
||
|
const { promisify } = require('util');
|
||
|
const { cppdb } = require('../util');
|
||
|
const fsAccess = promisify(fs.access);
|
||
|
|
||
|
module.exports = async function backup(filename, options) {
|
||
|
if (options == null) options = {};
|
||
|
|
||
|
// Validate arguments
|
||
|
if (typeof filename !== 'string') throw new TypeError('Expected first argument to be a string');
|
||
|
if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object');
|
||
|
|
||
|
// Interpret options
|
||
|
filename = filename.trim();
|
||
|
const attachedName = 'attached' in options ? options.attached : 'main';
|
||
|
const handler = 'progress' in options ? options.progress : null;
|
||
|
|
||
|
// Validate interpreted options
|
||
|
if (!filename) throw new TypeError('Backup filename cannot be an empty string');
|
||
|
if (filename === ':memory:') throw new TypeError('Invalid backup filename ":memory:"');
|
||
|
if (typeof attachedName !== 'string') throw new TypeError('Expected the "attached" option to be a string');
|
||
|
if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
|
||
|
if (handler != null && typeof handler !== 'function') throw new TypeError('Expected the "progress" option to be a function');
|
||
|
|
||
|
// Make sure the specified directory exists
|
||
|
await fsAccess(path.dirname(filename)).catch(() => {
|
||
|
throw new TypeError('Cannot save backup because the directory does not exist');
|
||
|
});
|
||
|
|
||
|
const isNewFile = await fsAccess(filename).then(() => false, () => true);
|
||
|
return runBackup(this[cppdb].backup(this, attachedName, filename, isNewFile), handler || null);
|
||
|
};
|
||
|
|
||
|
const runBackup = (backup, handler) => {
|
||
|
let rate = 0;
|
||
|
let useDefault = true;
|
||
|
|
||
|
return new Promise((resolve, reject) => {
|
||
|
setImmediate(function step() {
|
||
|
try {
|
||
|
const progress = backup.transfer(rate);
|
||
|
if (!progress.remainingPages) {
|
||
|
backup.close();
|
||
|
resolve(progress);
|
||
|
return;
|
||
|
}
|
||
|
if (useDefault) {
|
||
|
useDefault = false;
|
||
|
rate = 100;
|
||
|
}
|
||
|
if (handler) {
|
||
|
const ret = handler(progress);
|
||
|
if (ret !== undefined) {
|
||
|
if (typeof ret === 'number' && ret === ret) rate = Math.max(0, Math.min(0x7fffffff, Math.round(ret)));
|
||
|
else throw new TypeError('Expected progress callback to return a number or undefined');
|
||
|
}
|
||
|
}
|
||
|
setImmediate(step);
|
||
|
} catch (err) {
|
||
|
backup.close();
|
||
|
reject(err);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
};
|