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