MOBILE-2317 core: Migrate delegates to CoreDelegate

main
Pau Ferrer Ocaña 2018-01-25 17:28:47 +01:00
parent debfb5c6c6
commit 2319cf8074
7 changed files with 236 additions and 765 deletions

View File

@ -19,7 +19,7 @@ import { CoreEventsProvider } from '../providers/events';
export interface CoreDelegateHandler { export interface CoreDelegateHandler {
/** /**
* Name of the handler, or name and sub context (mmaMessages, mmaMessage:blockContact, ...). * Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...).
* @type {string} * @type {string}
*/ */
name: string; name: string;
@ -28,8 +28,8 @@ export interface CoreDelegateHandler {
* Whether or not the handler is enabled on a site level. * Whether or not the handler is enabled on a site level.
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled on a site level. * @return {boolean|Promise<boolean>} Whether or not the handler is enabled on a site level.
*/ */
isEnabled(): boolean|Promise<boolean>; isEnabled(): boolean | Promise<boolean>;
}; }
/** /**
* Superclass to help creating delegates * Superclass to help creating delegates
@ -47,13 +47,13 @@ export class CoreDelegate {
* List of registered handlers. * List of registered handlers.
* @type {any} * @type {any}
*/ */
protected handlers: {[s: string]: CoreDelegateHandler} = {}; protected handlers: { [s: string]: CoreDelegateHandler } = {};
/** /**
* List of registered handlers enabled for the current site. * List of registered handlers enabled for the current site.
* @type {any} * @type {any}
*/ */
protected enabledHandlers: {[s: string]: CoreDelegateHandler} = {}; protected enabledHandlers: { [s: string]: CoreDelegateHandler } = {};
/** /**
* Default handler * Default handler
@ -80,15 +80,19 @@ export class CoreDelegate {
* @param {string} delegateName Delegate name used for logging purposes. * @param {string} delegateName Delegate name used for logging purposes.
* @param {CoreLoggerProvider} loggerProvider CoreLoggerProvider instance, cannot be directly injected. * @param {CoreLoggerProvider} loggerProvider CoreLoggerProvider instance, cannot be directly injected.
* @param {CoreSitesProvider} sitesProvider CoreSitesProvider instance, cannot be directly injected. * @param {CoreSitesProvider} sitesProvider CoreSitesProvider instance, cannot be directly injected.
* @param {CoreEventsProvider} [eventsProvider] CoreEventsProvider instance, cannot be directly injected.
* If not set, no events will be fired.
*/ */
constructor(delegateName: string, protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, constructor(delegateName: string, protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider,
protected eventsProvider: CoreEventsProvider) { protected eventsProvider?: CoreEventsProvider) {
this.logger = this.loggerProvider.getInstance(delegateName); this.logger = this.loggerProvider.getInstance(delegateName);
// Update handlers on this cases. if (eventsProvider) {
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this)); // Update handlers on this cases.
eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this)); eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this)); eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this));
}
} }
/** /**
@ -100,7 +104,7 @@ export class CoreDelegate {
* @param {any[]} params Parameters to pass to the function. * @param {any[]} params Parameters to pass to the function.
* @return {any} Function returned value or default value. * @return {any} Function returned value or default value.
*/ */
protected executeFunctionOnEnabled(handlerName: string, fnName: string, params?: any[]) : any { protected executeFunctionOnEnabled(handlerName: string, fnName: string, params?: any[]): any {
return this.execute(this.enabledHandlers[handlerName], fnName, params); return this.execute(this.enabledHandlers[handlerName], fnName, params);
} }
@ -113,7 +117,7 @@ export class CoreDelegate {
* @param {any[]} params Parameters to pass to the function. * @param {any[]} params Parameters to pass to the function.
* @return {any} Function returned value or default value. * @return {any} Function returned value or default value.
*/ */
protected executeFunction(handlerName: string, fnName: string, params?: any[]) : any { protected executeFunction(handlerName: string, fnName: string, params?: any[]): any {
return this.execute(this.handlers[handlerName], fnName, params); return this.execute(this.handlers[handlerName], fnName, params);
} }
@ -126,7 +130,7 @@ export class CoreDelegate {
* @param {any[]} params Parameters to pass to the function. * @param {any[]} params Parameters to pass to the function.
* @return {any} Function returned value or default value. * @return {any} Function returned value or default value.
*/ */
private execute(handler: any, fnName: string, params?: any[]) : any { private execute(handler: any, fnName: string, params?: any[]): any {
if (handler && handler[fnName]) { if (handler && handler[fnName]) {
return handler[fnName].apply(handler, params); return handler[fnName].apply(handler, params);
} else if (this.defaultHandler && this.defaultHandler[fnName]) { } else if (this.defaultHandler && this.defaultHandler[fnName]) {
@ -163,7 +167,7 @@ export class CoreDelegate {
* @param {number} time Time to check. * @param {number} time Time to check.
* @return {boolean} Whether it's the last call. * @return {boolean} Whether it's the last call.
*/ */
isLastUpdateCall(time: number) : boolean { isLastUpdateCall(time: number): boolean {
if (!this.lastUpdateHandlersStart) { if (!this.lastUpdateHandlersStart) {
return true; return true;
} }
@ -194,7 +198,7 @@ export class CoreDelegate {
* @param {number} time Time this update process started. * @param {number} time Time this update process started.
* @return {Promise<void>} Resolved when done. * @return {Promise<void>} Resolved when done.
*/ */
protected updateHandler(handler: CoreDelegateHandler, time: number) : Promise<void> { protected updateHandler(handler: CoreDelegateHandler, time: number): Promise<void> {
let promise, let promise,
siteId = this.sitesProvider.getCurrentSiteId(), siteId = this.sitesProvider.getCurrentSiteId(),
currentSite = this.sitesProvider.getCurrentSite(); currentSite = this.sitesProvider.getCurrentSite();
@ -230,7 +234,7 @@ export class CoreDelegate {
* @param {any} site Site to check. * @param {any} site Site to check.
* @return {boolean} Whether is enabled or disabled in site. * @return {boolean} Whether is enabled or disabled in site.
*/ */
protected isFeatureDisabled(handler: CoreDelegateHandler, site: any) : boolean{ protected isFeatureDisabled(handler: CoreDelegateHandler, site: any): boolean {
return typeof this.featurePrefix != "undefined" && site.isFeatureDisabled(this.featurePrefix + handler.name); return typeof this.featurePrefix != "undefined" && site.isFeatureDisabled(this.featurePrefix + handler.name);
} }
@ -239,7 +243,7 @@ export class CoreDelegate {
* *
* @return {Promise<void>} Resolved when done. * @return {Promise<void>} Resolved when done.
*/ */
protected updateHandlers() : Promise<void> { protected updateHandlers(): Promise<void> {
let promises = [], let promises = [],
now = Date.now(); now = Date.now();

View File

@ -19,24 +19,12 @@ import { CoreLoggerProvider } from '../../../providers/logger';
import { CoreSitesProvider } from '../../../providers/sites'; import { CoreSitesProvider } from '../../../providers/sites';
import { CoreCourseProvider } from './course'; import { CoreCourseProvider } from './course';
import { CoreCourseFormatDefaultHandler } from './default-format'; import { CoreCourseFormatDefaultHandler } from './default-format';
import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate';
/** /**
* Interface that all course format handlers must implement. * Interface that all course format handlers must implement.
*/ */
export interface CoreCourseFormatHandler { export interface CoreCourseFormatHandler extends CoreDelegateHandler {
/**
* Name of the format. It should match the "format" returned in core_course_get_courses.
* @type {string}
*/
name: string;
/**
* Whether or not the handler is enabled on a site level.
*
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/
isEnabled(): boolean|Promise<boolean>;
/** /**
* Get the title to use in course page. If not defined, course fullname. * Get the title to use in course page. If not defined, course fullname.
* This function will be called without sections first, and then call it again when the sections are retrieved. * This function will be called without sections first, and then call it again when the sections are retrieved.
@ -45,7 +33,7 @@ export interface CoreCourseFormatHandler {
* @param {any[]} [sections] List of sections. * @param {any[]} [sections] List of sections.
* @return {string} Title. * @return {string} Title.
*/ */
getCourseTitle?(course: any, sections?: any[]) : string; getCourseTitle?(course: any, sections?: any[]): string;
/** /**
* Whether it allows seeing all sections at the same time. Defaults to true. * Whether it allows seeing all sections at the same time. Defaults to true.
@ -53,7 +41,7 @@ export interface CoreCourseFormatHandler {
* @param {any} course The course to check. * @param {any} course The course to check.
* @type {boolean} Whether it can view all sections. * @type {boolean} Whether it can view all sections.
*/ */
canViewAllSections?(course: any) : boolean; canViewAllSections?(course: any): boolean;
/** /**
* Whether the default section selector should be displayed. Defaults to true. * Whether the default section selector should be displayed. Defaults to true.
@ -61,7 +49,7 @@ export interface CoreCourseFormatHandler {
* @param {any} course The course to check. * @param {any} course The course to check.
* @type {boolean} Whether the default section selector should be displayed. * @type {boolean} Whether the default section selector should be displayed.
*/ */
displaySectionSelector?(course: any) : boolean; displaySectionSelector?(course: any): boolean;
/** /**
* Given a list of sections, get the "current" section that should be displayed first. Defaults to first section. * Given a list of sections, get the "current" section that should be displayed first. Defaults to first section.
@ -71,7 +59,7 @@ export interface CoreCourseFormatHandler {
* @return {any|Promise<any>} Current section (or promise resolved with current section). If a promise is returned, it should * @return {any|Promise<any>} Current section (or promise resolved with current section). If a promise is returned, it should
* never fail. * never fail.
*/ */
getCurrentSection?(course: any, sections: any[]) : any|Promise<any>; getCurrentSection?(course: any, sections: any[]): any | Promise<any>;
/** /**
* Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened. * Open the page to display a course. If not defined, the page CoreCourseSectionPage will be opened.
@ -83,7 +71,7 @@ export interface CoreCourseFormatHandler {
* @param {any} course The course to open. It should contain a "format" attribute. * @param {any} course The course to open. It should contain a "format" attribute.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
openCourse?(navCtrl: NavController, course: any) : Promise<any>; openCourse?(navCtrl: NavController, course: any): Promise<any>;
/** /**
* Return the Component to use to display the course format instead of using the default one. * Return the Component to use to display the course format instead of using the default one.
@ -93,7 +81,7 @@ export interface CoreCourseFormatHandler {
* @param {any} course The course to render. * @param {any} course The course to render.
* @return {any} The component to use, undefined if not found. * @return {any} The component to use, undefined if not found.
*/ */
getCourseFormatComponent?(course: any) : any; getCourseFormatComponent?(course: any): any;
/** /**
* Return the Component to use to display the course summary inside the default course format. * Return the Component to use to display the course summary inside the default course format.
@ -135,26 +123,21 @@ export interface CoreCourseFormatHandler {
* @param {any[]} sections List of sections. * @param {any[]} sections List of sections.
* @return {Promise<any>} Promise resolved when the data is invalidated. * @return {Promise<any>} Promise resolved when the data is invalidated.
*/ */
invalidateData?(course: any, sections: any[]) : Promise<any>; invalidateData?(course: any, sections: any[]): Promise<any>;
}; }
/** /**
* Service to interact with course formats. Provides the functions to register and interact with the addons. * Service to interact with course formats. Provides the functions to register and interact with the addons.
*/ */
@Injectable() @Injectable()
export class CoreCourseFormatDelegate { export class CoreCourseFormatDelegate extends CoreDelegate {
protected logger; protected handlers: { [s: string]: CoreCourseFormatHandler } = {}; // All registered handlers.
protected handlers: {[s: string]: CoreCourseFormatHandler} = {}; // All registered handlers. protected enabledHandlers: { [s: string]: CoreCourseFormatHandler } = {}; // Handlers enabled for the current site.
protected enabledHandlers: {[s: string]: CoreCourseFormatHandler} = {}; // Handlers enabled for the current site. protected featurePrefix = 'CoreCourseFormatHandler_';
protected lastUpdateHandlersStart: number;
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
private defaultHandler: CoreCourseFormatDefaultHandler) { protected defaultHandler: CoreCourseFormatDefaultHandler) {
this.logger = logger.getInstance('CoreCoursesCourseFormatDelegate'); super('CoreCoursesCourseFormatDelegate', loggerProvider, sitesProvider, eventsProvider);
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this));
} }
/** /**
@ -163,7 +146,7 @@ export class CoreCourseFormatDelegate {
* @param {any} course The course to check. * @param {any} course The course to check.
* @return {boolean} Whether it allows seeing all sections at the same time. * @return {boolean} Whether it allows seeing all sections at the same time.
*/ */
canViewAllSections(course: any) : boolean { canViewAllSections(course: any): boolean {
return this.executeFunction(course.format, 'canViewAllSections', [course]); return this.executeFunction(course.format, 'canViewAllSections', [course]);
} }
@ -173,7 +156,7 @@ export class CoreCourseFormatDelegate {
* @param {any} course The course to check. * @param {any} course The course to check.
* @return {boolean} Whether the section selector should be displayed. * @return {boolean} Whether the section selector should be displayed.
*/ */
displaySectionSelector(course: any) : boolean { displaySectionSelector(course: any): boolean {
return this.executeFunction(course.format, 'displaySectionSelector', [course]); return this.executeFunction(course.format, 'displaySectionSelector', [course]);
} }
@ -186,7 +169,7 @@ export class CoreCourseFormatDelegate {
* @param {any[]} params Parameters to pass to the function. * @param {any[]} params Parameters to pass to the function.
* @return {any} Function returned value or default value. * @return {any} Function returned value or default value.
*/ */
protected executeFunction(format: string, fnName: string, params?: any[]) : any { protected executeFunction(format: string, fnName: string, params?: any[]): any {
let handler = this.enabledHandlers[format]; let handler = this.enabledHandlers[format];
if (handler && handler[fnName]) { if (handler && handler[fnName]) {
return handler[fnName].apply(handler, params); return handler[fnName].apply(handler, params);
@ -201,7 +184,7 @@ export class CoreCourseFormatDelegate {
* @param {any} course The course to render. * @param {any} course The course to render.
* @return {any} The component to use, undefined if not found. * @return {any} The component to use, undefined if not found.
*/ */
getAllSectionsComponent(course: any) : any { getAllSectionsComponent(course: any): any {
return this.executeFunction(course.format, 'getAllSectionsComponent', [course]); return this.executeFunction(course.format, 'getAllSectionsComponent', [course]);
} }
@ -211,7 +194,7 @@ export class CoreCourseFormatDelegate {
* @param {any} course The course to render. * @param {any} course The course to render.
* @return {any} The component to use, undefined if not found. * @return {any} The component to use, undefined if not found.
*/ */
getCourseFormatComponent(course: any) : any { getCourseFormatComponent(course: any): any {
return this.executeFunction(course.format, 'getCourseFormatComponent', [course]); return this.executeFunction(course.format, 'getCourseFormatComponent', [course]);
} }
@ -221,7 +204,7 @@ export class CoreCourseFormatDelegate {
* @param {any} course The course to render. * @param {any} course The course to render.
* @return {any} The component to use, undefined if not found. * @return {any} The component to use, undefined if not found.
*/ */
getCourseSummaryComponent(course: any) : any { getCourseSummaryComponent(course: any): any {
return this.executeFunction(course.format, 'getCourseSummaryComponent', [course]); return this.executeFunction(course.format, 'getCourseSummaryComponent', [course]);
} }
@ -232,7 +215,7 @@ export class CoreCourseFormatDelegate {
* @param {any[]} [sections] List of sections. * @param {any[]} [sections] List of sections.
* @return {string} Course title. * @return {string} Course title.
*/ */
getCourseTitle(course: any, sections?: any[]) : string { getCourseTitle(course: any, sections?: any[]): string {
return this.executeFunction(course.format, 'getCourseTitle', [course, sections]); return this.executeFunction(course.format, 'getCourseTitle', [course, sections]);
} }
@ -243,13 +226,14 @@ export class CoreCourseFormatDelegate {
* @param {any[]} sections List of sections. * @param {any[]} sections List of sections.
* @return {Promise<any>} Promise resolved with current section. * @return {Promise<any>} Promise resolved with current section.
*/ */
getCurrentSection(course: any, sections: any[]) : Promise<any> { getCurrentSection(course: any, sections: any[]): Promise<any> {
// Convert the result to a Promise if it isn't. // Convert the result to a Promise if it isn't.
return Promise.resolve(this.executeFunction(course.format, 'getCurrentSection', [course, sections])).catch(() => { return Promise.resolve(this.executeFunction(course.format, 'getCurrentSection', [course, sections])).catch(() => {
// This function should never fail. Just return the first section. // This function should never fail. Just return the first section.
if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) { if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) {
return sections[0]; return sections[0];
} }
return sections[1]; return sections[1];
}); });
} }
@ -260,7 +244,7 @@ export class CoreCourseFormatDelegate {
* @param {any} course The course to render. * @param {any} course The course to render.
* @return {any} The component to use, undefined if not found. * @return {any} The component to use, undefined if not found.
*/ */
getSectionSelectorComponent(course: any) : any { getSectionSelectorComponent(course: any): any {
return this.executeFunction(course.format, 'getSectionSelectorComponent', [course]); return this.executeFunction(course.format, 'getSectionSelectorComponent', [course]);
} }
@ -271,7 +255,7 @@ export class CoreCourseFormatDelegate {
* @param {any} course The course to render. * @param {any} course The course to render.
* @return {any} The component to use, undefined if not found. * @return {any} The component to use, undefined if not found.
*/ */
getSingleSectionComponent(course: any) : any { getSingleSectionComponent(course: any): any {
return this.executeFunction(course.format, 'getSingleSectionComponent', [course]); return this.executeFunction(course.format, 'getSingleSectionComponent', [course]);
} }
@ -282,24 +266,10 @@ export class CoreCourseFormatDelegate {
* @param {any[]} sections List of sections. * @param {any[]} sections List of sections.
* @return {Promise<any>} Promise resolved when the data is invalidated. * @return {Promise<any>} Promise resolved when the data is invalidated.
*/ */
invalidateData(course: any, sections: any[]) : Promise<any> { invalidateData(course: any, sections: any[]): Promise<any> {
return this.executeFunction(course.format, 'invalidateData', [course, sections]); return this.executeFunction(course.format, 'invalidateData', [course, sections]);
} }
/**
* Check if a time belongs to the last update handlers call.
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
*
* @param {number} time Time to check.
* @return {boolean} Whether it's the last call.
*/
isLastUpdateCall(time: number) : boolean {
if (!this.lastUpdateHandlersStart) {
return true;
}
return time == this.lastUpdateHandlersStart;
}
/** /**
* Open a course. * Open a course.
* *
@ -307,85 +277,11 @@ export class CoreCourseFormatDelegate {
* @param {any} course The course to open. It should contain a "format" attribute. * @param {any} course The course to open. It should contain a "format" attribute.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
openCourse(navCtrl: NavController, course: any) : Promise<any> { openCourse(navCtrl: NavController, course: any): Promise<any> {
if (this.enabledHandlers[course.format] && this.enabledHandlers[course.format].openCourse) { if (this.enabledHandlers[course.format] && this.enabledHandlers[course.format].openCourse) {
return this.enabledHandlers[course.format].openCourse(navCtrl, course); return this.enabledHandlers[course.format].openCourse(navCtrl, course);
} }
return navCtrl.push('CoreCourseSectionPage', {course: course});
}
/** return navCtrl.push('CoreCourseSectionPage', { course: course });
* Register a handler.
*
* @param {CoreCourseFormatHandler} handler The handler to register.
* @return {boolean} True if registered successfully, false otherwise.
*/
registerHandler(handler: CoreCourseFormatHandler) : boolean {
if (typeof this.handlers[handler.name] !== 'undefined') {
this.logger.log(`Addon '${handler.name}' already registered`);
return false;
}
this.logger.log(`Registered addon '${handler.name}'`);
this.handlers[handler.name] = handler;
return true;
}
/**
* Update the handler for the current site.
*
* @param {CoreCourseFormatHandler} handler The handler to check.
* @param {number} time Time this update process started.
* @return {Promise<void>} Resolved when done.
*/
protected updateHandler(handler: CoreCourseFormatHandler, time: number) : Promise<void> {
let promise,
siteId = this.sitesProvider.getCurrentSiteId(),
currentSite = this.sitesProvider.getCurrentSite();
if (!this.sitesProvider.isLoggedIn()) {
promise = Promise.reject(null);
} else if (currentSite.isFeatureDisabled('CoreCourseFormatHandler_' + handler.name)) {
promise = Promise.resolve(false);
} else {
promise = Promise.resolve(handler.isEnabled());
}
// Checks if the handler is enabled.
return promise.catch(() => {
return false;
}).then((enabled: boolean) => {
// Verify that this call is the last one that was started.
// Check that site hasn't changed since the check started.
if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() === siteId) {
if (enabled) {
this.enabledHandlers[handler.name] = handler;
} else {
delete this.enabledHandlers[handler.name];
}
}
});
}
/**
* Update the handlers for the current site.
*
* @return {Promise<any>} Resolved when done.
*/
protected updateHandlers() : Promise<any> {
let promises = [],
now = Date.now();
this.logger.debug('Updating handlers for current site.');
this.lastUpdateHandlersStart = now;
// Loop over all the handlers.
for (let name in this.handlers) {
promises.push(this.updateHandler(this.handlers[name], now));
}
return Promise.all(promises).catch(() => {
// Never reject.
});
} }
} }

View File

@ -19,30 +19,12 @@ import { CoreLoggerProvider } from '../../../providers/logger';
import { CoreSitesProvider } from '../../../providers/sites'; import { CoreSitesProvider } from '../../../providers/sites';
import { CoreCourseProvider } from './course'; import { CoreCourseProvider } from './course';
import { CoreSite } from '../../../classes/site'; import { CoreSite } from '../../../classes/site';
import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate';
/** /**
* Interface that all course module handlers must implement. * Interface that all course module handlers must implement.
*/ */
export interface CoreCourseModuleHandler { export interface CoreCourseModuleHandler extends CoreDelegateHandler {
/**
* A name to identify the addon.
* @type {string}
*/
name: string;
/**
* Name of the module. It should match the "modname" of the module returned in core_course_get_contents.
* @type {string}
*/
modname: string;
/**
* Whether or not the handler is enabled on a site level.
*
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/
isEnabled(): boolean|Promise<boolean>;
/** /**
* Get the data required to display the module in the course contents view. * Get the data required to display the module in the course contents view.
* *
@ -51,7 +33,7 @@ export interface CoreCourseModuleHandler {
* @param {number} sectionId The section ID. * @param {number} sectionId The section ID.
* @return {CoreCourseModuleHandlerData} Data to render the module. * @return {CoreCourseModuleHandlerData} Data to render the module.
*/ */
getData(module: any, courseId: number, sectionId: number) : CoreCourseModuleHandlerData; getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData;
/** /**
* Get the component to render the module. This is needed to support singleactivity course format. * Get the component to render the module. This is needed to support singleactivity course format.
@ -60,7 +42,7 @@ export interface CoreCourseModuleHandler {
* @param {any} module The module object. * @param {any} module The module object.
* @return {any} The component to use, undefined if not found. * @return {any} The component to use, undefined if not found.
*/ */
getMainComponent(course: any, module: any) : any; getMainComponent(course: any, module: any): any;
}; };
/** /**
@ -106,8 +88,8 @@ export interface CoreCourseModuleHandlerData {
* @param {number} courseId The course ID. * @param {number} courseId The course ID.
* @param {NavOptions} [options] Options for the navigation. * @param {NavOptions} [options] Options for the navigation.
*/ */
action?(event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions) : void; action?(event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions): void;
}; }
/** /**
* A button to display in a module item. * A button to display in a module item.
@ -151,26 +133,21 @@ export interface CoreCourseModuleHandlerButton {
* @param {any} module The module object. * @param {any} module The module object.
* @param {number} courseId The course ID. * @param {number} courseId The course ID.
*/ */
action(event: Event, navCtrl: NavController, module: any, courseId: number) : void; action(event: Event, navCtrl: NavController, module: any, courseId: number): void;
}; };
/** /**
* Delegate to register module handlers. * Delegate to register module handlers.
*/ */
@Injectable() @Injectable()
export class CoreCourseModuleDelegate { export class CoreCourseModuleDelegate extends CoreDelegate {
protected logger; protected handlers: { [s: string]: CoreCourseModuleHandler } = {}; // All registered handlers.
protected handlers: {[s: string]: CoreCourseModuleHandler} = {}; // All registered handlers. protected enabledHandlers: { [s: string]: CoreCourseModuleHandler } = {}; // Handlers enabled for the current site.
protected enabledHandlers: {[s: string]: CoreCourseModuleHandler} = {}; // Handlers enabled for the current site. protected featurePrefix = '$mmCourseDelegate_';
protected lastUpdateHandlersStart: number;
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
private courseProvider: CoreCourseProvider) { protected courseProvider: CoreCourseProvider) {
this.logger = logger.getInstance('CoreCourseModuleDelegate'); super('CoreCourseModuleDelegate', loggerProvider, sitesProvider, eventsProvider);
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this));
} }
/** /**
@ -180,7 +157,7 @@ export class CoreCourseModuleDelegate {
* @param {any} module The module object. * @param {any} module The module object.
* @return {any} The component to use, undefined if not found. * @return {any} The component to use, undefined if not found.
*/ */
getMainComponent?(course: any, module: any) : any { getMainComponent?(course: any, module: any): any {
let handler = this.enabledHandlers[module.modname]; let handler = this.enabledHandlers[module.modname];
if (handler && handler.getMainComponent) { if (handler && handler.getMainComponent) {
let component = handler.getMainComponent(course, module); let component = handler.getMainComponent(course, module);
@ -199,7 +176,7 @@ export class CoreCourseModuleDelegate {
* @param {number} sectionId The section ID. * @param {number} sectionId The section ID.
* @return {CoreCourseModuleHandlerData} Data to render the module. * @return {CoreCourseModuleHandlerData} Data to render the module.
*/ */
getModuleDataFor(modname: string, module: any, courseId: number, sectionId: number) : CoreCourseModuleHandlerData { getModuleDataFor(modname: string, module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData {
if (typeof this.enabledHandlers[modname] != 'undefined') { if (typeof this.enabledHandlers[modname] != 'undefined') {
return this.enabledHandlers[modname].getData(module, courseId, sectionId); return this.enabledHandlers[modname].getData(module, courseId, sectionId);
} }
@ -213,7 +190,7 @@ export class CoreCourseModuleDelegate {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
navCtrl.push('CoreCourseUnsupportedModulePage', {module: module}, options); navCtrl.push('CoreCourseUnsupportedModulePage', { module: module }, options);
} }
}; };
@ -230,16 +207,6 @@ export class CoreCourseModuleDelegate {
} }
return defaultData; return defaultData;
};
/**
* Check if a module has a registered handler (not necessarily enabled).
*
* @param {string} modname The name of the module type.
* @return {boolean} If the controller is installed or not.
*/
hasHandler(modname: string) : boolean {
return typeof this.handlers[modname] !== 'undefined';
} }
/** /**
@ -249,7 +216,7 @@ export class CoreCourseModuleDelegate {
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: whether module is disabled. * @return {Promise<boolean>} Promise resolved with boolean: whether module is disabled.
*/ */
isModuleDisabled(modname: string, siteId?: string) : Promise<boolean> { isModuleDisabled(modname: string, siteId?: string): Promise<boolean> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
return this.isModuleDisabledInSite(modname, site); return this.isModuleDisabledInSite(modname, site);
}); });
@ -262,101 +229,13 @@ export class CoreCourseModuleDelegate {
* @param {CoreSite} [site] Site. If not defined, use current site. * @param {CoreSite} [site] Site. If not defined, use current site.
* @return {boolean} Whether module is disabled. * @return {boolean} Whether module is disabled.
*/ */
isModuleDisabledInSite(modname: string, site?: CoreSite) : boolean { isModuleDisabledInSite(modname: string, site?: CoreSite): boolean {
site = site || this.sitesProvider.getCurrentSite();
if (typeof this.handlers[modname] != 'undefined') { if (typeof this.handlers[modname] != 'undefined') {
return site.isFeatureDisabled('$mmCourseDelegate_' + this.handlers[modname].name); site = site || this.sitesProvider.getCurrentSite();
return this.isFeatureDisabled(this.handlers[modname], site);
} }
return false; return false;
} }
/**
* Check if a time belongs to the last update handlers call.
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
*
* @param {number} time Time to check.
* @return {boolean} Whether it's the last call.
*/
isLastUpdateCall(time: number) : boolean {
if (!this.lastUpdateHandlersStart) {
return true;
}
return time == this.lastUpdateHandlersStart;
}
/**
* Register a handler.
*
* @param {CoreCourseModuleHandler} handler The handler to register.
* @return {boolean} True if registered successfully, false otherwise.
*/
registerHandler(handler: CoreCourseModuleHandler) : boolean {
if (typeof this.handlers[handler.modname] !== 'undefined') {
this.logger.log('There is an addon named \'' + this.handlers[handler.modname].name +
'\' already registered as handler for ' + handler.modname);
return false;
}
this.logger.log(`Registered addon '${handler.name}' for '${handler.modname}'`);
this.handlers[handler.modname] = handler;
return true;
}
/**
* Update the handler for the current site.
*
* @param {CoreCourseModuleHandler} handler The handler to check.
* @param {number} time Time this update process started.
* @return {Promise<void>} Resolved when done.
*/
protected updateHandler(handler: CoreCourseModuleHandler, time: number) : Promise<void> {
let promise,
siteId = this.sitesProvider.getCurrentSiteId(),
currentSite = this.sitesProvider.getCurrentSite();
if (!this.sitesProvider.isLoggedIn()) {
promise = Promise.reject(null);
} else if (currentSite.isFeatureDisabled('$mmCourseDelegate_' + handler.name)) {
promise = Promise.resolve(false);
} else {
promise = Promise.resolve(handler.isEnabled());
}
// Checks if the handler is enabled.
return promise.catch(() => {
return false;
}).then((enabled: boolean) => {
// Verify that this call is the last one that was started.
if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() === siteId) {
if (enabled) {
this.enabledHandlers[handler.modname] = handler;
} else {
delete this.enabledHandlers[handler.modname];
}
}
});
}
/**
* Update the handlers for the current site.
*
* @return {Promise<any>} Resolved when done.
*/
protected updateHandlers() : Promise<any> {
let promises = [],
now = Date.now();
this.logger.debug('Updating handlers for current site.');
this.lastUpdateHandlersStart = now;
// Loop over all the handlers.
for (let name in this.handlers) {
promises.push(this.updateHandler(this.handlers[name], now));
}
return Promise.all(promises).catch(() => {
// Never reject.
});
}
} }

View File

@ -26,6 +26,7 @@ import { CoreSiteWSPreSets } from '../../../classes/site';
import { CoreConstants } from '../../constants'; import { CoreConstants } from '../../constants';
import { Md5 } from 'ts-md5/dist/md5'; import { Md5 } from 'ts-md5/dist/md5';
import { Subject, BehaviorSubject, Subscription } from 'rxjs'; import { Subject, BehaviorSubject, Subscription } from 'rxjs';
import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate';
/** /**
* Progress of downloading a list of modules. * Progress of downloading a list of modules.
@ -54,19 +55,7 @@ export type CoreCourseModulesProgressFunction = (data: CoreCourseModulesProgress
/** /**
* Interface that all course prefetch handlers must implement. * Interface that all course prefetch handlers must implement.
*/ */
export interface CoreCourseModulePrefetchHandler { export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler {
/**
* A name to identify the addon.
* @type {string}
*/
name: string;
/**
* Name of the module. It should match the "modname" of the module returned in core_course_get_contents.
* @type {string}
*/
modname: string;
/** /**
* The handler's component. * The handler's component.
* @type {string} * @type {string}
@ -80,13 +69,6 @@ export interface CoreCourseModulePrefetchHandler {
*/ */
updatesNames?: RegExp; updatesNames?: RegExp;
/**
* Whether or not the handler is enabled on a site level.
*
* @return {boolean|Promise<boolean>} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled.
*/
isEnabled() : boolean|Promise<boolean>;
/** /**
* Get the download size of a module. * Get the download size of a module.
* *
@ -96,7 +78,7 @@ export interface CoreCourseModulePrefetchHandler {
* @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able * @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able
* to calculate the total size. * to calculate the total size.
*/ */
getDownloadSize(module: any, courseId: number, single?: boolean) : Promise<{size: number, total: boolean}>; getDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }>;
/** /**
* Prefetch a module. * Prefetch a module.
@ -117,7 +99,7 @@ export interface CoreCourseModulePrefetchHandler {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {boolean|Promise<boolean>} Whether the module can use check_updates. The promise should never be rejected. * @return {boolean|Promise<boolean>} Whether the module can use check_updates. The promise should never be rejected.
*/ */
canUseCheckUpdates?(module: any, courseId: number) : boolean|Promise<boolean>; canUseCheckUpdates?(module: any, courseId: number): boolean | Promise<boolean>;
/** /**
* Return the status to show based on current status. E.g. a module might want to show outdated instead of downloaded. * Return the status to show based on current status. E.g. a module might want to show outdated instead of downloaded.
@ -128,7 +110,7 @@ export interface CoreCourseModulePrefetchHandler {
* @param {boolean} canCheck Whether the site allows checking for updates. * @param {boolean} canCheck Whether the site allows checking for updates.
* @return {string} Status to display. * @return {string} Status to display.
*/ */
determineStatus?(module: any, status: string, canCheck: boolean) : string; determineStatus?(module: any, status: string, canCheck: boolean): string;
/** /**
* Get the downloaded size of a module. If not defined, we'll use getFiles to calculate it (it can be slow). * Get the downloaded size of a module. If not defined, we'll use getFiles to calculate it (it can be slow).
@ -137,7 +119,7 @@ export interface CoreCourseModulePrefetchHandler {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {number|Promise<number>} Size, or promise resolved with the size. * @return {number|Promise<number>} Size, or promise resolved with the size.
*/ */
getDownloadedSize?(module: any, courseId: number) : number|Promise<number>; getDownloadedSize?(module: any, courseId: number): number | Promise<number>;
/** /**
* Get the list of files of the module. If not defined, we'll assume they are in module.contents. * Get the list of files of the module. If not defined, we'll assume they are in module.contents.
@ -146,7 +128,7 @@ export interface CoreCourseModulePrefetchHandler {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {any[]|Promise<any[]>} List of files, or promise resolved with the files. * @return {any[]|Promise<any[]>} List of files, or promise resolved with the files.
*/ */
getFiles?(module: any, courseId: number) : any[]|Promise<any[]>; getFiles?(module: any, courseId: number): any[] | Promise<any[]>;
/** /**
* Check if a certain module has updates based on the result of check updates. * Check if a certain module has updates based on the result of check updates.
@ -156,7 +138,7 @@ export interface CoreCourseModulePrefetchHandler {
* @param {any[]} moduleUpdates List of updates for the module. * @param {any[]} moduleUpdates List of updates for the module.
* @return {boolean|Promise<boolean>} Whether the module has updates. The promise should never be rejected. * @return {boolean|Promise<boolean>} Whether the module has updates. The promise should never be rejected.
*/ */
hasUpdates?(module: any, courseId: number, moduleUpdates: any[]) : boolean|Promise<boolean>; hasUpdates?(module: any, courseId: number, moduleUpdates: any[]): boolean | Promise<boolean>;
/** /**
* Invalidate WS calls needed to determine module status. It doesn't need to invalidate check updates. * Invalidate WS calls needed to determine module status. It doesn't need to invalidate check updates.
@ -166,7 +148,7 @@ export interface CoreCourseModulePrefetchHandler {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {Promise<any>} Promise resolved when invalidated. * @return {Promise<any>} Promise resolved when invalidated.
*/ */
invalidateModule?(module: any, courseId: number) : Promise<any>; invalidateModule?(module: any, courseId: number): Promise<any>;
/** /**
* Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable. * Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable.
@ -175,7 +157,7 @@ export interface CoreCourseModulePrefetchHandler {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {boolean|Promise<boolean>} Whether the module can be downloaded. The promise should never be rejected. * @return {boolean|Promise<boolean>} Whether the module can be downloaded. The promise should never be rejected.
*/ */
isDownloadable?(module: any, courseId: number) : boolean|Promise<boolean>; isDownloadable?(module: any, courseId: number): boolean | Promise<boolean>;
/** /**
* Load module contents in module.contents if they aren't loaded already. This is meant for resources. * Load module contents in module.contents if they aren't loaded already. This is meant for resources.
@ -184,7 +166,7 @@ export interface CoreCourseModulePrefetchHandler {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
loadContents?(module: any, courseId: number) : Promise<any>; loadContents?(module: any, courseId: number): Promise<any>;
/** /**
* Remove module downloaded files. If not defined, we'll use getFiles to remove them (slow). * Remove module downloaded files. If not defined, we'll use getFiles to remove them (slow).
@ -193,14 +175,14 @@ export interface CoreCourseModulePrefetchHandler {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
removeFiles?(module: any, courseId: number) : Promise<any>; removeFiles?(module: any, courseId: number): Promise<any>;
}; }
/** /**
* Delegate to register module prefetch handlers. * Delegate to register module prefetch handlers.
*/ */
@Injectable() @Injectable()
export class CoreCourseModulePrefetchDelegate { export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
// Variables for database. // Variables for database.
protected CHECK_UPDATES_TIMES_TABLE = 'check_updates_times'; protected CHECK_UPDATES_TIMES_TABLE = 'check_updates_times';
protected checkUpdatesTableSchema = { protected checkUpdatesTableSchema = {
@ -217,31 +199,33 @@ export class CoreCourseModulePrefetchDelegate {
notNull: true notNull: true
} }
] ]
} };
protected ROOT_CACHE_KEY = 'mmCourse:'; protected ROOT_CACHE_KEY = 'mmCourse:';
protected logger; protected handlers: { [s: string]: CoreCourseModulePrefetchHandler } = {}; // All registered handlers.
protected handlers: {[s: string]: CoreCourseModulePrefetchHandler} = {}; // All registered handlers. protected enabledHandlers: { [s: string]: CoreCourseModulePrefetchHandler } = {}; // Handlers enabled for the current site.
protected enabledHandlers: {[s: string]: CoreCourseModulePrefetchHandler} = {}; // Handlers enabled for the current site.
protected statusCache = new CoreCache(); protected statusCache = new CoreCache();
protected lastUpdateHandlersStart: number;
// Promises for check updates, to prevent performing the same request twice at the same time. // Promises for check updates, to prevent performing the same request twice at the same time.
protected courseUpdatesPromises: {[s: string]: {[s: string]: Promise<any>}} = {}; protected courseUpdatesPromises: { [s: string]: { [s: string]: Promise<any> } } = {};
// Promises and observables for prefetching, to prevent downloading the same section twice at the same time // Promises and observables for prefetching, to prevent downloading the same section twice at the same time
// and notify the progress of the download. // and notify the progress of the download.
protected prefetchData: {[s: string]: {[s: string]: { protected prefetchData: {
promise: Promise<any>, [s: string]: {
observable: Subject<CoreCourseModulesProgress>, [s: string]: {
subscriptions: Subscription[] promise: Promise<any>,
}}} = {}; observable: Subject<CoreCourseModulesProgress>,
subscriptions: Subscription[]
}
}
} = {};
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider, constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, protected eventsProvider: CoreEventsProvider,
private courseProvider: CoreCourseProvider, private filepoolProvider: CoreFilepoolProvider, private courseProvider: CoreCourseProvider, private filepoolProvider: CoreFilepoolProvider,
private timeUtils: CoreTimeUtilsProvider, private utils: CoreUtilsProvider, private fileProvider: CoreFileProvider) { private timeUtils: CoreTimeUtilsProvider, private utils: CoreUtilsProvider, private fileProvider: CoreFileProvider) {
this.logger = logger.getInstance('CoreCourseModulePrefetchDelegate'); super('CoreCourseModulePrefetchDelegate', loggerProvider, sitesProvider);
this.sitesProvider.createTableFromSchema(this.checkUpdatesTableSchema); this.sitesProvider.createTableFromSchema(this.checkUpdatesTableSchema);
} }
@ -251,18 +235,18 @@ export class CoreCourseModulePrefetchDelegate {
* *
* @return {boolean} True if can check updates, false otherwise. * @return {boolean} True if can check updates, false otherwise.
*/ */
canCheckUpdates() : boolean { canCheckUpdates(): boolean {
return this.sitesProvider.getCurrentSite().wsAvailable('core_course_check_updates'); return this.sitesProvider.getCurrentSite().wsAvailable('core_course_check_updates');
} }
/** /**
* Check if a certain module can use core_course_check_updates. * Check if a certain module can use core_course_check_updates.
* *
* @param {any} module Module. * @param {any} module Module.
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {Promise<boolean>} Promise resolved with boolean: whether the module can use check updates WS. * @return {Promise<boolean>} Promise resolved with boolean: whether the module can use check updates WS.
*/ */
canModuleUseCheckUpdates(module: any, courseId: number) : Promise<boolean> { canModuleUseCheckUpdates(module: any, courseId: number): Promise<boolean> {
const handler = this.getPrefetchHandlerFor(module); const handler = this.getPrefetchHandlerFor(module);
if (!handler) { if (!handler) {
@ -281,7 +265,7 @@ export class CoreCourseModulePrefetchDelegate {
/** /**
* Clear the status cache. * Clear the status cache.
*/ */
clearStatusCache() : void { clearStatusCache(): void {
this.statusCache.clear(); this.statusCache.clear();
} }
@ -292,11 +276,11 @@ export class CoreCourseModulePrefetchDelegate {
* @param {number} courseId Course ID the modules belong to. * @param {number} courseId Course ID the modules belong to.
* @return {Promise<{toCheck: any[], cannotUse: any[]}>} Promise resolved with the lists. * @return {Promise<{toCheck: any[], cannotUse: any[]}>} Promise resolved with the lists.
*/ */
protected createToCheckList(modules: any[], courseId: number) : Promise<{toCheck: any[], cannotUse: any[]}> { protected createToCheckList(modules: any[], courseId: number): Promise<{ toCheck: any[], cannotUse: any[] }> {
let result = { let result = {
toCheck: [], toCheck: [],
cannotUse: [] cannotUse: []
}, },
promises = []; promises = [];
modules.forEach((module) => { modules.forEach((module) => {
@ -309,7 +293,7 @@ export class CoreCourseModulePrefetchDelegate {
result.toCheck.push({ result.toCheck.push({
contextlevel: 'module', contextlevel: 'module',
id: module.id, id: module.id,
since: data.downloadTime || 0 since: data.downloadTime || 0
}); });
} else { } else {
// Cannot use check updates, add it to the cannotUse array. // Cannot use check updates, add it to the cannotUse array.
@ -340,7 +324,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {boolean} [canCheck] True if updates can be checked using core_course_check_updates. * @param {boolean} [canCheck] True if updates can be checked using core_course_check_updates.
* @return {string} Module status. * @return {string} Module status.
*/ */
determineModuleStatus(module: any, status: string, canCheck?: boolean) : string { determineModuleStatus(module: any, status: string, canCheck?: boolean): string {
const handler = this.getPrefetchHandlerFor(module), const handler = this.getPrefetchHandlerFor(module),
siteId = this.sitesProvider.getCurrentSiteId(); siteId = this.sitesProvider.getCurrentSiteId();
@ -368,7 +352,7 @@ export class CoreCourseModulePrefetchDelegate {
* @return {Promise<any>} Promise resolved with the updates. If a module is set to false, it means updates cannot be * @return {Promise<any>} Promise resolved with the updates. If a module is set to false, it means updates cannot be
* checked for that module in the current site. * checked for that module in the current site.
*/ */
getCourseUpdates(modules: any[], courseId: number) : Promise<any> { getCourseUpdates(modules: any[], courseId: number): Promise<any> {
if (!this.canCheckUpdates()) { if (!this.canCheckUpdates()) {
return Promise.reject(null); return Promise.reject(null);
} }
@ -400,9 +384,9 @@ export class CoreCourseModulePrefetchDelegate {
// Get the site, maybe the user changed site. // Get the site, maybe the user changed site.
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
let params = { let params = {
courseid: courseId, courseid: courseId,
tocheck: data.toCheck tocheck: data.toCheck
}, },
preSets: CoreSiteWSPreSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getCourseUpdatesCacheKey(courseId), cacheKey: this.getCourseUpdatesCacheKey(courseId),
emergencyCache: false, // If downloaded data has changed and offline, just fail. See MOBILE-2085. emergencyCache: false, // If downloaded data has changed and offline, just fail. See MOBILE-2085.
@ -419,18 +403,18 @@ export class CoreCourseModulePrefetchDelegate {
courseId: courseId, courseId: courseId,
time: this.timeUtils.timestamp() time: this.timeUtils.timestamp()
}; };
site.getDb().insertOrUpdateRecord(this.CHECK_UPDATES_TIMES_TABLE, entry, {courseId: courseId}); site.getDb().insertOrUpdateRecord(this.CHECK_UPDATES_TIMES_TABLE, entry, { courseId: courseId });
return this.treatCheckUpdatesResult(data.toCheck, response, result); return this.treatCheckUpdatesResult(data.toCheck, response, result);
}).catch((error) => { }).catch((error) => {
// Cannot get updates. Get the cached entries but discard the modules with a download time higher // Cannot get updates. Get the cached entries but discard the modules with a download time higher
// than the last execution of check updates. // than the last execution of check updates.
return site.getDb().getRecord(this.CHECK_UPDATES_TIMES_TABLE, {courseId: courseId}).then((entry) => { return site.getDb().getRecord(this.CHECK_UPDATES_TIMES_TABLE, { courseId: courseId }).then((entry) => {
preSets.getCacheUsingCacheKey = true; preSets.getCacheUsingCacheKey = true;
preSets.omitExpires = true; preSets.omitExpires = true;
return site.read('core_course_check_updates', params, preSets).then((response) => { return site.read('core_course_check_updates', params, preSets).then((response) => {
if (!response || typeof response.instances == 'undefined') { if (!response || typeof response.instances == 'undefined') {
return Promise.reject(error); return Promise.reject(error);
} }
@ -450,20 +434,19 @@ export class CoreCourseModulePrefetchDelegate {
return this.courseUpdatesPromises[siteId][id]; return this.courseUpdatesPromises[siteId][id];
} }
/** /**
* Check for updates in a course. * Check for updates in a course.
* *
* @param {number} courseId Course ID the modules belong to. * @param {number} courseId Course ID the modules belong to.
* @return {Promise<any>} Promise resolved with the updates. * @return {Promise<any>} Promise resolved with the updates.
*/ */
getCourseUpdatesByCourseId(courseId: number) : Promise<any> { getCourseUpdatesByCourseId(courseId: number): Promise<any> {
if (!this.canCheckUpdates()) { if (!this.canCheckUpdates()) {
return Promise.reject(null); return Promise.reject(null);
} }
// Get course sections and all their modules. // Get course sections and all their modules.
return this.courseProvider.getSections(courseId, false, true, {omitExpires: true}).then((sections) => { return this.courseProvider.getSections(courseId, false, true, { omitExpires: true }).then((sections) => {
return this.getCourseUpdates(this.courseProvider.getSectionsModules(sections), courseId); return this.getCourseUpdates(this.courseProvider.getSectionsModules(sections), courseId);
}); });
} }
@ -474,7 +457,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @return {string} Cache key. * @return {string} Cache key.
*/ */
protected getCourseUpdatesCacheKey(courseId: number) : string { protected getCourseUpdatesCacheKey(courseId: number): string {
return this.ROOT_CACHE_KEY + 'courseUpdates:' + courseId; return this.ROOT_CACHE_KEY + 'courseUpdates:' + courseId;
} }
@ -486,7 +469,7 @@ export class CoreCourseModulePrefetchDelegate {
* @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able * @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able
* to calculate the total size. * to calculate the total size.
*/ */
getDownloadSize(modules: any[], courseId: number) : Promise<{size: number, total: boolean}> { getDownloadSize(modules: any[], courseId: number): Promise<{ size: number, total: boolean }> {
// Get the status of each module. // Get the status of each module.
return this.getModulesStatus(modules, courseId).then((data) => { return this.getModulesStatus(modules, courseId).then((data) => {
const downloadableModules = data[CoreConstants.NOT_DOWNLOADED].concat(data[CoreConstants.OUTDATED]), const downloadableModules = data[CoreConstants.NOT_DOWNLOADED].concat(data[CoreConstants.OUTDATED]),
@ -518,7 +501,7 @@ export class CoreCourseModulePrefetchDelegate {
* @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able * @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able
* to calculate the total size. * to calculate the total size.
*/ */
getModuleDownloadSize(module: any, courseId: number, single?: boolean) : Promise<{size: number, total: boolean}> { getModuleDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }> {
let downloadSize, let downloadSize,
packageId, packageId,
handler = this.getPrefetchHandlerFor(module); handler = this.getPrefetchHandlerFor(module);
@ -527,7 +510,7 @@ export class CoreCourseModulePrefetchDelegate {
if (handler) { if (handler) {
return this.isModuleDownloadable(module, courseId).then((downloadable) => { return this.isModuleDownloadable(module, courseId).then((downloadable) => {
if (!downloadable) { if (!downloadable) {
return {size: 0, total: true}; return { size: 0, total: true };
} }
packageId = this.filepoolProvider.getPackageId(handler.component, module.id); packageId = this.filepoolProvider.getPackageId(handler.component, module.id);
@ -548,7 +531,7 @@ export class CoreCourseModulePrefetchDelegate {
}); });
} }
return Promise.resolve({size: 0, total: false}); return Promise.resolve({ size: 0, total: false });
} }
/** /**
@ -558,7 +541,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {Promise<number>} Promise resolved with the size. * @return {Promise<number>} Promise resolved with the size.
*/ */
getModuleDownloadedSize(module: any, courseId: number) : Promise<number> { getModuleDownloadedSize(module: any, courseId: number): Promise<number> {
let downloadedSize, let downloadedSize,
packageId, packageId,
promise, promise,
@ -630,7 +613,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {Promise<any[]>} Promise resolved with the list of files. * @return {Promise<any[]>} Promise resolved with the list of files.
*/ */
getModuleFiles(module: any, courseId: number) : Promise<any[]> { getModuleFiles(module: any, courseId: number): Promise<any[]> {
const handler = this.getPrefetchHandlerFor(module); const handler = this.getPrefetchHandlerFor(module);
if (handler.getFiles) { if (handler.getFiles) {
@ -657,7 +640,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {number} [sectionId] ID of the section the module belongs to. * @param {number} [sectionId] ID of the section the module belongs to.
* @return {Promise<string>} Promise resolved with the status. * @return {Promise<string>} Promise resolved with the status.
*/ */
getModuleStatus(module: any, courseId: number, updates?: any, refresh?: boolean, sectionId?: number) : Promise<string> { getModuleStatus(module: any, courseId: number, updates?: any, refresh?: boolean, sectionId?: number): Promise<string> {
let handler = this.getPrefetchHandlerFor(module), let handler = this.getPrefetchHandlerFor(module),
siteId = this.sitesProvider.getCurrentSiteId(), siteId = this.sitesProvider.getCurrentSiteId(),
canCheck = this.canCheckUpdates(); canCheck = this.canCheckUpdates();
@ -757,7 +740,7 @@ export class CoreCourseModulePrefetchDelegate {
* - CoreConstants.DOWNLOADING (any[]) Modules with state DOWNLOADING. * - CoreConstants.DOWNLOADING (any[]) Modules with state DOWNLOADING.
* - CoreConstants.OUTDATED (any[]) Modules with state OUTDATED. * - CoreConstants.OUTDATED (any[]) Modules with state OUTDATED.
*/ */
getModulesStatus(modules: any[], courseId: number, sectionId?: number, refresh?: boolean) : any { getModulesStatus(modules: any[], courseId: number, sectionId?: number, refresh?: boolean): any {
let promises = [], let promises = [],
status = CoreConstants.NOT_DOWNLOADABLE, status = CoreConstants.NOT_DOWNLOADABLE,
result: any = { result: any = {
@ -822,7 +805,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {Promise<{status: string, downloadTime?: number}>} Promise resolved with the data. * @return {Promise<{status: string, downloadTime?: number}>} Promise resolved with the data.
*/ */
protected getModuleStatusAndDownloadTime(module: any, courseId: number) : Promise<{status: string, downloadTime?: number}> { protected getModuleStatusAndDownloadTime(module: any, courseId: number): Promise<{ status: string, downloadTime?: number }> {
let handler = this.getPrefetchHandlerFor(module), let handler = this.getPrefetchHandlerFor(module),
siteId = this.sitesProvider.getCurrentSiteId(); siteId = this.sitesProvider.getCurrentSiteId();
@ -839,7 +822,7 @@ export class CoreCourseModulePrefetchDelegate {
} }
// Check if the module is downloadable. // Check if the module is downloadable.
return this.isModuleDownloadable(module, courseId).then((downloadable: boolean) : any => { return this.isModuleDownloadable(module, courseId).then((downloadable: boolean): any => {
if (!downloadable) { if (!downloadable) {
return { return {
status: CoreConstants.NOT_DOWNLOADABLE status: CoreConstants.NOT_DOWNLOADABLE
@ -868,7 +851,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {any} module The module to work on. * @param {any} module The module to work on.
* @return {CoreCourseModulePrefetchHandler} Prefetch handler. * @return {CoreCourseModulePrefetchHandler} Prefetch handler.
*/ */
getPrefetchHandlerFor(module: any) : CoreCourseModulePrefetchHandler { getPrefetchHandlerFor(module: any): CoreCourseModulePrefetchHandler {
return this.enabledHandlers[module.modname]; return this.enabledHandlers[module.modname];
} }
@ -878,7 +861,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @return {Promise<any>} Promise resolved when data is invalidated. * @return {Promise<any>} Promise resolved when data is invalidated.
*/ */
invalidateCourseUpdates(courseId: number) : Promise<any> { invalidateCourseUpdates(courseId: number): Promise<any> {
return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getCourseUpdatesCacheKey(courseId)); return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getCourseUpdatesCacheKey(courseId));
} }
@ -889,7 +872,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @return {Promise<any>} Promise resolved when modules are invalidated. * @return {Promise<any>} Promise resolved when modules are invalidated.
*/ */
invalidateModules(modules: any[], courseId: number) : Promise<any> { invalidateModules(modules: any[], courseId: number): Promise<any> {
let promises = []; let promises = [];
modules.forEach((module) => { modules.forEach((module) => {
@ -916,7 +899,7 @@ export class CoreCourseModulePrefetchDelegate {
* *
* @param {any} module Module to be invalidated. * @param {any} module Module to be invalidated.
*/ */
invalidateModuleStatusCache(module: any) : void { invalidateModuleStatusCache(module: any): void {
const handler = this.getPrefetchHandlerFor(module); const handler = this.getPrefetchHandlerFor(module);
if (handler) { if (handler) {
this.statusCache.invalidate(this.filepoolProvider.getPackageId(handler.component, module.id)); this.statusCache.invalidate(this.filepoolProvider.getPackageId(handler.component, module.id));
@ -929,25 +912,11 @@ export class CoreCourseModulePrefetchDelegate {
* @param {string} id An ID to identify the download. * @param {string} id An ID to identify the download.
* @return {boolean} True if it's being downloaded, false otherwise. * @return {boolean} True if it's being downloaded, false otherwise.
*/ */
isBeingDownloaded(id: string) : boolean { isBeingDownloaded(id: string): boolean {
const siteId = this.sitesProvider.getCurrentSiteId(); const siteId = this.sitesProvider.getCurrentSiteId();
return !!(this.prefetchData[siteId] && this.prefetchData[siteId][id]); return !!(this.prefetchData[siteId] && this.prefetchData[siteId][id]);
} }
/**
* Check if a time belongs to the last update handlers call.
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
*
* @param {number} time Time to check.
* @return {boolean} Whether it's the last call.
*/
isLastUpdateCall(time: number) : boolean {
if (!this.lastUpdateHandlersStart) {
return true;
}
return time == this.lastUpdateHandlersStart;
}
/** /**
* Check if a module is downloadable. * Check if a module is downloadable.
* *
@ -955,7 +924,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {Number} courseId Course ID the module belongs to. * @param {Number} courseId Course ID the module belongs to.
* @return {Promise<boolean>} Promise resolved with true if downloadable, false otherwise. * @return {Promise<boolean>} Promise resolved with true if downloadable, false otherwise.
*/ */
isModuleDownloadable(module: any, courseId: number) : Promise<boolean> { isModuleDownloadable(module: any, courseId: number): Promise<boolean> {
let handler = this.getPrefetchHandlerFor(module); let handler = this.getPrefetchHandlerFor(module);
if (handler) { if (handler) {
@ -991,14 +960,14 @@ export class CoreCourseModulePrefetchDelegate {
* @param {any} updates Result of getCourseUpdates. * @param {any} updates Result of getCourseUpdates.
* @return {Promise<boolean>} Promise resolved with boolean: whether the module has updates. * @return {Promise<boolean>} Promise resolved with boolean: whether the module has updates.
*/ */
moduleHasUpdates(module: any, courseId: number, updates: any) : Promise<boolean> { moduleHasUpdates(module: any, courseId: number, updates: any): Promise<boolean> {
let handler = this.getPrefetchHandlerFor(module), let handler = this.getPrefetchHandlerFor(module),
moduleUpdates = updates[module.id]; moduleUpdates = updates[module.id];
if (handler && handler.hasUpdates) { if (handler && handler.hasUpdates) {
// Handler implements its own function to check the updates, use it. // Handler implements its own function to check the updates, use it.
return Promise.resolve(handler.hasUpdates(module, courseId, moduleUpdates)); return Promise.resolve(handler.hasUpdates(module, courseId, moduleUpdates));
} else if (!moduleUpdates || !moduleUpdates.updates || !moduleUpdates.updates.length) { } else if (!moduleUpdates || !moduleUpdates.updates || !moduleUpdates.updates.length) {
// Module doesn't have any update. // Module doesn't have any update.
return Promise.resolve(false); return Promise.resolve(false);
} else if (handler && handler.updatesNames && handler.updatesNames.test) { } else if (handler && handler.updatesNames && handler.updatesNames.test) {
@ -1024,7 +993,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section.
* @return {Promise<any>} Promise resolved when finished. * @return {Promise<any>} Promise resolved when finished.
*/ */
prefetchModule(module: any, courseId: number, single?: boolean) : Promise<any> { prefetchModule(module: any, courseId: number, single?: boolean): Promise<any> {
const handler = this.getPrefetchHandlerFor(module); const handler = this.getPrefetchHandlerFor(module);
// Check if the module has a prefetch handler. // Check if the module has a prefetch handler.
@ -1044,7 +1013,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {CoreCourseModulesProgressFunction} [onProgress] Function to call everytime a module is downloaded. * @param {CoreCourseModulesProgressFunction} [onProgress] Function to call everytime a module is downloaded.
* @return {Promise<any>} Promise resolved when all modules have been prefetched. * @return {Promise<any>} Promise resolved when all modules have been prefetched.
*/ */
prefetchModules(id: string, modules: any[], courseId: number, onProgress?: CoreCourseModulesProgressFunction) : Promise<any> { prefetchModules(id: string, modules: any[], courseId: number, onProgress?: CoreCourseModulesProgressFunction): Promise<any> {
const siteId = this.sitesProvider.getCurrentSiteId(), const siteId = this.sitesProvider.getCurrentSiteId(),
currentData = this.prefetchData[siteId] && this.prefetchData[siteId][id]; currentData = this.prefetchData[siteId] && this.prefetchData[siteId][id];
@ -1066,7 +1035,7 @@ export class CoreCourseModulePrefetchDelegate {
// Initialize the prefetch data. // Initialize the prefetch data.
const prefetchData = { const prefetchData = {
observable: new BehaviorSubject<CoreCourseModulesProgress>({count: count, total: total}), observable: new BehaviorSubject<CoreCourseModulesProgress>({ count: count, total: total }),
promise: undefined, promise: undefined,
subscriptions: [] subscriptions: []
}; };
@ -1090,7 +1059,7 @@ export class CoreCourseModulePrefetchDelegate {
// It's one of the modules we were expecting to download. // It's one of the modules we were expecting to download.
moduleIds.splice(index, 1); moduleIds.splice(index, 1);
count++; count++;
prefetchData.observable.next({count: count, total: total}); prefetchData.observable.next({ count: count, total: total });
} }
}); });
})); }));
@ -1115,23 +1084,6 @@ export class CoreCourseModulePrefetchDelegate {
return prefetchData.promise; return prefetchData.promise;
} }
/**
* Register a handler.
*
* @param {CoreCourseModulePrefetchHandler} handler The handler to register.
* @return {boolean} True if registered successfully, false otherwise.
*/
registerHandler(handler: CoreCourseModulePrefetchHandler) : boolean {
if (typeof this.handlers[handler.modname] !== 'undefined') {
this.logger.log('There is an addon named \'' + this.handlers[handler.modname].name +
'\' already registered as a prefetch handler for ' + handler.modname);
return false;
}
this.logger.log(`Registered addon '${handler.name}' as a prefetch handler for '${handler.modname}'`);
this.handlers[handler.modname] = handler;
return true;
}
/** /**
* Remove module Files from handler. * Remove module Files from handler.
* *
@ -1139,7 +1091,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @return {Promise<void>} Promise resolved when done. * @return {Promise<void>} Promise resolved when done.
*/ */
removeModuleFiles(module: any, courseId: number) : Promise<void> { removeModuleFiles(module: any, courseId: number): Promise<void> {
let handler = this.getPrefetchHandlerFor(module), let handler = this.getPrefetchHandlerFor(module),
siteId = this.sitesProvider.getCurrentSiteId(), siteId = this.sitesProvider.getCurrentSiteId(),
promise; promise;
@ -1176,7 +1128,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {string} id An ID to identify the download. * @param {string} id An ID to identify the download.
* @param {CoreCourseModulesProgressFunction} onProgress Function to call everytime a module is downloaded. * @param {CoreCourseModulesProgressFunction} onProgress Function to call everytime a module is downloaded.
*/ */
setOnProgress(id: string, onProgress: CoreCourseModulesProgressFunction) : void { setOnProgress(id: string, onProgress: CoreCourseModulesProgressFunction): void {
const siteId = this.sitesProvider.getCurrentSiteId(), const siteId = this.sitesProvider.getCurrentSiteId(),
currentData = this.prefetchData[siteId] && this.prefetchData[siteId][id]; currentData = this.prefetchData[siteId] && this.prefetchData[siteId][id];
@ -1196,7 +1148,7 @@ export class CoreCourseModulePrefetchDelegate {
* after this time will be ignored. * after this time will be ignored.
* @return {any} Result. * @return {any} Result.
*/ */
protected treatCheckUpdatesResult(toCheckList: any[], response: any, result: any, previousTime?: number) : any { protected treatCheckUpdatesResult(toCheckList: any[], response: any, result: any, previousTime?: number): any {
// Format the response to index it by module ID. // Format the response to index it by module ID.
this.utils.arrayToObject(response.instances, 'id', result); this.utils.arrayToObject(response.instances, 'id', result);
@ -1219,60 +1171,6 @@ export class CoreCourseModulePrefetchDelegate {
return result; return result;
} }
/**
* Update the enabled handlers for the current site.
*
* @param {CoreCourseModulePrefetchHandler} handler The handler to treat.
* @param {number} time Time this update process started.
* @return {Promise<void>} Resolved when done.
*/
updateHandler(handler: CoreCourseModulePrefetchHandler, time: number) : Promise<void> {
let promise,
siteId = this.sitesProvider.getCurrentSiteId();
if (!siteId) {
promise = Promise.reject(null);
} else {
promise = Promise.resolve(handler.isEnabled());
}
// Checks if the prefetch is enabled.
return promise.catch(() => {
return false;
}).then((enabled: boolean) => {
// Verify that this call is the last one that was started.
// Check that site hasn't changed since the check started.
if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() == siteId) {
if (enabled) {
this.enabledHandlers[handler.modname] = handler;
} else {
delete this.enabledHandlers[handler.modname];
}
}
});
}
/**
* Update the handlers for the current site.
*
* @return {Promise<any>} Resolved when done.
*/
updateHandlers() : Promise<any> {
const promises = [],
now = Date.now();
this.lastUpdateHandlersStart = now;
// Loop over all the handlers.
for (let name in this.handlers) {
promises.push(this.updateHandler(this.handlers[name], now));
}
return Promise.all(promises).catch(() => {
// Never reject.
});
}
/** /**
* Update the status of a module in the "cache". * Update the status of a module in the "cache".
* *
@ -1282,7 +1180,7 @@ export class CoreCourseModulePrefetchDelegate {
* @param {string|number} [componentId] An ID to use in conjunction with the component. * @param {string|number} [componentId] An ID to use in conjunction with the component.
* @param {number} [sectionId] Section ID of the module. * @param {number} [sectionId] Section ID of the module.
*/ */
updateStatusCache(status: string, courseId: number, component: string, componentId?: string|number, sectionId?: number) : void { updateStatusCache(status: string, courseId: number, component: string, componentId?: string | number, sectionId?: number): void {
let notify, let notify,
packageId = this.filepoolProvider.getPackageId(component, componentId), packageId = this.filepoolProvider.getPackageId(component, componentId),
cachedStatus = this.statusCache.getValue(packageId, 'status', true); cachedStatus = this.statusCache.getValue(packageId, 'status', true);

View File

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate';
import { CoreEventsProvider } from '../../../providers/events'; import { CoreEventsProvider } from '../../../providers/events';
import { CoreLoggerProvider } from '../../../providers/logger'; import { CoreLoggerProvider } from '../../../providers/logger';
import { CoreSitesProvider } from '../../../providers/sites'; import { CoreSitesProvider } from '../../../providers/sites';
@ -22,26 +23,13 @@ import { CoreCoursesProvider } from './courses';
/** /**
* Interface that all courses handlers must implement. * Interface that all courses handlers must implement.
*/ */
export interface CoreCoursesHandler { export interface CoreCoursesHandler extends CoreDelegateHandler {
/**
* Name of the handler.
* @type {string}
*/
name: string;
/** /**
* The highest priority is displayed first. * The highest priority is displayed first.
* @type {number} * @type {number}
*/ */
priority: number; priority: number;
/**
* Whether or not the handler is enabled on a site level.
*
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/
isEnabled(): boolean|Promise<boolean>;
/** /**
* Whether or not the handler is enabled for a certain course. * Whether or not the handler is enabled for a certain course.
* For perfomance reasons, do NOT call WebServices in here, call them in shouldDisplayForCourse. * For perfomance reasons, do NOT call WebServices in here, call them in shouldDisplayForCourse.
@ -52,7 +40,7 @@ export interface CoreCoursesHandler {
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled. * @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/ */
isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any) : boolean|Promise<boolean>; isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise<boolean>;
/** /**
* Whether or not the handler should be displayed for a course. If not implemented, assume it's true. * Whether or not the handler should be displayed for a course. If not implemented, assume it's true.
@ -63,7 +51,7 @@ export interface CoreCoursesHandler {
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled. * @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/ */
shouldDisplayForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any) : boolean|Promise<boolean>; shouldDisplayForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise<boolean>;
/** /**
* Returns the data needed to render the handler. * Returns the data needed to render the handler.
@ -81,7 +69,7 @@ export interface CoreCoursesHandler {
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
invalidateEnabledForCourse?(courseId: number, navOptions?: any, admOptions?: any) : Promise<any>; invalidateEnabledForCourse?(courseId: number, navOptions?: any, admOptions?: any): Promise<any>;
/** /**
* Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline. * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline.
@ -89,8 +77,8 @@ export interface CoreCoursesHandler {
* @param {any} course The course. * @param {any} course The course.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
prefetch?(course: any) : Promise<any>; prefetch?(course: any): Promise<any>;
}; }
/** /**
* Data needed to render a course handler. It's returned by the handler. * Data needed to render a course handler. It's returned by the handler.
@ -120,7 +108,7 @@ export interface CoreCoursesHandlerData {
* @param {any} course The course. * @param {any} course The course.
*/ */
action(course: any): void; action(course: any): void;
}; }
/** /**
* Data returned by the delegate for each handler. * Data returned by the delegate for each handler.
@ -144,30 +132,30 @@ export interface CoreCoursesHandlerToDisplay {
* @param {any} course The course. * @param {any} course The course.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
prefetch?(course: any) : Promise<any>; prefetch?(course: any): Promise<any>;
}; }
/** /**
* Service to interact with plugins to be shown in each course. * Service to interact with plugins to be shown in each course.
*/ */
@Injectable() @Injectable()
export class CoreCoursesDelegate { export class CoreCoursesDelegate extends CoreDelegate {
protected logger; protected handlers: { [s: string]: CoreCoursesHandler } = {}; // All registered handlers.
protected handlers: {[s: string]: CoreCoursesHandler} = {}; // All registered handlers. protected enabledHandlers: { [s: string]: CoreCoursesHandler } = {}; // Handlers enabled for the current site.
protected enabledHandlers: {[s: string]: CoreCoursesHandler} = {}; // Handlers enabled for the current site. protected loaded: { [courseId: number]: boolean } = {};
protected loaded: {[courseId: number]: boolean} = {};
protected lastUpdateHandlersStart: number;
protected lastUpdateHandlersForCoursesStart: any = {}; protected lastUpdateHandlersForCoursesStart: any = {};
protected coursesHandlers: {[courseId: number]: { protected coursesHandlers: {
access?: any, navOptions?: any, admOptions?: any, deferred?: PromiseDefer, enabledHandlers?: CoreCoursesHandler[]}} = {}; [courseId: number]: {
access?: any, navOptions?: any, admOptions?: any, deferred?: PromiseDefer, enabledHandlers?: CoreCoursesHandler[]
}
} = {};
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider, protected featurePrefix = '$mmCoursesDelegate_';
private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider) {
this.logger = logger.getInstance('CoreMainMenuDelegate'); constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
protected eventsProvider: CoreEventsProvider, private coursesProvider: CoreCoursesProvider) {
super('CoreMainMenuDelegate', loggerProvider, sitesProvider, eventsProvider);
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.LOGOUT, () => { eventsProvider.on(CoreEventsProvider.LOGOUT, () => {
this.clearCoursesHandlers(); this.clearCoursesHandlers();
}); });
@ -179,7 +167,7 @@ export class CoreCoursesDelegate {
* @param {number} courseId The course ID to check. * @param {number} courseId The course ID to check.
* @return {boolean} True if handlers are loaded, false otherwise. * @return {boolean} True if handlers are loaded, false otherwise.
*/ */
areHandlersLoaded(courseId: number) : boolean { areHandlersLoaded(courseId: number): boolean {
return !!this.loaded[courseId]; return !!this.loaded[courseId];
} }
@ -188,7 +176,7 @@ export class CoreCoursesDelegate {
* *
* @param {number} [courseId] The course ID. If not defined, all handlers will be cleared. * @param {number} [courseId] The course ID. If not defined, all handlers will be cleared.
*/ */
protected clearCoursesHandlers(courseId?: number) : void { protected clearCoursesHandlers(courseId?: number): void {
if (courseId) { if (courseId) {
this.loaded[courseId] = false; this.loaded[courseId] = false;
delete this.coursesHandlers[courseId]; delete this.coursesHandlers[courseId];
@ -204,8 +192,8 @@ export class CoreCoursesDelegate {
* @param {number} [courseId] The course ID. If not defined, all handlers will be cleared. * @param {number} [courseId] The course ID. If not defined, all handlers will be cleared.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
clearAndInvalidateCoursesOptions(courseId?: number) : Promise<any> { clearAndInvalidateCoursesOptions(courseId?: number): Promise<any> {
var promises = []; let promises = [];
this.eventsProvider.trigger(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED); this.eventsProvider.trigger(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED);
@ -240,14 +228,14 @@ export class CoreCoursesDelegate {
* @return {Promise<CoreCoursesHandler[]>} Promise resolved with array of handlers. * @return {Promise<CoreCoursesHandler[]>} Promise resolved with array of handlers.
*/ */
protected getHandlersForAccess(courseId: number, refresh: boolean, accessData: any, navOptions?: any, protected getHandlersForAccess(courseId: number, refresh: boolean, accessData: any, navOptions?: any,
admOptions?: any) : Promise<CoreCoursesHandler[]> { admOptions?: any): Promise<CoreCoursesHandler[]> {
// If the handlers aren't loaded, do not refresh. // If the handlers aren't loaded, do not refresh.
if (!this.loaded[courseId]) { if (!this.loaded[courseId]) {
refresh = false; refresh = false;
} }
if (refresh || !this.coursesHandlers[courseId] || this.coursesHandlers[courseId].access.type != accessData.type) { if (refresh || !this.coursesHandlers[courseId] || this.coursesHandlers[courseId].access.type != accessData.type) {
if (!this.coursesHandlers[courseId]) { if (!this.coursesHandlers[courseId]) {
this.coursesHandlers[courseId] = {}; this.coursesHandlers[courseId] = {};
} }
@ -274,8 +262,8 @@ export class CoreCoursesDelegate {
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return {Promise<CoreCoursesHandlerToDisplay[]>} Promise resolved with array of handlers. * @return {Promise<CoreCoursesHandlerToDisplay[]>} Promise resolved with array of handlers.
*/ */
getHandlersToDisplay(course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any) : getHandlersToDisplay(course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any):
Promise<CoreCoursesHandlerToDisplay[]> { Promise<CoreCoursesHandlerToDisplay[]> {
course.id = parseInt(course.id, 10); course.id = parseInt(course.id, 10);
let accessData = { let accessData = {
@ -300,7 +288,7 @@ export class CoreCoursesDelegate {
this.coursesHandlers[course.id].enabledHandlers.forEach((handler) => { this.coursesHandlers[course.id].enabledHandlers.forEach((handler) => {
if (handler.shouldDisplayForCourse) { if (handler.shouldDisplayForCourse) {
promise = Promise.resolve(handler.shouldDisplayForCourse( promise = Promise.resolve(handler.shouldDisplayForCourse(
course.id, accessData, course.navOptions, course.admOptions)); course.id, accessData, course.navOptions, course.admOptions));
} else { } else {
// Not implemented, assume it should be displayed. // Not implemented, assume it should be displayed.
promise = Promise.resolve(true); promise = Promise.resolve(true);
@ -335,7 +323,7 @@ export class CoreCoursesDelegate {
* @param {boolean} [refresh] True if it should refresh the list. * @param {boolean} [refresh] True if it should refresh the list.
* @return {Promise<boolean>} Promise resolved with boolean: true if it has handlers, false otherwise. * @return {Promise<boolean>} Promise resolved with boolean: true if it has handlers, false otherwise.
*/ */
hasHandlersForCourse(course: any, refresh?: boolean) : Promise<boolean> { hasHandlersForCourse(course: any, refresh?: boolean): Promise<boolean> {
// Load course options if missing. // Load course options if missing.
return this.loadCourseOptions(course, refresh).then(() => { return this.loadCourseOptions(course, refresh).then(() => {
return this.hasHandlersForDefault(course.id, refresh, course.navOptions, course.admOptions); return this.hasHandlersForDefault(course.id, refresh, course.navOptions, course.admOptions);
@ -351,7 +339,7 @@ export class CoreCoursesDelegate {
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return {Promise<boolean>} Promise resolved with boolean: true if it has handlers, false otherwise. * @return {Promise<boolean>} Promise resolved with boolean: true if it has handlers, false otherwise.
*/ */
hasHandlersForDefault(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any) : Promise<boolean> { hasHandlersForDefault(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any): Promise<boolean> {
// Default access. // Default access.
let accessData = { let accessData = {
type: CoreCoursesProvider.ACCESS_DEFAULT type: CoreCoursesProvider.ACCESS_DEFAULT
@ -370,7 +358,7 @@ export class CoreCoursesDelegate {
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return {Promise<boolean>} Promise resolved with boolean: true if it has handlers, false otherwise. * @return {Promise<boolean>} Promise resolved with boolean: true if it has handlers, false otherwise.
*/ */
hasHandlersForGuest(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any) : Promise<boolean> { hasHandlersForGuest(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any): Promise<boolean> {
// Guest access. // Guest access.
var accessData = { var accessData = {
type: CoreCoursesProvider.ACCESS_GUEST type: CoreCoursesProvider.ACCESS_GUEST
@ -386,7 +374,7 @@ export class CoreCoursesDelegate {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
invalidateCourseHandlers(courseId: number) : Promise<any> { invalidateCourseHandlers(courseId: number): Promise<any> {
let promises = [], let promises = [],
courseData = this.coursesHandlers[courseId]; courseData = this.coursesHandlers[courseId];
@ -397,27 +385,13 @@ export class CoreCoursesDelegate {
courseData.enabledHandlers.forEach((handler) => { courseData.enabledHandlers.forEach((handler) => {
if (handler && handler.invalidateEnabledForCourse) { if (handler && handler.invalidateEnabledForCourse) {
promises.push(Promise.resolve( promises.push(Promise.resolve(
handler.invalidateEnabledForCourse(courseId, courseData.navOptions, courseData.admOptions))); handler.invalidateEnabledForCourse(courseId, courseData.navOptions, courseData.admOptions)));
} }
}); });
return this.utils.allPromises(promises); return this.utils.allPromises(promises);
} }
/**
* Check if a time belongs to the last update handlers call.
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
*
* @param {number} time Time to check.
* @return {boolean} Whether it's the last call.
*/
isLastUpdateCall(time: number) : boolean {
if (!this.lastUpdateHandlersStart) {
return true;
}
return time == this.lastUpdateHandlersStart;
}
/** /**
* Check if a time belongs to the last update handlers for course call. * Check if a time belongs to the last update handlers for course call.
* This is to handle the cases where updateHandlersForCourse don't finish in the same order as they're called. * This is to handle the cases where updateHandlersForCourse don't finish in the same order as they're called.
@ -426,7 +400,7 @@ export class CoreCoursesDelegate {
* @param {number} time Time to check. * @param {number} time Time to check.
* @return {boolean} Whether it's the last call. * @return {boolean} Whether it's the last call.
*/ */
isLastUpdateCourseCall(courseId: number, time: number) : boolean { isLastUpdateCourseCall(courseId: number, time: number): boolean {
if (!this.lastUpdateHandlersForCoursesStart[courseId]) { if (!this.lastUpdateHandlersForCoursesStart[courseId]) {
return true; return true;
} }
@ -440,7 +414,7 @@ export class CoreCoursesDelegate {
* @param {boolean} [refresh] True if it should refresh the list. * @param {boolean} [refresh] True if it should refresh the list.
* @return {Promise<void>} Promise resolved when done. * @return {Promise<void>} Promise resolved when done.
*/ */
protected loadCourseOptions(course: any, refresh?: boolean) : Promise<void> { protected loadCourseOptions(course: any, refresh?: boolean): Promise<void> {
if (typeof course.navOptions == 'undefined' || typeof course.admOptions == 'undefined' || refresh) { if (typeof course.navOptions == 'undefined' || typeof course.admOptions == 'undefined' || refresh) {
return this.coursesProvider.getCoursesOptions([course.id]).then((options) => { return this.coursesProvider.getCoursesOptions([course.id]).then((options) => {
course.navOptions = options.navOptions[course.id]; course.navOptions = options.navOptions[course.id];
@ -451,92 +425,14 @@ export class CoreCoursesDelegate {
} }
} }
/** updateData(siteId?: string) {
* Register a handler. if (this.sitesProvider.getCurrentSiteId() === siteId) {
* // Update handlers for all courses.
* @param {CoreCoursesHandler} handler The handler to register. for (let courseId in this.coursesHandlers) {
* @return {boolean} True if registered successfully, false otherwise. let handler = this.coursesHandlers[courseId];
*/ this.updateHandlersForCourse(parseInt(courseId, 10), handler.access, handler.navOptions, handler.admOptions);
registerHandler(handler: CoreCoursesHandler) : boolean {
if (typeof this.handlers[handler.name] !== 'undefined') {
this.logger.log(`Addon '${handler.name}' already registered`);
return false;
}
this.logger.log(`Registered addon '${handler.name}'`);
this.handlers[handler.name] = handler;
return true;
}
/**
* Update the handler for the current site.
*
* @param {CoreInitHandler} handler The handler to check.
* @param {number} time Time this update process started.
* @return {Promise<void>} Resolved when done.
*/
protected updateHandler(handler: CoreCoursesHandler, time: number) : Promise<void> {
let promise,
siteId = this.sitesProvider.getCurrentSiteId(),
currentSite = this.sitesProvider.getCurrentSite();
if (!this.sitesProvider.isLoggedIn()) {
promise = Promise.reject(null);
} else if (currentSite.isFeatureDisabled('$mmCoursesDelegate_' + handler.name)) {
promise = Promise.resolve(false);
} else {
promise = Promise.resolve(handler.isEnabled());
}
// Checks if the handler is enabled.
return promise.catch(() => {
return false;
}).then((enabled: boolean) => {
// Verify that this call is the last one that was started.
// Check that site hasn't changed since the check started.
if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() === siteId) {
if (enabled) {
this.enabledHandlers[handler.name] = handler;
} else {
delete this.enabledHandlers[handler.name];
}
} }
});
}
/**
* Update the handlers for the current site.
*
* @return {Promise<void>} Resolved when done.
*/
protected updateHandlers() : Promise<void> {
let promises = [],
siteId = this.sitesProvider.getCurrentSiteId(),
now = Date.now();
this.logger.debug('Updating handlers for current site.');
this.lastUpdateHandlersStart = now;
// Loop over all the handlers.
for (let name in this.handlers) {
promises.push(this.updateHandler(this.handlers[name], now));
} }
return Promise.all(promises).then(() => {
return true;
}, () => {
// Never reject.
return true;
}).then(() => {
// Verify that this call is the last one that was started.
if (this.isLastUpdateCall(now) && this.sitesProvider.getCurrentSiteId() === siteId) {
// Update handlers for all courses.
for (let courseId in this.coursesHandlers) {
let handler = this.coursesHandlers[courseId];
this.updateHandlersForCourse(parseInt(courseId, 10), handler.access, handler.navOptions, handler.admOptions);
}
}
});
} }
/** /**
@ -549,7 +445,7 @@ export class CoreCoursesDelegate {
* @return {Promise} Resolved when updated. * @return {Promise} Resolved when updated.
* @protected * @protected
*/ */
updateHandlersForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any) : Promise<any> { updateHandlersForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): Promise<any> {
let promises = [], let promises = [],
enabledForCourse = [], enabledForCourse = [],
siteId = this.sitesProvider.getCurrentSiteId(), siteId = this.sitesProvider.getCurrentSiteId(),
@ -562,15 +458,15 @@ export class CoreCoursesDelegate {
// Checks if the handler is enabled for the user. // Checks if the handler is enabled for the user.
promises.push(Promise.resolve(handler.isEnabledForCourse(courseId, accessData, navOptions, admOptions)) promises.push(Promise.resolve(handler.isEnabledForCourse(courseId, accessData, navOptions, admOptions))
.then(function(enabled) { .then(function(enabled) {
if (enabled) { if (enabled) {
enabledForCourse.push(handler); enabledForCourse.push(handler);
} else { } else {
return Promise.reject(null); return Promise.reject(null);
} }
}).catch(() => { }).catch(() => {
// Nothing to do here, it is not enabled for this user. // Nothing to do here, it is not enabled for this user.
})); }));
} }
return Promise.all(promises).then(() => { return Promise.all(promises).then(() => {
@ -590,5 +486,5 @@ export class CoreCoursesDelegate {
this.coursesHandlers[courseId].deferred.resolve(); this.coursesHandlers[courseId].deferred.resolve();
} }
}); });
}; }
} }

View File

@ -16,45 +16,33 @@ import { Injectable } from '@angular/core';
import { CoreEventsProvider } from '../../../providers/events'; import { CoreEventsProvider } from '../../../providers/events';
import { CoreLoggerProvider } from '../../../providers/logger'; import { CoreLoggerProvider } from '../../../providers/logger';
import { CoreSitesProvider } from '../../../providers/sites'; import { CoreSitesProvider } from '../../../providers/sites';
import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate';
/** /**
* Interface that all handlers must implement. * Interface that all handlers must implement.
*/ */
export interface CoreFileUploaderHandler { export interface CoreFileUploaderHandler extends CoreDelegateHandler {
/**
* A name to identify the addon.
* @type {string}
*/
name: string;
/** /**
* Handler's priority. The highest priority, the highest position. * Handler's priority. The highest priority, the highest position.
* @type {string} * @type {string}
*/ */
priority?: number; priority?: number;
/**
* Whether or not the handler is enabled on a site level.
*
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/
isEnabled(): boolean|Promise<boolean>;
/** /**
* Given a list of mimetypes, return the ones that are supported by the handler. * Given a list of mimetypes, return the ones that are supported by the handler.
* *
* @param {string[]} [mimetypes] List of mimetypes. * @param {string[]} [mimetypes] List of mimetypes.
* @return {string[]} Supported mimetypes. * @return {string[]} Supported mimetypes.
*/ */
getSupportedMimetypes(mimetypes: string[]) : string[]; getSupportedMimetypes(mimetypes: string[]): string[];
/** /**
* Get the data to display the handler. * Get the data to display the handler.
* *
* @return {CoreFileUploaderHandlerData} Data. * @return {CoreFileUploaderHandlerData} Data.
*/ */
getData() : CoreFileUploaderHandlerData; getData(): CoreFileUploaderHandlerData;
}; }
/** /**
* Data needed to render the handler in the file picker. It must be returned by the handler. * Data needed to render the handler in the file picker. It must be returned by the handler.
@ -88,7 +76,7 @@ export interface CoreFileUploaderHandlerData {
* @return {Promise<CoreFileUploaderHandlerResult>} Promise resolved with the result of picking/uploading the file. * @return {Promise<CoreFileUploaderHandlerResult>} Promise resolved with the result of picking/uploading the file.
*/ */
action?(maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]) action?(maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[])
: Promise<CoreFileUploaderHandlerResult>; : Promise<CoreFileUploaderHandlerResult>;
/** /**
* Function called after the handler is rendered. * Function called after the handler is rendered.
@ -98,8 +86,8 @@ export interface CoreFileUploaderHandlerData {
* @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection. * @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection.
* @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported.
*/ */
afterRender?(maxSize: number, upload: boolean, allowOffline: boolean, mimetypes: string[]) : void; afterRender?(maxSize: number, upload: boolean, allowOffline: boolean, mimetypes: string[]): void;
}; }
/** /**
* The result of clicking a handler. * The result of clicking a handler.
@ -134,7 +122,7 @@ export interface CoreFileUploaderHandlerResult {
* @type {any} * @type {any}
*/ */
result?: any; result?: any;
}; }
/** /**
* Data returned by the delegate for each handler. * Data returned by the delegate for each handler.
@ -146,37 +134,32 @@ export interface CoreFileUploaderHandlerDataToReturn extends CoreFileUploaderHan
*/ */
priority?: number; priority?: number;
/** /**
* Supported mimetypes. * Supported mimetypes.
* @type {string[]} * @type {string[]}
*/ */
mimetypes?: string[]; mimetypes?: string[];
}; }
/** /**
* Delegate to register handlers to be shown in the file picker. * Delegate to register handlers to be shown in the file picker.
*/ */
@Injectable() @Injectable()
export class CoreFileUploaderDelegate { export class CoreFileUploaderDelegate extends CoreDelegate {
protected logger; protected handlers: { [s: string]: CoreFileUploaderHandler } = {}; // All registered handlers.
protected handlers: {[s: string]: CoreFileUploaderHandler} = {}; // All registered handlers. protected enabledHandlers: { [s: string]: CoreFileUploaderHandler } = {}; // Handlers enabled for the current site.
protected enabledHandlers: {[s: string]: CoreFileUploaderHandler} = {}; // Handlers enabled for the current site.
protected lastUpdateHandlersStart: number;
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider) { constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider,
this.logger = logger.getInstance('CoreFileUploaderDelegate'); protected eventsProvider: CoreEventsProvider) {
super('CoreFileUploaderDelegate', loggerProvider, sitesProvider, eventsProvider);
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this));
eventsProvider.on(CoreEventsProvider.LOGOUT, this.clearSiteHandlers.bind(this)); eventsProvider.on(CoreEventsProvider.LOGOUT, this.clearSiteHandlers.bind(this));
} }
/** /**
* Clear current site handlers. Reserved for core use. * Clear current site handlers. Reserved for core use.
*/ */
protected clearSiteHandlers() : void { protected clearSiteHandlers(): void {
this.enabledHandlers = {}; this.enabledHandlers = {};
} }
@ -186,7 +169,7 @@ export class CoreFileUploaderDelegate {
* @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported.
* @return {CoreFileUploaderHandlerDataToReturn[]} List of handlers data. * @return {CoreFileUploaderHandlerDataToReturn[]} List of handlers data.
*/ */
getHandlers(mimetypes: string[]) : CoreFileUploaderHandlerDataToReturn[] { getHandlers(mimetypes: string[]): CoreFileUploaderHandlerDataToReturn[] {
let handlers = []; let handlers = [];
for (let name in this.enabledHandlers) { for (let name in this.enabledHandlers) {
@ -207,7 +190,7 @@ export class CoreFileUploaderDelegate {
} }
} }
let data : CoreFileUploaderHandlerDataToReturn = handler.getData(); let data: CoreFileUploaderHandlerDataToReturn = handler.getData();
data.priority = handler.priority; data.priority = handler.priority;
data.mimetypes = supportedMimetypes; data.mimetypes = supportedMimetypes;
handlers.push(data); handlers.push(data);
@ -215,89 +198,4 @@ export class CoreFileUploaderDelegate {
return handlers; return handlers;
} }
/**
* Check if a time belongs to the last update handlers call.
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
*
* @param {number} time Time to check.
* @return {boolean} Whether it's the last call.
*/
isLastUpdateCall(time: number) : boolean {
if (!this.lastUpdateHandlersStart) {
return true;
}
return time == this.lastUpdateHandlersStart;
}
/**
* Register a handler.
*
* @param {CoreFileUploaderHandler} handler The handler to register.
* @return {boolean} True if registered successfully, false otherwise.
*/
registerHandler(handler: CoreFileUploaderHandler) : boolean {
if (typeof this.handlers[handler.name] !== 'undefined') {
this.logger.log(`Addon '${handler.name}' already registered`);
return false;
}
this.logger.log(`Registered addon '${handler.name}'`);
this.handlers[handler.name] = handler;
return true;
}
/**
* Update the handler for the current site.
*
* @param {CoreFileUploaderHandler} handler The handler to check.
* @param {number} time Time this update process started.
* @return {Promise<void>} Resolved when done.
*/
protected updateHandler(handler: CoreFileUploaderHandler, time: number) : Promise<void> {
let promise,
siteId = this.sitesProvider.getCurrentSiteId();
if (!this.sitesProvider.isLoggedIn()) {
promise = Promise.reject(null);
} else {
promise = Promise.resolve(handler.isEnabled());
}
// Checks if the handler is enabled.
return promise.catch(() => {
return false;
}).then((enabled: boolean) => {
// Verify that this call is the last one that was started.
if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() === siteId) {
if (enabled) {
this.enabledHandlers[handler.name] = handler;
} else {
delete this.enabledHandlers[handler.name];
}
}
});
}
/**
* Update the handlers for the current site.
*
* @return {Promise<any>} Resolved when done.
*/
protected updateHandlers() : Promise<any> {
let promises = [],
now = Date.now();
this.logger.debug('Updating handlers for current site.');
this.lastUpdateHandlersStart = now;
// Loop over all the handlers.
for (let name in this.handlers) {
promises.push(this.updateHandler(this.handlers[name], now));
}
return Promise.all(promises).catch(() => {
// Never reject.
});
}
} }

View File

@ -19,7 +19,7 @@ import { CoreLoggerProvider } from '../../../providers/logger';
import { CoreSitesProvider } from '../../../providers/sites'; import { CoreSitesProvider } from '../../../providers/sites';
import { CoreEventsProvider } from '../../../providers/events'; import { CoreEventsProvider } from '../../../providers/events';
export interface CoreUserProfileHandler extends CoreDelegateHandler { export interface CoreUserProfileHandler extends CoreDelegateHandler {
/** /**
* The highest priority is displayed first. * The highest priority is displayed first.
* @type {number} * @type {number}
@ -44,7 +44,7 @@ export interface CoreUserProfileHandler extends CoreDelegateHandler {
* @param {any} [admOptions] Admin options for the course. * @param {any} [admOptions] Admin options for the course.
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled for a user. * @return {boolean|Promise<boolean>} Whether or not the handler is enabled for a user.
*/ */
isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean|Promise<boolean>; isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise<boolean>;
/** /**
* Returns the data needed to render the handler. * Returns the data needed to render the handler.
@ -53,7 +53,7 @@ export interface CoreUserProfileHandler extends CoreDelegateHandler {
* @return {CoreUserProfileHandlerData} Data to be shown. * @return {CoreUserProfileHandlerData} Data to be shown.
*/ */
getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData; getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData;
}; }
export interface CoreUserProfileHandlerData { export interface CoreUserProfileHandlerData {
/** /**
@ -94,7 +94,7 @@ export interface CoreUserProfileHandlerData {
* @return {any} Action to be done. * @return {any} Action to be done.
*/ */
action?($event: any, user: any, courseId: number): any; action?($event: any, user: any, courseId: number): any;
}; }
/** /**
* Service to interact with plugins to be shown in user profile. Provides functions to register a plugin * Service to interact with plugins to be shown in user profile. Provides functions to register a plugin
@ -119,12 +119,12 @@ export class CoreUserDelegate extends CoreDelegate {
*/ */
public static TYPE_ACTION = 'action'; public static TYPE_ACTION = 'action';
protected handlers: {[s: string]: CoreUserProfileHandler} = {}; protected handlers: { [s: string]: CoreUserProfileHandler } = {};
protected enabledHandlers: {[s: string]: CoreUserProfileHandler} = {}; protected enabledHandlers: { [s: string]: CoreUserProfileHandler } = {};
protected featurePrefix = '$mmUserDelegate_'; protected featurePrefix = '$mmUserDelegate_';
constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider,
private coursesProvider: CoreCoursesProvider, protected eventsProvider: CoreEventsProvider) { private coursesProvider: CoreCoursesProvider, protected eventsProvider: CoreEventsProvider) {
super('CoreUserDelegate', loggerProvider, sitesProvider, eventsProvider); super('CoreUserDelegate', loggerProvider, sitesProvider, eventsProvider);
} }
@ -168,7 +168,7 @@ export class CoreUserDelegate extends CoreDelegate {
}).catch(() => { }).catch(() => {
// Nothing to do here, it is not enabled for this user. // Nothing to do here, it is not enabled for this user.
}); });
promises.push(promise); promises.push(promise);
} }
return Promise.all(promises).then(() => { return Promise.all(promises).then(() => {