All new app update flow

Fix an issue with scanning and multi toc
This commit is contained in:
jliddev
2021-06-13 14:27:58 -05:00
parent 84e7f30420
commit 4513a4831e
28 changed files with 492 additions and 368 deletions

View File

@@ -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<UpdateCheckResult> => {
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<void> {
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<UpdateCheckResult> => {
// 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);
// }
// });
// }

View File

@@ -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) => {

View File

@@ -45,7 +45,7 @@
],
"win": {
"icon": "electron-build/icon.ico",
"target": ["portable"],
"target": ["nsis"],
"forceCodeSigning": false,
"publisherName": "WowUp LLC"
},

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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);

View File

@@ -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
);
}
}

View File

@@ -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,

View File

@@ -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<void> {
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,

View File

@@ -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)
);
}
}

View File

@@ -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 {

View File

@@ -3,17 +3,25 @@
<div class="row align-items-center text-1">
<p class="mr-3">{{ sessionService.pageContextText$ | async }}</p>
<p>v{{ versionNumber | async }}</p>
<button *ngIf="isWowUpUpdateAvailable && !isWowUpdateDownloading" class="footer-button update-button ml-3 text-1"
matTooltip="{{ updateIconTooltip | translate }}" [disabled]="isUpdatingWowUp" (click)="onClickUpdateWowup()">
<h4>{{ 'APP.WOWUP_UPDATE.SNACKBAR_ACTION' | translate }}</h4>
</button>
<button *ngIf="isWowUpdateDownloading" class="footer-button downloading-button text-2 ml-3 animate">
<mat-icon svgIcon="fas:angle-double-down"></mat-icon>
</button>
<button *ngIf="!isWowUpUpdateAvailable && !isWowUpdateDownloading"
class="footer-button text-1 check-update-button ml-3" [ngClass]="{ animate: isCheckingForUpdates }"
(click)="onClickCheckForUpdates()" matTooltip="{{ 'APP.SYSTEM_TRAY.CHECK_UPDATE' | translate }}">
<mat-icon svgIcon="fas:sync-alt"></mat-icon>
</button>
<div class="ml-3" [ngSwitch]="appUpdateState$ | async">
<div *ngSwitchCase="appUpdateState.CheckingForUpdate">{{'APP.WOWUP_UPDATE.CHECKING_FOR_UPDATE' | translate}}</div>
<div *ngSwitchCase="appUpdateState.Downloading" class="row align-items-center">
<mat-progress-spinner class="" color="primary" mode="determinate" [value]="appUpdateProgress$ | async"
[diameter]="20"></mat-progress-spinner>
<span>{{'APP.WOWUP_UPDATE.DOWNLOADING_UPDATE' | translate}}</span>
</div>
<div *ngSwitchCase="appUpdateState.Downloaded">
<button class="footer-button update-button ml-3 text-1" matTooltip="{{ updateIconTooltip | translate }}"
(click)="onClickInstallUpdate()">
<h4>{{ 'APP.WOWUP_UPDATE.SNACKBAR_ACTION' | translate }}</h4>
</button>
</div>
<div *ngSwitchDefault>
<button class="footer-button text-1 check-update-button ml-3" (click)="onClickCheckForUpdates()"
matTooltip="{{ 'APP.SYSTEM_TRAY.CHECK_UPDATE' | translate }}">
<mat-icon svgIcon="fas:sync-alt"></mat-icon>
</button>
</div>
</div>
</div>
</footer>

View File

@@ -29,7 +29,6 @@ describe("FooterComponent", () => {
getApplicationVersion: () => Promise.resolve("TESTV"),
wowupUpdateCheck$: new Subject<UpdateCheckResult>().asObservable(),
wowupUpdateDownloaded$: new Subject<any>().asObservable(),
wowupUpdateCheckInProgress$: new Subject<boolean>().asObservable(),
wowupUpdateDownloadInProgress$: new Subject<boolean>().asObservable(),
});

View File

@@ -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<AppUpdateState> = this.electronService.appUpdate$.pipe(map((evt) => evt.state));
public appUpdateProgress$: Observable<number> = 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<void> {
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<void> {
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<void> {
// 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;
// }
// }
}

View File

@@ -9,8 +9,7 @@ export interface AddonFolder {
ignoreReason?: AddonIgnoreReason;
thumbnailUrl?: string;
latestVersion?: string;
toc: Toc;
tocMetaData: string[];
tocs: Toc[];
matchingAddon?: Addon;
fileStats?: FsStats;
}

View File

@@ -1,4 +1,5 @@
export interface Toc {
fileName: string;
interface: string;
title?: string;
author?: string;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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<Addon> {
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,

View File

@@ -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<AppUpdateEvent>();
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<T = any>(channel: RendererChannels, ...args: any[]): Promise<T> {
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 {

View File

@@ -31,6 +31,7 @@ export class TocService {
public constructor(private _fileService: FileService) {}
public async parse(tocPath: string): Promise<Toc> {
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) => /.*(?<!-classic|-bcc|-mainline)\.toc$/gi.test(tfn)) || "";
}
public getTocForGameType2(tocs: Toc[], clientType: WowClientType): Toc {
let matchedToc = "";
const tocFileNames = tocs.map((toc) => 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) => /.*(?<!-classic|-bcc|-mainline)\.toc$/gi.test(tfn)) || "";
return tocs.find((toc) => toc.fileName === matchedToc);
}
private getWebsite(tocText: string) {
return this.getValue(TOC_WEBSITE, tocText) || this.getValue(TOC_X_WEBSITE, tocText);
}

View File

@@ -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);

View File

@@ -26,10 +26,11 @@ const CHANGELOGS: ChangeLog[] = [
<img style="width: 200px;" loading="lazy" src="https://www.warcrafttavern.com/wp-content/uploads/2020/10/Warcraft-Tavern-Logo-768x246.png">
</a>
</li>
<li>Mac M1 build available</li>
</ul>
<h4 style="margin-top: 1em;">Changes</h4>
<ul>
<li>Russian locale updates (Medok)</li>
<li>German locale updates (Glow)</li>
<li>Revamped UI</li>
<li>WowUp updates will now download automatically</li>
<li>When starting with 0 installs found, user should go to installations page</li>
@@ -40,6 +41,8 @@ const CHANGELOGS: ChangeLog[] = [
<li>Fixed an issue with table header font in addon details</li>
<li>Fixed an issue with odd table de-select behavior</li>
<li>Fixed an issue with the client selector overlapping the top toolbar</li>
<li>Fixed an issue with re-scanning not showing the correct game version</li>
<li>Reworked the app update flow</li>
</ul>
</div>`,
},

View File

@@ -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<PreferenceChange>();
private readonly _wowupUpdateDownloadInProgressSrc = new Subject<boolean>();
private readonly _wowupUpdateDownloadedSrc = new Subject<any>();
private readonly _wowupUpdateCheckSrc = new Subject<UpdateCheckResult>();
private readonly _wowupUpdateCheckInProgressSrc = new Subject<boolean>();
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<UpdateCheckResult> {
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<boolean> {
@@ -377,15 +330,8 @@ export class WowUpService {
return updateCheckResult && updateCheckResult.updateInfo?.version === appVersion;
}
public async downloadUpdate(): Promise<any> {
const downloadResult = await this._electronService.invoke(APP_UPDATE_START_DOWNLOAD);
this._wowupUpdateDownloadedSrc.next(downloadResult);
return downloadResult;
}
public async installUpdate(): Promise<any> {
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) {

View File

@@ -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": {

View File

@@ -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";

View File

@@ -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 {

View File

@@ -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",