filelist.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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. var FILENAME_PROP = '{http://nextcloud.org/ns}trashbin-filename';
  13. var DELETION_TIME_PROP = '{http://nextcloud.org/ns}trashbin-deletion-time';
  14. /**
  15. * Convert a file name in the format filename.d12345 to the real file name.
  16. * This will use basename.
  17. * The name will not be changed if it has no ".d12345" suffix.
  18. * @param {String} name file name
  19. * @return {String} converted file name
  20. */
  21. function getDeletedFileName(name) {
  22. name = OC.basename(name);
  23. var match = DELETED_REGEXP.exec(name);
  24. if (match && match.length > 1) {
  25. name = match[1];
  26. }
  27. return name;
  28. }
  29. /**
  30. * @class OCA.Trashbin.FileList
  31. * @augments OCA.Files.FileList
  32. * @classdesc List of deleted files
  33. *
  34. * @param $el container element with existing markup for the #controls
  35. * and a table
  36. * @param [options] map of options
  37. */
  38. var FileList = function($el, options) {
  39. this.client = options.client;
  40. this.initialize($el, options);
  41. };
  42. FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
  43. /** @lends OCA.Trashbin.FileList.prototype */ {
  44. id: 'trashbin',
  45. appName: t('files_trashbin', 'Deleted files'),
  46. /** @type {OC.Files.Client} */
  47. client: null,
  48. /**
  49. * @private
  50. */
  51. initialize: function() {
  52. this.client.addFileInfoParser(function(response, data) {
  53. var props = response.propStat[0].properties;
  54. return {
  55. displayName: props[FILENAME_PROP],
  56. mtime: parseInt(props[DELETION_TIME_PROP], 10) * 1000,
  57. hasPreview: true,
  58. path: data.path.substr(6), // remove leading /trash
  59. }
  60. });
  61. var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments);
  62. this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this));
  63. this.setSort('mtime', 'desc');
  64. /**
  65. * Override crumb making to add "Deleted Files" entry
  66. * and convert files with ".d" extensions to a more
  67. * user friendly name.
  68. */
  69. this.breadcrumb._makeCrumbs = function() {
  70. var parts = OCA.Files.BreadCrumb.prototype._makeCrumbs.apply(this, arguments);
  71. for (var i = 1; i < parts.length; i++) {
  72. parts[i].name = getDeletedFileName(parts[i].name);
  73. }
  74. return parts;
  75. };
  76. OC.Plugins.attach('OCA.Trashbin.FileList', this);
  77. return result;
  78. },
  79. /**
  80. * Override to only return read permissions
  81. */
  82. getDirectoryPermissions: function() {
  83. return OC.PERMISSION_READ | OC.PERMISSION_DELETE;
  84. },
  85. _setCurrentDir: function(targetDir) {
  86. OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments);
  87. var baseDir = OC.basename(targetDir);
  88. if (baseDir !== '') {
  89. this.setPageTitle(getDeletedFileName(baseDir));
  90. }
  91. },
  92. _createRow: function() {
  93. // FIXME: MEGAHACK until we find a better solution
  94. var tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments);
  95. tr.find('td.filesize').remove();
  96. return tr;
  97. },
  98. getAjaxUrl: function(action, params) {
  99. var q = '';
  100. if (params) {
  101. q = '?' + OC.buildQueryString(params);
  102. }
  103. return OC.filePath('files_trashbin', 'ajax', action + '.php') + q;
  104. },
  105. setupUploadEvents: function() {
  106. // override and do nothing
  107. },
  108. linkTo: function(dir){
  109. return OC.linkTo('files', 'index.php')+"?view=trashbin&dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
  110. },
  111. elementToFile: function($el) {
  112. var fileInfo = OCA.Files.FileList.prototype.elementToFile($el);
  113. if (this.getCurrentDirectory() === '/') {
  114. fileInfo.displayName = getDeletedFileName(fileInfo.name);
  115. }
  116. // no size available
  117. delete fileInfo.size;
  118. return fileInfo;
  119. },
  120. updateEmptyContent: function(){
  121. var exists = this.$fileList.find('tr:first').exists();
  122. this.$el.find('#emptycontent').toggleClass('hidden', exists);
  123. this.$el.find('#filestable th').toggleClass('hidden', !exists);
  124. },
  125. _removeCallback: function(files) {
  126. var $el;
  127. for (var i = 0; i < files.length; i++) {
  128. $el = this.remove(OC.basename(files[i]), {updateSummary: false});
  129. this.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')});
  130. }
  131. this.fileSummary.update();
  132. this.updateEmptyContent();
  133. },
  134. _onClickRestoreSelected: function(event) {
  135. event.preventDefault();
  136. var self = this;
  137. var files = _.pluck(this.getSelectedFiles(), 'name');
  138. for (var i = 0; i < files.length; i++) {
  139. var tr = this.findFileEl(files[i]);
  140. this.showFileBusyState(tr, true);
  141. }
  142. this.fileMultiSelectMenu.toggleLoading('restore', true);
  143. var restorePromises = files.map(function(file) {
  144. return self.client.move(OC.joinPaths('trash', self.getCurrentDirectory(), file), OC.joinPaths('restore', file), true)
  145. .then(
  146. function() {
  147. self._removeCallback([file]);
  148. }
  149. );
  150. });
  151. return Promise.all(restorePromises).then(
  152. function() {
  153. self.fileMultiSelectMenu.toggleLoading('restore', false);
  154. },
  155. function() {
  156. OC.Notification.show(t('files_trashbin', 'Error while restoring files from trashbin'));
  157. }
  158. );
  159. },
  160. _onClickDeleteSelected: function(event) {
  161. event.preventDefault();
  162. var self = this;
  163. var allFiles = this.$el.find('.select-all').is(':checked');
  164. var files = _.pluck(this.getSelectedFiles(), 'name');
  165. for (var i = 0; i < files.length; i++) {
  166. var tr = this.findFileEl(files[i]);
  167. this.showFileBusyState(tr, true);
  168. }
  169. if (allFiles) {
  170. return this.client.remove(OC.joinPaths('trash', this.getCurrentDirectory()))
  171. .then(
  172. function() {
  173. self.hideMask();
  174. self.setFiles([]);
  175. },
  176. function() {
  177. OC.Notification.show(t('files_trashbin', 'Error while emptying trashbin'));
  178. }
  179. );
  180. } else {
  181. this.fileMultiSelectMenu.toggleLoading('delete', true);
  182. var deletePromises = files.map(function(file) {
  183. return self.client.remove(OC.joinPaths('trash', self.getCurrentDirectory(), file))
  184. .then(
  185. function() {
  186. self._removeCallback([file]);
  187. }
  188. );
  189. });
  190. return Promise.all(deletePromises).then(
  191. function() {
  192. self.fileMultiSelectMenu.toggleLoading('delete', false);
  193. },
  194. function() {
  195. OC.Notification.show(t('files_trashbin', 'Error while removing files from trashbin'));
  196. }
  197. );
  198. }
  199. },
  200. _onClickFile: function(event) {
  201. var mime = $(this).parent().parent().data('mime');
  202. if (mime !== 'httpd/unix-directory') {
  203. event.preventDefault();
  204. }
  205. return OCA.Files.FileList.prototype._onClickFile.apply(this, arguments);
  206. },
  207. generatePreviewUrl: function(urlSpec) {
  208. return OC.generateUrl('/apps/files_trashbin/preview?') + $.param(urlSpec);
  209. },
  210. getDownloadUrl: function() {
  211. // no downloads
  212. return '#';
  213. },
  214. updateStorageStatistics: function() {
  215. // no op because the trashbin doesn't have
  216. // storage info like free space / used space
  217. },
  218. isSelectedDeletable: function() {
  219. return true;
  220. },
  221. /**
  222. * Returns list of webdav properties to request
  223. */
  224. _getWebdavProperties: function() {
  225. return [FILENAME_PROP, DELETION_TIME_PROP].concat(this.filesClient.getPropfindProperties());
  226. },
  227. /**
  228. * Reloads the file list using ajax call
  229. *
  230. * @return ajax call object
  231. */
  232. reload: function() {
  233. this._selectedFiles = {};
  234. this._selectionSummary.clear();
  235. this.$el.find('.select-all').prop('checked', false);
  236. this.showMask();
  237. if (this._reloadCall) {
  238. this._reloadCall.abort();
  239. }
  240. this._reloadCall = this.client.getFolderContents(
  241. 'trash/' + this.getCurrentDirectory(), {
  242. includeParent: false,
  243. properties: this._getWebdavProperties()
  244. }
  245. );
  246. var callBack = this.reloadCallback.bind(this);
  247. return this._reloadCall.then(callBack, callBack);
  248. },
  249. reloadCallback: function(status, result) {
  250. delete this._reloadCall;
  251. this.hideMask();
  252. if (status === 401) {
  253. return false;
  254. }
  255. // Firewall Blocked request?
  256. if (status === 403) {
  257. // Go home
  258. this.changeDirectory('/');
  259. OC.Notification.show(t('files', 'This operation is forbidden'));
  260. return false;
  261. }
  262. // Did share service die or something else fail?
  263. if (status === 500) {
  264. // Go home
  265. this.changeDirectory('/');
  266. OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator'));
  267. return false;
  268. }
  269. if (status === 404) {
  270. // go back home
  271. this.changeDirectory('/');
  272. return false;
  273. }
  274. // aborted ?
  275. if (status === 0){
  276. return true;
  277. }
  278. this.setFiles(result);
  279. return true;
  280. },
  281. });
  282. OCA.Trashbin.FileList = FileList;
  283. })();