MOBILE-2850 quiz: Prefetch data after syncing quiz

main
Dani Palou 2019-02-28 15:10:17 +01:00
parent 0cec1cc01e
commit c6b51873f1
4 changed files with 137 additions and 25 deletions

View File

@ -50,6 +50,21 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils);
}
/**
* Download the module.
*
* @param {any} module The module object returned by WS.
* @param {number} courseId Course ID.
* @param {string} [dirPath] Path of the directory where to store all the content files.
* @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section.
* @param {boolean} [canStart=true] If true, start a new attempt if needed.
* @return {Promise<any>} Promise resolved when all content is downloaded.
*/
download(module: any, courseId: number, dirPath?: string, single?: boolean, canStart: boolean = true): Promise<any> {
// Same implementation for download and prefetch.
return this.prefetch(module, courseId, single, dirPath, canStart);
}
/**
* Get list of files. If not defined, we'll assume they're in module.contents.
*
@ -190,7 +205,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
const siteId = this.sitesProvider.getCurrentSiteId();
return this.quizProvider.getQuiz(courseId, module.id, false, siteId).then((quiz) => {
return this.quizProvider.getQuiz(courseId, module.id, false, false, siteId).then((quiz) => {
if (quiz.allowofflineattempts !== 1 || quiz.hasquestions === 0) {
return false;
}
@ -220,10 +235,11 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
* @param {number} courseId Course ID the module belongs to.
* @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section.
* @param {string} [dirPath] Path of the directory where to store all the content files.
* @param {boolean} [canStart=true] If true, start a new attempt if needed.
* @return {Promise<any>} Promise resolved when done.
*/
prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise<any> {
return this.prefetchPackage(module, courseId, single, this.prefetchQuiz.bind(this));
prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string, canStart: boolean = true): Promise<any> {
return this.prefetchPackage(module, courseId, single, this.prefetchQuiz.bind(this), undefined, canStart);
}
/**
@ -233,9 +249,10 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
* @param {number} courseId Course ID the module belongs to.
* @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section.
* @param {String} siteId Site ID.
* @param {boolean} canStart If true, start a new attempt if needed.
* @return {Promise<any>} Promise resolved when done.
*/
protected prefetchQuiz(module: any, courseId: number, single: boolean, siteId: string): Promise<any> {
protected prefetchQuiz(module: any, courseId: number, single: boolean, siteId: string, canStart: boolean): Promise<any> {
let attempts: any[],
startAttempt = false,
quiz,
@ -244,7 +261,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
preflightData;
// Get quiz.
return this.quizProvider.getQuiz(courseId, module.id, false, siteId).then((quizData) => {
return this.quizProvider.getQuiz(courseId, module.id, false, true, siteId).then((quizData) => {
quiz = quizData;
const promises = [],
@ -272,7 +289,13 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
}).then(() => {
// Check if we need to start a new attempt.
let attempt = attempts[attempts.length - 1];
if (!attempt || this.quizProvider.isAttemptFinished(attempt.state)) {
if (!canStart && !attempt) {
// No attempts and we won't start a new one, so we don't need preflight data.
return;
}
if (canStart && (!attempt || this.quizProvider.isAttemptFinished(attempt.state))) {
// Check if the user can attempt the quiz.
if (attemptAccessInfo.preventnewattemptreasons.length) {
return Promise.reject(this.textUtils.buildMessage(attemptAccessInfo.preventnewattemptreasons));
@ -331,6 +354,11 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
return Promise.all(promises);
}).then(() => {
if (!canStart) {
// Nothing else to do.
return;
}
// If there's nothing to send, mark the quiz as synchronized.
// We don't return the promises because it should be fast and we don't want to block the user for this.
if (!this.syncProvider) {
@ -477,14 +505,49 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
return this.prefetchAttempt(quiz, lastAttempt, preflightData, siteId);
}
}).then(() => {
// Prefetch finished, get current status to determine if we need to change it.
// Prefetch finished, set the right status.
return this.setStatusAfterPrefetch(quiz, attempts, true, false, siteId);
});
}
/**
* Set the right status to a quiz after prefetching.
* If the last attempt is finished or there isn't one, set it as not downloaded to show download icon.
*
* @param {any} quiz Quiz.
* @param {any[]} [attempts] List of attempts. If not provided, they will be calculated.
* @param {boolean} [forceCache] Whether it should always return cached data. Only if attempts is undefined.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). Only if
* attempts is undefined.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
setStatusAfterPrefetch(quiz: any, attempts?: any[], forceCache?: boolean, ignoreCache?: boolean, siteId?: string)
: Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const promises = [];
let status;
if (!attempts) {
// Get the attempts.
promises.push(this.quizProvider.getUserAttempts(quiz.id, 'all', true, forceCache, ignoreCache, siteId).then((atts) => {
attempts = atts;
}));
}
// Check the current status of the quiz.
promises.push(this.filepoolProvider.getPackageStatus(siteId, this.component, quiz.coursemodule).then((stat) => {
status = stat;
}));
return Promise.all(promises).then(() => {
return this.filepoolProvider.getPackageStatus(siteId, this.component, quiz.coursemodule);
}).then((status) => {
if (status !== CoreConstants.NOT_DOWNLOADED) {
// Quiz was downloaded, set the new status.
// If no attempts or last is finished we'll mark it as not downloaded to show download icon.
const isLastFinished = !lastAttempt || this.quizProvider.isAttemptFinished(lastAttempt.state),
const lastAttempt = attempts[attempts.length - 1],
isLastFinished = !lastAttempt || this.quizProvider.isAttemptFinished(lastAttempt.state),
newStatus = isLastFinished ? CoreConstants.NOT_DOWNLOADED : CoreConstants.DOWNLOADED;
return this.filepoolProvider.storePackageStatus(siteId, newStatus, this.component, quiz.coursemodule);

View File

@ -23,9 +23,10 @@ import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { CoreQuestionProvider } from '@core/question/providers/question';
import { CoreQuestionDelegate } from '@core/question/providers/delegate';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync';
import { AddonModQuizProvider } from './quiz';
import { AddonModQuizOfflineProvider } from './quiz-offline';
import { AddonModQuizPrefetchHandler } from './prefetch-handler';
@ -51,7 +52,7 @@ export interface AddonModQuizSyncResult {
* Service to sync quizzes.
*/
@Injectable()
export class AddonModQuizSyncProvider extends CoreSyncBaseProvider {
export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider {
static AUTO_SYNCED = 'addon_mod_quiz_autom_synced';
@ -59,13 +60,14 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider {
constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider,
syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService,
courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider,
private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider,
private quizProvider: AddonModQuizProvider, private quizOfflineProvider: AddonModQuizOfflineProvider,
private prefetchHandler: AddonModQuizPrefetchHandler, private questionProvider: CoreQuestionProvider,
private questionDelegate: CoreQuestionDelegate, private logHelper: CoreCourseLogHelperProvider) {
protected prefetchHandler: AddonModQuizPrefetchHandler, private questionProvider: CoreQuestionProvider,
private questionDelegate: CoreQuestionDelegate, private logHelper: CoreCourseLogHelperProvider,
prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider) {
super('AddonModQuizSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
timeUtils);
timeUtils, prefetchDelegate, prefetchHandler);
this.componentTranslate = courseProvider.translateModuleName('quiz');
}
@ -97,7 +99,11 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider {
}).then(() => {
if (updated) {
// Data has been sent. Update prefetched data.
return this.prefetchHandler.prefetchQuizAndLastAttempt(quiz, false, siteId);
return this.courseProvider.getModuleBasicInfoByInstance(quiz.id, 'quiz', siteId).then((module) => {
return this.prefetchAfterUpdateQuiz(module, quiz, courseId, undefined, siteId);
}).catch(() => {
// Ignore errors.
});
}
}).then(() => {
return this.setSyncTime(quiz.id, siteId).catch(() => {
@ -145,6 +151,41 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider {
});
}
/**
* Conveniece function to prefetch data after an update.
*
* @param {any} module Module.
* @param {any} quiz Quiz.
* @param {number} courseId Course ID.
* @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.
*/
prefetchAfterUpdateQuiz(module: any, quiz: any, courseId: number, regex?: RegExp, siteId?: string): Promise<any> {
regex = regex || /^.*files$/;
let shouldDownload;
// Get the module updates to check if the data was updated or not.
return this.prefetchDelegate.getModuleUpdates(module, courseId, true, siteId).then((result) => {
if (result && result.updates && result.updates.length > 0) {
// Only prefetch if files haven't changed.
shouldDownload = !result.updates.find((entry) => {
return entry.name.match(regex);
});
if (shouldDownload) {
return this.prefetchHandler.download(module, courseId, undefined, false, false);
}
}
}).then(() => {
// Prefetch finished or not needed, set the right status.
return this.prefetchHandler.setStatusAfterPrefetch(quiz, undefined, shouldDownload, false, siteId);
});
}
/**
* Try to synchronize all the quizzes in a certain site or in all sites.
*
@ -185,7 +226,7 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider {
if (!this.syncProvider.isBlocked(AddonModQuizProvider.COMPONENT, quiz.id, siteId)) {
// Quiz not blocked, try to synchronize it.
promises.push(this.quizProvider.getQuizById(quiz.courseid, quiz.id, false, siteId).then((quiz) => {
promises.push(this.quizProvider.getQuizById(quiz.courseid, quiz.id, false, false, siteId).then((quiz) => {
return this.syncQuizIfNeeded(quiz, false, siteId).then((data) => {
if (data && data.warnings && data.warnings.length) {
// Store the warnings to show them when the user opens the quiz.

View File

@ -672,10 +672,13 @@ export class AddonModQuizProvider {
* @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 Quiz is retrieved.
*/
protected getQuizByField(courseId: number, key: string, value: any, forceCache?: boolean, siteId?: string): Promise<any> {
protected getQuizByField(courseId: number, key: string, value: any, forceCache?: boolean, ignoreCache?: boolean,
siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
courseids: [courseId]
@ -686,6 +689,9 @@ export class AddonModQuizProvider {
if (forceCache) {
preSets.omitExpires = true;
} else if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_quiz_get_quizzes_by_courses', params, preSets).then((response) => {
@ -710,11 +716,12 @@ export class AddonModQuizProvider {
* @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 quiz is retrieved.
*/
getQuiz(courseId: number, cmId: number, forceCache?: boolean, siteId?: string): Promise<any> {
return this.getQuizByField(courseId, 'coursemodule', cmId, forceCache, siteId);
getQuiz(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.getQuizByField(courseId, 'coursemodule', cmId, forceCache, ignoreCache, siteId);
}
/**
@ -723,11 +730,12 @@ export class AddonModQuizProvider {
* @param {number} courseId Course ID.
* @param {number} id Quiz 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 quiz is retrieved.
*/
getQuizById(courseId: number, id: number, forceCache?: boolean, siteId?: string): Promise<any> {
return this.getQuizByField(courseId, 'id', id, forceCache, siteId);
getQuizById(courseId: number, id: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.getQuizByField(courseId, 'id', id, forceCache, ignoreCache, siteId);
}
/**
@ -1223,7 +1231,7 @@ export class AddonModQuizProvider {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
// Get required data to call the invalidate functions.
return this.getQuiz(courseId, moduleId, false, siteId).then((quiz) => {
return this.getQuiz(courseId, moduleId, true, false, siteId).then((quiz) => {
return this.getUserAttempts(quiz.id, 'all', true, false, false, siteId).then((attempts) => {
// Now invalidate it.
const lastAttemptId = attempts.length ? attempts[attempts.length - 1].id : undefined;

View File

@ -38,7 +38,7 @@ export class CoreCourseActivitySyncBaseProvider extends CoreSyncBaseProvider {
}
/**
* Conveniece function to refetch data after an update.
* Conveniece function to prefetch data after an update.
*
* @param {any} module Module.
* @param {number} courseId Course ID.