diff --git a/src/addons/block/myoverview/components/myoverview/myoverview.ts b/src/addons/block/myoverview/components/myoverview/myoverview.ts index 3f9ff1f7b..6e7897558 100644 --- a/src/addons/block/myoverview/components/myoverview/myoverview.ts +++ b/src/addons/block/myoverview/components/myoverview/myoverview.ts @@ -711,10 +711,10 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem * @param newCourses New courses. * @return Whether it has meaningful changes. */ - protected coursesHaveMeaningfulChanges( + protected async coursesHaveMeaningfulChanges( previousCourses: CoreEnrolledCourseDataWithExtraInfoAndOptions[], newCourses: CoreEnrolledCourseDataWithExtraInfoAndOptions[], - ): boolean { + ): Promise { if (previousCourses.length !== newCourses.length) { return true; } @@ -746,10 +746,10 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem * @param newCourses New courses. * @return Whether it has meaningful changes. */ - protected customFilterCoursesHaveMeaningfulChanges( + protected async customFilterCoursesHaveMeaningfulChanges( previousCourses: CoreCourseSummaryData[], newCourses: CoreCourseSummaryData[], - ): boolean { + ): Promise { if (previousCourses.length !== newCourses.length) { return true; } diff --git a/src/core/classes/page-load-watcher.ts b/src/core/classes/page-load-watcher.ts index a76594db2..988dca6d1 100644 --- a/src/core/classes/page-load-watcher.ts +++ b/src/core/classes/page-load-watcher.ts @@ -29,6 +29,7 @@ export class PageLoadWatcher { protected ongoingRequests = 0; protected components = new Set(); protected loadedTimeout?: number; + protected hasChangesPromises: Promise[] = []; constructor( protected loadsManager: PageLoadsManager, @@ -97,7 +98,7 @@ export class PageLoadWatcher { */ watchRequest( observable: WSObservable, - hasMeaningfulChanges?: (previousValue: T, newValue: T) => boolean, + hasMeaningfulChanges?: (previousValue: T, newValue: T) => Promise, ): Promise { const promisedValue = new CorePromisedValue(); let subscription: Subscription | null = null; @@ -124,9 +125,11 @@ export class PageLoadWatcher { } // Second value, it means data was updated in background. Compare data. - if (hasMeaningfulChanges?.(firstValue, value)) { - this.hasChanges = true; + if (!hasMeaningfulChanges) { + return; } + + this.hasChangesPromises.push(CoreUtils.ignoreErrors(hasMeaningfulChanges(firstValue, value), false)); }, error: (error) => { promisedValue.reject(error); @@ -150,8 +153,11 @@ export class PageLoadWatcher { // It seems load has finished. Wait to make sure no new component has been rendered and started loading. // If a new component or a new request starts the timeout will be cancelled, no need to double check it. clearTimeout(this.loadedTimeout); - this.loadedTimeout = window.setTimeout(() => { - // Loading finished. + this.loadedTimeout = window.setTimeout(async () => { + // Loading finished. Calculate has changes. + const values = await Promise.all(this.hasChangesPromises); + this.hasChanges = this.hasChanges || values.includes(true); + this.loadsManager.onPageLoaded(this); }, 100); } diff --git a/src/core/classes/site.ts b/src/core/classes/site.ts index c8781f9f7..0af78f156 100644 --- a/src/core/classes/site.ts +++ b/src/core/classes/site.ts @@ -125,7 +125,7 @@ export class CoreSite { protected lastAutoLogin = 0; protected offlineDisabled = false; // eslint-disable-next-line @typescript-eslint/no-explicit-any - protected ongoingRequests: { [cacheId: string]: WSObservable } = {}; + protected ongoingRequests: Record | undefined>> = {}; protected requestQueue: RequestQueueItem[] = []; protected requestQueueTimeout: number | null = null; protected tokenPluginFileWorks?: boolean; @@ -636,9 +636,10 @@ export class CoreSite { const cacheId = this.getCacheId(method, data); - // Check for an ongoing identical request if we're not ignoring cache. - if (preSets.getFromCache && this.ongoingRequests[cacheId] !== undefined) { - return this.ongoingRequests[cacheId]; + // Check for an ongoing identical request. + const ongoingRequest = this.getOngoingRequest(cacheId, preSets); + if (ongoingRequest) { + return ongoingRequest; } const observable = this.performRequest(method, data, preSets, wsPreSets).pipe( @@ -646,18 +647,68 @@ export class CoreSite { map((data) => CoreUtils.clone(data)), ); - this.ongoingRequests[cacheId] = observable; + this.setOngoingRequest(cacheId, preSets, observable); return observable.pipe( finalize(() => { - // Clear the ongoing request unless it has changed (e.g. a new request that ignores cache). - if (this.ongoingRequests[cacheId] === observable) { - delete this.ongoingRequests[cacheId]; - } + this.clearOngoingRequest(cacheId, preSets, observable); }), ); } + /** + * Get an ongoing request if there's one already. + * + * @param cacheId Cache ID. + * @param preSets Presets. + * @return Ongoing request if it exists. + */ + protected getOngoingRequest(cacheId: string, preSets: CoreSiteWSPreSets): WSObservable | undefined { + if (preSets.updateInBackground) { + return this.ongoingRequests[cacheId]?.[OngoingRequestType.UPDATE_IN_BACKGROUND]; + } else if (preSets.getFromCache) { // Only reuse ongoing request when using cache. + return this.ongoingRequests[cacheId]?.[OngoingRequestType.STANDARD]; + } + } + + /** + * Store an ongoing request in memory. + * + * @param cacheId Cache ID. + * @param preSets Presets. + * @param request Request to store. + */ + protected setOngoingRequest(cacheId: string, preSets: CoreSiteWSPreSets, request: WSObservable): void { + this.ongoingRequests[cacheId] = this.ongoingRequests[cacheId] ?? {}; + + if (preSets.updateInBackground) { + this.ongoingRequests[cacheId][OngoingRequestType.UPDATE_IN_BACKGROUND] = request; + } else { + this.ongoingRequests[cacheId][OngoingRequestType.STANDARD] = request; + } + } + + /** + * Clear the ongoing request unless it has changed (e.g. a new request that ignores cache). + * + * @param cacheId Cache ID. + * @param preSets Presets. + * @param request Current request. + */ + protected clearOngoingRequest(cacheId: string, preSets: CoreSiteWSPreSets, request: WSObservable): void { + this.ongoingRequests[cacheId] = this.ongoingRequests[cacheId] ?? {}; + + if (preSets.updateInBackground) { + if (this.ongoingRequests[cacheId][OngoingRequestType.UPDATE_IN_BACKGROUND] === request) { + delete this.ongoingRequests[cacheId][OngoingRequestType.UPDATE_IN_BACKGROUND]; + } + } else { + if (this.ongoingRequests[cacheId][OngoingRequestType.STANDARD] === request) { + delete this.ongoingRequests[cacheId][OngoingRequestType.STANDARD]; + } + } + } + /** * Perform a request, getting the response either from cache or WebService. * @@ -1640,8 +1691,11 @@ export class CoreSite { } // Check for an ongoing identical request if we're not ignoring cache. - if (cachePreSets.getFromCache && this.ongoingRequests[cacheId] !== undefined) { - return await firstValueFrom(this.ongoingRequests[cacheId]); + + // Check for an ongoing identical request. + const ongoingRequest = this.getOngoingRequest(cacheId, cachePreSets); + if (ongoingRequest) { + return firstValueFrom(ongoingRequest); } const subject = new Subject(); @@ -1649,14 +1703,11 @@ export class CoreSite { // Return a clone of the original object, this may prevent errors if in the callback the object is modified. map((data) => CoreUtils.clone(data)), finalize(() => { - // Clear the ongoing request unless it has changed (e.g. a new request that ignores cache). - if (this.ongoingRequests[cacheId] === observable) { - delete this.ongoingRequests[cacheId]; - } + this.clearOngoingRequest(cacheId, cachePreSets, observable); }), ); - this.ongoingRequests[cacheId] = observable; + this.setOngoingRequest(cacheId, cachePreSets, observable); this.getFromCache(method, {}, cachePreSets, false) .then(cachedData => cachedData.response) @@ -1916,16 +1967,16 @@ export class CoreSite { // Return the requested setting. for (const x in config.settings) { if (config.settings[x].name == name) { - return config.settings[x].value; + return String(config.settings[x].value); } } throw new CoreError('Site config not found: ' + name); } else { // Return all settings in the same array. - const settings = {}; + const settings: CoreSiteConfig = {}; config.settings.forEach((setting) => { - settings[setting.name] = setting.value; + settings[setting.name] = String(setting.value); }); return settings; @@ -2686,7 +2737,7 @@ export enum CoreSiteInfoUserHomepage { export type CoreSiteConfigResponse = { settings: { // Settings. name: string; // The name of the setting. - value: string; // The value of the setting. + value: string | number; // The value of the setting. }[]; warnings?: CoreWSExternalWarning[]; }; @@ -2804,3 +2855,11 @@ type WSCachedError = { * Otherwise, it will only return 1 value, either coming from cache or from the server. After this, it will complete. */ export type WSObservable = Observable; + +/** + * Type of ongoing requests stored in memory to avoid duplicating them. + */ +enum OngoingRequestType { + STANDARD = 0, + UPDATE_IN_BACKGROUND = 1, +}