message thread handling
This commit is contained in:
parent
1a60ca57f9
commit
f378460553
2 changed files with 54 additions and 9 deletions
|
|
@ -94,6 +94,20 @@ pub const Db = struct {
|
|||
if (status != c.NOTMUCH_STATUS_SUCCESS) return error.CouldNotReopenDatabase;
|
||||
}
|
||||
|
||||
/// Look up a single message by its exact message-id.
|
||||
///
|
||||
/// The id must be the bare message-id (no `mid:`/`id:` prefix and no angle
|
||||
/// brackets), exactly as notmuch stores it. Returns null when no message
|
||||
/// has that id. The returned Message owns a notmuch_message_t and must be
|
||||
/// released with Message.deinit.
|
||||
pub fn findMessage(db: Db, message_id: [:0]const u8) !?Message {
|
||||
// SAFETY: out parameter, set by notmuch_database_find_message
|
||||
var msg: ?*c.notmuch_message_t = null;
|
||||
const status = c.notmuch_database_find_message(db.handle, message_id.ptr, &msg);
|
||||
if (status != c.NOTMUCH_STATUS_SUCCESS) return error.CouldNotFindMessage;
|
||||
return if (msg) |m| Message{ .message_handle = m } else null;
|
||||
}
|
||||
|
||||
//
|
||||
// Execute a query for threads, returning a notmuch_threads_t object
|
||||
// which can be used to iterate over the results. The returned threads
|
||||
|
|
|
|||
49
src/root.zig
49
src/root.zig
|
|
@ -265,17 +265,24 @@ pub const NotmuchDb = struct {
|
|||
}
|
||||
};
|
||||
|
||||
/// Find a message by its exact id, reopening the snapshot and retrying
|
||||
/// once on failure (mirrors openThreads). Callers must hold self.mutex.
|
||||
fn findMessage(self: *NotmuchDb, message_id: [:0]const u8) !?notmuch.Db.Message {
|
||||
return self.db.findMessage(message_id) catch |first_err| {
|
||||
self.db.reopen() catch return first_err;
|
||||
return self.db.findMessage(message_id);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getMessage(self: *NotmuchDb, message_id: []const u8) !MessageDetail {
|
||||
var query_buf: [1024:0]u8 = undefined;
|
||||
const query_z = try std.fmt.bufPrintZ(&query_buf, "mid:{s}", .{message_id});
|
||||
var thread_iter = try self.openThreads(query_z);
|
||||
defer thread_iter.deinit();
|
||||
const message_id_z = try self.allocator.dupeZ(u8, message_id);
|
||||
defer self.allocator.free(message_id_z);
|
||||
|
||||
const thread = thread_iter.next() orelse return error.MessageNotFound;
|
||||
defer thread.deinit();
|
||||
|
||||
var msg_iter = try thread.getMessages();
|
||||
const notmuch_msg = msg_iter.next() orelse return error.MessageNotFound;
|
||||
// Look the message up by id directly. Previously this searched for the
|
||||
// thread containing the id and returned the thread's first message, so
|
||||
// every message in a reply thread rendered the same body.
|
||||
const notmuch_msg = (try self.findMessage(message_id_z)) orelse return error.MessageNotFound;
|
||||
defer notmuch_msg.deinit();
|
||||
|
||||
const filename_z = try self.allocator.dupeZ(u8, notmuch_msg.getFilename());
|
||||
defer self.allocator.free(filename_z);
|
||||
|
|
@ -447,3 +454,27 @@ test "can get message details with content" {
|
|||
|
||||
// TODO: Add test with attachment once we have a sample email with attachments
|
||||
}
|
||||
|
||||
test "getMessage returns the requested message, not the thread's first" {
|
||||
// Regression: getMessage used to search for the *thread* containing the id
|
||||
// and return that thread's first message, so every message in a reply
|
||||
// thread rendered the same (first) body. The fixture has a two-message
|
||||
// thread (mail/Inbox/cur/*.Zthreadtest*): an original and a reply with
|
||||
// distinct bodies. Fetching either id must return THAT message.
|
||||
const allocator = std.testing.allocator;
|
||||
var db = try openNotmuchDb(allocator, "mail", null);
|
||||
defer db.close();
|
||||
|
||||
const orig_id = "zetviel-thread-orig@example.com";
|
||||
const reply_id = "zetviel-thread-reply@example.com";
|
||||
|
||||
const orig = try db.getMessage(orig_id);
|
||||
defer orig.deinit(allocator);
|
||||
try std.testing.expectEqualStrings(orig_id, orig.message_id);
|
||||
try std.testing.expect(std.mem.indexOf(u8, orig.text_content, "UNIQUE-ORIGINAL-BODY-MARKER") != null);
|
||||
|
||||
const reply = try db.getMessage(reply_id);
|
||||
defer reply.deinit(allocator);
|
||||
try std.testing.expectEqualStrings(reply_id, reply.message_id);
|
||||
try std.testing.expect(std.mem.indexOf(u8, reply.text_content, "UNIQUE-REPLY-BODY-MARKER") != null);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue