MOBILE-4660 storagemanager: Prefetch subsections
parent
a3bb081f60
commit
d3c3c56296
|
@ -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';
|
|
@ -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);
|
|
@ -15,6 +15,8 @@
|
||||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
import { AddonModSubsectionIndexLinkHandler } from './services/handlers/index-link';
|
import { AddonModSubsectionIndexLinkHandler } from './services/handlers/index-link';
|
||||||
|
import { AddonModSubsectionPrefetchHandler } from './services/handlers/prefetch';
|
||||||
|
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -23,6 +25,7 @@ import { AddonModSubsectionIndexLinkHandler } from './services/handlers/index-li
|
||||||
multi: true,
|
multi: true,
|
||||||
useValue: () => {
|
useValue: () => {
|
||||||
CoreContentLinksDelegate.registerHandler(AddonModSubsectionIndexLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(AddonModSubsectionIndexLinkHandler.instance);
|
||||||
|
CoreCourseModulePrefetchDelegate.registerHandler(AddonModSubsectionPrefetchHandler.instance);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
<div class="storage-buttons" slot="end" *ngIf="(!section.calculatingSize && section.totalSize > 0) || downloadEnabled">
|
<div class="storage-buttons" slot="end" *ngIf="(!section.calculatingSize && section.totalSize > 0) || downloadEnabled">
|
||||||
<div *ngIf="downloadEnabled" slot="end" class="core-button-spinner">
|
<div *ngIf="downloadEnabled" slot="end" class="core-button-spinner">
|
||||||
<core-download-refresh *ngIf="!section.isDownloading && section.downloadStatus !== statusDownloaded"
|
<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"
|
[loading]="section.isDownloading || section.isCalculating" [canTrustDownload]="true"
|
||||||
[statusesTranslatable]="{notdownloaded: 'addon.storagemanager.downloaddatafrom' }"
|
[statusesTranslatable]="{notdownloaded: 'addon.storagemanager.downloaddatafrom' }"
|
||||||
[statusSubject]="section.name" />
|
[statusSubject]="section.name" />
|
||||||
|
|
|
@ -30,10 +30,10 @@ import { CoreLoadings } from '@services/loadings';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
|
import { CoreArray } from '@singletons/array';
|
||||||
import { CoreDom } from '@singletons/dom';
|
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
|
* 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 => ({
|
.map(section => ({
|
||||||
...section,
|
...section,
|
||||||
totalSize: 0,
|
totalSize: 0,
|
||||||
calculatingSize: true,
|
calculatingSize: false,
|
||||||
expanded: section.id === initialSectionId,
|
expanded: section.id === initialSectionId,
|
||||||
modules: section.modules.map(module => ({
|
modules: section.modules.map(module => ({
|
||||||
...module,
|
...module,
|
||||||
calculatingSize: true,
|
totalSize: 0,
|
||||||
|
calculatingSize: false,
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -162,8 +163,6 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init course prefetch information.
|
* Init course prefetch information.
|
||||||
*
|
|
||||||
* @returns Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async initCoursePrefetch(): Promise<void> {
|
protected async initCoursePrefetch(): Promise<void> {
|
||||||
if (!this.downloadCourseEnabled || this.courseStatusObserver) {
|
if (!this.downloadCourseEnabled || this.courseStatusObserver) {
|
||||||
|
@ -180,7 +179,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
// Determine the course prefetch status.
|
// Determine the course prefetch status.
|
||||||
await this.determineCoursePrefetchIcon();
|
await this.determineCoursePrefetchIcon();
|
||||||
|
|
||||||
if (this.prefetchCourseData.icon != CoreConstants.ICON_LOADING) {
|
if (this.prefetchCourseData.icon !== CoreConstants.ICON_LOADING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,8 +202,6 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init module prefetch information.
|
* Init module prefetch information.
|
||||||
*
|
|
||||||
* @returns Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async initModulePrefetch(): Promise<void> {
|
protected async initModulePrefetch(): Promise<void> {
|
||||||
if (!this.downloadEnabled || this.sectionStatusObserver) {
|
if (!this.downloadEnabled || this.sectionStatusObserver) {
|
||||||
|
@ -219,46 +216,44 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
return;
|
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.
|
// Get the affected section.
|
||||||
const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, data.sectionId);
|
const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, data.sectionId);
|
||||||
if (!sectionFinder?.section) {
|
if (!sectionFinder?.section) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recalculate the status.
|
const section = sectionFinder.section;
|
||||||
await CoreCourseHelper.calculateSectionStatus(sectionFinder.section, this.courseId, false);
|
|
||||||
if (sectionFinder.subSection) {
|
// Check if the affected section is being downloaded.
|
||||||
await CoreCourseHelper.calculateSectionStatus(sectionFinder.subSection, this.courseId, false);
|
// 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.
|
// All the modules are now downloading, set a download all promise.
|
||||||
this.prefecthSection(sectionFinder.section);
|
this.prefetchSection(section);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CoreSites.getCurrentSiteId(),
|
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) => {
|
this.moduleStatusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => {
|
||||||
let moduleFound: AddonStorageManagerModule | undefined;
|
let moduleFound: AddonStorageManagerModule | undefined;
|
||||||
|
|
||||||
this.sections.some((section) =>
|
this.sections.some((section) =>
|
||||||
section.modules.some((module) => {
|
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) => {
|
return module.subSection.modules.some((module) => {
|
||||||
if (module.id === data.componentId &&
|
if (module.id === data.componentId &&
|
||||||
module.prefetchHandler &&
|
module.prefetchHandler &&
|
||||||
|
@ -268,14 +263,6 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
if (module.id === data.componentId &&
|
|
||||||
module.prefetchHandler &&
|
|
||||||
data.component === module.prefetchHandler?.component) {
|
|
||||||
moduleFound = module;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -287,87 +274,81 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
// Call determineModuleStatus to get the right status to display.
|
// Call determineModuleStatus to get the right status to display.
|
||||||
const status = CoreCourseModulePrefetchDelegate.determineModuleStatus(moduleFound, data.status);
|
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.
|
// Update the status.
|
||||||
this.updateModuleStatus(moduleFound, status);
|
this.updateModuleStatus(moduleFound, status);
|
||||||
}, CoreSites.getCurrentSiteId());
|
}, 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.
|
* Init section, course and modules sizes.
|
||||||
*/
|
*/
|
||||||
protected async initSizes(): Promise<void> {
|
protected async initSizes(): Promise<void> {
|
||||||
const modules = this.getAllModulesList();
|
await this.updateSizes(this.sections);
|
||||||
await Promise.all(modules.map(async (module) => {
|
|
||||||
await this.calculateModuleSize(module);
|
|
||||||
}));
|
|
||||||
|
|
||||||
await this.updateModulesSizes(modules);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the sizes of some modules.
|
* Update the sizes of some sections and modules.
|
||||||
*
|
*
|
||||||
* @param modules Modules.
|
* @param sections Modules.
|
||||||
* @returns Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async updateModulesSizes(modules: AddonStorageManagerModule[]): Promise<void> {
|
protected async updateSizes(sections: AddonStorageManagerCourseSection[]): Promise<void> {
|
||||||
|
sections = CoreArray.unique(sections, 'id');
|
||||||
|
|
||||||
this.calculatingSize = true;
|
this.calculatingSize = true;
|
||||||
let section: AddonStorageManagerCourseSection | undefined;
|
sections.forEach((section) => {
|
||||||
let subSection: AddonStorageManagerCourseSection | undefined;
|
section.calculatingSize = true;
|
||||||
|
section.modules.map((module) => {
|
||||||
await Promise.all(modules.map(async (module) => {
|
if (module.subSection) {
|
||||||
if (module.calculatingSize) {
|
module.subSection.calculatingSize = true;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
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);
|
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.
|
// Update section and total sizes.
|
||||||
this.totalSize = 0;
|
this.totalSize = 0;
|
||||||
this.sections.forEach((section) => {
|
this.sections.forEach((section) => {
|
||||||
section.totalSize = 0;
|
updateSectionSize(section);
|
||||||
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;
|
|
||||||
this.totalSize += section.totalSize;
|
this.totalSize += section.totalSize;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.calculatingSize = false;
|
this.calculatingSize = false;
|
||||||
|
|
||||||
// Mark course as not downloaded if course size is 0.
|
// Mark course as not downloaded if course size is 0.
|
||||||
|
@ -376,6 +357,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
|
|
||||||
|
await this.calculateSectionsStatus(sections);
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -402,7 +386,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
return;
|
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);
|
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.
|
* Deletes the specified modules, showing the loading overlay while it happens.
|
||||||
*
|
*
|
||||||
* @param modules Modules to delete
|
* @param modules Modules to delete
|
||||||
* @returns Promise<void> Once deleting has finished
|
|
||||||
*/
|
*/
|
||||||
protected async deleteModules(modules: AddonStorageManagerModule[]): Promise<void> {
|
protected async deleteModules(modules: AddonStorageManagerModule[]): Promise<void> {
|
||||||
const modal = await CoreLoadings.show('core.deleting', true);
|
const modal = await CoreLoadings.show('core.deleting', true);
|
||||||
|
|
||||||
|
const sections: AddonStorageManagerCourseSection[] = [];
|
||||||
const promises = modules.map(async (module) => {
|
const promises = modules.map(async (module) => {
|
||||||
// Remove the files.
|
// Remove the files.
|
||||||
await CoreCourseHelper.removeModuleStoredData(module, this.courseId);
|
await CoreCourseHelper.removeModuleStoredData(module, this.courseId);
|
||||||
|
|
||||||
module.totalSize = 0;
|
module.totalSize = 0;
|
||||||
|
|
||||||
|
const sectionFinder = CoreCourseHelper.findSectionWithSubsection(this.sections, module.section);
|
||||||
|
if (sectionFinder?.section) {
|
||||||
|
sections.push(sectionFinder?.section);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -508,8 +497,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
} finally {
|
} finally {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
|
|
||||||
await this.updateModulesSizes(modules);
|
await this.updateSizes(sections);
|
||||||
CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
@ -526,39 +514,26 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
CoreCourse.setCourseStatus(this.courseId, DownloadStatus.DOWNLOADABLE_NOT_DOWNLOADED);
|
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.
|
* Confirm and prefetch a section. If the section is "all sections", prefetch all the sections.
|
||||||
*
|
*
|
||||||
* @param section Section to download.
|
* @param section Section to download.
|
||||||
*/
|
*/
|
||||||
async prefecthSection(section: AddonStorageManagerCourseSection): Promise<void> {
|
async prefetchSection(section: AddonStorageManagerCourseSection): Promise<void> {
|
||||||
section.isCalculating = true;
|
section.isCalculating = true;
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
try {
|
try {
|
||||||
await CoreCourseHelper.confirmDownloadSizeSection(this.courseId, section, this.sections);
|
await CoreCourseHelper.confirmDownloadSizeSection(this.courseId, [section]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await CoreCourseHelper.prefetchSection(section, this.courseId, this.sections);
|
await CoreCourseHelper.prefetchSections([section], this.courseId);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!this.isDestroyed) {
|
if (!this.isDestroyed) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true);
|
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await this.updateModulesSizes(section.modules);
|
await this.updateSizes([section]);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// User cancelled or there was an error calculating the size.
|
// User cancelled or there was an error calculating the size.
|
||||||
|
@ -594,12 +569,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get download size to ask for confirm if it's high.
|
// Get download size to ask for confirm if it's high.
|
||||||
|
|
||||||
const size = await module.prefetchHandler.getDownloadSize(module, module.course, true);
|
const size = await module.prefetchHandler.getDownloadSize(module, module.course, true);
|
||||||
|
|
||||||
await CoreCourseHelper.prefetchModule(module.prefetchHandler, module, size, module.course, refresh);
|
await CoreCourseHelper.prefetchModule(module.prefetchHandler, module, size, module.course, refresh);
|
||||||
|
|
||||||
CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!this.isDestroyed) {
|
if (!this.isDestroyed) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||||
|
@ -607,7 +579,10 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
} finally {
|
} finally {
|
||||||
module.spinner = false;
|
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> {
|
protected async calculateModulesStatusOnSection(section: AddonStorageManagerCourseSection): Promise<void> {
|
||||||
await Promise.all(section.modules.map(async (module) => {
|
await Promise.all(section.modules.map(async (module) => {
|
||||||
if (module.subSection) {
|
if (module.handlerData?.showDownloadButton) {
|
||||||
await this.calculateModulesStatusOnSection(module.subSection);
|
|
||||||
} else if (module.handlerData?.showDownloadButton) {
|
|
||||||
module.spinner = true;
|
module.spinner = true;
|
||||||
// Listen for changes on this module status, even if download isn't enabled.
|
// Listen for changes on this module status, even if download isn't enabled.
|
||||||
module.prefetchHandler = CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(module.modname);
|
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<void> {
|
|
||||||
if (!module) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(module, this.courseId);
|
|
||||||
|
|
||||||
this.updateModuleStatus(module, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the prefetch icon of the course.
|
* Determines the prefetch icon of the course.
|
||||||
*
|
|
||||||
* @returns Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async determineCoursePrefetchIcon(): Promise<void> {
|
protected async determineCoursePrefetchIcon(): Promise<void> {
|
||||||
this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.courseId);
|
this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.courseId);
|
||||||
|
@ -739,8 +700,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const modules = this.getAllModulesList();
|
await this.updateSizes(this.sections);
|
||||||
await this.updateModulesSizes(modules);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.isDestroyed) {
|
if (this.isDestroyed) {
|
||||||
return;
|
return;
|
||||||
|
@ -753,21 +713,20 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
/**
|
/**
|
||||||
* Get all modules list.
|
* Get all modules list.
|
||||||
*
|
*
|
||||||
|
* @param sections Sections to get the modules from.
|
||||||
* @returns All modules list.
|
* @returns All modules list.
|
||||||
*/
|
*/
|
||||||
protected getAllModulesList(): AddonStorageManagerModule[] {
|
protected getAllModulesList(sections: AddonStorageManagerCourseSection[]): AddonStorageManagerModule[] {
|
||||||
const modules: AddonStorageManagerModule[] = [];
|
const modules: AddonStorageManagerModule[] = [];
|
||||||
this.sections.forEach((section) => {
|
sections.forEach((section) => {
|
||||||
section.modules.forEach((module) => {
|
section.modules.forEach((module) => {
|
||||||
|
modules.push(module);
|
||||||
|
|
||||||
if (module.subSection) {
|
if (module.subSection) {
|
||||||
module.subSection.modules.forEach((module) => {
|
module.subSection.modules.forEach((module) => {
|
||||||
modules.push(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.
|
* @param module Module to calculate.
|
||||||
*/
|
*/
|
||||||
protected async calculateModuleSize(module: AddonStorageManagerModule): Promise<void> {
|
protected async calculateModuleSize(module: AddonStorageManagerModule): Promise<void> {
|
||||||
|
if (module.calculatingSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
module.calculatingSize = true;
|
module.calculatingSize = true;
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
|
||||||
// Note: This function only gets the size for modules which are downloadable.
|
// 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.
|
// 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.
|
// But these aren't necessarily consistent, for example mod_frog vs mmaModFrog.
|
||||||
// There is nothing enforcing correct values.
|
// There is nothing enforcing correct values.
|
||||||
// Most modules which have large files are downloadable, so I think this is sufficient.
|
// 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;
|
module.calculatingSize = false;
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -845,6 +804,39 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
|
||||||
this.isDestroyed = true;
|
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'> & {
|
type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & {
|
||||||
|
|
|
@ -17,7 +17,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreQueueRunner } from '@classes/queue-runner';
|
import { CoreQueueRunner } from '@classes/queue-runner';
|
||||||
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
||||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
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 { CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses';
|
||||||
import { CoreSettingsHelper, CoreSiteSpaceUsage } from '@features/settings/services/settings-helper';
|
import { CoreSettingsHelper, CoreSiteSpaceUsage } from '@features/settings/services/settings-helper';
|
||||||
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
|
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
|
||||||
|
@ -241,14 +240,8 @@ export class AddonStorageManagerCoursesStoragePage implements OnInit, OnDestroy
|
||||||
private async calculateDownloadedCourseSize(courseId: number): Promise<number> {
|
private async calculateDownloadedCourseSize(courseId: number): Promise<number> {
|
||||||
const sections = await CoreCourse.getSections(courseId);
|
const sections = await CoreCourse.getSections(courseId);
|
||||||
const modules = CoreCourseHelper.getSectionsModules(sections);
|
const modules = CoreCourseHelper.getSectionsModules(sections);
|
||||||
const promisedModuleSizes = modules.map(async (module) => {
|
|
||||||
const size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, courseId);
|
|
||||||
|
|
||||||
return isNaN(size) ? 0 : size;
|
return CoreCourseHelper.getModulesDownloadedSize(modules, courseId);
|
||||||
});
|
|
||||||
const moduleSizes = await Promise.all(promisedModuleSizes);
|
|
||||||
|
|
||||||
return moduleSizes.reduce((totalSize, moduleSize) => totalSize + moduleSize, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -142,11 +142,7 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moduleSize = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(this.module, this.courseId);
|
this.size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(this.module, this.courseId);
|
||||||
|
|
||||||
if (moduleSize) {
|
|
||||||
this.size = moduleSize;
|
|
||||||
}
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
this.fileStatusObserver = CoreEvents.on(
|
this.fileStatusObserver = CoreEvents.on(
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
CoreCourseModuleCompletionStatus,
|
CoreCourseModuleCompletionStatus,
|
||||||
CoreCourseGetContentsWSModule,
|
CoreCourseGetContentsWSModule,
|
||||||
sectionContentIsModule,
|
sectionContentIsModule,
|
||||||
|
CoreCourseAnyModuleData,
|
||||||
} from './course';
|
} from './course';
|
||||||
import { CoreConstants, DownloadStatus, ContextLevel } from '@/core/constants';
|
import { CoreConstants, DownloadStatus, ContextLevel } from '@/core/constants';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
|
@ -283,14 +284,14 @@ export class CoreCourseHelperProvider {
|
||||||
refresh?: boolean,
|
refresh?: boolean,
|
||||||
checkUpdates: boolean = true,
|
checkUpdates: boolean = true,
|
||||||
): Promise<{statusData: CoreCourseModulesStatus; section: CoreCourseSectionWithStatus}> {
|
): Promise<{statusData: CoreCourseModulesStatus; section: CoreCourseSectionWithStatus}> {
|
||||||
if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) {
|
if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||||
throw new CoreError('Invalid section');
|
throw new CoreError('Invalid section');
|
||||||
}
|
}
|
||||||
|
|
||||||
const sectionWithStatus = <CoreCourseSectionWithStatus> section;
|
const sectionWithStatus = <CoreCourseSectionWithStatus> section;
|
||||||
|
|
||||||
// Get the status of this section.
|
// Get the status of this section.
|
||||||
const result = await CoreCourseModulePrefetchDelegate.getModulesStatus(
|
const statusData = await CoreCourseModulePrefetchDelegate.getModulesStatus(
|
||||||
section.contents,
|
section.contents,
|
||||||
courseId,
|
courseId,
|
||||||
section.id,
|
section.id,
|
||||||
|
@ -302,13 +303,13 @@ export class CoreCourseHelperProvider {
|
||||||
// Check if it's being downloaded.
|
// Check if it's being downloaded.
|
||||||
const downloadId = this.getSectionDownloadId(section);
|
const downloadId = this.getSectionDownloadId(section);
|
||||||
if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
|
if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
|
||||||
result.status = DownloadStatus.DOWNLOADING;
|
statusData.status = DownloadStatus.DOWNLOADING;
|
||||||
}
|
}
|
||||||
|
|
||||||
sectionWithStatus.downloadStatus = result.status;
|
sectionWithStatus.downloadStatus = statusData.status;
|
||||||
|
|
||||||
// Set this section data.
|
// Set this section data.
|
||||||
if (result.status !== DownloadStatus.DOWNLOADING) {
|
if (statusData.status !== DownloadStatus.DOWNLOADING) {
|
||||||
sectionWithStatus.isDownloading = false;
|
sectionWithStatus.isDownloading = false;
|
||||||
sectionWithStatus.total = 0;
|
sectionWithStatus.total = 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -320,62 +321,7 @@ export class CoreCourseHelperProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { statusData: result, section: sectionWithStatus };
|
return { statusData, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -411,7 +357,7 @@ export class CoreCourseHelperProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm the download.
|
// 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.
|
// User confirmed, get the course handlers if needed.
|
||||||
if (!options.courseHandlers) {
|
if (!options.courseHandlers) {
|
||||||
|
@ -508,48 +454,36 @@ export class CoreCourseHelperProvider {
|
||||||
* Calculate the size to download a section and show a confirm modal if needed.
|
* Calculate the size to download a section and show a confirm modal if needed.
|
||||||
*
|
*
|
||||||
* @param courseId Course ID the section belongs to.
|
* @param courseId Course ID the section belongs to.
|
||||||
* @param section Section. If not provided, all sections.
|
* @param sections List of sections to download
|
||||||
* @param sections List of sections. Used when downloading all the sections.
|
|
||||||
* @param alwaysConfirm True to show a confirm even if the size isn't high, false otherwise.
|
* @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.
|
* @returns Promise resolved if the user confirms or there's no need to confirm.
|
||||||
*/
|
*/
|
||||||
async confirmDownloadSizeSection(
|
async confirmDownloadSizeSection(
|
||||||
courseId: number,
|
courseId: number,
|
||||||
section?: CoreCourseWSSection,
|
sections: CoreCourseWSSection[] = [],
|
||||||
sections?: CoreCourseWSSection[],
|
alwaysConfirm = false,
|
||||||
alwaysConfirm?: boolean,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let hasEmbeddedFiles = false;
|
let hasEmbeddedFiles = false;
|
||||||
let sizeSum: CoreFileSizeSum = {
|
const sizeSum: CoreFileSizeSum = {
|
||||||
size: 0,
|
size: 0,
|
||||||
total: true,
|
total: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate the size of the download.
|
await Promise.all(sections.map(async (section) => {
|
||||||
if (section && section.id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||||
sizeSum = await CoreCourseModulePrefetchDelegate.getDownloadSize(section.contents, courseId);
|
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.
|
// Check if the section has embedded files in the description.
|
||||||
hasEmbeddedFiles = CoreFilepool.extractDownloadableFilesFromHtml(section.summary).length > 0;
|
if (!hasEmbeddedFiles && CoreFilepool.extractDownloadableFilesFromHtml(section.summary).length > 0) {
|
||||||
} else if (sections) {
|
hasEmbeddedFiles = true;
|
||||||
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) {
|
if (hasEmbeddedFiles) {
|
||||||
sizeSum.total = false;
|
sizeSum.total = false;
|
||||||
|
@ -559,6 +493,20 @@ export class CoreCourseHelperProvider {
|
||||||
await CoreDomUtils.confirmDownloadSize(sizeSum, undefined, undefined, undefined, undefined, alwaysConfirm);
|
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.
|
* 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));
|
await CoreUtils.ignoreErrors(CoreCourseModulePrefetchDelegate.invalidateCourseUpdates(courseId));
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await Promise.all([
|
const [size, status, packageData] = await Promise.all([
|
||||||
CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, courseId),
|
CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, courseId),
|
||||||
CoreCourseModulePrefetchDelegate.getModuleStatus(module, courseId),
|
CoreCourseModulePrefetchDelegate.getModuleStatus(module, courseId),
|
||||||
this.getModulePackageLastDownloaded(module, component),
|
this.getModulePackageLastDownloaded(module, component),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Treat stored size.
|
// Treat stored size.
|
||||||
const size = results[0];
|
const sizeReadable = CoreText.bytesToSize(size, 2);
|
||||||
const sizeReadable = CoreText.bytesToSize(results[0], 2);
|
|
||||||
|
|
||||||
// Treat module status.
|
// Treat module status.
|
||||||
const status = results[1];
|
|
||||||
let statusIcon: string | undefined;
|
let statusIcon: string | undefined;
|
||||||
switch (results[1]) {
|
switch (status) {
|
||||||
case DownloadStatus.DOWNLOADABLE_NOT_DOWNLOADED:
|
case DownloadStatus.DOWNLOADABLE_NOT_DOWNLOADED:
|
||||||
statusIcon = CoreConstants.ICON_NOT_DOWNLOADED;
|
statusIcon = CoreConstants.ICON_NOT_DOWNLOADED;
|
||||||
break;
|
break;
|
||||||
|
@ -1380,8 +1326,6 @@ export class CoreCourseHelperProvider {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageData = results[2];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
size,
|
size,
|
||||||
sizeReadable,
|
sizeReadable,
|
||||||
|
@ -1625,12 +1569,7 @@ export class CoreCourseHelperProvider {
|
||||||
|
|
||||||
const promises: Promise<unknown>[] = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
// Prefetch all the sections. If the first section is "All sections", use it. Otherwise, use a fake "All sections".
|
promises.push(this.prefetchSections(sections, course.id, true));
|
||||||
let allSectionsSection: CoreCourseWSSection = sections[0];
|
|
||||||
if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) {
|
|
||||||
allSectionsSection = this.createAllSectionsSection();
|
|
||||||
}
|
|
||||||
promises.push(this.prefetchSection(allSectionsSection, course.id, sections));
|
|
||||||
|
|
||||||
// Prefetch course options.
|
// Prefetch course options.
|
||||||
courseHandlers.forEach((handler) => {
|
courseHandlers.forEach((handler) => {
|
||||||
|
@ -1700,41 +1639,32 @@ export class CoreCourseHelperProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefetch one section or all the sections.
|
* Prefetch some sections
|
||||||
* If the section is "All sections" it will prefetch all the sections.
|
|
||||||
*
|
*
|
||||||
* @param section Section.
|
* @param sections List of sections. .
|
||||||
* @param courseId Course ID the section belongs to.
|
* @param courseId Course ID the section belongs to.
|
||||||
* @param sections List of sections. Used when downloading all the sections.
|
* @param updateAllSections Update all sections status
|
||||||
* @returns Promise resolved when the prefetch is finished.
|
|
||||||
*/
|
*/
|
||||||
async prefetchSection(
|
async prefetchSections(
|
||||||
section: CoreCourseSectionWithStatus,
|
sections: (CoreCourseSectionWithStatus & CoreCourseSectionWithSubsections)[],
|
||||||
courseId: number,
|
courseId: number,
|
||||||
sections?: CoreCourseSectionWithStatus[],
|
updateAllSections = false,
|
||||||
): Promise<void> {
|
): 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 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) => {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1753,10 +1683,14 @@ export class CoreCourseHelperProvider {
|
||||||
await CoreUtils.allPromises(promises);
|
await CoreUtils.allPromises(promises);
|
||||||
|
|
||||||
// Set "All sections" data.
|
// Set "All sections" data.
|
||||||
section.downloadStatus = allSectionsStatus;
|
if (allSectionsSection) {
|
||||||
section.isDownloading = allSectionsStatus === DownloadStatus.DOWNLOADING;
|
allSectionsSection.downloadStatus = allSectionsStatus;
|
||||||
|
allSectionsSection.isDownloading = allSectionsStatus === DownloadStatus.DOWNLOADING;
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
section.isDownloading = false;
|
if (allSectionsSection) {
|
||||||
|
allSectionsSection.isDownloading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1769,7 +1703,7 @@ export class CoreCourseHelperProvider {
|
||||||
* @returns Promise resolved when the section is prefetched.
|
* @returns Promise resolved when the section is prefetched.
|
||||||
*/
|
*/
|
||||||
protected async prefetchSingleSectionIfNeeded(section: CoreCourseSectionWithStatus, courseId: number): Promise<void> {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1830,7 +1764,7 @@ export class CoreCourseHelperProvider {
|
||||||
result: CoreCourseModulesStatus,
|
result: CoreCourseModulesStatus,
|
||||||
courseId: number,
|
courseId: number,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) {
|
if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -472,17 +472,21 @@ export class CoreCourseModulePrefetchDelegateService extends CoreDelegate<CoreCo
|
||||||
* @returns Promise resolved with the total size (0 if unknown)
|
* @returns Promise resolved with the total size (0 if unknown)
|
||||||
*/
|
*/
|
||||||
async getModuleStoredSize(module: CoreCourseAnyModuleData, courseId: number): Promise<number> {
|
async getModuleStoredSize(module: CoreCourseAnyModuleData, courseId: number): Promise<number> {
|
||||||
const site = CoreSites.getCurrentSite();
|
try {
|
||||||
const handler = this.getPrefetchHandlerFor(module.modname);
|
const site = CoreSites.getCurrentSite();
|
||||||
|
const handler = this.getPrefetchHandlerFor(module.modname);
|
||||||
|
|
||||||
const [downloadedSize, cachedSize] = await Promise.all([
|
const [downloadedSize, cachedSize] = await Promise.all([
|
||||||
this.getModuleDownloadedSize(module, courseId),
|
this.getModuleDownloadedSize(module, courseId),
|
||||||
handler && site ? site.getComponentCacheSize(handler.component, module.id) : 0,
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue