MOBILE-2061 course: Synchronize offline manual completion
This commit is contained in:
		
							parent
							
								
									be209e9cb5
								
							
						
					
					
						commit
						e4248a8a44
					
				| @ -1108,10 +1108,12 @@ | ||||
|   "core.course.errorgetmodule": "local_moodlemobileapp", | ||||
|   "core.course.hiddenfromstudents": "moodle", | ||||
|   "core.course.hiddenoncoursepage": "moodle", | ||||
|   "core.course.manualcompletionnotsynced": "local_moodlemobileapp", | ||||
|   "core.course.nocontentavailable": "local_moodlemobileapp", | ||||
|   "core.course.overriddennotice": "grades", | ||||
|   "core.course.sections": "moodle", | ||||
|   "core.course.useactivityonbrowser": "local_moodlemobileapp", | ||||
|   "core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp", | ||||
|   "core.coursedetails": "moodle", | ||||
|   "core.courses.allowguests": "enrol_guest", | ||||
|   "core.courses.availablecourses": "moodle", | ||||
|  | ||||
| @ -72,7 +72,7 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { | ||||
|             const modal = this.domUtils.showModalLoading(); | ||||
| 
 | ||||
|             this.courseProvider.markCompletedManually(this.completion.cmid, this.completion.state === 1 ? 0 : 1, | ||||
|                     this.completion.courseId).then((response) => { | ||||
|                     this.completion.courseId, this.completion.courseName).then((response) => { | ||||
| 
 | ||||
|                 if (!response.status) { | ||||
|                     return Promise.reject(null); | ||||
|  | ||||
| @ -13,6 +13,7 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { CoreCronDelegate } from '@providers/cron'; | ||||
| import { CoreCourseProvider } from './providers/course'; | ||||
| import { CoreCourseHelperProvider } from './providers/helper'; | ||||
| import { CoreCourseFormatDelegate } from './providers/format-delegate'; | ||||
| @ -26,6 +27,8 @@ import { CoreCourseFormatSingleActivityModule } from './formats/singleactivity/s | ||||
| import { CoreCourseFormatSocialModule } from './formats/social/social.module'; | ||||
| import { CoreCourseFormatTopicsModule } from './formats/topics/topics.module'; | ||||
| import { CoreCourseFormatWeeksModule } from './formats/weeks/weeks.module'; | ||||
| import { CoreCourseSyncProvider } from './providers/sync'; | ||||
| import { CoreCourseSyncCronHandler } from './providers/sync-cron-handler'; | ||||
| 
 | ||||
| // List of providers (without handlers).
 | ||||
| export const CORE_COURSE_PROVIDERS: any[] = [ | ||||
| @ -35,7 +38,8 @@ export const CORE_COURSE_PROVIDERS: any[] = [ | ||||
|     CoreCourseModuleDelegate, | ||||
|     CoreCourseModulePrefetchDelegate, | ||||
|     CoreCourseOptionsDelegate, | ||||
|     CoreCourseOfflineProvider | ||||
|     CoreCourseOfflineProvider, | ||||
|     CoreCourseSyncProvider | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
| @ -54,9 +58,15 @@ export const CORE_COURSE_PROVIDERS: any[] = [ | ||||
|         CoreCourseModulePrefetchDelegate, | ||||
|         CoreCourseOptionsDelegate, | ||||
|         CoreCourseOfflineProvider, | ||||
|         CoreCourseSyncProvider, | ||||
|         CoreCourseFormatDefaultHandler, | ||||
|         CoreCourseModuleDefaultHandler | ||||
|         CoreCourseModuleDefaultHandler, | ||||
|         CoreCourseSyncCronHandler | ||||
|     ], | ||||
|     exports: [] | ||||
| }) | ||||
| export class CoreCourseModule {} | ||||
| export class CoreCourseModule { | ||||
|     constructor(cronDelegate: CoreCronDelegate, syncHandler: CoreCourseSyncCronHandler) { | ||||
|         cronDelegate.register(syncHandler); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -23,5 +23,6 @@ | ||||
|     "overriddennotice": "Your final grade from this activity was manually adjusted.", | ||||
|     "refreshcourse": "Refresh course", | ||||
|     "sections": "Sections", | ||||
|     "useactivityonbrowser": "You can still use it using your device's web browser." | ||||
|     "useactivityonbrowser": "You can still use it using your device's web browser.", | ||||
|     "warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}" | ||||
| } | ||||
| @ -24,6 +24,7 @@ import { CoreCourseHelperProvider } from '../../providers/helper'; | ||||
| import { CoreCourseFormatDelegate } from '../../providers/format-delegate'; | ||||
| import { CoreCourseModulePrefetchDelegate } from '../../providers/module-prefetch-delegate'; | ||||
| import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay } from '../../providers/options-delegate'; | ||||
| import { CoreCourseSyncProvider } from '../../providers/sync'; | ||||
| import { CoreCourseFormatComponent } from '../../components/format/format'; | ||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||
| import { CoreTabsComponent } from '@components/tabs/tabs'; | ||||
| @ -62,6 +63,7 @@ export class CoreCourseSectionPage implements OnDestroy { | ||||
|     protected module: any; | ||||
|     protected completionObserver; | ||||
|     protected courseStatusObserver; | ||||
|     protected syncObserver; | ||||
|     protected firstTabName: string; | ||||
|     protected isDestroyed = false; | ||||
| 
 | ||||
| @ -70,7 +72,7 @@ export class CoreCourseSectionPage implements OnDestroy { | ||||
|             private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, eventsProvider: CoreEventsProvider, | ||||
|             private textUtils: CoreTextUtilsProvider, private coursesProvider: CoreCoursesProvider, | ||||
|             sitesProvider: CoreSitesProvider, private navCtrl: NavController, private injector: Injector, | ||||
|             private prefetchDelegate: CoreCourseModulePrefetchDelegate) { | ||||
|             private prefetchDelegate: CoreCourseModulePrefetchDelegate, private syncProvider: CoreCourseSyncProvider) { | ||||
|         this.course = navParams.get('course'); | ||||
|         this.sectionId = navParams.get('sectionId'); | ||||
|         this.sectionNumber = navParams.get('sectionNumber'); | ||||
| @ -87,7 +89,17 @@ export class CoreCourseSectionPage implements OnDestroy { | ||||
|             if (shouldRefresh) { | ||||
|                 this.completionObserver = eventsProvider.on(CoreEventsProvider.COMPLETION_MODULE_VIEWED, (data) => { | ||||
|                     if (data && data.courseId == this.course.id) { | ||||
|                         this.refreshAfterCompletionChange(); | ||||
|                         this.refreshAfterCompletionChange(true); | ||||
|                     } | ||||
|                 }); | ||||
| 
 | ||||
|                 this.syncObserver = eventsProvider.on(CoreCourseSyncProvider.AUTO_SYNCED, (data) => { | ||||
|                     if (data && data.courseId == this.course.id) { | ||||
|                         this.refreshAfterCompletionChange(false); | ||||
| 
 | ||||
|                         if (data.warnings && data.warnings[0]) { | ||||
|                             this.domUtils.showErrorModal(data.warnings[0]); | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
| @ -113,7 +125,7 @@ export class CoreCourseSectionPage implements OnDestroy { | ||||
|             this.courseHelper.openModule(this.navCtrl, this.module, this.course.id, this.sectionId); | ||||
|         } | ||||
| 
 | ||||
|         this.loadData().finally(() => { | ||||
|         this.loadData(false, true).finally(() => { | ||||
|             this.dataLoaded = true; | ||||
| 
 | ||||
|             if (!this.downloadCourseEnabled) { | ||||
| @ -146,19 +158,34 @@ export class CoreCourseSectionPage implements OnDestroy { | ||||
| 
 | ||||
|     /** | ||||
|      * Fetch and load all the data required for the view. | ||||
|      * | ||||
|      * @param {boolean} [refresh] If it's refreshing content. | ||||
|      * @param {boolean} [sync] If the refresh is needs syncing. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     protected loadData(refresh?: boolean): Promise<any> { | ||||
|     protected loadData(refresh?: boolean, sync?: boolean): Promise<any> { | ||||
|         // First of all, get the course because the data might have changed.
 | ||||
|         return this.coursesProvider.getUserCourse(this.course.id).catch(() => { | ||||
|             // Error getting the course, probably guest access.
 | ||||
|         }).then((course) => { | ||||
|             const promises = []; | ||||
|             let promise; | ||||
| 
 | ||||
|             if (course) { | ||||
|                 this.course = course; | ||||
|             } | ||||
| 
 | ||||
|             if (sync) { | ||||
|                 // Try to synchronize the course data.
 | ||||
|                 return this.syncProvider.syncCourse(this.course.id).then((result) => { | ||||
|                     if (result.warnings && result.warnings.length) { | ||||
|                         this.domUtils.showErrorModal(result.warnings[0]); | ||||
|                     } | ||||
|                 }).catch(() => { | ||||
|                     // For now we don't allow manual syncing, so ignore errors.
 | ||||
|                 }); | ||||
|             } | ||||
|         }).then(() => { | ||||
|             const promises = []; | ||||
|             let promise; | ||||
| 
 | ||||
|             // Get the completion status.
 | ||||
|             if (this.course.enablecompletion === false) { | ||||
|                 // Completion not enabled.
 | ||||
| @ -185,7 +212,7 @@ export class CoreCourseSectionPage implements OnDestroy { | ||||
|                     } | ||||
|                 }).then((sections) => { | ||||
| 
 | ||||
|                     this.courseHelper.addHandlerDataForModules(sections, this.course.id, completionStatus); | ||||
|                     this.courseHelper.addHandlerDataForModules(sections, this.course.id, completionStatus, this.course.fullname); | ||||
| 
 | ||||
|                     // Format the name of each section and check if it has content.
 | ||||
|                     this.sections = sections.map((section) => { | ||||
| @ -268,7 +295,7 @@ export class CoreCourseSectionPage implements OnDestroy { | ||||
|      */ | ||||
|     doRefresh(refresher?: any): Promise<any> { | ||||
|         return this.invalidateData().finally(() => { | ||||
|             return this.loadData(true).finally(() => { | ||||
|             return this.loadData(true, true).finally(() => { | ||||
|                 /* Do not call doRefresh on the format component if the refresher is defined in the format component | ||||
|                    to prevent an inifinite loop. */ | ||||
|                  let promise; | ||||
| @ -290,7 +317,7 @@ export class CoreCourseSectionPage implements OnDestroy { | ||||
|      */ | ||||
|     onCompletionChange(): void { | ||||
|         this.invalidateData().finally(() => { | ||||
|             this.refreshAfterCompletionChange(); | ||||
|             this.refreshAfterCompletionChange(true); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| @ -314,8 +341,10 @@ export class CoreCourseSectionPage implements OnDestroy { | ||||
| 
 | ||||
|     /** | ||||
|      * Refresh list after a completion change since there could be new activities. | ||||
|      * | ||||
|      * @param {boolean} [sync] If the refresh is needs syncing. | ||||
|      */ | ||||
|     protected refreshAfterCompletionChange(): void { | ||||
|     protected refreshAfterCompletionChange(sync?: boolean): void { | ||||
|         // Save scroll position to restore it once done.
 | ||||
|         const scrollElement = this.content.getScrollElement(), | ||||
|             scrollTop = scrollElement.scrollTop || 0, | ||||
| @ -324,7 +353,7 @@ export class CoreCourseSectionPage implements OnDestroy { | ||||
|         this.dataLoaded = false; | ||||
|         this.domUtils.scrollToTop(this.content); // Scroll top so the spinner is seen.
 | ||||
| 
 | ||||
|         this.loadData().then(() => { | ||||
|         this.loadData(true, sync).then(() => { | ||||
|             return this.formatComponent.doRefresh(undefined, undefined, true); | ||||
|         }).finally(() => { | ||||
|             this.dataLoaded = true; | ||||
|  | ||||
| @ -40,6 +40,10 @@ export class CoreCourseOfflineProvider { | ||||
|                     name: 'courseid', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'coursename', | ||||
|                     type: 'TEXT' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'timecreated', | ||||
|                     type: 'INTEGER' | ||||
| @ -66,6 +70,19 @@ export class CoreCourseOfflineProvider { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all offline manual completions for a certain course. | ||||
|      * | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any[]>} Promise resolved with the list of completions. | ||||
|      */ | ||||
|     getAllManualCompletions(siteId?: string): Promise<any[]> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
| 
 | ||||
|             return site.getDb().getRecords(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all offline manual completions for a certain course. | ||||
|      * | ||||
| @ -100,10 +117,11 @@ export class CoreCourseOfflineProvider { | ||||
|      * @param {number} cmId The module ID to store the completion. | ||||
|      * @param {number} completed Whether the module is completed or not. | ||||
|      * @param {number} courseId Course ID the module belongs to. | ||||
|      * @param {string} [courseName] Course name. Recommended, it is used to display a better warning message. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<{status: boolean, offline: boolean}>} Promise resolved when completion is successfully stored. | ||||
|      */ | ||||
|     markCompletedManually(cmId: number, completed: number, courseId: number, siteId?: string) | ||||
|     markCompletedManually(cmId: number, completed: number, courseId: number, courseName?: string, siteId?: string) | ||||
|             : Promise<{status: boolean, offline: boolean}> { | ||||
| 
 | ||||
|         // Store the offline data.
 | ||||
| @ -112,6 +130,7 @@ export class CoreCourseOfflineProvider { | ||||
|                 cmid: cmId, | ||||
|                 completed: completed, | ||||
|                 courseid: courseId, | ||||
|                 coursename: courseName || '', | ||||
|                 timecreated: Date.now() | ||||
|             }; | ||||
| 
 | ||||
|  | ||||
| @ -677,15 +677,18 @@ export class CoreCourseProvider { | ||||
|      * @param {number} cmId The module ID. | ||||
|      * @param {number} completed Whether the module is completed or not. | ||||
|      * @param {number} courseId Course ID the module belongs to. | ||||
|      * @param {string} [courseName] Course name. Recommended, it is used to display a better warning message. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved when completion is successfully sent or stored. | ||||
|      */ | ||||
|     markCompletedManually(cmId: number, completed: number, courseId: number, siteId?: string): Promise<any> { | ||||
|     markCompletedManually(cmId: number, completed: number, courseId: number, courseName?: string, siteId?: string) | ||||
|             : Promise<any> { | ||||
| 
 | ||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||
| 
 | ||||
|         // Convenience function to store a message to be synchronized later.
 | ||||
|         const storeOffline = (): Promise<any> => { | ||||
|             return this.courseOffline.markCompletedManually(cmId, completed, courseId, siteId); | ||||
|             return this.courseOffline.markCompletedManually(cmId, completed, courseId, courseName, siteId); | ||||
|         }; | ||||
| 
 | ||||
|         // Check if we already have a completion stored.
 | ||||
| @ -703,7 +706,7 @@ export class CoreCourseProvider { | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             if (!this.appProvider.isOnline()) { | ||||
|             if (!this.appProvider.isOnline() && courseId) { | ||||
|                 // App is offline, store the action.
 | ||||
|                 return storeOffline(); | ||||
|             } | ||||
| @ -720,7 +723,7 @@ export class CoreCourseProvider { | ||||
| 
 | ||||
|                 return result; | ||||
|             }).catch((error) => { | ||||
|                 if (this.utils.isWebServiceError(error)) { | ||||
|                 if (this.utils.isWebServiceError(error) || !courseId) { | ||||
|                     // The WebService has thrown an error, this means that responses cannot be submitted.
 | ||||
|                     return Promise.reject(error); | ||||
|                 } else { | ||||
|  | ||||
| @ -131,9 +131,10 @@ export class CoreCourseHelperProvider { | ||||
|      * @param {any[]} sections List of sections to treat modules. | ||||
|      * @param {number} courseId Course ID of the modules. | ||||
|      * @param {any[]} [completionStatus] List of completion status. | ||||
|      * @param {string} [courseName] Course name. Recommended if completionStatus is supplied. | ||||
|      * @return {boolean} Whether the sections have content. | ||||
|      */ | ||||
|     addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any): boolean { | ||||
|     addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any, courseName?: string): boolean { | ||||
|         let hasContent = false; | ||||
| 
 | ||||
|         sections.forEach((section) => { | ||||
| @ -150,6 +151,7 @@ export class CoreCourseHelperProvider { | ||||
|                     // Check if activity has completions and if it's marked.
 | ||||
|                     module.completionstatus = completionStatus[module.id]; | ||||
|                     module.completionstatus.courseId = courseId; | ||||
|                     module.completionstatus.courseName = courseName; | ||||
|                 } | ||||
| 
 | ||||
|                 // Check if the module is stealth.
 | ||||
|  | ||||
							
								
								
									
										47
									
								
								src/core/course/providers/sync-cron-handler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/core/course/providers/sync-cron-handler.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| // (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 { CoreCronHandler } from '@providers/cron'; | ||||
| import { CoreCourseSyncProvider } from './sync'; | ||||
| 
 | ||||
| /** | ||||
|  * Synchronization cron handler. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class CoreCourseSyncCronHandler implements CoreCronHandler { | ||||
|     name = 'CoreCourseSyncCronHandler'; | ||||
| 
 | ||||
|     constructor(private courseSync: CoreCourseSyncProvider) {} | ||||
| 
 | ||||
|     /** | ||||
|      * Execute the process. | ||||
|      * Receives the ID of the site affected, undefined for all sites. | ||||
|      * | ||||
|      * @param {string} [siteId] ID of the site affected, undefined for all sites. | ||||
|      * @return {Promise<any>} Promise resolved when done, rejected if failure. | ||||
|      */ | ||||
|     execute(siteId?: string): Promise<any> { | ||||
|         return this.courseSync.syncAllCourses(siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the time between consecutive executions. | ||||
|      * | ||||
|      * @return {number} Time between consecutive executions (in ms). | ||||
|      */ | ||||
|     getInterval(): number { | ||||
|         return this.courseSync.syncInterval; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										178
									
								
								src/core/course/providers/sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/core/course/providers/sync.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,178 @@ | ||||
| // (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 { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreSyncBaseProvider } from '@classes/base-sync'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreAppProvider } from '@providers/app'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreCourseOfflineProvider } from './course-offline'; | ||||
| import { CoreCourseProvider } from './course'; | ||||
| import { CoreEventsProvider } from '@providers/events'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreSyncProvider } from '@providers/sync'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to sync course offline data. This only syncs the offline data of the course itself, not the offline data of | ||||
|  * the activities in the course. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class CoreCourseSyncProvider extends CoreSyncBaseProvider { | ||||
| 
 | ||||
|     static AUTO_SYNCED = 'core_course_autom_synced'; | ||||
| 
 | ||||
|     constructor(protected sitesProvider: CoreSitesProvider, loggerProvider: CoreLoggerProvider, | ||||
|             protected appProvider: CoreAppProvider, private courseOffline: CoreCourseOfflineProvider, | ||||
|             private eventsProvider: CoreEventsProvider,  private courseProvider: CoreCourseProvider, | ||||
|             translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider, | ||||
|             syncProvider: CoreSyncProvider) { | ||||
| 
 | ||||
|         super('CoreCourseSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Try to synchronize all the courses in a certain site or in all sites. | ||||
|      * | ||||
|      * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. | ||||
|      * @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails. | ||||
|      */ | ||||
|     syncAllCourses(siteId?: string): Promise<any> { | ||||
|         return this.syncOnSites('courses', this.syncAllCoursesFunc.bind(this), undefined, siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sync all courses on a site. | ||||
|      * | ||||
|      * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. | ||||
|      * @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails. | ||||
|      */ | ||||
|     protected syncAllCoursesFunc(siteId?: string): Promise<any> { | ||||
|         return this.courseOffline.getAllManualCompletions(siteId).then((completions) => { | ||||
|             const promises = []; | ||||
| 
 | ||||
|             // Sync all courses.
 | ||||
|             completions.forEach((completion) => { | ||||
|                 promises.push(this.syncCourseIfNeeded(completion.courseid, siteId).then((result) => { | ||||
|                     if (result && result.updated) { | ||||
|                         // Sync successful, send event.
 | ||||
|                         this.eventsProvider.trigger(CoreCourseSyncProvider.AUTO_SYNCED, { | ||||
|                             courseId: completion.courseid, | ||||
|                             warnings: result.warnings | ||||
|                         }, siteId); | ||||
|                     } | ||||
|                 })); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sync a course if it's needed. | ||||
|      * | ||||
|      * @param {number} courseId Course ID to be synced. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved when the course is synced or it doesn't need to be synced. | ||||
|      */ | ||||
|     syncCourseIfNeeded(courseId: number, siteId?: string): Promise<any> { | ||||
|         // Usually we call isSyncNeeded to check if a certain time has passed.
 | ||||
|         // However, since we barely send data for now just sync the course.
 | ||||
|         return this.syncCourse(courseId, siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Synchronize a course. | ||||
|      * | ||||
|      * @param {number} courseId Course ID to be synced. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise. | ||||
|      */ | ||||
|     syncCourse(courseId: number, siteId?: string): Promise<any> { | ||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||
| 
 | ||||
|         if (this.isSyncing(courseId, siteId)) { | ||||
|             // There's already a sync ongoing for this discussion, return the promise.
 | ||||
|             return this.getOngoingSync(courseId, siteId); | ||||
|         } | ||||
| 
 | ||||
|         this.logger.debug(`Try to sync course '${courseId}'`); | ||||
| 
 | ||||
|         const result = { | ||||
|             warnings: [], | ||||
|             updated: false | ||||
|         }; | ||||
| 
 | ||||
|         // Get offline responses to be sent.
 | ||||
|         const syncPromise = this.courseOffline.getCourseManualCompletions(courseId, siteId).catch(() => { | ||||
|             // No offline data found, return empty list.
 | ||||
|             return []; | ||||
|         }).then((completions) => { | ||||
|             if (!completions || !completions.length) { | ||||
|                 // Nothing to sync.
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (!this.appProvider.isOnline()) { | ||||
|                 // Cannot sync in offline.
 | ||||
|                 return Promise.reject(null); | ||||
|             } | ||||
| 
 | ||||
|             const promises = []; | ||||
| 
 | ||||
|             // Send all the completions.
 | ||||
|             completions.forEach((entry) => { | ||||
|                 promises.push(this.courseProvider.markCompletedManuallyOnline(entry.cmid, entry.completed, siteId).then(() => { | ||||
|                     result.updated = true; | ||||
| 
 | ||||
|                     return this.courseOffline.deleteManualCompletion(entry.cmid, siteId); | ||||
|                 }).catch((error) => { | ||||
|                     if (this.utils.isWebServiceError(error)) { | ||||
|                         // The WebService has thrown an error, this means that the completion cannot be submitted. Delete it.
 | ||||
|                         result.updated = true; | ||||
| 
 | ||||
|                         return this.courseOffline.deleteManualCompletion(entry.cmid, siteId).then(() => { | ||||
|                             // Responses deleted, add a warning.
 | ||||
|                             result.warnings.push(this.translate.instant('core.course.warningofflinemanualcompletiondeleted', { | ||||
|                                 name: entry.coursename || courseId, | ||||
|                                 error: error.error | ||||
|                             })); | ||||
|                         }); | ||||
|                     } | ||||
| 
 | ||||
|                     // Couldn't connect to server, reject.
 | ||||
|                     return Promise.reject(error); | ||||
|                 })); | ||||
|             }); | ||||
| 
 | ||||
|             return Promise.all(promises); | ||||
|         }).then(() => { | ||||
|             if (result.updated) { | ||||
|                 // Update data.
 | ||||
|                 return this.courseProvider.invalidateSections(courseId, siteId).then(() => { | ||||
|                     return this.courseProvider.getActivitiesCompletionStatus(courseId); | ||||
|                 }).catch(() => { | ||||
|                     // Ignore errors.
 | ||||
|                 }); | ||||
|             } | ||||
|         }).then(() => { | ||||
|             // Sync finished, set sync time.
 | ||||
|             return this.setSyncTime(courseId, siteId); | ||||
|         }).then(() => { | ||||
|             // All done, return the data.
 | ||||
|             return result; | ||||
|         }); | ||||
| 
 | ||||
|         return this.addOngoingSync(courseId, syncPromise, siteId); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user