StorageFile.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. /**
  2. * Copyright (c) 2006-2017, JGraph Ltd
  3. * Copyright (c) 2006-2017, Gaudenz Alder
  4. */
  5. /**
  6. * Constructs a new point for the optional x and y coordinates. If no
  7. * coordinates are given, then the default values for <x> and <y> are used.
  8. * @constructor
  9. * @class Implements a basic 2D point. Known subclassers = {@link mxRectangle}.
  10. * @param {number} x X-coordinate of the point.
  11. * @param {number} y Y-coordinate of the point.
  12. */
  13. StorageFile = function(ui, data, title)
  14. {
  15. DrawioFile.call(this, ui, data);
  16. this.title = title;
  17. this.etag = this.getEtag(data);
  18. };
  19. //Extends mxEventSource
  20. mxUtils.extend(StorageFile, DrawioFile);
  21. /**
  22. * Updates the descriptor of this file with the one from the given file.
  23. */
  24. StorageFile.prototype.getEtag = function(data)
  25. {
  26. return this.ui.hashValue((data != null) ? data : '');};
  27. /**
  28. * Sets the delay for autosave in milliseconds. Default is 1000.
  29. */
  30. StorageFile.prototype.autosaveDelay = 500;
  31. /**
  32. * Sets the delay for autosave in milliseconds. Default is 20000.
  33. */
  34. StorageFile.prototype.maxAutosaveDelay = 20000;
  35. /**
  36. * Maximum number if attempts to automatically catchup on save.
  37. */
  38. StorageFile.prototype.maxRetries = 5;
  39. /**
  40. * A differentiator of the stored object type (file or lib)
  41. */
  42. StorageFile.prototype.type = 'F';
  43. /**
  44. * Translates this point by the given vector.
  45. *
  46. * @param {number} dx X-coordinate of the translation.
  47. * @param {number} dy Y-coordinate of the translation.
  48. */
  49. StorageFile.prototype.getMode = function()
  50. {
  51. return App.MODE_BROWSER;
  52. };
  53. /**
  54. * Overridden to enable the autosave option in the document properties dialog.
  55. */
  56. StorageFile.prototype.isSyncSupported = function()
  57. {
  58. return true
  59. };
  60. /**
  61. * Overridden to enable the autosave option in the document properties dialog.
  62. */
  63. StorageFile.prototype.isPolling = function()
  64. {
  65. return this.isSyncSupported();
  66. };
  67. /**
  68. * Overridden to enable the autosave option in the document properties dialog.
  69. */
  70. StorageFile.prototype.getPollingInterval = function()
  71. {
  72. return 10000;
  73. };
  74. /**
  75. * Hook for subclassers to get the latest descriptor of this file
  76. * and return it in the success handler.
  77. */
  78. StorageFile.prototype.loadDescriptor = function(success, error)
  79. {
  80. this.getLatestVersionId(success, error);
  81. };
  82. /**
  83. * Hook for subclassers to get the latest version ID of this file
  84. * and return it in the success handler.
  85. */
  86. StorageFile.prototype.getLatestVersionId = function(success, error)
  87. {
  88. StorageFile.getFileContent(this.ui, this.title, mxUtils.bind(this, function(data)
  89. {
  90. success(this.getEtag(data));
  91. }), error);
  92. };
  93. /**
  94. * Overridden to enable the autosave option in the document properties dialog.
  95. */
  96. StorageFile.prototype.isAutosaveOptional = function()
  97. {
  98. return true;
  99. };
  100. /**
  101. * Translates this point by the given vector.
  102. *
  103. * @param {number} dx X-coordinate of the translation.
  104. * @param {number} dy Y-coordinate of the translation.
  105. */
  106. StorageFile.prototype.getHash = function()
  107. {
  108. return 'L' + encodeURIComponent(this.getTitle());
  109. };
  110. /**
  111. * Translates this point by the given vector.
  112. *
  113. * @param {number} dx X-coordinate of the translation.
  114. * @param {number} dy Y-coordinate of the translation.
  115. */
  116. StorageFile.prototype.getTitle = function()
  117. {
  118. return this.title;
  119. };
  120. /**
  121. * Translates this point by the given vector.
  122. *
  123. * @param {number} dx X-coordinate of the translation.
  124. * @param {number} dy Y-coordinate of the translation.
  125. */
  126. StorageFile.prototype.isRenamable = function()
  127. {
  128. return true;
  129. };
  130. /**
  131. * Adds all listeners.
  132. */
  133. StorageFile.prototype.getDescriptor = function()
  134. {
  135. return this.etag;
  136. };
  137. /**
  138. * Updates the descriptor of this file with the one from the given file.
  139. */
  140. StorageFile.prototype.setDescriptor = function(etag)
  141. {
  142. this.etag = etag;
  143. };
  144. /**
  145. * Returns the etag from the given descriptor.
  146. */
  147. StorageFile.prototype.getDescriptorEtag = function(desc)
  148. {
  149. return desc;
  150. };
  151. /**
  152. * Translates this point by the given vector.
  153. *
  154. * @param {number} dx X-coordinate of the translation.
  155. * @param {number} dy Y-coordinate of the translation.
  156. */
  157. StorageFile.prototype.save = function(revision, success, error)
  158. {
  159. this.saveAs(this.getTitle(), success, error);
  160. };
  161. /**
  162. * Translates this point by the given vector.
  163. *
  164. * @param {number} dx X-coordinate of the translation.
  165. * @param {number} dy Y-coordinate of the translation.
  166. */
  167. StorageFile.prototype.saveAs = function(title, success, error)
  168. {
  169. DrawioFile.prototype.save.apply(this, [false, mxUtils.bind(this, function()
  170. {
  171. this.saveFile(this.getTitle(), false, success, error);
  172. }), error]);
  173. };
  174. /**
  175. * Translates this point by the given vector.
  176. *
  177. * @param {number} dx X-coordinate of the translation.
  178. * @param {number} dy Y-coordinate of the translation.
  179. */
  180. StorageFile.insertFile = function(ui, title, data, success, error, file)
  181. {
  182. StorageFile.doInsertFile((file != null) ? file :
  183. new StorageFile(ui, data, title), success, error);
  184. };
  185. /**
  186. * Translates this point by the given vector.
  187. *
  188. * @param {number} dx X-coordinate of the translation.
  189. * @param {number} dy Y-coordinate of the translation.
  190. */
  191. StorageFile.doInsertFile = function(file, success, error)
  192. {
  193. var title = file.getTitle();
  194. var ui = file.getUi();
  195. var createStorageFile = mxUtils.bind(this, function(exists)
  196. {
  197. var fn = function()
  198. {
  199. file.writeFile(title, function()
  200. {
  201. success(file);
  202. }, error);
  203. };
  204. if (exists)
  205. {
  206. ui.confirm(mxResources.get('replaceIt', [title]), fn, error);
  207. }
  208. else
  209. {
  210. fn();
  211. }
  212. });
  213. StorageFile.getFileContent(ui, title, function(data)
  214. {
  215. createStorageFile(data != null);
  216. }, function()
  217. {
  218. createStorageFile(false);
  219. });
  220. };
  221. /**
  222. * Translates this point by the given vector.
  223. *
  224. * @param {number} dx X-coordinate of the translation.
  225. * @param {number} dy Y-coordinate of the translation.
  226. */
  227. StorageFile.getFileContent = function(ui, title, success, error)
  228. {
  229. ui.getDatabaseItem(title, function(obj)
  230. {
  231. success(obj != null? obj.data : null);
  232. },
  233. mxUtils.bind(this, function()
  234. {
  235. if (ui.database == null) //fallback to localstorage
  236. {
  237. ui.getLocalData(title, success);
  238. }
  239. else if (error != null)
  240. {
  241. error();
  242. }
  243. }), 'files');
  244. };
  245. /**
  246. * Translates this point by the given vector.
  247. *
  248. * @param {number} dx X-coordinate of the translation.
  249. * @param {number} dy Y-coordinate of the translation.
  250. */
  251. StorageFile.getFileInfo = function(ui, title, success, error)
  252. {
  253. ui.getDatabaseItem(title, function(obj)
  254. {
  255. success(obj);
  256. },
  257. mxUtils.bind(this, function()
  258. {
  259. if (ui.database == null) //fallback to localstorage
  260. {
  261. ui.getLocalData(title, function(data)
  262. {
  263. success(data != null? {title: title} : null);
  264. });
  265. }
  266. else if (error != null)
  267. {
  268. error();
  269. }
  270. }), 'filesInfo');
  271. };
  272. /**
  273. * Translates this point by the given vector.
  274. *
  275. * @param {number} dx X-coordinate of the translation.
  276. * @param {number} dy Y-coordinate of the translation.
  277. */
  278. StorageFile.prototype.saveFile = function(title, revision, success, error, retry)
  279. {
  280. retry = (retry != null) ? retry : 0;
  281. if (!this.isEditable())
  282. {
  283. if (success != null)
  284. {
  285. success();
  286. }
  287. }
  288. else
  289. {
  290. var fn = mxUtils.bind(this, function()
  291. {
  292. this.writeFile(title, success, error);
  293. });
  294. // Checks for trailing dots
  295. if (this.isRenamable() && title.charAt(0) == '.' && error != null)
  296. {
  297. error({message: mxResources.get('invalidName')});
  298. }
  299. else if (this instanceof StorageLibrary)
  300. {
  301. fn(); // No need to check for conflicts with libraries
  302. }
  303. else
  304. {
  305. StorageFile.getFileInfo(this.ui, title, mxUtils.bind(this, function(data)
  306. {
  307. if (!this.isRenamable() || this.getTitle() == title || data == null)
  308. {
  309. this.getLatestVersion(mxUtils.bind(this, function(file)
  310. {
  311. if (file.getDescriptor() != this.getDescriptor())
  312. {
  313. EditorUi.debug('StorageFile.saveFile',
  314. [this], 'conflict', [file]);
  315. this.mergeFile(file, mxUtils.bind(this, function()
  316. {
  317. if (retry >= this.maxRetries ||
  318. this.invalidChecksum ||
  319. this.inConflictState)
  320. {
  321. this.inConflictState = true;
  322. if (error != null)
  323. {
  324. error();
  325. }
  326. }
  327. else
  328. {
  329. this.retrySave(mxUtils.bind(this, function()
  330. {
  331. this.updateFileData();
  332. this.saveFile(title, revision,
  333. success, error, retry + 1);
  334. }));
  335. }
  336. }), error);
  337. }
  338. else
  339. {
  340. fn();
  341. }
  342. }), error);
  343. }
  344. else
  345. {
  346. this.ui.confirm(mxResources.get('replaceIt', [title]), fn, error);
  347. }
  348. }), error);
  349. }
  350. }
  351. EditorUi.debug('StorageFile.saveFile', [this], 'title', title,
  352. 'revision', revision, 'retry', retry);
  353. };
  354. /**
  355. * Translates this point by the given vector.
  356. *
  357. * @param {number} dx X-coordinate of the translation.
  358. * @param {number} dy Y-coordinate of the translation.
  359. */
  360. StorageFile.prototype.retrySave = function(fn)
  361. {
  362. var delay = 300 + Math.random() * 300;
  363. window.setTimeout(fn, delay);
  364. EditorUi.debug('StorageFile.retrySave', [this], 'delay', delay);
  365. };
  366. /**
  367. * Translates this point by the given vector.
  368. *
  369. * @param {number} dx X-coordinate of the translation.
  370. * @param {number} dy Y-coordinate of the translation.
  371. */
  372. StorageFile.prototype.writeFile = function(title, success, error)
  373. {
  374. if (this.isRenamable())
  375. {
  376. this.title = title;
  377. }
  378. try
  379. {
  380. var desc = this.getDescriptor();
  381. var data = this.getData();
  382. var saveDone = mxUtils.bind(this, function()
  383. {
  384. this.setModified(this.getShadowModified());
  385. this.setDescriptor(this.getEtag(data));
  386. this.contentChanged();
  387. this.fileSaved(data, desc, success, error);
  388. });
  389. this.setShadowModified(false);
  390. this.ui.setDatabaseItem(null, [{
  391. title: this.title,
  392. size: data.length,
  393. lastModified: Date.now(),
  394. type: this.type
  395. }, {
  396. title: this.title,
  397. data: data
  398. }], saveDone, mxUtils.bind(this, function()
  399. {
  400. if (this.ui.database == null) //fallback to localstorage
  401. {
  402. try
  403. {
  404. this.ui.setLocalData(this.title, data, saveDone);
  405. }
  406. catch (e)
  407. {
  408. if (error != null)
  409. {
  410. error(e);
  411. }
  412. }
  413. }
  414. else if (error != null)
  415. {
  416. error();
  417. }
  418. }), ['filesInfo', 'files']);
  419. }
  420. catch (e)
  421. {
  422. if (error != null)
  423. {
  424. error(e);
  425. }
  426. }
  427. };
  428. /**
  429. * Translates this point by the given vector.
  430. *
  431. * @param {number} dx X-coordinate of the translation.
  432. * @param {number} dy Y-coordinate of the translation.
  433. */
  434. StorageFile.prototype.rename = function(title, success, error)
  435. {
  436. var oldTitle = this.getTitle();
  437. if (oldTitle != title)
  438. {
  439. StorageFile.getFileInfo(this.ui, title, mxUtils.bind(this, function(data)
  440. {
  441. var fn = mxUtils.bind(this, function()
  442. {
  443. this.title = title;
  444. // Updates the data if the extension has changed
  445. if (!this.hasSameExtension(oldTitle, title))
  446. {
  447. this.setData(this.ui.getFileData());
  448. }
  449. this.saveFile(title, false, mxUtils.bind(this, function()
  450. {
  451. this.ui.removeLocalData(oldTitle, success);
  452. }), error);
  453. });
  454. if (data != null)
  455. {
  456. this.ui.confirm(mxResources.get('replaceIt', [title]), fn, error);
  457. }
  458. else
  459. {
  460. fn();
  461. }
  462. }), error);
  463. }
  464. else
  465. {
  466. success();
  467. }
  468. };
  469. /**
  470. * Adds the listener for automatically saving the diagram for local changes.
  471. */
  472. StorageFile.prototype.getLatestVersion = function(success, error)
  473. {
  474. StorageFile.getFileContent(this.ui, this.title, mxUtils.bind(this, function(data)
  475. {
  476. success(new StorageFile(this.ui, data, this.title));
  477. }), error);
  478. };
  479. /**
  480. * Stops any pending autosaves and removes all listeners.
  481. */
  482. StorageFile.prototype.destroy = function()
  483. {
  484. DrawioFile.prototype.destroy.apply(this, arguments);
  485. if (this.storageListener != null)
  486. {
  487. mxEvent.removeListener(window, 'storage', this.storageListener);
  488. this.storageListener = null;
  489. }
  490. };
  491. /**
  492. * Translates this point by the given vector.
  493. *
  494. * @param {number} dx X-coordinate of the translation.
  495. * @param {number} dy Y-coordinate of the translation.
  496. */
  497. StorageFile.listLocalStorageFiles = function(type)
  498. {
  499. var filesInfo = [];
  500. for (var i = 0; i < localStorage.length; i++)
  501. {
  502. var key = localStorage.key(i);
  503. var value = localStorage.getItem(key);
  504. if (key.length > 0 && key.charAt(0) != '.' && value.length > 0)
  505. {
  506. var isFile = (type == null || type == 'F') && (value.substring(0, 8) === '<mxfile ' ||
  507. value.substring(0, 5) === '<?xml' || value.substring(0, 12) === '<!--[if IE]>');
  508. var isLib = (type == null || type == 'L') && (value.substring(0, 11) === '<mxlibrary>');
  509. if (isFile || isLib)
  510. {
  511. filesInfo.push({
  512. title: key,
  513. type: isFile? 'F' : 'L',
  514. size: value.length,
  515. lastModified: Date.now()
  516. });
  517. }
  518. }
  519. }
  520. return filesInfo;
  521. };
  522. /**
  523. * Translates this point by the given vector.
  524. *
  525. * @param {number} dx X-coordinate of the translation.
  526. * @param {number} dy Y-coordinate of the translation.
  527. */
  528. StorageFile.migrate = function(db)
  529. {
  530. var lsFilesInfo = StorageFile.listLocalStorageFiles();
  531. lsFilesInfo.push({title: '.scratchpad', type: 'L'}); //Adding scratchpad also since it is a library (storage file)
  532. var tx = db.transaction(['files', 'filesInfo'], 'readwrite');
  533. var files = tx.objectStore('files');
  534. var filesInfo = tx.objectStore('filesInfo');
  535. for (var i = 0; i < lsFilesInfo.length; i++)
  536. {
  537. var lsFileInfo = lsFilesInfo[i];
  538. var data = localStorage.getItem(lsFileInfo.title);
  539. files.add({
  540. title: lsFileInfo.title,
  541. data: data
  542. });
  543. filesInfo.add(lsFileInfo);
  544. }
  545. };
  546. /**
  547. * Translates this point by the given vector.
  548. *
  549. * @param {number} dx X-coordinate of the translation.
  550. * @param {number} dy Y-coordinate of the translation.
  551. */
  552. StorageFile.listFiles = function(ui, type, success, error)
  553. {
  554. ui.getDatabaseItems(function(filesInfo)
  555. {
  556. var files = [];
  557. if (filesInfo != null)
  558. {
  559. for (var i = 0; i < filesInfo.length; i++)
  560. {
  561. if (filesInfo[i].title.charAt(0) != '.' && (type == null || filesInfo[i].type == type))
  562. {
  563. files.push(filesInfo[i]);
  564. }
  565. }
  566. }
  567. success(files);
  568. }, function()
  569. {
  570. if (ui.database == null) //fallback to localstorage
  571. {
  572. success(StorageFile.listLocalStorageFiles(type));
  573. }
  574. else if (error != null)
  575. {
  576. error();
  577. }
  578. }, 'filesInfo');
  579. };
  580. /**
  581. * Translates this point by the given vector.
  582. *
  583. * @param {number} dx X-coordinate of the translation.
  584. * @param {number} dy Y-coordinate of the translation.
  585. */
  586. StorageFile.deleteFile = function(ui, title, success, error)
  587. {
  588. ui.removeDatabaseItem([title, title], success, function()
  589. {
  590. if (ui.database == null) //fallback to localstorage
  591. {
  592. localStorage.removeItem(title)
  593. success();
  594. }
  595. else if (error != null)
  596. {
  597. error();
  598. }
  599. }, ['files', 'filesInfo']);
  600. };