1 /*
2 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
3 *
4 * This library is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This library is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Not Zed <notzed@lostzed.mmc.com.au>
17 */
18
19 #include "evolution-data-server-config.h"
20
21 #include <ctype.h>
22 #include <dirent.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #ifndef _WIN32
31 #include <sys/uio.h>
32 #else
33 #include <winsock2.h>
34 #endif
35
36 #include <glib/gstdio.h>
37 #include <glib/gi18n-lib.h>
38
39 #include "camel-maildir-message-info.h"
40 #include "camel-maildir-store.h"
41 #include "camel-maildir-summary.h"
42
43 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
44
45 #define CAMEL_MAILDIR_SUMMARY_VERSION (0x2000)
46
47 static CamelMessageInfo *
48 message_info_new_from_headers (CamelFolderSummary *,
49 const CamelNameValueArray *);
50 static gint maildir_summary_load (CamelLocalSummary *cls,
51 gint forceindex,
52 GError **error);
53 static gint maildir_summary_check (CamelLocalSummary *cls,
54 CamelFolderChangeInfo *changeinfo,
55 GCancellable *cancellable,
56 GError **error);
57 static gint maildir_summary_sync (CamelLocalSummary *cls,
58 gboolean expunge,
59 CamelFolderChangeInfo *changeinfo,
60 GCancellable *cancellable,
61 GError **error);
62 static CamelMessageInfo *
63 maildir_summary_add (CamelLocalSummary *cls,
64 CamelMimeMessage *msg,
65 const CamelMessageInfo *info,
66 CamelFolderChangeInfo *,
67 GError **error);
68
69 static gchar * maildir_summary_next_uid_string (CamelFolderSummary *s);
70 static gint maildir_summary_decode_x_evolution
71 (CamelLocalSummary *cls,
72 const gchar *xev,
73 CamelMessageInfo *mi);
74 static gchar * maildir_summary_encode_x_evolution
75 (CamelLocalSummary *cls,
76 const CamelMessageInfo *mi);
77
78 typedef struct _CamelMaildirMessageContentInfo CamelMaildirMessageContentInfo;
79
80 struct _CamelMaildirSummaryPrivate {
81 gchar *current_file;
82 gchar *hostname;
83 gchar filename_flag_sep;
84
85 GHashTable *load_map;
86 GMutex summary_lock;
87 };
88
89 struct _CamelMaildirMessageContentInfo {
90 CamelMessageContentInfo info;
91 };
92
G_DEFINE_TYPE_WITH_PRIVATE(CamelMaildirSummary,camel_maildir_summary,CAMEL_TYPE_LOCAL_SUMMARY)93 G_DEFINE_TYPE_WITH_PRIVATE (
94 CamelMaildirSummary,
95 camel_maildir_summary,
96 CAMEL_TYPE_LOCAL_SUMMARY)
97
98 static void
99 maildir_summary_finalize (GObject *object)
100 {
101 CamelMaildirSummaryPrivate *priv;
102
103 priv = CAMEL_MAILDIR_SUMMARY (object)->priv;
104
105 g_free (priv->hostname);
106 g_mutex_clear (&priv->summary_lock);
107
108 /* Chain up to parent's finalize() method. */
109 G_OBJECT_CLASS (camel_maildir_summary_parent_class)->finalize (object);
110 }
111
112 static void
camel_maildir_summary_class_init(CamelMaildirSummaryClass * class)113 camel_maildir_summary_class_init (CamelMaildirSummaryClass *class)
114 {
115 GObjectClass *object_class;
116 CamelFolderSummaryClass *folder_summary_class;
117 CamelLocalSummaryClass *local_summary_class;
118
119 object_class = G_OBJECT_CLASS (class);
120 object_class->finalize = maildir_summary_finalize;
121
122 folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
123 folder_summary_class->message_info_type = CAMEL_TYPE_MAILDIR_MESSAGE_INFO;
124 folder_summary_class->sort_by = "dreceived";
125 folder_summary_class->collate = NULL;
126 folder_summary_class->message_info_new_from_headers = message_info_new_from_headers;
127 folder_summary_class->next_uid_string = maildir_summary_next_uid_string;
128
129 local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (class);
130 local_summary_class->load = maildir_summary_load;
131 local_summary_class->check = maildir_summary_check;
132 local_summary_class->sync = maildir_summary_sync;
133 local_summary_class->add = maildir_summary_add;
134 local_summary_class->encode_x_evolution = maildir_summary_encode_x_evolution;
135 local_summary_class->decode_x_evolution = maildir_summary_decode_x_evolution;
136 }
137
138 static void
camel_maildir_summary_init(CamelMaildirSummary * maildir_summary)139 camel_maildir_summary_init (CamelMaildirSummary *maildir_summary)
140 {
141 CamelFolderSummary *folder_summary;
142 gchar hostname[256];
143
144 folder_summary = CAMEL_FOLDER_SUMMARY (maildir_summary);
145
146 maildir_summary->priv = camel_maildir_summary_get_instance_private (maildir_summary);
147
148 /* set unique file version */
149 camel_folder_summary_set_version (folder_summary, camel_folder_summary_get_version (folder_summary) + CAMEL_MAILDIR_SUMMARY_VERSION);
150
151 if (gethostname (hostname, 256) == 0) {
152 maildir_summary->priv->hostname = g_strdup (hostname);
153 } else {
154 maildir_summary->priv->hostname = g_strdup ("localhost");
155 }
156 g_mutex_init (&maildir_summary->priv->summary_lock);
157 }
158
159 /**
160 * camel_maildir_summary_new:
161 * @folder: parent folder.
162 * @maildirdir: a maildir directory for the new summary
163 * @index: (nullable): an optional #CamelIndex to use, or %NULL
164 *
165 * Create a new CamelMaildirSummary object.
166 *
167 * Returns: (transfer full): A new #CamelMaildirSummary object
168 **/
169 CamelMaildirSummary *
camel_maildir_summary_new(struct _CamelFolder * folder,const gchar * maildirdir,CamelIndex * index,gchar filename_flag_sep)170 camel_maildir_summary_new (struct _CamelFolder *folder,
171 const gchar *maildirdir,
172 CamelIndex *index,
173 gchar filename_flag_sep)
174 {
175 CamelMaildirSummary *o;
176
177 o = g_object_new (CAMEL_TYPE_MAILDIR_SUMMARY, "folder", folder, NULL);
178
179 o->priv->filename_flag_sep = filename_flag_sep;
180
181 if (folder) {
182 CamelStore *parent_store;
183
184 parent_store = camel_folder_get_parent_store (folder);
185 camel_db_set_collate (camel_store_get_db (parent_store), "dreceived", NULL, NULL);
186
187 if (!filename_flag_sep)
188 o->priv->filename_flag_sep = camel_maildir_store_get_filename_flag_sep (CAMEL_MAILDIR_STORE (parent_store));
189 }
190
191 if (!o->priv->filename_flag_sep)
192 o->priv->filename_flag_sep = CAMEL_MAILDIR_FILENAME_FLAG_SEP;
193
194 camel_local_summary_construct ((CamelLocalSummary *) o, maildirdir, index);
195 return o;
196 }
197
198 /* Flag separator used for this summary in the filename. */
199 gchar
camel_maildir_summary_get_filename_flag_sep(CamelMaildirSummary * maildir_summary)200 camel_maildir_summary_get_filename_flag_sep (CamelMaildirSummary *maildir_summary)
201 {
202 g_return_val_if_fail (CAMEL_IS_MAILDIR_SUMMARY (maildir_summary), CAMEL_MAILDIR_FILENAME_FLAG_SEP);
203
204 return maildir_summary->priv->filename_flag_sep;
205 }
206
207 /* the 'standard' maildir flags. should be defined in sorted order. */
208 static struct {
209 gchar flag;
210 guint32 flagbit;
211 } flagbits[] = {
212 { 'D', CAMEL_MESSAGE_DRAFT },
213 { 'F', CAMEL_MESSAGE_FLAGGED },
214 /*{ 'P', CAMEL_MESSAGE_FORWARDED },*/
215 { 'R', CAMEL_MESSAGE_ANSWERED },
216 { 'S', CAMEL_MESSAGE_SEEN },
217 { 'T', CAMEL_MESSAGE_DELETED },
218 };
219
220 /* convert the uid + flags into a unique:info maildir format */
221 gchar *
camel_maildir_summary_uid_and_flags_to_name(CamelMaildirSummary * maildir_summary,const gchar * uid,guint32 flags)222 camel_maildir_summary_uid_and_flags_to_name (CamelMaildirSummary *maildir_summary,
223 const gchar *uid,
224 guint32 flags)
225 {
226 gchar *p, *buf, filename_flag_sep;
227 gint i;
228
229 g_return_val_if_fail (uid != NULL, NULL);
230
231 filename_flag_sep = maildir_summary ? maildir_summary->priv->filename_flag_sep : CAMEL_MAILDIR_FILENAME_FLAG_SEP;
232
233 buf = g_alloca (strlen (uid) + 1 /* flag_sep */ + 2 /* "2," */ + G_N_ELEMENTS (flagbits) + 1);
234 p = buf + sprintf (buf, "%s%c2,", uid, filename_flag_sep);
235 for (i = 0; i < G_N_ELEMENTS (flagbits); i++) {
236 if ((flags & flagbits[i].flagbit) != 0)
237 *p++ = flagbits[i].flag;
238 }
239
240 *p = 0;
241
242 return g_strdup (buf);
243 }
244
245 gchar *
camel_maildir_summary_info_to_name(const CamelMessageInfo * info)246 camel_maildir_summary_info_to_name (const CamelMessageInfo *info)
247 {
248 CamelFolderSummary *summary;
249 gchar *res;
250
251 g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (info), NULL);
252
253 summary = camel_message_info_ref_summary (info);
254
255 res = camel_maildir_summary_uid_and_flags_to_name (CAMEL_MAILDIR_SUMMARY (summary),
256 camel_message_info_get_uid (info),
257 camel_message_info_get_flags (info));
258
259 g_clear_object (&summary);
260
261 return res;
262 }
263
264 /* returns whether the @info changed */
265 gboolean
camel_maildir_summary_name_to_info(CamelMessageInfo * info,const gchar * name)266 camel_maildir_summary_name_to_info (CamelMessageInfo *info,
267 const gchar *name)
268 {
269 CamelFolderSummary *summary;
270 gchar *p, c, pattern[4];
271 guint32 set = 0; /* what we set */
272 gint i;
273
274 summary = camel_message_info_ref_summary (info);
275
276 pattern[0] = camel_maildir_summary_get_filename_flag_sep (CAMEL_MAILDIR_SUMMARY (summary));
277 pattern[1] = '2';
278 pattern[2] = ',';
279 pattern[3] = '\0';
280
281 g_clear_object (&summary);
282
283 p = strstr (name, pattern);
284
285 if (p) {
286 guint32 flags;
287
288 flags = camel_message_info_get_flags (info);
289
290 p += 3;
291 while ((c = *p++)) {
292 /* we could assume that the flags are in order, but its just as easy not to require */
293 for (i = 0; i < G_N_ELEMENTS (flagbits); i++) {
294 if (flagbits[i].flag == c && (flags & flagbits[i].flagbit) == 0) {
295 set |= flagbits[i].flagbit;
296 }
297 }
298 }
299
300 /* changed? */
301 if ((flags & set) != set) {
302 return camel_message_info_set_flags (info, set, set);
303 }
304 }
305
306 return FALSE;
307 }
308
309 /* for maildir, x-evolution isn't used, so dont try and get anything out of it */
maildir_summary_decode_x_evolution(CamelLocalSummary * cls,const gchar * xev,CamelMessageInfo * mi)310 static gint maildir_summary_decode_x_evolution (CamelLocalSummary *cls, const gchar *xev, CamelMessageInfo *mi)
311 {
312 return -1;
313 }
314
maildir_summary_encode_x_evolution(CamelLocalSummary * cls,const CamelMessageInfo * mi)315 static gchar *maildir_summary_encode_x_evolution (CamelLocalSummary *cls, const CamelMessageInfo *mi)
316 {
317 return NULL;
318 }
319
320 /* FIXME:
321 * both 'new' and 'add' will try and set the filename, this is not ideal ...
322 */
323 static CamelMessageInfo *
maildir_summary_add(CamelLocalSummary * cls,CamelMimeMessage * msg,const CamelMessageInfo * info,CamelFolderChangeInfo * changes,GError ** error)324 maildir_summary_add (CamelLocalSummary *cls,
325 CamelMimeMessage *msg,
326 const CamelMessageInfo *info,
327 CamelFolderChangeInfo *changes,
328 GError **error)
329 {
330 CamelLocalSummaryClass *local_summary_class;
331 CamelMessageInfo *mi;
332
333 /* Chain up to parent's add() method. */
334 local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_maildir_summary_parent_class);
335 mi = local_summary_class->add (cls, msg, info, changes, error);
336 if (mi) {
337 if (info) {
338 CamelMaildirMessageInfo *mdi = CAMEL_MAILDIR_MESSAGE_INFO (mi);
339
340 camel_maildir_message_info_take_filename (mdi, camel_maildir_summary_info_to_name (mi));
341 d (printf ("Setting filename to %s\n", camel_maildir_message_info_get_filename (mdi)));
342
343 /* Inherit the Received date from the passed-in info only if it is set and
344 the new message info doesn't have it set or it's set to the default
345 value, derived from the message UID. */
346 if (camel_message_info_get_date_received (info) > 0 &&
347 (camel_message_info_get_date_received (mi) <= 0 ||
348 (camel_message_info_get_uid (mi) &&
349 camel_message_info_get_date_received (mi) == strtoul (camel_message_info_get_uid (mi), NULL, 10))))
350 camel_message_info_set_date_received (mi, camel_message_info_get_date_received (info));
351 }
352 }
353
354 return mi;
355 }
356
357 static CamelMessageInfo *
message_info_new_from_headers(CamelFolderSummary * summary,const CamelNameValueArray * headers)358 message_info_new_from_headers (CamelFolderSummary *summary,
359 const CamelNameValueArray *headers)
360 {
361 CamelMessageInfo *mi, *info;
362 CamelMaildirSummary *mds = (CamelMaildirSummary *) summary;
363 const gchar *uid;
364
365 mi = ((CamelFolderSummaryClass *) camel_maildir_summary_parent_class)->message_info_new_from_headers (summary, headers);
366 /* assign the uid and new filename */
367 if (mi) {
368 uid = camel_message_info_get_uid (mi);
369 if (uid == NULL || uid[0] == 0) {
370 gchar *new_uid = camel_folder_summary_next_uid_string (summary);
371
372 camel_message_info_set_uid (mi, new_uid);
373 g_free (new_uid);
374 }
375
376 /* handle 'duplicates' */
377 info = (uid && *uid) ? camel_folder_summary_peek_loaded (summary, uid) : NULL;
378 if (info) {
379 d (printf ("already seen uid '%s', just summarising instead\n", uid));
380 g_clear_object (&mi);
381 mi = info;
382 }
383
384 if (camel_message_info_get_date_received (mi) <= 0) {
385 /* with maildir we know the real received date, from the filename */
386 camel_message_info_set_date_received (mi, strtoul (camel_message_info_get_uid (mi), NULL, 10));
387 }
388
389 if (mds->priv->current_file) {
390 #if 0
391 gchar *p1, *p2, *p3;
392 gulong uid;
393 #endif
394 /* if setting from a file, grab the flags from it */
395 camel_maildir_message_info_take_filename (CAMEL_MAILDIR_MESSAGE_INFO (mi), g_strdup (mds->priv->current_file));
396 camel_maildir_summary_name_to_info (mi, mds->priv->current_file);
397
398 #if 0
399 /* Actually, I dont think all this effort is worth it at all ... */
400
401 /* also, see if we can extract the next-id from tne name, and safe-if-fy ourselves against collisions */
402 /* we check for something.something_number.something */
403 p1 = strchr (mdi->filename, '.');
404 if (p1) {
405 p2 = strchr (p1 + 1, '.');
406 p3 = strchr (p1 + 1, '_');
407 if (p2 && p3 && p3 < p2) {
408 uid = strtoul (p3 + 1, &p1, 10);
409 if (p1 == p2 && uid > 0)
410 camel_folder_summary_set_uid (s, uid);
411 }
412 }
413 #endif
414 } else {
415 /* if creating a file, set its name from the flags we have */
416 camel_maildir_message_info_take_filename (CAMEL_MAILDIR_MESSAGE_INFO (mi), camel_maildir_summary_info_to_name (mi));
417 d (printf ("Setting filename to %s\n", camel_maildir_message_info_get_filename (CAMEL_MAILDIR_MESSAGE_INFO (mi))));
418 }
419 }
420
421 return mi;
422 }
423
424 static gchar *
maildir_summary_next_uid_string(CamelFolderSummary * s)425 maildir_summary_next_uid_string (CamelFolderSummary *s)
426 {
427 CamelMaildirSummary *mds = (CamelMaildirSummary *) s;
428
429 d (printf ("next uid string called?\n"));
430
431 /* if we have a current file, then use that to get the uid */
432 if (mds->priv->current_file) {
433 gchar *cln;
434
435 cln = strchr (mds->priv->current_file, mds->priv->filename_flag_sep);
436 if (cln)
437 return g_strndup (mds->priv->current_file, cln - mds->priv->current_file);
438 else
439 return g_strdup (mds->priv->current_file);
440 } else {
441 /* the first would probably work, but just to be safe, check for collisions */
442 #if 0
443 return g_strdup_printf ("%ld.%d_%u.%s", time (0), getpid (), camel_folder_summary_next_uid (s), mds->priv->hostname);
444 #else
445 CamelLocalSummary *cls = (CamelLocalSummary *) s;
446 gchar *name = NULL, *uid = NULL;
447 struct stat st;
448 gint retry = 0;
449 guint32 nextuid = camel_folder_summary_next_uid (s);
450
451 /* we use time.pid_count.hostname */
452 do {
453 if (retry > 0) {
454 g_free (name);
455 g_free (uid);
456 g_usleep (2 * G_USEC_PER_SEC);
457 }
458 uid = g_strdup_printf ("%" G_GINT64_FORMAT ".%d_%u.%s", (gint64) time (NULL), getpid (), nextuid, mds->priv->hostname);
459 name = g_strdup_printf ("%s/tmp/%s", cls->folder_path, uid);
460 retry++;
461 } while (g_stat (name, &st) == 0 && retry < 3);
462
463 /* I dont know what we're supposed to do if it fails to find a unique name?? */
464
465 g_free (name);
466 return uid;
467 #endif
468 }
469 }
470
471 static gint
maildir_summary_load(CamelLocalSummary * cls,gint forceindex,GError ** error)472 maildir_summary_load (CamelLocalSummary *cls,
473 gint forceindex,
474 GError **error)
475 {
476 CamelLocalSummaryClass *local_summary_class;
477 gchar *cur;
478 DIR *dir;
479 struct dirent *d;
480 CamelMaildirSummary *mds = (CamelMaildirSummary *) cls;
481 gchar *uid;
482 CamelMemPool *pool;
483 gint ret;
484
485 cur = g_strdup_printf ("%s/cur", cls->folder_path);
486
487 d (printf ("pre-loading uid <> filename map\n"));
488
489 dir = opendir (cur);
490 if (dir == NULL) {
491 g_set_error (
492 error, G_IO_ERROR,
493 g_io_error_from_errno (errno),
494 _("Cannot open maildir directory path: %s: %s"),
495 cls->folder_path, g_strerror (errno));
496 g_free (cur);
497 return -1;
498 }
499
500 mds->priv->load_map = g_hash_table_new (g_str_hash, g_str_equal);
501 pool = camel_mempool_new (1024, 512, CAMEL_MEMPOOL_ALIGN_BYTE);
502
503 while ((d = readdir (dir))) {
504 if (d->d_name[0] == '.')
505 continue;
506
507 /* map the filename -> uid */
508 uid = strchr (d->d_name, mds->priv->filename_flag_sep);
509 if (uid) {
510 gint len = uid - d->d_name;
511 uid = camel_mempool_alloc (pool, len + 1);
512 memcpy (uid, d->d_name, len);
513 uid[len] = 0;
514 g_hash_table_insert (mds->priv->load_map, uid, camel_mempool_strdup (pool, d->d_name));
515 } else {
516 uid = camel_mempool_strdup (pool, d->d_name);
517 g_hash_table_insert (mds->priv->load_map, uid, uid);
518 }
519 }
520 closedir (dir);
521 g_free (cur);
522
523 /* Chain up to parent's load() method. */
524 local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_maildir_summary_parent_class);
525 ret = local_summary_class->load (cls, forceindex, error);
526
527 g_hash_table_destroy (mds->priv->load_map);
528 mds->priv->load_map = NULL;
529 camel_mempool_destroy (pool);
530
531 return ret;
532 }
533
534 static gint
camel_maildir_summary_add(CamelLocalSummary * cls,const gchar * name,gint forceindex,GCancellable * cancellable)535 camel_maildir_summary_add (CamelLocalSummary *cls,
536 const gchar *name,
537 gint forceindex,
538 GCancellable *cancellable)
539 {
540 CamelMessageInfo *info;
541 CamelFolderSummary *summary;
542 CamelMaildirSummary *maildirs = (CamelMaildirSummary *) cls;
543 gchar *filename = g_strdup_printf ("%s/cur/%s", cls->folder_path, name);
544 gint fd;
545 CamelMimeParser *mp;
546
547 d (printf ("summarising: %s\n", name));
548
549 summary = CAMEL_FOLDER_SUMMARY (cls);
550
551 fd = open (filename, O_RDONLY | O_LARGEFILE);
552 if (fd == -1) {
553 g_warning ("Cannot summarise/index: %s: %s", filename, g_strerror (errno));
554 g_free (filename);
555 return -1;
556 }
557 mp = camel_mime_parser_new ();
558 camel_mime_parser_scan_from (mp, FALSE);
559 camel_mime_parser_init_with_fd (mp, fd);
560 if (cls->index && (forceindex || !camel_index_has_name (cls->index, name))) {
561 d (printf ("forcing indexing of message content\n"));
562 camel_folder_summary_set_index (summary, cls->index);
563 } else {
564 camel_folder_summary_set_index (summary, NULL);
565 }
566 maildirs->priv->current_file = (gchar *) name;
567
568 info = camel_folder_summary_info_new_from_parser (summary, mp);
569 camel_folder_summary_add (summary, info, FALSE);
570 g_clear_object (&info);
571
572 g_object_unref (mp);
573 maildirs->priv->current_file = NULL;
574 camel_folder_summary_set_index (summary, NULL);
575 g_free (filename);
576 return 0;
577 }
578
579 struct _remove_data {
580 CamelLocalSummary *cls;
581 CamelFolderChangeInfo *changes;
582 GList *removed_uids;
583 };
584
585 static void
remove_summary(const gchar * uid,gpointer value,struct _remove_data * rd)586 remove_summary (const gchar *uid,
587 gpointer value,
588 struct _remove_data *rd)
589 {
590 d (printf ("removing message %s from summary\n", uid));
591 if (rd->cls->index)
592 camel_index_delete_name (rd->cls->index, uid);
593 if (rd->changes)
594 camel_folder_change_info_remove_uid (rd->changes, uid);
595 rd->removed_uids = g_list_prepend (rd->removed_uids, (gpointer) uid);
596 }
597
598 static gint
maildir_summary_check(CamelLocalSummary * cls,CamelFolderChangeInfo * changes,GCancellable * cancellable,GError ** error)599 maildir_summary_check (CamelLocalSummary *cls,
600 CamelFolderChangeInfo *changes,
601 GCancellable *cancellable,
602 GError **error)
603 {
604 DIR *dir;
605 struct dirent *d;
606 gchar *p;
607 CamelFolderSummary *s = (CamelFolderSummary *) cls;
608 CamelMaildirSummary *mds;
609 GHashTable *left;
610 gint i, count, total;
611 gint forceindex;
612 gchar *new, *cur;
613 gchar *uid;
614 struct _remove_data rd = { cls, changes, NULL };
615 GPtrArray *known_uids;
616
617 mds = CAMEL_MAILDIR_SUMMARY (s);
618
619 g_mutex_lock (&mds->priv->summary_lock);
620
621 new = g_strdup_printf ("%s/new", cls->folder_path);
622 cur = g_strdup_printf ("%s/cur", cls->folder_path);
623
624 d (printf ("checking summary ...\n"));
625
626 camel_operation_push_message (
627 cancellable, _("Checking folder consistency"));
628
629 /* scan the directory, check for mail files not in the index, or index entries that
630 * no longer exist */
631 dir = opendir (cur);
632 if (dir == NULL) {
633 g_set_error (
634 error, G_IO_ERROR,
635 g_io_error_from_errno (errno),
636 _("Cannot open maildir directory path: %s: %s"),
637 cls->folder_path, g_strerror (errno));
638 g_free (cur);
639 g_free (new);
640 camel_operation_pop_message (cancellable);
641 g_mutex_unlock (&mds->priv->summary_lock);
642 return -1;
643 }
644
645 /* keeps track of all uid's that have not been processed */
646 left = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
647 known_uids = camel_folder_summary_get_array (s);
648 forceindex = !known_uids || known_uids->len == 0;
649 for (i = 0; known_uids && i < known_uids->len; i++) {
650 const gchar *uid = g_ptr_array_index (known_uids, i);
651 guint32 flags;
652
653 flags = camel_folder_summary_get_info_flags ((CamelFolderSummary *) cls, uid);
654 if (flags != (~0)) {
655 g_hash_table_insert (left, (gchar *) camel_pstring_strdup (uid), GUINT_TO_POINTER (flags));
656 }
657 }
658
659 /* joy, use this to pre-count the total, so we can report progress meaningfully */
660 total = 0;
661 count = 0;
662 while (readdir (dir))
663 total++;
664 rewinddir (dir);
665
666 while ((d = readdir (dir))) {
667 guint32 stored_flags = 0;
668 gint pc;
669
670 /* Avoid a potential division by zero if the first loop
671 * (to calculate total) is executed on an empty
672 * directory, then the directory is populated before
673 * this loop is executed. */
674 total = MAX (total, count + 1);
675 pc = (total > 0) ? count * 100 / total : 0;
676
677 camel_operation_progress (cancellable, pc);
678 count++;
679
680 /* FIXME: also run stat to check for regular file */
681 p = d->d_name;
682 if (p[0] == '.')
683 continue;
684
685 /* map the filename -> uid */
686 uid = strchr (d->d_name, mds->priv->filename_flag_sep);
687 if (uid)
688 uid = g_strndup (d->d_name, uid - d->d_name);
689 else
690 uid = g_strdup (d->d_name);
691
692 if (g_hash_table_contains (left, uid)) {
693 stored_flags = GPOINTER_TO_UINT (g_hash_table_lookup (left, uid));
694 g_hash_table_remove (left, uid);
695 }
696
697 if (!camel_folder_summary_check_uid ((CamelFolderSummary *) cls, uid)) {
698 /* must be a message incorporated by another client, this is not a 'recent' uid */
699 if (camel_maildir_summary_add (cls, d->d_name, forceindex, cancellable) == 0)
700 if (changes)
701 camel_folder_change_info_add_uid (changes, uid);
702 } else {
703 CamelMaildirMessageInfo *mdi;
704 CamelMessageInfo *info;
705 gchar *expected_filename;
706
707 if (cls->index && (!camel_index_has_name (cls->index, uid))) {
708 /* message_info_new will handle duplicates */
709 camel_maildir_summary_add (cls, d->d_name, forceindex, cancellable);
710 }
711
712 info = camel_folder_summary_peek_loaded ((CamelFolderSummary *) cls, uid);
713 mdi = info ? CAMEL_MAILDIR_MESSAGE_INFO (info) : NULL;
714
715 expected_filename = camel_maildir_summary_uid_and_flags_to_name (mds, uid, stored_flags);
716 if ((mdi && !camel_maildir_message_info_get_filename (mdi)) ||
717 !expected_filename ||
718 strcmp (expected_filename, d->d_name) != 0) {
719 if (!mdi) {
720 g_clear_object (&info);
721 info = camel_folder_summary_get ((CamelFolderSummary *) cls, uid);
722 mdi = info ? CAMEL_MAILDIR_MESSAGE_INFO (info) : NULL;
723 }
724
725 g_warn_if_fail (mdi != NULL);
726
727 if (mdi)
728 camel_maildir_message_info_set_filename (mdi, d->d_name);
729 }
730
731 g_free (expected_filename);
732 g_clear_object (&info);
733 }
734 g_free (uid);
735 }
736 closedir (dir);
737 g_hash_table_foreach (left, (GHFunc) remove_summary, &rd);
738
739 if (rd.removed_uids)
740 camel_folder_summary_remove_uids ((CamelFolderSummary *) cls, rd.removed_uids);
741 g_list_free (rd.removed_uids);
742
743 /* Destroy the hash table only after the removed_uids GList is freed, because it has borrowed the UIDs */
744 g_hash_table_destroy (left);
745
746 camel_operation_pop_message (cancellable);
747
748 camel_operation_push_message (
749 cancellable, _("Checking for new messages"));
750
751 /* now, scan new for new messages, and copy them to cur, and so forth */
752 dir = opendir (new);
753 if (dir != NULL) {
754 total = 0;
755 count = 0;
756 while (readdir (dir))
757 total++;
758 rewinddir (dir);
759
760 while ((d = readdir (dir))) {
761 gchar *name, *newname, *destname, *destfilename;
762 gchar *src, *dest;
763 gint pc;
764
765 /* Avoid a potential division by zero if the first loop
766 * (to calculate total) is executed on an empty
767 * directory, then the directory is populated before
768 * this loop is executed. */
769 total = MAX (total, count + 1);
770 pc = (total > 0) ? count * 100 / total : 0;
771
772 camel_operation_progress (cancellable, pc);
773 count++;
774
775 name = d->d_name;
776 if (name[0] == '.')
777 continue;
778
779 /* already in summary? shouldn't happen, but just incase ... */
780 if (camel_folder_summary_check_uid ((CamelFolderSummary *) cls, name)) {
781 newname = destname = camel_folder_summary_next_uid_string (s);
782 } else {
783 gchar *nm;
784 newname = g_strdup (name);
785 nm = strrchr (newname, mds->priv->filename_flag_sep);
786 if (nm)
787 *nm = '\0';
788 destname = newname;
789 }
790
791 /* copy this to the destination folder, use 'standard' semantics for maildir info field */
792 src = g_strdup_printf ("%s/%s", new, name);
793 destfilename = g_strdup_printf ("%s%c2,", destname, mds->priv->filename_flag_sep);
794 dest = g_strdup_printf ("%s/%s", cur, destfilename);
795
796 /* FIXME: This should probably use link/unlink */
797
798 if (g_rename (src, dest) == 0) {
799 camel_maildir_summary_add (cls, destfilename, forceindex, cancellable);
800 if (changes) {
801 camel_folder_change_info_add_uid (changes, destname);
802 camel_folder_change_info_recent_uid (changes, destname);
803 }
804 } else {
805 /* else? we should probably care about failures, but wont */
806 g_warning ("Failed to move new maildir message %s to cur %s", src, dest);
807 }
808
809 /* c strings are painful to work with ... */
810 g_free (destfilename);
811 g_free (newname);
812 g_free (src);
813 g_free (dest);
814 }
815
816 camel_operation_pop_message (cancellable);
817 closedir (dir);
818 }
819
820 g_free (new);
821 g_free (cur);
822
823 camel_folder_summary_free_array (known_uids);
824 g_mutex_unlock (&mds->priv->summary_lock);
825
826 return 0;
827 }
828
829 /* sync the summary with the ondisk files. */
830 static gint
maildir_summary_sync(CamelLocalSummary * cls,gboolean expunge,CamelFolderChangeInfo * changes,GCancellable * cancellable,GError ** error)831 maildir_summary_sync (CamelLocalSummary *cls,
832 gboolean expunge,
833 CamelFolderChangeInfo *changes,
834 GCancellable *cancellable,
835 GError **error)
836 {
837 CamelLocalSummaryClass *local_summary_class;
838 gint i;
839 CamelMessageInfo *info;
840 CamelMaildirMessageInfo *mdi;
841 GList *removed_uids = NULL;
842 gchar *name;
843 struct stat st;
844 GPtrArray *known_uids;
845
846 d (printf ("summary_sync(expunge=%s)\n", expunge?"true":"false"));
847
848 /* Check consistency on save only if not exiting the application */
849 if (!camel_application_is_exiting &&
850 camel_local_summary_check (cls, changes, cancellable, error) == -1)
851 return -1;
852
853 camel_operation_push_message (cancellable, _("Storing folder"));
854
855 known_uids = camel_folder_summary_get_array ((CamelFolderSummary *) cls);
856 for (i = (known_uids ? known_uids->len : 0) - 1; i >= 0; i--) {
857 const gchar *uid = g_ptr_array_index (known_uids, i);
858 guint32 flags = 0;
859
860 camel_operation_progress (cancellable, (known_uids->len - i) * 100 / known_uids->len);
861
862 /* Message infos with folder-flagged flags are not removed from memory */
863 info = camel_folder_summary_peek_loaded ((CamelFolderSummary *) cls, uid);
864 mdi = info ? CAMEL_MAILDIR_MESSAGE_INFO (info) : NULL;
865 if (!mdi) {
866 flags = camel_folder_summary_get_info_flags ((CamelFolderSummary *) cls, uid);
867 if (flags == (~0))
868 flags = 0;
869 }
870
871 if (expunge && (
872 (mdi && (camel_message_info_get_flags (info) & CAMEL_MESSAGE_DELETED) != 0) ||
873 (!mdi && (flags & CAMEL_MESSAGE_DELETED) != 0))) {
874 const gchar *mdi_filename;
875 gchar *tmp = NULL;
876
877 if (mdi) {
878 mdi_filename = camel_maildir_message_info_get_filename (mdi);
879 } else {
880 tmp = camel_maildir_summary_uid_and_flags_to_name (CAMEL_MAILDIR_SUMMARY (cls), uid, flags);
881 mdi_filename = tmp;
882 }
883
884 name = g_strdup_printf ("%s/cur/%s", cls->folder_path, mdi_filename);
885
886 g_free (tmp);
887
888 d (printf ("deleting %s\n", name));
889 if (unlink (name) == 0 || errno == ENOENT) {
890
891 /* FIXME: put this in folder_summary::remove()? */
892 if (cls->index)
893 camel_index_delete_name (cls->index, uid);
894
895 camel_folder_change_info_remove_uid (changes, uid);
896 removed_uids = g_list_prepend (removed_uids, (gpointer) camel_pstring_strdup (uid));
897 }
898 g_free (name);
899 } else if (mdi && camel_message_info_get_folder_flagged (info)) {
900 gchar *newname = camel_maildir_summary_info_to_name (info);
901 gchar *dest;
902
903 /* do we care about additional metainfo stored inside the message? */
904 /* probably should all go in the filename? */
905
906 /* have our flags/ i.e. name changed? */
907 if (strcmp (newname, camel_maildir_message_info_get_filename (mdi))) {
908 name = g_strdup_printf ("%s/cur/%s", cls->folder_path, camel_maildir_message_info_get_filename (mdi));
909 dest = g_strdup_printf ("%s/cur/%s", cls->folder_path, newname);
910 if (g_rename (name, dest) == -1) {
911 g_warning ("%s: Failed to rename '%s' to '%s': %s", G_STRFUNC, name, dest, g_strerror (errno));
912 }
913 if (g_stat (dest, &st) == -1) {
914 /* we'll assume it didn't work, but dont change anything else */
915 } else {
916 /* TODO: If this is made mt-safe, then this code could be a problem, since
917 * the estrv is being modified.
918 * Sigh, this may mean the maildir name has to be cached another way */
919 camel_maildir_message_info_set_filename (mdi, newname);
920 }
921 g_free (name);
922 g_free (dest);
923 }
924
925 g_free (newname);
926
927 /* strip FOLDER_MESSAGE_FLAGED, etc */
928 camel_message_info_set_flags (info, 0xffff, camel_message_info_get_flags (info));
929 }
930 g_clear_object (&info);
931 }
932
933 if (removed_uids) {
934 camel_folder_summary_remove_uids (CAMEL_FOLDER_SUMMARY (cls), removed_uids);
935 g_list_free_full (removed_uids, (GDestroyNotify) camel_pstring_free);
936 }
937
938 camel_folder_summary_free_array (known_uids);
939 camel_operation_pop_message (cancellable);
940
941 /* Chain up to parent's sync() method. */
942 local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_maildir_summary_parent_class);
943 return local_summary_class->sync (cls, expunge, changes, cancellable, error);
944 }
945