Merge pull request #1488 from dpalou/MOBILE-2563

MOBILE-2563 course: Decrease calls to core_course_get_contents
main
Juan Leyva 2018-08-27 11:13:06 +01:00 committed by GitHub
commit d813d7bcd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 129 additions and 74 deletions

View File

@ -66,7 +66,7 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler {
};
const handlerData: CoreCourseModuleHandlerData = {
icon: this.courseProvider.getModuleIconSrc('resource'),
icon: this.courseProvider.getModuleIconSrc(this.modName),
title: module.name,
class: 'addon-mod_resource-handler',
showDownloadButton: true,
@ -88,16 +88,12 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler {
} ]
};
this.getResourceData(module, courseId).then((data) => {
this.getResourceData(module, courseId, handlerData).then((data) => {
handlerData.icon = data.icon;
handlerData.extraBadge = data.extra;
handlerData.extraBadgeColor = 'light';
});
this.hideOpenButton(module, courseId).then((hideOpenButton) => {
handlerData.buttons[0].hidden = hideOpenButton;
});
return handlerData;
}
@ -109,7 +105,8 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler {
* @return {Promise<boolean>} Resolved when done.
*/
protected hideOpenButton(module: any, courseId: number): Promise<boolean> {
return this.courseProvider.loadModuleContents(module, courseId).then(() => {
return this.courseProvider.loadModuleContents(module, courseId, undefined, false, false, undefined, this.modName)
.then(() => {
return this.prefetchDelegate.getModuleStatus(module, courseId).then((status) => {
return status !== CoreConstants.DOWNLOADED || this.resourceHelper.isDisplayedInIframe(module);
});
@ -123,55 +120,55 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler {
* @param {number} courseId The course ID.
* @return {Promise<any>} Resource data.
*/
protected getResourceData(module: any, courseId: number): Promise<any> {
return this.resourceProvider.getResourceData(courseId, module.id).then((info) => {
let promise;
protected getResourceData(module: any, courseId: number, handlerData: CoreCourseModuleHandlerData): Promise<any> {
const promises = [];
let resourceInfo;
if (info.contentfiles && info.contentfiles.length == 1) {
promise = Promise.resolve(info.contentfiles);
} else {
promise = this.courseProvider.loadModuleContents(module, courseId).then(() => {
if (module.contents.length) {
return module.contents;
}
});
// Check if the button needs to be shown or not. This also loads the module contents.
promises.push(this.hideOpenButton(module, courseId).then((hideOpenButton) => {
handlerData.buttons[0].hidden = hideOpenButton;
}));
// Get the resource data.
promises.push(this.resourceProvider.getResourceData(courseId, module.id).then((info) => {
resourceInfo = info;
}));
return Promise.all(promises).then(() => {
const files = module.contents && module.contents.length ? module.contents : resourceInfo.contentfiles,
resourceData = {
icon: '',
extra: ''
},
options = this.textUtils.unserialize(resourceInfo.displayoptions),
extra = [];
if (files && files.length) {
const file = files[0];
resourceData.icon = this.mimetypeUtils.getFileIcon(file.filename);
if (options.showsize) {
const size = files.reduce((result, file) => {
return result + file.filesize;
}, 0);
extra.push(this.textUtils.bytesToSize(size, 1));
}
if (options.showtype) {
extra.push(this.mimetypeUtils.getMimetypeDescription(file));
}
}
return promise.then((files) => {
const resourceData = {
icon: '',
extra: ''
},
options = this.textUtils.unserialize(info.displayoptions),
extra = [];
if (resourceData.icon == '') {
resourceData.icon = this.courseProvider.getModuleIconSrc(this.modName);
}
if (files && files.length) {
const file = files[0];
resourceData.icon = this.mimetypeUtils.getFileIcon(file.filename);
if (options.showdate) {
extra.push(this.translate.instant('addon.mod_resource.uploadeddate',
{$a: moment(resourceInfo.timemodified * 1000).format('LLL')}));
}
resourceData.extra += extra.join(' ');
if (options.showsize) {
const size = files.reduce((result, file) => {
return result + file.filesize;
}, 0);
extra.push(this.textUtils.bytesToSize(size, 1));
}
if (options.showtype) {
extra.push(this.mimetypeUtils.getMimetypeDescription(file));
}
}
if (resourceData.icon == '') {
resourceData.icon = this.courseProvider.getModuleIconSrc('resource');
}
if (options.showdate) {
extra.push(this.translate.instant('addon.mod_resource.uploadeddate',
{$a: moment(info.timemodified * 1000).format('LLL')}));
}
resourceData.extra += extra.join(' ');
return resourceData;
});
return resourceData;
});
}

View File

@ -70,6 +70,11 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH
promises.push(this.resourceProvider.getResourceData(courseId, module.id));
}
/* When prefetching we usually use ignoreCache=true. However, this WS call can return a lot of data, so if
a user downloads resources 1 by 1 we would be downloading the same data over and over again. Since
this data won't change often it's probably better to use ignoreCache=false. */
promises.push(this.courseProvider.getModule(module.id, courseId, undefined, false, false, undefined, this.modName));
return Promise.all(promises);
});
}
@ -96,7 +101,7 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH
const promises = [];
promises.push(this.resourceProvider.invalidateResourceData(courseId));
promises.push(this.courseProvider.invalidateModule(module.id));
promises.push(this.courseProvider.invalidateModule(module.id, undefined, this.modName));
return Promise.all(promises);
}

View File

@ -104,7 +104,7 @@ export class AddonModResourceProvider {
promises.push(this.invalidateResourceData(courseId, siteId));
promises.push(this.filepoolProvider.invalidateFilesByComponent(siteId, AddonModResourceProvider.COMPONENT, moduleId));
promises.push(this.courseProvider.invalidateModule(moduleId, siteId));
promises.push(this.courseProvider.invalidateModule(moduleId, siteId, 'resource'));
return this.utils.allPromises(promises);
}

View File

@ -78,7 +78,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
canGetUrl = false;
// Fallback in case is not prefetched or not available.
return this.courseProvider.getModule(this.module.id, this.courseId);
return this.courseProvider.getModule(this.module.id, this.courseId, undefined, false, false, undefined, 'url');
}).then((url) => {
this.description = url.intro || url.description;
this.dataRetrieved.emit(url);
@ -95,7 +95,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
if (!mod.contents || !mod.contents.length) {
// Try to load module contents, it's needed to get the URL with parameters.
return this.courseProvider.loadModuleContents(mod, this.courseId);
return this.courseProvider.loadModuleContents(mod, this.courseId, undefined, false, false, undefined, 'url');
}
}
}).then(() => {

View File

@ -22,6 +22,7 @@ import { CoreCourseHelperProvider } from '@core/course/providers/helper';
@Injectable()
export class AddonModUrlLinkHandler extends CoreContentLinksModuleIndexHandler {
name = 'AddonModUrlLinkHandler';
useModNameToGetModule = true;
constructor(courseHelper: CoreCourseHelperProvider) {
super(courseHelper, 'AddonModUrl', 'url');

View File

@ -50,7 +50,7 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler {
*/
getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData {
const handlerData = {
icon: this.courseProvider.getModuleIconSrc('url'),
icon: this.courseProvider.getModuleIconSrc(this.modName),
title: module.name,
class: 'addon-mod_url-handler',
showDownloadButton: false,
@ -89,7 +89,8 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler {
* @return {Promise<boolean>} Resolved when done.
*/
protected hideLinkButton(module: any, courseId: number): Promise<boolean> {
return this.courseProvider.loadModuleContents(module, courseId).then(() => {
return this.courseProvider.loadModuleContents(module, courseId, undefined, false, false, undefined, this.modName)
.then(() => {
return !(module.contents && module.contents[0] && module.contents[0].fileurl);
});
}

View File

@ -102,7 +102,7 @@ export class AddonModUrlProvider {
const promises = [];
promises.push(this.invalidateUrlData(courseId, siteId));
promises.push(this.courseProvider.invalidateModule(moduleId, siteId));
promises.push(this.courseProvider.invalidateModule(moduleId, siteId, 'url'));
return this.utils.allPromises(promises);
}

View File

@ -30,6 +30,13 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB
*/
canReview: boolean;
/**
* If this boolean is set to true, the app will retrieve all modules with this modName with a single WS call.
* This reduces the number of WS calls, but it isn't recommended for modules that can return a lot of contents.
* @type {boolean}
*/
protected useModNameToGetModule = false;
/**
* Construct the handler.
*
@ -69,7 +76,8 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB
this.sitesProvider.getSite(siteId).then((site) => {
if (!params.userid || params.userid == site.getUserId()) {
// No user specified or current user. Navigate to module.
this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId);
this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId, undefined,
this.useModNameToGetModule ? this.modName : undefined);
} else if (this.canReview) {
// Use the goToReview function.
this.goToReview(url, params, courseId, siteId, navCtrl);

View File

@ -21,6 +21,13 @@ import { CoreCourseHelperProvider } from '@core/course/providers/helper';
*/
export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerBase {
/**
* If this boolean is set to true, the app will retrieve all modules with this modName with a single WS call.
* This reduces the number of WS calls, but it isn't recommended for modules that can return a lot of contents.
* @type {boolean}
*/
protected useModNameToGetModule = false;
/**
* Construct the handler.
*
@ -52,7 +59,8 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
return [{
action: (siteId, navCtrl?): void => {
this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId);
this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId, undefined,
this.useModNameToGetModule ? this.modName : undefined);
}
}];
}

View File

@ -198,10 +198,12 @@ export class CoreCourseProvider {
* @param {boolean} [preferCache] True if shouldn't call WS if data is cached, false otherwise.
* @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.
* @param {string} [modName] If set, the app will retrieve all modules of this type with a single WS call. This reduces the
* number of WS calls, but it isn't recommended for modules that can return a lot of contents.
* @return {Promise<any>} Promise resolved with the module.
*/
getModule(moduleId: number, courseId?: number, sectionId?: number, preferCache?: boolean, ignoreCache?: boolean,
siteId?: string): Promise<any> {
siteId?: string, modName?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
let promise;
@ -226,18 +228,27 @@ export class CoreCourseProvider {
const params = {
courseid: courseId,
options: [
{
name: 'cmid',
value: moduleId
}
]
options: []
},
preSets: any = {
cacheKey: this.getModuleCacheKey(moduleId),
omitExpires: preferCache
};
// If modName is set, retrieve all modules of that type. Otherwise get only the module.
if (modName) {
params.options.push({
name: 'modname',
value: modName
});
preSets.cacheKey = this.getModuleByModNameCacheKey(modName);
} else {
params.options.push({
name: 'cmid',
value: moduleId
});
preSets.cacheKey = this.getModuleCacheKey(moduleId);
}
if (!preferCache && ignoreCache) {
preSets.getFromCache = 0;
preSets.emergencyCache = 0;
@ -376,6 +387,16 @@ export class CoreCourseProvider {
return this.ROOT_CACHE_KEY + 'module:' + moduleId;
}
/**
* Get cache key for module by modname WS calls.
*
* @param {string} modName Name of the module.
* @return {string} Cache key.
*/
protected getModuleByModNameCacheKey(modName: string): string {
return this.ROOT_CACHE_KEY + 'module:modName:' + modName;
}
/**
* Returns the source to a module icon.
*
@ -518,11 +539,20 @@ export class CoreCourseProvider {
*
* @param {number} moduleId Module ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {string} [modName] Module name. E.g. 'label', 'url', ...
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateModule(moduleId: number, siteId?: string): Promise<any> {
invalidateModule(moduleId: number, siteId?: string, modName?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKey(this.getModuleCacheKey(moduleId));
const promises = [];
if (modName) {
promises.push(site.invalidateWsCacheForKey(this.getModuleByModNameCacheKey(modName)));
}
promises.push(site.invalidateWsCacheForKey(this.getModuleCacheKey(moduleId)));
return Promise.all(promises);
});
}
@ -574,16 +604,19 @@ export class CoreCourseProvider {
* @param {boolean} [preferCache] True if shouldn't call WS if data is cached, false otherwise.
* @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.
* @param {string} [modName] If set, the app will retrieve all modules of this type with a single WS call. This reduces the
* number of WS calls, but it isn't recommended for modules that can return a lot of contents.
* @return {Promise<void>} Promise resolved when loaded.
*/
loadModuleContents(module: any, courseId?: number, sectionId?: number, preferCache?: boolean, ignoreCache?: boolean,
siteId?: string): Promise<void> {
siteId?: string, modName?: string): Promise<void> {
if (!ignoreCache && module.contents && module.contents.length) {
// Already loaded.
return Promise.resolve();
}
return this.getModule(module.id, courseId, sectionId, preferCache, ignoreCache, siteId).then((mod) => {
return this.getModule(module.id, courseId, sectionId, preferCache, ignoreCache, siteId, modName).then((mod) => {
module.contents = mod.contents;
});
}

View File

@ -887,9 +887,11 @@ export class CoreCourseHelperProvider {
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {number} [courseId] Course ID. If not defined we'll try to retrieve it from the site.
* @param {number} [sectionId] Section the module belongs to. If not defined we'll try to retrieve it from the site.
* @param {string} [modName] If set, the app will retrieve all modules of this type with a single WS call. This reduces the
* number of WS calls, but it isn't recommended for modules that can return a lot of contents.
* @return {Promise<void>} Promise resolved when done.
*/
navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number): Promise<void> {
navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number, modName?: string): Promise<void> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const modal = this.domUtils.showModalLoading();
@ -923,7 +925,7 @@ export class CoreCourseHelperProvider {
site = s;
// Get the module.
return this.courseProvider.getModule(moduleId, courseId, sectionId, false, false, siteId);
return this.courseProvider.getModule(moduleId, courseId, sectionId, false, false, siteId, modName);
}).then((module) => {
const params = {
course: { id: courseId },