MOBILE-2873 sync: Sync fixes

main
Pau Ferrer Ocaña 2019-03-08 10:24:25 +01:00
parent 2469d903a7
commit d767b7514e
24 changed files with 203 additions and 183 deletions

View File

@ -109,8 +109,8 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
/**
* Try to synchronize all the assignments in a certain site or in all sites.
*
* @param {boolean} force Wether to force sync not depending on last execution.
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
* @param {boolean} force Wether to force sync not depending on last execution.
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
syncAllAssignments(siteId?: string, force?: boolean): Promise<any> {
@ -120,8 +120,8 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
/**
* Sync all assignments on a site.
*
* @param {boolean} [force] Wether to force sync not depending on last execution.
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
* @param {boolean} [force] Wether to force sync not depending on last execution.
* @param {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
protected syncAllAssignmentsFunc(siteId?: string, force?: boolean): Promise<any> {

View File

@ -461,10 +461,11 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
return this.syncProvider.syncAssign(module.instance, siteId);
}
}

View File

@ -141,10 +141,11 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
if (!this.syncProvider) {
this.syncProvider = this.injector.get(AddonModChoiceSyncProvider);
}

View File

@ -67,8 +67,8 @@ export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvid
/**
* Try to synchronize all the choices in a certain site or in all sites.
*
* @param {boolean} force Wether to force sync not depending on last execution.
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
* @param {boolean} force Wether to force sync not depending on last execution.
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
syncAllChoices(siteId?: string, force?: boolean): Promise<any> {
@ -132,94 +132,96 @@ export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvid
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
*/
syncChoice(choiceId: number, userId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
userId = userId || this.sitesProvider.getCurrentSiteUserId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
siteId = site.getId();
const syncId = this.getSyncId(choiceId, userId);
if (this.isSyncing(syncId, siteId)) {
// There's already a sync ongoing for this discussion, return the promise.
return this.getOngoingSync(syncId, siteId);
}
this.logger.debug(`Try to sync choice '${choiceId}' for user '${userId}'`);
let courseId;
const result = {
warnings: [],
updated: false
};
// Sync offline logs.
const syncPromise = this.logHelper.syncIfNeeded(AddonModChoiceProvider.COMPONENT, choiceId, siteId).catch(() => {
// Ignore errors.
}).then(() => {
return this.choiceOffline.getResponse(choiceId, siteId, userId).catch(() => {
// No offline data found, return empty object.
return {};
});
}).then((data) => {
if (!data.choiceid) {
// Nothing to sync.
return;
const syncId = this.getSyncId(choiceId, userId);
if (this.isSyncing(syncId, siteId)) {
// There's already a sync ongoing for this discussion, return the promise.
return this.getOngoingSync(syncId, siteId);
}
if (!this.appProvider.isOnline()) {
// Cannot sync in offline.
return Promise.reject(null);
}
this.logger.debug(`Try to sync choice '${choiceId}' for user '${userId}'`);
courseId = data.courseid;
let courseId;
const result = {
warnings: [],
updated: false
};
// Send the responses.
let promise;
if (data.deleting) {
// A user has deleted some responses.
promise = this.choiceProvider.deleteResponsesOnline(choiceId, data.responses, siteId);
} else {
// A user has added some responses.
promise = this.choiceProvider.submitResponseOnline(choiceId, data.responses, siteId);
}
return promise.then(() => {
result.updated = true;
return this.choiceOffline.deleteResponse(choiceId, siteId, userId);
}).catch((error) => {
if (this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means that responses cannot be submitted. Delete them.
result.updated = true;
return this.choiceOffline.deleteResponse(choiceId, siteId, userId).then(() => {
// Responses deleted, add a warning.
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
component: this.componentTranslate,
name: data.name,
error: this.textUtils.getErrorMessageFromError(error)
}));
});
// Sync offline logs.
const syncPromise = this.logHelper.syncIfNeeded(AddonModChoiceProvider.COMPONENT, choiceId, siteId).catch(() => {
// Ignore errors.
}).then(() => {
return this.choiceOffline.getResponse(choiceId, siteId, userId).catch(() => {
// No offline data found, return empty object.
return {};
});
}).then((data) => {
if (!data.choiceid) {
// Nothing to sync.
return;
}
// Couldn't connect to server, reject.
return Promise.reject(error);
});
}).then(() => {
if (courseId) {
// Data has been sent to server, prefetch choice if needed.
return this.courseProvider.getModuleBasicInfoByInstance(choiceId, 'choice', siteId).then((module) => {
return this.prefetchAfterUpdate(module, courseId, undefined, siteId);
}).catch(() => {
// Ignore errors.
});
}
}).then(() => {
// Sync finished, set sync time.
return this.setSyncTime(syncId, siteId);
}).then(() => {
// All done, return the warnings.
return result;
});
if (!this.appProvider.isOnline()) {
// Cannot sync in offline.
return Promise.reject(null);
}
return this.addOngoingSync(syncId, syncPromise, siteId);
courseId = data.courseid;
// Send the responses.
let promise;
if (data.deleting) {
// A user has deleted some responses.
promise = this.choiceProvider.deleteResponsesOnline(choiceId, data.responses, siteId);
} else {
// A user has added some responses.
promise = this.choiceProvider.submitResponseOnline(choiceId, data.responses, siteId);
}
return promise.then(() => {
result.updated = true;
return this.choiceOffline.deleteResponse(choiceId, siteId, userId);
}).catch((error) => {
if (this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means that responses cannot be submitted. Delete them.
result.updated = true;
return this.choiceOffline.deleteResponse(choiceId, siteId, userId).then(() => {
// Responses deleted, add a warning.
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
component: this.componentTranslate,
name: data.name,
error: this.textUtils.getErrorMessageFromError(error)
}));
});
}
// Couldn't connect to server, reject.
return Promise.reject(error);
});
}).then(() => {
if (courseId) {
// Data has been sent to server, prefetch choice if needed.
return this.courseProvider.getModuleBasicInfoByInstance(choiceId, 'choice', siteId).then((module) => {
return this.prefetchAfterUpdate(module, courseId, undefined, siteId);
}).catch(() => {
// Ignore errors.
});
}
}).then(() => {
// Sync finished, set sync time.
return this.setSyncTime(syncId, siteId);
}).then(() => {
// All done, return the warnings.
return result;
});
return this.addOngoingSync(syncId, syncPromise, siteId);
});
}
}

View File

@ -521,7 +521,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
* @return {Promise<any>} Promise resolved when done.
*/
protected sync(): Promise<any> {
return this.prefetchHandler.sync(this.module);
return this.prefetchHandler.sync(this.module, this.courseId);
}
/**

View File

@ -307,10 +307,11 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
const promises = [
this.syncProvider.syncDatabase(module.instance, siteId),
this.syncProvider.syncRatings(module.id, true, siteId)

View File

@ -66,8 +66,8 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
/**
* Try to synchronize all the databases in a certain site or in all sites.
*
* @param {boolean} force Wether to force sync not depending on last execution.
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
* @param {boolean} force Wether to force sync not depending on last execution.
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
syncAllDatabases(siteId?: string, force?: boolean): Promise<any> {

View File

@ -248,10 +248,11 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
if (!this.syncProvider) {
this.syncProvider = this.injector.get(AddonModFeedbackSyncProvider);
}

View File

@ -71,8 +71,8 @@ export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProv
/**
* Try to synchronize all the feedbacks in a certain site or in all sites.
*
* @param {boolean} force Wether to force sync not depending on last execution.
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
* @param {boolean} force Wether to force sync not depending on last execution.
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
syncAllFeedbacks(siteId?: string, force?: boolean): Promise<any> {

View File

@ -376,7 +376,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
* @return {Promise<any>} Promise resolved when done.
*/
protected sync(): Promise<boolean> {
return this.prefetchHandler.sync(this.module);
return this.prefetchHandler.sync(this.module, this.courseId);
}
/**

View File

@ -271,10 +271,11 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
const promises = [];
promises.push(this.syncProvider.syncForumDiscussions(module.instance, undefined, siteId));

View File

@ -220,7 +220,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity
* @return {Promise<any>} Promise resolved when done.
*/
protected sync(): Promise<boolean> {
return this.prefetchHandler.sync(this.module);
return this.prefetchHandler.sync(this.module, this.courseId);
}
/**

View File

@ -194,10 +194,11 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
const promises = [
this.syncProvider.syncGlossaryEntries(module.instance, undefined, siteId),
this.syncProvider.syncRatings(module.id, undefined, siteId)

View File

@ -437,10 +437,11 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
if (!this.syncProvider) {
this.syncProvider = this.injector.get(AddonModLessonSyncProvider);
}

View File

@ -49,7 +49,7 @@ export class AddonModQuizHelperProvider {
getAndCheckPreflightData(quiz: any, accessInfo: any, preflightData: any, attempt: any, offline?: boolean, prefetch?: boolean,
title?: string, siteId?: string, retrying?: boolean): Promise<any> {
const rules = accessInfo.activerulenames;
const rules = accessInfo && accessInfo.activerulenames;
let isPreflightCheckRequired = false;
// Check if the user needs to input preflight data.

View File

@ -567,6 +567,8 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
this.syncProvider = this.injector.get(AddonModQuizSyncProvider);
}
return this.syncProvider.syncQuiz(module.instance, false, siteId);
return this.quizProvider.getQuiz(module.course, module.id).then((quiz) => {
return this.syncProvider.syncQuiz(quiz, false, siteId);
});
}
}

View File

@ -431,10 +431,11 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
if (!this.syncProvider) {
this.syncProvider = this.injector.get(AddonModScormSyncProvider);
}

View File

@ -443,8 +443,8 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide
/**
* Try to synchronize all the SCORMs in a certain site or in all sites.
*
* @param {boolean} force Wether to force sync not depending on last execution.
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
* @param {boolean} force Wether to force sync not depending on last execution.
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
syncAllScorms(siteId?: string, force?: boolean): Promise<any> {

View File

@ -134,10 +134,11 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
if (!this.syncProvider) {
this.syncProvider = this.injector.get(AddonModSurveySyncProvider);
}

View File

@ -135,87 +135,89 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
*/
syncSurvey(surveyId: number, userId?: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
userId = userId || this.sitesProvider.getCurrentSiteUserId();
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
siteId = site.getId();
const syncId = this.getSyncId(surveyId, userId);
if (this.isSyncing(syncId, siteId)) {
// There's already a sync ongoing for this survey and user, return the promise.
return this.getOngoingSync(syncId, siteId);
}
this.logger.debug(`Try to sync survey '${surveyId}' for user '${userId}'`);
let courseId;
const result = {
warnings: [],
answersSent: false
};
// Sync offline logs.
const syncPromise = this.logHelper.syncIfNeeded(AddonModSurveyProvider.COMPONENT, surveyId, siteId).catch(() => {
// Ignore errors.
}).then(() => {
// Get answers to be sent.
return this.surveyOffline.getSurveyData(surveyId, siteId, userId).catch(() => {
// No offline data found, return empty object.
return {};
});
}).then((data) => {
if (!data.answers || !data.answers.length) {
// Nothing to sync.
return;
const syncId = this.getSyncId(surveyId, userId);
if (this.isSyncing(syncId, siteId)) {
// There's already a sync ongoing for this survey and user, return the promise.
return this.getOngoingSync(syncId, siteId);
}
if (!this.appProvider.isOnline()) {
// Cannot sync in offline.
return Promise.reject(null);
}
this.logger.debug(`Try to sync survey '${surveyId}' for user '${userId}'`);
courseId = data.courseid;
let courseId;
const result = {
warnings: [],
answersSent: false
};
// Send the answers.
return this.surveyProvider.submitAnswersOnline(surveyId, data.answers, siteId).then(() => {
result.answersSent = true;
// Answers sent, delete them.
return this.surveyOffline.deleteSurveyAnswers(surveyId, siteId, userId);
}).catch((error) => {
if (this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means that answers cannot be submitted. Delete them.
result.answersSent = true;
return this.surveyOffline.deleteSurveyAnswers(surveyId, siteId, userId).then(() => {
// Answers deleted, add a warning.
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
component: this.componentTranslate,
name: data.name,
error: this.textUtils.getErrorMessageFromError(error)
}));
});
// Sync offline logs.
const syncPromise = this.logHelper.syncIfNeeded(AddonModSurveyProvider.COMPONENT, surveyId, siteId).catch(() => {
// Ignore errors.
}).then(() => {
// Get answers to be sent.
return this.surveyOffline.getSurveyData(surveyId, siteId, userId).catch(() => {
// No offline data found, return empty object.
return {};
});
}).then((data) => {
if (!data.answers || !data.answers.length) {
// Nothing to sync.
return;
}
// Couldn't connect to server, reject.
return Promise.reject(error);
});
}).then(() => {
if (courseId) {
// Data has been sent to server, update survey data.
return this.courseProvider.getModuleBasicInfoByInstance(surveyId, 'survey', siteId).then((module) => {
return this.prefetchAfterUpdate(module, courseId, undefined, siteId);
}).catch(() => {
// Ignore errors.
});
}
}).then(() => {
// Sync finished, set sync time.
return this.setSyncTime(syncId, siteId);
}).then(() => {
return result;
});
if (!this.appProvider.isOnline()) {
// Cannot sync in offline.
return Promise.reject(null);
}
return this.addOngoingSync(syncId, syncPromise, siteId);
courseId = data.courseid;
// Send the answers.
return this.surveyProvider.submitAnswersOnline(surveyId, data.answers, siteId).then(() => {
result.answersSent = true;
// Answers sent, delete them.
return this.surveyOffline.deleteSurveyAnswers(surveyId, siteId, userId);
}).catch((error) => {
if (this.utils.isWebServiceError(error)) {
// The WebService has thrown an error, this means that answers cannot be submitted. Delete them.
result.answersSent = true;
return this.surveyOffline.deleteSurveyAnswers(surveyId, siteId, userId).then(() => {
// Answers deleted, add a warning.
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
component: this.componentTranslate,
name: data.name,
error: this.textUtils.getErrorMessageFromError(error)
}));
});
}
// Couldn't connect to server, reject.
return Promise.reject(error);
});
}).then(() => {
if (courseId) {
// Data has been sent to server, update survey data.
return this.courseProvider.getModuleBasicInfoByInstance(surveyId, 'survey', siteId).then((module) => {
return this.prefetchAfterUpdate(module, courseId, undefined, siteId);
}).catch(() => {
// Ignore errors.
});
}
}).then(() => {
// Sync finished, set sync time.
return this.setSyncTime(syncId, siteId);
}).then(() => {
return result;
});
return this.addOngoingSync(syncId, syncPromise, siteId);
});
}
}

View File

@ -217,10 +217,11 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
return this.syncProvider.syncWiki(module.instance, module.course, module.id, siteId);
}
}

View File

@ -372,10 +372,11 @@ export class AddonModWorkshopPrefetchHandler extends CoreCourseActivityPrefetchH
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync(module: any, siteId?: string): Promise<any> {
sync(module: any, courseId: number, siteId?: any): Promise<any> {
return this.syncProvider.syncWorkshop(module.instance, siteId);
}
}

View File

@ -1319,7 +1319,7 @@ export class CoreCourseHelperProvider {
section.isDownloading = true;
// Sync the modules first.
promises.push(this.prefetchDelegate.syncModules(section.modules).then(() => {
promises.push(this.prefetchDelegate.syncModules(section.modules, courseId).then(() => {
// Validate the section needs to be downloaded and calculate amount of modules that need to be downloaded.
return this.prefetchDelegate.getModulesStatus(section.modules, courseId, section.id).then((result) => {
if (result.status == CoreConstants.DOWNLOADED || result.status == CoreConstants.NOT_DOWNLOADABLE) {

View File

@ -211,10 +211,11 @@ export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler {
* Sync a module.
*
* @param {any} module Module.
* @param {number} courseId Course ID the module belongs to
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
sync?(module: any, siteId?: any): Promise<any>;
sync?(module: any, courseId: number, siteId?: any): Promise<any>;
}
/**
@ -1148,7 +1149,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
// Check if the module has a prefetch handler.
if (handler) {
return this.syncModule(module).then(() => {
return this.syncModule(module, courseId).then(() => {
return handler.prefetch(module, courseId, single);
});
}
@ -1160,11 +1161,12 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
* Sync a group of modules.
*
* @param {any[]} modules Array of modules to sync.
* @param {number} courseId Course ID the module belongs to.
* @return {Promise<any>} Promise resolved when finished.
*/
syncModules(modules: any[]): Promise<any> {
syncModules(modules: any[], courseId: number): Promise<any> {
return Promise.all(modules.map((module) => {
return this.syncModule(module);
return this.syncModule(module, courseId);
}));
}
@ -1172,12 +1174,13 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
* Sync a module.
*
* @param {any} module Module to sync.
* @param {number} courseId Course ID the module belongs to.
* @return {Promise<any>} Promise resolved when finished.
*/
syncModule(module: any): Promise<any> {
syncModule(module: any, courseId: number): Promise<any> {
const handler = this.getPrefetchHandlerFor(module);
const promise = handler && handler.sync ? handler.sync(module) : Promise.resolve();
const promise = handler && handler.sync ? handler.sync(module, courseId) : Promise.resolve();
return promise.catch(() => {
// Ignore errors.