aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/services/DropServiceUtils.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/src/services/DropServiceUtils.ts')
-rw-r--r--apps/files/src/services/DropServiceUtils.ts168
1 files changed, 40 insertions, 128 deletions
diff --git a/apps/files/src/services/DropServiceUtils.ts b/apps/files/src/services/DropServiceUtils.ts
index f10a09cfe27..8b6ba2bb71e 100644
--- a/apps/files/src/services/DropServiceUtils.ts
+++ b/apps/files/src/services/DropServiceUtils.ts
@@ -5,155 +5,68 @@
import type { FileStat, ResponseDataDetailed } from 'webdav'
import { emit } from '@nextcloud/event-bus'
-import { Folder, Node, davGetClient, davGetDefaultPropfind, davResultToNode } from '@nextcloud/files'
-import { openConflictPicker } from '@nextcloud/upload'
+import { Folder, Node, davGetClient, davGetDefaultPropfind, davResultToNode, getNavigation, Permission } from '@nextcloud/files'
+import { getConflicts, openConflictPicker, type IDirectory } from '@nextcloud/upload'
import { showError, showInfo } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
-
import logger from '../logger.ts'
-/**
- * This represents a Directory in the file tree
- * We extend the File class to better handling uploading
- * and stay as close as possible as the Filesystem API.
- * This also allow us to hijack the size or lastModified
- * properties to compute them dynamically.
- */
-export class Directory extends File {
-
- /* eslint-disable no-use-before-define */
- _contents: (Directory|File)[]
-
- constructor(name, contents: (Directory|File)[] = []) {
- super([], name, { type: 'httpd/unix-directory' })
- this._contents = contents
- }
-
- set contents(contents: (Directory|File)[]) {
- this._contents = contents
- }
-
- get contents(): (Directory|File)[] {
- return this._contents
- }
-
- get size() {
- return this._computeDirectorySize(this)
- }
-
- get lastModified() {
- if (this._contents.length === 0) {
- return Date.now()
- }
- return this._computeDirectoryMtime(this)
- }
-
- /**
- * Get the last modification time of a file tree
- * This is not perfect, but will get us a pretty good approximation
- * @param directory the directory to traverse
- */
- _computeDirectoryMtime(directory: Directory): number {
- return directory.contents.reduce((acc, file) => {
- return file.lastModified > acc
- // If the file is a directory, the lastModified will
- // also return the results of its _computeDirectoryMtime method
- // Fancy recursion, huh?
- ? file.lastModified
- : acc
- }, 0)
- }
-
- /**
- * Get the size of a file tree
- * @param directory the directory to traverse
- */
- _computeDirectorySize(directory: Directory): number {
- return directory.contents.reduce((acc: number, entry: Directory|File) => {
- // If the file is a directory, the size will
- // also return the results of its _computeDirectorySize method
- // Fancy recursion, huh?
- return acc + entry.size
- }, 0)
+export const createDirectoryIfNotExists = async (absolutePath: string) => {
+ const davClient = davGetClient()
+ const dirExists = await davClient.exists(absolutePath)
+ if (!dirExists) {
+ logger.debug('Directory does not exist, creating it', { absolutePath })
+ await davClient.createDirectory(absolutePath, { recursive: true })
+ const stat = await davClient.stat(absolutePath, { details: true, data: davGetDefaultPropfind() }) as ResponseDataDetailed<FileStat>
+ emit('files:node:created', davResultToNode(stat.data))
}
-
-}
-
-export type RootDirectory = Directory & {
- name: 'root'
}
/**
- * Traverse a file tree using the Filesystem API
- * @param entry the entry to traverse
+ * Helper function to resolve conflicts when using batchUpload from `@nextcloud/upload`
+ * @param files Files that are going to be uploaded
+ * @param currentPath The path where the files are uploaded to
*/
-export const traverseTree = async (entry: FileSystemEntry): Promise<Directory|File> => {
- // Handle file
- if (entry.isFile) {
- return new Promise<File>((resolve, reject) => {
- (entry as FileSystemFileEntry).file(resolve, reject)
- })
+export async function resolveUploadConflicts(files: Array<IDirectory|File>, currentPath: string): Promise<Array<IDirectory|File>> {
+ const view = getNavigation().active!
+ try {
+ const { contents, folder } = await view.getContents(currentPath)
+ return await resolveConflict(files, folder, contents)
+ } catch (error) {
+ // If the folder does not exist then we can upload everything
+ logger.debug('Could not fetch folder with contents.', { error, currentPath })
}
-
- // Handle directory
- logger.debug('Handling recursive file tree', { entry: entry.name })
- const directory = entry as FileSystemDirectoryEntry
- const entries = await readDirectory(directory)
- const contents = (await Promise.all(entries.map(traverseTree))).flat()
- return new Directory(directory.name, contents)
+ return files
}
/**
- * Read a directory using Filesystem API
- * @param directory the directory to read
+ * Resolve conflicts on dropping files
+ * @param files Files to be uploaded
+ * @param destination The current folder to upload to
+ * @param contents The content of the folder
*/
-const readDirectory = (directory: FileSystemDirectoryEntry): Promise<FileSystemEntry[]> => {
- 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()
- })
-}
-
-export const createDirectoryIfNotExists = async (absolutePath: string) => {
- const davClient = davGetClient()
- const dirExists = await davClient.exists(absolutePath)
- if (!dirExists) {
- logger.debug('Directory does not exist, creating it', { absolutePath })
- await davClient.createDirectory(absolutePath, { recursive: true })
- const stat = await davClient.stat(absolutePath, { details: true, data: davGetDefaultPropfind() }) as ResponseDataDetailed<FileStat>
- emit('files:node:created', davResultToNode(stat.data))
+export async function resolveConflict<T extends((IDirectory|File)|Node)>(files: Array<T>, destination: Folder, contents: Node[]): Promise<T[]> {
+ // No permissions
+ if (!(destination.permissions & Permission.CREATE)) {
+ return []
}
-}
-export const resolveConflict = async <T extends ((Directory|File)|Node)>(files: Array<T>, destination: Folder, contents: Node[]): Promise<T[]> => {
try {
- // List all conflicting files
- const conflicts = files.filter((file: File|Node) => {
- return contents.find((node: Node) => node.basename === (file instanceof File ? file.name : file.basename))
- }).filter(Boolean) as (File|Node)[]
+ const conflicts = getConflicts(files, contents)
+ // No conflicts thus upload all
+ if (conflicts.length === 0) {
+ return files
+ }
// List of incoming files that are NOT in conflict
const uploads = files.filter((file: File|Node) => {
- return !conflicts.includes(file)
+ return !(conflicts as unknown[]).includes(file)
})
+ logger.debug('Starting conflict resolution', { path: destination.path, conflicts, contents })
+
// Let the user choose what to do with the conflicting files
- const { selected, renamed } = await openConflictPicker(destination.path, conflicts, contents)
+ const { selected, renamed } = await openConflictPicker(destination.path, conflicts, contents, { recursive: true })
logger.debug('Conflict resolution', { uploads, selected, renamed })
@@ -168,10 +81,9 @@ export const resolveConflict = async <T extends ((Directory|File)|Node)>(files:
// Update the list of files to upload
return [...uploads, ...selected, ...renamed] as (typeof files)
} catch (error) {
- console.error(error)
+ logger.error('User cancelled the upload', { error })
// User cancelled
showError(t('files', 'Upload cancelled'))
- logger.error('User cancelled the upload')
}
return []