From 83ef6467c89726c765a85dc5c72dc25892c86ceb Mon Sep 17 00:00:00 2001 From: dpalou Date: Fri, 5 Oct 2018 10:10:33 +0200 Subject: [PATCH] MOBILE-2061 course: Don't sync completion if modified in the site --- scripts/langindex.json | 1 + src/core/course/lang/en.json | 1 + src/core/course/providers/course-offline.ts | 4 +- src/core/course/providers/course.ts | 78 ++++++++++----------- src/core/course/providers/sync.ts | 67 ++++++++++++------ 5 files changed, 87 insertions(+), 64 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 0c767ab98..db7b45180 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1113,6 +1113,7 @@ "core.course.overriddennotice": "grades", "core.course.sections": "moodle", "core.course.useactivityonbrowser": "local_moodlemobileapp", + "core.course.warningmanualcompletionmodified": "local_moodlemobileapp", "core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp", "core.coursedetails": "moodle", "core.courses.allowguests": "enrol_guest", diff --git a/src/core/course/lang/en.json b/src/core/course/lang/en.json index 3b9028eba..0a2229b22 100644 --- a/src/core/course/lang/en.json +++ b/src/core/course/lang/en.json @@ -24,5 +24,6 @@ "refreshcourse": "Refresh course", "sections": "Sections", "useactivityonbrowser": "You can still use it using your device's web browser.", + "warningmanualcompletionmodified": "The manual completion of an activity was modified on the site.", "warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}" } \ No newline at end of file diff --git a/src/core/course/providers/course-offline.ts b/src/core/course/providers/course-offline.ts index 869161e8e..fadb65463 100644 --- a/src/core/course/providers/course-offline.ts +++ b/src/core/course/providers/course-offline.ts @@ -45,7 +45,7 @@ export class CoreCourseOfflineProvider { type: 'TEXT' }, { - name: 'timecreated', + name: 'timecompleted', type: 'INTEGER' } ] @@ -131,7 +131,7 @@ export class CoreCourseOfflineProvider { completed: completed, courseid: courseId, coursename: courseName || '', - timecreated: Date.now() + timecompleted: Date.now() }; return site.getDb().insertRecord(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, entry); diff --git a/src/core/course/providers/course.ts b/src/core/course/providers/course.ts index 4e30f65fb..13ab87bd1 100644 --- a/src/core/course/providers/course.ts +++ b/src/core/course/providers/course.ts @@ -121,9 +121,14 @@ export class CoreCourseProvider { * @param {number} courseId Course ID. * @param {string} [siteId] Site ID. If not defined, current site. * @param {number} [userId] User ID. If not defined, current user. + * @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). + * @param {boolean} [includeOffline=true] True if it should load offline data in the completion status. * @return {Promise} Promise resolved with the completion statuses: object where the key is module ID. */ - getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number): Promise { + getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number, forceCache: boolean = false, + ignoreCache: boolean = false, includeOffline: boolean = true): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -133,10 +138,17 @@ export class CoreCourseProvider { courseid: courseId, userid: userId }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getActivitiesCompletionCacheKey(courseId, userId) }; + if (forceCache) { + preSets.omitExpires = true; + } else if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('core_completion_get_activities_completion_status', params, preSets).then((data) => { if (data && data.statuses) { return this.utils.arrayToObject(data.statuses, 'cmid'); @@ -144,6 +156,10 @@ export class CoreCourseProvider { return Promise.reject(null); }).then((completionStatus) => { + if (!includeOffline) { + return completionStatus; + } + // Now get the offline completion (if any). return this.courseOffline.getCourseManualCompletions(courseId, site.id).then((offlineCompletions) => { offlineCompletions.forEach((offlineCompletion) => { @@ -686,51 +702,33 @@ export class CoreCourseProvider { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - // Convenience function to store a message to be synchronized later. + // Convenience function to store a completion to be synchronized later. const storeOffline = (): Promise => { return this.courseOffline.markCompletedManually(cmId, completed, courseId, courseName, siteId); }; - // Check if we already have a completion stored. - return this.courseOffline.getManualCompletion(cmId, siteId).catch(() => { - // No completion stored. - }).then((entry) => { + // The offline function requires a courseId and it could be missing because it's a calculated field. + if (!this.appProvider.isOnline() && courseId) { + // App is offline, store the action. + return storeOffline(); + } - if (entry && completed != entry.completed) { - // It has changed, this means that the offline data can be deleted because the action was undone. - return this.courseOffline.deleteManualCompletion(cmId, siteId).then(() => { - return { - status: true, - offline: true - }; - }); - } - - if (!this.appProvider.isOnline() && courseId) { - // App is offline, store the action. + // Try to send it to server. + return this.markCompletedManuallyOnline(cmId, completed, siteId).then((result) => { + // Data sent to server, if there is some offline data delete it now. + return this.courseOffline.deleteManualCompletion(cmId, siteId).catch(() => { + // Ignore errors, shouldn't happen. + }).then(() => { + return result; + }); + }).catch((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 { + // Couldn't connect to server, store it offline. return storeOffline(); } - - return this.markCompletedManuallyOnline(cmId, completed, siteId).then((result) => { - // Data sent to server, if there is some offline data delete it now. - if (entry) { - return this.courseOffline.deleteManualCompletion(cmId, siteId).catch(() => { - // Ignore errors, shouldn't happen. - }).then(() => { - return result; - }); - } - - return result; - }).catch((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 { - // Couldn't connect to server, store it offline. - return storeOffline(); - } - }); }); } diff --git a/src/core/course/providers/sync.ts b/src/core/course/providers/sync.ts index 8a5334677..87ebc23d8 100644 --- a/src/core/course/providers/sync.ts +++ b/src/core/course/providers/sync.ts @@ -128,34 +128,57 @@ export class CoreCourseSyncProvider extends CoreSyncBaseProvider { return Promise.reject(null); } - const promises = []; + // Get the current completion status to check if any completion was modified in web. + return this.courseProvider.getActivitiesCompletionStatus(courseId, siteId, undefined, false, true, false) + .then((onlineCompletions) => { - // Send all the completions. - completions.forEach((entry) => { - promises.push(this.courseProvider.markCompletedManuallyOnline(entry.cmid, entry.completed, siteId).then(() => { - result.updated = true; + const promises = []; - 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; + // Send all the completions. + completions.forEach((entry) => { + const onlineComp = onlineCompletions[entry.cmid]; - 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: this.textUtils.getErrorMessageFromError(error) - })); - }); + // Check if the completion was modified in online. If so, discard it. + if (onlineComp && onlineComp.timecompleted * 1000 > entry.timecompleted) { + promises.push(this.courseOffline.deleteManualCompletion(entry.cmid, siteId).then(() => { + + // Completion deleted, add a warning if the completion status doesn't match. + if (onlineComp.state != entry.completed) { + result.warnings.push(this.translate.instant('core.course.warningofflinemanualcompletiondeleted', { + name: entry.coursename || courseId, + error: this.translate.instant('core.course.warningmanualcompletionmodified') + })); + } + })); + + return; } - // Couldn't connect to server, reject. - return Promise.reject(error); - })); - }); + promises.push(this.courseProvider.markCompletedManuallyOnline(entry.cmid, entry.completed, siteId).then(() => { + result.updated = true; - return Promise.all(promises); + 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(() => { + // Completion deleted, add a warning. + result.warnings.push(this.translate.instant('core.course.warningofflinemanualcompletiondeleted', { + name: entry.coursename || courseId, + error: this.textUtils.getErrorMessageFromError(error) + })); + }); + } + + // Couldn't connect to server, reject. + return Promise.reject(error); + })); + }); + + return Promise.all(promises); + }); }).then(() => { if (result.updated) { // Update data.