diff --git a/TODO b/TODO index 8f9a1a98..baec040d 100644 --- a/TODO +++ b/TODO @@ -3,10 +3,3 @@ Write a "notmuch show" that displays a single thread. Fix to use the *last* Message-ID header if multiple such headers are encountered, (I noticed this is one thing that kept me from seeing the same message-ID values as sup). - -Fix "notmuch restore" to delete the old tags from a message/thread -before adding new ones. This will require someway to temporarily -'disconnect' a notmuch_message_t from the database, (that it, disable -automatic sync for add_tag, etc.), and then reconnect it. That is, the -removal and subsequent addition of tags to the message/thread needs to -be transactional. diff --git a/database.cc b/database.cc index ad91a7d7..9831d79c 100644 --- a/database.cc +++ b/database.cc @@ -166,6 +166,8 @@ notmuch_status_to_string (notmuch_status_t status) return "Erroneous NULL pointer"; case NOTMUCH_STATUS_TAG_TOO_LONG: return "Tag value is too long (exceeds NOTMUCH_TAG_MAX)"; + case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: + return "Unblanced number of calls to notmuch_message_freeze/thaw"; default: case NOTMUCH_STATUS_LAST_STATUS: return "Unknown error status value"; diff --git a/message.cc b/message.cc index 6b6141f3..6e15b511 100644 --- a/message.cc +++ b/message.cc @@ -26,6 +26,7 @@ struct _notmuch_message { notmuch_database_t *notmuch; Xapian::docid doc_id; + int frozen; char *message_id; char *thread_id; char *filename; @@ -33,7 +34,6 @@ struct _notmuch_message { Xapian::Document doc; }; - /* "128 bits of thread-id ought to be enough for anybody" */ #define NOTMUCH_THREAD_ID_BITS 128 #define NOTMUCH_THREAD_ID_DIGITS (NOTMUCH_THREAD_ID_BITS / 4) @@ -100,6 +100,8 @@ _notmuch_message_create (const void *talloc_owner, message->notmuch = notmuch; message->doc_id = doc_id; + message->frozen = 0; + /* Each of these will be lazily created as needed. */ message->message_id = NULL; message->thread_id = NULL; @@ -487,7 +489,8 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag) status); } - _notmuch_message_sync (message); + if (! message->frozen) + _notmuch_message_sync (message); return NOTMUCH_STATUS_SUCCESS; } @@ -509,11 +512,55 @@ notmuch_message_remove_tag (notmuch_message_t *message, const char *tag) status); } - _notmuch_message_sync (message); + if (! message->frozen) + _notmuch_message_sync (message); return NOTMUCH_STATUS_SUCCESS; } +void +notmuch_message_remove_all_tags (notmuch_message_t *message) +{ + notmuch_private_status_t status; + notmuch_tags_t *tags; + const char *tag; + + for (tags = notmuch_message_get_tags (message); + notmuch_tags_has_more (tags); + notmuch_tags_advance (tags)) + { + tag = notmuch_tags_get (tags); + + status = _notmuch_message_remove_term (message, "tag", tag); + if (status) { + INTERNAL_ERROR ("_notmuch_message_remove_term return unexpected value: %d\n", + status); + } + } + + if (! message->frozen) + _notmuch_message_sync (message); +} + +void +notmuch_message_freeze (notmuch_message_t *message) +{ + message->frozen++; +} + +notmuch_status_t +notmuch_message_thaw (notmuch_message_t *message) +{ + if (message->frozen > 0) { + message->frozen--; + if (message->frozen == 0) + _notmuch_message_sync (message); + return NOTMUCH_STATUS_SUCCESS; + } else { + return NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW; + } +} + void notmuch_message_destroy (notmuch_message_t *message) { diff --git a/notmuch.c b/notmuch.c index 3b0182a9..d716bce3 100644 --- a/notmuch.c +++ b/notmuch.c @@ -287,6 +287,7 @@ add_files_recursive (notmuch_database_t *notmuch, case NOTMUCH_STATUS_FILE_ERROR: case NOTMUCH_STATUS_NULL_POINTER: case NOTMUCH_STATUS_TAG_TOO_LONG: + case NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: case NOTMUCH_STATUS_LAST_STATUS: INTERNAL_ERROR ("add_message returned unexpected value: %d", status); goto DONE; @@ -854,6 +855,10 @@ restore_command (int argc, char *argv[]) goto NEXT_LINE; } + notmuch_message_freeze (message); + + notmuch_message_remove_all_tags (message); + next = tags; while (next) { tag = strsep (&next, " "); @@ -869,6 +874,7 @@ restore_command (int argc, char *argv[]) } } + notmuch_message_thaw (message); notmuch_message_destroy (message); } NEXT_LINE: diff --git a/notmuch.h b/notmuch.h index 5e7d1240..7e83d239 100644 --- a/notmuch.h +++ b/notmuch.h @@ -75,6 +75,11 @@ typedef int notmuch_bool_t; * NOTMUCH_STATUS_TAG_TOO_LONG: A tag value is too long (exceeds * NOTMUCH_TAG_MAX) * + * NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW: The notmuch_message_thaw + * function has been called more times than notmuch_message_freeze. + * + * And finally: + * * NOTMUCH_STATUS_LAST_STATUS: Not an actual status value. Just a way * to find out how many valid status values there are. */ @@ -87,6 +92,7 @@ typedef enum _notmuch_status { NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID, NOTMUCH_STATUS_NULL_POINTER, NOTMUCH_STATUS_TAG_TOO_LONG, + NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW, NOTMUCH_STATUS_LAST_STATUS } notmuch_status_t; @@ -650,8 +656,8 @@ notmuch_message_get_tags (notmuch_message_t *message); * * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL * - * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is longer than - * too long (exceeds NOTMUCH_TAG_MAX) + * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long + * (exceeds NOTMUCH_TAG_MAX) */ notmuch_status_t notmuch_message_add_tag (notmuch_message_t *message, const char *tag); @@ -660,16 +666,83 @@ notmuch_message_add_tag (notmuch_message_t *message, const char *tag); * * Return value: * - * NOTMUCH_STATUS_SUCCESS: Tag successfully added to message + * NOTMUCH_STATUS_SUCCESS: Tag successfully removed from message * * NOTMUCH_STATUS_NULL_POINTER: The 'tag' argument is NULL * - * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is longer than - * too long (exceeds NOTMUCH_TAG_MAX) + * NOTMUCH_STATUS_TAG_TOO_LONG: The length of 'tag' is too long + * (exceeds NOTMUCH_TAG_MAX) */ notmuch_status_t notmuch_message_remove_tag (notmuch_message_t *message, const char *tag); +/* Remove all tags from the given message. + * + * See notmuch_message_freeze for an example showing how to safely + * replace tag values. + */ +void +notmuch_message_remove_all_tags (notmuch_message_t *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 with + * "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 value, 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. + */ +void +notmuch_message_freeze (notmuch_message_t *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. + * + * Return value: + * + * NOTMUCH_STATUS_SUCCESS: Message successfully thawed, (or at least + * its frozen count has successfully been reduced by 1). + * + * NOTMUCH_STATUS_UNBALANCE_FREEZE_THAW: An attempt was made to thaw + * an unfrozen message. That is, there have been an unbalanced + * number of calls to notmuch_message_freeze and + * notmuch_message_thaw. + */ +notmuch_status_t +notmuch_message_thaw (notmuch_message_t *message); + /* Destroy a notmuch_message_t object. * * It can be useful to call this function in the case of a single