From 0196a738d745446a695d76d24a1ec6a6dbca0db9 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 26 May 2020 15:10:58 +0200 Subject: [PATCH] MOBILE-3411 h5pactivity: Add button to download the file --- .../index/addon-mod-h5pactivity-index.html | 18 +- .../mod/h5pactivity/components/index/index.ts | 189 +++++++++++++++++- src/addon/mod/h5pactivity/lang/en.json | 5 +- .../mod/h5pactivity/providers/h5pactivity.ts | 105 +++++++++- src/addon/mod/scorm/providers/scorm.ts | 4 +- src/assets/lang/en.json | 3 + src/core/course/providers/log-helper.ts | 4 + src/core/h5p/classes/helper.ts | 34 +++- src/core/h5p/classes/player.ts | 8 +- .../h5p/components/h5p-player/h5p-player.ts | 5 +- src/core/h5p/providers/pluginfile-handler.ts | 17 +- src/providers/filepool.ts | 2 +- src/providers/plugin-file-delegate.ts | 8 +- src/providers/utils/utils.ts | 10 + 14 files changed, 385 insertions(+), 27 deletions(-) diff --git a/src/addon/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html b/src/addon/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html index 9ce7786b3..5bb5d2599 100644 --- a/src/addon/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html +++ b/src/addon/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html @@ -13,5 +13,21 @@ - + + +

{{ stateMessage | translate }}

+
+ + + + {{ 'core.download' | translate }} + + + + + +

{{ progressMessage | translate }}

+

{{ 'core.percentagenumber' | translate:{$a: percentage} }}

+
+
diff --git a/src/addon/mod/h5pactivity/components/index/index.ts b/src/addon/mod/h5pactivity/components/index/index.ts index 37cd71688..61feb1127 100644 --- a/src/addon/mod/h5pactivity/components/index/index.ts +++ b/src/addon/mod/h5pactivity/components/index/index.ts @@ -14,8 +14,20 @@ import { Component, Optional, Injector } from '@angular/core'; import { Content } from 'ionic-angular'; + +import { CoreApp } from '@providers/app'; +import { CoreFilepool } from '@providers/filepool'; +import { CoreWSExternalFile } from '@providers/ws'; +import { CoreDomUtils } from '@providers/utils/dom'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; -import { AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData } from '../../providers/h5pactivity'; +import { CoreH5P } from '@core/h5p/providers/h5p'; +import { CoreH5PDisplayOptions } from '@core/h5p/classes/core'; +import { CoreH5PHelper } from '@core/h5p/classes/helper'; +import { CoreConstants } from '@core/constants'; + +import { + AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData, AddonModH5PActivityAccessInfo +} from '../../providers/h5pactivity'; /** * Component that displays an H5P activity entry page. @@ -29,8 +41,18 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv moduleName = 'h5pactivity'; h5pActivity: AddonModH5PActivityData; // The H5P activity object. + accessInfo: AddonModH5PActivityAccessInfo; // Info about the user capabilities. + deployedFile: CoreWSExternalFile; // The H5P deployed file. + + stateMessage: string; // Message about the file state. + downloading: boolean; // Whether the H5P file is being downloaded. + needsDownload: boolean; // Whether the file needs to be downloaded. + percentage: string; // Download/unzip percentage. + progressMessage: string; // Message about download/unzip. + playing: boolean; // Whether the package is being played. protected fetchContentDefaultError = 'addon.mod_h5pactivity.errorgetactivity'; + protected displayOptions: CoreH5PDisplayOptions; constructor(injector: Injector, @Optional() protected content: Content) { @@ -66,12 +88,61 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv this.h5pActivity = await AddonModH5PActivity.instance.getH5PActivity(this.courseId, this.module.id); this.description = this.h5pActivity.intro; + this.displayOptions = CoreH5PHelper.decodeDisplayOptions(this.h5pActivity.displayoptions); this.dataRetrieved.emit(this.h5pActivity); + + await Promise.all([ + this.fetchAccessInfo(), + this.fetchDeployedFileData(), + ]); } finally { this.fillContextMenu(refresh); } } + /** + * Fetch the access info and store it in the right variables. + * + * @return Promise resolved when done. + */ + protected async fetchAccessInfo(): Promise { + this.accessInfo = await AddonModH5PActivity.instance.getAccessInformation(this.h5pActivity.id); + } + + /** + * Fetch the deployed file data if needed and store it in the right variables. + * + * @return Promise resolved when done. + */ + protected async fetchDeployedFileData(): Promise { + if (this.h5pActivity.deployedfile) { + // File already deployed and still valid, use this one. + this.deployedFile = this.h5pActivity.deployedfile; + } else { + if (!this.h5pActivity.package || !this.h5pActivity.package[0]) { + // Shouldn't happen. + throw 'No H5P package found.'; + } + + // Deploy the file in the server. + this.deployedFile = await CoreH5P.instance.getTrustedH5PFile(this.h5pActivity.package[0].fileurl, this.displayOptions); + } + + await this.calculateFileStatus(); + } + + /** + * Calculate the status of the deployed file. + * + * @return Promise resolved when done. + */ + protected async calculateFileStatus(): Promise { + const state = await CoreFilepool.instance.getFileStateByUrl(this.siteId, this.deployedFile.fileurl, + this.deployedFile.timemodified); + + this.showFileState(state); + } + /** * Perform the invalidate content function. * @@ -80,4 +151,120 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv protected invalidateContent(): Promise { return AddonModH5PActivity.instance.invalidateActivityData(this.courseId); } + + /** + * Displays some data based on the state of the main file. + * + * @param state The state of the file. + */ + protected showFileState(state: string): void { + + if (state == CoreConstants.OUTDATED) { + this.stateMessage = 'addon.mod_h5pactivity.filestateoutdated'; + this.needsDownload = true; + } else if (state == CoreConstants.NOT_DOWNLOADED) { + this.stateMessage = 'addon.mod_h5pactivity.filestatenotdownloaded'; + this.needsDownload = true; + } else if (state == CoreConstants.DOWNLOADING) { + this.stateMessage = ''; + + if (!this.downloading) { + // It's being downloaded right now but the view isn't tracking it. "Restore" the download. + this.downloadDeployedFile().then(() => { + this.play(); + }); + } + } else { + this.stateMessage = ''; + this.needsDownload = false; + } + } + + /** + * Download the file and play it. + * + * @param e Click event. + * @return Promise resolved when done. + */ + async downloadAndPlay(e: MouseEvent): Promise { + e && e.preventDefault(); + e && e.stopPropagation(); + + if (!CoreApp.instance.isOnline()) { + CoreDomUtils.instance.showErrorModal('core.networkerrormsg', true); + + return; + } + + try { + // Confirm the download if needed. + await CoreDomUtils.instance.confirmDownloadSize({ size: this.deployedFile.filesize, total: true }); + + await this.downloadDeployedFile(); + + if (!this.isDestroyed) { + this.play(); + } + + } catch (error) { + if (CoreDomUtils.instance.isCanceledError(error) || this.isDestroyed) { + // User cancelled or view destroyed, stop. + return; + } + + CoreDomUtils.instance.showErrorModalDefault(error, 'core.errordownloading', true); + } + } + + /** + * Download athe H5P deployed file or restores an ongoing download. + * + * @return Promise resolved when done. + */ + protected async downloadDeployedFile(): Promise { + this.downloading = true; + this.progressMessage = 'core.downloading'; + + try { + await CoreFilepool.instance.downloadUrl(this.siteId, this.deployedFile.fileurl, false, this.component, this.componentId, + this.deployedFile.timemodified, (data) => { + + if (!data) { + return; + } + + if (data.message) { + // Show a message. + this.progressMessage = data.message; + this.percentage = undefined; + } else if (typeof data.loaded != 'undefined') { + if (this.progressMessage == 'core.downloading') { + // Downloading package. + this.percentage = (Number(data.loaded / this.deployedFile.filesize) * 100).toFixed(1); + } else if (typeof data.total != 'undefined') { + // Unzipping package. + this.percentage = (Number(data.loaded / data.total) * 100).toFixed(1); + } else { + this.percentage = undefined; + } + } else { + this.percentage = undefined; + } + }); + + } finally { + this.progressMessage = undefined; + this.percentage = undefined; + this.downloading = false; + } + } + + /** + * Play the package. + */ + play(): void { + this.playing = true; + + // @TODO + } } diff --git a/src/addon/mod/h5pactivity/lang/en.json b/src/addon/mod/h5pactivity/lang/en.json index 863053845..2f8ab42cf 100644 --- a/src/addon/mod/h5pactivity/lang/en.json +++ b/src/addon/mod/h5pactivity/lang/en.json @@ -1,4 +1,7 @@ { "errorgetactivity": "Error getting H5P activity data.", - "modulenameplural": "H5P" + "filestatenotdownloaded": "The H5P package is not downloaded. You need to download it to be able to use it.", + "filestateoutdated": "The H5P package has been modified since the last download. You need to download it again to be able to use it.", + "modulenameplural": "H5P", + "storingfiles": "Storing files" } \ No newline at end of file diff --git a/src/addon/mod/h5pactivity/providers/h5pactivity.ts b/src/addon/mod/h5pactivity/providers/h5pactivity.ts index c9431cddc..60a6f8abf 100644 --- a/src/addon/mod/h5pactivity/providers/h5pactivity.ts +++ b/src/addon/mod/h5pactivity/providers/h5pactivity.ts @@ -17,6 +17,7 @@ import { Injectable } from '@angular/core'; import { CoreSites } from '@providers/sites'; import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; +import { CoreCourseLogHelper } from '@core/course/providers/log-helper'; import { makeSingleton, Translate } from '@singletons/core.singletons'; @@ -29,6 +30,39 @@ export class AddonModH5PActivityProvider { protected ROOT_CACHE_KEY = 'mmaModH5PActivity:'; + /** + * Get cache key for access information WS calls. + * + * @param id H5P activity ID. + * @return Cache key. + */ + protected getAccessInformationCacheKey(id: number): string { + return this.ROOT_CACHE_KEY + 'accessInfo:' + id; + } + + /** + * Get access information for a given H5P activity. + * + * @param id H5P activity ID. + * @param forceCache True to always get the value from cache. false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the data. + */ + async getAccessInformation(id: number, forceCache?: boolean, siteId?: string): Promise { + + const site = await CoreSites.instance.getSite(siteId); + + const params = { + h5pactivityid: id, + }; + const preSets = { + cacheKey: this.getAccessInformationCacheKey(id), + omitExpires: forceCache, + }; + + return site.read('mod_h5pactivity_get_h5pactivity_access_information', params, preSets); + } + /** * Get cache key for H5P activity data WS calls. * @@ -54,6 +88,7 @@ export class AddonModH5PActivityProvider { : Promise { const site = await CoreSites.instance.getSite(siteId); + const params = { courseids: [courseId], }; @@ -66,7 +101,7 @@ export class AddonModH5PActivityProvider { preSets.omitExpires = true; } - const response: AddonModH5PActivityGetByCoursesRresult = + const response: AddonModH5PActivityGetByCoursesResult = await site.read('mod_h5pactivity_get_h5pactivities_by_courses', params, preSets); if (response && response.h5pactivities) { @@ -108,6 +143,20 @@ export class AddonModH5PActivityProvider { return this.getH5PActivityByField(courseId, 'id', id, forceCache, siteId); } + /** + * Invalidates access information. + * + * @param id H5P activity ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. + */ + async invalidateAccessInformation(id: number, siteId?: string): Promise { + + const site = await CoreSites.instance.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getAccessInformationCacheKey(id)); + } + /** * Invalidates H5P activity data. * @@ -115,10 +164,10 @@ export class AddonModH5PActivityProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the data is invalidated. */ - async invalidateActivityData(courseId: number, siteId?: string): Promise { + async invalidateActivityData(courseId: number, siteId?: string): Promise { const site = await CoreSites.instance.getSite(siteId); - return site.invalidateWsCacheForKey(this.getH5PActivityDataCacheKey(courseId)); + await site.invalidateWsCacheForKey(this.getH5PActivityDataCacheKey(courseId)); } /** @@ -131,6 +180,35 @@ export class AddonModH5PActivityProvider { return site.wsAvailable('mod_h5pactivity_get_h5pactivities_by_courses'); } + + /** + * Report an H5P activity as being viewed. + * + * @param id H5P activity ID. + * @param name Name of the activity. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. + */ + async logView(id: number, name?: string, siteId?: string): Promise { + const params = { + h5pactivityid: id, + }; + + const result: AddonModH5PActivityViewResult = await CoreCourseLogHelper.instance.logSingle( + 'mod_h5pactivity_view_h5pactivity', + params, + AddonModH5PActivityProvider.COMPONENT, + id, + name, + 'h5pactivity', + {}, + siteId + ); + + if (!result.status) { + throw result.warnings[0] || 'Error marking H5P activity as viewed.'; + } + } } export class AddonModH5PActivity extends makeSingleton(AddonModH5PActivityProvider) {} @@ -167,7 +245,26 @@ export type AddonModH5PActivityData = { /** * Result of WS mod_h5pactivity_get_h5pactivities_by_courses. */ -export type AddonModH5PActivityGetByCoursesRresult = { +export type AddonModH5PActivityGetByCoursesResult = { h5pactivities: AddonModH5PActivityData[]; warnings?: CoreWSExternalWarning[]; }; + +/** + * Result of WS mod_h5pactivity_get_h5pactivity_access_information. + */ +export type AddonModH5PActivityAccessInfo = { + warnings?: CoreWSExternalWarning[]; + canview?: boolean; // Whether the user has the capability mod/h5pactivity:view allowed. + canaddinstance?: boolean; // Whether the user has the capability mod/h5pactivity:addinstance allowed. + cansubmit?: boolean; // Whether the user has the capability mod/h5pactivity:submit allowed. + canreviewattempts?: boolean; // Whether the user has the capability mod/h5pactivity:reviewattempts allowed. +}; + +/** + * Result of WS mod_h5pactivity_view_h5pactivity. + */ +export type AddonModH5PActivityViewResult = { + status: boolean; // Status: true if success. + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/mod/scorm/providers/scorm.ts b/src/addon/mod/scorm/providers/scorm.ts index b02df74fc..2cec079ec 100644 --- a/src/addon/mod/scorm/providers/scorm.ts +++ b/src/addon/mod/scorm/providers/scorm.ts @@ -1257,7 +1257,7 @@ export class AddonModScormProvider { /** * Invalidates access information. * - * @param forumId SCORM ID. + * @param scormId SCORM ID. * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the data is invalidated. */ @@ -1544,7 +1544,7 @@ export class AddonModScormProvider { return this.logHelper.logSingle('mod_scorm_view_scorm', params, AddonModScormProvider.COMPONENT, id, name, 'scorm', {}, siteId); -} + } /** * Saves a SCORM tracking record. diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index b6a848c1f..7a09eb8fb 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -660,7 +660,10 @@ "addon.mod_glossary.searchquery": "Search query", "addon.mod_glossary.tagarea_glossary_entries": "Glossary entries", "addon.mod_h5pactivity.errorgetactivity": "Error getting H5P activity data.", + "addon.mod_h5pactivity.filestatenotdownloaded": "The H5P package is not downloaded. You need to download it to be able to use it.", + "addon.mod_h5pactivity.filestateoutdated": "The H5P package has been modified since the last download. You need to download it again to be able to use it.", "addon.mod_h5pactivity.modulenameplural": "H5P", + "addon.mod_h5pactivity.storingfiles": "Storing files", "addon.mod_imscp.deploymenterror": "Content package error!", "addon.mod_imscp.modulenameplural": "IMS content packages", "addon.mod_imscp.showmoduledescription": "Show description", diff --git a/src/core/course/providers/log-helper.ts b/src/core/course/providers/log-helper.ts index 8792eab81..d10aeaf43 100644 --- a/src/core/course/providers/log-helper.ts +++ b/src/core/course/providers/log-helper.ts @@ -20,6 +20,8 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreAppProvider } from '@providers/app'; import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; +import { makeSingleton } from '@singletons/core.singletons'; + /** * Helper to manage logging to Moodle. */ @@ -355,3 +357,5 @@ export class CoreCourseLogHelperProvider { })); } } + +export class CoreCourseLogHelper extends makeSingleton(CoreCourseLogHelperProvider) {} diff --git a/src/core/h5p/classes/helper.ts b/src/core/h5p/classes/helper.ts index dd6484cf2..4c268b71e 100644 --- a/src/core/h5p/classes/helper.ts +++ b/src/core/h5p/classes/helper.ts @@ -16,8 +16,9 @@ import { CoreFile, CoreFileProvider } from '@providers/file'; import { CoreSites } from '@providers/sites'; import { CoreMimetypeUtils } from '@providers/utils/mimetype'; import { CoreTextUtils } from '@providers/utils/text'; +import { CoreUtils } from '@providers/utils/utils'; import { CoreH5P } from '../providers/h5p'; -import { CoreH5PCore } from './core'; +import { CoreH5PCore, CoreH5PDisplayOptions } from './core'; import { FileEntry } from '@ionic-native/file'; /** @@ -25,6 +26,25 @@ import { FileEntry } from '@ionic-native/file'; */ export class CoreH5PHelper { + /** + * Convert the number representation of display options into an object. + * + * @param displayOptions Number representing display options. + * @return Object with display options. + */ + static decodeDisplayOptions(displayOptions: number): CoreH5PDisplayOptions { + const config: any = {}; + const displayOptionsObject = CoreH5P.instance.h5pCore.getDisplayOptionsAsObject(displayOptions); + + config.export = 0; // Don't allow downloading in the app. + config.embed = CoreUtils.instance.notNullOrUndefined(displayOptionsObject[CoreH5PCore.DISPLAY_OPTION_EMBED]) ? + displayOptionsObject[CoreH5PCore.DISPLAY_OPTION_EMBED] : 0; + config.copyright = CoreUtils.instance.notNullOrUndefined(displayOptionsObject[CoreH5PCore.DISPLAY_OPTION_COPYRIGHT]) ? + displayOptionsObject[CoreH5PCore.DISPLAY_OPTION_COPYRIGHT] : 0; + + return config; + } + /** * Get the core H5P assets, including all core H5P JavaScript and CSS. * @@ -107,19 +127,25 @@ export class CoreH5PHelper { * @param fileUrl The file URL used to download the file. * @param file The file entry of the downloaded file. * @param siteId Site ID. If not defined, current site. + * @param onProgress Function to call on progress. * @return Promise resolved when done. */ - static async saveH5P(fileUrl: string, file: FileEntry, siteId?: string): Promise { + static async saveH5P(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: (event: any) => any): Promise { siteId = siteId || CoreSites.instance.getCurrentSiteId(); - // Unzip the file. const folderName = CoreMimetypeUtils.instance.removeExtension(file.name); const destFolder = CoreTextUtils.instance.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName); + // Notify that the unzip is starting. + onProgress && onProgress({message: 'core.unzipping'}); + // Unzip the file. - await CoreFile.instance.unzipFile(file.toURL(), destFolder); + await CoreFile.instance.unzipFile(file.toURL(), destFolder, onProgress); try { + // Notify that the unzip is starting. + onProgress && onProgress({message: 'addon.mod_h5pactivity.storingfiles'}); + // Read the contents of the unzipped dir, process them and store them. const contents = await CoreFile.instance.getDirectoryContents(destFolder); diff --git a/src/core/h5p/classes/player.ts b/src/core/h5p/classes/player.ts index c4f461ef0..138a04d93 100644 --- a/src/core/h5p/classes/player.ts +++ b/src/core/h5p/classes/player.ts @@ -219,11 +219,11 @@ export class CoreH5PPlayer { * Get the content index file. * * @param fileUrl URL of the H5P package. - * @param urlParams URL params. + * @param displayOptions Display options. * @param siteId The site ID. If not defined, current site. * @return Promise resolved with the file URL if exists, rejected otherwise. */ - async getContentIndexFileUrl(fileUrl: string, urlParams?: {[name: string]: string}, siteId?: string): Promise { + async getContentIndexFileUrl(fileUrl: string, displayOptions?: CoreH5PDisplayOptions, siteId?: string): Promise { siteId = siteId || CoreSites.instance.getCurrentSiteId(); const path = await this.h5pCore.h5pFS.getContentIndexFileUrl(fileUrl, siteId); @@ -231,9 +231,9 @@ export class CoreH5PPlayer { // Add display options to the URL. const data = await this.h5pCore.h5pFramework.getContentDataByUrl(fileUrl, siteId); - const options = this.h5pCore.fixDisplayOptions(this.getDisplayOptionsFromUrlParams(urlParams), data.id); + displayOptions = this.h5pCore.fixDisplayOptions(displayOptions, data.id); - return CoreUrlUtils.instance.addParamsToUrl(path, options, undefined, true); + return CoreUrlUtils.instance.addParamsToUrl(path, displayOptions, undefined, true); } /** diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index bdace5d86..f46b37ab0 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -93,11 +93,12 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { this.loading = true; let localUrl: string; + const displayOptions = CoreH5P.instance.h5pPlayer.getDisplayOptionsFromUrlParams(this.urlParams); if (this.canDownload && CoreFileHelper.instance.isStateDownloaded(this.state)) { // Package is downloaded, use the local URL. try { - localUrl = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.urlParams.url, this.urlParams, this.siteId); + localUrl = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.urlParams.url, displayOptions, this.siteId); } catch (error) { // Index file doesn't exist, probably deleted because a lib was updated. Try to create it again. try { @@ -108,7 +109,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { await CoreH5PHelper.saveH5P(this.urlParams.url, file, this.siteId); // File treated. Try to get the index file URL again. - localUrl = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.urlParams.url, this.urlParams, + localUrl = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.urlParams.url, displayOptions, this.siteId); } catch (error) { // Still failing. Delete the H5P package? diff --git a/src/core/h5p/providers/pluginfile-handler.ts b/src/core/h5p/providers/pluginfile-handler.ts index 88a92e525..490b81bc9 100644 --- a/src/core/h5p/providers/pluginfile-handler.ts +++ b/src/core/h5p/providers/pluginfile-handler.ts @@ -18,6 +18,7 @@ import { CoreMimetypeUtils } from '@providers/utils/mimetype'; import { CoreUrlUtils } from '@providers/utils/url'; import { CoreUtils } from '@providers/utils/utils'; import { CoreH5P } from './h5p'; +import { CoreSites } from '@providers/sites'; import { CoreWSExternalFile } from '@providers/ws'; import { FileEntry } from '@ionic-native/file'; import { Translate } from '@singletons/core.singletons'; @@ -50,7 +51,14 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the file to use. Rejected if cannot download. */ - getDownloadableFile(file: CoreWSExternalFile, siteId?: string): Promise { + async getDownloadableFile(file: CoreWSExternalFile, siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + if (site.containsUrl(file.fileurl) && file.fileurl.match(/pluginfile\.php\/[^\/]+\/core_h5p\/export\//i)) { + // It's already a deployed file, use it. + return file; + } + return CoreH5P.instance.getTrustedH5PFile(file.fileurl, {}, false, siteId); } @@ -85,7 +93,7 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { */ async getFileSize(file: CoreWSExternalFile, siteId?: string): Promise { try { - const trustedFile = await CoreH5P.instance.getTrustedH5PFile(file.fileurl, {}, false, siteId); + const trustedFile = await this.getDownloadableFile(file, siteId); return trustedFile.filesize; } catch (error) { @@ -145,9 +153,10 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { * @param fileUrl The file URL used to download the file. * @param file The file entry of the downloaded file. * @param siteId Site ID. If not defined, current site. + * @param onProgress Function to call on progress. * @return Promise resolved when done. */ - treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string): Promise { - return CoreH5PHelper.saveH5P(fileUrl, file, siteId); + treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: (event: any) => any): Promise { + return CoreH5PHelper.saveH5P(fileUrl, file, siteId, onProgress); } } diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index e26d4d15b..f38610370 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -1058,7 +1058,7 @@ export class CoreFilepoolProvider { return this.wsProvider.downloadFile(fileUrl, filePath, addExtension, onProgress).then((entry) => { fileEntry = entry; - return this.pluginFileDelegate.treatDownloadedFile(fileUrl, fileEntry, siteId); + return this.pluginFileDelegate.treatDownloadedFile(fileUrl, fileEntry, siteId, onProgress); }).then(() => { const data: CoreFilepoolFileEntry = poolFileObject || {}; diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index b08989dc2..42188aea5 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -108,9 +108,10 @@ export interface CorePluginFileHandler extends CoreDelegateHandler { * @param fileUrl The file URL used to download the file. * @param file The file entry of the downloaded file. * @param siteId Site ID. If not defined, current site. + * @param onProgress Function to call on progress. * @return Promise resolved when done. */ - treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string): Promise; + treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: (event: any) => any): Promise; } /** @@ -360,13 +361,14 @@ export class CorePluginFileDelegate extends CoreDelegate { * @param fileUrl The file URL used to download the file. * @param file The file entry of the downloaded file. * @param siteId Site ID. If not defined, current site. + * @param onProgress Function to call on progress. * @return Promise resolved when done. */ - treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string): Promise { + treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: (event: any) => any): Promise { const handler = this.getHandlerForFile({fileurl: fileUrl}); if (handler && handler.treatDownloadedFile) { - return handler.treatDownloadedFile(fileUrl, file, siteId); + return handler.treatDownloadedFile(fileUrl, file, siteId, onProgress); } return Promise.resolve(); diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index 30752b8b2..8f2e6ecfc 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -872,6 +872,16 @@ export class CoreUtilsProvider { return this.uniqueArray(array1.concat(array2), key); } + /** + * Check if a value isn't null or undefined. + * + * @param value Value to check. + * @return True if not null and not undefined. + */ + notNullOrUndefined(value: any): boolean { + return typeof value != 'undefined' && value !== null; + } + /** * Open a file using platform specific method. *