MOBILE-2850 lesson: Prefetch data after syncing lesson

main
Dani Palou 2019-02-26 12:11:42 +01:00
parent 0dd4ac1a22
commit 0cec1cc01e
7 changed files with 129 additions and 53 deletions

View File

@ -53,6 +53,21 @@ export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProv
this.componentTranslate = courseProvider.translateModuleName('feedback');
}
/**
* Conveniece function to prefetch data after an update.
*
* @param {any} module Module.
* @param {number} courseId Course ID.
* @param {RegExp} [regex] If regex matches, don't download the data. Defaults to check files and timers.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
prefetchAfterUpdate(module: any, courseId: number, regex?: RegExp, siteId?: string): Promise<any> {
regex = regex || /^.*files$|^timers/;
return super.prefetchAfterUpdate(module, courseId, regex, siteId);
}
/**
* Try to synchronize all the feedbacks in a certain site or in all sites.
*

View File

@ -61,6 +61,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
protected accessInfo: any; // Lesson access info.
protected password: string; // The password for the lesson.
protected hasPlayed: boolean; // Whether the user has gone to the lesson player (attempted).
protected dataSentObserver; // To detect data sent to server.
protected dataSent = false; // Whether some data was sent to server while playing the lesson.
constructor(injector: Injector, protected lessonProvider: AddonModLessonProvider, @Optional() content: Content,
protected groupsProvider: CoreGroupsProvider, protected lessonOffline: AddonModLessonOfflineProvider,
@ -228,6 +230,13 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
* @return {boolean} If suceed or not.
*/
protected hasSyncSucceed(result: any): boolean {
if (result.updated || this.dataSent) {
// Check completion status if something was sent.
this.courseProvider.checkModuleCompletion(this.courseId, this.module.completiondata);
}
this.dataSent = false;
return result.updated;
}
@ -243,6 +252,10 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
if (this.hasPlayed) {
this.hasPlayed = false;
this.dataSentObserver && this.dataSentObserver.off(); // Stop listening for changes.
this.dataSentObserver = undefined;
// Refresh data.
this.showLoadingAndRefresh(true, false);
}
}
@ -257,6 +270,16 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
if (this.navCtrl.getActive().component.name == 'AddonModLessonPlayerPage') {
this.hasPlayed = true;
// Detect if anything was sent to server.
this.dataSentObserver && this.dataSentObserver.off();
this.dataSentObserver = this.eventsProvider.on(AddonModLessonProvider.DATA_SENT_EVENT, (data) => {
// Ignore launch sending because it only affects timers.
if (data.lessonId === this.lesson.id && data.type != 'launch') {
this.dataSent = true;
}
}, this.siteId);
}
}
@ -556,7 +579,18 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
* @return {Promise<any>} Promise resolved when done.
*/
protected sync(): Promise<any> {
return this.lessonSync.syncLesson(this.lesson.id, true);
return this.lessonSync.syncLesson(this.lesson.id, true).then((result) => {
if (!result.updated && this.dataSent && this.isPrefetched()) {
// The user sent data to server, but not in the sync process. Check if we need to fetch data.
return this.lessonSync.prefetchAfterUpdate(this.module, this.courseId).catch(() => {
// Ignore errors.
}).then(() => {
return result;
});
}
return result;
});
}
/**
@ -575,4 +609,13 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
return Promise.reject(error);
});
}
/**
* Component being destroyed.
*/
ngOnDestroy(): void {
super.ngOnDestroy();
this.dataSentObserver && this.dataSentObserver.off();
}
}

View File

@ -25,7 +25,8 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync';
import { AddonModLessonProvider } from './lesson';
import { AddonModLessonOfflineProvider } from './lesson-offline';
import { AddonModLessonPrefetchHandler } from './prefetch-handler';
@ -51,7 +52,7 @@ export interface AddonModLessonSyncResult {
* Service to sync lesson.
*/
@Injectable()
export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvider {
static AUTO_SYNCED = 'addon_mod_lesson_autom_synced';
@ -92,12 +93,12 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService,
courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider,
private lessonProvider: AddonModLessonProvider, private lessonOfflineProvider: AddonModLessonOfflineProvider,
private prefetchHandler: AddonModLessonPrefetchHandler, timeUtils: CoreTimeUtilsProvider,
protected prefetchHandler: AddonModLessonPrefetchHandler, timeUtils: CoreTimeUtilsProvider,
private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider,
private logHelper: CoreCourseLogHelperProvider) {
private logHelper: CoreCourseLogHelperProvider, prefetchDelegate: CoreCourseModulePrefetchDelegate) {
super('AddonModLessonSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
timeUtils);
timeUtils, prefetchDelegate, prefetchHandler);
this.componentTranslate = courseProvider.translateModuleName('lesson');
@ -288,7 +289,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
courseId = attempts[0].courseid;
// Get the info, access info and the lesson password if needed.
return this.lessonProvider.getLessonById(courseId, lessonId, false, siteId).then((lessonData) => {
return this.lessonProvider.getLessonById(courseId, lessonId, false, false, siteId).then((lessonData) => {
lesson = lessonData;
return this.prefetchHandler.getLessonPassword(lessonId, false, true, askPassword, siteId);
@ -364,7 +365,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
// Data already retrieved when syncing attempts.
promise = Promise.resolve();
} else {
promise = this.lessonProvider.getLessonById(courseId, lessonId, false, siteId).then((lessonData) => {
promise = this.lessonProvider.getLessonById(courseId, lessonId, false, false, siteId).then((lessonData) => {
lesson = lessonData;
return this.prefetchHandler.getLessonPassword(lessonId, false, true, askPassword, siteId);
@ -429,31 +430,9 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
});
}).then(() => {
if (result.updated && courseId) {
// Data has been sent to server. Now invalidate the WS calls.
const promises = [];
promises.push(this.lessonProvider.invalidateAccessInformation(lessonId, siteId));
promises.push(this.lessonProvider.invalidateContentPagesViewed(lessonId, siteId));
promises.push(this.lessonProvider.invalidateQuestionsAttempts(lessonId, siteId));
promises.push(this.lessonProvider.invalidatePagesPossibleJumps(lessonId, siteId));
promises.push(this.lessonProvider.invalidateTimers(lessonId, siteId));
return this.utils.allPromises(promises).catch(() => {
// Data has been sent to server, update data.
return this.prefetchAfterUpdate(module, courseId, undefined, siteId).catch(() => {
// Ignore errors.
}).then(() => {
// Sync successful, update some data that might have been modified.
return this.lessonProvider.getAccessInformation(lessonId, false, false, siteId).then((info) => {
const promises = [],
retake = info.attemptscount;
promises.push(this.lessonProvider.getContentPagesViewedOnline(lessonId, retake, false, false, siteId));
promises.push(this.lessonProvider.getQuestionsAttemptsOnline(lessonId, retake, false, undefined, false,
false, siteId));
return Promise.all(promises);
}).catch(() => {
// Ignore errors.
});
});
}
}).then(() => {

View File

@ -14,6 +14,7 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -113,6 +114,7 @@ export interface AddonModLessonGrade {
@Injectable()
export class AddonModLessonProvider {
static COMPONENT = 'mmaModLesson';
static DATA_SENT_EVENT = 'addon_mod_lesson_data_sent';
// This page.
static LESSON_THISPAGE = 0;
@ -186,7 +188,8 @@ export class AddonModLessonProvider {
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
private translate: TranslateService, private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider,
private lessonOfflineProvider: AddonModLessonOfflineProvider, private logHelper: CoreCourseLogHelperProvider) {
private lessonOfflineProvider: AddonModLessonOfflineProvider, private logHelper: CoreCourseLogHelperProvider,
private eventsProvider: CoreEventsProvider) {
this.logger = logger.getInstance('AddonModLessonProvider');
this.sitesProvider.registerSiteSchema(this.siteSchema);
@ -1087,7 +1090,17 @@ export class AddonModLessonProvider {
});
}
return this.finishRetakeOnline(lesson.id, password, outOfTime, review, siteId);
return this.finishRetakeOnline(lesson.id, password, outOfTime, review, siteId).then((response) => {
this.eventsProvider.trigger(AddonModLessonProvider.DATA_SENT_EVENT, {
lessonId: lesson.id,
type: 'finish',
courseId: courseId,
outOfTime: outOfTime,
review: review
}, this.sitesProvider.getCurrentSiteId());
return response;
});
}
/**
@ -1363,11 +1376,12 @@ export class AddonModLessonProvider {
* @param {number} courseId Course ID.
* @param {number} cmid Course module ID.
* @param {boolean} [forceCache] Whether it should always return cached data.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the lesson is retrieved.
*/
getLesson(courseId: number, cmId: number, forceCache?: boolean, siteId?: string): Promise<any> {
return this.getLessonByField(courseId, 'coursemodule', cmId, forceCache, siteId);
getLesson(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.getLessonByField(courseId, 'coursemodule', cmId, forceCache, ignoreCache, siteId);
}
/**
@ -1377,10 +1391,12 @@ export class AddonModLessonProvider {
* @param {string} key Name of the property to check.
* @param {any} value Value to search.
* @param {boolean} [forceCache] Whether it should always return cached data.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the lesson is retrieved.
*/
protected getLessonByField(courseId: number, key: string, value: any, forceCache?: boolean, siteId?: string): Promise<any> {
protected getLessonByField(courseId: number, key: string, value: any, forceCache?: boolean, ignoreCache?: boolean,
siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
@ -1392,6 +1408,9 @@ export class AddonModLessonProvider {
if (forceCache) {
preSets.omitExpires = true;
} else if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_lesson_get_lessons_by_courses', params, preSets).then((response) => {
@ -1416,11 +1435,12 @@ export class AddonModLessonProvider {
* @param {number} courseId Course ID.
* @param {number} id Lesson ID.
* @param {boolean} [forceCache] Whether it should always return cached data.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the lesson is retrieved.
*/
getLessonById(courseId: number, id: number, forceCache?: boolean, siteId?: string): Promise<any> {
return this.getLessonByField(courseId, 'id', id, forceCache, siteId);
getLessonById(courseId: number, id: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.getLessonByField(courseId, 'id', id, forceCache, ignoreCache, siteId);
}
/**
@ -2758,7 +2778,14 @@ export class AddonModLessonProvider {
params.pageid = pageId;
}
return site.write('mod_lesson_launch_attempt', params);
return site.write('mod_lesson_launch_attempt', params).then((response) => {
this.eventsProvider.trigger(AddonModLessonProvider.DATA_SENT_EVENT, {
lessonId: id,
type: 'launch'
}, this.sitesProvider.getCurrentSiteId());
return response;
});
});
}
@ -3028,7 +3055,17 @@ export class AddonModLessonProvider {
});
}
return this.processPageOnline(lesson.id, pageId, data, password, review, siteId);
return this.processPageOnline(lesson.id, pageId, data, password, review, siteId).then((response) => {
this.eventsProvider.trigger(AddonModLessonProvider.DATA_SENT_EVENT, {
lessonId: lesson.id,
type: 'process',
courseId: courseId,
pageId: pageId,
review: review
}, this.sitesProvider.getCurrentSiteId());
return response;
});
}
/**

View File

@ -83,7 +83,7 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
password,
result;
return this.lessonProvider.getLesson(courseId, module.id, false, siteId).then((lessonData) => {
return this.lessonProvider.getLesson(courseId, module.id, false, false, siteId).then((lessonData) => {
lesson = lessonData;
// Get the lesson password if it's needed.
@ -190,7 +190,7 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
const siteId = this.sitesProvider.getCurrentSiteId();
// Invalidate data to determine if module is downloadable.
return this.lessonProvider.getLesson(courseId, module.id, false, siteId).then((lesson) => {
return this.lessonProvider.getLesson(courseId, module.id, true, false, siteId).then((lesson) => {
const promises = [];
promises.push(this.lessonProvider.invalidateLessonData(courseId, siteId));
@ -210,7 +210,7 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
isDownloadable(module: any, courseId: number): boolean | Promise<boolean> {
const siteId = this.sitesProvider.getCurrentSiteId();
return this.lessonProvider.getLesson(courseId, module.id, false, siteId).then((lesson) => {
return this.lessonProvider.getLesson(courseId, module.id, false, false, siteId).then((lesson) => {
if (!this.lessonProvider.isLessonOffline(lesson)) {
return false;
}
@ -260,7 +260,7 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
password,
accessInfo;
return this.lessonProvider.getLesson(courseId, module.id, false, siteId).then((lessonData) => {
return this.lessonProvider.getLesson(courseId, module.id, false, true, siteId).then((lessonData) => {
lesson = lessonData;
// Get the lesson password if it's needed.
@ -360,7 +360,8 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
if (accessInfo.canviewreports) {
// Prefetch reports data.
promises.push(this.groupsProvider.getActivityAllowedGroupsIfEnabled(module.id, undefined, siteId).then((groups) => {
promises.push(this.groupsProvider.getActivityAllowedGroupsIfEnabled(module.id, undefined, siteId, true)
.then((groups) => {
const subPromises = [];
groups.forEach((group) => {

View File

@ -42,7 +42,7 @@ export class CoreCourseActivitySyncBaseProvider extends CoreSyncBaseProvider {
*
* @param {any} module Module.
* @param {number} courseId Course ID.
* @param {RegExp} [regex] RegExp to check if it should download data. Defaults to check files.
* @param {RegExp} [regex] If regex matches, don't download the data. Defaults to check files.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
@ -54,11 +54,11 @@ export class CoreCourseActivitySyncBaseProvider extends CoreSyncBaseProvider {
if (result && result.updates && result.updates.length > 0) {
// Only prefetch if files haven't changed.
const fileChanged = !!result.updates.find((entry) => {
const shouldDownload = !result.updates.find((entry) => {
return entry.name.match(regex);
});
if (!fileChanged) {
if (shouldDownload) {
return this.prefetchHandler.download(module, courseId);
}
}

View File

@ -124,16 +124,17 @@ export class CoreGroupsProvider {
* @param {number} cmId Course module ID.
* @param {number} [userId] User ID. If not defined, use current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any[]>} Promise resolved when the groups are retrieved. If not allowed, empty array will be returned.
*/
getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string): Promise<any[]> {
getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise<any[]> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
// Get real groupmode, in case it's forced by the course.
return this.activityHasGroups(cmId, siteId).then((hasGroups) => {
return this.activityHasGroups(cmId, siteId, ignoreCache).then((hasGroups) => {
if (hasGroups) {
// Get the groups available for the user.
return this.getActivityAllowedGroups(cmId, userId, siteId);
return this.getActivityAllowedGroups(cmId, userId, siteId, ignoreCache);
}
return [];
@ -147,7 +148,7 @@ export class CoreGroupsProvider {
* @param {boolean} [addAllParts=true] Whether to add the all participants option. Always true for visible groups.
* @param {number} [userId] User ID. If not defined, use current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down).
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<CoreGroupInfo>} Promise resolved with the group info.
*/
getActivityGroupInfo(cmId: number, addAllParts: boolean = true, userId?: number, siteId?: string, ignoreCache?: boolean)