expand covered apis
This commit is contained in:
parent
56d4e7a62d
commit
8d239f9651
1 changed files with 342 additions and 35 deletions
377
src/notmuch.zig
377
src/notmuch.zig
|
@ -1,13 +1,14 @@
|
|||
const std = @import("std");
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("stdlib.h");
|
||||
@cInclude("notmuch.h");
|
||||
});
|
||||
|
||||
const log = std.log.scoped(.notmuch);
|
||||
|
||||
fn generateEnum(comptime prefix: []const u8) type {
|
||||
@setEvalBranchQuota(10000);
|
||||
@setEvalBranchQuota(16000);
|
||||
const info = @typeInfo(c);
|
||||
var count: usize = 0;
|
||||
for (info.@"struct".decls) |d| {
|
||||
|
@ -30,16 +31,22 @@ fn generateEnum(comptime prefix: []const u8) type {
|
|||
index += 1;
|
||||
}
|
||||
}
|
||||
return @Type(.{ .@"enum" = .{
|
||||
.tag_type = std.math.IntFittingRange(0, max),
|
||||
.fields = &fields,
|
||||
.decls = &.{},
|
||||
.is_exhaustive = true,
|
||||
} });
|
||||
return @Type(
|
||||
.{
|
||||
.@"enum" = .{
|
||||
.tag_type = std.math.IntFittingRange(0, max),
|
||||
.fields = &fields,
|
||||
.decls = &.{},
|
||||
.is_exhaustive = true,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub const STATUS = generateEnum("NOTMUCH_STATUS_");
|
||||
pub const DATABASE_MODE = generateEnum("NOTMUCH_DATABASE_MODE_");
|
||||
pub const DECRYPT = generateEnum("NOTMUCH_DECRYPT_");
|
||||
pub const MESSAGE_FLAG = generateEnum("NOTMUCH_MESSAGE_FLAG_");
|
||||
pub const STATUS = generateEnum("NOTMUCH_STATUS_");
|
||||
|
||||
const Error = error{
|
||||
BadQuerySyntax,
|
||||
|
@ -69,9 +76,17 @@ const Error = error{
|
|||
XapianException,
|
||||
};
|
||||
|
||||
fn statusToError(comptime T: type, rc: c.notmuch_status_t, value: T) Error!T {
|
||||
fn wrapMessage(rc: c.notmuch_status_t, message: [*c]const u8) Error!void {
|
||||
if (message) |msg| {
|
||||
log.err("{s}", .{msg});
|
||||
c.free(@ptrCast(@constCast(msg)));
|
||||
}
|
||||
try wrap(rc);
|
||||
}
|
||||
|
||||
fn wrap(rc: c.notmuch_status_t) Error!void {
|
||||
return switch (@as(STATUS, @enumFromInt(rc))) {
|
||||
.SUCCESS => value,
|
||||
.SUCCESS => {},
|
||||
.BAD_QUERY_SYNTAX => error.BadQuerySyntax,
|
||||
.CLOSED_DATABASE => error.ClosedDatabase,
|
||||
.DATABASE_EXISTS => error.DatabaseExists,
|
||||
|
@ -100,9 +115,9 @@ fn statusToError(comptime T: type, rc: c.notmuch_status_t, value: T) Error!T {
|
|||
}
|
||||
|
||||
pub const Database = struct {
|
||||
database: ?*c.notmuch_database_t = null,
|
||||
database: *c.notmuch_database_t,
|
||||
|
||||
pub fn open_with_config(
|
||||
pub fn open(
|
||||
database_path: ?[*:0]const u8,
|
||||
mode: DATABASE_MODE,
|
||||
config_path: ?[:0]const u8,
|
||||
|
@ -113,55 +128,295 @@ pub const Database = struct {
|
|||
return error.NotmuchVersion;
|
||||
}
|
||||
|
||||
var error_message: [*c]u8 = null;
|
||||
var database: ?*c.notmuch_database_t = null;
|
||||
const rc = c.notmuch_database_open_with_config(
|
||||
if (database_path) |p| p else null,
|
||||
try wrapMessage(c.notmuch_database_open_with_config(
|
||||
database_path orelse null,
|
||||
@intFromEnum(mode),
|
||||
if (config_path) |p| p else null,
|
||||
if (profile) |p| p else null,
|
||||
config_path orelse null,
|
||||
profile orelse null,
|
||||
&database,
|
||||
null,
|
||||
&error_message,
|
||||
), error_message);
|
||||
return .{
|
||||
.database = database orelse unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
database_path: ?[*:0]const u8,
|
||||
config_path: ?[:0]const u8,
|
||||
profile: ?[:0]const u8,
|
||||
) Error!Database {
|
||||
if (!c.LIBNOTMUCH_CHECK_VERSION(5, 6, 0)) {
|
||||
log.err("need newer notmuch", .{});
|
||||
return error.NotmuchVersion;
|
||||
}
|
||||
|
||||
var error_message: [*c]u8 = null;
|
||||
var database: ?*c.notmuch_database_t = null;
|
||||
try wrapMessage(
|
||||
c.notmuch_database_create_with_config(
|
||||
database_path orelse null,
|
||||
config_path orelse null,
|
||||
profile orelse null,
|
||||
&database,
|
||||
&error_message,
|
||||
),
|
||||
error_message,
|
||||
);
|
||||
return try statusToError(Database, rc, .{ .database = database });
|
||||
return .{
|
||||
.database = database orelse unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(self: *const Database) void {
|
||||
_ = c.notmuch_database_close(self.database);
|
||||
}
|
||||
|
||||
pub fn index_file(self: *const Database, filename: [:0]const u8, indexopts: ?*c.notmuch_indexopts_t) Error!void {
|
||||
const rc = c.notmuch_database_index_file(self.database, filename, indexopts, null);
|
||||
return try statusToError(void, rc, {});
|
||||
pub fn indexFile(self: *const Database, filename: [:0]const u8, indexopts: ?IndexOpts) Error!void {
|
||||
try wrap(c.notmuch_database_index_file(
|
||||
self.database,
|
||||
filename,
|
||||
if (indexopts) |i| i.indexopts else null,
|
||||
null,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn index_file_get_message(self: *const Database, filename: [:0]const u8, indexopts: ?*c.notmuch_indexopts_t) Error!Message {
|
||||
pub fn indexFileGetMessage(self: *const Database, filename: [:0]const u8, indexopts: ?IndexOpts) Error!Message {
|
||||
var message: ?*c.notmuch_message_t = null;
|
||||
const rc = c.notmuch_database_index_file(self.database, filename, indexopts, &message);
|
||||
return statusToError(Message, rc, .{ .duplicate = false, .message = message }) catch |err| switch (err) {
|
||||
error.DuplicateMessageID => return .{ .duplicate = true, .message = message },
|
||||
wrap(c.notmuch_database_index_file(
|
||||
self.database,
|
||||
filename,
|
||||
if (indexopts) |i| i.indexopts else null,
|
||||
&message,
|
||||
)) catch |err| switch (err) {
|
||||
error.DuplicateMessageID => return .{
|
||||
.duplicate = true,
|
||||
.message = message orelse unreachable,
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
return .{
|
||||
.duplicate = false,
|
||||
.message = message orelse unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn find_message_by_filename(self: *const Database, filename: [:0]const u8) Error!Message {
|
||||
pub fn findMessageByFilename(self: *const Database, filename: [:0]const u8) Error!Message {
|
||||
var message: ?*c.notmuch_message_t = null;
|
||||
const rc = c.notmuch_database_find_message_by_filename(self.database, filename, &message);
|
||||
return try statusToError(Message, rc, .{ .message = message });
|
||||
try wrap(c.notmuch_database_find_message_by_filename(self.database, filename, &message));
|
||||
return .{
|
||||
.message = message orelse unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn remove_message(self: *const Database, filename: [:0]const u8) Error!void {
|
||||
const rc = c.notmuch_database_remove_message(self.database, filename);
|
||||
return try statusToError(void, rc, {});
|
||||
pub fn removeMessage(self: *const Database, filename: [:0]const u8) Error!void {
|
||||
try wrap(c.notmuch_database_remove_message(self.database, filename));
|
||||
}
|
||||
|
||||
pub fn getDefaultIndexOpts(self: *const Database) ?IndexOpts {
|
||||
return .{
|
||||
.indexopts = c.notmuch_database_get_default_indexopts(self.database) orelse return null,
|
||||
};
|
||||
}
|
||||
|
||||
///
|
||||
pub fn getConfigPath(self: *const Database) ?[]const u8 {
|
||||
const config = c.notmuch_config_path(self.database);
|
||||
return std.mem.span(config orelse return null);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Message = struct {
|
||||
duplicate: ?bool = null,
|
||||
message: ?*c.notmuch_message_t = null,
|
||||
message: *c.notmuch_message_t,
|
||||
|
||||
pub fn add_tag(self: *const Message, tag: [:0]const u8) Error!void {
|
||||
const rc = c.notmuch_message_add_tag(self.message, tag);
|
||||
return try statusToError(void, rc, {});
|
||||
/// Get the message ID of 'message'.
|
||||
///
|
||||
/// The returned string belongs to 'message' and as such, should not be
|
||||
/// modified by the caller and will only be valid for as long as the message
|
||||
/// is valid, (which is until the query from which it derived is destroyed).
|
||||
///
|
||||
/// This function will return NULL if triggers an unhandled Xapian
|
||||
/// exception.
|
||||
pub fn getMessageID(self: *const Message) ?[]const u8 {
|
||||
return std.mem.span(c.notmuch_message_get_message_id(self.message) orelse return null);
|
||||
}
|
||||
|
||||
/// Get the thread ID of 'message'.
|
||||
///
|
||||
/// The returned string belongs to 'message' and as such, should not be
|
||||
/// modified by the caller and will only be valid for as long as the message
|
||||
/// is valid, (for example, until the user calls notmuch_message_destroy on
|
||||
/// 'message' or until a query from which it derived is destroyed).
|
||||
///
|
||||
/// This function will return NULL if triggers an unhandled Xapian
|
||||
/// exception.
|
||||
pub fn getThreadID(self: *const Message) ?[:0]const u8 {
|
||||
return std.mem.span(c.notmuch_message_get_thread_id(self.message) orelse return null);
|
||||
}
|
||||
|
||||
/// Add a tag to the given message.
|
||||
pub fn addTag(self: *const Message, tag: [:0]const u8) Error!void {
|
||||
try wrap(c.notmuch_message_add_tag(self.message, tag));
|
||||
}
|
||||
|
||||
/// Remove a tag from the given message.
|
||||
pub fn removeTag(self: *const Message, tag: [:0]const u8) Error!void {
|
||||
try wrap(c.notmuch_message_add_tag(self.message, tag));
|
||||
}
|
||||
|
||||
/// Remove all tags from the given message.
|
||||
///
|
||||
/// See freeze for an example showing how to safely replace tag values.
|
||||
pub fn removeAllTags(self: *const Message) Error!void {
|
||||
try wrap(c.notmuch_message_remove_all_tags(self.message));
|
||||
}
|
||||
|
||||
/// Freeze the current state of 'message' within the database.
|
||||
///
|
||||
/// This means that changes to the message state, (via
|
||||
/// notmuch_message_add_tag, notmuch_message_remove_tag, and
|
||||
/// notmuch_message_remove_all_tags), will not be committed to the database
|
||||
/// until the message is thawed with notmuch_message_thaw.
|
||||
///
|
||||
/// Multiple calls to freeze/thaw are valid and these calls will "stack".
|
||||
/// That is there must be as many calls to thaw as to freeze before a
|
||||
/// message is actually thawed.
|
||||
///
|
||||
/// The ability to do freeze/thaw allows for safe transactions to change tag
|
||||
/// values. For example, explicitly setting a message to have a given set of
|
||||
/// tags might look like this:
|
||||
///
|
||||
/// notmuch_message_freeze (message);
|
||||
///
|
||||
/// notmuch_message_remove_all_tags (message);
|
||||
///
|
||||
/// for (i = 0; i < NUM_TAGS; i++)
|
||||
/// notmuch_message_add_tag (message, tags[i]);
|
||||
///
|
||||
/// notmuch_message_thaw (message);
|
||||
///
|
||||
/// With freeze/thaw used like this, the message in the database is
|
||||
/// guaranteed to have either the full set of original tag values, or the
|
||||
/// full set of new tag values, but nothing in between.
|
||||
///
|
||||
/// Imagine the example above without freeze/thaw and the operation somehow
|
||||
/// getting interrupted. This could result in the message being left with no
|
||||
/// tags if the interruption happened after notmuch_message_remove_all_tags
|
||||
/// but before notmuch_message_add_tag. Get a value of a flag for the email
|
||||
/// corresponding to 'message'.
|
||||
pub fn freeze(self: *const Message) Error!void {
|
||||
try wrap(c.notmuch_message_freeze(self.message));
|
||||
}
|
||||
|
||||
/// Thaw the current 'message', synchronizing any changes that may have
|
||||
/// occurred while 'message' was frozen into the notmuch database.
|
||||
///
|
||||
/// See notmuch_message_freeze for an example of how to use this function to
|
||||
/// safely provide tag changes.
|
||||
///
|
||||
/// Multiple calls to freeze/thaw are valid and these calls with "stack".
|
||||
/// That is there must be as many calls to thaw as to freeze before a
|
||||
/// message is actually thawed.
|
||||
pub fn thaw(self: *const Message) Error!void {
|
||||
try wrap(c.notmuch_message_thaw(self.message));
|
||||
}
|
||||
|
||||
/// Get a value of a flag for the email corresponding to 'message'.
|
||||
pub fn getFlag(self: *const Message, flag: MESSAGE_FLAG) Error!bool {
|
||||
var is_set: c.notmuch_bool_t = undefined;
|
||||
try wrap(c.notmuch_message_get_flag_st(self.message, @intFromEnum(flag), &is_set));
|
||||
return is_set != 0;
|
||||
}
|
||||
|
||||
/// Set a value of a flag for the email corresponding to 'message'.
|
||||
pub fn setFlag(self: *const Message, flag: MESSAGE_FLAG, value: bool) void {
|
||||
c.notmuch_message_set_flag(self.message, @intFromEnum(flag), @intFromBool(value));
|
||||
}
|
||||
|
||||
/// Get the date of 'message' as a nanosecond timestamp value.
|
||||
///
|
||||
/// For the original textual representation of the Date header from the
|
||||
/// message call getHeader() with a header value of
|
||||
/// "date".
|
||||
///
|
||||
/// Returns `null` in case of error.
|
||||
pub fn getDate(self: *const Message) ?i128 {
|
||||
const time = c.notmuch_message_get_date(self.message);
|
||||
if (time == 0) return null;
|
||||
return time * std.time.ns_per_s;
|
||||
}
|
||||
|
||||
/// Get the value of the specified header from 'message' as a UTF-8 string.
|
||||
///
|
||||
/// Common headers are stored in the database when the message is indexed and
|
||||
/// will be returned from the database. Other headers will be read from the
|
||||
/// actual message file.
|
||||
///
|
||||
/// The header name is case insensitive.
|
||||
///
|
||||
/// The returned string belongs to the message so should not be modified or
|
||||
/// freed by the caller (nor should it be referenced after the message is
|
||||
/// destroyed).
|
||||
///
|
||||
/// Returns an empty string ("") if the message does not contain a header line
|
||||
/// matching 'header'. Returns NULL if any error occurs.
|
||||
pub fn getHeader(self: *const Message, header: [:0]const u8) ?[:0]const u8 {
|
||||
return std.mem.span(c.notmuch_message_get_header(self.message, header) orelse return null);
|
||||
}
|
||||
|
||||
/// Retrieve the value for a single property key
|
||||
///
|
||||
/// Returns a string owned by the message or NULL if there is no such
|
||||
/// key. In the case of multiple values for the given key, the first one
|
||||
/// is retrieved.
|
||||
pub fn getProperty(self: *const Message, key: [:0]const u8) Error!?[:0]const u8 {
|
||||
var value: [*c]const u8 = undefined;
|
||||
try wrap(c.notmuch_message_get_property(self.message, key, &value));
|
||||
return std.mem.span(value orelse return null);
|
||||
}
|
||||
|
||||
/// Get the properties for *message*, returning a PropertyIterator object
|
||||
/// which can be used to iterate over all properties.
|
||||
///
|
||||
/// The PropertyIterator object is owned by the message and as such, will
|
||||
/// only be valid for as long as the message is valid, (which is until the
|
||||
/// query from which it derived is destroyed).
|
||||
pub fn getProperties(
|
||||
/// the message to examine
|
||||
self: *const Message,
|
||||
/// key or key prefix
|
||||
key: [:0]const u8,
|
||||
/// if true, require exact match with key, otherwise treat as prefix
|
||||
exact: bool,
|
||||
) PropertyIterator {
|
||||
return .{
|
||||
.properties_ = c.notmuch_message_get_properties(self.message, key, @intFromBool(exact)),
|
||||
};
|
||||
}
|
||||
|
||||
/// Add a (key,value) pair to a message.
|
||||
pub fn addProperty(self: *const Message, key: [:0]const u8, value: [:0]const u8) Error!void {
|
||||
try wrap(c.notmuch_message_add_property(self.message, key, value));
|
||||
}
|
||||
|
||||
/// Remove a (key,value) pair from a message.
|
||||
///
|
||||
/// It is not an error to remove a non-existent (key,value) pair
|
||||
pub fn removeProperty(self: *const Message, key: [:0]const u8, value: [:0]const u8) Error!void {
|
||||
try wrap(c.notmuch_message_remove_property(self.message, key, value));
|
||||
}
|
||||
|
||||
/// Remove all (key,value) pairs from the given message.
|
||||
pub fn removeAllProperties(
|
||||
/// the message to operate on
|
||||
self: *const Message,
|
||||
/// key to delete properties for. If NULL, delete properties for all keys
|
||||
key: ?[:0]const u8,
|
||||
) Error!void {
|
||||
try wrap(c.notmuch_message_remove_all_properties(self.message, key orelse null));
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Message) void {
|
||||
|
@ -169,6 +424,58 @@ pub const Message = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub const IndexOpts = struct {
|
||||
indexopts: *c.notmuch_indexopts_t,
|
||||
|
||||
pub fn getDecryptPolicy(self: IndexOpts) DECRYPT {
|
||||
return @enumFromInt(c.notmuch_indexopts_get_decrypt_policy(self.indexopts));
|
||||
}
|
||||
|
||||
pub fn setDecryptPolicy(self: IndexOpts, decrypt_policy: DECRYPT) Error!void {
|
||||
try wrap(c.notmuch_indexopts_set_decrypt_policy(self.indexopts, @intFromEnum(decrypt_policy)));
|
||||
}
|
||||
|
||||
pub fn deinit(self: IndexOpts) void {
|
||||
c.notmuch_indexopts_destroy(self.indexopts);
|
||||
}
|
||||
};
|
||||
|
||||
pub const TagIterator = struct {
|
||||
tags: *c.notmuch_tags_t,
|
||||
|
||||
pub fn next(self: *TagIterator) ?[]const u8 {
|
||||
if (c.notmuch_tags_valid(self.tags) == 0) return null;
|
||||
defer c.notmuch_tags_move_to_next(self.tags);
|
||||
return std.mem.span(c.notmuch_tags_get(self.tags) orelse unreachable);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *TagIterator) void {
|
||||
c.notmuch_tags_destroy(self.tags);
|
||||
}
|
||||
};
|
||||
|
||||
pub const PropertyIterator = struct {
|
||||
properties_: ?*c.notmuch_message_properties_t,
|
||||
|
||||
pub fn next(self: PropertyIterator) ?struct {
|
||||
key: [:0]const u8,
|
||||
value: [:0]const u8,
|
||||
} {
|
||||
const properties = self.properties_ orelse return null;
|
||||
if (c.notmuch_message_properties_valid(properties) == 0) return null;
|
||||
defer c.notmuch_message_properties_move_to_next(properties);
|
||||
return .{
|
||||
.key = std.mem.span(c.notmuch_message_properties_key(properties) orelse unreachable),
|
||||
.value = std.mem.span(c.notmuch_message_properties_value(properties) orelse unreachable),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: PropertyIterator) void {
|
||||
const properties = self.properties_ orelse return;
|
||||
c.notmuch_message_properties_destroy(properties);
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
std.testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue