forked from EVOgeek/Vmeda.Online
		
	MOBILE-3592 user: Implement user sync cron handler
This commit is contained in:
		
							parent
							
								
									3722126b5b
								
							
						
					
					
						commit
						a783a89db3
					
				
							
								
								
									
										307
									
								
								src/core/classes/base-sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										307
									
								
								src/core/classes/base-sync.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,307 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // 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 { CoreApp } from '@services/app'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreSync } from '@services/sync'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreTimeUtils } from '@services/utils/time'; | ||||
| import { Translate } from '@singletons'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| import { CoreError } from '@classes/errors/error'; | ||||
| 
 | ||||
| /** | ||||
|  * Blocked sync error. | ||||
|  */ | ||||
| export class CoreSyncBlockedError extends CoreError {} | ||||
| 
 | ||||
| /** | ||||
|  * Base class to create sync providers. It provides some common functions. | ||||
|  */ | ||||
| export class CoreSyncBaseProvider<T = void> { | ||||
| 
 | ||||
|     /** | ||||
|      * Logger instance. | ||||
|      */ | ||||
|     protected logger: CoreLogger; | ||||
| 
 | ||||
|     /** | ||||
|      * Component of the sync provider. | ||||
|      */ | ||||
|     component = 'core'; | ||||
| 
 | ||||
|     /** | ||||
|      * Sync provider's interval. | ||||
|      */ | ||||
|     syncInterval = 300000; | ||||
| 
 | ||||
|     // Store sync promises.
 | ||||
|     protected syncPromises: { [siteId: string]: { [uniqueId: string]: Promise<T> } } = {}; | ||||
| 
 | ||||
|     constructor(component: string) { | ||||
|         this.logger = CoreLogger.getInstance(component); | ||||
|         this.component = component; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Add an offline data deleted warning to a list of warnings. | ||||
|      * | ||||
|      * @param warnings List of warnings. | ||||
|      * @param component Component. | ||||
|      * @param name Instance name. | ||||
|      * @param error Specific error message. | ||||
|      */ | ||||
|     protected addOfflineDataDeletedWarning(warnings: string[], component: string, name: string, error: string): void { | ||||
|         const warning = Translate.instance.instant('core.warningofflinedatadeleted', { | ||||
|             component: component, | ||||
|             name: name, | ||||
|             error: error, | ||||
|         }); | ||||
| 
 | ||||
|         if (warnings.indexOf(warning) == -1) { | ||||
|             warnings.push(warning); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Add an ongoing sync to the syncPromises list. On finish the promise will be removed. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @param promise The promise of the sync to add. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return The sync promise. | ||||
|      */ | ||||
|     async addOngoingSync(id: string | number, promise: Promise<T>, siteId?: string): Promise<T> { | ||||
|         siteId = siteId || CoreSites.instance.getCurrentSiteId(); | ||||
| 
 | ||||
|         if (!siteId) { | ||||
|             throw new CoreError('CoreSyncBaseProvider: Site ID not supplied'); | ||||
|         } | ||||
| 
 | ||||
|         const uniqueId = this.getUniqueSyncId(id); | ||||
|         if (!this.syncPromises[siteId]) { | ||||
|             this.syncPromises[siteId] = {}; | ||||
|         } | ||||
| 
 | ||||
|         this.syncPromises[siteId][uniqueId] = promise; | ||||
| 
 | ||||
|         // Promise will be deleted when finish.
 | ||||
|         try { | ||||
|             return await promise; | ||||
|         } finally { | ||||
|             delete this.syncPromises[siteId!][uniqueId]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * If there's an ongoing sync for a certain identifier return it. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise of the current sync or undefined if there isn't any. | ||||
|      */ | ||||
|     getOngoingSync(id: string | number, siteId?: string): Promise<T> | undefined { | ||||
|         siteId = siteId || CoreSites.instance.getCurrentSiteId(); | ||||
| 
 | ||||
|         if (!this.isSyncing(id, siteId)) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // There's already a sync ongoing for this id, return the promise.
 | ||||
|         const uniqueId = this.getUniqueSyncId(id); | ||||
| 
 | ||||
|         return this.syncPromises[siteId][uniqueId]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the synchronization time in a human readable format. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the readable time. | ||||
|      */ | ||||
|     async getReadableSyncTime(id: string | number, siteId?: string): Promise<string> { | ||||
|         const time = await this.getSyncTime(id, siteId); | ||||
| 
 | ||||
|         return this.getReadableTimeFromTimestamp(time); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a timestamp return it in a human readable format. | ||||
|      * | ||||
|      * @param timestamp Timestamp | ||||
|      * @return Human readable time. | ||||
|      */ | ||||
|     getReadableTimeFromTimestamp(timestamp: number): string { | ||||
|         if (!timestamp) { | ||||
|             return Translate.instance.instant('core.never'); | ||||
|         } else { | ||||
|             return CoreTimeUtils.instance.userDate(timestamp); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the synchronization time. Returns 0 if no time stored. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the time. | ||||
|      */ | ||||
|     async getSyncTime(id: string | number, siteId?: string): Promise<number> { | ||||
|         try { | ||||
|             const entry = await CoreSync.instance.getSyncRecord(this.component, id, siteId); | ||||
| 
 | ||||
|             return entry.time; | ||||
|         } catch { | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the synchronization warnings of an instance. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the warnings. | ||||
|      */ | ||||
|     async getSyncWarnings(id: string | number, siteId?: string): Promise<string[]> { | ||||
|         try { | ||||
|             const entry = await CoreSync.instance.getSyncRecord(this.component, id, siteId); | ||||
| 
 | ||||
|             return  <string[]> CoreTextUtils.instance.parseJSON(entry.warnings, []); | ||||
|         } catch { | ||||
|             return []; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a unique identifier from component and id. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @return Unique identifier from component and id. | ||||
|      */ | ||||
|     protected getUniqueSyncId(id: string | number): string { | ||||
|         return this.component + '#' + id; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a there's an ongoing syncronization for the given id. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Whether it's synchronizing. | ||||
|      */ | ||||
|     isSyncing(id: string | number, siteId?: string): boolean { | ||||
|         siteId = siteId || CoreSites.instance.getCurrentSiteId(); | ||||
| 
 | ||||
|         const uniqueId = this.getUniqueSyncId(id); | ||||
| 
 | ||||
|         return !!(this.syncPromises[siteId] && this.syncPromises[siteId][uniqueId]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a sync is needed: if a certain time has passed since the last time. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with boolean: whether sync is needed. | ||||
|      */ | ||||
|     async isSyncNeeded(id: string | number, siteId?: string): Promise<boolean> { | ||||
|         const time = await this.getSyncTime(id, siteId); | ||||
| 
 | ||||
|         return Date.now() - this.syncInterval >= time; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the synchronization time. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @param time Time to set. If not defined, current time. | ||||
|      * @return Promise resolved when the time is set. | ||||
|      */ | ||||
|     async setSyncTime(id: string, siteId?: string, time?: number): Promise<void> { | ||||
|         time = typeof time != 'undefined' ? time : Date.now(); | ||||
| 
 | ||||
|         await CoreSync.instance.insertOrUpdateSyncRecord(this.component, id, { time: time }, siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the synchronization warnings. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @param warnings Warnings to set. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async setSyncWarnings(id: string, warnings: string[], siteId?: string): Promise<void> { | ||||
|         const warningsText = JSON.stringify(warnings || []); | ||||
| 
 | ||||
|         await CoreSync.instance.insertOrUpdateSyncRecord(this.component, id, { warnings: warningsText }, siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Execute a sync function on selected sites. | ||||
|      * | ||||
|      * @param syncFunctionLog Log message to explain the sync function purpose. | ||||
|      * @param syncFunction Sync function to execute. | ||||
|      * @param siteId Site ID to sync. If not defined, sync all sites. | ||||
|      * @return Resolved with siteIds selected. Rejected if offline. | ||||
|      */ | ||||
|     async syncOnSites(syncFunctionLog: string, syncFunction: (siteId: string) => void, siteId?: string): Promise<void> { | ||||
|         if (!CoreApp.instance.isOnline()) { | ||||
|             const message = `Cannot sync '${syncFunctionLog}' because device is offline.`; | ||||
|             this.logger.debug(message); | ||||
| 
 | ||||
|             throw new CoreError(message); | ||||
|         } | ||||
| 
 | ||||
|         let siteIds: string[] = []; | ||||
| 
 | ||||
|         if (!siteId) { | ||||
|             // No site ID defined, sync all sites.
 | ||||
|             this.logger.debug(`Try to sync '${syncFunctionLog}' in all sites.`); | ||||
|             siteIds = await CoreSites.instance.getLoggedInSitesIds(); | ||||
|         } else { | ||||
|             this.logger.debug(`Try to sync '${syncFunctionLog}' in site '${siteId}'.`); | ||||
|             siteIds = [siteId]; | ||||
|         } | ||||
| 
 | ||||
|         // Execute function for every site.
 | ||||
|         await Promise.all(siteIds.map((siteId) => syncFunction(siteId))); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * If there's an ongoing sync for a certain identifier, wait for it to end. | ||||
|      * If there's no sync ongoing the promise will be resolved right away. | ||||
|      * | ||||
|      * @param id Unique sync identifier per component. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when there's no sync going on for the identifier. | ||||
|      */ | ||||
|     async waitForSync(id: string | number, siteId?: string): Promise<T | undefined> { | ||||
|         const promise = this.getOngoingSync(id, siteId); | ||||
| 
 | ||||
|         if (!promise) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             return await promise; | ||||
|         } catch { | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										50
									
								
								src/core/features/user/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/core/features/user/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // 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 { CoreCronHandler } from '@services/cron'; | ||||
| import { CoreUserSync } from '../user-sync'; | ||||
| 
 | ||||
| /** | ||||
|  * Synchronization cron handler. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class CoreUserSyncCronHandler implements CoreCronHandler { | ||||
| 
 | ||||
|     name = 'CoreUserSyncCronHandler'; | ||||
| 
 | ||||
|     /** | ||||
|      * Execute the process. | ||||
|      * Receives the ID of the site affected, undefined for all sites. | ||||
|      * | ||||
|      * @param siteId ID of the site affected, undefined for all sites. | ||||
|      * @param force Wether the execution is forced (manual sync). | ||||
|      * @return Promise resolved when done, rejected if failure. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     execute(siteId?: string, force?: boolean): Promise<void> { | ||||
|         return CoreUserSync.instance.syncPreferences(siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the time between consecutive executions. | ||||
|      * | ||||
|      * @return Time between consecutive executions (in ms). | ||||
|      */ | ||||
|     getInterval(): number { | ||||
|         return 300000; // 5 minutes.
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										107
									
								
								src/core/features/user/services/user-sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/core/features/user/services/user-sync.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // 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 { CoreSites } from '@services/sites'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreSyncBaseProvider } from '@classes/base-sync'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { CoreUserOffline } from './user-offline'; | ||||
| import { CoreUser } from './user'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to sync user preferences. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class CoreUserSyncProvider extends CoreSyncBaseProvider<string[]> { | ||||
| 
 | ||||
|     static readonly AUTO_SYNCED = 'core_user_autom_synced'; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super('CoreUserSync'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Try to synchronize user preferences in a certain site or in all sites. | ||||
|      * | ||||
|      * @param siteId Site ID to sync. If not defined, sync all sites. | ||||
|      * @return Promise resolved with warnings if sync is successful, rejected if sync fails. | ||||
|      */ | ||||
|     syncPreferences(siteId?: string): Promise<void> { | ||||
|         return this.syncOnSites('all user preferences', this.syncSitePreferences.bind(this), siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sync user preferences of a site. | ||||
|      * | ||||
|      * @param siteId Site ID to sync. | ||||
|      * @param Promise resolved with warnings if sync is successful, rejected if sync fails. | ||||
|      */ | ||||
|     async syncSitePreferences(siteId: string): Promise<string[]> { | ||||
|         siteId = siteId || CoreSites.instance.getCurrentSiteId(); | ||||
| 
 | ||||
|         const syncId = 'preferences'; | ||||
| 
 | ||||
|         if (this.isSyncing(syncId, siteId)) { | ||||
|             // There's already a sync ongoing, return the promise.
 | ||||
|             return this.getOngoingSync(syncId, siteId)!; | ||||
|         } | ||||
| 
 | ||||
|         this.logger.debug('Try to sync user preferences'); | ||||
| 
 | ||||
|         const syncPromise = this.performSyncSitePreferences(siteId); | ||||
| 
 | ||||
|         return this.addOngoingSync(syncId, syncPromise, siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sync user preferences of a site. | ||||
|      * | ||||
|      * @param siteId Site ID to sync. | ||||
|      * @param Promise resolved if sync is successful, rejected if sync fails. | ||||
|      */ | ||||
|     protected async performSyncSitePreferences(siteId: string): Promise<string[]> { | ||||
|         const warnings: string[] = []; | ||||
| 
 | ||||
|         const preferences = await CoreUserOffline.instance.getChangedPreferences(siteId); | ||||
| 
 | ||||
|         await CoreUtils.instance.allPromises(preferences.map(async (preference) => { | ||||
|             const onlineValue = await CoreUser.instance.getUserPreferenceOnline(preference.name, siteId); | ||||
| 
 | ||||
|             if (onlineValue !== null && preference.onlinevalue != onlineValue) { | ||||
|                 // Preference was changed on web while the app was offline, do not sync.
 | ||||
|                 return CoreUserOffline.instance.setPreference(preference.name, onlineValue, onlineValue, siteId); | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|                 await CoreUser.instance.setUserPreference(preference.name, preference.value, siteId); | ||||
|             } catch (error) { | ||||
|                 if (CoreUtils.instance.isWebServiceError(error)) { | ||||
|                     warnings.push(CoreTextUtils.instance.getErrorMessageFromError(error)!); | ||||
|                 } else { | ||||
|                     // Couldn't connect to server, reject.
 | ||||
|                     throw error; | ||||
|                 } | ||||
|             } | ||||
|         })); | ||||
| 
 | ||||
|         // All done, return the warnings.
 | ||||
|         return warnings; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreUserSync extends makeSingleton(CoreUserSyncProvider) {} | ||||
| @ -41,6 +41,13 @@ const routes: Routes = [ | ||||
|             ], | ||||
|             multi: true, | ||||
|         }, | ||||
|         // { @todo: Uncomment when the init process has been fixed.
 | ||||
|         //     provide: APP_INITIALIZER,
 | ||||
|         //     multi: true,
 | ||||
|         //     deps: [CoreCronDelegate, CoreUserSyncCronHandler],
 | ||||
|         //     useFactory: (cronDelegate: CoreCronDelegate, syncHandler: CoreUserSyncCronHandler) =>
 | ||||
|         //         () => cronDelegate.register(syncHandler),
 | ||||
|         // },
 | ||||
|     ], | ||||
| }) | ||||
| export class CoreUserModule {} | ||||
|  | ||||
| @ -112,7 +112,7 @@ export class CoreSyncProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with done. | ||||
|      */ | ||||
|     async insertOrUpdateSyncRecord(component: string, id: string, data: CoreSyncRecord, siteId?: string): Promise<void> { | ||||
|     async insertOrUpdateSyncRecord(component: string, id: string, data: Partial<CoreSyncRecord>, siteId?: string): Promise<void> { | ||||
|         const db = await CoreSites.instance.getSiteDb(siteId); | ||||
| 
 | ||||
|         data.component = component; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user