MOBILE-2061 course: Synchronize offline manual completion
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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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…
Reference in New Issue