From a7ded97c11f3ad024d83f64ff26dbfee163d8071 Mon Sep 17 00:00:00 2001 From: jliddev Date: Wed, 7 Oct 2020 12:20:07 -0500 Subject: [PATCH] Speed up scanning some Add some status output --- wowup-electron/ipc-events.ts | 1 - wowup-electron/package.json | 2 + .../addon-providers/curse-addon-provider.ts | 41 +- .../curse/curse-folder-scanner.ts | 381 +++++++++--------- wowup-electron/src/app/app.component.ts | 49 ++- wowup-electron/src/app/app.module.ts | 69 ++-- .../components/footer/footer.component.html | 2 +- .../components/footer/footer.component.scss | 1 - .../src/app/pages/home/home.component.ts | 23 +- .../src/app/pages/home/home.module.ts | 2 +- .../services/addons/addon.provider.factory.ts | 68 ++++ .../src/app/services/addons/addon.service.ts | 380 +++++++++++------ .../app/services/session/session.service.ts | 32 +- wowup-electron/src/app/utils/file.utils.ts | 28 +- 14 files changed, 664 insertions(+), 415 deletions(-) create mode 100644 wowup-electron/src/app/services/addons/addon.provider.factory.ts diff --git a/wowup-electron/ipc-events.ts b/wowup-electron/ipc-events.ts index 16be6677..11fccfe5 100644 --- a/wowup-electron/ipc-events.ts +++ b/wowup-electron/ipc-events.ts @@ -52,7 +52,6 @@ ipcMain.on(CURSE_HASH_FILE_CHANNEL, async (evt, arg: CurseHashFileRequest) => { }); ipcMain.on(LIST_FILES_CHANNEL, async (evt, arg: ListFilesRequest) => { - console.log('list files', arg); const response: ListFilesResponse = { files: [] }; diff --git a/wowup-electron/package.json b/wowup-electron/package.json index 59de589d..c69a0bfb 100644 --- a/wowup-electron/package.json +++ b/wowup-electron/package.json @@ -60,6 +60,7 @@ "@ngx-translate/core": "13.0.0", "@ngx-translate/http-loader": "6.0.0", "@types/adm-zip": "0.4.33", + "@types/async": "3.2.3", "@types/globrex": "0.1.0", "@types/jasmine": "3.5.14", "@types/jasminewd2": "2.0.8", @@ -109,6 +110,7 @@ "@angular/material": "10.2.3", "@types/lodash": "4.14.161", "adm-zip": "0.4.16", + "async": "3.2.0", "compare-versions": "3.6.0", "conf": "7.1.2", "electron-dl": "3.0.2", 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 5866bb45..86fef9d1 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -7,6 +7,7 @@ import { map, mergeMap } from "rxjs/operators"; import { CurseFile } from "../models/curse/curse-file"; import * as _ from "lodash"; import * as fp from "lodash/fp"; +import * as path from "path"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { from, Observable, of } from "rxjs"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; @@ -23,6 +24,10 @@ import { CurseScanResult } from "../models/curse/curse-scan-result"; import { CurseFingerprintsResponse } from "app/models/curse/curse-fingerprint-response"; import { CurseMatch } from "app/models/curse/curse-match"; import { v4 as uuidv4 } from "uuid"; +import * as async from "async"; +import { SessionService } from "app/services/session/session.service"; +import { Inject } from "@angular/core"; +import { Session } from "inspector"; const API_URL = "https://addons-ecs.forgesvc.net/api/v2"; @@ -33,8 +38,9 @@ export class CurseAddonProvider implements AddonProvider { private _httpClient: HttpClient, private _cachingService: CachingService, private _electronService: ElectronService, + private _sessionService: SessionService, private _fileService: FileService - ) { } + ) {} async scan( clientType: WowClientType, @@ -157,30 +163,33 @@ export class CurseAddonProvider implements AddonProvider { return this._httpClient.post(url, addonIds); } - private async getScanResults( + private getScanResults = async ( addonFolders: AddonFolder[] - ): Promise { - const scanResults: CurseScanResult[] = []; + ): Promise => { + // const scanResults: CurseScanResult[] = []; const t1 = Date.now(); // Scan addon folders in parallel for speed!? - for (let folder of addonFolders) { - const scanResult = await new CurseFolderScanner( - this._electronService, - this._fileService - ).scanFolder(folder); - scanResults.push(scanResult); - } + const scanResults = await async.mapLimit( + addonFolders, + 2, + async (folder, callback) => { + this._sessionService.statusText = `Scanning ${folder.name}`; + const scanResult = await new CurseFolderScanner( + this._electronService, + this._fileService + ).scanFolder(folder); + + callback(undefined, scanResult); + } + ); console.log("scan delta", Date.now() - t1); - - // const str = _.orderBy(scanResults, sr => sr.folderName.toLowerCase()) - // .map(sr => `${sr.fingerprint} ${sr.folderName}`).join('\n'); - // console.log(str); + this._sessionService.statusText = ""; return scanResults; - } + }; async getAll( clientType: WowClientType, diff --git a/wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts b/wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts index e1140f6f..8f151569 100644 --- a/wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts +++ b/wowup-electron/src/app/addon-providers/curse/curse-folder-scanner.ts @@ -1,215 +1,234 @@ -import { FileService } from "app/services/files/file.service"; -import * as path from 'path'; -import * as fs from 'fs'; -import * as _ from 'lodash'; +import * as path from "path"; +import * as fs from "fs"; +import * as _ from "lodash"; import { AddonFolder } from "app/models/wowup/addon-folder"; import { ElectronService } from "app/services"; import { CurseHashFileResponse } from "common/models/curse-hash-file-response"; import { CurseHashFileRequest } from "common/models/curse-hash-file-request"; import { CURSE_HASH_FILE_CHANNEL } from "common/constants"; -import { v4 as uuidv4 } from 'uuid'; +import { v4 as uuidv4 } from "uuid"; import { CurseScanResult } from "../../models/curse/curse-scan-result"; -import { from } from "rxjs"; -import { mergeMap } from "rxjs/operators"; +import * as async from "async"; +import { FileService } from "app/services/files/file.service"; export class CurseFolderScanner { + constructor( + private _electronService: ElectronService, + private _fileService: FileService + ) {} - constructor( - private _electronService: ElectronService, - private _fileService: FileService - ) { } + private get tocFileCommentsRegex() { + return /\s*#.*$/gm; + } - private get tocFileCommentsRegex() { - return /\s*#.*$/mg; + private get tocFileIncludesRegex() { + return /^\s*((?:(?/gi; + } + + private get bindingsXmlCommentsRegex() { + return //gs; + } + + async scanFolder(addonFolder: AddonFolder): Promise { + const folderPath = addonFolder.path; + + const files = await this._fileService.listAllFiles(folderPath); + console.log("listAllFiles", folderPath, files.length); + + let matchingFiles = await this.getMatchingFiles(folderPath, files); + matchingFiles = _.sortBy(matchingFiles, (f) => f.toLowerCase()); + + // console.log('matching files', matchingFiles.length) + // const fst = matchingFiles.map(f => f.toLowerCase()).join('\n'); + + const individualFingerprints = await async.mapLimit( + matchingFiles, + 2, + async (path, callback) => { + const normalizedFileHash = await this.computeNormalizedFileHash(path); + callback(undefined, normalizedFileHash); + } + ); + + // const individualFingerprints: number[] = []; + // for (let path of matchingFiles) { + // const normalizedFileHash = await this.computeNormalizedFileHash(path); + // individualFingerprints.push(normalizedFileHash); + // } + + const hashConcat = _.orderBy(individualFingerprints).join(""); + const fingerprint = await this.computeStringHash(hashConcat); + console.log("fingerprint", fingerprint); + + return { + directory: folderPath, + fileCount: matchingFiles.length, + fingerprint, + folderName: path.basename(folderPath), + individualFingerprints, + addonFolder, + }; + } + + private async getMatchingFiles( + folderPath: string, + filePaths: string[] + ): Promise { + const parentDir = path.dirname(folderPath) + path.sep; + const matchingFileList: string[] = []; + const fileInfoList: string[] = []; + for (let filePath of filePaths) { + const input = filePath.toLowerCase().replace(parentDir.toLowerCase(), ""); + + if (this.tocFileRegex.test(input)) { + fileInfoList.push(filePath); + } else if (this.bindingsXmlRegex.test(input)) { + matchingFileList.push(filePath); + } } - private get tocFileIncludesRegex() { - return /^\s*((?:(?/ig; + const dirname = path.dirname(fileInfo); + for (let include of inclusions) { + const fileName = path.join(dirname, include.replace(/\\/g, path.sep)); + await this.processIncludeFile(matchingFileList, fileName); } + } - private get bindingsXmlCommentsRegex() { - return //gs; + private getFileInclusionMatches( + fileInfo: string, + fileContent: string + ): string[] | null { + const ext = path.extname(fileInfo); + switch (ext) { + case ".xml": + return this.matchAll(fileContent, this.bindingsXmlIncludesRegex); + case ".toc": + return this.matchAll(fileContent, this.tocFileIncludesRegex); + default: + return null; } + } - async scanFolder(addonFolder: AddonFolder): Promise { - const folderPath = addonFolder.path; - const files = await this._fileService.listAllFiles(folderPath); - console.log('listAllFiles', folderPath, files.length); + private removeComments(fileInfo: string, fileContent: string): string { + const ext = path.extname(fileInfo); + switch (ext) { + case ".xml": + return fileContent.replace(this.bindingsXmlCommentsRegex, ""); + case ".toc": + return fileContent.replace(this.tocFileCommentsRegex, ""); + default: + return fileContent; + } + } - let matchingFiles = await this.getMatchingFiles(folderPath, files); - matchingFiles = _.sortBy(matchingFiles, f => f.toLowerCase()); + private matchAll(str: string, regex: RegExp): string[] { + const matches: string[] = []; + let currentMatch: RegExpExecArray; + do { + currentMatch = regex.exec(str); + if (currentMatch) { + matches.push(currentMatch[1]); + } + } while (currentMatch); - // console.log('matching files', matchingFiles.length) - // const fst = matchingFiles.map(f => f.toLowerCase()).join('\n'); + return matches; + } - const individualFingerprints: number[] = []; - for (let path of matchingFiles) { - const normalizedFileHash = await this.computeNormalizedFileHash(path); - individualFingerprints.push(normalizedFileHash); + private computeNormalizedFileHash = (filePath: string) => { + return this.computeFileHash(filePath, true); + }; + + private computeFileHash = ( + filePath: string, + normalizeWhitespace: boolean + ) => { + return this.computeHash(filePath, 0, normalizeWhitespace); + }; + + private computeStringHash = (str: string): Promise => { + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { + if (arg.error) { + return reject(arg.error); } - const hashConcat = _.orderBy(individualFingerprints).join(''); - const fingerprint = await this.computeStringHash(hashConcat); - console.log('fingerprint', fingerprint); + resolve(arg.fingerprint); + }; - return { - directory: folderPath, - fileCount: matchingFiles.length, - fingerprint, - folderName: path.basename(folderPath), - individualFingerprints, - addonFolder - }; - } + const request: CurseHashFileRequest = { + targetString: str, + targetStringEncoding: "ascii", + responseKey: uuidv4(), + normalizeWhitespace: false, + precomputedLength: 0, + }; - private async getMatchingFiles(folderPath: string, filePaths: string[]): Promise { - const parentDir = path.dirname(folderPath) + path.sep; - const matchingFileList: string[] = []; - const fileInfoList: string[] = []; - for (let filePath of filePaths) { - const input = filePath.toLowerCase().replace(parentDir.toLowerCase(), ''); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); + }); + }; - if (this.tocFileRegex.test(input)) { - fileInfoList.push(filePath); - } else if (this.bindingsXmlRegex.test(input)) { - matchingFileList.push(filePath); - } + private computeHash = ( + filePath: string, + precomputedLength: number = 0, + normalizeWhitespace: boolean = false + ): Promise => { + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { + if (arg.error) { + return reject(arg.error); } - // console.log('fileInfoList', fileInfoList.length) - for (let fileInfo of fileInfoList) { - await this.processIncludeFile(matchingFileList, fileInfo); - } + resolve(arg.fingerprint); + }; - return matchingFileList; - } + const request: CurseHashFileRequest = { + responseKey: uuidv4(), + filePath, + normalizeWhitespace, + precomputedLength, + }; - private async processIncludeFile(matchingFileList: string[], fileInfo: string) { - if (!fs.existsSync(fileInfo) || matchingFileList.indexOf(fileInfo) !== -1) { - return; - } - - matchingFileList.push(fileInfo); - - let input = await this._fileService.readFile(fileInfo); - input = this.removeComments(fileInfo, input); - - const inclusions = this.getFileInclusionMatches(fileInfo, input); - if (!inclusions || !inclusions.length) { - return; - } - - const dirname = path.dirname(fileInfo); - for (let include of inclusions) { - const fileName = path.join(dirname, include.replace(/\\/g, path.sep)); - await this.processIncludeFile(matchingFileList, fileName); - } - } - - private getFileInclusionMatches(fileInfo: string, fileContent: string): string[] | null { - const ext = path.extname(fileInfo); - switch (ext) { - case '.xml': - return this.matchAll(fileContent, this.bindingsXmlIncludesRegex); - case '.toc': - return this.matchAll(fileContent, this.tocFileIncludesRegex); - default: - return null; - } - } - - private removeComments(fileInfo: string, fileContent: string): string { - const ext = path.extname(fileInfo); - switch (ext) { - case '.xml': - return fileContent.replace(this.bindingsXmlCommentsRegex, ''); - case '.toc': - return fileContent.replace(this.tocFileCommentsRegex, ''); - default: - return fileContent; - } - } - - private matchAll(str: string, regex: RegExp): string[] { - const matches: string[] = []; - let currentMatch: RegExpExecArray; - do { - currentMatch = regex.exec(str); - if (currentMatch) { - matches.push(currentMatch[1]); - } - } while (currentMatch); - - return matches; - } - - private computeNormalizedFileHash(filePath: string) { - return this.computeFileHash(filePath, true); - } - - private computeFileHash(filePath: string, normalizeWhitespace: boolean) { - return this.computeHash(filePath, 0, normalizeWhitespace); - } - - private computeStringHash(str: string): Promise { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { - if (arg.error) { - return reject(arg.error); - } - - resolve(arg.fingerprint); - }; - - const request: CurseHashFileRequest = { - targetString: str, - targetStringEncoding: 'ascii', - responseKey: uuidv4(), - normalizeWhitespace: false, - precomputedLength: 0 - }; - - this._electronService.ipcRenderer.once(request.responseKey, eventHandler); - this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); - }); - } - - private computeHash( - filePath: string, - precomputedLength: number = 0, - normalizeWhitespace: boolean = false - ): Promise { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: CurseHashFileResponse) => { - if (arg.error) { - return reject(arg.error); - } - - resolve(arg.fingerprint); - }; - - const request: CurseHashFileRequest = { - responseKey: uuidv4(), - filePath, - normalizeWhitespace, - precomputedLength - }; - - this._electronService.ipcRenderer.once(request.responseKey, eventHandler); - this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); - }); - } - -} \ No newline at end of file + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(CURSE_HASH_FILE_CHANNEL, request); + }); + }; +} diff --git a/wowup-electron/src/app/app.component.ts b/wowup-electron/src/app/app.component.ts index 24a117cb..5a4860a8 100644 --- a/wowup-electron/src/app/app.component.ts +++ b/wowup-electron/src/app/app.component.ts @@ -1,28 +1,34 @@ -import { AfterViewInit, Component } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; -import { TranslateService } from '@ngx-translate/core'; -import { AppConfig } from '../environments/environment'; -import { TelemetryDialogComponent } from './components/telemetry-dialog/telemetry-dialog.component'; -import { ElectronService } from './services'; -import { AnalyticsService } from './services/analytics/analytics.service'; -import { WarcraftService } from './services/warcraft/warcraft.service'; -import { WowUpService } from './services/wowup/wowup.service'; +import { AfterViewInit, Component } from "@angular/core"; +import { MatDialog } from "@angular/material/dialog"; +import { TranslateService } from "@ngx-translate/core"; +import { AppConfig } from "../environments/environment"; +import { TelemetryDialogComponent } from "./components/telemetry-dialog/telemetry-dialog.component"; +import { ElectronService } from "./services"; +import { AddonService } from "./services/addons/addon.service"; +import { AnalyticsService } from "./services/analytics/analytics.service"; +import { WarcraftService } from "./services/warcraft/warcraft.service"; +import { WowUpService } from "./services/wowup/wowup.service"; + +const AUTO_UPDATE_PERIOD_MS = 60 * 60 * 1000; // 1 hour @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + selector: "app-root", + templateUrl: "./app.component.html", + styleUrls: ["./app.component.scss"], }) export class AppComponent implements AfterViewInit { + private _autoUpdateInterval?: number; + constructor( private _analyticsService: AnalyticsService, private electronService: ElectronService, private translate: TranslateService, private warcraft: WarcraftService, private _wowUpService: WowUpService, - private _dialog: MatDialog + private _dialog: MatDialog, + private _addonService: AddonService ) { - this.translate.setDefaultLang('en'); + this.translate.setDefaultLang("en"); this.translate.use(this.electronService.locale); } @@ -33,15 +39,26 @@ export class AppComponent implements AfterViewInit { } else { // TODO track startup } + + this.onAutoUpdateInterval(); + this._autoUpdateInterval = window.setInterval( + this.onAutoUpdateInterval, + AUTO_UPDATE_PERIOD_MS + ); } openDialog(): void { const dialogRef = this._dialog.open(TelemetryDialogComponent, { - disableClose: true + disableClose: true, }); - dialogRef.afterClosed().subscribe(result => { + dialogRef.afterClosed().subscribe((result) => { this._wowUpService.telemetryEnabled = result; }); } + + private onAutoUpdateInterval = async () => { + console.log("Auto update"); + const updateCount = await this._addonService.processAutoUpdates(); + }; } diff --git a/wowup-electron/src/app/app.module.ts b/wowup-electron/src/app/app.module.ts index 12ad934f..357a2806 100644 --- a/wowup-electron/src/app/app.module.ts +++ b/wowup-electron/src/app/app.module.ts @@ -1,41 +1,41 @@ -import 'reflect-metadata'; -import '../polyfills'; +import "reflect-metadata"; +import "../polyfills"; -import { BrowserModule } from '@angular/platform-browser'; -import { ErrorHandler, InjectionToken, NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { HttpClientModule, HttpClient, HTTP_INTERCEPTORS } from '@angular/common/http'; -import { SharedModule } from './shared/shared.module'; +import { BrowserModule } from "@angular/platform-browser"; +import { ErrorHandler, NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { + HttpClientModule, + HttpClient, + HTTP_INTERCEPTORS, +} from "@angular/common/http"; +import { SharedModule } from "./shared/shared.module"; -import { AppRoutingModule } from './app-routing.module'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { AppRoutingModule } from "./app-routing.module"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; // NG Translate -import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; -import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { TranslateModule, TranslateLoader } from "@ngx-translate/core"; +import { TranslateHttpLoader } from "@ngx-translate/http-loader"; -import { HomeModule } from './pages/home/home.module'; +import { HomeModule } from "./pages/home/home.module"; -import { AppComponent } from './app.component'; -import { TitlebarComponent } from './components/titlebar/titlebar.component'; -import { FooterComponent } from './components/footer/footer.component'; -import { DefaultHeadersInterceptor } from './interceptors/default-headers.interceptor'; -import { AnalyticsService } from './services/analytics/analytics.service'; -import { DirectiveModule } from './directive.module'; -import { MatModule } from './mat-module'; -import { MatProgressButtonsModule } from 'mat-progress-buttons'; +import { AppComponent } from "./app.component"; +import { TitlebarComponent } from "./components/titlebar/titlebar.component"; +import { FooterComponent } from "./components/footer/footer.component"; +import { DefaultHeadersInterceptor } from "./interceptors/default-headers.interceptor"; +import { AnalyticsService } from "./services/analytics/analytics.service"; +import { DirectiveModule } from "./directive.module"; +import { MatModule } from "./mat-module"; +import { MatProgressButtonsModule } from "mat-progress-buttons"; // AoT requires an exported function for factories export function httpLoaderFactory(http: HttpClient): TranslateHttpLoader { - return new TranslateHttpLoader(http, './assets/i18n/', '.json'); + return new TranslateHttpLoader(http, "./assets/i18n/", ".json"); } @NgModule({ - declarations: [ - AppComponent, - TitlebarComponent, - FooterComponent, - ], + declarations: [AppComponent, TitlebarComponent, FooterComponent], imports: [ BrowserModule, FormsModule, @@ -50,16 +50,19 @@ export function httpLoaderFactory(http: HttpClient): TranslateHttpLoader { loader: { provide: TranslateLoader, useFactory: httpLoaderFactory, - deps: [HttpClient] - } + deps: [HttpClient], + }, }), BrowserAnimationsModule, - ], providers: [ - { provide: HTTP_INTERCEPTORS, useClass: DefaultHeadersInterceptor, multi: true }, - { provide: ErrorHandler, useClass: AnalyticsService } + { + provide: HTTP_INTERCEPTORS, + useClass: DefaultHeadersInterceptor, + multi: true, + }, + { provide: ErrorHandler, useClass: AnalyticsService }, ], - bootstrap: [AppComponent] + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/wowup-electron/src/app/components/footer/footer.component.html b/wowup-electron/src/app/components/footer/footer.component.html index e8791796..888a881c 100644 --- a/wowup-electron/src/app/components/footer/footer.component.html +++ b/wowup-electron/src/app/components/footer/footer.component.html @@ -2,7 +2,7 @@ -

{{sessionService.statusText$ | async}}

+

{{sessionService.statusText$ | async}}

{{sessionService.pageContextText$ | async}}

v{{wowUpService.applicationVersion}}

diff --git a/wowup-electron/src/app/components/footer/footer.component.scss b/wowup-electron/src/app/components/footer/footer.component.scss index ee251114..88723397 100644 --- a/wowup-electron/src/app/components/footer/footer.component.scss +++ b/wowup-electron/src/app/components/footer/footer.component.scss @@ -7,7 +7,6 @@ footer { height: 25px; padding: 0.25em 0.5em; display: flex; - justify-content: space-between; align-items: center; diff --git a/wowup-electron/src/app/pages/home/home.component.ts b/wowup-electron/src/app/pages/home/home.component.ts index ac32dd31..c04c1608 100644 --- a/wowup-electron/src/app/pages/home/home.component.ts +++ b/wowup-electron/src/app/pages/home/home.component.ts @@ -15,21 +15,18 @@ export class HomeComponent implements OnInit { private _sessionService: SessionService, private _warcraftService: WarcraftService ) { - this._warcraftService.installedClientTypes$ - .subscribe((clientTypes) => { - if(clientTypes === undefined){ - this.hasWowClient = false; - this.selectedIndex = 3; - } else { - this.hasWowClient = clientTypes.length > 0; - this.selectedIndex = this.hasWowClient ? 0 : 3; - } - }); + this._warcraftService.installedClientTypes$.subscribe((clientTypes) => { + if (clientTypes === undefined) { + this.hasWowClient = false; + this.selectedIndex = 3; + } else { + this.hasWowClient = clientTypes.length > 0; + this.selectedIndex = this.hasWowClient ? 0 : 3; + } + }); } - ngOnInit(): void { - this._sessionService.appLoaded(); - } + ngOnInit(): void {} onSelectedIndexChange(index: number) { this._sessionService.selectedHomeTab = index; diff --git a/wowup-electron/src/app/pages/home/home.module.ts b/wowup-electron/src/app/pages/home/home.module.ts index d63d93a0..a0dd5ede 100644 --- a/wowup-electron/src/app/pages/home/home.module.ts +++ b/wowup-electron/src/app/pages/home/home.module.ts @@ -43,7 +43,7 @@ import { AddonInstallButtonComponent } from "app/components/addon-install-button InstallFromUrlDialogComponent, AddonDetailComponent, AddonProviderBadgeComponent, - AddonInstallButtonComponent + AddonInstallButtonComponent, ], imports: [ CommonModule, diff --git a/wowup-electron/src/app/services/addons/addon.provider.factory.ts b/wowup-electron/src/app/services/addons/addon.provider.factory.ts new file mode 100644 index 00000000..5ccdeed3 --- /dev/null +++ b/wowup-electron/src/app/services/addons/addon.provider.factory.ts @@ -0,0 +1,68 @@ +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { AddonProvider } from "app/addon-providers/addon-provider"; +import { GitHubAddonProvider } from "app/addon-providers/github-addon-provider"; +import { TukUiAddonProvider } from "app/addon-providers/tukui-addon-provider"; +import { WowInterfaceAddonProvider } from "app/addon-providers/wow-interface-addon-provider"; + +import { CurseAddonProvider } from "../../addon-providers/curse-addon-provider"; +import { CachingService } from "../caching/caching-service"; +import { ElectronService } from "../electron/electron.service"; +import { FileService } from "../files/file.service"; +import { SessionService } from "../session/session.service"; + +@Injectable({ + providedIn: "root", +}) +export class AddonProviderFactory { + constructor( + private _cachingService: CachingService, + private _electronService: ElectronService, + private _httpClient: HttpClient, + private _sessionService: SessionService, + private _fileService: FileService + ) {} + + public getAddonProvider(providerType: T & AddonProvider) { + switch (providerType.name) { + case CurseAddonProvider.name: + return this.createCurseAddonProvider(); + case TukUiAddonProvider.name: + break; + default: + break; + } + } + + public createCurseAddonProvider(): CurseAddonProvider { + return new CurseAddonProvider( + this._httpClient, + this._cachingService, + this._electronService, + this._sessionService, + this._fileService + ); + } + + public createTukUiAddonProvider(): TukUiAddonProvider { + return new TukUiAddonProvider( + this._httpClient, + this._cachingService, + this._electronService, + this._fileService + ); + } + + public createWowInterfaceAddonProvider(): WowInterfaceAddonProvider { + return new WowInterfaceAddonProvider( + this._httpClient, + this._cachingService, + this._electronService, + this._fileService + ); + } + + public createGitHubAddonProvider(): GitHubAddonProvider { + return new GitHubAddonProvider(this._httpClient); + } +} diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 317b1a24..5511c732 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -1,14 +1,14 @@ -import { Injectable } from "@angular/core"; +import { Injectable, Injector } from "@angular/core"; import { AddonStorageService } from "../storage/addon-storage.service"; import { Addon } from "../../entities/addon"; import { WarcraftService } from "../warcraft/warcraft.service"; import { AddonProvider } from "../../addon-providers/addon-provider"; import { CurseAddonProvider } from "../../addon-providers/curse-addon-provider"; import { HttpClient } from "@angular/common/http"; -import * as _ from 'lodash'; -import { v4 as uuidv4 } from 'uuid'; -import * as path from 'path'; -import * as fs from 'fs'; +import * as _ from "lodash"; +import { v4 as uuidv4 } from "uuid"; +import * as path from "path"; +import * as fs from "fs"; import { WowUpApiService } from "../wowup-api/wowup-api.service"; import { WowClientType } from "app/models/warcraft/wow-client-type"; import { PotentialAddon } from "app/models/wowup/potential-addon"; @@ -29,12 +29,12 @@ import { TukUiAddonProvider } from "app/addon-providers/tukui-addon-provider"; import { AddonUpdateEvent } from "app/models/wowup/addon-update-event"; import { WowInterfaceAddonProvider } from "app/addon-providers/wow-interface-addon-provider"; import { GitHubAddonProvider } from "app/addon-providers/github-addon-provider"; +import { AddonProviderFactory } from "./addon.provider.factory"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class AddonService { - private readonly _addonProviders: AddonProvider[]; private readonly _addonInstalledSrc = new Subject(); private readonly _addonRemovedSrc = new Subject(); @@ -44,21 +44,18 @@ export class AddonService { constructor( private _addonStorage: AddonStorageService, - private _cachingService: CachingService, private _warcraftService: WarcraftService, private _wowUpService: WowUpService, - private _wowupApiService: WowUpApiService, private _downloadService: DownloadSevice, - private _electronService: ElectronService, private _fileService: FileService, private _tocService: TocService, - httpClient: HttpClient + private _addonProviderFactory: AddonProviderFactory ) { this._addonProviders = [ - new CurseAddonProvider(httpClient, this._cachingService, this._electronService, this._fileService), - new TukUiAddonProvider(httpClient, this._cachingService, this._electronService, this._fileService), - new WowInterfaceAddonProvider(httpClient, this._cachingService, this._electronService, this._fileService), - new GitHubAddonProvider(httpClient), + this._addonProviderFactory.createCurseAddonProvider(), + this._addonProviderFactory.createTukUiAddonProvider(), + this._addonProviderFactory.createWowInterfaceAddonProvider(), + this._addonProviderFactory.createGitHubAddonProvider(), ]; } @@ -66,41 +63,62 @@ export class AddonService { this._addonStorage.set(addon.id, addon); } - public async search(query: string, clientType: WowClientType): Promise { - var searchTasks = this._addonProviders.map(p => p.searchByQuery(query, clientType)); + public async search( + query: string, + clientType: WowClientType + ): Promise { + var searchTasks = this._addonProviders.map((p) => + p.searchByQuery(query, clientType) + ); var searchResults = await Promise.all(searchTasks); // await _analyticsService.TrackUserAction("Addons", "Search", $"{clientType}|{query}"); const flatResults = searchResults.flat(1); - return _.orderBy(flatResults, 'downloadCount').reverse(); + return _.orderBy(flatResults, "downloadCount").reverse(); } public async installPotentialAddon( potentialAddon: PotentialAddon, clientType: WowClientType, - onUpdate: (installState: AddonInstallState, progress: number) => void = undefined + onUpdate: ( + installState: AddonInstallState, + progress: number + ) => void = undefined ) { - var existingAddon = this._addonStorage.getByExternalId(potentialAddon.externalId, clientType); + var existingAddon = this._addonStorage.getByExternalId( + potentialAddon.externalId, + clientType + ); if (existingAddon) { - throw new Error('Addon already installed'); + throw new Error("Addon already installed"); } - const addon = await this.getAddon(potentialAddon.externalId, potentialAddon.providerName, clientType).toPromise(); + const addon = await this.getAddon( + potentialAddon.externalId, + potentialAddon.providerName, + clientType + ).toPromise(); this._addonStorage.set(addon.id, addon); await this.installAddon(addon.id, onUpdate); } public async processAutoUpdates(): Promise { const autoUpdateAddons = this.getAutoUpdateEnabledAddons(); - const clientTypeGroups = _.groupBy(autoUpdateAddons, addon => addon.clientType); + const clientTypeGroups = _.groupBy( + autoUpdateAddons, + (addon) => addon.clientType + ); let updateCt = 0; for (let clientTypeStr in clientTypeGroups) { const clientType: WowClientType = parseInt(clientTypeStr, 10); // console.log('clientType', clientType, clientTypeGroups[clientType]); - const synced = await this.syncAddons(clientType, clientTypeGroups[clientType]); + const synced = await this.syncAddons( + clientType, + clientTypeGroups[clientType] + ); if (!synced) { continue; } @@ -113,30 +131,33 @@ export class AddonService { try { await this.installAddon(addon.id); updateCt += 1; - } - catch (err) - { + } catch (err) { // _analyticsService.Track(ex, "Failed to install addon"); } } } - + return updateCt; } public canUpdateAddon(addon: Addon) { - return addon.installedVersion && addon.installedVersion !== addon.latestVersion; + return ( + addon.installedVersion && addon.installedVersion !== addon.latestVersion + ); } public getAutoUpdateEnabledAddons() { - return this._addonStorage.queryAll(addon => { + return this._addonStorage.queryAll((addon) => { return addon.isIgnored !== true && addon.autoUpdateEnabled; }); } public async installAddon( addonId: string, - onUpdate: (installState: AddonInstallState, progress: number) => void = undefined + onUpdate: ( + installState: AddonInstallState, + progress: number + ) => void = undefined ) { const addon = this.getAddonById(addonId); if (addon == null || !addon.downloadUrl) { @@ -144,35 +165,56 @@ export class AddonService { } onUpdate?.call(this, AddonInstallState.Downloading, 25); - this._addonInstalledSrc.next({ addon, installState: AddonInstallState.Downloading, progress: 25 }); + this._addonInstalledSrc.next({ + addon, + installState: AddonInstallState.Downloading, + progress: 25, + }); - let downloadedFilePath = ''; - let unzippedDirectory = ''; - let downloadedThumbnail = ''; + let downloadedFilePath = ""; + let unzippedDirectory = ""; + let downloadedThumbnail = ""; try { - downloadedFilePath = await this._downloadService.downloadZipFile(addon.downloadUrl, this._wowUpService.applicationDownloadsFolderPath); + downloadedFilePath = await this._downloadService.downloadZipFile( + addon.downloadUrl, + this._wowUpService.applicationDownloadsFolderPath + ); onUpdate?.call(this, AddonInstallState.Installing, 75); - this._addonInstalledSrc.next({ addon, installState: AddonInstallState.Installing, progress: 75 }); + this._addonInstalledSrc.next({ + addon, + installState: AddonInstallState.Installing, + progress: 75, + }); - const unzipPath = path.join(this._wowUpService.applicationDownloadsFolderPath, uuidv4()); - unzippedDirectory = await this._downloadService.unzipFile(downloadedFilePath, unzipPath); + const unzipPath = path.join( + this._wowUpService.applicationDownloadsFolderPath, + uuidv4() + ); + unzippedDirectory = await this._downloadService.unzipFile( + downloadedFilePath, + unzipPath + ); await this.installUnzippedDirectory(unzippedDirectory, addon.clientType); - const unzippedDirectoryNames = await this._fileService.listDirectories(unzippedDirectory); + const unzippedDirectoryNames = await this._fileService.listDirectories( + unzippedDirectory + ); addon.installedVersion = addon.latestVersion; addon.installedAt = new Date(); - addon.installedFolders = unzippedDirectoryNames.join(','); + addon.installedFolders = unzippedDirectoryNames.join(","); if (!!addon.gameVersion) { - addon.gameVersion = await this.getLatestGameVersion(unzippedDirectory, unzippedDirectoryNames); + addon.gameVersion = await this.getLatestGameVersion( + unzippedDirectory, + unzippedDirectoryNames + ); } this._addonStorage.set(addon.id, addon); // await _analyticsService.TrackUserAction("Addons", "InstallById", $"{addon.ClientType}|{addon.Name}"); - } catch (err) { console.error(err); @@ -188,10 +230,17 @@ export class AddonService { } onUpdate?.call(this, AddonInstallState.Complete, 100); - this._addonInstalledSrc.next({ addon, installState: AddonInstallState.Complete, progress: 100 }); + this._addonInstalledSrc.next({ + addon, + installState: AddonInstallState.Complete, + progress: 100, + }); } - private async getLatestGameVersion(baseDir: string, installedFolders: string[]) { + private async getLatestGameVersion( + baseDir: string, + installedFolders: string[] + ) { const versions = []; for (let dir of installedFolders) { @@ -211,31 +260,44 @@ export class AddonService { versions.push(toc.interface); } - return _.orderBy(versions)[0] || ''; + return _.orderBy(versions)[0] || ""; } - private async installUnzippedDirectory(unzippedDirectory: string, clientType: WowClientType) { - const addonFolderPath = this._warcraftService.getAddonFolderPath(clientType); - const unzippedFolders = await this._fileService.listDirectories(unzippedDirectory); + private async installUnzippedDirectory( + unzippedDirectory: string, + clientType: WowClientType + ) { + const addonFolderPath = this._warcraftService.getAddonFolderPath( + clientType + ); + const unzippedFolders = await this._fileService.listDirectories( + unzippedDirectory + ); for (let unzippedFolder of unzippedFolders) { const unzippedFilePath = path.join(unzippedDirectory, unzippedFolder); const unzipLocation = path.join(addonFolderPath, unzippedFolder); - const unzipBackupLocation = path.join(addonFolderPath, `${unzippedFolder}-bak`); + const unzipBackupLocation = path.join( + addonFolderPath, + `${unzippedFolder}-bak` + ); try { // If the user already has the addon installed, create a temporary backup if (fs.existsSync(unzipLocation)) { - console.log('BACKING UP', unzipLocation); - await this._fileService.renameDirectory(unzipLocation, unzipBackupLocation); + console.log("BACKING UP", unzipLocation); + await this._fileService.renameDirectory( + unzipLocation, + unzipBackupLocation + ); } // Copy contents from unzipped new directory to existing addon folder location - console.log('COPY', unzipLocation); + console.log("COPY", unzipLocation); await this._fileService.copyDirectory(unzippedFilePath, unzipLocation); // If the copy succeeds, delete the backup if (fs.existsSync(unzipBackupLocation)) { - console.log('DELETE BKUP', unzipLocation); + console.log("DELETE BKUP", unzipLocation); await this._fileService.deleteDirectory(unzipBackupLocation); } } catch (err) { @@ -251,7 +313,10 @@ export class AddonService { // Move the backup folder into the original location console.log(`Attempting to roll back ${unzipBackupLocation}`); - await this._fileService.copyDirectory(unzipBackupLocation, unzipLocation); + await this._fileService.copyDirectory( + unzipBackupLocation, + unzipLocation + ); } throw err; @@ -269,27 +334,39 @@ export class AddonService { return await provider.searchByUrl(url, clientType); } - public getAddon(externalId: string, providerName: string, clientType: WowClientType) { - const targetAddonChannel = this._wowUpService.getDefaultAddonChannel(clientType); + public getAddon( + externalId: string, + providerName: string, + clientType: WowClientType + ) { + const targetAddonChannel = this._wowUpService.getDefaultAddonChannel( + clientType + ); const provider = this.getProvider(providerName); - return provider.getById(externalId, clientType) - .pipe( - map(searchResult => { - console.log('SEARCH RES', searchResult); - let latestFile = this.getLatestFile(searchResult, targetAddonChannel); - if (!latestFile) { - latestFile = searchResult.files[0]; - } + return provider.getById(externalId, clientType).pipe( + map((searchResult) => { + console.log("SEARCH RES", searchResult); + let latestFile = this.getLatestFile(searchResult, targetAddonChannel); + if (!latestFile) { + latestFile = searchResult.files[0]; + } - return this.createAddon(latestFile.folders[0], searchResult, latestFile, clientType); - }) - ) + return this.createAddon( + latestFile.folders[0], + searchResult, + latestFile, + clientType + ); + }) + ); } public async removeAddon(addon: Addon) { - const installedDirectories = addon.installedFolders.split(','); + const installedDirectories = addon.installedFolders.split(","); - const addonFolderPath = this._warcraftService.getAddonFolderPath(addon.clientType); + const addonFolderPath = this._warcraftService.getAddonFolderPath( + addon.clientType + ); for (let directory of installedDirectories) { const addonDirectory = path.join(addonFolderPath, directory); await this._fileService.deleteDirectory(addonDirectory); @@ -299,7 +376,10 @@ export class AddonService { this._addonRemovedSrc.next(addon.id); } - public async getAddons(clientType: WowClientType, rescan = false): Promise { + public async getAddons( + clientType: WowClientType, + rescan = false + ): Promise { let addons = this._addonStorage.getAllForClientType(clientType); if (rescan || !addons.length) { const newAddons = await this.scanAddons(clientType); @@ -312,18 +392,28 @@ export class AddonService { } private updateAddons(existingAddons: Addon[], newAddons: Addon[]): Addon[] { - const removedAddons = existingAddons - .filter(existingAddon => !newAddons.some(newAddon => this.addonsMatch(existingAddon, newAddon))); + const removedAddons = existingAddons.filter( + (existingAddon) => + !newAddons.some((newAddon) => this.addonsMatch(existingAddon, newAddon)) + ); - const addedAddons = newAddons - .filter(newAddon => !existingAddons.some(existingAddon => this.addonsMatch(existingAddon, newAddon))); + const addedAddons = newAddons.filter( + (newAddon) => + !existingAddons.some((existingAddon) => + this.addonsMatch(existingAddon, newAddon) + ) + ); - _.remove(existingAddons, addon => removedAddons.some(removedAddon => removedAddon.id === addon.id)); + _.remove(existingAddons, (addon) => + removedAddons.some((removedAddon) => removedAddon.id === addon.id) + ); existingAddons.push(...addedAddons); for (let existingAddon of existingAddons) { - var matchingAddon = newAddons.find(newAddon => this.addonsMatch(newAddon, existingAddon)); + var matchingAddon = newAddons.find((newAddon) => + this.addonsMatch(newAddon, existingAddon) + ); if (!matchingAddon) { continue; } @@ -346,9 +436,11 @@ export class AddonService { } private addonsMatch(addon1: Addon, addon2: Addon): boolean { - return addon1.externalId == addon2.externalId && + return ( + addon1.externalId == addon2.externalId && addon1.providerName == addon2.providerName && - addon1.clientType == addon2.clientType; + addon1.clientType == addon2.clientType + ); } private async syncAddons(clientType: WowClientType, addons: Addon[]) { @@ -358,25 +450,40 @@ export class AddonService { } return true; - } - catch (err) { + } catch (err) { console.error(err); return false; } } - private async syncProviderAddons(clientType: WowClientType, addons: Addon[], addonProvider: AddonProvider) { - const providerAddonIds = this.getExternalIdsForProvider(addonProvider, addons); + private async syncProviderAddons( + clientType: WowClientType, + addons: Addon[], + addonProvider: AddonProvider + ) { + const providerAddonIds = this.getExternalIdsForProvider( + addonProvider, + addons + ); if (!providerAddonIds.length) { return; } - const searchResults = await addonProvider.getAll(clientType, providerAddonIds); + const searchResults = await addonProvider.getAll( + clientType, + providerAddonIds + ); for (let result of searchResults) { - const addon = addons.find(addon => addon.externalId === result?.externalId); + const addon = addons.find( + (addon) => addon.externalId === result?.externalId + ); const latestFile = this.getLatestFile(result, addon?.channelType); - if (!result || !latestFile || latestFile.version === addon.latestVersion) { + if ( + !result || + !latestFile || + latestFile.version === addon.latestVersion + ) { continue; } @@ -396,37 +503,57 @@ export class AddonService { } } - private getExternalIdsForProvider(addonProvider: AddonProvider, addons: Addon[]): string[] { - return addons.filter(addon => addon.providerName === addonProvider.name) - .map(addon => addon.externalId); + private getExternalIdsForProvider( + addonProvider: AddonProvider, + addons: Addon[] + ): string[] { + return addons + .filter((addon) => addon.providerName === addonProvider.name) + .map((addon) => addon.externalId); } private async scanAddons(clientType: WowClientType): Promise { const addonFolders = await this._warcraftService.listAddons(clientType); for (let provider of this._addonProviders) { try { - const validFolders = addonFolders.filter(af => !af.matchingAddon && af.toc) - await provider.scan(clientType, this._wowUpService.getDefaultAddonChannel(clientType), validFolders); + const validFolders = addonFolders.filter( + (af) => !af.matchingAddon && af.toc + ); + await provider.scan( + clientType, + this._wowUpService.getDefaultAddonChannel(clientType), + validFolders + ); } catch (err) { console.log(err); } } - const matchedAddonFolders = addonFolders.filter(addonFolder => !!addonFolder.matchingAddon); - const matchedGroups = _.groupBy(matchedAddonFolders, addonFolder => `${addonFolder.matchingAddon.providerName}${addonFolder.matchingAddon.externalId}`); + const matchedAddonFolders = addonFolders.filter( + (addonFolder) => !!addonFolder.matchingAddon + ); + const matchedGroups = _.groupBy( + matchedAddonFolders, + (addonFolder) => + `${addonFolder.matchingAddon.providerName}${addonFolder.matchingAddon.externalId}` + ); console.log(Object.keys(matchedGroups)); - console.log(matchedGroups['Curse2382']) - return Object.values(matchedGroups).map(value => value[0].matchingAddon); + console.log(matchedGroups["Curse2382"]); + + return Object.values(matchedGroups).map((value) => value[0].matchingAddon); } - public getFeaturedAddons(clientType: WowClientType): Observable { - return forkJoin(this._addonProviders.map(p => p.getFeaturedAddons(clientType))) - .pipe( - map(results => { - return _.orderBy(results.flat(1), ['downloadCount']).reverse(); - }) - ); + public getFeaturedAddons( + clientType: WowClientType + ): Observable { + return forkJoin( + this._addonProviders.map((p) => p.getFeaturedAddons(clientType)) + ).pipe( + map((results) => { + return _.orderBy(results.flat(1), ["downloadCount"]).reverse(); + }) + ); } public isInstalled(externalId: string, clientType: WowClientType) { @@ -434,17 +561,19 @@ export class AddonService { } private getProvider(providerName: string) { - return this._addonProviders.find(provider => provider.name === providerName); + return this._addonProviders.find( + (provider) => provider.name === providerName + ); } private getAllStoredAddons(clientType: WowClientType) { const addons: Addon[] = []; - this._addonStorage.query(store => { + this._addonStorage.query((store) => { for (const result of store) { addons.push(result[1] as Addon); } - }) + }); return addons; } @@ -452,7 +581,7 @@ export class AddonService { private async getLocalAddons(clientType: WowClientType): Promise { const addonFolders = await this._warcraftService.listAddons(clientType); const addons: Addon[] = []; - console.log('addonFolders', addonFolders); + console.log("addonFolders", addonFolders); for (const folder of addonFolders) { try { @@ -461,7 +590,6 @@ export class AddonService { if (folder.toc.curseProjectId) { addon = await this.getCurseAddonById(folder, clientType); } else { - } if (!addon) { @@ -478,22 +606,42 @@ export class AddonService { } private getAddonProvider(addonUri: URL): AddonProvider { - return this._addonProviders.find(provider => provider.isValidAddonUri(addonUri)); + return this._addonProviders.find((provider) => + provider.isValidAddonUri(addonUri) + ); } private async getCurseAddonById( addonFolder: AddonFolder, clientType: WowClientType ) { - const curseProvider = this._addonProviders.find(p => p instanceof CurseAddonProvider); - const searchResult = await curseProvider.getById(addonFolder.toc.curseProjectId, clientType).toPromise(); - const latestFile = this.getLatestFile(searchResult, AddonChannelType.Stable); - return this.createAddon(addonFolder.name, searchResult, latestFile, clientType); + const curseProvider = this._addonProviders.find( + (p) => p instanceof CurseAddonProvider + ); + const searchResult = await curseProvider + .getById(addonFolder.toc.curseProjectId, clientType) + .toPromise(); + const latestFile = this.getLatestFile( + searchResult, + AddonChannelType.Stable + ); + return this.createAddon( + addonFolder.name, + searchResult, + latestFile, + clientType + ); } - private getLatestFile(searchResult: AddonSearchResult, channelType: AddonChannelType): AddonSearchResultFile { - let files = _.filter(searchResult.files, (f: AddonSearchResultFile) => f.channelType <= channelType); - files = _.orderBy(files, ['releaseDate']).reverse(); + private getLatestFile( + searchResult: AddonSearchResult, + channelType: AddonChannelType + ): AddonSearchResultFile { + let files = _.filter( + searchResult.files, + (f: AddonSearchResultFile) => f.channelType <= channelType + ); + files = _.orderBy(files, ["releaseDate"]).reverse(); return _.first(files); } @@ -525,4 +673,4 @@ export class AddonService { autoUpdateEnabled: this._wowUpService.getDefaultAutoUpdate(clientType), }; } -} \ No newline at end of file +} diff --git a/wowup-electron/src/app/services/session/session.service.ts b/wowup-electron/src/app/services/session/session.service.ts index 18c7b6d5..ae7d07b6 100644 --- a/wowup-electron/src/app/services/session/session.service.ts +++ b/wowup-electron/src/app/services/session/session.service.ts @@ -1,14 +1,10 @@ -import { Injectable } from "@angular/core"; +import { Injectable, InjectionToken } from "@angular/core"; import { WowClientType } from "app/models/warcraft/wow-client-type"; import { BehaviorSubject } from "rxjs"; import { filter, first, map } from "rxjs/operators"; -import { AddonService } from "../addons/addon.service"; -import { ElectronService } from "../electron/electron.service"; import { WarcraftService } from "../warcraft/warcraft.service"; import { WowUpService } from "../wowup/wowup.service"; -const AUTO_UPDATE_PERIOD_MS = 60 * 60 * 1000; // 1 hour - @Injectable({ providedIn: "root", }) @@ -20,28 +16,29 @@ export class SessionService { private readonly _statusTextSrc = new BehaviorSubject(""); // left side bar text, context to the app private readonly _selectedHomeTabSrc = new BehaviorSubject(0); - private _autoUpdateInterval?: number; - public readonly selectedClientType$ = this._selectedClientTypeSrc.asObservable(); public readonly statusText$ = this._statusTextSrc.asObservable(); public readonly selectedHomeTab$ = this._selectedHomeTabSrc.asObservable(); public readonly pageContextText$ = this._pageContextTextSrc.asObservable(); constructor( - private _addonService: AddonService, private _warcraftService: WarcraftService, private _wowUpService: WowUpService ) { this.loadInitialClientType().pipe(first()).subscribe(); } - public set contextText(text: string){ + public set contextText(text: string) { this._pageContextTextSrc.next(text); } + public set statusText(text: string) { + this._statusTextSrc.next(text); + } + public set selectedHomeTab(tabIndex: number) { this._selectedHomeTabSrc.next(tabIndex); - this.contextText = ''; + this.contextText = ""; } public set selectedClientType(clientType: WowClientType) { @@ -53,25 +50,10 @@ export class SessionService { return this._selectedClientTypeSrc.value; } - public appLoaded() { - if (!this._autoUpdateInterval) { - this.onAutoUpdateInterval(); - this._autoUpdateInterval = window.setInterval( - this.onAutoUpdateInterval, - AUTO_UPDATE_PERIOD_MS - ); - } - } - public startUpdaterCheck() { this.checkUpdaterApp(); } - private onAutoUpdateInterval = async () => { - console.log("Auto update"); - const updateCount = await this._addonService.processAutoUpdates(); - }; - private loadInitialClientType() { return this._warcraftService.installedClientTypes$.pipe( filter((clientTypes) => clientTypes !== undefined), diff --git a/wowup-electron/src/app/utils/file.utils.ts b/wowup-electron/src/app/utils/file.utils.ts index 1b31cce0..4794a15d 100644 --- a/wowup-electron/src/app/utils/file.utils.ts +++ b/wowup-electron/src/app/utils/file.utils.ts @@ -1,23 +1,29 @@ -import * as fs from 'fs'; -import * as util from 'util'; -import { remote } from 'electron' +import * as fs from "fs"; +import * as util from "util"; +import { remote } from "electron"; +import { ListFilesResponse } from "common/models/list-files-response"; +import { ListFilesRequest } from "common/models/list-files-request"; +import { v4 as uuidv4 } from "uuid"; +import { LIST_FILES_CHANNEL, READ_FILE_CHANNEL } from "common/constants"; +import { ReadFileResponse } from "common/models/read-file-response"; +import { ReadFileRequest } from "common/models/read-file-request"; -const fsAccess = util.promisify(fs.access) -const fsReadFile = util.promisify(fs.readFile) -const userDataPath = remote.app.getPath('userData'); +const fsAccess = util.promisify(fs.access); +const fsReadFile = util.promisify(fs.readFile); +const userDataPath = remote.app.getPath("userData"); export class FileUtils { static async exists(path: string) { try { - await fsAccess(path, fs.constants.F_OK) - return true + await fsAccess(path, fs.constants.F_OK); + return true; } catch (e) { - return false + return false; } } static readFile(path: string) { - return fsReadFile(path) + return fsReadFile(path); } static readFileSync(path: string) { @@ -27,4 +33,4 @@ export class FileUtils { static getUserDataPath() { return userDataPath; } -} \ No newline at end of file +}