1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Authors: Michael Zucchi <notzed@ximian.com>
5 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
7 * This library is free software you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation.
11 * This library is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this library; if not, see <http://www.gnu.org/licenses/>.
32 #include <sys/types.h>
34 #include <glib/gi18n-lib.h>
35 #include <glib/gstdio.h>
37 #if !defined (G_OS_WIN32) && !defined (_POSIX_PATH_MAX)
38 #include <posix1_lim.h>
41 #include "camel-local-folder.h"
42 #include "camel-local-private.h"
43 #include "camel-local-store.h"
44 #include "camel-local-summary.h"
46 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
49 #define PATH_MAX _POSIX_PATH_MAX
52 #define CAMEL_LOCAL_FOLDER_GET_PRIVATE(obj) \
53 (G_TYPE_INSTANCE_GET_PRIVATE \
54 ((obj), CAMEL_TYPE_LOCAL_FOLDER, CamelLocalFolderPrivate))
56 /* The custom property ID is a CamelArg artifact.
57 * It still identifies the property in state files. */
60 PROP_INDEX_BODY = 0x2400
63 G_DEFINE_TYPE (CamelLocalFolder, camel_local_folder, CAMEL_TYPE_FOLDER)
66 local_folder_set_property (GObject *object,
71 switch (property_id) {
73 camel_local_folder_set_index_body (
74 CAMEL_LOCAL_FOLDER (object),
75 g_value_get_boolean (value));
79 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
83 local_folder_get_property (GObject *object,
88 switch (property_id) {
91 value, camel_local_folder_get_index_body (
92 CAMEL_LOCAL_FOLDER (object)));
96 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
100 local_folder_dispose (GObject *object)
103 CamelLocalFolder *local_folder;
105 folder = CAMEL_FOLDER (object);
106 local_folder = CAMEL_LOCAL_FOLDER (object);
108 if (folder->summary != NULL) {
109 camel_local_summary_sync (
110 CAMEL_LOCAL_SUMMARY (folder->summary),
111 FALSE, local_folder->changes, NULL, NULL);
112 g_object_unref (folder->summary);
113 folder->summary = NULL;
116 if (local_folder->search != NULL) {
117 g_object_unref (local_folder->search);
118 local_folder->search = NULL;
121 if (local_folder->index != NULL) {
122 g_object_unref (local_folder->index);
123 local_folder->index = NULL;
126 /* Chain up to parent's dispose() method. */
127 G_OBJECT_CLASS (camel_local_folder_parent_class)->dispose (object);
131 local_folder_finalize (GObject *object)
133 CamelLocalFolder *local_folder;
135 local_folder = CAMEL_LOCAL_FOLDER (object);
137 while (local_folder->locked > 0)
138 camel_local_folder_unlock (local_folder);
140 g_free (local_folder->base_path);
141 g_free (local_folder->folder_path);
142 g_free (local_folder->index_path);
144 camel_folder_change_info_free (local_folder->changes);
146 g_mutex_clear (&local_folder->priv->search_lock);
148 /* Chain up to parent's finalize() method. */
149 G_OBJECT_CLASS (camel_local_folder_parent_class)->finalize (object);
153 local_folder_constructed (GObject *object)
155 CamelLocalSettings *local_settings;
156 CamelProvider *provider;
157 CamelSettings *settings;
158 CamelService *service;
160 CamelStore *parent_store;
161 const gchar *full_name;
167 folder = CAMEL_FOLDER (object);
168 full_name = camel_folder_get_full_name (folder);
169 parent_store = camel_folder_get_parent_store (folder);
171 service = CAMEL_SERVICE (parent_store);
172 provider = camel_service_get_provider (service);
174 settings = camel_service_ref_settings (service);
176 local_settings = CAMEL_LOCAL_SETTINGS (settings);
177 root_path = camel_local_settings_dup_path (local_settings);
179 g_object_unref (settings);
181 if (root_path == NULL)
184 path = g_strdup_printf ("%s/%s", root_path, full_name);
186 if ((tmp = getenv ("HOME")) && strncmp (tmp, path, strlen (tmp)) == 0)
187 /* Translators: This is used for a folder description,
188 * for folders being under $HOME. The first %s is replaced
189 * with a relative path under $HOME, the second %s is
190 * replaced with a protocol name, like mbox/maldir/... */
191 description = g_strdup_printf (
195 else if ((tmp = "/var/spool/mail") && strncmp (tmp, path, strlen (tmp)) == 0)
196 /* Translators: This is used for a folder description, for
197 * folders being under /var/spool/mail. The first %s is
198 * replaced with a relative path under /var/spool/mail,
199 * the second %s is replaced with a protocol name, like
201 description = g_strdup_printf (
202 _("mailbox: %s (%s)"),
205 else if ((tmp = "/var/mail") && strncmp (tmp, path, strlen (tmp)) == 0)
206 /* Translators: This is used for a folder description, for
207 * folders being under /var/mail. The first %s is replaced
208 * with a relative path under /var/mail, the second %s is
209 * replaced with a protocol name, like mbox/maldir/... */
210 description = g_strdup_printf (
211 _("mailbox: %s (%s)"),
215 /* Translators: This is used for a folder description.
216 * The first %s is replaced with a folder's full path,
217 * the second %s is replaced with a protocol name, like
219 description = g_strdup_printf (
223 camel_folder_set_description (folder, description);
225 g_free (description);
231 local_folder_search_by_expression (CamelFolder *folder,
232 const gchar *expression,
233 GCancellable *cancellable,
236 CamelLocalFolder *local_folder = CAMEL_LOCAL_FOLDER (folder);
239 CAMEL_LOCAL_FOLDER_LOCK (folder, search_lock);
241 if (local_folder->search == NULL)
242 local_folder->search = camel_folder_search_new ();
244 camel_folder_search_set_folder (local_folder->search, folder);
245 if (camel_local_folder_get_index_body (local_folder))
246 camel_folder_search_set_body_index (local_folder->search, local_folder->index);
248 camel_folder_search_set_body_index (local_folder->search, NULL);
249 matches = camel_folder_search_search (local_folder->search, expression, NULL, cancellable, error);
251 CAMEL_LOCAL_FOLDER_UNLOCK (folder, search_lock);
257 local_folder_search_by_uids (CamelFolder *folder,
258 const gchar *expression,
260 GCancellable *cancellable,
263 CamelLocalFolder *local_folder = CAMEL_LOCAL_FOLDER (folder);
267 return g_ptr_array_new ();
269 CAMEL_LOCAL_FOLDER_LOCK (folder, search_lock);
271 if (local_folder->search == NULL)
272 local_folder->search = camel_folder_search_new ();
274 camel_folder_search_set_folder (local_folder->search, folder);
275 if (camel_local_folder_get_index_body (local_folder))
276 camel_folder_search_set_body_index (local_folder->search, local_folder->index);
278 camel_folder_search_set_body_index (local_folder->search, NULL);
279 matches = camel_folder_search_search (local_folder->search, expression, uids, cancellable, error);
281 CAMEL_LOCAL_FOLDER_UNLOCK (folder, search_lock);
287 local_folder_search_free (CamelFolder *folder,
290 CamelLocalFolder *local_folder = CAMEL_LOCAL_FOLDER (folder);
292 /* we need to lock this free because of the way search_free_result works */
293 /* FIXME: put the lock inside search_free_result */
294 CAMEL_LOCAL_FOLDER_LOCK (folder, search_lock);
296 camel_folder_search_free_result (local_folder->search, result);
298 CAMEL_LOCAL_FOLDER_UNLOCK (folder, search_lock);
302 local_folder_delete (CamelFolder *folder)
304 CamelLocalFolder *lf = (CamelLocalFolder *) folder;
307 camel_index_delete (lf->index);
309 CAMEL_FOLDER_CLASS (camel_local_folder_parent_class)->delete_ (folder);
313 local_folder_rename (CamelFolder *folder,
314 const gchar *newname)
316 CamelLocalFolder *lf = (CamelLocalFolder *) folder;
319 CamelStore *parent_store;
321 parent_store = camel_folder_get_parent_store (folder);
322 ls = CAMEL_LOCAL_STORE (parent_store);
324 d (printf ("renaming local folder paths to '%s'\n", newname));
328 g_free (lf->folder_path);
329 g_free (lf->index_path);
331 lf->folder_path = camel_local_store_get_full_path (ls, newname);
332 lf->index_path = camel_local_store_get_meta_path (ls, newname, ".ibex");
333 statepath = camel_local_store_get_meta_path (ls, newname, ".cmeta");
334 camel_object_set_state_filename (CAMEL_OBJECT (lf), statepath);
337 /* FIXME: Poke some internals, sigh */
338 g_free (((CamelLocalSummary *) folder->summary)->folder_path);
339 ((CamelLocalSummary *) folder->summary)->folder_path = g_strdup (lf->folder_path);
341 CAMEL_FOLDER_CLASS (camel_local_folder_parent_class)->rename (folder, newname);
345 local_folder_count_by_expression (CamelFolder *folder,
346 const gchar *expression,
347 GCancellable *cancellable,
350 CamelLocalFolder *local_folder = CAMEL_LOCAL_FOLDER (folder);
353 CAMEL_LOCAL_FOLDER_LOCK (folder, search_lock);
355 if (local_folder->search == NULL)
356 local_folder->search = camel_folder_search_new ();
358 camel_folder_search_set_folder (local_folder->search, folder);
359 if (camel_local_folder_get_index_body (local_folder))
360 camel_folder_search_set_body_index (local_folder->search, local_folder->index);
362 camel_folder_search_set_body_index (local_folder->search, NULL);
363 matches = camel_folder_search_count (local_folder->search, expression, cancellable, error);
365 CAMEL_LOCAL_FOLDER_UNLOCK (folder, search_lock);
371 local_folder_get_uncached_uids (CamelFolder *folder,
375 /* By default, we would have everything local.
376 * No need to fetch from anywhere. */
377 return g_ptr_array_new ();
381 local_folder_expunge_sync (CamelFolder *folder,
382 GCancellable *cancellable,
385 /* Just do a sync with expunge, serves the same purpose */
386 /* call the callback directly, to avoid locking problems */
387 return CAMEL_FOLDER_GET_CLASS (folder)->synchronize_sync (
388 folder, TRUE, cancellable, error);
392 local_folder_refresh_info_sync (CamelFolder *folder,
393 GCancellable *cancellable,
396 CamelStore *parent_store;
397 CamelLocalFolder *lf = (CamelLocalFolder *) folder;
398 gboolean need_summary_check;
400 parent_store = camel_folder_get_parent_store (folder);
402 need_summary_check = camel_local_store_get_need_summary_check (
403 CAMEL_LOCAL_STORE (parent_store));
405 if (need_summary_check &&
406 camel_local_summary_check ((CamelLocalSummary *) folder->summary, lf->changes, cancellable, error) == -1)
409 if (camel_folder_change_info_changed (lf->changes)) {
410 camel_folder_changed (folder, lf->changes);
411 camel_folder_change_info_clear (lf->changes);
418 local_folder_synchronize_sync (CamelFolder *folder,
420 GCancellable *cancellable,
423 CamelLocalFolder *lf = CAMEL_LOCAL_FOLDER (folder);
426 d (printf ("local sync '%s' , expunge=%s\n", folder->full_name, expunge?"true":"false"));
428 if (camel_local_folder_lock (lf, CAMEL_LOCK_WRITE, error) == -1)
431 camel_object_state_write (CAMEL_OBJECT (lf));
433 /* if sync fails, we'll pass it up on exit through ex */
434 success = (camel_local_summary_sync (
435 (CamelLocalSummary *) folder->summary,
436 expunge, lf->changes, cancellable, error) == 0);
437 camel_local_folder_unlock (lf);
439 if (camel_folder_change_info_changed (lf->changes)) {
440 camel_folder_changed (folder, lf->changes);
441 camel_folder_change_info_clear (lf->changes);
448 local_folder_lock (CamelLocalFolder *lf,
456 local_folder_unlock (CamelLocalFolder *lf)
462 camel_local_folder_class_init (CamelLocalFolderClass *class)
464 GObjectClass *object_class;
465 CamelFolderClass *folder_class;
467 g_type_class_add_private (class, sizeof (CamelLocalFolderPrivate));
469 object_class = G_OBJECT_CLASS (class);
470 object_class->set_property = local_folder_set_property;
471 object_class->get_property = local_folder_get_property;
472 object_class->dispose = local_folder_dispose;
473 object_class->finalize = local_folder_finalize;
474 object_class->constructed = local_folder_constructed;
476 folder_class = CAMEL_FOLDER_CLASS (class);
477 folder_class->search_by_expression = local_folder_search_by_expression;
478 folder_class->search_by_uids = local_folder_search_by_uids;
479 folder_class->search_free = local_folder_search_free;
480 folder_class->delete_ = local_folder_delete;
481 folder_class->rename = local_folder_rename;
482 folder_class->count_by_expression = local_folder_count_by_expression;
483 folder_class->get_uncached_uids = local_folder_get_uncached_uids;
484 folder_class->expunge_sync = local_folder_expunge_sync;
485 folder_class->refresh_info_sync = local_folder_refresh_info_sync;
486 folder_class->synchronize_sync = local_folder_synchronize_sync;
488 class->lock = local_folder_lock;
489 class->unlock = local_folder_unlock;
491 g_object_class_install_property (
494 g_param_spec_boolean (
497 _("_Index message body data"),
500 CAMEL_PARAM_PERSISTENT));
504 camel_local_folder_init (CamelLocalFolder *local_folder)
506 CamelFolder *folder = CAMEL_FOLDER (local_folder);
508 local_folder->priv = CAMEL_LOCAL_FOLDER_GET_PRIVATE (local_folder);
509 g_mutex_init (&local_folder->priv->search_lock);
511 folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY;
513 folder->permanent_flags = CAMEL_MESSAGE_ANSWERED |
514 CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_DRAFT |
515 CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN |
516 CAMEL_MESSAGE_ANSWERED_ALL | CAMEL_MESSAGE_USER;
518 folder->summary = NULL;
519 local_folder->search = NULL;
523 camel_local_folder_construct (CamelLocalFolder *lf,
525 GCancellable *cancellable,
529 CamelLocalSettings *local_settings;
530 CamelSettings *settings;
531 CamelService *service;
537 gchar folder_path[PATH_MAX];
543 CamelStore *parent_store;
544 const gchar *full_name;
545 gboolean need_summary_check;
547 folder = CAMEL_FOLDER (lf);
548 full_name = camel_folder_get_full_name (folder);
549 parent_store = camel_folder_get_parent_store (folder);
551 service = CAMEL_SERVICE (parent_store);
553 settings = camel_service_ref_settings (service);
555 local_settings = CAMEL_LOCAL_SETTINGS (settings);
556 lf->base_path = camel_local_settings_dup_path (local_settings);
558 g_object_unref (settings);
560 ls = CAMEL_LOCAL_STORE (parent_store);
561 need_summary_check = camel_local_store_get_need_summary_check (ls);
563 lf->folder_path = camel_local_store_get_full_path (ls, full_name);
564 lf->index_path = camel_local_store_get_meta_path (ls, full_name, ".ibex");
565 statepath = camel_local_store_get_meta_path (ls, full_name, ".cmeta");
567 camel_object_set_state_filename (CAMEL_OBJECT (lf), statepath);
572 if (camel_object_state_read (CAMEL_OBJECT (lf)) == -1) {
573 /* No metadata - load defaults and persitify */
574 camel_local_folder_set_index_body (lf, TRUE);
575 camel_object_state_write (CAMEL_OBJECT (lf));
578 /* XXX Canonicalizing the folder path portably is a messy affair.
579 * The proposed GLib function in [1] would be useful here.
581 * [1] https://bugzilla.gnome.org/show_bug.cgi?id=111848
584 /* follow any symlinks to the mailbox */
585 if (g_lstat (lf->folder_path, &st) != -1 && S_ISLNK (st.st_mode) &&
587 (folder_path = realpath (lf->folder_path, NULL)) != NULL) {
589 realpath (lf->folder_path, folder_path) != NULL) {
591 g_free (lf->folder_path);
592 lf->folder_path = g_strdup (folder_path);
594 /* Not a typo. Use free() here, not g_free().
595 * The path string was allocated by realpath(). */
600 lf->changes = camel_folder_change_info_new ();
602 /* TODO: Remove the following line, it is a temporary workaround to remove
603 * the old-format 'ibex' files that might be lying around */
604 g_unlink (lf->index_path);
606 /* FIXME: Need to run indexing off of the setv method */
608 /* if we have no/invalid index file, force it */
609 forceindex = camel_text_index_check (lf->index_path) == -1;
610 if (lf->flags & CAMEL_STORE_FOLDER_BODY_INDEX) {
611 gint flag = O_RDWR | O_CREAT;
616 lf->index = (CamelIndex *) camel_text_index_new (lf->index_path, flag);
617 if (lf->index == NULL) {
618 /* yes, this isn't fatal at all */
619 g_warning ("Could not open/create index file: %s: indexing not performed", g_strerror (errno));
621 /* record that we dont have an index afterall */
622 lf->flags &= ~CAMEL_STORE_FOLDER_BODY_INDEX;
625 /* if we do have an index file, remove it (?) */
626 if (forceindex == FALSE)
627 camel_text_index_remove (lf->index_path);
631 folder->summary = (CamelFolderSummary *) CAMEL_LOCAL_FOLDER_GET_CLASS (lf)->create_summary (lf, lf->folder_path, lf->index);
632 if (!(flags & CAMEL_STORE_IS_MIGRATING) && !camel_local_summary_load ((CamelLocalSummary *) folder->summary, forceindex, NULL)) {
634 if (need_summary_check &&
635 camel_local_summary_check ((CamelLocalSummary *) folder->summary, lf->changes, cancellable, error) == 0) {
636 /* we sync here so that any hard work setting up the folder isn't lost */
637 if (camel_local_summary_sync ((CamelLocalSummary *) folder->summary, FALSE, lf->changes, cancellable, error) == -1) {
638 g_object_unref (folder);
644 /* TODO: This probably shouldn't be here? */
645 if ((flags & CAMEL_STORE_FOLDER_CREATE) != 0) {
648 /* Use 'recursive' mode, even for just created folder, to have set whether
649 the folder has children or not properly. */
650 fi = camel_store_get_folder_info_sync (parent_store, full_name, CAMEL_STORE_FOLDER_INFO_RECURSIVE, NULL, NULL);
651 g_return_val_if_fail (fi != NULL, lf);
653 camel_store_folder_created (parent_store, fi);
654 camel_folder_info_free (fi);
661 camel_local_folder_get_index_body (CamelLocalFolder *local_folder)
663 g_return_val_if_fail (CAMEL_IS_LOCAL_FOLDER (local_folder), FALSE);
665 return (local_folder->flags & CAMEL_STORE_FOLDER_BODY_INDEX);
669 camel_local_folder_set_index_body (CamelLocalFolder *local_folder,
672 g_return_if_fail (CAMEL_IS_LOCAL_FOLDER (local_folder));
675 local_folder->flags |= CAMEL_STORE_FOLDER_BODY_INDEX;
677 local_folder->flags &= ~CAMEL_STORE_FOLDER_BODY_INDEX;
679 g_object_notify (G_OBJECT (local_folder), "index-body");
682 /* lock the folder, may be called repeatedly (with matching unlock calls),
683 * with type the same or less than the first call */
685 camel_local_folder_lock (CamelLocalFolder *lf,
689 if (lf->locked > 0) {
690 /* lets be anal here - its important the code knows what its doing */
691 g_assert (lf->locktype == type || lf->locktype == CAMEL_LOCK_WRITE);
693 if (CAMEL_LOCAL_FOLDER_GET_CLASS (lf)->lock (lf, type, error) == -1)
705 camel_local_folder_unlock (CamelLocalFolder *lf)
707 g_assert (lf->locked > 0);
710 CAMEL_LOCAL_FOLDER_GET_CLASS (lf)->unlock (lf);
716 set_cannot_get_message_ex (GError **error,
719 const gchar *folder_path,
720 const gchar *detailErr)
722 /* Translators: The first %s is replaced with a message ID,
723 * the second %s is replaced with the folder path,
724 * the third %s is replaced with a detailed error string */
726 error, CAMEL_ERROR, err_code,
727 _("Cannot get message %s from folder %s\n%s"),
728 msgID, folder_path, detailErr);