forked from EVOgeek/Vmeda.Online
		
	MOBILE-1748 core: Read file data in chunks to prevent crashes
This commit is contained in:
		
							parent
							
								
									8248890189
								
							
						
					
					
						commit
						14eb35927c
					
				| @ -1309,6 +1309,7 @@ | ||||
|   "core.fileuploader.more": "data", | ||||
|   "core.fileuploader.photoalbums": "local_moodlemobileapp", | ||||
|   "core.fileuploader.readingfile": "local_moodlemobileapp", | ||||
|   "core.fileuploader.readingfileperc": "local_moodlemobileapp", | ||||
|   "core.fileuploader.selectafile": "local_moodlemobileapp", | ||||
|   "core.fileuploader.uploadafile": "local_moodlemobileapp", | ||||
|   "core.fileuploader.uploading": "local_moodlemobileapp", | ||||
|  | ||||
| @ -1309,6 +1309,7 @@ | ||||
|     "core.fileuploader.more": "More", | ||||
|     "core.fileuploader.photoalbums": "Photo albums", | ||||
|     "core.fileuploader.readingfile": "Reading file", | ||||
|     "core.fileuploader.readingfileperc": "Reading file: {{$a}}%", | ||||
|     "core.fileuploader.selectafile": "Select a file", | ||||
|     "core.fileuploader.uploadafile": "Upload a file", | ||||
|     "core.fileuploader.uploading": "Uploading", | ||||
|  | ||||
| @ -20,6 +20,7 @@ | ||||
|     "more": "More", | ||||
|     "photoalbums": "Photo albums", | ||||
|     "readingfile": "Reading file", | ||||
|     "readingfileperc": "Reading file: {{$a}}%", | ||||
|     "selectafile": "Select a file", | ||||
|     "uploadafile": "Upload a file", | ||||
|     "uploading": "Uploading", | ||||
|  | ||||
| @ -13,12 +13,12 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { ActionSheetController, ActionSheet, Platform } from 'ionic-angular'; | ||||
| import { ActionSheetController, ActionSheet, Platform, Loading } from 'ionic-angular'; | ||||
| import { MediaFile } from '@ionic-native/media-capture'; | ||||
| import { Camera, CameraOptions } from '@ionic-native/camera'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreAppProvider } from '@providers/app'; | ||||
| import { CoreFileProvider } from '@providers/file'; | ||||
| import { CoreFileProvider, CoreFileProgressEvent } from '@providers/file'; | ||||
| import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| @ -93,18 +93,15 @@ export class CoreFileUploaderHelperProvider { | ||||
|         name = name || file.name; | ||||
| 
 | ||||
|         const modal = this.domUtils.showModalLoading('core.fileuploader.readingfile', true); | ||||
|         let fileData; | ||||
| 
 | ||||
|         // We have the data of the file to be uploaded, but not its URL (needed). Create a copy of the file to upload it.
 | ||||
|         return this.fileProvider.readFileData(file, CoreFileProvider.FORMATARRAYBUFFER).then((data) => { | ||||
|             fileData = data; | ||||
| 
 | ||||
|             // Get unique name for the copy.
 | ||||
|             return this.fileProvider.getUniqueNameInFolder(CoreFileProvider.TMPFOLDER, name); | ||||
|         }).then((newName) => { | ||||
|         // Get unique name for the copy.
 | ||||
|         return this.fileProvider.getUniqueNameInFolder(CoreFileProvider.TMPFOLDER, name).then((newName) => { | ||||
|             const filePath = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, newName); | ||||
| 
 | ||||
|             return this.fileProvider.writeFile(filePath, fileData); | ||||
|             // Write the data into the file.
 | ||||
|             return this.fileProvider.writeFileDataInFile(file, filePath, (progress: CoreFileProgressEvent) => { | ||||
|                 this.showProgressModal(modal, 'core.fileuploader.readingfileperc', progress); | ||||
|             }); | ||||
|         }).catch((error) => { | ||||
|             this.logger.error('Error reading file to upload.', error); | ||||
|             modal.dismiss(); | ||||
| @ -681,16 +678,7 @@ export class CoreFileUploaderHelperProvider { | ||||
| 
 | ||||
|             return this.fileUploaderProvider.uploadFile(path, options, (progress: ProgressEvent) => { | ||||
|                 // Progress uploading.
 | ||||
|                 if (progress && progress.lengthComputable) { | ||||
|                     const perc = Math.min((progress.loaded / progress.total) * 100, 100); | ||||
|                     if (perc >= 0) { | ||||
|                         modal.setContent(this.translate.instant('core.fileuploader.uploadingperc', { $a: perc.toFixed(1) })); | ||||
|                         if (modal._cmp && modal._cmp.changeDetectorRef) { | ||||
|                             // Force a change detection, otherwise the content is not updated.
 | ||||
|                             modal._cmp.changeDetectorRef.detectChanges(); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 this.showProgressModal(modal, 'core.fileuploader.uploadingperc', progress); | ||||
|             }, siteId).catch((error) => { | ||||
|                 this.logger.error('Error uploading file.', error); | ||||
| 
 | ||||
| @ -705,4 +693,27 @@ export class CoreFileUploaderHelperProvider { | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show a progress modal. | ||||
|      * | ||||
|      * @param {Loading} modal The modal where to show the progress. | ||||
|      * @param {string} stringKey The key of the string to display. | ||||
|      * @param {ProgressEvent|CoreFileProgressEvent} progress The progress event. | ||||
|      */ | ||||
|     protected showProgressModal(modal: Loading, stringKey: string, progress: ProgressEvent | CoreFileProgressEvent): void { | ||||
|         if (progress && progress.lengthComputable) { | ||||
|             // Calculate the progress percentage.
 | ||||
|             const perc = Math.min((progress.loaded / progress.total) * 100, 100); | ||||
| 
 | ||||
|             if (perc >= 0) { | ||||
|                 modal.setContent(this.translate.instant(stringKey, { $a: perc.toFixed(1) })); | ||||
| 
 | ||||
|                 if (modal._cmp && modal._cmp.changeDetectorRef) { | ||||
|                     // Force a change detection, otherwise the content is not updated.
 | ||||
|                     modal._cmp.changeDetectorRef.detectChanges(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -22,6 +22,29 @@ import { CoreMimetypeUtilsProvider } from './utils/mimetype'; | ||||
| import { CoreTextUtilsProvider } from './utils/text'; | ||||
| import { Zip } from '@ionic-native/zip'; | ||||
| 
 | ||||
| /** | ||||
|  * Progress event used when writing a file data into a file. | ||||
|  */ | ||||
| export interface CoreFileProgressEvent { | ||||
|     /** | ||||
|      * Whether the values are reliabñe. | ||||
|      * @type {boolean} | ||||
|      */ | ||||
|     lengthComputable?: boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * Number of treated bytes. | ||||
|      * @type {number} | ||||
|      */ | ||||
|     loaded?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * Total of bytes. | ||||
|      * @type {number} | ||||
|      */ | ||||
|     total?: number; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Factory to interact with the file system. | ||||
|  */ | ||||
| @ -41,6 +64,7 @@ export class CoreFileProvider { | ||||
|     protected initialized = false; | ||||
|     protected basePath = ''; | ||||
|     protected isHTMLAPI = false; | ||||
|     protected CHUNK_SIZE = 10485760; // 10 MB.
 | ||||
| 
 | ||||
|     constructor(logger: CoreLoggerProvider, private platform: Platform, private file: File, private appProvider: CoreAppProvider, | ||||
|             private textUtils: CoreTextUtilsProvider, private zip: Zip, private mimeUtils: CoreMimetypeUtilsProvider) { | ||||
| @ -543,9 +567,10 @@ export class CoreFileProvider { | ||||
|      * | ||||
|      * @param {string} path Relative path to the file. | ||||
|      * @param {any} data Data to write. | ||||
|      * @param {boolean} [append] Whether to append the data to the end of the file. | ||||
|      * @return {Promise<any>} Promise to be resolved when the file is written. | ||||
|      */ | ||||
|     writeFile(path: string, data: any): Promise<any> { | ||||
|     writeFile(path: string, data: any, append?: boolean): Promise<any> { | ||||
|         return this.init().then(() => { | ||||
|             // Remove basePath if it's in the path.
 | ||||
|             path = this.removeStartingSlash(path.replace(this.basePath, '')); | ||||
| @ -560,13 +585,67 @@ export class CoreFileProvider { | ||||
|                     data = new Blob([data], { type: type || 'text/plain' }); | ||||
|                 } | ||||
| 
 | ||||
|                 return this.file.writeFile(this.basePath, path, data, { replace: true }).then(() => { | ||||
|                 return this.file.writeFile(this.basePath, path, data, { replace: !append, append: !!append }).then(() => { | ||||
|                     return fileEntry; | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Write some file data into a filesystem file. | ||||
|      * It's done in chunks to prevent crashing the app for big files. | ||||
|      * | ||||
|      * @param {any} file The data to write. | ||||
|      * @param {string} path Path where to store the data. | ||||
|      * @param {Function} [onProgress] Function to call on progress. | ||||
|      * @param {number} [offset=0] Offset where to start reading from. | ||||
|      * @param {boolean} [append] Whether to append the data to the end of the file. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     writeFileDataInFile(file: any, path: string, onProgress?: (event: CoreFileProgressEvent) => any, offset: number = 0, | ||||
|             append?: boolean): Promise<any> { | ||||
| 
 | ||||
|         offset = offset || 0; | ||||
| 
 | ||||
|         // Get the chunk to read.
 | ||||
|         const blob = file.slice(offset, Math.min(offset + this.CHUNK_SIZE, file.size)); | ||||
| 
 | ||||
|         return this.writeFileDataInFileChunk(blob, path, append).then((fileEntry) => { | ||||
|             offset += this.CHUNK_SIZE; | ||||
| 
 | ||||
|             onProgress && onProgress({ | ||||
|                 lengthComputable: true, | ||||
|                 loaded: offset, | ||||
|                 total: file.size | ||||
|             }); | ||||
| 
 | ||||
|             if (offset >= file.size) { | ||||
|                 // Done, stop.
 | ||||
|                 return fileEntry; | ||||
|             } | ||||
| 
 | ||||
|             // Read the next chunk.
 | ||||
|             return this.writeFileDataInFile(file, path, onProgress, offset, true); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Write a chunk of data into a file. | ||||
|      * | ||||
|      * @param {any} chunkData The chunk of data. | ||||
|      * @param {string} path Path where to store the data. | ||||
|      * @param {boolean} [append] Whether to append the data to the end of the file. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     protected writeFileDataInFileChunk(chunkData: any, path: string, append?: boolean): Promise<any> { | ||||
|         // Read the chunk data.
 | ||||
|         return this.readFileData(chunkData, CoreFileProvider.FORMATARRAYBUFFER).then((fileData) => { | ||||
|             // Write the data in the file.
 | ||||
|             return this.writeFile(path, fileData, append); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gets a file that might be outside the app's folder. | ||||
|      * | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user