1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-maildir-folder.c : camel-folder subclass for maildir folders */
6 * Copyright (C) 1999 Bertrand Guiheneuf <Bertrand.Guiheneuf@inria.fr> .
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
25 * AUTHORS : Jukka Zitting
32 #include <sys/param.h>
34 #include <sys/types.h>
41 #include "camel-maildir-folder.h"
42 #include "camel-maildir-store.h"
43 #include "camel-stream-fs.h"
44 #include "camel-log.h"
46 static CamelFolderClass *parent_class=NULL;
48 /* Returns the class for a CamelMaildirFolder */
49 #define CMAILDIRF_CLASS(so) CAMEL_MAILDIR_FOLDER_CLASS (GTK_OBJECT(so)->klass)
50 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (GTK_OBJECT(so)->klass)
51 #define CMAILDIRS_CLASS(so) CAMEL_STORE_CLASS (GTK_OBJECT(so)->klass)
53 static void _init_with_store (CamelFolder *folder, CamelStore *parent_store, CamelException *ex);
54 static void _set_name (CamelFolder *folder, const gchar *name, CamelException *ex);
55 static gboolean _exists (CamelFolder *folder, CamelException *ex);
56 static gboolean _create (CamelFolder *folder, CamelException *ex);
57 static gboolean _delete (CamelFolder *folder, gboolean recurse, CamelException *ex);
58 static gboolean _delete_messages (CamelFolder *folder, CamelException *ex);
59 static CamelMimeMessage *_get_message (CamelFolder *folder, gint number, CamelException *ex);
60 static gint _get_message_count (CamelFolder *folder, CamelException *ex);
61 static void _expunge (CamelFolder *folder, CamelException *ex);
62 static GList *_list_subfolders (CamelFolder *folder, CamelException *ex);
64 /* fs utility functions */
65 static DIR * _xopendir (const gchar *path);
66 static gboolean _xstat (const gchar *path, struct stat *buf);
67 static gboolean _xmkdir (const gchar *path);
68 static gboolean _xrename (const gchar *from, const gchar *to);
69 static gboolean _xunlink (const gchar *path);
70 static gboolean _xrmdir (const gchar *path);
74 camel_maildir_folder_class_init (CamelMaildirFolderClass *camel_maildir_folder_class)
76 CamelFolderClass *camel_folder_class =
77 CAMEL_FOLDER_CLASS (camel_maildir_folder_class);
79 parent_class = gtk_type_class (camel_folder_get_type ());
81 /* virtual method definition */
82 /* virtual method overload */
83 camel_folder_class->init_with_store = _init_with_store;
84 camel_folder_class->set_name = _set_name;
85 camel_folder_class->exists = _exists;
86 camel_folder_class->create = _create;
87 camel_folder_class->delete = _delete;
88 camel_folder_class->delete_messages = _delete_messages;
89 camel_folder_class->expunge = _expunge;
90 camel_folder_class->get_message = _get_message;
91 camel_folder_class->get_message_count = _get_message_count;
92 camel_folder_class->list_subfolders = _list_subfolders;
96 camel_maildir_folder_get_type (void)
98 static GtkType camel_maildir_folder_type = 0;
100 if (!camel_maildir_folder_type) {
101 GtkTypeInfo camel_maildir_folder_info =
103 "CamelMaildirFolder",
104 sizeof (CamelMaildirFolder),
105 sizeof (CamelMaildirFolderClass),
106 (GtkClassInitFunc) camel_maildir_folder_class_init,
107 (GtkObjectInitFunc) NULL,
108 /* reserved_1 */ NULL,
109 /* reserved_2 */ NULL,
110 (GtkClassInitFunc) NULL,
113 camel_maildir_folder_type =
114 gtk_type_unique (CAMEL_FOLDER_TYPE, &camel_maildir_folder_info);
117 return camel_maildir_folder_type;
126 * CamelMaildirFolder::init_with_store: initializes the folder object
127 * @folder: folder object to initialize
128 * @parent_store: parent store object of the folder
130 * Simply tells that the folder can contain messages but not subfolders.
131 * Perhaps we'll later implement subfolders too...
134 _init_with_store (CamelFolder *folder, CamelStore *parent_store, CamelException *ex)
136 CAMEL_LOG_FULL_DEBUG ("Entering CamelMaildirFolder::init_with_store\n");
138 g_assert (parent_store);
140 /* call parent method */
141 parent_class->init_with_store (folder, parent_store, ex);
143 folder->can_hold_messages = TRUE;
144 folder->can_hold_folders = TRUE;
145 folder->has_summary_capability = FALSE;
147 CAMEL_LOG_FULL_DEBUG ("Leaving CamelMaildirFolder::init_with_store\n");
151 * CamelMaildirFolder::set_name: sets the name of the folder
152 * @folder: folder object
153 * @name: name of the folder
155 * Sets the name of the folder object. The existence of a folder with
156 * the given name is not checked in this function.
159 _set_name (CamelFolder *folder, const gchar *name, CamelException *ex)
161 CamelMaildirFolder *maildir_folder;
162 CamelMaildirStore *maildir_store;
164 CAMEL_LOG_FULL_DEBUG ("Entering CamelMaildirFolder::set_name\n");
167 g_assert (folder->parent_store);
169 maildir_folder = CAMEL_MAILDIR_FOLDER (folder);
170 maildir_store = CAMEL_MAILDIR_STORE (folder->parent_store);
172 /* call default implementation */
173 parent_class->set_name (folder, name, ex);
175 if (maildir_folder->directory_path)
176 g_free (maildir_folder->directory_path);
178 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::set_name full_name is %s\n", folder->full_name);
179 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::set_name toplevel_dir is %s\n", maildir_store->toplevel_dir);
180 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::set_name separator is %c\n", camel_store_get_separator (folder->parent_store));
182 if (folder->full_name && folder->full_name[0])
183 maildir_folder->directory_path =
184 g_strconcat (maildir_store->toplevel_dir, G_DIR_SEPARATOR_S,
185 folder->full_name, NULL);
187 maildir_folder->directory_path = g_strdup (maildir_store->toplevel_dir);
189 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::set_name: name set to %s\n", name);
190 CAMEL_LOG_FULL_DEBUG ("Leaving CamelMaildirFolder::set_name\n");
194 * CamelMaildirFolder::exists: tests whether the named maildir exists
195 * @folder: folder object
197 * A created maildir folder object doesn't necessarily exist yet in the
198 * filesystem. This function checks whether the maildir exists.
199 * The structure of the maildir is stated in the maildir.5 manpage.
202 * A directory in maildir format has three subdirectories,
203 * all on the same filesystem: tmp, new, and cur.
205 * Return value: TRUE if the maildir exists, FALSE otherwise
208 _exists (CamelFolder *folder, CamelException *ex)
210 CamelMaildirFolder *maildir_folder = CAMEL_MAILDIR_FOLDER (folder);
211 static const gchar *dir[3] = { "new", "cur", "tmp" };
214 const gchar *maildir;
218 CAMEL_LOG_FULL_DEBUG ("Entering CamelMaildirFolder::exists\n");
220 g_return_val_if_fail (maildir_folder->directory_path, FALSE);
222 maildir = maildir_folder->directory_path;
224 CAMEL_LOG_FULL_DEBUG ("CamelMailFolder::exists: checking maildir %s\n",
227 /* check whether the toplevel directory exists */
228 rv = _xstat (maildir, &statbuf) && S_ISDIR (statbuf.st_mode);
230 /* check whether the maildir subdirectories exist */
231 for (i = 0; rv && i < 3; i++) {
232 path = g_strconcat (maildir, G_DIR_SEPARATOR_S, dir[i], NULL);
234 rv = _xstat (path, &statbuf) && S_ISDIR (statbuf.st_mode);
239 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::exists: %s\n",
240 (rv) ? "maildir found" : "maildir not found");
241 CAMEL_LOG_FULL_DEBUG ("Leaving CamelMaildirFolder::exists\n");
246 * CamelMaildirFolder::create: creates the named maildir
247 * @folder: folder object
249 * A created maildir folder object doesn't necessarily exist yet in the
250 * filesystem. This function creates the maildir if it doesn't yet exist.
251 * The structure of the maildir is stated in the maildir.5 manpage.
254 * A directory in maildir format has three subdirectories,
255 * all on the same filesystem: tmp, new, and cur.
257 * Return value: TRUE if the maildir existed already or was created,
261 _create (CamelFolder *folder, CamelException *ex)
263 CamelMaildirFolder *maildir_folder = CAMEL_MAILDIR_FOLDER (folder);
264 static const gchar *dir[3] = { "new", "cur", "tmp" };
266 const gchar *maildir;
270 CAMEL_LOG_FULL_DEBUG ("Entering CamelMaildirFolder::create\n");
273 /* check whether the maildir already exists */
274 if (camel_folder_exists (folder, ex)) return TRUE;
276 maildir = maildir_folder->directory_path;
278 CAMEL_LOG_FULL_DEBUG ("CamelMailFolder::create: creating maildir %s\n",
281 /* create the toplevel directory */
282 rv = _xmkdir (maildir);
284 /* create the maildir subdirectories */
285 for (i = 0; rv && i < 3; i++) {
286 path = g_strconcat (maildir, G_DIR_SEPARATOR_S, dir[i], NULL);
293 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::create: %s\n",
294 rv ? "maildir created" : "an error occurred");
295 CAMEL_LOG_FULL_DEBUG ("Leaving CamelMaildirFolder::create\n");
300 * CamelMaildirFolder::delete: delete the maildir folder
301 * @folder: the folder object
304 * This function empties and deletes the maildir folder. The subdirectories
305 * "tmp", "cur", and "new" are removed first and then the toplevel maildir
306 * directory is deleted. All files from the directories are deleted as well,
307 * so you should be careful when using this function. If a subdirectory cannot
308 * be deleted, then the operation it is stopped. Thus if an error occurs, the
309 * maildir directory won't be removed, but it might no longer be a valid maildir.
312 _delete (CamelFolder *folder, gboolean recurse, CamelException *ex)
314 CamelMaildirFolder *maildir_folder = CAMEL_MAILDIR_FOLDER (folder);
315 static const gchar *dir[3] = { "new", "cur", "tmp" };
317 const gchar *maildir;
321 CAMEL_LOG_FULL_DEBUG ("Entering CamelMaildirFolder::create\n");
324 /* check whether the maildir already exists */
325 if (!camel_folder_exists (folder, ex)) return TRUE;
327 maildir = maildir_folder->directory_path;
329 CAMEL_LOG_FULL_DEBUG ("CamelMailFolder::delete: deleting maildir %s\n",
332 /* delete the maildir subdirectories */
333 for (i = 0; rv && i < 3; i++) {
334 path = g_strconcat (maildir, G_DIR_SEPARATOR_S, dir[i], NULL);
341 /* create the toplevel directory */
343 rv = _xrmdir (maildir);
345 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::delete: %s\n",
346 rv ? "maildir deleted" : "an error occurred");
347 CAMEL_LOG_FULL_DEBUG ("Leaving CamelMaildirFolder::delete\n");
352 * CamelMaildirFolder::delete_messages: empty the maildir folder
353 * @folder: the folder object
355 * This function empties the maildir folder. All messages from the
356 * "cur" subdirectory are deleted. If a message cannot be deleted, then
357 * it is just skipped and the rest of the messages are still deleted.
358 * Files with names starting with a dot are skipped as described in the
362 * It is a good idea for readers to skip all filenames in new
363 * and cur starting with a dot. Other than this, readers
364 * should not attempt to parse filenames.
366 * Return value: FALSE on error and if some messages could not be deleted.
370 _delete_messages (CamelFolder *folder, CamelException *ex)
372 CamelMaildirFolder *maildir_folder = CAMEL_MAILDIR_FOLDER (folder);
373 const gchar *maildir;
374 gchar *curdir, *file;
376 struct dirent *dir_entry;
379 CAMEL_LOG_FULL_DEBUG ("Entering CamelMaildirFolder::delete_messages\n");
382 /* call default implementation */
383 parent_class->delete_messages (folder, ex);
385 /* Check if the folder didn't exist */
386 if (!camel_folder_exists (folder, ex)) return TRUE;
388 maildir = maildir_folder->directory_path;
390 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::delete_messages: "
391 "deleting messages from %s\n", maildir);
393 /* delete messages from the maildir subdirectory "cur" */
394 curdir = g_strconcat (maildir, G_DIR_SEPARATOR_S, "cur", NULL);
396 dir_handle = _xopendir (curdir);
398 while ((dir_entry = readdir (dir_handle))) {
399 if (dir_entry->d_name[0] == '.') continue;
400 file = g_strconcat (curdir, G_DIR_SEPARATOR_S,
401 dir_entry->d_name, NULL);
403 if (!_xunlink (file)) rv = FALSE;
407 closedir (dir_handle);
413 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::delete_messages: %s\n",
414 rv ? "messages deleted" : "an error occurred");
415 CAMEL_LOG_FULL_DEBUG ("Leaving CamelMaildirFolder::delete_messages\n");
420 * CamelMaildirFolder::get_message: get a message from maildir
421 * @folder: the folder object
422 * @number: number of the message within the folder
424 * Return value: the message, NULL on error
426 static CamelMimeMessage *
427 _get_message (CamelFolder *folder, gint number, CamelException *ex)
429 CamelMaildirFolder *maildir_folder = CAMEL_MAILDIR_FOLDER(folder);
431 struct dirent *dir_entry;
433 CamelMimeMessage *message = NULL;
434 const gchar *maildir;
435 gchar *curdir, *file = NULL;
438 CAMEL_LOG_FULL_DEBUG ("Entering CamelMaildirFolder::get_message\n");
441 /* Check if the folder exists */
442 if (!camel_folder_exists (folder, ex)) return NULL;
444 maildir = maildir_folder->directory_path;
446 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::get_message: "
447 "getting message #%d from %s\n", number, maildir);
449 /* Count until the desired message is reached */
450 curdir = g_strconcat (maildir, G_DIR_SEPARATOR_S, "cur", NULL);
451 if ((dir_handle = _xopendir (curdir))) {
452 while ((count < number) && (dir_entry = readdir (dir_handle)))
453 if (dir_entry->d_name[0] != '.') count++;
456 file = g_strconcat (curdir, G_DIR_SEPARATOR_S,
457 dir_entry->d_name, NULL);
459 closedir (dir_handle);
462 if (!file) return NULL;
464 /* Create the message object */
465 #warning use session field here
466 message = camel_mime_message_new_with_session ((CamelSession *) NULL);
467 stream = camel_stream_fs_new_with_name (file, CAMEL_STREAM_FS_READ);
469 if (!message || !stream) {
471 if (stream) gtk_object_unref (GTK_OBJECT (stream));
472 if (message) gtk_object_unref (GTK_OBJECT (message));
476 camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (message),
478 gtk_object_unref (GTK_OBJECT (stream));
479 gtk_object_set_data_full (GTK_OBJECT (message),
480 "fullpath", file, g_free);
482 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::get_message: "
483 "message %p created from %s\n", message, file);
484 CAMEL_LOG_FULL_DEBUG ("Leaving CamelMaildirFolder::get_message\n");
489 * CamelMaildirFolder::get_message_count: count messages in maildir
490 * @folder: the folder object
492 * Returns the number of messages in the maildir folder. New messages
493 * are included in this count.
495 * Return value: number of messages in the maildir, -1 on error
498 _get_message_count (CamelFolder *folder, CamelException *ex)
500 CamelMaildirFolder *maildir_folder = CAMEL_MAILDIR_FOLDER(folder);
501 const gchar *maildir;
502 gchar *newdir, *curdir, *newfile, *curfile;
504 struct dirent *dir_entry;
507 CAMEL_LOG_FULL_DEBUG ("Entering "
508 "CamelMaildirFolder::get_message_count\n");
511 /* check if the maildir exists */
512 if (!camel_folder_exists (folder, ex)) return -1;
514 maildir = maildir_folder->directory_path;
516 newdir = g_strconcat (maildir, G_DIR_SEPARATOR_S, "new", NULL);
517 curdir = g_strconcat (maildir, G_DIR_SEPARATOR_S, "cur", NULL);
519 /* Check new messages */
520 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::get_message_count: "
521 "getting new messages from %s\n", newdir);
522 if ((dir_handle = _xopendir (newdir))) {
523 while ((dir_entry = readdir (dir_handle))) {
524 if (dir_entry->d_name[0] == '.') continue;
525 newfile = g_strconcat (newdir, G_DIR_SEPARATOR_S,
526 dir_entry->d_name, NULL);
527 curfile = g_strconcat (curdir, G_DIR_SEPARATOR_S,
528 dir_entry->d_name, ":2,", NULL);
530 _xrename (newfile, curfile);
535 closedir (dir_handle);
539 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::get_message_count: "
540 "counting messages in %s\n", curdir);
541 if ((dir_handle = _xopendir (curdir))) {
542 while ((dir_entry = readdir (dir_handle)))
543 if (dir_entry->d_name[0] != '.') count++;
544 closedir (dir_handle);
550 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::get_message_count: "
551 " found %d messages\n", count);
552 CAMEL_LOG_FULL_DEBUG ("Leaving "
553 "CamelMaildirFolder::get_message_count\n");
561 * CamelMaildirFolder::expunge: expunge messages marked as deleted
562 * @folder: the folder object
564 * Physically deletes the messages marked as deleted in the folder.
567 _expunge (CamelFolder *folder, CamelException *ex)
569 CamelMimeMessage *message;
573 CAMEL_LOG_FULL_DEBUG ("Entering CamelMaildirFolder::expunge\n");
576 /* expunge messages marked for deletion */
577 for (node = folder->message_list; node; node = g_list_next(node)) {
578 message = CAMEL_MIME_MESSAGE (node->data);
580 CAMEL_LOG_WARNING ("CamelMaildirFolder::expunge: "
581 "null message in node %p\n", node);
585 if (camel_mime_message_get_flag (message, "DELETED")) {
586 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::expunge: "
587 "expunging message #%d\n",
588 message->message_number);
590 /* expunge the message */
591 fullpath = gtk_object_get_data (GTK_OBJECT (message),
593 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::expunge: "
594 "message fullpath is %s\n",
597 if (_xunlink (fullpath))
598 message->expunged = TRUE;
600 CAMEL_LOG_FULL_DEBUG ("CamelMaildirFolder::expunge: "
601 "skipping message #%d\n",
602 message->message_number);
606 CAMEL_LOG_FULL_DEBUG ("Leaving CamelMaildirFolder::expunge\n");
613 * CamelMaildirFolder::list_subfolders: return a list of subfolders
614 * @folder: the folder object
616 * Returns the names of the maildir subfolders in a list.
618 * Return value: list of subfolder names
621 _list_subfolders (CamelFolder *folder, CamelException *ex)
623 CamelMaildirFolder *maildir_folder = CAMEL_MAILDIR_FOLDER (folder);
624 const gchar *maildir;
627 struct dirent *dir_entry;
629 GList *subfolders = NULL;
631 CAMEL_LOG_FULL_DEBUG ("Entering CamelMaildirFolder::list_subfolders\n");
634 /* check if the maildir exists */
635 if (!camel_folder_exists (folder, ex)) return NULL;
637 /* scan through the maildir toplevel directory */
638 maildir = maildir_folder->directory_path;
639 if ((dir_handle = _xopendir (maildir))) {
640 while ((dir_entry = readdir (dir_handle))) {
641 if (dir_entry->d_name[0] == '.') continue;
642 if (strcmp (dir_entry->d_name, "new") == 0) continue;
643 if (strcmp (dir_entry->d_name, "cur") == 0) continue;
644 if (strcmp (dir_entry->d_name, "tmp") == 0) continue;
646 subdir = g_strconcat (maildir, G_DIR_SEPARATOR_S,
647 dir_entry->d_name, NULL);
649 if (_xstat (subdir, &statbuf)
650 && S_ISDIR (statbuf.st_mode))
654 g_strdup (dir_entry->d_name));
658 closedir (dir_handle);
661 CAMEL_LOG_FULL_DEBUG ("Leaving CamelMaildirFolder::list_subfolders\n");
672 * fs utility function
677 _xopendir (const gchar *path)
682 handle = opendir (path);
684 CAMEL_LOG_WARNING ("ERROR: opendir (%s);\n", path);
685 CAMEL_LOG_FULL_DEBUG (" Full error text is: (%d) %s\n",
686 errno, strerror(errno));
693 _xstat (const gchar *path, struct stat *buf)
699 stat_error = stat (path, buf);
700 if (stat_error == 0) {
702 } else if (errno == ENOENT) {
706 CAMEL_LOG_WARNING ("ERROR: stat (%s, %p);\n", path, buf);
707 CAMEL_LOG_FULL_DEBUG (" Full error text is: (%d) %s\n",
708 errno, strerror(errno));
714 _xmkdir (const gchar *path)
718 if (mkdir (path, S_IRWXU) == -1) {
719 CAMEL_LOG_WARNING ("ERROR: mkdir (%s, S_IRWXU);\n", path);
720 CAMEL_LOG_FULL_DEBUG (" Full error text is: (%d) %s\n",
721 errno, strerror(errno));
729 _xrename (const gchar *from, const gchar *to)
734 if (rename (from, to) == 0) {
737 CAMEL_LOG_WARNING ("ERROR: rename (%s, %s);\n", from, to);
738 CAMEL_LOG_FULL_DEBUG (" Full error text is: (%d) %s\n",
739 errno, strerror(errno));
745 _xunlink (const gchar *path)
749 if (unlink (path) == 0) {
751 } else if (errno == ENOENT) {
754 CAMEL_LOG_WARNING ("ERROR: unlink (%s);\n", path);
755 CAMEL_LOG_FULL_DEBUG (" Full error text is: (%d) %s\n",
756 errno, strerror(errno));
762 _xrmdir (const gchar *path)
765 struct dirent *dir_entry;
770 dir_handle = opendir (path);
771 if (!dir_handle && errno == ENOENT) {
773 } else if (!dir_handle) {
774 CAMEL_LOG_WARNING ("ERROR: opendir (%s);\n", path);
775 CAMEL_LOG_FULL_DEBUG (" Full error text is: (%d) %s\n",
776 errno, strerror(errno));
780 while ((dir_entry = readdir (dir_handle))) {
781 file = g_strconcat (path, G_DIR_SEPARATOR_S, dir_entry->d_name,
783 if (_xstat (file, &statbuf) && S_ISREG (statbuf.st_mode))
788 closedir (dir_handle);
790 if (rmdir (path) == 0) {
792 } else if (errno == ENOENT) {
795 CAMEL_LOG_WARNING ("ERROR: rmdir (%s);\n", path);
796 CAMEL_LOG_FULL_DEBUG (" Full error text is: (%d) %s\n",
797 errno, strerror(errno));