P3-X2: tree.list synchronous WS RPC + per-session cache
Foundational for the restore wizard's tree browser. The wizard needs to lazy-load directory contents from a snapshot as the operator drills down; this lands the transport. - internal/api adds MsgTreeList (server → agent) + MsgTreeListResult (agent → server) with TreeListRequestPayload / TreeListEntry / TreeListResultPayload types. Reply correlates by Envelope.ID. - internal/restic.ListTreeChildren wraps 'restic ls --json' and filters its recursive output to direct children of the requested path. Parser + path-normalisation + isDirectChild are unit-tested. - internal/server/ws/rpc.go introduces a generic SendRPC helper on Hub: register a buffered channel keyed by ULID, send the request, block on ctx.Done()/timeout/reply. Reply routing piggybacks on the existing dispatchAgentMessage by adding a MsgTreeListResult case that forwards to the registered waiter; if no waiter is registered (caller already gave up) the stray reply is dropped quietly. - cmd/agent gains a tree.list handler that runs ListTreeChildren on a fresh per-call context (60s ceiling) and ships the matching tree.list.result envelope. Errors surface in result.Error rather than as transport failures so the server-side waiter can render a sensible UI message. - internal/server/http/tree_cache.go is the per-wizard-session cache layer (~30min TTL, sweep-on-access) that fetchTreeWithCache uses before falling through to SendRPC. Cached on success only; agent errors aren't cached so a transient failure doesn't poison the session. Tests: - internal/restic/ls_test.go covers parseLsChildren at root / mid-tree / leaf, plus normalizeTreePath and isDirectChild edge cases. - internal/server/ws/rpc_test.go unit-tests the registry: round-trip, release semantics, concurrent waiters, ctx-cancel. - internal/server/http/tree_rpc_test.go is the full round-trip: server SendRPC → fake-agent over a real WS → reply → server gets the payload. Plus a timeout test that confirms ~300ms timeouts terminate in ~300ms rather than waiting forever. The cache is plumbed but no UI handler hits fetchTreeWithCache yet — that lands with P3-01 (wizard backend). The unused-linter is suppressed via nolint until the wizard wires it in.
This commit is contained in:
@@ -337,3 +337,37 @@ type AgentUpdateAvailablePayload struct {
|
||||
PackageURL string `json:"package_url"` // apt repo / choco source
|
||||
Changelog string `json:"changelog,omitempty"`
|
||||
}
|
||||
|
||||
// TreeListRequestPayload is the body of a tree.list RPC. Used by the
|
||||
// restore wizard to lazy-load directory contents from a snapshot.
|
||||
//
|
||||
// The exchange is synchronous: the server marshals MsgTreeList with a
|
||||
// fresh Envelope.ID, sends to the agent, blocks on a channel keyed by
|
||||
// that ID. The agent runs `restic ls --json <SnapshotID> <Path>`,
|
||||
// emits direct children, and replies with MsgTreeListResult carrying
|
||||
// the same ID. The server-side handler matches on ID and forwards to
|
||||
// the waiting channel. See internal/server/ws/rpc.go for the helper.
|
||||
type TreeListRequestPayload struct {
|
||||
SnapshotID string `json:"snapshot_id"`
|
||||
Path string `json:"path"` // absolute path inside the snapshot, "/" for root
|
||||
}
|
||||
|
||||
// TreeListEntry is one direct child returned by a tree.list call.
|
||||
// Type is "dir" | "file" | "symlink"; size is best-effort (zero on
|
||||
// directories and symlinks).
|
||||
type TreeListEntry struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// TreeListResultPayload is the reply to a tree.list. Error is set
|
||||
// when the agent couldn't fulfill the request (missing snapshot,
|
||||
// path doesn't exist, restic invocation failed); Entries is empty in
|
||||
// that case. A successful empty directory has Error="" + nil Entries.
|
||||
type TreeListResultPayload struct {
|
||||
SnapshotID string `json:"snapshot_id"`
|
||||
Path string `json:"path"`
|
||||
Entries []TreeListEntry `json:"entries,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
+14
-12
@@ -12,18 +12,19 @@ type MessageType string
|
||||
|
||||
// Agent → server message types.
|
||||
const (
|
||||
MsgHello MessageType = "hello"
|
||||
MsgHeartbeat MessageType = "heartbeat"
|
||||
MsgJobStarted MessageType = "job.started"
|
||||
MsgJobProgress MessageType = "job.progress"
|
||||
MsgJobFinished MessageType = "job.finished"
|
||||
MsgSnapshotsRpt MessageType = "snapshots.report"
|
||||
MsgRepoStats MessageType = "repo.stats"
|
||||
MsgLogStream MessageType = "log.stream"
|
||||
MsgScheduleAck MessageType = "schedule.ack"
|
||||
MsgScheduleFire MessageType = "schedule.fire" // agent: a local cron entry fired, please dispatch a job
|
||||
MsgCommandResult MessageType = "command.result" // ack for command.run
|
||||
MsgError MessageType = "error"
|
||||
MsgHello MessageType = "hello"
|
||||
MsgHeartbeat MessageType = "heartbeat"
|
||||
MsgJobStarted MessageType = "job.started"
|
||||
MsgJobProgress MessageType = "job.progress"
|
||||
MsgJobFinished MessageType = "job.finished"
|
||||
MsgSnapshotsRpt MessageType = "snapshots.report"
|
||||
MsgRepoStats MessageType = "repo.stats"
|
||||
MsgLogStream MessageType = "log.stream"
|
||||
MsgScheduleAck MessageType = "schedule.ack"
|
||||
MsgScheduleFire MessageType = "schedule.fire" // agent: a local cron entry fired, please dispatch a job
|
||||
MsgCommandResult MessageType = "command.result" // ack for command.run
|
||||
MsgTreeListResult MessageType = "tree.list.result" // reply to a server-driven tree.list
|
||||
MsgError MessageType = "error"
|
||||
)
|
||||
|
||||
// Server → agent message types.
|
||||
@@ -33,6 +34,7 @@ const (
|
||||
MsgScheduleSet MessageType = "schedule.set"
|
||||
MsgConfigUpdate MessageType = "config.update"
|
||||
MsgAgentUpdateAvail MessageType = "agent.update.available"
|
||||
MsgTreeList MessageType = "tree.list" // sync RPC: list a snapshot's children
|
||||
)
|
||||
|
||||
// Envelope is the framing for every WS message in either direction.
|
||||
|
||||
Reference in New Issue
Block a user