diff --git a/src/addon/mod/forum/components/index/index.ts b/src/addon/mod/forum/components/index/index.ts index 6795fccac..34ea7ba6c 100644 --- a/src/addon/mod/forum/components/index/index.ts +++ b/src/addon/mod/forum/components/index/index.ts @@ -354,7 +354,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.page = 0; } - return this.forumProvider.getDiscussions(this.forum.id, this.selectedSortOrder.value, this.page).then((response) => { + return this.forumProvider.getDiscussions(this.forum.id, this.forum.cmid, + this.selectedSortOrder.value, this.page).then((response) => { let promise; if (this.usesGroups) { promise = this.forumProvider.formatDiscussionsGroups(this.forum.cmid, response.discussions); diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 7ef3aa4de..2bfd3f449 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -323,7 +323,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { let ratingInfo; return syncPromise.then(() => { - return this.forumProvider.getDiscussionPosts(this.discussionId).then((response) => { + return this.forumProvider.getDiscussionPosts(this.discussionId, this.cmId).then((response) => { onlinePosts = response.posts; ratingInfo = response.ratinginfo; }).then(() => { @@ -412,7 +412,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { // The discussion object was not passed as parameter and there is no starting post. Should not happen. if (!this.discussion) { - promises.push(this.loadDiscussion(this.forumId, this.discussionId)); + promises.push(this.loadDiscussion(this.forumId, this.cmId, this.discussionId)); } return Promise.all(promises); @@ -479,13 +479,14 @@ export class AddonModForumDiscussionPage implements OnDestroy { * Convenience function to load discussion. * * @param forumId Forum ID. + * @param cmId Forum cmid. * @param discussionId Discussion ID. * @return Promise resolved when done. */ - protected loadDiscussion(forumId: number, discussionId: number): Promise { + protected loadDiscussion(forumId: number, cmId: number, discussionId: number): Promise { // Fetch the discussion if not passed as parameter. if (!this.discussion && forumId) { - return this.forumHelper.getDiscussionById(forumId, discussionId).then((discussion) => { + return this.forumHelper.getDiscussionById(forumId, cmId, discussionId).then((discussion) => { this.discussion = discussion; this.discussionId = this.discussion.discussion; }).catch(() => { diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index 2c9d03cdb..fa6cebdfc 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -492,15 +492,18 @@ export class AddonModForumProvider { * Get forum discussion posts. * * @param discussionId Discussion ID. + * @param cmId Forum cmid. * @param siteId Site ID. If not defined, current site. * @return Promise resolved with forum posts and rating info. */ - getDiscussionPosts(discussionId: number, siteId?: string): Promise<{posts: any[], ratinginfo?: CoreRatingInfo}> { + getDiscussionPosts(discussionId: number, cmId: number, siteId?: string): Promise<{posts: any[], ratinginfo?: CoreRatingInfo}> { const params = { discussionid: discussionId }; const preSets = { - cacheKey: this.getDiscussionPostsCacheKey(discussionId) + cacheKey: this.getDiscussionPostsCacheKey(discussionId), + component: AddonModForumProvider.COMPONENT, + componentId: cmId }; return this.sitesProvider.getSite(siteId).then((site) => { @@ -592,6 +595,7 @@ export class AddonModForumProvider { * Get forum discussions. * * @param forumId Forum ID. + * @param cmId Forum cmid * @param sortOrder Sort order. * @param page Page. * @param forceCache True to always get the value from cache. false otherwise. @@ -601,7 +605,8 @@ export class AddonModForumProvider { * discussion ID is discussion.discussion. * - canLoadMore: True if there may be more discussions to load. */ - getDiscussions(forumId: number, sortOrder?: number, page: number = 0, forceCache?: boolean, siteId?: string): Promise { + getDiscussions(forumId: number, cmId: number, sortOrder?: number, page: number = 0, + forceCache?: boolean, siteId?: string): Promise { sortOrder = sortOrder || AddonModForumProvider.SORTORDER_LASTPOST_DESC; return this.sitesProvider.getSite(siteId).then((site) => { @@ -626,7 +631,9 @@ export class AddonModForumProvider { } } const preSets: CoreSiteWSPreSets = { - cacheKey: this.getDiscussionsListCacheKey(forumId, sortOrder) + cacheKey: this.getDiscussionsListCacheKey(forumId, sortOrder), + component: AddonModForumProvider.COMPONENT, + componentId: cmId }; if (forceCache) { preSets.omitExpires = true; @@ -673,6 +680,7 @@ export class AddonModForumProvider { * If a page fails, the discussions until that page will be returned along with a flag indicating an error occurred. * * @param forumId Forum ID. + * @param cmId Forum cmid. * @param sortOrder Sort order. * @param forceCache True to always get the value from cache, false otherwise. * @param numPages Number of pages to get. If not defined, all pages. @@ -682,8 +690,8 @@ export class AddonModForumProvider { * - discussions: List of discussions. * - error: True if an error occurred, false otherwise. */ - getDiscussionsInPages(forumId: number, sortOrder?: number, forceCache?: boolean, numPages?: number, startPage?: number, - siteId?: string): Promise { + getDiscussionsInPages(forumId: number, cmId: number, sortOrder?: number, forceCache?: boolean, + numPages?: number, startPage?: number, siteId?: string): Promise { if (typeof numPages == 'undefined') { numPages = -1; } @@ -700,7 +708,7 @@ export class AddonModForumProvider { const getPage = (page: number): Promise => { // Get page discussions. - return this.getDiscussions(forumId, sortOrder, page, forceCache, siteId).then((response) => { + return this.getDiscussions(forumId, cmId, sortOrder, page, forceCache, siteId).then((response) => { result.discussions = result.discussions.concat(response.discussions); numPages--; @@ -753,7 +761,7 @@ export class AddonModForumProvider { this.getAvailableSortOrders().forEach((sortOrder) => { // We need to get the list of discussions to be able to invalidate their posts. - promises.push(this.getDiscussionsInPages(forum.id, sortOrder.value, true).then((response) => { + promises.push(this.getDiscussionsInPages(forum.id, forum.cmid, sortOrder.value, true).then((response) => { // Now invalidate the WS calls. const promises = []; diff --git a/src/addon/mod/forum/providers/helper.ts b/src/addon/mod/forum/providers/helper.ts index 99ca1b487..7b3db2efa 100644 --- a/src/addon/mod/forum/providers/helper.ts +++ b/src/addon/mod/forum/providers/helper.ts @@ -270,15 +270,16 @@ export class AddonModForumHelperProvider { * This function is inefficient because it needs to fetch all discussion pages in the worst case. * * @param forumId Forum ID. + * @param cmId Forum cmid * @param discussionId Discussion ID. * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the discussion data. */ - getDiscussionById(forumId: number, discussionId: number, siteId?: string): Promise { + getDiscussionById(forumId: number, cmId: number, discussionId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const findDiscussion = (page: number): Promise => { - return this.forumProvider.getDiscussions(forumId, undefined, page, false, siteId).then((response) => { + return this.forumProvider.getDiscussions(forumId, cmId, undefined, page, false, siteId).then((response) => { if (response.discussions && response.discussions.length > 0) { // Note that discussion.id is the main post ID but discussion ID is discussion.discussion. const discussion = response.discussions.find((discussion) => discussion.discussion == discussionId); diff --git a/src/addon/mod/forum/providers/prefetch-handler.ts b/src/addon/mod/forum/providers/prefetch-handler.ts index 00af16f83..5a75af10a 100644 --- a/src/addon/mod/forum/providers/prefetch-handler.ts +++ b/src/addon/mod/forum/providers/prefetch-handler.ts @@ -114,7 +114,8 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand protected getPostsForPrefetch(forum: any, siteId?: string): Promise { const promises = this.forumProvider.getAvailableSortOrders().map((sortOrder) => { // Get discussions in first 2 pages. - return this.forumProvider.getDiscussionsInPages(forum.id, sortOrder.value, false, 2, 0, siteId).then((response) => { + return this.forumProvider.getDiscussionsInPages(forum.id, forum.cmid, + sortOrder.value, false, 2, 0, siteId).then((response) => { if (response.error) { return Promise.reject(null); } @@ -122,7 +123,7 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand const promises = []; response.discussions.forEach((discussion) => { - promises.push(this.forumProvider.getDiscussionPosts(discussion.discussion, siteId)); + promises.push(this.forumProvider.getDiscussionPosts(discussion.discussion, forum.cmid, siteId)); }); return Promise.all(promises); diff --git a/src/addon/storagemanager/pages/course-storage/course-storage.ts b/src/addon/storagemanager/pages/course-storage/course-storage.ts index 6bdeecc52..f3ec571e1 100644 --- a/src/addon/storagemanager/pages/course-storage/course-storage.ts +++ b/src/addon/storagemanager/pages/course-storage/course-storage.ts @@ -20,6 +20,7 @@ import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { TranslateService } from '@ngx-translate/core'; import { CoreConstants } from '@core/constants'; +import { CoreSitesProvider } from '@providers/sites'; /** * Page that displays the amount of file storage used by each activity on the course, and allows @@ -39,6 +40,7 @@ export class AddonStorageManagerCourseStoragePage { totalSize: number; constructor(navParams: NavParams, + private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider, private prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseHelperProvider: CoreCourseHelperProvider, @@ -70,7 +72,7 @@ export class AddonStorageManagerCourseStoragePage { // But these aren't necessarily consistent, for example mod_frog vs mmaModFrog. // There is nothing enforcing correct values. // Most modules which have large files are downloadable, so I think this is sufficient. - const promise = this.prefetchDelegate.getModuleDownloadedSize(module, this.course.id). + const promise = this.prefetchDelegate.getModuleStoredSize(module, this.course.id). then((size) => { // There are some cases where the return from this is not a valid number. if (!isNaN(size)) { @@ -185,7 +187,13 @@ export class AddonStorageManagerCourseStoragePage { modules.forEach((module) => { // Remove the files. const promise = this.prefetchDelegate.removeModuleFiles(module, this.course.id).then(() => { - // When the files are removed, update the size. + const handler = this.prefetchDelegate.getPrefetchHandlerFor(module); + if (handler) { + + return this.sitesProvider.getCurrentSite().deleteComponentFromCache(handler.component, module.id); + } + }).then(() => { + // When the files and cache are removed, update the size. module.parentSection.totalSize -= module.totalSize; this.totalSize -= module.totalSize; module.totalSize = 0; diff --git a/src/classes/site.ts b/src/classes/site.ts index 463b5a027..14bbf40fb 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -127,6 +127,17 @@ export interface CoreSiteWSPreSets { * Defaults to CoreSite.FREQUENCY_USUALLY. */ updateFrequency?: number; + + /** + * Component name. Optionally included if this request is being made on behalf of a specific + * component (e.g. activity). + */ + component?: string; + + /** + * Component id. Optionally included when 'component' is set. + */ + componentId?: number; } /** @@ -197,7 +208,7 @@ export class CoreSite { protected wsProvider: CoreWSProvider; // Variables for the database. - static WS_CACHE_TABLE = 'wscache'; + static WS_CACHE_TABLE = 'wscache_2'; static CONFIG_TABLE = 'core_site_config'; // Versions of Moodle releases. @@ -1089,6 +1100,25 @@ export class CoreSite { }); } + /** + * Gets the size of cached data for a specific component or component instance. + * + * @param component Component name + * @param componentId Optional component id (if not included, returns sum for whole component) + * @return Promise resolved when we have calculated the size + */ + getComponentCacheSize(component: string, componentId?: string): Promise { + const params = [component]; + let extraClause = ''; + if (componentId) { + params.push(componentId); + extraClause = ' AND componentId = ?'; + } + + return this.db.getFieldSql('SELECT SUM(length(data)) FROM ' + CoreSite.WS_CACHE_TABLE + + ' WHERE component = ?' + extraClause, params); + } + /** * Save a WS response to cache. * @@ -1128,6 +1158,13 @@ export class CoreSite { entry.key = preSets.cacheKey; } + if (preSets.component) { + entry.component = preSets.component; + if (preSets.componentId) { + entry.componentId = preSets.componentId; + } + } + return this.db.insertRecord(CoreSite.WS_CACHE_TABLE, entry); }); } @@ -1155,6 +1192,33 @@ export class CoreSite { return this.db.deleteRecords(CoreSite.WS_CACHE_TABLE, { id: id }); } + /** + * Deletes WS cache entries for all methods relating to a specific component (and + * optionally component id). + * + * @param component Component name. + * @param componentId Component id. + * @return Promise resolved when the entries are deleted. + */ + async deleteComponentFromCache(component: string, componentId?: string): Promise { + if (!component) { + return; + } + + if (!this.db) { + throw new Error('Site DB not initialized'); + } + + const params = { + component: component + } as any; + if (componentId) { + params.componentId = componentId; + } + + return this.db.deleteRecords(CoreSite.WS_CACHE_TABLE, params); + } + /* * Uploads a file using Cordova File API. * @@ -1324,6 +1388,29 @@ export class CoreSite { } } + /** + * Gets an approximation of the cache table usage of the site. + * + * Currently this is just the total length of the data fields in the cache table. + * + * @return Promise resolved with the total size of all data in the cache table (bytes) + */ + getCacheUsage(): Promise { + return this.db.getFieldSql('SELECT SUM(length(data)) FROM ' + CoreSite.WS_CACHE_TABLE); + } + + /** + * Gets a total of the file and cache usage. + * + * @return Promise with the total of getSpaceUsage and getCacheUsage + */ + async getTotalUsage(): Promise { + const space = await this.getSpaceUsage(); + const cache = await this.getCacheUsage(); + + return space + cache; + } + /** * Returns the URL to the documentation of the app, based on Moodle version and current language. * diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 1ba933eac..f91b189d9 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -425,6 +425,11 @@ export class CoreCourseHelperProvider { await this.prefetchDelegate.removeModuleFiles(module, courseId); + const handler = this.prefetchDelegate.getPrefetchHandlerFor(module); + if (handler) { + await this.sitesProvider.getCurrentSite().deleteComponentFromCache(handler.component, String(module.id)); + } + done && done(); } catch (error) { @@ -1131,7 +1136,7 @@ export class CoreCourseHelperProvider { this.prefetchDelegate.invalidateModuleStatusCache(module); } - promises.push(this.prefetchDelegate.getModuleDownloadedSize(module, courseId).then((moduleSize) => { + promises.push(this.prefetchDelegate.getModuleStoredSize(module, courseId).then((moduleSize) => { moduleInfo.size = moduleSize; moduleInfo.sizeReadable = this.textUtils.bytesToSize(moduleSize, 2); })); diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index 917a7bc4f..462f7ce83 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -690,6 +690,36 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { return Promise.resolve(0); } + /** + * Gets the estimated total size of data stored for a module. This includes + * the files downloaded for it (getModuleDownloadedSize) and also the total + * size of web service requests stored for it. + * + * @param module Module to get the size. + * @param courseId Course ID the module belongs to. + * @return Promise resolved with the total size (0 if unknown) + */ + getModuleStoredSize(module: any, courseId: number): Promise { + return this.getModuleDownloadedSize(module, courseId).then((downloadedSize) => { + if (isNaN(downloadedSize)) { + downloadedSize = 0; + } + const handler = this.getPrefetchHandlerFor(module); + if (handler) { + const site = this.sitesProvider.getCurrentSite(); + + return site.getComponentCacheSize(handler.component, module.id).then((cachedSize) => { + return cachedSize + downloadedSize; + }); + } else { + // If there is no handler then we can't find out the component name. + // So we can't work out the cached size, so just return downloaded size. + + return downloadedSize; + } + }); + } + /** * Get module files. * diff --git a/src/core/settings/pages/site/site.html b/src/core/settings/pages/site/site.html index 6c932d246..e03a107a1 100644 --- a/src/core/settings/pages/site/site.html +++ b/src/core/settings/pages/site/site.html @@ -33,7 +33,6 @@

{{ 'core.settings.spaceusage' | translate }}

{{ spaceUsage.spaceUsage | coreBytesToSize }}

-

{{ 'core.settings.entriesincache' | translate: { $a: spaceUsage.cacheEntries } }}

diff --git a/src/core/settings/pages/space-usage/space-usage.html b/src/core/settings/pages/space-usage/space-usage.html index 29dac4705..914cb1f9b 100644 --- a/src/core/settings/pages/space-usage/space-usage.html +++ b/src/core/settings/pages/space-usage/space-usage.html @@ -20,7 +20,6 @@

{{ site.fullName }}

{{ site.spaceUsage | coreBytesToSize }}

-

{{ 'core.settings.entriesincache' | translate: { $a: site.cacheEntries } }}