diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-09-20 13:46:39 +0200 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-09-20 13:46:39 +0200 |
commit | 311910865444860194a6f8b7ff85d554f348ace2 (patch) | |
tree | 773784b71d060390832d466f59fe2166616b8078 /apps/files/js/file-upload.js | |
parent | 7ff911665e7507a800e05fe9cd80e0304cd11dbc (diff) | |
download | nextcloud-server-311910865444860194a6f8b7ff85d554f348ace2.tar.gz nextcloud-server-311910865444860194a6f8b7ff85d554f348ace2.zip |
chore(files): Remove legacy UI scripts now that everything is migrated to Vue
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'apps/files/js/file-upload.js')
-rw-r--r-- | apps/files/js/file-upload.js | 1448 |
1 files changed, 0 insertions, 1448 deletions
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js deleted file mode 100644 index 5014eccb349..00000000000 --- a/apps/files/js/file-upload.js +++ /dev/null @@ -1,1448 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2013-2016 ownCloud, Inc. - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -/** - * The file upload code uses several hooks to interact with blueimps jQuery file upload library: - * 1. the core upload handling hooks are added when initializing the plugin, - * 2. if the browser supports progress events they are added in a separate set after the initialization - * 3. every app can add its own triggers for fileupload - * - files adds d'n'd handlers and also reacts to done events to add new rows to the filelist - * - TODO pictures upload button - * - TODO music upload button - */ - -/* global jQuery, md5 */ - -/** - * File upload object - * - * @class OC.FileUpload - * @classdesc - * - * Represents a file upload - * - * @param {OC.Uploader} uploader uploader - * @param {Object} data blueimp data - */ -OC.FileUpload = function(uploader, data) { - this.uploader = uploader; - this.data = data; - var basePath = ''; - if (this.uploader.fileList) { - basePath = this.uploader.fileList.getCurrentDirectory(); - } - var path = OC.joinPaths(basePath, this.getFile().relativePath || '', this.getFile().name); - this.id = 'web-file-upload-' + md5(path) + '-' + (new Date()).getTime(); -}; -OC.FileUpload.CONFLICT_MODE_DETECT = 0; -OC.FileUpload.CONFLICT_MODE_OVERWRITE = 1; -OC.FileUpload.CONFLICT_MODE_AUTORENAME = 2; - -// IE11 polyfill -// TODO: nuke out of orbit as well as this legacy code -if (!FileReader.prototype.readAsBinaryString) { - FileReader.prototype.readAsBinaryString = function(fileData) { - var binary = '' - var pt = this - var reader = new FileReader() - reader.onload = function (e) { - var bytes = new Uint8Array(reader.result) - var length = bytes.byteLength - for (var i = 0; i < length; i++) { - binary += String.fromCharCode(bytes[i]) - } - // pt.result - readonly so assign binary - pt.content = binary - $(pt).trigger('onload') - } - reader.readAsArrayBuffer(fileData) - } -} - -OC.FileUpload.prototype = { - - /** - * Unique upload id - * - * @type string - */ - id: null, - - /** - * Upload data structure - */ - data: null, - - /** - * Upload element - * - * @type Object - */ - $uploadEl: null, - - /** - * Target folder - * - * @type string - */ - _targetFolder: '', - - /** - * @type int - */ - _conflictMode: OC.FileUpload.CONFLICT_MODE_DETECT, - - /** - * New name from server after autorename - * - * @type String - */ - _newName: null, - - /** - * Returns the unique upload id - * - * @return string - */ - getId: function() { - return this.id; - }, - - /** - * Returns the file to be uploaded - * - * @return {File} file - */ - getFile: function() { - return this.data.files[0]; - }, - - /** - * Return the final filename. - * - * @return {String} file name - */ - getFileName: function() { - // autorenamed name - if (this._newName) { - return this._newName; - } - return this.getFile().name; - }, - - setTargetFolder: function(targetFolder) { - this._targetFolder = targetFolder; - }, - - getTargetFolder: function() { - return this._targetFolder; - }, - - /** - * Get full path for the target file, including relative path, - * without the file name. - * - * @return {String} full path - */ - getFullPath: function() { - return OC.joinPaths(this._targetFolder, this.getFile().relativePath || ''); - }, - - /** - * Get full path for the target file, - * including relative path and file name. - * - * @return {String} full path - */ - getFullFilePath: function() { - return OC.joinPaths(this.getFullPath(), this.getFile().name); - }, - - /** - * Returns conflict resolution mode. - * - * @return {number} conflict mode - */ - getConflictMode: function() { - return this._conflictMode || OC.FileUpload.CONFLICT_MODE_DETECT; - }, - - /** - * Set conflict resolution mode. - * See CONFLICT_MODE_* constants. - * - * @param {number} mode conflict mode - */ - setConflictMode: function(mode) { - this._conflictMode = mode; - }, - - deleteUpload: function() { - delete this.data.jqXHR; - }, - - /** - * Trigger autorename and append "(2)". - * Multiple calls will increment the appended number. - */ - autoRename: function() { - var name = this.getFile().name; - if (!this._renameAttempt) { - this._renameAttempt = 1; - } - - var dotPos = name.lastIndexOf('.'); - var extPart = ''; - if (dotPos > 0) { - this._newName = name.substr(0, dotPos); - extPart = name.substr(dotPos); - } else { - this._newName = name; - } - - // generate new name - this._renameAttempt++; - this._newName = this._newName + ' (' + this._renameAttempt + ')' + extPart; - }, - - /** - * Submit the upload - */ - submit: function() { - var self = this; - var data = this.data; - var file = this.getFile(); - - // if file is a directory, just create it - // files are handled separately - if (file.isDirectory) { - return this.uploader.ensureFolderExists(OC.joinPaths(this._targetFolder, file.fullPath)); - } - - if (self.aborted === true) { - return $.Deferred().resolve().promise(); - } - // it was a folder upload, so make sure the parent directory exists already - var folderPromise; - if (file.relativePath) { - folderPromise = this.uploader.ensureFolderExists(this.getFullPath()); - } else { - folderPromise = $.Deferred().resolve().promise(); - } - - if (this.uploader.fileList) { - this.data.url = this.uploader.fileList.getUploadUrl(this.getFileName(), this.getFullPath()); - } - - if (!this.data.headers) { - this.data.headers = {}; - } - - // webdav without multipart - this.data.multipart = false; - this.data.type = 'PUT'; - - delete this.data.headers['If-None-Match']; - if (this._conflictMode === OC.FileUpload.CONFLICT_MODE_DETECT - || this._conflictMode === OC.FileUpload.CONFLICT_MODE_AUTORENAME) { - this.data.headers['If-None-Match'] = '*'; - } - - var userName = this.uploader.davClient.getUserName(); - var password = this.uploader.davClient.getPassword(); - if (userName) { - // copy username/password from DAV client - this.data.headers['Authorization'] = - 'Basic ' + btoa(userName + ':' + (password || '')); - } - - var chunkFolderPromise; - if ($.support.blobSlice - && this.uploader.fileUploadParam.maxChunkSize - && this.getFile().size > this.uploader.fileUploadParam.maxChunkSize - ) { - data.isChunked = true; - var headers = { - Destination: this.uploader.davClient._buildUrl(this.getTargetDestination()) - }; - - chunkFolderPromise = this.uploader.davClient.createDirectory( - 'uploads/' + OC.getCurrentUser().uid + '/' + this.getId(), headers - ); - // TODO: if fails, it means same id already existed, need to retry - } else { - chunkFolderPromise = $.Deferred().resolve().promise(); - var mtime = this.getFile().lastModified; - if (mtime) { - data.headers['X-OC-Mtime'] = mtime / 1000; - } - } - - // wait for creation of the required directory before uploading - return Promise.all([folderPromise, chunkFolderPromise]).then(function() { - if (self.aborted !== true) { - data.submit(); - } - }, function() { - self.abort(); - }); - - }, - - /** - * Process end of transfer - */ - done: function() { - if (!this.data.isChunked) { - return $.Deferred().resolve().promise(); - } - - var uid = OC.getCurrentUser().uid; - var mtime = this.getFile().lastModified; - var size = this.getFile().size; - var headers = {}; - if (mtime) { - headers['X-OC-Mtime'] = mtime / 1000; - } - if (size) { - headers['OC-Total-Length'] = size; - } - headers['Destination'] = this.uploader.davClient._buildUrl(this.getTargetDestination()); - - return this.uploader.davClient.move( - 'uploads/' + uid + '/' + this.getId() + '/.file', - this.getTargetDestination(), - true, - headers - ); - }, - - getTargetDestination: function() { - var uid = OC.getCurrentUser().uid; - return 'files/' + uid + '/' + OC.joinPaths(this.getFullPath(), this.getFileName()); - }, - - _deleteChunkFolder: function() { - // delete transfer directory for this upload - this.uploader.davClient.remove( - 'uploads/' + OC.getCurrentUser().uid + '/' + this.getId() - ); - }, - - _delete: function() { - if (this.data.isChunked) { - this._deleteChunkFolder() - } - this.deleteUpload(); - }, - - /** - * Abort the upload - */ - abort: function() { - if (this.aborted) { - return - } - this.aborted = true; - if (this.data) { - // abort running XHR - this.data.abort(); - } - this._delete(); - }, - - /** - * Fail the upload - */ - fail: function() { - if (this.aborted) { - return - } - this._delete(); - }, - - /** - * Returns the server response - * - * @return {Object} response - */ - getResponse: function() { - var response = this.data.response(); - if (response.errorThrown || response.textStatus === 'error') { - // attempt parsing Sabre exception is available - var xml = response.jqXHR.responseXML; - if (xml && xml.documentElement.localName === 'error' && xml.documentElement.namespaceURI === 'DAV:') { - var messages = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'message'); - var exceptions = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'exception'); - if (messages.length) { - response.message = messages[0].textContent; - } - if (exceptions.length) { - response.exception = exceptions[0].textContent; - } - return response; - } - } - - if (typeof response.result !== 'string' && response.result) { - //fetch response from iframe - response = $.parseJSON(response.result[0].body.innerText); - if (!response) { - // likely due to internal server error - response = {status: 500}; - } - } else { - response = response.result; - } - return response; - }, - - /** - * Returns the status code from the response - * - * @return {number} status code - */ - getResponseStatus: function() { - if (this.uploader.isXHRUpload()) { - var xhr = this.data.response().jqXHR; - if (xhr) { - return xhr.status; - } - return null; - } - return this.getResponse().status; - }, - - /** - * Returns the response header by name - * - * @param {String} headerName header name - * @return {Array|String} response header value(s) - */ - getResponseHeader: function(headerName) { - headerName = headerName.toLowerCase(); - if (this.uploader.isXHRUpload()) { - return this.data.response().jqXHR.getResponseHeader(headerName); - } - - var headers = this.getResponse().headers; - if (!headers) { - return null; - } - - var value = _.find(headers, function(value, key) { - return key.toLowerCase() === headerName; - }); - if (_.isArray(value) && value.length === 1) { - return value[0]; - } - return value; - } -}; - -/** - * keeps track of uploads in progress and implements callbacks for the conflicts dialog - * @namespace - */ - -OC.Uploader = function() { - this.init.apply(this, arguments); -}; - -OC.Uploader.prototype = _.extend({ - /** - * @type Array<OC.FileUpload> - */ - _uploads: {}, - - /** - * Count of upload done promises that have not finished yet. - * - * @type int - */ - _pendingUploadDoneCount: 0, - - /** - * Is it currently uploading? - * - * @type boolean - */ - _uploading: false, - - /** - * List of directories known to exist. - * - * Key is the fullpath and value is boolean, true meaning that the directory - * was already created so no need to create it again. - */ - _knownDirs: {}, - - /** - * @type OCA.Files.FileList - */ - fileList: null, - - /** - * @type OCA.Files.OperationProgressBar - */ - progressBar: null, - - /** - * @type OC.Files.Client - */ - filesClient: null, - - /** - * Webdav client pointing at the root "dav" endpoint - * - * @type OC.Files.Client - */ - davClient: null, - - /** - * Function that will allow us to know if Ajax uploads are supported - * @link https://github.com/New-Bamboo/example-ajax-upload/blob/master/public/index.html - * also see article @link http://blog.new-bamboo.co.uk/2012/01/10/ridiculously-simple-ajax-uploads-with-formdata - */ - _supportAjaxUploadWithProgress: function() { - if (window.TESTING) { - return true; - } - return supportFileAPI() && supportAjaxUploadProgressEvents() && supportFormData(); - - // Is the File API supported? - function supportFileAPI() { - var fi = document.createElement('INPUT'); - fi.type = 'file'; - return 'files' in fi; - } - - // Are progress events supported? - function supportAjaxUploadProgressEvents() { - var xhr = new XMLHttpRequest(); - return !! (xhr && ('upload' in xhr) && ('onprogress' in xhr.upload)); - } - - // Is FormData supported? - function supportFormData() { - return !! window.FormData; - } - }, - - /** - * Returns whether an XHR upload will be used - * - * @return {boolean} true if XHR upload will be used, - * false for iframe upload - */ - isXHRUpload: function () { - return !this.fileUploadParam.forceIframeTransport && - ((!this.fileUploadParam.multipart && $.support.xhrFileUpload) || - $.support.xhrFormDataFileUpload); - }, - - /** - * Makes sure that the upload folder and its parents exists - * - * @param {String} fullPath full path - * @return {Promise} promise that resolves when all parent folders - * were created - */ - ensureFolderExists: function(fullPath) { - if (!fullPath || fullPath === '/') { - return $.Deferred().resolve().promise(); - } - - // remove trailing slash - if (fullPath.charAt(fullPath.length - 1) === '/') { - fullPath = fullPath.substr(0, fullPath.length - 1); - } - - var self = this; - var promise = this._knownDirs[fullPath]; - - if (this.fileList) { - // assume the current folder exists - this._knownDirs[this.fileList.getCurrentDirectory()] = $.Deferred().resolve().promise(); - } - - if (!promise) { - var deferred = new $.Deferred(); - promise = deferred.promise(); - this._knownDirs[fullPath] = promise; - - // make sure all parents already exist - var parentPath = OC.dirname(fullPath); - var parentPromise = this._knownDirs[parentPath]; - if (!parentPromise) { - parentPromise = this.ensureFolderExists(parentPath); - } - - parentPromise.then(function() { - self.filesClient.createDirectory(fullPath).always(function(status) { - // 405 is expected if the folder already exists - if ((status >= 200 && status < 300) || status === 405) { - if (status !== 405) { - self.trigger('createdfolder', fullPath); - } - deferred.resolve(); - return; - } - OC.Notification.show(t('files', 'Could not create folder "{dir}"', {dir: fullPath}), {type: 'error'}); - deferred.reject(); - }); - }, function() { - deferred.reject(); - }); - } - - return promise; - }, - - /** - * Submit the given uploads - * - * @param {Array} array of uploads to start - */ - submitUploads: function(uploads) { - var self = this; - _.each(uploads, function(upload) { - self._uploads[upload.data.uploadId] = upload; - }); - if (!self._uploading) { - self.totalToUpload = 0; - self.totalUploadCount = 0; - } - self.totalToUpload += _.reduce(uploads, function(memo, upload) { return memo+upload.getFile().size; }, 0); - self.totalUploadCount += uploads.length; - var semaphore = new OCA.Files.Semaphore(5); - var promises = _.map(uploads, function(upload) { - return semaphore.acquire().then(function(){ - return upload.submit().then(function(){ - semaphore.release(); - }); - }); - }); - }, - - confirmBeforeUnload: function() { - if (this._uploading) { - return t('files', 'This will stop your current uploads.') - } - }, - - /** - * Show conflict for the given file object - * - * @param {OC.FileUpload} file upload object - */ - showConflict: function(fileUpload) { - //show "file already exists" dialog - var self = this; - var file = fileUpload.getFile(); - // already attempted autorename but the server said the file exists ? (concurrently added) - if (fileUpload.getConflictMode() === OC.FileUpload.CONFLICT_MODE_AUTORENAME) { - // attempt another autorename, defer to let the current callback finish - _.defer(function() { - self.onAutorename(fileUpload); - }); - return; - } - // retrieve more info about this file - this.filesClient.getFileInfo(fileUpload.getFullFilePath()).then(function(status, fileInfo) { - var original = fileInfo; - var replacement = file; - original.directory = original.path; - OC.dialogs.fileexists(fileUpload, original, replacement, self); - }); - }, - /** - * cancels all uploads - */ - cancelUploads:function() { - this.log('canceling uploads'); - jQuery.each(this._uploads, function(i, upload) { - upload.abort(); - }); - this.clear(); - }, - /** - * Clear uploads - */ - clear: function() { - this._knownDirs = {}; - }, - /** - * Returns an upload by id - * - * @param {number} data uploadId - * @return {OC.FileUpload} file upload - */ - getUpload: function(data) { - if (_.isString(data)) { - return this._uploads[data]; - } else if (data.uploadId && this._uploads[data.uploadId]) { - this._uploads[data.uploadId].data = data; - return this._uploads[data.uploadId]; - } - return null; - }, - - /** - * Removes an upload from the list of known uploads. - * - * @param {OC.FileUpload} upload the upload to remove. - */ - removeUpload: function(upload) { - if (!upload || !upload.data || !upload.data.uploadId) { - return; - } - - // defer as some calls/chunks might still be busy failing, so we need - // the upload info there still - var self = this; - var uploadId = upload.data.uploadId; - // mark as deleted for the progress bar - this._uploads[uploadId].deleted = true; - window.setTimeout(function() { - delete self._uploads[uploadId]; - }, 5000) - }, - - _activeUploadCount: function() { - var count = 0; - for (var key in this._uploads) { - if (!this._uploads[key].deleted) { - count++; - } - } - - return count; - }, - - showUploadCancelMessage: _.debounce(function() { - OC.Notification.show(t('files', 'Upload cancelled.'), { timeout : 7000, type: 'error' }); - }, 500), - - /** - * callback for the conflicts dialog - */ - onCancel:function() { - this.cancelUploads(); - }, - /** - * callback for the conflicts dialog - * calls onSkip, onReplace or onAutorename for each conflict - * @param {object} conflicts - list of conflict elements - */ - onContinue:function(conflicts) { - var self = this; - //iterate over all conflicts - jQuery.each(conflicts, function (i, conflict) { - conflict = $(conflict); - var keepOriginal = conflict.find('.original input[type="checkbox"]:checked').length === 1; - var keepReplacement = conflict.find('.replacement input[type="checkbox"]:checked').length === 1; - if (keepOriginal && keepReplacement) { - // when both selected -> autorename - self.onAutorename(conflict.data('data')); - } else if (keepReplacement) { - // when only replacement selected -> overwrite - self.onReplace(conflict.data('data')); - } else { - // when only original selected -> skip - // when none selected -> skip - self.onSkip(conflict.data('data')); - } - }); - }, - /** - * handle skipping an upload - * @param {OC.FileUpload} upload - */ - onSkip:function(upload) { - this.log('skip', null, upload); - upload.deleteUpload(); - }, - /** - * handle replacing a file on the server with an uploaded file - * @param {FileUpload} data - */ - onReplace:function(upload) { - this.log('replace', null, upload); - upload.setConflictMode(OC.FileUpload.CONFLICT_MODE_OVERWRITE); - this.submitUploads([upload]); - }, - /** - * handle uploading a file and letting the server decide a new name - * @param {object} upload - */ - onAutorename:function(upload) { - this.log('autorename', null, upload); - upload.setConflictMode(OC.FileUpload.CONFLICT_MODE_AUTORENAME); - - do { - upload.autoRename(); - // if file known to exist on the client side, retry - } while (this.fileList && this.fileList.inList(upload.getFileName())); - - // resubmit upload - this.submitUploads([upload]); - }, - _trace: false, //TODO implement log handler for JS per class? - log: function(caption, e, data) { - if (this._trace) { - console.log(caption); - console.log(data); - } - }, - /** - * checks the list of existing files prior to uploading and shows a simple dialog to choose - * skip all, replace all or choose which files to keep - * - * @param {array} selection of files to upload - * @param {object} callbacks - object with several callback methods - * @param {Function} callbacks.onNoConflicts - * @param {Function} callbacks.onSkipConflicts - * @param {Function} callbacks.onReplaceConflicts - * @param {Function} callbacks.onChooseConflicts - * @param {Function} callbacks.onCancel - */ - checkExistingFiles: function (selection, callbacks) { - var fileList = this.fileList; - var conflicts = []; - // only keep non-conflicting uploads - selection.uploads = _.filter(selection.uploads, function(upload) { - var file = upload.getFile(); - if (file.relativePath) { - // can't check in subfolder contents - return true; - } - if (!fileList) { - // no list to check against - return true; - } - if (upload.getTargetFolder() !== fileList.getCurrentDirectory()) { - // not uploading to the current folder - return true; - } - var fileInfo = fileList.findFile(file.name); - if (fileInfo) { - conflicts.push([ - // original - _.extend(fileInfo, { - directory: fileInfo.directory || fileInfo.path || fileList.getCurrentDirectory() - }), - // replacement (File object) - upload - ]); - return false; - } - return true; - }); - if (conflicts.length) { - // wait for template loading - OC.dialogs.fileexists(null, null, null, this).done(function() { - _.each(conflicts, function(conflictData) { - OC.dialogs.fileexists(conflictData[1], conflictData[0], conflictData[1].getFile(), this); - }); - }); - } - - // upload non-conflicting files - // note: when reaching the server they might still meet conflicts - // if the folder was concurrently modified, these will get added - // to the already visible dialog, if applicable - callbacks.onNoConflicts(selection); - }, - - _updateProgressBarOnUploadStop: function() { - if (this._pendingUploadDoneCount === 0) { - // All the uploads ended and there is no pending operation, so hide - // the progress bar. - // Note that this happens here only with non-chunked uploads; if the - // upload was chunked then this will have been executed after all - // the uploads ended but before the upload done handler that reduces - // the pending operation count was executed. - this._hideProgressBar(); - - return; - } - - this._setProgressBarText(t('files', 'Processing files …'), t('files', '…')); - - // Nothing is being uploaded at this point, and the pending operations - // can not be cancelled, so the cancel button should be hidden. - this._hideCancelButton(); - }, - - _hideProgressBar: function() { - this.progressBar.hideProgressBar(); - }, - - _hideCancelButton: function() { - this.progressBar.hideCancelButton(); - }, - - _showProgressBar: function() { - this.progressBar.showProgressBar(); - }, - - _setProgressBarValue: function(value) { - this.progressBar.setProgressBarValue(value); - }, - - _setProgressBarText: function(textDesktop, textMobile, title) { - this.progressBar.setProgressBarText(textDesktop, textMobile, title); - }, - - /** - * Returns whether the given file is known to be a received shared file - * - * @param {Object} file file - * @return {boolean} true if the file is a shared file - */ - _isReceivedSharedFile: function(file) { - if (!window.FileList) { - return false; - } - var $tr = window.FileList.findFileEl(file.name); - if (!$tr.length) { - return false; - } - - return ($tr.attr('data-mounttype') === 'shared-root' && $tr.attr('data-mime') !== 'httpd/unix-directory'); - }, - - /** - * Initialize the upload object - * - * @param {Object} $uploadEl upload element - * @param {Object} options - * @param {OCA.Files.FileList} [options.fileList] file list object - * @param {OC.Files.Client} [options.filesClient] files client object - * @param {Object} [options.dropZone] drop zone for drag and drop upload - */ - init: function($uploadEl, options) { - var self = this; - - options = options || {}; - - this.fileList = options.fileList; - this.progressBar = options.progressBar; - this.filesClient = options.filesClient || OC.Files.getClient(); - this.davClient = new OC.Files.Client({ - host: this.filesClient.getHost(), - root: OC.linkToRemoteBase('dav'), - useHTTPS: OC.getProtocol() === 'https', - userName: this.filesClient.getUserName(), - password: this.filesClient.getPassword() - }); - - $uploadEl = $($uploadEl); - this.$uploadEl = $uploadEl; - - if ($uploadEl.exists()) { - this.progressBar.on('cancel', function() { - self.cancelUploads(); - self.showUploadCancelMessage(); - }); - - this.fileUploadParam = { - type: 'PUT', - dropZone: options.dropZone, // restrict dropZone to content div - autoUpload: false, - progressInterval: 300, // increased from the default of 100ms for more stable behvaviour when predicting remaining time - sequentialUploads: false, - limitConcurrentUploads: 4, - /** - * on first add of every selection - * - check all files of originalFiles array with files in dir - * - on conflict show dialog - * - skip all -> remember as single skip action for all conflicting files - * - replace all -> remember as single replace action for all conflicting files - * - choose -> show choose dialog - * - mark files to keep - * - when only existing -> remember as single skip action - * - when only new -> remember as single replace action - * - when both -> remember as single autorename action - * - start uploading selection - * @param {object} e - * @param {object} data - * @returns {boolean} - */ - add: function(e, data) { - self.log('add', e, data); - var that = $(this), freeSpace = 0; - - var upload = new OC.FileUpload(self, data); - // can't link directly due to jQuery not liking cyclic deps on its ajax object - data.uploadId = upload.getId(); - - // create a container where we can store the data objects - if ( ! data.originalFiles.selection ) { - // initialize selection and remember number of files to upload - data.originalFiles.selection = { - uploads: [], - filesToUpload: data.originalFiles.length, - totalBytes: 0 - }; - } - // TODO: move originalFiles to a separate container, maybe inside OC.Upload - var selection = data.originalFiles.selection; - - // add uploads - if ( selection.uploads.length < selection.filesToUpload ) { - // remember upload - selection.uploads.push(upload); - } - - //examine file - var file = upload.getFile(); - try { - // FIXME: not so elegant... need to refactor that method to return a value - Files.isFileNameValid(file.name); - } - catch (errorMessage) { - data.textStatus = 'invalidcharacters'; - data.errorThrown = errorMessage; - } - - if (data.targetDir) { - upload.setTargetFolder(data.targetDir); - delete data.targetDir; - } - - // in case folder drag and drop is not supported file will point to a directory - // http://stackoverflow.com/a/20448357 - if ( !file.type && file.size % 4096 === 0 && file.size <= 102400) { - var dirUploadFailure = false; - try { - var reader = new FileReader(); - reader.readAsBinaryString(file); - } catch (error) { - console.log(reader, error) - //file is a directory - dirUploadFailure = true; - } - - if (dirUploadFailure) { - data.textStatus = 'dirorzero'; - data.errorThrown = t('files', - 'Unable to upload {filename} as it is a directory or has 0 bytes', - {filename: file.name} - ); - } - } - - // only count if we're not overwriting an existing shared file - if (self._isReceivedSharedFile(file)) { - file.isReceivedShare = true; - } else { - // add size - selection.totalBytes += file.size; - } - - // check free space - if (!self.fileList || upload.getTargetFolder() === self.fileList.getCurrentDirectory()) { - // Use global free space if there is no file list to check or the current directory is the target - freeSpace = $('input[name=free_space]').val() - } else if (upload.getTargetFolder().indexOf(self.fileList.getCurrentDirectory()) === 0) { - // Check subdirectory free space if file is uploaded there - // Retrieve the folder destination name - var targetSubdir = upload._targetFolder.split('/').pop() - freeSpace = parseInt(upload.uploader.fileList.getModelForFile(targetSubdir).get('quotaAvailableBytes')) - } - if (freeSpace >= 0 && selection.totalBytes > freeSpace) { - data.textStatus = 'notenoughspace'; - data.errorThrown = t('files', - 'Not enough free space, you are uploading {size1} but only {size2} is left', { - 'size1': OC.Util.humanFileSize(selection.totalBytes, false, false), - 'size2': OC.Util.humanFileSize(freeSpace, false, false) - }); - } - - // end upload for whole selection on error - if (data.errorThrown) { - // trigger fileupload fail handler - var fu = that.data('blueimp-fileupload') || that.data('fileupload'); - fu._trigger('fail', e, data); - return false; //don't upload anything - } - - // check existing files when all is collected - if ( selection.uploads.length >= selection.filesToUpload ) { - - //remove our selection hack: - delete data.originalFiles.selection; - - var callbacks = { - - onNoConflicts: function (selection) { - self.submitUploads(selection.uploads); - }, - onSkipConflicts: function (selection) { - //TODO mark conflicting files as toskip - }, - onReplaceConflicts: function (selection) { - //TODO mark conflicting files as toreplace - }, - onChooseConflicts: function (selection) { - //TODO mark conflicting files as chosen - }, - onCancel: function (selection) { - $.each(selection.uploads, function(i, upload) { - upload.abort(); - }); - } - }; - - self.checkExistingFiles(selection, callbacks); - - } - - return true; // continue adding files - }, - /** - * called after the first add, does NOT have the data param - * @param {object} e - */ - start: function(e) { - self.log('start', e, null); - self._uploading = true; - }, - fail: function(e, data) { - var upload = self.getUpload(data); - var status = null; - if (upload) { - if (upload.aborted) { - // uploads might fail with errors from the server when aborted - return - } - status = upload.getResponseStatus(); - } - self.log('fail', e, upload); - - self.removeUpload(upload); - - if (data.textStatus === 'abort' || data.errorThrown === 'abort') { - return - } else if (status === 412) { - // file already exists - self.showConflict(upload); - } else if (status === 404) { - // target folder does not exist any more - OC.Notification.show(t('files', 'Target folder "{dir}" does not exist any more', {dir: upload.getFullPath()} ), {type: 'error'}); - self.cancelUploads(); - } else if (data.textStatus === 'notenoughspace') { - // not enough space - OC.Notification.show(t('files', 'Not enough free space'), {type: 'error'}); - self.cancelUploads(); - } else { - // HTTP connection problem or other error - var message = t('files', 'An unknown error has occurred'); - if (upload) { - var response = upload.getResponse(); - if (response) { - message = response.message; - } - } - console.error(e, data, response) - OC.Notification.show(message || data.errorThrown || t('files', 'File could not be uploaded'), {type: 'error'}); - } - - if (upload) { - upload.fail(); - } - }, - /** - * called for every successful upload - * @param {object} e - * @param {object} data - */ - done:function(e, data) { - var upload = self.getUpload(data); - var that = $(this); - self.log('done', e, upload); - - self.removeUpload(upload); - - var status = upload.getResponseStatus(); - if (status < 200 || status >= 300) { - // trigger fail handler - var fu = that.data('blueimp-fileupload') || that.data('fileupload'); - fu._trigger('fail', e, data); - return; - } - }, - /** - * called after last upload - * @param {object} e - * @param {object} data - */ - stop: function(e, data) { - self.log('stop', e, data); - self._uploading = false; - } - }; - - if (options.maxChunkSize) { - this.fileUploadParam.maxChunkSize = options.maxChunkSize; - } - - // initialize jquery fileupload (blueimp) - var fileupload = this.$uploadEl.fileupload(this.fileUploadParam); - - if (this._supportAjaxUploadWithProgress()) { - //remaining time - var lastUpdate, lastSize, bufferSize, buffer, bufferIndex, bufferTotal, smoothRemainingSeconds, smoothBitrate; - - var dragging = false; - - // add progress handlers - fileupload.on('fileuploadadd', function(e, data) { - self.log('progress handle fileuploadadd', e, data); - self.trigger('add', e, data); - }); - // add progress handlers - fileupload.on('fileuploadstart', function(e, data) { - self.log('progress handle fileuploadstart', e, data); - self._setProgressBarText(t('files', 'Uploading …'), t('files', '…')); - self._setProgressBarValue(0); - self._showProgressBar(); - // initial remaining time variables - lastUpdate = new Date().getTime(); - lastSize = 0; - bufferSize = 20; // length of the ring buffer - buffer = []; - bufferIndex = 0; // index of the ring buffer, runs from 0 to bufferSize continuously - bufferTotal = 0; - newTotal = 0; - smoothing = 0.02; // smoothing factor for EMA - h = ''; - bufferFilled = false; - - for(var i = 0; i < bufferSize; i++){ - buffer[i] = 0; - } - self.trigger('start', e, data); - }); - fileupload.on('fileuploadprogress', function(e, data) { - self.log('progress handle fileuploadprogress', e, data); - //TODO progressbar in row - self.trigger('progress', e, data); - }); - fileupload.on('fileuploadprogressall', function(e, data) { - self.log('progress handle fileuploadprogressall', e, data); - var total = self.totalToUpload; - var progress = (data.loaded / total) * 100; - var thisUpdate = new Date().getTime(); - var diffUpdate = (thisUpdate - lastUpdate)/1000; // eg. 2s - lastUpdate = thisUpdate; - var diffSize = data.loaded - lastSize; - if (diffSize <= 0) { - diffSize = lastSize; - } - lastSize = data.loaded; - diffSize = diffSize / diffUpdate; // apply timing factor, eg. 1MiB/2s = 0.5MiB/s, unit is byte per second - var remainingSeconds = ((total - data.loaded) / diffSize); - - if(remainingSeconds >= 0) { - // bufferTotal holds the sum of all entries in the buffer, initially 0 like the entries itself - // substract current entry from total and add the current value to total - bufferTotal = bufferTotal - (buffer[bufferIndex]) + remainingSeconds; - // put current value to the entry - buffer[bufferIndex] = remainingSeconds; //buffer to make it smoother - - bufferIndex = (bufferIndex + 1) % bufferSize; - } - if (bufferIndex === bufferSize - 1) { - bufferFilled = true; - } - //console.log('#', ' idx: ',bufferIndex, ' Total: ', bufferTotal, ' remainSeconds: ', remainingSeconds, ' during: ', diffUpdate); - - if (smoothRemainingSeconds) { - smoothRemainingSeconds = smoothing * (bufferTotal / bufferSize) + ((1-smoothing) * smoothRemainingSeconds); - } else { - smoothRemainingSeconds = bufferTotal / bufferSize; - } - - // the number of currently running uploads - const runningUploads = Object.keys(self._uploads).length; - - // Only show remaining time if enough buffer information is available and debounce to 1/4 - if (bufferFilled && bufferIndex % 4 === 0) { - h = moment.duration(smoothRemainingSeconds, "seconds").humanize({m: 50, h: 50}); - if (self.totalUploadCount > 1) { - h = t('files', '{remainingTime} ({currentNumber}/{total})', { remainingTime: h, currentNumber: self.totalUploadCount - runningUploads + 1, total: self.totalUploadCount }); - } - } - - // wait for the buffer to be filled and also show "Uploading ..." for durations longer than 4 hours - if (!bufferFilled || !(smoothRemainingSeconds >= 0 && smoothRemainingSeconds < 14400)) { - // Do not show file index when there is just one - if (self.totalUploadCount > 1) { - h = t('files', 'Uploading … ({currentNumber}/{total})', { currentNumber: self.totalUploadCount - runningUploads + 1, total: self.totalUploadCount }); - } else { - h = t('files', 'Uploading …'); - } - } - - // smooth bitrate - if (smoothBitrate) { - smoothBitrate = smoothing * data.bitrate + ((1-smoothing) * smoothBitrate); - } else { - smoothBitrate = data.bitrate; - } - - self._setProgressBarText(h, h, t('files', '{loadedSize} of {totalSize} ({bitrate})' , { - loadedSize: OC.Util.humanFileSize(data.loaded, false, false), - totalSize: OC.Util.humanFileSize(total, false, false), - bitrate: OC.Util.humanFileSize(smoothBitrate / 8, false, false) + '/s' - })); - self._setProgressBarValue(progress); - self.trigger('progressall', e, data); - }); - fileupload.on('fileuploadstop', function(e, data) { - self.log('progress handle fileuploadstop', e, data); - - self.clear(); - self._updateProgressBarOnUploadStop(); - self.trigger('stop', e, data); - }); - fileupload.on('fileuploadfail', function(e, data) { - self.log('progress handle fileuploadfail', e, data); - self.trigger('fail', e, data); - }); - fileupload.on('fileuploaddragover', function(e){ - $('#app-content').addClass('file-drag'); - $('.emptyfilelist.emptycontent .icon-folder').addClass('icon-filetype-folder-drag-accept'); - - var filerow = $(e.delegatedEvent.target).closest('tr'); - - if(!filerow.hasClass('dropping-to-dir')){ - $('.dropping-to-dir .icon-filetype-folder-drag-accept').removeClass('icon-filetype-folder-drag-accept'); - $('.dropping-to-dir').removeClass('dropping-to-dir'); - $('.dir-drop').removeClass('dir-drop'); - } - - if(filerow.attr('data-type') === 'dir'){ - $('#app-content').addClass('dir-drop'); - filerow.addClass('dropping-to-dir'); - filerow.find('.thumbnail').addClass('icon-filetype-folder-drag-accept'); - } - - dragging = true; - }); - - var disableDropState = function() { - $('#app-content').removeClass('file-drag'); - $('.dropping-to-dir').removeClass('dropping-to-dir'); - $('.dir-drop').removeClass('dir-drop'); - $('.icon-filetype-folder-drag-accept').removeClass('icon-filetype-folder-drag-accept'); - - dragging = false; - }; - - fileupload.on('fileuploaddragleave fileuploaddrop', disableDropState); - - // In some browsers the "drop" event can be triggered with no - // files even if the "dragover" event seemed to suggest that a - // file was being dragged (and thus caused "fileuploaddragover" - // to be triggered). - fileupload.on('fileuploaddropnofiles', function() { - if (!dragging) { - return; - } - - disableDropState(); - - OC.Notification.show(t('files', 'Uploading that item is not supported'), {type: 'error'}); - }); - - fileupload.on('fileuploadchunksend', function(e, data) { - // modify the request to adjust it to our own chunking - var upload = self.getUpload(data); - if (!upload) { - // likely cancelled - return - } - var range = data.contentRange.split(' ')[1]; - var chunkId = range.split('/')[0].split('-')[0]; - // Use a numeric chunk id and set the Destination header on all request for ChunkingV2 - chunkId = Math.ceil((data.chunkSize+Number(chunkId)) / upload.uploader.fileUploadParam.maxChunkSize); - data.headers['Destination'] = self.davClient._buildUrl(upload.getTargetDestination()); - - data.url = OC.getRootPath() + - '/remote.php/dav/uploads' + - '/' + OC.getCurrentUser().uid + - '/' + upload.getId() + - '/' + chunkId; - delete data.contentRange; - delete data.headers['Content-Range']; - }); - fileupload.on('fileuploaddone', function(e, data) { - var upload = self.getUpload(data); - - self._pendingUploadDoneCount++; - - upload.done().always(function() { - self._pendingUploadDoneCount--; - if (self._activeUploadCount() === 0 && self._pendingUploadDoneCount === 0) { - // All the uploads ended and there is no pending - // operation, so hide the progress bar. - // Note that this happens here only with chunked - // uploads; if the upload was non-chunked then this - // handler is immediately executed, before the - // jQuery upload done handler that removes the - // upload from the list, and thus at this point - // there is still at least one upload that has not - // ended (although the upload stop handler is always - // executed after all the uploads have ended, which - // hides the progress bar in that case). - self._hideProgressBar(); - } - }).done(function() { - self.trigger('done', e, upload); - }).fail(function(status, response) { - if (upload.aborted) { - return - } - - var message = response.message; - if (status === 507) { - // not enough space - OC.Notification.show(message || t('files', 'Not enough free space'), {type: 'error'}); - self.cancelUploads(); - } else if (status === 409) { - OC.Notification.show(message || t('files', 'Target folder does not exist any more'), {type: 'error'}); - } else if (status === 403) { - OC.Notification.show(message || t('files', 'Operation is blocked by access control'), {type: 'error'}); - } else { - OC.Notification.show(message || t('files', 'Error when assembling chunks, status code {status}', {status: status}), {type: 'error'}); - } - self.trigger('fail', e, data); - }); - }); - fileupload.on('fileuploaddrop', function(e, data) { - self.trigger('drop', e, data); - if (e.isPropagationStopped()) { - return false; - } - }); - } - window.onbeforeunload = function() { - return self.confirmBeforeUnload(); - } - } - - //add multiply file upload attribute to all browsers except konqueror (which crashes when it's used) - if (navigator.userAgent.search(/konqueror/i) === -1) { - this.$uploadEl.attr('multiple', 'multiple'); - } - - return this.fileUploadParam; - } -}, OC.Backbone.Events); |