commit
92419e2799
|
@ -61,7 +61,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
protected lastAttempt: number; // Last attempt.
|
||||
protected lastIsOffline: boolean; // Whether the last attempt is offline.
|
||||
protected hasPlayed = false; // Whether the user has opened the player page.
|
||||
protected syncDueToPlayerLeft = false; // Whether a sync was due to the user leaving the player.
|
||||
protected dataSentObserver; // To detect data sent to server.
|
||||
protected dataSent = false; // Whether some data was sent to server while playing the SCORM.
|
||||
|
||||
constructor(injector: Injector, protected scormProvider: AddonModScormProvider, @Optional() protected content: Content,
|
||||
protected scormHelper: AddonModScormHelperProvider, protected scormOffline: AddonModScormOfflineProvider,
|
||||
|
@ -330,13 +331,12 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
* @return {boolean} If suceed or not.
|
||||
*/
|
||||
protected hasSyncSucceed(result: any): boolean {
|
||||
if (result.updated || this.syncDueToPlayerLeft) {
|
||||
// Check completion status if something was sent or the user just left the player.
|
||||
// If the user plays the SCORM in online we don't know if he sent data or not, so always check completion.
|
||||
if (result.updated || this.dataSent) {
|
||||
// Check completion status if something was sent.
|
||||
this.checkCompletion();
|
||||
}
|
||||
|
||||
this.syncDueToPlayerLeft = false;
|
||||
this.dataSent = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -349,11 +349,13 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
|
||||
if (this.hasPlayed) {
|
||||
this.hasPlayed = false;
|
||||
this.syncDueToPlayerLeft = true;
|
||||
this.scormOptions.newAttempt = false; // Uncheck new attempt.
|
||||
|
||||
// Add a delay to make sure the player has started the last writing calls so we can detect conflicts.
|
||||
setTimeout(() => {
|
||||
this.dataSentObserver && this.dataSentObserver.off(); // Stop listening for changes.
|
||||
this.dataSentObserver = undefined;
|
||||
|
||||
// Refresh data.
|
||||
this.showLoadingAndRefresh(true, false);
|
||||
}, 500);
|
||||
|
@ -368,6 +370,15 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
|
||||
if (this.navCtrl.getActive().component.name == 'AddonModScormPlayerPage') {
|
||||
this.hasPlayed = true;
|
||||
|
||||
// Detect if anything was sent to server.
|
||||
this.dataSentObserver && this.dataSentObserver.off();
|
||||
|
||||
this.dataSentObserver = this.eventsProvider.on(AddonModScormProvider.DATA_SENT_EVENT, (data) => {
|
||||
if (data.scormId === this.scorm.id) {
|
||||
this.dataSent = true;
|
||||
}
|
||||
}, this.siteId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -533,6 +544,17 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
|||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected sync(): Promise<any> {
|
||||
return this.scormSync.syncScorm(this.scorm);
|
||||
return this.scormSync.syncScorm(this.scorm).then((result) => {
|
||||
if (!result.updated && this.dataSent) {
|
||||
// The user sent data to server, but not in the sync process. Check if we need to fetch data.
|
||||
return this.scormSync.prefetchAfterUpdate(this.module, this.courseId).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -430,6 +430,8 @@ export class AddonModScormOfflineProvider {
|
|||
* @return {{[scoId: number]: string}} Launch URLs indexed by SCO ID.
|
||||
*/
|
||||
protected getLaunchUrlsFromScos(scos: any[]): {[scoId: number]: string} {
|
||||
scos = scos || [];
|
||||
|
||||
const response = {};
|
||||
|
||||
scos.forEach((sco) => {
|
||||
|
@ -487,12 +489,15 @@ export class AddonModScormOfflineProvider {
|
|||
*
|
||||
* @param {number} scormId SCORM ID.
|
||||
* @param {number} attempt Attempt number.
|
||||
* @param {any[]} scos SCOs returned by AddonModScormProvider.getScos.
|
||||
* @param {any[]} scos SCOs returned by AddonModScormProvider.getScos. If not supplied, this function will only return the
|
||||
* SCOs that have something stored and cmi.launch_data will be undefined.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @param {number} [userId] User ID. If not defined use site's current user.
|
||||
* @return {Promise<any>} Promise resolved when the user data is retrieved.
|
||||
*/
|
||||
getScormUserData(scormId: number, attempt: number, scos: any[], siteId?: string, userId?: number): Promise<any> {
|
||||
scos = scos || [];
|
||||
|
||||
let fullName = '',
|
||||
userName = '';
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text';
|
|||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
|
||||
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||
import { AddonModScormProvider, AddonModScormAttemptCountResult } from './scorm';
|
||||
import { AddonModScormOfflineProvider } from './scorm-offline';
|
||||
|
@ -63,9 +64,10 @@ export class AddonModScormSyncProvider 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 scormProvider: AddonModScormProvider, private scormOfflineProvider: AddonModScormOfflineProvider,
|
||||
private prefetchHandler: AddonModScormPrefetchHandler, private utils: CoreUtilsProvider) {
|
||||
private prefetchHandler: AddonModScormPrefetchHandler, private utils: CoreUtilsProvider,
|
||||
private prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider) {
|
||||
|
||||
super('AddonModScormSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
|
||||
timeUtils);
|
||||
|
@ -190,11 +192,11 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider {
|
|||
let promise;
|
||||
|
||||
if (updated) {
|
||||
// Update the WS data.
|
||||
promise = this.scormProvider.invalidateAllScormData(scorm.id, siteId).catch(() => {
|
||||
// Update downloaded data.
|
||||
promise = this.courseProvider.getModuleBasicInfoByInstance(scorm.id, 'scorm', siteId).then((module) => {
|
||||
return this.prefetchAfterUpdate(module, scorm.course);
|
||||
}).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return this.prefetchHandler.fetchWSData(scorm, siteId);
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
|
@ -357,6 +359,31 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch data after an update. It won't prefetch the data if the package file was updated.
|
||||
*
|
||||
* @param {any} module Module.
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
prefetchAfterUpdate(module: any, courseId: number, siteId?: string): Promise<any> {
|
||||
// Get the module updates to check if the package was updated or not.
|
||||
return this.prefetchDelegate.getModuleUpdates(module, courseId, true, siteId).then((result) => {
|
||||
|
||||
if (result && result.updates) {
|
||||
// Only prefetch if the package file hasn't changed.
|
||||
const fileChanged = !!result.updates.find((entry) => {
|
||||
return entry.name == 'packagefiles';
|
||||
});
|
||||
|
||||
if (!fileChanged) {
|
||||
return this.prefetchHandler.download(module, courseId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a snapshot from a synchronization.
|
||||
*
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
|
@ -86,6 +87,7 @@ export class AddonModScormProvider {
|
|||
static LAUNCH_PREV_SCO_EVENT = 'addon_mod_scorm_launch_prev_sco';
|
||||
static UPDATE_TOC_EVENT = 'addon_mod_scorm_update_toc';
|
||||
static GO_OFFLINE_EVENT = 'addon_mod_scorm_go_offline';
|
||||
static DATA_SENT_EVENT = 'addon_mod_scorm_data_sent';
|
||||
|
||||
// Protected constants.
|
||||
protected VALID_STATUSES = ['notattempted', 'passed', 'completed', 'failed', 'incomplete', 'browsed', 'suspend'];
|
||||
|
@ -110,7 +112,8 @@ export class AddonModScormProvider {
|
|||
constructor(logger: CoreLoggerProvider, private translate: TranslateService, private sitesProvider: CoreSitesProvider,
|
||||
private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider,
|
||||
private filepoolProvider: CoreFilepoolProvider, private scormOfflineProvider: AddonModScormOfflineProvider,
|
||||
private timeUtils: CoreTimeUtilsProvider, private syncProvider: CoreSyncProvider) {
|
||||
private timeUtils: CoreTimeUtilsProvider, private syncProvider: CoreSyncProvider,
|
||||
private eventsProvider: CoreEventsProvider) {
|
||||
this.logger = logger.getInstance('AddonModScormProvider');
|
||||
}
|
||||
|
||||
|
@ -1483,6 +1486,12 @@ export class AddonModScormProvider {
|
|||
return this.saveTracksOnline(scorm.id, scoId, attempt, tracks, siteId).then(() => {
|
||||
// Tracks have been saved, update cached user data.
|
||||
this.updateUserDataAfterSave(scorm.id, attempt, tracks, siteId);
|
||||
|
||||
this.eventsProvider.trigger(AddonModScormProvider.DATA_SENT_EVENT, {
|
||||
scormId: scorm.id,
|
||||
scoId: scoId,
|
||||
attempt: attempt
|
||||
}, this.sitesProvider.getCurrentSiteId());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1546,6 +1555,12 @@ export class AddonModScormProvider {
|
|||
if (success) {
|
||||
// Tracks have been saved, update cached user data.
|
||||
this.updateUserDataAfterSave(scorm.id, attempt, tracks);
|
||||
|
||||
this.eventsProvider.trigger(AddonModScormProvider.DATA_SENT_EVENT, {
|
||||
scormId: scorm.id,
|
||||
scoId: scoId,
|
||||
attempt: attempt
|
||||
}, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
return success;
|
||||
|
|
|
@ -826,7 +826,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get a module status and download time. It will only return the download time if the module is downloaded and not outdated.
|
||||
* Get a module status and download time. It will only return the download time if the module is downloaded or outdated.
|
||||
*
|
||||
* @param {any} module Module.
|
||||
* @param {number} courseId Course ID the module belongs to.
|
||||
|
@ -841,8 +841,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
|
|||
const packageId = this.filepoolProvider.getPackageId(handler.component, module.id),
|
||||
status = this.statusCache.getValue(packageId, 'status');
|
||||
|
||||
if (typeof status != 'undefined' && status != CoreConstants.DOWNLOADED) {
|
||||
// Status is different than downloaded, just return the status.
|
||||
if (typeof status != 'undefined' && status != CoreConstants.DOWNLOADED && status != CoreConstants.OUTDATED) {
|
||||
// Module isn't downloaded, just return the status.
|
||||
return Promise.resolve({
|
||||
status: status
|
||||
});
|
||||
|
@ -872,6 +872,75 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updates for a certain module.
|
||||
* It will only return the updates if the module can use check updates and it's downloaded or outdated.
|
||||
*
|
||||
* @param {any} module Module to check.
|
||||
* @param {number} courseId Course the module belongs to.
|
||||
* @param {boolean} [ignoreCache] True if 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 with the updates.
|
||||
*/
|
||||
getModuleUpdates(module: any, courseId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
// Get the status and download time of the module.
|
||||
return this.getModuleStatusAndDownloadTime(module, courseId).then((data) => {
|
||||
if (data.status != CoreConstants.DOWNLOADED && data.status != CoreConstants.OUTDATED) {
|
||||
// Not downloaded, no updates.
|
||||
return {};
|
||||
}
|
||||
|
||||
// Module is downloaded. Check if it can check updates.
|
||||
return this.canModuleUseCheckUpdates(module, courseId).then((canUse) => {
|
||||
if (!canUse) {
|
||||
// Can't use check updates, no updates.
|
||||
return {};
|
||||
}
|
||||
|
||||
const params = {
|
||||
courseid: courseId,
|
||||
tocheck: [
|
||||
{
|
||||
contextlevel: 'module',
|
||||
id: module.id,
|
||||
since: data.downloadTime || 0
|
||||
}
|
||||
]
|
||||
},
|
||||
preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getModuleUpdatesCacheKey(courseId, module.id),
|
||||
};
|
||||
|
||||
if (ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
return site.read('core_course_check_updates', params, preSets).then((response) => {
|
||||
if (!response || !response.instances || !response.instances[0]) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return response.instances[0];
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for module updates WS calls.
|
||||
*
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {number} moduleId Module ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getModuleUpdatesCacheKey(courseId: number, moduleId: number): string {
|
||||
return this.getCourseUpdatesCacheKey(courseId) + ':' + moduleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a prefetch handler.
|
||||
*
|
||||
|
@ -933,6 +1002,20 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate check updates WS call for a certain module.
|
||||
*
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {number} moduleId Module ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when data is invalidated.
|
||||
*/
|
||||
invalidateModuleUpdates(courseId: number, moduleId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getModuleUpdatesCacheKey(courseId, moduleId));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a list of modules is being downloaded.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue