Re: [PATCH 2/3] new: Add all initial tags at once
[notmuch-archives.git] / 80 / 1918bff95b40df949fe959a4cbd60a01c7ea02
1 Return-Path: <sojkam1@fel.cvut.cz>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id EAFAA40496B\r
6         for <notmuch@notmuchmail.org>; Thu, 18 Mar 2010 08:40:56 -0700 (PDT)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -2.267\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-2.267 tagged_above=-999 required=5 tests=[AWL=0.332,\r
12         BAYES_00=-2.599] autolearn=ham\r
13 Received: from olra.theworths.org ([127.0.0.1])\r
14         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
15         with ESMTP id O22AJJFgyTIw for <notmuch@notmuchmail.org>;\r
16         Thu, 18 Mar 2010 08:40:54 -0700 (PDT)\r
17 Received: from max.feld.cvut.cz (max.feld.cvut.cz [147.32.192.36])\r
18         by olra.theworths.org (Postfix) with ESMTP id 4A33A404963\r
19         for <notmuch@notmuchmail.org>; Thu, 18 Mar 2010 08:40:54 -0700 (PDT)\r
20 Received: from localhost (unknown [192.168.200.4])\r
21         by max.feld.cvut.cz (Postfix) with ESMTP id B4B5B19F33D7;\r
22         Thu, 18 Mar 2010 16:40:53 +0100 (CET)\r
23 X-Virus-Scanned: IMAP AMAVIS\r
24 Received: from max.feld.cvut.cz ([192.168.200.1])\r
25         by localhost (styx.feld.cvut.cz [192.168.200.4]) (amavisd-new,\r
26         port 10044)\r
27         with ESMTP id Mui2WfO4RWL8; Thu, 18 Mar 2010 16:40:51 +0100 (CET)\r
28 Received: from imap.feld.cvut.cz (imap.feld.cvut.cz [147.32.192.34])\r
29         by max.feld.cvut.cz (Postfix) with ESMTP id 9BB9D19F3409;\r
30         Thu, 18 Mar 2010 16:40:51 +0100 (CET)\r
31 Received: from steelpick.localdomain (k335-30.felk.cvut.cz [147.32.86.30])\r
32         (Authenticated sender: sojkam1)\r
33         by imap.feld.cvut.cz (Postfix) with ESMTPSA id 74A60FA003;\r
34         Thu, 18 Mar 2010 16:40:51 +0100 (CET)\r
35 Received: from wsh by steelpick.localdomain with local (Exim 4.71)\r
36         (envelope-from <sojkam1@fel.cvut.cz>)\r
37         id 1NsHq7-0005Hl-71; Thu, 18 Mar 2010 16:40:51 +0100\r
38 From: Michal Sojka <sojkam1@fel.cvut.cz>\r
39 To: notmuch@notmuchmail.org\r
40 Date: Thu, 18 Mar 2010 16:39:39 +0100\r
41 Message-Id: <1268926780-20045-4-git-send-email-sojkam1@fel.cvut.cz>\r
42 X-Mailer: git-send-email 1.7.0\r
43 In-Reply-To: <1268926780-20045-1-git-send-email-sojkam1@fel.cvut.cz>\r
44 References: <1268926780-20045-1-git-send-email-sojkam1@fel.cvut.cz>\r
45 Subject: [notmuch] [PATCH 3/4] Add maildir-based mailstore\r
46 X-BeenThere: notmuch@notmuchmail.org\r
47 X-Mailman-Version: 2.1.13\r
48 Precedence: list\r
49 List-Id: "Use and development of the notmuch mail system."\r
50         <notmuch.notmuchmail.org>\r
51 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
52         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
53 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
54 List-Post: <mailto:notmuch@notmuchmail.org>\r
55 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
56 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
57         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
58 X-List-Received-Date: Thu, 18 Mar 2010 15:40:57 -0000\r
59 \r
60 This mailstore allows bi-directional synchronization between maildir\r
61 flags and certain tags. The flag-to-tag mapping is defined by flag2tag\r
62 array.\r
63 \r
64 The synchronization works this way:\r
65 \r
66 1) Whenever notmuch new is executed, then for every new/renamed\r
67    message the tags defined in flag2tag are either added or removed\r
68    depending on the flags from the file name.\r
69 \r
70 2) Whenever notmuch tag is executed, a new set of flags based on the\r
71    tags is constructed and if the new flags differ from that stored in\r
72    the file name, the file is renamed and notmuch database is updated\r
73    to contain the new name for the file.\r
74 \r
75 This mailstore is enabled by putting\r
76     [mailstore]\r
77     type=maildir\r
78 to your .notmuch-config.\r
79 \r
80 Signed-off-by: Michal Sojka <sojkam1@fel.cvut.cz>\r
81 ---\r
82  lib/database.cc       |    7 ++\r
83  lib/mailstore-files.c |  167 ++++++++++++++++++++++++++++++++++++++++++++++++-\r
84  lib/mailstore.c       |    1 +\r
85  lib/message.cc        |   41 ++++++++++++-\r
86  lib/notmuch-private.h |    4 +\r
87  lib/notmuch.h         |    1 +\r
88  6 files changed, 217 insertions(+), 4 deletions(-)\r
89 \r
90 diff --git a/lib/database.cc b/lib/database.cc\r
91 index 93c8d0f..33ef889 100644\r
92 --- a/lib/database.cc\r
93 +++ b/lib/database.cc\r
94 @@ -1471,6 +1471,13 @@ notmuch_database_add_message (notmuch_database_t *notmuch,\r
95  \r
96         _notmuch_message_add_filename (message, filename);\r
97  \r
98 +       /* This is a new message or it has a new filename and as such,\r
99 +        * its tags in database either do not exists or might be out\r
100 +        * of date. Mailstore assigns the tags later in index_new(),\r
101 +        * but until then we should not synchronize the tags back to\r
102 +        * the mailstore. */\r
103 +       notmuch_message_set_flag(message, NOTMUCH_MESSAGE_FLAG_TAGS_INVALID, TRUE);\r
104 +\r
105         /* Is this a newly created message object? */\r
106         if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {\r
107             _notmuch_message_add_term (message, "type", "mail");\r
108 diff --git a/lib/mailstore-files.c b/lib/mailstore-files.c\r
109 index ace2664..d89e183 100644\r
110 --- a/lib/mailstore-files.c\r
111 +++ b/lib/mailstore-files.c\r
112 @@ -24,6 +24,9 @@\r
113  #include "notmuch.h"\r
114  #include "mailstore-private.h"\r
115  #include <dirent.h>\r
116 +#include <stdbool.h>\r
117 +\r
118 +#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))\r
119  \r
120  typedef struct _filename_node {\r
121      char *filename;\r
122 @@ -69,8 +72,9 @@ _filename_list_add (_filename_list_t *list,\r
123  }\r
124  \r
125  static void\r
126 -tag_inbox_and_unread (notmuch_message_t *message)\r
127 +tag_inbox_and_unread (notmuch_message_t *message, const char *filename)\r
128  {\r
129 +    (void)filename;\r
130      notmuch_message_add_tag (message, "inbox");\r
131      notmuch_message_add_tag (message, "unread");\r
132  }\r
133 @@ -117,6 +121,141 @@ _entries_resemble_maildir (struct dirent **entries, int count)\r
134  }\r
135  \r
136  \r
137 +struct mailstore_priv {\r
138 +    void (*tag_new)(notmuch_message_t *message, const char *filename);\r
139 +    void (*tag_renamed)(notmuch_message_t *message, const char *filename);\r
140 +};\r
141 +\r
142 +struct maildir_flag_tag {\r
143 +    char flag;\r
144 +    const char *tag;\r
145 +    bool inverse;\r
146 +};\r
147 +\r
148 +/* ASCII ordered table of Maildir flags and assiciated tags */\r
149 +struct maildir_flag_tag flag2tag[] = {\r
150 +    { 'D', "draft",   false},\r
151 +    { 'F', "flagged", false},\r
152 +    { 'P', "passed",  false},\r
153 +    { 'R', "replied", false},\r
154 +    { 'S', "unread",  true },\r
155 +    { 'T', "delete",  false},\r
156 +};\r
157 +\r
158 +static void\r
159 +tag_from_maildir_flags(notmuch_message_t *message, const char *filename)\r
160 +{\r
161 +    const char *flags, *p;\r
162 +    char f;\r
163 +    bool valid, unread;\r
164 +    unsigned i;\r
165 +\r
166 +    flags = strstr(filename, ":2,");\r
167 +    if (!flags)\r
168 +       return;\r
169 +    flags += 3;\r
170 +\r
171 +    /*  Check that the letters are valid Maildir flags */\r
172 +    f = 0;\r
173 +    valid = true;\r
174 +    for (p=flags; valid && *p; p++) {\r
175 +       switch (*p) {\r
176 +       case 'P':\r
177 +       case 'R':\r
178 +       case 'S':\r
179 +       case 'T':\r
180 +       case 'D':\r
181 +       case 'F':\r
182 +           if (*p > f) f=*p;\r
183 +           else valid = false;\r
184 +       break;\r
185 +       default:\r
186 +           valid = false;\r
187 +       }\r
188 +    }\r
189 +    if (!valid) {\r
190 +       /* fprintf(stderr, "Note: Invalid maildir flags: %s\n", message->filename); */\r
191 +       return;\r
192 +    }\r
193 +\r
194 +    notmuch_message_freeze(message);\r
195 +    unread = true;\r
196 +    for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {\r
197 +       if ((strchr(flags, flag2tag[i].flag) != NULL) ^ flag2tag[i].inverse) {\r
198 +           notmuch_message_add_tag (message, flag2tag[i].tag);\r
199 +       } else {\r
200 +           notmuch_message_remove_tag (message, flag2tag[i].tag);\r
201 +       }\r
202 +    }\r
203 +    notmuch_message_thaw(message);\r
204 +\r
205 +    /* From now on, we can synchronize the tags from the database to\r
206 +     * the mailstore. */\r
207 +    notmuch_message_set_flag(message, NOTMUCH_MESSAGE_FLAG_TAGS_INVALID, FALSE);\r
208 +}\r
209 +\r
210 +/* Store maildir-related tags as maildir flags */\r
211 +static notmuch_private_status_t\r
212 +maildir_sync_tags(notmuch_mailstore_t *mailstore,\r
213 +                 notmuch_message_t *message)\r
214 +{\r
215 +    notmuch_tags_t *tags;\r
216 +    const char *tag;\r
217 +    char flags[ARRAY_SIZE(flag2tag)+1];\r
218 +    unsigned i;\r
219 +    char *p;\r
220 +    const char *filename;\r
221 +    char *filename_new;\r
222 +\r
223 +    (void)mailstore;\r
224 +    for (i = 0; i < ARRAY_SIZE(flag2tag); i++)\r
225 +       flags[i] = flag2tag[i].inverse ? flag2tag[i].flag : '\0';\r
226 +\r
227 +    for (tags = notmuch_message_get_tags (message);\r
228 +        notmuch_tags_valid (tags);\r
229 +        notmuch_tags_move_to_next (tags))\r
230 +    {\r
231 +       tag = notmuch_tags_get (tags);\r
232 +       for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {\r
233 +           if (strcmp(tag, flag2tag[i].tag) == 0)\r
234 +               flags[i] = flag2tag[i].inverse ? '\0' : flag2tag[i].flag;\r
235 +       }\r
236 +    }\r
237 +\r
238 +    p = flags;\r
239 +    for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {\r
240 +       if (flags[i])\r
241 +           *p++ = flags[i];\r
242 +    }\r
243 +    *p = '\0';\r
244 +\r
245 +    filename = notmuch_message_get_filename(message);\r
246 +    p = strstr(filename, ":2,");\r
247 +    if (!p)\r
248 +       return NOTMUCH_STATUS_SUCCESS; /* FIXME: Is this OK?  */\r
249 +    p += 3;\r
250 +    filename_new = talloc_size(message, (p-filename) + sizeof(flags));\r
251 +    if (unlikely (filename_new == NULL))\r
252 +       return NOTMUCH_STATUS_OUT_OF_MEMORY;\r
253 +    memcpy(filename_new, filename, p-filename);\r
254 +    strcpy(filename_new+(p-filename), flags);\r
255 +\r
256 +    if (strcmp (filename, filename_new) != 0) {\r
257 +       rename (filename, filename_new);\r
258 +\r
259 +       return _notmuch_message_rename (message, filename_new);\r
260 +       /* _notmuch_message_sync is our caller. Do not call it here. */\r
261 +    }\r
262 +    return NOTMUCH_STATUS_SUCCESS;\r
263 +}\r
264 +\r
265 +static void\r
266 +tag_inbox_and_maildir_flags (notmuch_message_t *message, const char *filename)\r
267 +{\r
268 +    notmuch_message_add_tag (message, "inbox");\r
269 +    tag_from_maildir_flags(message, filename);\r
270 +}\r
271 +\r
272  /* Examine 'path' recursively as follows:\r
273   *\r
274   *   o Ask the filesystem for the mtime of 'path' (fs_mtime)\r
275 @@ -171,6 +310,7 @@ add_files_recursive (notmuch_mailstore_t *mailstore,\r
276      struct stat st;\r
277      notmuch_bool_t is_maildir, new_directory;\r
278      _indexing_context_priv_t *priv = state->priv;\r
279 +    struct mailstore_priv *mailstore_priv = mailstore->priv;\r
280      notmuch_database_t *notmuch = mailstore->notmuch;\r
281  \r
282      if (stat (path, &st)) {\r
283 @@ -362,11 +502,12 @@ add_files_recursive (notmuch_mailstore_t *mailstore,\r
284         /* success */\r
285         case NOTMUCH_STATUS_SUCCESS:\r
286             state->added_messages++;\r
287 -           tag_inbox_and_unread (message);\r
288 +           mailstore_priv->tag_new (message, next);\r
289             break;\r
290         /* Non-fatal issues (go on to next file) */\r
291         case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:\r
292 -           /* Stay silent on this one. */\r
293 +           if (mailstore_priv->tag_renamed)\r
294 +               mailstore_priv->tag_renamed (message, next);\r
295             break;\r
296         case NOTMUCH_STATUS_FILE_NOT_EMAIL:\r
297             fprintf (stderr, "Note: Ignoring non-mail file: %s\n",\r
298 @@ -623,6 +764,10 @@ open_file(notmuch_mailstore_t *mailstore, const char *filename)\r
299      return fopen (abs_filename, "r");\r
300  }\r
301  \r
302 +struct mailstore_priv files_priv = {\r
303 +    .tag_new = tag_inbox_and_unread,\r
304 +};\r
305 +\r
306  /* Original notmuch mail store */\r
307  struct _notmuch_mailstore mailstore_files = {\r
308      .type = "files",\r
309 @@ -630,4 +775,20 @@ struct _notmuch_mailstore mailstore_files = {\r
310      .index_new = index_new,\r
311      .sync_tags = NULL,\r
312      .open_file = open_file,\r
313 +    .priv = &files_priv,\r
314 +};\r
315 +\r
316 +struct mailstore_priv maildir_priv = {\r
317 +    .tag_new = tag_inbox_and_maildir_flags,\r
318 +    .tag_renamed = tag_from_maildir_flags,\r
319 +};\r
320 +\r
321 +/* Similar to mailstore_files, but does bi-directional synchronization between certain tags and maildir flags */\r
322 +struct _notmuch_mailstore mailstore_maildir = {\r
323 +    .type = "maildir",\r
324 +    .count_files = count_files,\r
325 +    .index_new = index_new,\r
326 +    .sync_tags = maildir_sync_tags,\r
327 +    .open_file = open_file,\r
328 +    .priv = &maildir_priv,\r
329  };\r
330 diff --git a/lib/mailstore.c b/lib/mailstore.c\r
331 index cdac9a6..7dc55ad 100644\r
332 --- a/lib/mailstore.c\r
333 +++ b/lib/mailstore.c\r
334 @@ -25,6 +25,7 @@\r
335  \r
336  static notmuch_mailstore_t *available[] = {\r
337      &mailstore_files,\r
338 +    &mailstore_maildir,\r
339  };\r
340  \r
341  notmuch_mailstore_t *\r
342 diff --git a/lib/message.cc b/lib/message.cc\r
343 index c32ee7d..51208ba 100644\r
344 --- a/lib/message.cc\r
345 +++ b/lib/message.cc\r
346 @@ -561,7 +561,8 @@ _notmuch_message_sync (notmuch_message_t *message)\r
347      if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)\r
348         return;\r
349  \r
350 -    if (message->notmuch->mailstore->sync_tags) {\r
351 +    if (message->notmuch->mailstore->sync_tags &&\r
352 +       !notmuch_message_get_flag(message, NOTMUCH_MESSAGE_FLAG_TAGS_INVALID)) {\r
353         status = message->notmuch->mailstore->sync_tags (message->notmuch->mailstore,\r
354                                                          message);\r
355         if (status != NOTMUCH_PRIVATE_STATUS_SUCCESS) {\r
356 @@ -683,6 +684,44 @@ _notmuch_message_remove_term (notmuch_message_t *message,\r
357      return NOTMUCH_PRIVATE_STATUS_SUCCESS;\r
358  }\r
359  \r
360 +/* Change the message filename stored in the database.\r
361 + *\r
362 + * This change will not be reflected in the database until the next\r
363 + * call to _notmuch_message_sync.\r
364 + */\r
365 +notmuch_private_status_t\r
366 +_notmuch_message_rename (notmuch_message_t *message,\r
367 +                        const char *new_filename)\r
368 +{\r
369 +    void *local = talloc_new (message);\r
370 +    char *direntry;\r
371 +    Xapian::PostingIterator i, end;\r
372 +    Xapian::Document document;\r
373 +    notmuch_private_status_t pstatus;\r
374 +    notmuch_status_t status;\r
375 +    const char *old_filename;\r
376 +\r
377 +    old_filename = notmuch_message_get_filename(message);\r
378 +    old_filename = talloc_reference(local, old_filename);\r
379 +    if (unlikely(!old_filename))\r
380 +       return NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;\r
381 +\r
382 +    status = _notmuch_message_add_filename (message, new_filename);\r
383 +    if (status)\r
384 +       return (notmuch_private_status_t)status;\r
385 +\r
386 +    status = _notmuch_database_filename_to_direntry (local, message->notmuch,\r
387 +                                                    old_filename, &direntry);\r
388 +    if (status)\r
389 +       return (notmuch_private_status_t)status;\r
390 +\r
391 +    pstatus = _notmuch_message_remove_term (message, "file-direntry", direntry);\r
392 +\r
393 +    talloc_free (local);\r
394 +\r
395 +    return pstatus;\r
396 +}\r
397 +\r
398  notmuch_status_t\r
399  notmuch_message_add_tag (notmuch_message_t *message, const char *tag)\r
400  {\r
401 diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h\r
402 index d52d84d..63e75ec 100644\r
403 --- a/lib/notmuch-private.h\r
404 +++ b/lib/notmuch-private.h\r
405 @@ -245,6 +245,10 @@ notmuch_status_t\r
406  _notmuch_message_add_filename (notmuch_message_t *message,\r
407                                const char *filename);\r
408  \r
409 +notmuch_private_status_t\r
410 +_notmuch_message_rename (notmuch_message_t *message,\r
411 +                        const char *new_filename);\r
412 +\r
413  void\r
414  _notmuch_message_ensure_thread_id (notmuch_message_t *message);\r
415  \r
416 diff --git a/lib/notmuch.h b/lib/notmuch.h\r
417 index 4f4cc8b..eab909b 100644\r
418 --- a/lib/notmuch.h\r
419 +++ b/lib/notmuch.h\r
420 @@ -761,6 +761,7 @@ notmuch_message_get_filename (notmuch_message_t *message);\r
421  /* Message flags */\r
422  typedef enum _notmuch_message_flag {\r
423      NOTMUCH_MESSAGE_FLAG_MATCH,\r
424 +    NOTMUCH_MESSAGE_FLAG_TAGS_INVALID,\r
425  } notmuch_message_flag_t;\r
426  \r
427  /* Get a value of a flag for the email corresponding to 'message'. */\r
428 -- \r
429 1.7.0\r
430 \r