2017-11-08 13:48:34 +00:00
|
|
|
// (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 { Platform } from 'ionic-angular';
|
|
|
|
import { File, FileEntry, DirectoryEntry } from '@ionic-native/file';
|
|
|
|
|
|
|
|
import { CoreAppProvider } from './app';
|
|
|
|
import { CoreLoggerProvider } from './logger';
|
|
|
|
import { CoreMimetypeUtilsProvider } from './utils/mimetype';
|
|
|
|
import { CoreTextUtilsProvider } from './utils/text';
|
|
|
|
import { Zip } from '@ionic-native/zip';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Factory to interact with the file system.
|
|
|
|
*/
|
|
|
|
@Injectable()
|
|
|
|
export class CoreFileProvider {
|
2018-01-29 09:05:20 +00:00
|
|
|
// Formats to read a file.
|
|
|
|
static FORMATTEXT = 0;
|
|
|
|
static FORMATDATAURL = 1;
|
|
|
|
static FORMATBINARYSTRING = 2;
|
|
|
|
static FORMATARRAYBUFFER = 3;
|
|
|
|
|
|
|
|
// Folders.
|
|
|
|
static SITESFOLDER = 'sites';
|
|
|
|
static TMPFOLDER = 'tmp';
|
|
|
|
|
2018-01-12 13:28:46 +00:00
|
|
|
protected logger;
|
|
|
|
protected initialized = false;
|
|
|
|
protected basePath = '';
|
|
|
|
protected isHTMLAPI = false;
|
2017-11-08 13:48:34 +00:00
|
|
|
|
|
|
|
constructor(logger: CoreLoggerProvider, private platform: Platform, private file: File, private appProvider: CoreAppProvider,
|
|
|
|
private textUtils: CoreTextUtilsProvider, private zip: Zip, private mimeUtils: CoreMimetypeUtilsProvider) {
|
|
|
|
this.logger = logger.getInstance('CoreFileProvider');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets basePath to use with HTML API. Reserved for core use.
|
|
|
|
*
|
|
|
|
* @param {string} path Base path to use.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
setHTMLBasePath(path: string): void {
|
2017-11-08 13:48:34 +00:00
|
|
|
this.isHTMLAPI = true;
|
|
|
|
this.basePath = path;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if we're using HTML API.
|
|
|
|
*
|
|
|
|
* @return {boolean} True if uses HTML API, false otherwise.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
usesHTMLAPI(): boolean {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.isHTMLAPI;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize basePath based on the OS if it's not initialized already.
|
|
|
|
*
|
|
|
|
* @return {Promise<void>} Promise to be resolved when the initialization is finished.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
init(): Promise<void> {
|
2017-11-08 13:48:34 +00:00
|
|
|
if (this.initialized) {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.platform.ready().then(() => {
|
|
|
|
|
|
|
|
if (this.platform.is('android')) {
|
2018-11-27 08:34:00 +00:00
|
|
|
this.basePath = this.file.externalApplicationStorageDirectory || this.basePath;
|
2017-11-08 13:48:34 +00:00
|
|
|
} else if (this.platform.is('ios')) {
|
2018-11-27 08:34:00 +00:00
|
|
|
this.basePath = this.file.documentsDirectory || this.basePath;
|
2017-11-08 13:48:34 +00:00
|
|
|
} else if (!this.isAvailable() || this.basePath === '') {
|
|
|
|
this.logger.error('Error getting device OS.');
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return Promise.reject(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.initialized = true;
|
|
|
|
this.logger.debug('FS initialized: ' + this.basePath);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the plugin is available.
|
|
|
|
*
|
|
|
|
* @return {boolean} Whether the plugin is available.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
isAvailable(): boolean {
|
2017-12-18 12:06:12 +00:00
|
|
|
return typeof window.resolveLocalFileSystemURL !== 'undefined';
|
2017-11-08 13:48:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a file.
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the file.
|
|
|
|
* @return {Promise<FileEntry>} Promise resolved when the file is retrieved.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getFile(path: string): Promise<FileEntry> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.init().then(() => {
|
|
|
|
this.logger.debug('Get file: ' + path);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.resolveLocalFilesystemUrl(this.addBasePathIfNeeded(path));
|
|
|
|
}).then((entry) => {
|
|
|
|
return <FileEntry> entry;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a directory.
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the directory.
|
|
|
|
* @return {Promise<DirectoryEntry>} Promise resolved when the directory is retrieved.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getDir(path: string): Promise<DirectoryEntry> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.init().then(() => {
|
|
|
|
this.logger.debug('Get directory: ' + path);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.resolveDirectoryUrl(this.addBasePathIfNeeded(path));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get site folder path.
|
|
|
|
*
|
|
|
|
* @param {string} siteId Site ID.
|
|
|
|
* @return {string} Site folder path.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getSiteFolder(siteId: string): string {
|
2018-01-12 13:28:46 +00:00
|
|
|
return CoreFileProvider.SITESFOLDER + '/' + siteId;
|
2017-11-08 13:48:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a directory or a file.
|
|
|
|
*
|
|
|
|
* @param {boolean} isDirectory True if a directory should be created, false if it should create a file.
|
|
|
|
* @param {string} path Relative path to the dir/file.
|
|
|
|
* @param {boolean} [failIfExists] True if it should fail if the dir/file exists, false otherwise.
|
|
|
|
* @param {string} [base] Base path to create the dir/file in. If not set, use basePath.
|
|
|
|
* @return {Promise<any>} Promise to be resolved when the dir/file is created.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
protected create(isDirectory: boolean, path: string, failIfExists?: boolean, base?: string): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.init().then(() => {
|
|
|
|
// Remove basePath if it's in the path.
|
|
|
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
|
|
|
base = base || this.basePath;
|
|
|
|
|
|
|
|
if (path.indexOf('/') == -1) {
|
|
|
|
if (isDirectory) {
|
|
|
|
this.logger.debug('Create dir ' + path + ' in ' + base);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.createDir(base, path, !failIfExists);
|
|
|
|
} else {
|
|
|
|
this.logger.debug('Create file ' + path + ' in ' + base);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.createFile(base, path, !failIfExists);
|
|
|
|
}
|
|
|
|
} else {
|
2018-01-29 09:05:20 +00:00
|
|
|
// The file plugin doesn't allow creating more than 1 level at a time (e.g. tmp/folder).
|
2017-11-08 13:48:34 +00:00
|
|
|
// We need to create them 1 by 1.
|
2018-01-29 09:05:20 +00:00
|
|
|
const firstDir = path.substr(0, path.indexOf('/')),
|
2017-11-08 13:48:34 +00:00
|
|
|
restOfPath = path.substr(path.indexOf('/') + 1);
|
|
|
|
|
|
|
|
this.logger.debug('Create dir ' + firstDir + ' in ' + base);
|
|
|
|
|
|
|
|
return this.file.createDir(base, firstDir, true).then((newDirEntry) => {
|
|
|
|
return this.create(isDirectory, restOfPath, failIfExists, newDirEntry.toURL());
|
|
|
|
}).catch((error) => {
|
|
|
|
this.logger.error('Error creating directory ' + firstDir + ' in ' + base);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return Promise.reject(error);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a directory.
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the directory.
|
|
|
|
* @param {boolean} [failIfExists] True if it should fail if the directory exists, false otherwise.
|
|
|
|
* @return {Promise<DirectoryEntry>} Promise to be resolved when the directory is created.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
createDir(path: string, failIfExists?: boolean): Promise<DirectoryEntry> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.create(true, path, failIfExists);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a file.
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the file.
|
|
|
|
* @param {boolean} [failIfExists] True if it should fail if the file exists, false otherwise..
|
|
|
|
* @return {Promise<FileEntry>} Promise to be resolved when the file is created.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
createFile(path: string, failIfExists?: boolean): Promise<FileEntry> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.create(false, path, failIfExists);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a directory and all its contents.
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the directory.
|
|
|
|
* @return {Promise<any>} Promise to be resolved when the directory is deleted.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
removeDir(path: string): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.init().then(() => {
|
|
|
|
// Remove basePath if it's in the path.
|
|
|
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
|
|
|
this.logger.debug('Remove directory: ' + path);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.removeRecursively(this.basePath, path);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a file and all its contents.
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the file.
|
|
|
|
* @return {Promise<any>} Promise to be resolved when the file is deleted.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
removeFile(path: string): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.init().then(() => {
|
|
|
|
// Remove basePath if it's in the path.
|
|
|
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
|
|
|
this.logger.debug('Remove file: ' + path);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.removeFile(this.basePath, path);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a file given its FileEntry.
|
|
|
|
*
|
|
|
|
* @param {FileEntry} fileEntry File Entry.
|
|
|
|
* @return {Promise<any>} Promise resolved when the file is deleted.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
removeFileByFileEntry(fileEntry: any): Promise<any> {
|
|
|
|
return new Promise((resolve, reject): void => {
|
2017-11-08 13:48:34 +00:00
|
|
|
fileEntry.remove(resolve, reject);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the contents of a directory (not subdirectories).
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the directory.
|
|
|
|
* @return {Promise<any>} Promise to be resolved when the contents are retrieved.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getDirectoryContents(path: string): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.init().then(() => {
|
|
|
|
// Remove basePath if it's in the path.
|
|
|
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
|
|
|
this.logger.debug('Get contents of dir: ' + path);
|
|
|
|
|
|
|
|
return this.file.listDir(this.basePath, path);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the size of a directory or a file.
|
|
|
|
*
|
|
|
|
* @param {any} entry Directory or file.
|
|
|
|
* @return {Promise<number>} Promise to be resolved when the size is calculated.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
protected getSize(entry: any): Promise<number> {
|
|
|
|
return new Promise((resolve, reject): void => {
|
2017-11-08 13:48:34 +00:00
|
|
|
if (entry.isDirectory) {
|
2018-01-29 09:05:20 +00:00
|
|
|
const directoryReader = entry.createReader();
|
2017-11-08 13:48:34 +00:00
|
|
|
directoryReader.readEntries((entries) => {
|
|
|
|
|
2018-01-29 09:05:20 +00:00
|
|
|
const promises = [];
|
2017-11-08 13:48:34 +00:00
|
|
|
for (let i = 0; i < entries.length; i++) {
|
|
|
|
promises.push(this.getSize(entries[i]));
|
|
|
|
}
|
|
|
|
|
|
|
|
Promise.all(promises).then((sizes) => {
|
|
|
|
|
|
|
|
let directorySize = 0;
|
|
|
|
for (let i = 0; i < sizes.length; i++) {
|
2018-01-29 09:05:20 +00:00
|
|
|
const fileSize = parseInt(sizes[i]);
|
2017-11-08 13:48:34 +00:00
|
|
|
if (isNaN(fileSize)) {
|
|
|
|
reject();
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
directorySize += fileSize;
|
|
|
|
}
|
|
|
|
resolve(directorySize);
|
|
|
|
|
|
|
|
}, reject);
|
|
|
|
|
|
|
|
}, reject);
|
|
|
|
|
|
|
|
} else if (entry.isFile) {
|
|
|
|
entry.file((file) => {
|
|
|
|
resolve(file.size);
|
|
|
|
}, reject);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the size of a directory.
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the directory.
|
|
|
|
* @return {Promise<number>} Promise to be resolved when the size is calculated.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getDirectorySize(path: string): Promise<number> {
|
2017-11-08 13:48:34 +00:00
|
|
|
// Remove basePath if it's in the path.
|
|
|
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
|
|
|
|
|
|
|
this.logger.debug('Get size of dir: ' + path);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.getDir(path).then((dirEntry) => {
|
2018-01-29 09:05:20 +00:00
|
|
|
return this.getSize(dirEntry);
|
2017-11-08 13:48:34 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the size of a file.
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the file.
|
|
|
|
* @return {Promise<number>} Promise to be resolved when the size is calculated.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getFileSize(path: string): Promise<number> {
|
2017-11-08 13:48:34 +00:00
|
|
|
// Remove basePath if it's in the path.
|
|
|
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
|
|
|
|
|
|
|
this.logger.debug('Get size of file: ' + path);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.getFile(path).then((fileEntry) => {
|
2018-01-29 09:05:20 +00:00
|
|
|
return this.getSize(fileEntry);
|
2017-11-08 13:48:34 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get file object from a FileEntry.
|
|
|
|
*
|
|
|
|
* @param {FileEntry} path Relative path to the file.
|
|
|
|
* @return {Promise<any>} Promise to be resolved when the file is retrieved.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getFileObjectFromFileEntry(entry: FileEntry): Promise<any> {
|
|
|
|
return new Promise((resolve, reject): void => {
|
2017-11-08 13:48:34 +00:00
|
|
|
this.logger.debug('Get file object of: ' + entry.fullPath);
|
|
|
|
entry.file(resolve, reject);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the free space in the disk.
|
2018-08-31 10:09:46 +00:00
|
|
|
* Please notice that this function isn't reliable and it's not documented in the Cordova File plugin.
|
2017-11-08 13:48:34 +00:00
|
|
|
*
|
|
|
|
* @return {Promise<number>} Promise resolved with the estimated free space in bytes.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
calculateFreeSpace(): Promise<number> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.getFreeDiskSpace().then((size) => {
|
2018-08-31 10:09:46 +00:00
|
|
|
if (this.platform.is('ios')) {
|
|
|
|
// In iOS the size is in bytes.
|
|
|
|
return Number(size);
|
|
|
|
}
|
|
|
|
|
|
|
|
// The size is in KB, convert it to bytes.
|
|
|
|
return Number(size) * 1024;
|
2017-11-08 13:48:34 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Normalize a filename that usually comes URL encoded.
|
|
|
|
*
|
|
|
|
* @param {string} filename The file name.
|
|
|
|
* @return {string} The file name normalized.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
normalizeFileName(filename: string): string {
|
2017-11-08 13:48:34 +00:00
|
|
|
filename = this.textUtils.decodeURIComponent(filename);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return filename;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Read a file from local file system.
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the file.
|
|
|
|
* @param {number} [format=FORMATTEXT] Format to read the file. Must be one of:
|
|
|
|
* FORMATTEXT
|
|
|
|
* FORMATDATAURL
|
|
|
|
* FORMATBINARYSTRING
|
|
|
|
* FORMATARRAYBUFFER
|
|
|
|
* @return {Promise<any>} Promise to be resolved when the file is read.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
readFile(path: string, format: number = CoreFileProvider.FORMATTEXT): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
// Remove basePath if it's in the path.
|
|
|
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
|
|
|
this.logger.debug('Read file ' + path + ' with format ' + format);
|
|
|
|
|
|
|
|
switch (format) {
|
2018-01-12 13:28:46 +00:00
|
|
|
case CoreFileProvider.FORMATDATAURL:
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.readAsDataURL(this.basePath, path);
|
2018-01-12 13:28:46 +00:00
|
|
|
case CoreFileProvider.FORMATBINARYSTRING:
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.readAsBinaryString(this.basePath, path);
|
2018-01-12 13:28:46 +00:00
|
|
|
case CoreFileProvider.FORMATARRAYBUFFER:
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.readAsArrayBuffer(this.basePath, path);
|
|
|
|
default:
|
|
|
|
return this.file.readAsText(this.basePath, path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Read file contents from a file data object.
|
|
|
|
*
|
|
|
|
* @param {any} fileData File's data.
|
|
|
|
* @param {number} [format=FORMATTEXT] Format to read the file. Must be one of:
|
|
|
|
* FORMATTEXT
|
|
|
|
* FORMATDATAURL
|
|
|
|
* FORMATBINARYSTRING
|
|
|
|
* FORMATARRAYBUFFER
|
|
|
|
* @return {Promise<any>} Promise to be resolved when the file is read.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
readFileData(fileData: any, format: number = CoreFileProvider.FORMATTEXT): Promise<any> {
|
2018-01-12 13:28:46 +00:00
|
|
|
format = format || CoreFileProvider.FORMATTEXT;
|
2017-11-08 13:48:34 +00:00
|
|
|
this.logger.debug('Read file from file data with format ' + format);
|
|
|
|
|
2018-01-29 09:05:20 +00:00
|
|
|
return new Promise((resolve, reject): void => {
|
|
|
|
const reader = new FileReader();
|
2018-04-19 11:28:22 +00:00
|
|
|
|
2018-01-29 09:05:20 +00:00
|
|
|
reader.onloadend = (evt): void => {
|
|
|
|
const target = <any> evt.target; // Convert to <any> to be able to use non-standard properties.
|
2017-11-08 13:48:34 +00:00
|
|
|
if (target.result !== undefined || target.result !== null) {
|
|
|
|
resolve(target.result);
|
|
|
|
} else if (target.error !== undefined || target.error !== null) {
|
|
|
|
reject(target.error);
|
|
|
|
} else {
|
2018-01-29 09:05:20 +00:00
|
|
|
reject({ code: null, message: 'READER_ONLOADEND_ERR' });
|
2017-11-08 13:48:34 +00:00
|
|
|
}
|
2018-01-29 09:05:20 +00:00
|
|
|
};
|
2017-11-08 13:48:34 +00:00
|
|
|
|
2018-04-19 11:28:22 +00:00
|
|
|
// Check if the load starts. If it doesn't start in 3 seconds, reject.
|
|
|
|
// Sometimes in Android the read doesn't start for some reason, so the promise never finishes.
|
|
|
|
let hasStarted = false;
|
|
|
|
reader.onloadstart = (evt): void => {
|
|
|
|
hasStarted = true;
|
|
|
|
};
|
|
|
|
setTimeout(() => {
|
|
|
|
if (!hasStarted) {
|
2018-11-27 08:34:00 +00:00
|
|
|
reject('Upload cannot start.');
|
2018-04-19 11:28:22 +00:00
|
|
|
}
|
|
|
|
}, 3000);
|
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
switch (format) {
|
2018-01-12 13:28:46 +00:00
|
|
|
case CoreFileProvider.FORMATDATAURL:
|
2017-11-08 13:48:34 +00:00
|
|
|
reader.readAsDataURL(fileData);
|
|
|
|
break;
|
2018-01-12 13:28:46 +00:00
|
|
|
case CoreFileProvider.FORMATBINARYSTRING:
|
2017-11-08 13:48:34 +00:00
|
|
|
reader.readAsBinaryString(fileData);
|
|
|
|
break;
|
2018-01-12 13:28:46 +00:00
|
|
|
case CoreFileProvider.FORMATARRAYBUFFER:
|
2017-11-08 13:48:34 +00:00
|
|
|
reader.readAsArrayBuffer(fileData);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
reader.readAsText(fileData);
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Writes some data in a file.
|
|
|
|
*
|
|
|
|
* @param {string} path Relative path to the file.
|
|
|
|
* @param {any} data Data to write.
|
|
|
|
* @return {Promise<any>} Promise to be resolved when the file is written.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
writeFile(path: string, data: any): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.init().then(() => {
|
|
|
|
// Remove basePath if it's in the path.
|
|
|
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
|
|
|
this.logger.debug('Write file: ' + path);
|
|
|
|
|
|
|
|
// Create file (and parent folders) to prevent errors.
|
|
|
|
return this.createFile(path).then((fileEntry) => {
|
2018-01-09 07:50:08 +00:00
|
|
|
if (this.isHTMLAPI && !this.appProvider.isDesktop() &&
|
2018-01-29 09:05:20 +00:00
|
|
|
(typeof data == 'string' || data.toString() == '[object ArrayBuffer]')) {
|
2017-11-08 13:48:34 +00:00
|
|
|
// We need to write Blobs.
|
2018-01-29 09:05:20 +00:00
|
|
|
const type = this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(path));
|
|
|
|
data = new Blob([data], { type: type || 'text/plain' });
|
2017-11-08 13:48:34 +00:00
|
|
|
}
|
2018-01-29 09:05:20 +00:00
|
|
|
|
|
|
|
return this.file.writeFile(this.basePath, path, data, { replace: true }).then(() => {
|
2017-11-08 13:48:34 +00:00
|
|
|
return fileEntry;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a file that might be outside the app's folder.
|
|
|
|
*
|
|
|
|
* @param {string} fullPath Absolute path to the file.
|
|
|
|
* @return {Promise<FileEntry>} Promise to be resolved when the file is retrieved.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getExternalFile(fullPath: string): Promise<FileEntry> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.resolveLocalFilesystemUrl(fullPath).then((entry) => {
|
|
|
|
return <FileEntry> entry;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a file that might be outside the app's folder.
|
|
|
|
*
|
|
|
|
* @param {string} fullPath Absolute path to the file.
|
|
|
|
* @return {Promise<any>} Promise to be resolved when the file is removed.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
removeExternalFile(fullPath: string): Promise<any> {
|
|
|
|
const directory = fullPath.substring(0, fullPath.lastIndexOf('/')),
|
2017-11-08 13:48:34 +00:00
|
|
|
filename = fullPath.substr(fullPath.lastIndexOf('/') + 1);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.file.removeFile(directory, filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the base path where the application files are stored.
|
|
|
|
*
|
|
|
|
* @return {Promise<string>} Promise to be resolved when the base path is retrieved.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getBasePath(): Promise<string> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.init().then(() => {
|
|
|
|
if (this.basePath.slice(-1) == '/') {
|
|
|
|
return this.basePath;
|
|
|
|
} else {
|
|
|
|
return this.basePath + '/';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the base path where the application files are stored in the format to be used for downloads.
|
|
|
|
* iOS: Internal URL (cdvfile://).
|
|
|
|
* Others: basePath (file://)
|
|
|
|
*
|
|
|
|
* @return {Promise<string>} Promise to be resolved when the base path is retrieved.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getBasePathToDownload(): Promise<string> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.init().then(() => {
|
|
|
|
if (this.platform.is('ios')) {
|
|
|
|
// In iOS we want the internal URL (cdvfile://localhost/persistent/...).
|
|
|
|
return this.file.resolveDirectoryUrl(this.basePath).then((dirEntry) => {
|
|
|
|
return dirEntry.toInternalURL();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// In the other platforms we use the basePath as it is (file://...).
|
|
|
|
return this.basePath;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the base path where the application files are stored. Returns the value instantly, without waiting for it to be ready.
|
|
|
|
*
|
|
|
|
* @return {string} Base path. If the service hasn't been initialized it will return an invalid value.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getBasePathInstant(): string {
|
2017-11-08 13:48:34 +00:00
|
|
|
if (!this.basePath) {
|
|
|
|
return this.basePath;
|
|
|
|
} else if (this.basePath.slice(-1) == '/') {
|
|
|
|
return this.basePath;
|
|
|
|
} else {
|
|
|
|
return this.basePath + '/';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Move a file.
|
|
|
|
*
|
|
|
|
* @param {string} [originalPath] Path to the file to move.
|
|
|
|
* @param {string} [newPath] New path of the file.
|
|
|
|
* @return {Promise<any>} Promise resolved when the entry is moved.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
moveFile(originalPath: string, newPath: string): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.init().then(() => {
|
|
|
|
// Remove basePath if it's in the paths.
|
|
|
|
originalPath = this.removeStartingSlash(originalPath.replace(this.basePath, ''));
|
|
|
|
newPath = this.removeStartingSlash(newPath.replace(this.basePath, ''));
|
|
|
|
|
|
|
|
if (this.isHTMLAPI) {
|
|
|
|
// In Cordova API we need to calculate the longest matching path to make it work.
|
2018-01-29 09:05:20 +00:00
|
|
|
// The function this.file.moveFile('a/', 'b/c.ext', 'a/', 'b/d.ext') doesn't work.
|
|
|
|
// The function this.file.moveFile('a/b/', 'c.ext', 'a/b/', 'd.ext') works.
|
|
|
|
const dirsA = originalPath.split('/'),
|
2017-11-08 13:48:34 +00:00
|
|
|
dirsB = newPath.split('/');
|
2018-01-29 09:05:20 +00:00
|
|
|
let commonPath = this.basePath;
|
2017-11-08 13:48:34 +00:00
|
|
|
|
|
|
|
for (let i = 0; i < dirsA.length; i++) {
|
|
|
|
let dir = dirsA[i];
|
|
|
|
if (dirsB[i] === dir) {
|
|
|
|
// Found a common folder, add it to common path and remove it from each specific path.
|
|
|
|
dir = dir + '/';
|
|
|
|
commonPath = this.textUtils.concatenatePaths(commonPath, dir);
|
|
|
|
originalPath = originalPath.replace(dir, '');
|
|
|
|
newPath = newPath.replace(dir, '');
|
|
|
|
} else {
|
|
|
|
// Folder doesn't match, stop searching.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.file.moveFile(commonPath, originalPath, commonPath, newPath);
|
|
|
|
} else {
|
2018-06-20 11:18:44 +00:00
|
|
|
return this.file.moveFile(this.basePath, originalPath, this.basePath, newPath).catch((error) => {
|
|
|
|
// The move can fail if the path has encoded characters. Try again if that's the case.
|
|
|
|
const decodedOriginal = decodeURI(originalPath),
|
|
|
|
decodedNew = decodeURI(newPath);
|
|
|
|
|
|
|
|
if (decodedOriginal != originalPath || decodedNew != newPath) {
|
|
|
|
return this.file.moveFile(this.basePath, decodedOriginal, this.basePath, decodedNew);
|
|
|
|
} else {
|
|
|
|
return Promise.reject(error);
|
|
|
|
}
|
|
|
|
});
|
2017-11-08 13:48:34 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy a file.
|
|
|
|
*
|
|
|
|
* @param {string} from Path to the file to move.
|
|
|
|
* @param {string} to New path of the file.
|
|
|
|
* @return {Promise<any>} Promise resolved when the entry is copied.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
copyFile(from: string, to: string): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
let fromFileAndDir,
|
|
|
|
toFileAndDir;
|
|
|
|
|
|
|
|
return this.init().then(() => {
|
|
|
|
// Paths cannot start with "/". Remove basePath if present.
|
|
|
|
from = this.removeStartingSlash(from.replace(this.basePath, ''));
|
|
|
|
to = this.removeStartingSlash(to.replace(this.basePath, ''));
|
|
|
|
|
|
|
|
fromFileAndDir = this.getFileAndDirectoryFromPath(from);
|
|
|
|
toFileAndDir = this.getFileAndDirectoryFromPath(to);
|
|
|
|
|
|
|
|
if (toFileAndDir.directory) {
|
|
|
|
// Create the target directory if it doesn't exist.
|
|
|
|
return this.createDir(toFileAndDir.directory);
|
|
|
|
}
|
|
|
|
}).then(() => {
|
|
|
|
if (this.isHTMLAPI) {
|
|
|
|
// In HTML API, the file name cannot include a directory, otherwise it fails.
|
2018-01-29 09:05:20 +00:00
|
|
|
const fromDir = this.textUtils.concatenatePaths(this.basePath, fromFileAndDir.directory),
|
2017-11-08 13:48:34 +00:00
|
|
|
toDir = this.textUtils.concatenatePaths(this.basePath, toFileAndDir.directory);
|
|
|
|
|
|
|
|
return this.file.copyFile(fromDir, fromFileAndDir.name, toDir, toFileAndDir.name);
|
|
|
|
} else {
|
2018-06-20 11:18:44 +00:00
|
|
|
return this.file.copyFile(this.basePath, from, this.basePath, to).catch((error) => {
|
|
|
|
// The copy can fail if the path has encoded characters. Try again if that's the case.
|
|
|
|
const decodedFrom = decodeURI(from),
|
|
|
|
decodedTo = decodeURI(to);
|
|
|
|
|
|
|
|
if (from != decodedFrom || to != decodedTo) {
|
|
|
|
return this.file.copyFile(this.basePath, decodedFrom, this.basePath, decodedTo);
|
|
|
|
} else {
|
|
|
|
return Promise.reject(error);
|
|
|
|
}
|
|
|
|
});
|
2017-11-08 13:48:34 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract the file name and directory from a given path.
|
|
|
|
*
|
|
|
|
* @param {string} path Path to be extracted.
|
|
|
|
* @return {any} Plain object containing the file name and directory.
|
|
|
|
* @description
|
|
|
|
* file.pdf -> directory: '', name: 'file.pdf'
|
|
|
|
* /file.pdf -> directory: '', name: 'file.pdf'
|
|
|
|
* path/file.pdf -> directory: 'path', name: 'file.pdf'
|
|
|
|
* path/ -> directory: 'path', name: ''
|
|
|
|
* path -> directory: '', name: 'path'
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getFileAndDirectoryFromPath(path: string): any {
|
|
|
|
const file = {
|
2017-11-08 13:48:34 +00:00
|
|
|
directory: '',
|
|
|
|
name: ''
|
2018-01-29 09:05:20 +00:00
|
|
|
};
|
2017-11-08 13:48:34 +00:00
|
|
|
|
2018-01-29 09:05:20 +00:00
|
|
|
file.directory = path.substring(0, path.lastIndexOf('/'));
|
2017-11-08 13:48:34 +00:00
|
|
|
file.name = path.substr(path.lastIndexOf('/') + 1);
|
|
|
|
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the internal URL of a file.
|
|
|
|
*
|
|
|
|
* @param {FileEntry} fileEntry File Entry.
|
|
|
|
* @return {string} Internal URL.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getInternalURL(fileEntry: FileEntry): string {
|
2017-11-08 13:48:34 +00:00
|
|
|
if (!fileEntry.toInternalURL) {
|
|
|
|
// File doesn't implement toInternalURL, use toURL.
|
|
|
|
return fileEntry.toURL();
|
|
|
|
}
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return fileEntry.toInternalURL();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds the basePath to a path if it doesn't have it already.
|
|
|
|
*
|
|
|
|
* @param {string} path Path to treat.
|
|
|
|
* @return {string} Path with basePath added.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
addBasePathIfNeeded(path: string): string {
|
2017-11-08 13:48:34 +00:00
|
|
|
if (path.indexOf(this.basePath) > -1) {
|
|
|
|
return path;
|
|
|
|
} else {
|
|
|
|
return this.textUtils.concatenatePaths(this.basePath, path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the base path from a path. If basePath isn't found, return false.
|
|
|
|
*
|
|
|
|
* @param {string} path Path to treat.
|
|
|
|
* @return {string} Path without basePath if basePath was found, undefined otherwise.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
removeBasePath(path: string): string {
|
2017-11-08 13:48:34 +00:00
|
|
|
if (path.indexOf(this.basePath) > -1) {
|
|
|
|
return path.replace(this.basePath, '');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unzips a file.
|
|
|
|
*
|
|
|
|
* @param {string} path Path to the ZIP file.
|
|
|
|
* @param {string} [destFolder] Path to the destination folder. If not defined, a new folder will be created with the
|
|
|
|
* same location and name as the ZIP file (without extension).
|
2017-11-17 13:20:19 +00:00
|
|
|
* @param {Function} [onProgress] Function to call on progress.
|
2017-11-08 13:48:34 +00:00
|
|
|
* @return {Promise<any>} Promise resolved when the file is unzipped.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
unzipFile(path: string, destFolder?: string, onProgress?: Function): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
// Get the source file.
|
|
|
|
return this.getFile(path).then((fileEntry) => {
|
|
|
|
// If destFolder is not set, use same location as ZIP file. We need to use absolute paths (including basePath).
|
|
|
|
destFolder = this.addBasePathIfNeeded(destFolder || this.mimeUtils.removeExtension(path));
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-17 13:20:19 +00:00
|
|
|
return this.zip.unzip(fileEntry.toURL(), destFolder, onProgress);
|
2018-04-26 07:59:00 +00:00
|
|
|
}).then((result) => {
|
|
|
|
if (result == -1) {
|
|
|
|
return Promise.reject(null);
|
|
|
|
}
|
2017-11-08 13:48:34 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Search a string or regexp in a file contents and replace it. The result is saved in the same file.
|
|
|
|
*
|
|
|
|
* @param {string} path Path to the file.
|
|
|
|
* @param {string|RegExp} search Value to search.
|
|
|
|
* @param {string} newValue New value.
|
|
|
|
* @return {Promise<any>} Promise resolved in success.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
replaceInFile(path: string, search: string | RegExp, newValue: string): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.readFile(path).then((content) => {
|
|
|
|
if (typeof content == 'undefined' || content === null || !content.replace) {
|
|
|
|
return Promise.reject(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (content.match(search)) {
|
|
|
|
content = content.replace(search, newValue);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.writeFile(path, content);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a file/dir metadata given the file's entry.
|
|
|
|
*
|
|
|
|
* @param {Entry} fileEntry FileEntry retrieved from getFile or similar.
|
|
|
|
* @return {Promise<any>} Promise resolved with metadata.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getMetadata(fileEntry: Entry): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
if (!fileEntry || !fileEntry.getMetadata) {
|
|
|
|
return Promise.reject(null);
|
|
|
|
}
|
|
|
|
|
2018-01-29 09:05:20 +00:00
|
|
|
return new Promise((resolve, reject): void => {
|
2017-11-08 13:48:34 +00:00
|
|
|
fileEntry.getMetadata(resolve, reject);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a file/dir metadata given the path.
|
|
|
|
*
|
|
|
|
* @param {string} path Path to the file/dir.
|
|
|
|
* @param {boolean} [isDir] True if directory, false if file.
|
|
|
|
* @return {Promise<any>} Promise resolved with metadata.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getMetadataFromPath(path: string, isDir?: boolean): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
let promise;
|
|
|
|
if (isDir) {
|
|
|
|
promise = this.getDir(path);
|
|
|
|
} else {
|
|
|
|
promise = this.getFile(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
return promise.then((entry) => {
|
|
|
|
return this.getMetadata(entry);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the starting slash of a path if it's there. E.g. '/sites/filepool' -> 'sites/filepool'.
|
|
|
|
*
|
|
|
|
* @param {string} path Path.
|
|
|
|
* @return {string} Path without a slash in the first position.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
removeStartingSlash(path: string): string {
|
2017-11-08 13:48:34 +00:00
|
|
|
if (path[0] == '/') {
|
|
|
|
return path.substr(1);
|
|
|
|
}
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convenience function to copy or move an external file.
|
|
|
|
*
|
|
|
|
* @param {string} from Absolute path to the file to copy/move.
|
|
|
|
* @param {string} to Relative new path of the file (inside the app folder).
|
|
|
|
* @param {boolean} copy True to copy, false to move.
|
|
|
|
* @return {Promise<any>} Promise resolved when the entry is copied/moved.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
protected copyOrMoveExternalFile(from: string, to: string, copy?: boolean): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
// Get the file to copy/move.
|
|
|
|
return this.getExternalFile(from).then((fileEntry) => {
|
|
|
|
// Create the destination dir if it doesn't exist.
|
2018-01-29 09:05:20 +00:00
|
|
|
const dirAndFile = this.getFileAndDirectoryFromPath(to);
|
|
|
|
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.createDir(dirAndFile.directory).then((dirEntry) => {
|
|
|
|
// Now copy/move the file.
|
2018-01-29 09:05:20 +00:00
|
|
|
return new Promise((resolve, reject): void => {
|
2017-11-08 13:48:34 +00:00
|
|
|
if (copy) {
|
|
|
|
fileEntry.copyTo(dirEntry, dirAndFile.name, resolve, reject);
|
|
|
|
} else {
|
|
|
|
fileEntry.moveTo(dirEntry, dirAndFile.name, resolve, reject);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy a file from outside of the app folder to somewhere inside the app folder.
|
|
|
|
*
|
|
|
|
* @param {string} from Absolute path to the file to copy.
|
|
|
|
* @param {string} to Relative new path of the file (inside the app folder).
|
|
|
|
* @return {Promise<any>} Promise resolved when the entry is copied.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
copyExternalFile(from: string, to: string): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.copyOrMoveExternalFile(from, to, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Move a file from outside of the app folder to somewhere inside the app folder.
|
|
|
|
*
|
|
|
|
* @param {string} from Absolute path to the file to move.
|
|
|
|
* @param {string} to Relative new path of the file (inside the app folder).
|
|
|
|
* @return {Promise<any>} Promise resolved when the entry is moved.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
moveExternalFile(from: string, to: string): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
return this.copyOrMoveExternalFile(from, to, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a unique file name inside a folder, adding numbers to the file name if needed.
|
|
|
|
*
|
|
|
|
* @param {string} dirPath Path to the destination folder.
|
|
|
|
* @param {string} fileName File name that wants to be used.
|
|
|
|
* @param {string} [defaultExt] Default extension to use if no extension found in the file.
|
|
|
|
* @return {Promise<string>} Promise resolved with the unique file name.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
getUniqueNameInFolder(dirPath: string, fileName: string, defaultExt?: string): Promise<string> {
|
2017-11-08 13:48:34 +00:00
|
|
|
// Get existing files in the folder.
|
|
|
|
return this.getDirectoryContents(dirPath).then((entries) => {
|
2018-01-29 09:05:20 +00:00
|
|
|
const files = {};
|
|
|
|
let num = 1,
|
2017-11-08 13:48:34 +00:00
|
|
|
fileNameWithoutExtension = this.mimeUtils.removeExtension(fileName),
|
|
|
|
extension = this.mimeUtils.getFileExtension(fileName) || defaultExt,
|
2018-01-29 09:05:20 +00:00
|
|
|
newName;
|
2017-11-08 13:48:34 +00:00
|
|
|
|
|
|
|
// Clean the file name.
|
|
|
|
fileNameWithoutExtension = this.textUtils.removeSpecialCharactersForFiles(
|
2018-01-29 09:05:20 +00:00
|
|
|
this.textUtils.decodeURIComponent(fileNameWithoutExtension));
|
2017-11-08 13:48:34 +00:00
|
|
|
|
|
|
|
// Index the files by name.
|
|
|
|
entries.forEach((entry) => {
|
|
|
|
files[entry.name] = entry;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Format extension.
|
|
|
|
if (extension) {
|
|
|
|
extension = '.' + extension;
|
|
|
|
} else {
|
|
|
|
extension = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
newName = fileNameWithoutExtension + extension;
|
|
|
|
if (typeof files[newName] == 'undefined') {
|
|
|
|
// No file with the same name.
|
|
|
|
return newName;
|
|
|
|
} else {
|
|
|
|
// Repeated name. Add a number until we find a free name.
|
|
|
|
do {
|
2018-01-29 09:05:20 +00:00
|
|
|
newName = fileNameWithoutExtension + '(' + num + ')' + extension;
|
|
|
|
num++;
|
2017-11-08 13:48:34 +00:00
|
|
|
} while (typeof files[newName] != 'undefined');
|
|
|
|
|
|
|
|
// Ask the user what he wants to do.
|
|
|
|
return newName;
|
|
|
|
}
|
|
|
|
}).catch(() => {
|
|
|
|
// Folder doesn't exist, name is unique. Clean it and return it.
|
|
|
|
return this.textUtils.removeSpecialCharactersForFiles(this.textUtils.decodeURIComponent(fileName));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove app temporary folder.
|
|
|
|
*
|
|
|
|
* @return {Promise<any>} Promise resolved when done.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
clearTmpFolder(): Promise<any> {
|
2018-05-07 08:08:24 +00:00
|
|
|
return this.removeDir(CoreFileProvider.TMPFOLDER).catch(() => {
|
|
|
|
// Ignore errors because the folder might not exist.
|
|
|
|
});
|
2017-11-08 13:48:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a folder path and a list of used files, remove all the files of the folder that aren't on the list of used files.
|
|
|
|
*
|
|
|
|
* @param {string} dirPath Folder path.
|
|
|
|
* @param {any[]} files List of used files.
|
|
|
|
* @return {Promise<any>} Promise resolved when done, rejected if failure.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
removeUnusedFiles(dirPath: string, files: any[]): Promise<any> {
|
2017-11-08 13:48:34 +00:00
|
|
|
// Get the directory contents.
|
|
|
|
return this.getDirectoryContents(dirPath).then((contents) => {
|
|
|
|
if (!contents.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-01-29 09:05:20 +00:00
|
|
|
const filesMap = {},
|
2017-11-08 13:48:34 +00:00
|
|
|
promises = [];
|
|
|
|
|
|
|
|
// Index the received files by fullPath and ignore the invalid ones.
|
|
|
|
files.forEach((file) => {
|
|
|
|
if (file.fullPath) {
|
|
|
|
filesMap[file.fullPath] = file;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Check which of the content files aren't used anymore and delete them.
|
|
|
|
contents.forEach((file) => {
|
|
|
|
if (!filesMap[file.fullPath]) {
|
|
|
|
// File isn't used, delete it.
|
|
|
|
promises.push(this.removeFileByFileEntry(file));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return Promise.all(promises);
|
|
|
|
}).catch(() => {
|
|
|
|
// Ignore errors, maybe it doesn't exist.
|
|
|
|
});
|
|
|
|
}
|
2018-01-09 07:50:08 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a file is inside the app's folder.
|
|
|
|
*
|
|
|
|
* @param {string} path The absolute path of the file to check.
|
|
|
|
* @return {boolean} Whether the file is in the app's folder.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
isFileInAppFolder(path: string): boolean {
|
2018-01-09 07:50:08 +00:00
|
|
|
return path.indexOf(this.basePath) != -1;
|
|
|
|
}
|
2017-11-08 13:48:34 +00:00
|
|
|
}
|