/* Return two stable query strings that identify exactly the matched * and unmatched messages currently in thread. If there are no * matched or unmatched messages, the returned buffers will be * NULL. */ static int get_thread_query (notmuch_thread_t *thread, char **matched_out, char **unmatched_out) { notmuch_messages_t *messages; char *escaped = NULL; size_t escaped_len = 0; *matched_out = *unmatched_out = NULL; for (messages = notmuch_thread_get_messages (thread); notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { notmuch_message_t *message = notmuch_messages_get (messages); const char *mid = notmuch_message_get_message_id (message); /* Determine which query buffer to extend */ char **buf = notmuch_message_get_flag ( message, NOTMUCH_MESSAGE_FLAG_MATCH) ? matched_out : unmatched_out; /* Add this message's id: query. Since "id" is an exclusive * prefix, it is implicitly 'or'd together, so we only need to * join queries with a space. */ if (make_boolean_term (thread, "id", mid, &escaped, &escaped_len) < 0) return -1; if (*buf) *buf = talloc_asprintf_append_buffer (*buf, " %s", escaped); else *buf = talloc_strdup (thread, escaped); if (!*buf) return -1; } talloc_free (escaped); return 0; }
static int init_header(HEADER *h, const char *path, notmuch_message_t *msg) { const char *id; if (h->data) return 0; id = notmuch_message_get_message_id(msg); h->data = safe_calloc(1, sizeof(struct nm_hdrdata)); h->free_cb = deinit_header; /* * Notmuch ensures that message Id exists (if not notmuch Notmuch will * generate an ID), so it's more safe than use mutt HEADER->env->id */ ((struct nm_hdrdata *) h->data)->virtual_id = safe_strdup( id ); dprint(2, (debugfile, "nm: initialize header data: [hdr=%p, data=%p] (%s)\n", h, h->data, id)); if (!h->env->message_id) h->env->message_id = nm2mutt_message_id( id ); if (update_message_path(h, path)) return -1; update_header_tags(h, msg); return 0; }
static void format_message_json (const void *ctx, notmuch_message_t *message) { notmuch_tags_t *tags; int first = 1; void *ctx_quote = talloc_new (ctx); time_t date; const char *relative_date; date = notmuch_message_get_date (message); relative_date = notmuch_time_relative_date (ctx, date); printf ("\"id\": %s, \"match\": %s, \"excluded\": %s, \"filename\": %s, \"timestamp\": %ld, \"date_relative\": \"%s\", \"tags\": [", json_quote_str (ctx_quote, notmuch_message_get_message_id (message)), notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false", notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? "true" : "false", json_quote_str (ctx_quote, notmuch_message_get_filename (message)), date, relative_date); for (tags = notmuch_message_get_tags (message); notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) { printf("%s%s", first ? "" : ",", json_quote_str (ctx_quote, notmuch_tags_get (tags))); first = 0; } printf("], "); talloc_free (ctx_quote); }
/* This format is currently tuned for a git send-email --notmuch hook */ static int notmuch_reply_format_headers_only(void *ctx, notmuch_config_t *config, notmuch_query_t *query, unused (notmuch_show_params_t *params), notmuch_bool_t reply_all, unused (sprinter_t *sp)) { GMimeMessage *reply; notmuch_messages_t *messages; notmuch_message_t *message; const char *in_reply_to, *orig_references, *references; char *reply_headers; for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { message = notmuch_messages_get (messages); /* The 0 means we do not want headers in a "pretty" order. */ reply = g_mime_message_new (0); if (reply == NULL) { fprintf (stderr, "Out of memory\n"); return 1; } in_reply_to = talloc_asprintf (ctx, "<%s>", notmuch_message_get_message_id (message)); g_mime_object_set_header (GMIME_OBJECT (reply), "In-Reply-To", in_reply_to); orig_references = notmuch_message_get_header (message, "references"); /* We print In-Reply-To followed by References because git format-patch treats them * specially. Git does not interpret the other headers specially */ references = talloc_asprintf (ctx, "%s%s%s", orig_references ? orig_references : "", orig_references ? " " : "", in_reply_to); g_mime_object_set_header (GMIME_OBJECT (reply), "References", references); (void)add_recipients_from_message (reply, config, message, reply_all); reply_headers = g_mime_object_to_string (GMIME_OBJECT (reply)); printf ("%s", reply_headers); free (reply_headers); g_object_unref (G_OBJECT (reply)); reply = NULL; notmuch_message_destroy (message); } return 0; }
static void format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent) { printf ("id:%s depth:%d match:%d filename:%s\n", notmuch_message_get_message_id (message), indent, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH), notmuch_message_get_filename (message)); }
static GMimeMessage * create_reply_message(void *ctx, notmuch_config_t *config, notmuch_message_t *message, notmuch_bool_t reply_all) { const char *subject, *from_addr = NULL; const char *in_reply_to, *orig_references, *references; /* The 1 means we want headers in a "pretty" order. */ GMimeMessage *reply = g_mime_message_new (1); if (reply == NULL) { fprintf (stderr, "Out of memory\n"); return NULL; } subject = notmuch_message_get_header (message, "subject"); if (subject) { if (strncasecmp (subject, "Re:", 3)) subject = talloc_asprintf (ctx, "Re: %s", subject); g_mime_message_set_subject (reply, subject); } from_addr = add_recipients_from_message (reply, config, message, reply_all); if (from_addr == NULL) from_addr = guess_from_received_header (config, message); if (from_addr == NULL) from_addr = notmuch_config_get_user_primary_email (config); from_addr = talloc_asprintf (ctx, "%s <%s>", notmuch_config_get_user_name (config), from_addr); g_mime_object_set_header (GMIME_OBJECT (reply), "From", from_addr); in_reply_to = talloc_asprintf (ctx, "<%s>", notmuch_message_get_message_id (message)); g_mime_object_set_header (GMIME_OBJECT (reply), "In-Reply-To", in_reply_to); orig_references = notmuch_message_get_header (message, "references"); if (!orig_references) /* Treat errors like missing References headers. */ orig_references = ""; references = talloc_asprintf (ctx, "%s%s%s", *orig_references ? orig_references : "", *orig_references ? " " : "", in_reply_to); g_mime_object_set_header (GMIME_OBJECT (reply), "References", references); return reply; }
static inline void message_error (notmuch_message_t *message, notmuch_status_t status, const char *format, ...) { va_list va_args; va_start (va_args, format); vfprintf (stderr, format, va_args); fprintf (stderr, "Message-ID: %s\n", notmuch_message_get_message_id (message)); fprintf (stderr, "Status: %s\n", notmuch_status_to_string (status)); }
static int msg_id(Tcl_Interp *interp, notmuch_message_t *msg, int argc, const char *argv[]) { if (argc != 0) { tcl_result_printf(interp, "msg id takes no arguments"); return TCL_ERROR; } char *msg_id = strdup(notmuch_message_get_message_id(msg)); if (msg_id == NULL) { return TCL_ERROR; } tcl_result_printf(interp, "%s", msg_id); return TCL_OK; }
static int do_search_messages (const search_format_t *format, notmuch_query_t *query, output_t output) { notmuch_message_t *message; notmuch_messages_t *messages; int first_message = 1; messages = notmuch_query_search_messages (query); if (messages == NULL) return 1; fputs (format->results_start, stdout); for (; notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { message = notmuch_messages_get (messages); if (! first_message) fputs (format->item_sep, stdout); if (output == OUTPUT_FILES) { format->item_id (message, "", notmuch_message_get_filename (message)); } else { /* output == OUTPUT_MESSAGES */ format->item_id (message, "id:", notmuch_message_get_message_id (message)); } first_message = 0; notmuch_message_destroy (message); } notmuch_messages_destroy (messages); fputs (format->results_end, stdout); return 0; }
int cmd_tag_message(ClientData data, Tcl_Interp *interp, int argc, const char *argv[]) { filter_context_t *ctx = FILTER_CONTEXT(data); notmuch_message_t *msg = ctx->current_message; log_debug("tagging message %s", notmuch_message_get_message_id(msg)); if (!msg) { tcl_result_printf(interp, "no active message"); return TCL_ERROR; } for (int i = 1; i < argc; i++) { const char *tspec = argv[i]; notmuch_status_t status = NOTMUCH_STATUS_SUCCESS; switch (*tspec) { case '+': log_debug("adding tag %s", tspec + 1); if (!ctx->dry_run) { status = notmuch_message_add_tag(msg, tspec+1); } break; case '-': log_debug("removing tag %s", tspec + 1); if (!ctx->dry_run) { status = notmuch_message_remove_tag(msg, tspec+1); } break; default: tcl_result_printf(interp, "invalid tag spec %s", tspec); return TCL_ERROR; break; } if (status != NOTMUCH_STATUS_SUCCESS) { tcl_result_printf(interp, "failed to apply tag %s", tspec); return TCL_ERROR; } } notmuch_message_tags_to_maildir_flags(msg); return TCL_OK; }
/* Emit a sequence of key/value pairs for the metadata of message. * The caller should begin a map before calling this. */ static void format_message_sprinter (sprinter_t *sp, notmuch_message_t *message) { /* Any changes to the JSON or S-Expression format should be * reflected in the file devel/schemata. */ void *local = talloc_new (NULL); notmuch_tags_t *tags; time_t date; const char *relative_date; sp->map_key (sp, "id"); sp->string (sp, notmuch_message_get_message_id (message)); sp->map_key (sp, "match"); sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH)); sp->map_key (sp, "excluded"); sp->boolean (sp, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED)); sp->map_key (sp, "filename"); sp->string (sp, notmuch_message_get_filename (message)); sp->map_key (sp, "timestamp"); date = notmuch_message_get_date (message); sp->integer (sp, date); sp->map_key (sp, "date_relative"); relative_date = notmuch_time_relative_date (local, date); sp->string (sp, relative_date); sp->map_key (sp, "tags"); sp->begin_list (sp); for (tags = notmuch_message_get_tags (message); notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) sp->string (sp, notmuch_tags_get (tags)); sp->end (sp); talloc_free (local); }
static void append_message(CONTEXT *ctx, notmuch_query_t *q, notmuch_message_t *msg, int dedup) { char *newpath = NULL; const char *path; HEADER *h = NULL; /* deduplicate */ if (dedup && get_mutt_header(ctx, msg)) { get_ctxdata(ctx)->ignmsgcount++; nm_progress_update(ctx, q); dprint(2, (debugfile, "nm: ignore id=%s, already in the context\n", notmuch_message_get_message_id(msg))); return; } path = get_message_last_filename(msg); if (!path) return; dprint(2, (debugfile, "nm: appending message, i=%d, id=%s, path=%s\n", ctx->msgcount, notmuch_message_get_message_id(msg), path)); if (ctx->msgcount >= ctx->hdrmax) { dprint(2, (debugfile, "nm: allocate mx memory\n")); mx_alloc_memory(ctx); } if (access(path, F_OK) == 0) h = maildir_parse_message(M_MAILDIR, path, 0, NULL); else { /* maybe moved try find it... */ char *folder = get_folder_from_path(path); if (folder) { FILE *f = maildir_open_find_message(folder, path, &newpath); if (f) { h = maildir_parse_stream(M_MAILDIR, f, newpath, 0, NULL); fclose(f); dprint(1, (debugfile, "nm: not up-to-date: %s -> %s\n", path, newpath)); } } FREE(&folder); } if (!h) { dprint(1, (debugfile, "nm: failed to parse message: %s\n", path)); goto done; } if (init_header(h, newpath ? newpath : path, msg) != 0) { mutt_free_header(&h); dprint(1, (debugfile, "nm: failed to append header!\n")); goto done; } h->active = 1; h->index = ctx->msgcount; ctx->size += h->content->length + h->content->offset - h->content->hdr_offset; ctx->hdrs[ctx->msgcount] = h; ctx->msgcount++; if (newpath) { /* remember that file has been moved -- nm_sync() will update the DB */ struct nm_hdrdata *hd = (struct nm_hdrdata *) h->data; if (hd) { dprint(1, (debugfile, "nm: remember obsolete path: %s\n", path)); hd->oldpath = safe_strdup(path); } } nm_progress_update(ctx, q); done: FREE(&newpath); }
int cmd_move_message(ClientData data, Tcl_Interp *interp, int argc, const char *argv[]) { filter_context_t *ctx = FILTER_CONTEXT(data); notmuch_message_t *msg = ctx->current_message; notmuch_status_t nmrc; if (argc != 2) { tcl_result_printf(interp, "wrong # of args: got %d, expected move folder", argc); return TCL_ERROR; } const char *folder = argv[1]; if (folder[0] == '/') { tcl_result_printf(interp, "invalid folder '%s'", folder); return TCL_ERROR; } log_debug("moving message %s to folder %s", notmuch_message_get_message_id(msg), folder); char *folder_path = g_strdup_printf("%s/%s", notmuch_database_get_path(ctx->database), folder); int status = TCL_ERROR; notmuch_filenames_t *fns = notmuch_message_get_filenames(msg); int nmoved = 0; while (notmuch_filenames_valid(fns)) { const char *fn = notmuch_filenames_get(fns); char *new_fn = NULL; if (g_str_has_prefix(fn, folder_path)) { log_debug("file %s already in folder %s", fn, folder); } else if (ctx->dry_run) { log_debug("moving %s to folder %s", fn, folder); } else { log_debug("moving %s to folder %s", fn, folder); int rc = maildir_deliver_link(fn, folder_path, &new_fn); if (rc) { tcl_result_printf(interp, "delivery error: %s", strerror(errno)); goto done; } log_debug("delivered as %s", new_fn); nmrc = notmuch_database_add_message(ctx->database, new_fn, NULL); switch (nmrc) { case NOTMUCH_STATUS_SUCCESS: log_warning("%s: message was not in database", new_fn); case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: log_debug("added new file to database"); break; default: tcl_result_printf(interp, "error adding %s to database: %s", new_fn, notmuch_status_to_string(nmrc)); goto done; } nmrc = notmuch_database_remove_message(ctx->database, fn); switch (nmrc) { case NOTMUCH_STATUS_SUCCESS: log_warning("%s: file was only file for message", fn); case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: log_debug("removed file %s from database", fn); break; default: tcl_result_printf(interp, "error removing %s from database: %s", fn, notmuch_status_to_string(nmrc)); goto done; } if (unlink(fn)) { tcl_result_printf(interp, "error unlinking %s: %s", fn, strerror(errno)); goto done; } nmoved += 1; } notmuch_filenames_move_to_next(fns); } notmuch_filenames_destroy(fns); fns = NULL; if (nmoved == 0) { Tcl_SetObjResult(interp, Tcl_NewIntObj(0)); status = TCL_OK; goto done; } log_debug("syncing maildir flags"); const char *mid = notmuch_message_get_message_id(msg); nmrc = notmuch_database_find_message(ctx->database, mid, &msg); if (nmrc != NOTMUCH_STATUS_SUCCESS) { tcl_result_printf(interp, "cannot re-find message %s", mid); goto done; } notmuch_message_destroy(ctx->current_message); ctx->current_message = msg; nmrc = notmuch_message_tags_to_maildir_flags(msg); if (nmrc != NOTMUCH_STATUS_SUCCESS) { tcl_result_printf(interp, "error syncing tags back to flags"); goto done; } Tcl_SetObjResult(interp, Tcl_NewIntObj(nmoved)); status = TCL_OK; done: if (fns) { notmuch_filenames_destroy(fns); } g_free(folder_path); return status; }
/* Examine 'path' recursively as follows: * * o Ask the filesystem for the mtime of 'path' (fs_mtime) * o Ask the database for its timestamp of 'path' (db_mtime) * * o Ask the filesystem for files and directories within 'path' * (via scandir and stored in fs_entries) * * o Pass 1: For each directory in fs_entries, recursively call into * this same function. * * o Compare fs_mtime to db_mtime. If they are equivalent, terminate * the algorithm at this point, (this directory has not been * updated in the filesystem since the last database scan of PASS * 2). * * o Ask the database for files and directories within 'path' * (db_files and db_subdirs) * * o Pass 2: Walk fs_entries simultaneously with db_files and * db_subdirs. Look for one of three interesting cases: * * 1. Regular file in fs_entries and not in db_files * This is a new file to add_message into the database. * * 2. Filename in db_files not in fs_entries. * This is a file that has been removed from the mail store. * * 3. Directory in db_subdirs not in fs_entries * This is a directory that has been removed from the mail store. * * Note that the addition of a directory is not interesting here, * since that will have been taken care of in pass 1. Also, we * don't immediately act on file/directory removal since we must * ensure that in the case of a rename that the new filename is * added before the old filename is removed, (so that no * information is lost from the database). * * o Tell the database to update its time of 'path' to 'fs_mtime' * if fs_mtime isn't the current wall-clock time. */ static notmuch_status_t add_files_recursive (notmuch_database_t *notmuch, const char *path, add_files_state_t *state) { DIR *dir = NULL; struct dirent *entry = NULL; char *next = NULL; time_t fs_mtime, db_mtime; notmuch_status_t status, ret = NOTMUCH_STATUS_SUCCESS; notmuch_message_t *message = NULL; struct dirent **fs_entries = NULL; int i, num_fs_entries; notmuch_directory_t *directory; notmuch_filenames_t *db_files = NULL; notmuch_filenames_t *db_subdirs = NULL; time_t stat_time; struct stat st; notmuch_bool_t is_maildir, new_directory; const char **tag; if (stat (path, &st)) { fprintf (stderr, "Error reading directory %s: %s\n", path, strerror (errno)); return NOTMUCH_STATUS_FILE_ERROR; } stat_time = time (NULL); /* This is not an error since we may have recursed based on a * symlink to a regular file, not a directory, and we don't know * that until this stat. */ if (! S_ISDIR (st.st_mode)) return NOTMUCH_STATUS_SUCCESS; fs_mtime = st.st_mtime; directory = notmuch_database_get_directory (notmuch, path); db_mtime = notmuch_directory_get_mtime (directory); new_directory = db_mtime ? FALSE : TRUE; /* XXX This is a temporary workaround. If we don't update the * database mtime until after processing messages in this * directory, then a 0 mtime is *not* sufficient to indicate that * this directory has no messages or subdirs in the database (for * example, if an earlier run skipped the mtime update because * fs_mtime == stat_time, or was interrupted before updating the * mtime at the end). To address this, we record a (bogus) * non-zero value before processing any child messages so that a * later run won't mistake this for a new directory (and, for * example, fail to detect removed files and subdirs). * * A better solution would be for notmuch_database_get_directory * to indicate if it really created a new directory or not, either * by a new out-argument, or by recording this information and * providing an accessor. */ if (new_directory) notmuch_directory_set_mtime (directory, -1); /* If the database knows about this directory, then we sort based * on strcmp to match the database sorting. Otherwise, we can do * inode-based sorting for faster filesystem operation. */ num_fs_entries = scandir (path, &fs_entries, 0, new_directory ? dirent_sort_inode : dirent_sort_strcmp_name); if (num_fs_entries == -1) { fprintf (stderr, "Error opening directory %s: %s\n", path, strerror (errno)); ret = NOTMUCH_STATUS_FILE_ERROR; goto DONE; } /* Pass 1: Recurse into all sub-directories. */ is_maildir = _entries_resemble_maildir (fs_entries, num_fs_entries); for (i = 0; i < num_fs_entries; i++) { if (interrupted) break; entry = fs_entries[i]; /* We only want to descend into directories. * But symlinks can be to directories too, of course. * * And if the filesystem doesn't tell us the file type in the * scandir results, then it might be a directory (and if not, * then we'll stat and return immediately in the next level of * recursion). */ if (entry->d_type != DT_DIR && entry->d_type != DT_LNK && entry->d_type != DT_UNKNOWN) { continue; } /* Ignore special directories to avoid infinite recursion. * Also ignore the .notmuch directory and any "tmp" directory * that appears within a maildir. */ /* XXX: Eventually we'll want more sophistication to let the * user specify files to be ignored. */ if (strcmp (entry->d_name, ".") == 0 || strcmp (entry->d_name, "..") == 0 || (is_maildir && strcmp (entry->d_name, "tmp") == 0) || strcmp (entry->d_name, ".notmuch") ==0) { continue; } next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name); status = add_files_recursive (notmuch, next, state); if (status && ret == NOTMUCH_STATUS_SUCCESS) ret = status; talloc_free (next); next = NULL; } /* If the directory's modification time in the filesystem is the * same as what we recorded in the database the last time we * scanned it, then we can skip the second pass entirely. * * We test for strict equality here to avoid a bug that can happen * if the system clock jumps backward, (preventing new mail from * being discovered until the clock catches up and the directory * is modified again). */ if (fs_mtime == db_mtime) goto DONE; /* new_directory means a directory that the database has never * seen before. In that case, we can simply leave db_files and * db_subdirs NULL. */ if (!new_directory) { db_files = notmuch_directory_get_child_files (directory); db_subdirs = notmuch_directory_get_child_directories (directory); } /* Pass 2: Scan for new files, removed files, and removed directories. */ for (i = 0; i < num_fs_entries; i++) { if (interrupted) break; entry = fs_entries[i]; /* Check if we've walked past any names in db_files or * db_subdirs. If so, these have been deleted. */ while (notmuch_filenames_valid (db_files) && strcmp (notmuch_filenames_get (db_files), entry->d_name) < 0) { char *absolute = talloc_asprintf (state->removed_files, "%s/%s", path, notmuch_filenames_get (db_files)); _filename_list_add (state->removed_files, absolute); notmuch_filenames_move_to_next (db_files); } while (notmuch_filenames_valid (db_subdirs) && strcmp (notmuch_filenames_get (db_subdirs), entry->d_name) <= 0) { const char *filename = notmuch_filenames_get (db_subdirs); if (strcmp (filename, entry->d_name) < 0) { char *absolute = talloc_asprintf (state->removed_directories, "%s/%s", path, filename); _filename_list_add (state->removed_directories, absolute); } notmuch_filenames_move_to_next (db_subdirs); } /* If we're looking at a symlink, we only want to add it if it * links to a regular file, (and not to a directory, say). * * Similarly, if the file is of unknown type (due to filesystem * limitations), then we also need to look closer. * * In either case, a stat does the trick. */ if (entry->d_type == DT_LNK || entry->d_type == DT_UNKNOWN) { int err; next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name); err = stat (next, &st); talloc_free (next); next = NULL; /* Don't emit an error for a link pointing nowhere, since * the directory-traversal pass will have already done * that. */ if (err) continue; if (! S_ISREG (st.st_mode)) continue; } else if (entry->d_type != DT_REG) { continue; } /* Don't add a file that we've added before. */ if (notmuch_filenames_valid (db_files) && strcmp (notmuch_filenames_get (db_files), entry->d_name) == 0) { notmuch_filenames_move_to_next (db_files); continue; } /* We're now looking at a regular file that doesn't yet exist * in the database, so add it. */ next = talloc_asprintf (notmuch, "%s/%s", path, entry->d_name); state->processed_files++; if (state->verbose) { if (state->output_is_a_tty) printf("\r\033[K"); printf ("%i/%i: %s", state->processed_files, state->total_files, next); putchar((state->output_is_a_tty) ? '\r' : '\n'); fflush (stdout); } status = notmuch_database_add_message (notmuch, next, &message); switch (status) { /* success */ case NOTMUCH_STATUS_SUCCESS: state->added_messages++; notmuch_message_freeze (message); for (tag=state->new_tags; *tag != NULL; tag++) notmuch_message_add_tag (message, *tag); if (state->synchronize_flags == TRUE) notmuch_message_maildir_flags_to_tags (message); notmuch_message_thaw (message); break; /* Non-fatal issues (go on to next file) */ case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID: /* Defer sync of maildir flags until after old filenames * are removed in the case of a rename. */ if (state->synchronize_flags == TRUE) _filename_list_add (state->message_ids_to_sync, notmuch_message_get_message_id (message)); break; case NOTMUCH_STATUS_FILE_NOT_EMAIL: fprintf (stderr, "Note: Ignoring non-mail file: %s\n", next); break; /* Fatal issues. Don't process anymore. */ case NOTMUCH_STATUS_READ_ONLY_DATABASE: case NOTMUCH_STATUS_XAPIAN_EXCEPTION: case NOTMUCH_STATUS_OUT_OF_MEMORY: fprintf (stderr, "Error: %s. Halting processing.\n", notmuch_status_to_string (status)); ret = status; goto DONE; default: 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; } if (message) { notmuch_message_destroy (message); message = NULL; } if (do_print_progress) { do_print_progress = 0; generic_print_progress ("Processed", "files", state->tv_start, state->processed_files, state->total_files); } talloc_free (next); next = NULL; } if (interrupted) goto DONE; /* Now that we've walked the whole filesystem list, anything left * over in the database lists has been deleted. */ while (notmuch_filenames_valid (db_files)) { char *absolute = talloc_asprintf (state->removed_files, "%s/%s", path, notmuch_filenames_get (db_files)); _filename_list_add (state->removed_files, absolute); notmuch_filenames_move_to_next (db_files); } while (notmuch_filenames_valid (db_subdirs)) { char *absolute = talloc_asprintf (state->removed_directories, "%s/%s", path, notmuch_filenames_get (db_subdirs)); _filename_list_add (state->removed_directories, absolute); notmuch_filenames_move_to_next (db_subdirs); } /* If the directory's mtime is the same as the wall-clock time * when we stat'ed the directory, we skip updating the mtime in * the database because a message could be delivered later in this * same second. This may lead to unnecessary re-scans, but it * avoids overlooking messages. */ if (! interrupted && fs_mtime != stat_time) { status = notmuch_directory_set_mtime (directory, fs_mtime); if (status && ret == NOTMUCH_STATUS_SUCCESS) ret = status; } DONE: if (next) talloc_free (next); if (entry) free (entry); if (dir) closedir (dir); if (fs_entries) free (fs_entries); if (db_subdirs) notmuch_filenames_destroy (db_subdirs); if (db_files) notmuch_filenames_destroy (db_files); if (directory) notmuch_directory_destroy (directory); return ret; }
static int database_dump_file (notmuch_database_t *notmuch, gzFile output, const char *query_str, int output_format) { notmuch_query_t *query; notmuch_messages_t *messages; notmuch_message_t *message; notmuch_tags_t *tags; if (! query_str) query_str = ""; query = notmuch_query_create (notmuch, query_str); if (query == NULL) { fprintf (stderr, "Out of memory\n"); return EXIT_FAILURE; } /* Don't ask xapian to sort by Message-ID. Xapian optimizes returning the * first results quickly at the expense of total time. */ notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED); char *buffer = NULL; size_t buffer_size = 0; for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { int first = 1; const char *message_id; message = notmuch_messages_get (messages); message_id = notmuch_message_get_message_id (message); if (output_format == DUMP_FORMAT_BATCH_TAG && strchr (message_id, '\n')) { /* This will produce a line break in the output, which * would be difficult to handle in tools. However, it's * also impossible to produce an email containing a line * break in a message ID because of unfolding, so we can * safely disallow it. */ fprintf (stderr, "Warning: skipping message id containing line break: \"%s\"\n", message_id); notmuch_message_destroy (message); continue; } if (output_format == DUMP_FORMAT_SUP) { gzprintf (output, "%s (", message_id); } for (tags = notmuch_message_get_tags (message); notmuch_tags_valid (tags); notmuch_tags_move_to_next (tags)) { const char *tag_str = notmuch_tags_get (tags); if (! first) gzputs (output, " "); first = 0; if (output_format == DUMP_FORMAT_SUP) { gzputs (output, tag_str); } else { if (hex_encode (notmuch, tag_str, &buffer, &buffer_size) != HEX_SUCCESS) { fprintf (stderr, "Error: failed to hex-encode tag %s\n", tag_str); return EXIT_FAILURE; } gzprintf (output, "+%s", buffer); } } if (output_format == DUMP_FORMAT_SUP) { gzputs (output, ")\n"); } else { if (make_boolean_term (notmuch, "id", message_id, &buffer, &buffer_size)) { fprintf (stderr, "Error quoting message id %s: %s\n", message_id, strerror (errno)); return EXIT_FAILURE; } gzprintf (output, " -- %s\n", buffer); } notmuch_message_destroy (message); } notmuch_query_destroy (query); return EXIT_SUCCESS; }
int main (int argc, char** argv) { GFile *db_dir, *db_file; notmuch_status_t status; notmuch_database_t *db = NULL; notmuch_query_t *query = NULL; notmuch_messages_t *messages = NULL; notmuch_message_t *message = NULL; GMainLoop *loop = NULL; const char *query_string = "date:2014-02-01.."; if (argc != 2) { g_warning ("Usage: %s EVOLUTION_MAILDIR", argv[0]); return 1; } db_dir = g_file_new_for_path (argv[1]); db_file = g_file_get_child (db_dir, ".notmuch"); if (!g_file_query_exists (db_dir, NULL)) { g_object_unref (db_dir); g_object_unref (db_file); g_error ("directory %s does not exists"); return 2; } if (!g_file_query_exists (db_file, NULL)) status = notmuch_database_create (argv[1], &db); else status = notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db); if (status) { g_error ("Could not open database: %d", status); g_object_unref (db_dir); g_object_unref (db_file); notmuch_database_destroy (db); return 3; } scan_directory (db, db_dir); //loop = g_main_loop_new (NULL, FALSE); //g_main_loop_run (loop); query = notmuch_query_create (db, query_string); if (!query) { g_error ("Could not create query from string = \"%s\"", query_string); notmuch_database_destroy (db); g_object_unref (db_file); g_object_unref (db_dir); return 4; } g_message ("Query results -\n\n"); for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { message = notmuch_messages_get (messages); g_message ("Message file: %s", notmuch_message_get_filename (message)); g_message ("Message ID: %s", notmuch_message_get_message_id (message)); g_message ("Message Sender: %s", notmuch_message_get_header (message, "from")); g_message ("Message Recipients: %s", notmuch_message_get_header (message, "to")); g_message ("Message Subject: %s", notmuch_message_get_header (message, "subject")); g_message ("Message date: %s\n", notmuch_message_get_header (message, "date")); notmuch_message_destroy (message); } notmuch_query_destroy (query); notmuch_database_destroy (db); g_object_unref (db_file); g_object_unref (db_dir); return 0; }
static GMimeMessage * create_reply_message(void *ctx, notmuch_config_t *config, notmuch_message_t *message, notmuch_bool_t reply_all) { const char *subject, *from_addr = NULL; const char *in_reply_to, *orig_references, *references; /* The 1 means we want headers in a "pretty" order. */ GMimeMessage *reply = g_mime_message_new (1); if (reply == NULL) { fprintf (stderr, "Out of memory\n"); return NULL; } subject = notmuch_message_get_header (message, "subject"); if (subject) { if (strncasecmp (subject, "Re:", 3)) subject = talloc_asprintf (ctx, "Re: %s", subject); g_mime_message_set_subject (reply, subject); } from_addr = add_recipients_from_message (reply, config, message, reply_all); /* * Sadly, there is no standard way to find out to which email * address a mail was delivered - what is in the headers depends * on the MTAs used along the way. * * If none of the user's email addresses are in the To: or Cc: * headers, we try a number of heuristics which hopefully will * answer this question. * * First, check for Envelope-To:, X-Original-To:, and * Delivered-To: headers. */ if (from_addr == NULL) from_addr = get_from_in_to_headers (config, message); /* * Check for a (for <*****@*****.**>) clause in Received: headers, * and the domain part of known email addresses in the 'by' part * of Received: headers */ if (from_addr == NULL) from_addr = guess_from_in_received_headers (config, message); /* Default to user's primary address. */ if (from_addr == NULL) from_addr = notmuch_config_get_user_primary_email (config); from_addr = talloc_asprintf (ctx, "%s <%s>", notmuch_config_get_user_name (config), from_addr); g_mime_object_set_header (GMIME_OBJECT (reply), "From", from_addr); in_reply_to = talloc_asprintf (ctx, "<%s>", notmuch_message_get_message_id (message)); g_mime_object_set_header (GMIME_OBJECT (reply), "In-Reply-To", in_reply_to); orig_references = notmuch_message_get_header (message, "references"); if (!orig_references) /* Treat errors like missing References headers. */ orig_references = ""; references = talloc_asprintf (ctx, "%s%s%s", *orig_references ? orig_references : "", *orig_references ? " " : "", in_reply_to); g_mime_object_set_header (GMIME_OBJECT (reply), "References", references); return reply; }
static int notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params) { GMimeMessage *reply; notmuch_messages_t *messages; notmuch_message_t *message; const char *subject, *from_addr = NULL; const char *in_reply_to, *orig_references, *references; const notmuch_show_format_t *format = &format_reply; for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { message = notmuch_messages_get (messages); /* The 1 means we want headers in a "pretty" order. */ reply = g_mime_message_new (1); if (reply == NULL) { fprintf (stderr, "Out of memory\n"); return 1; } subject = notmuch_message_get_header (message, "subject"); if (subject) { if (strncasecmp (subject, "Re:", 3)) subject = talloc_asprintf (ctx, "Re: %s", subject); g_mime_message_set_subject (reply, subject); } from_addr = add_recipients_from_message (reply, config, message); if (from_addr == NULL) from_addr = guess_from_received_header (config, message); if (from_addr == NULL) from_addr = notmuch_config_get_user_primary_email (config); from_addr = talloc_asprintf (ctx, "%s <%s>", notmuch_config_get_user_name (config), from_addr); g_mime_object_set_header (GMIME_OBJECT (reply), "From", from_addr); in_reply_to = talloc_asprintf (ctx, "<%s>", notmuch_message_get_message_id (message)); g_mime_object_set_header (GMIME_OBJECT (reply), "In-Reply-To", in_reply_to); orig_references = notmuch_message_get_header (message, "references"); references = talloc_asprintf (ctx, "%s%s%s", orig_references ? orig_references : "", orig_references ? " " : "", in_reply_to); g_mime_object_set_header (GMIME_OBJECT (reply), "References", references); show_reply_headers (reply); g_object_unref (G_OBJECT (reply)); reply = NULL; printf ("On %s, %s wrote:\n", notmuch_message_get_header (message, "date"), notmuch_message_get_header (message, "from")); show_message_body (notmuch_message_get_filename (message), format, params); notmuch_message_destroy (message); } return 0; }
QString Message::id() const { if(!libnotmuch_message) return QString(); return QString(notmuch_message_get_message_id(libnotmuch_message)); }
static int do_search_messages (search_context_t *ctx) { notmuch_message_t *message; notmuch_messages_t *messages; notmuch_filenames_t *filenames; sprinter_t *format = ctx->format; int i; notmuch_status_t status; if (ctx->offset < 0) { unsigned count; notmuch_status_t status; status = notmuch_query_count_messages_st (ctx->query, &count); if (print_status_query ("notmuch search", ctx->query, status)) return 1; ctx->offset += count; if (ctx->offset < 0) ctx->offset = 0; } status = notmuch_query_search_messages_st (ctx->query, &messages); if (print_status_query ("notmuch search", ctx->query, status)) return 1; format->begin_list (format); for (i = 0; notmuch_messages_valid (messages) && (ctx->limit < 0 || i < ctx->offset + ctx->limit); notmuch_messages_move_to_next (messages), i++) { if (i < ctx->offset) continue; message = notmuch_messages_get (messages); if (ctx->output == OUTPUT_FILES) { int j; filenames = notmuch_message_get_filenames (message); for (j = 1; notmuch_filenames_valid (filenames); notmuch_filenames_move_to_next (filenames), j++) { if (ctx->dupe < 0 || ctx->dupe == j) { format->string (format, notmuch_filenames_get (filenames)); format->separator (format); } } notmuch_filenames_destroy( filenames ); } else if (ctx->output == OUTPUT_MESSAGES) { /* special case 1 for speed */ if (ctx->dupe <= 1 || ctx->dupe <= _count_filenames (message)) { format->set_prefix (format, "id"); format->string (format, notmuch_message_get_message_id (message)); format->separator (format); } } else { if (ctx->output & OUTPUT_SENDER) { const char *addrs; addrs = notmuch_message_get_header (message, "from"); process_address_header (ctx, addrs); } if (ctx->output & OUTPUT_RECIPIENTS) { const char *hdrs[] = { "to", "cc", "bcc" }; const char *addrs; size_t j; for (j = 0; j < ARRAY_SIZE (hdrs); j++) { addrs = notmuch_message_get_header (message, hdrs[j]); process_address_header (ctx, addrs); } } } notmuch_message_destroy (message); } if (ctx->addresses && (ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS)) g_hash_table_foreach (ctx->addresses, print_hash_value, ctx); notmuch_messages_destroy (messages); format->end (format); return 0; }
static notmuch_status_t format_part_text (const void *ctx, mime_node_t *node, int indent, const notmuch_show_params_t *params) { /* The disposition and content-type metadata are associated with * the envelope for message parts */ GMimeObject *meta = node->envelope_part ? GMIME_OBJECT (node->envelope_part) : node->part; GMimeContentType *content_type = g_mime_object_get_content_type (meta); const notmuch_bool_t leaf = GMIME_IS_PART (node->part); const char *part_type; int i; if (node->envelope_file) { notmuch_message_t *message = node->envelope_file; part_type = "message"; printf ("\f%s{ id:%s depth:%d match:%d excluded:%d filename:%s\n", part_type, notmuch_message_get_message_id (message), indent, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0, notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0, notmuch_message_get_filename (message)); } else { GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (meta); const char *cid = g_mime_object_get_content_id (meta); const char *filename = leaf ? g_mime_part_get_filename (GMIME_PART (node->part)) : NULL; if (disposition && strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0) part_type = "attachment"; else part_type = "part"; printf ("\f%s{ ID: %d", part_type, node->part_num); if (filename) printf (", Filename: %s", filename); if (cid) printf (", Content-id: %s", cid); printf (", Content-type: %s\n", g_mime_content_type_to_string (content_type)); } if (GMIME_IS_MESSAGE (node->part)) { GMimeMessage *message = GMIME_MESSAGE (node->part); InternetAddressList *recipients; const char *recipients_string; printf ("\fheader{\n"); if (node->envelope_file) printf ("%s\n", _get_one_line_summary (ctx, node->envelope_file)); printf ("Subject: %s\n", g_mime_message_get_subject (message)); printf ("From: %s\n", g_mime_message_get_sender (message)); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO); recipients_string = internet_address_list_to_string (recipients, 0); if (recipients_string) printf ("To: %s\n", recipients_string); recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC); recipients_string = internet_address_list_to_string (recipients, 0); if (recipients_string) printf ("Cc: %s\n", recipients_string); printf ("Date: %s\n", g_mime_message_get_date_as_string (message)); printf ("\fheader}\n"); printf ("\fbody{\n"); } if (leaf) { if (g_mime_content_type_is_type (content_type, "text", "*") && !g_mime_content_type_is_type (content_type, "text", "html")) { GMimeStream *stream_stdout = g_mime_stream_file_new (stdout); g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE); show_text_part_content (node->part, stream_stdout, 0); g_object_unref(stream_stdout); } else { printf ("Non-text part: %s\n", g_mime_content_type_to_string (content_type)); } } for (i = 0; i < node->nchildren; i++) format_part_text (ctx, mime_node_child (node, i), indent, params); if (GMIME_IS_MESSAGE (node->part)) printf ("\fbody}\n"); printf ("\f%s}\n", part_type); return NOTMUCH_STATUS_SUCCESS; }
static int do_search_messages (const search_format_t *format, notmuch_query_t *query, output_t output, int offset, int limit) { notmuch_message_t *message; notmuch_messages_t *messages; notmuch_filenames_t *filenames; int first_message = 1; int i; if (offset < 0) { offset += notmuch_query_count_messages (query); if (offset < 0) offset = 0; } messages = notmuch_query_search_messages (query); if (messages == NULL) return 1; fputs (format->results_start, stdout); for (i = 0; notmuch_messages_valid (messages) && (limit < 0 || i < offset + limit); notmuch_messages_move_to_next (messages), i++) { if (i < offset) continue; message = notmuch_messages_get (messages); if (output == OUTPUT_FILES) { filenames = notmuch_message_get_filenames (message); for (; notmuch_filenames_valid (filenames); notmuch_filenames_move_to_next (filenames)) { if (! first_message) fputs (format->item_sep, stdout); format->item_id (message, "", notmuch_filenames_get (filenames)); first_message = 0; } notmuch_filenames_destroy( filenames ); } else { /* output == OUTPUT_MESSAGES */ if (! first_message) fputs (format->item_sep, stdout); format->item_id (message, "id:", notmuch_message_get_message_id (message)); first_message = 0; } notmuch_message_destroy (message); } notmuch_messages_destroy (messages); if (first_message) fputs (format->results_null, stdout); else fputs (format->results_end, stdout); return 0; }