diff --git a/internal/fs/remote/client.go b/internal/fs/remote/client.go index 34e534f..d509221 100644 --- a/internal/fs/remote/client.go +++ b/internal/fs/remote/client.go @@ -574,6 +574,24 @@ func (c *SSHClient) walk(dirPath string, walkFn walkFunc, info os.FileInfo) erro // filepathSkipDir is used as a return value from Walk to skip a directory. var filepathSkipDir = fmt.Errorf("skip this directory") +// DirectorySize recursively walks a remote directory and sums up file sizes. +func (c *SSHClient) DirectorySize(dirPath string) (int64, error) { + var total int64 + err := c.Walk(dirPath, func(_ string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + total += info.Size() + } + return nil + }) + if err != nil { + return 0, err + } + return total, nil +} + // SftpToFileInfo converts an os.FileInfo to a vfs-compatible file info. // This is used for consistent file information handling across local and remote. func SftpToFileInfo(name string, info os.FileInfo) (os.FileInfo, error) { diff --git a/internal/ui/model.go b/internal/ui/model.go index 4a5dbc6..40035e9 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -2325,6 +2325,9 @@ func (m *Model) handleDirSize() (tea.Model, tea.Cmd) { log.Printf("[ACTION] DirSize: path=%s pane=%s", selected.Path, m.active) m.busy = true m.status = fmt.Sprintf("Calculating directory size for %s", selected.DisplayName()) + if mount, ok := m.activePane().CurrentRemote(); ok { + return m, remoteDirSizeCmd(mount.Client, selected.Path) + } return m, dirSizeCmd(selected.Path) } @@ -4692,6 +4695,13 @@ func dirSizeCmd(path string) tea.Cmd { } } +func remoteDirSizeCmd(client *remote.SSHClient, path string) tea.Cmd { + return func() tea.Msg { + size, err := client.DirectorySize(path) + return dirSizeMsg{path: path, size: size, err: err} + } +} + func copyPlanCmd(kind fileOpKind, sourcePaths []string, targetDir string, overwrite bool, existingTargets int) tea.Cmd { return func() tea.Msg { stats := vfs.TransferStats{}