Rename camel_service_get_settings().
[platform/upstream/evolution-data-server.git] / camel / providers / local / camel-maildir-folder.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
2  *
3  * Authors: Michael Zucchi <notzed@ximian.com>
4  *
5  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19  * USA
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <dirent.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34
35 #include <glib/gi18n-lib.h>
36 #include <glib/gstdio.h>
37
38 #include "camel-maildir-folder.h"
39 #include "camel-maildir-store.h"
40 #include "camel-maildir-summary.h"
41
42 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
43
44 G_DEFINE_TYPE (CamelMaildirFolder, camel_maildir_folder, CAMEL_TYPE_LOCAL_FOLDER)
45
46 static gint
47 maildir_folder_cmp_uids (CamelFolder *folder,
48                          const gchar *uid1,
49                          const gchar *uid2)
50 {
51         CamelMessageInfo *a, *b;
52         time_t tma, tmb;
53
54         g_return_val_if_fail (folder != NULL, 0);
55         g_return_val_if_fail (folder->summary != NULL, 0);
56
57         a = camel_folder_summary_get (folder->summary, uid1);
58         b = camel_folder_summary_get (folder->summary, uid2);
59
60         g_return_val_if_fail (a != NULL, 0);
61         g_return_val_if_fail (b != NULL, 0);
62
63         tma = camel_message_info_date_received (a);
64         tmb = camel_message_info_date_received (b);
65
66         camel_message_info_free (a);
67         camel_message_info_free (b);
68
69         return tma < tmb ? -1 : tma == tmb ? 0 : 1;
70 }
71
72 static void
73 maildir_folder_sort_uids (CamelFolder *folder,
74                           GPtrArray *uids)
75 {
76         g_return_if_fail (camel_maildir_folder_parent_class != NULL);
77         g_return_if_fail (folder != NULL);
78
79         if (uids && uids->len > 1)
80                 camel_folder_summary_prepare_fetch_all (folder->summary, NULL);
81
82         /* Chain up to parent's sort_uids() method. */
83         CAMEL_FOLDER_CLASS (camel_maildir_folder_parent_class)->sort_uids (folder, uids);
84 }
85
86 static gchar *
87 maildir_folder_get_filename (CamelFolder *folder,
88                              const gchar *uid,
89                              GError **error)
90 {
91         CamelLocalFolder *lf = (CamelLocalFolder *) folder;
92         CamelMaildirMessageInfo *mdi;
93         CamelMessageInfo *info;
94         gchar *res;
95
96         /* get the message summary info */
97         if ((info = camel_folder_summary_get (folder->summary, uid)) == NULL) {
98                 set_cannot_get_message_ex (
99                         error, CAMEL_FOLDER_ERROR_INVALID_UID,
100                         uid, lf->folder_path, _("No such message"));
101                 return NULL;
102         }
103
104         mdi = (CamelMaildirMessageInfo *) info;
105
106         /* If filename is NULL, it means folder_summary_check is not yet executed.
107          * Try to find the file in the folder and use it, otherwise construct its
108          * name based on actual flags.
109         */
110         if (!camel_maildir_info_filename (mdi)) {
111                 const gchar *uid = camel_message_info_uid (info);
112
113                 if (uid) {
114                         GDir *dir;
115                         gchar *dirname;
116
117                         dirname = g_strdup_printf ("%s/cur", lf->folder_path);
118                         dir = g_dir_open (dirname, 0, NULL);
119                         g_free (dirname);
120
121                         if (dir) {
122                                 const gchar *filename;
123                                 gint uid_len = strlen (uid);
124
125                                 while (filename = g_dir_read_name (dir), filename) {
126                                         if (g_str_has_prefix (filename, uid) && (filename[uid_len] == '\0' || filename[uid_len] == ':')) {
127                                                 camel_maildir_info_set_filename (mdi, g_strdup (filename));
128                                                 break;
129                                         }
130                                 }
131
132                                 g_dir_close (dir);
133                         }
134                 }
135
136                 if (!camel_maildir_info_filename (mdi)) {
137                         camel_maildir_info_set_filename (mdi, camel_maildir_summary_info_to_name (mdi));
138                 }
139         }
140
141         res = g_strdup_printf ("%s/cur/%s", lf->folder_path, camel_maildir_info_filename (mdi));
142
143         camel_message_info_free (info);
144
145         return res;
146 }
147
148 static gboolean
149 maildir_folder_append_message_sync (CamelFolder *folder,
150                                     CamelMimeMessage *message,
151                                     CamelMessageInfo *info,
152                                     gchar **appended_uid,
153                                     GCancellable *cancellable,
154                                     GError **error)
155 {
156         CamelLocalFolder *lf = (CamelLocalFolder *) folder;
157         CamelStream *output_stream;
158         CamelMessageInfo *mi;
159         CamelMaildirMessageInfo *mdi;
160         gchar *name, *dest = NULL;
161         gboolean success = TRUE, has_attachment;
162
163         d(printf("Appending message\n"));
164
165         /* If we can't lock, don't do anything */
166         if (!lf || camel_local_folder_lock (lf, CAMEL_LOCK_WRITE, error) == -1)
167                 return FALSE;
168
169         /* add it to the summary/assign the uid, etc */
170         mi = camel_local_summary_add (
171                 CAMEL_LOCAL_SUMMARY (folder->summary),
172                 message, info, lf->changes, error);
173         if (mi == NULL)
174                 goto check_changed;
175
176         has_attachment = camel_mime_message_has_attachment (message);
177         if (((camel_message_info_flags (mi) & CAMEL_MESSAGE_ATTACHMENTS) && !has_attachment) ||
178             ((camel_message_info_flags (mi) & CAMEL_MESSAGE_ATTACHMENTS) == 0 && has_attachment)) {
179                 camel_message_info_set_flags (mi, CAMEL_MESSAGE_ATTACHMENTS, has_attachment ? CAMEL_MESSAGE_ATTACHMENTS : 0);
180         }
181
182         mdi = (CamelMaildirMessageInfo *) mi;
183
184         d(printf("Appending message: uid is %s filename is %s\n", camel_message_info_uid(mi), mdi->filename));
185
186         /* write it out to tmp, use the uid we got from the summary */
187         name = g_strdup_printf ("%s/tmp/%s", lf->folder_path, camel_message_info_uid(mi));
188         output_stream = camel_stream_fs_new_with_name (
189                 name, O_WRONLY | O_CREAT, 0600, error);
190         if (output_stream == NULL)
191                 goto fail_write;
192
193         if (camel_data_wrapper_write_to_stream_sync (
194                 (CamelDataWrapper *) message, output_stream, cancellable, error) == -1
195             || camel_stream_close (output_stream, cancellable, error) == -1)
196                 goto fail_write;
197
198         /* now move from tmp to cur (bypass new, does it matter?) */
199         dest = g_strdup_printf("%s/cur/%s", lf->folder_path, camel_maildir_info_filename (mdi));
200         if (g_rename (name, dest) == -1) {
201                 g_set_error (
202                         error, G_IO_ERROR,
203                         g_io_error_from_errno (errno),
204                         "%s", g_strerror (errno));
205                 goto fail_write;
206         }
207
208         g_free (dest);
209         g_free (name);
210
211         if (appended_uid)
212                 *appended_uid = g_strdup(camel_message_info_uid(mi));
213
214         if (output_stream)
215                 g_object_unref (output_stream);
216
217         goto check_changed;
218
219  fail_write:
220
221         /* remove the summary info so we are not out-of-sync with the mh folder */
222         camel_folder_summary_remove (CAMEL_FOLDER_SUMMARY (folder->summary), mi);
223
224         g_prefix_error (
225                 error, _("Cannot append message to maildir folder: %s: "),
226                 name);
227
228         if (output_stream) {
229                 g_object_unref (output_stream);
230                 unlink (name);
231         }
232
233         g_free (name);
234         g_free (dest);
235
236         success = FALSE;
237
238  check_changed:
239         camel_local_folder_unlock (lf);
240
241         if (camel_folder_change_info_changed (lf->changes)) {
242                 camel_folder_changed (folder, lf->changes);
243                 camel_folder_change_info_clear (lf->changes);
244         }
245
246         return success;
247 }
248
249 static CamelMimeMessage *
250 maildir_folder_get_message_sync (CamelFolder *folder,
251                                  const gchar *uid,
252                                  GCancellable *cancellable,
253                                  GError **error)
254 {
255         CamelLocalFolder *lf = (CamelLocalFolder *) folder;
256         CamelStream *message_stream = NULL;
257         CamelMimeMessage *message = NULL;
258         gchar *name = NULL;
259
260         d(printf("getting message: %s\n", uid));
261
262         if (!lf || camel_local_folder_lock (lf, CAMEL_LOCK_WRITE, error) == -1)
263                 return NULL;
264
265         name = maildir_folder_get_filename (folder, uid, error);
266         if (!name)
267                 goto fail;
268
269         message_stream = camel_stream_fs_new_with_name (
270                 name, O_RDONLY, 0, error);
271         if (message_stream == NULL) {
272                 g_prefix_error (
273                         error, _("Cannot get message %s from folder %s: "),
274                         uid, lf->folder_path);
275                 goto fail;
276         }
277
278         message = camel_mime_message_new ();
279         if (!camel_data_wrapper_construct_from_stream_sync (
280                 (CamelDataWrapper *) message,
281                 message_stream, cancellable, error)) {
282                 g_prefix_error (
283                         error, _("Cannot get message %s from folder %s: "),
284                         uid, lf->folder_path);
285                 g_object_unref (message);
286                 message = NULL;
287
288         }
289         g_object_unref (message_stream);
290  fail:
291         g_free (name);
292
293         camel_local_folder_unlock (lf);
294
295         if (lf && camel_folder_change_info_changed (lf->changes)) {
296                 camel_folder_changed (folder, lf->changes);
297                 camel_folder_change_info_clear (lf->changes);
298         }
299
300         return message;
301 }
302
303 static gboolean
304 maildir_folder_transfer_messages_to_sync (CamelFolder *source,
305                                           GPtrArray *uids,
306                                           CamelFolder *dest,
307                                           gboolean delete_originals,
308                                           GPtrArray **transferred_uids,
309                                           GCancellable *cancellable,
310                                           GError **error)
311 {
312         gboolean fallback = FALSE;
313
314         if (delete_originals && CAMEL_IS_MAILDIR_FOLDER (source) && CAMEL_IS_MAILDIR_FOLDER (dest)
315             && camel_folder_get_parent_store (source) == camel_folder_get_parent_store (dest)) {
316                 gint i;
317                 CamelLocalFolder *lf = (CamelLocalFolder *) source;
318                 CamelLocalFolder *df = (CamelLocalFolder *) dest;
319
320                 camel_operation_push_message (
321                         cancellable, _("Moving messages"));
322
323                 camel_folder_freeze (dest);
324                 camel_folder_freeze (source);
325
326                 for (i = 0; i < uids->len; i++) {
327                         gchar *uid = (gchar *) uids->pdata[i];
328                         gchar *s_filename, *d_filename, *new_filename;
329                         CamelMaildirMessageInfo *mdi;
330                         CamelMessageInfo *info;
331
332                         if ((info = camel_folder_summary_get (source->summary, uid)) == NULL) {
333                                 set_cannot_get_message_ex (
334                                         error, CAMEL_FOLDER_ERROR_INVALID_UID,
335                                         uid, lf->folder_path, _("No such message"));
336                                 return FALSE;
337                         }
338
339                         mdi = (CamelMaildirMessageInfo *) info;
340                         new_filename = camel_maildir_summary_info_to_name (mdi);
341
342                         d_filename = g_strdup_printf ("%s/cur/%s", df->folder_path, new_filename);
343                         s_filename = g_strdup_printf("%s/cur/%s", lf->folder_path, camel_maildir_info_filename (mdi));
344
345                         if (g_rename (s_filename, d_filename) != 0) {
346                                 if (errno == EXDEV) {
347                                         i = uids->len + 1;
348                                         fallback = TRUE;
349                                 } else {
350                                         g_set_error (
351                                                 error, G_IO_ERROR,
352                                                 g_io_error_from_errno (errno),
353                                                 _("Cannot transfer message to destination folder: %s"),
354                                                 g_strerror (errno));
355                                         camel_message_info_free (info);
356                                         g_free (s_filename);
357                                         g_free (d_filename);
358                                         g_free (new_filename);
359                                         break;
360                                 }
361                         } else {
362                                 CamelMessageInfo *clone;
363                                 CamelMaildirMessageInfo *mclone;
364
365                                 clone = camel_message_info_clone (info);
366                                 clone->summary = dest->summary;
367
368                                 mclone = (CamelMaildirMessageInfo *) clone;
369                                 /* preserve also UID, as it matches the file name */
370                                 mclone->info.info.uid = camel_pstring_strdup (camel_message_info_uid (info));
371                                 camel_maildir_info_set_filename (clone, g_strdup (new_filename));
372                                 /* unset deleted flag when transferring from trash folder */
373                                 if ((source->folder_flags & CAMEL_FOLDER_IS_TRASH) != 0)
374                                         camel_message_info_set_flags (info, CAMEL_MESSAGE_DELETED, 0);
375                                 /* unset junk flag when transferring from junk folder */
376                                 if ((source->folder_flags & CAMEL_FOLDER_IS_JUNK) != 0)
377                                         camel_message_info_set_flags (info, CAMEL_MESSAGE_JUNK, 0);
378                                 camel_folder_summary_add (dest->summary, clone);
379
380                                 camel_folder_change_info_add_uid (df->changes, camel_message_info_uid (clone));
381
382                                 camel_folder_set_message_flags (
383                                         source, uid, CAMEL_MESSAGE_DELETED |
384                                         CAMEL_MESSAGE_SEEN, ~0);
385                                 camel_folder_change_info_remove_uid (lf->changes, camel_message_info_uid (info));
386                                 camel_folder_summary_remove (source->summary, info);
387                         }
388
389                         camel_message_info_free (info);
390                         g_free (s_filename);
391                         g_free (d_filename);
392                         g_free (new_filename);
393                 }
394
395                 if (lf && camel_folder_change_info_changed (lf->changes)) {
396                         camel_folder_changed (source, lf->changes);
397                         camel_folder_change_info_clear (lf->changes);
398                 }
399
400                 if (df && camel_folder_change_info_changed (df->changes)) {
401                         camel_folder_changed (dest, df->changes);
402                         camel_folder_change_info_clear (df->changes);
403                 }
404
405                 camel_folder_thaw (source);
406                 camel_folder_thaw (dest);
407
408                 camel_operation_pop_message (cancellable);
409         } else
410                 fallback = TRUE;
411
412         if (fallback) {
413                 CamelFolderClass *folder_class;
414
415                 /* Chain up to parent's transfer_messages_to() method. */
416                 folder_class = CAMEL_FOLDER_CLASS (camel_maildir_folder_parent_class);
417                 return folder_class->transfer_messages_to_sync (
418                         source, uids, dest, delete_originals,
419                         transferred_uids, cancellable, error);
420         }
421
422         return TRUE;
423 }
424
425 static CamelLocalSummary *
426 maildir_folder_create_summary (CamelLocalFolder *lf,
427                                const gchar *folder,
428                                CamelIndex *index)
429 {
430         return (CamelLocalSummary *) camel_maildir_summary_new (
431                 CAMEL_FOLDER (lf), folder, index);
432 }
433
434 static void
435 camel_maildir_folder_class_init (CamelMaildirFolderClass *class)
436 {
437         CamelFolderClass *folder_class;
438         CamelLocalFolderClass *local_folder_class;
439
440         folder_class = CAMEL_FOLDER_CLASS (class);
441         folder_class->cmp_uids = maildir_folder_cmp_uids;
442         folder_class->sort_uids = maildir_folder_sort_uids;
443         folder_class->get_filename = maildir_folder_get_filename;
444         folder_class->append_message_sync = maildir_folder_append_message_sync;
445         folder_class->get_message_sync = maildir_folder_get_message_sync;
446         folder_class->transfer_messages_to_sync = maildir_folder_transfer_messages_to_sync;
447
448         local_folder_class = CAMEL_LOCAL_FOLDER_CLASS (class);
449         local_folder_class->create_summary = maildir_folder_create_summary;
450 }
451
452 static void
453 camel_maildir_folder_init (CamelMaildirFolder *maildir_folder)
454 {
455 }
456
457 CamelFolder *
458 camel_maildir_folder_new (CamelStore *parent_store,
459                           const gchar *full_name,
460                           guint32 flags,
461                           GCancellable *cancellable,
462                           GError **error)
463 {
464         CamelFolder *folder;
465         CamelService *service;
466         CamelSettings *settings;
467         gboolean filter_inbox;
468         gchar *basename;
469
470         d(printf("Creating maildir folder: %s\n", full_name));
471
472         if (g_strcmp0 (full_name, ".") == 0)
473                 basename = g_strdup (_("Inbox"));
474         else
475                 basename = g_path_get_basename (full_name);
476
477         folder = g_object_new (
478                 CAMEL_TYPE_MAILDIR_FOLDER,
479                 "display-name", basename,
480                 "full-name", full_name,
481                 "parent-store", parent_store,
482                 NULL);
483
484         service = CAMEL_SERVICE (parent_store);
485
486         settings = camel_service_ref_settings (service);
487
488         filter_inbox = camel_store_settings_get_filter_inbox (
489                 CAMEL_STORE_SETTINGS (settings));
490
491         g_object_unref (settings);
492
493         if (filter_inbox && strcmp (full_name, ".") == 0)
494                 folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
495
496         folder = (CamelFolder *) camel_local_folder_construct (
497                 CAMEL_LOCAL_FOLDER (folder), flags, cancellable, error);
498
499         g_free (basename);
500
501         /* indexing doesn't work with maildir properly, thus disable it */
502         g_object_set (folder, "index-body", FALSE, NULL);
503
504         return folder;
505 }
506