filelist.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*
  2. * Copyright (c) 2014
  3. *
  4. * This file is licensed under the Affero General Public License version 3
  5. * or later.
  6. *
  7. * See the COPYING-README file.
  8. *
  9. */
  10. (function() {
  11. var DELETED_REGEXP = new RegExp(/^(.+)\.d[0-9]+$/);
  12. /**
  13. * Convert a file name in the format filename.d12345 to the real file name.
  14. * This will use basename.
  15. * The name will not be changed if it has no ".d12345" suffix.
  16. * @param {String} name file name
  17. * @return {String} converted file name
  18. */
  19. function getDeletedFileName(name) {
  20. name = OC.basename(name);
  21. var match = DELETED_REGEXP.exec(name);
  22. if (match && match.length > 1) {
  23. name = match[1];
  24. }
  25. return name;
  26. }
  27. /**
  28. * @class OCA.Trashbin.FileList
  29. * @augments OCA.Files.FileList
  30. * @classdesc List of deleted files
  31. *
  32. * @param $el container element with existing markup for the #controls
  33. * and a table
  34. * @param [options] map of options
  35. */
  36. var FileList = function($el, options) {
  37. this.initialize($el, options);
  38. };
  39. FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
  40. /** @lends OCA.Trashbin.FileList.prototype */ {
  41. id: 'trashbin',
  42. appName: t('files_trashbin', 'Deleted files'),
  43. /**
  44. * @private
  45. */
  46. initialize: function() {
  47. var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments);
  48. this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this));
  49. this.setSort('mtime', 'desc');
  50. /**
  51. * Override crumb making to add "Deleted Files" entry
  52. * and convert files with ".d" extensions to a more
  53. * user friendly name.
  54. */
  55. this.breadcrumb._makeCrumbs = function() {
  56. var parts = OCA.Files.BreadCrumb.prototype._makeCrumbs.apply(this, arguments);
  57. for (var i = 1; i < parts.length; i++) {
  58. parts[i].name = getDeletedFileName(parts[i].name);
  59. }
  60. return parts;
  61. };
  62. OC.Plugins.attach('OCA.Trashbin.FileList', this);
  63. return result;
  64. },
  65. /**
  66. * Override to only return read permissions
  67. */
  68. getDirectoryPermissions: function() {
  69. return OC.PERMISSION_READ | OC.PERMISSION_DELETE;
  70. },
  71. _setCurrentDir: function(targetDir) {
  72. OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments);
  73. var baseDir = OC.basename(targetDir);
  74. if (baseDir !== '') {
  75. this.setPageTitle(getDeletedFileName(baseDir));
  76. }
  77. },
  78. _createRow: function() {
  79. // FIXME: MEGAHACK until we find a better solution
  80. var tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments);
  81. tr.find('td.filesize').remove();
  82. return tr;
  83. },
  84. _renderRow: function(fileData, options) {
  85. options = options || {};
  86. // make a copy to avoid changing original object
  87. fileData = _.extend({}, fileData);
  88. var dir = this.getCurrentDirectory();
  89. var dirListing = dir !== '' && dir !== '/';
  90. // show deleted time as mtime
  91. if (fileData.mtime) {
  92. fileData.mtime = parseInt(fileData.mtime, 10);
  93. }
  94. if (!dirListing) {
  95. fileData.displayName = fileData.name;
  96. fileData.name = fileData.name + '.d' + Math.floor(fileData.mtime / 1000);
  97. }
  98. return OCA.Files.FileList.prototype._renderRow.call(this, fileData, options);
  99. },
  100. getAjaxUrl: function(action, params) {
  101. var q = '';
  102. if (params) {
  103. q = '?' + OC.buildQueryString(params);
  104. }
  105. return OC.filePath('files_trashbin', 'ajax', action + '.php') + q;
  106. },
  107. setupUploadEvents: function() {
  108. // override and do nothing
  109. },
  110. linkTo: function(dir){
  111. return OC.linkTo('files', 'index.php')+"?view=trashbin&dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
  112. },
  113. elementToFile: function($el) {
  114. var fileInfo = OCA.Files.FileList.prototype.elementToFile($el);
  115. if (this.getCurrentDirectory() === '/') {
  116. fileInfo.displayName = getDeletedFileName(fileInfo.name);
  117. }
  118. // no size available
  119. delete fileInfo.size;
  120. return fileInfo;
  121. },
  122. updateEmptyContent: function(){
  123. var exists = this.$fileList.find('tr:first').exists();
  124. this.$el.find('#emptycontent').toggleClass('hidden', exists);
  125. this.$el.find('#filestable th').toggleClass('hidden', !exists);
  126. },
  127. _removeCallback: function(result) {
  128. if (result.status !== 'success') {
  129. OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
  130. }
  131. var files = result.data.success;
  132. var $el;
  133. for (var i = 0; i < files.length; i++) {
  134. $el = this.remove(OC.basename(files[i].filename), {updateSummary: false});
  135. this.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')});
  136. }
  137. this.fileSummary.update();
  138. this.updateEmptyContent();
  139. this.enableActions();
  140. },
  141. _onClickRestoreSelected: function(event) {
  142. event.preventDefault();
  143. var self = this;
  144. var allFiles = this.$el.find('.select-all').is(':checked');
  145. var files = [];
  146. var params = {};
  147. this.disableActions();
  148. if (allFiles) {
  149. this.showMask();
  150. params = {
  151. allfiles: true,
  152. dir: this.getCurrentDirectory()
  153. };
  154. }
  155. else {
  156. files = _.pluck(this.getSelectedFiles(), 'name');
  157. for (var i = 0; i < files.length; i++) {
  158. var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete");
  159. deleteAction.removeClass('icon-delete').addClass('icon-loading-small');
  160. }
  161. params = {
  162. files: JSON.stringify(files),
  163. dir: this.getCurrentDirectory()
  164. };
  165. }
  166. $.post(OC.filePath('files_trashbin', 'ajax', 'undelete.php'),
  167. params,
  168. function(result) {
  169. if (allFiles) {
  170. if (result.status !== 'success') {
  171. OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
  172. }
  173. self.hideMask();
  174. // simply remove all files
  175. self.setFiles([]);
  176. self.enableActions();
  177. }
  178. else {
  179. self._removeCallback(result);
  180. }
  181. }
  182. );
  183. },
  184. _onClickDeleteSelected: function(event) {
  185. event.preventDefault();
  186. var self = this;
  187. var allFiles = this.$el.find('.select-all').is(':checked');
  188. var files = [];
  189. var params = {};
  190. if (allFiles) {
  191. params = {
  192. allfiles: true,
  193. dir: this.getCurrentDirectory()
  194. };
  195. }
  196. else {
  197. files = _.pluck(this.getSelectedFiles(), 'name');
  198. params = {
  199. files: JSON.stringify(files),
  200. dir: this.getCurrentDirectory()
  201. };
  202. }
  203. this.disableActions();
  204. if (allFiles) {
  205. this.showMask();
  206. }
  207. else {
  208. for (var i = 0; i < files.length; i++) {
  209. var deleteAction = this.findFileEl(files[i]).children("td.date").children(".action.delete");
  210. deleteAction.removeClass('icon-delete').addClass('icon-loading-small');
  211. }
  212. }
  213. $.post(OC.filePath('files_trashbin', 'ajax', 'delete.php'),
  214. params,
  215. function(result) {
  216. if (allFiles) {
  217. if (result.status !== 'success') {
  218. OC.dialogs.alert(result.data.message, t('files_trashbin', 'Error'));
  219. }
  220. self.hideMask();
  221. // simply remove all files
  222. self.setFiles([]);
  223. self.enableActions();
  224. }
  225. else {
  226. self._removeCallback(result);
  227. }
  228. }
  229. );
  230. },
  231. _onClickFile: function(event) {
  232. var mime = $(this).parent().parent().data('mime');
  233. if (mime !== 'httpd/unix-directory') {
  234. event.preventDefault();
  235. }
  236. return OCA.Files.FileList.prototype._onClickFile.apply(this, arguments);
  237. },
  238. generatePreviewUrl: function(urlSpec) {
  239. return OC.generateUrl('/apps/files_trashbin/ajax/preview.php?') + $.param(urlSpec);
  240. },
  241. getDownloadUrl: function() {
  242. // no downloads
  243. return '#';
  244. },
  245. enableActions: function() {
  246. this.$el.find('.action').css('display', 'inline');
  247. this.$el.find('input:checkbox').removeClass('u-hidden');
  248. },
  249. disableActions: function() {
  250. this.$el.find('.action').css('display', 'none');
  251. this.$el.find('input:checkbox').addClass('u-hidden');
  252. },
  253. updateStorageStatistics: function() {
  254. // no op because the trashbin doesn't have
  255. // storage info like free space / used space
  256. },
  257. isSelectedDeletable: function() {
  258. return true;
  259. },
  260. /**
  261. * Reloads the file list using ajax call
  262. *
  263. * @return ajax call object
  264. */
  265. reload: function() {
  266. this._selectedFiles = {};
  267. this._selectionSummary.clear();
  268. this.$el.find('.select-all').prop('checked', false);
  269. this.showMask();
  270. if (this._reloadCall) {
  271. this._reloadCall.abort();
  272. }
  273. this._reloadCall = $.ajax({
  274. url: this.getAjaxUrl('list'),
  275. data: {
  276. dir : this.getCurrentDirectory(),
  277. sort: this._sort,
  278. sortdirection: this._sortDirection
  279. }
  280. });
  281. var callBack = this.reloadCallback.bind(this);
  282. return this._reloadCall.then(callBack, callBack);
  283. },
  284. reloadCallback: function(result) {
  285. delete this._reloadCall;
  286. this.hideMask();
  287. if (!result || result.status === 'error') {
  288. // if the error is not related to folder we're trying to load, reload the page to handle logout etc
  289. if (result.data.error === 'authentication_error' ||
  290. result.data.error === 'token_expired' ||
  291. result.data.error === 'application_not_enabled'
  292. ) {
  293. OC.redirect(OC.generateUrl('apps/files'));
  294. }
  295. OC.Notification.show(result.data.message);
  296. return false;
  297. }
  298. if (result.status === 401) {
  299. return false;
  300. }
  301. // Firewall Blocked request?
  302. if (result.status === 403) {
  303. // Go home
  304. this.changeDirectory('/');
  305. OC.Notification.show(t('files', 'This operation is forbidden'));
  306. return false;
  307. }
  308. // Did share service die or something else fail?
  309. if (result.status === 500) {
  310. // Go home
  311. this.changeDirectory('/');
  312. OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator'));
  313. return false;
  314. }
  315. if (result.status === 404) {
  316. // go back home
  317. this.changeDirectory('/');
  318. return false;
  319. }
  320. // aborted ?
  321. if (result.status === 0){
  322. return true;
  323. }
  324. this.setFiles(result.data.files);
  325. return true;
  326. },
  327. });
  328. OCA.Trashbin.FileList = FileList;
  329. })();