MOBILE-3833 course: Improve performance after download or delete

main
Dani Palou 2022-03-28 12:54:14 +02:00
parent 1a07331396
commit 37af8e3c69
2 changed files with 82 additions and 32 deletions

View File

@ -25,8 +25,8 @@
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totaldownloads' | translate }}</p> <p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totaldownloads' | translate }}</p>
</ion-label> </ion-label>
<ion-badge color="light" slot="end"> <ion-badge color="light" slot="end">
<ng-container *ngIf="sizeLoaded">{{ totalSize | coreBytesToSize }}</ng-container> <ng-container *ngIf="!calculatingSize">{{ totalSize | coreBytesToSize }}</ng-container>
<ng-container *ngIf="!sizeLoaded">{{ 'core.calculating' | translate }}</ng-container> <ng-container *ngIf="calculatingSize">{{ 'core.calculating' | translate }}</ng-container>
</ion-badge> </ion-badge>
</ion-item> </ion-item>
<ion-button *ngIf="downloadCourseEnabled" (click)="prefetchCourse()" expand="block" fill="outline" class="ion-no-margin" <ion-button *ngIf="downloadCourseEnabled" (click)="prefetchCourse()" expand="block" fill="outline" class="ion-no-margin"
@ -35,7 +35,7 @@
<ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner> <ion-spinner *ngIf="prefetchCourseData.loading" slot="start"></ion-spinner>
{{ prefetchCourseData.statusTranslatable | translate }} {{ prefetchCourseData.statusTranslatable | translate }}
</ion-button> </ion-button>
<ion-button *ngIf="sizeLoaded && totalSize > 0" (click)="deleteForCourse()" expand="block" color="danger" <ion-button [disabled]="calculatingSize || totalSize <= 0" (click)="deleteForCourse()" expand="block" color="danger"
class="ion-no-margin ion-margin-top"> class="ion-no-margin ion-margin-top">
<ion-icon name="fas-trash" slot="start" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: <ion-icon name="fas-trash" slot="start" [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate:
{ name: title }"> { name: title }">
@ -55,12 +55,12 @@
</core-format-text> </core-format-text>
</p> </p>
<ion-badge [color]="section.downloadStatus == statusDownloaded ? 'success' : 'light'" <ion-badge [color]="section.downloadStatus == statusDownloaded ? 'success' : 'light'"
*ngIf="section.sizeLoaded && section.totalSize > 0"> *ngIf="!section.calculatingSize && section.totalSize > 0">
<ion-icon name="fam-cloud-done" *ngIf="section.downloadStatus == statusDownloaded" <ion-icon name="fam-cloud-done" *ngIf="section.downloadStatus == statusDownloaded"
[attr.aria-label]="'core.downloaded' | translate"> [attr.aria-label]="'core.downloaded' | translate">
</ion-icon>{{ section.totalSize | coreBytesToSize }} </ion-icon>{{ section.totalSize | coreBytesToSize }}
</ion-badge> </ion-badge>
<ion-badge color="light" *ngIf="!section.sizeLoaded"> <ion-badge color="light" *ngIf="section.calculatingSize">
{{ 'core.calculating' | translate }} {{ 'core.calculating' | translate }}
</ion-badge> </ion-badge>
<!-- Download progress. --> <!-- Download progress. -->
@ -69,7 +69,8 @@
</core-progress-bar> </core-progress-bar>
</p> </p>
</ion-label> </ion-label>
<div class="storage-buttons" slot="end" *ngIf="(section.sizeLoaded && 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)="prefecthSection(section)"
@ -83,7 +84,7 @@
{{section.count}} / {{section.total}} {{section.count}} / {{section.total}}
</ion-badge> </ion-badge>
</div> </div>
<ion-button (click)="deleteForSection(section)" *ngIf="section.sizeLoaded && section.totalSize > 0" <ion-button (click)="deleteForSection(section)" *ngIf="!section.calculatingSize && section.totalSize > 0"
color="danger" fill="clear"> color="danger" fill="clear">
<ion-icon name="fas-trash" slot="icon-only" <ion-icon name="fas-trash" slot="icon-only"
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }"> [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: section.name }">
@ -95,7 +96,7 @@
<ion-card-content> <ion-card-content>
<ng-container *ngFor="let module of section.modules"> <ng-container *ngFor="let module of section.modules">
<ion-item class="ion-no-padding core-course-storage-activity" <ion-item class="ion-no-padding core-course-storage-activity"
*ngIf="downloadEnabled || (module.sizeLoaded && module.totalSize > 0)"> *ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)">
<core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" <core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon"
[modname]="module.modname" [componentId]="module.instance"> [modname]="module.modname" [componentId]="module.instance">
</core-mod-icon> </core-mod-icon>
@ -106,12 +107,12 @@
</core-format-text> </core-format-text>
</h3> </h3>
<ion-badge [color]="module.downloadStatus == statusDownloaded ? 'success' : 'light'" <ion-badge [color]="module.downloadStatus == statusDownloaded ? 'success' : 'light'"
*ngIf="module.sizeLoaded && module.totalSize > 0"> *ngIf="!module.calculatingSize && module.totalSize > 0">
<ion-icon name="fam-cloud-done" *ngIf="module.downloadStatus == statusDownloaded" <ion-icon name="fam-cloud-done" *ngIf="module.downloadStatus == statusDownloaded"
[attr.aria-label]="'core.downloaded' | translate"> [attr.aria-label]="'core.downloaded' | translate">
</ion-icon>{{ module.totalSize | coreBytesToSize }} </ion-icon>{{ module.totalSize | coreBytesToSize }}
</ion-badge> </ion-badge>
<ion-badge color="light" *ngIf="!module.sizeLoaded"> <ion-badge color="light" *ngIf="module.calculatingSize">
{{ 'core.calculating' | translate }} {{ 'core.calculating' | translate }}
</ion-badge> </ion-badge>
</ion-label> </ion-label>
@ -123,7 +124,7 @@
(action)="prefetchModule(module, section)"> (action)="prefetchModule(module, section)">
</core-download-refresh> </core-download-refresh>
<ion-button fill="clear" (click)="deleteForModule(module, section)" <ion-button fill="clear" (click)="deleteForModule(module, section)"
*ngIf="module.sizeLoaded && module.totalSize > 0" color="danger"> *ngIf="!module.calculatingSize && module.totalSize > 0" color="danger">
<ion-icon name="fas-trash" slot="icon-only" <ion-icon name="fas-trash" slot="icon-only"
[attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }"> [attr.aria-label]="'addon.storagemanager.deletedatafrom' | translate: { name: module.name }">
</ion-icon> </ion-icon>

View File

@ -48,7 +48,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
loaded = false; loaded = false;
sections: AddonStorageManagerCourseSection[] = []; sections: AddonStorageManagerCourseSection[] = [];
totalSize = 0; totalSize = 0;
sizeLoaded = false; calculatingSize = true;
downloadEnabled = false; downloadEnabled = false;
downloadCourseEnabled = false; downloadCourseEnabled = false;
@ -106,12 +106,20 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
const sections = await CoreCourse.getSections(this.courseId, false, true); const sections = await CoreCourse.getSections(this.courseId, false, true);
this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
.map((section) => ({ ...section, totalSize: 0 })); .map(section => ({
...section,
totalSize: 0,
calculatingSize: true,
modules: section.modules.map(module => ({
...module,
calculatingSize: true,
})),
}));
this.loaded = true; this.loaded = true;
await Promise.all([ await Promise.all([
this.loadSizes(), this.initSizes(),
this.initCoursePrefetch(), this.initCoursePrefetch(),
this.initModulePrefetch(), this.initModulePrefetch(),
]); ]);
@ -240,18 +248,9 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
/** /**
* Init section, course and modules sizes. * Init section, course and modules sizes.
*/ */
protected async loadSizes(): Promise<void> { protected async initSizes(): Promise<void> {
this.totalSize = 0;
this.sizeLoaded = false;
await Promise.all(this.sections.map(async (section) => { await Promise.all(this.sections.map(async (section) => {
section.totalSize = 0;
section.sizeLoaded = false;
await Promise.all(section.modules.map(async (module) => { await Promise.all(section.modules.map(async (module) => {
module.totalSize = 0;
module.sizeLoaded = false;
// 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.
// However there is no 100% reliable way to actually track the files in this case. // However there is no 100% reliable way to actually track the files in this case.
@ -268,13 +267,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
this.totalSize += size; this.totalSize += size;
} }
module.sizeLoaded = true; module.calculatingSize = false;
})); }));
section.sizeLoaded = true; section.calculatingSize = false;
})); }));
this.sizeLoaded = true; this.calculatingSize = false;
// Mark course as not downloaded if course size is 0. // Mark course as not downloaded if course size is 0.
if (this.totalSize == 0) { if (this.totalSize == 0) {
@ -282,6 +281,56 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
} }
} }
/**
* Update the sizes of some modules.
*
* @param modules Modules.
* @param section Section the modules belong to.
* @return Promise resolved when done.
*/
protected async updateModulesSizes(
modules: AddonStorageManagerModule[],
section?: AddonStorageManagerCourseSection,
): Promise<void> {
this.calculatingSize = true;
await Promise.all(modules.map(async (module) => {
if (module.calculatingSize) {
return;
}
module.calculatingSize = true;
if (!section) {
section = this.sections.find((section) => section.modules.some((mod) => mod.id === module.id));
if (section) {
section.calculatingSize = true;
}
}
try {
const size = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, this.courseId);
const diff = (isNaN(size) ? 0 : size) - (module.totalSize ?? 0);
module.totalSize = Number(size);
this.totalSize += diff;
if (section) {
section.totalSize += diff;
}
} catch {
// Ignore errors, it shouldn't happen.
} finally {
module.calculatingSize = false;
}
}));
this.calculatingSize = false;
if (section) {
section.calculatingSize = false;
}
}
/** /**
* The user has requested a delete for the whole course data. * The user has requested a delete for the whole course data.
* *
@ -406,7 +455,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
} finally { } finally {
modal.dismiss(); modal.dismiss();
await this.loadSizes(); await this.updateModulesSizes(modules, section);
CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false); CoreCourseHelper.calculateSectionsStatus(this.sections, this.courseId, false, false);
} }
} }
@ -455,7 +504,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true); CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingsection', true);
} }
} finally { } finally {
await this.loadSizes(); await this.updateModulesSizes(section.modules, 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.
@ -501,7 +550,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
} finally { } finally {
module.spinner = false; module.spinner = false;
await this.loadSizes(); await this.updateModulesSizes([module]);
} }
} }
@ -613,13 +662,13 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & { type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & {
totalSize: number; totalSize: number;
sizeLoaded?: boolean; calculatingSize: boolean;
modules: AddonStorageManagerModule[]; modules: AddonStorageManagerModule[];
}; };
type AddonStorageManagerModule = CoreCourseModuleData & { type AddonStorageManagerModule = CoreCourseModuleData & {
totalSize?: number; totalSize?: number;
sizeLoaded?: boolean; calculatingSize: boolean;
prefetchHandler?: CoreCourseModulePrefetchHandler; prefetchHandler?: CoreCourseModulePrefetchHandler;
spinner?: boolean; spinner?: boolean;
downloadStatus?: string; downloadStatus?: string;