Re: [PATCH] emacs: wash: make word-wrap bound message width
[notmuch-archives.git] / 4a / e97db4bcfc1895845cec74f192902bb911ff8c
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 50F4741733F\r
6         for <notmuch@notmuchmail.org>; Sat, 27 Mar 2010 13:44:40 -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: -1.9\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-1.9 tagged_above=-999 required=5\r
12         tests=[BAYES_00=-1.9] 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 ROd3QpHXNb1P for <notmuch@notmuchmail.org>;\r
16         Sat, 27 Mar 2010 13:44:35 -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 A58CE4196F4\r
19         for <notmuch@notmuchmail.org>; Sat, 27 Mar 2010 13:44:33 -0700 (PDT)\r
20 Received: from localhost (unknown [192.168.200.4])\r
21         by max.feld.cvut.cz (Postfix) with ESMTP id 0D05819F341A;\r
22         Sat, 27 Mar 2010 21:44:33 +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 fX7gtpoLXFYa; Sat, 27 Mar 2010 21:44:31 +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 4DDD019F3419;\r
30         Sat, 27 Mar 2010 21:44:31 +0100 (CET)\r
31 Received: from steelpick.2x.cz (r5da224.net.upc.cz [86.49.116.224])\r
32         (Authenticated sender: sojkam1)\r
33         by imap.feld.cvut.cz (Postfix) with ESMTPSA id D5742FA004;\r
34         Sat, 27 Mar 2010 21:44:30 +0100 (CET)\r
35 Received: from wsh by steelpick.2x.cz with local (Exim 4.71)\r
36         (envelope-from <sojkam1@fel.cvut.cz>)\r
37         id 1Nvcrt-0001ns-QB; Sat, 27 Mar 2010 21:44:29 +0100\r
38 From: Michal Sojka <sojkam1@fel.cvut.cz>\r
39 To: notmuch@notmuchmail.org\r
40 Date: Sat, 27 Mar 2010 21:44:18 +0100\r
41 Message-Id: <1269722661-6894-3-git-send-email-sojkam1@fel.cvut.cz>\r
42 X-Mailer: git-send-email 1.7.0.2\r
43 In-Reply-To: <8739zlijhp.fsf@steelpick.2x.cz>\r
44 References: <8739zlijhp.fsf@steelpick.2x.cz>\r
45 Subject: [notmuch] [PATCH v3 3/6] 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: Sat, 27 Mar 2010 20:44:40 -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, the following happens:\r
67    o New messages are tagged with inbox.\r
68    o New messages without maildir info in the file name (typically\r
69      files in new/) are tagged with unread.\r
70    o For new or renamed messages with maildir info present in the\r
71      file name, the tags defined in flag2tag are either added or\r
72      removed depending on the flags from the file name.\r
73 \r
74 2) Whenever notmuch tag (or notmuch restore) is executed, a new set of\r
75    flags based on the tags is constructed for every message and a new\r
76    file name is prepared based on the old file name but with the new\r
77    flags. If the old message file was in 'new' directory then this is\r
78    replaced with 'cur' in the new file name. If the new and old file\r
79    names differ, the file is renamed and notmuch database is updated\r
80    accordingly.\r
81 \r
82    The rename happens before the database is updated. In case of crash\r
83    between rename and database update, the next run of notmuch new\r
84    brings the database in sync with the mail store again.\r
85 \r
86 This mailstore is enabled by putting the following to your\r
87 .notmuch-config:\r
88 \r
89 [mailstore]\r
90 type=maildir\r
91 \r
92 Signed-off-by: Michal Sojka <sojkam1@fel.cvut.cz>\r
93 ---\r
94  lib/database.cc       |    7 ++\r
95  lib/mailstore-files.c |  203 ++++++++++++++++++++++++++++++++++++++++++++++++-\r
96  lib/mailstore.c       |    1 +\r
97  lib/message.cc        |   41 ++++++++++-\r
98  lib/notmuch-private.h |    4 +\r
99  lib/notmuch.h         |    1 +\r
100  6 files changed, 253 insertions(+), 4 deletions(-)\r
101 \r
102 diff --git a/lib/database.cc b/lib/database.cc\r
103 index 93c8d0f..33ef889 100644\r
104 --- a/lib/database.cc\r
105 +++ b/lib/database.cc\r
106 @@ -1471,6 +1471,13 @@ notmuch_database_add_message (notmuch_database_t *notmuch,\r
107  \r
108         _notmuch_message_add_filename (message, filename);\r
109  \r
110 +       /* This is a new message or it has a new filename and as such,\r
111 +        * its tags in database either do not exists or might be out\r
112 +        * of date. Mailstore assigns the tags later in index_new(),\r
113 +        * but until then we should not synchronize the tags back to\r
114 +        * the mailstore. */\r
115 +       notmuch_message_set_flag(message, NOTMUCH_MESSAGE_FLAG_TAGS_INVALID, TRUE);\r
116 +\r
117         /* Is this a newly created message object? */\r
118         if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {\r
119             _notmuch_message_add_term (message, "type", "mail");\r
120 diff --git a/lib/mailstore-files.c b/lib/mailstore-files.c\r
121 index e4086ba..b4e6e42 100644\r
122 --- a/lib/mailstore-files.c\r
123 +++ b/lib/mailstore-files.c\r
124 @@ -24,6 +24,30 @@\r
125  #include "notmuch.h"\r
126  #include "mailstore-private.h"\r
127  #include <dirent.h>\r
128 +#include <stdbool.h>\r
129 +\r
130 +#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))\r
131 +\r
132 +struct mailstore_priv {\r
133 +    void (*tag_new)(notmuch_message_t *message, const char *filename);\r
134 +    int  (*tag_renamed)(notmuch_message_t *message, const char *filename);\r
135 +};\r
136 +\r
137 +struct maildir_flag_tag {\r
138 +    char flag;\r
139 +    const char *tag;\r
140 +    bool inverse;\r
141 +};\r
142 +\r
143 +/* ASCII ordered table of Maildir flags and assiciated tags */\r
144 +struct maildir_flag_tag flag2tag[] = {\r
145 +    { 'D', "draft",   false},\r
146 +    { 'F', "flagged", false},\r
147 +    { 'P', "passed",  false},\r
148 +    { 'R', "replied", false},\r
149 +    { 'S', "unread",  true },\r
150 +    { 'T', "delete",  false},\r
151 +};\r
152  \r
153  typedef struct _filename_node {\r
154      char *filename;\r
155 @@ -69,8 +93,9 @@ _filename_list_add (_filename_list_t *list,\r
156  }\r
157  \r
158  static void\r
159 -tag_inbox_and_unread (notmuch_message_t *message)\r
160 +tag_inbox_and_unread (notmuch_message_t *message, const char *filename)\r
161  {\r
162 +    (void)filename;\r
163      notmuch_message_add_tag (message, "inbox");\r
164      notmuch_message_add_tag (message, "unread");\r
165  }\r
166 @@ -116,6 +141,156 @@ _entries_resemble_maildir (struct dirent **entries, int count)\r
167      return 0;\r
168  }\r
169  \r
170 +static int\r
171 +tag_from_maildir_flags (notmuch_message_t *message, const char *filename)\r
172 +{\r
173 +    const char *flags, *p;\r
174 +    char f;\r
175 +    bool valid, unread;\r
176 +    unsigned i;\r
177 +\r
178 +    flags = strstr(filename, ":2,");\r
179 +    if (!flags)\r
180 +       return -1;\r
181 +    flags += 3;\r
182 +\r
183 +    /*  Check that the letters are valid Maildir flags */\r
184 +    f = 0;\r
185 +    valid = true;\r
186 +    for (p=flags; valid && *p; p++) {\r
187 +       switch (*p) {\r
188 +       case 'P':\r
189 +       case 'R':\r
190 +       case 'S':\r
191 +       case 'T':\r
192 +       case 'D':\r
193 +       case 'F':\r
194 +           if (*p > f) f=*p;\r
195 +           else valid = false;\r
196 +       break;\r
197 +       default:\r
198 +           valid = false;\r
199 +       }\r
200 +    }\r
201 +    if (!valid) {\r
202 +       fprintf (stderr, "Warning: Invalid maildir flags in filename %s\n", filename);\r
203 +       return -1;\r
204 +    }\r
205 +\r
206 +    notmuch_message_freeze(message);\r
207 +    unread = true;\r
208 +    for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {\r
209 +       if ((strchr(flags, flag2tag[i].flag) != NULL) ^ flag2tag[i].inverse) {\r
210 +           notmuch_message_add_tag (message, flag2tag[i].tag);\r
211 +       } else {\r
212 +           notmuch_message_remove_tag (message, flag2tag[i].tag);\r
213 +       }\r
214 +    }\r
215 +    notmuch_message_thaw(message);\r
216 +\r
217 +    /* From now on, we can synchronize the tags from the database to\r
218 +     * the mailstore. */\r
219 +    notmuch_message_set_flag(message, NOTMUCH_MESSAGE_FLAG_TAGS_INVALID, FALSE);\r
220 +    return 0;\r
221 +}\r
222 +\r
223 +static void\r
224 +get_new_flags(notmuch_message_t *message, char *flags)\r
225 +{\r
226 +    notmuch_tags_t *tags;\r
227 +    const char *tag;\r
228 +    unsigned i;\r
229 +    char *p;\r
230 +\r
231 +    for (i = 0; i < ARRAY_SIZE(flag2tag); i++)\r
232 +       flags[i] = flag2tag[i].inverse ? flag2tag[i].flag : '\0';\r
233 +\r
234 +    for (tags = notmuch_message_get_tags (message);\r
235 +        notmuch_tags_valid (tags);\r
236 +        notmuch_tags_move_to_next (tags))\r
237 +    {\r
238 +       tag = notmuch_tags_get (tags);\r
239 +       for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {\r
240 +           if (strcmp(tag, flag2tag[i].tag) == 0)\r
241 +               flags[i] = flag2tag[i].inverse ? '\0' : flag2tag[i].flag;\r
242 +       }\r
243 +    }\r
244 +\r
245 +    p = flags;\r
246 +    for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {\r
247 +       if (flags[i])\r
248 +           *p++ = flags[i];\r
249 +    }\r
250 +    *p = '\0';\r
251 +}\r
252 +\r
253 +static char *\r
254 +get_subdir (char *filename)\r
255 +{\r
256 +    char *p, *subdir = NULL;\r
257 +\r
258 +    p = filename + strlen (filename) - 1;\r
259 +    while (p > filename + 3 && *p != '/')\r
260 +       p--;\r
261 +    if (*p == '/') {\r
262 +       subdir = p - 3;\r
263 +       if (subdir > filename && *(subdir - 1) != '/')\r
264 +           subdir = NULL;\r
265 +    }\r
266 +    return subdir;\r
267 +}\r
268 +\r
269 +/* Store maildir-related tags as maildir flags */\r
270 +static notmuch_private_status_t\r
271 +maildir_sync_tags (notmuch_mailstore_t *mailstore,\r
272 +                  notmuch_message_t *message)\r
273 +{\r
274 +    char flags[ARRAY_SIZE(flag2tag)+1];\r
275 +    const char *filename, *p;\r
276 +    char *filename_new, *subdir = NULL;\r
277 +    int ret;\r
278 +\r
279 +    (void)mailstore;\r
280 +    get_new_flags (message, flags);\r
281 +\r
282 +    filename = notmuch_message_get_filename (message);\r
283 +    p = strstr(filename, ":2,");\r
284 +    if (!p)\r
285 +       p = filename + strlen(filename);\r
286 +\r
287 +    filename_new = talloc_size(message, (p-filename) + 3 + sizeof(flags));\r
288 +    if (unlikely (filename_new == NULL))\r
289 +       return NOTMUCH_STATUS_OUT_OF_MEMORY;\r
290 +    memcpy(filename_new, filename, p-filename);\r
291 +    filename_new[p-filename] = '\0';\r
292 +\r
293 +    /* If message is in new/ move it under cur/. */\r
294 +    subdir = get_subdir (filename_new);\r
295 +    if (subdir && memcmp (subdir, "new/", 4) == 0)\r
296 +       memcpy (subdir, "cur/", 4);\r
297 +\r
298 +    strcpy (filename_new+(p-filename), ":2,");\r
299 +    strcpy (filename_new+(p-filename)+3, flags);\r
300 +\r
301 +    if (strcmp (filename, filename_new) != 0) {\r
302 +       ret = rename (filename, filename_new);\r
303 +       if (ret == -1) {\r
304 +           perror (talloc_asprintf (message, "rename of %s to %s failed", filename, filename_new));\r
305 +           exit (1);\r
306 +       }\r
307 +       return _notmuch_message_rename (message, filename_new);\r
308 +       /* _notmuch_message_sync is our caller. Do not call it here. */\r
309 +    }\r
310 +    return NOTMUCH_STATUS_SUCCESS;\r
311 +}\r
312 +\r
313 +static void\r
314 +tag_inbox_and_maildir_flags (notmuch_message_t *message, const char *filename)\r
315 +{\r
316 +    notmuch_message_add_tag (message, "inbox");\r
317 +    if (tag_from_maildir_flags(message, filename) != 0)\r
318 +       notmuch_message_add_tag (message, "unread");\r
319 +}\r
320  \r
321  /* Examine 'path' recursively as follows:\r
322   *\r
323 @@ -171,6 +346,7 @@ add_files_recursive (notmuch_mailstore_t *mailstore,\r
324      struct stat st;\r
325      notmuch_bool_t is_maildir, new_directory;\r
326      _indexing_context_priv_t *priv = state->priv;\r
327 +    struct mailstore_priv *mailstore_priv = mailstore->priv;\r
328      notmuch_database_t *notmuch = mailstore->notmuch;\r
329  \r
330      if (stat (path, &st)) {\r
331 @@ -352,11 +528,12 @@ add_files_recursive (notmuch_mailstore_t *mailstore,\r
332         /* success */\r
333         case NOTMUCH_STATUS_SUCCESS:\r
334             state->added_messages++;\r
335 -           tag_inbox_and_unread (message);\r
336 +           mailstore_priv->tag_new (message, next);\r
337             break;\r
338         /* Non-fatal issues (go on to next file) */\r
339         case NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID:\r
340 -           /* Stay silent on this one. */\r
341 +           if (mailstore_priv->tag_renamed)\r
342 +               mailstore_priv->tag_renamed (message, next);\r
343             break;\r
344         case NOTMUCH_STATUS_FILE_NOT_EMAIL:\r
345             fprintf (stderr, "Note: Ignoring non-mail file: %s\n",\r
346 @@ -614,6 +791,10 @@ open_file(notmuch_mailstore_t *mailstore, const char *filename)\r
347      return fopen (abs_filename, "r");\r
348  }\r
349  \r
350 +struct mailstore_priv files_priv = {\r
351 +    .tag_new = tag_inbox_and_unread,\r
352 +};\r
353 +\r
354  /* Original notmuch mail store */\r
355  struct _notmuch_mailstore mailstore_files = {\r
356      .type = "files",\r
357 @@ -621,4 +802,20 @@ struct _notmuch_mailstore mailstore_files = {\r
358      .index_new = index_new,\r
359      .sync_tags = NULL,\r
360      .open_file = open_file,\r
361 +    .priv = &files_priv,\r
362 +};\r
363 +\r
364 +struct mailstore_priv maildir_priv = {\r
365 +    .tag_new = tag_inbox_and_maildir_flags,\r
366 +    .tag_renamed = tag_from_maildir_flags,\r
367 +};\r
368 +\r
369 +/* Similar to mailstore_files, but does bi-directional synchronization between certain tags and maildir flags */\r
370 +struct _notmuch_mailstore mailstore_maildir = {\r
371 +    .type = "maildir",\r
372 +    .count_files = count_files,\r
373 +    .index_new = index_new,\r
374 +    .sync_tags = maildir_sync_tags,\r
375 +    .open_file = open_file,\r
376 +    .priv = &maildir_priv,\r
377  };\r
378 diff --git a/lib/mailstore.c b/lib/mailstore.c\r
379 index cdac9a6..7dc55ad 100644\r
380 --- a/lib/mailstore.c\r
381 +++ b/lib/mailstore.c\r
382 @@ -25,6 +25,7 @@\r
383  \r
384  static notmuch_mailstore_t *available[] = {\r
385      &mailstore_files,\r
386 +    &mailstore_maildir,\r
387  };\r
388  \r
389  notmuch_mailstore_t *\r
390 diff --git a/lib/message.cc b/lib/message.cc\r
391 index c32ee7d..51208ba 100644\r
392 --- a/lib/message.cc\r
393 +++ b/lib/message.cc\r
394 @@ -561,7 +561,8 @@ _notmuch_message_sync (notmuch_message_t *message)\r
395      if (message->notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)\r
396         return;\r
397  \r
398 -    if (message->notmuch->mailstore->sync_tags) {\r
399 +    if (message->notmuch->mailstore->sync_tags &&\r
400 +       !notmuch_message_get_flag(message, NOTMUCH_MESSAGE_FLAG_TAGS_INVALID)) {\r
401         status = message->notmuch->mailstore->sync_tags (message->notmuch->mailstore,\r
402                                                          message);\r
403         if (status != NOTMUCH_PRIVATE_STATUS_SUCCESS) {\r
404 @@ -683,6 +684,44 @@ _notmuch_message_remove_term (notmuch_message_t *message,\r
405      return NOTMUCH_PRIVATE_STATUS_SUCCESS;\r
406  }\r
407  \r
408 +/* Change the message filename stored in the database.\r
409 + *\r
410 + * This change will not be reflected in the database until the next\r
411 + * call to _notmuch_message_sync.\r
412 + */\r
413 +notmuch_private_status_t\r
414 +_notmuch_message_rename (notmuch_message_t *message,\r
415 +                        const char *new_filename)\r
416 +{\r
417 +    void *local = talloc_new (message);\r
418 +    char *direntry;\r
419 +    Xapian::PostingIterator i, end;\r
420 +    Xapian::Document document;\r
421 +    notmuch_private_status_t pstatus;\r
422 +    notmuch_status_t status;\r
423 +    const char *old_filename;\r
424 +\r
425 +    old_filename = notmuch_message_get_filename(message);\r
426 +    old_filename = talloc_reference(local, old_filename);\r
427 +    if (unlikely(!old_filename))\r
428 +       return NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;\r
429 +\r
430 +    status = _notmuch_message_add_filename (message, new_filename);\r
431 +    if (status)\r
432 +       return (notmuch_private_status_t)status;\r
433 +\r
434 +    status = _notmuch_database_filename_to_direntry (local, message->notmuch,\r
435 +                                                    old_filename, &direntry);\r
436 +    if (status)\r
437 +       return (notmuch_private_status_t)status;\r
438 +\r
439 +    pstatus = _notmuch_message_remove_term (message, "file-direntry", direntry);\r
440 +\r
441 +    talloc_free (local);\r
442 +\r
443 +    return pstatus;\r
444 +}\r
445 +\r
446  notmuch_status_t\r
447  notmuch_message_add_tag (notmuch_message_t *message, const char *tag)\r
448  {\r
449 diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h\r
450 index d52d84d..63e75ec 100644\r
451 --- a/lib/notmuch-private.h\r
452 +++ b/lib/notmuch-private.h\r
453 @@ -245,6 +245,10 @@ notmuch_status_t\r
454  _notmuch_message_add_filename (notmuch_message_t *message,\r
455                                const char *filename);\r
456  \r
457 +notmuch_private_status_t\r
458 +_notmuch_message_rename (notmuch_message_t *message,\r
459 +                        const char *new_filename);\r
460 +\r
461  void\r
462  _notmuch_message_ensure_thread_id (notmuch_message_t *message);\r
463  \r
464 diff --git a/lib/notmuch.h b/lib/notmuch.h\r
465 index 8fa7b31..c554f6d 100644\r
466 --- a/lib/notmuch.h\r
467 +++ b/lib/notmuch.h\r
468 @@ -760,6 +760,7 @@ notmuch_message_get_filename (notmuch_message_t *message);\r
469  /* Message flags */\r
470  typedef enum _notmuch_message_flag {\r
471      NOTMUCH_MESSAGE_FLAG_MATCH,\r
472 +    NOTMUCH_MESSAGE_FLAG_TAGS_INVALID,\r
473  } notmuch_message_flag_t;\r
474  \r
475  /* Get a value of a flag for the email corresponding to 'message'. */\r
476 -- \r
477 1.7.0.2\r
478 \r