MOBILE-2061 course: Don't sync completion if modified in the site

main
dpalou 2018-10-05 10:10:33 +02:00
parent 1c6618b42a
commit 83ef6467c8
5 changed files with 87 additions and 64 deletions

View File

@ -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",

View File

@ -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}}"
}

View File

@ -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);

View File

@ -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<any>} Promise resolved with the completion statuses: object where the key is module ID.
*/
getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number): Promise<any> {
getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number, forceCache: boolean = false,
ignoreCache: boolean = false, includeOffline: boolean = true): Promise<any> {
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<any> => {
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();
}
});
});
}

View File

@ -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.