summaryrefslogtreecommitdiffstats
path: root/apps/files/src/services
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2023-12-02 03:05:30 +0100
committerFerdinand Thiessen <opensource@fthiessen.de>2023-12-06 16:45:29 +0100
commitdd8897bb3155b6b4e56f10ca7a8e33578fbdd1ea (patch)
tree5ee8f874ed645d48a30a4d24b66638777a826cf2 /apps/files/src/services
parente1ecf798d9ccce0b5d0c1b90d6cd93e22c2d9588 (diff)
downloadnextcloud-server-dd8897bb3155b6b4e56f10ca7a8e33578fbdd1ea.tar.gz
nextcloud-server-dd8897bb3155b6b4e56f10ca7a8e33578fbdd1ea.zip
fix(files): Correctly handle dropping folders on file list
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'apps/files/src/services')
-rw-r--r--apps/files/src/services/DropService.ts133
1 files changed, 133 insertions, 0 deletions
diff --git a/apps/files/src/services/DropService.ts b/apps/files/src/services/DropService.ts
new file mode 100644
index 00000000000..4b4f98a01d6
--- /dev/null
+++ b/apps/files/src/services/DropService.ts
@@ -0,0 +1,133 @@
+/**
+ * @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>
+ *
+ * @author Ferdinand Thiessen <opensource@fthiessen.de>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import type { Upload } from '@nextcloud/upload'
+import type { FileStat, ResponseDataDetailed } from 'webdav'
+
+import { showError } from '@nextcloud/dialogs'
+import { emit } from '@nextcloud/event-bus'
+import { davGetClient, davGetDefaultPropfind, davResultToNode, davRootPath } from '@nextcloud/files'
+import { translate as t } from '@nextcloud/l10n'
+import { getUploader } from '@nextcloud/upload'
+import logger from '../logger.js'
+
+export const handleDrop = async (data: DataTransfer) => {
+ // TODO: Maybe handle `getAsFileSystemHandle()` in the future
+
+ const uploads = [] as Upload[]
+ for (const item of data.items) {
+ if (item.kind !== 'file') {
+ logger.debug('Skipping dropped item', { kind: item.kind, type: item.type })
+ continue
+ }
+
+ // MDN recommends to try both, as it might be renamed in the future
+ const entry = (item as unknown as { getAsEntry?: () => FileSystemEntry|undefined})?.getAsEntry?.() ?? item.webkitGetAsEntry()
+
+ // Handle browser issues if Filesystem API is not available. Fallback to File API
+ if (entry === null) {
+ logger.debug('Could not get FilesystemEntry of item, falling back to file')
+ const file = item.getAsFile()
+ if (file === null) {
+ logger.warn('Could not process DataTransferItem', { type: item.type, kind: item.kind })
+ showError(t('files', 'One of the dropped files could not be processed'))
+ } else {
+ uploads.push(await handleFileUpload(file))
+ }
+ } else {
+ logger.debug('Handle recursive upload', { entry: entry.name })
+ // Use Filesystem API
+ uploads.push(...await handleRecursiveUpload(entry))
+ }
+ }
+ return uploads
+}
+
+const handleFileUpload = async (file: File, path: string = '') => {
+ const uploader = getUploader()
+
+ try {
+ return await uploader.upload(`${path}${file.name}`, file)
+ } catch (e) {
+ showError(t('files', 'Uploading "{filename}" failed', { filename: file.name }))
+ throw e
+ }
+}
+
+const handleRecursiveUpload = async (entry: FileSystemEntry, path: string = ''): Promise<Upload[]> => {
+ if (entry.isFile) {
+ return [
+ await new Promise<Upload>((resolve, reject) => {
+ (entry as FileSystemFileEntry).file(
+ async (file) => resolve(await handleFileUpload(file, path)),
+ (error) => reject(error),
+ )
+ }),
+ ]
+ } else {
+ const directory = entry as FileSystemDirectoryEntry
+ logger.debug('Handle directory recursivly', { name: directory.name })
+
+ // TODO: Implement this on `@nextcloud/upload`
+ const absolutPath = `${davRootPath}${getUploader().destination.path}${path}${directory.name}`
+ const davClient = davGetClient()
+ const dirExists = await davClient.exists(absolutPath)
+ if (!dirExists) {
+ logger.debug('Directory does not exist, creating it', { absolutPath })
+ await davClient.createDirectory(absolutPath, { recursive: true })
+ const stat = await davClient.stat(absolutPath, { details: true, data: davGetDefaultPropfind() }) as ResponseDataDetailed<FileStat>
+ emit('files:node:created', davResultToNode(stat.data))
+ }
+
+ const entries = await readDirectory(directory)
+ // sorted so we upload files first before starting next level
+ const promises = entries.sort((a) => a.isFile ? -1 : 1)
+ .map((file) => handleRecursiveUpload(file, `${path}${directory.name}/`))
+ return (await Promise.all(promises)).flat()
+ }
+}
+
+/**
+ * Read a directory using Filesystem API
+ * @param directory the directory to read
+ */
+function readDirectory(directory: FileSystemDirectoryEntry) {
+ const dirReader = directory.createReader()
+
+ return new Promise<FileSystemEntry[]>((resolve, reject) => {
+ const entries = [] as FileSystemEntry[]
+ const getEntries = () => {
+ dirReader.readEntries((results) => {
+ if (results.length) {
+ entries.push(...results)
+ getEntries()
+ } else {
+ resolve(entries)
+ }
+ }, (error) => {
+ reject(error)
+ })
+ }
+
+ getEntries()
+ })
+}