MOBILE-4138 core: Wait for ready on delegates

main
Pau Ferrer Ocaña 2024-11-19 12:46:51 +01:00
parent a5cec5860c
commit bfe8f4c24f
6 changed files with 109 additions and 64 deletions

View File

@ -16,7 +16,6 @@ import { BehaviorSubject, Subject } from 'rxjs';
import { CoreEvents } from '@singletons/events';
import { CoreDelegate, CoreDelegateDisplayHandler, CoreDelegateToDisplay } from './delegate';
import { CoreSites } from '@services/sites';
import { CorePromisedValue } from '@classes/promised-value';
/**
* Superclass to help creating sorted delegates.
@ -26,7 +25,6 @@ export class CoreSortedDelegate<
HandlerType extends CoreDelegateDisplayHandler<DisplayType>>
extends CoreDelegate<HandlerType> {
protected loaded = false;
protected sortedHandlersRxJs: Subject<DisplayType[]> = new BehaviorSubject<DisplayType[]>([]);
protected sortedHandlers: DisplayType[] = [];
@ -53,14 +51,14 @@ export class CoreSortedDelegate<
* @returns True if handlers are loaded, false otherwise.
*/
areHandlersLoaded(): boolean {
return this.loaded;
return this.handlersLoaded;
}
/**
* Clear current site handlers. Reserved for core use.
*/
protected clearSortedHandlers(): void {
this.loaded = false;
this.handlersLoaded = false;
this.sortedHandlersRxJs.next([]);
this.sortedHandlers = [];
}
@ -74,6 +72,13 @@ export class CoreSortedDelegate<
return this.sortedHandlers;
}
/**
* @inheritdoc
*/
hasHandlers(enabled = false): boolean {
return enabled ? !!this.sortedHandlers.length : !!this.handlers.length;
}
/**
* Get the handlers for the current site.
*
@ -89,27 +94,15 @@ export class CoreSortedDelegate<
* @returns Promise resolved with the handlers.
*/
async getHandlersWhenLoaded(): Promise<DisplayType[]> {
if (this.loaded) {
return this.sortedHandlers;
}
await this.waitForReady();
const promisedHandlers = new CorePromisedValue<DisplayType[]>();
const subscription = this.getHandlersObservable().subscribe((handlers) => {
if (this.loaded) {
subscription?.unsubscribe();
// Return main handlers.
promisedHandlers.resolve(handlers);
}
});
return promisedHandlers;
return this.sortedHandlers;
}
/**
* Update handlers Data.
*/
updateData(): void {
protected updateData(): void {
const displayData: DisplayType[] = [];
for (const name in this.enabledHandlers) {
@ -125,7 +118,7 @@ export class CoreSortedDelegate<
// Sort them by priority.
displayData.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
this.loaded = true;
this.handlersLoaded = true;
this.sortedHandlersRxJs.next(displayData);
this.sortedHandlers = displayData;
}

View File

@ -16,6 +16,8 @@ import { CoreSites } from '@services/sites';
import { CoreEvents } from '@singletons/events';
import { CoreSite } from '@classes/sites/site';
import { CoreLogger } from '@singletons/logger';
import { Subject, BehaviorSubject } from 'rxjs';
import { CorePromisedValue } from './promised-value';
/**
* Superclass to help creating delegates
@ -66,21 +68,14 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
protected updatePromises: {[siteId: string]: {[name: string]: Promise<void>}} = {};
/**
* Whether handlers have been initialized.
* Subject to subscribe to handlers changes.
*/
protected handlersInitialized = false;
protected handlersUpdated: Subject<void> = new BehaviorSubject<void>(undefined);
/**
* Promise to wait for handlers to be initialized.
*
* @returns Promise resolved when handlers are enabled.
* Handlers loaded flag.
*/
protected handlersInitPromise: Promise<boolean>;
/**
* Function to resolve the handlers init promise.
*/
protected handlersInitResolve!: (enabled: boolean) => void;
protected handlersLoaded = false;
/**
* Constructor of the Delegate.
@ -90,10 +85,6 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
constructor(delegateName: string) {
this.logger = CoreLogger.getInstance(delegateName);
this.handlersInitPromise = new Promise((resolve): void => {
this.handlersInitResolve = resolve;
});
// Update handlers on this cases.
CoreEvents.on(CoreEvents.LOGIN, () => this.updateHandlers());
CoreEvents.on(CoreEvents.SITE_UPDATED, () => this.updateHandlers());
@ -120,6 +111,7 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
}
/**
* Check if handlers are loaded.
* Execute a certain function in a enabled handler.
* If the handler isn't found or function isn't defined, call the same function in the default handler.
*
@ -216,12 +208,13 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
}
/**
* Check if the delegate has at least 1 registered handler (not necessarily enabled).
* Returns if the delegate has any handler.
*
* @returns If there is at least 1 handler.
* @param enabled Check only enabled handlers or all.
* @returns True if there's any registered handler, false otherwise.
*/
hasHandlers(): boolean {
return Object.keys(this.handlers).length > 0;
hasHandlers(enabled = false): boolean {
return enabled ? !!this.enabledHandlers.length : !!this.handlers.length;
}
/**
@ -324,13 +317,16 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
*
* @returns Resolved when done.
*/
async updateHandlers(): Promise<void> {
protected async updateHandlers(): Promise<void> {
this.handlersLoaded = false;
const enabled = await this.isEnabled();
if (!enabled) {
this.logger.debug('Delegate not enabled.');
this.handlersInitResolve(false);
this.handlersLoaded = true;
this.handlersUpdated.next();
return;
}
@ -355,10 +351,10 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
// Verify that this call is the last one that was started.
if (this.isLastUpdateCall(now)) {
this.handlersInitialized = true;
this.handlersInitResolve(true);
this.updateData();
this.handlersLoaded = true;
this.handlersUpdated.next();
}
}
@ -366,10 +362,34 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> {
* Update handlers Data.
* Override this function to update handlers data.
*/
updateData(): void {
protected updateData(): void {
// To be overridden.
}
/**
* Waits the handlers to be ready.
*
* @returns Resolved when the handlers are ready.
*/
async waitForReady(): Promise<void> {
if (this.handlersLoaded) {
return;
}
const promise = new CorePromisedValue<void>();
const subscription = this.handlersUpdated.subscribe(() => {
if (this.handlersLoaded) {
// Resolve.
promise.resolve();
subscription?.unsubscribe();
}
});
return promise;
}
}
/**

View File

@ -212,7 +212,7 @@ export class CoreBlockDelegateService extends CoreDelegate<CoreBlockHandler> {
* Called when there are new block handlers available. Informs anyone who subscribed to the
* observable.
*/
updateData(): void {
protected updateData(): void {
this.blocksUpdateObservable.next();
}

View File

@ -26,6 +26,8 @@ import {
import { CoreCourseAccessDataType } from '../constants';
import { Params } from '@angular/router';
import { makeSingleton } from '@singletons';
import { Subject } from 'rxjs/internal/Subject';
import { BehaviorSubject } from 'rxjs';
import { CorePromisedValue } from '@classes/promised-value';
import { CORE_COURSES_MY_COURSES_REFRESHED_EVENT } from '@features/courses/constants';
@ -214,7 +216,8 @@ export interface CoreCourseOptionsMenuHandlerToDisplay {
@Injectable( { providedIn: 'root' })
export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOptionsHandler> {
protected loaded: { [courseId: number]: boolean } = {};
protected courseHandlersUpdated: { [courseId: number]: Subject<void> } = {};
protected courseHandlersLoaded: { [courseId: number]: boolean } = {};
protected lastUpdateHandlersForCoursesStart: {
[courseId: number]: number;
} = {};
@ -224,7 +227,6 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
access: CoreCourseAccess;
navOptions?: CoreCourseUserAdminOrNavOptionIndexed;
admOptions?: CoreCourseUserAdminOrNavOptionIndexed;
deferred: CorePromisedValue<void>;
enabledHandlers: CoreCourseOptionsHandler[];
enabledMenuHandlers: CoreCourseOptionsMenuHandler[];
};
@ -247,7 +249,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
* @returns True if handlers are loaded, false otherwise.
*/
areHandlersLoaded(courseId: number): boolean {
return !!this.loaded[courseId];
return !!this.courseHandlersLoaded[courseId];
}
/**
@ -257,12 +259,12 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
*/
protected clearCoursesHandlers(courseId?: number): void {
if (courseId) {
if (!this.loaded[courseId]) {
if (!this.courseHandlersLoaded[courseId]) {
// Don't clear if not loaded, it's probably an ongoing load and it could cause JS errors.
return;
}
this.loaded[courseId] = false;
this.courseHandlersLoaded[courseId] = false;
delete this.coursesHandlers[courseId];
} else {
for (const courseId in this.coursesHandlers) {
@ -321,7 +323,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
): Promise<void> {
// If the handlers aren't loaded, do not refresh.
if (!this.loaded[courseId]) {
if (!this.courseHandlersLoaded[courseId]) {
refresh = false;
}
@ -331,21 +333,20 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
access: accessData,
navOptions,
admOptions,
deferred: new CorePromisedValue(),
enabledHandlers: [],
enabledMenuHandlers: [],
};
this.courseHandlersUpdated[courseId] = new BehaviorSubject<void>(undefined);
} else {
this.coursesHandlers[courseId].access = accessData;
this.coursesHandlers[courseId].navOptions = navOptions;
this.coursesHandlers[courseId].admOptions = admOptions;
this.coursesHandlers[courseId].deferred = new CorePromisedValue();
}
this.updateHandlersForCourse(courseId, accessData, navOptions, admOptions);
}
await this.coursesHandlers[courseId].deferred;
await this.waitCourseHandlersForReady(courseId);
}
/**
@ -612,7 +613,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
/**
* Update handlers for each course.
*/
updateData(): void {
protected updateData(): void {
// Update handlers for all courses.
for (const courseId in this.coursesHandlers) {
const handler = this.coursesHandlers[courseId];
@ -635,6 +636,8 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
): Promise<void> {
this.courseHandlersLoaded[courseId] = false;
const promises: Promise<void>[] = [];
const enabledForCourse: CoreCourseOptionsHandler[] = [];
const enabledForCourseMenu: CoreCourseOptionsMenuHandler[] = [];
@ -675,13 +678,38 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
// Update the coursesHandlers array with the new enabled addons.
this.coursesHandlers[courseId].enabledHandlers = enabledForCourse;
this.coursesHandlers[courseId].enabledMenuHandlers = enabledForCourseMenu;
this.loaded[courseId] = true;
// Resolve the promise.
this.coursesHandlers[courseId].deferred.resolve();
// Notify changes.
this.courseHandlersLoaded[courseId] = true;
this.courseHandlersUpdated[courseId].next();
}
}
/**
* Waits the course handlers to be ready.
*
* @param courseId The course ID.
* @returns Promise resolved when the handlers are ready.
*/
async waitCourseHandlersForReady(courseId: number): Promise<void> {
if (this.courseHandlersLoaded[courseId]) {
return;
}
const promise = new CorePromisedValue<void>();
const subscription = this.courseHandlersUpdated[courseId].subscribe(() => {
if (this.courseHandlersLoaded[courseId]) {
// Resolve.
promise.resolve();
subscription?.unsubscribe();
}
});
return promise;
}
}
export const CoreCourseOptionsDelegate = makeSingleton(CoreCourseOptionsDelegateService);

View File

@ -112,9 +112,9 @@ export class CoreFilterDelegateService extends CoreDelegate<CoreFilterHandler> {
skipFilters?: string[],
siteId?: string,
): Promise<string> {
// Wait for filters to be initialized.
const enabled = await this.handlersInitPromise;
await this.waitForReady();
const enabled = this.hasHandlers(true);
if (!enabled) {
// No enabled filters, return the text.
return text;
@ -201,7 +201,8 @@ export class CoreFilterDelegateService extends CoreDelegate<CoreFilterHandler> {
): Promise<void> {
// Wait for filters to be initialized.
const enabled = await this.handlersInitPromise;
await this.waitForReady();
const enabled = this.hasHandlers(true);
if (!enabled) {
return;
}
@ -276,7 +277,8 @@ export class CoreFilterDelegateService extends CoreDelegate<CoreFilterHandler> {
*/
async shouldBeApplied(filters: CoreFilterFilter[], options: CoreFilterFormatTextOptions, site?: CoreSite): Promise<boolean> {
// Wait for filters to be initialized.
const enabled = await this.handlersInitPromise;
await this.waitForReady();
const enabled = this.hasHandlers(true);
if (!enabled) {
return false;
}

View File

@ -16,6 +16,7 @@ import { mock, mockSingleton } from '@/testing/utils';
import { CoreSite } from '@classes/sites/site';
import { CorePluginFileDelegateService, CorePluginFileHandler } from '@services/plugin-file-delegate';
import { CoreSites } from '@services/sites';
import { CoreEvents } from '@singletons/events';
import { CoreUrl } from '@singletons/url';
describe('CorePluginFileDelegate', () => {
@ -32,7 +33,8 @@ describe('CorePluginFileDelegate', () => {
pluginFileDelegate = new CorePluginFileDelegateService();
pluginFileDelegate.registerHandler(new ModFooRevisionHandler());
await pluginFileDelegate.updateHandlers();
CoreEvents.trigger(CoreEvents.LOGIN, { siteId: '42' }, '42');
await pluginFileDelegate.waitForReady();
});
it('removes revision from a URL', () => {