From 8196c79954411d10ea21ea5ea11b88b59d5c99d4 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 7 Nov 2017 13:01:32 +0100 Subject: [PATCH] MOBILE-2261 init: Implement init delegate --- src/app/app.module.ts | 22 ++++- src/providers/app.ts | 46 ----------- src/providers/init.ts | 156 +++++++++++++++++++++++++++++++++++ src/providers/utils/utils.ts | 14 ++++ 4 files changed, 189 insertions(+), 49 deletions(-) create mode 100644 src/providers/init.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0a428fa01..f06925b83 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,6 +1,6 @@ import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; -import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; +import { IonicApp, IonicErrorHandler, IonicModule, Platform } from 'ionic-angular'; import { HttpClient, HttpClientModule } from '@angular/common/http'; import { SplashScreen } from '@ionic-native/splash-screen'; @@ -23,6 +23,7 @@ import { CoreTimeUtilsProvider } from '../providers/utils/time'; import { CoreUrlUtilsProvider } from '../providers/utils/url'; import { CoreUtilsProvider } from '../providers/utils/utils'; import { CoreMimetypeUtilsProvider } from '../providers/utils/mimetype'; +import { CoreInitDelegate } from '../providers/init'; // For translate loader. AoT requires an exported function for factories. export function createTranslateLoader(http: HttpClient) { @@ -66,7 +67,22 @@ export function createTranslateLoader(http: HttpClient) { CoreTimeUtilsProvider, CoreUrlUtilsProvider, CoreUtilsProvider, - CoreMimetypeUtilsProvider + CoreMimetypeUtilsProvider, + CoreInitDelegate ] }) -export class AppModule {} +export class AppModule { + constructor(platform: Platform, initDelegate: CoreInitDelegate) { + // Create a handler for platform ready and register it in the init delegate. + let handler = { + name: 'CorePlatformReady', + priority: initDelegate.MAX_RECOMMENDED_PRIORITY + 400, + blocking: true, + load: platform.ready + }; + initDelegate.registerProcess(handler); + + // Execute the init processes. + initDelegate.executeInitProcesses(); + } +} diff --git a/src/providers/app.ts b/src/providers/app.ts index 7160bea79..ec267f4f6 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -91,22 +91,6 @@ export class CoreAppProvider { return this.db; }; - /** - * Core init process for the app. - * - * @description - * This should be the first init process of all, no other process should run until we are certain that the cordova plugins - * are loaded, which is what platform.ready tells us. - * - * Reserved for core use, do not call directly. - * - * @protected - * @return Promise resolved when ready. - */ - initProcess() : Promise { - return this.platform.ready(); - }; - /** * Checks if the app is running in a desktop environment (not browser). * @@ -165,19 +149,6 @@ export class CoreAppProvider { return limited.indexOf(type) > -1; }; - /** - * Instantly returns if the app is ready. - * - * To be notified when the app is ready, refer to {@link $mmApp#ready}. - * - * @return {Boolean} True when it is, false when not. - * @todo - */ - isReady() { - // var promise = $injector.get('$mmInitDelegate').ready(); - // return promise.$$state.status === 1; - }; - /** * Open the keyboard. */ @@ -188,23 +159,6 @@ export class CoreAppProvider { } }; - /** - * Resolves when the app is ready. - * - * Usage: - * - * $mmApp.ready().then(function() { - * // What you want to do. - * }); - * - * @return {Promise} Resolved when the app is initialised. Never rejected. - * @todo - */ - ready() { - // Injects to prevent circular dependencies. - // return $injector.get('$mmInitDelegate').ready(); - }; - /** * Start an SSO authentication process. * Please notice that this function should be called when the app receives the new token from the browser, diff --git a/src/providers/init.ts b/src/providers/init.ts new file mode 100644 index 000000000..e90393203 --- /dev/null +++ b/src/providers/init.ts @@ -0,0 +1,156 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { Platform } from 'ionic-angular'; +import { CoreLoggerProvider } from './logger'; +import { CoreUtilsProvider } from './utils/utils'; + +export interface CoreInitHandler { + name: string; // Name of the handler. + load(): Promise; // Function to execute during the init process. + priority?: number; // The highest priority is executed first. You should use values lower than MAX_RECOMMENDED_PRIORITY. + blocking?: boolean; // Set this to true when this process should be resolved before any following one. +}; + +/* + * Provider for initialisation mechanisms. + */ +@Injectable() +export class CoreInitDelegate { + DEFAULT_PRIORITY = 100; // Default priority for init processes. + MAX_RECOMMENDED_PRIORITY = 600; + initProcesses = {}; + logger; + readiness; + + constructor(logger: CoreLoggerProvider, platform: Platform, private utils: CoreUtilsProvider) { + this.logger = logger.getInstance('CoreInitDelegate'); + } + + /** + * Executes the registered init processes. + * + * Reserved for core use, do not call directly. + */ + executeInitProcesses() : void { + let ordered = []; + + if (typeof this.readiness == 'undefined') { + this.initReadiness(); + } + + // Re-ordering by priority. + for (let name in this.initProcesses) { + ordered.push(this.initProcesses[name]); + } + ordered.sort((a, b) => { + return b.priority - a.priority; + }); + + ordered = ordered.map((data: CoreInitHandler) => { + return { + context: this, + func: this.prepareProcess, + params: [data], + blocking: !!data.blocking + }; + }); + + // Execute all the processes in order to solve dependencies. + this.utils.executeOrderedPromises(ordered).finally(this.readiness.resolve); + } + + /** + * Init the readiness promise. + */ + protected initReadiness() : void { + this.readiness = this.utils.promiseDefer(); + this.readiness.promise.then(() => this.readiness.resolved = true); + } + + /** + * Instantly returns if the app is ready. + * + * To be notified when the app is ready, refer to {@link $mmApp#ready}. + * + * @return {boolean} Whether it's ready. + */ + isReady(): boolean { + return this.readiness.resolved; + }; + + /** + * Convenience function to return a function that executes the process. + * + * @param {CoreInitHandler} data The data of the process. + * @return {Promise} Promise of the process. + */ + protected prepareProcess(data: CoreInitHandler) : Promise { + let promise; + + this.logger.debug(`Executing init process '${data.name}'`); + + try { + promise = data.load(); + } catch (e) { + this.logger.error('Error while calling the init process \'' + data.name + '\'. ' + e); + return; + } + + return promise; + } + + /** + * Notifies when the app is ready. This returns a promise that is resolved when the app is initialised. + * + * @return {Promise} Resolved when the app is initialised. Never rejected. + */ + ready() : Promise { + if (typeof this.readiness === 'undefined') { + // Prevent race conditions if this is called before executeInitProcesses. + this.initReadiness(); + } + + return this.readiness.promise; + } + + /** + * Registers an initialisation process. + * + * @description + * Init processes can be used to add initialisation logic to the app. Anything that should block the user interface while + * some processes are done should be an init process. It is recommended to use a priority lower than MAX_RECOMMENDED_PRIORITY + * to make sure that your process does not happen before some essential other core processes. + * + * An init process should never change state or prompt user interaction. + * + * This delegate cannot be used in remote addons. + * + * @param {CoreInitHandler} instance The instance of the handler. + */ + registerProcess(handler: CoreInitHandler) : void { + if (typeof handler.priority == 'undefined') { + handler.priority = this.DEFAULT_PRIORITY; + } + + if (typeof this.initProcesses[handler.name] != 'undefined') { + this.logger.log(`Process '${handler.name}' already registered.`); + return; + } + + this.logger.log(`Registering process '${handler.name}'.`); + this.initProcesses[handler.name] = handler; + } +} diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index 2633f600f..c1644346b 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -916,6 +916,20 @@ export class CoreUtilsProvider { return mapped; } + /** + * Similar to AngularJS $q.defer(). It will return an object containing the promise, and the resolve and reject functions. + * + * @return {any} Object containing the promise, and the resolve and reject functions. + */ + promiseDefer() : any { + let deferred: any = {}; + deferred.promise = new Promise((resolve, reject) => { + deferred.resolve = resolve; + deferred.reject = reject; + }); + return deferred; + } + /** * Given a promise, returns true if it's rejected or false if it's resolved. *