srctree

Gregory Mullen parent 33a01e99 4d2d7964
rewrite threads to use message union

previously this was a raw comment, but eventually there will be support for addtional types than just comments. e.g. events, reviews, changes, repo changes, mod actions, etc.
src/endpoints/repos/commits.zig added: 166, removed: 115, total 51
@@ -133,16 +133,29 @@ fn commitHtml(ctx: *Context, sha: []const u8, repo_name: []const u8, repo: Git.R
};
if (dlt) |*delta| {
_ = delta.loadThread(ctx.alloc) catch unreachable;
if (delta.getComments(ctx.alloc)) |thread_comments| {
thread = try ctx.alloc.alloc(Template.Structs.Thread, thread_comments.len);
for (thread_comments, thread) |thr_cmt, *pg_comment| {
pg_comment.* = .{
.author = try Bleach.sanitizeAlloc(ctx.alloc, thr_cmt.author, .{}),
.date = try allocPrint(ctx.alloc, "{}", .{Humanize.unix(thr_cmt.updated)}),
.message = try Bleach.sanitizeAlloc(ctx.alloc, thr_cmt.message, .{}),
.direct_reply = null,
.sub_thread = null,
};
if (delta.getMessages(ctx.alloc)) |messages| {
thread = try ctx.alloc.alloc(Template.Structs.Thread, messages.len);
for (messages, thread) |msg, *pg_comment| {
switch (msg) {
.comment => |cmt| {
pg_comment.* = .{
.author = try Bleach.sanitizeAlloc(ctx.alloc, cmt.author, .{}),
.date = try allocPrint(ctx.alloc, "{}", .{Humanize.unix(cmt.updated)}),
.message = try Bleach.sanitizeAlloc(ctx.alloc, cmt.message, .{}),
.direct_reply = null,
.sub_thread = null,
};
},
else => {
pg_comment.* = .{
.author = "",
.date = "",
.message = "unsupported message type",
.direct_reply = null,
.sub_thread = null,
};
},
}
}
} else |err| {
std.debug.print("Unable to load comments for thread {} {}\n", .{ map.attach.delta, err });
 
src/endpoints/repos/diffs.zig added: 166, removed: 115, total 51
@@ -701,16 +701,29 @@ fn view(ctx: *Context) Error!void {
}
 
var root_thread: []S.Thread = &[0]S.Thread{};
if (delta.getComments(ctx.alloc)) |comments| {
root_thread = try ctx.alloc.alloc(S.Thread, comments.len);
for (comments, root_thread) |comment, *c_ctx| {
c_ctx.* = .{
.author = try Bleach.sanitizeAlloc(ctx.alloc, comment.author, .{}),
.date = try allocPrint(ctx.alloc, "{}", .{Humanize.unix(comment.updated)}),
.message = translateComment(ctx.alloc, comment.message, patch, &repo) catch unreachable,
.direct_reply = .{ .uri = try allocPrint(ctx.alloc, "{}/direct_reply/{x}", .{ index, fmtSliceHexLower(comment.hash[0..]) }) },
.sub_thread = null,
};
if (delta.getMessages(ctx.alloc)) |messages| {
root_thread = try ctx.alloc.alloc(S.Thread, messages.len);
for (messages, root_thread) |msg, *c_ctx| {
switch (msg) {
.comment => |comment| {
c_ctx.* = .{
.author = try Bleach.sanitizeAlloc(ctx.alloc, comment.author, .{}),
.date = try allocPrint(ctx.alloc, "{}", .{Humanize.unix(comment.updated)}),
.message = translateComment(ctx.alloc, comment.message, patch, &repo) catch unreachable,
.direct_reply = .{ .uri = try allocPrint(ctx.alloc, "{}/direct_reply/{x}", .{ index, fmtSliceHexLower(comment.hash[0..]) }) },
.sub_thread = null,
};
},
else => {
c_ctx.* = .{
.author = "",
.date = "",
.message = "unsupported message type",
.direct_reply = null,
.sub_thread = null,
};
},
}
}
} else |err| {
std.debug.print("Unable to load comments for thread {} {}\n", .{ index, err });
@@ -750,11 +763,9 @@ fn list(ctx: *Context) Error!void {
const rd = Repos.RouteData.make(&ctx.uri) orelse return error.Unrouteable;
 
const last = Delta.last(rd.name) + 1;
const ts = std.time.timestamp() - 86400;
 
var d_list = std.ArrayList(S.DeltaList).init(ctx.alloc);
for (0..last) |i| {
var new_comments = false;
var d = Delta.open(ctx.alloc, rd.name, i) catch continue orelse continue;
if (!std.mem.eql(u8, d.repo, rd.name) or d.attach != .diff) {
d.raze(ctx.alloc);
@@ -762,13 +773,7 @@ fn list(ctx: *Context) Error!void {
}
 
_ = d.loadThread(ctx.alloc) catch unreachable;
var cmtslen: usize = 0;
if (d.getComments(ctx.alloc)) |cmts| {
cmtslen = cmts.len;
for (cmts) |c| {
if (c.updated > ts) new_comments = true;
}
} else |_| unreachable;
const cmtsmeta = d.countComments();
try d_list.append(.{
.index = try allocPrint(ctx.alloc, "0x{x}", .{d.index}),
.title_uri = try allocPrint(
@@ -780,7 +785,7 @@ fn list(ctx: *Context) Error!void {
.comments_icon = try allocPrint(
ctx.alloc,
"<span><span class=\"icon{s}\">\xee\xa0\x9c</span> {}</span>",
.{ if (new_comments) " new" else "", cmtslen },
.{ if (cmtsmeta.new) " new" else "", cmtsmeta.count },
),
.desc = try Bleach.sanitizeAlloc(ctx.alloc, d.message, .{}),
});
 
src/endpoints/repos/issues.zig added: 166, removed: 115, total 51
@@ -135,19 +135,26 @@ fn view(ctx: *Context) Error!void {
);
 
_ = delta.loadThread(ctx.alloc) catch unreachable;
if (delta.getComments(ctx.alloc)) |cm| {
const comments: []Template.Context = try ctx.alloc.alloc(Template.Context, cm.len);
 
for (cm, comments) |comment, *cctx| {
if (delta.getMessages(ctx.alloc)) |msgs| {
const comments: []Template.Context = try ctx.alloc.alloc(Template.Context, msgs.len);
for (msgs, comments) |msg, *cctx| {
cctx.* = Template.Context.init(ctx.alloc);
const builder = comment.builder();
builder.build(ctx.alloc, cctx) catch unreachable;
try cctx.put(
"Date",
.{ .slice = try std.fmt.allocPrint(ctx.alloc, "{}", .{Humanize.unix(comment.updated)}) },
);
switch (msg) {
.comment => |comment| {
const builder = comment.builder();
builder.build(ctx.alloc, cctx) catch unreachable;
try cctx.put(
"Date",
.{ .slice = try std.fmt.allocPrint(ctx.alloc, "{}", .{Humanize.unix(comment.updated)}) },
);
},
else => try cctx.put("Message", .{ .slice = "unsupported message found" }),
}
}
try ctx.putContext("Comments", .{ .block = comments });
const thread: []Template.Context = try ctx.alloc.alloc(Template.Context, 1);
thread[0] = Template.Context.init(ctx.alloc);
try thread[0].putBlock("Thread", comments);
try ctx.putContext("Comments", .{ .block = thread });
} else |err| {
std.debug.print("Unable to load comments for thread {} {}\n", .{ index, err });
@panic("oops");
@@ -164,12 +171,10 @@ fn list(ctx: *Context) Error!void {
const rd = Repos.RouteData.make(&ctx.uri) orelse return error.Unrouteable;
 
const last = Delta.last(rd.name) + 1;
const ts = std.time.timestamp() - 86400;
 
var d_list = std.ArrayList(S.DeltaList).init(ctx.alloc);
for (0..last) |i| {
// TODO implement seen
var new_comments = false;
var d = Delta.open(ctx.alloc, rd.name, i) catch continue orelse continue;
if (!std.mem.eql(u8, d.repo, rd.name) or d.attach != .issue) {
d.raze(ctx.alloc);
@@ -177,13 +182,7 @@ fn list(ctx: *Context) Error!void {
}
 
_ = d.loadThread(ctx.alloc) catch unreachable;
var cmtslen: usize = 0;
if (d.getComments(ctx.alloc)) |cmts| {
cmtslen = cmts.len;
for (cmts) |c| {
if (c.updated > ts) new_comments = true;
}
} else |_| {}
const cmtsmeta = d.countComments();
 
try d_list.append(.{
.index = try allocPrint(ctx.alloc, "0x{x}", .{d.index}),
@@ -196,7 +195,7 @@ fn list(ctx: *Context) Error!void {
.comments_icon = try allocPrint(
ctx.alloc,
"<span><span class=\"icon{s}\">\xee\xa0\x9c</span> {}</span>",
.{ if (new_comments) " new" else "", cmtslen },
.{ if (cmtsmeta.new) " new" else "", cmtsmeta.count },
),
.desc = try Bleach.sanitizeAlloc(ctx.alloc, d.message, .{}),
});
 
src/endpoints/search.zig added: 166, removed: 115, total 51
@@ -77,7 +77,7 @@ fn custom(ctx: *Context, search_str: []const u8) Error!void {
var next: Delta = next_;
 
if (next.loadThread(ctx.alloc)) |*thread| {
_ = thread.*.loadComments(ctx.alloc) catch return error.Unknown;
_ = thread.*.loadMessages(ctx.alloc) catch return error.Unknown;
} else |_| continue;
try list.append(try next.toContext(ctx.alloc));
}
 
src/template.zig added: 166, removed: 115, total 51
@@ -383,7 +383,7 @@ pub const Directive = struct {
std.debug.assert(block.len == 1);
try self.with(block[0], out);
},
.build => unreachable,
.build => try self.with(block[0], out),
.variable => unreachable,
}
return;
 
src/types/comment.zig added: 166, removed: 115, total 51
@@ -223,30 +223,6 @@ pub fn new(ath: []const u8, msg: []const u8) !Comment {
return c;
}
 
pub fn loadFromData(a: Allocator, cd: []const u8) ![]Comment {
if (cd.len < 32) {
std.debug.print("unexpected number in comment data {}\n", .{cd.len});
return &[0]Comment{};
}
const count = cd.len / 32;
if (count == 0) return &[0]Comment{};
const comments = try a.alloc(Comment, count);
var data = cd[0..];
for (comments, 0..count) |*c, i| {
c.* = Comment.open(a, data[0..32]) catch |err| {
std.debug.print(
\\Error loading comment data {} of {}
\\error: {} target {any}
\\
, .{ i, count, err, data[0..32] });
data = data[32..];
continue;
};
data = data[32..];
}
return comments;
}
 
test "comment" {
var a = std.testing.allocator;
 
 
src/types/delta.zig added: 166, removed: 115, total 51
@@ -7,6 +7,7 @@ const Bleach = @import("../bleach.zig");
const Types = @import("../types.zig");
const Comment = Types.Comment;
const Thread = Types.Thread;
const Message = Thread.Message;
const Template = @import("../template.zig");
 
pub const Delta = @This();
@@ -178,13 +179,13 @@ pub fn loadThread(self: *Delta, a: Allocator) !*Thread {
return t;
}
 
pub fn getComments(self: *Delta, a: Allocator) ![]Comment {
pub fn getMessages(self: *Delta, a: Allocator) ![]Message {
if (self.thread) |thread| {
if (thread.getComments()) |c| {
return c;
if (thread.getMessages()) |msgs| {
return msgs;
} else |_| {
try thread.loadComments(a);
return try thread.getComments();
try thread.loadMessages(a);
return try thread.getMessages();
}
}
return error.ThreadNotLoaded;
@@ -197,6 +198,25 @@ pub fn addComment(self: *Delta, a: Allocator, c: Comment) !void {
return error.ThreadNotLoaded;
}
 
pub fn countComments(self: Delta) struct { count: usize, new: bool } {
if (self.thread) |thread| {
if (thread.getMessages()) |msgs| {
const ts = std.time.timestamp() - 86400;
var cmtnew: bool = false;
var cmtlen: usize = 0;
for (msgs) |m| switch (m) {
.comment => |c| {
cmtnew = cmtnew or c.updated > ts;
cmtlen += 1;
},
else => {},
};
return .{ .count = cmtlen, .new = cmtnew };
} else |_| {}
}
return .{ .count = 0, .new = false };
}
 
pub fn toContext(self: Delta, a: Allocator) !Template.Context {
return Template.Context.initBuildable(a, self);
}
@@ -216,10 +236,10 @@ pub fn contextBuilder(self: Delta, a: Allocator, ctx: *Template.Context) !void {
self.index,
}));
 
if (self.thread) |thread| if (thread.getComments()) |comments| {
if (self.thread) |thread| if (thread.getMessages()) |messages| {
try ctx.putSlice(
"CommentsIcon",
try std.fmt.allocPrint(a, "<span class=\"icon\">\xee\xa0\x9c {}</span>", .{comments.len}),
try std.fmt.allocPrint(a, "<span class=\"icon\">\xee\xa0\x9c {}</span>", .{messages.len}),
);
} else |_| {};
}
 
src/types/thread.zig added: 166, removed: 115, total 51
@@ -31,7 +31,7 @@ fn readVersioned(a: Allocator, idx: usize, reader: *std.io.AnyReader) !Thread {
.updated = try reader.readInt(i64, endian),
};
_ = try reader.read(&t.delta_hash);
t.comment_data = try reader.readAllAlloc(a, 0xFFFF);
t.message_data = try reader.readAllAlloc(a, 0x8FFFF);
return t;
},
 
@@ -46,13 +46,26 @@ updated: i64 = 0,
delta_hash: [32]u8 = [_]u8{0} ** 32,
hash: [32]u8 = [_]u8{0} ** 32,
 
comment_data: ?[]const u8 = null,
comments: ?[]Comment = null,
message_data: ?[]const u8 = null,
messages: ?[]Message = null,
 
pub const MessageTypes = enum {
comment,
unknown,
};
 
pub const Message = union(MessageTypes) {
comment: Comment,
unknown: void,
};
 
pub fn commit(self: Thread) !void {
if (self.comments) |cmts| {
if (self.messages) |msgs| {
// Make a best effort to save/protect all data
for (cmts) |cmt| cmt.commit() catch continue;
for (msgs) |msg| switch (msg) {
.comment => |cmt| cmt.commit() catch continue,
.unknown => {},
};
}
const file = try openFile(self.index);
defer file.close();
@@ -67,10 +80,11 @@ fn writeOut(self: Thread, writer: std.io.AnyWriter) !void {
try writer.writeInt(i64, self.updated, endian);
try writer.writeAll(&self.delta_hash);
 
if (self.comments) |cmts| {
for (cmts) |*c| {
try writer.writeAll(c.toHash());
}
if (self.messages) |msgs| {
for (msgs) |*msg| switch (msg.*) {
.comment => |*c| try writer.writeAll(c.toHash()),
.unknown => {},
};
}
try writer.writeAll("\x00");
}
@@ -79,32 +93,57 @@ fn writeOut(self: Thread, writer: std.io.AnyWriter) !void {
pub fn readFile(a: std.mem.Allocator, idx: usize, reader: *std.io.AnyReader) !Thread {
// TODO I hate this, but I'm prototyping, plz rewrite
var thread: Thread = readVersioned(a, idx, reader) catch return error.InputOutput;
try thread.loadComments(a);
try thread.loadMessages(a);
return thread;
}
 
pub fn loadComments(self: *Thread, a: Allocator) !void {
if (self.comment_data) |cd| {
self.comments = try Comment.loadFromData(a, cd);
fn loadFromData(a: Allocator, cd: []const u8) ![]Message {
if (cd.len < 32) {
if (cd.len != 1) { // ignore single null
std.debug.print("unexpected number in comment data {}\n", .{cd.len});
}
return &[0]Message{};
}
const count = cd.len / 32;
if (count == 0) return &[0]Message{};
const msgs = try a.alloc(Message, count);
var data = cd[0..];
for (msgs, 0..count) |*c, i| {
c.* = .{ .comment = Comment.open(a, data[0..32]) catch |err| {
std.debug.print(
\\Error loading msg data {} of {}
\\error: {} target {any}
\\
, .{ i, count, err, data[0..32] });
data = data[32..];
continue;
} };
data = data[32..];
}
return msgs;
}
pub fn loadMessages(self: *Thread, a: Allocator) !void {
if (self.message_data) |cd| {
self.messages = try loadFromData(a, cd);
}
}
 
pub fn getComments(self: *Thread) ![]Comment {
if (self.comments) |c| return c;
pub fn getMessages(self: Thread) ![]Message {
if (self.messages) |c| return c;
return error.NotLoaded;
}
 
pub fn addComment(self: *Thread, a: Allocator, c: Comment) !void {
if (self.comments) |*comments| {
if (a.resize(comments.*, comments.len + 1)) {
comments.*.len += 1;
if (self.messages) |*messages| {
if (a.resize(messages.*, messages.len + 1)) {
messages.*.len += 1;
} else {
self.comments = try a.realloc(comments.*, comments.len + 1);
self.messages = try a.realloc(messages.*, messages.len + 1);
}
} else {
self.comments = try a.alloc(Comment, 1);
self.messages = try a.alloc(Message, 1);
}
self.comments.?[self.comments.?.len - 1] = c;
self.messages.?[self.messages.?.len - 1] = .{ .comment = c };
self.updated = std.time.timestamp();
try self.commit();
}
@@ -113,10 +152,9 @@ pub fn raze(self: Thread, a: std.mem.Allocator) void {
//if (self.alloc_data) |data| {
// a.free(data);
//}
if (self.comments) |c| {
if (self.messages) |c| {
a.free(c);
}
self.file.close();
}
 
fn currMaxSet(count: usize) !void {