feat: send or move files and folders to another machine or an SSH host from the move dialog, streamed as tar with progress, speed and cancel; same conflict rules as a local move

This commit is contained in:
redtea committed 2026-10-14 21:29:25 +03:00
1 parent 1a38c2ac18
commit 18d6684c55
7 files changed
+1274 -28

No files matched your search

+31 -19
View File
@@ -148,32 +148,22 @@ func (s *sshShell) Close() error {
return nil
}
// runSSH connects to the target and bridges its shell to the session.
func (a *Agent) runSSH(parent context.Context, sid string, t proto.SSHTarget) {
status := func(st, note string) {
a.postOut(context.Background(), sid, proto.OutMessage{Status: st, Note: note})
}
fail := func(err error) {
log.Printf("ssh %s: %v", sid, err)
status(proto.SSHErr, err.Error())
a.postOut(context.Background(), sid, proto.OutMessage{Data: b64.EncodeToString([]byte("\x1b[31m" + err.Error() + "\x1b[0m\r\n"))})
a.postOut(context.Background(), sid, proto.OutMessage{Exit: true})
}
// sshConnect connects and signs in to t, saying how it goes by status (the
// SSHStatus steps). It returns the client and the host key's fingerprint.
func (a *Agent) sshConnect(ctx context.Context, t proto.SSHTarget, status func(st, note string)) (*ssh.Client, string, error) {
if t.Port == 0 {
t.Port = 22
}
addr := net.JoinHostPort(t.Host, strconv.Itoa(t.Port))
auth, err := sshAuth(t)
if err != nil {
fail(err)
return
return nil, "", err
}
status(proto.SSHDial, "подключение к "+addr)
d := net.Dialer{Timeout: 15 * time.Second, KeepAlive: 30 * time.Second}
conn, err := d.DialContext(parent, "tcp", addr)
conn, err := d.DialContext(ctx, "tcp", addr)
if err != nil {
fail(fmt.Errorf("не подключиться к %s: %v", addr, err))
return
return nil, "", fmt.Errorf("не подключиться к %s: %v", addr, err)
}
status(proto.SSHAuth, "вход как "+t.User)
var fp string
@@ -190,11 +180,33 @@ func (a *Agent) runSSH(parent context.Context, sid string, t proto.SSHTarget) {
if strings.Contains(msg, "unable to authenticate") {
msg = "сервер не принял вход как " + t.User + " - проверьте пароль или ключ"
}
fail(errors.New(msg))
return
return nil, "", errors.New(msg)
}
_ = conn.SetDeadline(time.Time{})
client := ssh.NewClient(c, chans, reqs)
return ssh.NewClient(c, chans, reqs), fp, nil
}
// runSSH connects to the target and bridges its shell to the session.
func (a *Agent) runSSH(parent context.Context, sid string, t proto.SSHTarget) {
status := func(st, note string) {
a.postOut(context.Background(), sid, proto.OutMessage{Status: st, Note: note})
}
fail := func(err error) {
log.Printf("ssh %s: %v", sid, err)
status(proto.SSHErr, err.Error())
a.postOut(context.Background(), sid, proto.OutMessage{Data: b64.EncodeToString([]byte("\x1b[31m" + err.Error() + "\x1b[0m\r\n"))})
a.postOut(context.Background(), sid, proto.OutMessage{Exit: true})
}
client, fp, err := a.sshConnect(parent, t, status)
if err != nil {
fail(err)
return
}
port := t.Port
if port == 0 {
port = 22
}
addr := net.JoinHostPort(t.Host, strconv.Itoa(port))
sess, err := client.NewSession()
if err != nil {
client.Close()