From 93568a9c9768937a1c8ee977fdcbdb9ac16c072f Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 21 Nov 2017 09:56:16 +0100 Subject: [PATCH] MOBILE-2261 core: Implement first version of update manager --- src/app/app.module.ts | 9 +- src/core/emulator/providers/helper.ts | 6 +- src/providers/init.ts | 7 +- src/providers/update-manager.ts | 176 ++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 src/providers/update-manager.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a511cad3f..e01e009b3 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -48,6 +48,7 @@ import { CoreGroupsProvider } from '../providers/groups'; import { CoreCronDelegate } from '../providers/cron'; import { CoreFileSessionProvider } from '../providers/file-session'; import { CoreFilepoolProvider } from '../providers/filepool'; +import { CoreUpdateManagerProvider } from '../providers/update-manager'; // For translate loader. AoT requires an exported function for factories. export function createTranslateLoader(http: HttpClient) { @@ -103,19 +104,23 @@ export function createTranslateLoader(http: HttpClient) { CoreCronDelegate, CoreFileSessionProvider, CoreFilepoolProvider, + CoreUpdateManagerProvider, ] }) export class AppModule { - constructor(platform: Platform, initDelegate: CoreInitDelegate) { + constructor(platform: Platform, initDelegate: CoreInitDelegate, updateManager: CoreUpdateManagerProvider) { // Create a handler for platform ready and register it in the init delegate. let handler = { name: 'CorePlatformReady', - priority: initDelegate.MAX_RECOMMENDED_PRIORITY + 400, + priority: CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 400, blocking: true, load: platform.ready }; initDelegate.registerProcess(handler); + // Register the update manager as an init process. + initDelegate.registerProcess(updateManager); + // Execute the init processes. initDelegate.executeInitProcesses(); } diff --git a/src/core/emulator/providers/helper.ts b/src/core/emulator/providers/helper.ts index 7e2e76c5f..987835a22 100644 --- a/src/core/emulator/providers/helper.ts +++ b/src/core/emulator/providers/helper.ts @@ -27,13 +27,11 @@ import { FileTransferErrorMock } from './file-transfer'; @Injectable() export class CoreEmulatorHelper implements CoreInitHandler { name = 'CoreEmulator'; - priority; + priority = CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 500; blocking = true; constructor(private file: File, private fileProvider: CoreFileProvider, private utils: CoreUtilsProvider, - initDelegate: CoreInitDelegate, private localNotif: LocalNotifications, private appProvider: CoreAppProvider) { - this.priority = initDelegate.MAX_RECOMMENDED_PRIORITY + 500; - } + initDelegate: CoreInitDelegate, private localNotif: LocalNotifications, private appProvider: CoreAppProvider) {} /** * Check if the app is running in a Linux environment. diff --git a/src/providers/init.ts b/src/providers/init.ts index e90393203..37dd18ab2 100644 --- a/src/providers/init.ts +++ b/src/providers/init.ts @@ -29,8 +29,9 @@ export interface CoreInitHandler { */ @Injectable() export class CoreInitDelegate { - DEFAULT_PRIORITY = 100; // Default priority for init processes. - MAX_RECOMMENDED_PRIORITY = 600; + public static DEFAULT_PRIORITY = 100; // Default priority for init processes. + public static MAX_RECOMMENDED_PRIORITY = 600; + initProcesses = {}; logger; readiness; @@ -142,7 +143,7 @@ export class CoreInitDelegate { */ registerProcess(handler: CoreInitHandler) : void { if (typeof handler.priority == 'undefined') { - handler.priority = this.DEFAULT_PRIORITY; + handler.priority = CoreInitDelegate.DEFAULT_PRIORITY; } if (typeof this.initProcesses[handler.name] != 'undefined') { diff --git a/src/providers/update-manager.ts b/src/providers/update-manager.ts new file mode 100644 index 000000000..dc7d16889 --- /dev/null +++ b/src/providers/update-manager.ts @@ -0,0 +1,176 @@ +// (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 { CoreConfigProvider } from './config'; +import { CoreFilepoolProvider } from './filepool'; +import { CoreInitHandler, CoreInitDelegate } from './init'; +import { CoreLocalNotificationsProvider } from './local-notifications'; +import { CoreLoggerProvider } from './logger'; +import { CoreSitesProvider } from './sites'; +import { CoreConfigConstants } from '../configconstants'; + +/** + * Factory to handle app updates. This factory shouldn't be used outside of core. + * + * This service handles processes that need to be run when updating the app, like migrate Ionic 1 database data to Ionic 3. + */ +@Injectable() +export class CoreUpdateManagerProvider implements CoreInitHandler { + // Data for init delegate. + public name = 'CoreUpdateManager'; + public priority = CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 300; + public blocking = true; + + protected VERSION_APPLIED = 'version_applied'; + protected logger; + + constructor(logger: CoreLoggerProvider, private configProvider: CoreConfigProvider, private sitesProvider: CoreSitesProvider, + private filepoolProvider: CoreFilepoolProvider, private notifProvider: CoreLocalNotificationsProvider) { + this.logger = logger.getInstance('CoreUpdateManagerProvider'); + } + + /** + * Check if the app has been updated and performs the needed processes. + * This function shouldn't be used outside of core. + * + * @return {Promise} Promise resolved when the update process finishes. + */ + load() : Promise { + let promises = [], + versionCode = CoreConfigConstants.versioncode; + + return this.configProvider.get(this.VERSION_APPLIED, 0).then((versionApplied: number) => { + // @todo: Migrate all data from ydn-db to SQLite if there is no versionApplied. + + if (versionCode >= 2013 && versionApplied < 2013) { + promises.push(this.migrateFileExtensions()); + } + + if (versionCode >= 2017 && versionApplied < 2017) { + promises.push(this.setCalendarDefaultNotifTime()); + promises.push(this.setSitesConfig()); + } + + if (versionCode >= 2018 && versionApplied < 2018) { + promises.push(this.adaptForumOfflineStores()); + } + + return Promise.all(promises).then(() => { + return this.configProvider.set(this.VERSION_APPLIED, versionCode); + }).catch((error) => { + this.logger.error(`Error applying update from ${versionApplied} to ${versionCode}`, error); + }); + }); + } + + /** + * Migrates files filling extensions. + * + * @return {Promise} Promise resolved when the site migration is finished. + */ + protected migrateFileExtensions() : Promise { + return this.sitesProvider.getSitesIds().then((sites) => { + let promises = []; + sites.forEach((siteId) => { + promises.push(this.filepoolProvider.fillMissingExtensionInFiles(siteId)); + }); + promises.push(this.filepoolProvider.treatExtensionInQueue()); + return Promise.all(promises); + }); + } + + /** + * Calendar default notification time is configurable from version 3.2.1, and a new option "Default" is added. + * All events that were configured to use the fixed default time should now be configured to use "Default" option. + * + * @return {Promise} Promise resolved when the events are configured. + */ + protected setCalendarDefaultNotifTime() : Promise { + if (!this.notifProvider.isAvailable()) { + // Local notifications not available, nothing to do. + return Promise.resolve(); + } + + // @todo: Implement it once Calendar addon is implemented. + return Promise.resolve(); + } + + /** + * In version 3.2.1 we want the site config to be stored in each site if available. + * Since it can be slow, we'll only block retrieving the config of current site, the rest will be in background. + * + * @return {Promise} Promise resolved when the config is loaded for the current site (if any). + */ + protected setSitesConfig() : Promise { + return this.sitesProvider.getSitesIds().then((siteIds) => { + + return this.sitesProvider.getStoredCurrentSiteId().catch(() => { + // Error getting current site. + }).then((currentSiteId) => { + let promise; + + // Load the config of current site first. + if (currentSiteId) { + promise = this.setSiteConfig(currentSiteId); + } else { + promise = Promise.resolve(); + } + + // Load the config of rest of sites in background. + siteIds.forEach((siteId) => { + if (siteId != currentSiteId) { + this.setSiteConfig(siteId); + } + }); + + return promise; + }); + }); + } + + /** + * Store the config of a site. + * + * @param {String} siteId Site ID. + * @return {Promise} Promise resolved when the config is loaded for the site. + */ + protected setSiteConfig(siteId: string) : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + if (site.getStoredConfig() || !site.wsAvailable('tool_mobile_get_config')) { + // Site already has the config or it cannot be retrieved. Stop. + return; + } + + // Get the site config. + return site.getConfig().then((config) => { + return this.sitesProvider.addSite( + site.getId(), site.getURL(), site.getToken(), site.getInfo(), site.getPrivateToken(), config); + }).catch(() => { + // Ignore errors. + }); + }); + } + + /** + * The data stored for offline discussions and posts changed its format. Adapt the entries already stored. + * Since it can be slow, we'll only block migrating the db of current site, the rest will be in background. + * + * @return {Promise} Promise resolved when the db is migrated. + */ + protected adaptForumOfflineStores() : Promise { + // @todo: Implement it once Forum addon is implemented. + return Promise.resolve(); + } +}