From 361edb125236aefec83a8847f8ef22a18b9e665a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 11 Dec 2020 15:40:34 +0100 Subject: [PATCH] MOBILE-3608 block: Add CoreCache and some types --- src/core/classes/cache.ts | 112 ++++++++++++++++++++ src/core/features/course/services/course.ts | 78 +++++++------- src/core/services/filepool.ts | 8 +- src/core/services/plugin-file-delegate.ts | 12 ++- src/core/services/sites.ts | 3 + src/core/services/utils/dom.ts | 3 +- src/core/services/utils/utils.ts | 3 +- src/core/singletons/events.ts | 9 ++ 8 files changed, 182 insertions(+), 46 deletions(-) create mode 100644 src/core/classes/cache.ts diff --git a/src/core/classes/cache.ts b/src/core/classes/cache.ts new file mode 100644 index 000000000..833ecac56 --- /dev/null +++ b/src/core/classes/cache.ts @@ -0,0 +1,112 @@ +// (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. + +/** + * A cache to store values in memory to speed up processes. + * + * The data is organized by "entries" that are identified by an ID. Each entry can have multiple values stored, + * and each value has its own timemodified. + * + * Values expire after a certain time. + */ +export class CoreCache { + + protected cacheStore: { + [key: string]: CoreCacheEntry; + } = {}; + + /** + * Clear the cache. + */ + clear(): void { + this.cacheStore = {}; + } + + /** + * Get all the data stored in the cache for a certain id. + * + * @param id The ID to identify the entry. + * @return The data from the cache. Undefined if not found. + */ + getEntry(id: string): CoreCacheEntry { + if (!this.cacheStore[id]) { + this.cacheStore[id] = {}; + } + + return this.cacheStore[id]; + } + + /** + * Get the status of a module from the "cache". + * + * @param id The ID to identify the entry. + * @param name Name of the value to get. + * @param ignoreInvalidate Whether it should always return the cached data, even if it's expired. + * @return Cached value. Undefined if not cached or expired. + */ + getValue(id: string, name: string, ignoreInvalidate = false): T | undefined { + const entry = this.getEntry(id); + + if (entry[name] && typeof entry[name].value != 'undefined') { + const now = Date.now(); + // Invalidate after 5 minutes. + if (ignoreInvalidate || entry[name].timemodified + 300000 >= now) { + return entry[name].value; + } + } + + return undefined; + } + + /** + * Invalidate all the cached data for a certain entry. + * + * @param id The ID to identify the entry. + */ + invalidate(id: string): void { + const entry = this.getEntry(id); + for (const name in entry) { + entry[name].timemodified = 0; + } + } + + /** + * Update the status of a module in the "cache". + * + * @param id The ID to identify the entry. + * @param name Name of the value to set. + * @param value Value to set. + * @return The set value. + */ + setValue(id: string, name: string, value: T): T { + const entry = this.getEntry(id); + entry[name] = { + value: value, + timemodified: Date.now(), + }; + + return value; + } + +} + +/** + * Cache entry + */ +export type CoreCacheEntry = { + [name: string]: { + value?: any; // eslint-disable-line @typescript-eslint/no-explicit-any + timemodified: number; + }; +}; diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index 537f2d6aa..3086104af 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -345,7 +345,7 @@ export class CoreCourseProvider { ignoreCache: boolean = false, siteId?: string, modName?: string, - ): Promise { + ): Promise { siteId = siteId || CoreSites.instance.getCurrentSiteId(); // Helper function to do the WS request without processing the result. @@ -439,7 +439,7 @@ export class CoreCourseProvider { sections = await this.getSections(courseId, false, false, preSets, siteId); } - let foundModule: CoreCourseModule | undefined; + let foundModule: CoreCourseModuleData | undefined; const foundSection = sections.some((section) => { if (sectionId != null && @@ -739,12 +739,12 @@ export class CoreCourseProvider { * @param sections Sections. * @return Modules. */ - getSectionsModules(sections: CoreCourseSection[]): CoreCourseModule[] { + getSectionsModules(sections: CoreCourseSection[]): CoreCourseModuleData[] { if (!sections || !sections.length) { return []; } - return sections.reduce((previous: CoreCourseModule[], section) => previous.concat(section.modules || []), []); + return sections.reduce((previous: CoreCourseModuleData[], section) => previous.concat(section.modules || []), []); } /** @@ -829,7 +829,7 @@ export class CoreCourseProvider { * @return Promise resolved when loaded. */ async loadModuleContents( - module: CoreCourseModule & CoreCourseModuleBasicInfo, + module: CoreCourseModuleData & CoreCourseModuleBasicInfo, courseId?: number, sectionId?: number, preferCache?: boolean, @@ -964,7 +964,7 @@ export class CoreCourseProvider { * @param module The module object. * @return Whether the module has a view page. */ - moduleHasView(module: CoreCourseModuleSummary | CoreCourseModule): boolean { + moduleHasView(module: CoreCourseModuleSummary | CoreCourseModuleData): boolean { return !!module.url; } @@ -1345,7 +1345,7 @@ export type CoreCourseSection = { hiddenbynumsections?: number; // Whether is a section hidden in the course format. uservisible?: boolean; // Is the section visible for the user?. availabilityinfo?: string; // Availability information. - modules: CoreCourseModule[]; + modules: CoreCourseModuleData[]; }; /** @@ -1371,11 +1371,10 @@ export type CoreCourseGetCourseModuleWSResponse = { warnings?: CoreStatusWithWarningsWSResponse[]; }; - /** * Course module type. */ -export type CoreCourseModule = { // List of module. +export type CoreCourseModuleData = { // List of module. id: number; // Activity id. course?: number; // The course id. url?: string; // Activity url. @@ -1403,35 +1402,7 @@ export type CoreCourseModule = { // List of module. overrideby: number; // The user id who has overriden the status. valueused?: boolean; // Whether the completion status affects the availability of another activity. }; - contents: { - type: string; // A file or a folder or external link. - filename: string; // Filename. - filepath: string; // Filepath. - filesize: number; // Filesize. - fileurl?: string; // Downloadable file url. - content?: string; // Raw content, will be used when type is content. - timecreated: number; // Time created. - timemodified: number; // Time modified. - sortorder: number; // Content sort order. - mimetype?: string; // File mime type. - isexternalfile?: boolean; // Whether is an external file. - repositorytype?: string; // The repository type for external files. - userid: number; // User who added this content to moodle. - author: string; // Content owner. - license: string; // Content license. - tags?: { // Tags. - id: number; // Tag id. - name: string; // Tag name. - rawname: string; // The raw, unnormalised name for the tag as entered by users. - isstandard: boolean; // Whether this tag is standard. - tagcollid: number; // Tag collection id. - taginstanceid: number; // Tag instance id. - taginstancecontextid: number; // Context the tag instance belongs to. - itemid: number; // Id of the record tagged. - ordering: number; // Tag ordering. - flag: number; // Whether the tag is flagged as inappropriate. - }[]; - }[]; + contents: CoreCourseModuleContentFile[]; contentsinfo?: { // Contents summary information. filescount: number; // Total number of files. filessize: number; // Total files size. @@ -1441,6 +1412,37 @@ export type CoreCourseModule = { // List of module. }; }; +export type CoreCourseModuleContentFile = { + type: string; // A file or a folder or external link. + filename: string; // Filename. + filepath: string; // Filepath. + filesize: number; // Filesize. + fileurl?: string; // Downloadable file url. + url?: string; // @deprecated. Use fileurl instead. + content?: string; // Raw content, will be used when type is content. + timecreated: number; // Time created. + timemodified: number; // Time modified. + sortorder: number; // Content sort order. + mimetype?: string; // File mime type. + isexternalfile?: boolean; // Whether is an external file. + repositorytype?: string; // The repository type for external files. + userid: number; // User who added this content to moodle. + author: string; // Content owner. + license: string; // Content license. + tags?: { // Tags. + id: number; // Tag id. + name: string; // Tag name. + rawname: string; // The raw, unnormalised name for the tag as entered by users. + isstandard: boolean; // Whether this tag is standard. + tagcollid: number; // Tag collection id. + taginstanceid: number; // Tag instance id. + taginstancecontextid: number; // Context the tag instance belongs to. + itemid: number; // Id of the record tagged. + ordering: number; // Tag ordering. + flag: number; // Whether the tag is flagged as inappropriate. + }[]; +}; + /** * Course module basic info type. */ diff --git a/src/core/services/filepool.ts b/src/core/services/filepool.ts index 52b84b08a..a82ae02d0 100644 --- a/src/core/services/filepool.ts +++ b/src/core/services/filepool.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { Md5 } from 'ts-md5/dist/md5'; import { CoreApp } from '@services/app'; -import { CoreEvents } from '@singletons/events'; +import { CoreEventPackageStatusChanged, CoreEvents } from '@singletons/events'; import { CoreFile } from '@services/file'; import { CorePluginFileDelegate } from '@services/plugin-file-delegate'; import { CoreSites } from '@services/sites'; @@ -544,7 +544,7 @@ export class CoreFilepoolProvider { entries.forEach((entry) => { // Trigger module status changed, setting it as not downloaded. - this.triggerPackageStatusChanged(siteId, CoreConstants.NOT_DOWNLOADED, entry.component, entry.componentId); + this.triggerPackageStatusChanged(siteId, CoreConstants.NOT_DOWNLOADED, entry.component!, entry.componentId); }); } @@ -2949,8 +2949,8 @@ export class CoreFilepoolProvider { * @param component Package's component. * @param componentId An ID to use in conjunction with the component. */ - protected triggerPackageStatusChanged(siteId: string, status: string, component?: string, componentId?: string | number): void { - const data = { + protected triggerPackageStatusChanged(siteId: string, status: string, component: string, componentId?: string | number): void { + const data: CoreEventPackageStatusChanged = { component, componentId: this.fixComponentId(componentId), status, diff --git a/src/core/services/plugin-file-delegate.ts b/src/core/services/plugin-file-delegate.ts index 67c377e33..93cd9340b 100644 --- a/src/core/services/plugin-file-delegate.ts +++ b/src/core/services/plugin-file-delegate.ts @@ -133,7 +133,7 @@ export class CorePluginFileDelegateService extends CoreDelegate { + async getFilesDownloadSize(files: CoreWSExternalFile[], siteId: string): Promise { const filteredFiles = []; await Promise.all(files.map(async (file) => { @@ -154,7 +154,7 @@ export class CorePluginFileDelegateService extends CoreDelegate { + async getFilesSize(files: CoreWSExternalFile[], siteId?: string): Promise { const result = { size: 0, total: true, @@ -402,3 +402,11 @@ export type CorePluginFileDownloadableResult = { */ reason?: string; }; + +/** + * Sum of file sizes. + */ +export type CoreFileSizeSum = { + size: number; // Sum of file sizes. + total: boolean; // False if any file size is not avalaible. +}; diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index b2f8ecb08..7c6c6ac16 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -304,6 +304,9 @@ export class CoreSitesProvider { async siteExists(siteUrl: string): Promise { let data: CoreSitesLoginTokenResponse; + // Use a valid path first. + siteUrl = CoreUrlUtils.instance.removeUrlParams(siteUrl); + try { data = await Http.instance.post(siteUrl + '/login/token.php', {}).pipe(timeout(CoreWS.instance.getRequestTimeout())) .toPromise(); diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index fa85d4366..cff84e7d1 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -34,6 +34,7 @@ import { CoreSilentError } from '@classes/errors/silenterror'; import { makeSingleton, Translate, AlertController, LoadingController, ToastController } from '@singletons'; import { CoreLogger } from '@singletons/logger'; +import { CoreFileSizeSum } from '@services/plugin-file-delegate'; /* * "Utils" service with helper functions for UI, DOM elements and HTML code. @@ -134,7 +135,7 @@ export class CoreDomUtilsProvider { * @return Promise resolved when the user confirms or if no confirm needed. */ async confirmDownloadSize( - size: {size: number; total: boolean}, + size: CoreFileSizeSum, message?: string, unknownMessage?: string, wifiThreshold?: number, diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index b554e0999..ba4861dad 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -28,6 +28,7 @@ import { CoreTextUtils } from '@services/utils/text'; import { CoreWSError } from '@classes/errors/wserror'; import { makeSingleton, Clipboard, InAppBrowser, FileOpener, WebIntent, QRScanner, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; +import { CoreFileSizeSum } from '@services/plugin-file-delegate'; type TreeNode = T & { children: TreeNode[] }; @@ -1327,7 +1328,7 @@ export class CoreUtilsProvider { * @return File size and a boolean to indicate if it is the total size or only partial. * @deprecated since 3.8.0. Use CorePluginFileDelegate.getFilesSize instead. */ - sumFileSizes(files: CoreWSExternalFile[]): { size: number; total: boolean } { + sumFileSizes(files: CoreWSExternalFile[]): CoreFileSizeSum { const result = { size: 0, total: true, diff --git a/src/core/singletons/events.ts b/src/core/singletons/events.ts index eae75d94f..65b738a3a 100644 --- a/src/core/singletons/events.ts +++ b/src/core/singletons/events.ts @@ -242,6 +242,15 @@ export type CoreEventCourseStatusChanged = { status: string; }; +/** + * Data passed to PACKAGE_STATUS_CHANGED event. + */ +export type CoreEventPackageStatusChanged = { + component: string; + componentId: string | number; + status: string; +}; + /** * Data passed to USER_DELETED event. */