MOBILE-2348 quiz: Fix problems in prefetch and offline

main
Dani Palou 2018-04-05 16:17:03 +02:00
parent 0b50cdfc21
commit ad1e564600
11 changed files with 51 additions and 23 deletions

View File

@ -16,6 +16,7 @@ import { Component, Optional, Injector } from '@angular/core';
import { Content, NavController } from 'ionic-angular';
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate';
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { AddonModQuizProvider } from '../../providers/quiz';
import { AddonModQuizHelperProvider } from '../../providers/helper';
import { AddonModQuizOfflineProvider } from '../../providers/quiz-offline';
@ -70,7 +71,8 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
constructor(injector: Injector, protected quizProvider: AddonModQuizProvider, @Optional() protected content: Content,
protected quizHelper: AddonModQuizHelperProvider, protected quizOffline: AddonModQuizOfflineProvider,
protected quizSync: AddonModQuizSyncProvider, protected behaviourDelegate: CoreQuestionBehaviourDelegate,
protected prefetchHandler: AddonModQuizPrefetchHandler, protected navCtrl: NavController) {
protected prefetchHandler: AddonModQuizPrefetchHandler, protected navCtrl: NavController,
protected prefetchDelegate: CoreCourseModulePrefetchDelegate) {
super(injector);
}
@ -87,6 +89,8 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
this.quizProvider.logViewQuiz(this.quizData.id).then(() => {
this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
}).catch((error) => {
// Ignore errors.
});
});
@ -110,7 +114,10 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
if (this.quizProvider.isQuizOffline(this.quizData)) {
// Quiz supports offline, check if it needs to be downloaded.
if (this.currentStatus != CoreConstants.DOWNLOADED) {
// If the site doesn't support check updates, always prefetch it because we cannot tell if there's something new.
const isDownloaded = this.currentStatus == CoreConstants.DOWNLOADED;
if (!isDownloaded || !this.prefetchDelegate.canCheckUpdates()) {
// Prefetch the quiz.
this.showStatusSpinner = true;
@ -118,8 +125,9 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
// Success downloading, open quiz.
this.openQuiz();
}).catch((error) => {
if (this.hasOffline) {
if (this.hasOffline || (isDownloaded && !this.prefetchDelegate.canCheckUpdates())) {
// Error downloading but there is something offline, allow continuing it.
// If the site doesn't support check updates, continue too because we cannot tell if there's something new.
this.openQuiz();
} else {
this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);

View File

@ -420,7 +420,9 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
});
// Mark the page as viewed. We'll ignore errors in this call.
this.quizProvider.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline);
this.quizProvider.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline).catch((error) => {
// Ignore errors.
});
// Start looking for changes.
this.autoSave.startCheckChangesProcess(this.quiz, this.attempt, this.preflightData, this.offline);
@ -445,7 +447,9 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
this.attempt.dueDateWarning = this.quizProvider.getAttemptDueDateWarning(this.quiz, this.attempt);
// Log summary as viewed.
this.quizProvider.logViewAttemptSummary(this.attempt.id, this.preflightData);
this.quizProvider.logViewAttemptSummary(this.attempt.id, this.preflightData).catch((error) => {
// Ignore errors.
});
});
}

View File

@ -77,7 +77,9 @@ export class AddonModQuizReviewPage implements OnInit {
*/
ngOnInit(): void {
this.fetchData().then(() => {
this.quizProvider.logViewAttemptReview(this.attemptId);
this.quizProvider.logViewAttemptReview(this.attemptId).catch((error) => {
// Ignore errors.
});
}).finally(() => {
this.loaded = true;
});

View File

@ -78,7 +78,7 @@ export class AddonModQuizHelperProvider {
return Promise.reject(error);
} else if (retrying && !isPreflightCheckRequired) {
// We're retrying after a failure, but the preflight check wasn't required.
// If this happens it means there's something wrong with some access rule.
// This means there's something wrong with some access rule or user is offline and data isn't cached.
// Don't retry again because it would lead to an infinite loop.
return Promise.reject(error);
} else {

View File

@ -233,7 +233,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseModulePrefetchHandler
return Promise.all(promises);
}).then(() => {
// Check if we need to start a new attempt.
const attempt = attempts[attempts.length - 1];
let attempt = attempts[attempts.length - 1];
if (!attempt || this.quizProvider.isAttemptFinished(attempt.state)) {
// Check if the user can attempt the quiz.
if (attemptAccessInfo.preventnewattemptreasons.length) {
@ -241,6 +241,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseModulePrefetchHandler
}
startAttempt = true;
attempt = undefined;
}
// Get the preflight data. This function will also start a new attempt if needed.
@ -407,9 +408,11 @@ export class AddonModQuizPrefetchHandler extends CoreCourseModulePrefetchHandler
}));
promises.push(this.quizProvider.getGradeFromGradebook(quiz.course, quiz.coursemodule, true, siteId)
.then((gradebookData) => {
if (typeof gradebookData.grade != 'undefined') {
if (typeof gradebookData.graderaw != 'undefined') {
return this.quizProvider.getFeedbackForGrade(quiz.id, gradebookData.graderaw, true, siteId);
}
}).catch(() => {
// Ignore errors.
}));
promises.push(this.quizProvider.getAttemptAccessInformation(quiz.id, 0, false, true, siteId)); // Last attempt.

View File

@ -66,6 +66,10 @@ export class AddonModQuizOfflineProvider {
name: 'timecreated',
type: 'INTEGER'
},
{
name: 'timemodified',
type: 'INTEGER'
},
{
name: 'finished',
type: 'INTEGER'
@ -245,7 +249,7 @@ export class AddonModQuizOfflineProvider {
}).then((entry) => {
// Save attempt in DB.
entry.timemodified = now;
entry.finished = !!finish;
entry.finished = finish ? 1 : 0;
return db.insertRecord(this.ATTEMPTS_TABLE, entry);
}).then(() => {

View File

@ -343,6 +343,10 @@ export class SQLiteDB {
* @param {object} data Data to insert.
*/
protected formatDataToInsert(data: object): void {
if (!data) {
return;
}
// Remove undefined entries and convert null to "NULL".
for (const name in data) {
const value = data[name];
@ -782,6 +786,8 @@ export class SQLiteDB {
*/
updateRecords(table: string, data: any, conditions?: any): Promise<any> {
this.formatDataToInsert(data);
if (!data || !Object.keys(data).length) {
// No fields to update, consider it's done.
return Promise.resolve();
@ -792,8 +798,6 @@ export class SQLiteDB {
let sql,
params;
this.formatDataToInsert(data);
for (const key in data) {
sets.push(`${key} = ?`);
}

View File

@ -205,6 +205,7 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
// Also, get the current status.
this.modulePrefetchProvider.getModuleStatus(this.module, this.courseId).then((status) => {
this.currentStatus = status;
this.showStatus(status);
});
}

View File

@ -66,6 +66,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
protected prefetchHandler: CoreCourseModulePrefetchHandler;
protected statusObserver;
protected isDestroyed = false;
constructor(@Optional() protected navCtrl: NavController, protected prefetchDelegate: CoreCourseModulePrefetchDelegate,
protected domUtils: CoreDomUtilsProvider, protected courseHelper: CoreCourseHelperProvider,
@ -128,14 +129,13 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
// Get download size to ask for confirm if it's high.
this.prefetchHandler.getDownloadSize(module, this.courseId).then((size) => {
this.courseHelper.prefetchModule(this.prefetchHandler, this.module, size, this.courseId, refresh).catch((error) => {
// Error or cancelled.
this.spinner = false;
});
return this.courseHelper.prefetchModule(this.prefetchHandler, this.module, size, this.courseId, refresh);
}).catch((error) => {
// Error getting download size, hide spinner.
// Error, hide spinner.
this.spinner = false;
this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);
if (!this.isDestroyed && error) {
this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);
}
});
}
@ -158,5 +158,6 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
*/
ngOnDestroy(): void {
this.statusObserver && this.statusObserver.off();
this.isDestroyed = true;
}
}

View File

@ -405,7 +405,8 @@ export class CoreQuestionProvider {
*/
getQuestionAnswers(component: string, attemptId: number, slot: number, filter?: boolean, siteId?: string): Promise<any[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId, slot}).then((answers) => {
return site.getDb().getRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId, questionSlot: slot})
.then((answers) => {
if (filter) {
// Get only answers that isn't "extra" data like sequencecheck or certainty.
return this.getBasicAnswersFromArray(answers);
@ -525,7 +526,7 @@ export class CoreQuestionProvider {
*/
removeQuestionAnswers(component: string, attemptId: number, slot: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId, slot});
return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId, questionSlot: slot});
});
}

View File

@ -653,10 +653,10 @@ export class CoreFilepoolProvider {
if (filePath && entry.path !== filePath) {
newData.path = filePath;
}
if (entry.isexternalfile !== options.isexternalfile) {
if (entry.isexternalfile !== options.isexternalfile && (entry.isexternalfile || options.isexternalfile)) {
newData.isexternalfile = options.isexternalfile;
}
if (entry.repositorytype !== options.repositorytype) {
if (entry.repositorytype !== options.repositorytype && (entry.repositorytype || options.repositorytype)) {
newData.repositorytype = options.repositorytype;
}
@ -2659,7 +2659,7 @@ export class CoreFilepoolProvider {
// Going back from downloading to previous status, restore previous download time.
newData.downloadTime = entry.previousDownloadTime;
}
newData.status = entry.previous || CoreConstants.DOWNLOADED;
newData.status = entry.previous || CoreConstants.NOT_DOWNLOADED;
newData.updated = Date.now();
this.logger.debug(`Set previous status '${entry.status}' for package ${component} ${componentId}`);