Files
Foundry-VTT-Docker/resources/app/dist/packages/package-backups.mjs

1 line
13 KiB
JavaScript
Raw Normal View History

2025-01-04 00:34:03 +01:00
import fs from"node:fs";import path from"node:path";import archiver from"archiver";import Files from"../files/files.mjs";import DataModel from"../../common/abstract/data.mjs";import*as fields from"../../common/data/fields.mjs";import{PackageCompatibility}from"../../common/packages/module.mjs";import BasePackage,{PackageRelationships}from"../../common/packages/base-package.mjs";import ProgressEmitter from"../components/progress-emitter.mjs";import{formatFileSize}from"../../common/utils/helpers.mjs";export default class PackageBackups{static BACKUP_TYPES=["module","system","world","snapshot"];async createBackups(e,{level:t=6,onProgress:a}={}){const s=[];for(const{packageId:i,type:n,note:r,snapshotId:o,id:c}of e)s.push(await this.createBackup(i,n,{level:t,note:r,snapshotId:o,id:c,onProgress:a}));return s}createBackup(e,t,{level:a=6,note:s="",onProgress:i,snapshotId:n,id:r}={}){const{logger:o,packages:c}=global,{ACTIONS:l,STEPS:p}=CONST.SETUP_PACKAGE_PROGRESS,d=c.PACKAGE_TYPE_MAPPING[t].get(e);if(!d)throw new Error(`Cannot create a backup of ${t} '${e}' because it is not installed.`);const{backups:g,install:S}=this.#e(e,t);return fs.mkdirSync(g,{recursive:!0}),o.info(`Writing backup for ${t} '${e}'.`),new Promise((async(c,u)=>{const P=new Date;r??=`${t}.${e}.${P.toDateInputString()}.${P.valueOf()}`;const f=path.join(g,`${r}.bak`),h=path.join(g,`${r}.json`),k=fs.createWriteStream(f),m=archiver("zip",{zlib:{level:a}}),E=await Files.getDirectorySize(S),b=new ProgressEmitter(l.CREATE_BACKUP,p.ARCHIVE,E,{packageId:e,type:t,id:r,title:d.title,message:"SETUP.BACKUPS.BackingUp"},{onProgress:i});let A;m.on("warning",(e=>o.warn(e))),m.on("error",(e=>{A=e,b.error(e),u(e)})),m.on("progress",(({fs:e})=>b.emit(e.processedBytes))),k.on("close",(()=>{if(A)return void(fs.existsSync(f)&&fs.unlinkSync(f));const a=m.pointer(),i=`Wrote backup for ${t} '${e}' to '${f}'. Wrote ${a} bytes.`,o=this.#t(d,h,{id:r,size:a,note:s,snapshotId:n,originalSize:E,createdAt:P.valueOf()});b.complete({log:i,context:{backupData:o}}),c(o)})),m.pipe(k),m.directory(S,!1),m.finalize()}))}async restoreBackups(e,{onProgress:t}={}){for(const a of e)await this.restoreBackup(a,{onProgress:t})}async restoreBackup(e,{onProgress:t}={}){const{paths:a}=global,{id:s,packageId:i,type:n}=e,{ACTIONS:r}=CONST.SETUP_PACKAGE_PROGRESS,o=path.join(a.backups,"tmp",s);fs.mkdirSync(o,{recursive:!0});const c=new ProgressEmitter(r.RESTORE_BACKUP,null,1,{id:s});try{const a=await this.#a(e,o,{onProgress:t});await this.#s(e,o,a,{onProgress:t}),this.#i(i,n),c.complete({context:{backupData:e}})}catch(e){c.error(e)}finally{fs.rmSync(o,{recursive:!0,maxRetries:10})}}async deleteBackups(e){const{ACTIONS:t,STEPS:a}=CONST.SETUP_PACKAGE_PROGRESS,s=new ProgressEmitter(t.DELETE_BACKUP,a.DELETE,e.length,{id:t.DELETE_BACKUP,message:"SETUP.BACKUPS.DeletingBackup"});try{let t=0;for(const a of e)await this.deleteBackup(a),s.emit(++t);s.complete()}catch(e){s.error(e)}}async deleteBackup({id:e,packageId:t,type:a}){const{logger:s}=global,{backups:i}=this.#e(t,a);await fs.promises.unlink(path.join(i,`${e}.json`)),await fs.promises.unlink(path.join(i,`${e}.bak`)),s.info(`Deleted backup ${e}`)}listBackups(){const{paths:e,logger:t}=global,a=Object.keys(packages.PACKAGE_TYPE_MAPPING).reduce(((a,s)=>{a[s]={};const i=packages.PACKAGE_TYPE_MAPPING[s],n=path.join(e.backups,i.collection);if(!fs.existsSync(n))return a;for(const e of fs.readdirSync(n,{withFileTypes:!0})){if(!e.isDirectory())continue;const i=e.name;try{BasePackage.validateId(i)}catch(e){t.warn(`Invalid backup directory '${i}' found when listing backups: ${e.message}`);continue}a[s][i]=this.getBackups(i,s)}return a}),{});return a.snapshots=this.getSnapshots(),a}getBackups(e,t){const{logger:a}=global,{backups:s}=this.#e(e,t);if(!fs.existsSync(s))return[];const i=[];for(const e of fs.readdirSync(s))if(".json"===path.extname(e))try{const t=JSON.parse(fs.readFileSync(path.join(s,e),{encoding:"utf8"}));i.push(new BackupData(t))}catch(t){a.warn(`Found JSON file '${path.join(s,e)}' that was not a valid backup manifest: ${t.message}`)}return i.sort(((e,t)=>t.created