From 4513a4831e2eebdeb3e91c6e0cadb53cd31a42eb Mon Sep 17 00:00:00 2001 From: jliddev Date: Sun, 13 Jun 2021 14:27:58 -0500 Subject: [PATCH] All new app update flow Fix an issue with scanning and multi toc --- wowup-electron/app/app-updater.ts | 228 +++++++++++++----- wowup-electron/app/main.ts | 12 +- wowup-electron/electron-builder-local.json | 2 +- wowup-electron/package-lock.json | 11 +- wowup-electron/package.json | 2 +- .../addon-providers/curse-addon-provider.ts | 5 +- .../app/addon-providers/raiderio-provider.ts | 27 ++- .../addon-providers/tukui-addon-provider.ts | 34 +-- .../wow-interface-addon-provider.ts | 26 +- .../wowup-companion-addon-provider.ts | 23 +- wowup-electron/src/app/app.component.ts | 28 ++- .../components/footer/footer.component.html | 32 ++- .../footer/footer.component.spec.ts | 1 - .../app/components/footer/footer.component.ts | 158 +++++------- .../src/app/models/wowup/addon-folder.ts | 3 +- wowup-electron/src/app/models/wowup/toc.ts | 1 + .../src/app/pages/home/home.component.ts | 13 +- .../services/addons/addon.provider.factory.ts | 9 +- .../src/app/services/addons/addon.service.ts | 44 ++-- .../app/services/electron/electron.service.ts | 38 ++- .../src/app/services/toc/toc.service.ts | 29 +++ .../app/services/warcraft/warcraft.service.ts | 17 +- .../app/services/wowup/patch-notes.service.ts | 5 +- .../src/app/services/wowup/wowup.service.ts | 66 +---- wowup-electron/src/assets/i18n/en.json | 4 +- wowup-electron/src/common/constants.ts | 14 +- wowup-electron/src/common/wowup.d.ts | 6 +- wowup-electron/src/common/wowup/models.ts | 22 ++ 28 files changed, 492 insertions(+), 368 deletions(-) diff --git a/wowup-electron/app/app-updater.ts b/wowup-electron/app/app-updater.ts index e095d9f6..5909a44e 100644 --- a/wowup-electron/app/app-updater.ts +++ b/wowup-electron/app/app-updater.ts @@ -1,83 +1,177 @@ import { app, BrowserWindow, ipcMain } from "electron"; import * as log from "electron-log"; -import { autoUpdater, UpdateCheckResult } from "electron-updater"; -import { - APP_UPDATE_AVAILABLE, - APP_UPDATE_CHECK_END, - APP_UPDATE_CHECK_FOR_UPDATE, - APP_UPDATE_CHECK_START, - APP_UPDATE_DOWNLOADED, - APP_UPDATE_ERROR, - APP_UPDATE_INSTALL, - APP_UPDATE_NOT_AVAILABLE, - APP_UPDATE_START_DOWNLOAD, -} from "../src/common/constants"; +import { autoUpdater } from "electron-updater"; +import { IPC_APP_CHECK_UPDATE, IPC_APP_INSTALL_UPDATE, IPC_APP_UPDATE_STATE } from "../src/common/constants"; +import { AppUpdateDownloadProgress, AppUpdateEvent, AppUpdateState } from "../src/common/wowup/models"; -export const checkForUpdates = async (win: BrowserWindow): Promise => { - let result = null; +export class AppUpdater { + private _win: BrowserWindow; - try { - win.webContents.send(APP_UPDATE_CHECK_START); - result = await autoUpdater.checkForUpdates(); - } finally { - win.webContents.send(APP_UPDATE_CHECK_END); + public constructor(win: BrowserWindow) { + this._win = win; + this.initUpdater(); + this.initIpcHandlers(); } - return result; -}; + public dispose(): void {} + + public async checkForUpdates(): Promise { + try { + const result = await autoUpdater.checkForUpdates(); + log.info(`checkForUpdates`, result); + } catch (e) { + log.error("checkForUpdates", e); + } + } + + private initIpcHandlers() { + ipcMain.on(IPC_APP_CHECK_UPDATE, () => { + this.checkForUpdates().catch((e) => console.error(e)); + }); + + // Used this solution for Mac support + // https://github.com/electron-userland/electron-builder/issues/1604#issuecomment-372091881 + ipcMain.on(IPC_APP_INSTALL_UPDATE, () => { + app.removeAllListeners("window-all-closed"); + const browserWindows = BrowserWindow.getAllWindows(); + browserWindows.forEach(function (browserWindow) { + browserWindow.removeAllListeners("close"); + }); + autoUpdater.quitAndInstall(); + }); + } + + private initUpdater() { + autoUpdater.logger = log; + autoUpdater.autoDownload = true; + + autoUpdater.on("checking-for-update", () => { + log.info("autoUpdater checking-for-update"); + const evt: AppUpdateEvent = { + state: AppUpdateState.CheckingForUpdate, + }; + + this._win?.webContents?.send(IPC_APP_UPDATE_STATE, evt); + }); + + autoUpdater.on("update-available", () => { + log.info("autoUpdater update-available"); + const evt: AppUpdateEvent = { + state: AppUpdateState.UpdateAvailable, + }; + + this._win?.webContents?.send(IPC_APP_UPDATE_STATE, evt); + }); + + autoUpdater.on("update-not-available", () => { + log.info("autoUpdater update-not-available"); + const evt: AppUpdateEvent = { + state: AppUpdateState.UpdateNotAvailable, + }; + + this._win?.webContents?.send(IPC_APP_UPDATE_STATE, evt); + }); + + autoUpdater.on("download-progress", (progressObj: AppUpdateDownloadProgress) => { + const evt: AppUpdateEvent = { + state: AppUpdateState.Downloading, + progress: { ...progressObj }, + }; + + this._win?.webContents?.send(IPC_APP_UPDATE_STATE, evt); + }); + + autoUpdater.on("update-downloaded", () => { + log.info("autoUpdater update-downloaded"); + const evt: AppUpdateEvent = { + state: AppUpdateState.Downloaded, + }; + + this._win?.webContents?.send(IPC_APP_UPDATE_STATE, evt); + }); + + autoUpdater.on("error", (e) => { + log.error("autoUpdater error", e); + const evt: AppUpdateEvent = { + state: AppUpdateState.Error, + error: e.toString(), + }; + + this._win?.webContents?.send(IPC_APP_UPDATE_STATE, evt); + }); + } +} + +// export const checkForUpdates = async (win: BrowserWindow): Promise => { +// let result = undefined; +// try { +// win.webContents.send(APP_UPDATE_CHECK_START); +// result = await autoUpdater.checkForUpdates(); +// } catch (e) { +// console.error(e); +// } finally { +// win.webContents.send(APP_UPDATE_CHECK_END); +// } + +// return result; +// }; // Example: https://github.com/electron-userland/electron-builder/blob/docs/encapsulated%20manual%20update%20via%20menu.js -export function initializeAppUpdater(win: BrowserWindow): void { - autoUpdater.logger = log; - autoUpdater.autoDownload = true; - // autoUpdater.allowPrerelease = true; +// export function initializeAppUpdater(win: BrowserWindow): void { +// autoUpdater.logger = log; +// autoUpdater.autoDownload = true; +// // autoUpdater.allowPrerelease = true; - autoUpdater.on("update-available", () => { - log.info(APP_UPDATE_AVAILABLE); - win.webContents.send(APP_UPDATE_AVAILABLE); - }); +// autoUpdater.on("update-available", () => { +// log.info(APP_UPDATE_AVAILABLE); +// win.webContents.send(APP_UPDATE_AVAILABLE); +// }); - autoUpdater.on("update-not-available", () => { - log.info(APP_UPDATE_AVAILABLE); - win.webContents.send(APP_UPDATE_NOT_AVAILABLE); - }); +// autoUpdater.on("update-not-available", () => { +// log.info(APP_UPDATE_AVAILABLE); +// win.webContents.send(APP_UPDATE_NOT_AVAILABLE); +// }); - autoUpdater.on("update-downloaded", () => { - log.info(APP_UPDATE_DOWNLOADED); - win.webContents.send(APP_UPDATE_DOWNLOADED); - }); +// autoUpdater.on("update-downloaded", () => { +// log.info(APP_UPDATE_DOWNLOADED); +// win.webContents.send(APP_UPDATE_DOWNLOADED); +// }); - autoUpdater.on("error", (e) => { - if (e.message.indexOf("dev-app-update.yml") !== -1) { - return; - } +// autoUpdater.on("error", (e) => { +// if (e.message.indexOf("dev-app-update.yml") !== -1) { +// return; +// } - log.error(APP_UPDATE_ERROR, e); - win.webContents.send(APP_UPDATE_ERROR, e); - }); -} +// log.error(APP_UPDATE_ERROR, e); +// win.webContents.send(APP_UPDATE_ERROR, e); +// }); +// } -export function initializeAppUpdateIpcHandlers(win: BrowserWindow): void { - ipcMain.handle(APP_UPDATE_START_DOWNLOAD, async () => { - log.info(APP_UPDATE_START_DOWNLOAD); - win.webContents.send(APP_UPDATE_START_DOWNLOAD); - return await autoUpdater.downloadUpdate(); - }); +// export function initializeAppUpdateIpcHandlers(win: BrowserWindow): void { +// ipcMain.handle(APP_UPDATE_START_DOWNLOAD, async () => { +// log.info(APP_UPDATE_START_DOWNLOAD); +// win.webContents.send(APP_UPDATE_START_DOWNLOAD); +// return await autoUpdater.downloadUpdate(); +// }); - // Used this solution for Mac support - // https://github.com/electron-userland/electron-builder/issues/1604#issuecomment-372091881 - ipcMain.handle(APP_UPDATE_INSTALL, () => { - log.info(APP_UPDATE_INSTALL); - app.removeAllListeners("window-all-closed"); - const browserWindows = BrowserWindow.getAllWindows(); - browserWindows.forEach(function (browserWindow) { - browserWindow.removeAllListeners("close"); - }); - autoUpdater.quitAndInstall(); - }); +// // Used this solution for Mac support +// // https://github.com/electron-userland/electron-builder/issues/1604#issuecomment-372091881 +// ipcMain.handle(APP_UPDATE_INSTALL, () => { +// log.info(APP_UPDATE_INSTALL); +// app.removeAllListeners("window-all-closed"); +// const browserWindows = BrowserWindow.getAllWindows(); +// browserWindows.forEach(function (browserWindow) { +// browserWindow.removeAllListeners("close"); +// }); +// autoUpdater.quitAndInstall(); +// }); - ipcMain.handle(APP_UPDATE_CHECK_FOR_UPDATE, async () => { - log.info(APP_UPDATE_CHECK_FOR_UPDATE); - return await checkForUpdates(win); - }); -} +// ipcMain.handle(APP_UPDATE_CHECK_FOR_UPDATE, async () => { +// log.info(APP_UPDATE_CHECK_FOR_UPDATE); +// try { +// return await checkForUpdates(win); +// } catch (e) { +// console.error(e); +// } +// }); +// } diff --git a/wowup-electron/app/main.ts b/wowup-electron/app/main.ts index eb2c7fcb..d023e207 100644 --- a/wowup-electron/app/main.ts +++ b/wowup-electron/app/main.ts @@ -4,11 +4,11 @@ import { find } from "lodash"; import * as minimist from "minimist"; import { arch as osArch, release as osRelease, type as osType } from "os"; import { join } from "path"; -import { format as urlFormat, pathToFileURL } from "url"; +import { pathToFileURL } from "url"; import { inspect } from "util"; import { createAppMenu } from "./app-menu"; -import { initializeAppUpdateIpcHandlers, initializeAppUpdater } from "./app-updater"; +import { AppUpdater } from "./app-updater"; import { initializeIpcHandlers, setPendingOpenUrl } from "./ipc-events"; import * as platform from "./platform"; import { @@ -74,6 +74,7 @@ log.info("USER_AGENT", USER_AGENT); let appIsQuitting = false; let win: BrowserWindow = null; +let appUpdater: AppUpdater | undefined = undefined; let loadFailCount = 0; // APP MENU SETUP @@ -156,6 +157,7 @@ if (app.isReady()) { app.on("before-quit", () => { win = null; appIsQuitting = true; + appUpdater?.dispose(); }); // Quit when all windows are closed. @@ -265,10 +267,10 @@ function createWindow(): BrowserWindow { // Create the browser window. win = new BrowserWindow(windowOptions); + appUpdater = new AppUpdater(win); + initializeIpcHandlers(win, USER_AGENT); initializeStoreIpcHandlers(); - initializeAppUpdater(win); - initializeAppUpdateIpcHandlers(win); // Keep track of window state mainWindowManager.monitorState(win); @@ -329,6 +331,8 @@ function createWindow(): BrowserWindow { } else if (mainWindowManager.isMaximized) { win.maximize(); } + + appUpdater.checkForUpdates().catch((e) => console.error(e)); }); win.on("close", (e) => { diff --git a/wowup-electron/electron-builder-local.json b/wowup-electron/electron-builder-local.json index 15711120..e7e86ea2 100644 --- a/wowup-electron/electron-builder-local.json +++ b/wowup-electron/electron-builder-local.json @@ -45,7 +45,7 @@ ], "win": { "icon": "electron-build/icon.ico", - "target": ["portable"], + "target": ["nsis"], "forceCodeSigning": false, "publisherName": "WowUp LLC" }, diff --git a/wowup-electron/package-lock.json b/wowup-electron/package-lock.json index 0595a11d..0725d1b9 100644 --- a/wowup-electron/package-lock.json +++ b/wowup-electron/package-lock.json @@ -1,6 +1,6 @@ { "name": "wowup", - "version": "2.4.0-beta.1", + "version": "2.4.0-beta.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -11441,7 +11441,8 @@ }, "hosted-git-info": { "version": "2.8.8", - "resolved": "", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "is-fullwidth-code-point": { @@ -13590,7 +13591,8 @@ }, "hosted-git-info": { "version": "2.8.8", - "resolved": "", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "locate-path": { @@ -14495,7 +14497,8 @@ }, "normalize-url": { "version": "4.5.0", - "resolved": "", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", "dev": true }, "npm-bundled": { diff --git a/wowup-electron/package.json b/wowup-electron/package.json index 5fd6370d..086ae428 100644 --- a/wowup-electron/package.json +++ b/wowup-electron/package.json @@ -1,7 +1,7 @@ { "name": "wowup", "productName": "WowUp", - "version": "2.4.0-beta.2", + "version": "2.4.0-beta.1", "description": "World of Warcraft addon updater", "homepage": "https://wowup.io", "author": { 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 dd2d5a22..08dd42de 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -43,6 +43,7 @@ import * as AddonUtils from "../utils/addon.utils"; import { getEnumName } from "../utils/enum.utils"; import { AddonProvider, GetAllResult } from "./addon-provider"; import { strictFilter } from "../utils/array.utils"; +import { TocService } from "../services/toc/toc.service"; interface ProtocolData { addonId: number; @@ -84,6 +85,7 @@ export class CurseAddonProvider extends AddonProvider { private _cachingService: CachingService, private _electronService: ElectronService, private _wowupApiService: WowUpApiService, + private _tocService: TocService, _networkService: NetworkService ) { super(); @@ -817,7 +819,8 @@ export class CurseAddonProvider extends AddonProvider { const latestFiles = this.getLatestFiles(scanResult.searchResult, installation.clientType); - const gameVersion = AddonUtils.getGameVersion(scanResult.addonFolder?.toc.interface); + const targetToc = this._tocService.getTocForGameType2(scanResult.addonFolder.tocs, installation.clientType); + const gameVersion = AddonUtils.getGameVersion(targetToc.interface); let channelType = this.getChannelType(scanResult.exactMatch.file.releaseType); let latestVersion = latestFiles.find((lf) => this.getChannelType(lf.releaseType) <= channelType); diff --git a/wowup-electron/src/app/addon-providers/raiderio-provider.ts b/wowup-electron/src/app/addon-providers/raiderio-provider.ts index 34c347ad..14b16011 100644 --- a/wowup-electron/src/app/addon-providers/raiderio-provider.ts +++ b/wowup-electron/src/app/addon-providers/raiderio-provider.ts @@ -8,6 +8,7 @@ import { AddonFolder } from "../models/wowup/addon-folder"; import { getEnumName } from "../utils/enum.utils"; import { AddonProvider } from "./addon-provider"; import { getGameVersion } from "../utils/addon.utils"; +import { TocService } from "../services/toc/toc.service"; export class RaiderIoAddonProvider extends AddonProvider { private readonly _scanWebsite = "https://raider.io"; @@ -21,7 +22,7 @@ export class RaiderIoAddonProvider extends AddonProvider { public readonly allowEdit = false; public enabled = true; - public constructor() { + public constructor(private _tocService: TocService) { super(); } @@ -36,6 +37,7 @@ export class RaiderIoAddonProvider extends AddonProvider { return Promise.resolve(undefined); } + const targetToc = this._tocService.getTocForGameType2(raiderIo.tocs, installation.clientType); const dependencies = _.filter(addonFolders, (addonFolder) => this.isRaiderIoDependant(addonFolder)); console.debug("RAIDER IO CLIENT FOUND", dependencies); @@ -44,31 +46,33 @@ export class RaiderIoAddonProvider extends AddonProvider { const installedFolders = installedFolderList.join(","); for (const rioAddonFolder of rioAddonFolders) { + const subTargetToc = this._tocService.getTocForGameType2(rioAddonFolder.tocs, installation.clientType); + rioAddonFolder.matchingAddon = { autoUpdateEnabled: false, channelType: AddonChannelType.Stable, clientType: installation.clientType, id: uuidv4(), isIgnored: true, - name: raiderIo.toc.title ?? "unknown", - author: rioAddonFolder.toc.author, + name: targetToc.title ?? "unknown", + author: subTargetToc.author, downloadUrl: "", externalId: this.name, externalUrl: this._scanWebsite, - gameVersion: getGameVersion(rioAddonFolder.toc.interface), + gameVersion: getGameVersion(subTargetToc.interface), installedAt: new Date(), installedFolders: installedFolders, installedFolderList: installedFolderList, - installedVersion: rioAddonFolder.toc.version || raiderIo.toc.version, - latestVersion: raiderIo.toc.version, + installedVersion: subTargetToc.version || targetToc.version, + latestVersion: subTargetToc.version, providerName: this.name, thumbnailUrl: "http://cdnassets.raider.io/images/fb_app_image.jpg?2019-11-18", updatedAt: new Date(), - summary: rioAddonFolder.toc.notes, + summary: subTargetToc.notes, downloadCount: 0, screenshotUrls: [], releasedAt: new Date(), - isLoadOnDemand: rioAddonFolder.toc.loadOnDemand === "1", + isLoadOnDemand: subTargetToc.loadOnDemand === "1", externalChannel: getEnumName(AddonChannelType, AddonChannelType.Stable), installationId: installation.id, }; @@ -80,14 +84,13 @@ export class RaiderIoAddonProvider extends AddonProvider { private isRaiderIo(addonFolder: AddonFolder) { return ( addonFolder.name === this._scanFolderName && - addonFolder.toc?.website === this._scanWebsite && - addonFolder.toc?.addonProvider === this._scanAddonProvider + addonFolder.tocs.some((toc) => toc.website === this._scanWebsite && toc.addonProvider === this._scanAddonProvider) ); } private isRaiderIoDependant(addonFolder: AddonFolder) { - return ( - addonFolder.toc?.dependencies !== undefined && addonFolder.toc.dependencies.indexOf(this._scanFolderName) !== -1 + return addonFolder.tocs.some( + (toc) => toc.dependencies !== undefined && toc.dependencies.indexOf(this._scanFolderName) !== -1 ); } } 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 2b27e6ff..2c5b878b 100644 --- a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts @@ -17,6 +17,7 @@ import { CircuitBreakerWrapper, NetworkService } from "../services/network/netwo import { getGameVersion } from "../utils/addon.utils"; import { getEnumName } from "../utils/enum.utils"; import { AddonProvider, GetAllResult } from "./addon-provider"; +import { TocService } from "../services/toc/toc.service"; const API_URL = "https://www.tukui.org/api.php"; const CLIENT_API_URL = "https://www.tukui.org/client-api.php"; @@ -34,7 +35,11 @@ export class TukUiAddonProvider extends AddonProvider { public enabled = true; - public constructor(private _cachingService: CachingService, private _networkService: NetworkService) { + public constructor( + private _cachingService: CachingService, + private _networkService: NetworkService, + private _tocService: TocService + ) { super(); this._circuitBreaker = this._networkService.getCircuitBreaker(`${this.name}_main`); } @@ -170,20 +175,23 @@ export class TukUiAddonProvider extends AddonProvider { const matches: TukUiAddon[] = []; // Sort folders to prioritize ones with a toc id - let sortedAddonFolders = _.orderBy(addonFolders, ["toc.tukUiProjectId"], ["desc"]); - sortedAddonFolders = _.filter(sortedAddonFolders, (folder) => folder.toc.loadOnDemand !== "1"); + const tukProjectAddonFolders = addonFolders.filter((folder) => + folder.tocs.some((toc) => !!toc.tukUiProjectId && toc.loadOnDemand !== "1") + ); + + for (const addonFolder of tukProjectAddonFolders) { + const targetToc = this._tocService.getTocForGameType2(addonFolder.tocs, installation.clientType); - for (const addonFolder of sortedAddonFolders) { let tukUiAddon: TukUiAddon; - if (addonFolder.toc?.tukUiProjectId) { - const match = _.find(allAddons, (addon) => addon.id.toString() === addonFolder.toc.tukUiProjectId); + if (targetToc?.tukUiProjectId) { + const match = _.find(allAddons, (addon) => addon.id.toString() === targetToc.tukUiProjectId); if (!match) { continue; } tukUiAddon = match; } else { - const results = await this.searchAddons(addonFolder.toc.title, installation.clientType); + const results = await this.searchAddons(targetToc.title, installation.clientType); const firstResult = _.first(results); if (!firstResult) { continue; @@ -193,7 +201,7 @@ export class TukUiAddonProvider extends AddonProvider { // If we got a fuzzy name match, ensure it's not already added to prevent hiding addons if (tukUiAddon && _.findIndex(matches, (match) => match.id.toString() === tukUiAddon.id.toString()) !== -1) { - console.warn(`Overlapping addon: ${addonFolder.toc.title ?? ""} => ${tukUiAddon.name}`); + console.warn(`Overlapping addon: ${targetToc.title ?? ""} => ${tukUiAddon.name}`); continue; } } @@ -204,12 +212,10 @@ export class TukUiAddonProvider extends AddonProvider { matches.push({ ...tukUiAddon }); - const installedFolders = addonFolder.toc.tukUiProjectFolders - ? addonFolder.toc.tukUiProjectFolders - : tukUiAddon.name; + const installedFolders = targetToc.tukUiProjectFolders ? targetToc.tukUiProjectFolders : tukUiAddon.name; - const installedFolderList = addonFolder.toc.tukUiProjectFolders - ? addonFolder.toc.tukUiProjectFolders.split(",").map((f) => f.trim()) + const installedFolderList = targetToc.tukUiProjectFolders + ? targetToc.tukUiProjectFolders.split(",").map((f) => f.trim()) : [tukUiAddon.name]; addonFolder.matchingAddon = { @@ -227,7 +233,7 @@ export class TukUiAddonProvider extends AddonProvider { installedAt: addonFolder.fileStats?.birthtime ?? new Date(0), installedFolders: installedFolders, installedFolderList: installedFolderList, - installedVersion: addonFolder.toc.version, + installedVersion: targetToc.version, latestVersion: tukUiAddon.version, providerName: this.name, thumbnailUrl: tukUiAddon.screenshot_url, 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 49875c78..3b05e3c9 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 @@ -21,6 +21,7 @@ import { convertBbcode } from "../utils/bbcode.utils"; import { getEnumName } from "../utils/enum.utils"; import { AddonProvider, GetAllResult } from "./addon-provider"; import { strictFilter } from "../utils/array.utils"; +import { TocService } from "../services/toc/toc.service"; const API_URL = "https://api.mmoui.com/v4/game/WOW"; const ADDON_URL = "https://www.wowinterface.com/downloads/info"; @@ -36,7 +37,11 @@ export class WowInterfaceAddonProvider extends AddonProvider { public readonly allowEdit = true; public enabled = true; - public constructor(private _cachingService: CachingService, private _networkService: NetworkService) { + public constructor( + private _cachingService: CachingService, + private _networkService: NetworkService, + private _tocService: TocService + ) { super(); this._circuitBreaker = this._networkService.getCircuitBreaker(`${this.name}_main`); } @@ -136,15 +141,20 @@ export class WowInterfaceAddonProvider extends AddonProvider { addonChannelType: AddonChannelType, addonFolders: AddonFolder[] ): Promise { - const addonIds = addonFolders.filter((af) => !!af.toc.wowInterfaceId).map((af) => af.toc.wowInterfaceId ?? ""); + const wowiFolders = addonFolders.filter((folder) => + folder.tocs.some((toc) => !!toc.wowInterfaceId && toc.loadOnDemand !== "1") + ); + const addonIds = _.uniq(_.flatten(wowiFolders.map((folder) => folder.tocs.map((toc) => toc.wowInterfaceId)))); + const addonDetails = await this.getAllAddonDetails(addonIds); - for (const addonFolder of addonFolders) { - if (!addonFolder?.toc?.wowInterfaceId) { + for (const addonFolder of wowiFolders) { + const targetToc = this._tocService.getTocForGameType2(addonFolder.tocs, installation.clientType); + if (!targetToc?.wowInterfaceId) { continue; } - const details = addonDetails.find((ad) => ad.id.toString() === addonFolder.toc.wowInterfaceId); + const details = addonDetails.find((ad) => ad.id.toString() === targetToc.wowInterfaceId); if (!details) { console.warn("Details not found"); continue; @@ -213,6 +223,8 @@ export class WowInterfaceAddonProvider extends AddonProvider { addonChannelType: AddonChannelType, addonFolder: AddonFolder ): Addon { + const targetToc = this._tocService.getTocForGameType2(addonFolder.tocs, installation.clientType); + return { id: uuidv4(), author: response.author, @@ -222,11 +234,11 @@ export class WowInterfaceAddonProvider extends AddonProvider { downloadUrl: response.downloadUri, externalId: response.id.toString(), externalUrl: this.getAddonUrl(response), - gameVersion: getGameVersion(addonFolder.toc.interface), + gameVersion: getGameVersion(targetToc.interface), installedAt: new Date(), installedFolders: addonFolder.name, installedFolderList: [addonFolder.name], - installedVersion: addonFolder.toc?.version, + installedVersion: targetToc?.version, isIgnored: false, latestVersion: response.version, name: response.title, 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 339e3381..111dc518 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,8 +4,10 @@ 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 { Toc } from "../models/wowup/toc"; import { WowInstallation } from "../models/wowup/wow-installation"; import { FileService } from "../services/files/file.service"; +import { TocService } from "../services/toc/toc.service"; import { getGameVersion } from "../utils/addon.utils"; import { getEnumName } from "../utils/enum.utils"; import { AddonProvider } from "./addon-provider"; @@ -21,7 +23,7 @@ export class WowUpCompanionAddonProvider extends AddonProvider { public readonly allowEdit = false; public enabled = true; - public constructor(private _fileService: FileService) { + public constructor(private _fileService: FileService, private _tocService: TocService) { super(); } @@ -35,6 +37,7 @@ export class WowUpCompanionAddonProvider extends AddonProvider { return; } + const targetToc = this._tocService.getTocForGameType2(companion.tocs, installation.clientType); const lastUpdatedAt = await this._fileService.getLatestDirUpdateTime(companion.path); companion.matchingAddon = { @@ -43,25 +46,25 @@ export class WowUpCompanionAddonProvider extends AddonProvider { clientType: installation.clientType, id: uuidv4(), isIgnored: true, - name: companion.toc.title ?? "unknown", - author: companion.toc.author ?? "unknown", + name: targetToc.title ?? "unknown", + author: targetToc.author ?? "unknown", downloadUrl: "", externalId: this.name, externalUrl: X_WEBSITE, - gameVersion: getGameVersion(companion.toc.interface), + gameVersion: getGameVersion(targetToc.interface), installedAt: new Date(lastUpdatedAt), installedFolders: companion.name, installedFolderList: [companion.name], - installedVersion: companion.toc.version, - latestVersion: companion.toc.version, + installedVersion: targetToc.version, + latestVersion: targetToc.version, providerName: this.name, thumbnailUrl: "https://avatars.githubusercontent.com/u/74023737?s=400&v=4", updatedAt: new Date(lastUpdatedAt), - summary: companion.toc.notes, + summary: targetToc.notes, downloadCount: 0, screenshotUrls: [], releasedAt: new Date(lastUpdatedAt), - isLoadOnDemand: companion.toc.loadOnDemand === "1", + isLoadOnDemand: targetToc.loadOnDemand === "1", externalChannel: getEnumName(AddonChannelType, AddonChannelType.Stable), installationId: installation.id, }; @@ -70,8 +73,8 @@ export class WowUpCompanionAddonProvider extends AddonProvider { private isWowUpCompanion(addonFolder: AddonFolder) { return ( addonFolder.name === WOWUP_DATA_ADDON_FOLDER_NAME && - addonFolder.toc?.website === X_WEBSITE && - addonFolder.toc?.addonProvider === X_WOWUP_ADDON_PROVIDER + addonFolder.tocs?.some((toc) => toc.website === X_WEBSITE) && + addonFolder.tocs?.some((toc) => toc.addonProvider === X_WOWUP_ADDON_PROVIDER) ); } } diff --git a/wowup-electron/src/app/app.component.ts b/wowup-electron/src/app/app.component.ts index e26ca4c2..a4da7c0b 100644 --- a/wowup-electron/src/app/app.component.ts +++ b/wowup-electron/src/app/app.component.ts @@ -32,7 +32,7 @@ import { IPC_REQUEST_INSTALL_FROM_URL, WOWUP_LOGO_FILENAME, } from "../common/constants"; -import { MenuConfig, SystemTrayConfig } from "../common/wowup/models"; +import { AppUpdateState, MenuConfig, SystemTrayConfig } from "../common/wowup/models"; import { TelemetryDialogComponent } from "./components/telemetry-dialog/telemetry-dialog.component"; import { ElectronService } from "./services"; import { AddonService } from "./services/addons/addon.service"; @@ -50,6 +50,7 @@ import { AddonSyncError, GitHubFetchReleasesError, GitHubFetchRepositoryError, G import { SnackbarService } from "./services/snackbar/snackbar.service"; import { WarcraftInstallationService } from "./services/warcraft/warcraft-installation.service"; import { ZoomService } from "./services/zoom/zoom.service"; +import { AlertDialogComponent } from "./components/alert-dialog/alert-dialog.component"; @Component({ selector: "app-root", @@ -103,6 +104,31 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { switchMap(() => from(this.initializeAutoUpdate())) ) .subscribe(); + + this.electronService.appUpdate$.subscribe((evt) => { + if (evt.state === AppUpdateState.Error) { + if (evt.error.indexOf("dev-app-update.yml") === -1) { + this._snackbarService.showErrorSnackbar("APP.WOWUP_UPDATE.UPDATE_ERROR"); + } + } else if (evt.state === AppUpdateState.Downloaded) { + // Force the user to update when one is ready + const dialogRef = this._dialog.open(AlertDialogComponent, { + minWidth: 250, + disableClose: true, + data: { + title: this.translate.instant("APP.WOWUP_UPDATE.INSTALL_TITLE"), + message: this.translate.instant("APP.WOWUP_UPDATE.SNACKBAR_TEXT"), + }, + }); + + dialogRef + .afterClosed() + .pipe(first()) + .subscribe(() => { + this.wowUpService.installUpdate(); + }); + } + }); } public ngOnInit(): void { diff --git a/wowup-electron/src/app/components/footer/footer.component.html b/wowup-electron/src/app/components/footer/footer.component.html index 05c21377..6409d314 100644 --- a/wowup-electron/src/app/components/footer/footer.component.html +++ b/wowup-electron/src/app/components/footer/footer.component.html @@ -3,17 +3,25 @@

{{ sessionService.pageContextText$ | async }}

v{{ versionNumber | async }}

- - - +
+
{{'APP.WOWUP_UPDATE.CHECKING_FOR_UPDATE' | translate}}
+
+ + {{'APP.WOWUP_UPDATE.DOWNLOADING_UPDATE' | translate}} +
+
+ +
+
+ +
+
\ No newline at end of file diff --git a/wowup-electron/src/app/components/footer/footer.component.spec.ts b/wowup-electron/src/app/components/footer/footer.component.spec.ts index f0dfdcfc..c3bd1a16 100644 --- a/wowup-electron/src/app/components/footer/footer.component.spec.ts +++ b/wowup-electron/src/app/components/footer/footer.component.spec.ts @@ -29,7 +29,6 @@ describe("FooterComponent", () => { getApplicationVersion: () => Promise.resolve("TESTV"), wowupUpdateCheck$: new Subject().asObservable(), wowupUpdateDownloaded$: new Subject().asObservable(), - wowupUpdateCheckInProgress$: new Subject().asObservable(), wowupUpdateDownloadInProgress$: new Subject().asObservable(), }); diff --git a/wowup-electron/src/app/components/footer/footer.component.ts b/wowup-electron/src/app/components/footer/footer.component.ts index 506ef0b9..6f14b03c 100644 --- a/wowup-electron/src/app/components/footer/footer.component.ts +++ b/wowup-electron/src/app/components/footer/footer.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, NgZone, OnInit } from "@angular/core"; +import { Component, NgZone, OnInit } from "@angular/core"; import { MatDialog } from "@angular/material/dialog"; import { TranslateService } from "@ngx-translate/core"; import { ElectronService } from "../../services"; @@ -7,9 +7,10 @@ import { AppConfig } from "../../../environments/environment"; import { SessionService } from "../../services/session/session.service"; import { WowUpService } from "../../services/wowup/wowup.service"; import { ConfirmDialogComponent } from "../confirm-dialog/confirm-dialog.component"; -import { from, of } from "rxjs"; +import { combineLatest, from, Observable, of } from "rxjs"; import { SnackbarService } from "../../services/snackbar/snackbar.service"; -import { catchError, switchMap } from "rxjs/operators"; +import { catchError, map, switchMap } from "rxjs/operators"; +import { AppUpdateState } from "../../../common/wowup/models"; @Component({ selector: "app-footer", @@ -24,48 +25,27 @@ export class FooterComponent implements OnInit { public isWowUpdateDownloading = false; public updateIconTooltip = "APP.WOWUP_UPDATE.TOOLTIP"; public versionNumber = from(this.wowUpService.getApplicationVersion()); + public appUpdateState = AppUpdateState; + + public appUpdateState$: Observable = this.electronService.appUpdate$.pipe(map((evt) => evt.state)); + + public appUpdateProgress$: Observable = combineLatest([ + of(0), + this.electronService.appUpdate$.pipe(map((evt) => evt.progress?.percent ?? 0)), + ]).pipe(map(([def, val]) => Math.max(def, val))); public constructor( private _dialog: MatDialog, private _translateService: TranslateService, private _zone: NgZone, - private _cdRef: ChangeDetectorRef, public wowUpService: WowUpService, public sessionService: SessionService, private _snackBarService: SnackbarService, - private _electronService: ElectronService, + private electronService: ElectronService, private _wowupService: WowUpService ) {} public ngOnInit(): void { - this.wowUpService.wowupUpdateCheck$.subscribe((updateCheckResult) => { - console.debug("updateCheckResult", updateCheckResult); - this.isWowUpUpdateAvailable = true; - this._snackBarService.showSuccessSnackbar("APP.WOWUP_UPDATE.SNACKBAR_TEXT"); - this._cdRef.detectChanges(); - }); - - this.wowUpService.wowupUpdateDownloaded$.subscribe((result) => { - console.debug("wowupUpdateDownloaded", result); - this._zone.run(() => { - this.isWowUpUpdateDownloaded = true; - this.updateIconTooltip = "APP.WOWUP_UPDATE.DOWNLOADED_TOOLTIP"; - this.onClickUpdateWowup().catch((e) => console.error(e)); - }); - }); - - this.wowUpService.wowupUpdateCheckInProgress$.subscribe((inProgress) => { - console.debug("wowUpUpdateCheckInProgress", inProgress); - this.isCheckingForUpdates = inProgress; - this._cdRef.detectChanges(); - }); - - this.wowUpService.wowupUpdateDownloadInProgress$.subscribe((inProgress) => { - console.debug("wowupUpdateDownloadInProgress", inProgress); - this.isWowUpdateDownloading = inProgress; - this._cdRef.detectChanges(); - }); - // Force the angular zone to pump for every progress update since its outside the zone this.sessionService.statusText$.subscribe(() => { this._zone.run(() => {}); @@ -76,22 +56,8 @@ export class FooterComponent implements OnInit { }); } - public async onClickCheckForUpdates(): Promise { - if (this.isCheckingForUpdates) { - return; - } - - let result: UpdateCheckResult | null = null; - try { - result = await this.wowUpService.checkForAppUpdate(); - - if (result === null || (await this.wowUpService.isSameVersion(result))) { - this._snackBarService.showSnackbar("APP.WOWUP_UPDATE.NOT_AVAILABLE"); - } - } catch (e) { - console.error(e); - this._snackBarService.showErrorSnackbar("APP.WOWUP_UPDATE.UPDATE_ERROR"); - } + public onClickCheckForUpdates(): void { + this.wowUpService.checkForAppUpdate(); } private portableUpdate() { @@ -126,50 +92,54 @@ export class FooterComponent implements OnInit { return; } - public async onClickUpdateWowup(): Promise { - if (!this.isWowUpUpdateAvailable) { - return; - } - - if (this._electronService.isPortable) { - this.portableUpdate(); - return; - } - - if (this.isWowUpUpdateDownloaded) { - const dialogRef = this._dialog.open(ConfirmDialogComponent, { - data: { - title: this._translateService.instant("APP.WOWUP_UPDATE.INSTALL_TITLE"), - message: this._translateService.instant("APP.WOWUP_UPDATE.INSTALL_MESSAGE"), - }, - }); - - dialogRef - .afterClosed() - .pipe( - switchMap((result) => { - if (!result) { - return of(undefined); - } - return from(this.wowUpService.installUpdate()); - }), - catchError((e) => { - console.error(e); - return of(undefined); - }) - ) - .subscribe(); - - return; - } - - this.isUpdatingWowUp = true; - try { - await this.wowUpService.downloadUpdate(); - } catch (e) { - console.error("onClickUpdateWowup", e); - } finally { - this.isUpdatingWowUp = false; - } + public onClickInstallUpdate(): void { + this._wowupService.installUpdate(); } + + // public async onClickUpdateWowup(): Promise { + // if (!this.isWowUpUpdateAvailable) { + // return; + // } + + // if (this.electronService.isPortable) { + // this.portableUpdate(); + // return; + // } + + // if (this.isWowUpUpdateDownloaded) { + // const dialogRef = this._dialog.open(ConfirmDialogComponent, { + // data: { + // title: this._translateService.instant("APP.WOWUP_UPDATE.INSTALL_TITLE"), + // message: this._translateService.instant("APP.WOWUP_UPDATE.INSTALL_MESSAGE"), + // }, + // }); + + // dialogRef + // .afterClosed() + // .pipe( + // switchMap((result) => { + // if (!result) { + // return of(undefined); + // } + // return from(this.wowUpService.installUpdate()); + // }), + // catchError((e) => { + // console.error(e); + // return of(undefined); + // }) + // ) + // .subscribe(); + + // return; + // } + + // this.isUpdatingWowUp = true; + // try { + // await this.wowUpService.downloadUpdate(); + // } catch (e) { + // console.error("onClickUpdateWowup", e); + // } finally { + // this.isUpdatingWowUp = false; + // } + // } } diff --git a/wowup-electron/src/app/models/wowup/addon-folder.ts b/wowup-electron/src/app/models/wowup/addon-folder.ts index 9e8ba5d9..ff03d9c2 100644 --- a/wowup-electron/src/app/models/wowup/addon-folder.ts +++ b/wowup-electron/src/app/models/wowup/addon-folder.ts @@ -9,8 +9,7 @@ export interface AddonFolder { ignoreReason?: AddonIgnoreReason; thumbnailUrl?: string; latestVersion?: string; - toc: Toc; - tocMetaData: string[]; + tocs: Toc[]; matchingAddon?: Addon; fileStats?: FsStats; } diff --git a/wowup-electron/src/app/models/wowup/toc.ts b/wowup-electron/src/app/models/wowup/toc.ts index 0b041e38..d8ba6478 100644 --- a/wowup-electron/src/app/models/wowup/toc.ts +++ b/wowup-electron/src/app/models/wowup/toc.ts @@ -1,4 +1,5 @@ export interface Toc { + fileName: string; interface: string; title?: string; author?: string; diff --git a/wowup-electron/src/app/pages/home/home.component.ts b/wowup-electron/src/app/pages/home/home.component.ts index 5a7dc901..e8c609bc 100644 --- a/wowup-electron/src/app/pages/home/home.component.ts +++ b/wowup-electron/src/app/pages/home/home.component.ts @@ -155,10 +155,8 @@ export class HomeComponent implements AfterViewInit, OnDestroy { // check for an app update every so often this._appUpdateInterval = window.setInterval(() => { - this.checkForAppUpdate().catch((e) => console.error(e)); + this._wowupService.checkForAppUpdate(); }, AppConfig.appUpdateIntervalMs); - - this.checkForAppUpdate().catch((e) => console.error(e)); } private destroyAppUpdateCheck() { @@ -245,15 +243,6 @@ export class HomeComponent implements AfterViewInit, OnDestroy { } }; - private async checkForAppUpdate() { - try { - const appUpdateResponse = await this._wowupService.checkForAppUpdate(); - console.log(appUpdateResponse); - } catch (e) { - console.error(e); - } - } - private onAddonInstalledEvent = (evt: AddonUpdateEvent) => { if (evt.installState !== AddonInstallState.Error) { return; diff --git a/wowup-electron/src/app/services/addons/addon.provider.factory.ts b/wowup-electron/src/app/services/addons/addon.provider.factory.ts index 72e63711..e544a126 100644 --- a/wowup-electron/src/app/services/addons/addon.provider.factory.ts +++ b/wowup-electron/src/app/services/addons/addon.provider.factory.ts @@ -37,11 +37,11 @@ export class AddonProviderFactory { ) {} public createWowUpCompanionAddonProvider(): WowUpCompanionAddonProvider { - return new WowUpCompanionAddonProvider(this._fileService); + return new WowUpCompanionAddonProvider(this._fileService, this._tocService); } public createRaiderIoAddonProvider(): RaiderIoAddonProvider { - return new RaiderIoAddonProvider(); + return new RaiderIoAddonProvider(this._tocService); } public createCurseAddonProvider(): CurseAddonProvider { @@ -49,16 +49,17 @@ export class AddonProviderFactory { this._cachingService, this._electronService, this._wowupApiService, + this._tocService, this._networkService ); } public createTukUiAddonProvider(): TukUiAddonProvider { - return new TukUiAddonProvider(this._cachingService, this._networkService); + return new TukUiAddonProvider(this._cachingService, this._networkService, this._tocService); } public createWowInterfaceAddonProvider(): WowInterfaceAddonProvider { - return new WowInterfaceAddonProvider(this._cachingService, this._networkService); + return new WowInterfaceAddonProvider(this._cachingService, this._networkService, this._tocService); } public createGitHubAddonProvider(): GitHubAddonProvider { diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 7fe7855a..153cdd98 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -1438,7 +1438,7 @@ export class AddonService { for (const provider of this.getEnabledAddonProviders()) { try { - const validFolders = addonFolders.filter((af) => !af.ignoreReason && !af.matchingAddon && af.toc); + const validFolders = addonFolders.filter((af) => !af.ignoreReason && !af.matchingAddon && af.tocs.length > 0); await provider.scan(installation, defaultAddonChannel, validFolders); } catch (e) { console.error(e); @@ -1456,7 +1456,8 @@ export class AddonService { matchedAddonFolders.forEach((maf) => { if (maf.matchingAddon) { - this.setExternalIds(maf.matchingAddon, maf.toc); + const targetToc = this._tocService.getTocForGameType2(maf.tocs, installation.clientType); + this.setExternalIds(maf.matchingAddon, targetToc); } }); @@ -1466,6 +1467,8 @@ export class AddonService { `${addonFolder.matchingAddon?.providerName ?? ""}${addonFolder.matchingAddon?.externalId ?? ""}` ); + console.debug("matchedGroups", matchedGroups); + const addonList: Addon[] = []; for (const value of Object.values(matchedGroups)) { const ordered = _.orderBy(value, (v) => v.matchingAddon?.externalIds?.length ?? 0).reverse(); @@ -1478,7 +1481,9 @@ export class AddonService { // (value) => _.orderBy(value, (v) => v.matchingAddon?.externalIds?.length ?? 0).reverse()[0].matchingAddon // ); - const unmatchedFolders = addonFolders.filter((af) => this.isAddonFolderUnmatched(matchedAddonFolderNames, af)); + const unmatchedFolders = addonFolders.filter((af) => + this.isAddonFolderUnmatched(matchedAddonFolderNames, af, installation) + ); for (const uf of unmatchedFolders) { const unmatchedAddon = await this.createUnmatchedAddon(uf, installation, matchedAddonFolderNames); @@ -1518,7 +1523,7 @@ export class AddonService { if (!addon.providerName || !addon.externalId) { return; } - + this.insertExternalId(externalIds, addon.providerName, addon.externalId); } @@ -1554,14 +1559,20 @@ export class AddonService { * This should verify that a folder that did not have a match, is actually unmatched * This will happen for any sub folders of TukUI or WowInterface addons */ - private isAddonFolderUnmatched(matchedFolderNames: string[], addonFolder: AddonFolder) { + private isAddonFolderUnmatched( + matchedFolderNames: string[], + addonFolder: AddonFolder, + installation: WowInstallation + ) { if (addonFolder.matchingAddon) { return false; } + const targetToc = this._tocService.getTocForGameType2(addonFolder.tocs, installation.clientType); + // if the folder is load on demand, it 'should' be a sub folder - const isLoadOnDemand = addonFolder.toc?.loadOnDemand === "1"; - if (isLoadOnDemand && this.allItemsMatch(addonFolder.toc.dependencyList, matchedFolderNames)) { + const isLoadOnDemand = targetToc?.loadOnDemand === "1"; + if (isLoadOnDemand && this.allItemsMatch(targetToc.dependencyList, matchedFolderNames)) { return false; } @@ -1832,8 +1843,8 @@ export class AddonService { }; } - private hasValidTocTitle(addonFolder: AddonFolder) { - return addonFolder.toc?.title && /[a-zA-Z]/g.test(addonFolder.toc.title); + private hasValidTocTitle(toc: Toc) { + return toc?.title && /[a-zA-Z]/g.test(toc.title); } private async createUnmatchedAddon( @@ -1841,19 +1852,20 @@ export class AddonService { installation: WowInstallation, matchedAddonFolderNames: string[] ): Promise { - const tocMissingDependencies = _.difference(addonFolder.toc?.dependencyList, matchedAddonFolderNames); + const targetToc = this._tocService.getTocForGameType2(addonFolder.tocs, installation.clientType); + const tocMissingDependencies = _.difference(targetToc?.dependencyList, matchedAddonFolderNames); const lastUpdatedAt = await this._fileService.getLatestDirUpdateTime(addonFolder.path); return { id: uuidv4(), - name: this.hasValidTocTitle(addonFolder) ? addonFolder.toc.title ?? addonFolder.name : addonFolder.name, + name: this.hasValidTocTitle(targetToc) ? targetToc.title ?? addonFolder.name : addonFolder.name, thumbnailUrl: "", - latestVersion: addonFolder.toc?.version || "", - installedVersion: addonFolder.toc?.version || "", + latestVersion: targetToc?.version || "", + installedVersion: targetToc?.version || "", clientType: installation.clientType, externalId: "", - gameVersion: AddonUtils.getGameVersion(addonFolder.toc?.interface) || "", - author: addonFolder.toc?.author || "", + gameVersion: AddonUtils.getGameVersion(targetToc?.interface) || "", + author: targetToc?.author || "", downloadUrl: "", externalUrl: "", providerName: ADDON_PROVIDER_UNKNOWN, @@ -1866,7 +1878,7 @@ export class AddonService { installedFolderList: [addonFolder.name], summary: "", screenshotUrls: [], - isLoadOnDemand: addonFolder.toc?.loadOnDemand === "1", + isLoadOnDemand: targetToc?.loadOnDemand === "1", externalChannel: getEnumName(AddonChannelType, AddonChannelType.Stable), missingDependencies: tocMissingDependencies, ignoreReason: addonFolder.ignoreReason, diff --git a/wowup-electron/src/app/services/electron/electron.service.ts b/wowup-electron/src/app/services/electron/electron.service.ts index e100aa9c..653a0cfb 100644 --- a/wowup-electron/src/app/services/electron/electron.service.ts +++ b/wowup-electron/src/app/services/electron/electron.service.ts @@ -4,16 +4,13 @@ import { IpcRendererEvent, OpenDialogOptions, OpenDialogReturnValue, OpenExterna import { LoginItemSettings } from "electron/main"; import { find } from "lodash"; import * as minimist from "minimist"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, ReplaySubject } from "rxjs"; import { v4 as uuidv4 } from "uuid"; import { Injectable } from "@angular/core"; import { - APP_UPDATE_CHECK_END, - APP_UPDATE_CHECK_START, - APP_UPDATE_DOWNLOADED, - APP_UPDATE_START_DOWNLOAD, + IPC_APP_UPDATE_STATE, IPC_CLOSE_WINDOW, IPC_CUSTOM_PROTOCOL_RECEIVED, IPC_FOCUS_WINDOW, @@ -47,7 +44,7 @@ import { IpcResponse } from "../../../common/models/ipc-response"; import { ValueRequest } from "../../../common/models/value-request"; import { ValueResponse } from "../../../common/models/value-response"; import { MainChannels, RendererChannels } from "../../../common/wowup"; -import { AppOptions } from "../../../common/wowup/models"; +import { AppOptions, AppUpdateEvent } from "../../../common/wowup/models"; import { isProtocol } from "../../utils/string.utils"; @Injectable({ @@ -56,18 +53,18 @@ import { isProtocol } from "../../utils/string.utils"; export class ElectronService { private readonly _windowMaximizedSrc = new BehaviorSubject(false); private readonly _windowMinimizedSrc = new BehaviorSubject(false); - private readonly _ipcEventReceivedSrc = new BehaviorSubject(""); private readonly _powerMonitorSrc = new BehaviorSubject(""); private readonly _customProtocolSrc = new BehaviorSubject(""); + private readonly _appUpdateSrc = new ReplaySubject(); private _appVersion = ""; private _opts!: AppOptions; public readonly windowMaximized$ = this._windowMaximizedSrc.asObservable(); public readonly windowMinimized$ = this._windowMinimizedSrc.asObservable(); - public readonly ipcEventReceived$ = this._ipcEventReceivedSrc.asObservable(); public readonly powerMonitor$ = this._powerMonitorSrc.asObservable(); public readonly customProtocol$ = this._customProtocolSrc.asObservable(); + public readonly appUpdate$ = this._appUpdateSrc.asObservable(); public readonly isWin = process.platform === "win32"; public readonly isMac = process.platform === "darwin"; public readonly isLinux = process.platform === "linux"; @@ -100,20 +97,10 @@ export class ElectronService { console.error("Failed to get app version", e); }); - this.onRendererEvent(APP_UPDATE_CHECK_START, () => { - this._ipcEventReceivedSrc.next(APP_UPDATE_CHECK_START); - }); - - this.onRendererEvent(APP_UPDATE_CHECK_END, () => { - this._ipcEventReceivedSrc.next(APP_UPDATE_CHECK_END); - }); - - this.onRendererEvent(APP_UPDATE_START_DOWNLOAD, () => { - this._ipcEventReceivedSrc.next(APP_UPDATE_START_DOWNLOAD); - }); - - this.onRendererEvent(APP_UPDATE_DOWNLOADED, () => { - this._ipcEventReceivedSrc.next(APP_UPDATE_DOWNLOADED); + this.onRendererEvent(IPC_APP_UPDATE_STATE, (evt, updateEvt: AppUpdateEvent) => { + console.log("IPC_APP_UPDATE_STATE", IPC_APP_UPDATE_STATE); + console.log(updateEvt); + this._appUpdateSrc.next(updateEvt); }); this.onRendererEvent(IPC_WINDOW_MINIMIZED, () => { @@ -305,7 +292,12 @@ export class ElectronService { } public async invoke(channel: RendererChannels, ...args: any[]): Promise { - return await window.wowup.rendererInvoke(channel, ...args); + try { + return await window.wowup.rendererInvoke(channel, ...args); + } catch (e) { + console.error("Invoke failed", e); + throw e; + } } public on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void { diff --git a/wowup-electron/src/app/services/toc/toc.service.ts b/wowup-electron/src/app/services/toc/toc.service.ts index 84c1aed4..61f1f2a2 100644 --- a/wowup-electron/src/app/services/toc/toc.service.ts +++ b/wowup-electron/src/app/services/toc/toc.service.ts @@ -31,6 +31,7 @@ export class TocService { public constructor(private _fileService: FileService) {} public async parse(tocPath: string): Promise { + const fileName = path.basename(tocPath); let tocText = await this._fileService.readFile(tocPath); tocText = tocText.trim(); @@ -39,6 +40,7 @@ export class TocService { const dependencyList: string[] = this.getDependencyList(tocText); return { + fileName, author: this.getValue(TOC_AUTHOR, tocText), curseProjectId: this.getValue(TOC_X_CURSE_PROJECT_ID, tocText), interface: this.getValue(TOC_INTERFACE, tocText), @@ -133,6 +135,33 @@ export class TocService { return matchedToc || tocFileNames.find((tfn) => /.*(? toc.fileName); + switch (clientType) { + case WowClientType.Beta: + case WowClientType.Retail: + case WowClientType.RetailPtr: + matchedToc = tocFileNames.find((tfn) => /.*-mainline\.toc$/gi.test(tfn)) || ""; + break; + case WowClientType.ClassicEra: + matchedToc = tocFileNames.find((tfn) => /.*-classic\.toc$/gi.test(tfn)) || ""; + break; + case WowClientType.Classic: + case WowClientType.ClassicBeta: + case WowClientType.ClassicPtr: + matchedToc = tocFileNames.find((tfn) => /.*-bcc\.toc$/gi.test(tfn)) || ""; + break; + default: + break; + } + + matchedToc = matchedToc || tocFileNames.find((tfn) => /.*(? toc.fileName === matchedToc); + } + private getWebsite(tocText: string) { return this.getValue(TOC_WEBSITE, tocText) || this.getValue(TOC_X_WEBSITE, tocText); } diff --git a/wowup-electron/src/app/services/warcraft/warcraft.service.ts b/wowup-electron/src/app/services/warcraft/warcraft.service.ts index 36a2181f..fbf34261 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft.service.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft.service.ts @@ -29,6 +29,7 @@ import { WarcraftServiceImpl } from "./warcraft.service.impl"; import { WarcraftServiceLinux } from "./warcraft.service.linux"; import { WarcraftServiceMac } from "./warcraft.service.mac"; import { WarcraftServiceWin } from "./warcraft.service.win"; +import { Toc } from "../../models/wowup/toc"; // WOW STRINGS @@ -174,21 +175,23 @@ export class WarcraftService { try { const dirPath = path.join(addonFolderPath, dir); const dirFiles = await this._fileService.readdir(dirPath); - const tocFile = dirFiles.find((f) => path.extname(f) === ".toc"); - if (!tocFile) { + const tocFiles = dirFiles.filter((f) => path.extname(f) === ".toc"); + if (tocFiles.length === 0) { return undefined; } - const tocPath = path.join(dirPath, tocFile); - const toc = await this._tocService.parse(tocPath); - const tocMetaData = await this._tocService.parseMetaData(tocPath); + const tocs: Toc[] = []; + for (const tocFile of tocFiles) { + const tocPath = path.join(dirPath, tocFile); + const toc = await this._tocService.parse(tocPath); + tocs.push(toc); + } return { name: dir, path: dirPath, status: "Pending", - toc: toc, - tocMetaData: tocMetaData, + tocs: tocs, }; } catch (e) { console.error(e); 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 e7c83f20..c34a3099 100644 --- a/wowup-electron/src/app/services/wowup/patch-notes.service.ts +++ b/wowup-electron/src/app/services/wowup/patch-notes.service.ts @@ -26,10 +26,11 @@ const CHANGELOGS: ChangeLog[] = [ -
  • Mac M1 build available
  • Changes

      +
    • Russian locale updates (Medok)
    • +
    • German locale updates (Glow)
    • Revamped UI
    • WowUp updates will now download automatically
    • When starting with 0 installs found, user should go to installations page
    • @@ -40,6 +41,8 @@ const CHANGELOGS: ChangeLog[] = [
    • Fixed an issue with table header font in addon details
    • Fixed an issue with odd table de-select behavior
    • Fixed an issue with the client selector overlapping the top toolbar
    • +
    • Fixed an issue with re-scanning not showing the correct game version
    • +
    • Reworked the app update flow
    `, }, diff --git a/wowup-electron/src/app/services/wowup/wowup.service.ts b/wowup-electron/src/app/services/wowup/wowup.service.ts index 5ec03227..279e3b03 100644 --- a/wowup-electron/src/app/services/wowup/wowup.service.ts +++ b/wowup-electron/src/app/services/wowup/wowup.service.ts @@ -9,25 +9,16 @@ import { TranslateService } from "@ngx-translate/core"; import { ADDON_MIGRATION_VERSION_KEY, ADDON_PROVIDERS_KEY, - ALLIANCE_LIGHT_THEME, - ALLIANCE_THEME, - APP_UPDATE_CHECK_END, - APP_UPDATE_CHECK_FOR_UPDATE, - APP_UPDATE_CHECK_START, - APP_UPDATE_DOWNLOADED, - APP_UPDATE_INSTALL, - APP_UPDATE_START_DOWNLOAD, COLLAPSE_TO_TRAY_PREFERENCE_KEY, CURRENT_THEME_KEY, DEFAULT_AUTO_UPDATE_PREFERENCE_KEY_SUFFIX, DEFAULT_CHANNEL_PREFERENCE_KEY_SUFFIX, - DEFAULT_LIGHT_THEME, DEFAULT_THEME, ENABLE_SYSTEM_NOTIFICATIONS_PREFERENCE_KEY, GET_ADDONS_HIDDEN_COLUMNS_KEY, GET_ADDONS_SORT_ORDER, - HORDE_LIGHT_THEME, - HORDE_THEME, + IPC_APP_CHECK_UPDATE, + IPC_APP_INSTALL_UPDATE, IPC_GET_APP_VERSION, LAST_SELECTED_WOW_CLIENT_TYPE_PREFERENCE_KEY, MY_ADDONS_HIDDEN_COLUMNS_KEY, @@ -59,10 +50,6 @@ import { PreferenceStorageService } from "../storage/preference-storage.service" }) export class WowUpService { private readonly _preferenceChangeSrc = new Subject(); - private readonly _wowupUpdateDownloadInProgressSrc = new Subject(); - private readonly _wowupUpdateDownloadedSrc = new Subject(); - private readonly _wowupUpdateCheckSrc = new Subject(); - private readonly _wowupUpdateCheckInProgressSrc = new Subject(); private _availableVersion = ""; @@ -73,10 +60,6 @@ export class WowUpService { public readonly applicationUpdaterPath: string = join(this.applicationFolderPath, this.updaterName); public readonly preferenceChange$ = this._preferenceChangeSrc.asObservable(); - public readonly wowupUpdateDownloaded$ = this._wowupUpdateDownloadedSrc.asObservable(); - public readonly wowupUpdateDownloadInProgress$ = this._wowupUpdateDownloadInProgressSrc.asObservable(); - public readonly wowupUpdateCheck$ = this._wowupUpdateCheckSrc.asObservable(); - public readonly wowupUpdateCheckInProgress$ = this._wowupUpdateCheckInProgressSrc.asObservable(); public constructor( private _preferenceStorageService: PreferenceStorageService, @@ -94,27 +77,6 @@ export class WowUpService { // .then(() => console.debug("createDownloadDirectory complete")) .catch((e) => console.error("Failed to create download directory", e)); - this._electronService.ipcEventReceived$.subscribe((evt) => { - switch (evt) { - case APP_UPDATE_CHECK_START: - console.log(APP_UPDATE_CHECK_START); - this._wowupUpdateCheckInProgressSrc.next(true); - break; - case APP_UPDATE_CHECK_END: - console.log(APP_UPDATE_CHECK_END); - this._wowupUpdateCheckInProgressSrc.next(false); - break; - case APP_UPDATE_START_DOWNLOAD: - console.log(APP_UPDATE_START_DOWNLOAD); - this._wowupUpdateDownloadInProgressSrc.next(true); - break; - case APP_UPDATE_DOWNLOADED: - console.log(APP_UPDATE_DOWNLOADED); - this._wowupUpdateDownloadInProgressSrc.next(false); - break; - } - }); - this.setAutoStartup() .then(() => console.log("loginItemSettings", this._electronService.getLoginItemSettings())) .catch((e) => console.error(e)); @@ -359,17 +321,8 @@ export class WowUpService { await this._fileService.showDirectory(this.applicationLogsFolderPath); } - public async checkForAppUpdate(): Promise { - const updateCheckResult: UpdateCheckResult = await this._electronService.invoke(APP_UPDATE_CHECK_FOR_UPDATE); - - // only notify things when the version changes - const isSameVersion = await this.isSameVersion(updateCheckResult); - if (!isSameVersion) { - this._availableVersion = updateCheckResult.updateInfo.version; - this._wowupUpdateCheckSrc.next(updateCheckResult); - } - - return updateCheckResult; + public checkForAppUpdate(): void { + this._electronService.send(IPC_APP_CHECK_UPDATE); } public async isSameVersion(updateCheckResult: UpdateCheckResult): Promise { @@ -377,15 +330,8 @@ export class WowUpService { return updateCheckResult && updateCheckResult.updateInfo?.version === appVersion; } - public async downloadUpdate(): Promise { - const downloadResult = await this._electronService.invoke(APP_UPDATE_START_DOWNLOAD); - - this._wowupUpdateDownloadedSrc.next(downloadResult); - return downloadResult; - } - - public async installUpdate(): Promise { - return await this._electronService.invoke(APP_UPDATE_INSTALL); + public installUpdate(): void { + return this._electronService.send(IPC_APP_INSTALL_UPDATE); } private setDefaultPreference(key: string, defaultValue: any) { diff --git a/wowup-electron/src/assets/i18n/en.json b/wowup-electron/src/assets/i18n/en.json index 2c45f3e2..063fdf02 100644 --- a/wowup-electron/src/assets/i18n/en.json +++ b/wowup-electron/src/assets/i18n/en.json @@ -70,7 +70,9 @@ "SNACKBAR_ACTION": "Update", "SNACKBAR_TEXT": "A new version of WowUp is available", "TOOLTIP": "WowUp update available", - "UPDATE_ERROR": "Failed to get WowUp update" + "UPDATE_ERROR": "Failed to get WowUp update", + "CHECKING_FOR_UPDATE": "Checking for update", + "DOWNLOADING_UPDATE": "Downloading update" } }, "COMMON": { diff --git a/wowup-electron/src/common/constants.ts b/wowup-electron/src/common/constants.ts index a991a7dc..7e3ac7db 100644 --- a/wowup-electron/src/common/constants.ts +++ b/wowup-electron/src/common/constants.ts @@ -80,6 +80,9 @@ export const IPC_GET_PENDING_OPEN_URLS = "get-pending-open-urls"; export const IPC_GET_LATEST_DIR_UPDATE_TIME = "get-latest-dir-update-time"; export const IPC_SYSTEM_PREFERENCES_GET_USER_DEFAULT = "system-preferences-get-user-default"; export const IPC_SHOW_OPEN_DIALOG = "show-open-dialog"; +export const IPC_APP_UPDATE_STATE = "app-update-state"; +export const IPC_APP_INSTALL_UPDATE = "app-install-update"; +export const IPC_APP_CHECK_UPDATE = "app-check-update"; // IPC STORAGE export const IPC_STORE_GET_OBJECT = "store-get-object"; @@ -115,17 +118,6 @@ export const SELECTED_DETAILS_TAB_KEY = "selected_details_tab"; export const ADDON_MIGRATION_VERSION_KEY = "addon_migration_version"; export const UPDATE_NOTES_POPUP_VERSION_KEY = "update_notes_popup_version"; -// APP UPDATER -export const APP_UPDATE_ERROR = "app-update-error"; -export const APP_UPDATE_DOWNLOADED = "app-update-downloaded"; -export const APP_UPDATE_NOT_AVAILABLE = "app-update-not-available"; -export const APP_UPDATE_AVAILABLE = "app-update-available"; -export const APP_UPDATE_START_DOWNLOAD = "app-update-start-download"; -export const APP_UPDATE_INSTALL = "app-update-install"; -export const APP_UPDATE_CHECK_FOR_UPDATE = "app-update-check-for-update"; -export const APP_UPDATE_CHECK_START = "app-update-check-start"; -export const APP_UPDATE_CHECK_END = "app-update-check-end"; - // THEMES export const DEFAULT_THEME = "default-theme"; export const DEFAULT_LIGHT_THEME = "default-theme-light-theme"; diff --git a/wowup-electron/src/common/wowup.d.ts b/wowup-electron/src/common/wowup.d.ts index 222d9469..d185442e 100644 --- a/wowup-electron/src/common/wowup.d.ts +++ b/wowup-electron/src/common/wowup.d.ts @@ -17,7 +17,8 @@ declare type MainChannels = | "power-monitor-lock" | "power-monitor-unlock" | "request-install-from-url" - | "custom-protocol-received"; + | "custom-protocol-received" + | "app-update-state"; // Events that can be sent from renderer to main declare type RendererChannels = @@ -68,7 +69,8 @@ declare type RendererChannels = | "store-set-object" | "get-latest-dir-update-time" | "system-preferences-get-user-default" - | "show-open-dialog"; + | "show-open-dialog" + | "app-install-update"; declare global { interface Window { diff --git a/wowup-electron/src/common/wowup/models.ts b/wowup-electron/src/common/wowup/models.ts index 2d4d6076..ece98106 100644 --- a/wowup-electron/src/common/wowup/models.ts +++ b/wowup-electron/src/common/wowup/models.ts @@ -11,6 +11,28 @@ export enum AddonDependencyType { Other = 4, } +export enum AppUpdateState { + CheckingForUpdate = 1, + UpdateAvailable, + UpdateNotAvailable, + Downloading, + Downloaded, + Error, +} + +export interface AppUpdateEvent { + state: AppUpdateState; + progress?: AppUpdateDownloadProgress; + error?: string; +} + +export interface AppUpdateDownloadProgress { + bytesPerSecond: number; + percent: number; + transferred: number; + total: number; +} + export enum AddonWarningType { MissingOnProvider = "missing-on-provider", NoProviderFiles = "no-provider-files",