diff options
Diffstat (limited to 'apps/files/src/services/DropService.ts')
-rw-r--r-- | apps/files/src/services/DropService.ts | 143 |
1 files changed, 27 insertions, 116 deletions
diff --git a/apps/files/src/services/DropService.ts b/apps/files/src/services/DropService.ts index 1013baeda6c..58d8239deb1 100644 --- a/apps/files/src/services/DropService.ts +++ b/apps/files/src/services/DropService.ts @@ -4,17 +4,14 @@ */ import type { Upload } from '@nextcloud/upload' -import type { RootDirectory } from './DropServiceUtils' -import { Folder, Node, NodeStatus, davRootPath } from '@nextcloud/files' +import { Folder, Node, NodeStatus } from '@nextcloud/files' import { getUploader, hasConflict } from '@nextcloud/upload' -import { join } from 'path' -import { joinPaths } from '@nextcloud/paths' import { showError, showInfo, showSuccess, showWarning } from '@nextcloud/dialogs' import { translate as t } from '@nextcloud/l10n' import Vue from 'vue' -import { Directory, traverseTree, resolveConflict, createDirectoryIfNotExists } from './DropServiceUtils' +import { resolveConflict } from './DropServiceUtils' import { handleCopyMoveNodeTo } from '../actions/moveOrCopyAction' import { MoveCopyAction } from '../actions/moveOrCopyActionUtils' import logger from '../logger.ts' @@ -28,7 +25,7 @@ import logger from '../logger.ts' * * @param items the list of DataTransferItems */ -export const dataTransferToFileTree = async (items: DataTransferItem[]): Promise<RootDirectory> => { +export async function onDropExternalFiles(items: DataTransferItem[]): Promise<Upload[]> { // Check if the browser supports the Filesystem API // We need to cache the entries to prevent Blink engine bug that clears // the list (`data.items`) after first access props of one of the entries @@ -41,125 +38,39 @@ export const dataTransferToFileTree = async (items: DataTransferItem[]): Promise return true }).map((item) => { // MDN recommends to try both, as it might be renamed in the future - return (item as unknown as { getAsEntry?: () => FileSystemEntry|undefined })?.getAsEntry?.() - ?? item?.webkitGetAsEntry?.() - ?? item - }) as (FileSystemEntry | DataTransferItem)[] - - let warned = false - const fileTree = new Directory('root') as RootDirectory - - // Traverse the file tree - for (const entry of entries) { - // Handle browser issues if Filesystem API is not available. Fallback to File API - if (entry instanceof DataTransferItem) { - logger.warn('Could not get FilesystemEntry of item, falling back to file') - - const file = entry.getAsFile() - if (file === null) { - logger.warn('Could not process DataTransferItem', { type: entry.type, kind: entry.kind }) - showError(t('files', 'One of the dropped files could not be processed')) - continue - } - - // Warn the user that the browser does not support the Filesystem API - // we therefore cannot upload directories recursively. - if (file.type === 'httpd/unix-directory' || !file.type) { - if (!warned) { - logger.warn('Browser does not support Filesystem API. Directories will not be uploaded') - showWarning(t('files', 'Your browser does not support the Filesystem API. Directories will not be uploaded')) - warned = true - } - continue + return (item as unknown as DataTransferItem & { getAsEntry?: () => FileSystemEntry|undefined }).getAsEntry?.() + ?? item.webkitGetAsEntry?.() + ?? item.getAsFile() + }).filter((item: FileSystemEntry | File | null) => { + if (item === null) { + showWarning(t('files', 'One of the dropped files could not be processed')) + return false } + return true + }) as (FileSystemEntry | File)[] - fileTree.contents.push(file) - continue - } - - // Use Filesystem API - try { - fileTree.contents.push(await traverseTree(entry)) - } catch (error) { - // Do not throw, as we want to continue with the other files - logger.error('Error while traversing file tree', { error }) - } - } - - return fileTree -} - -export const onDropExternalFiles = async (root: RootDirectory, destination: Folder, contents: Node[]): Promise<Upload[]> => { - const uploader = getUploader() - - // Check for conflicts on root elements - if (await hasConflict(root.contents, contents)) { - root.contents = await resolveConflict(root.contents, destination, contents) - } - - if (root.contents.length === 0) { - logger.info('No files to upload', { root }) - showInfo(t('files', 'No files to upload')) + if (entries.length === 0) { + logger.info('No valid files were dropped.') return [] } - // Let's process the files - logger.debug(`Uploading files to ${destination.path}`, { root, contents: root.contents }) - const queue = [] as Promise<Upload>[] - - const uploadDirectoryContents = async (directory: Directory, path: string) => { - for (const file of directory.contents) { - // This is the relative path to the resource - // from the current uploader destination - const relativePath = join(path, file.name) - - // If the file is a directory, we need to create it first - // then browse its tree and upload its contents. - if (file instanceof Directory) { - const absolutePath = joinPaths(davRootPath, destination.path, relativePath) - try { - console.debug('Processing directory', { relativePath }) - await createDirectoryIfNotExists(absolutePath) - await uploadDirectoryContents(file, relativePath) - } catch (error) { - showError(t('files', 'Unable to create the directory {directory}', { directory: file.name })) - logger.error('', { error, absolutePath, directory: file }) - } - continue - } - - // If we've reached a file, we can upload it - logger.debug('Uploading file to ' + join(destination.path, relativePath), { file }) - - // Overriding the root to avoid changing the current uploader context - queue.push(uploader.upload(relativePath, file, destination.source)) - } - } - - // Pause the uploader to prevent it from starting - // while we compute the queue - uploader.pause() - - // Upload the files. Using '/' as the starting point - // as we already adjusted the uploader destination - await uploadDirectoryContents(root, '/') - uploader.start() - - // Wait for all promises to settle - const results = await Promise.allSettled(queue) + // Get the uploader and start the batch upload + const uploader = getUploader() - // Check for errors - const errors = results.filter(result => result.status === 'rejected') - if (errors.length > 0) { - logger.error('Error while uploading files', { errors }) + try { + uploader.pause() + const promise = uploader.batchUpload('', entries) + uploader.start() + + const uploads = await promise + logger.debug('Files uploaded successfully', { uploads }) + showSuccess(t('files', 'Files uploaded successfully')) + return uploads + } catch (error) { + logger.error('Error while uploading files', { error }) showError(t('files', 'Some files could not be uploaded')) return [] } - - logger.debug('Files uploaded successfully') - showSuccess(t('files', 'Files uploaded successfully')) - - return Promise.all(queue) } export const onDropInternalFiles = async (nodes: Node[], destination: Folder, contents: Node[], isCopy = false) => { |