208 lines
9.2 KiB
TypeScript
Raw Normal View History

// (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 { CoreTimeUtilsProvider } from '@providers/utils/time';
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, timeUtils: CoreTimeUtilsProvider) {
super('CoreCourseSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils);
}
/**
* 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);
}
// Get the current completion status to check if any completion was modified in web.
// This can be retrieved on core_course_get_contents since 3.6 but this is an easy way to get them.
return this.courseProvider.getActivitiesCompletionStatus(courseId, siteId, undefined, false, true, false)
.then((onlineCompletions) => {
const promises = [];
// Send all the completions.
completions.forEach((entry) => {
const onlineComp = onlineCompletions[entry.cmid];
// 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;
}
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(() => {
// 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.
return this.courseProvider.invalidateSections(courseId, siteId).then(() => {
if (this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan('3.6')) {
return this.courseProvider.getSections(courseId, false, true, undefined, siteId);
} else {
return this.courseProvider.getActivitiesCompletionStatus(courseId, siteId);
}
}).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);
}
}