unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
* [PATCH v7 00/12] insert command
@ 2013-06-23  4:23 Peter Wang
  2013-06-23  4:23 ` [PATCH v7 01/12] tag-util: move out 'tag' command-line check Peter Wang
                   ` (11 more replies)
  0 siblings, 12 replies; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:23 UTC (permalink / raw)
  To: notmuch

- address Mark's review comments

Peter Wang (12):
  tag-util: move out 'tag' command-line check
  tag-util: do not reset list in parse_tag_command_line
  cli: add insert command
  man: document 'insert' command
  man: reference notmuch-insert.1
  test: add tests for insert
  insert: add --folder option
  man: document insert --folder option
  test: test insert --folder option
  insert: add --create-folder option
  man: document insert --create-folder
  test: test insert --create-folder option

 Makefile.local                  |   1 +
 man/Makefile.local              |   1 +
 man/man1/notmuch-config.1       |   4 +-
 man/man1/notmuch-count.1        |   4 +-
 man/man1/notmuch-dump.1         |   4 +-
 man/man1/notmuch-insert.1       |  75 +++++++
 man/man1/notmuch-new.1          |   4 +-
 man/man1/notmuch-reply.1        |   3 +-
 man/man1/notmuch-restore.1      |   3 +-
 man/man1/notmuch-search.1       |   3 +-
 man/man1/notmuch-show.1         |   3 +-
 man/man1/notmuch-tag.1          |   3 +-
 man/man1/notmuch.1              |   3 +-
 man/man5/notmuch-hooks.5        |   4 +-
 man/man7/notmuch-search-terms.7 |   3 +-
 notmuch-client.h                |   3 +
 notmuch-insert.c                | 477 ++++++++++++++++++++++++++++++++++++++++
 notmuch-tag.c                   |   5 +
 notmuch.c                       |   2 +
 tag-util.c                      |   8 +-
 tag-util.h                      |   2 +
 test/insert                     | 121 ++++++++++
 test/notmuch-test               |   1 +
 23 files changed, 715 insertions(+), 22 deletions(-)
 create mode 100644 man/man1/notmuch-insert.1
 create mode 100644 notmuch-insert.c
 create mode 100755 test/insert

-- 
1.7.12.1

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v7 01/12] tag-util: move out 'tag' command-line check
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
@ 2013-06-23  4:23 ` Peter Wang
  2013-06-23  4:23 ` [PATCH v7 02/12] tag-util: do not reset list in parse_tag_command_line Peter Wang
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:23 UTC (permalink / raw)
  To: notmuch

Move an error condition specific to the 'tag' command out of
parse_tag_command_line so that parse_tag_command_line can be used for
the forthcoming 'insert' command.
---
 notmuch-tag.c | 5 +++++
 tag-util.c    | 6 +++---
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/notmuch-tag.c b/notmuch-tag.c
index 9a5d3e7..3b09df9 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -247,6 +247,11 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
 	    fprintf (stderr, "Error: 'notmuch tag' requires at least one tag to add or remove.\n");
 	    return 1;
 	}
+
+	if (*query_string == '\0') {
+	    fprintf (stderr, "Error: notmuch tag requires at least one search term.\n");
+	    return 1;
+	}
     }
 
     if (notmuch_database_open (notmuch_config_get_database_path (config),
diff --git a/tag-util.c b/tag-util.c
index c5f5859..92e08a1 100644
--- a/tag-util.c
+++ b/tag-util.c
@@ -190,9 +190,9 @@ parse_tag_command_line (void *ctx, int argc, char **argv,
 
     *query_str = query_string_from_args (ctx, argc - i, &argv[i]);
 
-    if (*query_str == NULL || **query_str == '\0') {
-	fprintf (stderr, "Error: notmuch tag requires at least one search term.\n");
-	return TAG_PARSE_INVALID;
+    if (*query_str == NULL) {
+	fprintf (stderr, "Out of memory.\n");
+	return TAG_PARSE_OUT_OF_MEMORY;
     }
 
     return TAG_PARSE_SUCCESS;
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 02/12] tag-util: do not reset list in parse_tag_command_line
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
  2013-06-23  4:23 ` [PATCH v7 01/12] tag-util: move out 'tag' command-line check Peter Wang
@ 2013-06-23  4:23 ` Peter Wang
  2013-06-23  4:23 ` [PATCH v7 03/12] cli: add insert command Peter Wang
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:23 UTC (permalink / raw)
  To: notmuch

The 'insert' command will be better served if parse_tag_command_line
modifies a pre-populated list (of new.tags) instead of clobbering the
list outright.  The sole existing caller, notmuch_tag_command, is
unaffected by this change.
---
 tag-util.c | 2 --
 tag-util.h | 2 ++
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/tag-util.c b/tag-util.c
index 92e08a1..3bde409 100644
--- a/tag-util.c
+++ b/tag-util.c
@@ -165,8 +165,6 @@ parse_tag_command_line (void *ctx, int argc, char **argv,
 
     int i;
 
-    tag_op_list_reset (tag_ops);
-
     for (i = 0; i < argc; i++) {
 	if (strcmp (argv[i], "--") == 0) {
 	    i++;
diff --git a/tag-util.h b/tag-util.h
index 246de85..4628f16 100644
--- a/tag-util.h
+++ b/tag-util.h
@@ -81,6 +81,8 @@ parse_tag_line (void *ctx, char *line,
  * Output Parameters:
  *	ops	contains a list of tag operations
  *	query_str the search terms.
+ *
+ * The ops argument is not cleared.
  */
 
 tag_parse_status_t
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 03/12] cli: add insert command
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
  2013-06-23  4:23 ` [PATCH v7 01/12] tag-util: move out 'tag' command-line check Peter Wang
  2013-06-23  4:23 ` [PATCH v7 02/12] tag-util: do not reset list in parse_tag_command_line Peter Wang
@ 2013-06-23  4:23 ` Peter Wang
  2013-06-23  6:42   ` Mark Walters
  2013-06-23  4:23 ` [PATCH v7 04/12] man: document 'insert' command Peter Wang
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:23 UTC (permalink / raw)
  To: notmuch

The notmuch insert command reads a message from standard input,
writes it to a Maildir folder, and then incorporates the message into
the notmuch database.  Essentially it moves the functionality of
notmuch-deliver into notmuch.

Though it could be used as an alternative to notmuch new, the reason
I want this is to allow my notmuch frontend to add postponed or sent
messages to the mail store and notmuch database, without resorting to
another tool (e.g. notmuch-deliver) nor directly modifying the maildir.
---
 Makefile.local   |   1 +
 notmuch-client.h |   3 +
 notmuch-insert.c | 335 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 notmuch.c        |   2 +
 4 files changed, 341 insertions(+)
 create mode 100644 notmuch-insert.c

diff --git a/Makefile.local b/Makefile.local
index 644623f..84043fe 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -261,6 +261,7 @@ notmuch_client_srcs =		\
 	notmuch-config.c	\
 	notmuch-count.c		\
 	notmuch-dump.c		\
+	notmuch-insert.c	\
 	notmuch-new.c		\
 	notmuch-reply.c		\
 	notmuch-restore.c	\
diff --git a/notmuch-client.h b/notmuch-client.h
index 4a3c7ac..dfe81e6 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -181,6 +181,9 @@ int
 notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]);
 
 int
+notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
 notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);
 
 int
diff --git a/notmuch-insert.c b/notmuch-insert.c
new file mode 100644
index 0000000..591189f
--- /dev/null
+++ b/notmuch-insert.c
@@ -0,0 +1,335 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2013 Peter Wang
+ *
+ * Based in part on notmuch-deliver
+ * Copyright © 2010 Ali Polatel
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Peter Wang <novalazy@gmail.com>
+ */
+
+#include "notmuch-client.h"
+#include "tag-util.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+static volatile sig_atomic_t interrupted;
+
+static void
+handle_sigint (unused (int sig))
+{
+    static char msg[] = "Stopping...         \n";
+
+    /* This write is "opportunistic", so it's okay to ignore the
+     * result.  It is not required for correctness, and if it does
+     * fail or produce a short write, we want to get out of the signal
+     * handler as quickly as possible, not retry it. */
+    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
+    interrupted = 1;
+}
+
+/* Like gethostname but guarantees that a null-terminated hostname is
+ * returned, even if it has to make one up. Invalid characters are
+ * substituted such that the hostname can be used within a filename.
+ */
+static void
+safe_gethostname (char *hostname, size_t len)
+{
+    char *p;
+
+    if (gethostname (hostname, len) == -1) {
+	strncpy (hostname, "unknown", len);
+    }
+    hostname[len - 1] = '\0';
+
+    for (p = hostname; *p != '\0'; p++) {
+	if (*p == '/' || *p == ':')
+	    *p = '_';
+    }
+}
+
+/* Call fsync() on a directory path. */
+static notmuch_bool_t
+sync_dir (const char *dir)
+{
+    notmuch_bool_t ret;
+    int fd;
+
+    fd = open (dir, O_RDONLY);
+    if (fd == -1) {
+	fprintf (stderr, "Error: open() dir failed: %s\n", strerror (errno));
+	return FALSE;
+    }
+    ret = (fsync (fd) == 0);
+    if (! ret) {
+	fprintf (stderr, "Error: fsync() dir failed: %s\n", strerror (errno));
+    }
+    close (fd);
+    return ret;
+}
+
+/* Open a unique file in the 'tmp' sub-directory of dir.
+ * Returns the file descriptor on success, or -1 on failure.
+ * On success, file paths for the message in the 'tmp' and 'new'
+ * directories are returned via tmppath and newpath,
+ * and the path of the 'new' directory itself in newdir. */
+static int
+maildir_open_tmp_file (void *ctx, const char *dir,
+		       char **tmppath, char **newpath, char **newdir)
+{
+    pid_t pid;
+    char hostname[256];
+    struct timeval tv;
+    char *filename;
+    int fd = -1;
+
+    /* We follow the Dovecot file name generation algorithm. */
+    pid = getpid ();
+    safe_gethostname (hostname, sizeof (hostname));
+    do {
+	gettimeofday (&tv, NULL);
+	filename = talloc_asprintf (ctx, "%ld.M%ldP%d.%s",
+				    tv.tv_sec, tv.tv_usec, pid, hostname);
+	if (! filename) {
+	    fprintf (stderr, "Out of memory\n");
+	    return -1;
+	}
+
+	*tmppath = talloc_asprintf (ctx, "%s/tmp/%s", dir, filename);
+	if (! *tmppath) {
+	    fprintf (stderr, "Out of memory\n");
+	    return -1;
+	}
+
+	fd = open (*tmppath, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
+    } while (fd == -1 && errno == EEXIST);
+
+    if (fd == -1) {
+	fprintf (stderr, "Error: opening %s: %s\n", *tmppath, strerror (errno));
+	return -1;
+    }
+
+    *newdir = talloc_asprintf (ctx, "%s/new", dir);
+    *newpath = talloc_asprintf (ctx, "%s/new/%s", dir, filename);
+    if (! *newdir || ! *newpath) {
+	fprintf (stderr, "Out of memory\n");
+	close (fd);
+	unlink (*tmppath);
+	return -1;
+    }
+
+    talloc_free (filename);
+
+    return fd;
+}
+
+/* Copy the contents of standard input (fdin) into fdout.
+ * Returns TRUE if a non-empty file was written successfully.
+ * Otherwise, return FALSE. */
+static notmuch_bool_t
+copy_stdin (int fdin, int fdout)
+{
+    notmuch_bool_t empty = TRUE;
+
+    while (! interrupted) {
+	ssize_t remain;
+	char buf[4096];
+	char *p;
+
+	remain = read (fdin, buf, sizeof (buf));
+	if (remain == 0)
+	    break;
+	if (remain < 0) {
+	    if (errno == EINTR)
+		continue;
+	    fprintf (stderr, "Error: reading from standard input: %s\n",
+		     strerror (errno));
+	    return FALSE;
+	}
+
+	p = buf;
+	do {
+	    ssize_t written = write (fdout, p, remain);
+	    if (written < 0 && errno == EINTR)
+		continue;
+	    if (written <= 0) {
+		fprintf (stderr, "Error: writing to temporary file: %s",
+			 strerror (errno));
+		return FALSE;
+	    }
+	    p += written;
+	    remain -= written;
+	    empty = FALSE;
+	} while (remain > 0);
+    }
+
+    return (!interrupted && !empty);
+}
+
+/* Add the specified message file to the notmuch database, applying tags.
+ * The file is renamed to encode notmuch tags as maildir flags. */
+static void
+add_file_to_database (notmuch_database_t *notmuch, const char *path,
+		      tag_op_list_t *tag_ops)
+{
+    notmuch_message_t *message;
+    notmuch_status_t status;
+
+    status = notmuch_database_add_message (notmuch, path, &message);
+    switch (status) {
+    case NOTMUCH_STATUS_SUCCESS:
+    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+	break;
+    default:
+    case NOTMUCH_STATUS_FILE_NOT_EMAIL:
+    case NOTMUCH_STATUS_READ_ONLY_DATABASE:
+    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
+    case NOTMUCH_STATUS_OUT_OF_MEMORY:
+    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_UNBALANCED_ATOMIC:
+    case NOTMUCH_STATUS_LAST_STATUS:
+	fprintf (stderr, "Error: failed to add `%s' to notmuch database: %s\n",
+		 path, notmuch_status_to_string (status));
+	return;
+    }
+
+    if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
+	/* Don't change tags of an existing message. */
+	status = notmuch_message_tags_to_maildir_flags (message);
+	if (status != NOTMUCH_STATUS_SUCCESS)
+	    fprintf (stderr, "Error: failed to sync tags to maildir flags\n");
+    } else {
+	tag_op_list_apply (message, tag_ops, TAG_FLAG_MAILDIR_SYNC);
+    }
+
+    notmuch_message_destroy (message);
+}
+
+static notmuch_bool_t
+insert_message (void *ctx, notmuch_database_t *notmuch, int fdin,
+		const char *dir, tag_op_list_t *tag_ops)
+{
+    char *tmppath;
+    char *newpath;
+    char *newdir;
+    int fdout;
+    char *cleanup_path;
+
+    fdout = maildir_open_tmp_file (ctx, dir, &tmppath, &newpath, &newdir);
+    if (fdout < 0)
+	return FALSE;
+
+    cleanup_path = tmppath;
+
+    if (! copy_stdin (fdin, fdout))
+	goto FAIL;
+
+    if (fsync (fdout) != 0) {
+	fprintf (stderr, "Error: fsync failed: %s\n", strerror (errno));
+	goto FAIL;
+    }
+
+    close (fdout);
+    fdout = -1;
+
+    /* Atomically move the new message file from the Maildir 'tmp' directory
+     * to the 'new' directory.  We follow the Dovecot recommendation to
+     * simply use rename() instead of link() and unlink().
+     * See also: http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery
+     */
+    if (rename (tmppath, newpath) != 0) {
+	fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));
+	goto FAIL;
+    }
+
+    if (! sync_dir (newdir))
+	goto FAIL;
+
+    /* Even if adding the message to the notmuch database fails,
+     * the message is on disk and we consider the delivery completed. */
+    add_file_to_database (notmuch, newpath, tag_ops);
+
+    return TRUE;
+
+  FAIL:
+    if (fdout >= 0)
+	close (fdout);
+    unlink (cleanup_path);
+    return FALSE;
+}
+
+int
+notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    notmuch_database_t *notmuch;
+    struct sigaction action;
+    const char *db_path;
+    const char **new_tags;
+    size_t new_tags_length;
+    tag_op_list_t *tag_ops;
+    char *query_string = NULL;
+    const char *maildir;
+    int opt_index = 1;
+    unsigned int i;
+    notmuch_bool_t ret;
+
+    db_path = notmuch_config_get_database_path (config);
+    new_tags = notmuch_config_get_new_tags (config, &new_tags_length);
+
+    tag_ops = tag_op_list_create (config);
+    if (tag_ops == NULL) {
+	fprintf (stderr, "Out of memory.\n");
+	return 1;
+    }
+    for (i = 0; i < new_tags_length; i++) {
+	if (tag_op_list_append (tag_ops, new_tags[i], FALSE))
+	    return 1;
+    }
+
+    if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,
+				&query_string, tag_ops))
+	return 1;
+
+    if (*query_string != '\0') {
+	fprintf (stderr, "Error: unexpected query string: %s\n", query_string);
+	return 1;
+    }
+
+    maildir = db_path;
+
+    /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying
+     * from standard input may be interrupted. */
+    memset (&action, 0, sizeof (struct sigaction));
+    action.sa_handler = handle_sigint;
+    sigemptyset (&action.sa_mask);
+    action.sa_flags = 0;
+    sigaction (SIGINT, &action, NULL);
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+			       NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
+	return 1;
+
+    ret = insert_message (config, notmuch, STDIN_FILENO, maildir, tag_ops);
+
+    notmuch_database_destroy (notmuch);
+
+    return (ret) ? 0 : 1;
+}
diff --git a/notmuch.c b/notmuch.c
index 45a73ce..d11f214 100644
--- a/notmuch.c
+++ b/notmuch.c
@@ -44,6 +44,8 @@ static command_t commands[] = {
       "Interactively setup notmuch for first use." },
     { "new", notmuch_new_command, FALSE,
       "Find and import new messages to the notmuch database." },
+    { "insert", notmuch_insert_command, FALSE,
+      "Add a new message into the maildir and notmuch database." },
     { "search", notmuch_search_command, FALSE,
       "Search for messages matching the given search terms." },
     { "show", notmuch_show_command, FALSE,
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 04/12] man: document 'insert' command
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
                   ` (2 preceding siblings ...)
  2013-06-23  4:23 ` [PATCH v7 03/12] cli: add insert command Peter Wang
@ 2013-06-23  4:23 ` Peter Wang
  2013-06-23  4:23 ` [PATCH v7 05/12] man: reference notmuch-insert.1 Peter Wang
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:23 UTC (permalink / raw)
  To: notmuch

Add initial documentation for notmuch insert command.
---
 man/Makefile.local        |  1 +
 man/man1/notmuch-insert.1 | 49 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 50 insertions(+)
 create mode 100644 man/man1/notmuch-insert.1

diff --git a/man/Makefile.local b/man/Makefile.local
index 72e2a18..216aaa0 100644
--- a/man/Makefile.local
+++ b/man/Makefile.local
@@ -12,6 +12,7 @@ MAN1 := \
 	$(dir)/man1/notmuch-count.1 \
 	$(dir)/man1/notmuch-dump.1 \
 	$(dir)/man1/notmuch-restore.1 \
+	$(dir)/man1/notmuch-insert.1 \
 	$(dir)/man1/notmuch-new.1 \
 	$(dir)/man1/notmuch-reply.1 \
 	$(dir)/man1/notmuch-search.1 \
diff --git a/man/man1/notmuch-insert.1 b/man/man1/notmuch-insert.1
new file mode 100644
index 0000000..fbf83e0
--- /dev/null
+++ b/man/man1/notmuch-insert.1
@@ -0,0 +1,49 @@
+.TH NOTMUCH-INSERT 1 2013-xx-xx "Notmuch 0.xx"
+.SH NAME
+notmuch-insert \- add a message to the maildir and notmuch database
+.SH SYNOPSIS
+
+.B notmuch insert
+.RI "[ +<" tag> "|\-<" tag "> ... ]"
+
+.SH DESCRIPTION
+
+.B notmuch insert
+reads a message from standard input
+and delivers it into the maildir directory given by configuration option
+.BR database.path ,
+then incorporates the message into the notmuch database.
+It is an alternative to using a separate tool to deliver
+the message then running
+.B notmuch new
+afterwards.
+
+The new message will be tagged with the tags specified by the
+.B new.tags
+configuration option, then by operations specified on the command-line:
+tags prefixed by '+' are added while
+those prefixed by '\-' are removed.
+
+If the new message is a duplicate of an existing message in the database
+(it has same Message-ID), it will be added to the maildir folder and
+notmuch database, but the tags will not be changed.
+
+.RE
+.SH EXIT STATUS
+
+This command returns exit status 0 if the message was successfully
+added to the mail directory, even if the message could not be indexed
+and added to the notmuch database.  In the latter case, a warning will
+be printed to standard error but the message file will be left on disk.
+
+If the message could not be written to disk then a non-zero exit
+status is returned.
+
+.RE
+.SH SEE ALSO
+
+\fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
+\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-reply\fR(1),
+\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
+\fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1),
+\fBnotmuch-tag\fR(1)
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 05/12] man: reference notmuch-insert.1
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
                   ` (3 preceding siblings ...)
  2013-06-23  4:23 ` [PATCH v7 04/12] man: document 'insert' command Peter Wang
@ 2013-06-23  4:23 ` Peter Wang
  2013-06-23  4:23 ` [PATCH v7 06/12] test: add tests for insert Peter Wang
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:23 UTC (permalink / raw)
  To: notmuch

Add references to notmuch-insert.1 from other man pages.
---
 man/man1/notmuch-config.1       | 4 ++--
 man/man1/notmuch-count.1        | 4 ++--
 man/man1/notmuch-dump.1         | 4 ++--
 man/man1/notmuch-new.1          | 4 ++--
 man/man1/notmuch-reply.1        | 3 ++-
 man/man1/notmuch-restore.1      | 3 ++-
 man/man1/notmuch-search.1       | 3 ++-
 man/man1/notmuch-show.1         | 3 ++-
 man/man1/notmuch-tag.1          | 3 ++-
 man/man1/notmuch.1              | 3 ++-
 man/man5/notmuch-hooks.5        | 4 ++--
 man/man7/notmuch-search-terms.7 | 3 ++-
 12 files changed, 24 insertions(+), 17 deletions(-)

diff --git a/man/man1/notmuch-config.1 b/man/man1/notmuch-config.1
index db31166..1635951 100644
--- a/man/man1/notmuch-config.1
+++ b/man/man1/notmuch-config.1
@@ -152,7 +152,7 @@ use ${HOME}/.notmuch\-config if this variable is not set.
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-count\fR(1), \fBnotmuch-dump\fR(1),
-\fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-reply\fR(1),
-\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
+\fBnotmuch-hooks\fR(5), \fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
+\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
 \fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1),
 \fBnotmuch-tag\fR(1)
diff --git a/man/man1/notmuch-count.1 b/man/man1/notmuch-count.1
index 7fc4378..c172a22 100644
--- a/man/man1/notmuch-count.1
+++ b/man/man1/notmuch-count.1
@@ -72,7 +72,7 @@ Read input from given file, instead of from stdin. Implies
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-dump\fR(1),
-\fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-reply\fR(1),
-\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
+\fBnotmuch-hooks\fR(5), \fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
+\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
 \fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1),
 \fBnotmuch-tag\fR(1)
diff --git a/man/man1/notmuch-dump.1 b/man/man1/notmuch-dump.1
index 3fa51bd..a758a52 100644
--- a/man/man1/notmuch-dump.1
+++ b/man/man1/notmuch-dump.1
@@ -92,7 +92,7 @@ for details of the supported syntax for <search-terms>.
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
-\fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1), \fBnotmuch-reply\fR(1),
-\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
+\fBnotmuch-hooks\fR(5), \fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
+\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
 \fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1),
 \fBnotmuch-tag\fR(1)
diff --git a/man/man1/notmuch-new.1 b/man/man1/notmuch-new.1
index 02f7954..3fcfc29 100644
--- a/man/man1/notmuch-new.1
+++ b/man/man1/notmuch-new.1
@@ -64,7 +64,7 @@ Prevents hooks from being run.
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
-\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-reply\fR(1),
-\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
+\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-insert\fR(1),
+\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
 \fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1),
 \fBnotmuch-tag\fR(1)
diff --git a/man/man1/notmuch-reply.1 b/man/man1/notmuch-reply.1
index bf2021f..f0394b2 100644
--- a/man/man1/notmuch-reply.1
+++ b/man/man1/notmuch-reply.1
@@ -126,7 +126,8 @@ The requested format version is too new.
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
-\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1),
+\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5),
+\fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
 \fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
 \fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1),
 \fBnotmuch-tag\fR(1)
diff --git a/man/man1/notmuch-restore.1 b/man/man1/notmuch-restore.1
index 4ec4c80..686c0ac 100644
--- a/man/man1/notmuch-restore.1
+++ b/man/man1/notmuch-restore.1
@@ -84,7 +84,8 @@ should be accurate.
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
-\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1),
+\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5),
+\fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
 \fBnotmuch-reply\fR(1), \fBnotmuch-search\fR(1),
 \fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1),
 \fBnotmuch-tag\fR(1)
diff --git a/man/man1/notmuch-search.1 b/man/man1/notmuch-search.1
index 1c1e049..1206770 100644
--- a/man/man1/notmuch-search.1
+++ b/man/man1/notmuch-search.1
@@ -172,7 +172,8 @@ The requested format version is too new.
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
-\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1),
+\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5),
+\fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
 \fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1),
 \fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1),
 \fBnotmuch-tag\fR(1)
diff --git a/man/man1/notmuch-show.1 b/man/man1/notmuch-show.1
index 7697dfc..d87ae62 100644
--- a/man/man1/notmuch-show.1
+++ b/man/man1/notmuch-show.1
@@ -229,7 +229,8 @@ The requested format version is too new.
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
-\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1),
+\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5),
+\fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
 \fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1),
 \fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7),
 \fBnotmuch-tag\fR(1)
diff --git a/man/man1/notmuch-tag.1 b/man/man1/notmuch-tag.1
index c7d7a4d..e282bf2 100644
--- a/man/man1/notmuch-tag.1
+++ b/man/man1/notmuch-tag.1
@@ -135,7 +135,8 @@ of the tag
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
-\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1),
+\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5),
+\fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
 \fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1),
 \fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7),
 \fBnotmuch-show\fR(1),
diff --git a/man/man1/notmuch.1 b/man/man1/notmuch.1
index f5ca0ad..5548f99 100644
--- a/man/man1/notmuch.1
+++ b/man/man1/notmuch.1
@@ -179,7 +179,8 @@ queries it constructs.
 .SH SEE ALSO
 
 \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
-\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1),
+\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5),
+\fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
 \fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1),
 \fBnotmuch-search\fR(1), \fBnotmuch-search-terms\fR(7),
 \fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1)
diff --git a/man/man5/notmuch-hooks.5 b/man/man5/notmuch-hooks.5
index 402c0ef..188cd07 100644
--- a/man/man5/notmuch-hooks.5
+++ b/man/man5/notmuch-hooks.5
@@ -42,7 +42,7 @@ imported messages.
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
-\fBnotmuch-dump\fR(1), \fBnotmuch-new\fR(1), \fBnotmuch-reply\fR(1),
-\fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
+\fBnotmuch-dump\fR(1), \fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
+\fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1), \fBnotmuch-search\fR(1),
 \fBnotmuch-search-terms\fR(7), \fBnotmuch-show\fR(1),
 \fBnotmuch-tag\fR(1)
diff --git a/man/man7/notmuch-search-terms.7 b/man/man7/notmuch-search-terms.7
index eb417ba..11b90ed 100644
--- a/man/man7/notmuch-search-terms.7
+++ b/man/man7/notmuch-search-terms.7
@@ -261,6 +261,7 @@ Some time zone codes, e.g. UTC, EET.
 .SH SEE ALSO
 
 \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),
-\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5), \fBnotmuch-new\fR(1),
+\fBnotmuch-dump\fR(1), \fBnotmuch-hooks\fR(5),
+\fBnotmuch-insert\fR(1), \fBnotmuch-new\fR(1),
 \fBnotmuch-reply\fR(1), \fBnotmuch-restore\fR(1),
 \fBnotmuch-search\fR(1), \fBnotmuch-show\fR(1), \fBnotmuch-tag\fR(1)
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 06/12] test: add tests for insert
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
                   ` (4 preceding siblings ...)
  2013-06-23  4:23 ` [PATCH v7 05/12] man: reference notmuch-insert.1 Peter Wang
@ 2013-06-23  4:23 ` Peter Wang
  2013-06-30 14:44   ` David Bremner
  2013-06-23  4:24 ` [PATCH v7 07/12] insert: add --folder option Peter Wang
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:23 UTC (permalink / raw)
  To: notmuch

Add tests for new 'insert' command.
---
 test/insert       | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 test/notmuch-test |  1 +
 2 files changed, 81 insertions(+)
 create mode 100755 test/insert

diff --git a/test/insert b/test/insert
new file mode 100755
index 0000000..b432a74
--- /dev/null
+++ b/test/insert
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+test_description='"notmuch insert"'
+. ./test-lib.sh
+
+# Create directories and database before inserting.
+mkdir -p "$MAIL_DIR"/{cur,new,tmp}
+mkdir -p "$MAIL_DIR"/Drafts/{cur,new,tmp}
+notmuch new > /dev/null
+
+# We use generate_message to create the temporary message files.
+# They happen to be in the mail directory already but that is okay
+# since we do not call notmuch new hereafter.
+
+gen_insert_msg() {
+    generate_message \
+	"[subject]=\"insert-subject\"" \
+	"[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" \
+	"[body]=\"insert-message\""
+}
+
+test_expect_code 1 "Insert zero-length file" \
+    "notmuch insert < /dev/null"
+
+# This test is a proxy for other errors that may occur while trying to
+# add a message to the notmuch database, e.g. database locked.
+test_expect_code 0 "Insert non-message" \
+    "echo bad_message | notmuch insert"
+
+test_begin_subtest "Database empty so far"
+test_expect_equal "0" "`notmuch count --output=messages '*'`"
+
+test_begin_subtest "Insert message"
+gen_insert_msg
+notmuch insert < "$gen_msg_filename"
+cur_msg_filename=$(notmuch search --output=files "subject:insert-subject")
+test_expect_equal_file "$cur_msg_filename" "$gen_msg_filename"
+
+test_begin_subtest "Insert message adds default tags"
+output=$(notmuch show --format=json "subject:insert-subject")
+expected='[[[{
+ "id": "'"${gen_msg_id}"'",
+ "match": true,
+ "excluded": false,
+ "filename": "'"${cur_msg_filename}"'",
+ "timestamp": 946728000,
+ "date_relative": "2000-01-01",
+ "tags": ["inbox","unread"],
+ "headers": {
+  "Subject": "insert-subject",
+  "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+  "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+  "Date": "Sat, 01 Jan 2000 12:00:00 +0000"},
+ "body": [{"id": 1,
+  "content-type": "text/plain",
+  "content": "insert-message\n"}]},
+ []]]]'
+test_expect_equal_json "$output" "$expected"
+
+test_begin_subtest "Insert duplicate message"
+notmuch insert +duptag -unread < "$gen_msg_filename"
+output=$(notmuch search --output=files "subject:insert-subject" | wc -l)
+test_expect_equal "$output" 2
+
+test_begin_subtest "Duplicate message does not change tags"
+output=$(notmuch search --format=json --output=tags "subject:insert-subject")
+test_expect_equal_json "$output" '["inbox", "unread"]'
+
+test_begin_subtest "Insert message, add tag"
+gen_insert_msg
+notmuch insert +custom < "$gen_msg_filename"
+output=$(notmuch count tag:custom)
+test_expect_equal "$output" "1"
+
+test_begin_subtest "Insert message, add/remove tags"
+gen_insert_msg
+notmuch insert +custom -unread < "$gen_msg_filename"
+output=$(notmuch count tag:custom NOT tag:unread)
+test_expect_equal "$output" "1"
+
+test_done
diff --git a/test/notmuch-test b/test/notmuch-test
index a0c47d4..6db7979 100755
--- a/test/notmuch-test
+++ b/test/notmuch-test
@@ -23,6 +23,7 @@ TESTS="
   setup
   new
   count
+  insert
   search
   search-output
   search-by-folder
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 07/12] insert: add --folder option
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
                   ` (5 preceding siblings ...)
  2013-06-23  4:23 ` [PATCH v7 06/12] test: add tests for insert Peter Wang
@ 2013-06-23  4:24 ` Peter Wang
  2013-06-23  4:24 ` [PATCH v7 08/12] man: document insert " Peter Wang
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:24 UTC (permalink / raw)
  To: notmuch

Allow the new message to be inserted into a folder within the Maildir
hierarchy instead of the top-level folder.
---
 notmuch-insert.c | 46 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 44 insertions(+), 2 deletions(-)

diff --git a/notmuch-insert.c b/notmuch-insert.c
index 591189f..5a4b3ea 100644
--- a/notmuch-insert.c
+++ b/notmuch-insert.c
@@ -83,6 +83,23 @@ sync_dir (const char *dir)
     return ret;
 }
 
+/* Check the specified folder name does not contain a directory
+ * component ".." to prevent writes outside of the Maildir hierarchy. */
+static notmuch_bool_t
+check_folder_name (const char *folder)
+{
+    const char *p = folder;
+
+    for (;;) {
+	if ((p[0] == '.') && (p[1] == '.') && (p[2] == '\0' || p[2] == '/'))
+	    return FALSE;
+	p = strchr (p, '/');
+	if (!p)
+	    return TRUE;
+	p++;
+    }
+}
+
 /* Open a unique file in the 'tmp' sub-directory of dir.
  * Returns the file descriptor on success, or -1 on failure.
  * On success, file paths for the message in the 'tmp' and 'new'
@@ -286,11 +303,24 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
     size_t new_tags_length;
     tag_op_list_t *tag_ops;
     char *query_string = NULL;
+    const char *folder = NULL;
     const char *maildir;
-    int opt_index = 1;
+    int opt_index;
     unsigned int i;
     notmuch_bool_t ret;
 
+    notmuch_opt_desc_t options[] = {
+	{ NOTMUCH_OPT_STRING, &folder, "folder", 0, 0 },
+	{ NOTMUCH_OPT_END, 0, 0, 0, 0 }
+    };
+
+    opt_index = parse_arguments (argc, argv, options, 1);
+
+    if (opt_index < 0) {
+	/* diagnostics already printed */
+	return 1;
+    }
+
     db_path = notmuch_config_get_database_path (config);
     new_tags = notmuch_config_get_new_tags (config, &new_tags_length);
 
@@ -313,7 +343,19 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
 	return 1;
     }
 
-    maildir = db_path;
+    if (folder == NULL) {
+	maildir = db_path;
+    } else {
+	if (! check_folder_name (folder)) {
+	    fprintf (stderr, "Error: bad folder name: %s\n", folder);
+	    return 1;
+	}
+	maildir = talloc_asprintf (config, "%s/%s", db_path, folder);
+	if (! maildir) {
+	    fprintf (stderr, "Out of memory\n");
+	    return 1;
+	}
+    }
 
     /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying
      * from standard input may be interrupted. */
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 08/12] man: document insert --folder option
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
                   ` (6 preceding siblings ...)
  2013-06-23  4:24 ` [PATCH v7 07/12] insert: add --folder option Peter Wang
@ 2013-06-23  4:24 ` Peter Wang
  2013-06-23  4:24 ` [PATCH v7 09/12] test: test " Peter Wang
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:24 UTC (permalink / raw)
  To: notmuch

Add documentation for notmuch insert --folder option.
---
 man/man1/notmuch-insert.1 | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/man/man1/notmuch-insert.1 b/man/man1/notmuch-insert.1
index fbf83e0..e85fef8 100644
--- a/man/man1/notmuch-insert.1
+++ b/man/man1/notmuch-insert.1
@@ -4,6 +4,7 @@ notmuch-insert \- add a message to the maildir and notmuch database
 .SH SYNOPSIS
 
 .B notmuch insert
+.RI "[" options "]"
 .RI "[ +<" tag> "|\-<" tag "> ... ]"
 
 .SH DESCRIPTION
@@ -28,6 +29,19 @@ If the new message is a duplicate of an existing message in the database
 (it has same Message-ID), it will be added to the maildir folder and
 notmuch database, but the tags will not be changed.
 
+Option arguments must appear before any tag operation arguments.
+Supported options for
+.B insert
+include
+.RS 4
+.TP 4
+.BI "--folder=<" folder ">"
+
+Deliver the message to the specified folder,
+relative to the top-level directory given by the value of
+\fBdatabase.path\fR.
+The default is to deliver to the top-level directory.
+
 .RE
 .SH EXIT STATUS
 
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 09/12] test: test insert --folder option
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
                   ` (7 preceding siblings ...)
  2013-06-23  4:24 ` [PATCH v7 08/12] man: document insert " Peter Wang
@ 2013-06-23  4:24 ` Peter Wang
  2013-07-01 15:29   ` David Bremner
  2013-06-23  4:24 ` [PATCH v7 10/12] insert: add --create-folder option Peter Wang
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:24 UTC (permalink / raw)
  To: notmuch

Add tests for notmuch insert --folder option.
---
 test/insert | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/test/insert b/test/insert
index b432a74..f573c76 100755
--- a/test/insert
+++ b/test/insert
@@ -77,4 +77,21 @@ notmuch insert +custom -unread < "$gen_msg_filename"
 output=$(notmuch count tag:custom NOT tag:unread)
 test_expect_equal "$output" "1"
 
+test_begin_subtest "Insert message into folder"
+gen_insert_msg
+notmuch insert --folder=Drafts < "$gen_msg_filename"
+output=$(notmuch search --output=files folder:Drafts)
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/Drafts/cur"
+
+test_begin_subtest "Insert message into folder, add/remove tags"
+gen_insert_msg
+notmuch insert --folder=Drafts +draft -unread < "$gen_msg_filename"
+output=$(notmuch count folder:Drafts tag:draft NOT tag:unread)
+test_expect_equal "$output" "1"
+
+gen_insert_msg
+test_expect_code 1 "Insert message into non-existent folder" \
+    "notmuch insert --folder=nonesuch < $gen_msg_filename"
+
 test_done
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 10/12] insert: add --create-folder option
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
                   ` (8 preceding siblings ...)
  2013-06-23  4:24 ` [PATCH v7 09/12] test: test " Peter Wang
@ 2013-06-23  4:24 ` Peter Wang
  2013-06-23  4:24 ` [PATCH v7 11/12] man: document insert --create-folder Peter Wang
  2013-06-23  4:24 ` [PATCH v7 12/12] test: test insert --create-folder option Peter Wang
  11 siblings, 0 replies; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:24 UTC (permalink / raw)
  To: notmuch

Allow the insert command to create the maildir folder
into which the new message should be delivered.
---
 notmuch-insert.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 100 insertions(+)

diff --git a/notmuch-insert.c b/notmuch-insert.c
index 5a4b3ea..8d0235d 100644
--- a/notmuch-insert.c
+++ b/notmuch-insert.c
@@ -100,6 +100,99 @@ check_folder_name (const char *folder)
     }
 }
 
+/* Make the given directory, succeeding if it already exists. */
+static notmuch_bool_t
+make_directory (char *path, int mode)
+{
+    notmuch_bool_t ret;
+    char *slash;
+
+    if (mkdir (path, mode) != 0)
+	return (errno == EEXIST);
+
+    /* Sync the parent directory for durability. */
+    ret = TRUE;
+    slash = strrchr (path, '/');
+    if (slash) {
+	*slash = '\0';
+	ret = sync_dir (path);
+	*slash = '/';
+    }
+    return ret;
+}
+
+/* Make the given directory including its parent directories as necessary.
+ * Return TRUE on success, FALSE on error. */
+static notmuch_bool_t
+make_directory_and_parents (char *path, int mode)
+{
+    struct stat st;
+    char *start;
+    char *end;
+    notmuch_bool_t ret;
+
+    /* First check the common case: directory already exists. */
+    if (stat (path, &st) == 0)
+	return S_ISDIR (st.st_mode) ? TRUE : FALSE;
+
+    for (start = path; *start != '\0'; start = end + 1) {
+	/* start points to the first unprocessed character.
+	 * Find the next slash from start onwards. */
+	end = strchr (start, '/');
+
+	/* If there are no more slashes then all the parent directories
+	 * have been made.  Now attempt to make the whole path. */
+	if (end == NULL)
+	    return make_directory (path, mode);
+
+	/* Make the path up to the next slash, unless the current
+	 * directory component is actually empty. */
+	if (end > start) {
+	    *end = '\0';
+	    ret = make_directory (path, mode);
+	    *end = '/';
+	    if (! ret)
+		return FALSE;
+	}
+    }
+
+    return TRUE;
+}
+
+/* Create the given maildir folder, i.e. dir and its subdirectories
+ * 'cur', 'new', 'tmp'. */
+static notmuch_bool_t
+maildir_create_folder (void *ctx, const char *dir)
+{
+    const int mode = 0700;
+    char *subdir;
+    char *tail;
+
+    /* Create 'cur' directory, including parent directories. */
+    subdir = talloc_asprintf (ctx, "%s/cur", dir);
+    if (! subdir) {
+	fprintf (stderr, "Out of memory.\n");
+	return FALSE;
+    }
+    if (! make_directory_and_parents (subdir, mode))
+	return FALSE;
+
+    tail = subdir + strlen (subdir) - 3;
+
+    /* Create 'new' directory. */
+    strcpy (tail, "new");
+    if (! make_directory (subdir, mode))
+	return FALSE;
+
+    /* Create 'tmp' directory. */
+    strcpy (tail, "tmp");
+    if (! make_directory (subdir, mode))
+	return FALSE;
+
+    talloc_free (subdir);
+    return TRUE;
+}
+
 /* Open a unique file in the 'tmp' sub-directory of dir.
  * Returns the file descriptor on success, or -1 on failure.
  * On success, file paths for the message in the 'tmp' and 'new'
@@ -304,6 +397,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
     tag_op_list_t *tag_ops;
     char *query_string = NULL;
     const char *folder = NULL;
+    notmuch_bool_t create_folder = FALSE;
     const char *maildir;
     int opt_index;
     unsigned int i;
@@ -311,6 +405,7 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
 
     notmuch_opt_desc_t options[] = {
 	{ NOTMUCH_OPT_STRING, &folder, "folder", 0, 0 },
+	{ NOTMUCH_OPT_BOOLEAN, &create_folder, "create-folder", 0, 0 },
 	{ NOTMUCH_OPT_END, 0, 0, 0, 0 }
     };
 
@@ -355,6 +450,11 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
 	    fprintf (stderr, "Out of memory\n");
 	    return 1;
 	}
+	if (create_folder && ! maildir_create_folder (config, maildir)) {
+	    fprintf (stderr, "Error: creating maildir %s: %s\n",
+		     maildir, strerror (errno));
+	    return 1;
+	}
     }
 
     /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 11/12] man: document insert --create-folder
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
                   ` (9 preceding siblings ...)
  2013-06-23  4:24 ` [PATCH v7 10/12] insert: add --create-folder option Peter Wang
@ 2013-06-23  4:24 ` Peter Wang
  2013-06-23  4:24 ` [PATCH v7 12/12] test: test insert --create-folder option Peter Wang
  11 siblings, 0 replies; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:24 UTC (permalink / raw)
  To: notmuch

Add documentation for notmuch insert --create-folder option.
---
 man/man1/notmuch-insert.1 | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/man/man1/notmuch-insert.1 b/man/man1/notmuch-insert.1
index e85fef8..d5202ac 100644
--- a/man/man1/notmuch-insert.1
+++ b/man/man1/notmuch-insert.1
@@ -43,6 +43,18 @@ relative to the top-level directory given by the value of
 The default is to deliver to the top-level directory.
 
 .RE
+
+.RS 4
+.TP 4
+.B "--create-folder"
+
+Try to create the folder named by the
+.B "--folder"
+option, if it does not exist.
+Otherwise the folder must already exist for mail
+delivery to succeed.
+
+.RE
 .SH EXIT STATUS
 
 This command returns exit status 0 if the message was successfully
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v7 12/12] test: test insert --create-folder option
  2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
                   ` (10 preceding siblings ...)
  2013-06-23  4:24 ` [PATCH v7 11/12] man: document insert --create-folder Peter Wang
@ 2013-06-23  4:24 ` Peter Wang
  2013-07-04  3:33   ` David Bremner
  11 siblings, 1 reply; 20+ messages in thread
From: Peter Wang @ 2013-06-23  4:24 UTC (permalink / raw)
  To: notmuch

Add tests for notmuch insert --create-folder option.
---
 test/insert | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/test/insert b/test/insert
index f573c76..021edb6 100755
--- a/test/insert
+++ b/test/insert
@@ -94,4 +94,28 @@ gen_insert_msg
 test_expect_code 1 "Insert message into non-existent folder" \
     "notmuch insert --folder=nonesuch < $gen_msg_filename"
 
+test_begin_subtest "Insert message, create folder"
+gen_insert_msg
+notmuch insert --folder=F --create-folder +folder < "$gen_msg_filename"
+output=$(notmuch search --output=files folder:F tag:folder)
+basename=$(basename "$output")
+test_expect_equal_file "$gen_msg_filename" "$MAIL_DIR/F/cur/${basename}"
+
+test_begin_subtest "Insert message, create subfolder"
+gen_insert_msg
+notmuch insert --folder=F/G/H/I/J --create-folder +folder < "$gen_msg_filename"
+output=$(notmuch search --output=files folder:F/G/H/I/J tag:folder)
+basename=$(basename "$output")
+test_expect_equal_file "$gen_msg_filename" "${MAIL_DIR}/F/G/H/I/J/cur/${basename}"
+
+test_begin_subtest "Insert message, create existing subfolder"
+gen_insert_msg
+notmuch insert --folder=F/G/H/I/J --create-folder +folder < "$gen_msg_filename"
+output=$(notmuch count folder:F/G/H/I/J tag:folder)
+test_expect_equal "$output" "2"
+
+gen_insert_msg
+test_expect_code 1 "Insert message, create invalid subfolder" \
+    "notmuch insert --folder=../G --create-folder $gen_msg_filename"
+
 test_done
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 03/12] cli: add insert command
  2013-06-23  4:23 ` [PATCH v7 03/12] cli: add insert command Peter Wang
@ 2013-06-23  6:42   ` Mark Walters
  2013-06-23 12:19     ` Peter Wang
  0 siblings, 1 reply; 20+ messages in thread
From: Mark Walters @ 2013-06-23  6:42 UTC (permalink / raw)
  To: Peter Wang, notmuch


This is a +1 modulo one small bug (I think) I found below. I am happy to
delay the fail-on-index-fail option, especially as that will need some
bikeshedding on its name.

Also when posting new versions please include a diff from the previous
version (this is more difficult if there was significant rebasing). This
makew the v6 to v7 change obvious (the one comment change and the
bugfix).

Moreover doing the diff with v4 (which I happen to have locally) I
found the bug below.

Best wishes

Mark





On Sun, 23 Jun 2013, Peter Wang <novalazy@gmail.com> wrote:
> The notmuch insert command reads a message from standard input,
> writes it to a Maildir folder, and then incorporates the message into
> the notmuch database.  Essentially it moves the functionality of
> notmuch-deliver into notmuch.
>
> Though it could be used as an alternative to notmuch new, the reason
> I want this is to allow my notmuch frontend to add postponed or sent
> messages to the mail store and notmuch database, without resorting to
> another tool (e.g. notmuch-deliver) nor directly modifying the maildir.
> ---
>  Makefile.local   |   1 +
>  notmuch-client.h |   3 +
>  notmuch-insert.c | 335 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  notmuch.c        |   2 +
>  4 files changed, 341 insertions(+)
>  create mode 100644 notmuch-insert.c
>
> diff --git a/Makefile.local b/Makefile.local
> index 644623f..84043fe 100644
> --- a/Makefile.local
> +++ b/Makefile.local
> @@ -261,6 +261,7 @@ notmuch_client_srcs =		\
>  	notmuch-config.c	\
>  	notmuch-count.c		\
>  	notmuch-dump.c		\
> +	notmuch-insert.c	\
>  	notmuch-new.c		\
>  	notmuch-reply.c		\
>  	notmuch-restore.c	\
> diff --git a/notmuch-client.h b/notmuch-client.h
> index 4a3c7ac..dfe81e6 100644
> --- a/notmuch-client.h
> +++ b/notmuch-client.h
> @@ -181,6 +181,9 @@ int
>  notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]);
>  
>  int
> +notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);
> +
> +int
>  notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);
>  
>  int
> diff --git a/notmuch-insert.c b/notmuch-insert.c
> new file mode 100644
> index 0000000..591189f
> --- /dev/null
> +++ b/notmuch-insert.c
> @@ -0,0 +1,335 @@
> +/* notmuch - Not much of an email program, (just index and search)
> + *
> + * Copyright © 2013 Peter Wang
> + *
> + * Based in part on notmuch-deliver
> + * Copyright © 2010 Ali Polatel
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation, either version 3 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see http://www.gnu.org/licenses/ .
> + *
> + * Author: Peter Wang <novalazy@gmail.com>
> + */
> +
> +#include "notmuch-client.h"
> +#include "tag-util.h"
> +
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <fcntl.h>
> +
> +static volatile sig_atomic_t interrupted;
> +
> +static void
> +handle_sigint (unused (int sig))
> +{
> +    static char msg[] = "Stopping...         \n";
> +
> +    /* This write is "opportunistic", so it's okay to ignore the
> +     * result.  It is not required for correctness, and if it does
> +     * fail or produce a short write, we want to get out of the signal
> +     * handler as quickly as possible, not retry it. */
> +    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
> +    interrupted = 1;
> +}
> +
> +/* Like gethostname but guarantees that a null-terminated hostname is
> + * returned, even if it has to make one up. Invalid characters are
> + * substituted such that the hostname can be used within a filename.
> + */
> +static void
> +safe_gethostname (char *hostname, size_t len)
> +{
> +    char *p;
> +
> +    if (gethostname (hostname, len) == -1) {
> +	strncpy (hostname, "unknown", len);
> +    }
> +    hostname[len - 1] = '\0';
> +
> +    for (p = hostname; *p != '\0'; p++) {
> +	if (*p == '/' || *p == ':')
> +	    *p = '_';
> +    }
> +}
> +
> +/* Call fsync() on a directory path. */
> +static notmuch_bool_t
> +sync_dir (const char *dir)
> +{
> +    notmuch_bool_t ret;
> +    int fd;
> +
> +    fd = open (dir, O_RDONLY);
> +    if (fd == -1) {
> +	fprintf (stderr, "Error: open() dir failed: %s\n", strerror (errno));
> +	return FALSE;
> +    }
> +    ret = (fsync (fd) == 0);
> +    if (! ret) {
> +	fprintf (stderr, "Error: fsync() dir failed: %s\n", strerror (errno));
> +    }
> +    close (fd);
> +    return ret;
> +}
> +
> +/* Open a unique file in the 'tmp' sub-directory of dir.
> + * Returns the file descriptor on success, or -1 on failure.
> + * On success, file paths for the message in the 'tmp' and 'new'
> + * directories are returned via tmppath and newpath,
> + * and the path of the 'new' directory itself in newdir. */
> +static int
> +maildir_open_tmp_file (void *ctx, const char *dir,
> +		       char **tmppath, char **newpath, char **newdir)
> +{
> +    pid_t pid;
> +    char hostname[256];
> +    struct timeval tv;
> +    char *filename;
> +    int fd = -1;
> +
> +    /* We follow the Dovecot file name generation algorithm. */
> +    pid = getpid ();
> +    safe_gethostname (hostname, sizeof (hostname));
> +    do {
> +	gettimeofday (&tv, NULL);
> +	filename = talloc_asprintf (ctx, "%ld.M%ldP%d.%s",
> +				    tv.tv_sec, tv.tv_usec, pid, hostname);
> +	if (! filename) {
> +	    fprintf (stderr, "Out of memory\n");
> +	    return -1;
> +	}
> +
> +	*tmppath = talloc_asprintf (ctx, "%s/tmp/%s", dir, filename);
> +	if (! *tmppath) {
> +	    fprintf (stderr, "Out of memory\n");
> +	    return -1;
> +	}
> +
> +	fd = open (*tmppath, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
> +    } while (fd == -1 && errno == EEXIST);
> +
> +    if (fd == -1) {
> +	fprintf (stderr, "Error: opening %s: %s\n", *tmppath, strerror (errno));
> +	return -1;
> +    }
> +
> +    *newdir = talloc_asprintf (ctx, "%s/new", dir);
> +    *newpath = talloc_asprintf (ctx, "%s/new/%s", dir, filename);
> +    if (! *newdir || ! *newpath) {
> +	fprintf (stderr, "Out of memory\n");
> +	close (fd);
> +	unlink (*tmppath);
> +	return -1;
> +    }
> +
> +    talloc_free (filename);
> +
> +    return fd;
> +}
> +
> +/* Copy the contents of standard input (fdin) into fdout.
> + * Returns TRUE if a non-empty file was written successfully.
> + * Otherwise, return FALSE. */
> +static notmuch_bool_t
> +copy_stdin (int fdin, int fdout)
> +{
> +    notmuch_bool_t empty = TRUE;
> +
> +    while (! interrupted) {
> +	ssize_t remain;
> +	char buf[4096];
> +	char *p;
> +
> +	remain = read (fdin, buf, sizeof (buf));
> +	if (remain == 0)
> +	    break;
> +	if (remain < 0) {
> +	    if (errno == EINTR)
> +		continue;
> +	    fprintf (stderr, "Error: reading from standard input: %s\n",
> +		     strerror (errno));
> +	    return FALSE;
> +	}
> +
> +	p = buf;
> +	do {
> +	    ssize_t written = write (fdout, p, remain);
> +	    if (written < 0 && errno == EINTR)
> +		continue;
> +	    if (written <= 0) {
> +		fprintf (stderr, "Error: writing to temporary file: %s",
> +			 strerror (errno));
> +		return FALSE;
> +	    }
> +	    p += written;
> +	    remain -= written;
> +	    empty = FALSE;
> +	} while (remain > 0);
> +    }
> +
> +    return (!interrupted && !empty);
> +}
> +
> +/* Add the specified message file to the notmuch database, applying tags.
> + * The file is renamed to encode notmuch tags as maildir flags. */
> +static void
> +add_file_to_database (notmuch_database_t *notmuch, const char *path,
> +		      tag_op_list_t *tag_ops)
> +{
> +    notmuch_message_t *message;
> +    notmuch_status_t status;
> +
> +    status = notmuch_database_add_message (notmuch, path, &message);
> +    switch (status) {
> +    case NOTMUCH_STATUS_SUCCESS:
> +    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
> +	break;
> +    default:
> +    case NOTMUCH_STATUS_FILE_NOT_EMAIL:
> +    case NOTMUCH_STATUS_READ_ONLY_DATABASE:
> +    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
> +    case NOTMUCH_STATUS_OUT_OF_MEMORY:
> +    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_UNBALANCED_ATOMIC:
> +    case NOTMUCH_STATUS_LAST_STATUS:
> +	fprintf (stderr, "Error: failed to add `%s' to notmuch database: %s\n",
> +		 path, notmuch_status_to_string (status));
> +	return;
> +    }
> +
> +    if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
> +	/* Don't change tags of an existing message. */
> +	status = notmuch_message_tags_to_maildir_flags (message);
> +	if (status != NOTMUCH_STATUS_SUCCESS)
> +	    fprintf (stderr, "Error: failed to sync tags to maildir flags\n");
> +    } else {
> +	tag_op_list_apply (message, tag_ops, TAG_FLAG_MAILDIR_SYNC);
> +    }
> +
> +    notmuch_message_destroy (message);
> +}
> +
> +static notmuch_bool_t
> +insert_message (void *ctx, notmuch_database_t *notmuch, int fdin,
> +		const char *dir, tag_op_list_t *tag_ops)
> +{
> +    char *tmppath;
> +    char *newpath;
> +    char *newdir;
> +    int fdout;
> +    char *cleanup_path;
> +
> +    fdout = maildir_open_tmp_file (ctx, dir, &tmppath, &newpath, &newdir);
> +    if (fdout < 0)
> +	return FALSE;
> +
> +    cleanup_path = tmppath;
> +
> +    if (! copy_stdin (fdin, fdout))
> +	goto FAIL;
> +
> +    if (fsync (fdout) != 0) {
> +	fprintf (stderr, "Error: fsync failed: %s\n", strerror (errno));
> +	goto FAIL;
> +    }
> +
> +    close (fdout);
> +    fdout = -1;
> +
> +    /* Atomically move the new message file from the Maildir 'tmp' directory
> +     * to the 'new' directory.  We follow the Dovecot recommendation to
> +     * simply use rename() instead of link() and unlink().
> +     * See also: http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery
> +     */
> +    if (rename (tmppath, newpath) != 0) {
> +	fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));
> +	goto FAIL;
> +    }
> +
> +    if (! sync_dir (newdir))
> +	goto FAIL;

I think cleanup_path needs to be updated before the sync_dir is test as
newpath should be unlinked rather than oldpath. (v4 explicitly unlinked newpath)


> +
> +    /* Even if adding the message to the notmuch database fails,
> +     * the message is on disk and we consider the delivery completed. */
> +    add_file_to_database (notmuch, newpath, tag_ops);
> +
> +    return TRUE;
> +
> +  FAIL:
> +    if (fdout >= 0)
> +	close (fdout);
> +    unlink (cleanup_path);
> +    return FALSE;
> +}
> +
> +int
> +notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
> +{
> +    notmuch_database_t *notmuch;
> +    struct sigaction action;
> +    const char *db_path;
> +    const char **new_tags;
> +    size_t new_tags_length;
> +    tag_op_list_t *tag_ops;
> +    char *query_string = NULL;
> +    const char *maildir;
> +    int opt_index = 1;
> +    unsigned int i;
> +    notmuch_bool_t ret;
> +
> +    db_path = notmuch_config_get_database_path (config);
> +    new_tags = notmuch_config_get_new_tags (config, &new_tags_length);
> +
> +    tag_ops = tag_op_list_create (config);
> +    if (tag_ops == NULL) {
> +	fprintf (stderr, "Out of memory.\n");
> +	return 1;
> +    }
> +    for (i = 0; i < new_tags_length; i++) {
> +	if (tag_op_list_append (tag_ops, new_tags[i], FALSE))
> +	    return 1;
> +    }
> +
> +    if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,
> +				&query_string, tag_ops))
> +	return 1;
> +
> +    if (*query_string != '\0') {
> +	fprintf (stderr, "Error: unexpected query string: %s\n", query_string);
> +	return 1;
> +    }
> +
> +    maildir = db_path;
> +
> +    /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying
> +     * from standard input may be interrupted. */
> +    memset (&action, 0, sizeof (struct sigaction));
> +    action.sa_handler = handle_sigint;
> +    sigemptyset (&action.sa_mask);
> +    action.sa_flags = 0;
> +    sigaction (SIGINT, &action, NULL);
> +
> +    if (notmuch_database_open (notmuch_config_get_database_path (config),
> +			       NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
> +	return 1;
> +
> +    ret = insert_message (config, notmuch, STDIN_FILENO, maildir, tag_ops);
> +
> +    notmuch_database_destroy (notmuch);
> +
> +    return (ret) ? 0 : 1;
> +}
> diff --git a/notmuch.c b/notmuch.c
> index 45a73ce..d11f214 100644
> --- a/notmuch.c
> +++ b/notmuch.c
> @@ -44,6 +44,8 @@ static command_t commands[] = {
>        "Interactively setup notmuch for first use." },
>      { "new", notmuch_new_command, FALSE,
>        "Find and import new messages to the notmuch database." },
> +    { "insert", notmuch_insert_command, FALSE,
> +      "Add a new message into the maildir and notmuch database." },
>      { "search", notmuch_search_command, FALSE,
>        "Search for messages matching the given search terms." },
>      { "show", notmuch_show_command, FALSE,
> -- 
> 1.7.12.1
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 03/12] cli: add insert command
  2013-06-23  6:42   ` Mark Walters
@ 2013-06-23 12:19     ` Peter Wang
  2013-06-23 12:20       ` [PATCH v7b] " Peter Wang
  0 siblings, 1 reply; 20+ messages in thread
From: Peter Wang @ 2013-06-23 12:19 UTC (permalink / raw)
  To: Mark Walters; +Cc: notmuch

On Sun, 23 Jun 2013 07:42:49 +0100, Mark Walters <markwalters1009@gmail.com> wrote:
> 
> This is a +1 modulo one small bug (I think) I found below. I am happy to
> delay the fail-on-index-fail option, especially as that will need some
> bikeshedding on its name.
> 
> Also when posting new versions please include a diff from the previous
> version (this is more difficult if there was significant rebasing). This
> makew the v6 to v7 change obvious (the one comment change and the
> bugfix).
> 
> Moreover doing the diff with v4 (which I happen to have locally) I
> found the bug below.
> 
> Best wishes
> 
> Mark
> 
> 
> 
...
> > +static notmuch_bool_t
> > +insert_message (void *ctx, notmuch_database_t *notmuch, int fdin,
> > +		const char *dir, tag_op_list_t *tag_ops)
> > +{
> > +    char *tmppath;
> > +    char *newpath;
> > +    char *newdir;
> > +    int fdout;
> > +    char *cleanup_path;
> > +
> > +    fdout = maildir_open_tmp_file (ctx, dir, &tmppath, &newpath, &newdir);
> > +    if (fdout < 0)
> > +	return FALSE;
> > +
> > +    cleanup_path = tmppath;
> > +
> > +    if (! copy_stdin (fdin, fdout))
> > +	goto FAIL;
> > +
> > +    if (fsync (fdout) != 0) {
> > +	fprintf (stderr, "Error: fsync failed: %s\n", strerror (errno));
> > +	goto FAIL;
> > +    }
> > +
> > +    close (fdout);
> > +    fdout = -1;
> > +
> > +    /* Atomically move the new message file from the Maildir 'tmp' directory
> > +     * to the 'new' directory.  We follow the Dovecot recommendation to
> > +     * simply use rename() instead of link() and unlink().
> > +     * See also: http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery
> > +     */
> > +    if (rename (tmppath, newpath) != 0) {
> > +	fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));
> > +	goto FAIL;
> > +    }
> > +
> > +    if (! sync_dir (newdir))
> > +	goto FAIL;
> 
> I think cleanup_path needs to be updated before the sync_dir is test as
> newpath should be unlinked rather than oldpath. (v4 explicitly unlinked newpath)
> 

Thanks for the continued close review.
I'll post a followup to this specific patch.

Peter

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v7b] cli: add insert command
  2013-06-23 12:19     ` Peter Wang
@ 2013-06-23 12:20       ` Peter Wang
  2013-06-24 19:02         ` Mark Walters
  0 siblings, 1 reply; 20+ messages in thread
From: Peter Wang @ 2013-06-23 12:20 UTC (permalink / raw)
  To: notmuch

The notmuch insert command reads a message from standard input,
writes it to a Maildir folder, and then incorporates the message into
the notmuch database.  Essentially it moves the functionality of
notmuch-deliver into notmuch.

Though it could be used as an alternative to notmuch new, the reason
I want this is to allow my notmuch frontend to add postponed or sent
messages to the mail store and notmuch database, without resorting to
another tool (e.g. notmuch-deliver) nor directly modifying the maildir.
---
 Makefile.local   |   1 +
 notmuch-client.h |   3 +
 notmuch-insert.c | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 notmuch.c        |   2 +
 4 files changed, 343 insertions(+)
 create mode 100644 notmuch-insert.c

diff --git a/Makefile.local b/Makefile.local
index 644623f..84043fe 100644
--- a/Makefile.local
+++ b/Makefile.local
@@ -261,6 +261,7 @@ notmuch_client_srcs =		\
 	notmuch-config.c	\
 	notmuch-count.c		\
 	notmuch-dump.c		\
+	notmuch-insert.c	\
 	notmuch-new.c		\
 	notmuch-reply.c		\
 	notmuch-restore.c	\
diff --git a/notmuch-client.h b/notmuch-client.h
index 4a3c7ac..dfe81e6 100644
--- a/notmuch-client.h
+++ b/notmuch-client.h
@@ -181,6 +181,9 @@ int
 notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]);
 
 int
+notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);
+
+int
 notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);
 
 int
diff --git a/notmuch-insert.c b/notmuch-insert.c
new file mode 100644
index 0000000..1228afa
--- /dev/null
+++ b/notmuch-insert.c
@@ -0,0 +1,337 @@
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2013 Peter Wang
+ *
+ * Based in part on notmuch-deliver
+ * Copyright © 2010 Ali Polatel
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Peter Wang <novalazy@gmail.com>
+ */
+
+#include "notmuch-client.h"
+#include "tag-util.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+static volatile sig_atomic_t interrupted;
+
+static void
+handle_sigint (unused (int sig))
+{
+    static char msg[] = "Stopping...         \n";
+
+    /* This write is "opportunistic", so it's okay to ignore the
+     * result.  It is not required for correctness, and if it does
+     * fail or produce a short write, we want to get out of the signal
+     * handler as quickly as possible, not retry it. */
+    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
+    interrupted = 1;
+}
+
+/* Like gethostname but guarantees that a null-terminated hostname is
+ * returned, even if it has to make one up. Invalid characters are
+ * substituted such that the hostname can be used within a filename.
+ */
+static void
+safe_gethostname (char *hostname, size_t len)
+{
+    char *p;
+
+    if (gethostname (hostname, len) == -1) {
+	strncpy (hostname, "unknown", len);
+    }
+    hostname[len - 1] = '\0';
+
+    for (p = hostname; *p != '\0'; p++) {
+	if (*p == '/' || *p == ':')
+	    *p = '_';
+    }
+}
+
+/* Call fsync() on a directory path. */
+static notmuch_bool_t
+sync_dir (const char *dir)
+{
+    notmuch_bool_t ret;
+    int fd;
+
+    fd = open (dir, O_RDONLY);
+    if (fd == -1) {
+	fprintf (stderr, "Error: open() dir failed: %s\n", strerror (errno));
+	return FALSE;
+    }
+    ret = (fsync (fd) == 0);
+    if (! ret) {
+	fprintf (stderr, "Error: fsync() dir failed: %s\n", strerror (errno));
+    }
+    close (fd);
+    return ret;
+}
+
+/* Open a unique file in the 'tmp' sub-directory of dir.
+ * Returns the file descriptor on success, or -1 on failure.
+ * On success, file paths for the message in the 'tmp' and 'new'
+ * directories are returned via tmppath and newpath,
+ * and the path of the 'new' directory itself in newdir. */
+static int
+maildir_open_tmp_file (void *ctx, const char *dir,
+		       char **tmppath, char **newpath, char **newdir)
+{
+    pid_t pid;
+    char hostname[256];
+    struct timeval tv;
+    char *filename;
+    int fd = -1;
+
+    /* We follow the Dovecot file name generation algorithm. */
+    pid = getpid ();
+    safe_gethostname (hostname, sizeof (hostname));
+    do {
+	gettimeofday (&tv, NULL);
+	filename = talloc_asprintf (ctx, "%ld.M%ldP%d.%s",
+				    tv.tv_sec, tv.tv_usec, pid, hostname);
+	if (! filename) {
+	    fprintf (stderr, "Out of memory\n");
+	    return -1;
+	}
+
+	*tmppath = talloc_asprintf (ctx, "%s/tmp/%s", dir, filename);
+	if (! *tmppath) {
+	    fprintf (stderr, "Out of memory\n");
+	    return -1;
+	}
+
+	fd = open (*tmppath, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
+    } while (fd == -1 && errno == EEXIST);
+
+    if (fd == -1) {
+	fprintf (stderr, "Error: opening %s: %s\n", *tmppath, strerror (errno));
+	return -1;
+    }
+
+    *newdir = talloc_asprintf (ctx, "%s/new", dir);
+    *newpath = talloc_asprintf (ctx, "%s/new/%s", dir, filename);
+    if (! *newdir || ! *newpath) {
+	fprintf (stderr, "Out of memory\n");
+	close (fd);
+	unlink (*tmppath);
+	return -1;
+    }
+
+    talloc_free (filename);
+
+    return fd;
+}
+
+/* Copy the contents of standard input (fdin) into fdout.
+ * Returns TRUE if a non-empty file was written successfully.
+ * Otherwise, return FALSE. */
+static notmuch_bool_t
+copy_stdin (int fdin, int fdout)
+{
+    notmuch_bool_t empty = TRUE;
+
+    while (! interrupted) {
+	ssize_t remain;
+	char buf[4096];
+	char *p;
+
+	remain = read (fdin, buf, sizeof (buf));
+	if (remain == 0)
+	    break;
+	if (remain < 0) {
+	    if (errno == EINTR)
+		continue;
+	    fprintf (stderr, "Error: reading from standard input: %s\n",
+		     strerror (errno));
+	    return FALSE;
+	}
+
+	p = buf;
+	do {
+	    ssize_t written = write (fdout, p, remain);
+	    if (written < 0 && errno == EINTR)
+		continue;
+	    if (written <= 0) {
+		fprintf (stderr, "Error: writing to temporary file: %s",
+			 strerror (errno));
+		return FALSE;
+	    }
+	    p += written;
+	    remain -= written;
+	    empty = FALSE;
+	} while (remain > 0);
+    }
+
+    return (!interrupted && !empty);
+}
+
+/* Add the specified message file to the notmuch database, applying tags.
+ * The file is renamed to encode notmuch tags as maildir flags. */
+static void
+add_file_to_database (notmuch_database_t *notmuch, const char *path,
+		      tag_op_list_t *tag_ops)
+{
+    notmuch_message_t *message;
+    notmuch_status_t status;
+
+    status = notmuch_database_add_message (notmuch, path, &message);
+    switch (status) {
+    case NOTMUCH_STATUS_SUCCESS:
+    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
+	break;
+    default:
+    case NOTMUCH_STATUS_FILE_NOT_EMAIL:
+    case NOTMUCH_STATUS_READ_ONLY_DATABASE:
+    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
+    case NOTMUCH_STATUS_OUT_OF_MEMORY:
+    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_UNBALANCED_ATOMIC:
+    case NOTMUCH_STATUS_LAST_STATUS:
+	fprintf (stderr, "Error: failed to add `%s' to notmuch database: %s\n",
+		 path, notmuch_status_to_string (status));
+	return;
+    }
+
+    if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
+	/* Don't change tags of an existing message. */
+	status = notmuch_message_tags_to_maildir_flags (message);
+	if (status != NOTMUCH_STATUS_SUCCESS)
+	    fprintf (stderr, "Error: failed to sync tags to maildir flags\n");
+    } else {
+	tag_op_list_apply (message, tag_ops, TAG_FLAG_MAILDIR_SYNC);
+    }
+
+    notmuch_message_destroy (message);
+}
+
+static notmuch_bool_t
+insert_message (void *ctx, notmuch_database_t *notmuch, int fdin,
+		const char *dir, tag_op_list_t *tag_ops)
+{
+    char *tmppath;
+    char *newpath;
+    char *newdir;
+    int fdout;
+    char *cleanup_path;
+
+    fdout = maildir_open_tmp_file (ctx, dir, &tmppath, &newpath, &newdir);
+    if (fdout < 0)
+	return FALSE;
+
+    cleanup_path = tmppath;
+
+    if (! copy_stdin (fdin, fdout))
+	goto FAIL;
+
+    if (fsync (fdout) != 0) {
+	fprintf (stderr, "Error: fsync failed: %s\n", strerror (errno));
+	goto FAIL;
+    }
+
+    close (fdout);
+    fdout = -1;
+
+    /* Atomically move the new message file from the Maildir 'tmp' directory
+     * to the 'new' directory.  We follow the Dovecot recommendation to
+     * simply use rename() instead of link() and unlink().
+     * See also: http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery
+     */
+    if (rename (tmppath, newpath) != 0) {
+	fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));
+	goto FAIL;
+    }
+
+    cleanup_path = newpath;
+
+    if (! sync_dir (newdir))
+	goto FAIL;
+
+    /* Even if adding the message to the notmuch database fails,
+     * the message is on disk and we consider the delivery completed. */
+    add_file_to_database (notmuch, newpath, tag_ops);
+
+    return TRUE;
+
+  FAIL:
+    if (fdout >= 0)
+	close (fdout);
+    unlink (cleanup_path);
+    return FALSE;
+}
+
+int
+notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
+{
+    notmuch_database_t *notmuch;
+    struct sigaction action;
+    const char *db_path;
+    const char **new_tags;
+    size_t new_tags_length;
+    tag_op_list_t *tag_ops;
+    char *query_string = NULL;
+    const char *maildir;
+    int opt_index = 1;
+    unsigned int i;
+    notmuch_bool_t ret;
+
+    db_path = notmuch_config_get_database_path (config);
+    new_tags = notmuch_config_get_new_tags (config, &new_tags_length);
+
+    tag_ops = tag_op_list_create (config);
+    if (tag_ops == NULL) {
+	fprintf (stderr, "Out of memory.\n");
+	return 1;
+    }
+    for (i = 0; i < new_tags_length; i++) {
+	if (tag_op_list_append (tag_ops, new_tags[i], FALSE))
+	    return 1;
+    }
+
+    if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,
+				&query_string, tag_ops))
+	return 1;
+
+    if (*query_string != '\0') {
+	fprintf (stderr, "Error: unexpected query string: %s\n", query_string);
+	return 1;
+    }
+
+    maildir = db_path;
+
+    /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying
+     * from standard input may be interrupted. */
+    memset (&action, 0, sizeof (struct sigaction));
+    action.sa_handler = handle_sigint;
+    sigemptyset (&action.sa_mask);
+    action.sa_flags = 0;
+    sigaction (SIGINT, &action, NULL);
+
+    if (notmuch_database_open (notmuch_config_get_database_path (config),
+			       NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
+	return 1;
+
+    ret = insert_message (config, notmuch, STDIN_FILENO, maildir, tag_ops);
+
+    notmuch_database_destroy (notmuch);
+
+    return (ret) ? 0 : 1;
+}
diff --git a/notmuch.c b/notmuch.c
index 45a73ce..d11f214 100644
--- a/notmuch.c
+++ b/notmuch.c
@@ -44,6 +44,8 @@ static command_t commands[] = {
       "Interactively setup notmuch for first use." },
     { "new", notmuch_new_command, FALSE,
       "Find and import new messages to the notmuch database." },
+    { "insert", notmuch_insert_command, FALSE,
+      "Add a new message into the maildir and notmuch database." },
     { "search", notmuch_search_command, FALSE,
       "Search for messages matching the given search terms." },
     { "show", notmuch_show_command, FALSE,
-- 
1.7.12.1

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v7b] cli: add insert command
  2013-06-23 12:20       ` [PATCH v7b] " Peter Wang
@ 2013-06-24 19:02         ` Mark Walters
  0 siblings, 0 replies; 20+ messages in thread
From: Mark Walters @ 2013-06-24 19:02 UTC (permalink / raw)
  To: Peter Wang, notmuch


Just to confirm my +1 for this series with patch 7b.

Mark

On Sun, 23 Jun 2013, Peter Wang <novalazy@gmail.com> wrote:
> The notmuch insert command reads a message from standard input,
> writes it to a Maildir folder, and then incorporates the message into
> the notmuch database.  Essentially it moves the functionality of
> notmuch-deliver into notmuch.
>
> Though it could be used as an alternative to notmuch new, the reason
> I want this is to allow my notmuch frontend to add postponed or sent
> messages to the mail store and notmuch database, without resorting to
> another tool (e.g. notmuch-deliver) nor directly modifying the maildir.
> ---
>  Makefile.local   |   1 +
>  notmuch-client.h |   3 +
>  notmuch-insert.c | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  notmuch.c        |   2 +
>  4 files changed, 343 insertions(+)
>  create mode 100644 notmuch-insert.c
>
> diff --git a/Makefile.local b/Makefile.local
> index 644623f..84043fe 100644
> --- a/Makefile.local
> +++ b/Makefile.local
> @@ -261,6 +261,7 @@ notmuch_client_srcs =		\
>  	notmuch-config.c	\
>  	notmuch-count.c		\
>  	notmuch-dump.c		\
> +	notmuch-insert.c	\
>  	notmuch-new.c		\
>  	notmuch-reply.c		\
>  	notmuch-restore.c	\
> diff --git a/notmuch-client.h b/notmuch-client.h
> index 4a3c7ac..dfe81e6 100644
> --- a/notmuch-client.h
> +++ b/notmuch-client.h
> @@ -181,6 +181,9 @@ int
>  notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]);
>  
>  int
> +notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);
> +
> +int
>  notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);
>  
>  int
> diff --git a/notmuch-insert.c b/notmuch-insert.c
> new file mode 100644
> index 0000000..1228afa
> --- /dev/null
> +++ b/notmuch-insert.c
> @@ -0,0 +1,337 @@
> +/* notmuch - Not much of an email program, (just index and search)
> + *
> + * Copyright © 2013 Peter Wang
> + *
> + * Based in part on notmuch-deliver
> + * Copyright © 2010 Ali Polatel
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation, either version 3 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see http://www.gnu.org/licenses/ .
> + *
> + * Author: Peter Wang <novalazy@gmail.com>
> + */
> +
> +#include "notmuch-client.h"
> +#include "tag-util.h"
> +
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <fcntl.h>
> +
> +static volatile sig_atomic_t interrupted;
> +
> +static void
> +handle_sigint (unused (int sig))
> +{
> +    static char msg[] = "Stopping...         \n";
> +
> +    /* This write is "opportunistic", so it's okay to ignore the
> +     * result.  It is not required for correctness, and if it does
> +     * fail or produce a short write, we want to get out of the signal
> +     * handler as quickly as possible, not retry it. */
> +    IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
> +    interrupted = 1;
> +}
> +
> +/* Like gethostname but guarantees that a null-terminated hostname is
> + * returned, even if it has to make one up. Invalid characters are
> + * substituted such that the hostname can be used within a filename.
> + */
> +static void
> +safe_gethostname (char *hostname, size_t len)
> +{
> +    char *p;
> +
> +    if (gethostname (hostname, len) == -1) {
> +	strncpy (hostname, "unknown", len);
> +    }
> +    hostname[len - 1] = '\0';
> +
> +    for (p = hostname; *p != '\0'; p++) {
> +	if (*p == '/' || *p == ':')
> +	    *p = '_';
> +    }
> +}
> +
> +/* Call fsync() on a directory path. */
> +static notmuch_bool_t
> +sync_dir (const char *dir)
> +{
> +    notmuch_bool_t ret;
> +    int fd;
> +
> +    fd = open (dir, O_RDONLY);
> +    if (fd == -1) {
> +	fprintf (stderr, "Error: open() dir failed: %s\n", strerror (errno));
> +	return FALSE;
> +    }
> +    ret = (fsync (fd) == 0);
> +    if (! ret) {
> +	fprintf (stderr, "Error: fsync() dir failed: %s\n", strerror (errno));
> +    }
> +    close (fd);
> +    return ret;
> +}
> +
> +/* Open a unique file in the 'tmp' sub-directory of dir.
> + * Returns the file descriptor on success, or -1 on failure.
> + * On success, file paths for the message in the 'tmp' and 'new'
> + * directories are returned via tmppath and newpath,
> + * and the path of the 'new' directory itself in newdir. */
> +static int
> +maildir_open_tmp_file (void *ctx, const char *dir,
> +		       char **tmppath, char **newpath, char **newdir)
> +{
> +    pid_t pid;
> +    char hostname[256];
> +    struct timeval tv;
> +    char *filename;
> +    int fd = -1;
> +
> +    /* We follow the Dovecot file name generation algorithm. */
> +    pid = getpid ();
> +    safe_gethostname (hostname, sizeof (hostname));
> +    do {
> +	gettimeofday (&tv, NULL);
> +	filename = talloc_asprintf (ctx, "%ld.M%ldP%d.%s",
> +				    tv.tv_sec, tv.tv_usec, pid, hostname);
> +	if (! filename) {
> +	    fprintf (stderr, "Out of memory\n");
> +	    return -1;
> +	}
> +
> +	*tmppath = talloc_asprintf (ctx, "%s/tmp/%s", dir, filename);
> +	if (! *tmppath) {
> +	    fprintf (stderr, "Out of memory\n");
> +	    return -1;
> +	}
> +
> +	fd = open (*tmppath, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
> +    } while (fd == -1 && errno == EEXIST);
> +
> +    if (fd == -1) {
> +	fprintf (stderr, "Error: opening %s: %s\n", *tmppath, strerror (errno));
> +	return -1;
> +    }
> +
> +    *newdir = talloc_asprintf (ctx, "%s/new", dir);
> +    *newpath = talloc_asprintf (ctx, "%s/new/%s", dir, filename);
> +    if (! *newdir || ! *newpath) {
> +	fprintf (stderr, "Out of memory\n");
> +	close (fd);
> +	unlink (*tmppath);
> +	return -1;
> +    }
> +
> +    talloc_free (filename);
> +
> +    return fd;
> +}
> +
> +/* Copy the contents of standard input (fdin) into fdout.
> + * Returns TRUE if a non-empty file was written successfully.
> + * Otherwise, return FALSE. */
> +static notmuch_bool_t
> +copy_stdin (int fdin, int fdout)
> +{
> +    notmuch_bool_t empty = TRUE;
> +
> +    while (! interrupted) {
> +	ssize_t remain;
> +	char buf[4096];
> +	char *p;
> +
> +	remain = read (fdin, buf, sizeof (buf));
> +	if (remain == 0)
> +	    break;
> +	if (remain < 0) {
> +	    if (errno == EINTR)
> +		continue;
> +	    fprintf (stderr, "Error: reading from standard input: %s\n",
> +		     strerror (errno));
> +	    return FALSE;
> +	}
> +
> +	p = buf;
> +	do {
> +	    ssize_t written = write (fdout, p, remain);
> +	    if (written < 0 && errno == EINTR)
> +		continue;
> +	    if (written <= 0) {
> +		fprintf (stderr, "Error: writing to temporary file: %s",
> +			 strerror (errno));
> +		return FALSE;
> +	    }
> +	    p += written;
> +	    remain -= written;
> +	    empty = FALSE;
> +	} while (remain > 0);
> +    }
> +
> +    return (!interrupted && !empty);
> +}
> +
> +/* Add the specified message file to the notmuch database, applying tags.
> + * The file is renamed to encode notmuch tags as maildir flags. */
> +static void
> +add_file_to_database (notmuch_database_t *notmuch, const char *path,
> +		      tag_op_list_t *tag_ops)
> +{
> +    notmuch_message_t *message;
> +    notmuch_status_t status;
> +
> +    status = notmuch_database_add_message (notmuch, path, &message);
> +    switch (status) {
> +    case NOTMUCH_STATUS_SUCCESS:
> +    case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:
> +	break;
> +    default:
> +    case NOTMUCH_STATUS_FILE_NOT_EMAIL:
> +    case NOTMUCH_STATUS_READ_ONLY_DATABASE:
> +    case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
> +    case NOTMUCH_STATUS_OUT_OF_MEMORY:
> +    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_UNBALANCED_ATOMIC:
> +    case NOTMUCH_STATUS_LAST_STATUS:
> +	fprintf (stderr, "Error: failed to add `%s' to notmuch database: %s\n",
> +		 path, notmuch_status_to_string (status));
> +	return;
> +    }
> +
> +    if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
> +	/* Don't change tags of an existing message. */
> +	status = notmuch_message_tags_to_maildir_flags (message);
> +	if (status != NOTMUCH_STATUS_SUCCESS)
> +	    fprintf (stderr, "Error: failed to sync tags to maildir flags\n");
> +    } else {
> +	tag_op_list_apply (message, tag_ops, TAG_FLAG_MAILDIR_SYNC);
> +    }
> +
> +    notmuch_message_destroy (message);
> +}
> +
> +static notmuch_bool_t
> +insert_message (void *ctx, notmuch_database_t *notmuch, int fdin,
> +		const char *dir, tag_op_list_t *tag_ops)
> +{
> +    char *tmppath;
> +    char *newpath;
> +    char *newdir;
> +    int fdout;
> +    char *cleanup_path;
> +
> +    fdout = maildir_open_tmp_file (ctx, dir, &tmppath, &newpath, &newdir);
> +    if (fdout < 0)
> +	return FALSE;
> +
> +    cleanup_path = tmppath;
> +
> +    if (! copy_stdin (fdin, fdout))
> +	goto FAIL;
> +
> +    if (fsync (fdout) != 0) {
> +	fprintf (stderr, "Error: fsync failed: %s\n", strerror (errno));
> +	goto FAIL;
> +    }
> +
> +    close (fdout);
> +    fdout = -1;
> +
> +    /* Atomically move the new message file from the Maildir 'tmp' directory
> +     * to the 'new' directory.  We follow the Dovecot recommendation to
> +     * simply use rename() instead of link() and unlink().
> +     * See also: http://wiki.dovecot.org/MailboxFormat/Maildir#Mail_delivery
> +     */
> +    if (rename (tmppath, newpath) != 0) {
> +	fprintf (stderr, "Error: rename() failed: %s\n", strerror (errno));
> +	goto FAIL;
> +    }
> +
> +    cleanup_path = newpath;
> +
> +    if (! sync_dir (newdir))
> +	goto FAIL;
> +
> +    /* Even if adding the message to the notmuch database fails,
> +     * the message is on disk and we consider the delivery completed. */
> +    add_file_to_database (notmuch, newpath, tag_ops);
> +
> +    return TRUE;
> +
> +  FAIL:
> +    if (fdout >= 0)
> +	close (fdout);
> +    unlink (cleanup_path);
> +    return FALSE;
> +}
> +
> +int
> +notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
> +{
> +    notmuch_database_t *notmuch;
> +    struct sigaction action;
> +    const char *db_path;
> +    const char **new_tags;
> +    size_t new_tags_length;
> +    tag_op_list_t *tag_ops;
> +    char *query_string = NULL;
> +    const char *maildir;
> +    int opt_index = 1;
> +    unsigned int i;
> +    notmuch_bool_t ret;
> +
> +    db_path = notmuch_config_get_database_path (config);
> +    new_tags = notmuch_config_get_new_tags (config, &new_tags_length);
> +
> +    tag_ops = tag_op_list_create (config);
> +    if (tag_ops == NULL) {
> +	fprintf (stderr, "Out of memory.\n");
> +	return 1;
> +    }
> +    for (i = 0; i < new_tags_length; i++) {
> +	if (tag_op_list_append (tag_ops, new_tags[i], FALSE))
> +	    return 1;
> +    }
> +
> +    if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,
> +				&query_string, tag_ops))
> +	return 1;
> +
> +    if (*query_string != '\0') {
> +	fprintf (stderr, "Error: unexpected query string: %s\n", query_string);
> +	return 1;
> +    }
> +
> +    maildir = db_path;
> +
> +    /* Setup our handler for SIGINT. We do not set SA_RESTART so that copying
> +     * from standard input may be interrupted. */
> +    memset (&action, 0, sizeof (struct sigaction));
> +    action.sa_handler = handle_sigint;
> +    sigemptyset (&action.sa_mask);
> +    action.sa_flags = 0;
> +    sigaction (SIGINT, &action, NULL);
> +
> +    if (notmuch_database_open (notmuch_config_get_database_path (config),
> +			       NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
> +	return 1;
> +
> +    ret = insert_message (config, notmuch, STDIN_FILENO, maildir, tag_ops);
> +
> +    notmuch_database_destroy (notmuch);
> +
> +    return (ret) ? 0 : 1;
> +}
> diff --git a/notmuch.c b/notmuch.c
> index 45a73ce..d11f214 100644
> --- a/notmuch.c
> +++ b/notmuch.c
> @@ -44,6 +44,8 @@ static command_t commands[] = {
>        "Interactively setup notmuch for first use." },
>      { "new", notmuch_new_command, FALSE,
>        "Find and import new messages to the notmuch database." },
> +    { "insert", notmuch_insert_command, FALSE,
> +      "Add a new message into the maildir and notmuch database." },
>      { "search", notmuch_search_command, FALSE,
>        "Search for messages matching the given search terms." },
>      { "show", notmuch_show_command, FALSE,
> -- 
> 1.7.12.1
>
> _______________________________________________
> notmuch mailing list
> notmuch@notmuchmail.org
> http://notmuchmail.org/mailman/listinfo/notmuch

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 06/12] test: add tests for insert
  2013-06-23  4:23 ` [PATCH v7 06/12] test: add tests for insert Peter Wang
@ 2013-06-30 14:44   ` David Bremner
  0 siblings, 0 replies; 20+ messages in thread
From: David Bremner @ 2013-06-30 14:44 UTC (permalink / raw)
  To: Peter Wang, notmuch

Peter Wang <novalazy@gmail.com> writes:

> Add tests for new 'insert' command.
> ---

I have pushed the first 6 patches in this series.

d

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 09/12] test: test insert --folder option
  2013-06-23  4:24 ` [PATCH v7 09/12] test: test " Peter Wang
@ 2013-07-01 15:29   ` David Bremner
  0 siblings, 0 replies; 20+ messages in thread
From: David Bremner @ 2013-07-01 15:29 UTC (permalink / raw)
  To: Peter Wang, notmuch

Peter Wang <novalazy@gmail.com> writes:

> Add tests for notmuch insert --folder option.

Pushed the next 3 patches in this series.

d

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v7 12/12] test: test insert --create-folder option
  2013-06-23  4:24 ` [PATCH v7 12/12] test: test insert --create-folder option Peter Wang
@ 2013-07-04  3:33   ` David Bremner
  0 siblings, 0 replies; 20+ messages in thread
From: David Bremner @ 2013-07-04  3:33 UTC (permalink / raw)
  To: Peter Wang, notmuch

Peter Wang <novalazy@gmail.com> writes:

> Add tests for notmuch insert --create-folder option.
> ---

Pushed the last 3. It would be good to have a NEWS patch.

d

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2013-07-04  3:34 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-06-23  4:23 [PATCH v7 00/12] insert command Peter Wang
2013-06-23  4:23 ` [PATCH v7 01/12] tag-util: move out 'tag' command-line check Peter Wang
2013-06-23  4:23 ` [PATCH v7 02/12] tag-util: do not reset list in parse_tag_command_line Peter Wang
2013-06-23  4:23 ` [PATCH v7 03/12] cli: add insert command Peter Wang
2013-06-23  6:42   ` Mark Walters
2013-06-23 12:19     ` Peter Wang
2013-06-23 12:20       ` [PATCH v7b] " Peter Wang
2013-06-24 19:02         ` Mark Walters
2013-06-23  4:23 ` [PATCH v7 04/12] man: document 'insert' command Peter Wang
2013-06-23  4:23 ` [PATCH v7 05/12] man: reference notmuch-insert.1 Peter Wang
2013-06-23  4:23 ` [PATCH v7 06/12] test: add tests for insert Peter Wang
2013-06-30 14:44   ` David Bremner
2013-06-23  4:24 ` [PATCH v7 07/12] insert: add --folder option Peter Wang
2013-06-23  4:24 ` [PATCH v7 08/12] man: document insert " Peter Wang
2013-06-23  4:24 ` [PATCH v7 09/12] test: test " Peter Wang
2013-07-01 15:29   ` David Bremner
2013-06-23  4:24 ` [PATCH v7 10/12] insert: add --create-folder option Peter Wang
2013-06-23  4:24 ` [PATCH v7 11/12] man: document insert --create-folder Peter Wang
2013-06-23  4:24 ` [PATCH v7 12/12] test: test insert --create-folder option Peter Wang
2013-07-04  3:33   ` David Bremner

Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).