X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=lib%2Fdatabase.cc;h=dc967c8c8ae62b65e25168747468a68d46c14d0e;hp=7d09119cecddf54a94e34b8f2a9948ee203fbb85;hb=957ae198e708dc901e3a8c89b7364c2bc75ce371;hpb=6ca6c089e9df7affe6bee0392197509a24ab2546 diff --git a/lib/database.cc b/lib/database.cc index 7d09119c..dc967c8c 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -63,10 +63,11 @@ typedef struct { * * tag: Any tags associated with this message by the user. * - * direntry: A colon-separated pair of values (INTEGER:STRING), - * where INTEGER is the document ID of a directory - * document, and STRING is the name of a file within - * that directory for this mail message. + * file-direntry: A colon-separated pair of values + * (INTEGER:STRING), where INTEGER is the + * document ID of a directory document, and + * STRING is the name of a file within that + * directory for this mail message. * * A mail document also has two values: * @@ -88,22 +89,28 @@ typedef struct { * maintain data necessary to allow for efficient polling of mail * directories. * - * The directory document contains the following terms: + * All directory documents contain one term: * * directory: The directory path (relative to the database path) * Or the SHA1 sum of the directory path (if the * path itself is too long to fit in a Xapian * term). * - * parent: The document ID of the parent directory document. - * Top-level directories will have a parent value of 0. + * And all directory documents for directories other than top-level + * directories also contain the following term: * - * and has a single value: + * directory-direntry: A colon-separated pair of values + * (INTEGER:STRING), where INTEGER is the + * document ID of the parent directory + * document, and STRING is the name of this + * directory within that parent. + * + * All directory documents have a single value: * * TIMESTAMP: The mtime of the directory (at last scan) * * The data portion of a directory document contains the path of the - * directory (relative to the datbase path). + * directory (relative to the database path). */ /* With these prefix values we follow the conventions published here: @@ -122,25 +129,25 @@ typedef struct { */ prefix_t BOOLEAN_PREFIX_INTERNAL[] = { - { "type", "T" }, - { "reference", "XREFERENCE" }, - { "replyto", "XREPLYTO" }, - { "directory", "XDIRECTORY" }, - { "direntry", "XDIRENTRY" }, - { "parent", "XPARENT" }, + { "type", "T" }, + { "reference", "XREFERENCE" }, + { "replyto", "XREPLYTO" }, + { "directory", "XDIRECTORY" }, + { "file-direntry", "XFDIRENTRY" }, + { "directory-direntry", "XDDIRENTRY" }, }; prefix_t BOOLEAN_PREFIX_EXTERNAL[] = { - { "thread", "G" }, - { "tag", "K" }, - { "id", "Q" } + { "thread", "G" }, + { "tag", "K" }, + { "id", "Q" } }; prefix_t PROBABILISTIC_PREFIX[]= { - { "from", "XFROM" }, - { "to", "XTO" }, - { "attachment", "XATTACHMENT" }, - { "subject", "XSUBJECT"} + { "from", "XFROM" }, + { "to", "XTO" }, + { "attachment", "XATTACHMENT" }, + { "subject", "XSUBJECT"} }; int @@ -192,7 +199,7 @@ notmuch_status_to_string (notmuch_status_t status) case NOTMUCH_STATUS_OUT_OF_MEMORY: return "Out of memory"; case NOTMUCH_STATUS_READONLY_DATABASE: - return "The database is read-only"; + return "Attempt to write to a read-only database"; case NOTMUCH_STATUS_XAPIAN_EXCEPTION: return "A Xapian exception occurred"; case NOTMUCH_STATUS_FILE_ERROR: @@ -213,6 +220,17 @@ notmuch_status_to_string (notmuch_status_t status) } } +static void +find_doc_ids_for_term (notmuch_database_t *notmuch, + const char *term, + Xapian::PostingIterator *begin, + Xapian::PostingIterator *end) +{ + *begin = notmuch->xapian_db->postlist_begin (term); + + *end = notmuch->xapian_db->postlist_end (term); +} + static void find_doc_ids (notmuch_database_t *notmuch, const char *prefix_name, @@ -220,24 +238,21 @@ find_doc_ids (notmuch_database_t *notmuch, Xapian::PostingIterator *begin, Xapian::PostingIterator *end) { - Xapian::PostingIterator i; char *term; term = talloc_asprintf (notmuch, "%s%s", _find_prefix (prefix_name), value); - *begin = notmuch->xapian_db->postlist_begin (term); - - *end = notmuch->xapian_db->postlist_end (term); + find_doc_ids_for_term (notmuch, term, begin, end); talloc_free (term); } -static notmuch_private_status_t -find_unique_doc_id (notmuch_database_t *notmuch, - const char *prefix_name, - const char *value, - unsigned int *doc_id) +notmuch_private_status_t +_notmuch_database_find_unique_doc_id (notmuch_database_t *notmuch, + const char *prefix_name, + const char *value, + unsigned int *doc_id) { Xapian::PostingIterator i, end; @@ -246,10 +261,19 @@ find_unique_doc_id (notmuch_database_t *notmuch, if (i == end) { *doc_id = 0; return NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND; - } else { - *doc_id = *i; - return NOTMUCH_PRIVATE_STATUS_SUCCESS; } + + *doc_id = *i; + +#if DEBUG_DATABASE_SANITY + i++; + + if (i != end) + INTERNAL_ERROR ("Term %s:%s is not unique as expected.\n", + prefix_name, value); +#endif + + return NOTMUCH_PRIVATE_STATUS_SUCCESS; } static Xapian::Document @@ -258,26 +282,6 @@ find_document_for_doc_id (notmuch_database_t *notmuch, unsigned doc_id) return notmuch->xapian_db->get_document (doc_id); } -static notmuch_private_status_t -find_unique_document (notmuch_database_t *notmuch, - const char *prefix_name, - const char *value, - Xapian::Document *document, - unsigned int *doc_id) -{ - notmuch_private_status_t status; - - status = find_unique_doc_id (notmuch, prefix_name, value, doc_id); - - if (status) { - *document = Xapian::Document (); - return status; - } - - *document = find_document_for_doc_id (notmuch, *doc_id); - return NOTMUCH_PRIVATE_STATUS_SUCCESS; -} - notmuch_message_t * notmuch_database_find_message (notmuch_database_t *notmuch, const char *message_id) @@ -285,7 +289,8 @@ notmuch_database_find_message (notmuch_database_t *notmuch, notmuch_private_status_t status; unsigned int doc_id; - status = find_unique_doc_id (notmuch, "id", message_id, &doc_id); + status = _notmuch_database_find_unique_doc_id (notmuch, "id", + message_id, &doc_id); if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) return NULL; @@ -576,13 +581,6 @@ notmuch_database_get_path (notmuch_database_t *notmuch) return notmuch->path; } -static notmuch_private_status_t -find_directory_document (notmuch_database_t *notmuch, const char *db_path, - Xapian::Document *doc, unsigned int *doc_id) -{ - return find_unique_document (notmuch, "directory", db_path, doc, doc_id); -} - /* We allow the user to use arbitrarily long paths for directories. But * we have a term-length limit. So if we exceed that, we'll use the * SHA-1 of the path for the database term. @@ -591,8 +589,8 @@ find_directory_document (notmuch_database_t *notmuch, const char *db_path, * does not, then the caller is responsible to free() the returned * value. */ -static const char * -directory_db_path (const char *path) +const char * +_notmuch_database_get_directory_db_path (const char *path) { int term_len = strlen (_find_prefix ("directory")) + strlen (path); @@ -688,38 +686,25 @@ _notmuch_database_find_directory_id (notmuch_database_t *notmuch, const char *path, unsigned int *directory_id) { - notmuch_private_status_t private_status; - notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; - const char *db_path; + notmuch_directory_t *directory; + notmuch_status_t status; if (path == NULL) { *directory_id = 0; return NOTMUCH_STATUS_SUCCESS; } - db_path = directory_db_path (path); - - private_status = find_unique_doc_id (notmuch, "directory", - db_path, directory_id); - if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) { - status = notmuch_database_set_directory_mtime (notmuch, - path, 0); - if (status) - goto DONE; - - private_status = find_unique_doc_id (notmuch, "directory", - db_path, directory_id); - status = COERCE_STATUS (private_status, "_find_directory_id"); + directory = _notmuch_directory_create (notmuch, path, &status); + if (status) { + *directory_id = -1; + return status; } - DONE: - if (db_path != path) - free ((char *) db_path); + *directory_id = _notmuch_directory_get_document_id (directory); - if (status) - *directory_id = -1; + notmuch_directory_destroy (directory); - return status; + return NOTMUCH_STATUS_SUCCESS; } const char * @@ -734,6 +719,38 @@ _notmuch_database_get_directory_path (void *ctx, return talloc_strdup (ctx, document.get_data ().c_str ()); } +/* Given a legal 'filename' for the database, (either relative to + * database path or absolute with initial components identical to + * database path), return a new string (with 'ctx' as the talloc + * owner) suitable for use as a direntry term value. + */ +notmuch_status_t +_notmuch_database_filename_to_direntry (void *ctx, + notmuch_database_t *notmuch, + const char *filename, + char **direntry) +{ + const char *relative, *directory, *basename; + Xapian::docid directory_id; + notmuch_status_t status; + + relative = _notmuch_database_relative_path (notmuch, filename); + + status = _notmuch_database_split_path (ctx, relative, + &directory, &basename); + if (status) + return status; + + status = _notmuch_database_find_directory_id (notmuch, directory, + &directory_id); + if (status) + return status; + + *direntry = talloc_asprintf (ctx, "%u:%s", directory_id, basename); + + return NOTMUCH_STATUS_SUCCESS; +} + /* Given a legal 'path' for the database, return the relative path. * * The return value will be a pointer to the originl path contents, @@ -768,102 +785,13 @@ _notmuch_database_relative_path (notmuch_database_t *notmuch, return relative; } -notmuch_status_t -notmuch_database_set_directory_mtime (notmuch_database_t *notmuch, - const char *path, - time_t mtime) +notmuch_directory_t * +notmuch_database_get_directory (notmuch_database_t *notmuch, + const char *path) { - Xapian::Document doc; - Xapian::WritableDatabase *db; - unsigned int doc_id; - notmuch_private_status_t status; - notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; - const char *parent, *db_path = NULL; - unsigned int parent_id; - void *local = talloc_new (notmuch); - - if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) { - fprintf (stderr, "Attempted to update a read-only database.\n"); - return NOTMUCH_STATUS_READONLY_DATABASE; - } - - path = _notmuch_database_relative_path (notmuch, path); - - db = static_cast (notmuch->xapian_db); - db_path = directory_db_path (path); - - try { - status = find_directory_document (notmuch, db_path, &doc, &doc_id); - - doc.add_value (NOTMUCH_VALUE_TIMESTAMP, - Xapian::sortable_serialise (mtime)); - - if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) { - char *term = talloc_asprintf (local, "%s%s", - _find_prefix ("directory"), db_path); - doc.add_term (term); - - doc.set_data (path); - - _notmuch_database_split_path (local, path, &parent, NULL); - - _notmuch_database_find_directory_id (notmuch, parent, &parent_id); - - term = talloc_asprintf (local, "%s%u", - _find_prefix ("parent"), - parent_id); - doc.add_term (term); - - db->add_document (doc); - } else { - db->replace_document (doc_id, doc); - } - - } catch (const Xapian::Error &error) { - fprintf (stderr, "A Xapian exception occurred setting directory mtime: %s.\n", - error.get_msg().c_str()); - notmuch->exception_reported = TRUE; - ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION; - } - - if (db_path != path) - free ((char *) db_path); - - talloc_free (local); - - return ret; -} - -time_t -notmuch_database_get_directory_mtime (notmuch_database_t *notmuch, - const char *path) -{ - Xapian::Document doc; - unsigned int doc_id; - notmuch_private_status_t status; - const char *db_path = NULL; - time_t ret = 0; - void *local = talloc_new (notmuch); - - db_path = directory_db_path (path); - - try { - status = find_directory_document (notmuch, db_path, &doc, &doc_id); - - if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) - goto DONE; - - ret = Xapian::sortable_unserialise (doc.get_value (NOTMUCH_VALUE_TIMESTAMP)); - } catch (Xapian::Error &error) { - ret = 0; - goto DONE; - } - - DONE: - if (db_path != path) - free ((char *) db_path); + notmuch_status_t status; - return ret; + return _notmuch_directory_create (notmuch, path, &status); } /* Find the thread ID to which the message with 'message_id' belongs. @@ -1188,23 +1116,24 @@ notmuch_database_add_message (notmuch_database_t *notmuch, goto DONE; } + _notmuch_message_add_filename (message, filename); + /* Is this a newly created message object? */ if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) { - _notmuch_message_set_filename (message, filename); _notmuch_message_add_term (message, "type", "mail"); - } else { - ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID; - goto DONE; - } - ret = _notmuch_database_link_message (notmuch, message, message_file); - if (ret) - goto DONE; + ret = _notmuch_database_link_message (notmuch, message, + message_file); + if (ret) + goto DONE; - date = notmuch_message_file_get_header (message_file, "date"); - _notmuch_message_set_date (message, date); + date = notmuch_message_file_get_header (message_file, "date"); + _notmuch_message_set_date (message, date); - _notmuch_message_index_file (message, filename); + _notmuch_message_index_file (message, filename); + } else { + ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID; + } _notmuch_message_sync (message); } catch (const Xapian::Error &error) { @@ -1229,6 +1158,61 @@ notmuch_database_add_message (notmuch_database_t *notmuch, return ret; } +notmuch_status_t +notmuch_database_remove_message (notmuch_database_t *notmuch, + const char *filename) +{ + Xapian::WritableDatabase *db; + void *local = talloc_new (notmuch); + const char *prefix = _find_prefix ("file-direntry"); + char *direntry, *term; + Xapian::PostingIterator i, end; + Xapian::Document document; + notmuch_status_t status; + + if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) { + fprintf (stderr, "Attempted to update a read-only database.\n"); + return NOTMUCH_STATUS_READONLY_DATABASE; + } + + db = static_cast (notmuch->xapian_db); + + status = _notmuch_database_filename_to_direntry (local, notmuch, + filename, &direntry); + if (status) + return status; + + term = talloc_asprintf (notmuch, "%s%s", prefix, direntry); + + find_doc_ids_for_term (notmuch, term, &i, &end); + + for ( ; i != end; i++) { + Xapian::TermIterator j; + + document = find_document_for_doc_id (notmuch, *i); + + document.remove_term (term); + + j = document.termlist_begin (); + j.skip_to (prefix); + + /* Was this the last file-direntry in the message? */ + if (j == document.termlist_end () || + strncmp ((*j).c_str (), prefix, strlen (prefix))) + { + db->delete_document (document.get_docid ()); + status = NOTMUCH_STATUS_SUCCESS; + } else { + db->replace_document (document.get_docid (), document); + status = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID; + } + } + + talloc_free (local); + + return status; +} + notmuch_tags_t * _notmuch_convert_tags (void *ctx, Xapian::TermIterator &i, Xapian::TermIterator &end)