From 0893b2fb03f3f65ae79aee71dade6a8afc0885eb Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 5 Mar 2020 17:41:04 +0100 Subject: [PATCH] MOBILE-3375 course: Make download warnings less intrusive Also, now the download function is called only if it's really needed --- .../index/addon-mod-book-index.html | 4 + src/addon/mod/book/components/index/index.ts | 34 ++--- .../mod/folder/components/index/index.ts | 7 +- .../index/addon-mod-imscp-index.html | 5 + src/addon/mod/imscp/components/index/index.ts | 35 ++--- .../index/addon-mod-page-index.html | 4 + src/addon/mod/page/components/index/index.ts | 37 ++--- .../index/addon-mod-resource-index.html | 4 + .../mod/resource/components/index/index.ts | 31 ++-- src/addon/mod/url/components/index/index.ts | 9 +- .../course/classes/main-activity-component.ts | 62 -------- .../course/classes/main-resource-component.ts | 143 +++++++++++++++++- .../providers/module-prefetch-delegate.ts | 19 +++ 13 files changed, 230 insertions(+), 164 deletions(-) diff --git a/src/addon/mod/book/components/index/addon-mod-book-index.html b/src/addon/mod/book/components/index/addon-mod-book-index.html index cfd0ff551..ee1e5bbad 100644 --- a/src/addon/mod/book/components/index/addon-mod-book-index.html +++ b/src/addon/mod/book/components/index/addon-mod-book-index.html @@ -18,6 +18,10 @@ + + + +
diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts index 50c69e8bd..9b3fa4574 100644 --- a/src/addon/mod/book/components/index/index.ts +++ b/src/addon/mod/book/components/index/index.ts @@ -16,7 +16,9 @@ import { Component, Optional, Injector, Input } from '@angular/core'; import { Content, ModalController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreCourseProvider } from '@core/course/providers/course'; -import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component'; +import { + CoreCourseModuleMainResourceComponent, CoreCourseResourceDownloadResult +} from '@core/course/classes/main-resource-component'; import { AddonModBookProvider, AddonModBookContentsMap, AddonModBookTocChapter, AddonModBookBook, AddonModBookNavStyle } from '../../providers/book'; @@ -41,6 +43,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp displayNavBar = true; previousNavBarTitle: string; nextNavBarTitle: string; + warning: string; protected chapters: AddonModBookTocChapter[]; protected currentChapter: string; @@ -48,9 +51,11 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp protected book: AddonModBookBook; protected displayTitlesInNavBar = false; - constructor(injector: Injector, private bookProvider: AddonModBookProvider, private courseProvider: CoreCourseProvider, - private appProvider: CoreAppProvider, private prefetchDelegate: AddonModBookPrefetchHandler, - private modalCtrl: ModalController, private tagProvider: CoreTagProvider, @Optional() private content: Content) { + constructor(injector: Injector, + protected bookProvider: AddonModBookProvider, + protected modalCtrl: ModalController, + protected tagProvider: CoreTagProvider, + @Optional() protected content: Content) { super(injector); } @@ -126,8 +131,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp */ protected fetchContent(refresh?: boolean): Promise { const promises = []; - let downloadFailed = false; - let downloadFailError; + let downloadResult: CoreCourseResourceDownloadResult; // Try to get the book data. promises.push(this.bookProvider.getBook(this.courseId, this.module.id).then((book) => { @@ -140,16 +144,9 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp // Ignore errors since this WS isn't available in some Moodle versions. })); - // Download content. This function also loads module contents if needed. - promises.push(this.prefetchDelegate.download(this.module, this.courseId).catch((error) => { - // Mark download as failed but go on since the main files could have been downloaded. - downloadFailed = true; - downloadFailError = error; - - if (!this.module.contents.length) { - // Try to load module contents for offline usage. - return this.courseProvider.loadModuleContents(this.module, this.courseId); - } + // Get module status to determine if it needs to be downloaded. + promises.push(this.downloadResourceIfNeeded(refresh).then((result) => { + downloadResult = result; })); return Promise.all(promises).then(() => { @@ -174,10 +171,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp // Show chapter. return this.loadChapter(this.currentChapter, refresh).then(() => { - if (downloadFailed && this.appProvider.isOnline()) { - // We could load the main file but the download failed. Show error message. - this.showErrorDownloadingSomeFiles(downloadFailError); - } + this.warning = downloadResult.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error) : ''; }).catch(() => { // Ignore errors, they're handled inside the loadChapter function. }); diff --git a/src/addon/mod/folder/components/index/index.ts b/src/addon/mod/folder/components/index/index.ts index 622acb449..805210106 100644 --- a/src/addon/mod/folder/components/index/index.ts +++ b/src/addon/mod/folder/components/index/index.ts @@ -13,8 +13,6 @@ // limitations under the License. import { Component, Input, Injector } from '@angular/core'; -import { CoreAppProvider } from '@providers/app'; -import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component'; import { AddonModFolderProvider } from '../../providers/folder'; import { AddonModFolderHelperProvider } from '../../providers/helper'; @@ -36,8 +34,9 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo canGetFolder: boolean; contents: any; - constructor(injector: Injector, private folderProvider: AddonModFolderProvider, private courseProvider: CoreCourseProvider, - private appProvider: CoreAppProvider, private folderHelper: AddonModFolderHelperProvider) { + constructor(injector: Injector, + protected folderProvider: AddonModFolderProvider, + protected folderHelper: AddonModFolderHelperProvider) { super(injector); } diff --git a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html index 6be152d02..9f767d27c 100644 --- a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html +++ b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html @@ -15,6 +15,11 @@ + + + + +
diff --git a/src/addon/mod/imscp/components/index/index.ts b/src/addon/mod/imscp/components/index/index.ts index a17aa8de4..246721aab 100644 --- a/src/addon/mod/imscp/components/index/index.ts +++ b/src/addon/mod/imscp/components/index/index.ts @@ -14,11 +14,10 @@ import { Component, Injector } from '@angular/core'; import { ModalController } from 'ionic-angular'; -import { CoreAppProvider } from '@providers/app'; -import { CoreCourseProvider } from '@core/course/providers/course'; -import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component'; +import { + CoreCourseModuleMainResourceComponent, CoreCourseResourceDownloadResult +} from '@core/course/classes/main-resource-component'; import { AddonModImscpProvider } from '../../providers/imscp'; -import { AddonModImscpPrefetchHandler } from '../../providers/prefetch-handler'; /** * Component that displays a IMSCP. @@ -33,14 +32,15 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom items = []; currentItem: string; src = ''; + warning: string; // Initialize empty previous/next to prevent showing arrows for an instant before they're hidden. previousItem = ''; nextItem = ''; - constructor(injector: Injector, private imscpProvider: AddonModImscpProvider, private courseProvider: CoreCourseProvider, - private appProvider: CoreAppProvider, private modalCtrl: ModalController, - private imscpPrefetch: AddonModImscpPrefetchHandler) { + constructor(injector: Injector, + protected imscpProvider: AddonModImscpProvider, + protected modalCtrl: ModalController) { super(injector); } @@ -75,8 +75,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom * @return Promise resolved when done. */ protected fetchContent(refresh?: boolean): Promise { - let downloadFailed = false; - let downloadFailError; + let downloadResult: CoreCourseResourceDownloadResult; const promises = []; promises.push(this.imscpProvider.getImscp(this.courseId, this.module.id).then((imscp) => { @@ -84,17 +83,8 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom this.dataRetrieved.emit(imscp); })); - promises.push(this.imscpPrefetch.download(this.module, this.courseId).catch((error) => { - // Mark download as failed but go on since the main files could have been downloaded. - downloadFailed = true; - downloadFailError = error; - - return this.courseProvider.loadModuleContents(this.module, this.courseId).catch((error) => { - // Error getting module contents, fail. - this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); - - return Promise.reject(null); - }); + promises.push(this.downloadResourceIfNeeded(refresh).then((result) => { + downloadResult = result; })); return Promise.all(promises).then(() => { @@ -109,10 +99,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom return Promise.reject(null); }); }).then(() => { - if (downloadFailed && this.appProvider.isOnline()) { - // We could load the main file but the download failed. Show error message. - this.showErrorDownloadingSomeFiles(downloadFailError); - } + this.warning = downloadResult.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error) : ''; }).finally(() => { this.fillContextMenu(refresh); diff --git a/src/addon/mod/page/components/index/addon-mod-page-index.html b/src/addon/mod/page/components/index/addon-mod-page-index.html index 12f6ddbd3..d3d6c7726 100644 --- a/src/addon/mod/page/components/index/addon-mod-page-index.html +++ b/src/addon/mod/page/components/index/addon-mod-page-index.html @@ -15,6 +15,10 @@ + + + +

diff --git a/src/addon/mod/page/components/index/index.ts b/src/addon/mod/page/components/index/index.ts index b8300246a..27f84b9b9 100644 --- a/src/addon/mod/page/components/index/index.ts +++ b/src/addon/mod/page/components/index/index.ts @@ -13,13 +13,12 @@ // limitations under the License. import { Component, Injector } from '@angular/core'; -import { CoreAppProvider } from '@providers/app'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { CoreCourseProvider } from '@core/course/providers/course'; -import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component'; +import { + CoreCourseModuleMainResourceComponent, CoreCourseResourceDownloadResult +} from '@core/course/classes/main-resource-component'; import { AddonModPageProvider, AddonModPagePage } from '../../providers/page'; import { AddonModPageHelperProvider } from '../../providers/helper'; -import { AddonModPagePrefetchHandler } from '../../providers/prefetch-handler'; /** * Component that displays a page. @@ -35,12 +34,14 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp displayDescription = true; displayTimemodified = true; page: AddonModPagePage; + warning: string; protected fetchContentDefaultError = 'addon.mod_page.errorwhileloadingthepage'; - constructor(injector: Injector, private pageProvider: AddonModPageProvider, private courseProvider: CoreCourseProvider, - private appProvider: CoreAppProvider, private pageHelper: AddonModPageHelperProvider, - private pagePrefetch: AddonModPagePrefetchHandler, private utils: CoreUtilsProvider) { + constructor(injector: Injector, + protected pageProvider: AddonModPageProvider, + protected pageHelper: AddonModPageHelperProvider, + protected utils: CoreUtilsProvider) { super(injector); } @@ -77,19 +78,11 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp * @return Promise resolved when done. */ protected fetchContent(refresh?: boolean): Promise { - let downloadFailed = false; - let downloadFailError; + let downloadResult: CoreCourseResourceDownloadResult; - // Download content. This function also loads module contents if needed. - return this.pagePrefetch.download(this.module, this.courseId).catch((error) => { - // Mark download as failed but go on since the main files could have been downloaded. - downloadFailed = true; - downloadFailError = error; - }).then(() => { - if (!this.module.contents.length) { - // Try to load module contents for offline usage. - return this.courseProvider.loadModuleContents(this.module, this.courseId); - } + // Download the resource if it needs to be downloaded. + return this.downloadResourceIfNeeded(refresh).then((result) => { + downloadResult = result; }).then(() => { const promises = []; @@ -131,11 +124,7 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp promises.push(this.pageHelper.getPageHtml(this.module.contents, this.module.id).then((content) => { this.contents = content; - - if (downloadFailed && this.appProvider.isOnline()) { - // We could load the main file but the download failed. Show error message. - this.showErrorDownloadingSomeFiles(downloadFailError); - } + this.warning = downloadResult.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error) : ''; })); return Promise.all(promises); diff --git a/src/addon/mod/resource/components/index/addon-mod-resource-index.html b/src/addon/mod/resource/components/index/addon-mod-resource-index.html index 9fcfdaa83..e6aa1efff 100644 --- a/src/addon/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addon/mod/resource/components/index/addon-mod-resource-index.html @@ -15,6 +15,10 @@ + + + + diff --git a/src/addon/mod/resource/components/index/index.ts b/src/addon/mod/resource/components/index/index.ts index 8875bae06..58810ad3d 100644 --- a/src/addon/mod/resource/components/index/index.ts +++ b/src/addon/mod/resource/components/index/index.ts @@ -13,14 +13,12 @@ // limitations under the License. import { Component, Injector } from '@angular/core'; -import { CoreAppProvider } from '@providers/app'; import { CoreFilepoolProvider } from '@providers/filepool'; -import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { CoreCourseProvider } from '@core/course/providers/course'; -import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component'; +import { + CoreCourseModuleMainResourceComponent, CoreCourseResourceDownloadResult +} from '@core/course/classes/main-resource-component'; import { AddonModResourceProvider } from '../../providers/resource'; -import { AddonModResourcePrefetchHandler } from '../../providers/prefetch-handler'; import { AddonModResourceHelperProvider } from '../../providers/helper'; /** @@ -38,14 +36,11 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource src: string; contentText: string; displayDescription = true; + warning: string; constructor(injector: Injector, protected resourceProvider: AddonModResourceProvider, - protected courseProvider: CoreCourseProvider, - protected appProvider: CoreAppProvider, - protected prefetchHandler: AddonModResourcePrefetchHandler, protected resourceHelper: AddonModResourceHelperProvider, - protected sitesProvider: CoreSitesProvider, protected utils: CoreUtilsProvider, protected filepoolProvider: CoreFilepoolProvider) { super(injector); @@ -109,13 +104,10 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource } if (this.resourceHelper.isDisplayedInIframe(this.module)) { - let downloadFailed = false; - let downloadFailError; + let downloadResult: CoreCourseResourceDownloadResult; - return this.prefetchHandler.download(this.module, this.courseId).catch((error) => { - // Mark download as failed but go on since the main files could have been downloaded. - downloadFailed = true; - downloadFailError = error; + return this.downloadResourceIfNeeded(refresh, true).then((result) => { + downloadResult = result; }).then(() => { return this.resourceHelper.getIframeSrc(this.module).then((src) => { this.mode = 'iframe'; @@ -131,14 +123,12 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource this.src = src; } - if (downloadFailed && this.appProvider.isOnline()) { - // We could load the main file but the download failed. Show error message. - this.showErrorDownloadingSomeFiles(downloadFailError); - } + this.warning = downloadResult.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error) : ''; }); }); } else if (this.resourceHelper.isDisplayedEmbedded(this.module, resource && resource.display)) { this.mode = 'embedded'; + this.warning = ''; return this.resourceHelper.getEmbeddedHtml(this.module, this.courseId).then((html) => { this.contentText = html; @@ -147,6 +137,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource }); } else { this.mode = 'external'; + this.warning = ''; } }).finally(() => { this.fillContextMenu(refresh); @@ -159,7 +150,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource * @return Promise resolved when done. */ async open(): Promise { - let downloadable = await this.prefetchHandler.isDownloadable(this.module, this.courseId); + let downloadable = await this.modulePrefetchDelegate.isModuleDownloadable(this.module, this.courseId); if (downloadable) { // Check if the main file is downloadle. diff --git a/src/addon/mod/url/components/index/index.ts b/src/addon/mod/url/components/index/index.ts index b9381aae0..1275b3796 100644 --- a/src/addon/mod/url/components/index/index.ts +++ b/src/addon/mod/url/components/index/index.ts @@ -13,9 +13,7 @@ // limitations under the License. import { Component, Injector } from '@angular/core'; -import { CoreSitesProvider } from '@providers/sites'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; -import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component'; import { AddonModUrlProvider } from '../../providers/url'; import { AddonModUrlHelperProvider } from '../../providers/helper'; @@ -43,9 +41,10 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo mimetype: string; displayDescription = true; - constructor(injector: Injector, private urlProvider: AddonModUrlProvider, private courseProvider: CoreCourseProvider, - private urlHelper: AddonModUrlHelperProvider, private mimeUtils: CoreMimetypeUtilsProvider, - private sitesProvider: CoreSitesProvider) { + constructor(injector: Injector, + protected urlProvider: AddonModUrlProvider, + protected urlHelper: AddonModUrlHelperProvider, + protected mimeUtils: CoreMimetypeUtilsProvider) { super(injector); } diff --git a/src/core/course/classes/main-activity-component.ts b/src/core/course/classes/main-activity-component.ts index 5b2d7f830..f30bad08d 100644 --- a/src/core/course/classes/main-activity-component.ts +++ b/src/core/course/classes/main-activity-component.ts @@ -14,12 +14,7 @@ import { Injector, Input, NgZone } from '@angular/core'; import { Content } from 'ionic-angular'; -import { CoreSitesProvider } from '@providers/sites'; -import { CoreCourseProvider } from '@core/course/providers/course'; -import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; -import { CoreEventsProvider } from '@providers/events'; import { Network } from '@ionic-native/network'; -import { CoreAppProvider } from '@providers/app'; import { CoreCourseModuleMainResourceComponent } from './main-resource-component'; /** @@ -35,30 +30,13 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR hasOffline: boolean; // If it has offline data to be synced. isOnline: boolean; // If the app is online or not. - protected siteId: string; // Current Site ID. protected syncObserver: any; // It will observe the sync auto event. - protected statusObserver: any; // It will observe changes on the status of the activity. Only if setStatusListener is called. protected onlineObserver: any; // It will observe the status of the network connection. protected syncEventName: string; // Auto sync event name. - protected currentStatus: string; // The current status of the activity. Only if setStatusListener is called. - - // List of services that will be injected using injector. - // It's done like this so subclasses don't have to send all the services to the parent in the constructor. - protected sitesProvider: CoreSitesProvider; - protected courseProvider: CoreCourseProvider; - protected appProvider: CoreAppProvider; - protected eventsProvider: CoreEventsProvider; - protected modulePrefetchDelegate: CoreCourseModulePrefetchDelegate; constructor(injector: Injector, protected content?: Content, loggerName: string = 'CoreCourseModuleMainResourceComponent') { super(injector, loggerName); - this.sitesProvider = injector.get(CoreSitesProvider); - this.courseProvider = injector.get(CoreCourseProvider); - this.appProvider = injector.get(CoreAppProvider); - this.eventsProvider = injector.get(CoreEventsProvider); - this.modulePrefetchDelegate = injector.get(CoreCourseModulePrefetchDelegate); - const network = injector.get(Network); const zone = injector.get(NgZone); @@ -79,7 +57,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR this.hasOffline = false; this.syncIcon = 'spinner'; - this.siteId = this.sitesProvider.getCurrentSiteId(); this.moduleName = this.courseProvider.translateModuleName(this.moduleName); if (this.syncEventName) { @@ -242,44 +219,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR }); } - /** - * Displays some data based on the current status. - * - * @param status The current status. - * @param previousStatus The previous status. If not defined, there is no previous status. - */ - protected showStatus(status: string, previousStatus?: string): void { - // To be overridden. - } - - /** - * Watch for changes on the status. - * - * @return Promise resolved when done. - */ - protected setStatusListener(): Promise { - if (typeof this.statusObserver == 'undefined') { - // Listen for changes on this module status. - this.statusObserver = this.eventsProvider.on(CoreEventsProvider.PACKAGE_STATUS_CHANGED, (data) => { - if (data.componentId === this.module.id && data.component === this.component) { - // The status has changed, update it. - const previousStatus = this.currentStatus; - this.currentStatus = data.status; - - this.showStatus(this.currentStatus, previousStatus); - } - }, this.siteId); - - // Also, get the current status. - return this.modulePrefetchDelegate.getModuleStatus(this.module, this.courseId).then((status) => { - this.currentStatus = status; - this.showStatus(status); - }); - } - - return Promise.resolve(); - } - /** * Performs the sync of the activity. * @@ -329,6 +268,5 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR this.onlineObserver && this.onlineObserver.unsubscribe(); this.syncObserver && this.syncObserver.off(); - this.statusObserver && this.statusObserver.off(); } } diff --git a/src/core/course/classes/main-resource-component.ts b/src/core/course/classes/main-resource-component.ts index 1bbff628d..b84b2964e 100644 --- a/src/core/course/classes/main-resource-component.ts +++ b/src/core/course/classes/main-resource-component.ts @@ -15,16 +15,29 @@ import { OnInit, OnDestroy, Input, Output, EventEmitter, Injector } from '@angular/core'; import { NavController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '@providers/app'; +import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider, CoreTextErrorObject } from '@providers/utils/text'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseModuleMainComponent, CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; +import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreCourseSectionPage } from '@core/course/pages/section/section.ts'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { AddonBlogProvider } from '@addon/blog/providers/blog'; import { CoreConstants } from '@core/constants'; +/** + * Result of a resource download. + */ +export type CoreCourseResourceDownloadResult = { + failed?: boolean; // Whether the download has failed. + error?: string | CoreTextErrorObject; // The error in case it failed. +}; + /** * Template class to easily create CoreCourseModuleMainComponent of resources (or activities without syncing). */ @@ -52,6 +65,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, protected contextFileStatusObserver; // Observer of file status changed, used when calling fillContextMenu. protected fetchContentDefaultError = 'core.course.errorgetmodule'; // Default error to show when loading contents. protected isCurrentView: boolean; // Whether the component is in the current view. + protected siteId: string; // Current Site ID. + protected statusObserver: any; // It will observe changes on the status of the module. Only if setStatusListener is called. + protected currentStatus: string; // The current status of the module. Only if setStatusListener is called. // List of services that will be injected using injector. // It's done like this so subclasses don't have to send all the services to the parent in the constructor. @@ -64,6 +80,11 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, protected linkHelper: CoreContentLinksHelperProvider; protected navCtrl: NavController; protected blogProvider: AddonBlogProvider; + protected sitesProvider: CoreSitesProvider; + protected eventsProvider: CoreEventsProvider; + protected modulePrefetchDelegate: CoreCourseModulePrefetchDelegate; + protected courseProvider: CoreCourseProvider; + protected appProvider: CoreAppProvider; protected logger; @@ -77,6 +98,12 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.linkHelper = injector.get(CoreContentLinksHelperProvider); this.navCtrl = injector.get(NavController, null); this.blogProvider = injector.get(AddonBlogProvider, null); + this.sitesProvider = injector.get(CoreSitesProvider); + this.eventsProvider = injector.get(CoreEventsProvider); + this.modulePrefetchDelegate = injector.get(CoreCourseModulePrefetchDelegate); + this.courseProvider = injector.get(CoreCourseProvider); + this.appProvider = injector.get(CoreAppProvider); + this.dataRetrieved = new EventEmitter(); const loggerProvider = injector.get(CoreLoggerProvider); @@ -87,6 +114,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, * Component being initialized. */ ngOnInit(): void { + this.siteId = this.sitesProvider.getCurrentSiteId(); this.description = this.module.description; this.componentId = this.module.id; this.externalUrl = this.module.url; @@ -274,18 +302,122 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.courseHelper.confirmAndRemoveFiles(this.module, this.courseId, done); } + /** + * Get message about an error occurred while downloading files. + * + * @param error The specific error. + * @param multiLine Whether to put each message in a different paragraph or in a single line. + */ + protected getErrorDownloadingSomeFilesMessage(error: string | CoreTextErrorObject, multiLine?: boolean): string { + if (multiLine) { + return this.textUtils.buildSeveralParagraphsMessage([ + this.translate.instant('core.errordownloadingsomefiles'), + error, + ]); + } else { + error = this.textUtils.getErrorMessageFromError(error); + + return this.translate.instant('core.errordownloadingsomefiles') + (error ? ' ' + error : ''); + } + } + /** * Show an error occurred while downloading files. * * @param error The specific error. */ protected showErrorDownloadingSomeFiles(error: string | CoreTextErrorObject): void { - const errorMessage = this.textUtils.buildSeveralParagraphsMessage([ - this.translate.instant('core.errordownloadingsomefiles'), - error, - ]); + this.domUtils.showErrorModal(this.getErrorDownloadingSomeFilesMessage(error, true)); + } - this.domUtils.showErrorModal(errorMessage); + /** + * Displays some data based on the current status. + * + * @param status The current status. + * @param previousStatus The previous status. If not defined, there is no previous status. + */ + protected showStatus(status: string, previousStatus?: string): void { + // To be overridden. + } + + /** + * Watch for changes on the status. + * + * @return Promise resolved when done. + */ + protected setStatusListener(): Promise { + if (typeof this.statusObserver == 'undefined') { + // Listen for changes on this module status. + this.statusObserver = this.eventsProvider.on(CoreEventsProvider.PACKAGE_STATUS_CHANGED, (data) => { + if (data.componentId === this.module.id && data.component === this.component) { + // The status has changed, update it. + const previousStatus = this.currentStatus; + this.currentStatus = data.status; + + this.showStatus(this.currentStatus, previousStatus); + } + }, this.siteId); + + // Also, get the current status. + return this.modulePrefetchDelegate.getModuleStatus(this.module, this.courseId).then((status) => { + this.currentStatus = status; + this.showStatus(status); + }); + } + + return Promise.resolve(); + } + + /** + * Download a resource if needed. + * If the download call fails the promise won't be rejected, but the error will be included in the returned object. + * If module.contents cannot be loaded then the Promise will be rejected. + * + * @param refresh Whether we're refreshing data. + * @return Promise resolved when done. + */ + protected async downloadResourceIfNeeded(refresh?: boolean, contentsAlreadyLoaded?: boolean) + : Promise { + + const result: CoreCourseResourceDownloadResult = { + failed: false, + }; + + // Get module status to determine if it needs to be downloaded. + await this.setStatusListener(); + + if (this.currentStatus != CoreConstants.DOWNLOADED) { + // Download content. This function also loads module contents if needed. + try { + await this.modulePrefetchDelegate.downloadModule(this.module, this.courseId); + + // If we reach here it means the download process already loaded the contents, no need to do it again. + contentsAlreadyLoaded = true; + } catch (error) { + // Mark download as failed but go on since the main files could have been downloaded. + result.failed = true; + result.error = error; + } + } + + if (!this.module.contents.length || (refresh && !contentsAlreadyLoaded)) { + // Try to load the contents. + const ignoreCache = refresh && this.appProvider.isOnline(); + + try { + await this.courseProvider.loadModuleContents(this.module, this.courseId, undefined, false, ignoreCache); + } catch (error) { + // Error loading contents. If we ignored cache, try to get the cached value. + if (ignoreCache && !this.module.contents) { + await this.courseProvider.loadModuleContents(this.module, this.courseId); + } else if (!this.module.contents) { + // Not able to load contents, throw the error. + throw error; + } + } + } + + return result; } /** @@ -295,6 +427,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.isDestroyed = true; this.contextMenuStatusObserver && this.contextMenuStatusObserver.off(); this.contextFileStatusObserver && this.contextFileStatusObserver.off(); + this.statusObserver && this.statusObserver.off(); } /** diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index ef0c1ad04..50fbdc5db 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -404,6 +404,25 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { return status; } + /** + * Download a module. + * + * @param module Module to download. + * @param courseId Course ID the module belongs to. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when finished. + */ + async downloadModule(module: any, courseId: number, dirPath?: string): Promise { + const handler = this.getPrefetchHandlerFor(module); + + // Check if the module has a prefetch handler. + if (handler) { + await this.syncModule(module, courseId); + + await handler.download(module, courseId, dirPath); + } + } + /** * Check for updates in a course. *