Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125//! The prototypes' mailbox: the sample threads, changed in memory by what//! the user does (archive, label, star, read). Nothing is saved.
const std = @import("std");const sample = @import("sample.zig");const ascii = std.ascii;const sort = std.sort;const testing = std.testing;
const Store = @This();
pub const Label = sample.Label;pub const Thread = sample.Thread;
/// Thread ids are indices into `threads`.pub const Id = u32;pub const capacity = sample.threads.len;
/// Which threads a view shows.// ast-grep-ignore: public-struct-field-default -- an options bag for query and count.pub const Filter = struct { /// Null is All Mail: everything but spam and trash. label: ?Label = .inbox, /// Matches sender, subject, or snippet, ignoring case; empty matches all. text: []const u8 = "",};
threads: [capacity]Thread,
pub fn init() Store { return .{ .threads = sample.threads };}
/// The ids of the threads `filter` selects, newest first, written into `out`.pub fn query(self: *const Store, filter: Filter, out: *[capacity]Id) []Id { var n: usize = 0; for (self.threads, 0..) |thread, i| { if (!matches(thread, filter)) continue; out[n] = @intCast(i); n += 1; } const ids = out[0..n]; sort.insertion(Id, ids, self, newerFirst); return ids;}
/// How many threads `filter` selects, and how many of those are unread.pub fn count(self: *const Store, filter: Filter) struct { total: u32, unread: u32 } { var total: u32 = 0; var unread: u32 = 0; for (self.threads) |thread| { if (!matches(thread, filter)) continue; total += 1; if (thread.unread) unread += 1; } return .{ .total = total, .unread = unread };}
pub fn get(self: *const Store, id: Id) *const Thread { return &self.threads[id];}
pub fn has(self: *const Store, id: Id, label: Label) bool { return self.threads[id].labels.contains(label);}
/// Archiving takes a thread out of the inbox and nothing else.pub fn archive(self: *Store, id: Id) void { self.threads[id].labels.remove(.inbox);}
/// Trash replaces every label, as Gmail does.pub fn trash(self: *Store, id: Id) void { self.threads[id].labels = .initOne(.trash);}
pub fn toggleLabel(self: *Store, id: Id, label: Label) void { self.threads[id].labels.toggle(label);}
pub fn setUnread(self: *Store, id: Id, unread: bool) void { self.threads[id].unread = unread;}
fn matches(thread: Thread, filter: Filter) bool { const in_label = if (filter.label) |label| thread.labels.contains(label) else !thread.labels.contains(.spam) and !thread.labels.contains(.trash); if (!in_label) return false; if (filter.text.len == 0) return true; return ascii.findIgnoreCase(thread.from, filter.text) != null or ascii.findIgnoreCase(thread.subject, filter.text) != null or ascii.findIgnoreCase(thread.snippet, filter.text) != null;}
fn newerFirst(self: *const Store, a: Id, b: Id) bool { return self.threads[a].minutes_ago < self.threads[b].minutes_ago;}
test "query filters by label and text, newest first" { var store: Store = .init(); var buf: [capacity]Id = undefined; const hiring = store.query(.{ .label = .hiring }, &buf); try testing.expect(hiring.len >= 3); for (hiring[1..], hiring[0 .. hiring.len - 1]) |later, earlier| { try testing.expect(store.get(earlier).minutes_ago <= store.get(later).minutes_ago); try testing.expect(store.has(later, .hiring)); } const lisbon = store.query(.{ .label = null, .text = "LISBON" }, &buf); try testing.expect(lisbon.len >= 3);}
test "archive leaves other labels; All Mail hides spam and trash" { var store: Store = .init(); var buf: [capacity]Id = undefined; const first = store.query(.{}, &buf)[0]; store.archive(first); try testing.expect(!store.has(first, .inbox)); try testing.expect(store.has(first, .work)); const all_before = store.count(.{ .label = null }).total; store.trash(first); try testing.expectEqual(all_before - 1, store.count(.{ .label = null }).total);}