diff --git a/api_directory.go b/api_directory.go index 61678d9..61d5cb4 100644 --- a/api_directory.go +++ b/api_directory.go @@ -142,5 +142,17 @@ func (s *Service) HandleCopyDirectory( req *httpserver.Request, resp *httpserver.Response, ) *httpserver.Response { + params := req.Binded.(*CopyParams) + srcPath := utils.NormalizePath(params.SrcPath) + dstPath := utils.NormalizePath(params.DstPath) + isDir := params.IsDir + + oldPath := utils.FullPath(srcPath) + newPath := utils.FullPath(dstPath) + if err := s.FileSystemManager.CopyDirectory(req.Context(), oldPath, newPath, isDir); err != nil { + logger.Error("copy dir %s to %s: %v", params.SrcPath, params.DstPath, err) + return resp.InternalServerError("copy dir is failed, " + err.Error()) + } + return resp.NoContent() } diff --git a/engine/filesystem_copy.go b/engine/filesystem_copy.go new file mode 100644 index 0000000..97ff6a8 --- /dev/null +++ b/engine/filesystem_copy.go @@ -0,0 +1,131 @@ +package engine + +import ( + "context" + "fmt" + "gosvc/logger" + "strings" + "time" + + "robotfs/utils" +) + +func (f *FileSystemManager) CopyDirectory(ctx context.Context, oldPath, newPath utils.FullPath, isDir bool) error { + f.Lock() + defer f.Unlock() + + if string(oldPath) == "/" { + return fmt.Errorf("cannot copy root directory") + } + + if isDir && strings.HasPrefix(string(newPath), string(oldPath)) { + return fmt.Errorf("cannot copy directory to a subdirectory of itself") + } + + oldEntry, err := f.FindEntry(ctx, oldPath) + if err != nil { + return fmt.Errorf("%s not found: %v", oldPath, err) + } + + if newEntry, err := f.FindEntry(ctx, newPath); newEntry != nil && err == nil { + return fmt.Errorf("destination %s already exists", newPath) + } + + parentDir, _ := newPath.DirAndName() + if parentDir != "/" { + if parentEntry, _ := f.FindEntry(ctx, utils.FullPath(parentDir)); parentEntry == nil { + return fmt.Errorf("parent directory %s does not exist", parentDir) + } + } + + if err := f.copyEntry(ctx, oldPath, oldEntry, newPath); err != nil { + return fmt.Errorf("copy metadata failed: %v", err) + } + + if err := utils.Clone(f.root, string(oldPath), string(newPath)); err != nil { + return fmt.Errorf("copy file data failed: %v", err) + } + + return nil +} + +func (f *FileSystemManager) copyEntry(ctx context.Context, oldPath utils.FullPath, entry *utils.Entry, newPath utils.FullPath) error { + if err := f.copySelfEntry(ctx, oldPath, entry, newPath, func() error { + if entry.IsDir { + if err := f.copyFolderSubEntries(ctx, oldPath, newPath); err != nil { + return err + } + } + return nil + }); err != nil { + return fmt.Errorf("fail to copy %s => %s: %v", oldPath, newPath, err) + } + + return nil +} + +func (f *FileSystemManager) copyFolderSubEntries(ctx context.Context, oldPath utils.FullPath, newPath utils.FullPath) error { + logger.Info("copying folder %s => %s", oldPath, newPath) + + lastFileName := "" + includeLastFile := false + for { + entries := make([]*utils.Entry, 0, 1000) + lastFileName, err := f.doListDirectoryEntries(ctx, oldPath, lastFileName, includeLastFile, 1000, func(entry *utils.Entry) bool { + entries = append(entries, entry) + return true + }) + if err != nil { + return err + } + + for _, item := range entries { + itemOldPath := oldPath.Child(item.FullPath.Name()) + itemNewPath := newPath.Child(item.FullPath.Name()) + err := f.copyEntry(ctx, itemOldPath, item, itemNewPath) + if err != nil { + return err + } + } + + if lastFileName == "" { + break + } + } + + return nil +} + +func (f *FileSystemManager) copySelfEntry(ctx context.Context, oldPath utils.FullPath, entry *utils.Entry, newPath utils.FullPath, copyFolderSubEntries func() error) error { + logger.Info("copying entry %s => %s", oldPath, newPath) + + if oldPath == newPath { + logger.Info("skip copying entry %s => %s", oldPath, newPath) + return nil + } + + newEntry := &utils.Entry{ + FullPath: newPath, + IsDir: entry.IsDir, + Size: entry.Size, + CreateTime: time.Now().Unix(), + S3Key: entry.S3Key, + ContentType: entry.ContentType, + Etag: entry.Etag, + VersionID: entry.VersionID, + LastModificationTime: time.Now().Unix(), + Extended: entry.Extended, + } + + if createErr := f.meta.InsertEntry(ctx, newEntry); createErr != nil { + return createErr + } + + if copyFolderSubEntries != nil { + if copyChildrenErr := copyFolderSubEntries(); copyChildrenErr != nil { + return copyChildrenErr + } + } + + return nil +} diff --git a/engine/filesystem_manager.go b/engine/filesystem_manager.go index dd3ca0b..93b873a 100644 --- a/engine/filesystem_manager.go +++ b/engine/filesystem_manager.go @@ -361,7 +361,7 @@ func (f *FileSystemManager) CopyFile(ctx context.Context, srcPath, dstPath utils return fmt.Errorf("find src file failed: %v", err) } - if dstEntry, _ := f.FindEntry(ctx, dstPath); dstEntry != nil { + if dstEntry, err := f.FindEntry(ctx, dstPath); dstEntry != nil && err == nil { return fmt.Errorf("dst file %s already exists", dstPath) } diff --git a/router.go b/router.go index 234f89d..b19c986 100644 --- a/router.go +++ b/router.go @@ -63,7 +63,6 @@ func (s *Service) RegisterRouteRules() { Handler: s.HandleRenameDirectory, Bind: &RenameParams{}, }, - // Todo: juicefs clone { Path: "/dir/copy", Method: http.MethodPost,