Skip to content

Commit 4ba9565

Browse files
committed
fix move & copy for prefix fs
1 parent ce9775c commit 4ba9565

File tree

11 files changed

+157
-173
lines changed

11 files changed

+157
-173
lines changed

frontend/app/store/wshclientapi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ class RpcApiType {
293293
}
294294

295295
// command "remotefilecopy" [call]
296-
RemoteFileCopyCommand(client: WshClient, data: CommandFileCopyData, opts?: RpcOpts): Promise<void> {
296+
RemoteFileCopyCommand(client: WshClient, data: CommandFileCopyData, opts?: RpcOpts): Promise<boolean> {
297297
return client.wshRpcCall("remotefilecopy", data, opts);
298298
}
299299

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ require (
3434
github.com/wavetermdev/htmltoken v0.2.0
3535
golang.org/x/crypto v0.33.0
3636
golang.org/x/mod v0.23.0
37+
golang.org/x/sync v0.11.0
3738
golang.org/x/sys v0.30.0
3839
golang.org/x/term v0.29.0
3940
google.golang.org/api v0.221.0
@@ -95,7 +96,6 @@ require (
9596
go.uber.org/atomic v1.7.0 // indirect
9697
golang.org/x/net v0.35.0 // indirect
9798
golang.org/x/oauth2 v0.26.0 // indirect
98-
golang.org/x/sync v0.11.0 // indirect
9999
golang.org/x/text v0.22.0 // indirect
100100
golang.org/x/time v0.10.0 // indirect
101101
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect

pkg/remote/fileshare/fileshare.go

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -129,19 +129,11 @@ func Move(ctx context.Context, data wshrpc.CommandFileCopyData) error {
129129
return fmt.Errorf("error creating fileshare client, could not parse destination connection %s", data.DestUri)
130130
}
131131
if srcConn.Host != destConn.Host {
132-
finfo, err := srcClient.Stat(ctx, srcConn)
133-
if err != nil {
134-
return fmt.Errorf("cannot stat %q: %w", data.SrcUri, err)
135-
}
136-
recursive := data.Opts != nil && data.Opts.Recursive
137-
if finfo.IsDir && data.Opts != nil && !recursive {
138-
return fmt.Errorf("cannot move directory %q to %q without recursive flag", data.SrcUri, data.DestUri)
139-
}
140-
err = destClient.CopyRemote(ctx, srcConn, destConn, srcClient, data.Opts)
132+
srcIsDir, err := destClient.CopyRemote(ctx, srcConn, destConn, srcClient, data.Opts)
141133
if err != nil {
142134
return fmt.Errorf("cannot copy %q to %q: %w", data.SrcUri, data.DestUri, err)
143135
}
144-
return srcClient.Delete(ctx, srcConn, recursive)
136+
return srcClient.Delete(ctx, srcConn, srcIsDir)
145137
} else {
146138
return srcClient.MoveInternal(ctx, srcConn, destConn, data.Opts)
147139
}
@@ -158,9 +150,11 @@ func Copy(ctx context.Context, data wshrpc.CommandFileCopyData) error {
158150
return fmt.Errorf("error creating fileshare client, could not parse destination connection %s", data.DestUri)
159151
}
160152
if srcConn.Host != destConn.Host {
161-
return destClient.CopyRemote(ctx, srcConn, destConn, srcClient, data.Opts)
153+
_, err := destClient.CopyRemote(ctx, srcConn, destConn, srcClient, data.Opts)
154+
return err
162155
} else {
163-
return srcClient.CopyInternal(ctx, srcConn, destConn, data.Opts)
156+
_, err := srcClient.CopyInternal(ctx, srcConn, destConn, data.Opts)
157+
return err
164158
}
165159
}
166160

pkg/remote/fileshare/fstype/fstype.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ type FileShareClient interface {
4040
Mkdir(ctx context.Context, conn *connparse.Connection) error
4141
// Move moves the file within the same connection
4242
MoveInternal(ctx context.Context, srcConn, destConn *connparse.Connection, opts *wshrpc.FileCopyOpts) error
43-
// Copy copies the file within the same connection
44-
CopyInternal(ctx context.Context, srcConn, destConn *connparse.Connection, opts *wshrpc.FileCopyOpts) error
45-
// CopyRemote copies the file between different connections
46-
CopyRemote(ctx context.Context, srcConn, destConn *connparse.Connection, srcClient FileShareClient, opts *wshrpc.FileCopyOpts) error
43+
// Copy copies the file within the same connection. Returns whether the copy source was a directory
44+
CopyInternal(ctx context.Context, srcConn, destConn *connparse.Connection, opts *wshrpc.FileCopyOpts) (bool, error)
45+
// CopyRemote copies the file between different connections. Returns whether the copy source was a directory
46+
CopyRemote(ctx context.Context, srcConn, destConn *connparse.Connection, srcClient FileShareClient, opts *wshrpc.FileCopyOpts) (bool, error)
4747
// Delete deletes the entry at the given path
4848
Delete(ctx context.Context, conn *connparse.Connection, recursive bool) error
4949
// Join joins the given parts to the connection path

pkg/remote/fileshare/fsutil/fsutil.go

Lines changed: 71 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -58,53 +58,12 @@ func GetPathPrefix(conn *connparse.Connection) string {
5858
return pathPrefix
5959
}
6060

61-
func PrefixCopyInternal(ctx context.Context, srcConn, destConn *connparse.Connection, c fstype.FileShareClient, opts *wshrpc.FileCopyOpts, listEntriesPrefix func(ctx context.Context, host string, path string) ([]string, error), copyFunc func(ctx context.Context, host string, path string) error) error {
61+
func PrefixCopyInternal(ctx context.Context, srcConn, destConn *connparse.Connection, c fstype.FileShareClient, opts *wshrpc.FileCopyOpts, listEntriesPrefix func(ctx context.Context, host string, path string) ([]string, error), copyFunc func(ctx context.Context, host string, path string) error) (bool, error) {
6262
log.Printf("PrefixCopyInternal: %v -> %v", srcConn.GetFullURI(), destConn.GetFullURI())
63-
merge := opts != nil && opts.Merge
64-
overwrite := opts != nil && opts.Overwrite
65-
if overwrite && merge {
66-
return fmt.Errorf("cannot specify both overwrite and merge")
67-
}
6863
srcHasSlash := strings.HasSuffix(srcConn.Path, fspath.Separator)
69-
srcPath, err := CleanPathPrefix(srcConn.Path)
70-
if err != nil {
71-
return fmt.Errorf("error cleaning source path: %w", err)
72-
}
73-
destHasSlash := strings.HasSuffix(destConn.Path, fspath.Separator)
74-
destPath, err := CleanPathPrefix(destConn.Path)
75-
if err != nil {
76-
return fmt.Errorf("error cleaning destination path: %w", err)
77-
}
78-
if !srcHasSlash {
79-
if !destHasSlash {
80-
destPath += fspath.Separator
81-
}
82-
destPath += fspath.Base(srcPath)
83-
}
84-
destConn.Path = destPath
85-
destInfo, err := c.Stat(ctx, destConn)
86-
destExists := err == nil && !destInfo.NotFound
87-
if err != nil && !errors.Is(err, fs.ErrNotExist) {
88-
return fmt.Errorf("error getting destination file info: %w", err)
89-
}
90-
91-
srcInfo, err := c.Stat(ctx, srcConn)
64+
srcPath, destPath, srcInfo, err := DetermineCopyDestPath(ctx, srcConn, destConn, c, c, opts)
9265
if err != nil {
93-
return fmt.Errorf("error getting source file info: %w", err)
94-
}
95-
if destExists {
96-
if overwrite {
97-
err = c.Delete(ctx, destConn, true)
98-
if err != nil {
99-
return fmt.Errorf("error deleting conflicting destination file: %w", err)
100-
}
101-
} else if destInfo.IsDir && srcInfo.IsDir {
102-
if !merge {
103-
return fmt.Errorf("destination and source are both directories, neither merge nor overwrite specified: %v", destConn.GetFullURI())
104-
}
105-
} else {
106-
return fmt.Errorf("destination already exists, overwrite not specified: %v", destConn.GetFullURI())
107-
}
66+
return false, err
10867
}
10968
if srcInfo.IsDir {
11069
if !srcHasSlash {
@@ -114,7 +73,7 @@ func PrefixCopyInternal(ctx context.Context, srcConn, destConn *connparse.Connec
11473
log.Printf("Copying directory: %v -> %v", srcPath, destPath)
11574
entries, err := listEntriesPrefix(ctx, srcConn.Host, srcPath)
11675
if err != nil {
117-
return fmt.Errorf("error listing source directory: %w", err)
76+
return false, fmt.Errorf("error listing source directory: %w", err)
11877
}
11978

12079
tree := pathtree.NewTree(srcPath, fspath.Separator)
@@ -129,7 +88,7 @@ func PrefixCopyInternal(ctx context.Context, srcConn, destConn *connparse.Connec
12988
if !srcHasSlash {
13089
prefixToRemove = fspath.Dir(srcPath) + fspath.Separator
13190
}
132-
return tree.Walk(func(path string, numChildren int) error {
91+
return true, tree.Walk(func(path string, numChildren int) error {
13392
// since this is a prefix filesystem, we only care about leafs
13493
if numChildren > 0 {
13594
return nil
@@ -138,56 +97,15 @@ func PrefixCopyInternal(ctx context.Context, srcConn, destConn *connparse.Connec
13897
return copyFunc(ctx, path, destFilePath)
13998
})
14099
} else {
141-
return copyFunc(ctx, srcPath, destPath)
100+
return false, copyFunc(ctx, srcPath, destPath)
142101
}
143102
}
144103

145-
func PrefixCopyRemote(ctx context.Context, srcConn, destConn *connparse.Connection, srcClient, destClient fstype.FileShareClient, destPutFile func(host string, path string, size int64, reader io.Reader) error, opts *wshrpc.FileCopyOpts) error {
146-
merge := opts != nil && opts.Merge
147-
overwrite := opts != nil && opts.Overwrite
148-
if overwrite && merge {
149-
return fmt.Errorf("cannot specify both overwrite and merge")
150-
}
151-
srcHasSlash := strings.HasSuffix(srcConn.Path, fspath.Separator)
152-
destHasSlash := strings.HasSuffix(destConn.Path, fspath.Separator)
153-
destPath, err := CleanPathPrefix(destConn.Path)
154-
if err != nil {
155-
return fmt.Errorf("error cleaning destination path: %w", err)
156-
}
157-
if !srcHasSlash {
158-
if !destHasSlash {
159-
destPath += fspath.Separator
160-
}
161-
destPath += fspath.Base(srcConn.Path)
162-
}
163-
destConn.Path = destPath
164-
destInfo, err := destClient.Stat(ctx, destConn)
165-
destExists := err == nil && !destInfo.NotFound
166-
if err != nil && !errors.Is(err, fs.ErrNotExist) {
167-
return fmt.Errorf("error getting destination file info: %w", err)
168-
}
169-
170-
srcInfo, err := srcClient.Stat(ctx, srcConn)
171-
if err != nil {
172-
return fmt.Errorf("error getting source file info: %w", err)
173-
}
174-
if destExists {
175-
if overwrite {
176-
err = destClient.Delete(ctx, destConn, true)
177-
if err != nil {
178-
return fmt.Errorf("error deleting conflicting destination file: %w", err)
179-
}
180-
} else if destInfo.IsDir && srcInfo.IsDir {
181-
if !merge {
182-
return fmt.Errorf("destination and source are both directories, neither merge nor overwrite specified: %v", destConn.GetFullURI())
183-
}
184-
} else {
185-
return fmt.Errorf("destination already exists, overwrite not specified: %v", destConn.GetFullURI())
186-
}
187-
}
104+
func PrefixCopyRemote(ctx context.Context, srcConn, destConn *connparse.Connection, srcClient, destClient fstype.FileShareClient, destPutFile func(host string, path string, size int64, reader io.Reader) error, opts *wshrpc.FileCopyOpts) (bool, error) {
105+
_, destPath, srcInfo, err := DetermineCopyDestPath(ctx, srcConn, destConn, srcClient, destClient, opts)
188106
if err != nil {
189107
if !errors.Is(err, fs.ErrNotExist) {
190-
return err
108+
return false, err
191109
}
192110
}
193111
log.Printf("Copying: %v -> %v", srcConn.GetFullURI(), destConn.GetFullURI())
@@ -202,8 +120,8 @@ func PrefixCopyRemote(ctx context.Context, srcConn, destConn *connparse.Connecti
202120
return fmt.Errorf("protocol error: source is a directory, but only a single file is being copied")
203121
}
204122
fileName, err := CleanPathPrefix(fspath.Join(destPath, next.Name))
205-
if singleFile && !destHasSlash {
206-
fileName, err = CleanPathPrefix(destConn.Path)
123+
if singleFile {
124+
fileName = destPath
207125
}
208126
if err != nil {
209127
return fmt.Errorf("error cleaning path: %w", err)
@@ -213,9 +131,67 @@ func PrefixCopyRemote(ctx context.Context, srcConn, destConn *connparse.Connecti
213131
})
214132
if err != nil {
215133
cancel(err)
216-
return err
134+
return false, err
135+
}
136+
return srcInfo.IsDir, nil
137+
}
138+
139+
func DetermineCopyDestPath(ctx context.Context, srcConn, destConn *connparse.Connection, srcClient, destClient fstype.FileShareClient, opts *wshrpc.FileCopyOpts) (srcPath, destPath string, srcInfo *wshrpc.FileInfo, err error) {
140+
merge := opts != nil && opts.Merge
141+
overwrite := opts != nil && opts.Overwrite
142+
if overwrite && merge {
143+
return "", "", nil, fmt.Errorf("cannot specify both overwrite and merge")
144+
}
145+
146+
srcHasSlash := strings.HasSuffix(srcConn.Path, fspath.Separator)
147+
srcPath, err = CleanPathPrefix(srcConn.Path)
148+
if err != nil {
149+
return "", "", nil, fmt.Errorf("error cleaning source path: %w", err)
150+
}
151+
destHasSlash := strings.HasSuffix(destConn.Path, fspath.Separator)
152+
destPath, err = CleanPathPrefix(destConn.Path)
153+
if err != nil {
154+
return "", "", nil, fmt.Errorf("error cleaning destination path: %w", err)
155+
}
156+
157+
srcInfo, err = srcClient.Stat(ctx, srcConn)
158+
if err != nil {
159+
return "", "", nil, fmt.Errorf("error getting source file info: %w", err)
160+
}
161+
destInfo, err := destClient.Stat(ctx, destConn)
162+
destExists := err == nil && !destInfo.NotFound
163+
if err != nil && !errors.Is(err, fs.ErrNotExist) {
164+
return "", "", nil, fmt.Errorf("error getting destination file info: %w", err)
165+
}
166+
originalDestPath := destPath
167+
if !srcHasSlash {
168+
if destInfo.IsDir || (!destExists && !destHasSlash && srcInfo.IsDir) {
169+
destPath = fspath.Join(destPath, fspath.Base(srcConn.Path))
170+
}
171+
}
172+
destConn.Path = destPath
173+
if originalDestPath != destPath {
174+
destInfo, err = destClient.Stat(ctx, destConn)
175+
destExists = err == nil && !destInfo.NotFound
176+
if err != nil && !errors.Is(err, fs.ErrNotExist) {
177+
return "", "", nil, fmt.Errorf("error getting destination file info: %w", err)
178+
}
179+
}
180+
if destExists {
181+
if overwrite {
182+
err = destClient.Delete(ctx, destConn, true)
183+
if err != nil {
184+
return "", "", nil, fmt.Errorf("error deleting conflicting destination file: %w", err)
185+
}
186+
} else if destInfo.IsDir && srcInfo.IsDir {
187+
if !merge {
188+
return "", "", nil, fmt.Errorf("destination and source are both directories, neither merge nor overwrite specified: %v", destConn.GetFullURI())
189+
}
190+
} else {
191+
return "", "", nil, fmt.Errorf("destination already exists, overwrite not specified: %v", destConn.GetFullURI())
192+
}
217193
}
218-
return nil
194+
return srcPath, destPath, srcInfo, nil
219195
}
220196

221197
// CleanPathPrefix corrects paths for prefix filesystems (i.e. ones that don't have directories)

0 commit comments

Comments
 (0)