╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭──────────────────────────────────────────────╮ │1063 io: std.Io, │ │1064 allocator: std.mem.Allocator, │ │1065 ref: rf.Ref, │ │1062 -) !bool { │ │1066 +) !?u64 { │ │1067 const DB = EventDB(repo_opts.hash); │ │1068 const read_state = state.readOnly();│ │1069 │ ╰──────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1080 const tip_hex = (try rf.readRecur(repo_kind, repo_opts, read_state, io, .{ .ref = ref })) orelse return error.OidNotFound;│ │1081 var tip: [hash.byteLen(repo_opts.hash)]u8 = undefined; │ │1082 _ = try std.fmt.hexToBytes(&tip, &tip_hex); │ │1079 - if (std.mem.eql(u8, last_object_id, &tip)) return false; │ │1083 + if (std.mem.eql(u8, last_object_id, &tip)) return error.CancelTransaction; │ │1084 } │ │1085 │ │1086 // the list with all of haxy's state, including materialized views. │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1121 │ │1122 try haxy_history.slice(moment_index + 1); │ │1123 try moment.put(hash.hashInt(repo_opts.hash, last_object_id_key), .{ .bytes = &head_oid });│ │1120 - return true; │ │1124 + return null; │ │1125 } │ │1126 │ │1127 // if this branch was rebased and force pushed, we need to detect that and │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1424 try moment.put(hash.hashInt(repo_opts.hash, last_object_id_key), .{ .bytes = last_object_id });│ │1425 } │ │1426 │ │1423 - return true; │ │1427 + return try haxy_history.count() - 1; │ │1428 } │ │1429 │ │1430 // the conflicted records of one kind, keyed by the same order key its id set │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/event/Notify.zig│ ╰────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │11 mode: Mode, │ │12 │ │13 pub const Mode = enum { │ │14 - // always notified │ │15 - subscribe, │ │14 + // notified as the thread's author or an assignee │ │15 + involved, │ │16 + // notified as a commenter │ │17 + commented, │ │18 + // notified by choice │ │19 + subscribed, │ │20 // never notified │ │17 - mute, │ │21 + muted, │ │22 }; │ │23 │ │24 // what the db stores: the event's data plus the commit-derived fields│ ╰──────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────╮ │src/notify.zig│ ╰──────────────╯ ╭─────────────────────────────────────────────╮ │3 const rp = xit.repo; │ │4 const hash = xit.hash; │ │5 const evt = @import("event.zig"); │ │6 +const progress = @import("progress.zig"); │ │7 │ │8 const UserDB = evt.UserDB; │ │9 const hash_kind = evt.user_repo_opts.hash;│ ╰─────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │13 const recent_notif_id_set_key = "haxy/recent-notif-id-set"; │ │14 const unread_notif_id_set_key = "haxy/unread-notif-id-set"; │ │15 │ │15 -// the owner's user repo key holding each repo's newest moment index already notified │ │16 -const last_notified_map_key = "haxy/repo-id->last-notified-moment-index"; │ │16 +// the most notifs a user keeps │ │17 +pub const max_notifs = 1000; │ │18 │ │18 -// the owner's user repo key mapping each thread's notif id to its subscribers' user ids│ │19 -const subscriber_map_key = "haxy/notif-id->subscriber-id-set"; │ │19 +// the most users a thread's capped tiers hold │ │20 +pub const max_commented = 250; │ │21 +pub const max_subscribed = 100; │ │22 │ │21 -// the most notifs a user keeps │ │22 -pub const max_notifs = 1000; │ │23 +// the owner's user repo key mapping each thread's notif id to the user ids in │ │24 +// `mode`'s tier, and the most that tier holds. a mute has no tier. │ │25 +fn tierOf(comptime mode: evt.Notify.Mode) ?struct { map_key: []const u8, cap: ?u64 } { │ │26 + const map_key = "haxy/notif-id->" ++ @tagName(mode) ++ "-id-set"; │ │27 + return switch (mode) { │ │28 + .involved => .{ .map_key = map_key, .cap = null }, │ │29 + .commented => .{ .map_key = map_key, .cap = max_commented }, │ │30 + .subscribed => .{ .map_key = map_key, .cap = max_subscribed }, │ │31 + .muted => null, │ │32 + }; │ │33 +} │ │34 │ │35 // one thread's item in a user's inbox. the names and title are as of its last bump. │ │36 pub const Notif = struct { │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │67 fn transact(io: std.Io, allocator: std.mem.Allocator, users_dir: []const u8, user_id: *const [evt.event_id_size]u8, ctx: anytype) !void {│ │68 var user_repo = (try evt.openUserRepo(io, allocator, users_dir, user_id)) orelse return error.NotFound; │ │69 defer user_repo.deinit(io, allocator); │ │59 - try transactIn(io, &user_repo, ctx); │ │60 -} │ │70 │ │62 -// run `ctx` in a transaction on an open user repo │ │63 -fn transactIn(io: std.Io, user_repo: *rp.Repo(.xit, evt.user_repo_opts), ctx: anytype) !void { │ │71 try user_repo.core.db_file.lock(io, .exclusive); │ │72 defer user_repo.core.db_file.unlock(io); │ │73 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │234 return try (try UserDB.SortedSet(.read_only).init(unread_cursor)).count();│ │235 } │ │236 │ │230 -// always notify `user_id` of the thread `thread_id` in the repo at `location`│ │237 +// notify `user_id` of the thread `thread_id` in the repo at `location` by │ │238 +// choice, unless a tier already holds them or the subscribed tier is full │ │239 pub fn subscribe( │ │240 io: std.Io, │ │241 allocator: std.mem.Allocator, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │245 thread_id: *const [evt.event_id_size]u8, │ │246 author: evt.CommitAuthor, │ │247 ) !void { │ │240 - try writeMode(io, allocator, users_dir, user_id, &location, thread_id, .subscribe, author); │ │241 - indexSubscriber(io, allocator, users_dir, location, thread_id, user_id, true); │ │248 + { │ │249 + var owner_repo = (try evt.openUserRepo(io, allocator, users_dir, &location.owner_id)) orelse return error.NotFound;│ │250 + defer owner_repo.deinit(io, allocator); │ │251 + const moment = try owner_repo.core.latestMoment(); │ │252 + const notif_id = idOf(&location.repo_id, thread_id); │ │253 + if (try isNotified(moment, &notif_id, user_id)) return; │ │254 + if (try tierFull(moment, &notif_id, .subscribed)) return; │ │255 + } │ │256 + try writeMode(io, allocator, users_dir, user_id, &location, thread_id, .subscribed, author); │ │257 } │ │258 │ │259 // never notify `user_id` of the thread `thread_id` in the repo at `location`, dropping its notif │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯