From e6818524c51881c29e914fa7197ac5ede406e019 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 27 May 2020 15:08:23 +0200 Subject: [PATCH] MOBILE-3411 h5pactivity: Implement prefetch --- .../index/addon-mod-h5pactivity-index.html | 2 + .../mod/h5pactivity/components/index/index.ts | 19 +- .../mod/h5pactivity/h5pactivity.module.ts | 6 + .../mod/h5pactivity/providers/h5pactivity.ts | 46 +++-- .../h5pactivity/providers/module-handler.ts | 1 + .../h5pactivity/providers/prefetch-handler.ts | 166 ++++++++++++++++++ 6 files changed, 218 insertions(+), 22 deletions(-) create mode 100644 src/addon/mod/h5pactivity/providers/prefetch-handler.ts 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 bc54e8a4c..28725aa6b 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 @@ -5,6 +5,8 @@ + + diff --git a/src/addon/mod/h5pactivity/components/index/index.ts b/src/addon/mod/h5pactivity/components/index/index.ts index 6d28a5fda..83d6469d5 100644 --- a/src/addon/mod/h5pactivity/components/index/index.ts +++ b/src/addon/mod/h5pactivity/components/index/index.ts @@ -142,18 +142,10 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv return; } - 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); - } + this.deployedFile = await AddonModH5PActivity.instance.getDeployedFile(this.h5pActivity, { + displayOptions: this.displayOptions, + siteId: this.siteId, + }); this.fileUrl = this.deployedFile.fileurl; @@ -300,6 +292,9 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv */ play(): void { this.playing = true; + + // Mark the activity as viewed. + AddonModH5PActivity.instance.logView(this.h5pActivity.id, this.h5pActivity.name, this.siteId); } /** diff --git a/src/addon/mod/h5pactivity/h5pactivity.module.ts b/src/addon/mod/h5pactivity/h5pactivity.module.ts index ed0fb852e..771ac2ad6 100644 --- a/src/addon/mod/h5pactivity/h5pactivity.module.ts +++ b/src/addon/mod/h5pactivity/h5pactivity.module.ts @@ -16,10 +16,12 @@ import { NgModule } from '@angular/core'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; +import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { AddonModH5PActivityComponentsModule } from './components/components.module'; import { AddonModH5PActivityModuleHandler } from './providers/module-handler'; import { AddonModH5PActivityProvider } from './providers/h5pactivity'; +import { AddonModH5PActivityPrefetchHandler } from './providers/prefetch-handler'; import { AddonModH5PActivityIndexLinkHandler } from './providers/index-link-handler'; // List of providers (without handlers). @@ -36,16 +38,20 @@ export const ADDON_MOD_H5P_ACTIVITY_PROVIDERS: any[] = [ providers: [ AddonModH5PActivityProvider, AddonModH5PActivityModuleHandler, + AddonModH5PActivityPrefetchHandler, AddonModH5PActivityIndexLinkHandler, ] }) export class AddonModH5PActivityModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModH5PActivityModuleHandler, + prefetchDelegate: CoreCourseModulePrefetchDelegate, + prefetchHandler: AddonModH5PActivityPrefetchHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModH5PActivityIndexLinkHandler) { moduleDelegate.registerHandler(moduleHandler); + prefetchDelegate.registerHandler(prefetchHandler); linksDelegate.registerHandler(indexHandler); } } diff --git a/src/addon/mod/h5pactivity/providers/h5pactivity.ts b/src/addon/mod/h5pactivity/providers/h5pactivity.ts index 60a6f8abf..70d42524b 100644 --- a/src/addon/mod/h5pactivity/providers/h5pactivity.ts +++ b/src/addon/mod/h5pactivity/providers/h5pactivity.ts @@ -18,6 +18,8 @@ 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 { CoreH5P } from '@core/h5p/providers/h5p'; +import { CoreH5PDisplayOptions } from '@core/h5p/classes/core'; import { makeSingleton, Translate } from '@singletons/core.singletons'; @@ -63,6 +65,33 @@ export class AddonModH5PActivityProvider { return site.read('mod_h5pactivity_get_h5pactivity_access_information', params, preSets); } + /** + * Get deployed file from an H5P activity instance. + * + * @param h5pActivity Activity instance. + * @param options Options + * @return Promise resolved with the file. + */ + async getDeployedFile(h5pActivity: AddonModH5PActivityData, options?: AddonModH5PActivityGetDeployedFileOptions) + : Promise { + + if (h5pActivity.deployedfile) { + // File already deployed and still valid, use this one. + return h5pActivity.deployedfile; + } else { + if (!h5pActivity.package || !h5pActivity.package[0]) { + // Shouldn't happen. + throw 'No H5P package found.'; + } + + options = options || {}; + + // Deploy the file in the server. + return CoreH5P.instance.getTrustedH5PFile(h5pActivity.package[0].fileurl, options.displayOptions, + options.ignoreCache, options.siteId); + } + } + /** * Get cache key for H5P activity data WS calls. * @@ -189,12 +218,12 @@ export class AddonModH5PActivityProvider { * @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 { + logView(id: number, name?: string, siteId?: string): Promise { const params = { h5pactivityid: id, }; - const result: AddonModH5PActivityViewResult = await CoreCourseLogHelper.instance.logSingle( + return CoreCourseLogHelper.instance.logSingle( 'mod_h5pactivity_view_h5pactivity', params, AddonModH5PActivityProvider.COMPONENT, @@ -204,10 +233,6 @@ export class AddonModH5PActivityProvider { {}, siteId ); - - if (!result.status) { - throw result.warnings[0] || 'Error marking H5P activity as viewed.'; - } } } @@ -262,9 +287,10 @@ export type AddonModH5PActivityAccessInfo = { }; /** - * Result of WS mod_h5pactivity_view_h5pactivity. + * Options to pass to getDeployedFile function. */ -export type AddonModH5PActivityViewResult = { - status: boolean; // Status: true if success. - warnings?: CoreWSExternalWarning[]; +export type AddonModH5PActivityGetDeployedFileOptions = { + displayOptions?: CoreH5PDisplayOptions; // Display options + ignoreCache?: boolean; // Whether to ignore cache. Will fail if offline or server down. + siteId?: string; // Site ID. If not defined, current site. }; diff --git a/src/addon/mod/h5pactivity/providers/module-handler.ts b/src/addon/mod/h5pactivity/providers/module-handler.ts index e6ad04b83..6f544a16c 100644 --- a/src/addon/mod/h5pactivity/providers/module-handler.ts +++ b/src/addon/mod/h5pactivity/providers/module-handler.ts @@ -65,6 +65,7 @@ export class AddonModH5PActivityModuleHandler implements CoreCourseModuleHandler icon: CoreCourse.instance.getModuleIconSrc(this.modName, module.modicon), title: module.name, class: 'addon-mod_h5pactivity-handler', + showDownloadButton: true, action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { const pageParams = {module: module, courseId: courseId}; if (params) { diff --git a/src/addon/mod/h5pactivity/providers/prefetch-handler.ts b/src/addon/mod/h5pactivity/providers/prefetch-handler.ts new file mode 100644 index 000000000..ccb03a168 --- /dev/null +++ b/src/addon/mod/h5pactivity/providers/prefetch-handler.ts @@ -0,0 +1,166 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable, Injector } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '@providers/app'; +import { CoreFilepoolProvider } from '@providers/filepool'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreWSExternalFile } from '@providers/ws'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CoreH5PHelper } from '@core/h5p/classes/helper'; +import { CoreH5P } from '@core/h5p/providers/h5p'; +import { CoreUserProvider } from '@core/user/providers/user'; +import { AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData } from './h5pactivity'; + +/** + * Handler to prefetch h5p activity. + */ +@Injectable() +export class AddonModH5PActivityPrefetchHandler extends CoreCourseActivityPrefetchHandlerBase { + name = 'AddonModH5PActivity'; + modName = 'h5pactivity'; + component = AddonModH5PActivityProvider.COMPONENT; + updatesNames = /^configuration$|^.*files$|^tracks$|^usertracks$/; + + constructor(translate: TranslateService, + appProvider: CoreAppProvider, + utils: CoreUtilsProvider, + courseProvider: CoreCourseProvider, + filepoolProvider: CoreFilepoolProvider, + sitesProvider: CoreSitesProvider, + domUtils: CoreDomUtilsProvider, + filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, + protected userProvider: CoreUserProvider, + protected injector: Injector) { + + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); + } + + /** + * Get list of files. + * + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved with the list of files. + */ + async getFiles(module: any, courseId: number, single?: boolean): Promise { + + const h5pActivity = await AddonModH5PActivity.instance.getH5PActivity(courseId, module.id); + + const displayOptions = CoreH5PHelper.decodeDisplayOptions(h5pActivity.displayoptions); + + const deployedFile = await AddonModH5PActivity.instance.getDeployedFile(h5pActivity, { + displayOptions: displayOptions, + }); + + return [deployedFile].concat(this.getIntroFilesFromInstance(module, h5pActivity)); + } + + /** + * Invalidate WS calls needed to determine module status (usually, to check if module is downloadable). + * It doesn't need to invalidate check updates. It should NOT invalidate files nor all the prefetched data. + * + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when invalidated. + */ + async invalidateModule(module: any, courseId: number): Promise { + // No need to invalidate anything. + } + + /** + * Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable. + * + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Whether the module can be downloaded. The promise should never be rejected. + */ + isDownloadable(module: any, courseId: number): boolean | Promise { + return this.sitesProvider.getCurrentSite().canDownloadFiles() && !CoreH5P.instance.isOfflineDisabledInSite(); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. + */ + isEnabled(): boolean | Promise { + return AddonModH5PActivity.instance.isPluginEnabled(); + } + + /** + * Prefetch a module. + * + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when done. + */ + prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise { + return this.prefetchPackage(module, courseId, single, this.prefetchActivity.bind(this)); + } + + /** + * Prefetch an H5P activity. + * + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param siteId Site ID. + * @return Promise resolved when done. + */ + protected async prefetchActivity(module: any, courseId: number, single: boolean, siteId: string): Promise { + + const h5pActivity = await AddonModH5PActivity.instance.getH5PActivity(courseId, module.id, true, siteId); + + const introFiles = this.getIntroFilesFromInstance(module, h5pActivity); + + await Promise.all([ + AddonModH5PActivity.instance.getAccessInformation(h5pActivity.id, true, siteId), + this.filepoolProvider.addFilesToQueue(siteId, introFiles, AddonModH5PActivityProvider.COMPONENT, module.id), + this.prefetchMainFile(module, h5pActivity, siteId), + ]); + } + + /** + * Prefetch the deployed file of the activity. + * + * @param module Module. + * @param h5pActivity Activity instance. + * @param siteId Site ID. + * @return Promise resolved when done. + */ + protected async prefetchMainFile(module: any, h5pActivity: AddonModH5PActivityData, siteId: string): Promise { + + const displayOptions = CoreH5PHelper.decodeDisplayOptions(h5pActivity.displayoptions); + + const deployedFile = await AddonModH5PActivity.instance.getDeployedFile(h5pActivity, { + displayOptions: displayOptions, + ignoreCache: true, + siteId: siteId, + }); + + await this.filepoolProvider.addFilesToQueue(siteId, [deployedFile], AddonModH5PActivityProvider.COMPONENT, module.id); + } +}