TripalUploadFile.js 13 KB

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