DropboxClient.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. /**
  2. * Copyright (c) 2006-2017, JGraph Ltd
  3. * Copyright (c) 2006-2017, Gaudenz Alder
  4. */
  5. DropboxClient = function(editorUi)
  6. {
  7. mxEventSource.call(this);
  8. /**
  9. * Holds the x-coordinate of the point.
  10. * @type number
  11. * @default 0
  12. */
  13. this.ui = editorUi;
  14. /**
  15. * Holds the x-coordinate of the point.
  16. * @type number
  17. * @default 0
  18. */
  19. this.client = new Dropbox.Client(
  20. {
  21. key: App.DROPBOX_APPKEY,
  22. sandbox: true
  23. });
  24. /**
  25. * Holds the x-coordinate of the point.
  26. * @type number
  27. * @default 0
  28. */
  29. this.client.authDriver(new Dropbox.AuthDriver.Popup(
  30. {
  31. rememberUser: true,
  32. receiverUrl: 'https://' + window.location.host + '/dropbox.html'
  33. }));
  34. };
  35. // Extends mxEventSource
  36. mxUtils.extend(DropboxClient, mxEventSource);
  37. /**
  38. * FIXME: How to find name of app folder for current user. The Apps part of the
  39. * name is internationalized so this hardcoded check does not work everywhere.
  40. */
  41. DropboxClient.prototype.appPath = '/drawio/';
  42. /**
  43. * Executes the first step for connecting to Google Drive.
  44. */
  45. DropboxClient.prototype.extension = '.html';
  46. /**
  47. * Executes the first step for connecting to Google Drive.
  48. */
  49. DriveClient.prototype.maxRetries = 4;
  50. /**
  51. * Executes the first step for connecting to Google Drive.
  52. */
  53. DropboxClient.prototype.user = null;
  54. /**
  55. * Executes the first step for connecting to Google Drive.
  56. */
  57. DropboxClient.prototype.writingFile = false;
  58. /**
  59. * Authorizes the client, gets the userId and calls <open>.
  60. */
  61. DropboxClient.prototype.logout = function()
  62. {
  63. this.client.signOut(mxUtils.bind(this, function()
  64. {
  65. this.setUser(null);
  66. }));
  67. };
  68. /**
  69. * Authorizes the client, gets the userId and calls <open>.
  70. */
  71. DropboxClient.prototype.setUser = function(user)
  72. {
  73. this.user = user;
  74. this.fireEvent(new mxEventObject('userChanged'));
  75. };
  76. /**
  77. * Authorizes the client, gets the userId and calls <open>.
  78. */
  79. DropboxClient.prototype.getUser = function()
  80. {
  81. return this.user;
  82. };
  83. /**
  84. * Checks if the client is authorized and calls the next step.
  85. */
  86. DropboxClient.prototype.updateUser = function(success, error, remember)
  87. {
  88. this.client.getUserInfo(null, mxUtils.bind(this, function(error, info)
  89. {
  90. if (error == null)
  91. {
  92. this.setUser(new DrawioUser(info.uid, info.email, info.name));
  93. }
  94. else
  95. {
  96. this.setUser(null);
  97. }
  98. }));
  99. };
  100. /**
  101. * Authorizes the client, gets the userId and calls <open>.
  102. */
  103. DropboxClient.prototype.execute = function(fn)
  104. {
  105. if (this.client.isAuthenticated())
  106. {
  107. fn();
  108. }
  109. else
  110. {
  111. this.authorize(false, mxUtils.bind(this, function(error, client)
  112. {
  113. if (error != null)
  114. {
  115. this.ui.handleError(error);
  116. }
  117. else
  118. {
  119. if (this.client.isAuthenticated())
  120. {
  121. this.updateUser();
  122. fn();
  123. }
  124. else
  125. {
  126. this.ui.showAuthDialog(this, false, mxUtils.bind(this, function(remember, success)
  127. {
  128. this.authorize(true, mxUtils.bind(this, function(error2, client)
  129. {
  130. if (error2 != null)
  131. {
  132. this.ui.handleError(error2);
  133. }
  134. else if (this.client.isAuthenticated())
  135. {
  136. this.updateUser();
  137. if (success != null)
  138. {
  139. success();
  140. }
  141. fn();
  142. }
  143. }));
  144. }));
  145. }
  146. }
  147. }));
  148. }
  149. };
  150. /**
  151. * Authorizes the client, gets the userId and calls <open>.
  152. */
  153. DropboxClient.prototype.authorize = function(interactive, fn)
  154. {
  155. this.client.authenticate({interactive: interactive}, mxUtils.bind(this, function(error, client)
  156. {
  157. if (error != null)
  158. {
  159. if (window.console != null)
  160. {
  161. console.log(error);
  162. }
  163. }
  164. else
  165. {
  166. fn();
  167. }
  168. }));
  169. };
  170. /**
  171. * Checks if the client is authorized and calls the next step.
  172. */
  173. DropboxClient.prototype.getLibrary = function(path, success, error)
  174. {
  175. this.getFile(path, success, error, false, true);
  176. };
  177. /**
  178. * DenyConvert is ignored in this client, just added for API compatibility.
  179. */
  180. DropboxClient.prototype.getFile = function(path, success, error, denyConvert, asLibrary)
  181. {
  182. asLibrary = (asLibrary != null) ? asLibrary : false;
  183. var fn = mxUtils.bind(this, function()
  184. {
  185. this.execute(mxUtils.bind(this, function()
  186. {
  187. var acceptResponse = true;
  188. var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  189. {
  190. acceptResponse = false;
  191. error({code: App.ERROR_TIMEOUT, retry: fn});
  192. }), this.ui.timeout);
  193. var options = null;
  194. if (urlParams['rev'] != null)
  195. {
  196. options = {versionTag: urlParams['rev']};
  197. }
  198. this.client.readFile('/' + path, options, mxUtils.bind(this, function(err, data, stat)
  199. {
  200. try
  201. {
  202. window.clearTimeout(timeoutThread);
  203. if (acceptResponse)
  204. {
  205. if (err != null)
  206. {
  207. error(err)
  208. }
  209. else
  210. {
  211. if (asLibrary)
  212. {
  213. success(new DropboxLibrary(this.ui, data, stat));
  214. }
  215. else
  216. {
  217. success(new DropboxFile(this.ui, data, stat));
  218. }
  219. }
  220. }
  221. }
  222. catch (e)
  223. {
  224. error(e);
  225. }
  226. }));
  227. }));
  228. });
  229. fn();
  230. };
  231. /**
  232. * Translates this point by the given vector.
  233. *
  234. * @param {number} dx X-coordinate of the translation.
  235. * @param {number} dy Y-coordinate of the translation.
  236. */
  237. DropboxClient.prototype.checkExists = function(filename, fn)
  238. {
  239. this.client.stat(filename, mxUtils.bind(this, function(err, stat)
  240. {
  241. if ((err != null && err.status == 404) || (stat != null && stat.isRemoved))
  242. {
  243. fn(true);
  244. }
  245. else
  246. {
  247. this.ui.confirm(mxResources.get('replaceIt', [filename]), function()
  248. {
  249. fn(true);
  250. }, function()
  251. {
  252. fn(false);
  253. });
  254. }
  255. }));
  256. };
  257. /**
  258. * Translates this point by the given vector.
  259. *
  260. * @param {number} dx X-coordinate of the translation.
  261. * @param {number} dy Y-coordinate of the translation.
  262. */
  263. DropboxClient.prototype.renameFile = function(file, filename, success, error)
  264. {
  265. if (file != null && filename != null)
  266. {
  267. // Checks if file exists
  268. this.execute(mxUtils.bind(this, function()
  269. {
  270. this.checkExists(filename, mxUtils.bind(this, function(checked)
  271. {
  272. if (checked)
  273. {
  274. // Uses write and remove because move does not allow overwriting an existing target
  275. this.writeFile(filename, file.getData(), mxUtils.bind(this, function(stat)
  276. {
  277. this.client.remove(file.getTitle(), function(err2, stat2)
  278. {
  279. if (err2 != null)
  280. {
  281. error(err2)
  282. }
  283. else
  284. {
  285. success(stat);
  286. }
  287. });
  288. }), error);
  289. }
  290. else
  291. {
  292. error();
  293. }
  294. }));
  295. }));
  296. }
  297. };
  298. /**
  299. * Translates this point by the given vector.
  300. *
  301. * @param {number} dx X-coordinate of the translation.
  302. * @param {number} dy Y-coordinate of the translation.
  303. */
  304. DropboxClient.prototype.insertLibrary = function(filename, data, success, error)
  305. {
  306. this.insertFile(filename, data, success, error, true);
  307. };
  308. /**
  309. * Translates this point by the given vector.
  310. *
  311. * @param {number} dx X-coordinate of the translation.
  312. * @param {number} dy Y-coordinate of the translation.
  313. */
  314. DropboxClient.prototype.insertFile = function(filename, data, success, error, asLibrary)
  315. {
  316. asLibrary = (asLibrary != null) ? asLibrary : false;
  317. this.execute(mxUtils.bind(this, function()
  318. {
  319. this.checkExists(filename, mxUtils.bind(this, function(checked)
  320. {
  321. if (checked)
  322. {
  323. this.writeFile(filename, data, mxUtils.bind(this, function(stat)
  324. {
  325. if (asLibrary)
  326. {
  327. success(new DropboxLibrary(this.ui, data, stat));
  328. }
  329. else
  330. {
  331. success(new DropboxFile(this.ui, data, stat));
  332. }
  333. }), error);
  334. }
  335. else
  336. {
  337. error();
  338. }
  339. }));
  340. }));
  341. };
  342. /**
  343. * Translates this point by the given vector.
  344. *
  345. * @param {number} dx X-coordinate of the translation.
  346. * @param {number} dy Y-coordinate of the translation.
  347. */
  348. DropboxClient.prototype.saveFile = function(filename, data, success, error)
  349. {
  350. this.execute(mxUtils.bind(this, function()
  351. {
  352. this.writeFile(filename, data, success, error);
  353. }));
  354. };
  355. /**
  356. * Translates this point by the given vector.
  357. *
  358. * @param {number} dx X-coordinate of the translation.
  359. * @param {number} dy Y-coordinate of the translation.
  360. */
  361. DropboxClient.prototype.writeFile = function(filename, data, success, error)
  362. {
  363. if (/[\\\/:\?\*"\|]/.test(filename))
  364. {
  365. if (error != null)
  366. {
  367. error({message: mxResources.get('dropboxCharsNotAllowed')});
  368. }
  369. }
  370. else if (!this.writingFile)
  371. {
  372. var acceptResponse = true;
  373. var timeoutThread = null;
  374. this.writingFile = true;
  375. var retryCount = 0;
  376. // Cancels any pending requests
  377. if (this.requestThread != null)
  378. {
  379. window.clearTimeout(this.requestThread);
  380. }
  381. var fn = mxUtils.bind(this, function()
  382. {
  383. if (timeoutThread != null)
  384. {
  385. window.clearTimeout(timeoutThread);
  386. }
  387. timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  388. {
  389. this.writingFile = false;
  390. acceptResponse = false;
  391. if (error != null)
  392. {
  393. error({code: App.ERROR_TIMEOUT, retry: fn});
  394. }
  395. }), this.ui.timeout);
  396. this.client.writeFile(filename, data, mxUtils.bind(this, function(err, stat)
  397. {
  398. window.clearTimeout(timeoutThread);
  399. if (acceptResponse)
  400. {
  401. if (err != null)
  402. {
  403. if (retryCount < this.maxRetries)
  404. {
  405. retryCount++;
  406. var jitter = 1 + 0.1 * (Math.random() - 0.5);
  407. this.requestThread = window.setTimeout(fn, Math.round(Math.pow(2, retryCount) * jitter * 1000));
  408. }
  409. else if (error != null)
  410. {
  411. this.writingFile = false;
  412. error(err);
  413. }
  414. }
  415. else
  416. {
  417. this.writingFile = false;
  418. if (success != null)
  419. {
  420. success(stat);
  421. }
  422. }
  423. }
  424. }));
  425. });
  426. fn();
  427. }
  428. else if (error != null)
  429. {
  430. error({code: App.ERROR_BUSY});
  431. }
  432. };
  433. /**
  434. * Translates this point by the given vector.
  435. *
  436. * @param {number} dx X-coordinate of the translation.
  437. * @param {number} dy Y-coordinate of the translation.
  438. */
  439. DropboxClient.prototype.pickLibrary = function(fn)
  440. {
  441. // Authentication will be carried out on open to make sure the
  442. // autosave does not show an auth dialog. Showing it here will
  443. // block the second dialog (the file picker) so it's too early.
  444. Dropbox.choose(
  445. {
  446. linkType : 'direct',
  447. cancel: mxUtils.bind(this, function()
  448. {
  449. // do nothing
  450. }),
  451. success : mxUtils.bind(this, function(files)
  452. {
  453. if (this.ui.spinner.spin(document.body, mxResources.get('loading')))
  454. {
  455. var error = mxUtils.bind(this, function(e)
  456. {
  457. this.ui.spinner.stop();
  458. this.ui.handleError(e);
  459. });
  460. var tmp = files[0].link.indexOf(this.appPath);
  461. if (tmp > 0)
  462. {
  463. // Checks if file is in app folder by loading file from there and comparing relative path and size
  464. // KNOWN: This check fails if a file is inside a drawio directory with same relative path and size
  465. this.execute(mxUtils.bind(this, function()
  466. {
  467. var rel = decodeURIComponent(files[0].link.substring(tmp + this.appPath.length - 1));
  468. this.client.readFile(rel, null, mxUtils.bind(this, function(err, data, stat)
  469. {
  470. if (stat != null && parseInt(files[0].bytes) === parseInt(stat.size) && rel === stat.path)
  471. {
  472. // No need to load file a second time
  473. try
  474. {
  475. this.ui.spinner.stop();
  476. fn(rel.substring(1), new DropboxLibrary(this.ui, data, stat));
  477. }
  478. catch (e)
  479. {
  480. this.ui.handleError(e);
  481. }
  482. }
  483. else
  484. {
  485. this.createLibrary(files[0], fn, error);
  486. }
  487. }));
  488. }));
  489. }
  490. else
  491. {
  492. this.execute(mxUtils.bind(this, function()
  493. {
  494. this.createLibrary(files[0], fn, error);
  495. }));
  496. }
  497. }
  498. })
  499. });
  500. };
  501. /**
  502. * Translates this point by the given vector.
  503. *
  504. * @param {number} dx X-coordinate of the translation.
  505. * @param {number} dy Y-coordinate of the translation.
  506. */
  507. DropboxClient.prototype.createLibrary = function(file, success, error)
  508. {
  509. this.ui.confirm(mxResources.get('note') + ': ' + mxResources.get('fileWillBeSavedInAppFolder', [file.name]), mxUtils.bind(this, function()
  510. {
  511. this.ui.loadUrl(file.link, mxUtils.bind(this, function(data)
  512. {
  513. this.insertFile(file.name, data, mxUtils.bind(this, function(newFile)
  514. {
  515. try
  516. {
  517. this.ui.spinner.stop();
  518. success(newFile.getHash().substring(1), newFile);
  519. }
  520. catch (e)
  521. {
  522. error(e);
  523. }
  524. }), error, true);
  525. }), error);
  526. }), mxUtils.bind(this, function()
  527. {
  528. this.ui.spinner.stop();
  529. }));
  530. };
  531. /**
  532. * Translates this point by the given vector.
  533. *
  534. * @param {number} dx X-coordinate of the translation.
  535. * @param {number} dy Y-coordinate of the translation.
  536. */
  537. DropboxClient.prototype.pickFile = function(fn, readOnly)
  538. {
  539. if (Dropbox.choose != null)
  540. {
  541. fn = (fn != null) ? fn : mxUtils.bind(this, function(path, file)
  542. {
  543. this.ui.loadFile('D' + encodeURIComponent(path), null, file);
  544. });
  545. // Authentication will be carried out on open to make sure the
  546. // autosave does not show an auth dialog. Showing it here will
  547. // block the second dialog (the file picker) so it's too early.
  548. Dropbox.choose(
  549. {
  550. linkType : 'direct',
  551. cancel: mxUtils.bind(this, function()
  552. {
  553. // do nothing
  554. }),
  555. success : mxUtils.bind(this, function(files)
  556. {
  557. if (this.ui.spinner.spin(document.body, mxResources.get('loading')))
  558. {
  559. // File used for read-only
  560. if (readOnly)
  561. {
  562. this.ui.spinner.stop();
  563. fn(files[0].link);
  564. }
  565. else
  566. {
  567. var error = mxUtils.bind(this, function(e)
  568. {
  569. this.ui.spinner.stop();
  570. this.ui.handleError(e);
  571. });
  572. var success = mxUtils.bind(this, function(path, file)
  573. {
  574. this.ui.spinner.stop();
  575. fn(path, file);
  576. });
  577. var tmp = files[0].link.indexOf(this.appPath);
  578. if (tmp > 0 && !/(\.png)$/i.test(files[0].name) && !/(\.vs?dx)$/i.test(files[0].name) && !/(\.gliffy)$/i.test(files[0].name))
  579. {
  580. // Checks if file is in app folder by loading file from there and comparing relative path and size
  581. // KNOWN: This check fails if a file is inside a drawio directory with same relative path and size
  582. this.execute(mxUtils.bind(this, function()
  583. {
  584. var rel = decodeURIComponent(files[0].link.substring(tmp + this.appPath.length - 1));
  585. this.client.readFile(rel, null, mxUtils.bind(this, function(err, data, stat)
  586. {
  587. if (stat != null && parseInt(files[0].bytes) === parseInt(stat.size) && rel === stat.path)
  588. {
  589. this.ui.spinner.stop();
  590. // No need to load file a second time
  591. fn(rel.substring(1), new DropboxFile(this.ui, data, stat));
  592. }
  593. else
  594. {
  595. this.createFile(files[0], success, error);
  596. }
  597. }));
  598. }));
  599. }
  600. else
  601. {
  602. this.execute(mxUtils.bind(this, function()
  603. {
  604. this.createFile(files[0], success, error);
  605. }));
  606. }
  607. }
  608. }
  609. })
  610. });
  611. }
  612. else
  613. {
  614. this.ui.handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
  615. }
  616. };
  617. /**
  618. * Translates this point by the given vector.
  619. *
  620. * @param {number} dx X-coordinate of the translation.
  621. * @param {number} dy Y-coordinate of the translation.
  622. */
  623. DropboxClient.prototype.createFile = function(file, success, error)
  624. {
  625. var name = file.name;
  626. if (/(\.png)$/i.test(name) || /(\.vs?dx)$/i.test(name) || /(\.gliffy)$/i.test(name))
  627. {
  628. name = name.substring(0, name.lastIndexOf('.')) + this.extension;
  629. }
  630. var doInsert = mxUtils.bind(this, function(filename, data)
  631. {
  632. this.ui.confirm(mxResources.get('note') + ': ' + mxResources.get('fileWillBeSavedInAppFolder', [filename]), mxUtils.bind(this, function()
  633. {
  634. this.insertFile(filename, data, mxUtils.bind(this, function(newFile)
  635. {
  636. success(filename, newFile);
  637. }), error);
  638. }), mxUtils.bind(this, function()
  639. {
  640. this.ui.spinner.stop();
  641. }));
  642. });
  643. this.ui.loadUrl(file.link, mxUtils.bind(this, function(data)
  644. {
  645. if (/(\.vs?dx)$/i.test(file.name) || /(\.gliffy)$/i.test(file.name))
  646. {
  647. this.ui.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  648. {
  649. if (xhr.readyState == 4)
  650. {
  651. if (xhr.status >= 200 && xhr.status <= 299 && xhr.responseText.substring(0, 13) == '<mxGraphModel')
  652. {
  653. doInsert(name, xhr.responseText);
  654. }
  655. else
  656. {
  657. this.ui.spinner.stop();
  658. if (error != null)
  659. {
  660. error({message: mxResources.get('errorLoadingFile')});
  661. }
  662. }
  663. }
  664. }), file.name);
  665. }
  666. else
  667. {
  668. if (/(\.png)$/i.test(file.name))
  669. {
  670. data = this.ui.extractGraphModelFromPng(data);
  671. }
  672. if (data != null && data.length > 0)
  673. {
  674. doInsert(name, data);
  675. }
  676. else
  677. {
  678. this.ui.spinner.stop();
  679. if (error != null)
  680. {
  681. error({message: mxResources.get('errorLoadingFile')});
  682. }
  683. }
  684. }
  685. }), error, /(\.png)$/i.test(file.name));
  686. };