commit
78c0af8a4b
|
@ -1373,6 +1373,7 @@
|
||||||
"core.confirmgotabrootdefault": "local_moodlemobileapp",
|
"core.confirmgotabrootdefault": "local_moodlemobileapp",
|
||||||
"core.confirmloss": "local_moodlemobileapp",
|
"core.confirmloss": "local_moodlemobileapp",
|
||||||
"core.confirmopeninbrowser": "local_moodlemobileapp",
|
"core.confirmopeninbrowser": "local_moodlemobileapp",
|
||||||
|
"core.confirmreadfiletoobig": "local_moodlemobileapp",
|
||||||
"core.considereddigitalminor": "moodle",
|
"core.considereddigitalminor": "moodle",
|
||||||
"core.content": "moodle",
|
"core.content": "moodle",
|
||||||
"core.contenteditingsynced": "local_moodlemobileapp",
|
"core.contenteditingsynced": "local_moodlemobileapp",
|
||||||
|
|
|
@ -1373,6 +1373,7 @@
|
||||||
"core.confirmgotabrootdefault": "Are you sure you want to go to the initial page of the current tab?",
|
"core.confirmgotabrootdefault": "Are you sure you want to go to the initial page of the current tab?",
|
||||||
"core.confirmloss": "Are you sure? All changes will be lost.",
|
"core.confirmloss": "Are you sure? All changes will be lost.",
|
||||||
"core.confirmopeninbrowser": "Do you want to open it in a web browser?",
|
"core.confirmopeninbrowser": "Do you want to open it in a web browser?",
|
||||||
|
"core.confirmreadfiletoobig": "It seems the app previously crashed when trying to read a file as big as this one. Are you sure you want to continue?<br><br>If this file is stored in a repository like Google Drive, please try to download the file to your device first and use the downloaded file.",
|
||||||
"core.considereddigitalminor": "You are too young to create an account on this site.",
|
"core.considereddigitalminor": "You are too young to create an account on this site.",
|
||||||
"core.content": "Content",
|
"core.content": "Content",
|
||||||
"core.contenteditingsynced": "The content you are editing has been synced.",
|
"core.contenteditingsynced": "The content you are editing has been synced.",
|
||||||
|
|
|
@ -78,7 +78,7 @@ export class CoreLocalFileComponent implements OnInit {
|
||||||
this.size = this.textUtils.bytesToSize(metadata.size, 2);
|
this.size = this.textUtils.bytesToSize(metadata.size, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timemodified = this.timeUtils.userDate(metadata.modificationTime, 'core.strftimedatetimeshort');
|
this.timemodified = this.timeUtils.userDate(metadata.modificationTime.getTime(), 'core.strftimedatetimeshort');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreFileProvider } from '@providers/file';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
import { MediaFile } from '@ionic-native/media-capture';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page to capture media in browser or desktop.
|
* Page to capture media in browser or desktop.
|
||||||
|
@ -364,13 +365,21 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy {
|
||||||
if (this.isImage && !this.isCaptureImage) {
|
if (this.isImage && !this.isCaptureImage) {
|
||||||
this.dismissWithData(fileEntry.toURL());
|
this.dismissWithData(fileEntry.toURL());
|
||||||
} else {
|
} else {
|
||||||
// The capture plugin returns a MediaFile, not a FileEntry.
|
// The capture plugin should return a MediaFile, not a FileEntry. Convert it.
|
||||||
// The only difference is that it supports a new function that won't be supported in desktop.
|
return this.fileProvider.getMetadata(fileEntry).then((metadata) => {
|
||||||
fileEntry.getFormatData = (successFn, errorFn): any => {
|
const mediaFile: MediaFile = {
|
||||||
// Nothing to do.
|
name: fileEntry.name,
|
||||||
};
|
fullPath: fileEntry.fullPath,
|
||||||
|
type: null,
|
||||||
|
lastModifiedDate: metadata.modificationTime,
|
||||||
|
size: metadata.size,
|
||||||
|
getFormatData: (successFn, errorFn): void => {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.dismissWithData([fileEntry]);
|
this.dismissWithData([mediaFile]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.domUtils.showErrorModal(err);
|
this.domUtils.showErrorModal(err);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
import { CoreFileUploaderHandler, CoreFileUploaderHandlerData } from './delegate';
|
import { CoreFileUploaderHandler, CoreFileUploaderHandlerData } from './delegate';
|
||||||
import { CoreFileUploaderHelperProvider } from './helper';
|
import { CoreFileUploaderHelperProvider } from './helper';
|
||||||
import { CoreFileUploaderProvider } from './fileuploader';
|
import { CoreFileUploaderProvider } from './fileuploader';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
/**
|
/**
|
||||||
* Handler to upload any type of file.
|
* Handler to upload any type of file.
|
||||||
*/
|
*/
|
||||||
|
@ -28,9 +29,13 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler {
|
||||||
name = 'CoreFileUploaderFile';
|
name = 'CoreFileUploaderFile';
|
||||||
priority = 1200;
|
priority = 1200;
|
||||||
|
|
||||||
constructor(private appProvider: CoreAppProvider, private platform: Platform, private timeUtils: CoreTimeUtilsProvider,
|
constructor(protected appProvider: CoreAppProvider,
|
||||||
private uploaderHelper: CoreFileUploaderHelperProvider, private uploaderProvider: CoreFileUploaderProvider,
|
protected platform: Platform,
|
||||||
private domUtils: CoreDomUtilsProvider) { }
|
protected timeUtils: CoreTimeUtilsProvider,
|
||||||
|
protected uploaderHelper: CoreFileUploaderHelperProvider,
|
||||||
|
protected uploaderProvider: CoreFileUploaderProvider,
|
||||||
|
protected domUtils: CoreDomUtilsProvider,
|
||||||
|
protected translate: TranslateService) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the handler is enabled on a site level.
|
* Whether or not the handler is enabled on a site level.
|
||||||
|
@ -107,9 +112,8 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler {
|
||||||
this.uploaderHelper.uploadFileObject(file, maxSize, upload, allowOffline, fileName).then((result) => {
|
this.uploaderHelper.uploadFileObject(file, maxSize, upload, allowOffline, fileName).then((result) => {
|
||||||
this.uploaderHelper.fileUploaded(result);
|
this.uploaderHelper.fileUploaded(result);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (error) {
|
this.domUtils.showErrorModalDefault(error,
|
||||||
this.domUtils.showErrorModal(error);
|
this.translate.instant('core.fileuploader.errorreadingfile'));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { Camera, CameraOptions } from '@ionic-native/camera';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreFileProvider, CoreFileProgressEvent } from '@providers/file';
|
import { CoreFileProvider, CoreFileProgressEvent } from '@providers/file';
|
||||||
|
import { CoreFileHelperProvider } from '@providers/file-helper';
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
@ -36,11 +37,19 @@ export class CoreFileUploaderHelperProvider {
|
||||||
protected filePickerDeferred: PromiseDefer;
|
protected filePickerDeferred: PromiseDefer;
|
||||||
protected actionSheet: ActionSheet;
|
protected actionSheet: ActionSheet;
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private translate: TranslateService,
|
constructor(logger: CoreLoggerProvider,
|
||||||
private fileUploaderProvider: CoreFileUploaderProvider, private domUtils: CoreDomUtilsProvider,
|
protected appProvider: CoreAppProvider,
|
||||||
private textUtils: CoreTextUtilsProvider, private fileProvider: CoreFileProvider, private utils: CoreUtilsProvider,
|
protected translate: TranslateService,
|
||||||
private actionSheetCtrl: ActionSheetController, private uploaderDelegate: CoreFileUploaderDelegate,
|
protected fileUploaderProvider: CoreFileUploaderProvider,
|
||||||
private camera: Camera, private platform: Platform) {
|
protected domUtils: CoreDomUtilsProvider,
|
||||||
|
protected textUtils: CoreTextUtilsProvider,
|
||||||
|
protected fileProvider: CoreFileProvider,
|
||||||
|
protected utils: CoreUtilsProvider,
|
||||||
|
protected actionSheetCtrl: ActionSheetController,
|
||||||
|
protected uploaderDelegate: CoreFileUploaderDelegate,
|
||||||
|
protected camera: Camera,
|
||||||
|
protected platform: Platform,
|
||||||
|
protected fileHelper: CoreFileHelperProvider) {
|
||||||
this.logger = logger.getInstance('CoreFileUploaderProvider');
|
this.logger = logger.getInstance('CoreFileUploaderProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,14 +108,14 @@ export class CoreFileUploaderHelperProvider {
|
||||||
const filePath = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, newName);
|
const filePath = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, newName);
|
||||||
|
|
||||||
// Write the data into the file.
|
// Write the data into the file.
|
||||||
return this.fileProvider.writeFileDataInFile(file, filePath, (progress: CoreFileProgressEvent) => {
|
return this.fileHelper.writeFileDataInFile(file, filePath, (progress: CoreFileProgressEvent) => {
|
||||||
this.showProgressModal(modal, 'core.fileuploader.readingfileperc', progress);
|
this.showProgressModal(modal, 'core.fileuploader.readingfileperc', progress);
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.logger.error('Error reading file to upload.', error);
|
this.logger.error('Error reading file to upload.', error);
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
|
|
||||||
return Promise.reject(this.translate.instant('core.fileuploader.errorreadingfile'));
|
return Promise.reject(error);
|
||||||
}).then((fileEntry) => {
|
}).then((fileEntry) => {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
|
|
||||||
|
@ -319,9 +328,7 @@ export class CoreFileUploaderHelperProvider {
|
||||||
// Success uploading or picking, return the result.
|
// Success uploading or picking, return the result.
|
||||||
this.fileUploaded(result);
|
this.fileUploaded(result);
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (error) {
|
this.domUtils.showErrorModalDefault(error, this.translate.instant('core.fileuploader.errorreadingfile'));
|
||||||
this.domUtils.showErrorModal(error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Do not close the action sheet, it will be closed if success.
|
// Do not close the action sheet, it will be closed if success.
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
"confirmgotabrootdefault": "Are you sure you want to go to the initial page of the current tab?",
|
"confirmgotabrootdefault": "Are you sure you want to go to the initial page of the current tab?",
|
||||||
"confirmloss": "Are you sure? All changes will be lost.",
|
"confirmloss": "Are you sure? All changes will be lost.",
|
||||||
"confirmopeninbrowser": "Do you want to open it in a web browser?",
|
"confirmopeninbrowser": "Do you want to open it in a web browser?",
|
||||||
|
"confirmreadfiletoobig": "It seems the app previously crashed when trying to read a file as big as this one. Are you sure you want to continue?<br><br>If this file is stored in a repository like Google Drive, please try to download the file to your device first and use the downloaded file.",
|
||||||
"considereddigitalminor": "You are too young to create an account on this site.",
|
"considereddigitalminor": "You are too young to create an account on this site.",
|
||||||
"content": "Content",
|
"content": "Content",
|
||||||
"contenteditingsynced": "The content you are editing has been synced.",
|
"contenteditingsynced": "The content you are editing has been synced.",
|
||||||
|
|
|
@ -15,12 +15,15 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreAppProvider } from './app';
|
import { CoreAppProvider } from './app';
|
||||||
import { CoreFileProvider } from './file';
|
import { CoreConfigProvider } from './config';
|
||||||
|
import { CoreFileProvider, CoreFileProgressFunction } from './file';
|
||||||
import { CoreFilepoolProvider } from './filepool';
|
import { CoreFilepoolProvider } from './filepool';
|
||||||
import { CoreSitesProvider } from './sites';
|
import { CoreSitesProvider } from './sites';
|
||||||
import { CoreWSProvider } from './ws';
|
import { CoreWSProvider } from './ws';
|
||||||
|
import { CoreDomUtilsProvider } from './utils/dom';
|
||||||
import { CoreUtilsProvider } from './utils/utils';
|
import { CoreUtilsProvider } from './utils/utils';
|
||||||
import { CoreConstants } from '@core/constants';
|
import { CoreConstants } from '@core/constants';
|
||||||
|
import { FileEntry } from '@ionic-native/file';
|
||||||
import { makeSingleton } from '@singletons/core.singletons';
|
import { makeSingleton } from '@singletons/core.singletons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,9 +32,23 @@ import { makeSingleton } from '@singletons/core.singletons';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CoreFileHelperProvider {
|
export class CoreFileHelperProvider {
|
||||||
|
|
||||||
constructor(private fileProvider: CoreFileProvider, private filepoolProvider: CoreFilepoolProvider,
|
// Variables for reading files in chunks.
|
||||||
private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider, private translate: TranslateService,
|
protected MAX_CHUNK_SIZE_NAME = 'file_max_chunk_size';
|
||||||
private utils: CoreUtilsProvider, private wsProvider: CoreWSProvider) { }
|
protected READ_CHUNK_ATTEMPT_NAME = 'file_read_chunk_attempt';
|
||||||
|
protected maxChunkSize = -1;
|
||||||
|
|
||||||
|
constructor(protected fileProvider: CoreFileProvider,
|
||||||
|
protected filepoolProvider: CoreFilepoolProvider,
|
||||||
|
protected sitesProvider: CoreSitesProvider,
|
||||||
|
protected appProvider: CoreAppProvider,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
protected utils: CoreUtilsProvider,
|
||||||
|
protected wsProvider: CoreWSProvider,
|
||||||
|
protected configProvider: CoreConfigProvider,
|
||||||
|
protected domUtils: CoreDomUtilsProvider) {
|
||||||
|
|
||||||
|
this.initMaxChunkSize();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function to open a file, downloading it if needed.
|
* Convenience function to open a file, downloading it if needed.
|
||||||
|
@ -240,6 +257,34 @@ export class CoreFileHelperProvider {
|
||||||
return file.timemodified || 0;
|
return file.timemodified || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the max chunk size.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async initMaxChunkSize(): Promise<void> {
|
||||||
|
const sizes = await Promise.all([
|
||||||
|
await this.configProvider.get(this.READ_CHUNK_ATTEMPT_NAME, -1), // Check if there is any attempt pending.
|
||||||
|
await this.configProvider.get(this.MAX_CHUNK_SIZE_NAME, -1), // Retrieve current max chunk size from DB.
|
||||||
|
]);
|
||||||
|
|
||||||
|
const attemptSize = sizes[0];
|
||||||
|
const maxChunkSize = sizes[1];
|
||||||
|
|
||||||
|
if (attemptSize != -1 && (maxChunkSize == -1 || attemptSize < maxChunkSize)) {
|
||||||
|
// Store the attempt's size as the max size.
|
||||||
|
this.storeMaxChunkSize(attemptSize);
|
||||||
|
} else {
|
||||||
|
// No attempt or the max size is already lower. Keep current max size.
|
||||||
|
this.maxChunkSize = maxChunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attemptSize != -1) {
|
||||||
|
// Clean pending attempt.
|
||||||
|
await this.configProvider.delete(this.READ_CHUNK_ATTEMPT_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a state is downloaded or outdated.
|
* Check if a state is downloaded or outdated.
|
||||||
*
|
*
|
||||||
|
@ -334,6 +379,99 @@ export class CoreFileHelperProvider {
|
||||||
|
|
||||||
throw new Error('Couldn\'t determine file size: ' + file.fileurl);
|
throw new Error('Couldn\'t determine file size: ' + file.fileurl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save max chunk size.
|
||||||
|
*
|
||||||
|
* @param size Size to store.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async storeMaxChunkSize(size: number): Promise<void> {
|
||||||
|
this.maxChunkSize = size;
|
||||||
|
|
||||||
|
await this.configProvider.set(this.MAX_CHUNK_SIZE_NAME, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write some file data into a filesystem file.
|
||||||
|
* It's done in chunks to prevent crashing the app for big files.
|
||||||
|
*
|
||||||
|
* @param file The data to write.
|
||||||
|
* @param path Path where to store the data.
|
||||||
|
* @param onProgress Function to call on progress.
|
||||||
|
* @param offset Offset where to start reading from.
|
||||||
|
* @param append Whether to append the data to the end of the file.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async writeFileDataInFile(file: Blob, path: string, onProgress?: CoreFileProgressFunction, offset: number = 0,
|
||||||
|
append?: boolean): Promise<FileEntry> {
|
||||||
|
|
||||||
|
offset = offset || 0;
|
||||||
|
|
||||||
|
// Get the chunk to read and write.
|
||||||
|
const readWholeFile = offset === 0 && CoreFileProvider.CHUNK_SIZE >= file.size;
|
||||||
|
const chunk = readWholeFile ? file : file.slice(offset, Math.min(offset + CoreFileProvider.CHUNK_SIZE, file.size));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileEntry = await this.fileProvider.writeFileDataInFileChunk(chunk, path, append);
|
||||||
|
|
||||||
|
offset += CoreFileProvider.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);
|
||||||
|
} catch (error) {
|
||||||
|
if (readWholeFile || !error || error.name != 'NotReadableError') {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission error when reading file in chunks. This usually happens with Google Drive files.
|
||||||
|
// Try to read the whole file at once.
|
||||||
|
return this.writeBigFileDataInFile(file, path, onProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a big file data into a filesystem file without using chunks.
|
||||||
|
* The app can crash when doing this with big files, so this function will try to control the max size that works
|
||||||
|
* and warn the user if he's trying to upload a file that is too big.
|
||||||
|
*
|
||||||
|
* @param file The data to write.
|
||||||
|
* @param path Path where to store the data.
|
||||||
|
* @param onProgress Function to call on progress.
|
||||||
|
* @return Promise resolved with the file.
|
||||||
|
*/
|
||||||
|
protected async writeBigFileDataInFile(file: Blob, path: string, onProgress?: CoreFileProgressFunction): Promise<FileEntry> {
|
||||||
|
if (this.maxChunkSize != -1 && file.size >= this.maxChunkSize) {
|
||||||
|
// The file size is bigger than the max allowed size. Ask the user to confirm.
|
||||||
|
await this.domUtils.showConfirm(this.translate.instant('core.confirmreadfiletoobig'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the "attempt".
|
||||||
|
await this.configProvider.set(this.READ_CHUNK_ATTEMPT_NAME, file.size);
|
||||||
|
|
||||||
|
// Write the whole file.
|
||||||
|
const fileEntry = await this.fileProvider.writeFileDataInFileChunk(file, path, false);
|
||||||
|
|
||||||
|
// Success, remove the attempt and increase the max chunk size if needed.
|
||||||
|
await this.configProvider.delete(this.READ_CHUNK_ATTEMPT_NAME);
|
||||||
|
|
||||||
|
if (file.size > this.maxChunkSize) {
|
||||||
|
await this.storeMaxChunkSize(file.size + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileEntry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CoreFileHelper extends makeSingleton(CoreFileHelperProvider) {}
|
export class CoreFileHelper extends makeSingleton(CoreFileHelperProvider) {}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Platform } from 'ionic-angular';
|
import { Platform } from 'ionic-angular';
|
||||||
import { File, FileEntry, DirectoryEntry } from '@ionic-native/file';
|
import { File, FileEntry, DirectoryEntry, Entry, Metadata } from '@ionic-native/file';
|
||||||
|
|
||||||
import { CoreAppProvider } from './app';
|
import { CoreAppProvider } from './app';
|
||||||
import { CoreLoggerProvider } from './logger';
|
import { CoreLoggerProvider } from './logger';
|
||||||
|
@ -44,6 +44,11 @@ export interface CoreFileProgressEvent {
|
||||||
total?: number;
|
total?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Progress function.
|
||||||
|
*/
|
||||||
|
export type CoreFileProgressFunction = (event: CoreFileProgressEvent) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory to interact with the file system.
|
* Factory to interact with the file system.
|
||||||
*/
|
*/
|
||||||
|
@ -60,14 +65,21 @@ export class CoreFileProvider {
|
||||||
static SITESFOLDER = 'sites';
|
static SITESFOLDER = 'sites';
|
||||||
static TMPFOLDER = 'tmp';
|
static TMPFOLDER = 'tmp';
|
||||||
|
|
||||||
|
static CHUNK_SIZE = 10485760; // 10 MB.
|
||||||
|
|
||||||
protected logger;
|
protected logger;
|
||||||
protected initialized = false;
|
protected initialized = false;
|
||||||
protected basePath = '';
|
protected basePath = '';
|
||||||
protected isHTMLAPI = false;
|
protected isHTMLAPI = false;
|
||||||
protected CHUNK_SIZE = 10485760; // 10 MB.
|
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private platform: Platform, private file: File, private appProvider: CoreAppProvider,
|
constructor(logger: CoreLoggerProvider,
|
||||||
private textUtils: CoreTextUtilsProvider, private zip: Zip, private mimeUtils: CoreMimetypeUtilsProvider) {
|
protected platform: Platform,
|
||||||
|
protected file: File,
|
||||||
|
protected appProvider: CoreAppProvider,
|
||||||
|
protected textUtils: CoreTextUtilsProvider,
|
||||||
|
protected zip: Zip,
|
||||||
|
protected mimeUtils: CoreMimetypeUtilsProvider) {
|
||||||
|
|
||||||
this.logger = logger.getInstance('CoreFileProvider');
|
this.logger = logger.getInstance('CoreFileProvider');
|
||||||
|
|
||||||
if (platform.is('android') && !Object.getOwnPropertyDescriptor(FileReader.prototype, 'onloadend')) {
|
if (platform.is('android') && !Object.getOwnPropertyDescriptor(FileReader.prototype, 'onloadend')) {
|
||||||
|
@ -545,7 +557,7 @@ export class CoreFileProvider {
|
||||||
|
|
||||||
reader.onloadend = (evt): void => {
|
reader.onloadend = (evt): void => {
|
||||||
const target = <any> evt.target; // Convert to <any> to be able to use non-standard properties.
|
const target = <any> evt.target; // Convert to <any> to be able to use non-standard properties.
|
||||||
if (target.result !== undefined || target.result !== null) {
|
if (target.result !== undefined && target.result !== null) {
|
||||||
if (format == CoreFileProvider.FORMATJSON) {
|
if (format == CoreFileProvider.FORMATJSON) {
|
||||||
// Convert to object.
|
// Convert to object.
|
||||||
const parsed = this.textUtils.parseJSON(target.result, null);
|
const parsed = this.textUtils.parseJSON(target.result, null);
|
||||||
|
@ -558,7 +570,7 @@ export class CoreFileProvider {
|
||||||
} else {
|
} else {
|
||||||
resolve(target.result);
|
resolve(target.result);
|
||||||
}
|
}
|
||||||
} else if (target.error !== undefined || target.error !== null) {
|
} else if (target.error !== undefined && target.error !== null) {
|
||||||
reject(target.error);
|
reject(target.error);
|
||||||
} else {
|
} else {
|
||||||
reject({ code: null, message: 'READER_ONLOADEND_ERR' });
|
reject({ code: null, message: 'READER_ONLOADEND_ERR' });
|
||||||
|
@ -602,7 +614,7 @@ export class CoreFileProvider {
|
||||||
* @param append Whether to append the data to the end of the file.
|
* @param append Whether to append the data to the end of the file.
|
||||||
* @return Promise to be resolved when the file is written.
|
* @return Promise to be resolved when the file is written.
|
||||||
*/
|
*/
|
||||||
writeFile(path: string, data: any, append?: boolean): Promise<any> {
|
writeFile(path: string, data: any, append?: boolean): Promise<FileEntry> {
|
||||||
return this.init().then(() => {
|
return this.init().then(() => {
|
||||||
// Remove basePath if it's in the path.
|
// Remove basePath if it's in the path.
|
||||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||||
|
@ -634,17 +646,21 @@ export class CoreFileProvider {
|
||||||
* @param offset Offset where to start reading from.
|
* @param offset Offset where to start reading from.
|
||||||
* @param append Whether to append the data to the end of the file.
|
* @param append Whether to append the data to the end of the file.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
|
* @deprecated since 3.8.3. Please use CoreFileHelperProvider.writeFileDataInFile instead.
|
||||||
*/
|
*/
|
||||||
writeFileDataInFile(file: any, path: string, onProgress?: (event: CoreFileProgressEvent) => any, offset: number = 0,
|
async writeFileDataInFile(file: Blob, path: string, onProgress?: CoreFileProgressFunction, offset: number = 0,
|
||||||
append?: boolean): Promise<any> {
|
append?: boolean): Promise<FileEntry> {
|
||||||
|
|
||||||
offset = offset || 0;
|
offset = offset || 0;
|
||||||
|
|
||||||
// Get the chunk to read.
|
// Get the chunk to read and write.
|
||||||
const blob = file.slice(offset, Math.min(offset + this.CHUNK_SIZE, file.size));
|
const readWholeFile = offset === 0 && CoreFileProvider.CHUNK_SIZE >= file.size;
|
||||||
|
const chunk = readWholeFile ? file : file.slice(offset, Math.min(offset + CoreFileProvider.CHUNK_SIZE, file.size));
|
||||||
|
|
||||||
return this.writeFileDataInFileChunk(blob, path, append).then((fileEntry) => {
|
try {
|
||||||
offset += this.CHUNK_SIZE;
|
const fileEntry = await this.writeFileDataInFileChunk(chunk, path, append);
|
||||||
|
|
||||||
|
offset += CoreFileProvider.CHUNK_SIZE;
|
||||||
|
|
||||||
onProgress && onProgress({
|
onProgress && onProgress({
|
||||||
lengthComputable: true,
|
lengthComputable: true,
|
||||||
|
@ -659,7 +675,15 @@ export class CoreFileProvider {
|
||||||
|
|
||||||
// Read the next chunk.
|
// Read the next chunk.
|
||||||
return this.writeFileDataInFile(file, path, onProgress, offset, true);
|
return this.writeFileDataInFile(file, path, onProgress, offset, true);
|
||||||
});
|
} catch (error) {
|
||||||
|
if (readWholeFile || !error || error.name != 'NotReadableError') {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission error when reading file in chunks. This usually happens with Google Drive files.
|
||||||
|
// Try to read the whole file at once.
|
||||||
|
return this.writeFileDataInFileChunk(file, path, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -670,7 +694,7 @@ export class CoreFileProvider {
|
||||||
* @param append Whether to append the data to the end of the file.
|
* @param append Whether to append the data to the end of the file.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected writeFileDataInFileChunk(chunkData: any, path: string, append?: boolean): Promise<any> {
|
writeFileDataInFileChunk(chunkData: Blob, path: string, append?: boolean): Promise<FileEntry> {
|
||||||
// Read the chunk data.
|
// Read the chunk data.
|
||||||
return this.readFileData(chunkData, CoreFileProvider.FORMATARRAYBUFFER).then((fileData) => {
|
return this.readFileData(chunkData, CoreFileProvider.FORMATARRAYBUFFER).then((fileData) => {
|
||||||
// Write the data in the file.
|
// Write the data in the file.
|
||||||
|
@ -1053,7 +1077,7 @@ export class CoreFileProvider {
|
||||||
* @param fileEntry FileEntry retrieved from getFile or similar.
|
* @param fileEntry FileEntry retrieved from getFile or similar.
|
||||||
* @return Promise resolved with metadata.
|
* @return Promise resolved with metadata.
|
||||||
*/
|
*/
|
||||||
getMetadata(fileEntry: Entry): Promise<any> {
|
getMetadata(fileEntry: Entry): Promise<Metadata> {
|
||||||
if (!fileEntry || !fileEntry.getMetadata) {
|
if (!fileEntry || !fileEntry.getMetadata) {
|
||||||
return Promise.reject(null);
|
return Promise.reject(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
This files describes API changes in the Moodle Mobile app,
|
This files describes API changes in the Moodle Mobile app,
|
||||||
information provided here is intended especially for developers.
|
information provided here is intended especially for developers.
|
||||||
|
|
||||||
|
=== 3.8.3 ===
|
||||||
|
|
||||||
|
- CoreFileProvider.writeFileDataInFile has been deprecated. Please use CoreFileHelperProvider.writeFileDataInFile instead.
|
||||||
|
|
||||||
=== 3.8.0 ===
|
=== 3.8.0 ===
|
||||||
|
|
||||||
- CoreDomUtilsProvider.extractDownloadableFilesFromHtml and CoreDomUtilsProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects have been deprecated. Please use CoreFilepoolProvider.extractDownloadableFilesFromHtml and CoreFilepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects. We had to move them to prevent a circular dependency.
|
- CoreDomUtilsProvider.extractDownloadableFilesFromHtml and CoreDomUtilsProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects have been deprecated. Please use CoreFilepoolProvider.extractDownloadableFilesFromHtml and CoreFilepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects. We had to move them to prevent a circular dependency.
|
||||||
|
|
Loading…
Reference in New Issue