commit
92419e2799
|
@ -61,7 +61,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
protected lastAttempt: number; // Last attempt.
|
protected lastAttempt: number; // Last attempt.
|
||||||
protected lastIsOffline: boolean; // Whether the last attempt is offline.
|
protected lastIsOffline: boolean; // Whether the last attempt is offline.
|
||||||
protected hasPlayed = false; // Whether the user has opened the player page.
|
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,
|
constructor(injector: Injector, protected scormProvider: AddonModScormProvider, @Optional() protected content: Content,
|
||||||
protected scormHelper: AddonModScormHelperProvider, protected scormOffline: AddonModScormOfflineProvider,
|
protected scormHelper: AddonModScormHelperProvider, protected scormOffline: AddonModScormOfflineProvider,
|
||||||
|
@ -330,13 +331,12 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
* @return {boolean} If suceed or not.
|
* @return {boolean} If suceed or not.
|
||||||
*/
|
*/
|
||||||
protected hasSyncSucceed(result: any): boolean {
|
protected hasSyncSucceed(result: any): boolean {
|
||||||
if (result.updated || this.syncDueToPlayerLeft) {
|
if (result.updated || this.dataSent) {
|
||||||
// Check completion status if something was sent or the user just left the player.
|
// Check completion status if something was sent.
|
||||||
// If the user plays the SCORM in online we don't know if he sent data or not, so always check completion.
|
|
||||||
this.checkCompletion();
|
this.checkCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.syncDueToPlayerLeft = false;
|
this.dataSent = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -349,11 +349,13 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
|
|
||||||
if (this.hasPlayed) {
|
if (this.hasPlayed) {
|
||||||
this.hasPlayed = false;
|
this.hasPlayed = false;
|
||||||
this.syncDueToPlayerLeft = true;
|
|
||||||
this.scormOptions.newAttempt = false; // Uncheck new attempt.
|
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.
|
// Add a delay to make sure the player has started the last writing calls so we can detect conflicts.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
this.dataSentObserver && this.dataSentObserver.off(); // Stop listening for changes.
|
||||||
|
this.dataSentObserver = undefined;
|
||||||
|
|
||||||
// Refresh data.
|
// Refresh data.
|
||||||
this.showLoadingAndRefresh(true, false);
|
this.showLoadingAndRefresh(true, false);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
@ -368,6 +370,15 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
|
|
||||||
if (this.navCtrl.getActive().component.name == 'AddonModScormPlayerPage') {
|
if (this.navCtrl.getActive().component.name == 'AddonModScormPlayerPage') {
|
||||||
this.hasPlayed = true;
|
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.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected sync(): Promise<any> {
|
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.
|
* @return {{[scoId: number]: string}} Launch URLs indexed by SCO ID.
|
||||||
*/
|
*/
|
||||||
protected getLaunchUrlsFromScos(scos: any[]): {[scoId: number]: string} {
|
protected getLaunchUrlsFromScos(scos: any[]): {[scoId: number]: string} {
|
||||||
|
scos = scos || [];
|
||||||
|
|
||||||
const response = {};
|
const response = {};
|
||||||
|
|
||||||
scos.forEach((sco) => {
|
scos.forEach((sco) => {
|
||||||
|
@ -487,12 +489,15 @@ export class AddonModScormOfflineProvider {
|
||||||
*
|
*
|
||||||
* @param {number} scormId SCORM ID.
|
* @param {number} scormId SCORM ID.
|
||||||
* @param {number} attempt Attempt number.
|
* @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 {string} [siteId] Site ID. If not defined, current site.
|
||||||
* @param {number} [userId] User ID. If not defined use site's current user.
|
* @param {number} [userId] User ID. If not defined use site's current user.
|
||||||
* @return {Promise<any>} Promise resolved when the user data is retrieved.
|
* @return {Promise<any>} Promise resolved when the user data is retrieved.
|
||||||
*/
|
*/
|
||||||
getScormUserData(scormId: number, attempt: number, scos: any[], siteId?: string, userId?: number): Promise<any> {
|
getScormUserData(scormId: number, attempt: number, scos: any[], siteId?: string, userId?: number): Promise<any> {
|
||||||
|
scos = scos || [];
|
||||||
|
|
||||||
let fullName = '',
|
let fullName = '',
|
||||||
userName = '';
|
userName = '';
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
|
||||||
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||||
import { AddonModScormProvider, AddonModScormAttemptCountResult } from './scorm';
|
import { AddonModScormProvider, AddonModScormAttemptCountResult } from './scorm';
|
||||||
import { AddonModScormOfflineProvider } from './scorm-offline';
|
import { AddonModScormOfflineProvider } from './scorm-offline';
|
||||||
|
@ -63,9 +64,10 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider {
|
||||||
|
|
||||||
constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider,
|
constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider,
|
||||||
syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService,
|
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 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,
|
super('AddonModScormSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
|
||||||
timeUtils);
|
timeUtils);
|
||||||
|
@ -190,11 +192,11 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider {
|
||||||
let promise;
|
let promise;
|
||||||
|
|
||||||
if (updated) {
|
if (updated) {
|
||||||
// Update the WS data.
|
// Update downloaded data.
|
||||||
promise = this.scormProvider.invalidateAllScormData(scorm.id, siteId).catch(() => {
|
promise = this.courseProvider.getModuleBasicInfoByInstance(scorm.id, 'scorm', siteId).then((module) => {
|
||||||
|
return this.prefetchAfterUpdate(module, scorm.course);
|
||||||
|
}).catch(() => {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}).then(() => {
|
|
||||||
return this.prefetchHandler.fetchWSData(scorm, siteId);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
promise = Promise.resolve();
|
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.
|
* Save a snapshot from a synchronization.
|
||||||
*
|
*
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
@ -86,6 +87,7 @@ export class AddonModScormProvider {
|
||||||
static LAUNCH_PREV_SCO_EVENT = 'addon_mod_scorm_launch_prev_sco';
|
static LAUNCH_PREV_SCO_EVENT = 'addon_mod_scorm_launch_prev_sco';
|
||||||
static UPDATE_TOC_EVENT = 'addon_mod_scorm_update_toc';
|
static UPDATE_TOC_EVENT = 'addon_mod_scorm_update_toc';
|
||||||
static GO_OFFLINE_EVENT = 'addon_mod_scorm_go_offline';
|
static GO_OFFLINE_EVENT = 'addon_mod_scorm_go_offline';
|
||||||
|
static DATA_SENT_EVENT = 'addon_mod_scorm_data_sent';
|
||||||
|
|
||||||
// Protected constants.
|
// Protected constants.
|
||||||
protected VALID_STATUSES = ['notattempted', 'passed', 'completed', 'failed', 'incomplete', 'browsed', 'suspend'];
|
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,
|
constructor(logger: CoreLoggerProvider, private translate: TranslateService, private sitesProvider: CoreSitesProvider,
|
||||||
private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider,
|
private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider,
|
||||||
private filepoolProvider: CoreFilepoolProvider, private scormOfflineProvider: AddonModScormOfflineProvider,
|
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');
|
this.logger = logger.getInstance('AddonModScormProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1483,6 +1486,12 @@ export class AddonModScormProvider {
|
||||||
return this.saveTracksOnline(scorm.id, scoId, attempt, tracks, siteId).then(() => {
|
return this.saveTracksOnline(scorm.id, scoId, attempt, tracks, siteId).then(() => {
|
||||||
// Tracks have been saved, update cached user data.
|
// Tracks have been saved, update cached user data.
|
||||||
this.updateUserDataAfterSave(scorm.id, attempt, tracks, siteId);
|
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) {
|
if (success) {
|
||||||
// Tracks have been saved, update cached user data.
|
// Tracks have been saved, update cached user data.
|
||||||
this.updateUserDataAfterSave(scorm.id, attempt, tracks);
|
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;
|
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 {any} module Module.
|
||||||
* @param {number} courseId Course ID the module belongs to.
|
* @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),
|
const packageId = this.filepoolProvider.getPackageId(handler.component, module.id),
|
||||||
status = this.statusCache.getValue(packageId, 'status');
|
status = this.statusCache.getValue(packageId, 'status');
|
||||||
|
|
||||||
if (typeof status != 'undefined' && status != CoreConstants.DOWNLOADED) {
|
if (typeof status != 'undefined' && status != CoreConstants.DOWNLOADED && status != CoreConstants.OUTDATED) {
|
||||||
// Status is different than downloaded, just return the status.
|
// Module isn't downloaded, just return the status.
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
status: status
|
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.
|
* 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.
|
* Check if a list of modules is being downloaded.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue