MOBILE-4660 storagemanager: Prefetch subsections

main
Pau Ferrer Ocaña 2024-09-26 15:54:35 +02:00 committed by Dani Palou
parent a3bb081f60
commit d3c3c56296
9 changed files with 348 additions and 313 deletions

View File

@ -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';

View File

@ -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<void> {
const section = await this.getSection(module, courseId, siteId);
if (!section) {
return;
}
await CoreCourseHelper.prefetchSections([section], courseId);
}
/**
* @inheritdoc
*/
async getDownloadSize(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreFileSizeSum> {
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<number> {
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<CoreCourseWSSection | undefined> {
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);

View File

@ -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);
},
},
],

View File

@ -81,7 +81,7 @@
<div class="storage-buttons" slot="end" *ngIf="(!section.calculatingSize && section.totalSize > 0) || downloadEnabled">
<div *ngIf="downloadEnabled" slot="end" class="core-button-spinner">
<core-download-refresh *ngIf="!section.isDownloading && section.downloadStatus !== statusDownloaded"
[status]="section.downloadStatus" [enabled]="true" (action)="prefecthSection(section)"
[status]="section.downloadStatus" [enabled]="true" (action)="prefetchSection(section)"
[loading]="section.isDownloading || section.isCalculating" [canTrustDownload]="true"
[statusesTranslatable]="{notdownloaded: 'addon.storagemanager.downloaddatafrom' }"
[statusSubject]="section.name" />

View File

@ -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<void> {
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<void> {
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<void> {
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<void> {
protected async updateSizes(sections: AddonStorageManagerCourseSection[]): Promise<void> {
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) {
sections.forEach((section) => {
section.calculatingSize = true;
section.modules.map((module) => {
if (module.subSection) {
module.subSection.calculatingSize = true;
}
});
});
subSection = sectionFinder?.subSection;
if (subSection) {
subSection.calculatingSize = true;
}
}
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<void> Once deleting has finished
*/
protected async deleteModules(modules: AddonStorageManagerModule[]): Promise<void> {
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<void> {
async prefetchSection(section: AddonStorageManagerCourseSection): Promise<void> {
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<void> {
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);
}
}));
}
/**
* Calculate and show module status.
*
* @param module Module to update.
* @returns Promise resolved when done.
*/
protected async calculateModuleStatus(module: AddonStorageManagerModule): Promise<void> {
if (!module) {
return;
}
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(module, this.courseId);
this.updateModuleStatus(module, status);
}
if (module.subSection) {
await this.calculateModulesStatusOnSection(module.subSection);
}
}));
}
/**
* Determines the prefetch icon of the course.
*
* @returns Promise resolved when done.
*/
protected async determineCoursePrefetchIcon(): Promise<void> {
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<void> {
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<void> {
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<CoreCourseSectionWithStatus, 'modules'> & {

View File

@ -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<number> {
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);
}
/**

View File

@ -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(

View File

@ -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 = <CoreCourseSectionWithStatus> 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<CoreCourseSectionWithStatus[]> {
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,36 +454,27 @@ 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<void> {
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);
// 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) {
if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) {
return;
}
const sectionSize = await CoreCourseModulePrefetchDelegate.getDownloadSize(section.contents, courseId);
const sectionSize = await CoreCourseModulePrefetchDelegate.getDownloadSize(section.modules, courseId);
sizeSum.total = sizeSum.total && sectionSize.total;
sizeSum.size += sectionSize.size;
@ -547,9 +484,6 @@ export class CoreCourseHelperProvider {
hasEmbeddedFiles = true;
}
}));
} else {
throw new CoreError('Either section or list of sections needs to be supplied.');
}
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<number> {
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<unknown>[] = [];
// 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<void> {
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<void> {
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<void> {
if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) {
if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) {
return;
}

View File

@ -472,6 +472,7 @@ export class CoreCourseModulePrefetchDelegateService extends CoreDelegate<CoreCo
* @returns Promise resolved with the total size (0 if unknown)
*/
async getModuleStoredSize(module: CoreCourseAnyModuleData, courseId: number): Promise<number> {
try {
const site = CoreSites.getCurrentSite();
const handler = this.getPrefetchHandlerFor(module.modname);
@ -483,6 +484,9 @@ export class CoreCourseModulePrefetchDelegateService extends CoreDelegate<CoreCo
const totalSize = cachedSize + downloadedSize;
return isNaN(totalSize) ? 0 : totalSize;
} catch {
return 0;
}
}
/**