var CordovaAppLoader = /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { var CordovaFileCache = __webpack_require__(1); var CordovaPromiseFS = __webpack_require__(3); var Promise = null; var BUNDLE_ROOT = location.href.replace(location.hash,''); BUNDLE_ROOT = BUNDLE_ROOT.substr(0,BUNDLE_ROOT.lastIndexOf('/')+1); if(/ip(hone|ad|od)/i.test(navigator.userAgent)){ BUNDLE_ROOT = location.pathname.substr(location.pathname.indexOf('/www/')); BUNDLE_ROOT = BUNDLE_ROOT.substr(0,BUNDLE_ROOT.lastIndexOf('/')+1); BUNDLE_ROOT = 'cdvfile://localhost/bundle' + BUNDLE_ROOT; } function hash(files){ var keys = Object.keys(files); keys.sort(); var str = ''; keys.forEach(function(key){ if(files[key] && files[key].version); str += '@' + files[key].version; }); return CordovaFileCache.hash(str) + ''; } function AppLoader(options){ if(!options) throw new Error('CordovaAppLoader has no options!'); if(!options.fs) throw new Error('CordovaAppLoader has no "fs" option (cordova-promise-fs)'); if(!options.serverRoot) throw new Error('CordovaAppLoader has no "serverRoot" option.'); if(!window.pegasus || !window.Manifest) throw new Error('CordovaAppLoader bootstrap.js is missing.'); this.allowServerRootFromManifest = options.allowServerRootFromManifest === true; Promise = options.fs.Promise; // initialize variables this.manifest = window.Manifest; this.newManifest = null; this.bundledManifest = null; this.preventAutoUpdateLoop = options.preventAutoUpdateLoop === true; this._lastUpdateFiles = localStorage.getItem('last_update_files'); // only prevent autoupdateloop if last update was less than 1 minute ago var lastUpdateTime = 1.0 * localStorage.getItem('last_update_time'); if(Date.now() - this._lastUpdateTime > 60000){ this.preventAutoUpdateLoop = false; } // normalize serverRoot and set remote manifest url options.serverRoot = options.serverRoot || ''; if(!!options.serverRoot && options.serverRoot[options.serverRoot.length-1] !== '/') options.serverRoot += '/'; this.newManifestUrl = options.manifestUrl || options.serverRoot + (options.manifest || 'manifest.json'); // initialize a file cache if(options.mode) options.mode = 'mirror'; this.cache = new CordovaFileCache(options); // private stuff this.corruptNewManifest = false; this._toBeCopied = []; this._toBeDeleted = []; this._toBeDownloaded = []; this._updateReady = false; this._checkTimeout = options.checkTimeout || 10000; } AppLoader.prototype._createFilemap = function(files){ var result = {}; var normalize = this.cache._fs.normalize; Object.keys(files || []).forEach(function(key){ files[key].filename = normalize(files[key].filename); result[files[key].filename] = files[key]; }); return result; }; AppLoader.prototype.copyFromBundle = function(file){ var url = BUNDLE_ROOT + file; return this.cache._fs.download(url,this.cache.localRoot + file); }; AppLoader.prototype.getBundledManifest = function(){ var self = this; var bootstrapScript = document.querySelector('script[manifest]'); var bundledManifestUrl = (bootstrapScript? bootstrapScript.getAttribute('manifest'): null) || 'manifest.json'; return new Promise(function(resolve,reject){ if(self.bundledManifest) { resolve(self.bundledManifest); } else { pegasus(bundledManifestUrl).then(function(bundledManifest){ self.bundledManifest = bundledManifest; resolve(bundledManifest); },reject); setTimeout(function(){reject(new Error('bundled manifest timeout'));},self._checkTimeout); } }); }; AppLoader.prototype.check = function(newManifest){ var self = this, manifest = this.manifest; if(typeof newManifest === "string") { self.newManifestUrl = newManifest; newManifest = undefined; } var gotNewManifest = new Promise(function(resolve,reject){ if(typeof newManifest === "object") { resolve(newManifest); } else { var url = self.cache._cacheBuster? self.newManifestUrl + '?' + Date.now(): self.newManifestUrl; pegasus(url).then(resolve,reject); setTimeout(function(){reject(new Error('new manifest timeout'));},self._checkTimeout); } }); return new Promise(function(resolve,reject){ Promise.all([gotNewManifest,self.getBundledManifest(),self.cache.list()]) .then(function(values){ var newManifest = values[0]; var bundledManifest = values[1]; var newFiles = hash(newManifest.files); // Prevent end-less update loop, check if new manifest // has been downloaded before (but failes) // Check if the newFiles match the previous files (last_update_files) if(self.preventAutoUpdateLoop === true && newFiles === self._lastUpdateFiles) { // YES! So we're doing the same update again! // Check if our current Manifest has indeed the "last_update_files" var currentFiles = hash(Manifest.files); if(self._lastUpdateFiles !== currentFiles){ // No! So we've updated, yet they don't appear in our manifest. This means: console.warn('New manifest available, but an earlier update attempt failed. Will not download.'); self.corruptNewManifest = true; resolve(null); } // Yes, we've updated and we've succeeded. resolve(false); return; } // Check if new manifest is valid if(!newManifest.files){ reject(new Error('Downloaded Manifest has no "files" attribute.')); return; } // We're good to go check! Get all the files we need var cachedFiles = values[2]; // files in cache var oldFiles = self._createFilemap(manifest.files); // files in current manifest var newFiles = self._createFilemap(newManifest.files); // files in new manifest var bundledFiles = self._createFilemap(bundledManifest.files); // files in app bundle // Create COPY and DOWNLOAD lists self._toBeDownloaded = []; self._toBeCopied = []; self._toBeDeleted= []; var isCordova = self.cache._fs.isCordova; var changes = 0; Object.keys(newFiles) // Find files that have changed version or are missing .filter(function(file){ // if new file, or... return !oldFiles[file] || // version has changed, or... oldFiles[file].version !== newFiles[file].version || // not in cache for some reason !self.cache.isCached(file); }) // Add them to the correct list .forEach(function(file){ // bundled version matches new version, so we can copy! if(isCordova && bundledFiles[file] && bundledFiles[file].version === newFiles[file].version){ self._toBeCopied.push(file); // othwerwise, we must download } else { self._toBeDownloaded.push(file); } if(!bundledFiles[file] || bundledFiles[file].version !== newFiles[file].version){ changes++; } }); // Delete files self._toBeDeleted = cachedFiles .map(function(file){ return file.substr(self.cache.localRoot.length); }) .filter(function(file){ // Everything that is not in new manifest, or.... return !newFiles[file] || // Files that will be downloaded, or... self._toBeDownloaded.indexOf(file) >= 0 || // Files that will be copied self._toBeCopied.indexOf(file) >= 0; }); changes += self._toBeDeleted.length; // Note: if we only need to copy files, we can keep serving from bundle! // So no update is needed! if(changes > 0){ // Save the new Manifest self.newManifest = newManifest; self.newManifest.root = self.cache.localUrl; resolve(true); } else { resolve(false); } }, function(err){ reject(err); }); // end of .then }); // end of new Promise }; AppLoader.prototype.canDownload = function(){ return !!this.newManifest && !this._updateReady; }; AppLoader.prototype.canUpdate = function(){ return this._updateReady; }; AppLoader.prototype.download = function(onprogress,includeFileProgressEvents){ var self = this; if(!self.canDownload()) { return new Promise(function(resolve){ resolve(null); }); } // we will delete files, which will invalidate the current manifest... localStorage.removeItem('manifest'); this.manifest.files = Manifest.files = {}; return self.cache.remove(self._toBeDeleted,true) .then(function(){ return Promise.all(self._toBeCopied.map(function(file){ return self.cache._fs.download(BUNDLE_ROOT + file,self.cache.localRoot + file); })); }) .then(function(){ if(self.allowServerRootFromManifest && self.newManifest.serverRoot){ self.cache.serverRoot = self.newManifest.serverRoot; } self.cache.add(self._toBeDownloaded); return self.cache.download(onprogress,includeFileProgressEvents); }).then(function(){ self._toBeDeleted = []; self._toBeDownloaded = []; self._updateReady = true; return self.newManifest; }); }; AppLoader.prototype.update = function(reload){ if(this._updateReady) { // update manifest localStorage.setItem('manifest',JSON.stringify(this.newManifest)); // only attempt this once - set 'last_update_files' localStorage.setItem('last_update_files',hash(this.newManifest.files)); localStorage.setItem('last_update_time',Date.now()); if(reload !== false) location.reload(); return true; } return false; }; AppLoader.prototype.clear = function(){ localStorage.removeItem('last_update_files'); localStorage.removeItem('manifest'); return this.cache.clear(); }; AppLoader.prototype.reset = function(){ return this.clear().then(function(){ location.reload(); },function(){ location.reload(); }); }; module.exports = AppLoader; /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { var hash = __webpack_require__(2); var Promise = null; /* Cordova File Cache x */ function FileCache(options){ var self = this; // cordova-promise-fs this._fs = options.fs; if(!this._fs) { throw new Error('Missing required option "fs". Add an instance of cordova-promise-fs.'); } // Use Promises from fs. Promise = this._fs.Promise; // 'mirror' mirrors files structure from "serverRoot" to "localRoot" // 'hash' creates a 1-deep filestructure, where the filenames are hashed server urls (with extension) this._mirrorMode = options.mode !== 'hash'; this._retry = options.retry || [500,1500,8000]; this._cacheBuster = !!options.cacheBuster; // normalize path this.localRoot = this._fs.normalize(options.localRoot || 'data'); this.serverRoot = this._fs.normalize(options.serverRoot || ''); // set internal variables this._downloading = []; // download promises this._added = []; // added files this._cached = {}; // cached files // list existing cache contents this.ready = this._fs.ensure(this.localRoot) .then(function(entry){ self.localInternalURL = entry.toInternalURL? entry.toInternalURL(): entry.toURL(); self.localUrl = entry.toURL(); return self.list(); }); } FileCache.hash = hash; /** * Helper to cache all 'internalURL' and 'URL' for quick synchronous access * to the cached files. */ FileCache.prototype.list = function list(){ var self = this; return new Promise(function(resolve,reject){ self._fs.list(self.localRoot,'rfe').then(function(entries){ self._cached = {}; entries = entries.map(function(entry){ var fullPath = self._fs.normalize(entry.fullPath); self._cached[fullPath] = { toInternalURL: entry.toInternalURL? entry.toInternalURL(): entry.toURL(), toURL: entry.toURL(), }; return fullPath; }); resolve(entries); },function(){ resolve([]); }); }); }; FileCache.prototype.add = function add(urls){ if(!urls) urls = []; if(typeof urls === 'string') urls = [urls]; var self = this; urls.forEach(function(url){ url = self.toServerURL(url); if(self._added.indexOf(url) === -1) { self._added.push(url); } }); return self.isDirty(); }; FileCache.prototype.remove = function remove(urls,returnPromises){ if(!urls) urls = []; var promises = []; if(typeof urls === 'string') urls = [urls]; var self = this; urls.forEach(function(url){ var index = self._added.indexOf(self.toServerURL(url)); if(index >= 0) self._added.splice(index,1); var path = self.toPath(url); promises.push(self._fs.remove(path)); delete self._cached[path]; }); return returnPromises? Promise.all(promises): self.isDirty(); }; FileCache.prototype.getDownloadQueue = function(){ var self = this; var queue = self._added.filter(function(url){ return !self.isCached(url); }); return queue; }; FileCache.prototype.getAdded = function() { return this._added; }; FileCache.prototype.isDirty = function isDirty(){ return this.getDownloadQueue().length > 0; }; FileCache.prototype.download = function download(onprogress,includeFileProgressEvents){ var fs = this._fs; var self = this; includeFileProgressEvents = includeFileProgressEvents || false; self.abort(); return new Promise(function(resolve,reject){ // make sure cache directory exists and that // we have retrieved the latest cache contents // to avoid downloading files we already have! fs.ensure(self.localRoot).then(function(){ return self.list(); }).then(function(){ // no dowloads needed, resolve if(!self.isDirty()) { resolve(self); return; } // keep track of number of downloads! var queue = self.getDownloadQueue(); var done = self._downloading.length; var total = self._downloading.length + queue.length; var percentage = 0; var errors = []; // download every file in the queue (which is the diff from _added with _cached) queue.forEach(function(url){ var path = self.toPath(url); // augment progress event with done/total stats var onSingleDownloadProgress; if(typeof onprogress === 'function') { onSingleDownloadProgress = function(ev){ ev.queueIndex = done; ev.queueSize = total; ev.url = url; ev.path = path; ev.percentage = done / total; if(ev.loaded > 0 && ev.total > 0 && done !== total){ ev.percentage += (ev.loaded / ev.total) / total; } ev.percentage = Math.max(percentage,ev.percentage); percentage = ev.percentage; onprogress(ev); }; } // callback var onDone = function(){ done++; onSingleDownloadProgress(new ProgressEvent()); // when we're done if(done === total) { // reset downloads self._downloading = []; // check if we got everything self.list().then(function(){ // final progress event! if(onSingleDownloadProgress) onSingleDownloadProgress(new ProgressEvent()); // Yes, we're not dirty anymore! if(!self.isDirty()) { resolve(self); // Aye, some files got left behind! } else { reject(errors); } },reject); } }; var onErr = function(err){ if(err && err.target && err.target.error) err = err.target.error; errors.push(err); onDone(); }; var downloadUrl = url; if(self._cacheBuster) downloadUrl += "?"+Date.now(); var download = fs.download(downloadUrl,path,{retry:self._retry},includeFileProgressEvents? onSingleDownloadProgress: undefined); download.then(onDone,onErr); self._downloading.push(download); }); },reject); }); }; FileCache.prototype.abort = function abort(){ this._downloading.forEach(function(download){ download.abort(); }); this._downloading = []; }; FileCache.prototype.isCached = function isCached(url){ url = this.toPath(url); return !!this._cached[url]; }; FileCache.prototype.clear = function clear(){ var self = this; this._cached = {}; return this._fs.removeDir(this.localRoot).then(function(){ return self._fs.ensure(self.localRoot); }); }; /** * Helpers to output to various formats */ FileCache.prototype.toInternalURL = function toInternalURL(url){ var path = this.toPath(url); if(this._cached[path]) return this._cached[path].toInternalURL; return url; }; FileCache.prototype.get = function get(url){ var path = this.toPath(url); if(this._cached[path]) return this._cached[path].toURL; return this.toServerURL(url); }; FileCache.prototype.toDataURL = function toDataURL(url){ return this._fs.toDataURL(this.toPath(url)); }; FileCache.prototype.toURL = function toURL(url){ var path = this.toPath(url); return this._cached[path]? this._cached[path].toURL: url; }; FileCache.prototype.toServerURL = function toServerURL(path){ var path = this._fs.normalize(path); return path.indexOf('://') < 0? this.serverRoot + path: path; }; /** * Helper to transform remote URL to a local path (for cordova-promise-fs) */ FileCache.prototype.toPath = function toPath(url){ if(this._mirrorMode) { var query = url.indexOf('?'); if(query > -1){ url = url.substr(0,query); } url = this._fs.normalize(url || ''); var len = this.serverRoot.length; if(url.substr(0,len) !== this.serverRoot) { return this.localRoot + url; } else { return this.localRoot + url.substr(len); } } else { var ext = url.substr(url.lastIndexOf('.')); if ((ext.indexOf("?") > 0) || (ext.indexOf("/") > 0)) { ext = ".txt"; } return this.localRoot + hash(url) + ext; } }; module.exports = FileCache; /***/ }, /* 2 */ /***/ function(module, exports) { /** * JS Implementation of MurmurHash3 (r136) (as of May 20, 2011) * * @author Gary Court * @see http://github.com/garycourt/murmurhash-js * @author Austin Appleby * @see http://sites.google.com/site/murmurhash/ * * @param {string} key ASCII only * @param {number} seed Positive integer only * @return {number} 32-bit positive integer hash */ function murmurhash3_32_gc(key, seed) { var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i; remainder = key.length & 3; // key.length % 4 bytes = key.length - remainder; h1 = seed; c1 = 0xcc9e2d51; c2 = 0x1b873593; i = 0; while (i < bytes) { k1 = ((key.charCodeAt(i) & 0xff)) | ((key.charCodeAt(++i) & 0xff) << 8) | ((key.charCodeAt(++i) & 0xff) << 16) | ((key.charCodeAt(++i) & 0xff) << 24); ++i; k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; k1 = (k1 << 15) | (k1 >>> 17); k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; h1 ^= k1; h1 = (h1 << 13) | (h1 >>> 19); h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); } k1 = 0; switch (remainder) { case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; case 1: k1 ^= (key.charCodeAt(i) & 0xff); k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; k1 = (k1 << 15) | (k1 >>> 17); k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; h1 ^= k1; } h1 ^= key.length; h1 ^= h1 >>> 16; h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; h1 ^= h1 >>> 13; h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; h1 ^= h1 >>> 16; return h1 >>> 0; } module.exports = murmurhash3_32_gc; /***/ }, /* 3 */ /***/ function(module, exports) { /** * Static Private functions */ /* createDir, recursively */ function __createDir(rootDirEntry, folders, success,error) { rootDirEntry.getDirectory(folders[0], {create: true}, function(dirEntry) { // Recursively add the new subfolder (if we still have another to create). if (folders.length > 1) { __createDir(dirEntry, folders.slice(1),success,error); } else { success(dirEntry); } }, error); } function dirname(str) { str = str.substr(0,str.lastIndexOf('/')+1); if(str[0] === '/') str = str.substr(1); return str; } function filename(str) { return str.substr(str.lastIndexOf('/')+1); } function normalize(str){ str = str || ''; if(str[0] === '/') str = str.substr(1); var tokens = str.split('/'), last = tokens[0]; // check tokens for instances of .. and . for(var i=1;i < tokens.length;i++) { last = tokens[i]; if (tokens[i] === '..') { // remove the .. and the previous token tokens.splice(i-1,2); // rewind 'cursor' 2 tokens i = i - 2; } else if (tokens[i] === '.') { // remove the .. and the previous token tokens.splice(i,1); // rewind 'cursor' 1 token i--; } } str = tokens.join('/'); if(str === './') { str = ''; } else if(last && last.indexOf('.') < 0 && str[str.length - 1] != '/'){ str += '/'; } return str; } var transferQueue = [], // queued fileTransfers inprogress = 0; // currently active filetransfers /** * Factory function: Create a single instance (based on single FileSystem) */ module.exports = function(options){ /* Promise implementation */ var Promise = options.Promise || window.Promise; if(!Promise) { throw new Error("No Promise library given in options.Promise"); } /* default options */ options = options || {}; options.persistent = options.persistent !== undefined? options.persistent: true; options.storageSize = options.storageSize || 20*1024*1024; options.concurrency = options.concurrency || 3; options.retry = options.retry || []; options.debug = !!options.debug; /* Cordova deviceready promise */ var deviceready, isCordova = typeof cordova !== 'undefined'; if(isCordova){ deviceready = new Promise(function(resolve,reject){ document.addEventListener("deviceready", resolve, false); setTimeout(function(){ reject(new Error('deviceready has not fired after 5 seconds.')); },5100); }); } else { /* FileTransfer implementation for Chrome */ deviceready = ResolvedPromise(true); if(typeof webkitRequestFileSystem !== 'undefined'){ window.requestFileSystem = webkitRequestFileSystem; window.FileTransfer = function FileTransfer(){}; FileTransfer.prototype.download = function download(url,file,win,fail) { var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.responseType = "blob"; xhr.onreadystatechange = function(onSuccess, onError, cb) { if (xhr.readyState == 4) { if(xhr.status === 200 && !this._aborted){ write(file,xhr.response).then(win,fail); } else { fail(xhr.status); } } }; xhr.send(); return xhr; }; FileTransfer.prototype.abort = function(){ this._aborted = true; }; window.ProgressEvent = function ProgressEvent(){}; window.FileEntry = function FileEntry(){}; } else { window.requestFileSystem = function(x,y,z,fail){ fail(new Error('requestFileSystem not supported!')); }; } } /* Promise resolve helper */ function ResolvedPromise(value){ return new Promise(function(resolve){ return resolve(value); }); } /* the filesystem! */ var fs = new Promise(function(resolve,reject){ deviceready.then(function(){ var type = options.persistent? 1: 0; if(options.fileSystem && isCordova){ type = options.fileSystem; } // On chrome, request quota to store persistent files if (!isCordova && type === 1 && navigator.webkitPersistentStorage) { navigator.webkitPersistentStorage.requestQuota(options.storageSize, function(grantedBytes) { window.requestFileSystem(type, grantedBytes, resolve, reject); }, reject); } else { // Exotic Cordova Directories (options.fileSystem = string) if(isNaN(type)) { window.resolveLocalFileSystemURL(type,function(directory){ resolve(directory.filesystem); },reject); // Normal browser usage } else { window.requestFileSystem(type, options.storageSize, resolve, reject); } } setTimeout(function(){ reject(new Error('Could not retrieve FileSystem after 5 seconds.')); },5100); },reject); }); /* debug */ fs.then(function(fs){ CDV_URL_ROOT = fs.root.toURL(); CDV_INTERNAL_URL_ROOT = isCordova? fs.root.toInternalURL(): CDV_URL_ROOT; window.__fs = fs; },function(err){ console.error('Could not get Cordova FileSystem:',err); }); /* ensure directory exists */ function ensure(folders) { return new Promise(function(resolve,reject){ return fs.then(function(fs){ if(!folders) { resolve(fs.root); } else { folders = folders.split('/').filter(function(folder) { return folder && folder.length > 0 && folder !== '.' && folder !== '..'; }); __createDir(fs.root,folders,resolve,reject); } },reject); }); } /* get file file */ function file(path,options){ return new Promise(function(resolve,reject){ if(typeof path === 'object') { return resolve(path); } path = normalize(path); options = options || {}; return fs.then(function(fs){ fs.root.getFile(path,options,resolve,reject); },reject); }); } /* get directory entry */ function dir(path,options){ path = normalize(path); options = options || {}; return new Promise(function(resolve,reject){ return fs.then(function(fs){ if(!path || path === '/') { resolve(fs.root); } else { fs.root.getDirectory(path,options,resolve,reject); } },reject); }); } /* list contents of a directory */ function list(path,mode) { mode = mode || ''; var recursive = mode.indexOf('r') > -1; var getAsEntries = mode.indexOf('e') > -1; var onlyFiles = mode.indexOf('f') > -1; var onlyDirs = mode.indexOf('d') > -1; if(onlyFiles && onlyDirs) { onlyFiles = false; onlyDirs = false; } return new Promise(function(resolve,reject){ return dir(path).then(function(dirEntry){ var dirReader = dirEntry.createReader(); dirReader.readEntries(function(entries) { var promises = [ResolvedPromise(entries)]; if(recursive) { entries .filter(function(entry){return entry.isDirectory; }) .forEach(function(entry){ promises.push(list(entry.fullPath,'re')); }); } Promise.all(promises).then(function(values){ var entries = []; entries = entries.concat.apply(entries,values); if(onlyFiles) entries = entries.filter(function(entry) { return entry.isFile; }); if(onlyDirs) entries = entries.filter(function(entry) { return entry.isDirectory; }); if(!getAsEntries) entries = entries.map(function(entry) { return entry.fullPath; }); resolve(entries); },reject); }, reject); },reject); }); } /* does file exist? If so, resolve with fileEntry, if not, resolve with false. */ function exists(path){ return new Promise(function(resolve,reject){ file(path).then( function(fileEntry){ resolve(fileEntry); }, function(err){ if(err.code === 1) { resolve(false); } else { reject(err); } } ); }); } /* does dir exist? If so, resolve with fileEntry, if not, resolve with false. */ function existsDir(path){ return new Promise(function(resolve,reject){ dir(path).then( function(dirEntry){ resolve(dirEntry); }, function(err){ if(err.code === 1) { resolve(false); } else { reject(err); } } ); }); } function create(path){ return ensure(dirname(path)).then(function(){ return file(path,{create:true}); }); } /* convert path to URL to be used in JS/CSS/HTML */ function toURL(path) { return file(path).then(function(fileEntry) { return fileEntry.toURL(); }); } /* convert path to URL to be used in JS/CSS/HTML */ var toInternalURL,toInternalURLSync,toURLSync; CDV_INTERNAL_URL_ROOT = 'cdvfile://localhost/'+(options.persistent? 'persistent/':'temporary/'); CDV_URL_ROOT = ''; if(isCordova) { /* synchronous helper to get internal URL. */ toInternalURLSync = function(path){ path = normalize(path); return path.indexOf('://') < 0? CDV_INTERNAL_URL_ROOT + path: path; }; /* synchronous helper to get native URL. */ toURLSync = function(path){ path = normalize(path); return path.indexOf('://') < 0? CDV_URL_ROOT + path: path; }; toInternalURL = function(path) { return file(path).then(function(fileEntry) { return fileEntry.toInternalURL(); }); }; } else { /* synchronous helper to get internal URL. */ toInternalURLSync = function(path){ path = normalize(path); return 'filesystem:'+location.origin+(options.persistent? '/persistent/':'/temporary/') + path; }; toInternalURL = function(path) { return file(path).then(function(fileEntry) { return fileEntry.toURL(); }); }; toURLSync = toInternalURLSync; } /* return contents of a file */ function read(path,method) { method = method || 'readAsText'; return file(path).then(function(fileEntry) { return new Promise(function(resolve,reject){ fileEntry.file(function(file){ var reader = new FileReader(); reader.onloadend = function(){ resolve(this.result); }; reader[method](file); },reject); }); }); } /* convert path to base64 date URI */ function toDataURL(path) { return read(path,'readAsDataURL'); } function readJSON(path){ return read(path).then(JSON.parse); } /* write contents to a file */ function write(path,blob,mimeType) { return ensure(dirname(path)) .then(function() { return file(path,{create:true}); }) .then(function(fileEntry) { return new Promise(function(resolve,reject){ fileEntry.createWriter(function(writer){ writer.onwriteend = resolve; writer.onerror = reject; if(typeof blob === 'string') { blob = createBlob([blob], mimeType || 'text/plain'); } else if(blob instanceof Blob !== true){ blob = createBlob([JSON.stringify(blob,null,4)], mimeType || 'application/json'); } writer.write(blob); },reject); }); }); } function createBlob(parts, type) { var BlobBuilder, bb; try { return new Blob(parts, {type: type}); } catch(e) { BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; if(BlobBuilder) { bb = new BlobBuilder(); bb.append(parts); return bb.getBlob(type); } else { throw new Error("Unable to create blob"); } } } /* move a file */ function move(src,dest) { return ensure(dirname(dest)) .then(function(dir) { return file(src).then(function(fileEntry){ return new Promise(function(resolve,reject){ fileEntry.moveTo(dir,filename(dest),resolve,reject); }); }); }); } /* move a dir */ function moveDir(src,dest) { src = src.replace(/\/$/, ''); dest = dest.replace(/\/$/, ''); return ensure(dirname(dest)) .then(function(destDir) { return dir(src).then(function(dirEntry){ return new Promise(function(resolve,reject){ dirEntry.moveTo(destDir,filename(dest),resolve,reject); }); }); }); } /* copy a file */ function copy(src,dest) { return ensure(dirname(dest)) .then(function(dir) { return file(src).then(function(fileEntry){ return new Promise(function(resolve,reject){ fileEntry.copyTo(dir,filename(dest),resolve,reject); }); }); }); } /* delete a file */ function remove(path,mustExist) { var method = mustExist? file:exists; return new Promise(function(resolve,reject){ method(path).then(function(fileEntry){ if(fileEntry !== false) { fileEntry.remove(resolve,reject); } else { resolve(1); } },reject); }).then(function(val){ return val === 1? false: true; }); } /* delete a directory */ function removeDir(path) { return dir(path).then(function(dirEntry){ return new Promise(function(resolve,reject) { dirEntry.removeRecursively(resolve,reject); }); }); } // Whenever we want to start a transfer, we call popTransferQueue function popTransferQueue(){ // while we are not at max concurrency while(transferQueue.length > 0 && inprogress < options.concurrency){ // increment activity counter inprogress++; // fetch filetranfer, method-type (isDownload) and arguments var args = transferQueue.pop(); var ft = args.fileTransfer, isDownload = args.isDownload, serverUrl = args.serverUrl, localPath = args.localPath, trustAllHosts = args.trustAllHosts, transferOptions = args.transferOptions, win = args.win, fail = args.fail; if(ft._aborted) { inprogress--; } else if(isDownload){ ft.download.call(ft,serverUrl,localPath,win,fail,trustAllHosts,transferOptions); if(ft.onprogress) ft.onprogress(new ProgressEvent()); } else { ft.upload.call(ft,localPath,serverUrl,win,fail,transferOptions,trustAllHosts); } } // if we are at max concurrency, popTransferQueue() will be called whenever // the transfer is ready and there is space avaialable. } // Promise callback to check if there are any more queued transfers function nextTransfer(result){ inprogress--; // decrement counter to free up one space to start transfers again! popTransferQueue(); // check if there are any queued transfers return result; } function filetransfer(isDownload,serverUrl,localPath,transferOptions,onprogress){ if(typeof transferOptions === 'function') { onprogress = transferOptions; transferOptions = {}; } if(isCordova && localPath.indexOf('://') < 0) localPath = toURLSync(localPath); transferOptions = transferOptions || {}; if(!transferOptions.retry || !transferOptions.retry.length) { transferOptions.retry = options.retry; } transferOptions.retry = transferOptions.retry.concat(); if(!transferOptions.file && !isDownload){ transferOptions.fileName = filename(localPath); } var ft = new FileTransfer(); onprogress = onprogress || transferOptions.onprogress; if(typeof onprogress === 'function') ft.onprogress = onprogress; var promise = new Promise(function(resolve,reject){ var attempt = function(err){ if(transferOptions.retry.length === 0) { if(options.debug) console.log('FileTransfer Error: '+serverUrl,err); reject(err); } else { var transferJob = { fileTransfer:ft, isDownload:isDownload, serverUrl:serverUrl, localPath:localPath, trustAllHosts:transferOptions.trustAllHosts || false, transferOptions:transferOptions, win:resolve, fail:attempt }; transferQueue.unshift(transferJob); var timeout = transferOptions.retry.shift(); if(timeout > 0) { setTimeout(nextTransfer,timeout); } else { nextTransfer(); } } }; transferOptions.retry.unshift(0); inprogress++; attempt(); }); promise.then(nextTransfer,nextTransfer); promise.progress = function(onprogress){ ft.onprogress = onprogress; return promise; }; promise.abort = function(){ ft._aborted = true; ft.abort(); return promise; }; return promise; } function download(url,dest,options,onprogress){ return filetransfer(true,url,dest,options,onprogress); } function upload(source,dest,options,onprogress){ return filetransfer(false,dest,source,options,onprogress); } return { fs: fs, normalize: normalize, file: file, filename: filename, dir: dir, dirname: dirname, create:create, read: read, readJSON: readJSON, write: write, move: move, moveDir: moveDir, copy: copy, remove: remove, removeDir: removeDir, list: list, ensure: ensure, exists: exists, existsDir: existsDir, download: download, upload: upload, toURL:toURL, isCordova:isCordova, toInternalURLSync: toInternalURLSync, toInternalURL:toInternalURL, toDataURL:toDataURL, deviceready: deviceready, options: options, Promise: Promise }; }; /***/ } /******/ ]);