2222 */
2323
2424import type { Upload } from '@nextcloud/upload'
25- import type { FileStat , ResponseDataDetailed } from 'webdav '
25+ import type { RootDirectory } from './DropServiceUtils '
2626
27- import { emit } from '@nextcloud/event-bus'
28- import { Folder , Node , NodeStatus , davGetClient , davGetDefaultPropfind , davResultToNode , davRootPath } from '@nextcloud/files'
29- import { getUploader , hasConflict , openConflictPicker } from '@nextcloud/upload'
27+ import { Folder , Node , NodeStatus , davRootPath } from '@nextcloud/files'
28+ import { getUploader , hasConflict } from '@nextcloud/upload'
3029import { join } from 'path'
3130import { joinPaths } from '@nextcloud/paths'
3231import { showError , showInfo , showSuccess , showWarning } from '@nextcloud/dialogs'
3332import { translate as t } from '@nextcloud/l10n'
3433import Vue from 'vue'
3534
35+ import { Directory , traverseTree , resolveConflict , createDirectoryIfNotExists } from './DropServiceUtils'
3636import { handleCopyMoveNodeTo } from '../actions/moveOrCopyAction'
3737import { MoveCopyAction } from '../actions/moveOrCopyActionUtils'
3838import logger from '../logger.js'
3939
40- /**
41- * This represents a Directory in the file tree
42- * We extend the File class to better handling uploading
43- * and stay as close as possible as the Filesystem API.
44- * This also allow us to hijack the size or lastModified
45- * properties to compute them dynamically.
46- */
47- class Directory extends File {
48-
49- /* eslint-disable no-use-before-define */
50- _contents : ( Directory | File ) [ ]
51-
52- constructor ( name , contents : ( Directory | File ) [ ] = [ ] ) {
53- super ( [ ] , name , { type : 'httpd/unix-directory' } )
54- this . _contents = contents
55- }
56-
57- set contents ( contents : ( Directory | File ) [ ] ) {
58- this . _contents = contents
59- }
60-
61- get contents ( ) : ( Directory | File ) [ ] {
62- return this . _contents
63- }
64-
65- get size ( ) {
66- return this . _computeDirectorySize ( this )
67- }
68-
69- get lastModified ( ) {
70- if ( this . _contents . length === 0 ) {
71- return Date . now ( )
72- }
73- return this . _computeDirectoryMtime ( this )
74- }
75-
76- /**
77- * Get the last modification time of a file tree
78- * This is not perfect, but will get us a pretty good approximation
79- * @param directory the directory to traverse
80- */
81- _computeDirectoryMtime ( directory : Directory ) : number {
82- return directory . contents . reduce ( ( acc , file ) => {
83- return file . lastModified > acc
84- // If the file is a directory, the lastModified will
85- // also return the results of its _computeDirectoryMtime method
86- // Fancy recursion, huh?
87- ? file . lastModified
88- : acc
89- } , 0 )
90- }
91-
92- /**
93- * Get the size of a file tree
94- * @param directory the directory to traverse
95- */
96- _computeDirectorySize ( directory : Directory ) : number {
97- return directory . contents . reduce ( ( acc : number , entry : Directory | File ) => {
98- // If the file is a directory, the size will
99- // also return the results of its _computeDirectorySize method
100- // Fancy recursion, huh?
101- return acc + entry . size
102- } , 0 )
103- }
104-
105- }
106-
107- type RootDirectory = Directory & {
108- name : 'root'
109- }
110-
111- /**
112- * Traverse a file tree using the Filesystem API
113- * @param entry the entry to traverse
114- */
115- const traverseTree = async ( entry : FileSystemEntry ) : Promise < Directory | File > => {
116- // Handle file
117- if ( entry . isFile ) {
118- return new Promise < File > ( ( resolve , reject ) => {
119- ( entry as FileSystemFileEntry ) . file ( resolve , reject )
120- } )
121- }
122-
123- // Handle directory
124- logger . debug ( 'Handling recursive file tree' , { entry : entry . name } )
125- const directory = entry as FileSystemDirectoryEntry
126- const entries = await readDirectory ( directory )
127- const contents = ( await Promise . all ( entries . map ( traverseTree ) ) ) . flat ( )
128- return new Directory ( directory . name , contents )
129- }
130-
131- /**
132- * Read a directory using Filesystem API
133- * @param directory the directory to read
134- */
135- const readDirectory = ( directory : FileSystemDirectoryEntry ) : Promise < FileSystemEntry [ ] > => {
136- const dirReader = directory . createReader ( )
137-
138- return new Promise < FileSystemEntry [ ] > ( ( resolve , reject ) => {
139- const entries = [ ] as FileSystemEntry [ ]
140- const getEntries = ( ) => {
141- dirReader . readEntries ( ( results ) => {
142- if ( results . length ) {
143- entries . push ( ...results )
144- getEntries ( )
145- } else {
146- resolve ( entries )
147- }
148- } , ( error ) => {
149- reject ( error )
150- } )
151- }
152-
153- getEntries ( )
154- } )
155- }
156-
157- const createDirectoryIfNotExists = async ( absolutePath : string ) => {
158- const davClient = davGetClient ( )
159- const dirExists = await davClient . exists ( absolutePath )
160- if ( ! dirExists ) {
161- logger . debug ( 'Directory does not exist, creating it' , { absolutePath } )
162- await davClient . createDirectory ( absolutePath , { recursive : true } )
163- const stat = await davClient . stat ( absolutePath , { details : true , data : davGetDefaultPropfind ( ) } ) as ResponseDataDetailed < FileStat >
164- emit ( 'files:node:created' , davResultToNode ( stat . data ) )
165- }
166- }
167-
168- const resolveConflict = async < T extends ( ( Directory | File ) | Node ) > ( files : Array < T > , destination : Folder , contents : Node [ ] ) : Promise < T [ ] > => {
169- try {
170- // List all conflicting files
171- const conflicts = files . filter ( ( file : File | Node ) => {
172- return contents . find ( ( node : Node ) => node . basename === ( file instanceof File ? file . name : file . basename ) )
173- } ) . filter ( Boolean ) as ( File | Node ) [ ]
174-
175- // List of incoming files that are NOT in conflict
176- const uploads = files . filter ( ( file : File | Node ) => {
177- return ! conflicts . includes ( file )
178- } )
179-
180- // Let the user choose what to do with the conflicting files
181- const { selected, renamed } = await openConflictPicker ( destination . path , conflicts , contents )
182-
183- logger . debug ( 'Conflict resolution' , { uploads, selected, renamed } )
184-
185- // If the user selected nothing, we cancel the upload
186- if ( selected . length === 0 && renamed . length === 0 ) {
187- // User skipped
188- showInfo ( t ( 'files' , 'Conflicts resolution skipped' ) )
189- logger . info ( 'User skipped the conflict resolution' )
190- return [ ]
191- }
192-
193- // Update the list of files to upload
194- return [ ...uploads , ...selected , ...renamed ] as ( typeof files )
195- } catch ( error ) {
196- console . error ( error )
197- // User cancelled
198- showError ( t ( 'files' , 'Upload cancelled' ) )
199- logger . error ( 'User cancelled the upload' )
200- }
201-
202- return [ ]
203- }
204-
20540/**
20641 * This function converts a list of DataTransferItems to a file tree.
20742 * It uses the Filesystem API if available, otherwise it falls back to the File API.
@@ -225,7 +60,7 @@ export const dataTransferToFileTree = async (items: DataTransferItem[]): Promise
22560 } ) . map ( ( item ) => {
22661 // MDN recommends to try both, as it might be renamed in the future
22762 return ( item as unknown as { getAsEntry ?: ( ) => FileSystemEntry | undefined } ) ?. getAsEntry ?.( )
228- ?? item . webkitGetAsEntry ( )
63+ ?? item ? .webkitGetAsEntry ?. ( )
22964 ?? item
23065 } ) as ( FileSystemEntry | DataTransferItem ) [ ]
23166
@@ -249,7 +84,8 @@ export const dataTransferToFileTree = async (items: DataTransferItem[]): Promise
24984 // we therefore cannot upload directories recursively.
25085 if ( file . type === 'httpd/unix-directory' || ! file . type ) {
25186 if ( ! warned ) {
252- showWarning ( t ( 'files' , 'Your browser does not support the Filesystem API. Directories will not be uploaded.' ) )
87+ logger . warn ( 'Browser does not support Filesystem API. Directories will not be uploaded' )
88+ showWarning ( t ( 'files' , 'Your browser does not support the Filesystem API. Directories will not be uploaded' ) )
25389 warned = true
25490 }
25591 continue
0 commit comments