···11const std = @import("std");
2233const c = @cImport({
44+ @cInclude("stdlib.h");
45 @cInclude("notmuch.h");
56});
6778const log = std.log.scoped(.notmuch);
89910fn generateEnum(comptime prefix: []const u8) type {
1010- @setEvalBranchQuota(10000);
1111+ @setEvalBranchQuota(16000);
1112 const info = @typeInfo(c);
1213 var count: usize = 0;
1314 for (info.@"struct".decls) |d| {
···3031 index += 1;
3132 }
3233 }
3333- return @Type(.{ .@"enum" = .{
3434- .tag_type = std.math.IntFittingRange(0, max),
3535- .fields = &fields,
3636- .decls = &.{},
3737- .is_exhaustive = true,
3838- } });
3434+ return @Type(
3535+ .{
3636+ .@"enum" = .{
3737+ .tag_type = std.math.IntFittingRange(0, max),
3838+ .fields = &fields,
3939+ .decls = &.{},
4040+ .is_exhaustive = true,
4141+ },
4242+ },
4343+ );
3944}
40454141-pub const STATUS = generateEnum("NOTMUCH_STATUS_");
4246pub const DATABASE_MODE = generateEnum("NOTMUCH_DATABASE_MODE_");
4747+pub const DECRYPT = generateEnum("NOTMUCH_DECRYPT_");
4848+pub const MESSAGE_FLAG = generateEnum("NOTMUCH_MESSAGE_FLAG_");
4949+pub const STATUS = generateEnum("NOTMUCH_STATUS_");
43504451const Error = error{
4552 BadQuerySyntax,
···6976 XapianException,
7077};
71787272-fn statusToError(comptime T: type, rc: c.notmuch_status_t, value: T) Error!T {
7979+fn wrapMessage(rc: c.notmuch_status_t, message: [*c]const u8) Error!void {
8080+ if (message) |msg| {
8181+ log.err("{s}", .{msg});
8282+ c.free(@ptrCast(@constCast(msg)));
8383+ }
8484+ try wrap(rc);
8585+}
8686+8787+fn wrap(rc: c.notmuch_status_t) Error!void {
7388 return switch (@as(STATUS, @enumFromInt(rc))) {
7474- .SUCCESS => value,
8989+ .SUCCESS => {},
7590 .BAD_QUERY_SYNTAX => error.BadQuerySyntax,
7691 .CLOSED_DATABASE => error.ClosedDatabase,
7792 .DATABASE_EXISTS => error.DatabaseExists,
···100115}
101116102117pub const Database = struct {
103103- database: ?*c.notmuch_database_t = null,
118118+ database: *c.notmuch_database_t,
104119105105- pub fn open_with_config(
120120+ pub fn open(
106121 database_path: ?[*:0]const u8,
107122 mode: DATABASE_MODE,
108123 config_path: ?[:0]const u8,
···113128 return error.NotmuchVersion;
114129 }
115130131131+ var error_message: [*c]u8 = null;
116132 var database: ?*c.notmuch_database_t = null;
117117- const rc = c.notmuch_database_open_with_config(
118118- if (database_path) |p| p else null,
133133+ try wrapMessage(c.notmuch_database_open_with_config(
134134+ database_path orelse null,
119135 @intFromEnum(mode),
120120- if (config_path) |p| p else null,
121121- if (profile) |p| p else null,
136136+ config_path orelse null,
137137+ profile orelse null,
122138 &database,
123123- null,
139139+ &error_message,
140140+ ), error_message);
141141+ return .{
142142+ .database = database orelse unreachable,
143143+ };
144144+ }
145145+146146+ pub fn create(
147147+ database_path: ?[*:0]const u8,
148148+ config_path: ?[:0]const u8,
149149+ profile: ?[:0]const u8,
150150+ ) Error!Database {
151151+ if (!c.LIBNOTMUCH_CHECK_VERSION(5, 6, 0)) {
152152+ log.err("need newer notmuch", .{});
153153+ return error.NotmuchVersion;
154154+ }
155155+156156+ var error_message: [*c]u8 = null;
157157+ var database: ?*c.notmuch_database_t = null;
158158+ try wrapMessage(
159159+ c.notmuch_database_create_with_config(
160160+ database_path orelse null,
161161+ config_path orelse null,
162162+ profile orelse null,
163163+ &database,
164164+ &error_message,
165165+ ),
166166+ error_message,
124167 );
125125- return try statusToError(Database, rc, .{ .database = database });
168168+ return .{
169169+ .database = database orelse unreachable,
170170+ };
126171 }
127172128173 pub fn close(self: *const Database) void {
129174 _ = c.notmuch_database_close(self.database);
130175 }
131176132132- pub fn index_file(self: *const Database, filename: [:0]const u8, indexopts: ?*c.notmuch_indexopts_t) Error!void {
133133- const rc = c.notmuch_database_index_file(self.database, filename, indexopts, null);
134134- return try statusToError(void, rc, {});
177177+ pub fn indexFile(self: *const Database, filename: [:0]const u8, indexopts: ?IndexOpts) Error!void {
178178+ try wrap(c.notmuch_database_index_file(
179179+ self.database,
180180+ filename,
181181+ if (indexopts) |i| i.indexopts else null,
182182+ null,
183183+ ));
135184 }
136185137137- pub fn index_file_get_message(self: *const Database, filename: [:0]const u8, indexopts: ?*c.notmuch_indexopts_t) Error!Message {
186186+ pub fn indexFileGetMessage(self: *const Database, filename: [:0]const u8, indexopts: ?IndexOpts) Error!Message {
138187 var message: ?*c.notmuch_message_t = null;
139139- const rc = c.notmuch_database_index_file(self.database, filename, indexopts, &message);
140140- return statusToError(Message, rc, .{ .duplicate = false, .message = message }) catch |err| switch (err) {
141141- error.DuplicateMessageID => return .{ .duplicate = true, .message = message },
188188+ wrap(c.notmuch_database_index_file(
189189+ self.database,
190190+ filename,
191191+ if (indexopts) |i| i.indexopts else null,
192192+ &message,
193193+ )) catch |err| switch (err) {
194194+ error.DuplicateMessageID => return .{
195195+ .duplicate = true,
196196+ .message = message orelse unreachable,
197197+ },
142198 else => |e| return e,
199199+ };
200200+ return .{
201201+ .duplicate = false,
202202+ .message = message orelse unreachable,
143203 };
144204 }
145205146146- pub fn find_message_by_filename(self: *const Database, filename: [:0]const u8) Error!Message {
206206+ pub fn findMessageByFilename(self: *const Database, filename: [:0]const u8) Error!Message {
147207 var message: ?*c.notmuch_message_t = null;
148148- const rc = c.notmuch_database_find_message_by_filename(self.database, filename, &message);
149149- return try statusToError(Message, rc, .{ .message = message });
208208+ try wrap(c.notmuch_database_find_message_by_filename(self.database, filename, &message));
209209+ return .{
210210+ .message = message orelse unreachable,
211211+ };
150212 }
151213152152- pub fn remove_message(self: *const Database, filename: [:0]const u8) Error!void {
153153- const rc = c.notmuch_database_remove_message(self.database, filename);
154154- return try statusToError(void, rc, {});
214214+ pub fn removeMessage(self: *const Database, filename: [:0]const u8) Error!void {
215215+ try wrap(c.notmuch_database_remove_message(self.database, filename));
216216+ }
217217+218218+ pub fn getDefaultIndexOpts(self: *const Database) ?IndexOpts {
219219+ return .{
220220+ .indexopts = c.notmuch_database_get_default_indexopts(self.database) orelse return null,
221221+ };
222222+ }
223223+224224+ ///
225225+ pub fn getConfigPath(self: *const Database) ?[]const u8 {
226226+ const config = c.notmuch_config_path(self.database);
227227+ return std.mem.span(config orelse return null);
155228 }
156229};
157230158231pub const Message = struct {
159232 duplicate: ?bool = null,
160160- message: ?*c.notmuch_message_t = null,
233233+ message: *c.notmuch_message_t,
161234162162- pub fn add_tag(self: *const Message, tag: [:0]const u8) Error!void {
163163- const rc = c.notmuch_message_add_tag(self.message, tag);
164164- return try statusToError(void, rc, {});
235235+ /// Get the message ID of 'message'.
236236+ ///
237237+ /// The returned string belongs to 'message' and as such, should not be
238238+ /// modified by the caller and will only be valid for as long as the message
239239+ /// is valid, (which is until the query from which it derived is destroyed).
240240+ ///
241241+ /// This function will return NULL if triggers an unhandled Xapian
242242+ /// exception.
243243+ pub fn getMessageID(self: *const Message) ?[]const u8 {
244244+ return std.mem.span(c.notmuch_message_get_message_id(self.message) orelse return null);
245245+ }
246246+247247+ /// Get the thread ID of 'message'.
248248+ ///
249249+ /// The returned string belongs to 'message' and as such, should not be
250250+ /// modified by the caller and will only be valid for as long as the message
251251+ /// is valid, (for example, until the user calls notmuch_message_destroy on
252252+ /// 'message' or until a query from which it derived is destroyed).
253253+ ///
254254+ /// This function will return NULL if triggers an unhandled Xapian
255255+ /// exception.
256256+ pub fn getThreadID(self: *const Message) ?[:0]const u8 {
257257+ return std.mem.span(c.notmuch_message_get_thread_id(self.message) orelse return null);
258258+ }
259259+260260+ /// Add a tag to the given message.
261261+ pub fn addTag(self: *const Message, tag: [:0]const u8) Error!void {
262262+ try wrap(c.notmuch_message_add_tag(self.message, tag));
263263+ }
264264+265265+ /// Remove a tag from the given message.
266266+ pub fn removeTag(self: *const Message, tag: [:0]const u8) Error!void {
267267+ try wrap(c.notmuch_message_add_tag(self.message, tag));
268268+ }
269269+270270+ /// Remove all tags from the given message.
271271+ ///
272272+ /// See freeze for an example showing how to safely replace tag values.
273273+ pub fn removeAllTags(self: *const Message) Error!void {
274274+ try wrap(c.notmuch_message_remove_all_tags(self.message));
275275+ }
276276+277277+ /// Freeze the current state of 'message' within the database.
278278+ ///
279279+ /// This means that changes to the message state, (via
280280+ /// notmuch_message_add_tag, notmuch_message_remove_tag, and
281281+ /// notmuch_message_remove_all_tags), will not be committed to the database
282282+ /// until the message is thawed with notmuch_message_thaw.
283283+ ///
284284+ /// Multiple calls to freeze/thaw are valid and these calls will "stack".
285285+ /// That is there must be as many calls to thaw as to freeze before a
286286+ /// message is actually thawed.
287287+ ///
288288+ /// The ability to do freeze/thaw allows for safe transactions to change tag
289289+ /// values. For example, explicitly setting a message to have a given set of
290290+ /// tags might look like this:
291291+ ///
292292+ /// notmuch_message_freeze (message);
293293+ ///
294294+ /// notmuch_message_remove_all_tags (message);
295295+ ///
296296+ /// for (i = 0; i < NUM_TAGS; i++)
297297+ /// notmuch_message_add_tag (message, tags[i]);
298298+ ///
299299+ /// notmuch_message_thaw (message);
300300+ ///
301301+ /// With freeze/thaw used like this, the message in the database is
302302+ /// guaranteed to have either the full set of original tag values, or the
303303+ /// full set of new tag values, but nothing in between.
304304+ ///
305305+ /// Imagine the example above without freeze/thaw and the operation somehow
306306+ /// getting interrupted. This could result in the message being left with no
307307+ /// tags if the interruption happened after notmuch_message_remove_all_tags
308308+ /// but before notmuch_message_add_tag. Get a value of a flag for the email
309309+ /// corresponding to 'message'.
310310+ pub fn freeze(self: *const Message) Error!void {
311311+ try wrap(c.notmuch_message_freeze(self.message));
312312+ }
313313+314314+ /// Thaw the current 'message', synchronizing any changes that may have
315315+ /// occurred while 'message' was frozen into the notmuch database.
316316+ ///
317317+ /// See notmuch_message_freeze for an example of how to use this function to
318318+ /// safely provide tag changes.
319319+ ///
320320+ /// Multiple calls to freeze/thaw are valid and these calls with "stack".
321321+ /// That is there must be as many calls to thaw as to freeze before a
322322+ /// message is actually thawed.
323323+ pub fn thaw(self: *const Message) Error!void {
324324+ try wrap(c.notmuch_message_thaw(self.message));
325325+ }
326326+327327+ /// Get a value of a flag for the email corresponding to 'message'.
328328+ pub fn getFlag(self: *const Message, flag: MESSAGE_FLAG) Error!bool {
329329+ var is_set: c.notmuch_bool_t = undefined;
330330+ try wrap(c.notmuch_message_get_flag_st(self.message, @intFromEnum(flag), &is_set));
331331+ return is_set != 0;
332332+ }
333333+334334+ /// Set a value of a flag for the email corresponding to 'message'.
335335+ pub fn setFlag(self: *const Message, flag: MESSAGE_FLAG, value: bool) void {
336336+ c.notmuch_message_set_flag(self.message, @intFromEnum(flag), @intFromBool(value));
337337+ }
338338+339339+ /// Get the date of 'message' as a nanosecond timestamp value.
340340+ ///
341341+ /// For the original textual representation of the Date header from the
342342+ /// message call getHeader() with a header value of
343343+ /// "date".
344344+ ///
345345+ /// Returns `null` in case of error.
346346+ pub fn getDate(self: *const Message) ?i128 {
347347+ const time = c.notmuch_message_get_date(self.message);
348348+ if (time == 0) return null;
349349+ return time * std.time.ns_per_s;
350350+ }
351351+352352+ /// Get the value of the specified header from 'message' as a UTF-8 string.
353353+ ///
354354+ /// Common headers are stored in the database when the message is indexed and
355355+ /// will be returned from the database. Other headers will be read from the
356356+ /// actual message file.
357357+ ///
358358+ /// The header name is case insensitive.
359359+ ///
360360+ /// The returned string belongs to the message so should not be modified or
361361+ /// freed by the caller (nor should it be referenced after the message is
362362+ /// destroyed).
363363+ ///
364364+ /// Returns an empty string ("") if the message does not contain a header line
365365+ /// matching 'header'. Returns NULL if any error occurs.
366366+ pub fn getHeader(self: *const Message, header: [:0]const u8) ?[:0]const u8 {
367367+ return std.mem.span(c.notmuch_message_get_header(self.message, header) orelse return null);
368368+ }
369369+370370+ /// Retrieve the value for a single property key
371371+ ///
372372+ /// Returns a string owned by the message or NULL if there is no such
373373+ /// key. In the case of multiple values for the given key, the first one
374374+ /// is retrieved.
375375+ pub fn getProperty(self: *const Message, key: [:0]const u8) Error!?[:0]const u8 {
376376+ var value: [*c]const u8 = undefined;
377377+ try wrap(c.notmuch_message_get_property(self.message, key, &value));
378378+ return std.mem.span(value orelse return null);
379379+ }
380380+381381+ /// Get the properties for *message*, returning a PropertyIterator object
382382+ /// which can be used to iterate over all properties.
383383+ ///
384384+ /// The PropertyIterator object is owned by the message and as such, will
385385+ /// only be valid for as long as the message is valid, (which is until the
386386+ /// query from which it derived is destroyed).
387387+ pub fn getProperties(
388388+ /// the message to examine
389389+ self: *const Message,
390390+ /// key or key prefix
391391+ key: [:0]const u8,
392392+ /// if true, require exact match with key, otherwise treat as prefix
393393+ exact: bool,
394394+ ) PropertyIterator {
395395+ return .{
396396+ .properties_ = c.notmuch_message_get_properties(self.message, key, @intFromBool(exact)),
397397+ };
398398+ }
399399+400400+ /// Add a (key,value) pair to a message.
401401+ pub fn addProperty(self: *const Message, key: [:0]const u8, value: [:0]const u8) Error!void {
402402+ try wrap(c.notmuch_message_add_property(self.message, key, value));
403403+ }
404404+405405+ /// Remove a (key,value) pair from a message.
406406+ ///
407407+ /// It is not an error to remove a non-existent (key,value) pair
408408+ pub fn removeProperty(self: *const Message, key: [:0]const u8, value: [:0]const u8) Error!void {
409409+ try wrap(c.notmuch_message_remove_property(self.message, key, value));
410410+ }
411411+412412+ /// Remove all (key,value) pairs from the given message.
413413+ pub fn removeAllProperties(
414414+ /// the message to operate on
415415+ self: *const Message,
416416+ /// key to delete properties for. If NULL, delete properties for all keys
417417+ key: ?[:0]const u8,
418418+ ) Error!void {
419419+ try wrap(c.notmuch_message_remove_all_properties(self.message, key orelse null));
165420 }
166421167422 pub fn deinit(self: *const Message) void {
168423 _ = c.notmuch_message_destroy(self.message);
424424+ }
425425+};
426426+427427+pub const IndexOpts = struct {
428428+ indexopts: *c.notmuch_indexopts_t,
429429+430430+ pub fn getDecryptPolicy(self: IndexOpts) DECRYPT {
431431+ return @enumFromInt(c.notmuch_indexopts_get_decrypt_policy(self.indexopts));
432432+ }
433433+434434+ pub fn setDecryptPolicy(self: IndexOpts, decrypt_policy: DECRYPT) Error!void {
435435+ try wrap(c.notmuch_indexopts_set_decrypt_policy(self.indexopts, @intFromEnum(decrypt_policy)));
436436+ }
437437+438438+ pub fn deinit(self: IndexOpts) void {
439439+ c.notmuch_indexopts_destroy(self.indexopts);
440440+ }
441441+};
442442+443443+pub const TagIterator = struct {
444444+ tags: *c.notmuch_tags_t,
445445+446446+ pub fn next(self: *TagIterator) ?[]const u8 {
447447+ if (c.notmuch_tags_valid(self.tags) == 0) return null;
448448+ defer c.notmuch_tags_move_to_next(self.tags);
449449+ return std.mem.span(c.notmuch_tags_get(self.tags) orelse unreachable);
450450+ }
451451+452452+ pub fn deinit(self: *TagIterator) void {
453453+ c.notmuch_tags_destroy(self.tags);
454454+ }
455455+};
456456+457457+pub const PropertyIterator = struct {
458458+ properties_: ?*c.notmuch_message_properties_t,
459459+460460+ pub fn next(self: PropertyIterator) ?struct {
461461+ key: [:0]const u8,
462462+ value: [:0]const u8,
463463+ } {
464464+ const properties = self.properties_ orelse return null;
465465+ if (c.notmuch_message_properties_valid(properties) == 0) return null;
466466+ defer c.notmuch_message_properties_move_to_next(properties);
467467+ return .{
468468+ .key = std.mem.span(c.notmuch_message_properties_key(properties) orelse unreachable),
469469+ .value = std.mem.span(c.notmuch_message_properties_value(properties) orelse unreachable),
470470+ };
471471+ }
472472+473473+ pub fn deinit(self: PropertyIterator) void {
474474+ const properties = self.properties_ orelse return;
475475+ c.notmuch_message_properties_destroy(properties);
169476 }
170477};
171478