diff --git a/scripts/langindex.json b/scripts/langindex.json
index 212d23e5c..504e2bc4d 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -1390,6 +1390,7 @@
"core.course.allsections": "local_moodlemobileapp",
"core.course.askadmintosupport": "local_moodlemobileapp",
"core.course.availablespace": "local_moodlemobileapp",
+ "core.course.cannotdeletewhiledownloading": "local_moodlemobileapp",
"core.course.confirmdeletemodulefiles": "local_moodlemobileapp",
"core.course.confirmdownload": "local_moodlemobileapp",
"core.course.confirmdownloadunknownsize": "local_moodlemobileapp",
diff --git a/src/addon/mod/assign/components/index/addon-mod-assign-index.html b/src/addon/mod/assign/components/index/addon-mod-assign-index.html
index 467f9e49d..ee087a9df 100644
--- a/src/addon/mod/assign/components/index/addon-mod-assign-index.html
+++ b/src/addon/mod/assign/components/index/addon-mod-assign-index.html
@@ -7,7 +7,7 @@
-
+
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 6fcc9f70d..cfd0ff551 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
@@ -9,7 +9,7 @@
-
+
diff --git a/src/addon/mod/choice/components/index/addon-mod-choice-index.html b/src/addon/mod/choice/components/index/addon-mod-choice-index.html
index 762ad5066..bc5de5829 100644
--- a/src/addon/mod/choice/components/index/addon-mod-choice-index.html
+++ b/src/addon/mod/choice/components/index/addon-mod-choice-index.html
@@ -7,7 +7,7 @@
-
+
diff --git a/src/addon/mod/data/components/index/addon-mod-data-index.html b/src/addon/mod/data/components/index/addon-mod-data-index.html
index 200fb4387..af4a82ad6 100644
--- a/src/addon/mod/data/components/index/addon-mod-data-index.html
+++ b/src/addon/mod/data/components/index/addon-mod-data-index.html
@@ -12,7 +12,7 @@
-
+
diff --git a/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html b/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html
index 4355c5310..b491576c5 100644
--- a/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html
+++ b/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html
@@ -7,7 +7,7 @@
-
+
diff --git a/src/addon/mod/folder/components/index/addon-mod-folder-index.html b/src/addon/mod/folder/components/index/addon-mod-folder-index.html
index 03eb3838a..2aa7976f3 100644
--- a/src/addon/mod/folder/components/index/addon-mod-folder-index.html
+++ b/src/addon/mod/folder/components/index/addon-mod-folder-index.html
@@ -4,9 +4,9 @@
-
+
-
+
@@ -17,11 +17,11 @@
0">
-
+
{{file.name}}
-
+
diff --git a/src/addon/mod/folder/components/index/index.ts b/src/addon/mod/folder/components/index/index.ts
index 922ec3836..622acb449 100644
--- a/src/addon/mod/folder/components/index/index.ts
+++ b/src/addon/mod/folder/components/index/index.ts
@@ -29,7 +29,8 @@ import { AddonModFolderHelperProvider } from '../../providers/helper';
templateUrl: 'addon-mod-folder-index.html',
})
export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceComponent {
- @Input() path: string; // For subfolders. Use the path instead of a boolean so Angular detects them as different states.
+ @Input() folderInstance?: any; // The mod_folder instance.
+ @Input() subfolder?: any; // Subfolder to show.
component = AddonModFolderProvider.COMPONENT;
canGetFolder: boolean;
@@ -48,9 +49,9 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
this.canGetFolder = this.folderProvider.isGetFolderWSAvailable();
- if (this.path) {
+ if (this.subfolder) {
// Subfolder. Use module param.
- this.showModuleData(this.module, this.module.contents);
+ this.showModuleData(this.subfolder.contents);
this.loaded = true;
this.refreshIcon = 'refresh';
} else {
@@ -77,15 +78,14 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
}
/**
- * Convenience function to set scope data using module.
- * @param module Module to show.
+ * Convenience function to set data to display.
+ *
+ * @param folderContents Contents to show.
*/
- protected showModuleData(module: any, folderContents: any): void {
- this.description = module.intro || module.description;
+ protected showModuleData(folderContents: any): void {
+ this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
- this.dataRetrieved.emit(module);
-
- if (this.path) {
+ if (this.subfolder) {
// Subfolder.
this.contents = folderContents;
} else {
@@ -107,25 +107,29 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
promise = this.folderProvider.getFolder(this.courseId, this.module.id).then((folder) => {
return this.courseProvider.loadModuleContents(this.module, this.courseId, undefined, false, refresh).then(() => {
folderContents = this.module.contents;
+ this.folderInstance = folder;
return folder;
});
});
} else {
- promise = this.courseProvider.getModule(this.module.id, this.courseId).then((folder) => {
- if (!folder.contents.length && this.module.contents.length && !this.appProvider.isOnline()) {
+ promise = this.courseProvider.getModule(this.module.id, this.courseId).then((module) => {
+ if (!module.contents.length && this.module.contents.length && !this.appProvider.isOnline()) {
// The contents might be empty due to a cached data. Use the old ones.
- folder.contents = this.module.contents;
+ module.contents = this.module.contents;
}
- this.module = folder;
- folderContents = folder.contents;
+ this.module = module;
+ folderContents = module.contents;
- return folder;
+ return module;
});
}
- return promise.then((folder) => {
- this.showModuleData(folder, folderContents);
+ return promise.then(() => {
+
+ this.dataRetrieved.emit(this.folderInstance || this.module);
+
+ this.showModuleData(folderContents);
}).finally(() => {
this.fillContextMenu(refresh);
});
diff --git a/src/addon/mod/folder/pages/index/index.html b/src/addon/mod/folder/pages/index/index.html
index c435f6866..27f9f1509 100644
--- a/src/addon/mod/folder/pages/index/index.html
+++ b/src/addon/mod/folder/pages/index/index.html
@@ -8,9 +8,9 @@
-
+
-
+
diff --git a/src/addon/mod/folder/pages/index/index.ts b/src/addon/mod/folder/pages/index/index.ts
index 74c63b22e..a64d7b499 100644
--- a/src/addon/mod/folder/pages/index/index.ts
+++ b/src/addon/mod/folder/pages/index/index.ts
@@ -30,13 +30,15 @@ export class AddonModFolderIndexPage {
title: string;
module: any;
courseId: number;
- path: string;
+ folderInstance: any;
+ subfolder: any;
constructor(navParams: NavParams) {
this.module = navParams.get('module') || {};
this.courseId = navParams.get('courseId');
- this.path = navParams.get('path');
- this.title = this.module.name;
+ this.folderInstance = navParams.get('folderInstance');
+ this.subfolder = navParams.get('subfolder');
+ this.title = this.subfolder ? this.subfolder.name : this.module.name;
}
/**
diff --git a/src/addon/mod/forum/components/index/addon-mod-forum-index.html b/src/addon/mod/forum/components/index/addon-mod-forum-index.html
index e941c514a..7e7935ae6 100644
--- a/src/addon/mod/forum/components/index/addon-mod-forum-index.html
+++ b/src/addon/mod/forum/components/index/addon-mod-forum-index.html
@@ -7,7 +7,7 @@
-
+
diff --git a/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html b/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html
index 235c51297..0c01a1288 100644
--- a/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html
+++ b/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html
@@ -14,7 +14,7 @@
-
+
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 35cbae509..6be152d02 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
@@ -9,7 +9,7 @@
-
+
diff --git a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html
index f7ac672e0..834abcf38 100644
--- a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html
+++ b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html
@@ -7,7 +7,7 @@
-
+
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 378796f11..12f6ddbd3 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
@@ -6,7 +6,7 @@
-
+
diff --git a/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html b/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html
index 88de59576..ec8685448 100644
--- a/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html
+++ b/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html
@@ -7,7 +7,7 @@
-
+
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 7d2e36a94..9fcfdaa83 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
@@ -6,7 +6,7 @@
-
+
diff --git a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html
index a9fcfd058..cd5e6b01a 100644
--- a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html
+++ b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html
@@ -7,7 +7,7 @@
-
+
diff --git a/src/addon/mod/survey/components/index/addon-mod-survey-index.html b/src/addon/mod/survey/components/index/addon-mod-survey-index.html
index 42c41ddd0..eb101c594 100644
--- a/src/addon/mod/survey/components/index/addon-mod-survey-index.html
+++ b/src/addon/mod/survey/components/index/addon-mod-survey-index.html
@@ -7,7 +7,7 @@
-
+
diff --git a/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html b/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html
index e043a6299..44a57c1ca 100644
--- a/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html
+++ b/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html
@@ -19,7 +19,7 @@
-
+
diff --git a/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html b/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html
index 3ce7e2c32..c10216d25 100644
--- a/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html
+++ b/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html
@@ -7,7 +7,7 @@
-
+
diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json
index 3561e6111..3c3ea32d7 100644
--- a/src/assets/lang/en.json
+++ b/src/assets/lang/en.json
@@ -1390,6 +1390,7 @@
"core.course.allsections": "All sections",
"core.course.askadmintosupport": "Contact the site administrator and tell them you want to use this activity with the Moodle Mobile app.",
"core.course.availablespace": " You currently have about {{available}} free space.",
+ "core.course.cannotdeletewhiledownloading": "Files cannot be deleted while the activity is being downloaded. Please wait for the download to finish.",
"core.course.confirmdeletemodulefiles": "Are you sure you want to delete these files?",
"core.course.confirmdownload": "You are about to download {{size}}.{{availableSpace}} Are you sure you want to continue?",
"core.course.confirmdownloadunknownsize": "It was not possible to calculate the size of the download.{{availableSpace}} Are you sure you want to continue?",
diff --git a/src/core/course/classes/main-resource-component.ts b/src/core/course/classes/main-resource-component.ts
index 50f432894..1bbff628d 100644
--- a/src/core/course/classes/main-resource-component.ts
+++ b/src/core/course/classes/main-resource-component.ts
@@ -49,6 +49,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
protected isDestroyed; // Whether the component is destroyed, used when calling fillContextMenu.
protected contextMenuStatusObserver; // Observer of package status changed, used when calling fillContextMenu.
+ 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.
@@ -260,9 +261,17 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
/**
* Confirm and remove downloaded files.
+ *
+ * @param done Function to call when done.
*/
- removeFiles(): void {
- this.courseHelper.confirmAndRemoveFiles(this.module, this.courseId);
+ removeFiles(done?: () => void): void {
+ if (this.prefetchStatus == CoreConstants.DOWNLOADING) {
+ this.domUtils.showAlertTranslated(null, 'core.course.cannotdeletewhiledownloading');
+
+ return;
+ }
+
+ this.courseHelper.confirmAndRemoveFiles(this.module, this.courseId, done);
}
/**
@@ -285,6 +294,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
ngOnDestroy(): void {
this.isDestroyed = true;
this.contextMenuStatusObserver && this.contextMenuStatusObserver.off();
+ this.contextFileStatusObserver && this.contextFileStatusObserver.off();
}
/**
diff --git a/src/core/course/lang/en.json b/src/core/course/lang/en.json
index 2fe5518e9..69cbe5cf9 100644
--- a/src/core/course/lang/en.json
+++ b/src/core/course/lang/en.json
@@ -5,6 +5,7 @@
"allsections": "All sections",
"askadmintosupport": "Contact the site administrator and tell them you want to use this activity with the Moodle Mobile app.",
"availablespace": " You currently have about {{available}} free space.",
+ "cannotdeletewhiledownloading": "Files cannot be deleted while the activity is being downloaded. Please wait for the download to finish.",
"confirmdeletemodulefiles": "Are you sure you want to delete these files?",
"confirmdownload": "You are about to download {{size}}.{{availableSpace}} Are you sure you want to continue?",
"confirmdownloadunknownsize": "It was not possible to calculate the size of the download.{{availableSpace}} Are you sure you want to continue?",
diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts
index 7fbc8b740..b93d3191b 100644
--- a/src/core/course/providers/helper.ts
+++ b/src/core/course/providers/helper.ts
@@ -13,12 +13,12 @@
// limitations under the License.
import { Injectable, Injector } from '@angular/core';
-import { NavController } from 'ionic-angular';
+import { NavController, Loading } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events';
import { CoreFileProvider } from '@providers/file';
-import { CoreFilepoolProvider } from '@providers/filepool';
+import { CoreFilepoolProvider, CoreFilepoolComponentFileEventData } from '@providers/filepool';
import { CoreFileHelperProvider } from '@providers/file-helper';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
@@ -408,16 +408,29 @@ export class CoreCourseHelperProvider {
*
* @param module Module to remove the files.
* @param courseId Course ID the module belongs to.
+ * @param done Function to call when done. It will close the context menu.
* @return Promise resolved when done.
*/
- confirmAndRemoveFiles(module: any, courseId: number): Promise {
- return this.domUtils.showDeleteConfirm('core.course.confirmdeletemodulefiles').then(() => {
- return this.prefetchDelegate.removeModuleFiles(module, courseId);
- }).catch((error) => {
+ async confirmAndRemoveFiles(module: any, courseId: number, done?: () => void): Promise {
+ let modal: Loading;
+
+ try {
+
+ await this.domUtils.showDeleteConfirm('core.course.confirmdeletemodulefiles');
+
+ modal = this.domUtils.showModalLoading();
+
+ await this.prefetchDelegate.removeModuleFiles(module, courseId);
+
+ done && done();
+
+ } catch (error) {
if (error) {
this.domUtils.showErrorModal(error);
}
- });
+ } finally {
+ modal && modal.dismiss();
+ }
}
/**
@@ -800,6 +813,8 @@ export class CoreCourseHelperProvider {
* @return Promise resolved when done.
*/
fillContextMenu(instance: any, module: any, courseId: number, invalidateCache?: boolean, component?: string): Promise {
+ const siteId = this.sitesProvider.getCurrentSiteId();
+
return this.getModulePrefetchInfo(module, courseId, invalidateCache, component).then((moduleInfo) => {
instance.size = moduleInfo.size > 0 ? moduleInfo.sizeReadable : 0;
instance.prefetchStatusIcon = moduleInfo.statusIcon;
@@ -825,7 +840,32 @@ export class CoreCourseHelperProvider {
if (data.componentId == module.id && data.component == component) {
this.fillContextMenu(instance, module, courseId, false, component);
}
- }, this.sitesProvider.getCurrentSiteId());
+ }, siteId);
+ }
+
+ if (typeof instance.contextFileStatusObserver == 'undefined' && component) {
+ // Debounce the update size function to prevent too many calls when downloading or deleting a whole activity.
+ const debouncedUpdateSize = this.utils.debounce(() => {
+ this.prefetchDelegate.getModuleDownloadedSize(module, courseId).then((moduleSize) => {
+ instance.size = moduleSize > 0 ? this.textUtils.bytesToSize(moduleSize, 2) : 0;
+ });
+ }, 1000);
+
+ instance.contextFileStatusObserver = this.eventsProvider.on(CoreEventsProvider.COMPONENT_FILE_ACTION,
+ (data: CoreFilepoolComponentFileEventData) => {
+
+ if (data.component != component || data.componentId != module.id) {
+ // The event doesn't belong to this component, ignore.
+ return;
+ }
+
+ if (!this.filepoolProvider.isFileEventDownloadedOrDeleted(data)) {
+ return;
+ }
+
+ // Update the module size.
+ debouncedUpdateSize();
+ }, siteId);
}
});
}
diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts
index 6e05b283b..ef0c1ad04 100644
--- a/src/core/course/providers/module-prefetch-delegate.ts
+++ b/src/core/course/providers/module-prefetch-delegate.ts
@@ -15,7 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreFileProvider } from '@providers/file';
-import { CoreFilepoolProvider } from '@providers/filepool';
+import { CoreFilepoolProvider, CoreFilepoolComponentFileEventData } from '@providers/filepool';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
@@ -276,6 +276,15 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
eventsProvider.on(CoreEventsProvider.PACKAGE_STATUS_CHANGED, (data) => {
this.updateStatusCache(data.status, data.component, data.componentId);
}, this.sitesProvider.getCurrentSiteId());
+
+ // If a file inside a module is downloaded/deleted, clear the corresponding cache.
+ eventsProvider.on(CoreEventsProvider.COMPONENT_FILE_ACTION, (data: CoreFilepoolComponentFileEventData) => {
+ if (!this.filepoolProvider.isFileEventDownloadedOrDeleted(data)) {
+ return;
+ }
+
+ this.statusCache.invalidate(this.filepoolProvider.getPackageId(data.component, data.componentId));
+ }, this.sitesProvider.getCurrentSiteId());
}
/**
diff --git a/src/core/siteplugins/components/module-index/core-siteplugins-module-index.html b/src/core/siteplugins/components/module-index/core-siteplugins-module-index.html
index f3c8b832d..538055ea5 100644
--- a/src/core/siteplugins/components/module-index/core-siteplugins-module-index.html
+++ b/src/core/siteplugins/components/module-index/core-siteplugins-module-index.html
@@ -5,7 +5,7 @@
-
+
diff --git a/src/providers/events.ts b/src/providers/events.ts
index abed87a95..4da2900c0 100644
--- a/src/providers/events.ts
+++ b/src/providers/events.ts
@@ -47,6 +47,7 @@ export class CoreEventsProvider {
static PACKAGE_STATUS_CHANGED = 'package_status_changed';
static COURSE_STATUS_CHANGED = 'course_status_changed';
static SECTION_STATUS_CHANGED = 'section_status_changed';
+ static COMPONENT_FILE_ACTION = 'component_file_action';
static SITE_PLUGINS_LOADED = 'site_plugins_loaded';
static SITE_PLUGINS_COURSE_RESTRICT_UPDATED = 'site_plugins_course_restrict_updated';
static LOGIN_SITE_CHECKED = 'login_site_checked';
diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts
index a4faa08f3..a3889773d 100644
--- a/src/providers/filepool.ts
+++ b/src/providers/filepool.ts
@@ -144,7 +144,7 @@ export interface CoreFilepoolQueueEntry {
/**
* File links (to link the file to components and componentIds).
*/
- links?: any[];
+ links?: CoreFilepoolComponentLink[];
}
/**
@@ -197,6 +197,66 @@ export interface CoreFilepoolPackageEntry {
extra?: string;
}
+/**
+ * A component link.
+ */
+export interface CoreFilepoolComponentLink {
+ /**
+ * Link's component.
+ */
+ component: string;
+
+ /**
+ * Link's componentId.
+ */
+ componentId?: string | number;
+}
+
+/**
+ * File actions.
+ */
+export const enum CoreFilepoolFileActions {
+ DOWNLOAD = 'download',
+ DOWNLOADING = 'downloading',
+ DELETED = 'deleted',
+ OUTDATED = 'outdated',
+}
+
+/**
+ * Data sent to file events.
+ */
+export interface CoreFilepoolFileEventData {
+ /**
+ * The file ID.
+ */
+ fileId: string;
+
+ /**
+ * The file ID.
+ */
+ action: CoreFilepoolFileActions;
+
+ /**
+ * Whether the action was a success. Only for DOWNLOAD action.
+ */
+ success?: boolean;
+}
+
+/**
+ * Data sent to component file events.
+ */
+export interface CoreFilepoolComponentFileEventData extends CoreFilepoolFileEventData {
+ /**
+ * The component.
+ */
+ component: string;
+
+ /**
+ * The component ID.
+ */
+ componentId: string | number;
+}
+
/*
* Factory for handling downloading files and retrieve downloaded files.
*
@@ -495,7 +555,7 @@ export class CoreFilepoolProvider {
* @param links Array of objects containing the component and optionally componentId.
* @return Promise resolved on success.
*/
- protected addFileLinks(siteId: string, fileId: string, links: any[]): Promise {
+ protected addFileLinks(siteId: string, fileId: string, links: CoreFilepoolComponentLink[]): Promise {
const promises = [];
links.forEach((link) => {
promises.push(this.addFileLink(siteId, fileId, link.component, link.componentId));
@@ -575,7 +635,9 @@ export class CoreFilepoolProvider {
* @return Promise resolved when the file is downloaded.
*/
protected async addToQueue(siteId: string, fileId: string, url: string, priority: number, revision: number,
- timemodified: number, filePath: string, onProgress?: (event: any) => any, options: any = {}, link?: any): Promise {
+ timemodified: number, filePath: string, onProgress?: (event: any) => any, options: any = {},
+ link?: CoreFilepoolComponentLink): Promise {
+
await this.dbReady;
this.logger.debug(`Adding ${fileId} to the queue`);
@@ -596,7 +658,7 @@ export class CoreFilepoolProvider {
// Check if the queue is running.
this.checkQueueProcessing();
- this.notifyFileDownloading(siteId, fileId);
+ this.notifyFileDownloading(siteId, fileId, link ? [link] : []);
return this.getQueuePromise(siteId, fileId, true, onProgress);
}
@@ -623,7 +685,6 @@ export class CoreFilepoolProvider {
await this.dbReady;
let fileId,
- link,
queueDeferred;
if (!this.fileProvider.isAvailable()) {
@@ -651,12 +712,7 @@ export class CoreFilepoolProvider {
const primaryKey = { siteId: siteId, fileId: fileId };
// Set up the component.
- if (typeof component != 'undefined') {
- link = {
- component: component,
- componentId: this.fixComponentId(componentId)
- };
- }
+ const link = this.createComponentLink(component, componentId);
// Retrieve the queue deferred now if it exists.
// This is to prevent errors if file is removed from queue while we're checking if the file is in queue.
@@ -866,7 +922,7 @@ export class CoreFilepoolProvider {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
const conditions = {
component: component,
- componentId: componentId || ''
+ componentId: this.fixComponentId(componentId)
};
return db.countRecords(this.LINKS_TABLE, conditions).then((count) => {
@@ -877,6 +933,34 @@ export class CoreFilepoolProvider {
});
}
+ /**
+ * Prepare a component link.
+ *
+ * @param component The component to link the file to.
+ * @param componentId An ID to use in conjunction with the component.
+ * @return Link, null if nothing to link.
+ */
+ protected createComponentLink(component: string, componentId?: string | number): CoreFilepoolComponentLink {
+ if (typeof component != 'undefined' && component != null) {
+ return { component: component, componentId: this.fixComponentId(componentId) };
+ }
+
+ return null;
+ }
+
+ /**
+ * Prepare list of links from component and componentId.
+ *
+ * @param component The component to link the file to.
+ * @param componentId An ID to use in conjunction with the component.
+ * @return Links.
+ */
+ protected createComponentLinks(component: string, componentId?: string | number): CoreFilepoolComponentLink[] {
+ const link = this.createComponentLink(component, componentId);
+
+ return link ? [link] : [];
+ }
+
/**
* Given the current status of a list of packages and the status of one of the packages,
* determine the new status for the list of packages. The status of a list of packages is:
@@ -1179,8 +1263,9 @@ export class CoreFilepoolProvider {
downloadUrl(siteId: string, fileUrl: string, ignoreStale?: boolean, component?: string, componentId?: string | number,
timemodified: number = 0, onProgress?: (event: any) => any, filePath?: string, options: any = {}, revision?: number)
: Promise {
- let fileId,
- promise;
+ let fileId;
+ let promise;
+ let alreadyDownloaded = true;
if (this.fileProvider.isAvailable()) {
return this.fixPluginfileURL(siteId, fileUrl).then((file) => {
@@ -1193,18 +1278,22 @@ export class CoreFilepoolProvider {
options.revision = revision || this.getRevisionFromUrl(fileUrl);
fileId = this.getFileIdByUrl(fileUrl);
+ const links = this.createComponentLinks(component, componentId);
+
return this.hasFileInPool(siteId, fileId).then((fileObject) => {
if (typeof fileObject === 'undefined') {
// We do not have the file, download and add to pool.
- this.notifyFileDownloading(siteId, fileId);
+ this.notifyFileDownloading(siteId, fileId, links);
+ alreadyDownloaded = false;
return this.downloadForPoolByUrl(siteId, fileUrl, options, filePath, onProgress);
} else if (this.isFileOutdated(fileObject, options.revision, options.timemodified) &&
- this.appProvider.isOnline() && !ignoreStale) {
+ this.appProvider.isOnline() && !ignoreStale) {
// The file is outdated, force the download and update it.
- this.notifyFileDownloading(siteId, fileId);
+ this.notifyFileDownloading(siteId, fileId, links);
+ alreadyDownloaded = false;
return this.downloadForPoolByUrl(siteId, fileUrl, options, filePath, onProgress, fileObject);
}
@@ -1220,14 +1309,16 @@ export class CoreFilepoolProvider {
return response;
}, () => {
// The file was not found in the pool, weird.
- this.notifyFileDownloading(siteId, fileId);
+ this.notifyFileDownloading(siteId, fileId, links);
+ alreadyDownloaded = false;
return this.downloadForPoolByUrl(siteId, fileUrl, options, filePath, onProgress, fileObject);
});
}, () => {
// The file is not in the pool just yet.
- this.notifyFileDownloading(siteId, fileId);
+ this.notifyFileDownloading(siteId, fileId, links);
+ alreadyDownloaded = false;
return this.downloadForPoolByUrl(siteId, fileUrl, options, filePath, onProgress);
}).then((response) => {
@@ -1236,11 +1327,14 @@ export class CoreFilepoolProvider {
// Ignore errors.
});
}
- this.notifyFileDownloaded(siteId, fileId);
+
+ if (!alreadyDownloaded) {
+ this.notifyFileDownloaded(siteId, fileId, links);
+ }
return response;
}, (err) => {
- this.notifyFileDownloadError(siteId, fileId);
+ this.notifyFileDownloadError(siteId, fileId, links);
return Promise.reject(err);
});
@@ -1404,10 +1498,16 @@ export class CoreFilepoolProvider {
protected getComponentFiles(db: SQLiteDB, component: string, componentId?: string | number): Promise {
const conditions = {
component: component,
- componentId: componentId || ''
+ componentId: this.fixComponentId(componentId)
};
- return db.getRecords(this.LINKS_TABLE, conditions);
+ return db.getRecords(this.LINKS_TABLE, conditions).then((items) => {
+ items.forEach((item) => {
+ item.componentId = this.fixComponentId(item.componentId);
+ });
+
+ return items;
+ });
}
/**
@@ -1516,6 +1616,12 @@ export class CoreFilepoolProvider {
protected getFileLinks(siteId: string, fileId: string): Promise {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
return db.getRecords(this.LINKS_TABLE, { fileId: fileId });
+ }).then((items) => {
+ items.forEach((item) => {
+ item.componentId = this.fixComponentId(item.componentId);
+ });
+
+ return items;
});
}
@@ -2385,6 +2491,17 @@ export class CoreFilepoolProvider {
});
}
+ /**
+ * Whether a file action indicates a file was downloaded or deleted.
+ *
+ * @param data Event data.
+ * @return Whether downloaded or deleted.
+ */
+ isFileEventDownloadedOrDeleted(data: CoreFilepoolFileEventData): boolean {
+ return (data.action == CoreFilepoolFileActions.DOWNLOAD && data.success == true) ||
+ data.action == CoreFilepoolFileActions.DELETED;
+ }
+
/**
* Check whether a file is downloadable.
*
@@ -2439,14 +2556,41 @@ export class CoreFilepoolProvider {
return !!entry.isexternalfile || (entry.revision < 1 && !entry.timemodified);
}
+ /**
+ * Notify an action performed on a file to a list of components.
+ *
+ * @param siteId The site ID.
+ * @param eventData The file event data.
+ * @param links The links to the components.
+ */
+ protected notifyFileActionToComponents(siteId: string, eventData: CoreFilepoolFileEventData,
+ links: CoreFilepoolComponentLink[]): void {
+
+ links.forEach((link) => {
+ const data: CoreFilepoolComponentFileEventData = Object.assign({
+ component: link.component,
+ componentId: link.componentId,
+ }, eventData);
+
+ this.eventsProvider.trigger(CoreEventsProvider.COMPONENT_FILE_ACTION, data, siteId);
+ });
+ }
+
/**
* Notify a file has been deleted.
*
* @param siteId The site ID.
* @param fileId The file ID.
+ * @param links The links to components.
*/
- protected notifyFileDeleted(siteId: string, fileId: string): void {
- this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'deleted' });
+ protected notifyFileDeleted(siteId: string, fileId: string, links: CoreFilepoolComponentLink[]): void {
+ const data: CoreFilepoolFileEventData = {
+ fileId: fileId,
+ action: CoreFilepoolFileActions.DELETED,
+ };
+
+ this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), data);
+ this.notifyFileActionToComponents(siteId, data, links);
}
/**
@@ -2454,9 +2598,17 @@ export class CoreFilepoolProvider {
*
* @param siteId The site ID.
* @param fileId The file ID.
+ * @param links The links to components.
*/
- protected notifyFileDownloaded(siteId: string, fileId: string): void {
- this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'download', success: true });
+ protected notifyFileDownloaded(siteId: string, fileId: string, links: CoreFilepoolComponentLink[]): void {
+ const data: CoreFilepoolFileEventData = {
+ fileId: fileId,
+ action: CoreFilepoolFileActions.DOWNLOAD,
+ success: true,
+ };
+
+ this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), data);
+ this.notifyFileActionToComponents(siteId, data, links);
}
/**
@@ -2464,9 +2616,17 @@ export class CoreFilepoolProvider {
*
* @param siteId The site ID.
* @param fileId The file ID.
+ * @param links The links to components.
*/
- protected notifyFileDownloadError(siteId: string, fileId: string): void {
- this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'download', success: false });
+ protected notifyFileDownloadError(siteId: string, fileId: string, links: CoreFilepoolComponentLink[]): void {
+ const data: CoreFilepoolFileEventData = {
+ fileId: fileId,
+ action: CoreFilepoolFileActions.DOWNLOAD,
+ success: false,
+ };
+
+ this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), data);
+ this.notifyFileActionToComponents(siteId, data, links);
}
/**
@@ -2474,9 +2634,17 @@ export class CoreFilepoolProvider {
*
* @param siteId The site ID.
* @param fileId The file ID.
+ * @param links The links to components.
*/
- protected notifyFileDownloading(siteId: string, fileId: string): void {
- this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'downloading' });
+ protected notifyFileDownloading(siteId: string, fileId: string, links: CoreFilepoolComponentLink[]): void {
+ const data: CoreFilepoolFileEventData = {
+ fileId: fileId,
+ action: CoreFilepoolFileActions.DOWNLOADING,
+ };
+
+ this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), data);
+ this.notifyFileActionToComponents(siteId, data, links);
+
}
/**
@@ -2484,9 +2652,16 @@ export class CoreFilepoolProvider {
*
* @param siteId The site ID.
* @param fileId The file ID.
+ * @param links The links to components.
*/
- protected notifyFileOutdated(siteId: string, fileId: string): void {
- this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'outdated' });
+ protected notifyFileOutdated(siteId: string, fileId: string, links: CoreFilepoolComponentLink[]): void {
+ const data: CoreFilepoolFileEventData = {
+ fileId: fileId,
+ action: CoreFilepoolFileActions.OUTDATED,
+ };
+
+ this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), data);
+ this.notifyFileActionToComponents(siteId, data, links);
}
/**
@@ -2612,7 +2787,6 @@ export class CoreFilepoolProvider {
}).finally(() => {
this.treatQueueDeferred(siteId, fileId, true);
});
- this.notifyFileDownloaded(siteId, fileId);
return;
}
@@ -2627,7 +2801,7 @@ export class CoreFilepoolProvider {
});
this.treatQueueDeferred(siteId, fileId, true);
- this.notifyFileDownloaded(siteId, fileId);
+ this.notifyFileDownloaded(siteId, fileId, links);
// Wait for the item to be removed from queue before resolving the promise.
// If the item could not be removed from queue we still resolve the promise.
@@ -2677,12 +2851,12 @@ export class CoreFilepoolProvider {
// Consider this as a silent error, never reject the promise here.
}).then(() => {
this.treatQueueDeferred(siteId, fileId, false, errorMessage);
- this.notifyFileDownloadError(siteId, fileId);
+ this.notifyFileDownloadError(siteId, fileId, links);
});
} else {
// We considered the file as legit but did not get it, failure.
this.treatQueueDeferred(siteId, fileId, false, errorMessage);
- this.notifyFileDownloadError(siteId, fileId);
+ this.notifyFileDownloadError(siteId, fileId, links);
return Promise.reject(errorObject);
}
@@ -2730,30 +2904,37 @@ export class CoreFilepoolProvider {
// If file not found, use the path without extension.
return path;
}).then((path) => {
- const promises = [];
+ const conditions = {
+ fileId: fileId
+ };
- // Remove entry from filepool store.
- promises.push(db.deleteRecords(this.FILES_TABLE, { fileId: fileId }));
+ // Get links to components to notify them after remove.
+ return this.getFileLinks(siteId, fileId).then((links) => {
+ const promises = [];
- // Remove links.
- promises.push(db.deleteRecords(this.LINKS_TABLE, { fileId: fileId }));
+ // Remove entry from filepool store.
+ promises.push(db.deleteRecords(this.FILES_TABLE, conditions));
- // Remove the file.
- if (this.fileProvider.isAvailable()) {
- promises.push(this.fileProvider.removeFile(path).catch((error) => {
- if (error && error.code == 1) {
- // Not found, ignore error since maybe it was deleted already.
- } else {
- return Promise.reject(error);
- }
- }));
- }
+ // Remove links.
+ promises.push(db.deleteRecords(this.LINKS_TABLE, conditions));
- return Promise.all(promises).then(() => {
- this.notifyFileDeleted(siteId, fileId);
+ // Remove the file.
+ if (this.fileProvider.isAvailable()) {
+ promises.push(this.fileProvider.removeFile(path).catch((error) => {
+ if (error && error.code == 1) {
+ // Not found, ignore error since maybe it was deleted already.
+ } else {
+ return Promise.reject(error);
+ }
+ }));
+ }
- return this.pluginFileDelegate.fileDeleted(fileUrl, path, siteId).catch((error) => {
- // Ignore errors.
+ return Promise.all(promises).then(() => {
+ this.notifyFileDeleted(siteId, fileId, links);
+
+ return this.pluginFileDelegate.fileDeleted(fileUrl, path, siteId).catch((error) => {
+ // Ignore errors.
+ });
});
});
});
diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts
index bedd0e4b0..bebc3ce1c 100644
--- a/src/providers/utils/utils.ts
+++ b/src/providers/utils/utils.ts
@@ -1392,4 +1392,27 @@ export class CoreUtilsProvider {
return filtered;
}
+
+ /**
+ * Debounce a function so consecutive calls are ignored until a certain time has passed since the last call.
+ *
+ * @param context The context to apply to the function.
+ * @param fn Function to debounce.
+ * @param delay Time that must pass until the function is called.
+ * @return Debounced function.
+ */
+ debounce(fn: (...args: any[]) => any, delay: number): (...args: any[]) => void {
+
+ let timeoutID: number;
+
+ const debounced = (...args: any[]): void => {
+ clearTimeout(timeoutID);
+
+ timeoutID = window.setTimeout(() => {
+ fn.apply(null, args);
+ }, delay);
+ };
+
+ return debounced;
+ }
}