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 = { const handlerData: CoreCourseModuleHandlerData = {
icon: this.courseProvider.getModuleIconSrc('resource'), icon: this.courseProvider.getModuleIconSrc(this.modName),
title: module.name, title: module.name,
class: 'addon-mod_resource-handler', class: 'addon-mod_resource-handler',
showDownloadButton: true, 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.icon = data.icon;
handlerData.extraBadge = data.extra; handlerData.extraBadge = data.extra;
handlerData.extraBadgeColor = 'light'; handlerData.extraBadgeColor = 'light';
}); });
this.hideOpenButton(module, courseId).then((hideOpenButton) => {
handlerData.buttons[0].hidden = hideOpenButton;
});
return handlerData; return handlerData;
} }
@ -109,7 +105,8 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler {
* @return {Promise<boolean>} Resolved when done. * @return {Promise<boolean>} Resolved when done.
*/ */
protected hideOpenButton(module: any, courseId: number): Promise<boolean> { 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 this.prefetchDelegate.getModuleStatus(module, courseId).then((status) => {
return status !== CoreConstants.DOWNLOADED || this.resourceHelper.isDisplayedInIframe(module); return status !== CoreConstants.DOWNLOADED || this.resourceHelper.isDisplayedInIframe(module);
}); });
@ -123,26 +120,27 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler {
* @param {number} courseId The course ID. * @param {number} courseId The course ID.
* @return {Promise<any>} Resource data. * @return {Promise<any>} Resource data.
*/ */
protected getResourceData(module: any, courseId: number): Promise<any> { protected getResourceData(module: any, courseId: number, handlerData: CoreCourseModuleHandlerData): Promise<any> {
return this.resourceProvider.getResourceData(courseId, module.id).then((info) => { const promises = [];
let promise; let resourceInfo;
if (info.contentfiles && info.contentfiles.length == 1) { // Check if the button needs to be shown or not. This also loads the module contents.
promise = Promise.resolve(info.contentfiles); promises.push(this.hideOpenButton(module, courseId).then((hideOpenButton) => {
} else { handlerData.buttons[0].hidden = hideOpenButton;
promise = this.courseProvider.loadModuleContents(module, courseId).then(() => { }));
if (module.contents.length) {
return module.contents;
}
});
}
return promise.then((files) => { // Get the resource data.
const resourceData = { 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: '', icon: '',
extra: '' extra: ''
}, },
options = this.textUtils.unserialize(info.displayoptions), options = this.textUtils.unserialize(resourceInfo.displayoptions),
extra = []; extra = [];
if (files && files.length) { if (files && files.length) {
@ -161,18 +159,17 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler {
} }
if (resourceData.icon == '') { if (resourceData.icon == '') {
resourceData.icon = this.courseProvider.getModuleIconSrc('resource'); resourceData.icon = this.courseProvider.getModuleIconSrc(this.modName);
} }
if (options.showdate) { if (options.showdate) {
extra.push(this.translate.instant('addon.mod_resource.uploadeddate', extra.push(this.translate.instant('addon.mod_resource.uploadeddate',
{$a: moment(info.timemodified * 1000).format('LLL')})); {$a: moment(resourceInfo.timemodified * 1000).format('LLL')}));
} }
resourceData.extra += extra.join(' '); 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)); 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); return Promise.all(promises);
}); });
} }
@ -96,7 +101,7 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH
const promises = []; const promises = [];
promises.push(this.resourceProvider.invalidateResourceData(courseId)); 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); return Promise.all(promises);
} }

View File

@ -104,7 +104,7 @@ export class AddonModResourceProvider {
promises.push(this.invalidateResourceData(courseId, siteId)); promises.push(this.invalidateResourceData(courseId, siteId));
promises.push(this.filepoolProvider.invalidateFilesByComponent(siteId, AddonModResourceProvider.COMPONENT, moduleId)); 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); return this.utils.allPromises(promises);
} }

View File

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

View File

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

View File

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

View File

@ -102,7 +102,7 @@ export class AddonModUrlProvider {
const promises = []; const promises = [];
promises.push(this.invalidateUrlData(courseId, siteId)); 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); return this.utils.allPromises(promises);
} }

View File

@ -30,6 +30,13 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB
*/ */
canReview: boolean; 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. * Construct the handler.
* *
@ -69,7 +76,8 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB
this.sitesProvider.getSite(siteId).then((site) => { this.sitesProvider.getSite(siteId).then((site) => {
if (!params.userid || params.userid == site.getUserId()) { if (!params.userid || params.userid == site.getUserId()) {
// No user specified or current user. Navigate to module. // 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) { } else if (this.canReview) {
// Use the goToReview function. // Use the goToReview function.
this.goToReview(url, params, courseId, siteId, navCtrl); 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 { 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. * Construct the handler.
* *
@ -52,7 +59,8 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB
return [{ return [{
action: (siteId, navCtrl?): void => { 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} [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 {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} [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. * @return {Promise<any>} Promise resolved with the module.
*/ */
getModule(moduleId: number, courseId?: number, sectionId?: number, preferCache?: boolean, ignoreCache?: boolean, 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(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
let promise; let promise;
@ -226,18 +228,27 @@ export class CoreCourseProvider {
const params = { const params = {
courseid: courseId, courseid: courseId,
options: [ options: []
{
name: 'cmid',
value: moduleId
}
]
}, },
preSets: any = { preSets: any = {
cacheKey: this.getModuleCacheKey(moduleId),
omitExpires: preferCache 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) { if (!preferCache && ignoreCache) {
preSets.getFromCache = 0; preSets.getFromCache = 0;
preSets.emergencyCache = 0; preSets.emergencyCache = 0;
@ -376,6 +387,16 @@ export class CoreCourseProvider {
return this.ROOT_CACHE_KEY + 'module:' + moduleId; 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. * Returns the source to a module icon.
* *
@ -518,11 +539,20 @@ export class CoreCourseProvider {
* *
* @param {number} moduleId Module ID. * @param {number} moduleId Module ID.
* @param {string} [siteId] Site ID. If not defined, current site. * @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. * @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 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} [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 {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} [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. * @return {Promise<void>} Promise resolved when loaded.
*/ */
loadModuleContents(module: any, courseId?: number, sectionId?: number, preferCache?: boolean, ignoreCache?: boolean, 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) { if (!ignoreCache && module.contents && module.contents.length) {
// Already loaded. // Already loaded.
return Promise.resolve(); 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; module.contents = mod.contents;
}); });
} }

View File

@ -887,9 +887,11 @@ export class CoreCourseHelperProvider {
* @param {string} [siteId] Site ID. If not defined, current site. * @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} [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 {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. * @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(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
const modal = this.domUtils.showModalLoading(); const modal = this.domUtils.showModalLoading();
@ -923,7 +925,7 @@ export class CoreCourseHelperProvider {
site = s; site = s;
// Get the module. // 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) => { }).then((module) => {
const params = { const params = {
course: { id: courseId }, course: { id: courseId },