From a3c865c88cfbbceb604bcfe54e57efe1d1be54d2 Mon Sep 17 00:00:00 2001 From: jliddev Date: Tue, 23 Feb 2021 22:04:28 -0600 Subject: [PATCH] #743 Improvements to the installtion management flow --- .../options-wow-section.component.html | 27 +-- .../options-wow-section.component.ts | 72 +++++- .../wow-client-options.component.html | 29 ++- .../wow-client-options.component.scss | 2 +- .../wow-client-options.component.ts | 100 ++++---- .../src/app/services/icons/icon.service.ts | 8 +- .../warcraft/warcraft-installation.service.ts | 222 ++++++++++++++++-- .../warcraft/warcraft.service.impl.ts | 2 + .../warcraft/warcraft.service.linux.ts | 32 ++- .../services/warcraft/warcraft.service.mac.ts | 22 ++ .../app/services/warcraft/warcraft.service.ts | 14 ++ .../services/warcraft/warcraft.service.win.ts | 22 ++ wowup-electron/src/assets/i18n/en.json | 14 +- wowup-electron/src/styles.scss | 4 + 14 files changed, 455 insertions(+), 115 deletions(-) diff --git a/wowup-electron/src/app/components/options-wow-section/options-wow-section.component.html b/wowup-electron/src/app/components/options-wow-section/options-wow-section.component.html index e5cae84c..7dd7c1c9 100644 --- a/wowup-electron/src/app/components/options-wow-section/options-wow-section.component.html +++ b/wowup-electron/src/app/components/options-wow-section/options-wow-section.component.html @@ -7,29 +7,20 @@
{{ "PAGES.OPTIONS.WOW.RESCAN_CLIENTS_LABEL" | translate }}
- - - +

+ No World of Warcraft installations found, please make sure your Battle.net client is up to date or add a client + manually +

+ - + \ No newline at end of file diff --git a/wowup-electron/src/app/components/options-wow-section/options-wow-section.component.ts b/wowup-electron/src/app/components/options-wow-section/options-wow-section.component.ts index 82f879f9..6de3e7ad 100644 --- a/wowup-electron/src/app/components/options-wow-section/options-wow-section.component.ts +++ b/wowup-electron/src/app/components/options-wow-section/options-wow-section.component.ts @@ -1,13 +1,18 @@ import { Component, OnInit } from "@angular/core"; -import { MatSelectChange } from "@angular/material/select"; import { WowInstallation } from "app/models/wowup/wow-installation"; +import { ElectronService } from "app/services"; import { WarcraftInstallationService } from "app/services/warcraft/warcraft-installation.service"; -import { Observable } from "rxjs"; +import { from, Observable, of } from "rxjs"; +import { catchError } from "rxjs/operators"; import { WowClientType } from "../../models/warcraft/wow-client-type"; import { WowUpReleaseChannelType } from "../../models/wowup/wowup-release-channel-type"; import { WarcraftService } from "../../services/warcraft/warcraft.service"; import { WowUpService } from "../../services/wowup/wowup.service"; import { getEnumList, getEnumName } from "../../utils/enum.utils"; +import * as _ from "lodash"; +import { TranslateService } from "@ngx-translate/core"; +import { MatDialog } from "@angular/material/dialog"; +import { AlertDialogComponent } from "../alert-dialog/alert-dialog.component"; @Component({ selector: "app-options-wow-section", @@ -31,9 +36,12 @@ export class OptionsWowSectionComponent implements OnInit { })); constructor( + private _dialog: MatDialog, + private _electronService: ElectronService, private _warcraftService: WarcraftService, private _wowupService: WowUpService, - private _warcraftInstallationService: WarcraftInstallationService + private _warcraftInstallationService: WarcraftInstallationService, + private _translateService: TranslateService ) { this.wowInstallations$ = _warcraftInstallationService.wowInstallations$; } @@ -42,11 +50,61 @@ export class OptionsWowSectionComponent implements OnInit { this.wowUpReleaseChannel = this._wowupService.wowUpReleaseChannel; } - public onReScan = () => { - this._warcraftService.scanProducts().catch((e) => console.error(e)); + public onReScan = (): void => { + this._warcraftInstallationService + .importWowInstallations(this._warcraftInstallationService.blizzardAgentPath) + .catch((e) => console.error(e)); }; - public onWowUpChannelChange(evt: MatSelectChange) { - this._wowupService.wowUpReleaseChannel = evt.value; + public onAddNew(): void { + from(this.addNewClient()) + .pipe( + catchError((error) => { + console.error(error); + return of(undefined); + }) + ) + .subscribe(); + } + + private async addNewClient() { + const selectedPath = await this._warcraftInstallationService.selectWowClientPath(); + if (!selectedPath) { + return; + } + + console.log("dialogResult", selectedPath); + + const isWowApplication = await this._warcraftService.isWowApplication(selectedPath); + + if (!isWowApplication) { + this.showInvalidWowApplication(selectedPath); + return; + } + + console.log("isWowApplication", isWowApplication); + + const wowInstallation = await this._warcraftInstallationService.createWowInstallationForPath(selectedPath); + console.log("wowInstallation", wowInstallation); + + this._warcraftInstallationService.addInstallation(wowInstallation); + } + + private showInvalidWowApplication(selectedPath: string) { + const dialogMessage = this._translateService.instant("DIALOGS.SELECT_INSTALLATION.INVALID_INSTALLATION_PATH", { + selectedPath, + }); + + this.showError(dialogMessage); + } + + private showError(message: string) { + const title = this._translateService.instant("DIALOGS.ALERT.ERROR_TITLE"); + this._dialog.open(AlertDialogComponent, { + data: { + title, + message, + }, + }); } } diff --git a/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html b/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html index f3dbf8ed..b253c94c 100644 --- a/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html +++ b/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html @@ -1,12 +1,17 @@ - {{ clientTypeName | translate }} + +
{{ installationModel.label }}
+ + + +
{{ "PAGES.OPTIONS.WOW.CLIENT_TYPE_PATH_LABEL" | translate: { clientTypeName: (clientTypeName | translate) } }} - + {{ "PAGES.OPTIONS.WOW.CLIENT_TYPE_INPUT_HINT" @@ -41,9 +46,21 @@
- - - + + +
+
+ + +
+
+ + +
\ No newline at end of file diff --git a/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.scss b/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.scss index bb14b290..7c0eecf3 100644 --- a/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.scss +++ b/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.scss @@ -32,7 +32,7 @@ p { flex-direction: row; align-items: center; // margin: 1em 0; - padding: 0.5em; + // padding: 0.5em; &.np { padding: 0; } diff --git a/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.ts b/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.ts index 231edf31..4927da33 100644 --- a/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.ts +++ b/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.ts @@ -8,6 +8,7 @@ import { WarcraftInstallationService } from "app/services/warcraft/warcraft-inst import * as _ from "lodash"; import * as path from "path"; import { Subscription } from "rxjs"; +import { map } from "rxjs/operators"; import { WowClientType } from "../../models/warcraft/wow-client-type"; import { AddonChannelType } from "../../models/wowup/addon-channel-type"; import { ElectronService } from "../../services"; @@ -26,6 +27,7 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy { @Input("installationId") installationId: string; private installation: WowInstallation; + private installationModel: WowInstallation; private subscriptions: Subscription[] = []; public readonly addonChannelInfos: { @@ -39,6 +41,17 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy { public selectedAddonChannelType: AddonChannelType; public clientAutoUpdate: boolean; + public editMode = false; + + public get installationLabel(): string { + return this.installation?.label ?? ""; + } + + public set installationLabel(input: string) { + if (this.installation) { + this.installation.label = input; + } + } constructor( private _dialog: MatDialog, @@ -64,6 +77,7 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy { ngOnInit(): void { this.installation = this._warcraftInstallationService.getWowInstallation(this.installationId); + this.installationModel = { ...this.installation }; this.selectedAddonChannelType = this.installation.defaultAddonChannelType; this.clientAutoUpdate = this.installation.defaultAutoUpdate; this.clientTypeName = `COMMON.CLIENT_TYPES.${getEnumName( @@ -88,6 +102,41 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy { this._warcraftInstallationService.updateWowInstallation(this.installation); } + onClickCancel(): void { + this.installationModel = { ...this.installation }; + this.editMode = false; + } + + onClickSave(): void { + this.installation = { ...this.installationModel }; + this._warcraftInstallationService.updateWowInstallation(this.installation); + this.editMode = false; + } + + onClickRemove(): void { + const dialogRef = this._dialog.open(ConfirmDialogComponent, { + data: { + title: this._translateService.instant("PAGES.OPTIONS.WOW.CLEAR_INSTALL_LOCATION_DIALOG.TITLE"), + message: this._translateService.instant("PAGES.OPTIONS.WOW.CLEAR_INSTALL_LOCATION_DIALOG.MESSAGE", { + location: this._translateService.instant(this.installation.location), + }), + }, + }); + + dialogRef + .afterClosed() + .pipe( + map((result) => { + if (!result) { + return; + } + + this._warcraftInstallationService.removeWowInstallation(this.installation); + }) + ) + .subscribe(); + } + public async clearInstallPath(): Promise { const dialogRef = this._dialog.open(ConfirmDialogComponent, { data: { @@ -114,15 +163,6 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy { } } - async onSelectClientPath(): Promise { - const selectedPath = await this.selectWowClientPath(this.installation.clientType); - if (selectedPath) { - console.debug("selectedPath", selectedPath); - this.clientLocation = selectedPath; - this._cdRef.detectChanges(); - } - } - private getAddonChannelInfos() { return getEnumList(AddonChannelType).map((type: AddonChannelType) => { const channelName = getEnumName(AddonChannelType, type).toUpperCase(); @@ -132,46 +172,4 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy { }; }); } - - private async selectWowClientPath(clientType: WowClientType): Promise { - const dialogResult = await this._electronService.showOpenDialog({ - properties: ["openDirectory"], - }); - - if (dialogResult.canceled) { - return ""; - } - - const selectedPath = _.first(dialogResult.filePaths); - if (!selectedPath) { - console.warn("No path selected"); - return ""; - } - - console.log("dialogResult", selectedPath); - - const clientRelativePath = this._warcraftService.getClientRelativePath(clientType, selectedPath); - - const didSetWowPath = await this._warcraftService.setWowFolderPath(clientType, clientRelativePath); - if (didSetWowPath) { - return clientRelativePath; - } - - const clientFolderName = this._warcraftService.getClientFolderName(clientType); - const clientExecutableName = this._warcraftService.getExecutableName(clientType); - const clientExecutablePath = path.join(clientRelativePath, clientFolderName, clientExecutableName); - const dialogRef = this._dialog.open(AlertDialogComponent, { - data: { - title: `Alert`, - message: `Unable to set "${clientRelativePath}" as your ${getEnumName( - WowClientType, - clientType - )} folder.\nPath not found: "${clientExecutablePath}".`, - }, - }); - - await dialogRef.afterClosed().toPromise(); - - return ""; - } } diff --git a/wowup-electron/src/app/services/icons/icon.service.ts b/wowup-electron/src/app/services/icons/icon.service.ts index 0ef40d32..81ddc4b1 100644 --- a/wowup-electron/src/app/services/icons/icon.service.ts +++ b/wowup-electron/src/app/services/icons/icon.service.ts @@ -18,6 +18,7 @@ import { faCode, faCoins, faCompressArrowsAlt, + faPencilAlt, } from "@fortawesome/free-solid-svg-icons"; import { faQuestionCircle, faClock } from "@fortawesome/free-regular-svg-icons"; import { faDiscord, faGithub, faPatreon } from "@fortawesome/free-brands-svg-icons"; @@ -47,10 +48,13 @@ export class IconService { this.addSvg(faPatreon); this.addSvg(faCoins); this.addSvg(faCompressArrowsAlt); + this.addSvg(faPencilAlt); } - async addSvg(icon: IconDefinition) { - const svg = ``; + addSvg(icon: IconDefinition): void { + const svg = ``; this._matIconRegistry.addSvgIconLiteralInNamespace( icon.prefix, diff --git a/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts b/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts index c940f6c1..2db1e332 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts @@ -1,17 +1,25 @@ import * as _ from "lodash"; import { BehaviorSubject } from "rxjs"; -import { filter, map } from "rxjs/operators"; +import { filter, map, switchMap, tap } from "rxjs/operators"; import { v4 as uuidv4 } from "uuid"; import * as path from "path"; import { Injectable } from "@angular/core"; -import { WOW_INSTALLATIONS_KEY } from "../../../common/constants"; +import { + DEFAULT_AUTO_UPDATE_PREFERENCE_KEY_SUFFIX, + DEFAULT_CHANNEL_PREFERENCE_KEY_SUFFIX, + WOW_INSTALLATIONS_KEY, +} from "../../../common/constants"; import { WowClientType } from "../../models/warcraft/wow-client-type"; import { WowInstallation } from "../../models/wowup/wow-installation"; import { PreferenceStorageService } from "../storage/preference-storage.service"; import { WarcraftService } from "./warcraft.service"; import { AddonChannelType } from "app/models/wowup/addon-channel-type"; +import { getEnumName } from "app/utils/enum.utils"; +import { TranslateService } from "@ngx-translate/core"; +import { FileService } from "../files/file.service"; +import { ElectronService } from "../electron/electron.service"; const CLIENT_RETAIL_FOLDER = "_retail_"; const CLIENT_RETAIL_PTR_FOLDER = "_ptr_"; @@ -27,12 +35,28 @@ const INTERFACE_FOLDER_NAME = "Interface"; export class WarcraftInstallationService { private readonly _wowInstallationsSrc = new BehaviorSubject([]); + private _blizzardAgentPath = ""; + public readonly wowInstallations$ = this._wowInstallationsSrc.asObservable(); - constructor(private _preferenceStorageService: PreferenceStorageService, private _warcraftService: WarcraftService) { + public get blizzardAgentPath(): string { + return `${this._blizzardAgentPath}`; + } + + constructor( + private _preferenceStorageService: PreferenceStorageService, + private _warcraftService: WarcraftService, + private _translateService: TranslateService, + private _fileService: FileService, + private _electronService: ElectronService + ) { this._warcraftService.blizzardAgent$ .pipe( filter((blizzardAgentPath) => !!blizzardAgentPath), + tap((blizzardAgentPath) => { + this._blizzardAgentPath = blizzardAgentPath; + }), + switchMap((blizzardAgentPath) => this.migrateAllLegacyInstallations(blizzardAgentPath)), map((blizzardAgentPath) => this.importWowInstallations(blizzardAgentPath)) ) .subscribe(); @@ -80,38 +104,192 @@ export class WarcraftInstallationService { this.setWowInstallations(storedInstallations); } + public async selectWowClientPath(): Promise { + const selectionName = this._translateService.instant("COMMON.WOW_EXE_SELECTION_NAME"); + const extensionFilter = this._warcraftService.getExecutableExtension(); + let dialogResult: Electron.OpenDialogReturnValue; + if (extensionFilter) { + dialogResult = await this._electronService.showOpenDialog({ + filters: [{ extensions: [extensionFilter], name: selectionName }], + properties: ["openFile"], + }); + } else { + // platforms like linux don't really fit the rule + dialogResult = await this._electronService.showOpenDialog({ + properties: ["openFile"], + }); + } + + if (dialogResult.canceled) { + return ""; + } + + const selectedPath = _.first(dialogResult.filePaths); + if (!selectedPath || !selectedPath.endsWith("")) { + console.warn("No path selected"); + return ""; + } + + return selectedPath; + } + + public addInstallation(installation: WowInstallation): void { + const existingInstallations = this.getWowInstallations(); + const exists = _.findIndex(existingInstallations, (inst) => inst.location === installation.location) !== -1; + if (exists) { + throw new Error(`Installation already exists: ${installation.location}`); + } + + existingInstallations.push(installation); + + console.debug("ADDED INSTALL"); + this.setWowInstallations(existingInstallations); + this._wowInstallationsSrc.next(existingInstallations); + } + + public removeWowInstallation(installation: WowInstallation): void { + const installations = this.getWowInstallations(); + const installationExists = _.findIndex(installations, (inst) => inst.id === installation.id) !== -1; + if (!installationExists) { + throw new Error(`Installation does not exist: ${installation.id}`); + } + + _.remove(installations, (inst) => inst.id === installation.id); + + this.setWowInstallations(installations); + this._wowInstallationsSrc.next(installations); + } + + public async createWowInstallationForPath(applicationPath: string): Promise { + const clientDir = path.dirname(applicationPath); + const clientType = this._warcraftService.getClientTypeForFolderName(clientDir); + const typeName = getEnumName(WowClientType, clientType); + const currentInstallations = this.getWowInstallationsByClientType(clientType); + + let label = await this._translateService.get(`COMMON.CLIENT_TYPES.${typeName.toUpperCase()}`).toPromise(); + if (currentInstallations.length > 0) { + label += `${currentInstallations.length + 1}`; + } + + const installation: WowInstallation = { + id: uuidv4(), + clientType: clientType, + defaultAddonChannelType: AddonChannelType.Stable, + defaultAutoUpdate: false, + label: label, + location: applicationPath, + selected: false, + }; + + return installation; + } + public async importWowInstallations(blizzardAgentPath: string): Promise { - const wowInstallationPreferences = this.getWowInstallations(); const installedProducts = await this._warcraftService.getInstalledProducts(blizzardAgentPath); - const installations = _.map(Array.from(installedProducts.values()), (product) => { + for (const product of Array.from(installedProducts.values())) { + const typeName = getEnumName(WowClientType, product.clientType); + const currentInstallations = this.getWowInstallationsByClientType(product.clientType); + + let label = await this._translateService.get(`COMMON.CLIENT_TYPES.${typeName.toUpperCase()}`).toPromise(); + if (currentInstallations.length > 0) { + label += `${currentInstallations.length + 1}`; + } + + const fullProductPath = this.getFullProductPath(product.location, product.clientType); const wowInstallation: WowInstallation = { id: uuidv4(), clientType: product.clientType, - label: product.name, - location: product.location, + label, + location: fullProductPath, selected: false, defaultAddonChannelType: AddonChannelType.Stable, defaultAutoUpdate: false, }; - return wowInstallation; - }); - console.debug("db installations", installations); - _.remove( - installations, - (installation) => - _.findIndex(wowInstallationPreferences, (pref) => pref.location === installation.location) !== -1 - ); - - const allInstallations = [...wowInstallationPreferences, ...installations]; - - if (allInstallations.length !== wowInstallationPreferences.length) { - this.setWowInstallations(allInstallations); + try { + this.addInstallation(wowInstallation); + } catch (e) { + // Ignore duplicate error + } } - console.debug("WOWINSTALLS", allInstallations); - this._wowInstallationsSrc.next(allInstallations); + this._wowInstallationsSrc.next(this.getWowInstallations()); + } + + private async migrateAllLegacyInstallations(blizzardAgentPath: string): Promise { + for (const clientType of this._warcraftService.getAllClientTypes()) { + try { + await this.migrateLegacyInstallations(clientType); + } catch (e) { + console.error(e); + } + } + + return blizzardAgentPath; + } + + private async migrateLegacyInstallations(clientType: WowClientType) { + if (clientType === WowClientType.None) { + return; + } + + const typeName = getEnumName(WowClientType, clientType); + + const existingInstallations = this.getWowInstallationsByClientType(clientType); + if (existingInstallations.length > 0) { + console.debug(`Existing install exists for: ${typeName}`); + return; + } + + const legacyLocationKey = this._warcraftService.getClientLocationKey(clientType); + const legacyLocation = this._preferenceStorageService.findByKey(legacyLocationKey); + if (!legacyLocation) { + console.debug(`Legacy ${typeName}: nothing to migrate`); + return; + } + + console.debug(`Migrating legacy ${typeName} installation`); + + const legacyDefaultChannel = this.getLegacyDefaultAddonChannel(typeName); + const legacyDefaultAutoUpdate = this.getLegacyDefaultAutoUpdate(typeName); + + const label = await this._translateService.get(`COMMON.CLIENT_TYPES.${typeName.toUpperCase()}`).toPromise(); + + const newLocation = this.getFullProductPath(legacyLocation, clientType); + + const newLocationExists = await this._fileService.pathExists(newLocation); + if (!newLocationExists) { + throw new Error(`Could not migrate legacy installation, path does not exist: ${newLocation}`); + } + + const installation: WowInstallation = { + id: uuidv4(), + clientType, + defaultAddonChannelType: legacyDefaultChannel, + defaultAutoUpdate: legacyDefaultAutoUpdate, + label, + location: newLocation, + selected: false, + }; + + this.addInstallation(installation); + } + + private getFullProductPath(location: string, clientType: WowClientType): string { + const clientFolderName = this._warcraftService.getClientFolderName(clientType); + const executableName = this._warcraftService.getExecutableName(clientType); + return path.join(location, clientFolderName, executableName); + } + + private getLegacyDefaultAddonChannel(typeName: string): AddonChannelType { + const legacyDefaultChannelKey = `${typeName}${DEFAULT_CHANNEL_PREFERENCE_KEY_SUFFIX}`.toLowerCase(); + return parseInt(this._preferenceStorageService.findByKey(legacyDefaultChannelKey), 10) as AddonChannelType; + } + + private getLegacyDefaultAutoUpdate(typeName: string): boolean { + const legacyDefaultAutoUpdateKey = `${typeName}${DEFAULT_AUTO_UPDATE_PREFERENCE_KEY_SUFFIX}`.toLowerCase(); + return this._preferenceStorageService.findByKey(legacyDefaultAutoUpdateKey) === true.toString(); } // public getClientFolderName(clientType: WowClientType): string { diff --git a/wowup-electron/src/app/services/warcraft/warcraft.service.impl.ts b/wowup-electron/src/app/services/warcraft/warcraft.service.impl.ts index ad31155e..f4a506c3 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft.service.impl.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft.service.impl.ts @@ -1,6 +1,8 @@ import { WowClientType } from "../../models/warcraft/wow-client-type"; export interface WarcraftServiceImpl { + getExecutableExtension(): string; + isWowApplication(appName: string): boolean; getBlizzardAgentPath(): Promise; getExecutableName(clientType: WowClientType): string; } diff --git a/wowup-electron/src/app/services/warcraft/warcraft.service.linux.ts b/wowup-electron/src/app/services/warcraft/warcraft.service.linux.ts index 49b533eb..0df6ae1e 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft.service.linux.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft.service.linux.ts @@ -1,23 +1,45 @@ import { WowClientType } from "../../models/warcraft/wow-client-type"; import { WarcraftServiceImpl } from "./warcraft.service.impl"; +const WOW_RETAIL_NAME = "Wow.exe"; +const WOW_RETAIL_PTR_NAME = "WowT.exe"; +const WOW_CLASSIC_NAME = "WowClassic.exe"; +const WOW_CLASSIC_PTR_NAME = "WowClassicT.exe"; +const WOW_RETAIL_BETA_NAME = "WowB.exe"; + +const WOW_APP_NAMES = [ + WOW_RETAIL_NAME, + WOW_RETAIL_PTR_NAME, + WOW_CLASSIC_NAME, + WOW_CLASSIC_PTR_NAME, + WOW_RETAIL_BETA_NAME, +]; + export class WarcraftServiceLinux implements WarcraftServiceImpl { + public getExecutableExtension(): string { + return ""; + } + public getBlizzardAgentPath(): Promise { return Promise.resolve(""); } + public isWowApplication(appName: string): boolean { + return WOW_APP_NAMES.includes(appName); + } + public getExecutableName(clientType: WowClientType): string { switch (clientType) { case WowClientType.Retail: - return "Wow.exe"; + return WOW_RETAIL_NAME; case WowClientType.Classic: - return "WowClassic.exe"; + return WOW_CLASSIC_NAME; case WowClientType.RetailPtr: - return "WowT.exe"; + return WOW_RETAIL_PTR_NAME; case WowClientType.ClassicPtr: - return "WowClassicT.exe"; + return WOW_CLASSIC_PTR_NAME; case WowClientType.Beta: - return "WowB.exe"; + return WOW_RETAIL_BETA_NAME; default: return ""; } diff --git a/wowup-electron/src/app/services/warcraft/warcraft.service.mac.ts b/wowup-electron/src/app/services/warcraft/warcraft.service.mac.ts index de191d91..9091e34d 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft.service.mac.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft.service.mac.ts @@ -3,12 +3,34 @@ import { WowClientType } from "../../models/warcraft/wow-client-type"; import { FileService } from "../files/file.service"; import { WarcraftServiceImpl } from "./warcraft.service.impl"; +const WOW_RETAIL_NAME = "World of Warcraft.app"; +const WOW_RETAIL_PTR_NAME = "World of Warcraft Test.app"; +const WOW_CLASSIC_NAME = "World of Warcraft Classic.app"; +const WOW_CLASSIC_PTR_NAME = "World of Warcraft Classic Test.app"; +const WOW_RETAIL_BETA_NAME = "World of Warcraft Beta.app"; + +const WOW_APP_NAMES = [ + WOW_RETAIL_NAME, + WOW_RETAIL_PTR_NAME, + WOW_CLASSIC_NAME, + WOW_CLASSIC_PTR_NAME, + WOW_RETAIL_BETA_NAME, +]; + const BLIZZARD_AGENT_PATH = "/Users/Shared/Battle.net/Agent"; const BLIZZARD_PRODUCT_DB_NAME = "product.db"; export class WarcraftServiceMac implements WarcraftServiceImpl { constructor(private _fileService: FileService) {} + public getExecutableExtension(): string { + return "app"; + } + + public isWowApplication(appName: string): boolean { + return WOW_APP_NAMES.includes(appName); + } + public async getBlizzardAgentPath(): Promise { const agentPath = join(BLIZZARD_AGENT_PATH, BLIZZARD_PRODUCT_DB_NAME); const exists = await this._fileService.pathExists(agentPath); diff --git a/wowup-electron/src/app/services/warcraft/warcraft.service.ts b/wowup-electron/src/app/services/warcraft/warcraft.service.ts index 8fc393f7..b9a24c4d 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft.service.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft.service.ts @@ -95,6 +95,20 @@ export class WarcraftService { return this._impl.getExecutableName(clientType); } + public getExecutableExtension(): string { + return this._impl.getExecutableExtension(); + } + + public async isWowApplication(appPath: string): Promise { + const pathExists = await this._fileService.pathExists(appPath); + if (!pathExists) { + return false; + } + + const fileName = path.basename(appPath); + return this._impl.isWowApplication(fileName); + } + public getFullExecutablePath(clientType: WowClientType): string { const clientLocation = this.getClientLocation(clientType); const clientFolder = this.getClientFolderName(clientType); diff --git a/wowup-electron/src/app/services/warcraft/warcraft.service.win.ts b/wowup-electron/src/app/services/warcraft/warcraft.service.win.ts index 2c92f4bb..781c951a 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft.service.win.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft.service.win.ts @@ -5,6 +5,20 @@ import { FileService } from "../files/file.service"; import { WarcraftServiceImpl } from "./warcraft.service.impl"; import { IPC_LIST_DISKS_WIN32 } from "../../../common/constants"; +const WOW_RETAIL_NAME = "Wow.exe"; +const WOW_RETAIL_PTR_NAME = "WowT.exe"; +const WOW_CLASSIC_NAME = "WowClassic.exe"; +const WOW_CLASSIC_PTR_NAME = "WowClassicT.exe"; +const WOW_RETAIL_BETA_NAME = "WowB.exe"; + +const WOW_APP_NAMES = [ + WOW_RETAIL_NAME, + WOW_RETAIL_PTR_NAME, + WOW_CLASSIC_NAME, + WOW_CLASSIC_PTR_NAME, + WOW_RETAIL_BETA_NAME, +]; + // BLIZZARD STRINGS const WINDOWS_BLIZZARD_AGENT_PATH = "ProgramData/Battle.net/Agent"; const BLIZZARD_PRODUCT_DB_NAME = "product.db"; @@ -12,6 +26,14 @@ const BLIZZARD_PRODUCT_DB_NAME = "product.db"; export class WarcraftServiceWin implements WarcraftServiceImpl { constructor(private _electronService: ElectronService, private _fileService: FileService) {} + public getExecutableExtension(): string { + return "exe"; + } + + public isWowApplication(appName: string): boolean { + return WOW_APP_NAMES.includes(appName); + } + async getBlizzardAgentPath(): Promise { try { const diskInfo = await this._electronService.invoke(IPC_LIST_DISKS_WIN32); diff --git a/wowup-electron/src/assets/i18n/en.json b/wowup-electron/src/assets/i18n/en.json index cc01c500..8da4e9af 100644 --- a/wowup-electron/src/assets/i18n/en.json +++ b/wowup-electron/src/assets/i18n/en.json @@ -151,9 +151,13 @@ "PROVIDER_ERROR": "Error contacting {providerName}", "SEARCH": { "NO_ADDONS": "No addons found" - } + }, + "WOW_EXE_SELECTION_NAME": "WoW Executable" }, "DIALOGS": { + "SELECT_INSTALLATION": { + "INVALID_INSTALLATION_PATH": "This does not appear to be a valid World of Warcraft application:\n{selectedPath}" + }, "ADDON_DETAILS": { "ADDON_ID_PREFIX": "Addon ID:", "BY_AUTHOR": "By {authorName}", @@ -377,14 +381,18 @@ "AUTO_UPDATE_DESCRIPTION": "Newly installed addons will be set to auto update by default", "AUTO_UPDATE_LABEL": "Auto Update", "CLEAR_INSTALL_LOCATION_DIALOG": { - "MESSAGE": "Are you sure you want to clear the install path for {clientName}? This will remove all stored addon information for this client.\n\nYour addon folders will not be removed.", - "TITLE": "Clear Install Location?" + "MESSAGE": "Are you sure you want to remove the installation at \"{location}\"? This will remove all stored addon information for this client.\n\nYour addon folders will not be removed.", + "TITLE": "Remove World of Warcraft Installation?" }, "CLIENT_TYPE_INPUT_HINT": "The folder that contains the {clientTypeName} client folder \"{clientFolderName}\"", "CLIENT_TYPE_PATH_LABEL": "{clientTypeName} path", "DEFAULT_ADDON_CHANNEL_LABEL": "Default Addon Channel", "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Addon Channel", + "REMOVE_WOW_DIRECTORY_SELECT_BUTTON": "Remove", "OPEN_WOW_DIRECTORY_SELECT_BUTTON": "Select", + "EDIT_WOW_DIRECTORY_SELECT_BUTTON": "Edit", + "CANCEL_WOW_DIRECTORY_SELECT_BUTTON": "Cancel", + "SAVE_WOW_DIRECTORY_SELECT_BUTTON": "Save", "RESCAN_CLIENTS_BUTTON": "Re-Scan", "ADD_CLIENT_BUTTON": "Add New", "RESCAN_CLIENTS_LABEL": "Rescan installed World of Warcraft products", diff --git a/wowup-electron/src/styles.scss b/wowup-electron/src/styles.scss index 2c9415db..88872d69 100644 --- a/wowup-electron/src/styles.scss +++ b/wowup-electron/src/styles.scss @@ -79,6 +79,10 @@ img { padding: 0 0.25em; } +.pt-3 { + padding-top: 1em; +} + .m-0 { margin: 0 !important; }