Skip to content

Commit e681852

Browse files
committed
MOBILE-3411 h5pactivity: Implement prefetch
1 parent a8e336f commit e681852

File tree

6 files changed

+218
-22
lines changed

6 files changed

+218
-22
lines changed

src/addon/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></core-context-menu-item>
66
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'fa-newspaper-o'" (action)="gotoBlog($event)"></core-context-menu-item>
77
<core-context-menu-item *ngIf="loaded && isOnline" [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
8+
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
9+
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.removefiles' | translate:{$a: size}" [iconDescription]="'cube'" (action)="removeFiles($event)" [iconAction]="'trash'" [closeOnClick]="false"></core-context-menu-item>
810
</core-context-menu>
911
</core-navbar-buttons>
1012

src/addon/mod/h5pactivity/components/index/index.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -142,18 +142,10 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
142142
return;
143143
}
144144

145-
if (this.h5pActivity.deployedfile) {
146-
// File already deployed and still valid, use this one.
147-
this.deployedFile = this.h5pActivity.deployedfile;
148-
} else {
149-
if (!this.h5pActivity.package || !this.h5pActivity.package[0]) {
150-
// Shouldn't happen.
151-
throw 'No H5P package found.';
152-
}
153-
154-
// Deploy the file in the server.
155-
this.deployedFile = await CoreH5P.instance.getTrustedH5PFile(this.h5pActivity.package[0].fileurl, this.displayOptions);
156-
}
145+
this.deployedFile = await AddonModH5PActivity.instance.getDeployedFile(this.h5pActivity, {
146+
displayOptions: this.displayOptions,
147+
siteId: this.siteId,
148+
});
157149

158150
this.fileUrl = this.deployedFile.fileurl;
159151

@@ -300,6 +292,9 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
300292
*/
301293
play(): void {
302294
this.playing = true;
295+
296+
// Mark the activity as viewed.
297+
AddonModH5PActivity.instance.logView(this.h5pActivity.id, this.h5pActivity.name, this.siteId);
303298
}
304299

305300
/**

src/addon/mod/h5pactivity/h5pactivity.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import { NgModule } from '@angular/core';
1616

1717
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
1818
import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
19+
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
1920

2021
import { AddonModH5PActivityComponentsModule } from './components/components.module';
2122
import { AddonModH5PActivityModuleHandler } from './providers/module-handler';
2223
import { AddonModH5PActivityProvider } from './providers/h5pactivity';
24+
import { AddonModH5PActivityPrefetchHandler } from './providers/prefetch-handler';
2325
import { AddonModH5PActivityIndexLinkHandler } from './providers/index-link-handler';
2426

2527
// List of providers (without handlers).
@@ -36,16 +38,20 @@ export const ADDON_MOD_H5P_ACTIVITY_PROVIDERS: any[] = [
3638
providers: [
3739
AddonModH5PActivityProvider,
3840
AddonModH5PActivityModuleHandler,
41+
AddonModH5PActivityPrefetchHandler,
3942
AddonModH5PActivityIndexLinkHandler,
4043
]
4144
})
4245
export class AddonModH5PActivityModule {
4346
constructor(moduleDelegate: CoreCourseModuleDelegate,
4447
moduleHandler: AddonModH5PActivityModuleHandler,
48+
prefetchDelegate: CoreCourseModulePrefetchDelegate,
49+
prefetchHandler: AddonModH5PActivityPrefetchHandler,
4550
linksDelegate: CoreContentLinksDelegate,
4651
indexHandler: AddonModH5PActivityIndexLinkHandler) {
4752

4853
moduleDelegate.registerHandler(moduleHandler);
54+
prefetchDelegate.registerHandler(prefetchHandler);
4955
linksDelegate.registerHandler(indexHandler);
5056
}
5157
}

src/addon/mod/h5pactivity/providers/h5pactivity.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { CoreSites } from '@providers/sites';
1818
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
1919
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
2020
import { CoreCourseLogHelper } from '@core/course/providers/log-helper';
21+
import { CoreH5P } from '@core/h5p/providers/h5p';
22+
import { CoreH5PDisplayOptions } from '@core/h5p/classes/core';
2123

2224
import { makeSingleton, Translate } from '@singletons/core.singletons';
2325

@@ -63,6 +65,33 @@ export class AddonModH5PActivityProvider {
6365
return site.read('mod_h5pactivity_get_h5pactivity_access_information', params, preSets);
6466
}
6567

68+
/**
69+
* Get deployed file from an H5P activity instance.
70+
*
71+
* @param h5pActivity Activity instance.
72+
* @param options Options
73+
* @return Promise resolved with the file.
74+
*/
75+
async getDeployedFile(h5pActivity: AddonModH5PActivityData, options?: AddonModH5PActivityGetDeployedFileOptions)
76+
: Promise<CoreWSExternalFile> {
77+
78+
if (h5pActivity.deployedfile) {
79+
// File already deployed and still valid, use this one.
80+
return h5pActivity.deployedfile;
81+
} else {
82+
if (!h5pActivity.package || !h5pActivity.package[0]) {
83+
// Shouldn't happen.
84+
throw 'No H5P package found.';
85+
}
86+
87+
options = options || {};
88+
89+
// Deploy the file in the server.
90+
return CoreH5P.instance.getTrustedH5PFile(h5pActivity.package[0].fileurl, options.displayOptions,
91+
options.ignoreCache, options.siteId);
92+
}
93+
}
94+
6695
/**
6796
* Get cache key for H5P activity data WS calls.
6897
*
@@ -189,12 +218,12 @@ export class AddonModH5PActivityProvider {
189218
* @param siteId Site ID. If not defined, current site.
190219
* @return Promise resolved when the WS call is successful.
191220
*/
192-
async logView(id: number, name?: string, siteId?: string): Promise<void> {
221+
logView(id: number, name?: string, siteId?: string): Promise<void> {
193222
const params = {
194223
h5pactivityid: id,
195224
};
196225

197-
const result: AddonModH5PActivityViewResult = await CoreCourseLogHelper.instance.logSingle(
226+
return CoreCourseLogHelper.instance.logSingle(
198227
'mod_h5pactivity_view_h5pactivity',
199228
params,
200229
AddonModH5PActivityProvider.COMPONENT,
@@ -204,10 +233,6 @@ export class AddonModH5PActivityProvider {
204233
{},
205234
siteId
206235
);
207-
208-
if (!result.status) {
209-
throw result.warnings[0] || 'Error marking H5P activity as viewed.';
210-
}
211236
}
212237
}
213238

@@ -262,9 +287,10 @@ export type AddonModH5PActivityAccessInfo = {
262287
};
263288

264289
/**
265-
* Result of WS mod_h5pactivity_view_h5pactivity.
290+
* Options to pass to getDeployedFile function.
266291
*/
267-
export type AddonModH5PActivityViewResult = {
268-
status: boolean; // Status: true if success.
269-
warnings?: CoreWSExternalWarning[];
292+
export type AddonModH5PActivityGetDeployedFileOptions = {
293+
displayOptions?: CoreH5PDisplayOptions; // Display options
294+
ignoreCache?: boolean; // Whether to ignore cache. Will fail if offline or server down.
295+
siteId?: string; // Site ID. If not defined, current site.
270296
};

src/addon/mod/h5pactivity/providers/module-handler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export class AddonModH5PActivityModuleHandler implements CoreCourseModuleHandler
6565
icon: CoreCourse.instance.getModuleIconSrc(this.modName, module.modicon),
6666
title: module.name,
6767
class: 'addon-mod_h5pactivity-handler',
68+
showDownloadButton: true,
6869
action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void {
6970
const pageParams = {module: module, courseId: courseId};
7071
if (params) {
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Injectable, Injector } from '@angular/core';
16+
import { TranslateService } from '@ngx-translate/core';
17+
import { CoreAppProvider } from '@providers/app';
18+
import { CoreFilepoolProvider } from '@providers/filepool';
19+
import { CorePluginFileDelegate } from '@providers/plugin-file-delegate';
20+
import { CoreSitesProvider } from '@providers/sites';
21+
import { CoreWSExternalFile } from '@providers/ws';
22+
import { CoreDomUtilsProvider } from '@providers/utils/dom';
23+
import { CoreUtilsProvider } from '@providers/utils/utils';
24+
import { CoreCourseProvider } from '@core/course/providers/course';
25+
import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler';
26+
import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
27+
import { CoreH5PHelper } from '@core/h5p/classes/helper';
28+
import { CoreH5P } from '@core/h5p/providers/h5p';
29+
import { CoreUserProvider } from '@core/user/providers/user';
30+
import { AddonModH5PActivity, AddonModH5PActivityProvider, AddonModH5PActivityData } from './h5pactivity';
31+
32+
/**
33+
* Handler to prefetch h5p activity.
34+
*/
35+
@Injectable()
36+
export class AddonModH5PActivityPrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {
37+
name = 'AddonModH5PActivity';
38+
modName = 'h5pactivity';
39+
component = AddonModH5PActivityProvider.COMPONENT;
40+
updatesNames = /^configuration$|^.*files$|^tracks$|^usertracks$/;
41+
42+
constructor(translate: TranslateService,
43+
appProvider: CoreAppProvider,
44+
utils: CoreUtilsProvider,
45+
courseProvider: CoreCourseProvider,
46+
filepoolProvider: CoreFilepoolProvider,
47+
sitesProvider: CoreSitesProvider,
48+
domUtils: CoreDomUtilsProvider,
49+
filterHelper: CoreFilterHelperProvider,
50+
pluginFileDelegate: CorePluginFileDelegate,
51+
protected userProvider: CoreUserProvider,
52+
protected injector: Injector) {
53+
54+
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper,
55+
pluginFileDelegate);
56+
}
57+
58+
/**
59+
* Get list of files.
60+
*
61+
* @param module Module.
62+
* @param courseId Course ID the module belongs to.
63+
* @param single True if we're downloading a single module, false if we're downloading a whole section.
64+
* @return Promise resolved with the list of files.
65+
*/
66+
async getFiles(module: any, courseId: number, single?: boolean): Promise<CoreWSExternalFile[]> {
67+
68+
const h5pActivity = await AddonModH5PActivity.instance.getH5PActivity(courseId, module.id);
69+
70+
const displayOptions = CoreH5PHelper.decodeDisplayOptions(h5pActivity.displayoptions);
71+
72+
const deployedFile = await AddonModH5PActivity.instance.getDeployedFile(h5pActivity, {
73+
displayOptions: displayOptions,
74+
});
75+
76+
return [deployedFile].concat(this.getIntroFilesFromInstance(module, h5pActivity));
77+
}
78+
79+
/**
80+
* Invalidate WS calls needed to determine module status (usually, to check if module is downloadable).
81+
* It doesn't need to invalidate check updates. It should NOT invalidate files nor all the prefetched data.
82+
*
83+
* @param module Module.
84+
* @param courseId Course ID the module belongs to.
85+
* @return Promise resolved when invalidated.
86+
*/
87+
async invalidateModule(module: any, courseId: number): Promise<void> {
88+
// No need to invalidate anything.
89+
}
90+
91+
/**
92+
* Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable.
93+
*
94+
* @param module Module.
95+
* @param courseId Course ID the module belongs to.
96+
* @return Whether the module can be downloaded. The promise should never be rejected.
97+
*/
98+
isDownloadable(module: any, courseId: number): boolean | Promise<boolean> {
99+
return this.sitesProvider.getCurrentSite().canDownloadFiles() && !CoreH5P.instance.isOfflineDisabledInSite();
100+
}
101+
102+
/**
103+
* Whether or not the handler is enabled on a site level.
104+
*
105+
* @return A boolean, or a promise resolved with a boolean, indicating if the handler is enabled.
106+
*/
107+
isEnabled(): boolean | Promise<boolean> {
108+
return AddonModH5PActivity.instance.isPluginEnabled();
109+
}
110+
111+
/**
112+
* Prefetch a module.
113+
*
114+
* @param module Module.
115+
* @param courseId Course ID the module belongs to.
116+
* @param single True if we're downloading a single module, false if we're downloading a whole section.
117+
* @param dirPath Path of the directory where to store all the content files.
118+
* @return Promise resolved when done.
119+
*/
120+
prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise<any> {
121+
return this.prefetchPackage(module, courseId, single, this.prefetchActivity.bind(this));
122+
}
123+
124+
/**
125+
* Prefetch an H5P activity.
126+
*
127+
* @param module Module.
128+
* @param courseId Course ID the module belongs to.
129+
* @param single True if we're downloading a single module, false if we're downloading a whole section.
130+
* @param siteId Site ID.
131+
* @return Promise resolved when done.
132+
*/
133+
protected async prefetchActivity(module: any, courseId: number, single: boolean, siteId: string): Promise<void> {
134+
135+
const h5pActivity = await AddonModH5PActivity.instance.getH5PActivity(courseId, module.id, true, siteId);
136+
137+
const introFiles = this.getIntroFilesFromInstance(module, h5pActivity);
138+
139+
await Promise.all([
140+
AddonModH5PActivity.instance.getAccessInformation(h5pActivity.id, true, siteId),
141+
this.filepoolProvider.addFilesToQueue(siteId, introFiles, AddonModH5PActivityProvider.COMPONENT, module.id),
142+
this.prefetchMainFile(module, h5pActivity, siteId),
143+
]);
144+
}
145+
146+
/**
147+
* Prefetch the deployed file of the activity.
148+
*
149+
* @param module Module.
150+
* @param h5pActivity Activity instance.
151+
* @param siteId Site ID.
152+
* @return Promise resolved when done.
153+
*/
154+
protected async prefetchMainFile(module: any, h5pActivity: AddonModH5PActivityData, siteId: string): Promise<void> {
155+
156+
const displayOptions = CoreH5PHelper.decodeDisplayOptions(h5pActivity.displayoptions);
157+
158+
const deployedFile = await AddonModH5PActivity.instance.getDeployedFile(h5pActivity, {
159+
displayOptions: displayOptions,
160+
ignoreCache: true,
161+
siteId: siteId,
162+
});
163+
164+
await this.filepoolProvider.addFilesToQueue(siteId, [deployedFile], AddonModH5PActivityProvider.COMPONENT, module.id);
165+
}
166+
}

0 commit comments

Comments
 (0)