X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=lib%2Fdatabase.cc;h=5d300732d4a635a369b569ddbe452f39bff73f59;hp=7efd3b4ae3c1dea17833de6e1dba9f8cc9cbf536;hb=d7e5f5827e21be7dd8993e5a877bdb73cdb64325;hpb=50ae83a17feb1fc2f48fb8e51ef73da08ae4e2f2 diff --git a/lib/database.cc b/lib/database.cc index 7efd3b4a..5d300732 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -63,6 +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. + * * A mail document also has two values: * * TIMESTAMP: The time_t value corresponding to the message's @@ -75,19 +80,30 @@ typedef struct { * user in searching. But the database doesn't really care itself * about any of these. * + * The data portion of a mail document is empty. + * * Directory document * ------------------ * A directory document is used by a client of the notmuch library to * maintain data necessary to allow for efficient polling of mail * directories. * - * The directory document is indexed with a single prefixed term: + * The directory document contains the following terms: * - * directory: The directory path (an absolute path) + * 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 has 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). */ /* With these prefix values we follow the conventions published here: @@ -109,8 +125,9 @@ prefix_t BOOLEAN_PREFIX_INTERNAL[] = { { "type", "T" }, { "reference", "XREFERENCE" }, { "replyto", "XREPLYTO" }, - /* XXX: Need a flag day to rename XTIMESTAMP. */ - { "directory", "XTIMESTAMP" }, + { "directory", "XDIRECTORY" }, + { "direntry", "XDIRENTRY" }, + { "parent", "XPARENT" }, }; prefix_t BOOLEAN_PREFIX_EXTERNAL[] = { @@ -229,10 +246,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 @@ -585,6 +611,170 @@ directory_db_path (const char *path) return path; } +/* Given a path, split it into two parts: the directory part is all + * components except for the last, and the basename is that last + * component. Getting the return-value for either part is optional + * (the caller can pass NULL). + * + * The original 'path' can represent either a regular file or a + * directory---the splitting will be carried out in the same way in + * either case. Trailing slashes on 'path' will be ignored, and any + * cases of multiple '/' characters appearing in series will be + * treated as a single '/'. + * + * Allocation (if any) will have 'ctx' as the talloc owner. But + * pointers will be returned within the original path string whenever + * possible. + * + * Note: If 'path' is non-empty and contains no non-trailing slash, + * (that is, consists of a filename with no parent directory), then + * the directory returned will be an empty string. However, if 'path' + * is an empty string, then both directory and basename will be + * returned as NULL. + */ +notmuch_status_t +_notmuch_database_split_path (void *ctx, + const char *path, + const char **directory, + const char **basename) +{ + const char *slash; + + if (path == NULL || *path == '\0') { + if (directory) + *directory = NULL; + if (basename) + *basename = NULL; + return NOTMUCH_STATUS_SUCCESS; + } + + /* Find the last slash (not counting a trailing slash), if any. */ + + slash = path + strlen (path) - 1; + + /* First, skip trailing slashes. */ + while (slash != path) { + if (*slash != '/') + break; + + --slash; + } + + /* Then, find a slash. */ + while (slash != path) { + if (*slash == '/') + break; + + if (basename) + *basename = slash; + + --slash; + } + + /* Finally, skip multiple slashes. */ + while (slash != path) { + if (*slash != '/') + break; + + --slash; + } + + if (slash == path) { + if (directory) + *directory = talloc_strdup (ctx, ""); + if (basename) + *basename = path; + } else { + if (directory) + *directory = talloc_strndup (ctx, path, slash - path + 1); + } + + return NOTMUCH_STATUS_SUCCESS; +} + +notmuch_status_t +_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; + + 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"); + } + + DONE: + if (db_path != path) + free ((char *) db_path); + + if (status) + *directory_id = -1; + + return status; +} + +const char * +_notmuch_database_get_directory_path (void *ctx, + notmuch_database_t *notmuch, + unsigned int doc_id) +{ + Xapian::Document document; + + document = find_document_for_doc_id (notmuch, doc_id); + + 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, @@ -629,13 +819,17 @@ notmuch_database_set_directory_mtime (notmuch_database_t *notmuch, unsigned int doc_id; notmuch_private_status_t status; notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS; - const char *db_path = NULL; + 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); @@ -646,10 +840,20 @@ notmuch_database_set_directory_mtime (notmuch_database_t *notmuch, Xapian::sortable_serialise (mtime)); if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) { - char *term = talloc_asprintf (NULL, "%s%s", + char *term = talloc_asprintf (local, "%s%s", _find_prefix ("directory"), db_path); doc.add_term (term); - talloc_free (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 { @@ -666,6 +870,8 @@ notmuch_database_set_directory_mtime (notmuch_database_t *notmuch, if (db_path != path) free ((char *) db_path); + talloc_free (local); + return ret; } @@ -1022,23 +1228,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) {