1
0

files.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  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. /* global getURLParameter */
  11. /**
  12. * Utility class for file related operations
  13. */
  14. (function() {
  15. var Files = {
  16. // file space size sync
  17. _updateStorageStatistics: function(currentDir) {
  18. var state = Files.updateStorageStatistics;
  19. if (state.dir){
  20. if (state.dir === currentDir) {
  21. return;
  22. }
  23. // cancel previous call, as it was for another dir
  24. state.call.abort();
  25. }
  26. state.dir = currentDir;
  27. state.call = $.getJSON(OC.filePath('files','ajax','getstoragestats.php') + '?dir=' + encodeURIComponent(currentDir),function(response) {
  28. state.dir = null;
  29. state.call = null;
  30. Files.updateMaxUploadFilesize(response);
  31. });
  32. },
  33. // update quota
  34. updateStorageQuotas: function() {
  35. Files._updateStorageQuotasThrottled();
  36. },
  37. _updateStorageQuotas: function() {
  38. var state = Files.updateStorageQuotas;
  39. state.call = $.getJSON(OC.filePath('files','ajax','getstoragestats.php'),function(response) {
  40. Files.updateQuota(response);
  41. });
  42. },
  43. /**
  44. * Update storage statistics such as free space, max upload,
  45. * etc based on the given directory.
  46. *
  47. * Note this function is debounced to avoid making too
  48. * many ajax calls in a row.
  49. *
  50. * @param dir directory
  51. * @param force whether to force retrieving
  52. */
  53. updateStorageStatistics: function(dir, force) {
  54. if (!OC.currentUser) {
  55. return;
  56. }
  57. if (force) {
  58. Files._updateStorageStatistics(dir);
  59. }
  60. else {
  61. Files._updateStorageStatisticsDebounced(dir);
  62. }
  63. },
  64. updateMaxUploadFilesize:function(response) {
  65. if (response === undefined) {
  66. return;
  67. }
  68. if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) {
  69. $('#free_space').val(response.data.freeSpace);
  70. $('#upload.button').attr('data-original-title', response.data.maxHumanFilesize);
  71. $('#usedSpacePercent').val(response.data.usedSpacePercent);
  72. $('#owner').val(response.data.owner);
  73. $('#ownerDisplayName').val(response.data.ownerDisplayName);
  74. Files.displayStorageWarnings();
  75. OCA.Files.App.fileList._updateDirectoryPermissions();
  76. }
  77. if (response[0] === undefined) {
  78. return;
  79. }
  80. if (response[0].uploadMaxFilesize !== undefined) {
  81. $('#upload.button').attr('data-original-title', response[0].maxHumanFilesize);
  82. $('#usedSpacePercent').val(response[0].usedSpacePercent);
  83. Files.displayStorageWarnings();
  84. }
  85. },
  86. updateQuota:function(response) {
  87. if (response === undefined) {
  88. return;
  89. }
  90. if (response.data !== undefined
  91. && response.data.quota !== undefined
  92. && response.data.used !== undefined
  93. && response.data.usedSpacePercent !== undefined) {
  94. var humanUsed = OC.Util.humanFileSize(response.data.used, true);
  95. var humanQuota = OC.Util.humanFileSize(response.data.quota, true);
  96. if (response.data.quota > 0) {
  97. $('#quota').attr('data-original-title', Math.floor(response.data.used/response.data.quota*1000)/10 + '%');
  98. $('#quota progress').val(response.data.usedSpacePercent);
  99. $('#quotatext').text(t('files', '{used} of {quota} used', {used: humanUsed, quota: humanQuota}));
  100. } else {
  101. $('#quotatext').text(t('files', '{used} used', {used: humanUsed}));
  102. }
  103. if (response.data.usedSpacePercent > 80) {
  104. $('#quota progress').addClass('warn');
  105. } else {
  106. $('#quota progress').removeClass('warn');
  107. }
  108. }
  109. },
  110. /**
  111. * Fix path name by removing double slash at the beginning, if any
  112. */
  113. fixPath: function(fileName) {
  114. if (fileName.substr(0, 2) == '//') {
  115. return fileName.substr(1);
  116. }
  117. return fileName;
  118. },
  119. /**
  120. * Checks whether the given file name is valid.
  121. * @param name file name to check
  122. * @return true if the file name is valid.
  123. * Throws a string exception with an error message if
  124. * the file name is not valid
  125. *
  126. * NOTE: This function is duplicated in the filepicker inside core/src/OC/dialogs.js
  127. */
  128. isFileNameValid: function (name) {
  129. var trimmedName = name.trim();
  130. if (trimmedName === '.' || trimmedName === '..')
  131. {
  132. throw t('files', '"{name}" is an invalid file name.', {name: name});
  133. } else if (trimmedName.length === 0) {
  134. throw t('files', 'File name cannot be empty.');
  135. } else if (trimmedName.indexOf('/') !== -1) {
  136. throw t('files', '"/" is not allowed inside a file name.');
  137. } else if (!!(trimmedName.match(OC.config.blacklist_files_regex))) {
  138. throw t('files', '"{name}" is not an allowed filetype', {name: name});
  139. }
  140. return true;
  141. },
  142. displayStorageWarnings: function() {
  143. if (!OC.Notification.isHidden()) {
  144. return;
  145. }
  146. var usedSpacePercent = $('#usedSpacePercent').val(),
  147. owner = $('#owner').val(),
  148. ownerDisplayName = $('#ownerDisplayName').val();
  149. if (usedSpacePercent > 98) {
  150. if (owner !== OC.getCurrentUser().uid) {
  151. OC.Notification.show(t('files', 'Storage of {owner} is full, files can not be updated or synced anymore!',
  152. {owner: ownerDisplayName}), {type: 'error'}
  153. );
  154. return;
  155. }
  156. OC.Notification.show(t('files',
  157. 'Your storage is full, files can not be updated or synced anymore!'),
  158. {type : 'error'}
  159. );
  160. return;
  161. }
  162. if (usedSpacePercent > 90) {
  163. if (owner !== OC.getCurrentUser().uid) {
  164. OC.Notification.show(t('files', 'Storage of {owner} is almost full ({usedSpacePercent}%)',
  165. {
  166. usedSpacePercent: usedSpacePercent,
  167. owner: ownerDisplayName
  168. }),
  169. {
  170. type: 'error'
  171. }
  172. );
  173. return;
  174. }
  175. OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)',
  176. {usedSpacePercent: usedSpacePercent}),
  177. {type : 'error'}
  178. );
  179. }
  180. },
  181. /**
  182. * Returns the download URL of the given file(s)
  183. * @param {string} filename string or array of file names to download
  184. * @param {string} [dir] optional directory in which the file name is, defaults to the current directory
  185. * @param {bool} [isDir=false] whether the given filename is a directory and might need a special URL
  186. */
  187. getDownloadUrl: function(filename, dir, isDir) {
  188. if (!_.isArray(filename) && !isDir) {
  189. var pathSections = dir.split('/');
  190. pathSections.push(filename);
  191. var encodedPath = '';
  192. _.each(pathSections, function(section) {
  193. if (section !== '') {
  194. encodedPath += '/' + encodeURIComponent(section);
  195. }
  196. });
  197. return OC.linkToRemoteBase('webdav') + encodedPath;
  198. }
  199. if (_.isArray(filename)) {
  200. filename = JSON.stringify(filename);
  201. }
  202. var params = {
  203. dir: dir,
  204. files: filename
  205. };
  206. return this.getAjaxUrl('download', params);
  207. },
  208. /**
  209. * Returns the ajax URL for a given action
  210. * @param action action string
  211. * @param params optional params map
  212. */
  213. getAjaxUrl: function(action, params) {
  214. var q = '';
  215. if (params) {
  216. q = '?' + OC.buildQueryString(params);
  217. }
  218. return OC.filePath('files', 'ajax', action + '.php') + q;
  219. },
  220. /**
  221. * Fetch the icon url for the mimetype
  222. * @param {string} mime The mimetype
  223. * @param {Files~mimeicon} ready Function to call when mimetype is retrieved
  224. * @deprecated use OC.MimeType.getIconUrl(mime)
  225. */
  226. getMimeIcon: function(mime, ready) {
  227. ready(OC.MimeType.getIconUrl(mime));
  228. },
  229. /**
  230. * Generates a preview URL based on the URL space.
  231. * @param urlSpec attributes for the URL
  232. * @param {int} urlSpec.x width
  233. * @param {int} urlSpec.y height
  234. * @param {String} urlSpec.file path to the file
  235. * @return preview URL
  236. * @deprecated used OCA.Files.FileList.generatePreviewUrl instead
  237. */
  238. generatePreviewUrl: function(urlSpec) {
  239. console.warn('DEPRECATED: please use generatePreviewUrl() from an OCA.Files.FileList instance');
  240. return OCA.Files.App.fileList.generatePreviewUrl(urlSpec);
  241. },
  242. /**
  243. * Lazy load preview
  244. * @deprecated used OCA.Files.FileList.lazyLoadPreview instead
  245. */
  246. lazyLoadPreview : function(path, mime, ready, width, height, etag) {
  247. console.warn('DEPRECATED: please use lazyLoadPreview() from an OCA.Files.FileList instance');
  248. return FileList.lazyLoadPreview({
  249. path: path,
  250. mime: mime,
  251. callback: ready,
  252. width: width,
  253. height: height,
  254. etag: etag
  255. });
  256. },
  257. /**
  258. * Initialize the files view
  259. */
  260. initialize: function() {
  261. Files.bindKeyboardShortcuts(document, $);
  262. // TODO: move file list related code (upload) to OCA.Files.FileList
  263. $('#file_action_panel').attr('activeAction', false);
  264. // drag&drop support using jquery.fileupload
  265. // TODO use OC.dialogs
  266. $(document).bind('drop dragover', function (e) {
  267. e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone
  268. });
  269. // display storage warnings
  270. setTimeout(Files.displayStorageWarnings, 100);
  271. // only possible at the moment if user is logged in or the files app is loaded
  272. if (OC.currentUser && OCA.Files.App && OC.config.session_keepalive) {
  273. // start on load - we ask the server every 5 minutes
  274. var func = _.bind(OCA.Files.App.fileList.updateStorageStatistics, OCA.Files.App.fileList);
  275. var updateStorageStatisticsInterval = 5*60*1000;
  276. var updateStorageStatisticsIntervalId = setInterval(func, updateStorageStatisticsInterval);
  277. // TODO: this should also stop when switching to another view
  278. // Use jquery-visibility to de-/re-activate file stats sync
  279. if ($.support.pageVisibility) {
  280. $(document).on({
  281. 'show': function() {
  282. if (!updateStorageStatisticsIntervalId) {
  283. updateStorageStatisticsIntervalId = setInterval(func, updateStorageStatisticsInterval);
  284. }
  285. },
  286. 'hide': function() {
  287. clearInterval(updateStorageStatisticsIntervalId);
  288. updateStorageStatisticsIntervalId = 0;
  289. }
  290. });
  291. }
  292. }
  293. $('#webdavurl').on('click touchstart', function () {
  294. this.focus();
  295. this.setSelectionRange(0, this.value.length);
  296. });
  297. $('#upload').tooltip({placement:'right'});
  298. //FIXME scroll to and highlight preselected file
  299. /*
  300. if (getURLParameter('scrollto')) {
  301. FileList.scrollTo(getURLParameter('scrollto'));
  302. }
  303. */
  304. },
  305. /**
  306. * Handles the download and calls the callback function once the download has started
  307. * - browser sends download request and adds parameter with a token
  308. * - server notices this token and adds a set cookie to the download response
  309. * - browser now adds this cookie for the domain
  310. * - JS periodically checks for this cookie and then knows when the download has started to call the callback
  311. *
  312. * @param {string} url download URL
  313. * @param {Function} callback function to call once the download has started
  314. */
  315. handleDownload: function(url, callback) {
  316. var randomToken = Math.random().toString(36).substring(2),
  317. checkForDownloadCookie = function() {
  318. if (!OC.Util.isCookieSetToValue('ocDownloadStarted', randomToken)){
  319. return false;
  320. } else {
  321. callback();
  322. return true;
  323. }
  324. };
  325. if (url.indexOf('?') >= 0) {
  326. url += '&';
  327. } else {
  328. url += '?';
  329. }
  330. OC.redirect(url + 'downloadStartSecret=' + randomToken);
  331. OC.Util.waitFor(checkForDownloadCookie, 500);
  332. }
  333. };
  334. Files._updateStorageStatisticsDebounced = _.debounce(Files._updateStorageStatistics, 250);
  335. Files._updateStorageQuotasThrottled = _.throttle(Files._updateStorageQuotas, 30000);
  336. OCA.Files.Files = Files;
  337. })();
  338. // TODO: move to FileList
  339. var createDragShadow = function(event) {
  340. // FIXME: inject file list instance somehow
  341. /* global FileList, Files */
  342. //select dragged file
  343. var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked');
  344. if (!isDragSelected) {
  345. //select dragged file
  346. FileList._selectFileEl($(event.target).parents('tr:first'), true, false);
  347. }
  348. // do not show drag shadow for too many files
  349. var selectedFiles = _.first(FileList.getSelectedFiles(), FileList.pageSize());
  350. selectedFiles = _.sortBy(selectedFiles, FileList._fileInfoCompare);
  351. if (!isDragSelected && selectedFiles.length === 1) {
  352. //revert the selection
  353. FileList._selectFileEl($(event.target).parents('tr:first'), false, false);
  354. }
  355. // build dragshadow
  356. var dragshadow = $('<table class="dragshadow"></table>');
  357. var tbody = $('<tbody></tbody>');
  358. dragshadow.append(tbody);
  359. var dir = FileList.getCurrentDirectory();
  360. $(selectedFiles).each(function(i,elem) {
  361. // TODO: refactor this with the table row creation code
  362. var newtr = $('<tr/>')
  363. .attr('data-dir', dir)
  364. .attr('data-file', elem.name)
  365. .attr('data-origin', elem.origin);
  366. newtr.append($('<td class="filename" />').text(elem.name).css('background-size', 32));
  367. newtr.append($('<td class="size" />').text(OC.Util.humanFileSize(elem.size)));
  368. tbody.append(newtr);
  369. if (elem.type === 'dir') {
  370. newtr.find('td.filename')
  371. .css('background-image', 'url(' + OC.MimeType.getIconUrl('folder') + ')');
  372. } else {
  373. var path = dir + '/' + elem.name;
  374. Files.lazyLoadPreview(path, elem.mimetype, function(previewpath) {
  375. newtr.find('td.filename')
  376. .css('background-image', 'url(' + previewpath + ')');
  377. }, null, null, elem.etag);
  378. }
  379. });
  380. return dragshadow;
  381. };
  382. //options for file drag/drop
  383. //start&stop handlers needs some cleaning up
  384. // TODO: move to FileList class
  385. var dragOptions={
  386. revert: 'invalid',
  387. revertDuration: 300,
  388. opacity: 0.7,
  389. appendTo: 'body',
  390. cursorAt: { left: 24, top: 18 },
  391. helper: createDragShadow,
  392. cursor: 'move',
  393. start: function(event, ui){
  394. var $selectedFiles = $('td.filename input:checkbox:checked');
  395. if (!$selectedFiles.length) {
  396. $selectedFiles = $(this);
  397. }
  398. $selectedFiles.closest('tr').addClass('animate-opacity dragging');
  399. $selectedFiles.closest('tr').filter('.ui-droppable').droppable( 'disable' );
  400. // Show breadcrumbs menu
  401. $('.crumbmenu').addClass('canDropChildren');
  402. },
  403. stop: function(event, ui) {
  404. var $selectedFiles = $('td.filename input:checkbox:checked');
  405. if (!$selectedFiles.length) {
  406. $selectedFiles = $(this);
  407. }
  408. var $tr = $selectedFiles.closest('tr');
  409. $tr.removeClass('dragging');
  410. $tr.filter('.ui-droppable').droppable( 'enable' );
  411. setTimeout(function() {
  412. $tr.removeClass('animate-opacity');
  413. }, 300);
  414. // Hide breadcrumbs menu
  415. $('.crumbmenu').removeClass('canDropChildren');
  416. },
  417. drag: function(event, ui) {
  418. var scrollingArea = window;
  419. var currentScrollTop = $(scrollingArea).scrollTop();
  420. var scrollArea = Math.min(Math.floor($(window).innerHeight() / 2), 100);
  421. var bottom = $(window).innerHeight() - scrollArea;
  422. var top = $(window).scrollTop() + scrollArea;
  423. if (event.pageY < top) {
  424. $(scrollingArea).animate({
  425. scrollTop: currentScrollTop - 10
  426. }, 400);
  427. } else if (event.pageY > bottom) {
  428. $(scrollingArea).animate({
  429. scrollTop: currentScrollTop + 10
  430. }, 400);
  431. }
  432. }
  433. };
  434. // sane browsers support using the distance option
  435. if ( $('html.ie').length === 0) {
  436. dragOptions['distance'] = 20;
  437. }
  438. // TODO: move to FileList class
  439. var folderDropOptions = {
  440. hoverClass: "canDrop",
  441. drop: function( event, ui ) {
  442. // don't allow moving a file into a selected folder
  443. /* global FileList */
  444. if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) {
  445. return false;
  446. }
  447. var $tr = $(this).closest('tr');
  448. if (($tr.data('permissions') & OC.PERMISSION_CREATE) === 0) {
  449. FileList._showPermissionDeniedNotification();
  450. return false;
  451. }
  452. var targetPath = FileList.getCurrentDirectory() + '/' + $tr.data('file');
  453. var files = FileList.getSelectedFiles();
  454. if (files.length === 0) {
  455. // single one selected without checkbox?
  456. files = _.map(ui.helper.find('tr'), function(el) {
  457. return FileList.elementToFile($(el));
  458. });
  459. }
  460. FileList.move(_.pluck(files, 'name'), targetPath);
  461. },
  462. tolerance: 'pointer'
  463. };
  464. // for backward compatibility
  465. window.Files = OCA.Files.Files;