feat: drag files between file panes (move or copy, SSH hosts too) and into a terminal of the same machine

This commit is contained in:
redtea committed 2026-10-15 21:42:21 +03:00
1 parent a30b89f1d8
commit f2740c50b4
12 files changed
+484 -32

No files matched your search

+48 -10
View File
@@ -179,7 +179,15 @@ func (a *Agent) doShipOut(parent context.Context, env proto.TaskEnvelope) proto.
return proto.TaskResult{Error: err.Error()}
}
src := filepath.Clean(args.Src)
if _, err := os.Lstat(src); err != nil {
var conn *sshfsConn
if args.Conn != "" {
// The files are on an SSH host this agent holds a connection to.
c, err := sshfsGet(args.Conn)
if err != nil {
return proto.TaskResult{Error: err.Error()}
}
conn, src = c, path.Clean(args.Src)
} else if _, err := os.Lstat(src); err != nil {
return proto.TaskResult{Error: err.Error()}
}
ctx, cancel := context.WithCancel(parent)
@@ -187,14 +195,22 @@ func (a *Agent) doShipOut(parent context.Context, env proto.TaskEnvelope) proto.
m := &shipMeter{}
m.note.Store("подсчёт")
go a.reportShip(ctx, args.Job, m, cancel)
m.total.Store(treeSize(ctx, src))
if conn != nil {
m.total.Store(a.remoteSize(conn, src))
} else {
m.total.Store(treeSize(ctx, src))
}
pack := func(w io.Writer) error { return packTar(ctx, w, src, args.Name, m) }
if conn != nil {
pack = func(w io.Writer) error { return a.packRemote(ctx, w, conn, src, args.Name, m) }
}
var err error
if args.SSH != nil {
err = a.shipSSH(ctx, args, src, m)
} else {
m.note.Store("")
err = a.shipRelay(ctx, args, src, m)
err = a.shipRelay(ctx, args, pack)
}
if err != nil {
if ctx.Err() != nil && parent.Err() == nil {
@@ -218,14 +234,14 @@ func (a *Agent) relayURL(id string, i int) string {
// shipRelay posts the tar stream in pieces, a few at once; the last carries
// X-Last.
func (a *Agent) shipRelay(ctx context.Context, args proto.FSShipArgs, src string, m *shipMeter) error {
func (a *Agent) shipRelay(ctx context.Context, args proto.FSShipArgs, pack func(io.Writer) error) error {
if args.Relay == "" || args.Chunk <= 0 {
return errors.New("не задана передача")
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
pr, pw := io.Pipe()
go func() { pw.CloseWithError(packTar(ctx, pw, src, args.Name, m)) }()
go func() { pw.CloseWithError(pack(pw)) }()
defer pr.Close()
type piece struct {
@@ -306,11 +322,21 @@ func (a *Agent) doShipIn(ctx context.Context, env proto.TaskEnvelope) proto.Task
return proto.TaskResult{Error: err.Error()}
}
dir := filepath.Clean(args.Dir)
if _, err := os.Lstat(filepath.Join(dir, args.Name)); err == nil && args.Conflict != "replace" && args.Conflict != "skip" {
return proto.TaskResult{Error: filepath.Join(dir, args.Name) + " уже существует"}
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return proto.TaskResult{Error: err.Error()}
var conn *sshfsConn
if args.Conn != "" {
// Into an SSH host this agent holds a connection to.
c, err := sshfsGet(args.Conn)
if err != nil {
return proto.TaskResult{Error: err.Error()}
}
conn, dir = c, path.Clean(args.Dir)
} else {
if _, err := os.Lstat(filepath.Join(dir, args.Name)); err == nil && args.Conflict != "replace" && args.Conflict != "skip" {
return proto.TaskResult{Error: filepath.Join(dir, args.Name) + " уже существует"}
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return proto.TaskResult{Error: err.Error()}
}
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
@@ -339,6 +365,18 @@ func (a *Agent) doShipIn(ctx context.Context, env proto.TaskEnvelope) proto.Task
}
}
}()
if conn != nil {
err := a.unpackRemote(conn, pr, dir, args.Name, args.Conflict)
if err == nil {
_, _ = io.Copy(io.Discard, pr)
}
pr.CloseWithError(errors.New("распаковка закончена"))
if err != nil {
return proto.TaskResult{Error: err.Error()}
}
data, _ := json.Marshal(map[string]int{})
return proto.TaskResult{OK: true, Data: data}
}
u := &unpacker{dir: dir, name: args.Name, conflict: args.Conflict}
if os.Geteuid() == 0 {
// New things belong to whoever owns the folder they land in.