1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
2 /*
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4 *
5 * This library is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Michael Zucchi <notzed@ximian.com>
18 */
19
20 #include "evolution-data-server-config.h"
21
22 #include <ctype.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
31 #include <glib/gi18n-lib.h>
32 #include <glib/gstdio.h>
33
34 #include "camel-mbox-message-info.h"
35 #include "camel-mbox-summary.h"
36 #include "camel-local-private.h"
37
38 #define io(x)
39 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
40
41 /* This uses elm/pine style "Status" & "X-Status" headers */
42
43 #define CAMEL_MBOX_SUMMARY_VERSION (1)
44
45 #define CHECK_CALL(x) G_STMT_START { \
46 if ((x) == -1) { \
47 g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \
48 } \
49 } G_STMT_END
50
51 typedef struct _CamelMboxMessageContentInfo CamelMboxMessageContentInfo;
52
53 struct _CamelMboxMessageContentInfo {
54 CamelMessageContentInfo info;
55 };
56
57 static CamelFIRecord *
58 summary_header_save (CamelFolderSummary *,
59 GError **error);
60 static gboolean summary_header_load (CamelFolderSummary *,
61 CamelFIRecord *);
62 static CamelMessageInfo *
63 message_info_new_from_headers (CamelFolderSummary *,
64 const CamelNameValueArray *);
65 static CamelMessageInfo *
66 message_info_new_from_parser (CamelFolderSummary *,
67 CamelMimeParser *);
68
69 static gchar * mbox_summary_encode_x_evolution (CamelLocalSummary *cls,
70 const CamelMessageInfo *mi);
71
72 static gint mbox_summary_check (CamelLocalSummary *cls,
73 CamelFolderChangeInfo *changeinfo,
74 GCancellable *cancellable,
75 GError **error);
76 static gint mbox_summary_sync (CamelLocalSummary *cls,
77 gboolean expunge,
78 CamelFolderChangeInfo *changeinfo,
79 GCancellable *cancellable,
80 GError **error);
81 static CamelMessageInfo *
82 mbox_summary_add (CamelLocalSummary *cls,
83 CamelMimeMessage *msg,
84 const CamelMessageInfo *info,
85 CamelFolderChangeInfo *ci,
86 GError **error);
87 static gint mbox_summary_sync_quick (CamelMboxSummary *cls,
88 gboolean expunge,
89 CamelFolderChangeInfo *changeinfo,
90 GCancellable *cancellable,
91 GError **error);
92 static gint mbox_summary_sync_full (CamelMboxSummary *cls,
93 gboolean expunge,
94 CamelFolderChangeInfo *changeinfo,
95 GCancellable *cancellable,
96 GError **error);
97
98 /* Which status flags are stored in each separate header */
99 #define STATUS_XSTATUS \
100 (CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_DELETED)
101 #define STATUS_STATUS (CAMEL_MESSAGE_SEEN)
102
103 static void encode_status (guint32 flags, gchar status[8]);
104 static guint32 decode_status (const gchar *status);
105
G_DEFINE_TYPE(CamelMboxSummary,camel_mbox_summary,CAMEL_TYPE_LOCAL_SUMMARY)106 G_DEFINE_TYPE (
107 CamelMboxSummary,
108 camel_mbox_summary,
109 CAMEL_TYPE_LOCAL_SUMMARY)
110
111 static void
112 camel_mbox_summary_class_init (CamelMboxSummaryClass *class)
113 {
114 CamelFolderSummaryClass *folder_summary_class;
115 CamelLocalSummaryClass *local_summary_class;
116
117 folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
118 folder_summary_class->message_info_type = CAMEL_TYPE_MBOX_MESSAGE_INFO;
119 folder_summary_class->sort_by = "bdata";
120 folder_summary_class->collate = "mbox_frompos_sort";
121 folder_summary_class->summary_header_load = summary_header_load;
122 folder_summary_class->summary_header_save = summary_header_save;
123 folder_summary_class->message_info_new_from_headers = message_info_new_from_headers;
124 folder_summary_class->message_info_new_from_parser = message_info_new_from_parser;
125
126 local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (class);
127 local_summary_class->encode_x_evolution = mbox_summary_encode_x_evolution;
128 local_summary_class->check = mbox_summary_check;
129 local_summary_class->sync = mbox_summary_sync;
130 local_summary_class->add = mbox_summary_add;
131
132 class->sync_quick = mbox_summary_sync_quick;
133 class->sync_full = mbox_summary_sync_full;
134 }
135
136 static void
camel_mbox_summary_init(CamelMboxSummary * mbox_summary)137 camel_mbox_summary_init (CamelMboxSummary *mbox_summary)
138 {
139 CamelFolderSummary *folder_summary;
140
141 folder_summary = CAMEL_FOLDER_SUMMARY (mbox_summary);
142
143 /* and a unique file version */
144 camel_folder_summary_set_version (folder_summary, camel_folder_summary_get_version (folder_summary) + CAMEL_MBOX_SUMMARY_VERSION);
145 }
146
147 /**
148 * camel_mbox_summary_new:
149 * @folder: a parent #CamelFolder
150 * @mbox_name: filename of the mbox
151 * @index: (nullable): an optional #CamelIndex to use, or %NULL
152 *
153 * Create a new #CamelMboxSummary object.
154 *
155 * Returns: (transfer full): A new #CamelMboxSummary object
156 **/
157 CamelMboxSummary *
camel_mbox_summary_new(CamelFolder * folder,const gchar * mbox_name,CamelIndex * index)158 camel_mbox_summary_new (CamelFolder *folder,
159 const gchar *mbox_name,
160 CamelIndex *index)
161 {
162 CamelMboxSummary *new;
163
164 new = g_object_new (CAMEL_TYPE_MBOX_SUMMARY, "folder", folder, NULL);
165 if (folder) {
166 CamelStore *parent_store;
167
168 parent_store = camel_folder_get_parent_store (folder);
169
170 /* Set the functions for db sorting */
171 camel_db_set_collate (camel_store_get_db (parent_store), "bdata", "mbox_frompos_sort", (CamelDBCollate) camel_local_frompos_sort);
172 }
173 camel_local_summary_construct ((CamelLocalSummary *) new, mbox_name, index);
174 return new;
175 }
176
camel_mbox_summary_xstatus(CamelMboxSummary * mbs,gint state)177 void camel_mbox_summary_xstatus (CamelMboxSummary *mbs, gint state)
178 {
179 mbs->xstatus = state;
180 }
181
182 static gchar *
mbox_summary_encode_x_evolution(CamelLocalSummary * cls,const CamelMessageInfo * mi)183 mbox_summary_encode_x_evolution (CamelLocalSummary *cls,
184 const CamelMessageInfo *mi)
185 {
186 const gchar *p, *uidstr;
187 guint32 uid, flags;
188
189 /* This is busted, it is supposed to encode ALL DATA */
190 p = uidstr = camel_message_info_get_uid (mi);
191 while (*p && isdigit (*p))
192 p++;
193
194 flags = camel_message_info_get_flags (mi);
195
196 if (*p == 0 && sscanf (uidstr, "%u", &uid) == 1) {
197 return g_strdup_printf ("%08x-%04x", uid, flags & 0xffff);
198 } else {
199 return g_strdup_printf ("%s-%04x", uidstr, flags & 0xffff);
200 }
201 }
202
203 static gboolean
summary_header_load(CamelFolderSummary * s,struct _CamelFIRecord * fir)204 summary_header_load (CamelFolderSummary *s,
205 struct _CamelFIRecord *fir)
206 {
207 CamelMboxSummary *mbs = CAMEL_MBOX_SUMMARY (s);
208 gchar *part;
209
210 if (!CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->summary_header_load (s, fir))
211 return FALSE;
212
213 part = fir->bdata;
214 if (part) {
215 mbs->version = camel_util_bdata_get_number (&part, 0);
216 mbs->folder_size = camel_util_bdata_get_number (&part, 0);
217 }
218
219 return TRUE;
220 }
221
222 static CamelFIRecord *
summary_header_save(CamelFolderSummary * s,GError ** error)223 summary_header_save (CamelFolderSummary *s,
224 GError **error)
225 {
226 CamelFolderSummaryClass *folder_summary_class;
227 CamelMboxSummary *mbs = CAMEL_MBOX_SUMMARY (s);
228 struct _CamelFIRecord *fir;
229 gchar *tmp;
230
231 /* Chain up to parent's summary_header_save() method. */
232 folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class);
233 fir = folder_summary_class->summary_header_save (s, error);
234 if (fir) {
235 tmp = fir->bdata;
236 fir->bdata = g_strdup_printf ("%s %d %d", tmp ? tmp : "", CAMEL_MBOX_SUMMARY_VERSION, (gint) mbs->folder_size);
237 g_free (tmp);
238 }
239
240 return fir;
241 }
242
243 static CamelMessageInfo *
message_info_new_from_headers(CamelFolderSummary * summary,const CamelNameValueArray * headers)244 message_info_new_from_headers (CamelFolderSummary *summary,
245 const CamelNameValueArray *headers)
246 {
247 CamelMessageInfo *mi;
248 CamelMboxSummary *mbs = (CamelMboxSummary *) summary;
249
250 mi = CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->message_info_new_from_headers (summary, headers);
251 if (mi) {
252 const gchar *xev, *uid;
253 CamelMessageInfo *info = NULL;
254 gint add = 0; /* bitmask of things to add, 1 assign uid, 2, just add as new, 4 = recent */
255 const gchar *status = NULL, *xstatus = NULL;
256 guint32 flags = 0;
257
258 if (mbs->xstatus) {
259 /* check for existance of status & x-status headers */
260 status = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "Status");
261 if (status)
262 flags = decode_status (status);
263 xstatus = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "X-Status");
264 if (xstatus)
265 flags |= decode_status (xstatus);
266 }
267
268 /* if we have an xev header, use it, else assign a new one */
269 xev = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "X-Evolution");
270 if (xev != NULL
271 && camel_local_summary_decode_x_evolution ((CamelLocalSummary *) summary, xev, mi) == 0) {
272 uid = camel_message_info_get_uid (mi);
273 d (printf ("found valid x-evolution: %s\n", uid));
274 /* If one is there, it should be there already */
275 info = camel_folder_summary_peek_loaded (summary, uid);
276 if (info) {
277 if ((camel_message_info_get_flags (info) & CAMEL_MESSAGE_FOLDER_NOTSEEN)) {
278 camel_message_info_set_flags (info, CAMEL_MESSAGE_FOLDER_NOTSEEN, 0);
279 g_clear_object (&mi);
280 mi = info;
281 } else {
282 add = 7;
283 d (printf ("seen '%s' before, adding anew\n", uid));
284 g_clear_object (&info);
285 }
286 } else {
287 add = 2;
288 d (printf ("but isn't present in summary\n"));
289 }
290 } else {
291 d (printf ("didn't find x-evolution\n"));
292 add = 7;
293 }
294
295 if ((add & 1) != 0) {
296 gchar *new_uid = camel_folder_summary_next_uid_string (summary);
297
298 camel_message_info_set_flags (mi, CAMEL_MESSAGE_FOLDER_FLAGGED | CAMEL_MESSAGE_FOLDER_NOXEV, CAMEL_MESSAGE_FOLDER_FLAGGED | CAMEL_MESSAGE_FOLDER_NOXEV);
299 camel_message_info_set_uid (mi, new_uid);
300
301 g_free (new_uid);
302 } else {
303 camel_folder_summary_set_next_uid (summary, strtoul (camel_message_info_get_uid (mi), NULL, 10));
304 }
305
306 if (mbs->xstatus && (add & 2) != 0) {
307 /* use the status as the flags when we read it the first time */
308 if (status)
309 camel_message_info_set_flags (mi, STATUS_STATUS, flags);
310 if (xstatus)
311 camel_message_info_set_flags (mi, STATUS_XSTATUS, flags);
312 }
313
314 if (mbs->changes) {
315 if (add&2)
316 camel_folder_change_info_add_uid (mbs->changes, camel_message_info_get_uid (mi));
317 if ((add&4) && status == NULL)
318 camel_folder_change_info_recent_uid (mbs->changes, camel_message_info_get_uid (mi));
319 }
320
321 camel_mbox_message_info_set_offset (CAMEL_MBOX_MESSAGE_INFO (mi), -1);
322 }
323
324 return (CamelMessageInfo *) mi;
325 }
326
327 static CamelMessageInfo *
message_info_new_from_parser(CamelFolderSummary * s,CamelMimeParser * mp)328 message_info_new_from_parser (CamelFolderSummary *s,
329 CamelMimeParser *mp)
330 {
331 CamelMessageInfo *mi;
332
333 mi = CAMEL_FOLDER_SUMMARY_CLASS (camel_mbox_summary_parent_class)->message_info_new_from_parser (s, mp);
334 if (mi) {
335 camel_mbox_message_info_set_offset (CAMEL_MBOX_MESSAGE_INFO (mi), camel_mime_parser_tell_start_from (mp));
336 }
337
338 return mi;
339 }
340
341 /* like summary_rebuild, but also do changeinfo stuff (if supplied) */
342 static gint
summary_update(CamelLocalSummary * cls,goffset offset,CamelFolderChangeInfo * changeinfo,GCancellable * cancellable,GError ** error)343 summary_update (CamelLocalSummary *cls,
344 goffset offset,
345 CamelFolderChangeInfo *changeinfo,
346 GCancellable *cancellable,
347 GError **error)
348 {
349 gint i;
350 CamelFolderSummary *s = (CamelFolderSummary *) cls;
351 CamelMboxSummary *mbs = (CamelMboxSummary *) cls;
352 CamelMimeParser *mp;
353 CamelMessageInfo *mi;
354 CamelStore *parent_store;
355 const gchar *full_name;
356 gint fd;
357 gint ok = 0;
358 struct stat st;
359 goffset size = 0;
360 GList *del = NULL;
361 GPtrArray *known_uids;
362
363 d (printf ("Calling summary update, from pos %d\n", (gint) offset));
364
365 cls->index_force = FALSE;
366
367 camel_operation_push_message (cancellable, _("Storing folder"));
368
369 camel_folder_summary_lock (s);
370 fd = g_open (cls->folder_path, O_LARGEFILE | O_RDONLY | O_BINARY, 0);
371 if (fd == -1) {
372 d (printf ("%s failed to open: %s\n", cls->folder_path, g_strerror (errno)));
373 camel_folder_summary_unlock (s);
374 g_set_error (
375 error, G_IO_ERROR,
376 g_io_error_from_errno (errno),
377 _("Could not open folder: %s: %s"),
378 cls->folder_path, g_strerror (errno));
379 camel_operation_pop_message (cancellable);
380 return -1;
381 }
382
383 if (fstat (fd, &st) == 0)
384 size = st.st_size;
385
386 mp = camel_mime_parser_new ();
387 camel_mime_parser_init_with_fd (mp, fd);
388 camel_mime_parser_scan_from (mp, TRUE);
389 camel_mime_parser_seek (mp, offset, SEEK_SET);
390
391 if (offset > 0) {
392 if (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM
393 && camel_mime_parser_tell_start_from (mp) == offset) {
394 camel_mime_parser_unstep (mp);
395 } else {
396 g_warning ("The next message didn't start where I expected, building summary from start");
397 camel_mime_parser_drop_step (mp);
398 offset = 0;
399 camel_mime_parser_seek (mp, offset, SEEK_SET);
400 }
401 }
402
403 /* we mark messages as to whether we've seen them or not.
404 * If we're not starting from the start, we must be starting
405 * from the old end, so everything must be treated as new */
406 camel_folder_summary_prepare_fetch_all (s, NULL);
407 known_uids = camel_folder_summary_get_array (s);
408 for (i = 0; known_uids && i < known_uids->len; i++) {
409 mi = camel_folder_summary_get (s, g_ptr_array_index (known_uids, i));
410 camel_message_info_set_flags (mi, CAMEL_MESSAGE_FOLDER_NOTSEEN, offset == 0 ? CAMEL_MESSAGE_FOLDER_NOTSEEN : 0);
411 g_clear_object (&mi);
412 }
413 camel_folder_summary_free_array (known_uids);
414 mbs->changes = changeinfo;
415
416 while (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM) {
417 CamelMessageInfo *info;
418 goffset pc = camel_mime_parser_tell_start_from (mp) + 1;
419 gint progress;
420
421 /* To avoid a division by zero if the fstat()
422 * call above failed. */
423 size = MAX (size, pc);
424 progress = (size > 0) ? (gint) (((gfloat) pc / size) * 100) : 0;
425
426 camel_operation_progress (cancellable, progress);
427
428 info = camel_folder_summary_info_new_from_parser (s, mp);
429 camel_folder_summary_add (s, info, FALSE);
430 g_clear_object (&info);
431
432 g_warn_if_fail (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM_END);
433 }
434
435 g_object_unref (mp);
436
437 known_uids = camel_folder_summary_get_array (s);
438 for (i = 0; known_uids && i < known_uids->len; i++) {
439 const gchar *uid;
440
441 uid = g_ptr_array_index (known_uids, i);
442 if (!uid)
443 continue;
444
445 mi = camel_folder_summary_get (s, uid);
446 /* must've dissapeared from the file? */
447 if (!mi || (camel_message_info_get_flags (mi) & CAMEL_MESSAGE_FOLDER_NOTSEEN) != 0) {
448 d (printf ("uid '%s' vanished, removing", uid));
449 if (changeinfo)
450 camel_folder_change_info_remove_uid (changeinfo, uid);
451 del = g_list_prepend (del, (gpointer) camel_pstring_strdup (uid));
452 if (mi)
453 camel_folder_summary_remove (s, mi);
454 }
455
456 g_clear_object (&mi);
457 }
458
459 if (known_uids)
460 camel_folder_summary_free_array (known_uids);
461
462 /* Delete all in one transaction */
463 full_name = camel_folder_get_full_name (camel_folder_summary_get_folder (s));
464 parent_store = camel_folder_get_parent_store (camel_folder_summary_get_folder (s));
465 camel_db_delete_uids (camel_store_get_db (parent_store), full_name, del, NULL);
466 g_list_foreach (del, (GFunc) camel_pstring_free, NULL);
467 g_list_free (del);
468
469 mbs->changes = NULL;
470
471 /* update the file size/mtime in the summary */
472 if (ok != -1) {
473 if (g_stat (cls->folder_path, &st) == 0) {
474 camel_folder_summary_touch (s);
475 mbs->folder_size = st.st_size;
476 camel_folder_summary_set_timestamp (s, st.st_mtime);
477 }
478 }
479
480 camel_operation_pop_message (cancellable);
481 camel_folder_summary_unlock (s);
482
483 return ok;
484 }
485
486 static gint
mbox_summary_check(CamelLocalSummary * cls,CamelFolderChangeInfo * changes,GCancellable * cancellable,GError ** error)487 mbox_summary_check (CamelLocalSummary *cls,
488 CamelFolderChangeInfo *changes,
489 GCancellable *cancellable,
490 GError **error)
491 {
492 CamelMboxSummary *mbs = (CamelMboxSummary *) cls;
493 CamelFolderSummary *s = (CamelFolderSummary *) cls;
494 struct stat st;
495 gint ret = 0;
496 gint i;
497
498 d (printf ("Checking summary\n"));
499
500 camel_folder_summary_lock (s);
501
502 /* check if the summary is up-to-date */
503 if (g_stat (cls->folder_path, &st) == -1) {
504 camel_folder_summary_clear (s, NULL);
505 camel_folder_summary_unlock (s);
506 g_set_error (
507 error, G_IO_ERROR,
508 g_io_error_from_errno (errno),
509 _("Cannot check folder: %s: %s"),
510 cls->folder_path, g_strerror (errno));
511 return -1;
512 }
513
514 if (cls->check_force)
515 mbs->folder_size = 0;
516 cls->check_force = 0;
517
518 if (st.st_size == 0) {
519 GPtrArray *known_uids;
520
521 /* empty? No need to scan at all */
522 d (printf ("Empty mbox, clearing summary\n"));
523 camel_folder_summary_prepare_fetch_all (s, NULL);
524 known_uids = camel_folder_summary_get_array (s);
525 for (i = 0; known_uids && i < known_uids->len; i++) {
526 CamelMessageInfo *info = camel_folder_summary_get (s, g_ptr_array_index (known_uids, i));
527
528 if (info) {
529 camel_folder_change_info_remove_uid (changes, camel_message_info_get_uid (info));
530 g_clear_object (&info);
531 }
532 }
533 camel_folder_summary_free_array (known_uids);
534 camel_folder_summary_clear (s, NULL);
535 ret = 0;
536 } else {
537 /* is the summary uptodate? */
538 if (st.st_size != mbs->folder_size || st.st_mtime != camel_folder_summary_get_timestamp (s)) {
539 if (mbs->folder_size < st.st_size) {
540 /* this will automatically rescan from 0 if there is a problem */
541 d (printf ("folder grew, attempting to rebuild from %d\n", mbs->folder_size));
542 ret = summary_update (cls, mbs->folder_size, changes, cancellable, error);
543 } else {
544 d (printf ("folder shrank! rebuilding from start\n"));
545 ret = summary_update (cls, 0, changes, cancellable, error);
546 }
547 } else {
548 d (printf ("Folder unchanged, do nothing\n"));
549 }
550 }
551
552 /* FIXME: move upstream? */
553
554 if (ret != -1) {
555 if (mbs->folder_size != st.st_size || camel_folder_summary_get_timestamp (s) != st.st_mtime) {
556 mbs->folder_size = st.st_size;
557 camel_folder_summary_set_timestamp (s, st.st_mtime);
558 camel_folder_summary_touch (s);
559 }
560 }
561
562 camel_folder_summary_unlock (s);
563
564 return ret;
565 }
566
567 /* perform a full sync */
568 static gint
mbox_summary_sync_full(CamelMboxSummary * mbs,gboolean expunge,CamelFolderChangeInfo * changeinfo,GCancellable * cancellable,GError ** error)569 mbox_summary_sync_full (CamelMboxSummary *mbs,
570 gboolean expunge,
571 CamelFolderChangeInfo *changeinfo,
572 GCancellable *cancellable,
573 GError **error)
574 {
575 CamelLocalSummary *cls = (CamelLocalSummary *) mbs;
576 CamelFolderSummary *s = CAMEL_FOLDER_SUMMARY (mbs);
577 gint fd = -1, fdout = -1;
578 gchar *tmpname = NULL;
579 gsize tmpname_len = 0;
580 guint32 flags = (expunge ? 1 : 0), filemode = 0600;
581 struct stat st;
582
583 d (printf ("performing full summary/sync\n"));
584
585 camel_operation_push_message (cancellable, _("Storing folder"));
586 camel_folder_summary_lock (s);
587
588 fd = g_open (cls->folder_path, O_LARGEFILE | O_RDONLY | O_BINARY, 0);
589 if (fd == -1) {
590 camel_folder_summary_unlock (s);
591 g_set_error (
592 error, G_IO_ERROR,
593 g_io_error_from_errno (errno),
594 _("Could not open file: %s: %s"),
595 cls->folder_path, g_strerror (errno));
596 camel_operation_pop_message (cancellable);
597 return -1;
598 }
599
600 /* preserve attributes as set on the file previously */
601 if (fstat (fd, &st) == 0)
602 filemode = 07777 & st.st_mode;
603
604 tmpname_len = strlen (cls->folder_path) + 5;
605 tmpname = g_alloca (tmpname_len);
606 g_snprintf (tmpname, tmpname_len, "%s.tmp", cls->folder_path);
607 d (printf ("Writing temporary file to %s\n", tmpname));
608 fdout = g_open (tmpname, O_LARGEFILE | O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, filemode);
609 if (fdout == -1) {
610 g_set_error (
611 error, G_IO_ERROR,
612 g_io_error_from_errno (errno),
613 _("Cannot open temporary mailbox: %s"),
614 g_strerror (errno));
615 goto error;
616 }
617
618 if (camel_mbox_summary_sync_mbox (
619 (CamelMboxSummary *) cls, flags, changeinfo,
620 fd, fdout, cancellable, error) == -1)
621 goto error;
622
623 d (printf ("Closing folders\n"));
624
625 if (close (fd) == -1) {
626 g_warning ("Cannot close source folder: %s", g_strerror (errno));
627 g_set_error (
628 error, G_IO_ERROR,
629 g_io_error_from_errno (errno),
630 _("Could not close source folder %s: %s"),
631 cls->folder_path, g_strerror (errno));
632 fd = -1;
633 goto error;
634 }
635
636 fd = -1;
637
638 if (close (fdout) == -1) {
639 g_warning ("Cannot close temporary folder: %s", g_strerror (errno));
640 g_set_error (
641 error, G_IO_ERROR,
642 g_io_error_from_errno (errno),
643 _("Could not close temporary folder: %s"),
644 g_strerror (errno));
645 fdout = -1;
646 goto error;
647 }
648
649 fdout = -1;
650
651 /* this should probably either use unlink/link/unlink, or recopy over
652 * the original mailbox, for various locking reasons/etc */
653 #ifdef G_OS_WIN32
654 if (g_file_test (cls->folder_path,G_FILE_TEST_IS_REGULAR) && g_remove (cls->folder_path) == -1)
655 g_warning ("Cannot remove %s: %s", cls->folder_path, g_strerror (errno));
656 #endif
657 if (g_rename (tmpname, cls->folder_path) == -1) {
658 g_warning ("Cannot rename folder: %s", g_strerror (errno));
659 g_set_error (
660 error, G_IO_ERROR,
661 g_io_error_from_errno (errno),
662 _("Could not rename folder: %s"),
663 g_strerror (errno));
664 goto error;
665 }
666
667 camel_operation_pop_message (cancellable);
668 camel_folder_summary_unlock (s);
669
670 return 0;
671 error:
672 if (fd != -1)
673 close (fd);
674
675 if (fdout != -1)
676 close (fdout);
677
678 g_unlink (tmpname);
679
680 camel_operation_pop_message (cancellable);
681 camel_folder_summary_unlock (s);
682
683 return -1;
684 }
685
686 static gint
cms_sort_frompos(gconstpointer a,gconstpointer b,gpointer data)687 cms_sort_frompos (gconstpointer a,
688 gconstpointer b,
689 gpointer data)
690 {
691 CamelFolderSummary *summary = (CamelFolderSummary *) data;
692 CamelMboxMessageInfo *info1, *info2;
693 goffset afrompos, bfrompos;
694 gint ret = 0;
695
696 /* Things are in memory already. Sorting speeds up syncing, if things are sorted by from pos. */
697 info1 = (CamelMboxMessageInfo *) camel_folder_summary_get (summary, *(gchar **) a);
698 info2 = (CamelMboxMessageInfo *) camel_folder_summary_get (summary, *(gchar **) b);
699
700 afrompos = camel_mbox_message_info_get_offset (info1);
701 bfrompos = camel_mbox_message_info_get_offset (info2);
702
703 if (afrompos > bfrompos)
704 ret = 1;
705 else if (afrompos < bfrompos)
706 ret = -1;
707 else
708 ret = 0;
709
710 g_clear_object (&info1);
711 g_clear_object (&info2);
712
713 return ret;
714
715 }
716
717 /* perform a quick sync - only system flags have changed */
718 static gint
mbox_summary_sync_quick(CamelMboxSummary * mbs,gboolean expunge,CamelFolderChangeInfo * changeinfo,GCancellable * cancellable,GError ** error)719 mbox_summary_sync_quick (CamelMboxSummary *mbs,
720 gboolean expunge,
721 CamelFolderChangeInfo *changeinfo,
722 GCancellable *cancellable,
723 GError **error)
724 {
725 CamelLocalSummary *cls = (CamelLocalSummary *) mbs;
726 CamelFolderSummary *s = (CamelFolderSummary *) mbs;
727 CamelMimeParser *mp = NULL;
728 gint i;
729 CamelMessageInfo *info = NULL;
730 gint fd = -1, pfd;
731 gchar *xevnew, *xevtmp;
732 const gchar *xev;
733 gint len;
734 goffset lastpos;
735 GPtrArray *summary = NULL;
736
737 d (printf ("Performing quick summary sync\n"));
738
739 camel_operation_push_message (cancellable, _("Storing folder"));
740 camel_folder_summary_lock (s);
741
742 fd = g_open (cls->folder_path, O_LARGEFILE | O_RDWR | O_BINARY, 0);
743 if (fd == -1) {
744 camel_folder_summary_unlock (s);
745 g_set_error (
746 error, G_IO_ERROR,
747 g_io_error_from_errno (errno),
748 _("Could not open file: %s: %s"),
749 cls->folder_path, g_strerror (errno));
750
751 camel_operation_pop_message (cancellable);
752 return -1;
753 }
754
755 /* need to dup since mime parser closes its fd once it is finalized */
756 pfd = dup (fd);
757 if (pfd == -1) {
758 camel_folder_summary_unlock (s);
759 g_set_error (
760 error, G_IO_ERROR,
761 g_io_error_from_errno (errno),
762 _("Could not store folder: %s"),
763 g_strerror (errno));
764 close (fd);
765 return -1;
766 }
767
768 mp = camel_mime_parser_new ();
769 camel_mime_parser_scan_from (mp, TRUE);
770 camel_mime_parser_scan_pre_from (mp, TRUE);
771 camel_mime_parser_init_with_fd (mp, pfd);
772
773 /* Sync only the changes */
774 summary = camel_folder_summary_get_changed ((CamelFolderSummary *) mbs);
775 if (summary->len)
776 g_ptr_array_sort_with_data (summary, cms_sort_frompos, mbs);
777
778 for (i = 0; i < summary->len; i++) {
779 goffset frompos;
780 gint xevoffset;
781 gint pc = (i + 1) * 100 / summary->len;
782
783 camel_operation_progress (cancellable, pc);
784
785 info = camel_folder_summary_get (s, summary->pdata[i]);
786
787 d (printf ("Checking message %s %08x\n", camel_message_info_get_uid (info), camel_message_info_get_flags (info)));
788
789 if (!camel_message_info_get_folder_flagged (info)) {
790 g_clear_object (&info);
791 continue;
792 }
793
794 frompos = camel_mbox_message_info_get_offset (CAMEL_MBOX_MESSAGE_INFO (info));
795
796 d (printf ("Updating message %s: %d\n", camel_message_info_get_uid (info), (gint) frompos));
797
798 camel_mime_parser_seek (mp, frompos, SEEK_SET);
799
800 if (camel_mime_parser_step (mp, NULL, NULL) != CAMEL_MIME_PARSER_STATE_FROM) {
801 g_set_error (
802 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
803 _("MBOX file is corrupted, please fix it. (Expected a From line, but didn’t get it.)"));
804 goto error;
805 }
806
807 if (camel_mime_parser_tell_start_from (mp) != frompos) {
808 g_warning (
809 "Didn't get the next message where I expected (%d) got %d instead",
810 (gint) frompos, (gint) camel_mime_parser_tell_start_from (mp));
811 g_set_error (
812 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
813 _("Summary and folder mismatch, even after a sync"));
814 goto error;
815 }
816
817 if (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM_END) {
818 g_warning ("camel_mime_parser_step failed (2)");
819 goto error;
820 }
821
822 xev = camel_mime_parser_header (mp, "X-Evolution", &xevoffset);
823 if (xev == NULL || camel_local_summary_decode_x_evolution (cls, xev, NULL) == -1) {
824 g_warning ("We're supposed to have a valid x-ev header, but we dont");
825 goto error;
826 }
827 xevnew = camel_local_summary_encode_x_evolution (cls, info);
828 /* SIGH: encode_param_list is about the only function which folds headers by itself.
829 * This should be fixed somehow differently (either parser doesn't fold headers,
830 * or param_list doesn't, or something */
831 xevtmp = camel_header_unfold (xevnew);
832 /* the raw header contains a leading ' ', so (dis)count that too */
833 if (strlen (xev) - 1 != strlen (xevtmp)) {
834 g_free (xevnew);
835 g_free (xevtmp);
836 g_warning ("Hmm, the xev headers shouldn't have changed size, but they did");
837 goto error;
838 }
839 g_free (xevtmp);
840
841 /* we write out the xevnew string, assuming its been folded identically to the original too! */
842
843 lastpos = lseek (fd, 0, SEEK_CUR);
844 CHECK_CALL (lseek (fd, xevoffset + strlen ("X-Evolution: "), SEEK_SET));
845 do {
846 len = write (fd, xevnew, strlen (xevnew));
847 } while (len == -1 && errno == EINTR);
848
849 if (lastpos != -1 && lseek (fd, lastpos, SEEK_SET) == (off_t) -1) {
850 g_warning (
851 "%s: Failed to rewind file to last position: %s",
852 G_STRFUNC, g_strerror (errno));
853 }
854 g_free (xevnew);
855
856 camel_mime_parser_drop_step (mp);
857 camel_mime_parser_drop_step (mp);
858
859 camel_message_info_set_flags (info, 0xffff, camel_message_info_get_flags (info));
860 g_clear_object (&info);
861 }
862
863 d (printf ("Closing folders\n"));
864
865 if (close (fd) == -1) {
866 g_warning ("Cannot close source folder: %s", g_strerror (errno));
867 g_set_error (
868 error, G_IO_ERROR,
869 g_io_error_from_errno (errno),
870 _("Could not close source folder %s: %s"),
871 cls->folder_path, g_strerror (errno));
872 fd = -1;
873 goto error;
874 }
875
876 g_ptr_array_foreach (summary, (GFunc) camel_pstring_free, NULL);
877 g_ptr_array_free (summary, TRUE);
878 g_object_unref (mp);
879
880 camel_operation_pop_message (cancellable);
881 camel_folder_summary_unlock (s);
882
883 return 0;
884 error:
885 g_ptr_array_foreach (summary, (GFunc) camel_pstring_free, NULL);
886 g_ptr_array_free (summary, TRUE);
887 g_object_unref (mp);
888 if (fd != -1)
889 close (fd);
890 g_clear_object (&info);
891
892 camel_operation_pop_message (cancellable);
893 camel_folder_summary_unlock (s);
894
895 return -1;
896 }
897
898 static gint
mbox_summary_sync(CamelLocalSummary * cls,gboolean expunge,CamelFolderChangeInfo * changeinfo,GCancellable * cancellable,GError ** error)899 mbox_summary_sync (CamelLocalSummary *cls,
900 gboolean expunge,
901 CamelFolderChangeInfo *changeinfo,
902 GCancellable *cancellable,
903 GError **error)
904 {
905 struct stat st;
906 CamelMboxSummary *mbs = (CamelMboxSummary *) cls;
907 CamelFolderSummary *s = (CamelFolderSummary *) cls;
908 CamelStore *parent_store;
909 const gchar *full_name;
910 gint i;
911 gint quick = TRUE, work = FALSE;
912 gint ret;
913 GPtrArray *summary = NULL;
914
915 camel_folder_summary_lock (s);
916
917 /* first, sync ourselves up, just to make sure */
918 if (camel_local_summary_check (cls, changeinfo, cancellable, error) == -1) {
919 camel_folder_summary_unlock (s);
920 return -1;
921 }
922
923 full_name = camel_folder_get_full_name (camel_folder_summary_get_folder (s));
924 parent_store = camel_folder_get_parent_store (camel_folder_summary_get_folder (s));
925
926 /* Sync only the changes */
927
928 summary = camel_folder_summary_get_changed ((CamelFolderSummary *) mbs);
929 for (i = 0; i < summary->len; i++) {
930 CamelMessageInfo *info = camel_folder_summary_get (s, summary->pdata[i]);
931
932 if ((expunge && (camel_message_info_get_flags (info) & CAMEL_MESSAGE_DELETED) != 0) ||
933 (camel_message_info_get_flags (info) & (CAMEL_MESSAGE_FOLDER_NOXEV | CAMEL_MESSAGE_FOLDER_XEVCHANGE)) != 0)
934 quick = FALSE;
935 else
936 work |= camel_message_info_get_folder_flagged (info);
937 g_clear_object (&info);
938 }
939
940 g_ptr_array_foreach (summary, (GFunc) camel_pstring_free, NULL);
941 g_ptr_array_free (summary, TRUE);
942
943 if (quick && expunge) {
944 guint32 dcount =0;
945
946 if (camel_db_count_deleted_message_info (camel_store_get_db (parent_store), full_name, &dcount, error) == -1) {
947 camel_folder_summary_unlock (s);
948 return -1;
949 }
950 if (dcount)
951 quick = FALSE;
952 }
953
954 /* yuck i hate this logic, but its to simplify the 'all ok, update summary' and failover cases */
955 ret = -1;
956 if (quick) {
957 if (work) {
958 ret = CAMEL_MBOX_SUMMARY_GET_CLASS (cls)->sync_quick (
959 mbs, expunge, changeinfo, cancellable, NULL);
960 if (ret == -1)
961 g_warning ("failed a quick-sync, trying a full sync");
962 } else {
963 ret = 0;
964 }
965 }
966
967 if (ret == -1)
968 ret = CAMEL_MBOX_SUMMARY_GET_CLASS (cls)->sync_full (
969 mbs, expunge, changeinfo, cancellable, error);
970 if (ret == -1) {
971 camel_folder_summary_unlock (s);
972 return -1;
973 }
974
975 if (g_stat (cls->folder_path, &st) == -1) {
976 g_set_error (
977 error, G_IO_ERROR,
978 g_io_error_from_errno (errno),
979 _("Unknown error: %s"), g_strerror (errno));
980 camel_folder_summary_unlock (s);
981 return -1;
982 }
983
984 if (mbs->folder_size != st.st_size || camel_folder_summary_get_timestamp (s) != st.st_mtime) {
985 camel_folder_summary_set_timestamp (s, st.st_mtime);
986 mbs->folder_size = st.st_size;
987 camel_folder_summary_touch (s);
988 }
989
990 ret = CAMEL_LOCAL_SUMMARY_CLASS (camel_mbox_summary_parent_class)->sync (cls, expunge, changeinfo, cancellable, error);
991 camel_folder_summary_unlock (s);
992
993 return ret;
994 }
995
996 gint
camel_mbox_summary_sync_mbox(CamelMboxSummary * cls,guint32 flags,CamelFolderChangeInfo * changeinfo,gint fd,gint fdout,GCancellable * cancellable,GError ** error)997 camel_mbox_summary_sync_mbox (CamelMboxSummary *cls,
998 guint32 flags,
999 CamelFolderChangeInfo *changeinfo,
1000 gint fd,
1001 gint fdout,
1002 GCancellable *cancellable,
1003 GError **error)
1004 {
1005 CamelMboxSummary *mbs = (CamelMboxSummary *) cls;
1006 CamelFolderSummary *s = (CamelFolderSummary *) mbs;
1007 CamelMimeParser *mp = NULL;
1008 CamelStore *parent_store;
1009 const gchar *full_name;
1010 gint i;
1011 CamelMessageInfo *info = NULL;
1012 gchar *buffer, *xevnew = NULL;
1013 gsize len;
1014 const gchar *fromline;
1015 gint lastdel = FALSE;
1016 gboolean touched = FALSE;
1017 GList *del = NULL;
1018 GPtrArray *known_uids = NULL;
1019 gchar statnew[8], xstatnew[8];
1020
1021 d (printf ("performing full summary/sync\n"));
1022
1023 camel_folder_summary_lock (s);
1024
1025 /* need to dup this because the mime-parser owns the fd after we give it to it */
1026 fd = dup (fd);
1027 if (fd == -1) {
1028 camel_folder_summary_unlock (s);
1029 g_set_error (
1030 error, G_IO_ERROR,
1031 g_io_error_from_errno (errno),
1032 _("Could not store folder: %s"),
1033 g_strerror (errno));
1034 return -1;
1035 }
1036
1037 mp = camel_mime_parser_new ();
1038 camel_mime_parser_scan_from (mp, TRUE);
1039 camel_mime_parser_scan_pre_from (mp, TRUE);
1040 camel_mime_parser_init_with_fd (mp, fd);
1041
1042 camel_folder_summary_prepare_fetch_all (s, NULL);
1043 known_uids = camel_folder_summary_get_array (s);
1044 /* walk them in the same order as stored in the file */
1045 if (known_uids && known_uids->len)
1046 g_ptr_array_sort_with_data (known_uids, cms_sort_frompos, mbs);
1047 for (i = 0; known_uids && i < known_uids->len; i++) {
1048 gint pc = (i + 1) * 100 / known_uids->len;
1049 goffset frompos;
1050
1051 camel_operation_progress (cancellable, pc);
1052
1053 info = camel_folder_summary_get (s, g_ptr_array_index (known_uids, i));
1054
1055 if (!info)
1056 continue;
1057
1058 d (printf (
1059 "Looking at message %s\n",
1060 camel_message_info_get_uid (info)));
1061
1062 frompos = camel_mbox_message_info_get_offset (CAMEL_MBOX_MESSAGE_INFO (info));
1063
1064 d (printf (
1065 "seeking (%s) to %d\n",
1066 camel_message_info_get_uid (info),
1067 (gint) frompos));
1068
1069 if (lastdel)
1070 camel_mime_parser_seek (mp, frompos, SEEK_SET);
1071
1072 if (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_FROM) {
1073 g_set_error (
1074 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1075 _("MBOX file is corrupted, please fix it. "
1076 "(Expected a From line, but didn’t get it.)"));
1077 goto error;
1078 }
1079
1080 if (camel_mime_parser_tell_start_from (mp) != frompos) {
1081 g_warning (
1082 "Didn't get the next message where I expected (%d) got %d instead",
1083 (gint) frompos,
1084 (gint) camel_mime_parser_tell_start_from (mp));
1085 g_set_error (
1086 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1087 _("Summary and folder mismatch, even after a sync"));
1088 goto error;
1089 }
1090
1091 lastdel = FALSE;
1092 if ((flags & 1) && (camel_message_info_get_flags (info) & CAMEL_MESSAGE_DELETED) != 0) {
1093 const gchar *uid = camel_message_info_get_uid (info);
1094 d (printf ("Deleting %s\n", uid));
1095
1096 if (((CamelLocalSummary *) cls)->index)
1097 camel_index_delete_name (((CamelLocalSummary *) cls)->index, uid);
1098
1099 /* remove it from the change list */
1100 camel_folder_change_info_remove_uid (changeinfo, uid);
1101 camel_folder_summary_remove (s, (CamelMessageInfo *) info);
1102 del = g_list_prepend (del, (gpointer) camel_pstring_strdup (uid));
1103 g_clear_object (&info);
1104 lastdel = TRUE;
1105 touched = TRUE;
1106 } else {
1107 /* otherwise, the message is staying, copy its From_ line across */
1108 #if 0
1109 if (i > 0)
1110 write (fdout, "\n", 1);
1111 #endif
1112 frompos = lseek (fdout, 0, SEEK_CUR);
1113 camel_mbox_message_info_set_offset (CAMEL_MBOX_MESSAGE_INFO (info), frompos);
1114 camel_message_info_set_dirty (info, TRUE);
1115 fromline = camel_mime_parser_from_line (mp);
1116 d (printf ("Saving %s:%d\n", camel_message_info_get_uid (info), frompos));
1117 g_warn_if_fail (write (fdout, fromline, strlen (fromline)) != -1);
1118 }
1119
1120 if (info && (camel_message_info_get_flags (info) & (CAMEL_MESSAGE_FOLDER_NOXEV | CAMEL_MESSAGE_FOLDER_FLAGGED)) != 0) {
1121 CamelNameValueArray *header = NULL;
1122 d (printf ("Updating header for %s flags = %08x\n", camel_message_info_get_uid (info), camel_message_info_get_flags (info)));
1123
1124 if (camel_mime_parser_step (mp, &buffer, &len) == CAMEL_MIME_PARSER_STATE_FROM_END) {
1125 g_warning ("camel_mime_parser_step failed (2)");
1126 goto error;
1127 }
1128
1129 header = camel_mime_parser_dup_headers (mp);
1130 xevnew = camel_local_summary_encode_x_evolution ((CamelLocalSummary *) cls, info);
1131 if (mbs->xstatus) {
1132 guint32 info_flags = camel_message_info_get_flags (info);
1133
1134 encode_status (info_flags & STATUS_STATUS, statnew);
1135 encode_status (info_flags & STATUS_XSTATUS, xstatnew);
1136
1137 len = camel_local_summary_write_headers (fdout, header, xevnew, statnew, xstatnew);
1138 } else {
1139 len = camel_local_summary_write_headers (fdout, header, xevnew, NULL, NULL);
1140 }
1141
1142 camel_name_value_array_free (header);
1143 if (len == -1) {
1144 d (printf ("Error writing to temporary mailbox\n"));
1145 g_set_error (
1146 error, G_IO_ERROR,
1147 g_io_error_from_errno (errno),
1148 _("Writing to temporary mailbox failed: %s"),
1149 g_strerror (errno));
1150 goto error;
1151 }
1152 camel_message_info_set_flags (info, 0xffff, camel_message_info_get_flags (info));
1153 g_free (xevnew);
1154 xevnew = NULL;
1155 camel_mime_parser_drop_step (mp);
1156 }
1157
1158 camel_mime_parser_drop_step (mp);
1159 if (info) {
1160 d (printf ("looking for message content to copy across from %d\n", (gint) camel_mime_parser_tell (mp)));
1161 while (camel_mime_parser_step (mp, &buffer, &len) == CAMEL_MIME_PARSER_STATE_PRE_FROM) {
1162 /*d(printf("copying mbox contents to temporary: '%.*s'\n", len, buffer));*/
1163 if (write (fdout, buffer, len) != len) {
1164 g_set_error (
1165 error, G_IO_ERROR,
1166 g_io_error_from_errno (errno),
1167 _("Writing to temporary mailbox failed: %s: %s"),
1168 ((CamelLocalSummary *) cls)->folder_path,
1169 g_strerror (errno));
1170 goto error;
1171 }
1172 }
1173
1174 if (write (fdout, "\n", 1) != 1) {
1175 g_set_error (
1176 error, G_IO_ERROR,
1177 g_io_error_from_errno (errno),
1178 _("Writing to temporary mailbox failed: %s"),
1179 g_strerror (errno));
1180 goto error;
1181 }
1182
1183 d (printf (
1184 "we are now at %d, from = %d\n",
1185 (gint) camel_mime_parser_tell (mp),
1186 (gint) camel_mime_parser_tell_start_from (mp)));
1187 camel_mime_parser_unstep (mp);
1188 g_clear_object (&info);
1189 }
1190 }
1191
1192 full_name = camel_folder_get_full_name (camel_folder_summary_get_folder (s));
1193 parent_store = camel_folder_get_parent_store (camel_folder_summary_get_folder (s));
1194 camel_db_delete_uids (camel_store_get_db (parent_store), full_name, del, NULL);
1195 g_list_foreach (del, (GFunc) camel_pstring_free, NULL);
1196 g_list_free (del);
1197
1198 #if 0
1199 /* if last was deleted, append the \n we removed */
1200 if (lastdel && count > 0)
1201 write (fdout, "\n", 1);
1202 #endif
1203
1204 g_object_unref (mp);
1205
1206 /* clear working flags */
1207 for (i = 0; known_uids && i < known_uids->len; i++) {
1208 info = camel_folder_summary_get (s, g_ptr_array_index (known_uids, i));
1209 if (info) {
1210 camel_message_info_set_flags (info, CAMEL_MESSAGE_FOLDER_NOXEV | CAMEL_MESSAGE_FOLDER_FLAGGED | CAMEL_MESSAGE_FOLDER_XEVCHANGE, 0);
1211 g_clear_object (&info);
1212 }
1213 }
1214
1215 camel_folder_summary_free_array (known_uids);
1216
1217 if (touched)
1218 camel_folder_summary_header_save (s, NULL);
1219
1220 camel_folder_summary_unlock (s);
1221
1222 return 0;
1223 error:
1224 g_free (xevnew);
1225 g_object_unref (mp);
1226 g_clear_object (&info);
1227
1228 camel_folder_summary_free_array (known_uids);
1229 camel_folder_summary_unlock (s);
1230
1231 return -1;
1232 }
1233
1234 static CamelMessageInfo *
mbox_summary_add(CamelLocalSummary * cls,CamelMimeMessage * msg,const CamelMessageInfo * info,CamelFolderChangeInfo * ci,GError ** error)1235 mbox_summary_add (CamelLocalSummary *cls,
1236 CamelMimeMessage *msg,
1237 const CamelMessageInfo *info,
1238 CamelFolderChangeInfo *ci,
1239 GError **error)
1240 {
1241 CamelLocalSummaryClass *local_summary_class;
1242 CamelMessageInfo *mi;
1243
1244 /* Chain up to parent's add() method. */
1245 local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_mbox_summary_parent_class);
1246 mi = local_summary_class->add (cls, msg, info, ci, error);
1247 if (mi && ((CamelMboxSummary *) cls)->xstatus) {
1248 gchar status[8];
1249 guint32 flags = camel_message_info_get_flags (mi);
1250
1251 /* we snoop and add status/x-status headers to suit */
1252 encode_status (flags & STATUS_STATUS, status);
1253 camel_medium_set_header ((CamelMedium *) msg, "Status", status);
1254 encode_status (flags & STATUS_XSTATUS, status);
1255 camel_medium_set_header ((CamelMedium *) msg, "X-Status", status);
1256 }
1257
1258 return mi;
1259 }
1260
1261 static struct {
1262 gchar tag;
1263 guint32 flag;
1264 } status_flags[] = {
1265 { 'F', CAMEL_MESSAGE_FLAGGED },
1266 { 'A', CAMEL_MESSAGE_ANSWERED },
1267 { 'D', CAMEL_MESSAGE_DELETED },
1268 { 'R', CAMEL_MESSAGE_SEEN },
1269 };
1270
1271 static void
encode_status(guint32 flags,gchar status[8])1272 encode_status (guint32 flags,
1273 gchar status[8])
1274 {
1275 gsize i;
1276 gchar *p;
1277
1278 p = status;
1279 for (i = 0; i < G_N_ELEMENTS (status_flags); i++)
1280 if (status_flags[i].flag & flags)
1281 *p++ = status_flags[i].tag;
1282 *p++ = 'O';
1283 *p = '\0';
1284 }
1285
1286 static guint32
decode_status(const gchar * status)1287 decode_status (const gchar *status)
1288 {
1289 const gchar *p;
1290 guint32 flags = 0;
1291 gsize i;
1292 gchar c;
1293
1294 p = status;
1295 while ((c = *p++)) {
1296 for (i = 0; i < G_N_ELEMENTS (status_flags); i++)
1297 if (status_flags[i].tag == c)
1298 flags |= status_flags[i].flag;
1299 }
1300
1301 return flags;
1302 }
1303