TripalUploadFile.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  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/' + this.options['module'];
  120. var self = this;
  121. $.ajax({
  122. url : url,
  123. success : function(data, textStatus, jqXHR) {
  124. if (data['status'] == 'completed') {
  125. self.file_id = data['file_id'];
  126. self.status = 'completed';
  127. self.updateStatus();
  128. }
  129. else {
  130. self.status = 'failed';
  131. self.updateStatus();
  132. alert(data['message']);
  133. }
  134. },
  135. error : function() {
  136. self.status = 'failed';
  137. self.updateStatus();
  138. }
  139. });
  140. }
  141. // ------------------------------------------------------------------------
  142. // Event Handlers
  143. // ------------------------------------------------------------------------
  144. this._onChunkComplete = function() {
  145. // If the curr_chunk and the total_chunks is the same then
  146. // we've reached the end.
  147. if (this.curr_chunk >= this.total_chunks) {
  148. this.updateStatus();
  149. this._onUploadComplete();
  150. return;
  151. }
  152. // Continue as long as we aren't paused
  153. if (this.status == 'uploading') {
  154. this._upload();
  155. this.curr_chunk++;
  156. this.updateProgressBar();
  157. }
  158. };
  159. /**
  160. *
  161. */
  162. this._onUploadComplete = function() {
  163. this.status = 'merging';
  164. this._mergeChunks();
  165. this.updateStatus();
  166. };
  167. /**
  168. * When a connection has been lost but reistablished then resume uploads.
  169. */
  170. this._onConnectionFound = function() {
  171. this.resume();
  172. };
  173. /**
  174. * When a cnnection has been lost then pause uploads.
  175. */
  176. this._onConnectionLost = function() {
  177. this.pause();
  178. };
  179. // ------------------------------------------------------------------------
  180. // Public Methods
  181. // ------------------------------------------------------------------------
  182. /**
  183. *
  184. */
  185. this.getProgressBar = function() {
  186. var progress_id = this.options['progress'];
  187. return '<div id="' + progress_id + '" class="tripal-uploader-progress-label">0%</div>';
  188. };
  189. /**
  190. *
  191. */
  192. this.getLinks = function() {
  193. var links_id = this.options['links'];
  194. return '<div id="' + links_id + '" class="tripal-uploader-links">0%</div>';
  195. }
  196. this.getCategory = function() {
  197. return this.options['category'];
  198. }
  199. this.getIndex = function() {
  200. return this.options['category'];
  201. }
  202. this.getTName = function() {
  203. return this.options['tname'];
  204. }
  205. this.getFileName = function() {
  206. return this.file.name;
  207. }
  208. /**
  209. *
  210. */
  211. this.getFileSize = function(readable) {
  212. if (readable) {
  213. return this._getReadableSize(this.file.size, true);
  214. }
  215. else {
  216. return this.file.size;
  217. }
  218. };
  219. /**
  220. * Updates the links, status text and status bar.
  221. */
  222. this.updateStatus = function() {
  223. var progress_id = this.options['progress'];
  224. // Add the progress text.
  225. $('#' + progress_id).html('');
  226. if (this.status == 'cancelled') {
  227. $("<span>", {
  228. 'text' : 'Cancelled',
  229. }).appendTo('#' + progress_id)
  230. }
  231. else if (this.status == 'checking') {
  232. $("<span>", {
  233. 'text' : 'Checking...',
  234. }).appendTo('#' + progress_id)
  235. }
  236. else if (this.status == 'merging') {
  237. $("<span>", {
  238. 'text' : 'Processing...',
  239. }).appendTo('#' + progress_id)
  240. }
  241. else if (this.status == 'failed') {
  242. $("<span>", {
  243. 'text' : 'Failed',
  244. }).appendTo('#' + progress_id)
  245. }
  246. else if (this.status == 'completed') {
  247. $("<span>", {
  248. 'text' : 'Complete',
  249. }).appendTo('#' + progress_id)
  250. // Set the parent's target field.
  251. var parent = self.options['parent'];
  252. var tname = self.options['tname'];
  253. var category = self.options['category'];
  254. parent.setTarget(this.file_id, tname, category);
  255. }
  256. else if (this.status == 'paused') {
  257. $("<span>", {
  258. 'text' : 'Paused',
  259. }).appendTo('#' + progress_id)
  260. }
  261. // Add a throbber if the status is uploading
  262. if (this.status == 'uploading' || this.status == 'checking' || this.status == 'merging') {
  263. $("<img>", {
  264. 'src': tripal_path + '/theme/images/ajax-loader.gif',
  265. 'class' : 'tripal-chunked-file-progress-throbber',
  266. }).appendTo('#' + progress_id);
  267. }
  268. // Add the appropriate links.
  269. var links_id = this.options['links'];
  270. var category = this.options['category'];
  271. $('#' + links_id).html('');
  272. if (this.status == 'cancelled') {
  273. $("<a>", {
  274. 'id': links_id + '-pending',
  275. 'class': category + '-pending',
  276. 'href': 'javascript:void(0);',
  277. 'text': 'Restore',
  278. }).appendTo('#' + links_id);
  279. $('#' + links_id + '-pending').click(function() {
  280. self.pending();
  281. })
  282. }
  283. if (this.status == 'pending') {
  284. $("<a>", {
  285. 'id': links_id + '-cancel',
  286. 'class': category + '-cancel',
  287. 'href': 'javascript:void(0);',
  288. 'text': 'Cancel',
  289. }).appendTo('#' + links_id);
  290. $('#' + links_id + '-cancel').click(function() {
  291. self.cancel();
  292. })
  293. }
  294. if (this.status == 'uploading') {
  295. $("<a>", {
  296. 'id': links_id + '-pause',
  297. 'class': category + '-pause',
  298. 'href': 'javascript:void(0);',
  299. 'text': 'Pause',
  300. }).appendTo('#' + links_id);
  301. $('#' + links_id + '-pause').click(function() {
  302. self.pause();
  303. })
  304. }
  305. if (this.status == 'paused') {
  306. $("<a>", {
  307. 'id': links_id + '-resume',
  308. 'class': category + '-resume',
  309. 'href': 'javascript:void(0);',
  310. 'text': 'Resume',
  311. }).appendTo('#' + links_id);
  312. $('#' + links_id + '-resume').click(function() {
  313. self.resume();
  314. })
  315. }
  316. // Add the remove link.
  317. $("<a>", {
  318. 'id': links_id + '-remove',
  319. 'class': category + '-remove',
  320. 'href': 'javascript:void(0);',
  321. 'text': ' Remove',
  322. }).appendTo('#' + links_id);
  323. $('#' + links_id + '-remove').click(function() {
  324. var parent = self.options['parent'];
  325. var index = self.options['index'];
  326. var tname = self.options['tname'];
  327. var category = self.options['category'];
  328. parent.removeFile(category, index);
  329. parent.updateTable(category);
  330. // Unset the parent's target field.
  331. parent.setTarget(null, tname, category);
  332. self.cancel();
  333. })
  334. }
  335. /**
  336. * Updates the status bar progress only.
  337. */
  338. this.updateProgressBar = function() {
  339. var progress_id = this.options['progress'];
  340. var progress = (this.curr_chunk / this.total_chunks) * 100;
  341. var self = this;
  342. // Calculate the amount of the file transferred.
  343. var size_transferred = this.curr_chunk * this.chunk_size;
  344. size_transferred = this._getReadableSize(size_transferred, true);
  345. if (this.status == 'uploading') {
  346. $('#' + progress_id).html('');
  347. $("<span>", {
  348. 'class': 'tripal-chunked-file-progress-label',
  349. 'text': size_transferred,
  350. }).appendTo($("<div>", {
  351. 'id': progress_id + '-bar',
  352. 'class': 'tripal-chunked-file-progress',
  353. 'width': progress + '%'
  354. }).appendTo($("<div>", {
  355. 'id': progress_id + '-box',
  356. 'class': 'tripal-chunked-file-progress',
  357. }).appendTo('#' + progress_id)));
  358. }
  359. if (this.status == 'uploading' || this.status == 'checking' || this.status == 'merging') {
  360. $("<img>", {
  361. 'src': tripal_path + '/theme/images/ajax-loader.gif',
  362. 'class' : 'tripal-chunked-file-progress-throbber',
  363. }).appendTo('#' + progress_id);
  364. }
  365. };
  366. /**
  367. *
  368. */
  369. this.cancel = function() {
  370. this.status = 'cancelled';
  371. this.updateStatus();
  372. }
  373. /**
  374. *
  375. */
  376. this.pending = function() {
  377. this.status = 'pending';
  378. this.updateStatus();
  379. }
  380. /**
  381. *
  382. */
  383. this.start = function() {
  384. if (this.status == 'pending') {
  385. // Change the status to checking. The first thing we'll
  386. // do is see what's already present on the server.
  387. this.status = 'checking';
  388. this.curr_chunk = this._checkUpload();
  389. }
  390. };
  391. /**
  392. *
  393. */
  394. this.pause = function() {
  395. this.status = 'paused';
  396. this.updateStatus();
  397. };
  398. /**
  399. *
  400. */
  401. this.resume = function() {
  402. this.status = 'uploading';
  403. this.updateStatus();
  404. this.updateProgressBar();
  405. this._upload();
  406. };
  407. };
  408. // Export the objects to the window for use in other JS files.
  409. window.TripalUploadFile = TripalUploadFile;
  410. })(jQuery);