MOBILE-2312 uploader: Implement file uploader helper
parent
a8477e1cf5
commit
4492b43061
|
@ -53,6 +53,7 @@ import { CoreEmulatorModule } from '../core/emulator/emulator.module';
|
|||
import { CoreLoginModule } from '../core/login/login.module';
|
||||
import { CoreMainMenuModule } from '../core/mainmenu/mainmenu.module';
|
||||
import { CoreCoursesModule } from '../core/courses/courses.module';
|
||||
import { CoreFileUploaderModule } from '../core/fileuploader/fileuploader.module';
|
||||
|
||||
|
||||
// For translate loader. AoT requires an exported function for factories.
|
||||
|
@ -82,6 +83,7 @@ export function createTranslateLoader(http: HttpClient) {
|
|||
CoreLoginModule,
|
||||
CoreMainMenuModule,
|
||||
CoreCoursesModule,
|
||||
CoreFileUploaderModule,
|
||||
CoreComponentsModule
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CoreFileUploaderProvider } from './providers/fileuploader';
|
||||
import { CoreFileUploaderHelperProvider } from './providers/helper';
|
||||
import { CoreFileUploaderDelegate } from './providers/delegate';
|
||||
import { CoreFileUploaderAlbumHandler } from './providers/album-handler';
|
||||
import { CoreFileUploaderAudioHandler } from './providers/audio-handler';
|
||||
import { CoreFileUploaderCameraHandler } from './providers/camera-handler';
|
||||
import { CoreFileUploaderFileHandler } from './providers/file-handler';
|
||||
import { CoreFileUploaderVideoHandler } from './providers/video-handler';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
],
|
||||
imports: [
|
||||
],
|
||||
providers: [
|
||||
CoreFileUploaderProvider,
|
||||
CoreFileUploaderHelperProvider,
|
||||
CoreFileUploaderDelegate,
|
||||
CoreFileUploaderAlbumHandler,
|
||||
CoreFileUploaderAudioHandler,
|
||||
CoreFileUploaderCameraHandler,
|
||||
CoreFileUploaderFileHandler,
|
||||
CoreFileUploaderVideoHandler
|
||||
]
|
||||
})
|
||||
export class CoreFileUploaderModule {
|
||||
constructor(delegate: CoreFileUploaderDelegate, albumHandler: CoreFileUploaderAlbumHandler,
|
||||
audioHandler: CoreFileUploaderAudioHandler, cameraHandler: CoreFileUploaderCameraHandler,
|
||||
videoHandler: CoreFileUploaderVideoHandler, fileHandler: CoreFileUploaderFileHandler) {
|
||||
delegate.registerHandler(albumHandler);
|
||||
delegate.registerHandler(audioHandler);
|
||||
delegate.registerHandler(cameraHandler);
|
||||
delegate.registerHandler(fileHandler);
|
||||
delegate.registerHandler(videoHandler);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,687 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActionSheetController, ActionSheet, Platform } from 'ionic-angular';
|
||||
import { MediaCapture, 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 { CoreLoggerProvider } from '../../../providers/logger';
|
||||
import { CoreDomUtilsProvider } from '../../../providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '../../../providers/utils/text';
|
||||
import { CoreUtilsProvider, PromiseDefer } from '../../../providers/utils/utils';
|
||||
import { CoreFileUploaderProvider, CoreFileUploaderOptions } from './fileuploader';
|
||||
import { CoreFileUploaderDelegate } from './delegate';
|
||||
|
||||
/**
|
||||
* Helper service to upload files.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreFileUploaderHelperProvider {
|
||||
|
||||
protected logger;
|
||||
protected filePickerDeferred: PromiseDefer;
|
||||
protected actionSheet: ActionSheet;
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private translate: TranslateService,
|
||||
private fileUploaderProvider: CoreFileUploaderProvider, private domUtils: CoreDomUtilsProvider,
|
||||
private textUtils: CoreTextUtilsProvider, private fileProvider: CoreFileProvider, private utils: CoreUtilsProvider,
|
||||
private actionSheetCtrl: ActionSheetController, private uploaderDelegate: CoreFileUploaderDelegate,
|
||||
private mediaCapture: MediaCapture, private camera: Camera, private platform: Platform) {
|
||||
this.logger = logger.getInstance('CoreFileUploaderProvider');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a confirmation modal to the user if the size of the file is bigger than the allowed threshold.
|
||||
*
|
||||
* @param {number} size File size.
|
||||
* @param {boolean} [alwaysConfirm] True to show a confirm even if the size isn't high.
|
||||
* @param {boolean} [allowOffline] True to allow uploading in offline.
|
||||
* @param {number} [wifiThreshold] Threshold for WiFi connection. Default: CoreFileUploaderProvider.WIFI_SIZE_WARNING.
|
||||
* @param {number} [limitedThreshold] Threshold for limited connection. Default: CoreFileUploaderProvider.LIMITED_SIZE_WARNING.
|
||||
* @return {Promise<void>} Promise resolved when the user confirms or if there's no need to show a modal.
|
||||
*/
|
||||
confirmUploadFile(size: number, alwaysConfirm?: boolean, allowOffline?: boolean, wifiThreshold?: number,
|
||||
limitedThreshold?: number) : Promise<void> {
|
||||
if (size == 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!allowOffline && !this.appProvider.isOnline()) {
|
||||
return Promise.reject(this.translate.instant('core.fileuploader.errormustbeonlinetoupload'));
|
||||
}
|
||||
|
||||
wifiThreshold = typeof wifiThreshold == 'undefined' ? CoreFileUploaderProvider.WIFI_SIZE_WARNING : wifiThreshold;
|
||||
limitedThreshold = typeof limitedThreshold == 'undefined' ? CoreFileUploaderProvider.LIMITED_SIZE_WARNING : limitedThreshold;
|
||||
|
||||
if (size < 0) {
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.fileuploader.confirmuploadunknownsize'));
|
||||
} else if (size >= wifiThreshold || (this.appProvider.isNetworkAccessLimited() && size >= limitedThreshold)) {
|
||||
let readableSize = this.textUtils.bytesToSize(size, 2);
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.fileuploader.confirmuploadfile', {size: readableSize}));
|
||||
} else if (alwaysConfirm) {
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.areyousure'));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a temporary copy of a file and upload it.
|
||||
*
|
||||
* @param {any} file File to copy and upload.
|
||||
* @param {boolean} [upload] True if the file should be uploaded, false to return the copy of the file.
|
||||
* @param {string} [name] Name to use when uploading the file. If not defined, use the file's name.
|
||||
* @return {Promise<any>} Promise resolved when the file is uploaded.
|
||||
*/
|
||||
copyAndUploadFile(file: any, upload?: boolean, name?: string) : Promise<any> {
|
||||
name = name || file.name;
|
||||
|
||||
let modal = this.domUtils.showModalLoading('core.fileuploader.readingfile', true),
|
||||
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, this.fileProvider.FORMATARRAYBUFFER).then((data) => {
|
||||
fileData = data;
|
||||
|
||||
// Get unique name for the copy.
|
||||
return this.fileProvider.getUniqueNameInFolder(this.fileProvider.TMPFOLDER, name);
|
||||
}).then((newName) => {
|
||||
let filePath = this.textUtils.concatenatePaths(this.fileProvider.TMPFOLDER, newName);
|
||||
|
||||
return this.fileProvider.writeFile(filePath, fileData);
|
||||
}).catch((error) => {
|
||||
this.logger.error('Error reading file to upload.', error);
|
||||
modal.dismiss();
|
||||
return Promise.reject(this.translate.instant('core.fileuploader.errorreadingfile'));
|
||||
}).then((fileEntry) => {
|
||||
modal.dismiss();
|
||||
|
||||
if (upload) {
|
||||
// Pass true to delete the copy after the upload.
|
||||
return this.uploadGenericFile(fileEntry.toURL(), name, file.type, true);
|
||||
} else {
|
||||
return fileEntry;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy or move a file to the app temporary folder.
|
||||
*
|
||||
* @param {string} path Path of the file.
|
||||
* @param {boolean} shouldDelete True if original file should be deleted (move), false otherwise (copy).
|
||||
* @param {number} [maxSize] Max size of the file. If not defined or -1, no max size.
|
||||
* @param {string} [defaultExt] Defaut extension to use if the file doesn't have any.
|
||||
* @return {Promise<any>} Promise resolved with the copied file.
|
||||
*/
|
||||
protected copyToTmpFolder(path: string, shouldDelete: boolean, maxSize?: number, defaultExt?: string) : Promise<any> {
|
||||
let fileName = this.fileProvider.getFileAndDirectoryFromPath(path).name,
|
||||
promise,
|
||||
fileTooLarge;
|
||||
|
||||
// Check that size isn't too large.
|
||||
if (typeof maxSize != 'undefined' && maxSize != -1) {
|
||||
promise = this.fileProvider.getExternalFile(path).then((fileEntry) => {
|
||||
return this.fileProvider.getFileObjectFromFileEntry(fileEntry).then((file) => {
|
||||
if (file.size > maxSize) {
|
||||
fileTooLarge = file;
|
||||
}
|
||||
});
|
||||
}).catch(() => {
|
||||
// Ignore failures.
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
if (fileTooLarge) {
|
||||
return this.errorMaxBytes(maxSize, fileTooLarge.name);
|
||||
}
|
||||
|
||||
// File isn't too large.
|
||||
// Picking an image from album in Android adds a timestamp at the end of the file. Delete it.
|
||||
fileName = fileName.replace(/(\.[^\.]*)\?[^\.]*$/, '$1');
|
||||
|
||||
// Get a unique name in the folder to prevent overriding another file.
|
||||
return this.fileProvider.getUniqueNameInFolder(this.fileProvider.TMPFOLDER, fileName, defaultExt);
|
||||
}).then((newName) => {
|
||||
// Now move or copy the file.
|
||||
const destPath = this.textUtils.concatenatePaths(this.fileProvider.TMPFOLDER, newName);
|
||||
if (shouldDelete) {
|
||||
return this.fileProvider.moveExternalFile(path, destPath);
|
||||
} else {
|
||||
return this.fileProvider.copyExternalFile(path, destPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when trying to upload a file bigger than max size. Shows an error.
|
||||
*
|
||||
* @param {number} maxSize Max size (bytes).
|
||||
* @param {string} fileName Name of the file.
|
||||
* @return {Promise<any>} Rejected promise.
|
||||
*/
|
||||
protected errorMaxBytes(maxSize: number, fileName: string) : Promise<any> {
|
||||
let errorMessage = this.translate.instant('core.fileuploader.maxbytesfile', {$a: {
|
||||
file: fileName,
|
||||
size: this.textUtils.bytesToSize(maxSize, 2)
|
||||
}});
|
||||
|
||||
this.domUtils.showErrorModal(errorMessage);
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when the file picker is closed.
|
||||
*/
|
||||
filePickerClosed() : void {
|
||||
if (this.filePickerDeferred) {
|
||||
this.filePickerDeferred.reject();
|
||||
this.filePickerDeferred = undefined;
|
||||
}
|
||||
// Close the action sheet if it's opened.
|
||||
if (this.actionSheet) {
|
||||
this.actionSheet.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to call once a file is uploaded using the file picker.
|
||||
*
|
||||
* @param {any} result Result of the upload process.
|
||||
*/
|
||||
fileUploaded(result: any) : void {
|
||||
if (this.filePickerDeferred) {
|
||||
this.filePickerDeferred.resolve(result);
|
||||
this.filePickerDeferred = undefined;
|
||||
}
|
||||
// Close the action sheet if it's opened.
|
||||
if (this.actionSheet) {
|
||||
this.actionSheet.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the "file picker" to select and upload a file.
|
||||
*
|
||||
* @param {number} [maxSize] Max size of the file to upload. If not defined or -1, no max size.
|
||||
* @param {string} [title] File picker title.
|
||||
* @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported.
|
||||
* @return {Promise<any>} Promise resolved when a file is uploaded, rejected if file picker is closed without a file uploaded.
|
||||
* The resolve value is the response of the upload request.
|
||||
*/
|
||||
selectAndUploadFile(maxSize?: number, title?: string, mimetypes?: string[]) : Promise<any> {
|
||||
return this.selectFileWithPicker(maxSize, false, title, mimetypes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the "file picker" to select a file without uploading it.
|
||||
*
|
||||
* @param {number} [maxSize] Max size of the file. If not defined or -1, no max size.
|
||||
* @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection.
|
||||
* @param {string} [title] File picker title.
|
||||
* @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported.
|
||||
* @return {Promise<any>} Promise resolved when a file is selected, rejected if file picker is closed without selecting a file.
|
||||
* The resolve value is the FileEntry of a copy of the picked file, so it can be deleted afterwards.
|
||||
*/
|
||||
selectFile(maxSize?: number, allowOffline?: boolean, title?: string, mimetypes?: string[])
|
||||
: Promise<any> {
|
||||
return this.selectFileWithPicker(maxSize, allowOffline, title, mimetypes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the "file picker" to select a file and maybe uploading it.
|
||||
*
|
||||
* @param {number} [maxSize] Max size of the file. If not defined or -1, no max size.
|
||||
* @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection.
|
||||
* @param {string} [title] File picker title.
|
||||
* @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported.
|
||||
* @param {boolean} [upload] Whether the file should be uploaded.
|
||||
* @return {Promise<any>} Promise resolved when a file is selected/uploaded, rejected if file picker is closed.
|
||||
*/
|
||||
protected selectFileWithPicker(maxSize?: number, allowOffline?: boolean, title?: string, mimetypes?: string[],
|
||||
upload?: boolean) : Promise<any> {
|
||||
// Create the cancel button and get the handlers to upload the file.
|
||||
let buttons: any[] = [{
|
||||
text: this.translate.instant('core.cancel'),
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
// User cancelled the action sheet.
|
||||
this.filePickerClosed();
|
||||
}
|
||||
}],
|
||||
handlers = this.uploaderDelegate.getHandlers(mimetypes);
|
||||
|
||||
this.filePickerDeferred = this.utils.promiseDefer();
|
||||
|
||||
// Sort the handlers by priority.
|
||||
handlers.sort((a, b) => {
|
||||
return a.priority <= b.priority ? 1 : -1;
|
||||
});
|
||||
|
||||
// Create a button for each handler.
|
||||
handlers.forEach((handler) => {
|
||||
buttons.push({
|
||||
text: this.translate.instant(handler.title),
|
||||
icon: handler.icon,
|
||||
cssClass: handler.class,
|
||||
handler: () => {
|
||||
if (!handler.action) {
|
||||
// Nothing to do.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!allowOffline && !this.appProvider.isOnline()) {
|
||||
// Not allowed, show error.
|
||||
this.domUtils.showErrorModal('core.fileuploader.errormustbeonlinetoupload', true);
|
||||
return false;
|
||||
}
|
||||
|
||||
handler.action(maxSize, upload, allowOffline, handler.mimetypes).then((data) => {
|
||||
if (data.treated) {
|
||||
// The handler already treated the file. Return the result.
|
||||
return data.result;
|
||||
} else {
|
||||
// The handler didn't treat the file, we need to do it.
|
||||
if (data.fileEntry) {
|
||||
// The handler provided us a fileEntry, use it.
|
||||
return this.uploadFileEntry(data.fileEntry, data.delete, maxSize, upload, allowOffline);
|
||||
} else if (data.path) {
|
||||
// The handler provided a path. First treat it like it's a relative path.
|
||||
return this.fileProvider.getFile(data.path).catch(() => {
|
||||
// File not found, it's probably an absolute path.
|
||||
return this.fileProvider.getExternalFile(data.path);
|
||||
}).then((fileEntry) => {
|
||||
// File found, treat it.
|
||||
return this.uploadFileEntry(fileEntry, data.delete, maxSize, upload, allowOffline);
|
||||
});
|
||||
}
|
||||
|
||||
// Nothing received, fail.
|
||||
return Promise.reject('No file received');
|
||||
}
|
||||
}).then((result) => {
|
||||
// Success uploading or picking, return the result.
|
||||
this.fileUploaded(result);
|
||||
}).catch((error) => {
|
||||
if (error) {
|
||||
this.domUtils.showErrorModal(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Do not close the action sheet, it will be closed if success.
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.actionSheet = this.actionSheetCtrl.create({
|
||||
title: title ? title : this.translate.instant('core.fileuploader.' + (upload ? 'uploadafile' : 'selectafile')),
|
||||
buttons: buttons
|
||||
});
|
||||
this.actionSheet.present();
|
||||
|
||||
// Call afterRender for each button.
|
||||
setTimeout(() => {
|
||||
handlers.forEach((handler) => {
|
||||
if (handler.afterRender) {
|
||||
handler.afterRender(maxSize, upload, allowOffline, handler.mimetypes);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
|
||||
return this.filePickerDeferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to upload a file on a certain site, showing a confirm if needed.
|
||||
*
|
||||
* @param {any} fileEntry FileEntry of the file to upload.
|
||||
* @param {boolean} [deleteAfterUpload] Whether the file should be deleted after upload.
|
||||
* @param {string} [siteId] Id of the site to upload the file to. If not defined, use current site.
|
||||
* @return {Promise<any>} Promise resolved when the file is uploaded.
|
||||
*/
|
||||
showConfirmAndUploadInSite(fileEntry: any, deleteAfterUpload?: boolean, siteId?: string) : Promise<void> {
|
||||
return this.fileProvider.getFileObjectFromFileEntry(fileEntry).then((file) => {
|
||||
return this.confirmUploadFile(file.size).then(() => {
|
||||
return this.uploadGenericFile(fileEntry.toURL(), file.name, file.type, deleteAfterUpload, siteId).then(() => {
|
||||
this.domUtils.showAlert('core.success', 'core.fileuploader.fileuploaded');
|
||||
});
|
||||
}).catch((err) => {
|
||||
if (err) {
|
||||
this.domUtils.showErrorModal(err);
|
||||
}
|
||||
return Promise.reject(null);
|
||||
});
|
||||
}, () => {
|
||||
this.domUtils.showErrorModal('core.fileuploader.errorreadingfile', true);
|
||||
return Promise.reject(null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat a capture audio/video error.
|
||||
*
|
||||
* @param {any} error Error returned by the Cordova plugin. Can be a string or an object.
|
||||
* @param {string} defaultMessage Key of the default message to show.
|
||||
* @return {Promise<any>} Rejected promise. If it doesn't have an error message it means it was cancelled.
|
||||
*/
|
||||
protected treatCaptureError(error: any, defaultMessage: string) : Promise<any> {
|
||||
// Cancelled or error. If cancelled, error is an object with code = 3.
|
||||
if (error) {
|
||||
if (typeof error === 'string') {
|
||||
this.logger.error('Error while recording audio/video: ' + error);
|
||||
if (error.indexOf('No Activity found') > -1) {
|
||||
// User doesn't have an app to do this.
|
||||
return Promise.reject(this.translate.instant('core.fileuploader.errornoapp'));
|
||||
} else {
|
||||
return Promise.reject(this.translate.instant(defaultMessage));
|
||||
}
|
||||
} else {
|
||||
if (error.code != 3) {
|
||||
// Error, not cancelled.
|
||||
this.logger.error('Error while recording audio/video', error);
|
||||
return Promise.reject(this.translate.instant(defaultMessage));
|
||||
} else {
|
||||
this.logger.debug('Cancelled');
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat a capture image or browse album error.
|
||||
*
|
||||
* @param {string} error Error returned by the Cordova plugin.
|
||||
* @param {string} defaultMessage Key of the default message to show.
|
||||
* @return {Promise<any>} Rejected promise. If it doesn't have an error message it means it was cancelled.
|
||||
*/
|
||||
protected treatImageError(error: string, defaultMessage: string) : Promise<any> {
|
||||
// Cancelled or error.
|
||||
if (error) {
|
||||
if (typeof error == 'string') {
|
||||
if (error.toLowerCase().indexOf('error') > -1 || error.toLowerCase().indexOf('unable') > -1) {
|
||||
this.logger.error('Error getting image: ' + error);
|
||||
return Promise.reject(error);
|
||||
} else {
|
||||
// User cancelled.
|
||||
this.logger.debug('Cancelled');
|
||||
}
|
||||
} else {
|
||||
return Promise.reject(this.translate.instant(defaultMessage));
|
||||
}
|
||||
}
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient helper for the user to record and upload a video.
|
||||
*
|
||||
* @param {boolean} isAudio True if uploading an audio, false if it's a video.
|
||||
* @param {number} maxSize Max size of the upload. -1 for no max size.
|
||||
* @param {boolean} [upload] True if the file should be uploaded, false to return the picked file.
|
||||
* @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
uploadAudioOrVideo(isAudio: boolean, maxSize: number, upload?: boolean, mimetypes?: string[]) : Promise<any> {
|
||||
this.logger.debug('Trying to record a video file');
|
||||
|
||||
const options = {limit: 1, mimetypes: mimetypes},
|
||||
promise = isAudio ? this.mediaCapture.captureAudio(options) : this.mediaCapture.captureVideo(options);
|
||||
|
||||
// The mimetypes param is only for desktop apps, the Cordova plugin doesn't support it.
|
||||
return promise.then((medias) => {
|
||||
// We used limit 1, we only want 1 media.
|
||||
let media: MediaFile = medias[0],
|
||||
path = media.fullPath,
|
||||
error = this.fileUploaderProvider.isInvalidMimetype(mimetypes, path); // Verify that the mimetype is supported.
|
||||
|
||||
if (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (upload) {
|
||||
return this.uploadFile(path, maxSize, true, this.fileUploaderProvider.getMediaUploadOptions(media));
|
||||
} else {
|
||||
// Copy or move the file to our temporary folder.
|
||||
return this.copyToTmpFolder(path, true, maxSize);
|
||||
}
|
||||
}, (error) => {
|
||||
const defaultError = isAudio ? 'core.fileuploader.errorcapturingaudio' : 'core.fileuploader.errorcapturingvideo';
|
||||
return this.treatCaptureError(error, defaultError);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a file of any type.
|
||||
* This function will not check the size of the file, please check it before calling this function.
|
||||
*
|
||||
* @param {string} uri File URI.
|
||||
* @param {string} name File name.
|
||||
* @param {string} type File type.
|
||||
* @param {boolean} [deleteAfterUpload] Whether the file should be deleted after upload.
|
||||
* @param {string} [siteId] Id of the site to upload the file to. If not defined, use current site.
|
||||
* @return {Promise<any>} Promise resolved when the file is uploaded.
|
||||
*/
|
||||
uploadGenericFile(uri: string, name: string, type: string, deleteAfterUpload?: boolean, siteId?: string) : Promise<any> {
|
||||
let options = this.fileUploaderProvider.getFileUploadOptions(uri, name, type, deleteAfterUpload);
|
||||
return this.uploadFile(uri, -1, false, options, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient helper for the user to upload an image, either from the album or taking it with the camera.
|
||||
*
|
||||
* @param {Boolean} fromAlbum True if the image should be selected from album, false if it should be taken with camera.
|
||||
* @param {Number} maxSize Max size of the upload. -1 for no max size.
|
||||
* @param {Boolean} upload True if the image should be uploaded, false to return the picked file.
|
||||
* @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported.
|
||||
* @return {Promise} The reject contains the error message, if there is no error message
|
||||
* then we can consider that this is a silent fail.
|
||||
*/
|
||||
uploadImage(fromAlbum, maxSize, upload, mimetypes) {
|
||||
this.logger.debug('Trying to capture an image with camera');
|
||||
|
||||
let options: CameraOptions = {
|
||||
quality: 50,
|
||||
destinationType: this.camera.DestinationType.FILE_URI,
|
||||
correctOrientation: true
|
||||
};
|
||||
|
||||
if (fromAlbum) {
|
||||
const imageSupported = !mimetypes || this.utils.indexOfRegexp(mimetypes, /^image\//) > -1,
|
||||
videoSupported = !mimetypes || this.utils.indexOfRegexp(mimetypes, /^video\//) > -1;
|
||||
|
||||
options.sourceType = this.camera.PictureSourceType.PHOTOLIBRARY;
|
||||
options.popoverOptions = {
|
||||
x: 10,
|
||||
y: 10,
|
||||
width: this.platform.width() - 200,
|
||||
height: this.platform.height() - 200,
|
||||
arrowDir: this.camera.PopoverArrowDirection.ARROW_ANY
|
||||
};
|
||||
|
||||
// Determine the mediaType based on the mimetypes.
|
||||
if (imageSupported && !videoSupported) {
|
||||
options.mediaType = this.camera.MediaType.PICTURE;
|
||||
} else if (!imageSupported && videoSupported) {
|
||||
options.mediaType = this.camera.MediaType.VIDEO;
|
||||
} else if (this.platform.is('ios')) {
|
||||
// Only get all media in iOS because in Android using this option allows uploading any kind of file.
|
||||
options.mediaType = this.camera.MediaType.ALLMEDIA;
|
||||
}
|
||||
} else if (mimetypes) {
|
||||
if (mimetypes.indexOf('image/jpeg') > -1) {
|
||||
options.encodingType = this.camera.EncodingType.JPEG;
|
||||
} else if (mimetypes.indexOf('image/png') > -1) {
|
||||
options.encodingType = this.camera.EncodingType.PNG;
|
||||
}
|
||||
}
|
||||
|
||||
return this.camera.getPicture(options).then((path) => {
|
||||
let error = this.fileUploaderProvider.isInvalidMimetype(mimetypes, path); // Verify that the mimetype is supported.
|
||||
if (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (upload) {
|
||||
return this.uploadFile(path, maxSize, true, this.fileUploaderProvider.getCameraUploadOptions(path, fromAlbum));
|
||||
} else {
|
||||
// Copy or move the file to our temporary folder.
|
||||
return this.copyToTmpFolder(path, !fromAlbum, maxSize, 'jpg');
|
||||
}
|
||||
}, (error) => {
|
||||
let defaultError = fromAlbum ? 'core.fileuploader.errorgettingimagealbum' : 'core.fileuploader.errorcapturingimage';
|
||||
return this.treatImageError(error, defaultError);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file given the file entry.
|
||||
*
|
||||
* @param {any} fileEntry The file entry.
|
||||
* @param {boolean} deleteAfter True if the file should be deleted once treated.
|
||||
* @param {number} [maxSize] Max size of the file. If not defined or -1, no max size.
|
||||
* @param {boolean} [upload] True if the file should be uploaded, false to return the picked file.
|
||||
* @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection.
|
||||
* @param {string} [name] Name to use when uploading the file. If not defined, use the file's name.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
uploadFileEntry(fileEntry: any, deleteAfter: boolean, maxSize?: number, upload?: boolean, allowOffline?: boolean,
|
||||
name?: string) : Promise<any> {
|
||||
return this.fileProvider.getFileObjectFromFileEntry(fileEntry).then((file) => {
|
||||
return this.uploadFileObject(file, maxSize, upload, allowOffline, name).then((result) => {
|
||||
if (deleteAfter) {
|
||||
// We have uploaded and deleted a copy of the file. Now delete the original one.
|
||||
this.fileProvider.removeFileByFileEntry(fileEntry);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a file given the file object.
|
||||
*
|
||||
* @param {any} file The file object.
|
||||
* @param {number} [maxSize] Max size of the file. If not defined or -1, no max size.
|
||||
* @param {boolean} [upload] True if the file should be uploaded, false to return the picked file.
|
||||
* @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection.
|
||||
* @param {string} [name] Name to use when uploading the file. If not defined, use the file's name.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
uploadFileObject(file: any, maxSize?: number, upload?: boolean, allowOffline?: boolean, name?: string) : Promise<any> {
|
||||
if (maxSize != -1 && file.size > maxSize) {
|
||||
return this.errorMaxBytes(maxSize, file.name);
|
||||
}
|
||||
|
||||
return this.confirmUploadFile(file.size, false, allowOffline).then(() => {
|
||||
// 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.copyAndUploadFile(file, upload, name);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to upload a file, allowing to retry if it fails.
|
||||
*
|
||||
* @param {string} path Absolute path of the file to upload.
|
||||
* @param {number} maxSize Max size of the upload. -1 for no max size.
|
||||
* @param {boolean} checkSize True to check size.
|
||||
* @param {CoreFileUploaderOptions} Options.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if the file is uploaded, rejected otherwise.
|
||||
*/
|
||||
protected uploadFile(path: string, maxSize: number, checkSize: boolean, options: CoreFileUploaderOptions, siteId?: string)
|
||||
: Promise<any> {
|
||||
|
||||
let errorStr = this.translate.instant('core.error'),
|
||||
retryStr = this.translate.instant('core.retry'),
|
||||
uploadingStr = this.translate.instant('core.fileuploader.uploading'),
|
||||
promise,
|
||||
file,
|
||||
errorUploading = (error) => {
|
||||
// Allow the user to retry.
|
||||
return this.domUtils.showConfirm(error, errorStr, retryStr).then(() => {
|
||||
// Try again.
|
||||
return this.uploadFile(path, maxSize, checkSize, options, siteId);
|
||||
}, () => {
|
||||
// User cancelled. Delete the file if needed.
|
||||
if (options.deleteAfterUpload) {
|
||||
this.fileProvider.removeExternalFile(path);
|
||||
}
|
||||
return Promise.reject(null);
|
||||
});
|
||||
};
|
||||
|
||||
if (!this.appProvider.isOnline()) {
|
||||
return errorUploading(this.translate.instant('core.fileuploader.errormustbeonlinetoupload'));
|
||||
}
|
||||
|
||||
if (checkSize) {
|
||||
// Check that file size is the right one.
|
||||
promise = this.fileProvider.getExternalFile(path).then((fileEntry) => {
|
||||
return this.fileProvider.getFileObjectFromFileEntry(fileEntry).then((f) => {
|
||||
file = f;
|
||||
return file.size;
|
||||
});
|
||||
}).catch(() => {
|
||||
// Ignore failures.
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve(0);
|
||||
}
|
||||
|
||||
return promise.then((size) => {
|
||||
if (maxSize != -1 && size > maxSize) {
|
||||
return this.errorMaxBytes(maxSize, file.name);
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
return this.confirmUploadFile(size);
|
||||
}
|
||||
}).then(() => {
|
||||
// File isn't too large and user confirmed, let's upload.
|
||||
let modal = this.domUtils.showModalLoading(uploadingStr);
|
||||
|
||||
return this.fileUploaderProvider.uploadFile(path, options, (progress: ProgressEvent) => {
|
||||
// Progress uploading.
|
||||
if (progress && progress.lengthComputable) {
|
||||
let 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, siteId).catch((error) => {
|
||||
this.logger.error('Error uploading file.', error);
|
||||
|
||||
modal.dismiss();
|
||||
if (typeof error != 'string') {
|
||||
error = this.translate.instant('core.fileuploader.errorwhileuploading');
|
||||
}
|
||||
return errorUploading(error);
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue