files.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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. /**
  34. * Update storage statistics such as free space, max upload,
  35. * etc based on the given directory.
  36. *
  37. * Note this function is debounced to avoid making too
  38. * many ajax calls in a row.
  39. *
  40. * @param dir directory
  41. * @param force whether to force retrieving
  42. */
  43. updateStorageStatistics: function(dir, force) {
  44. if (!OC.currentUser) {
  45. return;
  46. }
  47. if (force) {
  48. Files._updateStorageStatistics(dir);
  49. }
  50. else {
  51. Files._updateStorageStatisticsDebounced(dir);
  52. }
  53. },
  54. updateMaxUploadFilesize:function(response) {
  55. if (response === undefined) {
  56. return;
  57. }
  58. if (response.data !== undefined && response.data.uploadMaxFilesize !== undefined) {
  59. $('#max_upload').val(response.data.uploadMaxFilesize);
  60. $('#free_space').val(response.data.freeSpace);
  61. $('#upload.button').attr('original-title', response.data.maxHumanFilesize);
  62. $('#usedSpacePercent').val(response.data.usedSpacePercent);
  63. Files.displayStorageWarnings();
  64. }
  65. if (response[0] === undefined) {
  66. return;
  67. }
  68. if (response[0].uploadMaxFilesize !== undefined) {
  69. $('#max_upload').val(response[0].uploadMaxFilesize);
  70. $('#upload.button').attr('original-title', response[0].maxHumanFilesize);
  71. $('#usedSpacePercent').val(response[0].usedSpacePercent);
  72. Files.displayStorageWarnings();
  73. }
  74. },
  75. /**
  76. * Fix path name by removing double slash at the beginning, if any
  77. */
  78. fixPath: function(fileName) {
  79. if (fileName.substr(0, 2) == '//') {
  80. return fileName.substr(1);
  81. }
  82. return fileName;
  83. },
  84. /**
  85. * Checks whether the given file name is valid.
  86. * @param name file name to check
  87. * @return true if the file name is valid.
  88. * Throws a string exception with an error message if
  89. * the file name is not valid
  90. */
  91. isFileNameValid: function (name) {
  92. var trimmedName = name.trim();
  93. if (trimmedName === '.' || trimmedName === '..')
  94. {
  95. throw t('files', '"{name}" is an invalid file name.', {name: name});
  96. } else if (trimmedName.length === 0) {
  97. throw t('files', 'File name cannot be empty.');
  98. }
  99. // check for invalid characters
  100. var invalidCharacters =
  101. ['\\', '/', '<', '>', ':', '"', '|', '?', '*', '\n'];
  102. for (var i = 0; i < invalidCharacters.length; i++) {
  103. if (trimmedName.indexOf(invalidCharacters[i]) !== -1) {
  104. throw t('files', "Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed.");
  105. }
  106. }
  107. return true;
  108. },
  109. displayStorageWarnings: function() {
  110. if (!OC.Notification.isHidden()) {
  111. return;
  112. }
  113. var usedSpacePercent = $('#usedSpacePercent').val();
  114. if (usedSpacePercent > 98) {
  115. OC.Notification.show(t('files', 'Your storage is full, files can not be updated or synced anymore!'));
  116. return;
  117. }
  118. if (usedSpacePercent > 90) {
  119. OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)',
  120. {usedSpacePercent: usedSpacePercent}));
  121. }
  122. },
  123. displayEncryptionWarning: function() {
  124. if (!OC.Notification.isHidden()) {
  125. return;
  126. }
  127. var encryptedFiles = $('#encryptedFiles').val();
  128. var initStatus = $('#encryptionInitStatus').val();
  129. if (initStatus === '0') { // enc not initialized, but should be
  130. OC.Notification.show(t('files', 'Encryption App is enabled but your keys are not initialized, please log-out and log-in again'));
  131. return;
  132. }
  133. if (initStatus === '1') { // encryption tried to init but failed
  134. OC.Notification.show(t('files', 'Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.'));
  135. return;
  136. }
  137. if (encryptedFiles === '1') {
  138. OC.Notification.show(t('files', 'Encryption was disabled but your files are still encrypted. Please go to your personal settings to decrypt your files.'));
  139. return;
  140. }
  141. },
  142. /**
  143. * Returns the download URL of the given file(s)
  144. * @param filename string or array of file names to download
  145. * @param dir optional directory in which the file name is, defaults to the current directory
  146. */
  147. getDownloadUrl: function(filename, dir) {
  148. if ($.isArray(filename)) {
  149. filename = JSON.stringify(filename);
  150. }
  151. var params = {
  152. dir: dir,
  153. files: filename
  154. };
  155. return this.getAjaxUrl('download', params);
  156. },
  157. /**
  158. * Returns the ajax URL for a given action
  159. * @param action action string
  160. * @param params optional params map
  161. */
  162. getAjaxUrl: function(action, params) {
  163. var q = '';
  164. if (params) {
  165. q = '?' + OC.buildQueryString(params);
  166. }
  167. return OC.filePath('files', 'ajax', action + '.php') + q;
  168. },
  169. getMimeIcon: function(mime, ready) {
  170. if (Files.getMimeIcon.cache[mime]) {
  171. ready(Files.getMimeIcon.cache[mime]);
  172. } else {
  173. $.get( OC.filePath('files','ajax','mimeicon.php'), {mime: mime}, function(path) {
  174. if(OC.Util.hasSVGSupport()){
  175. path = path.substr(0, path.length-4) + '.svg';
  176. }
  177. Files.getMimeIcon.cache[mime]=path;
  178. ready(Files.getMimeIcon.cache[mime]);
  179. });
  180. }
  181. },
  182. /**
  183. * Generates a preview URL based on the URL space.
  184. * @param urlSpec attributes for the URL
  185. * @param {int} urlSpec.x width
  186. * @param {int} urlSpec.y height
  187. * @param {String} urlSpec.file path to the file
  188. * @return preview URL
  189. * @deprecated used OCA.Files.FileList.generatePreviewUrl instead
  190. */
  191. generatePreviewUrl: function(urlSpec) {
  192. console.warn('DEPRECATED: please use generatePreviewUrl() from an OCA.Files.FileList instance');
  193. return OCA.Files.App.fileList.generatePreviewUrl(urlSpec);
  194. },
  195. /**
  196. * Lazy load preview
  197. * @deprecated used OCA.Files.FileList.lazyLoadPreview instead
  198. */
  199. lazyLoadPreview : function(path, mime, ready, width, height, etag) {
  200. console.warn('DEPRECATED: please use lazyLoadPreview() from an OCA.Files.FileList instance');
  201. return OCA.Files.App.fileList.lazyLoadPreview({
  202. path: path,
  203. mime: mime,
  204. callback: ready,
  205. width: width,
  206. height: height,
  207. etag: etag
  208. });
  209. },
  210. /**
  211. * Initialize the files view
  212. */
  213. initialize: function() {
  214. Files.getMimeIcon.cache = {};
  215. Files.displayEncryptionWarning();
  216. Files.bindKeyboardShortcuts(document, $);
  217. // TODO: move file list related code (upload) to OCA.Files.FileList
  218. $('#file_action_panel').attr('activeAction', false);
  219. // Triggers invisible file input
  220. $('#upload a').on('click', function() {
  221. $(this).parent().children('#file_upload_start').trigger('click');
  222. return false;
  223. });
  224. // Trigger cancelling of file upload
  225. $('#uploadprogresswrapper .stop').on('click', function() {
  226. OC.Upload.cancelUploads();
  227. });
  228. // drag&drop support using jquery.fileupload
  229. // TODO use OC.dialogs
  230. $(document).bind('drop dragover', function (e) {
  231. e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone
  232. });
  233. //do a background scan if needed
  234. scanFiles();
  235. // display storage warnings
  236. setTimeout(Files.displayStorageWarnings, 100);
  237. OC.Notification.setDefault(Files.displayStorageWarnings);
  238. // only possible at the moment if user is logged in or the files app is loaded
  239. if (OC.currentUser && OCA.Files.App) {
  240. // start on load - we ask the server every 5 minutes
  241. var func = _.bind(OCA.Files.App.fileList.updateStorageStatistics, OCA.Files.App.fileList);
  242. var updateStorageStatisticsInterval = 5*60*1000;
  243. var updateStorageStatisticsIntervalId = setInterval(func, updateStorageStatisticsInterval);
  244. // TODO: this should also stop when switching to another view
  245. // Use jquery-visibility to de-/re-activate file stats sync
  246. if ($.support.pageVisibility) {
  247. $(document).on({
  248. 'show.visibility': function() {
  249. if (!updateStorageStatisticsIntervalId) {
  250. updateStorageStatisticsIntervalId = setInterval(func, updateStorageStatisticsInterval);
  251. }
  252. },
  253. 'hide.visibility': function() {
  254. clearInterval(updateStorageStatisticsIntervalId);
  255. updateStorageStatisticsIntervalId = 0;
  256. }
  257. });
  258. }
  259. }
  260. $('#webdavurl').on('click', function () {
  261. $('#webdavurl').select();
  262. });
  263. //FIXME scroll to and highlight preselected file
  264. /*
  265. if (getURLParameter('scrollto')) {
  266. FileList.scrollTo(getURLParameter('scrollto'));
  267. }
  268. */
  269. }
  270. }
  271. Files._updateStorageStatisticsDebounced = _.debounce(Files._updateStorageStatistics, 250);
  272. OCA.Files.Files = Files;
  273. })();
  274. function scanFiles(force, dir, users) {
  275. if (!OC.currentUser) {
  276. return;
  277. }
  278. if (!dir) {
  279. dir = '';
  280. }
  281. force = !!force; //cast to bool
  282. scanFiles.scanning = true;
  283. var scannerEventSource;
  284. if (users) {
  285. var usersString;
  286. if (users === 'all') {
  287. usersString = users;
  288. } else {
  289. usersString = JSON.stringify(users);
  290. }
  291. scannerEventSource = new OC.EventSource(OC.filePath('files','ajax','scan.php'),{force: force,dir: dir, users: usersString});
  292. } else {
  293. scannerEventSource = new OC.EventSource(OC.filePath('files','ajax','scan.php'),{force: force,dir: dir});
  294. }
  295. scanFiles.cancel = scannerEventSource.close.bind(scannerEventSource);
  296. scannerEventSource.listen('count',function(count) {
  297. console.log(count + ' files scanned');
  298. });
  299. scannerEventSource.listen('folder',function(path) {
  300. console.log('now scanning ' + path);
  301. });
  302. scannerEventSource.listen('done',function(count) {
  303. scanFiles.scanning=false;
  304. console.log('done after ' + count + ' files');
  305. if (OCA.Files.App) {
  306. OCA.Files.App.fileList.updateStorageStatistics(true);
  307. }
  308. });
  309. scannerEventSource.listen('user',function(user) {
  310. console.log('scanning files for ' + user);
  311. });
  312. }
  313. scanFiles.scanning=false;
  314. // TODO: move to FileList
  315. var createDragShadow = function(event) {
  316. //select dragged file
  317. var FileList = OCA.Files.App.fileList;
  318. var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked');
  319. if (!isDragSelected) {
  320. //select dragged file
  321. FileList._selectFileEl($(event.target).parents('tr:first'), true);
  322. }
  323. // do not show drag shadow for too many files
  324. var selectedFiles = _.first(FileList.getSelectedFiles(), FileList.pageSize());
  325. selectedFiles = _.sortBy(selectedFiles, FileList._fileInfoCompare);
  326. if (!isDragSelected && selectedFiles.length === 1) {
  327. //revert the selection
  328. FileList._selectFileEl($(event.target).parents('tr:first'), false);
  329. }
  330. // build dragshadow
  331. var dragshadow = $('<table class="dragshadow"></table>');
  332. var tbody = $('<tbody></tbody>');
  333. dragshadow.append(tbody);
  334. var dir = FileList.getCurrentDirectory();
  335. $(selectedFiles).each(function(i,elem) {
  336. // TODO: refactor this with the table row creation code
  337. var newtr = $('<tr/>')
  338. .attr('data-dir', dir)
  339. .attr('data-file', elem.name)
  340. .attr('data-origin', elem.origin);
  341. newtr.append($('<td class="filename" />').text(elem.name).css('background-size', 32));
  342. newtr.append($('<td class="size" />').text(OC.Util.humanFileSize(elem.size)));
  343. tbody.append(newtr);
  344. if (elem.type === 'dir') {
  345. newtr.find('td.filename')
  346. .css('background-image', 'url(' + OC.imagePath('core', 'filetypes/folder.png') + ')');
  347. } else {
  348. var path = dir + '/' + elem.name;
  349. OCA.Files.App.files.lazyLoadPreview(path, elem.mime, function(previewpath) {
  350. newtr.find('td.filename')
  351. .css('background-image', 'url(' + previewpath + ')');
  352. }, null, null, elem.etag);
  353. }
  354. });
  355. return dragshadow;
  356. };
  357. //options for file drag/drop
  358. //start&stop handlers needs some cleaning up
  359. // TODO: move to FileList class
  360. var dragOptions={
  361. revert: 'invalid',
  362. revertDuration: 300,
  363. opacity: 0.7,
  364. zIndex: 100,
  365. appendTo: 'body',
  366. cursorAt: { left: 24, top: 18 },
  367. helper: createDragShadow,
  368. cursor: 'move',
  369. start: function(event, ui){
  370. var $selectedFiles = $('td.filename input:checkbox:checked');
  371. if($selectedFiles.length > 1){
  372. $selectedFiles.parents('tr').fadeTo(250, 0.2);
  373. }
  374. else{
  375. $(this).fadeTo(250, 0.2);
  376. }
  377. },
  378. stop: function(event, ui) {
  379. var $selectedFiles = $('td.filename input:checkbox:checked');
  380. if($selectedFiles.length > 1){
  381. $selectedFiles.parents('tr').fadeTo(250, 1);
  382. }
  383. else{
  384. $(this).fadeTo(250, 1);
  385. }
  386. $('#fileList tr td.filename').addClass('ui-draggable');
  387. }
  388. };
  389. // sane browsers support using the distance option
  390. if ( $('html.ie').length === 0) {
  391. dragOptions['distance'] = 20;
  392. }
  393. // TODO: move to FileList class
  394. var folderDropOptions = {
  395. hoverClass: "canDrop",
  396. drop: function( event, ui ) {
  397. // don't allow moving a file into a selected folder
  398. var FileList = OCA.Files.App.fileList;
  399. if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) {
  400. return false;
  401. }
  402. var $tr = $(this).closest('tr');
  403. if (($tr.data('permissions') & OC.PERMISSION_CREATE) === 0) {
  404. FileList._showPermissionDeniedNotification();
  405. return false;
  406. }
  407. var targetPath = FileList.getCurrentDirectory() + '/' + $tr.data('file');
  408. var files = FileList.getSelectedFiles();
  409. if (files.length === 0) {
  410. // single one selected without checkbox?
  411. files = _.map(ui.helper.find('tr'), FileList.elementToFile);
  412. }
  413. FileList.move(_.pluck(files, 'name'), targetPath);
  414. },
  415. tolerance: 'pointer'
  416. };
  417. // override core's fileDownloadPath (legacy)
  418. function fileDownloadPath(dir, file) {
  419. return OCA.Files.Files.getDownloadUrl(file, dir);
  420. }
  421. // for backward compatibility
  422. window.Files = OCA.Files.Files;