Merge pull request #1902 from marxjohnson/MOBILE-2939_integration
MOBILE-2939 offline: Improve download confirm and error messages.main
commit
4eeb3de749
|
@ -23,6 +23,8 @@ export class CoreConstants {
|
|||
static SECONDS_MINUTE = 60;
|
||||
static WIFI_DOWNLOAD_THRESHOLD = 104857600; // 100MB.
|
||||
static DOWNLOAD_THRESHOLD = 10485760; // 10MB.
|
||||
static MINIMUM_FREE_SPACE = 10485760; // 10MB.
|
||||
static IOS_FREE_SPACE_THRESHOLD = 524288000; // 500MB.
|
||||
static DONT_SHOW_ERROR = 'CoreDontShowError';
|
||||
static NO_SITE_ID = 'NoSite';
|
||||
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
"activitynotyetviewablesiteupgradeneeded": "Your organisation's Moodle installation needs to be updated.",
|
||||
"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.",
|
||||
"confirmdeletemodulefiles": "Are you sure you want to delete these files?",
|
||||
"confirmdownload": "You are about to download {{size}}. Are you sure you want to continue?",
|
||||
"confirmdownloadunknownsize": "It was not possible to calculate the size of the download. Are you sure you want to continue?",
|
||||
"confirmpartialdownloadsize": "You are about to download <strong>at least</strong> {{size}}. Are you sure you want to continue?",
|
||||
"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?",
|
||||
"confirmpartialdownloadsize": "You are about to download <strong>at least</strong> {{size}}.{{availableSpace}} Are you sure you want to continue?",
|
||||
"confirmlimiteddownload": "You are not currently connected to WiFi. ",
|
||||
"contents": "Contents",
|
||||
"couldnotloadsectioncontent": "Could not load the section content. Please try again later.",
|
||||
"couldnotloadsections": "Could not load the sections. Please try again later.",
|
||||
|
@ -18,6 +20,8 @@
|
|||
"errorgetmodule": "Error getting activity data.",
|
||||
"hiddenfromstudents": "Hidden from students",
|
||||
"hiddenoncoursepage": "Available but not shown on course page",
|
||||
"insufficientavailablespace": "You are trying to download {{size}}. This will leave your device with insufficient space to operate normally. Please clear some storage space first.",
|
||||
"insufficientavailablequota": "Your device could not allocate space to save this download. It may be reserving space for app and system updates. Please clear some storage space first.",
|
||||
"manualcompletionnotsynced": "Manual completion not synchronised.",
|
||||
"nocontentavailable": "No content available at the moment.",
|
||||
"overriddennotice": "Your final grade from this activity was manually adjusted.",
|
||||
|
|
|
@ -1122,10 +1122,10 @@ export class CoreFilepoolProvider {
|
|||
return Promise.all(promises).then(() => {
|
||||
// Success prefetching, store package as downloaded.
|
||||
return this.storePackageStatus(siteId, CoreConstants.DOWNLOADED, component, componentId, extra);
|
||||
}).catch(() => {
|
||||
}).catch((error) => {
|
||||
// Error downloading, go back to previous status and reject the promise.
|
||||
return this.setPackagePreviousStatus(siteId, component, componentId).then(() => {
|
||||
return Promise.reject(null);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2566,18 +2566,26 @@ export class CoreFilepoolProvider {
|
|||
dropFromQueue = true;
|
||||
}
|
||||
|
||||
let errorMessage = null;
|
||||
// Some Android devices restrict the amount of usable storage using quotas.
|
||||
// If this quota would be exceeded by the download, it throws an exception.
|
||||
// We catch this exception here, and report a meaningful error message to the user.
|
||||
if (errorObject instanceof FileTransferError && errorObject.exception.includes('EDQUOT')) {
|
||||
errorMessage = 'core.course.insufficientavailablequota';
|
||||
}
|
||||
|
||||
if (dropFromQueue) {
|
||||
this.logger.debug('Item dropped from queue due to error: ' + fileUrl, errorObject);
|
||||
|
||||
return this.removeFromQueue(siteId, fileId).catch(() => {
|
||||
// Consider this as a silent error, never reject the promise here.
|
||||
}).then(() => {
|
||||
this.treatQueueDeferred(siteId, fileId, false);
|
||||
this.treatQueueDeferred(siteId, fileId, false, errorMessage);
|
||||
this.notifyFileDownloadError(siteId, fileId);
|
||||
});
|
||||
} else {
|
||||
// We considered the file as legit but did not get it, failure.
|
||||
this.treatQueueDeferred(siteId, fileId, false);
|
||||
this.treatQueueDeferred(siteId, fileId, false, errorMessage);
|
||||
this.notifyFileDownloadError(siteId, fileId);
|
||||
|
||||
return Promise.reject(errorObject);
|
||||
|
@ -2912,13 +2920,14 @@ export class CoreFilepoolProvider {
|
|||
* @param {string} siteId The site ID.
|
||||
* @param {string} fileId The file ID.
|
||||
* @param {boolean} resolve True if promise should be resolved, false if it should be rejected.
|
||||
* @param {string} error String identifier for error message, if rejected.
|
||||
*/
|
||||
protected treatQueueDeferred(siteId: string, fileId: string, resolve: boolean): void {
|
||||
protected treatQueueDeferred(siteId: string, fileId: string, resolve: boolean, error?: string): void {
|
||||
if (this.queueDeferreds[siteId] && this.queueDeferreds[siteId][fileId]) {
|
||||
if (resolve) {
|
||||
this.queueDeferreds[siteId][fileId].resolve();
|
||||
} else {
|
||||
this.queueDeferreds[siteId][fileId].reject();
|
||||
this.queueDeferreds[siteId][fileId].reject(error);
|
||||
}
|
||||
delete this.queueDeferreds[siteId][fileId];
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import { CoreTextUtilsProvider } from './text';
|
|||
import { CoreAppProvider } from '../app';
|
||||
import { CoreConfigProvider } from '../config';
|
||||
import { CoreUrlUtilsProvider } from './url';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip';
|
||||
import { Md5 } from 'ts-md5/dist/md5';
|
||||
|
@ -66,7 +67,8 @@ export class CoreDomUtilsProvider {
|
|||
constructor(private translate: TranslateService, private loadingCtrl: LoadingController, private toastCtrl: ToastController,
|
||||
private alertCtrl: AlertController, private textUtils: CoreTextUtilsProvider, private appProvider: CoreAppProvider,
|
||||
private platform: Platform, private configProvider: CoreConfigProvider, private urlUtils: CoreUrlUtilsProvider,
|
||||
private modalCtrl: ModalController, private sanitizer: DomSanitizer, private popoverCtrl: PopoverController) {
|
||||
private modalCtrl: ModalController, private sanitizer: DomSanitizer, private popoverCtrl: PopoverController,
|
||||
private fileProvider: CoreFileProvider) {
|
||||
|
||||
// Check if debug messages should be displayed.
|
||||
configProvider.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false).then((debugDisplay) => {
|
||||
|
@ -128,28 +130,73 @@ export class CoreDomUtilsProvider {
|
|||
*/
|
||||
confirmDownloadSize(size: any, message?: string, unknownMessage?: string, wifiThreshold?: number, limitedThreshold?: number,
|
||||
alwaysConfirm?: boolean): Promise<void> {
|
||||
const readableSize = this.textUtils.bytesToSize(size.size, 2);
|
||||
|
||||
const getAvailableBytes = new Promise((resolve): void => {
|
||||
if (this.appProvider.isDesktop()) {
|
||||
// Free space calculation is not supported on desktop.
|
||||
resolve(null);
|
||||
} else {
|
||||
this.fileProvider.calculateFreeSpace().then((availableBytes) => {
|
||||
if (this.platform.is('android')) {
|
||||
return availableBytes;
|
||||
} else {
|
||||
// Space calculation is not accurate on iOS, but it gets more accurate when space is lower.
|
||||
// We'll only use it when space is <500MB, or we're downloading more than twice the reported space.
|
||||
if (availableBytes < CoreConstants.IOS_FREE_SPACE_THRESHOLD || size.size > availableBytes / 2) {
|
||||
return availableBytes;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}).then((availableBytes) => {
|
||||
resolve(availableBytes);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const getAvailableSpace = getAvailableBytes.then((availableBytes: number) => {
|
||||
if (availableBytes === null) {
|
||||
return '';
|
||||
} else {
|
||||
const availableSize = this.textUtils.bytesToSize(availableBytes, 2);
|
||||
if (this.platform.is('android') && size.size > availableBytes - CoreConstants.MINIMUM_FREE_SPACE) {
|
||||
return Promise.reject(this.translate.instant('core.course.insufficientavailablespace', { size: readableSize }));
|
||||
}
|
||||
|
||||
return this.translate.instant('core.course.availablespace', {available: availableSize});
|
||||
}
|
||||
});
|
||||
|
||||
return getAvailableSpace.then((availableSpace) => {
|
||||
wifiThreshold = typeof wifiThreshold == 'undefined' ? CoreConstants.WIFI_DOWNLOAD_THRESHOLD : wifiThreshold;
|
||||
limitedThreshold = typeof limitedThreshold == 'undefined' ? CoreConstants.DOWNLOAD_THRESHOLD : limitedThreshold;
|
||||
|
||||
let wifiPrefix = '';
|
||||
if (this.appProvider.isNetworkAccessLimited()) {
|
||||
wifiPrefix = this.translate.instant('core.course.confirmlimiteddownload');
|
||||
}
|
||||
|
||||
if (size.size < 0 || (size.size == 0 && !size.total)) {
|
||||
// Seems size was unable to be calculated. Show a warning.
|
||||
unknownMessage = unknownMessage || 'core.course.confirmdownloadunknownsize';
|
||||
|
||||
return this.showConfirm(this.translate.instant(unknownMessage));
|
||||
return this.showConfirm(wifiPrefix + this.translate.instant(unknownMessage, {availableSpace: availableSpace}));
|
||||
} else if (!size.total) {
|
||||
// Filesize is only partial.
|
||||
const readableSize = this.textUtils.bytesToSize(size.size, 2);
|
||||
|
||||
return this.showConfirm(this.translate.instant('core.course.confirmpartialdownloadsize', { size: readableSize }));
|
||||
return this.showConfirm(wifiPrefix + this.translate.instant('core.course.confirmpartialdownloadsize',
|
||||
{ size: readableSize, availableSpace: availableSpace }));
|
||||
} else if (alwaysConfirm || size.size >= wifiThreshold ||
|
||||
(this.appProvider.isNetworkAccessLimited() && size.size >= limitedThreshold)) {
|
||||
message = message || 'core.course.confirmdownload';
|
||||
const readableSize = this.textUtils.bytesToSize(size.size, 2);
|
||||
|
||||
return this.showConfirm(this.translate.instant(message, { size: readableSize }));
|
||||
return this.showConfirm(wifiPrefix + this.translate.instant(message,
|
||||
{ size: readableSize, availableSpace: availableSpace }));
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -113,7 +113,7 @@ export class CoreTextUtilsProvider {
|
|||
*/
|
||||
bytesToSize(bytes: number, precision: number = 2): string {
|
||||
|
||||
if (typeof bytes == 'undefined' || bytes < 0) {
|
||||
if (typeof bytes == 'undefined' || bytes === null || bytes < 0) {
|
||||
return this.translate.instant('core.notapplicable');
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue