Nenhuma descrição

FileProxy.js 43KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059
  1. /*
  2. *
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. *
  20. */
  21. (function () {
  22. /* global require, exports, module */
  23. /* global FILESYSTEM_PREFIX */
  24. /* global IDBKeyRange */
  25. /* global FileReader */
  26. /* global atob, btoa, Blob */
  27. /* Heavily based on https://github.com/ebidel/idb.filesystem.js */
  28. // For chrome we don't need to implement proxy methods
  29. // All functionality can be accessed natively.
  30. if (require('./isChrome')()) {
  31. var pathsPrefix = {
  32. // Read-only directory where the application is installed.
  33. applicationDirectory: location.origin + '/', // eslint-disable-line no-undef
  34. // Where to put app-specific data files.
  35. dataDirectory: 'filesystem:file:///persistent/',
  36. // Cached files that should survive app restarts.
  37. // Apps should not rely on the OS to delete files in here.
  38. cacheDirectory: 'filesystem:file:///temporary/'
  39. };
  40. exports.requestAllPaths = function (successCallback) {
  41. successCallback(pathsPrefix);
  42. };
  43. require('cordova/exec/proxy').add('File', module.exports);
  44. return;
  45. }
  46. var LocalFileSystem = require('./LocalFileSystem');
  47. var FileSystem = require('./FileSystem');
  48. var FileEntry = require('./FileEntry');
  49. var FileError = require('./FileError');
  50. var DirectoryEntry = require('./DirectoryEntry');
  51. var File = require('./File');
  52. (function (exports, global) {
  53. var indexedDB = global.indexedDB || global.mozIndexedDB;
  54. if (!indexedDB) {
  55. throw 'Firefox OS File plugin: indexedDB not supported';
  56. }
  57. var fs_ = null;
  58. var idb_ = {};
  59. idb_.db = null;
  60. var FILE_STORE_ = 'entries';
  61. var DIR_SEPARATOR = '/';
  62. var pathsPrefix = {
  63. // Read-only directory where the application is installed.
  64. applicationDirectory: location.origin + '/', // eslint-disable-line no-undef
  65. // Where to put app-specific data files.
  66. dataDirectory: 'file:///persistent/',
  67. // Cached files that should survive app restarts.
  68. // Apps should not rely on the OS to delete files in here.
  69. cacheDirectory: 'file:///temporary/'
  70. };
  71. var unicodeLastChar = 65535;
  72. /** * Exported functionality ***/
  73. exports.requestFileSystem = function (successCallback, errorCallback, args) {
  74. var type = args[0];
  75. // Size is ignored since IDB filesystem size depends
  76. // on browser implementation and can't be set up by user
  77. var size = args[1]; // eslint-disable-line no-unused-vars
  78. if (type !== LocalFileSystem.TEMPORARY && type !== LocalFileSystem.PERSISTENT) {
  79. if (errorCallback) {
  80. errorCallback(FileError.INVALID_MODIFICATION_ERR);
  81. }
  82. return;
  83. }
  84. var name = type === LocalFileSystem.TEMPORARY ? 'temporary' : 'persistent';
  85. var storageName = (location.protocol + location.host).replace(/:/g, '_'); // eslint-disable-line no-undef
  86. var root = new DirectoryEntry('', DIR_SEPARATOR);
  87. fs_ = new FileSystem(name, root);
  88. idb_.open(storageName, function () {
  89. successCallback(fs_);
  90. }, errorCallback);
  91. };
  92. // Overridden by Android, BlackBerry 10 and iOS to populate fsMap
  93. require('./fileSystems').getFs = function (name, callback) {
  94. callback(new FileSystem(name, fs_.root));
  95. };
  96. // list a directory's contents (files and folders).
  97. exports.readEntries = function (successCallback, errorCallback, args) {
  98. var fullPath = args[0];
  99. if (typeof successCallback !== 'function') {
  100. throw Error('Expected successCallback argument.');
  101. }
  102. var path = resolveToFullPath_(fullPath);
  103. exports.getDirectory(function () {
  104. idb_.getAllEntries(path.fullPath + DIR_SEPARATOR, path.storagePath, function (entries) {
  105. successCallback(entries);
  106. }, errorCallback);
  107. }, function () {
  108. if (errorCallback) {
  109. errorCallback(FileError.NOT_FOUND_ERR);
  110. }
  111. }, [path.storagePath, path.fullPath, {create: false}]);
  112. };
  113. exports.getFile = function (successCallback, errorCallback, args) {
  114. var fullPath = args[0];
  115. var path = args[1];
  116. var options = args[2] || {};
  117. // Create an absolute path if we were handed a relative one.
  118. path = resolveToFullPath_(fullPath, path);
  119. idb_.get(path.storagePath, function (fileEntry) {
  120. if (options.create === true && options.exclusive === true && fileEntry) {
  121. // If create and exclusive are both true, and the path already exists,
  122. // getFile must fail.
  123. if (errorCallback) {
  124. errorCallback(FileError.PATH_EXISTS_ERR);
  125. }
  126. } else if (options.create === true && !fileEntry) {
  127. // If create is true, the path doesn't exist, and no other error occurs,
  128. // getFile must create it as a zero-length file and return a corresponding
  129. // FileEntry.
  130. var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
  131. newFileEntry.file_ = new MyFile({
  132. size: 0,
  133. name: newFileEntry.name,
  134. lastModifiedDate: new Date(),
  135. storagePath: path.storagePath
  136. });
  137. idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
  138. } else if (options.create === true && fileEntry) {
  139. if (fileEntry.isFile) {
  140. // Overwrite file, delete then create new.
  141. idb_['delete'](path.storagePath, function () {
  142. var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
  143. newFileEntry.file_ = new MyFile({
  144. size: 0,
  145. name: newFileEntry.name,
  146. lastModifiedDate: new Date(),
  147. storagePath: path.storagePath
  148. });
  149. idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
  150. }, errorCallback);
  151. } else {
  152. if (errorCallback) {
  153. errorCallback(FileError.INVALID_MODIFICATION_ERR);
  154. }
  155. }
  156. } else if ((!options.create || options.create === false) && !fileEntry) {
  157. // If create is not true and the path doesn't exist, getFile must fail.
  158. if (errorCallback) {
  159. errorCallback(FileError.NOT_FOUND_ERR);
  160. }
  161. } else if ((!options.create || options.create === false) && fileEntry &&
  162. fileEntry.isDirectory) {
  163. // If create is not true and the path exists, but is a directory, getFile
  164. // must fail.
  165. if (errorCallback) {
  166. errorCallback(FileError.TYPE_MISMATCH_ERR);
  167. }
  168. } else {
  169. // Otherwise, if no other error occurs, getFile must return a FileEntry
  170. // corresponding to path.
  171. successCallback(fileEntryFromIdbEntry(fileEntry));
  172. }
  173. }, errorCallback);
  174. };
  175. exports.getFileMetadata = function (successCallback, errorCallback, args) {
  176. var fullPath = args[0];
  177. exports.getFile(function (fileEntry) {
  178. successCallback(new File(fileEntry.file_.name, fileEntry.fullPath, '', fileEntry.file_.lastModifiedDate,
  179. fileEntry.file_.size));
  180. }, errorCallback, [fullPath, null]);
  181. };
  182. exports.getMetadata = function (successCallback, errorCallback, args) {
  183. exports.getFile(function (fileEntry) {
  184. successCallback(
  185. {
  186. modificationTime: fileEntry.file_.lastModifiedDate,
  187. size: fileEntry.file_.lastModifiedDate
  188. });
  189. }, errorCallback, args);
  190. };
  191. exports.setMetadata = function (successCallback, errorCallback, args) {
  192. var fullPath = args[0];
  193. var metadataObject = args[1];
  194. exports.getFile(function (fileEntry) {
  195. fileEntry.file_.lastModifiedDate = metadataObject.modificationTime;
  196. idb_.put(fileEntry, fileEntry.file_.storagePath, successCallback, errorCallback);
  197. }, errorCallback, [fullPath, null]);
  198. };
  199. exports.write = function (successCallback, errorCallback, args) {
  200. var fileName = args[0];
  201. var data = args[1];
  202. var position = args[2];
  203. var isBinary = args[3]; // eslint-disable-line no-unused-vars
  204. if (!data) {
  205. if (errorCallback) {
  206. errorCallback(FileError.INVALID_MODIFICATION_ERR);
  207. }
  208. return;
  209. }
  210. if (typeof data === 'string' || data instanceof String) {
  211. data = new Blob([data]); // eslint-disable-line no-undef
  212. }
  213. exports.getFile(function (fileEntry) {
  214. var blob_ = fileEntry.file_.blob_;
  215. if (!blob_) {
  216. blob_ = new Blob([data], {type: data.type}); // eslint-disable-line no-undef
  217. } else {
  218. // Calc the head and tail fragments
  219. var head = blob_.slice(0, position);
  220. var tail = blob_.slice(position + (data.size || data.byteLength));
  221. // Calc the padding
  222. var padding = position - head.size;
  223. if (padding < 0) {
  224. padding = 0;
  225. }
  226. // Do the "write". In fact, a full overwrite of the Blob.
  227. blob_ = new Blob([head, new Uint8Array(padding), data, tail], // eslint-disable-line no-undef
  228. {type: data.type});
  229. }
  230. // Set the blob we're writing on this file entry so we can recall it later.
  231. fileEntry.file_.blob_ = blob_;
  232. fileEntry.file_.lastModifiedDate = new Date() || null;
  233. fileEntry.file_.size = blob_.size;
  234. fileEntry.file_.name = blob_.name;
  235. fileEntry.file_.type = blob_.type;
  236. idb_.put(fileEntry, fileEntry.file_.storagePath, function () {
  237. successCallback(data.size || data.byteLength);
  238. }, errorCallback);
  239. }, errorCallback, [fileName, null]);
  240. };
  241. exports.readAsText = function (successCallback, errorCallback, args) {
  242. var fileName = args[0];
  243. var enc = args[1];
  244. var startPos = args[2];
  245. var endPos = args[3];
  246. readAs('text', fileName, enc, startPos, endPos, successCallback, errorCallback);
  247. };
  248. exports.readAsDataURL = function (successCallback, errorCallback, args) {
  249. var fileName = args[0];
  250. var startPos = args[1];
  251. var endPos = args[2];
  252. readAs('dataURL', fileName, null, startPos, endPos, successCallback, errorCallback);
  253. };
  254. exports.readAsBinaryString = function (successCallback, errorCallback, args) {
  255. var fileName = args[0];
  256. var startPos = args[1];
  257. var endPos = args[2];
  258. readAs('binaryString', fileName, null, startPos, endPos, successCallback, errorCallback);
  259. };
  260. exports.readAsArrayBuffer = function (successCallback, errorCallback, args) {
  261. var fileName = args[0];
  262. var startPos = args[1];
  263. var endPos = args[2];
  264. readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback);
  265. };
  266. exports.removeRecursively = exports.remove = function (successCallback, errorCallback, args) {
  267. if (typeof successCallback !== 'function') {
  268. throw Error('Expected successCallback argument.');
  269. }
  270. var fullPath = resolveToFullPath_(args[0]).storagePath;
  271. if (fullPath === pathsPrefix.cacheDirectory || fullPath === pathsPrefix.dataDirectory) {
  272. errorCallback(FileError.NO_MODIFICATION_ALLOWED_ERR);
  273. return;
  274. }
  275. function deleteEntry (isDirectory) {
  276. // TODO: This doesn't protect against directories that have content in it.
  277. // Should throw an error instead if the dirEntry is not empty.
  278. idb_['delete'](fullPath, function () {
  279. successCallback();
  280. }, function () {
  281. if (errorCallback) { errorCallback(); }
  282. }, isDirectory);
  283. }
  284. // We need to to understand what we are deleting:
  285. exports.getDirectory(function (entry) {
  286. deleteEntry(entry.isDirectory);
  287. }, function () {
  288. // DirectoryEntry was already deleted or entry is FileEntry
  289. deleteEntry(false);
  290. }, [fullPath, null, {create: false}]);
  291. };
  292. exports.getDirectory = function (successCallback, errorCallback, args) {
  293. var fullPath = args[0];
  294. var path = args[1];
  295. var options = args[2];
  296. // Create an absolute path if we were handed a relative one.
  297. path = resolveToFullPath_(fullPath, path);
  298. idb_.get(path.storagePath, function (folderEntry) {
  299. if (!options) {
  300. options = {};
  301. }
  302. if (options.create === true && options.exclusive === true && folderEntry) {
  303. // If create and exclusive are both true, and the path already exists,
  304. // getDirectory must fail.
  305. if (errorCallback) {
  306. errorCallback(FileError.PATH_EXISTS_ERR);
  307. }
  308. // There is a strange bug in mobilespec + FF, which results in coming to multiple else-if's
  309. // so we are shielding from it with returns.
  310. return;
  311. }
  312. if (options.create === true && !folderEntry) {
  313. // If create is true, the path doesn't exist, and no other error occurs,
  314. // getDirectory must create it as a zero-length file and return a corresponding
  315. // MyDirectoryEntry.
  316. var dirEntry = new DirectoryEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
  317. idb_.put(dirEntry, path.storagePath, successCallback, errorCallback);
  318. return;
  319. }
  320. if (options.create === true && folderEntry) {
  321. if (folderEntry.isDirectory) {
  322. // IDB won't save methods, so we need re-create the MyDirectoryEntry.
  323. successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem));
  324. } else {
  325. if (errorCallback) {
  326. errorCallback(FileError.INVALID_MODIFICATION_ERR);
  327. }
  328. }
  329. return;
  330. }
  331. if ((!options.create || options.create === false) && !folderEntry) {
  332. // Handle root special. It should always exist.
  333. if (path.fullPath === DIR_SEPARATOR) {
  334. successCallback(fs_.root);
  335. return;
  336. }
  337. // If create is not true and the path doesn't exist, getDirectory must fail.
  338. if (errorCallback) {
  339. errorCallback(FileError.NOT_FOUND_ERR);
  340. }
  341. return;
  342. }
  343. if ((!options.create || options.create === false) && folderEntry && folderEntry.isFile) {
  344. // If create is not true and the path exists, but is a file, getDirectory
  345. // must fail.
  346. if (errorCallback) {
  347. errorCallback(FileError.TYPE_MISMATCH_ERR);
  348. }
  349. return;
  350. }
  351. // Otherwise, if no other error occurs, getDirectory must return a
  352. // MyDirectoryEntry corresponding to path.
  353. // IDB won't' save methods, so we need re-create MyDirectoryEntry.
  354. successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem));
  355. }, errorCallback);
  356. };
  357. exports.getParent = function (successCallback, errorCallback, args) {
  358. if (typeof successCallback !== 'function') {
  359. throw Error('Expected successCallback argument.');
  360. }
  361. var fullPath = args[0];
  362. // fullPath is like this:
  363. // file:///persistent/path/to/file or
  364. // file:///persistent/path/to/directory/
  365. if (fullPath === DIR_SEPARATOR || fullPath === pathsPrefix.cacheDirectory ||
  366. fullPath === pathsPrefix.dataDirectory) {
  367. successCallback(fs_.root);
  368. return;
  369. }
  370. // To delete all slashes at the end
  371. while (fullPath[fullPath.length - 1] === '/') {
  372. fullPath = fullPath.substr(0, fullPath.length - 1);
  373. }
  374. var pathArr = fullPath.split(DIR_SEPARATOR);
  375. pathArr.pop();
  376. var parentName = pathArr.pop();
  377. var path = pathArr.join(DIR_SEPARATOR) + DIR_SEPARATOR;
  378. // To get parent of root files
  379. var joined = path + parentName + DIR_SEPARATOR;// is like this: file:///persistent/
  380. if (joined === pathsPrefix.cacheDirectory || joined === pathsPrefix.dataDirectory) {
  381. exports.getDirectory(successCallback, errorCallback, [joined, DIR_SEPARATOR, {create: false}]);
  382. return;
  383. }
  384. exports.getDirectory(successCallback, errorCallback, [path, parentName, {create: false}]);
  385. };
  386. exports.copyTo = function (successCallback, errorCallback, args) {
  387. var srcPath = args[0];
  388. var parentFullPath = args[1];
  389. var name = args[2];
  390. if (name.indexOf('/') !== -1 || srcPath === parentFullPath + name) {
  391. if (errorCallback) {
  392. errorCallback(FileError.INVALID_MODIFICATION_ERR);
  393. }
  394. return;
  395. }
  396. // Read src file
  397. exports.getFile(function (srcFileEntry) {
  398. var path = resolveToFullPath_(parentFullPath);
  399. // Check directory
  400. exports.getDirectory(function () {
  401. // Create dest file
  402. exports.getFile(function (dstFileEntry) {
  403. exports.write(function () {
  404. successCallback(dstFileEntry);
  405. }, errorCallback, [dstFileEntry.file_.storagePath, srcFileEntry.file_.blob_, 0]);
  406. }, errorCallback, [parentFullPath, name, {create: true}]);
  407. }, function () { if (errorCallback) { errorCallback(FileError.NOT_FOUND_ERR); } },
  408. [path.storagePath, null, {create: false}]);
  409. }, errorCallback, [srcPath, null]);
  410. };
  411. exports.moveTo = function (successCallback, errorCallback, args) {
  412. var srcPath = args[0];
  413. // parentFullPath and name parameters is ignored because
  414. // args is being passed downstream to exports.copyTo method
  415. var parentFullPath = args[1]; // eslint-disable-line
  416. var name = args[2]; // eslint-disable-line
  417. exports.copyTo(function (fileEntry) {
  418. exports.remove(function () {
  419. successCallback(fileEntry);
  420. }, errorCallback, [srcPath]);
  421. }, errorCallback, args);
  422. };
  423. exports.resolveLocalFileSystemURI = function (successCallback, errorCallback, args) {
  424. var path = args[0];
  425. // Ignore parameters
  426. if (path.indexOf('?') !== -1) {
  427. path = String(path).split('?')[0];
  428. }
  429. // support for encodeURI
  430. if (/\%5/g.test(path) || /\%20/g.test(path)) { // eslint-disable-line no-useless-escape
  431. path = decodeURI(path);
  432. }
  433. if (path.trim()[0] === '/') {
  434. if (errorCallback) {
  435. errorCallback(FileError.ENCODING_ERR);
  436. }
  437. return;
  438. }
  439. // support for cdvfile
  440. if (path.trim().substr(0, 7) === 'cdvfile') {
  441. if (path.indexOf('cdvfile://localhost') === -1) {
  442. if (errorCallback) {
  443. errorCallback(FileError.ENCODING_ERR);
  444. }
  445. return;
  446. }
  447. var indexPersistent = path.indexOf('persistent');
  448. var indexTemporary = path.indexOf('temporary');
  449. // cdvfile://localhost/persistent/path/to/file
  450. if (indexPersistent !== -1) {
  451. path = 'file:///persistent' + path.substr(indexPersistent + 10);
  452. } else if (indexTemporary !== -1) {
  453. path = 'file:///temporary' + path.substr(indexTemporary + 9);
  454. } else {
  455. if (errorCallback) {
  456. errorCallback(FileError.ENCODING_ERR);
  457. }
  458. return;
  459. }
  460. }
  461. // to avoid path form of '///path/to/file'
  462. function handlePathSlashes (path) {
  463. var cutIndex = 0;
  464. for (var i = 0; i < path.length - 1; i++) {
  465. if (path[i] === DIR_SEPARATOR && path[i + 1] === DIR_SEPARATOR) {
  466. cutIndex = i + 1;
  467. } else break;
  468. }
  469. return path.substr(cutIndex);
  470. }
  471. // Handle localhost containing paths (see specs )
  472. if (path.indexOf('file://localhost/') === 0) {
  473. path = path.replace('file://localhost/', 'file:///');
  474. }
  475. if (path.indexOf(pathsPrefix.dataDirectory) === 0) {
  476. path = path.substring(pathsPrefix.dataDirectory.length - 1);
  477. path = handlePathSlashes(path);
  478. exports.requestFileSystem(function () {
  479. exports.getFile(successCallback, function () {
  480. exports.getDirectory(successCallback, errorCallback, [pathsPrefix.dataDirectory, path,
  481. {create: false}]);
  482. }, [pathsPrefix.dataDirectory, path, {create: false}]);
  483. }, errorCallback, [LocalFileSystem.PERSISTENT]);
  484. } else if (path.indexOf(pathsPrefix.cacheDirectory) === 0) {
  485. path = path.substring(pathsPrefix.cacheDirectory.length - 1);
  486. path = handlePathSlashes(path);
  487. exports.requestFileSystem(function () {
  488. exports.getFile(successCallback, function () {
  489. exports.getDirectory(successCallback, errorCallback, [pathsPrefix.cacheDirectory, path,
  490. {create: false}]);
  491. }, [pathsPrefix.cacheDirectory, path, {create: false}]);
  492. }, errorCallback, [LocalFileSystem.TEMPORARY]);
  493. } else if (path.indexOf(pathsPrefix.applicationDirectory) === 0) {
  494. path = path.substring(pathsPrefix.applicationDirectory.length);
  495. // TODO: need to cut out redundant slashes?
  496. var xhr = new XMLHttpRequest(); // eslint-disable-line no-undef
  497. xhr.open('GET', path, true);
  498. xhr.onreadystatechange = function () {
  499. if (xhr.status === 200 && xhr.readyState === 4) {
  500. exports.requestFileSystem(function (fs) {
  501. fs.name = location.hostname; // eslint-disable-line no-undef
  502. // TODO: need to call exports.getFile(...) to handle errors correct
  503. fs.root.getFile(path, {create: true}, writeFile, errorCallback);
  504. }, errorCallback, [LocalFileSystem.PERSISTENT]);
  505. }
  506. };
  507. xhr.onerror = function () {
  508. if (errorCallback) {
  509. errorCallback(FileError.NOT_READABLE_ERR);
  510. }
  511. };
  512. xhr.send();
  513. } else {
  514. if (errorCallback) {
  515. errorCallback(FileError.NOT_FOUND_ERR);
  516. }
  517. }
  518. function writeFile (entry) {
  519. entry.createWriter(function (fileWriter) {
  520. fileWriter.onwriteend = function (evt) {
  521. if (!evt.target.error) {
  522. entry.filesystemName = location.hostname; // eslint-disable-line no-undef
  523. successCallback(entry);
  524. }
  525. };
  526. fileWriter.onerror = function () {
  527. if (errorCallback) {
  528. errorCallback(FileError.NOT_READABLE_ERR);
  529. }
  530. };
  531. fileWriter.write(new Blob([xhr.response])); // eslint-disable-line no-undef
  532. }, errorCallback); // eslint-disable-line no-undef
  533. }
  534. };
  535. exports.requestAllPaths = function (successCallback) {
  536. successCallback(pathsPrefix);
  537. };
  538. /** * Helpers ***/
  539. /**
  540. * Interface to wrap the native File interface.
  541. *
  542. * This interface is necessary for creating zero-length (empty) files,
  543. * something the Filesystem API allows you to do. Unfortunately, File's
  544. * constructor cannot be called directly, making it impossible to instantiate
  545. * an empty File in JS.
  546. *
  547. * @param {Object} opts Initial values.
  548. * @constructor
  549. */
  550. function MyFile (opts) {
  551. var blob_ = new Blob(); // eslint-disable-line no-undef
  552. this.size = opts.size || 0;
  553. this.name = opts.name || '';
  554. this.type = opts.type || '';
  555. this.lastModifiedDate = opts.lastModifiedDate || null;
  556. this.storagePath = opts.storagePath || '';
  557. // Need some black magic to correct the object's size/name/type based on the
  558. // blob that is saved.
  559. Object.defineProperty(this, 'blob_', {
  560. enumerable: true,
  561. get: function () {
  562. return blob_;
  563. },
  564. set: function (val) {
  565. blob_ = val;
  566. this.size = blob_.size;
  567. this.name = blob_.name;
  568. this.type = blob_.type;
  569. this.lastModifiedDate = blob_.lastModifiedDate;
  570. }.bind(this)
  571. });
  572. }
  573. MyFile.prototype.constructor = MyFile;
  574. var MyFileHelper = {
  575. toJson: function (myFile, success) {
  576. /*
  577. Safari private browse mode cannot store Blob object to indexeddb.
  578. Then use pure json object instead of Blob object.
  579. */
  580. var fr = new FileReader();
  581. fr.onload = function (ev) {
  582. var base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(fr.result)));
  583. success({
  584. opt: {
  585. size: myFile.size,
  586. name: myFile.name,
  587. type: myFile.type,
  588. lastModifiedDate: myFile.lastModifiedDate,
  589. storagePath: myFile.storagePath
  590. },
  591. base64: base64
  592. });
  593. };
  594. fr.readAsArrayBuffer(myFile.blob_);
  595. },
  596. setBase64: function (myFile, base64) {
  597. if (base64) {
  598. var arrayBuffer = (new Uint8Array(
  599. [].map.call(atob(base64), function (c) { return c.charCodeAt(0); })
  600. )).buffer;
  601. myFile.blob_ = new Blob([arrayBuffer], { type: myFile.type });
  602. } else {
  603. myFile.blob_ = new Blob();
  604. }
  605. }
  606. };
  607. // When saving an entry, the fullPath should always lead with a slash and never
  608. // end with one (e.g. a directory). Also, resolve '.' and '..' to an absolute
  609. // one. This method ensures path is legit!
  610. function resolveToFullPath_ (cwdFullPath, path) {
  611. path = path || '';
  612. var fullPath = path;
  613. var prefix = '';
  614. cwdFullPath = cwdFullPath || DIR_SEPARATOR;
  615. if (cwdFullPath.indexOf(FILESYSTEM_PREFIX) === 0) {
  616. prefix = cwdFullPath.substring(0, cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
  617. cwdFullPath = cwdFullPath.substring(cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
  618. }
  619. var relativePath = path[0] !== DIR_SEPARATOR;
  620. if (relativePath) {
  621. fullPath = cwdFullPath;
  622. if (cwdFullPath !== DIR_SEPARATOR) {
  623. fullPath += DIR_SEPARATOR + path;
  624. } else {
  625. fullPath += path;
  626. }
  627. }
  628. // Remove doubled separator substrings
  629. var re = new RegExp(DIR_SEPARATOR + DIR_SEPARATOR, 'g');
  630. fullPath = fullPath.replace(re, DIR_SEPARATOR);
  631. // Adjust '..'s by removing parent directories when '..' flows in path.
  632. var parts = fullPath.split(DIR_SEPARATOR);
  633. for (var i = 0; i < parts.length; ++i) {
  634. var part = parts[i];
  635. if (part === '..') {
  636. parts[i - 1] = '';
  637. parts[i] = '';
  638. }
  639. }
  640. fullPath = parts.filter(function (el) {
  641. return el;
  642. }).join(DIR_SEPARATOR);
  643. // Add back in leading slash.
  644. if (fullPath[0] !== DIR_SEPARATOR) {
  645. fullPath = DIR_SEPARATOR + fullPath;
  646. }
  647. // Replace './' by current dir. ('./one/./two' -> one/two)
  648. fullPath = fullPath.replace(/\.\//g, DIR_SEPARATOR);
  649. // Replace '//' with '/'.
  650. fullPath = fullPath.replace(/\/\//g, DIR_SEPARATOR);
  651. // Replace '/.' with '/'.
  652. fullPath = fullPath.replace(/\/\./g, DIR_SEPARATOR);
  653. // Remove '/' if it appears on the end.
  654. if (fullPath[fullPath.length - 1] === DIR_SEPARATOR &&
  655. fullPath !== DIR_SEPARATOR) {
  656. fullPath = fullPath.substring(0, fullPath.length - 1);
  657. }
  658. var storagePath = prefix + fullPath;
  659. storagePath = decodeURI(storagePath);
  660. fullPath = decodeURI(fullPath);
  661. return {
  662. storagePath: storagePath,
  663. fullPath: fullPath,
  664. fileName: fullPath.split(DIR_SEPARATOR).pop(),
  665. fsName: prefix.split(DIR_SEPARATOR).pop()
  666. };
  667. }
  668. function fileEntryFromIdbEntry (fileEntry) {
  669. // IDB won't save methods, so we need re-create the FileEntry.
  670. var clonedFileEntry = new FileEntry(fileEntry.name, fileEntry.fullPath, fileEntry.filesystem);
  671. clonedFileEntry.file_ = fileEntry.file_;
  672. return clonedFileEntry;
  673. }
  674. function readAs (what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) {
  675. exports.getFile(function (fileEntry) {
  676. var fileReader = new FileReader(); // eslint-disable-line no-undef
  677. var blob = fileEntry.file_.blob_.slice(startPos, endPos);
  678. fileReader.onload = function (e) {
  679. successCallback(e.target.result);
  680. };
  681. fileReader.onerror = errorCallback;
  682. switch (what) {
  683. case 'text':
  684. fileReader.readAsText(blob, encoding);
  685. break;
  686. case 'dataURL':
  687. fileReader.readAsDataURL(blob);
  688. break;
  689. case 'arrayBuffer':
  690. fileReader.readAsArrayBuffer(blob);
  691. break;
  692. case 'binaryString':
  693. fileReader.readAsBinaryString(blob);
  694. break;
  695. }
  696. }, errorCallback, [fullPath, null]);
  697. }
  698. /** * Core logic to handle IDB operations ***/
  699. idb_.open = function (dbName, successCallback, errorCallback) {
  700. var self = this;
  701. // TODO: FF 12.0a1 isn't liking a db name with : in it.
  702. var request = indexedDB.open(dbName.replace(':', '_')/*, 1 /*version */);
  703. request.onerror = errorCallback || onError;
  704. request.onupgradeneeded = function (e) {
  705. // First open was called or higher db version was used.
  706. // console.log('onupgradeneeded: oldVersion:' + e.oldVersion,
  707. // 'newVersion:' + e.newVersion);
  708. self.db = e.target.result;
  709. self.db.onerror = onError;
  710. if (!self.db.objectStoreNames.contains(FILE_STORE_)) {
  711. self.db.createObjectStore(FILE_STORE_/*, {keyPath: 'id', autoIncrement: true} */);
  712. }
  713. };
  714. request.onsuccess = function (e) {
  715. self.db = e.target.result;
  716. self.db.onerror = onError;
  717. successCallback(e);
  718. };
  719. request.onblocked = errorCallback || onError;
  720. };
  721. idb_.close = function () {
  722. this.db.close();
  723. this.db = null;
  724. };
  725. idb_.get = function (fullPath, successCallback, errorCallback) {
  726. if (!this.db) {
  727. if (errorCallback) {
  728. errorCallback(FileError.INVALID_MODIFICATION_ERR);
  729. }
  730. return;
  731. }
  732. var tx = this.db.transaction([FILE_STORE_], 'readonly');
  733. var request = tx.objectStore(FILE_STORE_).get(fullPath);
  734. tx.onabort = errorCallback || onError;
  735. tx.oncomplete = function () {
  736. var entry = request.result;
  737. if (entry && entry.file_json) {
  738. /*
  739. Safari private browse mode cannot store Blob object to indexeddb.
  740. Then use pure json object instead of Blob object.
  741. */
  742. entry.file_ = new MyFile(entry.file_json.opt);
  743. MyFileHelper.setBase64(entry.file_, entry.file_json.base64);
  744. delete entry.file_json;
  745. }
  746. successCallback(entry);
  747. };
  748. };
  749. idb_.getAllEntries = function (fullPath, storagePath, successCallback, errorCallback) {
  750. if (!this.db) {
  751. if (errorCallback) {
  752. errorCallback(FileError.INVALID_MODIFICATION_ERR);
  753. }
  754. return;
  755. }
  756. var results = [];
  757. if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) {
  758. storagePath = storagePath.substring(0, storagePath.length - 1);
  759. }
  760. var range = IDBKeyRange.bound(storagePath + DIR_SEPARATOR + ' ',
  761. storagePath + DIR_SEPARATOR + String.fromCharCode(unicodeLastChar));
  762. var tx = this.db.transaction([FILE_STORE_], 'readonly');
  763. tx.onabort = errorCallback || onError;
  764. tx.oncomplete = function () {
  765. results = results.filter(function (val) {
  766. var pathWithoutSlash = val.fullPath;
  767. if (val.fullPath[val.fullPath.length - 1] === DIR_SEPARATOR) {
  768. pathWithoutSlash = pathWithoutSlash.substr(0, pathWithoutSlash.length - 1);
  769. }
  770. var valPartsLen = pathWithoutSlash.split(DIR_SEPARATOR).length;
  771. var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
  772. /* Input fullPath parameter equals '//' for root folder */
  773. /* Entries in root folder has valPartsLen equals 2 (see below) */
  774. if (fullPath[fullPath.length - 1] === DIR_SEPARATOR && fullPath.trim().length === 2) {
  775. fullPathPartsLen = 1;
  776. } else if (fullPath[fullPath.length - 1] === DIR_SEPARATOR) {
  777. fullPathPartsLen = fullPath.substr(0, fullPath.length - 1).split(DIR_SEPARATOR).length;
  778. } else {
  779. fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
  780. }
  781. if (valPartsLen === fullPathPartsLen + 1) {
  782. // If this a subfolder and entry is a direct child, include it in
  783. // the results. Otherwise, it's not an entry of this folder.
  784. return val;
  785. } else return false;
  786. });
  787. successCallback(results);
  788. };
  789. var request = tx.objectStore(FILE_STORE_).openCursor(range);
  790. request.onsuccess = function (e) {
  791. var cursor = e.target.result;
  792. if (cursor) {
  793. var val = cursor.value;
  794. results.push(val.isFile ? fileEntryFromIdbEntry(val) : new DirectoryEntry(val.name, val.fullPath, val.filesystem));
  795. cursor['continue']();
  796. }
  797. };
  798. };
  799. idb_['delete'] = function (fullPath, successCallback, errorCallback, isDirectory) {
  800. if (!idb_.db) {
  801. if (errorCallback) {
  802. errorCallback(FileError.INVALID_MODIFICATION_ERR);
  803. }
  804. return;
  805. }
  806. var tx = this.db.transaction([FILE_STORE_], 'readwrite');
  807. tx.oncomplete = successCallback;
  808. tx.onabort = errorCallback || onError;
  809. tx.oncomplete = function () {
  810. if (isDirectory) {
  811. // We delete nested files and folders after deleting parent folder
  812. // We use ranges: https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange
  813. fullPath = fullPath + DIR_SEPARATOR;
  814. // Range contains all entries in the form fullPath<symbol> where
  815. // symbol in the range from ' ' to symbol which has code `unicodeLastChar`
  816. var range = IDBKeyRange.bound(fullPath + ' ', fullPath + String.fromCharCode(unicodeLastChar));
  817. var newTx = this.db.transaction([FILE_STORE_], 'readwrite');
  818. newTx.oncomplete = successCallback;
  819. newTx.onabort = errorCallback || onError;
  820. newTx.objectStore(FILE_STORE_)['delete'](range);
  821. } else {
  822. successCallback();
  823. }
  824. };
  825. tx.objectStore(FILE_STORE_)['delete'](fullPath);
  826. };
  827. idb_.put = function (entry, storagePath, successCallback, errorCallback, retry) {
  828. if (!this.db) {
  829. if (errorCallback) {
  830. errorCallback(FileError.INVALID_MODIFICATION_ERR);
  831. }
  832. return;
  833. }
  834. var tx = this.db.transaction([FILE_STORE_], 'readwrite');
  835. tx.onabort = errorCallback || onError;
  836. tx.oncomplete = function () {
  837. // TODO: Error is thrown if we pass the request event back instead.
  838. successCallback(entry);
  839. };
  840. try {
  841. tx.objectStore(FILE_STORE_).put(entry, storagePath);
  842. } catch (e) {
  843. if (e.name === 'DataCloneError') {
  844. tx.oncomplete = null;
  845. /*
  846. Safari private browse mode cannot store Blob object to indexeddb.
  847. Then use pure json object instead of Blob object.
  848. */
  849. var successCallback2 = function (entry) {
  850. entry.file_ = new MyFile(entry.file_json.opt);
  851. delete entry.file_json;
  852. successCallback(entry);
  853. };
  854. if (!retry) {
  855. if (entry.file_ && entry.file_ instanceof MyFile && entry.file_.blob_) {
  856. MyFileHelper.toJson(entry.file_, function (json) {
  857. entry.file_json = json;
  858. delete entry.file_;
  859. idb_.put(entry, storagePath, successCallback2, errorCallback, true);
  860. });
  861. return;
  862. }
  863. }
  864. }
  865. throw e;
  866. }
  867. };
  868. // Global error handler. Errors bubble from request, to transaction, to db.
  869. function onError (e) {
  870. switch (e.target.errorCode) {
  871. case 12:
  872. console.log('Error - Attempt to open db with a lower version than the ' +
  873. 'current one.');
  874. break;
  875. default:
  876. console.log('errorCode: ' + e.target.errorCode);
  877. }
  878. console.log(e, e.code, e.message);
  879. }
  880. })(module.exports, window);
  881. require('cordova/exec/proxy').add('File', module.exports);
  882. })();