Add the getall route for wago

Fix some wago caching issues
Fix some async startup issues
This commit is contained in:
jliddev
2022-01-11 09:45:15 -06:00
parent 76ce7ff6c0
commit eb092ce0fb
16 changed files with 155 additions and 83 deletions

View File

@@ -23,6 +23,7 @@ import { getGameVersion } from "../utils/addon.utils";
import { getEnumName } from "../utils/enum.utils";
import { convertMarkdown } from "../utils/markdown.utlils";
import { AddonProvider, GetAllResult } from "./addon-provider";
import { SourceRemovedAddonError } from "../errors";
declare type WagoGameVersion = "retail" | "classic" | "bc";
declare type WagoStability = "stable" | "beta" | "alpha";
@@ -139,6 +140,15 @@ interface WagoPopularAddonsResponse {
data: WagoSearchResponseItem[];
}
interface WagoRecentsRequest {
game_version: WagoGameVersion;
addons: string[];
}
interface WagoRecentsResponse {
addons: { [addonId: string]: WagoScanAddon };
}
const WAGO_BASE_URL = "https://addons.wago.io/api/external";
const WAGO_AD_URL = "https://addons.wago.io/wowup_ad";
const WAGO_AD_REFERRER = "https://wago.io";
@@ -164,13 +174,14 @@ export class WagoAddonProvider extends AddonProvider {
public adRequired = true;
public allowEdit = true;
public allowReinstall = true;
public allowChannelChange = true;
public constructor(
private _electronService: ElectronService,
private _cachingService: CachingService,
private _networkService: NetworkService,
private _warcraftService: WarcraftService,
private _tocService: TocService
private _tocService: TocService,
_networkService: NetworkService
) {
super();
@@ -193,10 +204,7 @@ export class WagoAddonProvider extends AddonProvider {
addonFolders: AddonFolder[]
): Promise<void> {
const gameVersion = this.getGameVersion(installation.clientType);
console.time("WagoScan");
const scanResults = await this.getScanResults(addonFolders);
console.timeEnd("WagoScan");
const request: WagoFingerprintRequest = {
game_version: gameVersion,
@@ -222,7 +230,7 @@ export class WagoAddonProvider extends AddonProvider {
console.debug(`[wago] scan`, request);
console.debug(JSON.stringify(request));
const matchResult = await this.sendMatchesRequest(request);
const matchResult = await this.sendMatchesRequest(installation, request);
console.debug(`[wago] matchResult`, matchResult);
const scanResultMap: { [folder: string]: WagoScanAddon } = {};
@@ -278,7 +286,7 @@ export class WagoAddonProvider extends AddonProvider {
url.searchParams.set("game_version", this.getGameVersion(installation.clientType));
const response = await this._cachingService.transaction(
url.toString(),
`${installation.id}|${url.toString()}`,
() => this._circuitBreaker.getJson<WagoPopularAddonsResponse>(url, this.getRequestHeaders()),
WAGO_FEATURED_ADDONS_CACHE_TIME_SEC
);
@@ -307,7 +315,7 @@ export class WagoAddonProvider extends AddonProvider {
url.searchParams.set("stability", this.getStability(channelType));
const response = await this._cachingService.transaction(
url.toString(),
`${installation.id}|${query}|${url.toString()}`,
() => this._circuitBreaker.getJson<WagoSearchResponse>(url, this.getRequestHeaders()),
WAGO_SEARCH_CACHE_TIME_SEC
);
@@ -371,14 +379,36 @@ export class WagoAddonProvider extends AddonProvider {
}
// used when checking for new addon updates
public async getAll(): Promise<GetAllResult> {
public async getAll(installation: WowInstallation, addonIds: string[]): Promise<GetAllResult> {
await this.ensureToken().toPromise();
console.debug(`[wago] getAll`);
const url = new URL(`${WAGO_BASE_URL}/addons/_recents`).toString();
const request: WagoRecentsRequest = {
game_version: this.getGameVersion(installation.clientType),
addons: [...addonIds],
};
const response = await this._cachingService.transaction(
`${installation.id}|${url.toString()}`,
() => this._circuitBreaker.postJson<WagoRecentsResponse>(url, request, this.getRequestHeaders()),
WAGO_DETAILS_CACHE_TIME_SEC
);
const searchResults: AddonSearchResult[] = [];
for (const [addonId, addon] of Object.entries(response.addons)) {
searchResults.push(this.toSearchResultFromScan(addon));
}
const missingAddonIds = _.filter(
addonIds,
(addonId) => _.find(searchResults, (sr) => sr.externalId === addonId) === undefined
);
const deletedErrors = _.map(missingAddonIds, (addonId) => new SourceRemovedAddonError(addonId, undefined));
return Promise.resolve({
errors: [],
searchResults: [],
errors: [...deletedErrors],
searchResults,
});
}
@@ -412,15 +442,39 @@ export class WagoAddonProvider extends AddonProvider {
return await prom;
}
private async sendMatchesRequest(request: WagoFingerprintRequest) {
private async sendMatchesRequest(installation: WowInstallation, request: WagoFingerprintRequest) {
const url = new URL(`${WAGO_BASE_URL}/addons/_match`);
return await this._cachingService.transaction(
url.toString(),
`${installation.id}|${url.toString()}`,
() => this._circuitBreaker.postJson<WagoScanResponse>(url, request, this.getRequestHeaders()),
WAGO_DETAILS_CACHE_TIME_SEC
);
}
private toSearchResultFromScan(item: WagoScanAddon): AddonSearchResult {
const releaseObj = item.recent_releases;
const releaseTypes = Object.keys(releaseObj);
const searchResultFiles: AddonSearchResultFile[] = [];
for (const type of releaseTypes) {
if (releaseObj[type] !== null) {
searchResultFiles.push(this.toSearchResultFileFromDetails(releaseObj[type], type as WagoStability));
}
}
return {
author: item.authors.join(", "),
externalId: item.id,
externalUrl: item.website_url,
name: item.name,
providerName: this.name,
thumbnailUrl: item.thumbnail,
downloadCount: 0,
files: searchResultFiles,
releasedAt: new Date(),
summary: "",
};
}
private toSearchResultFromDetails(item: WagoAddon): AddonSearchResult {
const releaseObj = item.recent_releases ?? item.recent_release;
const releaseTypes = Object.keys(releaseObj);
@@ -502,7 +556,6 @@ export class WagoAddonProvider extends AddonProvider {
const authors = wagoScanAddon?.authors?.join(", ") ?? "";
const name = wagoScanAddon?.name ?? "";
const downloadUrl = wagoScanAddon?.matched_release?.link ?? wagoScanAddon.matched_release.link ?? "";
const externalUrl = wagoScanAddon?.website_url ?? "";
const externalId = wagoScanAddon?.id ?? "";
const gameVersion = getGameVersion(wagoScanAddon?.matched_release?.patch);
@@ -529,6 +582,7 @@ export class WagoAddonProvider extends AddonProvider {
const latestVersion = validVersion.label;
const externalLatestReleaseId = validVersion.id;
const externalChannel = getEnumName(AddonChannelType, validVersion.addonChannelType);
const downloadUrl = validVersion.link ?? "";
return {
id: uuidv4(),
@@ -562,7 +616,7 @@ export class WagoAddonProvider extends AddonProvider {
/** Convert a stability map of addons into a sorted list of addons */
private getSortedReleaseList(wagoScanAddon: WagoScanAddon): WagoScanReleaseSortable[] {
let releaseList: WagoScanReleaseSortable[] = [];
for (let [key, value] of Object.entries(wagoScanAddon.recent_releases)) {
for (const [key, value] of Object.entries(wagoScanAddon.recent_releases)) {
releaseList.push({ ...value, stability: key, addonChannelType: this.getAddonChannelType(key as WagoStability) });
}

View File

@@ -272,8 +272,10 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
dialogRef
.afterClosed()
.pipe(
switchMap((result: ConsentDialogResult) =>
from(this._addonProviderService.setProviderEnabled("Wago", result.wagoProvider)).pipe(map(() => result))
),
switchMap((result: ConsentDialogResult) => {
this._addonProviderService.setProviderEnabled("Wago", result.wagoProvider);
this._addonProviderService.updateWagoConsent();
this._analyticsService.telemetryEnabled = result.telemetry;

View File

@@ -30,15 +30,24 @@ import { HorizontalTabsComponent } from "./components/common/horizontal-tabs/hor
import { CommonUiModule } from "./modules/common-ui.module";
import { FooterComponent } from "./components/common/footer/footer.component";
import { VerticalTabsComponent } from "./components/common/vertical-tabs/vertical-tabs.component";
import { AddonProviderFactory } from "./services/addons/addon.provider.factory";
// AoT requires an exported function for factories
export function httpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http, "./assets/i18n/", ".json");
}
export function initializeApp(wowupService: WowUpService) {
export function initializeApp(
wowupService: WowUpService,
wowUpApiService: WowUpApiService,
addonService: AddonService,
warcraftInstallationService: WarcraftInstallationService,
iconService: IconService,
addonProviderFactory: AddonProviderFactory
) {
return async (): Promise<void> => {
await wowupService.initializeLanguage();
await addonProviderFactory.loadProviders();
};
}
@@ -71,7 +80,14 @@ export function initializeApp(wowupService: WowUpService) {
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [WowUpService, WowUpApiService, AddonService, WarcraftInstallationService, IconService],
deps: [
WowUpService,
WowUpApiService,
AddonService,
WarcraftInstallationService,
IconService,
AddonProviderFactory,
],
multi: true,
},
{

View File

@@ -137,7 +137,7 @@ export class InstallFromUrlDialogComponent implements OnDestroy {
this.hasThumbnail = !!this.addon.thumbnailUrl;
this.thumbnailLetter = this.addon.name.charAt(0).toUpperCase();
const addonInstalled = this._addonService.isInstalled(
const addonInstalled = await this._addonService.isInstalled(
this.addon.externalId,
this.addon.providerName,
selectedInstallation

View File

@@ -39,9 +39,9 @@ export class ClientSelectorComponent implements OnInit {
public ngOnInit(): void {}
public onClientChange(evt: any): void {
public async onClientChange(evt: any): Promise<void> {
const val: string = evt.value.toString();
this._sessionService.setSelectedWowInstallation(val);
await this._sessionService.setSelectedWowInstallation(val);
}
private async mapInstallations(installations: WowInstallation[]): Promise<WowInstallation[]> {

View File

@@ -1,4 +1,4 @@
import { AfterViewInit, Component, ElementRef, Input, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { AfterViewInit, Component, ElementRef, Input, NgZone, OnDestroy, ViewChild } from "@angular/core";
import { nanoid } from "nanoid";
import { Subject, takeUntil } from "rxjs";
import { AdPageOptions } from "../../../../common/wowup/models";

View File

@@ -27,11 +27,11 @@ export class OptionsAddonSectionComponent implements OnInit {
this.loadProviderStates();
}
public onProviderStateSelectionChange(event: MatSelectionListChange): void {
event.options.forEach((option) => {
public async onProviderStateSelectionChange(event: MatSelectionListChange): Promise<void> {
for (const option of event.options) {
const providerName: AddonProviderType = option.value;
this._addonProviderService.setProviderEnabled(providerName, option.selected);
});
await this._addonProviderService.setProviderEnabled(providerName, option.selected);
}
}
private loadProviderStates() {

View File

@@ -72,7 +72,7 @@ export class OptionsWowSectionComponent implements OnInit {
const wowInstallation = await this._warcraftInstallationService.createWowInstallationForPath(selectedPath);
console.log("wowInstallation", wowInstallation);
this._warcraftInstallationService.addInstallation(wowInstallation);
await this._warcraftInstallationService.addInstallation(wowInstallation);
}
private showInvalidWowApplication(selectedPath: string) {

View File

@@ -178,7 +178,7 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy {
this._editModeSrc.next(false);
}
public onClickSave(): void {
public async onClickSave(): Promise<void> {
if (!this.installationModel) {
return;
}
@@ -189,7 +189,7 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy {
this.installation = { ...this.installationModel };
if (this.installation) {
this._warcraftInstallationService.updateWowInstallation(this.installation);
await this._warcraftInstallationService.updateWowInstallation(this.installation);
}
// if (saveAutoUpdate) {

View File

@@ -1,6 +1,6 @@
import * as _ from "lodash";
import { BehaviorSubject, from } from "rxjs";
import { first, map, switchMap } from "rxjs/operators";
import { first, switchMap } from "rxjs/operators";
import { Component, Input, OnDestroy, OnInit } from "@angular/core";

View File

@@ -8,10 +8,10 @@ import {
RowNode,
} from "ag-grid-community";
import * as _ from "lodash";
import { BehaviorSubject, combineLatest, from, Observable, of, Subject, Subscription } from "rxjs";
import { BehaviorSubject, combineLatest, from, Observable, of, Subscription } from "rxjs";
import { catchError, filter, first, map, switchMap } from "rxjs/operators";
import { ChangeDetectorRef, Component, Input, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { MatCheckboxChange } from "@angular/material/checkbox";
import { MatDialog } from "@angular/material/dialog";
import { MatMenuTrigger } from "@angular/material/menu";
@@ -487,8 +487,8 @@ export class GetAddonsComponent implements OnInit, OnDestroy {
});
}
public onClientChange(): void {
this._sessionService.setSelectedWowInstallation(this.selectedInstallationId);
public async onClientChange(): Promise<void> {
await this._sessionService.setSelectedWowInstallation(this.selectedInstallationId);
}
public onRefresh(): void {

View File

@@ -402,7 +402,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
defaultState: { sort: null },
});
this.loadSortOrder();
this.loadSortOrder().catch((e) => console.error(e));
this.rowData$
.pipe(
@@ -729,9 +729,9 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
.subscribe();
}
public onClientChange(evt: any): void {
public async onClientChange(evt: any): Promise<void> {
const val: string = evt.value.toString();
this._sessionService.setSelectedWowInstallation(val);
await this._sessionService.setSelectedWowInstallation(val);
}
public onRemoveAddon(addon: Addon): void {
@@ -784,8 +784,8 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
.subscribe();
}
public onClickIgnoreAddon(listItem: AddonViewModel): void {
this.onClickIgnoreAddons([listItem]);
public async onClickIgnoreAddon(listItem: AddonViewModel): Promise<void> {
await this.onClickIgnoreAddons([listItem]);
}
public async onClickIgnoreAddons(listItems: AddonViewModel[]): Promise<void> {
@@ -815,12 +815,12 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
}
}
public onClickAutoUpdateAddon(listItem: AddonViewModel): void {
this.onClickAutoUpdateAddons([listItem]);
public async onClickAutoUpdateAddon(listItem: AddonViewModel): Promise<void> {
await this.onClickAutoUpdateAddons([listItem]);
}
public onClickAutoUpdateAddonNotifications(listItem: AddonViewModel): void {
this.onClickAutoUpdateAddonsNotifications([listItem]);
public async onClickAutoUpdateAddonNotifications(listItem: AddonViewModel): Promise<void> {
await this.onClickAutoUpdateAddonsNotifications([listItem]);
}
public onRowClicked(event: RowClickedEvent): void {

View File

@@ -44,8 +44,29 @@ export class AddonProviderFactory {
private _warcraftService: WarcraftService,
private _wowupApiService: WowUpApiService,
private _preferenceStorageService: PreferenceStorageService
) {
this.loadProviders();
) {}
/** This is part of the APP_INITIALIZER and called before the app is bootstrapped */
public async loadProviders(): Promise<void> {
if (this._providerMap.size !== 0) {
return;
}
const providers = [
this.createZipAddonProvider(),
this.createRaiderIoAddonProvider(),
this.createWowUpCompanionAddonProvider(),
this.createWowUpAddonProvider(),
this.createWagoAddonProvider(),
this.createCurseAddonProvider(),
this.createTukUiAddonProvider(),
this.createWowInterfaceAddonProvider(),
this.createGitHubAddonProvider(),
];
for (const provider of providers) {
await this.setProviderState(provider);
this._providerMap.set(provider.name, provider);
}
}
public shouldShowConsentDialog(): boolean {
@@ -56,7 +77,7 @@ export class AddonProviderFactory {
return this._preferenceStorageService.set(WAGO_PROMPT_KEY, true);
}
public setProviderEnabled(type: AddonProviderType, enabled: boolean) {
public async setProviderEnabled(type: AddonProviderType, enabled: boolean): Promise<void> {
if (!this._providerMap.has(type)) {
throw new Error("cannot set provider state, not found");
}
@@ -66,7 +87,7 @@ export class AddonProviderFactory {
throw new Error(`this provider is not editable: ${type}`);
}
this._wowupService.setAddonProviderState({
await this._wowupService.setAddonProviderState({
providerName: type,
enabled: enabled,
canEdit: true,
@@ -80,9 +101,9 @@ export class AddonProviderFactory {
return new WagoAddonProvider(
this._electronService,
this._cachingService,
this._networkService,
this._warcraftService,
this._tocService
this._tocService,
this._networkService
);
}
@@ -239,27 +260,6 @@ export class AddonProviderFactory {
return providerName !== ADDON_PROVIDER_UNKNOWN && (provider?.allowChannelChange ?? false);
}
private loadProviders() {
if (this._providerMap.size === 0) {
const providers = [
this.createZipAddonProvider(),
this.createRaiderIoAddonProvider(),
this.createWowUpCompanionAddonProvider(),
this.createWowUpAddonProvider(),
this.createWagoAddonProvider(),
this.createCurseAddonProvider(),
this.createTukUiAddonProvider(),
this.createWowInterfaceAddonProvider(),
this.createGitHubAddonProvider(),
];
providers.forEach((provider) => {
this.setProviderState(provider);
this._providerMap.set(provider.name, provider);
});
}
}
private setProviderState = async (provider: AddonProvider): Promise<void> => {
const state = await this._wowupService.getAddonProviderState(provider.name);
if (state) {

View File

@@ -1210,7 +1210,7 @@ export class AddonService {
}
const getAllResult = await addonProvider.getAll(installation, providerAddonIds);
this.handleSyncErrors(installation, getAllResult.errors, addonProvider, addons);
await this.handleSyncErrors(installation, getAllResult.errors, addonProvider, addons);
await this.handleSyncResults(getAllResult.searchResults, addons, installation);
}

View File

@@ -1,5 +1,5 @@
import * as _ from "lodash";
import { BehaviorSubject, Subject } from "rxjs";
import { BehaviorSubject, from, Subject } from "rxjs";
import { Injectable } from "@angular/core";
@@ -8,7 +8,7 @@ import { WowInstallation } from "../../../common/warcraft/wow-installation";
import { PreferenceStorageService } from "../storage/preference-storage.service";
import { WarcraftInstallationService } from "../warcraft/warcraft-installation.service";
import { ColumnState } from "../../models/wowup/column-state";
import { map } from "rxjs/operators";
import { map, switchMap } from "rxjs/operators";
import { WowUpAccountService } from "../wowup/wowup-account.service";
import { AddonService } from "../addons/addon.service";
import { AddonProviderFactory } from "../addons/addon.provider.factory";
@@ -70,9 +70,9 @@ export class SessionService {
})
.catch((e) => console.error(e));
this._warcraftInstallationService.wowInstallations$.subscribe((installations) =>
this.onWowInstallationsChange(installations)
);
this._warcraftInstallationService.wowInstallations$
.pipe(switchMap((installations) => from(this.onWowInstallationsChange(installations))))
.subscribe();
this._addonProviderService.addonProviderChange$.subscribe(() => {
this.updateAdSpace();
@@ -128,7 +128,7 @@ export class SessionService {
this._preferenceStorageService.set(SELECTED_DETAILS_TAB_KEY, tabType);
}
public onWowInstallationsChange(wowInstallations: WowInstallation[]): void {
public async onWowInstallationsChange(wowInstallations: WowInstallation[]): Promise<void> {
if (wowInstallations.length === 0) {
this._selectedHomeTabSrc.next(TAB_INDEX_SETTINGS);
return;
@@ -138,7 +138,7 @@ export class SessionService {
if (!selectedInstall) {
selectedInstall = _.first(wowInstallations);
if (selectedInstall) {
this.setSelectedWowInstallation(selectedInstall.id);
await this.setSelectedWowInstallation(selectedInstall.id);
}
}
@@ -172,7 +172,7 @@ export class SessionService {
this._selectedHomeTabSrc.next(tabIndex);
}
public setSelectedWowInstallation(installationId: string): void {
public async setSelectedWowInstallation(installationId: string): Promise<void> {
if (!installationId) {
return;
}
@@ -182,7 +182,7 @@ export class SessionService {
return;
}
this._warcraftInstallationService.setSelectedWowInstallation(installation);
await this._warcraftInstallationService.setSelectedWowInstallation(installation);
this._selectedWowInstallationSrc.next(installation);
}

View File

@@ -248,7 +248,7 @@ export class WarcraftInstallationService {
};
try {
this.addInstallation(wowInstallation, false);
await this.addInstallation(wowInstallation, false);
} catch (e) {
// Ignore duplicate error
}
@@ -338,7 +338,7 @@ export class WarcraftInstallationService {
selected: false,
};
this.addInstallation(installation, false);
await this.addInstallation(installation, false);
return installation;
}