TripalUploadFile.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. /**
  2. * TripalUploadFile Object
  3. */
  4. (function($) {
  5. "use strict";
  6. /**
  7. * The constructor function.
  8. */
  9. var TripalUploadFile = function (file, options) {
  10. this.file = file;
  11. this.options = options;
  12. this.file_size = file.size;
  13. this.chunk_size = (1024 * 2000); // 2024MB
  14. this.total_chunks = ((this.file.size % this.chunk_size == 0) ? Math.floor(this.file.size / this.chunk_size) : Math.floor(this.file.size / this.chunk_size) + 1);
  15. this.curr_chunk = 0;
  16. this.status = 'pending';
  17. this.file_id = null;
  18. if ('mozSlice' in file) {
  19. this.slice_method = 'mozSlice';
  20. }
  21. else if ('webkitSlice' in file) {
  22. this.slice_method = 'webkitSlice';
  23. }
  24. else {
  25. this.slice_method = 'slice';
  26. }
  27. var self = this;
  28. this.xhr = new XMLHttpRequest();
  29. this.xhr.onload = function() {
  30. self._onChunkComplete();
  31. }
  32. // Respond to changes in connection
  33. if ('onLine' in navigator) {
  34. window.addEventListener('online', function () {self._onConnectionFound});
  35. window.addEventListener('offline', function () {self._onConnectionLost});
  36. }
  37. // ------------------------------------------------------------------------
  38. // Internal Methods
  39. // ------------------------------------------------------------------------
  40. /**
  41. *
  42. */
  43. this._upload = function() {
  44. // Cacluate the range for the current chunk
  45. var range_start = this.curr_chunk * this.chunk_size;
  46. var range_end = range_start + this.chunk_size;
  47. // If we've gone beyond the number of chunks then just quit.
  48. if (this.curr_chunk > this.total_chunks) {
  49. this._onChunkComplete();
  50. return;
  51. }
  52. // Prevent range overflow
  53. if (this.range_end > this.file_size) {
  54. this.range_end = this.file_size;
  55. }
  56. var chunk = this.file[this.slice_method](range_start, range_end);
  57. var url = this.options.url + '/' + this.file.name + '/save/' + this.curr_chunk;
  58. this.xhr.open('PUT', url, true);
  59. this.xhr.overrideMimeType('application/octet-stream');
  60. this.xhr.setRequestHeader('Content-Range', 'bytes ' + range_start + '-' + range_end + '/' + this.file_size);
  61. this.xhr.send(chunk);
  62. };
  63. /**
  64. * Converts a file size into a human readable value.
  65. * Borrowed function from:
  66. * http://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable
  67. */
  68. this._getReadableSize = function(bytes, si) {
  69. var thresh = si ? 1000 : 1024;
  70. if(Math.abs(bytes) < thresh) {
  71. return bytes + ' B';
  72. }
  73. var units = si
  74. ? ['kB','MB','GB','TB','PB','EB','ZB','YB']
  75. : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
  76. var u = -1;
  77. do {
  78. bytes /= thresh;
  79. ++u;
  80. }
  81. while(Math.abs(bytes) >= thresh && u < units.length - 1);
  82. return bytes.toFixed(1) + ' ' + units[u];
  83. };
  84. /**
  85. * Queries server to see what chunk the loading left off at.
  86. */
  87. this._checkUpload = function() {
  88. var url = this.options.url + '/' + this.file.name + '/check/';
  89. var self = this;
  90. $.ajax({
  91. url : url,
  92. data : {
  93. 'chunk_size' : this.chunk_size,
  94. },
  95. success : function(data, textStatus, jqXHR) {
  96. if (data['status'] == 'failed') {
  97. self.status = 'failed';
  98. self.updateStatus();
  99. alert(data['message']);
  100. }
  101. else {
  102. self.curr_chunk = data['curr_chunk'];
  103. self.status = 'uploading';
  104. self._upload();
  105. self.updateStatus();
  106. self.updateProgressBar();
  107. }
  108. },
  109. error : function() {
  110. self.curr_chunk = 0;
  111. self._upload();
  112. }
  113. });
  114. }
  115. /**
  116. *
  117. */
  118. this._mergeChunks = function() {
  119. var url = this.options.url + '/' + this.file.name + '/merge';
  120. var self = this;
  121. $.ajax({
  122. url : url,
  123. data : {
  124. 'module' : this.options['module'],
  125. },
  126. success : function(data, textStatus, jqXHR) {
  127. if (data['status'] == 'completed') {
  128. self.file_id = data['file_id'];
  129. self.status = 'completed';
  130. self.updateStatus();
  131. }
  132. else {
  133. self.status = 'failed';
  134. self.updateStatus();
  135. alert(data['message']);
  136. }
  137. },
  138. error : function() {
  139. self.status = 'failed';
  140. self.updateStatus();
  141. }
  142. });
  143. }
  144. // ------------------------------------------------------------------------
  145. // Event Handlers
  146. // ------------------------------------------------------------------------
  147. this._onChunkComplete = function() {
  148. // If the curr_chunk and the total_chunks is the same then
  149. // we've reached the end.
  150. if (this.curr_chunk >= this.total_chunks) {
  151. this.updateStatus();
  152. this._onUploadComplete();
  153. return;
  154. }
  155. // Continue as long as we aren't paused
  156. if (this.status == 'uploading') {
  157. this._upload();
  158. this.curr_chunk++;
  159. this.updateProgressBar();
  160. }
  161. };
  162. /**
  163. *
  164. */
  165. this._onUploadComplete = function() {
  166. this.status = 'merging';
  167. this._mergeChunks();
  168. this.updateStatus();
  169. };
  170. /**
  171. * When a connection has been lost but reistablished then resume uploads.
  172. */
  173. this._onConnectionFound = function() {
  174. this.resume();
  175. };
  176. /**
  177. * When a cnnection has been lost then pause uploads.
  178. */
  179. this._onConnectionLost = function() {
  180. this.pause();
  181. };
  182. // ------------------------------------------------------------------------
  183. // Public Methods
  184. // ------------------------------------------------------------------------
  185. /**
  186. *
  187. */
  188. this.getProgressBar = function() {
  189. var progress_id = this.options['progress'];
  190. return '<div id="' + progress_id + '" class="tripal-uploader-progress-label">0%</div>';
  191. };
  192. /**
  193. *
  194. */
  195. this.getLinks = function() {
  196. var links_id = this.options['links'];
  197. return '<div id="' + links_id + '" class="tripal-uploader-links">0%</div>';
  198. }
  199. this.getCategory = function() {
  200. return this.options['category'];
  201. }
  202. this.getIndex = function() {
  203. return this.options['category'];
  204. }
  205. this.getTName = function() {
  206. return this.options['tname'];
  207. }
  208. this.getFileName = function() {
  209. return this.file.name;
  210. }
  211. /**
  212. *
  213. */
  214. this.getFileSize = function(readable) {
  215. if (readable) {
  216. return this._getReadableSize(this.file.size, true);
  217. }
  218. else {
  219. return this.file.size;
  220. }
  221. };
  222. /**
  223. * Updates the links, status text and status bar.
  224. */
  225. this.updateStatus = function() {
  226. var progress_id = this.options['progress'];
  227. // Add the progress text.
  228. $('#' + progress_id).html('');
  229. if (this.status == 'cancelled') {
  230. $("<span>", {
  231. 'text' : 'Cancelled',
  232. }).appendTo('#' + progress_id)
  233. }
  234. else if (this.status == 'checking') {
  235. $("<span>", {
  236. 'text' : 'Checking...',
  237. }).appendTo('#' + progress_id)
  238. }
  239. else if (this.status == 'merging') {
  240. $("<span>", {
  241. 'text' : 'Processing...',
  242. }).appendTo('#' + progress_id)
  243. }
  244. else if (this.status == 'failed') {
  245. $("<span>", {
  246. 'text' : 'Failed',
  247. }).appendTo('#' + progress_id)
  248. }
  249. else if (this.status == 'completed') {
  250. $("<span>", {
  251. 'text' : 'Complete',
  252. }).appendTo('#' + progress_id)
  253. // Set the parent's target field.
  254. var parent = self.options['parent'];
  255. var tname = self.options['tname'];
  256. var category = self.options['category'];
  257. parent.setTarget(this.file_id, tname, category);
  258. }
  259. else if (this.status == 'paused') {
  260. $("<span>", {
  261. 'text' : 'Paused',
  262. }).appendTo('#' + progress_id)
  263. }
  264. // Add a throbber if the status is uploading
  265. if (this.status == 'uploading' || this.status == 'checking' || this.status == 'merging') {
  266. $("<img>", {
  267. 'src': tripal_path + '/theme/images/ajax-loader.gif',
  268. 'class' : 'tripal-chunked-file-progress-throbber',
  269. }).appendTo('#' + progress_id);
  270. }
  271. // Add the appropriate links.
  272. var links_id = this.options['links'];
  273. var category = this.options['category'];
  274. $('#' + links_id).html('');
  275. if (this.status == 'cancelled') {
  276. $("<a>", {
  277. 'id': links_id + '-pending',
  278. 'class': category + '-pending',
  279. 'href': 'javascript:void(0);',
  280. 'text': 'Restore',
  281. }).appendTo('#' + links_id);
  282. $('#' + links_id + '-pending').click(function() {
  283. self.pending();
  284. })
  285. }
  286. if (this.status == 'pending') {
  287. $("<a>", {
  288. 'id': links_id + '-cancel',
  289. 'class': category + '-cancel',
  290. 'href': 'javascript:void(0);',
  291. 'text': 'Cancel',
  292. }).appendTo('#' + links_id);
  293. $('#' + links_id + '-cancel').click(function() {
  294. self.cancel();
  295. })
  296. }
  297. if (this.status == 'uploading') {
  298. $("<a>", {
  299. 'id': links_id + '-pause',
  300. 'class': category + '-pause',
  301. 'href': 'javascript:void(0);',
  302. 'text': 'Pause',
  303. }).appendTo('#' + links_id);
  304. $('#' + links_id + '-pause').click(function() {
  305. self.pause();
  306. })
  307. }
  308. if (this.status == 'paused') {
  309. $("<a>", {
  310. 'id': links_id + '-resume',
  311. 'class': category + '-resume',
  312. 'href': 'javascript:void(0);',
  313. 'text': 'Resume',
  314. }).appendTo('#' + links_id);
  315. $('#' + links_id + '-resume').click(function() {
  316. self.resume();
  317. })
  318. }
  319. // Add the remove link.
  320. $("<a>", {
  321. 'id': links_id + '-remove',
  322. 'class': category + '-remove',
  323. 'href': 'javascript:void(0);',
  324. 'text': ' Remove',
  325. }).appendTo('#' + links_id);
  326. $('#' + links_id + '-remove').click(function() {
  327. var parent = self.options['parent'];
  328. var index = self.options['index'];
  329. var tname = self.options['tname'];
  330. var category = self.options['category'];
  331. parent.removeFile(category, index);
  332. parent.updateTable(category);
  333. // Unset the parent's target field.
  334. parent.setTarget(null, tname, category);
  335. self.cancel();
  336. })
  337. }
  338. /**
  339. * Updates the status bar progress only.
  340. */
  341. this.updateProgressBar = function() {
  342. var progress_id = this.options['progress'];
  343. var progress = (this.curr_chunk / this.total_chunks) * 100;
  344. var self = this;
  345. // Calculate the amount of the file transferred.
  346. var size_transferred = this.curr_chunk * this.chunk_size;
  347. size_transferred = this._getReadableSize(size_transferred, true);
  348. if (this.status == 'uploading') {
  349. $('#' + progress_id).html('');
  350. $("<span>", {
  351. 'class': 'tripal-chunked-file-progress-label',
  352. 'text': size_transferred,
  353. }).appendTo($("<div>", {
  354. 'id': progress_id + '-bar',
  355. 'class': 'tripal-chunked-file-progress',
  356. 'width': progress + '%'
  357. }).appendTo($("<div>", {
  358. 'id': progress_id + '-box',
  359. 'class': 'tripal-chunked-file-progress',
  360. }).appendTo('#' + progress_id)));
  361. }
  362. if (this.status == 'uploading' || this.status == 'checking' || this.status == 'merging') {
  363. $("<img>", {
  364. 'src': tripal_path + '/theme/images/ajax-loader.gif',
  365. 'class' : 'tripal-chunked-file-progress-throbber',
  366. }).appendTo('#' + progress_id);
  367. }
  368. };
  369. /**
  370. *
  371. */
  372. this.cancel = function() {
  373. this.status = 'cancelled';
  374. this.updateStatus();
  375. }
  376. /**
  377. *
  378. */
  379. this.pending = function() {
  380. this.status = 'pending';
  381. this.updateStatus();
  382. }
  383. /**
  384. *
  385. */
  386. this.start = function() {
  387. if (this.status == 'pending') {
  388. // Change the status to checking. The first thing we'll
  389. // do is see what's already present on the server.
  390. this.status = 'checking';
  391. this.curr_chunk = this._checkUpload();
  392. }
  393. };
  394. /**
  395. *
  396. */
  397. this.pause = function() {
  398. this.status = 'paused';
  399. this.updateStatus();
  400. };
  401. /**
  402. *
  403. */
  404. this.resume = function() {
  405. this.status = 'uploading';
  406. this.updateStatus();
  407. this.updateProgressBar();
  408. this._upload();
  409. };
  410. };
  411. // Export the objects to the window for use in other JS files.
  412. window.TripalUploadFile = TripalUploadFile;
  413. })(jQuery);