diff --git a/wowup-electron/app/file.utils.ts b/wowup-electron/app/file.utils.ts index 0ebd615d..990e9f83 100644 --- a/wowup-electron/app/file.utils.ts +++ b/wowup-electron/app/file.utils.ts @@ -1,11 +1,34 @@ -import { exec } from 'child_process'; -import * as log from 'electron-log'; -import * as fsp from 'fs/promises'; -import { max, sumBy } from 'lodash'; -import * as path from 'path'; +import { exec } from "child_process"; +import * as log from "electron-log"; +import * as fsp from "fs/promises"; +import { max, sumBy } from "lodash"; +import * as path from "path"; +import * as crypto from "crypto"; +import * as AdmZip from "adm-zip"; -import { TreeNode } from '../src/common/models/ipc-events'; -import { isWin } from './platform'; +import { TreeNode } from "../src/common/models/ipc-events"; +import { GetDirectoryTreeOptions } from "../src/common/models/ipc-request"; +import { isWin } from "./platform"; + +export function zipFile(srcPath: string, outPath: string): Promise { + return new Promise((resolve, reject) => { + const zip = new AdmZip(); + zip.addLocalFolder(srcPath); + + zip.writeZip(outPath, (e) => { + return e ? reject(e) : resolve(true); + }); + }); +} + +export function readFileInZip(zipPath: string, filePath: string) { + return new Promise((resolve, reject) => { + const zip = new AdmZip(zipPath); + zip.readAsTextAsync(filePath, (data, err) => { + return err ? reject(err) : resolve(data); + }); + }); +} export async function exists(path: string): Promise { try { @@ -95,7 +118,7 @@ export async function readDirRecursive(sourcePath: string): Promise { return dirFiles; } -export async function getDirTree(sourcePath: string): Promise { +export async function getDirTree(sourcePath: string, opts?: GetDirectoryTreeOptions): Promise { const files = await fsp.readdir(sourcePath, { withFileTypes: true }); const node: TreeNode = { @@ -109,10 +132,18 @@ export async function getDirTree(sourcePath: string): Promise { for (const file of files) { const filePath = path.join(sourcePath, file.name); if (file.isDirectory()) { - const nestedNode = await getDirTree(filePath); + const nestedNode = await getDirTree(filePath, opts); node.children.push(nestedNode); node.size = sumBy(node.children, (n) => n.size); + if (opts?.includeHash) { + node.hash = hashString(node.children.map((n) => n.hash).join(""), "sha256"); + } } else { + let hash = ""; + if (opts?.includeHash) { + hash = await hashFile(filePath, "sha256"); + } + const stats = await fsp.stat(filePath); node.size += stats.size; node.children.push({ @@ -121,10 +152,15 @@ export async function getDirTree(sourcePath: string): Promise { children: [], isDirectory: false, size: stats.size, + hash, }); } } + if (opts?.includeHash) { + node.hash = hashString(node.children.map((n) => n.hash).join(""), "sha256"); + } + return node; } @@ -139,3 +175,14 @@ export async function getLastModifiedFileDate(sourcePath: string): Promise { + const text = await fsp.readFile(filePath); + return hashString(text, alg); +} diff --git a/wowup-electron/app/ipc-events.ts b/wowup-electron/app/ipc-events.ts index 62803df5..3742e557 100644 --- a/wowup-electron/app/ipc-events.ts +++ b/wowup-electron/app/ipc-events.ts @@ -89,11 +89,28 @@ import { createAppMenu } from "./app-menu"; import { CurseFolderScanner } from "./curse-folder-scanner"; import * as fsp from "fs/promises"; -import { chmodDir, copyDir, getDirTree, getLastModifiedFileDate, readDirRecursive, remove } from "./file.utils"; +import { + chmodDir, + copyDir, + exists, + getDirTree, + getLastModifiedFileDate, + readDirRecursive, + readFileInZip, + remove, + zipFile, +} from "./file.utils"; import { addonStore } from "./stores"; import { createTray, restoreWindow } from "./system-tray"; import { WowUpFolderScanner } from "./wowup-folder-scanner"; import * as push from "./push"; +import { WowInstallation } from "../src/common/warcraft/wow-installation"; +import { + BackupCreateRequest, + BackupGetExistingRequest, + GetDirectoryTreeRequest, +} from "../src/common/models/ipc-request"; +import { BackupCreateResponse, BackupGetExistingResponse } from "../src/common/models/ipc-response"; let PENDING_OPEN_URLS: string[] = []; @@ -310,6 +327,11 @@ export function initializeIpcHandlers(window: BrowserWindow, userAgent: string): }); handle(IPC_LIST_FILES_CHANNEL, async (evt, sourcePath: string, filter: string) => { + const pathExists = await exists(sourcePath); + if (!pathExists) { + return []; + } + const globFilter = globrex(filter); const results = await fsp.readdir(sourcePath, { withFileTypes: true }); const matches = _.filter(results, (entry) => globFilter.regex.test(entry.name)); @@ -363,6 +385,14 @@ export function initializeIpcHandlers(window: BrowserWindow, userAgent: string): return arg.outputFolder; }); + handle("zip-file", async (evt, srcPath: string, destPath: string) => { + return await zipFile(srcPath, destPath); + }); + + handle("zip-read-file", async (evt, zipPath: string, filePath: string) => { + return await readFileInZip(zipPath, filePath); + }); + handle(IPC_COPY_FILE_CHANNEL, async (evt, arg: CopyFileRequest): Promise => { log.info(`[FileCopy] '${arg.sourceFilePath}' -> '${arg.destinationFilePath}'`); const stat = await fsp.lstat(arg.sourceFilePath); @@ -409,8 +439,9 @@ export function initializeIpcHandlers(window: BrowserWindow, userAgent: string): return readDirRecursive(dirPath); }); - handle(IPC_GET_DIRECTORY_TREE, (evt, dirPath: string): Promise => { - return getDirTree(dirPath); + handle(IPC_GET_DIRECTORY_TREE, (evt, args: GetDirectoryTreeRequest): Promise => { + log.debug(IPC_GET_DIRECTORY_TREE, args); + return getDirTree(args.dirPath, args.opts); }); handle(IPC_MINIMIZE_WINDOW, () => { @@ -484,6 +515,37 @@ export function initializeIpcHandlers(window: BrowserWindow, userAgent: string): return await push.subscribeToChannel(channel); }); + handle("backup-get-existing", async (evt, req: BackupGetExistingRequest) => { + const response: BackupGetExistingResponse = { + exists: false, + }; + + log.debug("backup-get-existing", req); + + const backupFolder = path.join(req.backupPath, req.installation.id); + response.exists = await exists(backupFolder); + if (!response.exists) { + return response; + } + + return response; + }); + + handle("backup-create", async (evt, req: BackupCreateRequest) => { + const response: BackupCreateResponse = {}; + + log.debug("backup-get-existing", req); + + const backupFolder = path.join(req.backupPath, req.installation.id); + await fsp.mkdir(backupFolder, { recursive: true }); + + // TODO create backup tree + + // TODO copy backup contents + + return response; + }); + ipcMain.on(IPC_DOWNLOAD_FILE_CHANNEL, (evt, arg: DownloadRequest) => { handleDownloadFile(arg).catch((e) => console.error(e.toString())); }); diff --git a/wowup-electron/app/wowup-folder-scanner.ts b/wowup-electron/app/wowup-folder-scanner.ts index 5950ff17..47401b07 100644 --- a/wowup-electron/app/wowup-folder-scanner.ts +++ b/wowup-electron/app/wowup-folder-scanner.ts @@ -4,7 +4,7 @@ import * as path from "path"; import * as pLimit from "p-limit"; import * as log from "electron-log"; import { WowUpScanResult } from "../src/common/wowup/models"; -import { exists, readDirRecursive } from "./file.utils"; +import { exists, readDirRecursive, hashFile, hashString } from "./file.utils"; import * as fsp from "fs/promises"; const INVALID_PATH_CHARS = [ @@ -86,14 +86,14 @@ export class WowUpFolderScanner { const limit = pLimit(4); const tasks = _.map(matchingFiles, (file) => limit(async () => { - return { hash: await this.hashFile(file), file }; + return { hash: await hashFile(file), file }; }) ); const fileFingerprints = await Promise.all(tasks); const fingerprintList = _.map(fileFingerprints, (ff) => ff.hash); const hashConcat = _.orderBy(fingerprintList).join(""); - const fingerprint = this.hashString(hashConcat); + const fingerprint = hashString(hashConcat); const result: WowUpScanResult = { fileFingerprints: fingerprintList, @@ -204,17 +204,6 @@ export class WowUpFolderScanner { return matches; } - private hashString(str: string | crypto.BinaryLike) { - const md5 = crypto.createHash("md5"); - md5.update(str); - return md5.digest("hex"); - } - - private async hashFile(filePath: string): Promise { - const text = await fsp.readFile(filePath); - return this.hashString(text); - } - private getRealPath(filePath: string) { const lowerPath = filePath.toLowerCase(); const matchedPath = this._fileMap[lowerPath]; diff --git a/wowup-electron/package-lock.json b/wowup-electron/package-lock.json index dd5c6539..043798db 100644 --- a/wowup-electron/package-lock.json +++ b/wowup-electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "wowup", - "version": "2.5.0-beta.20", + "version": "2.5.0-beta.21", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wowup", - "version": "2.5.0-beta.20", + "version": "2.5.0-beta.21", "hasInstallScript": true, "dependencies": { "@angular/animations": "12.2.10", @@ -29,8 +29,10 @@ "@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4", "@microsoft/applicationinsights-web": "2.7.0", + "adm-zip": "0.5.9", "ag-grid-angular": "26.1.0", "ag-grid-community": "26.1.0", + "archiver": "5.3.0", "auto-launch": "5.0.5", "compare-versions": "3.6.0", "electron-log": "4.4.1", @@ -73,6 +75,8 @@ "@angular/cli": "12.2.10", "@ngx-translate/core": "13.0.0", "@ngx-translate/http-loader": "6.0.0", + "@types/adm-zip": "0.4.34", + "@types/archiver": "5.1.1", "@types/globrex": "0.1.1", "@types/jasmine": "3.10.0", "@types/jasminewd2": "2.0.10", @@ -3562,6 +3566,24 @@ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", "dev": true }, + "node_modules/@types/adm-zip": { + "version": "0.4.34", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.34.tgz", + "integrity": "sha512-8ToYLLAYhkRfcmmljrKi22gT2pqu7hGMDtORP1emwIEGmgUTZOsaDjzWFzW5N2frcFRz/50CWt4zA1CxJ73pmQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/archiver": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.1.tgz", + "integrity": "sha512-heuaCk0YH5m274NOLSi66H1zX6GtZoMsdE6TYFcpFFjBjg0FoU4i4/M/a/kNlgNg26Xk3g364mNOYe1JaiEPOQ==", + "dev": true, + "dependencies": { + "@types/glob": "*" + } + }, "node_modules/@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -4795,12 +4817,11 @@ } }, "node_modules/adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true, + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", + "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==", "engines": { - "node": ">=0.3.0" + "node": ">=6.0" } }, "node_modules/ag-grid-angular": { @@ -5094,7 +5115,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", - "dev": true, "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.0", @@ -5112,7 +5132,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -5132,14 +5151,12 @@ "node_modules/archiver/node_modules/async": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==", - "dev": true + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" }, "node_modules/archiver/node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -5620,8 +5637,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base": { "version": "0.11.2", @@ -5666,7 +5682,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -5737,7 +5752,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -5748,7 +5762,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -5991,7 +6004,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6040,7 +6052,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -6955,7 +6966,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", - "dev": true, "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -6970,7 +6980,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -7037,8 +7046,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -7651,8 +7659,7 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cors": { "version": "2.8.5", @@ -7697,7 +7704,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", - "dev": true, "dependencies": { "exit-on-epipe": "~1.0.1", "printj": "~1.1.0" @@ -7713,7 +7719,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", - "dev": true, "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -7726,7 +7731,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -9544,7 +9548,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -10675,7 +10678,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", - "dev": true, "engines": { "node": ">=0.8" } @@ -11407,8 +11409,7 @@ "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "node_modules/fs-extra": { "version": "9.1.0", @@ -11446,8 +11447,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "node_modules/fsevents": { "version": "2.3.2", @@ -11788,7 +11788,6 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -12882,7 +12881,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -13068,7 +13066,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -13077,8 +13074,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -13828,8 +13824,7 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "node_modules/isbinaryfile": { "version": "4.0.8", @@ -14574,7 +14569,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "dev": true, "dependencies": { "readable-stream": "^2.0.5" }, @@ -14878,14 +14872,12 @@ "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", - "dev": true + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" }, "node_modules/lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", - "dev": true + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" }, "node_modules/lodash.escaperegexp": { "version": "4.1.2", @@ -14895,8 +14887,7 @@ "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" }, "node_modules/lodash.isequal": { "version": "4.5.0", @@ -14918,8 +14909,7 @@ "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "node_modules/lodash.memoize": { "version": "3.0.4", @@ -14942,8 +14932,7 @@ "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", - "dev": true + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, "node_modules/lodash.uniq": { "version": "4.5.0", @@ -15747,7 +15736,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -17009,7 +16997,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "dependencies": { "wrappy": "1" } @@ -19675,7 +19662,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", - "dev": true, "bin": { "printj": "bin/printj.njs" }, @@ -19686,8 +19672,7 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/progress": { "version": "2.0.3", @@ -21621,7 +21606,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -21636,7 +21620,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", - "dev": true, "dependencies": { "minimatch": "^3.0.4" } @@ -23660,7 +23643,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -24112,7 +24094,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -24128,7 +24109,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -25255,8 +25235,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -25455,6 +25434,15 @@ "node": ">=6.9.x" } }, + "node_modules/webdriver-manager/node_modules/adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true, + "engines": { + "node": ">=0.3.0" + } + }, "node_modules/webdriver-manager/node_modules/ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -27042,8 +27030,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/write-file-atomic": { "version": "3.0.3", @@ -27286,7 +27273,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", - "dev": true, "dependencies": { "archiver-utils": "^2.1.0", "compress-commons": "^4.1.0", @@ -27300,7 +27286,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -29810,6 +29795,24 @@ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", "dev": true }, + "@types/adm-zip": { + "version": "0.4.34", + "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.34.tgz", + "integrity": "sha512-8ToYLLAYhkRfcmmljrKi22gT2pqu7hGMDtORP1emwIEGmgUTZOsaDjzWFzW5N2frcFRz/50CWt4zA1CxJ73pmQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/archiver": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.1.tgz", + "integrity": "sha512-heuaCk0YH5m274NOLSi66H1zX6GtZoMsdE6TYFcpFFjBjg0FoU4i4/M/a/kNlgNg26Xk3g364mNOYe1JaiEPOQ==", + "dev": true, + "requires": { + "@types/glob": "*" + } + }, "@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -30789,10 +30792,9 @@ } }, "adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz", + "integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==" }, "ag-grid-angular": { "version": "26.1.0", @@ -31023,7 +31025,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", - "dev": true, "requires": { "archiver-utils": "^2.1.0", "async": "^3.2.0", @@ -31037,14 +31038,12 @@ "async": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==", - "dev": true + "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==" }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -31057,7 +31056,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, "requires": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -31429,8 +31427,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base": { "version": "0.11.2", @@ -31467,8 +31464,7 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "base64id": { "version": "2.0.0", @@ -31516,7 +31512,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -31527,7 +31522,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -31722,7 +31716,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -31758,7 +31751,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -32483,7 +32475,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", - "dev": true, "requires": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -32495,7 +32486,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -32554,8 +32544,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -33029,8 +33018,7 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "cors": { "version": "2.8.5", @@ -33069,7 +33057,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", - "dev": true, "requires": { "exit-on-epipe": "~1.0.1", "printj": "~1.1.0" @@ -33079,7 +33066,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", - "dev": true, "requires": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -33089,7 +33075,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -34481,7 +34466,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -35310,8 +35294,7 @@ "exit-on-epipe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", - "dev": true + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" }, "expand-brackets": { "version": "2.1.4", @@ -35902,8 +35885,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { "version": "9.1.0", @@ -35935,8 +35917,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.3.2", @@ -36200,7 +36181,6 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -37084,8 +37064,7 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "5.1.8", @@ -37211,7 +37190,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -37220,8 +37198,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "2.0.0", @@ -37764,8 +37741,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isbinaryfile": { "version": "4.0.8", @@ -38361,7 +38337,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "dev": true, "requires": { "readable-stream": "^2.0.5" } @@ -38599,14 +38574,12 @@ "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", - "dev": true + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" }, "lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", - "dev": true + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" }, "lodash.escaperegexp": { "version": "4.1.2", @@ -38616,8 +38589,7 @@ "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" }, "lodash.isequal": { "version": "4.5.0", @@ -38639,8 +38611,7 @@ "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.memoize": { "version": "3.0.4", @@ -38663,8 +38634,7 @@ "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", - "dev": true + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, "lodash.uniq": { "version": "4.5.0", @@ -39278,7 +39248,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -40240,7 +40209,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -42161,14 +42129,12 @@ "printj": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", - "dev": true + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", @@ -43996,7 +43962,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -44011,7 +43976,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", - "dev": true, "requires": { "minimatch": "^3.0.4" } @@ -45622,7 +45586,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -45964,7 +45927,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, "requires": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -45977,7 +45939,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -46844,8 +46805,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", @@ -47134,6 +47094,12 @@ "xml2js": "^0.4.17" }, "dependencies": { + "adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -48240,8 +48206,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "3.0.3", @@ -48413,7 +48378,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", - "dev": true, "requires": { "archiver-utils": "^2.1.0", "compress-commons": "^4.1.0", @@ -48424,7 +48388,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", diff --git a/wowup-electron/package.json b/wowup-electron/package.json index 3d9aea76..a452de8c 100644 --- a/wowup-electron/package.json +++ b/wowup-electron/package.json @@ -59,6 +59,8 @@ "@angular/cli": "12.2.10", "@ngx-translate/core": "13.0.0", "@ngx-translate/http-loader": "6.0.0", + "@types/adm-zip": "0.4.34", + "@types/archiver": "5.1.1", "@types/globrex": "0.1.1", "@types/jasmine": "3.10.0", "@types/jasminewd2": "2.0.10", @@ -131,8 +133,10 @@ "@fortawesome/free-regular-svg-icons": "5.15.4", "@fortawesome/free-solid-svg-icons": "5.15.4", "@microsoft/applicationinsights-web": "2.7.0", + "adm-zip": "0.5.9", "ag-grid-angular": "26.1.0", "ag-grid-community": "26.1.0", + "archiver": "5.3.0", "auto-launch": "5.0.5", "compare-versions": "3.6.0", "electron-log": "4.4.1", diff --git a/wowup-electron/src/app/addon-providers/addon-provider.ts b/wowup-electron/src/app/addon-providers/addon-provider.ts index 3d8d0213..39f9c166 100644 --- a/wowup-electron/src/app/addon-providers/addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/addon-provider.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { WowInstallation } from "../models/wowup/wow-installation"; +import { WowInstallation } from "../../common/warcraft/wow-installation"; import { Observable, of } from "rxjs"; import { Addon } from "../../common/entities/addon"; diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 40d35f9a..c38b923e 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -34,7 +34,7 @@ import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { AddonSearchResultDependency } from "../models/wowup/addon-search-result-dependency"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; import { ProtocolSearchResult } from "../models/wowup/protocol-search-result"; -import { WowInstallation } from "../models/wowup/wow-installation"; +import { WowInstallation } from "../../common/warcraft/wow-installation"; import { ElectronService } from "../services"; import { CachingService } from "../services/caching/caching-service"; import { CircuitBreakerWrapper, NetworkService } from "../services/network/network.service"; diff --git a/wowup-electron/src/app/addon-providers/github-addon-provider.ts b/wowup-electron/src/app/addon-providers/github-addon-provider.ts index 8943a816..942501f6 100644 --- a/wowup-electron/src/app/addon-providers/github-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/github-addon-provider.ts @@ -22,7 +22,7 @@ import { AddonChannelType } from "../../common/wowup/models"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; import { AddonProvider, GetAllResult } from "./addon-provider"; -import { WowInstallation } from "../models/wowup/wow-installation"; +import { WowInstallation } from "../../common/warcraft/wow-installation"; import { convertMarkdown } from "../utils/markdown.utlils"; import { strictFilterBy } from "../utils/array.utils"; diff --git a/wowup-electron/src/app/addon-providers/raiderio-provider.ts b/wowup-electron/src/app/addon-providers/raiderio-provider.ts index 846401af..4fc62ef1 100644 --- a/wowup-electron/src/app/addon-providers/raiderio-provider.ts +++ b/wowup-electron/src/app/addon-providers/raiderio-provider.ts @@ -1,4 +1,4 @@ -import { WowInstallation } from "../models/wowup/wow-installation"; +import { WowInstallation } from "../../common/warcraft/wow-installation"; import * as _ from "lodash"; import { v4 as uuidv4 } from "uuid"; diff --git a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts index e2acc59f..29e57c0c 100644 --- a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts @@ -11,7 +11,7 @@ import { TukUiAddon } from "../models/tukui/tukui-addon"; import { AddonFolder } from "../models/wowup/addon-folder"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; -import { WowInstallation } from "../models/wowup/wow-installation"; +import { WowInstallation } from "../../common/warcraft/wow-installation"; import { CachingService } from "../services/caching/caching-service"; import { CircuitBreakerWrapper, NetworkService } from "../services/network/network.service"; import { getGameVersion } from "../utils/addon.utils"; diff --git a/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts b/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts index a8ac2f1b..1e2f423c 100644 --- a/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts @@ -13,7 +13,7 @@ import { AddonDetailsResponse } from "../models/wow-interface/addon-details-resp import { AddonFolder } from "../models/wowup/addon-folder"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; -import { WowInstallation } from "../models/wowup/wow-installation"; +import { WowInstallation } from "../../common/warcraft/wow-installation"; import { CachingService } from "../services/caching/caching-service"; import { CircuitBreakerWrapper, NetworkService } from "../services/network/network.service"; import { getGameVersion } from "../utils/addon.utils"; diff --git a/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts b/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts index 0f2391f3..2fff7d8d 100644 --- a/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts @@ -23,7 +23,7 @@ import { AddonFolder } from "../models/wowup/addon-folder"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; import { AppWowUpScanResult } from "../models/wowup/app-wowup-scan-result"; -import { WowInstallation } from "../models/wowup/wow-installation"; +import { WowInstallation } from "../../common/warcraft/wow-installation"; import { ElectronService } from "../services"; import { CachingService } from "../services/caching/caching-service"; import { CircuitBreakerWrapper, NetworkService } from "../services/network/network.service"; diff --git a/wowup-electron/src/app/addon-providers/wowup-companion-addon-provider.ts b/wowup-electron/src/app/addon-providers/wowup-companion-addon-provider.ts index 2f66353c..6269254b 100644 --- a/wowup-electron/src/app/addon-providers/wowup-companion-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/wowup-companion-addon-provider.ts @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from "uuid"; import { ADDON_PROVIDER_WOWUP_COMPANION, WOWUP_DATA_ADDON_FOLDER_NAME } from "../../common/constants"; import { AddonChannelType } from "../../common/wowup/models"; import { AddonFolder } from "../models/wowup/addon-folder"; -import { WowInstallation } from "../models/wowup/wow-installation"; +import { WowInstallation } from "../../common/warcraft/wow-installation"; import { FileService } from "../services/files/file.service"; import { TocService } from "../services/toc/toc.service"; import { getGameVersion } from "../utils/addon.utils"; diff --git a/wowup-electron/src/app/addon-providers/zip-provider.ts b/wowup-electron/src/app/addon-providers/zip-provider.ts index e170bf11..443c8808 100644 --- a/wowup-electron/src/app/addon-providers/zip-provider.ts +++ b/wowup-electron/src/app/addon-providers/zip-provider.ts @@ -15,7 +15,7 @@ import { FileService } from "../services/files/file.service"; import { TocService } from "../services/toc/toc.service"; import { WarcraftService } from "../services/warcraft/warcraft.service"; import { AddonProvider } from "./addon-provider"; -import { WowInstallation } from "../models/wowup/wow-installation"; +import { WowInstallation } from "../../common/warcraft/wow-installation"; const VALID_ZIP_CONTENT_TYPES = ["application/zip", "application/x-zip-compressed", "application/octet-stream"]; diff --git a/wowup-electron/src/app/components/addons/addon-manage-dialog/addon-manage-dialog.component.ts b/wowup-electron/src/app/components/addons/addon-manage-dialog/addon-manage-dialog.component.ts index d8c1c287..bbacee76 100644 --- a/wowup-electron/src/app/components/addons/addon-manage-dialog/addon-manage-dialog.component.ts +++ b/wowup-electron/src/app/components/addons/addon-manage-dialog/addon-manage-dialog.component.ts @@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { BehaviorSubject, Subscription } from "rxjs"; import { map } from "rxjs/operators"; import { AddonInstallState } from "../../../models/wowup/addon-install-state"; -import { WowInstallation } from "../../../models/wowup/wow-installation"; +import { WowInstallation } from "../../../../common/warcraft/wow-installation"; import { AddonBrokerService, ExportPayload, diff --git a/wowup-electron/src/app/components/addons/install-from-protocol-dialog/install-from-protocol-dialog.component.ts b/wowup-electron/src/app/components/addons/install-from-protocol-dialog/install-from-protocol-dialog.component.ts index a4c71ee2..632653a9 100644 --- a/wowup-electron/src/app/components/addons/install-from-protocol-dialog/install-from-protocol-dialog.component.ts +++ b/wowup-electron/src/app/components/addons/install-from-protocol-dialog/install-from-protocol-dialog.component.ts @@ -7,10 +7,10 @@ import { FormControl } from "@angular/forms"; import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; import { ProtocolSearchResult } from "../../../models/wowup/protocol-search-result"; -import { WowInstallation } from "../../../models/wowup/wow-installation"; import { AddonService } from "../../../services/addons/addon.service"; import { SessionService } from "../../../services/session/session.service"; import { WarcraftInstallationService } from "../../../services/warcraft/warcraft-installation.service"; +import { WowInstallation } from "../../../../common/warcraft/wow-installation"; export interface InstallFromProtocolDialogComponentData { protocol: string; diff --git a/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.html b/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.html new file mode 100644 index 00000000..3f6129ce --- /dev/null +++ b/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.html @@ -0,0 +1,55 @@ +
+
+

+ {{ "WTF_BACKUP.DIALOG_TITLE" | translate: { clientType: selectedInstallation.label } }} +

+ +
+
+ +
+

No backups were found at:

+

{{ backupPath }}

+
+
+

+ {{ "WTF_BACKUP.BACKUP_COUNT_TEXT" | translate: { count: backupCt$ | async } }} +

+
    +
  • +
    +
    +
    +
    {{ backup.title }}
    +
    +
    +
    {{ backup.date | localeDate: "medium" }}
    +
    {{ backup.size }}
    +
    +
    + +
    {{ "WTF_BACKUP.ERROR." + backup.error | translate }}
    +
    + + +
    +
    +
  • +
+
+
+ + +
+ +
diff --git a/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.scss b/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.scss new file mode 100644 index 00000000..ca74b4e9 --- /dev/null +++ b/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.scss @@ -0,0 +1,28 @@ +.wtf-backup-dialog { + min-width: 600px; +} + +.backup-list { + background-color: var(--background-secondary-2); + margin: 0; + overflow: auto; + padding: 0 1em; + + .backup-list-item:not(:last-child) { + border-bottom: 1px solid var(--background-secondary-1); + } + + .backup-list-item { + padding: 1em 0; + list-style: none; + + .status-badge { + width: 20px; + height: 20px; + } + + .title { + font-size: 1.2em; + } + } +} diff --git a/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.spec.ts b/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.spec.ts new file mode 100644 index 00000000..c295417e --- /dev/null +++ b/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WtfBackupComponent } from './wtf-backup.component'; + +describe('WtfBackupComponent', () => { + let component: WtfBackupComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ WtfBackupComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(WtfBackupComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.ts b/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.ts new file mode 100644 index 00000000..51e4e9a5 --- /dev/null +++ b/wowup-electron/src/app/components/addons/wtf-backup/wtf-backup.component.ts @@ -0,0 +1,85 @@ +import { Component, OnInit } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { map } from "rxjs/operators"; +import { WowInstallation } from "../../../../common/warcraft/wow-installation"; +import { ElectronService } from "../../../services"; +import { SessionService } from "../../../services/session/session.service"; +import { WtfBackup, WtfService } from "../../../services/wtf/wtf.service"; +import { formatSize } from "../../../utils/number.utils"; + +interface WtfBackupViewModel { + title: string; + size: string; + date: number; + error?: string; +} + +@Component({ + selector: "app-wtf-backup", + templateUrl: "./wtf-backup.component.html", + styleUrls: ["./wtf-backup.component.scss"], +}) +export class WtfBackupComponent implements OnInit { + public readonly busy$ = new BehaviorSubject(false); + public readonly backups$ = new BehaviorSubject([]); + + public readonly selectedInstallation: WowInstallation; + public readonly hasBackups$ = this.backups$.pipe(map((backups) => backups.length > 0)); + public readonly backupCt$ = this.backups$.pipe(map((backups) => backups.length)); + public readonly backupPath: string; + + constructor( + private _electronService: ElectronService, + private _sessionService: SessionService, + private _wtfService: WtfService + ) { + this.selectedInstallation = this._sessionService.getSelectedWowInstallation(); + this.backupPath = this._wtfService.getBackupPath(this.selectedInstallation); + } + + ngOnInit(): void { + this.loadBackups(); + } + + async onShowFolder(): Promise { + const backupPath = this._wtfService.getBackupPath(this.selectedInstallation); + await this._electronService.openPath(backupPath); + } + + async onCreateBackup(): Promise { + this.busy$.next(true); + try { + await this._wtfService.createBackup(this.selectedInstallation); + await this.loadBackups(); + } catch (e) { + console.error(e); + } finally { + this.busy$.next(false); + } + } + + private async loadBackups() { + this.busy$.next(true); + try { + const backups = await this._wtfService.getBackupList(this.selectedInstallation); + console.debug(backups); + + const viewModels = backups.map((b) => this.toViewModel(b)); + console.debug(viewModels); + this.backups$.next(viewModels); + } catch (e) { + console.error(e); + } finally { + this.busy$.next(false); + } + } + + private toViewModel(backup: WtfBackup): WtfBackupViewModel { + return { + title: backup.fileName, + size: formatSize(backup.size), + date: backup.metadata?.createdAt ?? backup.birthtimeMs, + error: backup.error, + }; + } +} diff --git a/wowup-electron/src/app/components/common/footer/footer.component.html b/wowup-electron/src/app/components/common/footer/footer.component.html index 7f9d405e..670efa23 100644 --- a/wowup-electron/src/app/components/common/footer/footer.component.html +++ b/wowup-electron/src/app/components/common/footer/footer.component.html @@ -7,13 +7,6 @@ matTooltip="{{ 'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.JOIN_DISCORD' | translate }}"> -

{{ sessionService.statusText$ | async }}

{{ sessionService.pageContextText$ | async }}

diff --git a/wowup-electron/src/app/components/common/horizontal-tabs/horizontal-tabs.component.html b/wowup-electron/src/app/components/common/horizontal-tabs/horizontal-tabs.component.html index c1f019ce..afa09a45 100644 --- a/wowup-electron/src/app/components/common/horizontal-tabs/horizontal-tabs.component.html +++ b/wowup-electron/src/app/components/common/horizontal-tabs/horizontal-tabs.component.html @@ -1,13 +1,22 @@ -
-
- +
+ + [svgIcon]="tab.icon" + >
{{ tab.titleKey | translate }}
- + -
{{ 'PAGES.HOME.GUIDE_TAB_TITLE' | translate }}
+
{{ "PAGES.HOME.GUIDE_TAB_TITLE" | translate }}
- - -
- - + + +
+ -
-
{{ 'PAGES.HOME.ACCOUNT_TAB_TITLE' | translate }} -
+
{{ "PAGES.HOME.ACCOUNT_TAB_TITLE" | translate }}
Logged in
-
+
- +
{{ tab.titleKey | translate }}
-
\ No newline at end of file +
diff --git a/wowup-electron/src/app/components/common/horizontal-tabs/horizontal-tabs.component.ts b/wowup-electron/src/app/components/common/horizontal-tabs/horizontal-tabs.component.ts index 3efdd8a9..95eaaeaf 100644 --- a/wowup-electron/src/app/components/common/horizontal-tabs/horizontal-tabs.component.ts +++ b/wowup-electron/src/app/components/common/horizontal-tabs/horizontal-tabs.component.ts @@ -4,6 +4,7 @@ import { map } from "rxjs/operators"; import { Component, OnInit } from "@angular/core"; import { + FEATURE_ACCOUNTS_ENABLED, TAB_INDEX_ABOUT, TAB_INDEX_GET_ADDONS, TAB_INDEX_MY_ADDONS, @@ -33,6 +34,7 @@ interface Tab { export class HorizontalTabsComponent implements OnInit { public wowUpWebsiteUrl = AppConfig.wowUpWebsiteUrl; public TAB_INDEX_ACCOUNT = TAB_INDEX_ABOUT; + public FEATURE_ACCOUNTS_ENABLED = FEATURE_ACCOUNTS_ENABLED; public isAccountSelected$ = this.sessionService.selectedHomeTab$.pipe(map((result) => result === TAB_INDEX_ABOUT)); diff --git a/wowup-electron/src/app/components/options/options-wow-section/options-wow-section.component.ts b/wowup-electron/src/app/components/options/options-wow-section/options-wow-section.component.ts index 6d508684..373ae926 100644 --- a/wowup-electron/src/app/components/options/options-wow-section/options-wow-section.component.ts +++ b/wowup-electron/src/app/components/options/options-wow-section/options-wow-section.component.ts @@ -6,13 +6,13 @@ import { MatDialog } from "@angular/material/dialog"; import { TranslateService } from "@ngx-translate/core"; import { WowClientType } from "../../../../common/warcraft/wow-client-type"; -import { WowInstallation } from "../../../models/wowup/wow-installation"; import { WowUpReleaseChannelType } from "../../../models/wowup/wowup-release-channel-type"; import { WarcraftInstallationService } from "../../../services/warcraft/warcraft-installation.service"; import { WarcraftService } from "../../../services/warcraft/warcraft.service"; import { WowUpService } from "../../../services/wowup/wowup.service"; import { getEnumList, getEnumName } from "../../../utils/enum.utils"; import { AlertDialogComponent } from "../../common/alert-dialog/alert-dialog.component"; +import { WowInstallation } from "../../../../common/warcraft/wow-installation"; @Component({ selector: "app-options-wow-section", diff --git a/wowup-electron/src/app/components/options/wow-client-options/wow-client-options.component.ts b/wowup-electron/src/app/components/options/wow-client-options/wow-client-options.component.ts index 52492818..92b59e33 100644 --- a/wowup-electron/src/app/components/options/wow-client-options/wow-client-options.component.ts +++ b/wowup-electron/src/app/components/options/wow-client-options/wow-client-options.component.ts @@ -9,12 +9,12 @@ import { TranslateService } from "@ngx-translate/core"; import { WowClientType } from "../../../../common/warcraft/wow-client-type"; import { AddonChannelType } from "../../../../common/wowup/models"; -import { WowInstallation } from "../../../models/wowup/wow-installation"; import { WarcraftInstallationService } from "../../../services/warcraft/warcraft-installation.service"; import { getEnumList, getEnumName } from "../../../utils/enum.utils"; import { ConfirmDialogComponent } from "../../common/confirm-dialog/confirm-dialog.component"; import { WarcraftService } from "../../../services/warcraft/warcraft.service"; import { SessionService } from "../../../services/session/session.service"; +import { WowInstallation } from "../../../../common/warcraft/wow-installation"; @Component({ selector: "app-wow-client-options", diff --git a/wowup-electron/src/app/components/options/wtf-explorer/wtf-explorer.component.ts b/wowup-electron/src/app/components/options/wtf-explorer/wtf-explorer.component.ts index ef3ba650..cfd90601 100644 --- a/wowup-electron/src/app/components/options/wtf-explorer/wtf-explorer.component.ts +++ b/wowup-electron/src/app/components/options/wtf-explorer/wtf-explorer.component.ts @@ -9,9 +9,9 @@ import { WarcraftService } from "../../../services/warcraft/warcraft.service"; import { WtfNode, WtfService } from "../../../services/wtf/wtf.service"; import { removeExtension } from "../../../utils/string.utils"; import { AddonFolder } from "../../../models/wowup/addon-folder"; -import { WowInstallation } from "../../../models/wowup/wow-installation"; import { WarcraftInstallationService } from "../../../services/warcraft/warcraft-installation.service"; import { formatSize } from "../../../utils/number.utils"; +import { WowInstallation } from "../../../../common/warcraft/wow-installation"; interface SavedVariable { name: string; diff --git a/wowup-electron/src/app/modules/addons.module.ts b/wowup-electron/src/app/modules/addons.module.ts index bafc326d..695ce9c0 100644 --- a/wowup-electron/src/app/modules/addons.module.ts +++ b/wowup-electron/src/app/modules/addons.module.ts @@ -21,6 +21,7 @@ import { InstallFromUrlDialogComponent } from "../components/addons/install-from import { DirectiveModule } from "./directive.module"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { AddonManageDialogComponent } from "../components/addons/addon-manage-dialog/addon-manage-dialog.component"; +import { WtfBackupComponent } from "../components/addons/wtf-backup/wtf-backup.component"; @NgModule({ declarations: [ @@ -38,6 +39,7 @@ import { AddonManageDialogComponent } from "../components/addons/addon-manage-di MyAddonsAddonCellComponent, InstallFromUrlDialogComponent, AddonManageDialogComponent, + WtfBackupComponent, ], imports: [ CommonModule, @@ -65,6 +67,7 @@ import { AddonManageDialogComponent } from "../components/addons/addon-manage-di MyAddonsAddonCellComponent, InstallFromUrlDialogComponent, AddonManageDialogComponent, + WtfBackupComponent, ], providers: [ { diff --git a/wowup-electron/src/app/modules/pipes.module.ts b/wowup-electron/src/app/modules/pipes.module.ts index 66007018..106265ba 100644 --- a/wowup-electron/src/app/modules/pipes.module.ts +++ b/wowup-electron/src/app/modules/pipes.module.ts @@ -1,3 +1,4 @@ +import { DatePipe } from "@angular/common"; import { NgModule } from "@angular/core"; import { DownloadCountPipe } from "../pipes/download-count.pipe"; import { GetAddonListItemFilePropPipe } from "../pipes/get-addon-list-item-file-prop.pipe"; @@ -29,6 +30,6 @@ import { TrustHtmlPipe } from "../pipes/trust-html.pipe"; InterfaceFormatPipe, InvertBoolPipe, ], - providers: [RelativeDurationPipe, GetAddonListItemFilePropPipe, DownloadCountPipe], + providers: [RelativeDurationPipe, GetAddonListItemFilePropPipe, DownloadCountPipe, DatePipe], }) export class PipesModule {} diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts index 1940efe2..bf4cf99f 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts @@ -33,7 +33,6 @@ import { TableContextHeaderCellComponent } from "../../components/addons/table-c import { GenericProviderError } from "../../errors"; import { AddonSearchResult } from "../../models/wowup/addon-search-result"; import { ColumnState } from "../../models/wowup/column-state"; -import { WowInstallation } from "../../models/wowup/wow-installation"; import { DownloadCountPipe } from "../../pipes/download-count.pipe"; import { RelativeDurationPipe } from "../../pipes/relative-duration-pipe"; import { ElectronService } from "../../services"; @@ -46,6 +45,7 @@ import { WarcraftService } from "../../services/warcraft/warcraft.service"; import { WowUpService } from "../../services/wowup/wowup.service"; import { getEnumKeys } from "../../utils/enum.utils"; import { camelToSnakeCase } from "../../utils/string.utils"; +import { WowInstallation } from "../../../common/warcraft/wow-installation"; interface CategoryItem { category: AddonCategory; diff --git a/wowup-electron/src/app/pages/home/home.component.ts b/wowup-electron/src/app/pages/home/home.component.ts index 7aae4b1a..c58f5287 100644 --- a/wowup-electron/src/app/pages/home/home.component.ts +++ b/wowup-electron/src/app/pages/home/home.component.ts @@ -22,7 +22,6 @@ import { PatchNotesDialogComponent } from "../../components/common/patch-notes-d import { AddonScanError } from "../../errors"; import { AddonInstallState } from "../../models/wowup/addon-install-state"; import { AddonUpdateEvent } from "../../models/wowup/addon-update-event"; -import { WowInstallation } from "../../models/wowup/wow-installation"; import { ElectronService } from "../../services"; import { AddonService, ScanUpdate, ScanUpdateType } from "../../services/addons/addon.service"; import { DialogFactory } from "../../services/dialog/dialog.factory"; @@ -31,6 +30,7 @@ import { SnackbarService } from "../../services/snackbar/snackbar.service"; import { WarcraftInstallationService } from "../../services/warcraft/warcraft-installation.service"; import { WowUpService } from "../../services/wowup/wowup.service"; import { getProtocol } from "../../utils/string.utils"; +import { WowInstallation } from "../../../common/warcraft/wow-installation"; @Component({ selector: "app-home", diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index ffab05c5..433a51ce 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -297,6 +297,9 @@ + diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts index 6e1c3688..1fb3b60d 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts @@ -44,7 +44,7 @@ import { TableContextHeaderCellComponent } from "../../components/addons/table-c import { AddonInstallState } from "../../models/wowup/addon-install-state"; import { AddonUpdateEvent } from "../../models/wowup/addon-update-event"; import { ColumnState } from "../../models/wowup/column-state"; -import { WowInstallation } from "../../models/wowup/wow-installation"; +import { WowInstallation } from "../../../common/warcraft/wow-installation"; import { RelativeDurationPipe } from "../../pipes/relative-duration-pipe"; import { ElectronService } from "../../services"; import { AddonService } from "../../services/addons/addon.service"; @@ -61,6 +61,8 @@ import { SortOrder } from "../../models/wowup/sort-order"; import { PushService } from "../../services/push/push.service"; import { AddonUiService } from "../../services/addons/addon-ui.service"; import { AddonManageDialogComponent } from "../../components/addons/addon-manage-dialog/addon-manage-dialog.component"; +import { WtfService } from "../../services/wtf/wtf.service"; +import { WtfBackupComponent } from "../../components/addons/wtf-backup/wtf-backup.component"; @Component({ selector: "app-my-addons", @@ -241,6 +243,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit { private _snackbarService: SnackbarService, private _pushService: PushService, private _addonUiService: AddonUiService, + private _wtfService: WtfService, public addonService: AddonService, public electronService: ElectronService, public overlay: Overlay, @@ -670,6 +673,15 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit { return data.addon.id; }; + public onClickCreateBackup(): void { + const dialogRef = this._dialogFactory.getDialog(WtfBackupComponent, { + disableClose: true, + data: {}, + }); + + dialogRef.afterClosed().pipe(first()).subscribe(); + } + public onClickImportExport(): void { const dialogRef = this._dialogFactory.getDialog(AddonManageDialogComponent, { disableClose: true, diff --git a/wowup-electron/src/app/pipes/ngx-date.pipe.ts b/wowup-electron/src/app/pipes/ngx-date.pipe.ts index 864958c6..c288a4aa 100644 --- a/wowup-electron/src/app/pipes/ngx-date.pipe.ts +++ b/wowup-electron/src/app/pipes/ngx-date.pipe.ts @@ -1,3 +1,4 @@ +import { DatePipe } from "@angular/common"; import { Pipe, PipeTransform } from "@angular/core"; import { TranslateService } from "@ngx-translate/core"; @@ -5,9 +6,9 @@ import { TranslateService } from "@ngx-translate/core"; name: "localeDate", }) export class NgxDatePipe implements PipeTransform { - public constructor(private translateService: TranslateService) {} + public constructor(private translateService: TranslateService, private datePipe: DatePipe) {} - public transform(value: string | number): any { - return new Date(value).toLocaleString(this.translateService.currentLang); + public transform(value: string | number, pattern: string = "short"): any { + return this.datePipe.transform(value, pattern, undefined, this.translateService.currentLang); } } diff --git a/wowup-electron/src/app/services/addons/addon-broker.service.ts b/wowup-electron/src/app/services/addons/addon-broker.service.ts index ed9ce747..9ec4b5e0 100644 --- a/wowup-electron/src/app/services/addons/addon-broker.service.ts +++ b/wowup-electron/src/app/services/addons/addon-broker.service.ts @@ -5,7 +5,7 @@ import { nanoid } from "nanoid"; import { Addon } from "../../../common/entities/addon"; import { WowClientType } from "../../../common/warcraft/wow-client-type"; -import { WowInstallation } from "../../models/wowup/wow-installation"; +import { WowInstallation } from "../../../common/warcraft/wow-installation"; import { getEnumName } from "../../utils/enum.utils"; import { AddonStorageService } from "../storage/addon-storage.service"; import { WarcraftService } from "../warcraft/warcraft.service"; @@ -129,28 +129,53 @@ export class AddonBrokerService { } public async installImportSummary(importSummary: ImportSummary, installation: WowInstallation): Promise { - for (const comp of importSummary.comparisons) { - if (comp.state !== "added") { - continue; - } + const comps = importSummary.comparisons.filter((comp) => comp.state === "added"); - try { - await this._addonService.installBaseAddon( - comp.imported.id, - comp.imported.provider_name, - installation, - (installState, progress) => { - this._addonInstallSrc.next({ - comparisonId: comp.id, - installState, - progress, - }); - } - ); - } catch (e) { - console.error(`Failed to install imported addon`, e); - } - } + const tasks = comps.map((comp) => { + return (async (c) => { + try { + await this._addonService.installBaseAddon( + c.imported.id, + c.imported.provider_name, + installation, + (installState, progress) => { + this._addonInstallSrc.next({ + comparisonId: c.id, + installState, + progress, + }); + } + ); + } catch (e) { + console.error(`Failed to install imported addon`, e); + } + })(comp); + }); + + await Promise.all(tasks); + + // for (const comp of importSummary.comparisons) { + // if (comp.state !== "added") { + // continue; + // } + + // try { + // await this._addonService.installBaseAddon( + // comp.imported.id, + // comp.imported.provider_name, + // installation, + // (installState, progress) => { + // this._addonInstallSrc.next({ + // comparisonId: comp.id, + // installState, + // progress, + // }); + // } + // ); + // } catch (e) { + // console.error(`Failed to install imported addon`, e); + // } + // } } public getImportSummary(exportPayload: ExportPayload, installation: WowInstallation): ImportSummary { diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 5980f342..01ede106 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -46,7 +46,7 @@ import { AddonSearchResultFile } from "../../models/wowup/addon-search-result-fi import { AddonUpdateEvent } from "../../models/wowup/addon-update-event"; import { ProtocolSearchResult } from "../../models/wowup/protocol-search-result"; import { Toc } from "../../models/wowup/toc"; -import { WowInstallation } from "../../models/wowup/wow-installation"; +import { WowInstallation } from "../../../common/warcraft/wow-installation"; import * as AddonUtils from "../../utils/addon.utils"; import { getEnumName } from "../../utils/enum.utils"; import * as SearchResults from "../../utils/search-result.utils"; diff --git a/wowup-electron/src/app/services/files/file.service.ts b/wowup-electron/src/app/services/files/file.service.ts index ec71d758..038203cf 100644 --- a/wowup-electron/src/app/services/files/file.service.ts +++ b/wowup-electron/src/app/services/files/file.service.ts @@ -25,6 +25,7 @@ import { CopyFileRequest } from "../../../common/models/copy-file-request"; import { UnzipRequest } from "../../../common/models/unzip-request"; import { FsDirent, FsStats, TreeNode } from "../../../common/models/ipc-events"; import { ElectronService } from "../electron/electron.service"; +import { GetDirectoryTreeOptions, GetDirectoryTreeRequest } from "../../../common/models/ipc-request"; @Injectable({ providedIn: "root", @@ -127,8 +128,12 @@ export class FileService { return await this._electronService.invoke(IPC_LIST_DIR_RECURSIVE, dirPath); } - public async getDirectoryTree(dirPath: string): Promise { - return await this._electronService.invoke(IPC_GET_DIRECTORY_TREE, dirPath); + public async getDirectoryTree(dirPath: string, opts?: GetDirectoryTreeOptions): Promise { + const request: GetDirectoryTreeRequest = { + dirPath, + opts, + }; + return await this._electronService.invoke(IPC_GET_DIRECTORY_TREE, request); } public async writeFile(sourcePath: string, contents: string): Promise { @@ -155,6 +160,10 @@ export class FileService { return this._electronService.invoke(IPC_LIST_FILES_CHANNEL, sourcePath, filter); } + public readFileInZip(zipPath: string, filePath: string) { + return this._electronService.invoke("zip-read-file", zipPath, filePath); + } + public async unzipFile(zipFilePath: string, outputFolder: string): Promise { console.log("unzipFile", zipFilePath); @@ -166,4 +175,8 @@ export class FileService { return await this._electronService.invoke(IPC_UNZIP_FILE_CHANNEL, request); } + + public async zipFile(srcPath: string, destPath: string) { + await this._electronService.invoke("zip-file", srcPath, destPath); + } } diff --git a/wowup-electron/src/app/services/icons/icon.service.ts b/wowup-electron/src/app/services/icons/icon.service.ts index 0a4abfb4..e9981c68 100644 --- a/wowup-electron/src/app/services/icons/icon.service.ts +++ b/wowup-electron/src/app/services/icons/icon.service.ts @@ -14,6 +14,7 @@ import { faInfoCircle, faCodeBranch, faCaretDown, + faExclamation, faExclamationTriangle, faCode, faCoins, @@ -31,6 +32,8 @@ import { faUserCircle, faEllipsisV, faCopy, + faTrash, + faHistory, } from "@fortawesome/free-solid-svg-icons"; import { faQuestionCircle, faClock, faCheckCircle as farCheckCircle } from "@fortawesome/free-regular-svg-icons"; import { faDiscord, faGithub, faPatreon } from "@fortawesome/free-brands-svg-icons"; @@ -75,6 +78,9 @@ export class IconService { this.addSvg(faEllipsisV); this.addSvg(faCopy); this.addSvg(farCheckCircle); + this.addSvg(faExclamation); + this.addSvg(faTrash); + this.addSvg(faHistory); } private addSvg(icon: IconDefinition): void { diff --git a/wowup-electron/src/app/services/session/session.service.ts b/wowup-electron/src/app/services/session/session.service.ts index b91bb25a..5d9a68ed 100644 --- a/wowup-electron/src/app/services/session/session.service.ts +++ b/wowup-electron/src/app/services/session/session.service.ts @@ -4,7 +4,7 @@ import { BehaviorSubject, Subject } from "rxjs"; import { Injectable } from "@angular/core"; import { SELECTED_DETAILS_TAB_KEY, TAB_INDEX_SETTINGS } from "../../../common/constants"; -import { WowInstallation } from "../../models/wowup/wow-installation"; +import { WowInstallation } from "../../../common/warcraft/wow-installation"; import { PreferenceStorageService } from "../storage/preference-storage.service"; import { WarcraftInstallationService } from "../warcraft/warcraft-installation.service"; import { ColumnState } from "../../models/wowup/column-state"; diff --git a/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts b/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts index dbbdec03..cf936e44 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts @@ -14,7 +14,7 @@ import { } from "../../../common/constants"; import { WowClientType } from "../../../common/warcraft/wow-client-type"; import { AddonChannelType } from "../../../common/wowup/models"; -import { WowInstallation } from "../../models/wowup/wow-installation"; +import { WowInstallation } from "../../../common/warcraft/wow-installation"; import { getEnumName } from "../../utils/enum.utils"; import { ElectronService } from "../electron/electron.service"; import { FileService } from "../files/file.service"; diff --git a/wowup-electron/src/app/services/warcraft/warcraft.service.ts b/wowup-electron/src/app/services/warcraft/warcraft.service.ts index 6ef50510..41f7d965 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft.service.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft.service.ts @@ -11,7 +11,7 @@ import { InstalledProduct } from "../../models/warcraft/installed-product"; import { ProductDb } from "../../models/warcraft/product-db"; import { AddonFolder } from "../../models/wowup/addon-folder"; import { SelectItem } from "../../models/wowup/select-item"; -import { WowInstallation } from "../../models/wowup/wow-installation"; +import { WowInstallation } from "../../../common/warcraft/wow-installation"; import { getEnumList, getEnumName } from "../../utils/enum.utils"; import { FileService } from "../files/file.service"; import { PreferenceStorageService } from "../storage/preference-storage.service"; diff --git a/wowup-electron/src/app/services/wowup/patch-notes.service.ts b/wowup-electron/src/app/services/wowup/patch-notes.service.ts index 5fcdf708..9c8ac057 100644 --- a/wowup-electron/src/app/services/wowup/patch-notes.service.ts +++ b/wowup-electron/src/app/services/wowup/patch-notes.service.ts @@ -25,11 +25,11 @@ const CHANGELOGS: ChangeLog[] = [
  • Added the ability to remove addons from the details dialog on Get Addons tab (tellier-dev)
  • The first stage of a set of cloud based features you've been asking for
    - +
  • Added the ability to import/export a list of addons for a WoW client. The first step in backing up your list of addons or sharing them with your friends!
    - +
  • Added WowUpHub category support
  • Added WowUpHub preview support
  • @@ -79,7 +79,7 @@ const CHANGELOGS: ChangeLog[] = [

    Fixes

    `, }, diff --git a/wowup-electron/src/app/services/wowup/wowup-account.service.ts b/wowup-electron/src/app/services/wowup/wowup-account.service.ts index 03feadb4..49b4324c 100644 --- a/wowup-electron/src/app/services/wowup/wowup-account.service.ts +++ b/wowup-electron/src/app/services/wowup/wowup-account.service.ts @@ -7,6 +7,7 @@ import { ACCT_FEATURE_KEYS, ACCT_PUSH_ENABLED_KEY, APP_PROTOCOL_NAME, + FEATURE_ACCOUNTS_ENABLED, IPC_PUSH_INIT, IPC_PUSH_REGISTER, IPC_PUSH_SUBSCRIBE, @@ -52,6 +53,10 @@ export class WowUpAccountService { private _preferenceStorageService: PreferenceStorageService, private _wowUpApiService: WowUpApiService ) { + if (!FEATURE_ACCOUNTS_ENABLED) { + return; + } + this.wowUpAuthTokenSrc .pipe( filter((token) => !!token && token.length > 10), diff --git a/wowup-electron/src/app/services/wowup/wowup-addon.service.ts b/wowup-electron/src/app/services/wowup/wowup-addon.service.ts index 923eb5fd..91fed0cd 100644 --- a/wowup-electron/src/app/services/wowup/wowup-addon.service.ts +++ b/wowup-electron/src/app/services/wowup/wowup-addon.service.ts @@ -11,7 +11,7 @@ import { import { Addon } from "../../../common/entities/addon"; import { AddonChannelType } from "../../../common/wowup/models"; import { AddonInstallState } from "../../models/wowup/addon-install-state"; -import { WowInstallation } from "../../models/wowup/wow-installation"; +import { WowInstallation } from "../../../common/warcraft/wow-installation"; import { toInterfaceVersion } from "../../utils/addon.utils"; import { AddonProviderFactory } from "../addons/addon.provider.factory"; import { AddonService } from "../addons/addon.service"; diff --git a/wowup-electron/src/app/services/wowup/wowup.service.ts b/wowup-electron/src/app/services/wowup/wowup.service.ts index 3231964b..85baf741 100644 --- a/wowup-electron/src/app/services/wowup/wowup.service.ts +++ b/wowup-electron/src/app/services/wowup/wowup.service.ts @@ -61,6 +61,7 @@ export class WowUpService { public readonly applicationLogsFolderPath: string = window.logPath; public readonly applicationDownloadsFolderPath: string = join(this.applicationFolderPath, "downloads"); public readonly applicationUpdaterPath: string = join(this.applicationFolderPath, this.updaterName); + public readonly wtfBackupFolder: string = join(this.applicationFolderPath, "wtf_backups"); public readonly preferenceChange$ = this._preferenceChangeSrc.asObservable(); diff --git a/wowup-electron/src/app/services/wtf/wtf.service.ts b/wowup-electron/src/app/services/wtf/wtf.service.ts index 4cc660d7..4a4ee767 100644 --- a/wowup-electron/src/app/services/wtf/wtf.service.ts +++ b/wowup-electron/src/app/services/wtf/wtf.service.ts @@ -1,14 +1,19 @@ import { Injectable } from "@angular/core"; import * as path from "path"; +import { ElectronService } from ".."; import { Addon } from "../../../common/entities/addon"; import { FsStats, TreeNode } from "../../../common/models/ipc-events"; -import { WowInstallation } from "../../models/wowup/wow-installation"; +import { BackupGetExistingRequest } from "../../../common/models/ipc-request"; +import { BackupGetExistingResponse } from "../../../common/models/ipc-response"; +import { WowInstallation } from "../../../common/warcraft/wow-installation"; import { FileService } from "../files/file.service"; +import { WowUpService } from "../wowup/wowup.service"; const WTF_FOLDER = "WTF"; const ACCOUNT_FOLDER = "Account"; const SAVED_VARIABLES_FOLDER = "SavedVariables"; +const BACKUP_META_FILENAME = "wowup-meta.json"; export interface FileStats { name: string; @@ -27,15 +32,42 @@ export interface WtfNode extends TreeNode { children: WtfNode[]; } +export interface WtfBackupMetadataFile { + createdBy: string; + createdAt: number; + contents: WtfBackupMeta[]; +} + +export interface WtfBackupMeta { + path: string; + isDirectory: boolean; + size: number; + hash: string; +} + +export interface WtfBackup { + location: string; + fileName: string; + size: number; + birthtimeMs: number; + error?: string; + metadata?: WtfBackupMetadataFile; +} + @Injectable({ providedIn: "root", }) export class WtfService { - public constructor(private _fileService: FileService) {} + public constructor( + private _fileService: FileService, + private _electronService: ElectronService, + private _wowUpService: WowUpService + ) {} - public async getWtfContents(installation: WowInstallation): Promise { + // including the hash will make this operation much slower + public async getWtfContents(installation: WowInstallation, includeHash = false): Promise { const wtfPath = this.getWtfPath(installation); - const tree = await this._fileService.getDirectoryTree(wtfPath); + const tree = await this._fileService.getDirectoryTree(wtfPath, { includeHash }); return this.getWtfNode(tree); } @@ -136,4 +168,139 @@ export class WtfService { return fsStats; } + + public async getBackupList(installation: WowInstallation): Promise { + const wtfBackups: WtfBackup[] = []; + + const backupZipFiles = await this.listBackupFiles(installation); + const fsStats = await this._fileService.statFiles(backupZipFiles); + + for (let i = 0; i < backupZipFiles.length; i++) { + const zipFile = backupZipFiles[i]; + const stat = fsStats[zipFile]; + + const wtfBackup: WtfBackup = { + location: zipFile, + fileName: path.basename(zipFile), + size: stat.size, + birthtimeMs: stat.birthtimeMs, + }; + + try { + const zipMetaTxt = await this._fileService.readFileInZip(zipFile, BACKUP_META_FILENAME); + const zipMetaData: WtfBackupMetadataFile = JSON.parse(zipMetaTxt); + + if (!Array.isArray(zipMetaData.contents)) { + wtfBackup.error = "INVALID_CONTENTS"; + } else if (typeof zipMetaData.createdAt !== "number") { + wtfBackup.error = "INVALID_CREATED_AT"; + } else if (typeof zipMetaData.createdBy !== "string") { + wtfBackup.error = "INVALID_CREATED_BY"; + } + } catch (e) { + console.error("Failed to process backup metadata", zipFile, e); + wtfBackup.error = "GENERIC_ERROR"; + } finally { + wtfBackups.push(wtfBackup); + } + } + + return wtfBackups; + } + + public async createBackup(installation: WowInstallation): Promise { + await this.createBackupDirectory(installation); + + const metadataFilePath = await this.createBackupMetadataFile(installation); + + try { + await this.createBackupZip(installation); + } finally { + // always delete the metadata file + await this._fileService.remove(metadataFilePath); + } + } + + private async createBackupZip(installation: WowInstallation): Promise { + const wtfPath = this.getWtfPath(installation); + const zipPath = path.join(this.getBackupPath(installation), `wtf_${Date.now()}.zip`); + this._fileService.zipFile(wtfPath, zipPath); + } + + private async createBackupDirectory(installation: WowInstallation): Promise { + const backupPath = this.getBackupPath(installation); + await this._fileService.createDirectory(backupPath); + } + + private async createBackupMetadataFile(installation: WowInstallation): Promise { + const wtfTree = await this.getWtfContents(installation, true); + const wtfList = this.flattenTree([wtfTree]); + console.debug(wtfList); + + const backupMetadata: WtfBackupMetadataFile = { + contents: this.toBackupMeta(wtfList, installation), + createdAt: Date.now(), + createdBy: "manual", + }; + + return await this.writeWtfMetadataFile(backupMetadata, installation); + } + + private async writeWtfMetadataFile( + backupMetadata: WtfBackupMetadataFile, + installation: WowInstallation + ): Promise { + const wtfPath = this.getWtfPath(installation); + + const metaPath = path.join(wtfPath, BACKUP_META_FILENAME); + await this._fileService.writeFile(metaPath, JSON.stringify(backupMetadata, null, 2)); + + return metaPath; + } + + private async getBackup(installation: WowInstallation): Promise { + const args: BackupGetExistingRequest = { + backupPath: this._wowUpService.wtfBackupFolder, + installation, + }; + return await this._electronService.invoke("backup-get-existing", args); + } + + private async listBackupFiles(installation: WowInstallation) { + const backupPath = this.getBackupPath(installation); + const zipFiles = await this._fileService.listFiles(backupPath, "*.zip"); + return zipFiles.map((f) => path.join(backupPath, f)); + } + + private async createFullBackup(installation: WowInstallation): Promise {} + + private async backupNode(node: WtfNode, installation: WowInstallation) { + const wtfPath = this.getWtfPath(installation); + const nodeBase = node.path.replace(wtfPath, ""); + } + + public getBackupPath(installation: WowInstallation): string { + return path.join(this._wowUpService.wtfBackupFolder, installation.id); + } + + private flattenTree(nodes: WtfNode[]): WtfNode[] { + return Array.prototype.concat.apply( + nodes, + nodes.map((n) => this.flattenTree(n.children)) + ); + } + + private toBackupMeta(nodes: WtfNode[], installation: WowInstallation): WtfBackupMeta[] { + const wtfPath = this.getWtfPath(installation); + + return nodes.map((n) => { + const nodeBase = n.path.replace(wtfPath, ""); + return { + hash: n.hash, + isDirectory: n.isDirectory, + path: nodeBase, + size: n.size, + }; + }); + } } diff --git a/wowup-electron/src/assets/i18n/en.json b/wowup-electron/src/assets/i18n/en.json index ac454eae..9cd651ef 100644 --- a/wowup-electron/src/assets/i18n/en.json +++ b/wowup-electron/src/assets/i18n/en.json @@ -29,6 +29,22 @@ "RESET_BUTTON": "Reset", "VERSION_MISMATCH": "This addon is already installed, but the versions do not match" }, + "WTF_BACKUP": { + "DIALOG_TITLE": "WTF Settings Backup: {clientType}", + "CREATE_BACKUP_BUTTON": "Create Backup", + "SHOW_FOLDER_BUTTON": "Show Folder", + "BACKUP_COUNT_TEXT": "Found {count} {count, plural, =1{backup} other{backups}}", + "TOOL_TIP": { + "APPLY_BUTTON": "Apply this backup", + "DELETE_BUTTON": "Delete this backup" + }, + "ERROR": { + "INVALID_CONTENTS": "There was an issue processing this backup", + "INVALID_CREATED_AT": "There was an issue processing this backup", + "INVALID_CREATED_BY": "There was an issue processing this backup", + "GENERIC_ERROR": "There was an issue processing this backup" + } + }, "APP": { "APP_MENU": { "EDIT": { diff --git a/wowup-electron/src/common/constants.ts b/wowup-electron/src/common/constants.ts index 09291e74..3f2299e1 100644 --- a/wowup-electron/src/common/constants.ts +++ b/wowup-electron/src/common/constants.ts @@ -1,5 +1,8 @@ export const APP_USER_MODEL_ID = "io.wowup.jliddev"; // Bundle ID +// FEATURES +export const FEATURE_ACCOUNTS_ENABLED = false; + export const ADDON_PROVIDER_WOWINTERFACE = "WowInterface"; export const ADDON_PROVIDER_CURSEFORGE = "Curse"; export const ADDON_PROVIDER_GITHUB = "GitHub"; diff --git a/wowup-electron/src/common/models/ipc-events.ts b/wowup-electron/src/common/models/ipc-events.ts index 97ca7b4d..d5e791a7 100644 --- a/wowup-electron/src/common/models/ipc-events.ts +++ b/wowup-electron/src/common/models/ipc-events.ts @@ -44,4 +44,5 @@ export interface TreeNode { isDirectory: boolean; children: TreeNode[]; size: number; + hash?: string; } diff --git a/wowup-electron/src/common/models/ipc-request.ts b/wowup-electron/src/common/models/ipc-request.ts index 64abb4ee..7bfae57f 100644 --- a/wowup-electron/src/common/models/ipc-request.ts +++ b/wowup-electron/src/common/models/ipc-request.ts @@ -1,3 +1,24 @@ +import { WowInstallation } from "../warcraft/wow-installation"; + export interface IpcRequest { responseKey: string; } + +export interface BackupGetExistingRequest { + backupPath: string; + installation: WowInstallation; +} + +export interface BackupCreateRequest { + backupPath: string; + installation: WowInstallation; +} + +export interface GetDirectoryTreeOptions { + includeHash?: boolean; +} + +export interface GetDirectoryTreeRequest { + dirPath: string; + opts?: GetDirectoryTreeOptions; +} diff --git a/wowup-electron/src/common/models/ipc-response.ts b/wowup-electron/src/common/models/ipc-response.ts index 8f99c221..484ad1d7 100644 --- a/wowup-electron/src/common/models/ipc-response.ts +++ b/wowup-electron/src/common/models/ipc-response.ts @@ -1,3 +1,11 @@ export interface IpcResponse { error?: Error; } + +export interface BackupGetExistingResponse { + exists: boolean; +} + +export interface BackupCreateResponse { + error?: string; +} diff --git a/wowup-electron/src/app/models/wowup/wow-installation.ts b/wowup-electron/src/common/warcraft/wow-installation.ts similarity index 63% rename from wowup-electron/src/app/models/wowup/wow-installation.ts rename to wowup-electron/src/common/warcraft/wow-installation.ts index 2d6258a8..033fdf36 100644 --- a/wowup-electron/src/app/models/wowup/wow-installation.ts +++ b/wowup-electron/src/common/warcraft/wow-installation.ts @@ -1,5 +1,5 @@ -import { WowClientType } from "../../../common/warcraft/wow-client-type"; -import { AddonChannelType } from "../../../common/wowup/models"; +import { WowClientType } from "./wow-client-type"; +import { AddonChannelType } from "../wowup/models"; export interface WowInstallation { id: string; diff --git a/wowup-electron/src/common/wowup.d.ts b/wowup-electron/src/common/wowup.d.ts index c049cc71..d4956058 100644 --- a/wowup-electron/src/common/wowup.d.ts +++ b/wowup-electron/src/common/wowup.d.ts @@ -38,6 +38,8 @@ declare type RendererChannels = | "curse-get-scan-results" | "wowup-get-scan-results" | "unzip-file" + | "zip-file" + | "zip-read-file" | "copy-file" | "delete-directory" | "list-disks-win32" @@ -79,7 +81,9 @@ declare type RendererChannels = | "push-init" | "push-register" | "push-unregister" - | "push-subscribe"; + | "push-subscribe" + | "backup-get-existing" + | "backup-create"; declare global { interface Window { diff --git a/wowup-electron/src/custom-theme.scss b/wowup-electron/src/custom-theme.scss index fe7271a5..e310dbbc 100644 --- a/wowup-electron/src/custom-theme.scss +++ b/wowup-electron/src/custom-theme.scss @@ -535,6 +535,11 @@ body { } } +.changelog { + a { + color: var(--control-color); + } +} .tab-icon-inactive { svg { transition: fill 0.3s ease 0.1s; diff --git a/wowup-electron/src/styles.scss b/wowup-electron/src/styles.scss index 8f7fec03..27ee0e0c 100644 --- a/wowup-electron/src/styles.scss +++ b/wowup-electron/src/styles.scss @@ -162,6 +162,9 @@ img { margin-top: 1em !important; } +.mb-0 { + margin-bottom: 0 !important; +} .mb-1 { margin-bottom: 0.25em !important; }