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.
*