perf: several dragged items cross to another place as one tar stream; older agents get them one by one
This commit is contained in:
1 parent
6916af4236
commit
7d1996dd7d
8 files changed
+301
-72
No files matched your search
@@ -147,3 +147,14 @@ func TestAIChatNeedsUserOn(t *testing.T) {
|
||||
t.Fatalf("on: %d %q", w.Code, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionAtLeast(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
v string
|
||||
want bool
|
||||
}{{"0.2.0", true}, {"0.10.1", true}, {"0.1.9", false}, {"", false}, {"dev", false}, {"1.0", true}} {
|
||||
if got := versionAtLeast(c.v, "0.2.0"); got != c.want {
|
||||
t.Errorf("%q: %v", c.v, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
-12
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -119,6 +120,8 @@ type shipReq struct {
|
||||
// file pane (its connection id); To is still the agent holding ToConn.
|
||||
FromConn string `json:"from_conn"`
|
||||
ToConn string `json:"to_conn"`
|
||||
// More: other things of Src's folder sent along, under their own names.
|
||||
More []string `json:"more"`
|
||||
}
|
||||
|
||||
// POST /api/panel/agents/{id}/fs/ship - start sending; the answer names the
|
||||
@@ -214,7 +217,27 @@ func oldAgent(res proto.TaskResult) proto.TaskResult {
|
||||
}
|
||||
|
||||
func (s *Server) ship1(ctx context.Context, j *shipJob, from string, q shipReq) proto.TaskResult {
|
||||
args := proto.FSShipArgs{Src: q.Src, Name: q.Name, Dir: q.Dir, Conflict: q.Conflict, Job: j.id}
|
||||
if len(q.More) > 0 && !s.shipsSeveral(from, q) {
|
||||
// One at a time: an SSH address, or an agent from before 0.2.0.
|
||||
var last proto.TaskResult
|
||||
done, skipped := 0, 0
|
||||
for i, p := range append([]string{q.Src}, q.More...) {
|
||||
one := q
|
||||
one.Src, one.More = p, nil
|
||||
if i > 0 {
|
||||
one.Name = path.Base(path.Clean(p))
|
||||
}
|
||||
if last = s.ship1(ctx, j, from, one); !last.OK {
|
||||
return last
|
||||
}
|
||||
var d struct{ Done, Skipped int }
|
||||
_ = json.Unmarshal(last.Data, &d)
|
||||
done, skipped = done+d.Done, skipped+d.Skipped
|
||||
}
|
||||
data, _ := json.Marshal(map[string]int{"done": done, "skipped": skipped})
|
||||
return proto.TaskResult{OK: true, Data: data}
|
||||
}
|
||||
args := proto.FSShipArgs{Src: q.Src, Name: q.Name, Dir: q.Dir, Conflict: q.Conflict, Job: j.id, More: q.More}
|
||||
if q.SSH != nil {
|
||||
args.SSH, args.Move = q.SSH, q.Move
|
||||
res, err := s.waitTask(ctx, from, proto.TaskFSShipOut, args, xferTimeout)
|
||||
@@ -267,17 +290,19 @@ func (s *Server) ship1(ctx context.Context, j *shipJob, from string, q shipReq)
|
||||
return out
|
||||
}
|
||||
if q.Move {
|
||||
task, del := proto.TaskFSDelTree, any(proto.FSPathArgs{Path: q.Src})
|
||||
if q.FromConn != "" {
|
||||
raw, _ := json.Marshal(proto.FSPathArgs{Path: q.Src})
|
||||
task, del = proto.TaskSSHFSOp, proto.SSHFSArgs{Conn: q.FromConn, Op: "deltree", Args: raw}
|
||||
}
|
||||
res, err := s.waitTask(ctx, from, task, del, fsTaskTimeout)
|
||||
if err == nil && !res.OK {
|
||||
err = errText(res.Error)
|
||||
}
|
||||
if err != nil {
|
||||
return proto.TaskResult{Error: "отправлено, но здесь не удалилось: " + err.Error()}
|
||||
for _, p := range append([]string{q.Src}, q.More...) {
|
||||
task, del := proto.TaskFSDelTree, any(proto.FSPathArgs{Path: p})
|
||||
if q.FromConn != "" {
|
||||
raw, _ := json.Marshal(proto.FSPathArgs{Path: p})
|
||||
task, del = proto.TaskSSHFSOp, proto.SSHFSArgs{Conn: q.FromConn, Op: "deltree", Args: raw}
|
||||
}
|
||||
res, err := s.waitTask(ctx, from, task, del, fsTaskTimeout)
|
||||
if err == nil && !res.OK {
|
||||
err = errText(res.Error)
|
||||
}
|
||||
if err != nil {
|
||||
return proto.TaskResult{Error: "отправлено, но здесь не удалилось: " + err.Error()}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The receiver's count: what it put down and what it kept as it was.
|
||||
@@ -434,3 +459,38 @@ func (s *Server) handleRelayGet(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
|
||||
// shipsSeveral: both agents take several things in one stream (0.2.0 on).
|
||||
func (s *Server) shipsSeveral(from string, q shipReq) bool {
|
||||
if q.SSH != nil {
|
||||
return false
|
||||
}
|
||||
for _, id := range []string{from, q.To} {
|
||||
a, _ := s.db.GetAgent(id)
|
||||
if a == nil || !versionAtLeast(a.Version, "0.2.0") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// versionAtLeast: version v (dotted numbers) is min or newer; anything else
|
||||
// is taken for older.
|
||||
func versionAtLeast(v, min string) bool {
|
||||
a, b := strings.Split(v, "."), strings.Split(min, ".")
|
||||
for i := range b {
|
||||
var x, y int
|
||||
if i < len(a) {
|
||||
n, err := strconv.Atoi(a[i])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
x = n
|
||||
}
|
||||
y, _ = strconv.Atoi(b[i])
|
||||
if x != y {
|
||||
return x > y
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -155,14 +155,18 @@
|
||||
q('.dd-ptext').textContent = 'подготовка…';
|
||||
q('.dd-cancel').textContent = 'Стоп';
|
||||
}
|
||||
for (const p of item.paths) {
|
||||
// Between places, all of them go as one stream: the first, and the rest
|
||||
// as "more" (the server sends one by one to older agents).
|
||||
const batch = here ? item.paths.map(p => [p]) : [item.paths];
|
||||
for (const group of batch) {
|
||||
const p = group[0];
|
||||
if (c.stop) break;
|
||||
const name = p.split('/').pop();
|
||||
let r;
|
||||
if (here) {
|
||||
r = await from.src.op('copy', {src: p, dst: join(t.dir, name), move: isMove, conflict});
|
||||
} else {
|
||||
const body = {src: p, name, dir: t.dir, conflict, move: isMove};
|
||||
const body = {src: p, name, dir: t.dir, conflict, move: isMove, more: group.slice(1)};
|
||||
if (t.source.kind === 'ssh') { body.to = t.pane.agent.id; body.to_conn = t.pane.src.conn; }
|
||||
else body.to = t.pane.agent.id;
|
||||
if (from.kind === 'sshfiles') body.from_conn = from.src.conn;
|
||||
@@ -170,7 +174,7 @@
|
||||
r = await new MachineSource(from.agent).op('ship', body);
|
||||
if (r.ok) {
|
||||
c.job = r.data.job;
|
||||
const pre = item.paths.length > 1 ? name + ': ' : '';
|
||||
const pre = '';
|
||||
r = await FV.followJob(c.job, (pr, text) => {
|
||||
if (pr.total > 0 || pr.done > 0) {
|
||||
q('.dd-bar').parentNode.classList.remove('wait');
|
||||
@@ -182,7 +186,7 @@
|
||||
}
|
||||
}
|
||||
if (!r.ok) { err = r.error || 'ошибка'; break; }
|
||||
done++;
|
||||
done += group.length;
|
||||
skipped += (r.data && r.data.skipped) || 0;
|
||||
}
|
||||
close();
|
||||
|
||||
Reference in new issue
Block a user