diff --git a/src/addons/mod/subsection/constants.ts b/src/addons/mod/subsection/constants.ts new file mode 100644 index 000000000..ee4912a2b --- /dev/null +++ b/src/addons/mod/subsection/constants.ts @@ -0,0 +1,15 @@ +// (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. + +export const ADDON_MOD_SUBSECTION_COMPONENT = 'mmaModSubsection'; diff --git a/src/addons/mod/subsection/services/handlers/prefetch.ts b/src/addons/mod/subsection/services/handlers/prefetch.ts new file mode 100644 index 000000000..7049468df --- /dev/null +++ b/src/addons/mod/subsection/services/handlers/prefetch.ts @@ -0,0 +1,98 @@ +// (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. + +import { Injectable } from '@angular/core'; +import { CoreCourseResourcePrefetchHandlerBase } from '@features/course/classes/resource-prefetch-handler'; +import { CoreCourse, CoreCourseAnyModuleData, CoreCourseWSSection } from '@features/course/services/course'; +import { makeSingleton } from '@singletons'; +import { ADDON_MOD_SUBSECTION_COMPONENT } from '../../constants'; +import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreFileSizeSum } from '@services/plugin-file-delegate'; +import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; +import { CoreSites } from '@services/sites'; + +/** + * Handler to prefetch subsections. + */ +@Injectable({ providedIn: 'root' }) +export class AddonModSubsectionPrefetchHandlerService extends CoreCourseResourcePrefetchHandlerBase { + + name = 'AddonModSubsection'; + modName = 'subsection'; + component = ADDON_MOD_SUBSECTION_COMPONENT; + + /** + * @inheritdoc + */ + protected async performDownloadOrPrefetch( + siteId: string, + module: CoreCourseModuleData, + courseId: number, + ): Promise { + const section = await this.getSection(module, courseId, siteId); + if (!section) { + return; + } + + await CoreCourseHelper.prefetchSections([section], courseId); + } + + /** + * @inheritdoc + */ + async getDownloadSize(module: CoreCourseAnyModuleData, courseId: number): Promise { + const section = await this.getSection(module, courseId); + if (!section) { + return { size: 0, total: true }; + } + + return await CoreCourseModulePrefetchDelegate.getDownloadSize(section.modules, courseId); + } + + /** + * @inheritdoc + */ + async getDownloadedSize(module: CoreCourseAnyModuleData, courseId: number): Promise { + const section = await this.getSection(module, courseId); + if (!section) { + return 0; + } + + return CoreCourseHelper.getModulesDownloadedSize(section.modules, courseId); + + } + + /** + * Get the section of a module. + * + * @param module Module. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @returns Promise resolved with the section if found. + */ + protected async getSection( + module: CoreCourseAnyModuleData, + courseId: number, + siteId?: string, + ): Promise { + siteId = siteId ?? CoreSites.getCurrentSiteId(); + + const sections = await CoreCourse.getSections(courseId, false, true, undefined, siteId); + + return sections.find((section) => + section.component === 'mod_subsection' && section.itemid === module.instance); + } + +} +export const AddonModSubsectionPrefetchHandler = makeSingleton(AddonModSubsectionPrefetchHandlerService); diff --git a/src/addons/mod/subsection/subsection.module.ts b/src/addons/mod/subsection/subsection.module.ts index a917ddf0e..4f2d4ad19 100644 --- a/src/addons/mod/subsection/subsection.module.ts +++ b/src/addons/mod/subsection/subsection.module.ts @@ -15,6 +15,8 @@ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; import { AddonModSubsectionIndexLinkHandler } from './services/handlers/index-link'; +import { AddonModSubsectionPrefetchHandler } from './services/handlers/prefetch'; +import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; @NgModule({ providers: [ @@ -23,6 +25,7 @@ import { AddonModSubsectionIndexLinkHandler } from './services/handlers/index-li multi: true, useValue: () => { CoreContentLinksDelegate.registerHandler(AddonModSubsectionIndexLinkHandler.instance); + CoreCourseModulePrefetchDelegate.registerHandler(AddonModSubsectionPrefetchHandler.instance); }, }, ], diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.html b/src/addons/storagemanager/pages/course-storage/course-storage.html index a9d44dad8..0c501b90a 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.html +++ b/src/addons/storagemanager/pages/course-storage/course-storage.html @@ -81,7 +81,7 @@
diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.ts b/src/addons/storagemanager/pages/course-storage/course-storage.ts index e9adc9f31..7e9a5c1eb 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.ts +++ b/src/addons/storagemanager/pages/course-storage/course-storage.ts @@ -30,10 +30,10 @@ import { CoreLoadings } from '@services/loadings'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; +import { CoreArray } from '@singletons/array'; import { CoreDom } from '@singletons/dom'; -import { CoreEventObserver, CoreEvents } from '@singletons/events'; +import { CoreEventObserver, CoreEvents, CoreEventSectionStatusChangedData } from '@singletons/events'; /** * Page that displays the amount of file storage used by each activity on the course, and allows @@ -120,11 +120,12 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { .map(section => ({ ...section, totalSize: 0, - calculatingSize: true, + calculatingSize: false, expanded: section.id === initialSectionId, modules: section.modules.map(module => ({ ...module, - calculatingSize: true, + totalSize: 0, + calculatingSize: false, })), })); @@ -162,8 +163,6 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { /** * Init course prefetch information. - * - * @returns Promise resolved when done. */ protected async initCoursePrefetch(): Promise { if (!this.downloadCourseEnabled || this.courseStatusObserver) { @@ -180,7 +179,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { // Determine the course prefetch status. await this.determineCoursePrefetchIcon(); - if (this.prefetchCourseData.icon != CoreConstants.ICON_LOADING) { + if (this.prefetchCourseData.icon !== CoreConstants.ICON_LOADING) { return; } @@ -203,8 +202,6 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { /** * Init module prefetch information. - * - * @returns Promise resolved when done. */ protected async initModulePrefetch(): Promise { if (!this.downloadEnabled || this.sectionStatusObserver) { @@ -219,46 +216,44 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { return; } - // Check if the affected section is being downloaded. - // If so, we don't update section status because it'll already be updated when the download finishes. - const downloadId = CoreCourseHelper.getSectionDownloadId({ id: data.sectionId }); - if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) { - return; - } - // Get the affected section. const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, data.sectionId); if (!sectionFinder?.section) { return; } - // Recalculate the status. - await CoreCourseHelper.calculateSectionStatus(sectionFinder.section, this.courseId, false); - if (sectionFinder.subSection) { - await CoreCourseHelper.calculateSectionStatus(sectionFinder.subSection, this.courseId, false); + const section = sectionFinder.section; + + // Check if the affected section is being downloaded. + // If so, we don't update section status because it'll already be updated when the download finishes. + const downloadId = CoreCourseHelper.getSectionDownloadId({ id: section.id }); + if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) { + return; } - if (sectionFinder.section.isDownloading && !CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) { + // Recalculate the status. + await this.updateSizes([section]); + + if (section.isDownloading && !CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) { // All the modules are now downloading, set a download all promise. - this.prefecthSection(sectionFinder.section); + this.prefetchSection(section); } }, CoreSites.getCurrentSiteId(), ); - // The download status of a section might have been changed from within a module page. - CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false); - - this.sections.forEach((section) => { - this.calculateModulesStatusOnSection(section); - }); - this.moduleStatusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => { let moduleFound: AddonStorageManagerModule | undefined; this.sections.some((section) => section.modules.some((module) => { - if (module.subSection) { + if (module.id === data.componentId && + module.prefetchHandler && + data.component === module.prefetchHandler?.component) { + moduleFound = module; + + return true; + } else if (module.subSection) { return module.subSection.modules.some((module) => { if (module.id === data.componentId && module.prefetchHandler && @@ -268,14 +263,6 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { return true; } }); - } else { - if (module.id === data.componentId && - module.prefetchHandler && - data.component === module.prefetchHandler?.component) { - moduleFound = module; - - return true; - } } return false; @@ -287,87 +274,81 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { // Call determineModuleStatus to get the right status to display. const status = CoreCourseModulePrefetchDelegate.determineModuleStatus(moduleFound, data.status); + if (moduleFound.subSection) { + const data: CoreEventSectionStatusChangedData = { + sectionId: moduleFound.subSection.id, + courseId: this.courseId, + }; + CoreEvents.trigger(CoreEvents.SECTION_STATUS_CHANGED, data, CoreSites.getCurrentSiteId()); + } // Update the status. this.updateModuleStatus(moduleFound, status); }, CoreSites.getCurrentSiteId()); + + // The download status of a section might have been changed from within a module page. + this.updateSizes(this.sections); } /** * Init section, course and modules sizes. */ protected async initSizes(): Promise { - const modules = this.getAllModulesList(); - await Promise.all(modules.map(async (module) => { - await this.calculateModuleSize(module); - })); - - await this.updateModulesSizes(modules); + await this.updateSizes(this.sections); } /** - * Update the sizes of some modules. + * Update the sizes of some sections and modules. * - * @param modules Modules. - * @returns Promise resolved when done. + * @param sections Modules. */ - protected async updateModulesSizes(modules: AddonStorageManagerModule[]): Promise { + protected async updateSizes(sections: AddonStorageManagerCourseSection[]): Promise { + sections = CoreArray.unique(sections, 'id'); + this.calculatingSize = true; - let section: AddonStorageManagerCourseSection | undefined; - let subSection: AddonStorageManagerCourseSection | undefined; - - await Promise.all(modules.map(async (module) => { - if (module.calculatingSize) { - return; - } - - module.calculatingSize = true; - - const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, module.section); - section = sectionFinder?.section; - if (section) { - section.calculatingSize = true; - - subSection = sectionFinder?.subSection; - if (subSection) { - subSection.calculatingSize = true; + sections.forEach((section) => { + section.calculatingSize = true; + section.modules.map((module) => { + if (module.subSection) { + module.subSection.calculatingSize = true; } - } - this.changeDetectorRef.markForCheck(); + }); + }); + this.changeDetectorRef.markForCheck(); + + // Update only affected module sections. + const modules = this.getAllModulesList(sections); + await Promise.all(modules.map(async (module) => { await this.calculateModuleSize(module); })); + const updateSectionSize = (section: AddonStorageManagerCourseSection): void => { + section.totalSize = 0; + section.calculatingSize = true; + + this.changeDetectorRef.markForCheck(); + + section.modules.forEach((module) => { + if (module.subSection) { + updateSectionSize(module.subSection); + module.totalSize = module.subSection.totalSize; + } + + section.totalSize += module.totalSize ?? 0; + this.changeDetectorRef.markForCheck(); + }); + section.calculatingSize = false; + + this.changeDetectorRef.markForCheck(); + }; + // Update section and total sizes. this.totalSize = 0; this.sections.forEach((section) => { - section.totalSize = 0; - section.modules.forEach((module) => { - if (module.subSection) { - const subSection = module.subSection; - - subSection.totalSize = 0; - subSection.modules.forEach((module) => { - if (module.totalSize && module.totalSize > 0) { - subSection.totalSize += module.totalSize; - } - }); - subSection.calculatingSize = false; - - section.totalSize += module.subSection.totalSize; - - return; - } - - if (module.totalSize && module.totalSize > 0) { - section.totalSize += module.totalSize; - } - }); - - section.calculatingSize = false; + updateSectionSize(section); this.totalSize += section.totalSize; }); - this.calculatingSize = false; // Mark course as not downloaded if course size is 0. @@ -376,6 +357,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { } this.changeDetectorRef.markForCheck(); + + await this.calculateSectionsStatus(sections); + this.changeDetectorRef.markForCheck(); } /** @@ -402,7 +386,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { return; } - const modules = this.getAllModulesList().filter((module) => module.totalSize && module.totalSize > 0); + const modules = this.getAllModulesList(this.sections).filter((module) => module.totalSize && module.totalSize > 0); await this.deleteModules(modules); } @@ -489,16 +473,21 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { * Deletes the specified modules, showing the loading overlay while it happens. * * @param modules Modules to delete - * @returns Promise Once deleting has finished */ protected async deleteModules(modules: AddonStorageManagerModule[]): Promise { const modal = await CoreLoadings.show('core.deleting', true); + const sections: AddonStorageManagerCourseSection[] = []; const promises = modules.map(async (module) => { // Remove the files. await CoreCourseHelper.removeModuleStoredData(module, this.courseId); module.totalSize = 0; + + const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, module.section); + if (sectionFinder?.section) { + sections.push(sectionFinder?.section); + } }); try { @@ -508,8 +497,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { } finally { modal.dismiss(); - await this.updateModulesSizes(modules); - CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false); + await this.updateSizes(sections); this.changeDetectorRef.markForCheck(); } @@ -526,39 +514,26 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { CoreCourse.setCourseStatus(this.courseId, DownloadStatus.DOWNLOADABLE_NOT_DOWNLOADED); } - /** - * Calculate the status of sections. - * - * @param refresh If refresh or not. - */ - protected calculateSectionsStatus(refresh?: boolean): void { - if (!this.sections) { - return; - } - - CoreUtils.ignoreErrors(CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, refresh)); - } - /** * Confirm and prefetch a section. If the section is "all sections", prefetch all the sections. * * @param section Section to download. */ - async prefecthSection(section: AddonStorageManagerCourseSection): Promise { + async prefetchSection(section: AddonStorageManagerCourseSection): Promise { section.isCalculating = true; this.changeDetectorRef.markForCheck(); try { - await CoreCourseHelper.confirmDownloadSizeSection(this.courseId, section, this.sections); + await CoreCourseHelper.confirmDownloadSizeSection(this.courseId, [section]); try { - await CoreCourseHelper.prefetchSection(section, this.courseId, this.sections); + await CoreCourseHelper.prefetchSections([section], this.courseId); } catch (error) { if (!this.isDestroyed) { CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true); } } finally { - await this.updateModulesSizes(section.modules); + await this.updateSizes([section]); } } catch (error) { // User cancelled or there was an error calculating the size. @@ -594,12 +569,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { try { // Get download size to ask for confirm if it's high. - const size = await module.prefetchHandler.getDownloadSize(module, module.course, true); await CoreCourseHelper.prefetchModule(module.prefetchHandler, module, size, module.course, refresh); - - CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false); } catch (error) { if (!this.isDestroyed) { CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true); @@ -607,7 +579,10 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { } finally { module.spinner = false; - await this.updateModulesSizes([module]); + const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, module.section); + if (sectionFinder?.section) { + await this.updateSizes([sectionFinder?.section]); + } } } @@ -636,37 +611,23 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { */ protected async calculateModulesStatusOnSection(section: AddonStorageManagerCourseSection): Promise { await Promise.all(section.modules.map(async (module) => { - if (module.subSection) { - await this.calculateModulesStatusOnSection(module.subSection); - } else if (module.handlerData?.showDownloadButton) { + if (module.handlerData?.showDownloadButton) { module.spinner = true; // Listen for changes on this module status, even if download isn't enabled. module.prefetchHandler = CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(module.modname); - await this.calculateModuleStatus(module); + const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(module, this.courseId); + + this.updateModuleStatus(module, status); + } + + if (module.subSection) { + await this.calculateModulesStatusOnSection(module.subSection); } })); } - /** - * Calculate and show module status. - * - * @param module Module to update. - * @returns Promise resolved when done. - */ - protected async calculateModuleStatus(module: AddonStorageManagerModule): Promise { - if (!module) { - return; - } - - const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(module, this.courseId); - - this.updateModuleStatus(module, status); - } - /** * Determines the prefetch icon of the course. - * - * @returns Promise resolved when done. */ protected async determineCoursePrefetchIcon(): Promise { this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.courseId); @@ -739,8 +700,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { }, ); - const modules = this.getAllModulesList(); - await this.updateModulesSizes(modules); + await this.updateSizes(this.sections); } catch (error) { if (this.isDestroyed) { return; @@ -753,21 +713,20 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { /** * Get all modules list. * + * @param sections Sections to get the modules from. * @returns All modules list. */ - protected getAllModulesList(): AddonStorageManagerModule[] { + protected getAllModulesList(sections: AddonStorageManagerCourseSection[]): AddonStorageManagerModule[] { const modules: AddonStorageManagerModule[] = []; - this.sections.forEach((section) => { + sections.forEach((section) => { section.modules.forEach((module) => { + modules.push(module); + if (module.subSection) { module.subSection.modules.forEach((module) => { modules.push(module); }); - - return; } - - modules.push(module); }); }); @@ -775,12 +734,17 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { } /** - * Calculate the size of a module. + * Calculate the size of the modules. * * @param module Module to calculate. */ protected async calculateModuleSize(module: AddonStorageManagerModule): Promise { + if (module.calculatingSize) { + return; + } + module.calculatingSize = true; + this.changeDetectorRef.markForCheck(); // Note: This function only gets the size for modules which are downloadable. // For other modules it always returns 0, even if they have downloaded some files. @@ -789,15 +753,10 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { // 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 size = await CoreUtils.ignoreErrors(CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId)); + module.totalSize = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId); - if (size !== undefined) { - // There are some cases where the return from this is not a valid number. - module.totalSize = !isNaN(size) ? Number(size) : 0; - } - - this.changeDetectorRef.markForCheck(); module.calculatingSize = false; + this.changeDetectorRef.markForCheck(); } /** @@ -845,6 +804,39 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy { this.isDestroyed = true; } + /** + * Calculate the status of a list of sections, setting attributes to determine the icons/data to be shown. + * + * @param sections Sections to calculate their status. + */ + protected async calculateSectionsStatus( + sections: AddonStorageManagerCourseSection[], + ): Promise { + if (!sections) { + return; + } + + await Promise.all(sections.map(async (section) => { + if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) { + return; + } + + try { + section.isCalculating = true; + await this.calculateModulesStatusOnSection(section); + await CoreCourseHelper.calculateSectionStatus(section, this.courseId, false, false); + + await Promise.all(section.modules.map(async (module) => { + if (module.subSection) { + return CoreCourseHelper.calculateSectionStatus(module.subSection, this.courseId, false, false); + } + })); + } finally { + section.isCalculating = false; + } + })); + } + } type AddonStorageManagerCourseSection = Omit & { diff --git a/src/addons/storagemanager/pages/courses-storage/courses-storage.ts b/src/addons/storagemanager/pages/courses-storage/courses-storage.ts index aa478bdcb..ca12f9d39 100644 --- a/src/addons/storagemanager/pages/courses-storage/courses-storage.ts +++ b/src/addons/storagemanager/pages/courses-storage/courses-storage.ts @@ -17,7 +17,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { CoreQueueRunner } from '@classes/queue-runner'; import { CoreCourse, CoreCourseProvider } from '@features/course/services/course'; import { CoreCourseHelper } from '@features/course/services/course-helper'; -import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; import { CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses'; import { CoreSettingsHelper, CoreSiteSpaceUsage } from '@features/settings/services/settings-helper'; import { CoreSiteHome } from '@features/sitehome/services/sitehome'; @@ -241,14 +240,8 @@ export class AddonStorageManagerCoursesStoragePage implements OnInit, OnDestroy private async calculateDownloadedCourseSize(courseId: number): Promise { const sections = await CoreCourse.getSections(courseId); const modules = CoreCourseHelper.getSectionsModules(sections); - const promisedModuleSizes = modules.map(async (module) => { - const size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, courseId); - return isNaN(size) ? 0 : size; - }); - const moduleSizes = await Promise.all(promisedModuleSizes); - - return moduleSizes.reduce((totalSize, moduleSize) => totalSize + moduleSize, 0); + return CoreCourseHelper.getModulesDownloadedSize(modules, courseId); } /** diff --git a/src/core/features/course/components/module-summary/module-summary.ts b/src/core/features/course/components/module-summary/module-summary.ts index a14798a75..910a892de 100644 --- a/src/core/features/course/components/module-summary/module-summary.ts +++ b/src/core/features/course/components/module-summary/module-summary.ts @@ -142,11 +142,7 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy { return; } - const moduleSize = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(this.module, this.courseId); - - if (moduleSize) { - this.size = moduleSize; - } + this.size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(this.module, this.courseId); }, 1000); this.fileStatusObserver = CoreEvents.on( diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 3023c7483..e105880a4 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -28,6 +28,7 @@ import { CoreCourseModuleCompletionStatus, CoreCourseGetContentsWSModule, sectionContentIsModule, + CoreCourseAnyModuleData, } from './course'; import { CoreConstants, DownloadStatus, ContextLevel } from '@/core/constants'; import { CoreLogger } from '@singletons/logger'; @@ -283,14 +284,14 @@ export class CoreCourseHelperProvider { refresh?: boolean, checkUpdates: boolean = true, ): Promise<{statusData: CoreCourseModulesStatus; section: CoreCourseSectionWithStatus}> { - if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) { + if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) { throw new CoreError('Invalid section'); } const sectionWithStatus = section; // Get the status of this section. - const result = await CoreCourseModulePrefetchDelegate.getModulesStatus( + const statusData = await CoreCourseModulePrefetchDelegate.getModulesStatus( section.contents, courseId, section.id, @@ -302,13 +303,13 @@ export class CoreCourseHelperProvider { // Check if it's being downloaded. const downloadId = this.getSectionDownloadId(section); if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) { - result.status = DownloadStatus.DOWNLOADING; + statusData.status = DownloadStatus.DOWNLOADING; } - sectionWithStatus.downloadStatus = result.status; + sectionWithStatus.downloadStatus = statusData.status; // Set this section data. - if (result.status !== DownloadStatus.DOWNLOADING) { + if (statusData.status !== DownloadStatus.DOWNLOADING) { sectionWithStatus.isDownloading = false; sectionWithStatus.total = 0; } else { @@ -320,62 +321,7 @@ export class CoreCourseHelperProvider { }); } - return { statusData: result, section: sectionWithStatus }; - } - - /** - * Calculate the status of a list of sections, setting attributes to determine the icons/data to be shown. - * - * @param sections Sections to calculate their status. - * @param courseId Course ID the sections belong to. - * @param refresh True if it shouldn't use module status cache (slower). - * @param checkUpdates Whether to use the WS to check updates. Defaults to true. - * @returns Promise resolved when the states are calculated. - */ - async calculateSectionsStatus( - sections: CoreCourseSection[], - courseId: number, - refresh?: boolean, - checkUpdates: boolean = true, - ): Promise { - let allSectionsSection: CoreCourseSectionWithStatus | undefined; - let allSectionsStatus = DownloadStatus.NOT_DOWNLOADABLE as DownloadStatus; - - const promises = sections.map(async (section: CoreCourseSectionWithStatus) => { - section.isCalculating = true; - - if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) { - // "All sections" section status is calculated using the status of the rest of sections. - allSectionsSection = section; - - return; - } - - try { - const result = await this.calculateSectionStatus(section, courseId, refresh, checkUpdates); - - // Calculate "All sections" status. - allSectionsStatus = CoreFilepool.determinePackagesStatus(allSectionsStatus, result.statusData.status); - } finally { - section.isCalculating = false; - } - }); - - try { - await Promise.all(promises); - - if (allSectionsSection) { - // Set "All sections" data. - allSectionsSection.downloadStatus = allSectionsStatus; - allSectionsSection.isDownloading = allSectionsStatus === DownloadStatus.DOWNLOADING; - } - - return sections; - } finally { - if (allSectionsSection) { - allSectionsSection.isCalculating = false; - } - } + return { statusData, section: sectionWithStatus }; } /** @@ -411,7 +357,7 @@ export class CoreCourseHelperProvider { } // Confirm the download. - await this.confirmDownloadSizeSection(course.id, undefined, options.sections, true); + await this.confirmDownloadSizeSection(course.id, options.sections, true); // User confirmed, get the course handlers if needed. if (!options.courseHandlers) { @@ -508,48 +454,36 @@ export class CoreCourseHelperProvider { * Calculate the size to download a section and show a confirm modal if needed. * * @param courseId Course ID the section belongs to. - * @param section Section. If not provided, all sections. - * @param sections List of sections. Used when downloading all the sections. + * @param sections List of sections to download * @param alwaysConfirm True to show a confirm even if the size isn't high, false otherwise. * @returns Promise resolved if the user confirms or there's no need to confirm. */ async confirmDownloadSizeSection( courseId: number, - section?: CoreCourseWSSection, - sections?: CoreCourseWSSection[], - alwaysConfirm?: boolean, + sections: CoreCourseWSSection[] = [], + alwaysConfirm = false, ): Promise { let hasEmbeddedFiles = false; - let sizeSum: CoreFileSizeSum = { + const sizeSum: CoreFileSizeSum = { size: 0, total: true, }; - // Calculate the size of the download. - if (section && section.id != CoreCourseProvider.ALL_SECTIONS_ID) { - sizeSum = await CoreCourseModulePrefetchDelegate.getDownloadSize(section.contents, courseId); + await Promise.all(sections.map(async (section) => { + if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) { + return; + } + + const sectionSize = await CoreCourseModulePrefetchDelegate.getDownloadSize(section.modules, courseId); + + sizeSum.total = sizeSum.total && sectionSize.total; + sizeSum.size += sectionSize.size; // Check if the section has embedded files in the description. - hasEmbeddedFiles = CoreFilepool.extractDownloadableFilesFromHtml(section.summary).length > 0; - } else if (sections) { - await Promise.all(sections.map(async (section) => { - if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) { - return; - } - - const sectionSize = await CoreCourseModulePrefetchDelegate.getDownloadSize(section.contents, courseId); - - sizeSum.total = sizeSum.total && sectionSize.total; - sizeSum.size += sectionSize.size; - - // Check if the section has embedded files in the description. - if (!hasEmbeddedFiles && CoreFilepool.extractDownloadableFilesFromHtml(section.summary).length > 0) { - hasEmbeddedFiles = true; - } - })); - } else { - throw new CoreError('Either section or list of sections needs to be supplied.'); - } + if (!hasEmbeddedFiles && CoreFilepool.extractDownloadableFilesFromHtml(section.summary).length > 0) { + hasEmbeddedFiles = true; + } + })); if (hasEmbeddedFiles) { sizeSum.total = false; @@ -559,6 +493,20 @@ export class CoreCourseHelperProvider { await CoreDomUtils.confirmDownloadSize(sizeSum, undefined, undefined, undefined, undefined, alwaysConfirm); } + /** + * Sums the stored module sizes. + * + * @param modules List of modules. + * @param courseId Course ID. + * @returns Promise resolved with the sum of the stored sizes. + */ + async getModulesDownloadedSize(modules: CoreCourseAnyModuleData[], courseId: number): Promise { + const moduleSizes = await Promise.all(modules.map(async (module) => + await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, courseId))); + + return moduleSizes.reduce((totalSize, moduleSize) => totalSize + moduleSize, 0); + } + /** * Check whether a course is accessed using guest access and if it requires user input to enter. * @@ -1350,20 +1298,18 @@ export class CoreCourseHelperProvider { await CoreUtils.ignoreErrors(CoreCourseModulePrefetchDelegate.invalidateCourseUpdates(courseId)); } - const results = await Promise.all([ + const [size, status, packageData] = await Promise.all([ CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, courseId), CoreCourseModulePrefetchDelegate.getModuleStatus(module, courseId), this.getModulePackageLastDownloaded(module, component), ]); // Treat stored size. - const size = results[0]; - const sizeReadable = CoreText.bytesToSize(results[0], 2); + const sizeReadable = CoreText.bytesToSize(size, 2); // Treat module status. - const status = results[1]; let statusIcon: string | undefined; - switch (results[1]) { + switch (status) { case DownloadStatus.DOWNLOADABLE_NOT_DOWNLOADED: statusIcon = CoreConstants.ICON_NOT_DOWNLOADED; break; @@ -1380,8 +1326,6 @@ export class CoreCourseHelperProvider { break; } - const packageData = results[2]; - return { size, sizeReadable, @@ -1625,12 +1569,7 @@ export class CoreCourseHelperProvider { const promises: Promise[] = []; - // Prefetch all the sections. If the first section is "All sections", use it. Otherwise, use a fake "All sections". - let allSectionsSection: CoreCourseWSSection = sections[0]; - if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) { - allSectionsSection = this.createAllSectionsSection(); - } - promises.push(this.prefetchSection(allSectionsSection, course.id, sections)); + promises.push(this.prefetchSections(sections, course.id, true)); // Prefetch course options. courseHandlers.forEach((handler) => { @@ -1700,41 +1639,32 @@ export class CoreCourseHelperProvider { } /** - * Prefetch one section or all the sections. - * If the section is "All sections" it will prefetch all the sections. + * Prefetch some sections * - * @param section Section. + * @param sections List of sections. . * @param courseId Course ID the section belongs to. - * @param sections List of sections. Used when downloading all the sections. - * @returns Promise resolved when the prefetch is finished. + * @param updateAllSections Update all sections status */ - async prefetchSection( - section: CoreCourseSectionWithStatus, + async prefetchSections( + sections: (CoreCourseSectionWithStatus & CoreCourseSectionWithSubsections)[], courseId: number, - sections?: CoreCourseSectionWithStatus[], + updateAllSections = false, ): Promise { - if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) { - try { - // Download only this section. - await this.prefetchSingleSectionIfNeeded(section, courseId); - } finally { - // Calculate the status of the section that finished. - await this.calculateSectionStatus(section, courseId, false, false); - } - return; - } - - if (!sections) { - throw new CoreError('List of sections is required when downloading all sections.'); - } - - // Download all the sections except "All sections". let allSectionsStatus = DownloadStatus.NOT_DOWNLOADABLE as DownloadStatus; + let allSectionsSection: (CoreCourseSectionWithStatus) | undefined; + if (updateAllSections) { + // Prefetch all the sections. If the first section is "All sections", use it. Otherwise, use a fake "All sections". + allSectionsSection = sections[0]; + if (sections[0].id !== CoreCourseProvider.ALL_SECTIONS_ID) { + allSectionsSection = this.createAllSectionsSection(); + } + allSectionsSection.isDownloading = true; + } - section.isDownloading = true; const promises = sections.map(async (section) => { - if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) { + // Download all the sections except "All sections". + if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) { return; } @@ -1753,10 +1683,14 @@ export class CoreCourseHelperProvider { await CoreUtils.allPromises(promises); // Set "All sections" data. - section.downloadStatus = allSectionsStatus; - section.isDownloading = allSectionsStatus === DownloadStatus.DOWNLOADING; + if (allSectionsSection) { + allSectionsSection.downloadStatus = allSectionsStatus; + allSectionsSection.isDownloading = allSectionsStatus === DownloadStatus.DOWNLOADING; + } } finally { - section.isDownloading = false; + if (allSectionsSection) { + allSectionsSection.isDownloading = false; + } } } @@ -1769,7 +1703,7 @@ export class CoreCourseHelperProvider { * @returns Promise resolved when the section is prefetched. */ protected async prefetchSingleSectionIfNeeded(section: CoreCourseSectionWithStatus, courseId: number): Promise { - if (section.id == CoreCourseProvider.ALL_SECTIONS_ID || section.hiddenbynumsections) { + if (section.id === CoreCourseProvider.ALL_SECTIONS_ID || section.hiddenbynumsections) { return; } @@ -1830,7 +1764,7 @@ export class CoreCourseHelperProvider { result: CoreCourseModulesStatus, courseId: number, ): Promise { - if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) { + if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) { return; } diff --git a/src/core/features/course/services/module-prefetch-delegate.ts b/src/core/features/course/services/module-prefetch-delegate.ts index 9fc20ea79..330de8d0a 100644 --- a/src/core/features/course/services/module-prefetch-delegate.ts +++ b/src/core/features/course/services/module-prefetch-delegate.ts @@ -472,17 +472,21 @@ export class CoreCourseModulePrefetchDelegateService extends CoreDelegate { - const site = CoreSites.getCurrentSite(); - const handler = this.getPrefetchHandlerFor(module.modname); + try { + const site = CoreSites.getCurrentSite(); + const handler = this.getPrefetchHandlerFor(module.modname); - const [downloadedSize, cachedSize] = await Promise.all([ - this.getModuleDownloadedSize(module, courseId), - handler && site ? site.getComponentCacheSize(handler.component, module.id) : 0, - ]); + const [downloadedSize, cachedSize] = await Promise.all([ + this.getModuleDownloadedSize(module, courseId), + handler && site ? site.getComponentCacheSize(handler.component, module.id) : 0, + ]); - const totalSize = cachedSize + downloadedSize; + const totalSize = cachedSize + downloadedSize; - return isNaN(totalSize) ? 0 : totalSize; + return isNaN(totalSize) ? 0 : totalSize; + } catch { + return 0; + } } /**