cleanup (dedup/remove dead code)
This commit is contained in:
parent
f378460553
commit
3c45c9b362
4 changed files with 16 additions and 148 deletions
|
|
@ -13,7 +13,7 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Setup Zig
|
- name: Setup Zig
|
||||||
uses: https://codeberg.org/mlugg/setup-zig@v2.2.1
|
uses: https://codeberg.org/mlugg/setup-zig@v2.2.1
|
||||||
- name: Install dependencies (gitea)
|
- name: Install dependencies
|
||||||
if: env.GITEA_ACTIONS == 'true'
|
if: env.GITEA_ACTIONS == 'true'
|
||||||
run: apt-get update && apt-get install --no-install-recommends -y libnotmuch-dev libgmime-3.0-dev libglib2.0-dev
|
run: apt-get update && apt-get install --no-install-recommends -y libnotmuch-dev libgmime-3.0-dev libglib2.0-dev
|
||||||
- name: Build project
|
- name: Build project
|
||||||
|
|
|
||||||
119
src/Email.zig
119
src/Email.zig
|
|
@ -212,73 +212,6 @@ pub const Message = struct {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rawBody(self: Message, allocator: std.mem.Allocator) ![]const u8 {
|
|
||||||
// Get the message body using GMime
|
|
||||||
const body = gmime.g_mime_message_get_body(self.message);
|
|
||||||
if (body == null) return error.NoMessageBody;
|
|
||||||
|
|
||||||
// Check if it's a multipart message
|
|
||||||
if (gmime.g_type_check_instance_is_a(@as(*gmime.GTypeInstance, @ptrCast(body)), gmime.g_mime_multipart_get_type()) != 0) {
|
|
||||||
const multipart: *gmime.GMimeMultipart = @ptrCast(body);
|
|
||||||
|
|
||||||
// Try to find HTML content in the multipart
|
|
||||||
if (try findHtmlInMultipart(multipart, allocator)) |html_content| {
|
|
||||||
// Trim trailing whitespace and newlines to match expected format
|
|
||||||
return html_content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's not multipart or we didn't find HTML, check if it's a single text part
|
|
||||||
if (gmime.g_type_check_instance_is_a(@as(*gmime.GTypeInstance, @ptrCast(body)), gmime.g_mime_text_part_get_type()) != 0) {
|
|
||||||
const text_part: *gmime.GMimeTextPart = @ptrCast(body);
|
|
||||||
const text = gmime.g_mime_text_part_get_text(text_part);
|
|
||||||
if (text != null) {
|
|
||||||
defer gmime.g_free(text);
|
|
||||||
const content = try allocator.dupe(u8, std.mem.span(text));
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: convert the entire body to string
|
|
||||||
const body_string = gmime.g_mime_object_to_string(body, null);
|
|
||||||
if (body_string == null) return error.BodyConversionFailed;
|
|
||||||
|
|
||||||
defer gmime.g_free(body_string);
|
|
||||||
return try allocator.dupe(u8, std.mem.span(body_string));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getContent(self: Message, allocator: std.mem.Allocator) !struct { content: []const u8, content_type: []const u8 } {
|
|
||||||
const body = gmime.g_mime_message_get_body(self.message);
|
|
||||||
if (body == null) return error.NoMessageBody;
|
|
||||||
|
|
||||||
// Check if it's a multipart message
|
|
||||||
if (gmime.g_type_check_instance_is_a(@as(*gmime.GTypeInstance, @ptrCast(body)), gmime.g_mime_multipart_get_type()) != 0) {
|
|
||||||
const multipart: *gmime.GMimeMultipart = @ptrCast(body);
|
|
||||||
if (try findHtmlInMultipart(multipart, allocator)) |html_content| {
|
|
||||||
return .{ .content = html_content, .content_type = "text/html" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's a single text part
|
|
||||||
if (gmime.g_type_check_instance_is_a(@as(*gmime.GTypeInstance, @ptrCast(body)), gmime.g_mime_text_part_get_type()) != 0) {
|
|
||||||
const text_part: *gmime.GMimeTextPart = @ptrCast(body);
|
|
||||||
const text = gmime.g_mime_text_part_get_text(text_part);
|
|
||||||
if (text != null) {
|
|
||||||
defer gmime.g_free(text);
|
|
||||||
const content = try allocator.dupe(u8, std.mem.span(text));
|
|
||||||
const content_type_obj = gmime.g_mime_object_get_content_type(body);
|
|
||||||
const mime_type = if (content_type_obj != null)
|
|
||||||
gmime.g_mime_content_type_get_mime_type(content_type_obj)
|
|
||||||
else
|
|
||||||
null;
|
|
||||||
const ct = if (mime_type != null) std.mem.span(mime_type) else "text/plain";
|
|
||||||
return .{ .content = content, .content_type = ct };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return error.NoTextContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getTextAndHtmlBodyVersions(self: Message, allocator: std.mem.Allocator) !struct { text: []const u8, html: []const u8 } {
|
pub fn getTextAndHtmlBodyVersions(self: Message, allocator: std.mem.Allocator) !struct { text: []const u8, html: []const u8 } {
|
||||||
const body = gmime.g_mime_message_get_body(self.message);
|
const body = gmime.g_mime_message_get_body(self.message);
|
||||||
if (body == null) return error.NoMessageBody;
|
if (body == null) return error.NoMessageBody;
|
||||||
|
|
@ -343,13 +276,6 @@ pub const Message = struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getHeader(self: Message, name: []const u8) ?[]const u8 {
|
|
||||||
const name_z = std.mem.sliceTo(name, 0);
|
|
||||||
const header = gmime.g_mime_message_get_header(self.message, name_z.ptr);
|
|
||||||
if (header == null) return null;
|
|
||||||
return std.mem.span(header);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const AttachmentInfo = struct {
|
pub const AttachmentInfo = struct {
|
||||||
filename: []const u8,
|
filename: []const u8,
|
||||||
content_type: []const u8,
|
content_type: []const u8,
|
||||||
|
|
@ -427,51 +353,6 @@ pub const Message = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn testPath(allocator: std.mem.Allocator) ![:0]const u8 {
|
|
||||||
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
||||||
const cwd = try std.fs.cwd().realpath(".", cwd_buf[0..]);
|
|
||||||
return std.fs.path.joinZ(allocator, &[_][]const u8{ cwd, "mail", "Inbox", "cur", "1721591945.R4187135327503631514.nucman:2,S" });
|
|
||||||
}
|
|
||||||
test "read raw body of message" {
|
|
||||||
var engine = Self.init();
|
|
||||||
defer engine.deinit();
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
const message_path = try testPath(allocator);
|
|
||||||
defer allocator.free(message_path);
|
|
||||||
const msg = try engine.openMessage(message_path);
|
|
||||||
defer msg.deinit();
|
|
||||||
const body = try msg.rawBody(allocator);
|
|
||||||
defer allocator.free(body);
|
|
||||||
try std.testing.expectEqualStrings(
|
|
||||||
\\<html>
|
|
||||||
\\<head>
|
|
||||||
\\<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
||||||
\\</head>
|
|
||||||
\\<body><a href="https://unmaskfauci.com/assets/images/chw.php"><img src="https://imgpx.com/dfE6oYsvHoYw.png"></a> <div><img width=1 height=1 alt="" src="https://vnevent.net/wp-content/plugins/wp-automatic/awe.php?QFYiTaVCm0ogM30sC5RNRb%2FKLO0%2FqO3iN9A89RgPbrGjPGsdVierqrtB7w8mnIqJugBVA5TZVG%2F6MFLMOrK9z4D6vgFBDRgH88%2FpEmohBbpaSFf4wx1l9S4LGJd87EK6"></div></body></html>
|
|
||||||
, std.mem.trimRight(u8, body, "\r\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "can get body from multipart/alternative html preferred" {
|
|
||||||
var engine = Self.init();
|
|
||||||
defer engine.deinit();
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
const message_path = try testPath(allocator);
|
|
||||||
defer allocator.free(message_path);
|
|
||||||
const msg = try engine.openMessage(message_path);
|
|
||||||
defer msg.deinit();
|
|
||||||
const body = try msg.rawBody(allocator);
|
|
||||||
defer allocator.free(body);
|
|
||||||
const b = "hi";
|
|
||||||
_ = b;
|
|
||||||
try std.testing.expectEqualStrings(
|
|
||||||
\\<html>
|
|
||||||
\\<head>
|
|
||||||
\\<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
||||||
\\</head>
|
|
||||||
\\<body><a href="https://unmaskfauci.com/assets/images/chw.php"><img src="https://imgpx.com/dfE6oYsvHoYw.png"></a> <div><img width=1 height=1 alt="" src="https://vnevent.net/wp-content/plugins/wp-automatic/awe.php?QFYiTaVCm0ogM30sC5RNRb%2FKLO0%2FqO3iN9A89RgPbrGjPGsdVierqrtB7w8mnIqJugBVA5TZVG%2F6MFLMOrK9z4D6vgFBDRgH88%2FpEmohBbpaSFf4wx1l9S4LGJd87EK6"></div></body></html>
|
|
||||||
, std.mem.trimRight(u8, body, "\r\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "can parse attachments" {
|
test "can parse attachments" {
|
||||||
var engine = Self.init();
|
var engine = Self.init();
|
||||||
defer engine.deinit();
|
defer engine.deinit();
|
||||||
|
|
|
||||||
|
|
@ -283,14 +283,6 @@ pub const Db = struct {
|
||||||
return c.notmuch_thread_get_total_messages(self.thread_handle);
|
return c.notmuch_thread_get_total_messages(self.thread_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the total number of files in 'thread'.
|
|
||||||
///
|
|
||||||
/// This sums notmuch_message_count_files over all messages in the
|
|
||||||
/// thread
|
|
||||||
pub fn getTotalFiles(self: Thread) c_int {
|
|
||||||
return c.notmuch_thread_get_total_files(self.thread_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getMessages(self: Thread) !MessageIterator {
|
pub fn getMessages(self: Thread) !MessageIterator {
|
||||||
return .{
|
return .{
|
||||||
.messages_state = c.notmuch_thread_get_messages(self.thread_handle) orelse return error.CouldNotGetIterator,
|
.messages_state = c.notmuch_thread_get_messages(self.thread_handle) orelse return error.CouldNotGetIterator,
|
||||||
|
|
|
||||||
35
src/root.zig
35
src/root.zig
|
|
@ -189,21 +189,25 @@ pub const NotmuchDb = struct {
|
||||||
if (self.email_owned) self.email.deinit();
|
if (self.email_owned) self.email.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a threads query, recovering once from a stale database snapshot.
|
/// Run a database operation, recovering once from a stale snapshot.
|
||||||
///
|
///
|
||||||
/// A read-only notmuch handle sees a snapshot of the database as of open
|
/// A read-only notmuch handle sees a snapshot of the database as of open
|
||||||
/// time. Mail delivery runs `notmuch new`, and a query issued against a
|
/// time. Mail delivery runs `notmuch new`, and an operation issued against
|
||||||
/// snapshot that has since been rewritten can fail with a Xapian error.
|
/// a snapshot that has since been rewritten can fail with a Xapian error.
|
||||||
/// When that happens we reopen to the latest revision and retry once,
|
/// When that happens we reopen to the latest revision and retry once,
|
||||||
/// rather than killing the whole process as the previous code did (it
|
/// rather than killing the whole process as the previous code did (it
|
||||||
/// called std.process.exit and leaned on the container restart policy). If
|
/// called std.process.exit and leaned on the container restart policy). If
|
||||||
/// the reopen itself fails we surface the original query error.
|
/// the reopen itself fails we surface the original error.
|
||||||
///
|
///
|
||||||
/// Callers must hold `self.mutex`.
|
/// `op` is a notmuch.Db method taking one [:0]const u8 argument (e.g.
|
||||||
fn openThreads(self: *NotmuchDb, query_z: [:0]const u8) !notmuch.Db.ThreadIterator {
|
/// searchThreads, findMessage). It's a comptime higher-order parameter
|
||||||
return self.db.searchThreads(query_z) catch |first_err| {
|
/// (anytype), which keeps the helper generic over each op's exact return
|
||||||
|
/// and error type -- naming a concrete fn type would force the shared
|
||||||
|
/// signature's error set to anyerror. Callers must hold `self.mutex`.
|
||||||
|
fn withReopenRetry(self: *NotmuchDb, comptime op: anytype, arg: [:0]const u8) @TypeOf(op(self.db, arg)) {
|
||||||
|
return op(self.db, arg) catch |first_err| {
|
||||||
self.db.reopen() catch return first_err;
|
self.db.reopen() catch return first_err;
|
||||||
return self.db.searchThreads(query_z);
|
return op(self.db, arg);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,7 +216,7 @@ pub const NotmuchDb = struct {
|
||||||
const query_z = try std.fmt.bufPrintZ(&query_buf, "{s}", .{query});
|
const query_z = try std.fmt.bufPrintZ(&query_buf, "{s}", .{query});
|
||||||
const ti = try self.allocator.create(notmuch.Db.ThreadIterator);
|
const ti = try self.allocator.create(notmuch.Db.ThreadIterator);
|
||||||
errdefer self.allocator.destroy(ti);
|
errdefer self.allocator.destroy(ti);
|
||||||
ti.* = try self.openThreads(query_z);
|
ti.* = try self.withReopenRetry(notmuch.Db.searchThreads, query_z);
|
||||||
return Threads.init(self.allocator, ti);
|
return Threads.init(self.allocator, ti);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,7 +225,7 @@ pub const NotmuchDb = struct {
|
||||||
const query_z = try std.fmt.bufPrintZ(&query_buf, "thread:{s}", .{thread_id});
|
const query_z = try std.fmt.bufPrintZ(&query_buf, "thread:{s}", .{thread_id});
|
||||||
const iter_ptr = try self.allocator.create(notmuch.Db.ThreadIterator);
|
const iter_ptr = try self.allocator.create(notmuch.Db.ThreadIterator);
|
||||||
errdefer self.allocator.destroy(iter_ptr);
|
errdefer self.allocator.destroy(iter_ptr);
|
||||||
iter_ptr.* = try self.openThreads(query_z);
|
iter_ptr.* = try self.withReopenRetry(notmuch.Db.searchThreads, query_z);
|
||||||
errdefer iter_ptr.deinit();
|
errdefer iter_ptr.deinit();
|
||||||
|
|
||||||
const thread = iter_ptr.next();
|
const thread = iter_ptr.next();
|
||||||
|
|
@ -265,15 +269,6 @@ 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 {
|
pub fn getMessage(self: *NotmuchDb, message_id: []const u8) !MessageDetail {
|
||||||
const message_id_z = try self.allocator.dupeZ(u8, message_id);
|
const message_id_z = try self.allocator.dupeZ(u8, message_id);
|
||||||
defer self.allocator.free(message_id_z);
|
defer self.allocator.free(message_id_z);
|
||||||
|
|
@ -281,7 +276,7 @@ pub const NotmuchDb = struct {
|
||||||
// Look the message up by id directly. Previously this searched for the
|
// Look the message up by id directly. Previously this searched for the
|
||||||
// thread containing the id and returned the thread's first message, so
|
// thread containing the id and returned the thread's first message, so
|
||||||
// every message in a reply thread rendered the same body.
|
// every message in a reply thread rendered the same body.
|
||||||
const notmuch_msg = (try self.findMessage(message_id_z)) orelse return error.MessageNotFound;
|
const notmuch_msg = (try self.withReopenRetry(notmuch.Db.findMessage, message_id_z)) orelse return error.MessageNotFound;
|
||||||
defer notmuch_msg.deinit();
|
defer notmuch_msg.deinit();
|
||||||
|
|
||||||
const filename_z = try self.allocator.dupeZ(u8, notmuch_msg.getFilename());
|
const filename_z = try self.allocator.dupeZ(u8, notmuch_msg.getFilename());
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue