forked from CIT/Vmeda.Online
		
	Merge pull request #1902 from marxjohnson/MOBILE-2939_integration
MOBILE-2939 offline: Improve download confirm and error messages.
This commit is contained in:
		
						commit
						4eeb3de749
					
				| @ -23,6 +23,8 @@ export class CoreConstants { | |||||||
|     static SECONDS_MINUTE = 60; |     static SECONDS_MINUTE = 60; | ||||||
|     static WIFI_DOWNLOAD_THRESHOLD = 104857600; // 100MB.
 |     static WIFI_DOWNLOAD_THRESHOLD = 104857600; // 100MB.
 | ||||||
|     static DOWNLOAD_THRESHOLD = 10485760; // 10MB.
 |     static DOWNLOAD_THRESHOLD = 10485760; // 10MB.
 | ||||||
|  |     static MINIMUM_FREE_SPACE = 10485760; // 10MB.
 | ||||||
|  |     static IOS_FREE_SPACE_THRESHOLD = 524288000; // 500MB.
 | ||||||
|     static DONT_SHOW_ERROR = 'CoreDontShowError'; |     static DONT_SHOW_ERROR = 'CoreDontShowError'; | ||||||
|     static NO_SITE_ID = 'NoSite'; |     static NO_SITE_ID = 'NoSite'; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,10 +4,12 @@ | |||||||
|     "activitynotyetviewablesiteupgradeneeded": "Your organisation's Moodle installation needs to be updated.", |     "activitynotyetviewablesiteupgradeneeded": "Your organisation's Moodle installation needs to be updated.", | ||||||
|     "allsections": "All sections", |     "allsections": "All sections", | ||||||
|     "askadmintosupport": "Contact the site administrator and tell them you want to use this activity with the Moodle Mobile app.", |     "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?", |     "confirmdeletemodulefiles": "Are you sure you want to delete these files?", | ||||||
|     "confirmdownload": "You are about to download {{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. 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}}. 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", |     "contents": "Contents", | ||||||
|     "couldnotloadsectioncontent": "Could not load the section content. Please try again later.", |     "couldnotloadsectioncontent": "Could not load the section content. Please try again later.", | ||||||
|     "couldnotloadsections": "Could not load the sections. Please try again later.", |     "couldnotloadsections": "Could not load the sections. Please try again later.", | ||||||
| @ -18,6 +20,8 @@ | |||||||
|     "errorgetmodule": "Error getting activity data.", |     "errorgetmodule": "Error getting activity data.", | ||||||
|     "hiddenfromstudents": "Hidden from students", |     "hiddenfromstudents": "Hidden from students", | ||||||
|     "hiddenoncoursepage": "Available but not shown on course page", |     "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.", |     "manualcompletionnotsynced": "Manual completion not synchronised.", | ||||||
|     "nocontentavailable": "No content available at the moment.", |     "nocontentavailable": "No content available at the moment.", | ||||||
|     "overriddennotice": "Your final grade from this activity was manually adjusted.", |     "overriddennotice": "Your final grade from this activity was manually adjusted.", | ||||||
|  | |||||||
| @ -1122,10 +1122,10 @@ export class CoreFilepoolProvider { | |||||||
|             return Promise.all(promises).then(() => { |             return Promise.all(promises).then(() => { | ||||||
|                 // Success prefetching, store package as downloaded.
 |                 // Success prefetching, store package as downloaded.
 | ||||||
|                 return this.storePackageStatus(siteId, CoreConstants.DOWNLOADED, component, componentId, extra); |                 return this.storePackageStatus(siteId, CoreConstants.DOWNLOADED, component, componentId, extra); | ||||||
|             }).catch(() => { |             }).catch((error) => { | ||||||
|                 // Error downloading, go back to previous status and reject the promise.
 |                 // Error downloading, go back to previous status and reject the promise.
 | ||||||
|                 return this.setPackagePreviousStatus(siteId, component, componentId).then(() => { |                 return this.setPackagePreviousStatus(siteId, component, componentId).then(() => { | ||||||
|                     return Promise.reject(null); |                     return Promise.reject(error); | ||||||
|                 }); |                 }); | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
| @ -2566,18 +2566,26 @@ export class CoreFilepoolProvider { | |||||||
|                     dropFromQueue = true; |                     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) { |                 if (dropFromQueue) { | ||||||
|                     this.logger.debug('Item dropped from queue due to error: ' + fileUrl, errorObject); |                     this.logger.debug('Item dropped from queue due to error: ' + fileUrl, errorObject); | ||||||
| 
 | 
 | ||||||
|                     return this.removeFromQueue(siteId, fileId).catch(() => { |                     return this.removeFromQueue(siteId, fileId).catch(() => { | ||||||
|                         // Consider this as a silent error, never reject the promise here.
 |                         // Consider this as a silent error, never reject the promise here.
 | ||||||
|                     }).then(() => { |                     }).then(() => { | ||||||
|                         this.treatQueueDeferred(siteId, fileId, false); |                         this.treatQueueDeferred(siteId, fileId, false, errorMessage); | ||||||
|                         this.notifyFileDownloadError(siteId, fileId); |                         this.notifyFileDownloadError(siteId, fileId); | ||||||
|                     }); |                     }); | ||||||
|                 } else { |                 } else { | ||||||
|                     // We considered the file as legit but did not get it, failure.
 |                     // 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); |                     this.notifyFileDownloadError(siteId, fileId); | ||||||
| 
 | 
 | ||||||
|                     return Promise.reject(errorObject); |                     return Promise.reject(errorObject); | ||||||
| @ -2912,13 +2920,14 @@ export class CoreFilepoolProvider { | |||||||
|      * @param {string} siteId The site ID. |      * @param {string} siteId The site ID. | ||||||
|      * @param {string} fileId The file ID. |      * @param {string} fileId The file ID. | ||||||
|      * @param {boolean} resolve True if promise should be resolved, false if it should be rejected. |      * @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 (this.queueDeferreds[siteId] && this.queueDeferreds[siteId][fileId]) { | ||||||
|             if (resolve) { |             if (resolve) { | ||||||
|                 this.queueDeferreds[siteId][fileId].resolve(); |                 this.queueDeferreds[siteId][fileId].resolve(); | ||||||
|             } else { |             } else { | ||||||
|                 this.queueDeferreds[siteId][fileId].reject(); |                 this.queueDeferreds[siteId][fileId].reject(error); | ||||||
|             } |             } | ||||||
|             delete this.queueDeferreds[siteId][fileId]; |             delete this.queueDeferreds[siteId][fileId]; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ import { CoreTextUtilsProvider } from './text'; | |||||||
| import { CoreAppProvider } from '../app'; | import { CoreAppProvider } from '../app'; | ||||||
| import { CoreConfigProvider } from '../config'; | import { CoreConfigProvider } from '../config'; | ||||||
| import { CoreUrlUtilsProvider } from './url'; | import { CoreUrlUtilsProvider } from './url'; | ||||||
|  | import { CoreFileProvider } from '@providers/file'; | ||||||
| import { CoreConstants } from '@core/constants'; | import { CoreConstants } from '@core/constants'; | ||||||
| import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip'; | import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip'; | ||||||
| import { Md5 } from 'ts-md5/dist/md5'; | import { Md5 } from 'ts-md5/dist/md5'; | ||||||
| @ -66,7 +67,8 @@ export class CoreDomUtilsProvider { | |||||||
|     constructor(private translate: TranslateService, private loadingCtrl: LoadingController, private toastCtrl: ToastController, |     constructor(private translate: TranslateService, private loadingCtrl: LoadingController, private toastCtrl: ToastController, | ||||||
|             private alertCtrl: AlertController, private textUtils: CoreTextUtilsProvider, private appProvider: CoreAppProvider, |             private alertCtrl: AlertController, private textUtils: CoreTextUtilsProvider, private appProvider: CoreAppProvider, | ||||||
|             private platform: Platform, private configProvider: CoreConfigProvider, private urlUtils: CoreUrlUtilsProvider, |             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.
 |         // Check if debug messages should be displayed.
 | ||||||
|         configProvider.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false).then((debugDisplay) => { |         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, |     confirmDownloadSize(size: any, message?: string, unknownMessage?: string, wifiThreshold?: number, limitedThreshold?: number, | ||||||
|             alwaysConfirm?: boolean): Promise<void> { |             alwaysConfirm?: boolean): Promise<void> { | ||||||
|         wifiThreshold = typeof wifiThreshold == 'undefined' ? CoreConstants.WIFI_DOWNLOAD_THRESHOLD : wifiThreshold; |         const readableSize = this.textUtils.bytesToSize(size.size, 2); | ||||||
|         limitedThreshold = typeof limitedThreshold == 'undefined' ? CoreConstants.DOWNLOAD_THRESHOLD : limitedThreshold; |  | ||||||
| 
 | 
 | ||||||
|         if (size.size < 0 || (size.size == 0 && !size.total)) { |         const getAvailableBytes = new Promise((resolve): void => { | ||||||
|             // Seems size was unable to be calculated. Show a warning.
 |             if (this.appProvider.isDesktop()) { | ||||||
|             unknownMessage = unknownMessage || 'core.course.confirmdownloadunknownsize'; |                 // 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); | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|             return this.showConfirm(this.translate.instant(unknownMessage)); |         const getAvailableSpace = getAvailableBytes.then((availableBytes: number) => { | ||||||
|         } else if (!size.total) { |             if (availableBytes === null) { | ||||||
|             // Filesize is only partial.
 |                 return ''; | ||||||
|             const readableSize = this.textUtils.bytesToSize(size.size, 2); |             } 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.showConfirm(this.translate.instant('core.course.confirmpartialdownloadsize', { size: readableSize })); |                 return this.translate.instant('core.course.availablespace', {available: availableSize}); | ||||||
|         } else if (alwaysConfirm || size.size >= wifiThreshold || |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         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(wifiPrefix + this.translate.instant(unknownMessage, {availableSpace: availableSpace})); | ||||||
|  |             } else if (!size.total) { | ||||||
|  |                 // Filesize is only partial.
 | ||||||
|  | 
 | ||||||
|  |                 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)) { |                 (this.appProvider.isNetworkAccessLimited() && size.size >= limitedThreshold)) { | ||||||
|             message = message || 'core.course.confirmdownload'; |                 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(); |             return Promise.resolve(); | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -113,7 +113,7 @@ export class CoreTextUtilsProvider { | |||||||
|      */ |      */ | ||||||
|     bytesToSize(bytes: number, precision: number = 2): string { |     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'); |             return this.translate.instant('core.notapplicable'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user