···1919const Error = @import("error.zig").Error;
2020const IndexOpts = @import("IndexOpts.zig");
2121const Message = @import("Message.zig");
2222+const PairsIterator = @import("PairsIterator.zig");
2223const Query = @import("Query.zig");
2324const QuerySyntax = enums.QuerySyntax;
2425const status = enums.status;
2526const Status = enums.Status;
2627const TagsIterator = @import("TagsIterator.zig");
2828+const ValuesIterator = @import("ValuesIterator.zig");
2729const wrap = @import("error.zig").wrap;
2830const wrapMessage = @import("error.zig").wrapMessage;
2931···749751 return .init(query orelse return error.OutOfMemory);
750752}
751753752752-pub fn getDefaultIndexOpts(self: *const Database) ?IndexOpts {
753753- return .{
754754- .indexopts = c.notmuch_database_get_default_indexopts(self.database) orelse return null,
755755- };
756756-}
757757-758758-///
759759-pub fn configPath(self: *const Database) ?[:0]const u8 {
760760- const config = c.notmuch_config_path(self.database);
761761- return std.mem.span(config orelse return null);
762762-}
763763-764754/// Get a configuration value from an open database.
765755///
766756/// This value reflects all configuration information given at the time
···773763 return std.mem.span(c.notmuch_config_get(self.database, @intFromEnum(key)) orelse return null);
774764}
775765776776-/// Set a configuration value
766766+/// Set config `key` to `value`.
777767pub fn configSet(self: *const Database, key: Config, value: [:0]const u8) Error!void {
778768 try wrap(c.notmuch_config_set(self.database, @intFromEnum(key), value));
779769}
···786776 self: *const Database,
787777 /// configuration key
788778 key: Config,
789789-) ValuesIterator {
779779+) ?ValuesIterator {
780780+ return .{
781781+ .values = c.notmuch_config_get_values(self.database, @intFromEnum(key)) orelse return null,
782782+ };
783783+}
784784+785785+/// Returns an iterator for a ';'-delimited list of configuration values
786786+///
787787+/// These values reflect all configuration information given at the
788788+/// time the database was opened.
789789+pub fn configGetValuesString(
790790+ self: *const Database,
791791+ /// configuration key
792792+ key: [:0]const u8,
793793+) ?ValuesIterator {
790794 return .{
791791- .values = c.notmuch_config_get_values(self.database, @intFromEnum(key)),
795795+ .values = c.notmuch_config_get_values_string(self.database, key) orelse return null,
796796+ };
797797+}
798798+799799+/// Returns an iterator for a (key, value) configuration pairs. Returns `null`
800800+/// in case of error.
801801+pub fn configGetPairs(self: *const Database, prefix: [:0]const u8) ?PairsIterator {
802802+ return .{
803803+ .pairs = c.notmuch_config_get_pairs(self.database, prefix) orelse return null,
792804 };
793805}
794806···800812/// Returns IllegalArgument error if either key is unknown or the
801813/// corresponding value does not convert to boolean.
802814pub fn configGetBool(
803803- /// the database
815815+ /// The database
804816 self: *const Database,
805817 /// configuration key
806818 key: Config,
···810822 return value != 0;
811823}
812824813813-/// Returns an iterator for a ';'-delimited list of configuration values
825825+/// Return the path of the config file loaded. Returns `null` if no config file
826826+/// was loaded.
827827+pub fn configPath(self: *const Database) ?[:0]const u8 {
828828+ return std.mem.span(c.notmuch_config_path(self.database) orelse return null);
829829+}
830830+831831+/// Get the current default indexing options for a given database.
814832///
815815-/// These values reflect all configuration information given at the
816816-/// time the database was opened.
817817-pub fn configGetValuesString(
818818- self: *const Database,
819819- /// configuration key
820820- key: Config,
821821-) ValuesIterator {
833833+/// This object will survive until the database itself is destroyed, but the
834834+/// caller may also release it earlier with `IndexOpts.deinit`.
835835+///
836836+/// This object represents a set of options on how a message can be added to the
837837+/// index. At the moment it is a featureless stub.
838838+pub fn getDefaultIndexOpts(self: *const Database) ?IndexOpts {
822839 return .{
823823- .values = c.notmuch_config_get_values_string(self.database, @intFromEnum(key)),
840840+ .indexopts = c.notmuch_database_get_default_indexopts(self.database) orelse return null,
824841 };
825842}
826826-827827-pub const ValuesIterator = struct {
828828- values: ?*c.notmuch_config_values_t,
829829-830830- pub fn next(self: *ValuesIterator) ?[:0]const u8 {
831831- const values = self.values orelse return null;
832832- if (c.notmuch_config_values_valid(values) == 0) return null;
833833- defer c.notmuch_config_values_move_to_next(values);
834834- return std.mem.span(c.notmuch_config_values_get(values) orelse unreachable);
835835- }
836836-837837- pub fn start(self: *ValuesIterator) void {
838838- const values = self.values orelse return;
839839- c.notmuch_config_values_start(values);
840840- }
841841-842842- pub fn deinit(self: *ValuesIterator) void {
843843- const values = self.values orelse return;
844844- c.notmuch_config_values_destroy(values);
845845- }
846846-};
847847-848848-pub const PairsIterator = struct {
849849- pairs: ?*c.notmuch_config_pairs_t,
850850-851851- pub const Pair = struct {
852852- key: [:0]const u8,
853853- value: [:0]const u8,
854854- };
855855-856856- pub fn next(self: *PairsIterator) ?Pair {
857857- const pairs = self.pairs orelse return null;
858858- if (c.notmuch_config_pairs_valid(pairs) == 0) return null;
859859- defer c.notmuch_config_pairs_move_to_next(pairs);
860860- return .{
861861- .key = std.mem.span(c.notmuch_config_pairs_key(pairs) orelse unreachable),
862862- .value = std.mem.span(c.notmuch_config_pairs_value(pairs) orelse unreachable),
863863- };
864864- }
865865-866866- pub fn deinit(self: *PairsIterator) void {
867867- const pairs = self.pairs orelse return;
868868- c.notmuch_config_pairs_destroy(pairs);
869869- }
870870-};
871843872844test {
873845 std.testing.refAllDecls(@This());
+79-3
src/Directory.zig
···22// SPDX-License-Identifier: GPL-3.0-or-later
3344//! Zig wrapper around the `notmuch` directory APIs.
55-65const Directory = @This();
7687const std = @import("std");
99-const log = std.log.scoped(.notmuch);
1010-118const c = @import("c");
1291310const Error = @import("error.zig").Error;
1111+const FilenamesIterator = @import("FilenamesIterator.zig");
1412const wrap = @import("error.zig").wrap;
15131614directory: *c.notmuch_directory_t,
1515+1616+/// Store an mtime within the database for `Directory`.
1717+///
1818+/// The `Directory` should be an object retrieved from the database
1919+/// with `Database.getDirectory` for a particular path.
2020+///
2121+/// The intention is for the caller to use the mtime to allow efficient
2222+/// identification of new messages to be added to the database. The recommended
2323+/// usage is as follows:
2424+///
2525+/// o Read the mtime of a directory from the filesystem
2626+///
2727+/// o Call `Directory.indexFile` for all mail files in the directory
2828+///
2929+/// o Call `setMtime` with the mtime read from the
3030+/// filesystem.
3131+///
3232+/// Then, when wanting to check for updates to the directory in the future, the
3333+/// client can call `getMtime` and know that it only needs to add files if the
3434+/// mtime of the directory and files are newer than the stored timestamp.
3535+///
3636+/// Note: The `getMtime` function does not allow the caller to distinguish a
3737+/// timestamp of 0 from a non-existent timestamp. So don't store a timestamp of
3838+/// 0 unless you are comfortable with that.
3939+pub fn setMtime(self: *const Directory, mtime: i64) Error!void {
4040+ try wrap(c.notmuch_directory_set_mtime(self.directory, mtime));
4141+}
4242+4343+/// Get the mtime of a directory, (as previously stored with `setMtime`).
4444+///
4545+/// Returns `null` if no mtime has previously been stored for this directory.
4646+pub fn getMtime(self: *const Directory) Error!?i64 {
4747+ const mtime = try wrap(c.notmuch_directory_get_mtime(self.directory));
4848+ if (mtime == 0) return null;
4949+ return @intCast(mtime);
5050+}
5151+5252+pub const GetChildFilesError = error{
5353+ XapianException,
5454+};
5555+5656+/// Get a `FilenamesIterator` listing all the filenames of messages in the
5757+/// database within the given directory.
5858+///
5959+/// The returned filenames will be the basename-entries only (not complete
6060+/// paths).
6161+pub fn getChildFiles(self: *const Directory) GetChildFilesError!FilenamesIterator {
6262+ return .{
6363+ .filenames = c.notmuch_directory_get_child_files(self.directory) orelse return error.XapianException,
6464+ };
6565+}
6666+6767+pub const GetChildDirectoriesError = error{
6868+ XapianException,
6969+};
7070+7171+/// Get a `FilenamesIterator` listing all the filenames of sub-directories in
7272+/// the database within the given directory.
7373+///
7474+/// The returned filenames will be the basename-entries only (not complete
7575+/// paths).
7676+pub fn getChildDirectories(self: *const Directory) GetChildDirectoriesError!FilenamesIterator {
7777+ return .{
7878+ .filenames = c.notmuch_directory_get_child_files(self.directory) orelse return error.XapianException,
7979+ };
8080+}
8181+8282+/// Delete directory document from the database, and deinitialize the
8383+/// `Directory` object. Assumes any child directories and files have been
8484+/// deleted by the caller.
8585+pub fn delete(self: *const Directory) Error!void {
8686+ try wrap(c.notmuch_directory_delete(self.directory));
8787+}
8888+8989+/// Deinitialize a `Directory` object.
9090+pub fn deinit(self: *const Directory) void {
9191+ c.notmuch_directory_destroy(self.directory);
9292+}
+17-30
src/FilenamesIterator.zig
···55//! lists of filenames.
66pub const FilenamesIterator = @This();
7788+const std = @import("std");
89const c = @import("c");
99-1010-const status = @import("enums.zig").status;
11101211filenames: ?*c.notmuch_filenames_t,
13121414-pub const NextError = error{
1515- /// Iteration failed to allocate memory.
1616- OutOfMemory,
1717- /// Iteration was invalidated by the database. Re-open the database and
1818- /// try again.
1919- OperationInvalidated,
2020-};
2121-2222-pub fn next(self: *FilenamesIterator) NextError!?[:0]const u8 {
2323- const threads = self.threads orelse return null;
2424- return switch (status(c.notmuch_threads_status(threads))) {
2525- .success => thread: {
2626- if (c.notmuch_threads_valid(threads) != 0) break :thread null;
2727- defer c.notmuch_threads_move_to_next(threads);
2828- break :thread .{
2929- .thread = c.notmuch_threads_get(threads) orelse unreachable,
3030- };
3131- },
3232- .iterator_exhausted => return null,
3333- .operation_invalidated => error.OperationInvalidated,
3434- .out_of_memory => error.OutOfMemory,
3535- else => unreachable,
1313+/// Get the next filename from `FilenamesIterator` as a string.
1414+///
1515+/// Note: The returned string belongs to `filenames` and has a lifetime
1616+/// identical to it (and the object to which it ultimately belongs).
1717+pub fn next(self: *FilenamesIterator) ?[:0]const u8 {
1818+ const filenames = self.filenames orelse return null;
1919+ if (c.notmuch_threads_valid(filenames) != 0) return null;
2020+ defer c.notmuch_threads_move_to_next(filenames);
2121+ return .{
2222+ .thread = std.mem.span(c.notmuch_threads_get(filenames) orelse unreachable),
3623 };
3724}
38253939-/// Deinitialize a `ThreadIterator` object.
2626+/// Deinitialize a `FilenamesIterator` object.
4027///
4141-/// It's not strictly necessary to call this function. All memory from
4242-/// the `ThreadIterator` object will be reclaimed when the
4343-/// containing query object is deinitialized.
2828+/// It's not strictly necessary to call this function. All memory from the
2929+/// `FilenamesIterator` object will be reclaimed when the containing object
3030+/// is deinitialized.
4431pub fn deinit(self: *FilenamesIterator) void {
4545- const threads = self.threads orelse return;
4646- c.notmuch_threads_destroy(threads);
3232+ const filenames = self.filenames orelse return;
3333+ c.notmuch_filenames_destroy(filenames);
4734}
+14-4
src/IndexOpts.zig
···14141515indexopts: *c.notmuch_indexopts_t,
16161717-pub fn getDecryptPolicy(self: IndexOpts) Decrypt {
1818- return @enumFromInt(c.notmuch_indexopts_get_decrypt_policy(self.indexopts));
1919-}
2020-1717+/// Specify whether to decrypt encrypted parts while indexing.
1818+///
1919+/// Be aware that the index is likely sufficient to reconstruct the cleartext
2020+/// of the message itself, so please ensure that the notmuch message index is
2121+/// adequately protected. DO NOT SET THIS FLAG TO TRUE without considering the
2222+/// security of your index.
2123pub fn setDecryptPolicy(self: IndexOpts, decrypt_policy: Decrypt) Error!void {
2224 try wrap(c.notmuch_indexopts_set_decrypt_policy(self.indexopts, @intFromEnum(decrypt_policy)));
2325}
24262727+/// Return whether to decrypt encrypted parts while indexing.
2828+///
2929+/// See `setDecryptPolicy`.
3030+pub fn getDecryptPolicy(self: IndexOpts) Decrypt {
3131+ return @enumFromInt(c.notmuch_indexopts_get_decrypt_policy(self.indexopts));
3232+}
3333+3434+/// Deinitialize the `IndexOpts` object.
2535pub fn deinit(self: IndexOpts) void {
2636 c.notmuch_indexopts_destroy(self.indexopts);
2737}
+283-140
src/Message.zig
···1212const Error = @import("error.zig").Error;
1313const FilenamesIterator = @import("FilenamesIterator.zig");
1414const IndexOpts = @import("IndexOpts.zig");
1515+const MaildirFlag = @import("enums.zig").MaildirFlag;
1516const MessageFlag = @import("enums.zig").MessageFlag;
1617const MessagesIterator = @import("MessagesIterator.zig");
1818+const PropertiesIterator = @import("PropertiesIterator.zig");
1719const status = @import("enums.zig").status;
1820const TagsIterator = @import("TagsIterator.zig");
1921const wrap = @import("error.zig").wrap;
···160162 };
161163}
162164163163-/// Add a tag to the given message.
164164-pub fn addTag(self: *const Message, tag: [:0]const u8) Error!void {
165165- try wrap(c.notmuch_message_add_tag(self.message, tag));
165165+/// Set a value of a flag for the email corresponding to `Message`.
166166+pub fn setFlag(self: *const Message, flag: MessageFlag, value: bool) void {
167167+ c.notmuch_message_set_flag(self.message, @intFromEnum(flag), @intFromBool(value));
166168}
167169168168-/// Remove a tag from the given message.
169169-pub fn removeTag(self: *const Message, tag: [:0]const u8) Error!void {
170170- try wrap(c.notmuch_message_add_tag(self.message, tag));
170170+/// Get the date of `Message` as the number of seconds since the Unix epoch
171171+/// (1970-01-01 00:00:00 UTC).
172172+///
173173+/// For the original textual representation of the `Date` header from the
174174+/// message call `getHeader` with a header value of "date".
175175+///
176176+/// Returns `null` in case of error.
177177+pub fn getDate(self: *const Message) ?i64 {
178178+ std.debug.assert(@typeInfo(c.time_t).int.bits <= @typeInfo(i64).int.bits);
179179+ const time = c.notmuch_message_get_date(self.message);
180180+ if (time == 0) return null;
181181+ return @intCast(time);
171182}
172183173173-/// Remove all tags from the given message.
184184+/// Get the value of the specified header from `Message` as a UTF-8 string.
174185///
175175-/// See freeze for an example showing how to safely replace tag values.
176176-pub fn removeAllTags(self: *const Message) Error!void {
177177- try wrap(c.notmuch_message_remove_all_tags(self.message));
186186+/// Common headers are stored in the database when the message is indexed and
187187+/// will be returned from the database. Other headers will be read from the
188188+/// actual message file.
189189+///
190190+/// The header name is case insensitive.
191191+///
192192+/// The returned string belongs to the message so should not be modified or
193193+/// freed by the caller (nor should it be referenced after the message is
194194+/// deinitialized).
195195+///
196196+/// Returns an empty string ("") if the message does not contain a header line
197197+/// matching 'header'. Returns `null` if any error occurs.
198198+pub fn getHeader(self: *const Message, header: [:0]const u8) ?[:0]const u8 {
199199+ return std.mem.span(c.notmuch_message_get_header(self.message, header) orelse return null);
178200}
179201180180-/// Get the tags for 'message', returning a TagsIterator object which can be
202202+/// Get the tags for `Message`, returning a `TagsIterator` object which can be
181203/// used to iterate over all tags.
182204///
183205/// The tags object is owned by the message and as such, will only be valid
184184-/// for as long as the message is valid, (which is until the query from
185185-/// which it derived is destroyed).
206206+/// for as long as the message is valid, which is until the query from which it
207207+/// derived is deinitialized.
186208pub fn getTags(self: *const Message) TagsIterator {
187209 return .{
188210 .tags = c.notmuch_message_get_tags(self.message),
189211 };
190212}
191213192192-/// Freeze the current state of 'message' within the database.
214214+pub const AddTagError = error{
215215+ /// The length of `tag` is too long (exceeds NOTMUCH_TAG_MAX).
216216+ TagTooLong,
217217+ /// Database was opened in read-only mode so message cannot be modified.
218218+ ReadOnlyDatabase,
219219+};
220220+221221+/// Add a tag to the given message.
222222+pub fn addTag(self: *const Message, tag: [:0]const u8) AddTagError!void {
223223+ return switch (status(c.notmuch_message_add_tag(self.message, tag))) {
224224+ .success => {},
225225+ .null_pointer => unreachable,
226226+ .tag_too_long => error.TagTooLong,
227227+ .read_only_database => error.ReadOnlyDatabase,
228228+ else => unreachable,
229229+ };
230230+}
231231+232232+pub const RemoveTagError = error{
233233+ /// The length of `tag` is too long (exceeds NOTMUCH_TAG_MAX).
234234+ TagTooLong,
235235+ /// Database was opened in read-only mode so message cannot be modified.
236236+ ReadOnlyDatabase,
237237+};
238238+239239+/// Remove a tag from the given message.
240240+pub fn removeTag(self: *const Message, tag: [:0]const u8) RemoveTagError!void {
241241+ return switch (status(c.notmuch_message_add_tag(self.message, tag))) {
242242+ .success => {},
243243+ .null_pointer => unreachable,
244244+ .tag_too_long => error.TagTooLong,
245245+ .read_only_database => error.ReadOnlyDatabase,
246246+ else => unreachable,
247247+ };
248248+}
249249+250250+pub const RemoveAllTagsError = error{
251251+ /// Database was opened in read-only mode so message cannot be modified.
252252+ ReadOnlyDatabase,
253253+ XapianException,
254254+};
255255+256256+/// Remove all tags from the given message.
193257///
194194-/// This means that changes to the message state, (via
195195-/// notmuch_message_add_tag, notmuch_message_remove_tag, and
196196-/// notmuch_message_remove_all_tags), will not be committed to the database
197197-/// until the message is thawed with notmuch_message_thaw.
258258+/// See `freeze` for an example showing how to safely replace tag values.
259259+pub fn removeAllTags(self: *const Message) RemoveAllTagsError!void {
260260+ return switch (status(c.notmuch_message_remove_all_tags(self.message))) {
261261+ .success => {},
262262+ .read_only_database => error.ReadOnlyDatabase,
263263+ .xapian_exception => error.XapianException,
264264+ else => unreachable,
265265+ };
266266+}
267267+268268+/// Add/remove tags according to maildir flags in the message filename(s).
198269///
199199-/// Multiple calls to freeze/thaw are valid and these calls will "stack".
200200-/// That is there must be as many calls to thaw as to freeze before a
201201-/// message is actually thawed.
270270+/// This function examines the filenames of `Message` for maildir flags, and
271271+/// adds or removes tags on `Message` as follows when these flags are present:
272272+///
273273+/// Flag Action if present
274274+/// ---- -----------------
275275+/// 'D' Adds the "draft" tag to the message
276276+/// 'F' Adds the "flagged" tag to the message
277277+/// 'P' Adds the "passed" tag to the message
278278+/// 'R' Adds the "replied" tag to the message
279279+/// 'S' Removes the "unread" tag from the message
280280+///
281281+/// For each flag that is not present, the opposite action (add/remove) is
282282+/// performed for the corresponding tags.
283283+///
284284+/// Flags are identified as trailing components of the filename after a
285285+/// sequence of ":2,".
286286+///
287287+/// If there are multiple filenames associated with this message, the flag
288288+/// is considered present if it appears in one or more filenames. (That is,
289289+/// the flags from the multiple filenames are combined with the logical OR
290290+/// operator.)
291291+///
292292+/// A client can ensure that notmuch database tags remain synchronized
293293+/// with maildir flags by calling this function after each call to
294294+/// `Database.indexFile`. See also `tagsToMaildirFlags` for synchronizing tag
295295+/// changes back to maildir flags.
296296+pub fn maildirFlagsToTags(self: *const Message) Error!void {
297297+ try wrap(c.notmuch_message_maildir_flags_to_tags(self.message));
298298+}
299299+300300+pub const HasMaildirFlagError = error{
301301+ XapianException,
302302+};
303303+304304+/// Check message for maildir flag.
305305+pub fn hasMaildirFlag(self: *const Message, flag: MaildirFlag) HasMaildirFlagError!bool {
306306+ var is_set: c.notmuch_bool_t = undefined;
307307+ return switch (status(c.notmuch_message_has_maildir_flag_st(self.message, @intFromEnum(flag), &is_set))) {
308308+ .success => is_set != 0,
309309+ .null_pointer => unreachable,
310310+ .xapian_exception => error.XapianException,
311311+ else => unreachable,
312312+ };
313313+}
314314+315315+/// Rename message filename(s) to encode tags as maildir flags.
202316///
203203-/// The ability to do freeze/thaw allows for safe transactions to change tag
204204-/// values. For example, explicitly setting a message to have a given set of
317317+/// Specifically, for each filename corresponding to this message:
318318+///
319319+/// If the filename is not in a maildir directory, do nothing. (A maildir
320320+/// directory is determined as a directory named "new" or "cur".) Similarly, if
321321+/// the filename has invalid maildir info, (repeated or outof-ASCII-order flag
322322+/// characters after ":2,"), then do nothing.
323323+///
324324+/// If the filename is in a maildir directory, rename the file so that its
325325+/// filename ends with the sequence ":2," followed by zero or more of the
326326+/// following single-character flags (in ASCII order):
327327+///
328328+/// * flag 'D' iff the message has the "draft" tag
329329+/// * flag 'F' iff the message has the "flagged" tag
330330+/// * flag 'P' iff the message has the "passed" tag
331331+/// * flag 'R' iff the message has the "replied" tag
332332+/// * flag 'S' iff the message does not have the "unread" tag
333333+///
334334+/// Any existing flags unmentioned in the list above will be preserved in the
335335+/// renaming.
336336+///
337337+/// Also, if this filename is in a directory named "new", rename it to be within
338338+/// the neighboring directory named "cur".
339339+///
340340+/// A client can ensure that maildir filename flags remain synchronized
341341+/// with notmuch database tags by calling this function after changing tags
342342+/// (after calls to `addTag`, `removeTag`, or `freeze`/`thaw`). See also
343343+/// `maildirFlagsToTags` for synchronizing maildir flag changes back to tags.
344344+pub fn tagsToMaildirFlags(self: *const Message) Error!void {
345345+ try wrap(c.notmuch_message_tags_to_maildir_flags(self.message));
346346+}
347347+348348+/// Freeze the current state of `Message` within the database.
349349+///
350350+/// This means that changes to the message state, (via `addTag`, `removeTag`,
351351+/// and `removeAllTags`), will not be committed to the database until the
352352+/// message is thawed with `thaw`.
353353+///
354354+/// Multiple calls to `freeze`/`thaw` are valid and these calls will "stack".
355355+/// That is there must be as many calls to thaw as to freeze before a message is
356356+/// actually thawed.
357357+///
358358+/// The ability to do `freeze`/`thaw` allows for safe transactions to change
359359+/// tag values. For example, explicitly setting a message to have a given set of
205360/// tags might look like this:
206361///
207207-/// notmuch_message_freeze (message);
362362+/// try message.freeze();
363363+/// defer message.thaw() catch {};
208364///
209209-/// notmuch_message_remove_all_tags (message);
365365+/// message.removeAllTags();
210366///
211211-/// for (i = 0; i < NUM_TAGS; i++)
212212-/// notmuch_message_add_tag (message, tags[i]);
367367+/// for (tags) |tag|
368368+/// try message.addTag(tag);
213369///
214214-/// notmuch_message_thaw (message);
370370+/// With `freeze`/`thaw` used like this, the message in the database is
371371+/// guaranteed to have either the full set of original tag values, or the full
372372+/// set of new tag values, but nothing in between.
215373///
216216-/// With freeze/thaw used like this, the message in the database is
217217-/// guaranteed to have either the full set of original tag values, or the
218218-/// full set of new tag values, but nothing in between.
219219-///
220220-/// Imagine the example above without freeze/thaw and the operation somehow
374374+/// Imagine the example above without `freeze`/`thaw` and the operation somehow
221375/// getting interrupted. This could result in the message being left with no
222222-/// tags if the interruption happened after notmuch_message_remove_all_tags
223223-/// but before notmuch_message_add_tag. Get a value of a flag for the email
224224-/// corresponding to 'message'.
376376+/// tags if the interruption happened after `removeAllTags` but before `addTag`.
225377pub fn freeze(self: *const Message) Error!void {
226378 try wrap(c.notmuch_message_freeze(self.message));
227379}
228380229229-/// Thaw the current 'message', synchronizing any changes that may have
230230-/// occurred while 'message' was frozen into the notmuch database.
381381+/// Thaw the current `Message`, synchronizing any changes that may have
382382+/// occurred while `Message` was frozen into the `notmuch` database.
231383///
232232-/// See notmuch_message_freeze for an example of how to use this function to
384384+/// See `freeze` for an example of how to use this function to
233385/// safely provide tag changes.
234386///
235235-/// Multiple calls to freeze/thaw are valid and these calls with "stack".
236236-/// That is there must be as many calls to thaw as to freeze before a
387387+/// Multiple calls to `freeze`/`thaw` are valid and these calls with "stack".
388388+/// That is there must be as many calls to `thaw` as to `freeze` before a
237389/// message is actually thawed.
238390pub fn thaw(self: *const Message) Error!void {
239391 try wrap(c.notmuch_message_thaw(self.message));
240392}
241393242242-/// Set a value of a flag for the email corresponding to 'message'.
243243-pub fn setFlag(self: *const Message, flag: MessageFlag, value: bool) void {
244244- c.notmuch_message_set_flag(self.message, @intFromEnum(flag), @intFromBool(value));
245245-}
246246-247247-/// Get the date of 'message' as a nanosecond timestamp value.
394394+/// Deinitialize a `Message` object.
248395///
249249-/// For the original textual representation of the Date header from the
250250-/// message call getHeader() with a header value of
251251-/// "date".
252252-///
253253-/// Returns `null` in case of error.
254254-pub fn getDate(self: *const Message) ?i128 {
255255- const time = c.notmuch_message_get_date(self.message);
256256- if (time == 0) return null;
257257- return time * std.time.ns_per_s;
396396+/// It can be useful to call this function in the case of a single query
397397+/// object with many messages in the result, such as iterating over the entire
398398+/// database. Otherwise, it's fine to never call this function and there will
399399+/// still be no memory leaks. (The memory from the messages get reclaimed when
400400+/// the containing query is deinitialized.)
401401+pub fn deinit(self: *const Message) void {
402402+ c.notmuch_message_destroy(self.message);
258403}
259404260260-/// Get the value of the specified header from 'message' as a UTF-8 string.
261261-///
262262-/// Common headers are stored in the database when the message is indexed and
263263-/// will be returned from the database. Other headers will be read from the
264264-/// actual message file.
265265-///
266266-/// The header name is case insensitive.
267267-///
268268-/// The returned string belongs to the message so should not be modified or
269269-/// freed by the caller (nor should it be referenced after the message is
270270-/// destroyed).
405405+/// Retrieve the value for a single property key.
271406///
272272-/// Returns an empty string ("") if the message does not contain a header line
273273-/// matching 'header'. Returns NULL if any error occurs.
274274-pub fn getHeader(self: *const Message, header: [:0]const u8) ?[:0]const u8 {
275275- return std.mem.span(c.notmuch_message_get_header(self.message, header) orelse return null);
407407+/// Returns a string owned by the `Message` or `null` if there is no such
408408+/// key. In the case of multiple values for the given key, the first one is
409409+/// retrieved.
410410+pub fn getProperty(self: *const Message, key: [:0]const u8) ?[:0]const u8 {
411411+ var value: [*c]const u8 = null;
412412+ return switch (status(c.notmuch_message_get_property(self.message, key, &value))) {
413413+ .success => std.mem.span(value orelse return null),
414414+ .null_pointer => unreachable,
415415+ else => unreachable,
416416+ };
276417}
277418278278-/// Retrieve the value for a single property key
279279-///
280280-/// Returns a string owned by the message or NULL if there is no such
281281-/// key. In the case of multiple values for the given key, the first one
282282-/// is retrieved.
283283-pub fn getProperty(self: *const Message, key: [:0]const u8) Error!?[:0]const u8 {
284284- var value: [*c]const u8 = undefined;
285285- try wrap(c.notmuch_message_get_property(self.message, key, &value));
286286- return std.mem.span(value orelse return null);
287287-}
419419+pub const AddPropertyError = error{
420420+ /// `key` may not contain an '=' character.
421421+ IllegalArgument,
422422+};
288423289289-/// Get the properties for *message*, returning a PropertyIterator object
290290-/// which can be used to iterate over all properties.
291291-///
292292-/// The PropertyIterator object is owned by the message and as such, will
293293-/// only be valid for as long as the message is valid, (which is until the
294294-/// query from which it derived is destroyed).
295295-pub fn getProperties(
296296- /// the message to examine
297297- self: *const Message,
298298- /// key or key prefix
299299- key: [:0]const u8,
300300- /// if true, require exact match with key, otherwise treat as prefix
301301- exact: bool,
302302-) PropertyIterator {
303303- return .{
304304- .properties_ = c.notmuch_message_get_properties(self.message, key, @intFromBool(exact)),
424424+/// Add a (key,value) pair to a message.
425425+pub fn addProperty(self: *const Message, key: [:0]const u8, value: [:0]const u8) AddPropertyError!void {
426426+ return switch (status(c.notmuch_message_add_property(self.message, key, value))) {
427427+ .success => {},
428428+ .null_pointer => unreachable,
429429+ .illegal_argument => return error.IllegalArgument,
430430+ else => unreachable,
305431 };
306432}
307433308308-/// Add a (key,value) pair to a message.
309309-pub fn addProperty(self: *const Message, key: [:0]const u8, value: [:0]const u8) Error!void {
310310- try wrap(c.notmuch_message_add_property(self.message, key, value));
311311-}
434434+pub const RemovePropertyError = error{
435435+ /// `key` may not contain an '=' character.
436436+ IllegalArgument,
437437+};
312438313439/// Remove a (key,value) pair from a message.
314440///
315441/// It is not an error to remove a non-existent (key,value) pair
316316-pub fn removeProperty(self: *const Message, key: [:0]const u8, value: [:0]const u8) Error!void {
317317- try wrap(c.notmuch_message_remove_property(self.message, key, value));
442442+pub fn removeProperty(self: *const Message, key: [:0]const u8, value: [:0]const u8) RemovePropertyError!void {
443443+ return switch (status(c.notmuch_message_remove_property(self.message, key, value))) {
444444+ .success => {},
445445+ .null_pointer => unreachable,
446446+ .illegal_argument => error.IllegalArgument,
447447+ else => unreachable,
448448+ };
318449}
319450451451+pub const RemoveAllPropertiesError = error{
452452+ /// Database was opened in read-only mode so message cannot be modified.
453453+ ReadOnlyDatabase,
454454+};
455455+320456/// Remove all (key,value) pairs from the given message.
321457pub fn removeAllProperties(
322322- /// the message to operate on
458458+ /// The message to operate on.
323459 self: *const Message,
324324- /// key to delete properties for. If NULL, delete properties for all keys
460460+ /// key to delete properties for. If `null`, delete properties for all keys
325461 key: ?[:0]const u8,
326326-) Error!void {
327327- try wrap(c.notmuch_message_remove_all_properties(self.message, key orelse null));
462462+) RemoveAllPropertiesError!void {
463463+ return switch (status(c.notmuch_message_remove_all_properties(self.message, key orelse null))) {
464464+ .success => {},
465465+ .read_only_database => error.ReadOnlyDatabase,
466466+ else => unreachable,
467467+ };
328468}
329469330330-/// Return the number of properties named "key" belonging to the specific message.
331331-pub fn countProperties(self: *const Message, key: [:0]const u8) Error!usize {
332332- var count: c_uint = undefined;
333333- try wrap(c.notmuch_message_count_properties(self.message, key, &count));
334334- return @intCast(count);
335335-}
470470+pub const RemoveAllPropertiesWithPrefixError = error{
471471+ /// Database was opened in read-only mode so message cannot be modified.
472472+ ReadOnlyDatabase,
473473+};
336474337475/// Remove all (prefix*,value) pairs from the given message
338476pub fn removeAllPropertiesWithPrefix(
339339- /// message to operate on
477477+ /// The message to operate on.
340478 self: *const Message,
341341- /// delete properties with keys that start with prefix. If NULL, delete all properties
479479+ /// Delete properties with keys that start with prefix. If `null`, delete all properties.
342480 prefix: ?[:0]const u8,
343343-) Error!void {
344344- try wrap(c.notmuch_message_remove_all_properties_with_prefix(self.message, prefix orelse null));
481481+) RemoveAllPropertiesWithPrefixError!void {
482482+ return switch (status(c.notmuch_message_remove_all_properties_with_prefix(self.message, prefix orelse null))) {
483483+ .success => {},
484484+ .read_only_database => error.ReadOnlyDatabase,
485485+ else => unreachable,
486486+ };
345487}
346488347347-pub fn deinit(self: *const Message) void {
348348- c.notmuch_message_destroy(self.message);
489489+/// Get the properties for `Message`, returning a `PropertyIterator` object
490490+/// which can be used to iterate over all properties.
491491+///
492492+/// The `PropertyIterator` object is owned by the message and as such, will only
493493+/// be valid for as long as the message is valid, which is until the query from
494494+/// which it derived is deinitialized.
495495+pub fn getProperties(
496496+ /// The message to examine.
497497+ self: *const Message,
498498+ /// Key or key prefix.
499499+ key: [:0]const u8,
500500+ /// If `true`, require exact match with key, otherwise treat as prefix.
501501+ exact: bool,
502502+) PropertiesIterator {
503503+ return .{
504504+ .properties = c.notmuch_message_get_properties(self.message, key, @intFromBool(exact)),
505505+ };
349506}
350507351351-pub const PropertyIterator = struct {
352352- properties_: ?*c.notmuch_message_properties_t,
353353-354354- pub fn next(self: PropertyIterator) ?struct {
355355- key: [:0]const u8,
356356- value: [:0]const u8,
357357- } {
358358- const properties = self.properties_ orelse return null;
359359- if (c.notmuch_message_properties_valid(properties) == 0) return null;
360360- defer c.notmuch_message_properties_move_to_next(properties);
361361- return .{
362362- .key = std.mem.span(c.notmuch_message_properties_key(properties) orelse unreachable),
363363- .value = std.mem.span(c.notmuch_message_properties_value(properties) orelse unreachable),
364364- };
365365- }
366366-367367- pub fn deinit(self: PropertyIterator) void {
368368- const properties = self.properties_ orelse return;
369369- c.notmuch_message_properties_destroy(properties);
370370- }
371371-};
508508+/// Return the number of properties named "key" belonging to the specific message.
509509+pub fn countProperties(self: *const Message, key: [:0]const u8) Error!usize {
510510+ std.debug.assert(@typeInfo(c_uint).int.bits <= @typeInfo(usize).int.bits);
511511+ var count: c_uint = undefined;
512512+ try wrap(c.notmuch_message_count_properties(self.message, key, &count));
513513+ return @intCast(count);
514514+}
372515373516test {
374517 _ = std.testing.refAllDecls(@This());