backup.js 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. 'use strict';
  2. const fs = require('fs');
  3. const path = require('path');
  4. const { promisify } = require('util');
  5. const { cppdb } = require('../util');
  6. const fsAccess = promisify(fs.access);
  7. module.exports = async function backup(filename, options) {
  8. if (options == null) options = {};
  9. // Validate arguments
  10. if (typeof filename !== 'string') throw new TypeError('Expected first argument to be a string');
  11. if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object');
  12. // Interpret options
  13. filename = filename.trim();
  14. const attachedName = 'attached' in options ? options.attached : 'main';
  15. const handler = 'progress' in options ? options.progress : null;
  16. // Validate interpreted options
  17. if (!filename) throw new TypeError('Backup filename cannot be an empty string');
  18. if (filename === ':memory:') throw new TypeError('Invalid backup filename ":memory:"');
  19. if (typeof attachedName !== 'string') throw new TypeError('Expected the "attached" option to be a string');
  20. if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
  21. if (handler != null && typeof handler !== 'function') throw new TypeError('Expected the "progress" option to be a function');
  22. // Make sure the specified directory exists
  23. await fsAccess(path.dirname(filename)).catch(() => {
  24. throw new TypeError('Cannot save backup because the directory does not exist');
  25. });
  26. const isNewFile = await fsAccess(filename).then(() => false, () => true);
  27. return runBackup(this[cppdb].backup(this, attachedName, filename, isNewFile), handler || null);
  28. };
  29. const runBackup = (backup, handler) => {
  30. let rate = 0;
  31. let useDefault = true;
  32. return new Promise((resolve, reject) => {
  33. setImmediate(function step() {
  34. try {
  35. const progress = backup.transfer(rate);
  36. if (!progress.remainingPages) {
  37. backup.close();
  38. resolve(progress);
  39. return;
  40. }
  41. if (useDefault) {
  42. useDefault = false;
  43. rate = 100;
  44. }
  45. if (handler) {
  46. const ret = handler(progress);
  47. if (ret !== undefined) {
  48. if (typeof ret === 'number' && ret === ret) rate = Math.max(0, Math.min(0x7fffffff, Math.round(ret)));
  49. else throw new TypeError('Expected progress callback to return a number or undefined');
  50. }
  51. }
  52. setImmediate(step);
  53. } catch (err) {
  54. backup.close();
  55. reject(err);
  56. }
  57. });
  58. });
  59. };