MOBILE-2261 file: Implement file provider and its Mock
parent
8196c79954
commit
6c19fd1b0e
|
@ -64,6 +64,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@ionic-native/core/-/core-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ionic-native/core/-/core-4.3.0.tgz",
|
||||||
"integrity": "sha512-Pf0qCzqlVFmIpZpvo35Kl0e+1K8GUgPMcKBnN57gWh+5Ecj3dPcb+MbP4murJo/dnFsIYPYdXRZRf74hjo6gtw=="
|
"integrity": "sha512-Pf0qCzqlVFmIpZpvo35Kl0e+1K8GUgPMcKBnN57gWh+5Ecj3dPcb+MbP4murJo/dnFsIYPYdXRZRf74hjo6gtw=="
|
||||||
},
|
},
|
||||||
|
"@ionic-native/file": {
|
||||||
|
"version": "4.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ionic-native/file/-/file-4.3.3.tgz",
|
||||||
|
"integrity": "sha512-r273zw1gkgGTmlapyJnh31Yemt1P8u1CnTtiZGr3oC/BdlSvLppR7ONW7KbsAxA31UIDAXG+mXP3EqEP2AtVvw=="
|
||||||
|
},
|
||||||
"@ionic-native/globalization": {
|
"@ionic-native/globalization": {
|
||||||
"version": "4.3.2",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@ionic-native/globalization/-/globalization-4.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@ionic-native/globalization/-/globalization-4.3.2.tgz",
|
||||||
|
@ -99,6 +104,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@ionic-native/status-bar/-/status-bar-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ionic-native/status-bar/-/status-bar-4.3.0.tgz",
|
||||||
"integrity": "sha512-gjS0U2uT6XYshysvzNu98Pf6b5SZ7SGSYkZW1mft19geFn6/MKunX1CJkjpXmiTn14nAD1+FBxF43Oi2OfoM4g=="
|
"integrity": "sha512-gjS0U2uT6XYshysvzNu98Pf6b5SZ7SGSYkZW1mft19geFn6/MKunX1CJkjpXmiTn14nAD1+FBxF43Oi2OfoM4g=="
|
||||||
},
|
},
|
||||||
|
"@ionic-native/zip": {
|
||||||
|
"version": "4.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ionic-native/zip/-/zip-4.3.3.tgz",
|
||||||
|
"integrity": "sha512-QFmRDV6PDGkPDCMcD2cyk6XUvsYjn3T3QphCORxmWj+7szN7AXzfpmhkSMysBY7I5N3yjtNkRJY0XAIMTV/wLA=="
|
||||||
|
},
|
||||||
"@ionic/app-scripts": {
|
"@ionic/app-scripts": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ionic/app-scripts/-/app-scripts-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ionic/app-scripts/-/app-scripts-3.0.0.tgz",
|
||||||
|
@ -125,6 +135,16 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
|
||||||
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
|
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
|
||||||
},
|
},
|
||||||
|
"@types/cordova-plugin-file": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cordova-plugin-file/-/cordova-plugin-file-0.0.3.tgz",
|
||||||
|
"integrity": "sha1-HogEIYKUSk4zfbjQ5stA7FeFDKo="
|
||||||
|
},
|
||||||
|
"@types/cordova-plugin-file-transfer": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cordova-plugin-file-transfer/-/cordova-plugin-file-transfer-0.0.3.tgz",
|
||||||
|
"integrity": "sha1-u6d+jQTQejlRR5eiA8JXxbECNoA="
|
||||||
|
},
|
||||||
"@types/cordova-plugin-globalization": {
|
"@types/cordova-plugin-globalization": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cordova-plugin-globalization/-/cordova-plugin-globalization-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cordova-plugin-globalization/-/cordova-plugin-globalization-0.0.3.tgz",
|
||||||
|
@ -837,6 +857,11 @@
|
||||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
|
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"core-js": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz",
|
||||||
|
"integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU="
|
||||||
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
|
@ -1149,6 +1174,11 @@
|
||||||
"integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
|
"integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"es6-promise": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
|
||||||
|
"integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y="
|
||||||
|
},
|
||||||
"es6-set": {
|
"es6-set": {
|
||||||
"version": "0.1.5",
|
"version": "0.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
|
||||||
|
@ -2905,6 +2935,33 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jszip": {
|
||||||
|
"version": "3.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.4.tgz",
|
||||||
|
"integrity": "sha512-z6w8iYIxZ/fcgul0j/OerkYnkomH8BZigvzbxVmr2h5HkZUrPtk2kjYtLkqR9wwQxEP6ecKNoKLsbhd18jfnGA==",
|
||||||
|
"dependencies": {
|
||||||
|
"isarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||||
|
},
|
||||||
|
"lie": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||||
|
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4="
|
||||||
|
},
|
||||||
|
"pako": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg=="
|
||||||
|
},
|
||||||
|
"readable-stream": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
|
||||||
|
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"kind-of": {
|
"kind-of": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||||
|
@ -3882,8 +3939,7 @@
|
||||||
"process-nextick-args": {
|
"process-nextick-args": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
|
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"promise.prototype.finally": {
|
"promise.prototype.finally": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
|
@ -4863,8 +4919,7 @@
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"utils-merge": {
|
"utils-merge": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"@angular/platform-browser-dynamic": "4.4.3",
|
"@angular/platform-browser-dynamic": "4.4.3",
|
||||||
"@ionic-native/clipboard": "^4.3.2",
|
"@ionic-native/clipboard": "^4.3.2",
|
||||||
"@ionic-native/core": "4.3.0",
|
"@ionic-native/core": "4.3.0",
|
||||||
|
"@ionic-native/file": "^4.3.3",
|
||||||
"@ionic-native/globalization": "^4.3.2",
|
"@ionic-native/globalization": "^4.3.2",
|
||||||
"@ionic-native/in-app-browser": "^4.3.3",
|
"@ionic-native/in-app-browser": "^4.3.3",
|
||||||
"@ionic-native/keyboard": "^4.3.2",
|
"@ionic-native/keyboard": "^4.3.2",
|
||||||
|
@ -43,10 +44,12 @@
|
||||||
"@ionic-native/splash-screen": "4.3.0",
|
"@ionic-native/splash-screen": "4.3.0",
|
||||||
"@ionic-native/sqlite": "^4.3.2",
|
"@ionic-native/sqlite": "^4.3.2",
|
||||||
"@ionic-native/status-bar": "4.3.0",
|
"@ionic-native/status-bar": "4.3.0",
|
||||||
|
"@ionic-native/zip": "^4.3.3",
|
||||||
"@ionic/storage": "2.0.1",
|
"@ionic/storage": "2.0.1",
|
||||||
"@ngx-translate/core": "^8.0.0",
|
"@ngx-translate/core": "^8.0.0",
|
||||||
"@ngx-translate/http-loader": "^2.0.0",
|
"@ngx-translate/http-loader": "^2.0.0",
|
||||||
"@types/cordova": "0.0.34",
|
"@types/cordova": "0.0.34",
|
||||||
|
"@types/cordova-plugin-file-transfer": "0.0.3",
|
||||||
"@types/cordova-plugin-globalization": "0.0.3",
|
"@types/cordova-plugin-globalization": "0.0.3",
|
||||||
"@types/cordova-plugin-network-information": "0.0.3",
|
"@types/cordova-plugin-network-information": "0.0.3",
|
||||||
"@types/node": "^8.0.47",
|
"@types/node": "^8.0.47",
|
||||||
|
@ -55,6 +58,7 @@
|
||||||
"electron-windows-notifications": "^1.1.13",
|
"electron-windows-notifications": "^1.1.13",
|
||||||
"ionic-angular": "3.7.1",
|
"ionic-angular": "3.7.1",
|
||||||
"ionicons": "3.0.0",
|
"ionicons": "3.0.0",
|
||||||
|
"jszip": "^3.1.4",
|
||||||
"moment": "^2.19.1",
|
"moment": "^2.19.1",
|
||||||
"promise.prototype.finally": "^3.0.1",
|
"promise.prototype.finally": "^3.0.1",
|
||||||
"rxjs": "5.4.3",
|
"rxjs": "5.4.3",
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { CoreUrlUtilsProvider } from '../providers/utils/url';
|
||||||
import { CoreUtilsProvider } from '../providers/utils/utils';
|
import { CoreUtilsProvider } from '../providers/utils/utils';
|
||||||
import { CoreMimetypeUtilsProvider } from '../providers/utils/mimetype';
|
import { CoreMimetypeUtilsProvider } from '../providers/utils/mimetype';
|
||||||
import { CoreInitDelegate } from '../providers/init';
|
import { CoreInitDelegate } from '../providers/init';
|
||||||
|
import { CoreFileProvider } from '../providers/file';
|
||||||
|
|
||||||
// For translate loader. AoT requires an exported function for factories.
|
// For translate loader. AoT requires an exported function for factories.
|
||||||
export function createTranslateLoader(http: HttpClient) {
|
export function createTranslateLoader(http: HttpClient) {
|
||||||
|
@ -68,7 +69,8 @@ export function createTranslateLoader(http: HttpClient) {
|
||||||
CoreUrlUtilsProvider,
|
CoreUrlUtilsProvider,
|
||||||
CoreUtilsProvider,
|
CoreUtilsProvider,
|
||||||
CoreMimetypeUtilsProvider,
|
CoreMimetypeUtilsProvider,
|
||||||
CoreInitDelegate
|
CoreInitDelegate,
|
||||||
|
CoreFileProvider
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
|
|
|
@ -15,21 +15,31 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Platform } from 'ionic-angular';
|
import { Platform } from 'ionic-angular';
|
||||||
|
|
||||||
import { CoreAppProvider } from '../../providers/app';
|
|
||||||
import { Clipboard } from '@ionic-native/clipboard';
|
import { Clipboard } from '@ionic-native/clipboard';
|
||||||
|
import { File } from '@ionic-native/file';
|
||||||
import { Globalization } from '@ionic-native/globalization';
|
import { Globalization } from '@ionic-native/globalization';
|
||||||
import { Network } from '@ionic-native/network';
|
import { Network } from '@ionic-native/network';
|
||||||
|
import { Zip } from '@ionic-native/zip';
|
||||||
import { ClipboardMock } from './providers/clipboard';
|
import { ClipboardMock } from './providers/clipboard';
|
||||||
|
import { FileMock} from './providers/file';
|
||||||
import { GlobalizationMock } from './providers/globalization';
|
import { GlobalizationMock } from './providers/globalization';
|
||||||
import { NetworkMock } from './providers/network';
|
import { NetworkMock } from './providers/network';
|
||||||
|
import { ZipMock } from './providers/zip';
|
||||||
import { InAppBrowser } from '@ionic-native/in-app-browser';
|
import { InAppBrowser } from '@ionic-native/in-app-browser';
|
||||||
|
|
||||||
|
import { CoreEmulatorHelper } from './providers/helper';
|
||||||
|
import { CoreAppProvider } from '../../providers/app';
|
||||||
|
import { CoreTextUtilsProvider } from '../../providers/utils/text';
|
||||||
|
import { CoreMimetypeUtilsProvider } from '../../providers/utils/mimetype';
|
||||||
|
import { CoreInitDelegate } from '../../providers/init';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
CoreEmulatorHelper,
|
||||||
ClipboardMock,
|
ClipboardMock,
|
||||||
GlobalizationMock,
|
GlobalizationMock,
|
||||||
{
|
{
|
||||||
|
@ -39,6 +49,14 @@ import { InAppBrowser } from '@ionic-native/in-app-browser';
|
||||||
return appProvider.isMobile() ? new Clipboard() : new ClipboardMock(appProvider);
|
return appProvider.isMobile() ? new Clipboard() : new ClipboardMock(appProvider);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: File,
|
||||||
|
deps: [CoreAppProvider, CoreTextUtilsProvider],
|
||||||
|
useFactory: (appProvider: CoreAppProvider, textUtils: CoreTextUtilsProvider) => {
|
||||||
|
// Use platform instead of CoreAppProvider to prevent circular dependencies.
|
||||||
|
return appProvider.isMobile() ? new File() : new FileMock(appProvider, textUtils);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: Globalization,
|
provide: Globalization,
|
||||||
deps: [CoreAppProvider],
|
deps: [CoreAppProvider],
|
||||||
|
@ -54,18 +72,33 @@ import { InAppBrowser } from '@ionic-native/in-app-browser';
|
||||||
return platform.is('cordova') ? new Network() : new NetworkMock();
|
return platform.is('cordova') ? new Network() : new NetworkMock();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: Zip,
|
||||||
|
deps: [CoreAppProvider, File, CoreMimetypeUtilsProvider, CoreTextUtilsProvider],
|
||||||
|
useFactory: (appProvider: CoreAppProvider, file: File, mimeUtils: CoreMimetypeUtilsProvider) => {
|
||||||
|
// Use platform instead of CoreAppProvider to prevent circular dependencies.
|
||||||
|
return appProvider.isMobile() ? new Zip() : new ZipMock(file, mimeUtils);
|
||||||
|
}
|
||||||
|
},
|
||||||
InAppBrowser
|
InAppBrowser
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CoreEmulatorModule {
|
export class CoreEmulatorModule {
|
||||||
constructor(appProvider: CoreAppProvider) {
|
constructor(appProvider: CoreAppProvider, initDelegate: CoreInitDelegate, helper: CoreEmulatorHelper) {
|
||||||
let win = <any>window; // Convert the "window" to "any" type to be able to use non-standard properties.
|
let win = <any>window; // Convert the "window" to "any" type to be able to use non-standard properties.
|
||||||
|
|
||||||
// Emulate Custom URL Scheme plugin in desktop apps.
|
// Emulate Custom URL Scheme plugin in desktop apps.
|
||||||
if (appProvider.isDesktop()) {
|
if (appProvider.isDesktop()) {
|
||||||
require('electron').ipcRenderer.on('mmAppLaunched', function(event, url) {
|
require('electron').ipcRenderer.on('mmAppLaunched', (event, url) => {
|
||||||
win.handleOpenURL && win.handleOpenURL(url);
|
if (typeof win.handleOpenURL != 'undefined') {
|
||||||
|
win.handleOpenURL(url);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!appProvider.isMobile()) {
|
||||||
|
// Register an init process to load the Mocks that need it.
|
||||||
|
initDelegate.registerProcess(helper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,711 @@
|
||||||
|
// (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 { File, Entry, DirectoryEntry, FileEntry, FileError, IWriteOptions } from '@ionic-native/file';
|
||||||
|
import { CoreAppProvider } from '../../../providers/app';
|
||||||
|
import { CoreTextUtilsProvider } from '../../../providers/utils/text';
|
||||||
|
import { CoreConfigConstants } from '../../../configconstants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the Cordova File plugin in desktop apps and in browser.
|
||||||
|
* Most of the code is extracted from the File class of Ionic Native.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class FileMock extends File {
|
||||||
|
|
||||||
|
constructor(private appProvider: CoreAppProvider, private textUtils: CoreTextUtilsProvider) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a directory exists in a certain path, directory.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} dir Name of directory to check
|
||||||
|
* @returns {Promise<boolean>} Returns a Promise that resolves to true if the directory exists or rejects with an error.
|
||||||
|
*/
|
||||||
|
checkDir(path: string, dir: string) : Promise<boolean> {
|
||||||
|
let fullPath = this.textUtils.concatenatePaths(path, dir);
|
||||||
|
return this.resolveDirectoryUrl(fullPath).then(() => {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file exists in a certain path, directory.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} file Name of file to check.
|
||||||
|
* @returns {Promise<boolean>} Returns a Promise that resolves with a boolean or rejects with an error.
|
||||||
|
*/
|
||||||
|
checkFile(path: string, file: string): Promise<boolean> {
|
||||||
|
return this.resolveLocalFilesystemUrl(this.textUtils.concatenatePaths(path, file)).then((fse) => {
|
||||||
|
if (fse.isFile) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
let err = new FileError(13);
|
||||||
|
err.message = 'input is not a file';
|
||||||
|
return Promise.reject<boolean>(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private copyMock(srce: Entry, destdir: DirectoryEntry, newName: string): Promise<Entry> {
|
||||||
|
return new Promise<Entry>((resolve, reject) => {
|
||||||
|
srce.copyTo(destdir, newName, (deste) => {
|
||||||
|
resolve(deste);
|
||||||
|
}, (err) => {
|
||||||
|
this.fillErrorMessageMock(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a directory in various methods. If destination directory exists, will fail to copy.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above.
|
||||||
|
* @param {string} dirName Name of directory to copy.
|
||||||
|
* @param {string} newPath Base FileSystem of new location.
|
||||||
|
* @param {string} newDirName New name of directory to copy to (leave blank to remain the same).
|
||||||
|
* @returns {Promise<Entry>} Returns a Promise that resolves to the new Entry object or rejects with an error.
|
||||||
|
*/
|
||||||
|
copyDir(path: string, dirName: string, newPath: string, newDirName: string): Promise<Entry> {
|
||||||
|
return this.resolveDirectoryUrl(path).then((fse) => {
|
||||||
|
return this.getDirectory(fse, dirName, { create: false });
|
||||||
|
}).then((srcde) => {
|
||||||
|
return this.resolveDirectoryUrl(newPath).then((deste) => {
|
||||||
|
return this.copyMock(srcde, deste, newDirName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a file in various methods. If file exists, will fail to copy.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above
|
||||||
|
* @param {string} fileName Name of file to copy
|
||||||
|
* @param {string} newPath Base FileSystem of new location
|
||||||
|
* @param {string} newFileName New name of file to copy to (leave blank to remain the same)
|
||||||
|
* @returns {Promise<Entry>} Returns a Promise that resolves to an Entry or rejects with an error.
|
||||||
|
*/
|
||||||
|
copyFile(path: string, fileName: string, newPath: string, newFileName: string): Promise<Entry> {
|
||||||
|
newFileName = newFileName || fileName;
|
||||||
|
|
||||||
|
return this.resolveDirectoryUrl(path).then((fse) => {
|
||||||
|
return this.getFile(fse, fileName, { create: false });
|
||||||
|
}).then((srcfe) => {
|
||||||
|
return this.resolveDirectoryUrl(newPath).then((deste) => {
|
||||||
|
return this.copyMock(srcfe, deste, newFileName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new directory in the specific path.
|
||||||
|
* The replace boolean value determines whether to replace an existing directory with the same name.
|
||||||
|
* If an existing directory exists and the replace value is false, the promise will fail and return an error.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} dirName Name of directory to create
|
||||||
|
* @param {boolean} replace If true, replaces file with same name. If false returns error
|
||||||
|
* @returns {Promise<DirectoryEntry>} Returns a Promise that resolves with a DirectoryEntry or rejects with an error.
|
||||||
|
*/
|
||||||
|
createDir(path: string, dirName: string, replace: boolean): Promise<DirectoryEntry> {
|
||||||
|
let options: any = {
|
||||||
|
create: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!replace) {
|
||||||
|
options.exclusive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.resolveDirectoryUrl(path).then((fse) => {
|
||||||
|
return this.getDirectory(fse, dirName, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new file in the specific path.
|
||||||
|
* The replace boolean value determines whether to replace an existing file with the same name.
|
||||||
|
* If an existing file exists and the replace value is false, the promise will fail and return an error.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} fileName Name of file to create.
|
||||||
|
* @param {boolean} replace If true, replaces file with same name. If false returns error.
|
||||||
|
* @returns {Promise<FileEntry>} Returns a Promise that resolves to a FileEntry or rejects with an error.
|
||||||
|
*/
|
||||||
|
createFile(path: string, fileName: string, replace: boolean): Promise<FileEntry> {
|
||||||
|
let options: any = {
|
||||||
|
create: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!replace) {
|
||||||
|
options.exclusive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.resolveDirectoryUrl(path).then((fse) => {
|
||||||
|
return this.getFile(fse, fileName, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private createWriterMock(fe: FileEntry): Promise<FileWriter> {
|
||||||
|
return new Promise<FileWriter>((resolve, reject) => {
|
||||||
|
fe.createWriter((writer) => {
|
||||||
|
resolve(writer);
|
||||||
|
}, (err) => {
|
||||||
|
this.fillErrorMessageMock(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private fillErrorMessageMock(err: any): void {
|
||||||
|
try {
|
||||||
|
err.message = this.cordovaFileError[err.code];
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a directory.
|
||||||
|
*
|
||||||
|
* @param directoryEntry {DirectoryEntry} Directory entry, obtained by resolveDirectoryUrl method
|
||||||
|
* @param directoryName {string} Directory name
|
||||||
|
* @param flags {Flags} Options
|
||||||
|
* @returns {Promise<DirectoryEntry>}
|
||||||
|
*/
|
||||||
|
getDirectory(directoryEntry: DirectoryEntry, directoryName: string, flags: Flags): Promise<DirectoryEntry> {
|
||||||
|
return new Promise<DirectoryEntry>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
directoryEntry.getDirectory(directoryName, flags, (de) => {
|
||||||
|
resolve(de);
|
||||||
|
}, (err) => {
|
||||||
|
this.fillErrorMessageMock(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
} catch (xc) {
|
||||||
|
this.fillErrorMessageMock(xc);
|
||||||
|
reject(xc);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a file
|
||||||
|
* @param directoryEntry {DirectoryEntry} Directory entry, obtained by resolveDirectoryUrl method
|
||||||
|
* @param fileName {string} File name
|
||||||
|
* @param flags {Flags} Options
|
||||||
|
* @returns {Promise<FileEntry>}
|
||||||
|
*/
|
||||||
|
getFile(directoryEntry: DirectoryEntry, fileName: string, flags: Flags): Promise<FileEntry> {
|
||||||
|
return new Promise<FileEntry>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
directoryEntry.getFile(fileName, flags, resolve, (err) => {
|
||||||
|
this.fillErrorMessageMock(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
} catch (xc) {
|
||||||
|
this.fillErrorMessageMock(xc);
|
||||||
|
reject(xc);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get free disk space.
|
||||||
|
*
|
||||||
|
* @return {Promise<number>} Promise resolved with the free space.
|
||||||
|
*/
|
||||||
|
getFreeDiskSpace() : Promise<number> {
|
||||||
|
// FRequest a file system instance with a minimum size until we get an error.
|
||||||
|
if (window.requestFileSystem) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let iterations = 0,
|
||||||
|
maxIterations = 50,
|
||||||
|
calculateByRequest = (size: number, ratio: number) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
window.requestFileSystem(LocalFileSystem.PERSISTENT, size, () => {
|
||||||
|
iterations++;
|
||||||
|
if (iterations > maxIterations) {
|
||||||
|
resolve(size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
calculateByRequest(size * ratio, ratio).then(resolve);
|
||||||
|
}, () => {
|
||||||
|
resolve(size / ratio);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// General calculation, base 1MB and increasing factor 1.3.
|
||||||
|
calculateByRequest(1048576, 1.3).then((size: number) => {
|
||||||
|
iterations = 0;
|
||||||
|
maxIterations = 10;
|
||||||
|
// More accurate. Factor is 1.1.
|
||||||
|
calculateByRequest(size, 1.1).then((size: number) => {
|
||||||
|
return size / 1024; // Return size in KB.
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List files and directory from a given path.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above
|
||||||
|
* @param {string} dirName Name of directory
|
||||||
|
* @returns {Promise<Entry[]>} Returns a Promise that resolves to an array of Entry objects or rejects with an error.
|
||||||
|
*/
|
||||||
|
listDir(path: string, dirName: string): Promise<Entry[]> {
|
||||||
|
return this.resolveDirectoryUrl(path).then((fse) => {
|
||||||
|
return this.getDirectory(fse, dirName, { create: false, exclusive: false });
|
||||||
|
}).then((de) => {
|
||||||
|
let reader: any = de.createReader();
|
||||||
|
return this.readEntriesMock(reader);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an initialize the API for browser and desktop.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when loaded.
|
||||||
|
*/
|
||||||
|
load() : Promise<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let basePath,
|
||||||
|
win = <any> window; // Convert to <any> to be able to use non-standard properties.
|
||||||
|
|
||||||
|
if (typeof win.requestFileSystem == 'undefined') {
|
||||||
|
win.requestFileSystem = win.webkitRequestFileSystem;
|
||||||
|
}
|
||||||
|
if (typeof win.resolveLocalFileSystemURL == 'undefined') {
|
||||||
|
win.resolveLocalFileSystemURL = win.webkitResolveLocalFileSystemURL;
|
||||||
|
}
|
||||||
|
win.LocalFileSystem = {
|
||||||
|
PERSISTENT: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.appProvider.isDesktop()) {
|
||||||
|
let fs = require('fs'),
|
||||||
|
app = require('electron').remote.app;
|
||||||
|
|
||||||
|
// emulateCordovaFileForDesktop(fs);
|
||||||
|
|
||||||
|
// Initialize File System. Get the path to use.
|
||||||
|
basePath = app.getPath('documents') || app.getPath('home');
|
||||||
|
if (!basePath) {
|
||||||
|
reject('Cannot calculate base path for file system.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
basePath = this.textUtils.concatenatePaths(basePath.replace(/\\/g, '/'), CoreConfigConstants.app_id) + '/';
|
||||||
|
|
||||||
|
// Create the folder if needed.
|
||||||
|
fs.mkdir(basePath, (e) => {
|
||||||
|
if (!e || (e && e.code === 'EEXIST')) {
|
||||||
|
// Create successful or it already exists. Resolve.
|
||||||
|
// this.fileProvider.setHTMLBasePath(basePath);
|
||||||
|
resolve(basePath);
|
||||||
|
} else {
|
||||||
|
reject('Error creating base path.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// It's browser, request a quota to use. Request 500MB.
|
||||||
|
(<any>navigator).webkitPersistentStorage.requestQuota(500 * 1024 * 1024, (granted) => {
|
||||||
|
window.requestFileSystem(LocalFileSystem.PERSISTENT, granted, (entry) => {
|
||||||
|
basePath = entry.root.toURL();
|
||||||
|
// this.fileProvider.setHTMLBasePath(basePath);
|
||||||
|
resolve(basePath);
|
||||||
|
}, reject);
|
||||||
|
}, reject);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private moveMock(srce: Entry, destdir: DirectoryEntry, newName: string): Promise<Entry> {
|
||||||
|
return new Promise<Entry>((resolve, reject) => {
|
||||||
|
srce.moveTo(destdir, newName, (deste) => {
|
||||||
|
resolve(deste);
|
||||||
|
}, (err) => {
|
||||||
|
this.fillErrorMessageMock(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a directory to a given path.
|
||||||
|
*
|
||||||
|
* @param {string} path The source path to the directory.
|
||||||
|
* @param {string} dirName The source directory name.
|
||||||
|
* @param {string} newPath The destionation path to the directory.
|
||||||
|
* @param {string} newDirName The destination directory name.
|
||||||
|
* @returns {Promise<DirectoryEntry|Entry>} Returns a Promise that resolves to the new DirectoryEntry object or rejects with an error.
|
||||||
|
*/
|
||||||
|
moveDir(path: string, dirName: string, newPath: string, newDirName: string): Promise<DirectoryEntry | Entry> {
|
||||||
|
return this.resolveDirectoryUrl(path).then((fse) => {
|
||||||
|
return this.getDirectory(fse, dirName, { create: false });
|
||||||
|
}).then((srcde) => {
|
||||||
|
return this.resolveDirectoryUrl(newPath).then((deste) => {
|
||||||
|
return this.moveMock(srcde, deste, newDirName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a file to a given path.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above
|
||||||
|
* @param {string} fileName Name of file to move
|
||||||
|
* @param {string} newPath Base FileSystem of new location
|
||||||
|
* @param {string} newFileName New name of file to move to (leave blank to remain the same)
|
||||||
|
* @returns {Promise<Entry>} Returns a Promise that resolves to the new Entry or rejects with an error.
|
||||||
|
*/
|
||||||
|
moveFile(path: string, fileName: string, newPath: string, newFileName: string): Promise<Entry> {
|
||||||
|
newFileName = newFileName || fileName;
|
||||||
|
|
||||||
|
return this.resolveDirectoryUrl(path).then((fse) => {
|
||||||
|
return this.getFile(fse, fileName, { create: false });
|
||||||
|
}).then((srcfe) => {
|
||||||
|
return this.resolveDirectoryUrl(newPath).then((deste) => {
|
||||||
|
return this.moveMock(srcfe, deste, newFileName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read file and return data as an ArrayBuffer.
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} file Name of file, relative to path.
|
||||||
|
* @returns {Promise<ArrayBuffer>} Returns a Promise that resolves with the contents of the file as ArrayBuffer or rejects with an error.
|
||||||
|
*/
|
||||||
|
readAsArrayBuffer(path: string, file: string): Promise<ArrayBuffer> {
|
||||||
|
return this.readFileMock<ArrayBuffer>(path, file, 'ArrayBuffer');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read file and return data as a binary data.
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} file Name of file, relative to path.
|
||||||
|
* @returns {Promise<string>} Returns a Promise that resolves with the contents of the file as string rejects with an error.
|
||||||
|
*/
|
||||||
|
readAsBinaryString(path: string, file: string): Promise<string> {
|
||||||
|
return this.readFileMock<string>(path, file, 'BinaryString');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read file and return data as a base64 encoded data url.
|
||||||
|
* A data url is of the form:
|
||||||
|
* data: [<mediatype>][;base64],<data>
|
||||||
|
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} file Name of file, relative to path.
|
||||||
|
* @returns {Promise<string>} Returns a Promise that resolves with the contents of the file as data URL or rejects with an error.
|
||||||
|
*/
|
||||||
|
readAsDataURL(path: string, file: string): Promise<string> {
|
||||||
|
return this.readFileMock<string>(path, file, 'DataURL');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a file as text.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} file Name of file, relative to path.
|
||||||
|
* @returns {Promise<string>} Returns a Promise that resolves with the contents of the file as string or rejects with an error.
|
||||||
|
*/
|
||||||
|
readAsText(path: string, file: string): Promise<string> {
|
||||||
|
return this.readFileMock<string>(path, file, 'Text');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private readEntriesMock(dr: DirectoryReader): Promise<Entry[]> {
|
||||||
|
return new Promise<Entry[]>((resolve, reject) => {
|
||||||
|
dr.readEntries((entries: any) => {
|
||||||
|
resolve(entries);
|
||||||
|
}, (err) => {
|
||||||
|
this.fillErrorMessageMock(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a file.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} file Name of file, relative to path.
|
||||||
|
* @param {string} readAs Format to read as.
|
||||||
|
* @returns {Promise<string>} Returns a Promise that resolves with the contents of the file or rejects with an error.
|
||||||
|
*/
|
||||||
|
private readFileMock<T>(path: string, file: string, readAs: 'ArrayBuffer' | 'BinaryString' | 'DataURL' | 'Text'): Promise<T> {
|
||||||
|
return this.resolveDirectoryUrl(path).then((directoryEntry: DirectoryEntry) => {
|
||||||
|
return this.getFile(directoryEntry, file, { create: false });
|
||||||
|
}).then((fileEntry: FileEntry) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
reader.onloadend = () => {
|
||||||
|
if (reader.result !== undefined || reader.result !== null) {
|
||||||
|
resolve(<T><any>reader.result);
|
||||||
|
} else if (reader.error !== undefined || reader.error !== null) {
|
||||||
|
reject(reader.error);
|
||||||
|
} else {
|
||||||
|
reject({ code: null, message: 'READER_ONLOADEND_ERR' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fileEntry.file(file => {
|
||||||
|
reader[`readAs${readAs}`].call(reader, file);
|
||||||
|
}, error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private removeMock(fe: Entry): Promise<any> {
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
fe.remove(() => {
|
||||||
|
resolve({ success: true, fileRemoved: fe });
|
||||||
|
}, (err) => {
|
||||||
|
this.fillErrorMessageMock(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a directory at a given path.
|
||||||
|
*
|
||||||
|
* @param {string} path The path to the directory.
|
||||||
|
* @param {string} dirName The directory name.
|
||||||
|
* @returns {Promise<RemoveResult>} Returns a Promise that resolves to a RemoveResult or rejects with an error.
|
||||||
|
*/
|
||||||
|
removeDir(path: string, dirName: string): Promise<any> {
|
||||||
|
return this.resolveDirectoryUrl(path).then((fse) => {
|
||||||
|
return this.getDirectory(fse, dirName, { create: false });
|
||||||
|
}).then((de) => {
|
||||||
|
return this.removeMock(de);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a file from a desired location.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} fileName Name of file to remove.
|
||||||
|
* @returns {Promise<RemoveResult>} Returns a Promise that resolves to a RemoveResult or rejects with an error.
|
||||||
|
*/
|
||||||
|
removeFile(path: string, fileName: string): Promise<any> {
|
||||||
|
return this.resolveDirectoryUrl(path).then((fse) => {
|
||||||
|
return this.getFile(fse, fileName, { create: false });
|
||||||
|
}).then((fe) => {
|
||||||
|
return this.removeMock(fe);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all files and the directory from a desired location.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above
|
||||||
|
* @param {string} dirName Name of directory
|
||||||
|
* @returns {Promise<RemoveResult>} Returns a Promise that resolves with a RemoveResult or rejects with an error.
|
||||||
|
*/
|
||||||
|
removeRecursively(path: string, dirName: string): Promise<any> {
|
||||||
|
return this.resolveDirectoryUrl(path).then((fse) => {
|
||||||
|
return this.getDirectory(fse, dirName, { create: false });
|
||||||
|
}).then((de) => {
|
||||||
|
return this.rimrafMock(de);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a local directory url
|
||||||
|
* @param directoryUrl {string} directory system url
|
||||||
|
* @returns {Promise<DirectoryEntry>}
|
||||||
|
*/
|
||||||
|
resolveDirectoryUrl(directoryUrl: string): Promise<DirectoryEntry> {
|
||||||
|
return this.resolveLocalFilesystemUrl(directoryUrl).then((de) => {
|
||||||
|
if (de.isDirectory) {
|
||||||
|
return <DirectoryEntry>de;
|
||||||
|
} else {
|
||||||
|
const err = new FileError(13);
|
||||||
|
err.message = 'input is not a directory';
|
||||||
|
return Promise.reject<DirectoryEntry>(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a local file system URL
|
||||||
|
* @param fileUrl {string} file system url
|
||||||
|
* @returns {Promise<Entry>}
|
||||||
|
*/
|
||||||
|
resolveLocalFilesystemUrl(fileUrl: string): Promise<Entry> {
|
||||||
|
return new Promise<Entry>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
window.resolveLocalFileSystemURL(fileUrl, (entry: any) => {
|
||||||
|
resolve(entry);
|
||||||
|
}, (err) => {
|
||||||
|
this.fillErrorMessageMock(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
} catch (xc) {
|
||||||
|
this.fillErrorMessageMock(xc);
|
||||||
|
reject(xc);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private rimrafMock(de: DirectoryEntry): Promise<any> {
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
de.removeRecursively(() => {
|
||||||
|
resolve({ success: true, fileRemoved: de });
|
||||||
|
}, (err) => {
|
||||||
|
this.fillErrorMessageMock(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private writeMock(writer: FileWriter, gu: any): Promise<any> {
|
||||||
|
if (gu instanceof Blob) {
|
||||||
|
return this.writeFileInChunksMock(writer, gu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
writer.onwriteend = (evt) => {
|
||||||
|
if (writer.error) {
|
||||||
|
reject(writer.error);
|
||||||
|
} else {
|
||||||
|
resolve(evt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
writer.write(gu);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write to an existing file.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem.
|
||||||
|
* @param {string} fileName path relative to base path.
|
||||||
|
* @param {string | Blob} text content or blob to write.
|
||||||
|
* @returns {Promise<void>} Returns a Promise that resolves or rejects with an error.
|
||||||
|
*/
|
||||||
|
writeExistingFile(path: string, fileName: string, text: string | Blob): Promise<void> {
|
||||||
|
return this.writeFile(path, fileName, text, { replace: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write a new file to the desired location.
|
||||||
|
*
|
||||||
|
* @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above
|
||||||
|
* @param {string} fileName path relative to base path
|
||||||
|
* @param {string | Blob} text content or blob to write
|
||||||
|
* @param {any} options replace file if set to true. See WriteOptions for more information.
|
||||||
|
* @returns {Promise<any>} Returns a Promise that resolves to updated file entry or rejects with an error.
|
||||||
|
*/
|
||||||
|
writeFile(path: string, fileName: string, text: string | Blob | ArrayBuffer, options: IWriteOptions = {}): Promise<any> {
|
||||||
|
const getFileOpts: any = {
|
||||||
|
create: !options.append,
|
||||||
|
exclusive: !options.replace
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.resolveDirectoryUrl(path).then((directoryEntry: DirectoryEntry) => {
|
||||||
|
return this.getFile(directoryEntry, fileName, getFileOpts);
|
||||||
|
}).then((fileEntry: FileEntry) => {
|
||||||
|
return this.writeFileEntryMock(fileEntry, text, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write content to FileEntry.
|
||||||
|
*
|
||||||
|
* @hidden
|
||||||
|
* @param {FileEntry} fe File entry object.
|
||||||
|
* @param {string | Blob} text Content or blob to write.
|
||||||
|
* @param {IWriteOptions} options replace file if set to true. See WriteOptions for more information.
|
||||||
|
* @returns {Promise<FileEntry>} Returns a Promise that resolves to updated file entry or rejects with an error.
|
||||||
|
*/
|
||||||
|
private writeFileEntryMock(fe: FileEntry, text: string | Blob | ArrayBuffer, options: IWriteOptions) {
|
||||||
|
return this.createWriterMock(fe).then((writer) => {
|
||||||
|
if (options.append) {
|
||||||
|
writer.seek(writer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.truncate) {
|
||||||
|
writer.truncate(options.truncate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.writeMock(writer, text);
|
||||||
|
}).then(() => fe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private writeFileInChunksMock(writer: FileWriter, file: Blob) {
|
||||||
|
const BLOCK_SIZE = 1024 * 1024;
|
||||||
|
let writtenSize = 0;
|
||||||
|
|
||||||
|
function writeNextChunk() {
|
||||||
|
const size = Math.min(BLOCK_SIZE, file.size - writtenSize);
|
||||||
|
const chunk = file.slice(writtenSize, writtenSize + size);
|
||||||
|
|
||||||
|
writtenSize += size;
|
||||||
|
writer.write(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
writer.onerror = reject;
|
||||||
|
writer.onwrite = () => {
|
||||||
|
if (writtenSize < file.size) {
|
||||||
|
writeNextChunk();
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
writeNextChunk();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
// (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 { CoreFileProvider } from '../../../providers/file';
|
||||||
|
import { CoreUtilsProvider } from '../../../providers/utils/utils';
|
||||||
|
import { File } from '@ionic-native/file';
|
||||||
|
import { CoreInitDelegate, CoreInitHandler } from '../../../providers/init';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the Cordova Zip plugin in desktop apps and in browser.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreEmulatorHelper implements CoreInitHandler {
|
||||||
|
name = 'CoreEmulator';
|
||||||
|
priority;
|
||||||
|
blocking = true;
|
||||||
|
|
||||||
|
constructor(private file: File, private fileProvider: CoreFileProvider, private utils: CoreUtilsProvider,
|
||||||
|
initDelegate: CoreInitDelegate) {
|
||||||
|
this.priority = initDelegate.MAX_RECOMMENDED_PRIORITY + 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the Mocks that need it.
|
||||||
|
*
|
||||||
|
* @return {Promise<void>} Promise resolved when loaded.
|
||||||
|
*/
|
||||||
|
load() : Promise<void> {
|
||||||
|
let promises = [];
|
||||||
|
|
||||||
|
promises.push((<any>this.file).load().then((basePath: string) => {
|
||||||
|
this.fileProvider.setHTMLBasePath(basePath);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return this.utils.allPromises(promises);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
// (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 { Zip } from '@ionic-native/zip';
|
||||||
|
import { JSZip } from 'jszip';
|
||||||
|
import { File } from '@ionic-native/file';
|
||||||
|
import { CoreMimetypeUtilsProvider } from '../../../providers/utils/mimetype';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the Cordova Zip plugin in desktop apps and in browser.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ZipMock extends Zip {
|
||||||
|
|
||||||
|
constructor(private file: File, private mimeUtils: CoreMimetypeUtilsProvider) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts files from a ZIP archive.
|
||||||
|
*
|
||||||
|
* @param {string} source Path to the source ZIP file.
|
||||||
|
* @param {string} destination Destination folder.
|
||||||
|
* @param {Function} [onProgress] Optional callback to be called on progress update
|
||||||
|
* @return {Promise<number>} Promise that resolves with a number. 0 is success, -1 is error.
|
||||||
|
*/
|
||||||
|
unzip(source: string, destination: string, onProgress?: Function) : Promise<number> {
|
||||||
|
// Replace all %20 with spaces.
|
||||||
|
source = source.replace(/%20/g, ' ');
|
||||||
|
destination = destination.replace(/%20/g, ' ');
|
||||||
|
|
||||||
|
let sourceDir = source.substring(0, source.lastIndexOf('/')),
|
||||||
|
sourceName = source.substr(source.lastIndexOf('/') + 1);
|
||||||
|
|
||||||
|
return this.file.readAsArrayBuffer(sourceDir, sourceName).then((data) => {
|
||||||
|
let zip = new JSZip(data),
|
||||||
|
promises = [],
|
||||||
|
loaded = 0,
|
||||||
|
total = Object.keys(zip.files).length;
|
||||||
|
|
||||||
|
if (!zip.files || !zip.files.length) {
|
||||||
|
// Nothing to extract.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
zip.files.forEach((file, name) => {
|
||||||
|
let type,
|
||||||
|
promise;
|
||||||
|
|
||||||
|
if (!file.dir) {
|
||||||
|
// It's a file. Get the mimetype and write the file.
|
||||||
|
type = this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(name));
|
||||||
|
promise = this.file.writeFile(destination, name, new Blob([file.asArrayBuffer()], {type: type}));
|
||||||
|
} else {
|
||||||
|
// It's a folder, create it if it doesn't exist.
|
||||||
|
promise = this.file.createDir(destination, name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
promises.push(promise.then(() => {
|
||||||
|
// File unzipped, call the progress.
|
||||||
|
loaded++;
|
||||||
|
onProgress && onProgress({loaded: loaded, total: total});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(function() {
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}).catch(function() {
|
||||||
|
// Error.
|
||||||
|
return -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,925 @@
|
||||||
|
// (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 {
|
||||||
|
logger;
|
||||||
|
initialized = false;
|
||||||
|
basePath = '';
|
||||||
|
isHTMLAPI = false;
|
||||||
|
|
||||||
|
// Formats to read a file.
|
||||||
|
FORMATTEXT = 0;
|
||||||
|
FORMATDATAURL = 1;
|
||||||
|
FORMATBINARYSTRING = 2;
|
||||||
|
FORMATARRAYBUFFER = 3;
|
||||||
|
|
||||||
|
// Folders.
|
||||||
|
SITESFOLDER = 'sites';
|
||||||
|
TMPFOLDER = 'tmp';
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
setHTMLBasePath(path: string) {
|
||||||
|
this.isHTMLAPI = true;
|
||||||
|
this.basePath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if we're using HTML API.
|
||||||
|
*
|
||||||
|
* @return {boolean} True if uses HTML API, false otherwise.
|
||||||
|
*/
|
||||||
|
usesHTMLAPI() : boolean {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
init() : Promise<void> {
|
||||||
|
if (this.initialized) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.platform.ready().then(() => {
|
||||||
|
|
||||||
|
if (this.platform.is('android')) {
|
||||||
|
this.basePath = this.file.externalApplicationStorageDirectory;
|
||||||
|
} else if (this.platform.is('ios')) {
|
||||||
|
this.basePath = this.file.documentsDirectory;
|
||||||
|
} else if (!this.isAvailable() || this.basePath === '') {
|
||||||
|
this.logger.error('Error getting device OS.');
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
isAvailable() : boolean {
|
||||||
|
return typeof window.resolveLocalFileSystemURL !== 'undefined' && typeof FileTransfer !== 'undefined';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a file.
|
||||||
|
*
|
||||||
|
* @param {string} path Relative path to the file.
|
||||||
|
* @return {Promise<FileEntry>} Promise resolved when the file is retrieved.
|
||||||
|
*/
|
||||||
|
getFile(path: string) : Promise<FileEntry> {
|
||||||
|
return this.init().then(() => {
|
||||||
|
this.logger.debug('Get file: ' + path);
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getDir(path: string) : Promise<DirectoryEntry> {
|
||||||
|
return this.init().then(() => {
|
||||||
|
this.logger.debug('Get directory: ' + path);
|
||||||
|
return this.file.resolveDirectoryUrl(this.addBasePathIfNeeded(path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get site folder path.
|
||||||
|
*
|
||||||
|
* @param {string} siteId Site ID.
|
||||||
|
* @return {string} Site folder path.
|
||||||
|
*/
|
||||||
|
getSiteFolder(siteId: string) : string {
|
||||||
|
return this.SITESFOLDER + '/' + siteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
protected create(isDirectory: boolean, path: string, failIfExists?: boolean, base?: string) : Promise<any> {
|
||||||
|
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);
|
||||||
|
return this.file.createDir(base, path, !failIfExists);
|
||||||
|
} else {
|
||||||
|
this.logger.debug('Create file ' + path + ' in ' + base);
|
||||||
|
return this.file.createFile(base, path, !failIfExists);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// this.file doesn't allow creating more than 1 level at a time (e.g. tmp/folder).
|
||||||
|
// We need to create them 1 by 1.
|
||||||
|
let firstDir = path.substr(0, path.indexOf('/')),
|
||||||
|
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);
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
createDir(path: string, failIfExists?: boolean) : Promise<DirectoryEntry> {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
createFile(path: string, failIfExists?: boolean) : Promise<FileEntry> {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
removeDir(path: string) : Promise<any> {
|
||||||
|
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);
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
removeFile(path: string) : Promise<any> {
|
||||||
|
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);
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
removeFileByFileEntry(fileEntry: any) : Promise<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getDirectoryContents(path: string) : Promise<any> {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
protected getSize(entry: any) : Promise<number> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (entry.isDirectory) {
|
||||||
|
let directoryReader = entry.createReader();
|
||||||
|
directoryReader.readEntries((entries) => {
|
||||||
|
|
||||||
|
let promises = [];
|
||||||
|
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++) {
|
||||||
|
let fileSize = parseInt(sizes[i]);
|
||||||
|
if (isNaN(fileSize)) {
|
||||||
|
reject();
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getDirectorySize(path: string) : Promise<number> {
|
||||||
|
// Remove basePath if it's in the path.
|
||||||
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||||
|
|
||||||
|
this.logger.debug('Get size of dir: ' + path);
|
||||||
|
return this.getDir(path).then((dirEntry) => {
|
||||||
|
return this.getSize(dirEntry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
getFileSize(path: string) : Promise<number> {
|
||||||
|
// Remove basePath if it's in the path.
|
||||||
|
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||||
|
|
||||||
|
this.logger.debug('Get size of file: ' + path);
|
||||||
|
return this.getFile(path).then((fileEntry) => {
|
||||||
|
return this.getSize(fileEntry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
getFileObjectFromFileEntry(entry: FileEntry) : Promise<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.logger.debug('Get file object of: ' + entry.fullPath);
|
||||||
|
entry.file(resolve, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the free space in the disk.
|
||||||
|
*
|
||||||
|
* @return {Promise<number>} Promise resolved with the estimated free space in bytes.
|
||||||
|
*/
|
||||||
|
calculateFreeSpace() : Promise<number> {
|
||||||
|
return this.file.getFreeDiskSpace().then((size) => {
|
||||||
|
return size; // GetFreeDiskSpace returns KB.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a filename that usually comes URL encoded.
|
||||||
|
*
|
||||||
|
* @param {string} filename The file name.
|
||||||
|
* @return {string} The file name normalized.
|
||||||
|
*/
|
||||||
|
normalizeFileName(filename: string) : string {
|
||||||
|
filename = this.textUtils.decodeURIComponent(filename);
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
readFile(path: string, format = this.FORMATTEXT) : Promise<any> {
|
||||||
|
// 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) {
|
||||||
|
case this.FORMATDATAURL:
|
||||||
|
return this.file.readAsDataURL(this.basePath, path);
|
||||||
|
case this.FORMATBINARYSTRING:
|
||||||
|
return this.file.readAsBinaryString(this.basePath, path);
|
||||||
|
case this.FORMATARRAYBUFFER:
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
readFileData(fileData: any, format = this.FORMATTEXT) : Promise<any> {
|
||||||
|
format = format || this.FORMATTEXT;
|
||||||
|
this.logger.debug('Read file from file data with format ' + format);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onloadend = (evt) => {
|
||||||
|
let target = <any> evt.target; // Convert to <any> to be able to use non-standard properties.
|
||||||
|
if (target.result !== undefined || target.result !== null) {
|
||||||
|
resolve(target.result);
|
||||||
|
} else if (target.error !== undefined || target.error !== null) {
|
||||||
|
reject(target.error);
|
||||||
|
} else {
|
||||||
|
reject({code: null, message: 'READER_ONLOADEND_ERR'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case this.FORMATDATAURL:
|
||||||
|
reader.readAsDataURL(fileData);
|
||||||
|
break;
|
||||||
|
case this.FORMATBINARYSTRING:
|
||||||
|
reader.readAsBinaryString(fileData);
|
||||||
|
break;
|
||||||
|
case this.FORMATARRAYBUFFER:
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
writeFile(path: string, data: any) : Promise<any> {
|
||||||
|
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) => {
|
||||||
|
if (this.isHTMLAPI && this.appProvider.isDesktop() &&
|
||||||
|
(typeof data == 'string' || data.toString() == '[object ArrayBuffer]')) {
|
||||||
|
// We need to write Blobs.
|
||||||
|
let type = this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(path));
|
||||||
|
data = new Blob([data], {type: type || 'text/plain'});
|
||||||
|
}
|
||||||
|
return this.file.writeFile(this.basePath, path, data, {replace: true}).then(() => {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getExternalFile(fullPath: string) : Promise<FileEntry> {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
removeExternalFile(fullPath: string) : Promise<any> {
|
||||||
|
// removeFile(fullPath, '') does not work, we need to pass two valid parameters.
|
||||||
|
let directory = fullPath.substring(0, fullPath.lastIndexOf('/') ),
|
||||||
|
filename = fullPath.substr(fullPath.lastIndexOf('/') + 1);
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getBasePath() : Promise<string> {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getBasePathToDownload() : Promise<string> {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getBasePathInstant() : string {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
moveFile(originalPath: string, newPath: string) : Promise<any> {
|
||||||
|
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.
|
||||||
|
// this.file.moveFile('a/', 'b/c.ext', 'a/', 'b/d.ext') doesn't work.
|
||||||
|
// cordovaFile.moveFile('a/b/', 'c.ext', 'a/b/', 'd.ext') works.
|
||||||
|
let commonPath = this.basePath,
|
||||||
|
dirsA = originalPath.split('/'),
|
||||||
|
dirsB = newPath.split('/');
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return this.file.moveFile(this.basePath, originalPath, this.basePath, newPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
copyFile(from: string, to: string) : Promise<any> {
|
||||||
|
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.
|
||||||
|
let fromDir = this.textUtils.concatenatePaths(this.basePath, fromFileAndDir.directory),
|
||||||
|
toDir = this.textUtils.concatenatePaths(this.basePath, toFileAndDir.directory);
|
||||||
|
|
||||||
|
return this.file.copyFile(fromDir, fromFileAndDir.name, toDir, toFileAndDir.name);
|
||||||
|
} else {
|
||||||
|
return this.file.copyFile(this.basePath, from, this.basePath, to);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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'
|
||||||
|
*/
|
||||||
|
getFileAndDirectoryFromPath(path: string) : any {
|
||||||
|
let file = {
|
||||||
|
directory: '',
|
||||||
|
name: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
file.directory = path.substring(0, path.lastIndexOf('/') );
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getInternalURL(fileEntry: FileEntry) : string {
|
||||||
|
if (!fileEntry.toInternalURL) {
|
||||||
|
// File doesn't implement toInternalURL, use toURL.
|
||||||
|
return fileEntry.toURL();
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
addBasePathIfNeeded(path: string) : string {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
removeBasePath(path: string) : string {
|
||||||
|
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).
|
||||||
|
* @return {Promise<any>} Promise resolved when the file is unzipped.
|
||||||
|
*/
|
||||||
|
unzipFile(path: string, destFolder?: string) : Promise<any> {
|
||||||
|
// 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));
|
||||||
|
return this.zip.unzip(fileEntry.toURL(), destFolder);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
replaceInFile(path: string, search: string|RegExp, newValue: string) : Promise<any> {
|
||||||
|
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);
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getMetadata(fileEntry: Entry) : Promise<any> {
|
||||||
|
if (!fileEntry || !fileEntry.getMetadata) {
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getMetadataFromPath(path: string, isDir?: boolean) : Promise<any> {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
removeStartingSlash(path: string) : string {
|
||||||
|
if (path[0] == '/') {
|
||||||
|
return path.substr(1);
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
protected copyOrMoveExternalFile(from: string, to: string, copy?: boolean) : Promise<any> {
|
||||||
|
// Get the file to copy/move.
|
||||||
|
return this.getExternalFile(from).then((fileEntry) => {
|
||||||
|
// Create the destination dir if it doesn't exist.
|
||||||
|
let dirAndFile = this.getFileAndDirectoryFromPath(to);
|
||||||
|
return this.createDir(dirAndFile.directory).then((dirEntry) => {
|
||||||
|
// Now copy/move the file.
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
copyExternalFile(from: string, to: string) : Promise<any> {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
moveExternalFile(from: string, to: string) : Promise<any> {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
getUniqueNameInFolder(dirPath: string, fileName: string, defaultExt: string) : Promise<string> {
|
||||||
|
// Get existing files in the folder.
|
||||||
|
return this.getDirectoryContents(dirPath).then((entries) => {
|
||||||
|
let files = {},
|
||||||
|
fileNameWithoutExtension = this.mimeUtils.removeExtension(fileName),
|
||||||
|
extension = this.mimeUtils.getFileExtension(fileName) || defaultExt,
|
||||||
|
newName,
|
||||||
|
number = 1;
|
||||||
|
|
||||||
|
// Clean the file name.
|
||||||
|
fileNameWithoutExtension = this.textUtils.removeSpecialCharactersForFiles(
|
||||||
|
this.textUtils.decodeURIComponent(fileNameWithoutExtension));
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
newName = fileNameWithoutExtension + '(' + number + ')' + extension;
|
||||||
|
number++;
|
||||||
|
} 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.
|
||||||
|
*/
|
||||||
|
clearTmpFolder() : Promise<any> {
|
||||||
|
return this.removeDir(this.TMPFOLDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
removeUnusedFiles(dirPath: string, files: any[]) : Promise<any> {
|
||||||
|
// Get the directory contents.
|
||||||
|
return this.getDirectoryContents(dirPath).then((contents) => {
|
||||||
|
if (!contents.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let filesMap = {},
|
||||||
|
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.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue