╭ changed ───────────╮ │src/serve_common.zig│ ╰────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 1 const std = @import("std"); │ │ 2 const evt = @import("./event.zig"); │ │ 3 -const xit = @import("xit"); │ │ 4 -const rp = xit.repo; │ │ 5 -const ssh = @import("./serve_ssh_protocol.zig"); │ │ 6 - │ │ 7 -// progress is optional and best effort: a failed report must not fail the work │ │ 8 -pub fn reportProgress(comptime repo_opts: rp.RepoOpts(.xit), io: std.Io, progress_ctx_maybe: ?repo_opts.ProgressCtx, event: rp.ProgressEvent) void {│ │ 9 - if (repo_opts.ProgressCtx != void) { │ │ 10 - if (progress_ctx_maybe) |progress_ctx| progress_ctx.run(io, event) catch {}; │ │ 11 - } │ │ 12 -} │ │ 13 - │ │ 14 -// progress for a push or a clone, sent on the pack stream's sideband │ │ 15 -pub const SidebandProgress = struct { │ │ 16 - // a push reports through its deferred response, which knows whether the │ │ 17 - // client wanted sideband at all. a clone has none and writes band 2 itself. │ │ 18 - response: ?*xit.net_server_receive_pack.Response = null, │ │ 19 - writer: *std.Io.Writer, │ │ 20 - // the client to probe, absent when nothing is listening │ │ 21 - sess: ?*ssh.SessionCtx = null, │ │ 22 - // set once it has hung up, so the phase in flight can stop too │ │ 23 - gone: bool = false, │ │ 24 - label: []const u8 = "Processing commits", │ │ 25 - count: usize = 0, │ │ 26 - total: usize = 0, │ │ 27 - // the last reported step, so a line is sent only when it changes │ │ 28 - step: usize = 0, │ │ 29 - │ │ 30 - // how many items a phase with no total advances between reports │ │ 31 - const report_every = 1000; │ │ 32 - │ │ 33 - // the phases a client is told about, listed so a new kind has to be │ │ 34 - // decided on rather than inherit whatever label was last set │ │ 35 - fn reported(kind: rp.ProgressKind) bool { │ │ 36 - return switch (kind) { │ │ 37 - .writing_object_from_pack, .enumerating_object, .compressing_object, .writing_patch => true, │ │ 38 - .writing_object, .sending_bytes, .receiving_bytes => false, │ │ 39 - }; │ │ 40 - } │ │ 41 - │ │ 42 - pub fn run(self: *SidebandProgress, _: std.Io, event: rp.ProgressEvent) !void { │ │ 43 - switch (event) { │ │ 44 - .start => |start| { │ │ 45 - if (!reported(start.kind)) return; │ │ 46 - // a pack phase carries no label of its own; the rest send a │ │ 47 - // .text first │ │ 48 - switch (start.kind) { │ │ 49 - .writing_object_from_pack => self.label = "Unpacking objects", │ │ 50 - .enumerating_object => self.label = "Enumerating objects", │ │ 51 - .compressing_object => self.label = "Compressing objects", │ │ 52 - else => {}, │ │ 53 - } │ │ 54 - self.total = start.estimated_total_items; │ │ 55 - self.count = 0; │ │ 56 - self.step = 0; │ │ 57 - }, │ │ 58 - .complete_one => |kind| { │ │ 59 - if (!reported(kind)) return; │ │ 60 - self.count += 1; │ │ 61 - }, │ │ 62 - .end => |kind| { │ │ 63 - if (!reported(kind)) return; │ │ 64 - }, │ │ 65 - // each phase names itself before reporting its count │ │ 66 - .text => |text| { │ │ 67 - self.label = text; │ │ 68 - return; │ │ 69 - }, │ │ 70 - else => return, │ │ 71 - } │ │ 72 - // a phase that knows no total counts instead, and stays quiet until it │ │ 73 - // has something to count │ │ 74 - if (self.total == 0 and self.count == 0) return; │ │ 75 - │ │ 76 - // every report flushes, so one is sent per percent, or per block of │ │ 77 - // items when there is no total to turn into one │ │ 78 - const step: usize = if (self.total > 0) @intCast(@as(u128, self.count) * 100 / self.total) else self.count / report_every; │ │ 79 - if (event == .complete_one and step == self.step) return; │ │ 80 - self.step = step; │ │ 81 - │ │ 82 - // reporting is the only moment a transaction looks up from its work │ │ 83 - if (self.sess) |sess| if (sess.peerGone()) { │ │ 84 - self.gone = true; │ │ 85 - return error.ClientGone; │ │ 86 - }; │ │ 3 +const rp = @import("xit").repo; │ │ 4 │ │ 88 - var buffer: [128]u8 = undefined; │ │ 89 - const suffix = if (event == .end) "\n" else "\r"; │ │ 90 - const text = if (self.total > 0) │ │ 91 - try std.fmt.bufPrint(&buffer, "{s}: {d}% ({d}/{d}){s}", .{ self.label, step, self.count, self.total, suffix }) │ │ 92 - else │ │ 93 - try std.fmt.bufPrint(&buffer, "{s}: {d}{s}", .{ self.label, self.count, suffix }); │ │ 94 - if (self.response) |response| { │ │ 95 - try response.progress(self.writer, text); │ │ 96 - } else { │ │ 97 - try xit.net_server_pkt.sendSideband(self.writer, 2, text); │ │ 98 - try self.writer.flush(); │ │ 99 - } │ │100 - } │ │101 -}; │ │102 - │ │103 -// whether the client the work is reporting to has hung up. only a sideband │ │104 -// reporter probes, so any other context never cancels. │ │105 -pub fn progressCancelled(comptime repo_opts: rp.RepoOpts(.xit), progress_ctx_maybe: ?repo_opts.ProgressCtx) bool { │ │106 - if (repo_opts.ProgressCtx != *SidebandProgress) return false; │ │107 - const progress_ctx = progress_ctx_maybe orelse return false; │ │108 - return progress_ctx.gone; │ │109 -} │ │110 - │ │ 5 // the outcome of resolving a requested repo path to its on-disk directory. │ │ 6 // the http and ssh paths each map the cases to their own error responses. │ │ 7 pub const RepoPath = union(enum) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯