Fix include name
[claws-mail-maildir-plugin.git] / src / maildir.c
1 /*
2  * Maildir Plugin -- Maildir++ support for Sylpheed
3  * Copyright (C) 2003-2004 Christoph Hohmann
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "pluginconfig.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <glob.h>
30 #include <unistd.h>
31 #include <glib.h>
32
33 #include "utils.h"
34 #include "procmsg.h"
35 #include "procheader.h"
36 #include "folder.h"
37 #include "maildir.h"
38 #include "localfolder.h"
39 #include "uiddb.h"
40 #include "mainwindow.h"
41 #include "summaryview.h"
42 #include "messageview.h"
43
44 #define MAILDIR_FOLDERITEM(item) ((MaildirFolderItem *) item)
45
46 typedef struct _MaildirFolder MaildirFolder;
47 typedef struct _MaildirFolderItem MaildirFolderItem;
48
49 static Folder *maildir_folder_new(const gchar * name,
50                                   const gchar * folder);
51 static void maildir_folder_destroy(Folder * folder);
52 static gint maildir_scan_tree(Folder * folder);
53 static FolderItem *maildir_item_new(Folder * folder);
54 static void maildir_item_destroy(Folder * folder, FolderItem * item);
55 static gchar *maildir_item_get_path(Folder * folder, FolderItem * item);
56 static gint maildir_get_num_list(Folder * folder, FolderItem * item,
57                                  MsgNumberList ** list,
58                                  gboolean * old_uids_valid);
59 static MsgInfo *maildir_get_msginfo(Folder * folder, FolderItem * item,
60                                     gint num);
61 static gchar *maildir_fetch_msg(Folder * folder, FolderItem * item,
62                                 gint num);
63 static gint maildir_add_msg(Folder * folder, FolderItem * _dest,
64                             const gchar * file, MsgFlags * flags);
65 static gint maildir_copy_msg(Folder * folder, FolderItem * dest,
66                              MsgInfo * msginfo);
67 static gint maildir_remove_msg(Folder * folder, FolderItem * _item,
68                                gint num);
69 static void maildir_change_flags(Folder * folder, FolderItem * item,
70                                  MsgInfo * msginfo, MsgPermFlags newflags);
71 static FolderItem *maildir_create_folder(Folder * folder,
72                                          FolderItem * parent,
73                                          const gchar * name);
74 static gint maildir_create_tree(Folder *folder);
75 static void remove_missing_folder_items(Folder *folder);
76 static gint maildir_remove_folder(Folder *folder, FolderItem *item);
77 static gint maildir_rename_folder(Folder *folder, FolderItem *item,
78                              const gchar *name);
79 static gint maildir_get_flags (Folder *folder,  FolderItem *item,
80                                MsgInfoList *msglist, GHashTable *msgflags);
81
82 static gchar *filename_from_utf8(const gchar *path);
83 static gchar *filename_to_utf8(const gchar *path);
84
85 FolderClass maildir_class;
86
87 struct _MaildirFolder
88 {
89         LocalFolder folder;
90 };
91
92 struct _MaildirFolderItem
93 {
94         FolderItem item;
95
96         guint lastuid;
97         UIDDB *db;
98 };
99
100 FolderClass *maildir_get_class()
101 {
102         if (maildir_class.idstr == NULL) {
103                 maildir_class.type = F_MAILDIR;
104                 maildir_class.idstr = "maildir";
105                 maildir_class.uistr = "Maildir++";
106
107                 /* Folder functions */
108                 maildir_class.new_folder = maildir_folder_new;
109                 maildir_class.destroy_folder = maildir_folder_destroy;
110                 maildir_class.set_xml = folder_local_set_xml;
111                 maildir_class.get_xml = folder_local_get_xml;
112                 maildir_class.scan_tree = maildir_scan_tree;
113                 maildir_class.create_tree = maildir_create_tree;
114
115                 /* FolderItem functions */
116                 maildir_class.item_new = maildir_item_new;
117                 maildir_class.item_destroy = maildir_item_destroy;
118                 maildir_class.item_get_path = maildir_item_get_path;
119                 maildir_class.create_folder = maildir_create_folder;
120                 maildir_class.remove_folder = maildir_remove_folder;
121                 maildir_class.rename_folder = maildir_rename_folder;
122                 maildir_class.get_num_list = maildir_get_num_list;
123
124                 /* Message functions */
125                 maildir_class.get_msginfo = maildir_get_msginfo;
126                 maildir_class.fetch_msg = maildir_fetch_msg;
127                 maildir_class.add_msg = maildir_add_msg;
128                 maildir_class.copy_msg = maildir_copy_msg;
129                 maildir_class.remove_msg = maildir_remove_msg;
130                 maildir_class.change_flags = maildir_change_flags;
131                 maildir_class.get_flags = maildir_get_flags;
132         }
133
134         return &maildir_class;
135 }
136
137 static Folder *maildir_folder_new(const gchar * name,
138                                   const gchar * path)
139 {
140         MaildirFolder *folder;                   
141         
142         folder = g_new0(MaildirFolder, 1);
143         FOLDER(folder)->klass = &maildir_class;
144         folder_local_folder_init(FOLDER(folder), name, path);
145
146         return FOLDER(folder);
147 }
148
149 static void maildir_folder_destroy(Folder *_folder)
150 {
151         MaildirFolder *folder = (MaildirFolder *) _folder;
152
153         folder_local_folder_destroy(LOCAL_FOLDER(folder));
154 }
155
156 static gint open_database(MaildirFolderItem *item)
157 {
158         gchar *path, *database;
159
160         g_return_val_if_fail(item->db == NULL, -1);
161         
162         path = maildir_item_get_path(FOLDER_ITEM(item)->folder, FOLDER_ITEM(item));
163         Xstrcat_a(database, path, G_DIR_SEPARATOR_S "sylpheed_uid.db", return -1);
164         g_free(path);
165
166         item->db = uiddb_open(database);
167         g_return_val_if_fail(item->db != NULL, -1);
168         
169         return 0;
170 }
171
172 static void close_database(MaildirFolderItem *item)
173 {
174         g_return_if_fail(item->db != NULL);
175         
176         uiddb_close(item->db);
177         item->db = NULL;
178 }
179
180 static FolderItem *maildir_item_new(Folder *folder)
181 {
182         MaildirFolderItem *item;
183
184         item = g_new0(MaildirFolderItem, 1);
185         item->lastuid = 0;
186         item->db = NULL;
187         
188         return (FolderItem *) item;
189
190 }
191
192 static void maildir_item_destroy(Folder *folder, FolderItem *_item)
193 {
194         MaildirFolderItem *item = (MaildirFolderItem *)_item;
195
196         g_return_if_fail(item != NULL);
197         
198         g_free(item);
199 }
200
201 static gchar *maildir_item_get_path(Folder *folder, FolderItem *item)
202 {
203         gchar *folder_path, *path, *real_path;
204
205         g_return_val_if_fail(folder != NULL, NULL);
206         g_return_val_if_fail(item != NULL, NULL);
207
208         folder_path = g_strdup(LOCAL_FOLDER(folder)->rootpath);
209         g_return_val_if_fail(folder_path != NULL, NULL);
210
211         if (g_path_is_absolute(folder_path)) {
212                 if (item->path && strcmp(item->path, ".inbox"))
213                         path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
214                                            item->path, NULL);
215                 else
216                         path = g_strdup(folder_path);
217         } else {
218                 if (item->path && strcmp(item->path, ".inbox"))
219                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
220                                            folder_path, G_DIR_SEPARATOR_S,
221                                            item->path, NULL);
222                 else
223                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
224                                            folder_path, NULL);
225         }
226         g_free(folder_path);
227
228         real_path = filename_from_utf8(path);
229
230         g_free(path);
231         return real_path;
232 }
233
234 static void build_tree(GNode *node, glob_t *globbuf)
235 {
236         int i;
237         FolderItem *parent = FOLDER_ITEM(node->data);
238         gchar *prefix = parent->path ?  filename_from_utf8(parent->path) : g_strdup("");
239         Folder *folder = parent->folder;
240
241         for (i = 0; i < globbuf->gl_pathc; i++) {
242                 FolderItem *newitem;
243                 GNode *newnode;
244                 gchar *dirname;
245                 gchar *foldername;
246                 gchar *tmpstr, *dirname_utf8, *foldername_utf8;
247                 gboolean res;
248
249                 dirname = g_path_get_basename(globbuf->gl_pathv[i]);
250                 foldername = &(dirname[strlen(prefix) + 1]);
251
252                 if (dirname[0] == '.' && dirname[1] == '\0') {
253                         g_free(dirname);
254                         continue;
255                 }
256                 if (strncmp(dirname, prefix, strlen(prefix))) {
257                         g_free(dirname);
258                         continue;
259                 }
260                 if (dirname[strlen(prefix)] != '.') {
261                         g_free(dirname);
262                         continue;
263                 }
264                 if (strchr(foldername, '.') != NULL) {
265                         g_free(dirname);
266                         continue;
267                 }
268
269                 if (!is_dir_exist(globbuf->gl_pathv[i])) {
270                         g_free(dirname);
271                         continue;
272                 }
273                 tmpstr = g_strconcat(globbuf->gl_pathv[i], "/cur", NULL);
274                 res = is_dir_exist(tmpstr);
275                 g_free(tmpstr);
276                 if (!res) {
277                         g_free(dirname);
278                         continue;
279                 }
280                 dirname_utf8 = filename_to_utf8(dirname);
281                 foldername_utf8 = filename_to_utf8(foldername);
282
283                 /* don't add items that already exist in the tree */
284                 newitem = folder_find_child_item_by_name(parent, dirname_utf8);
285                 if (newitem == NULL) {
286                         newitem = folder_item_new(parent->folder, foldername_utf8, dirname_utf8);
287                         newitem->folder = folder;
288
289                         newnode = g_node_new(newitem);
290                         newitem->node = newnode;
291                         g_node_append(node, newnode);
292
293                         debug_print("added item %s\n", newitem->path);
294                 }
295
296                 g_free(dirname_utf8);
297                 g_free(foldername_utf8);
298
299                 if (!parent->path) {
300                         if (!folder->outbox && !strcmp(dirname, "." OUTBOX_DIR)) {
301                                 newitem->stype = F_OUTBOX;
302                                 folder->outbox = newitem;
303                         } else if (!folder->draft && !strcmp(dirname, "." DRAFT_DIR)) {
304                                 newitem->stype = F_DRAFT;
305                                 folder->draft = newitem;
306                         } else if (!folder->queue && !strcmp(dirname, "." QUEUE_DIR)) {
307                                 newitem->stype = F_QUEUE;
308                                 folder->queue = newitem;
309                         } else if (!folder->trash && !strcmp(dirname, "." TRASH_DIR)) {
310                                 newitem->stype = F_TRASH;
311                                 folder->trash = newitem;
312                         }
313                 }
314                 g_free(dirname);
315                 build_tree(newitem->node, globbuf);
316         }
317
318         g_free(prefix);
319 }
320
321 static gint maildir_scan_tree(Folder *folder)
322 {
323         FolderItem *rootitem, *inboxitem;
324         GNode *rootnode, *inboxnode;
325         glob_t globbuf;
326         gchar *rootpath, *globpat;
327         
328         g_return_val_if_fail(folder != NULL, -1);
329
330         if (!folder->node) {
331                 rootitem = folder_item_new(folder, folder->name, NULL);
332                 rootitem->folder = folder;
333                 rootnode = g_node_new(rootitem);
334                 folder->node = rootnode;
335                 rootitem->node = rootnode;
336         } else {
337                 rootitem = FOLDER_ITEM(folder->node->data);
338                 rootnode = folder->node;
339         }
340
341         /* Add inbox folder */
342         if (!folder->inbox) {
343                 inboxitem = folder_item_new(folder, "inbox", ".inbox");
344                 inboxitem->folder = folder;
345                 inboxitem->stype = F_INBOX;
346                 inboxnode = g_node_new(inboxitem);
347                 inboxitem->node = inboxnode;
348                 folder->inbox = inboxitem;
349                 g_node_append(rootnode, inboxnode);
350         }
351
352         rootpath = folder_item_get_path(rootitem);
353
354         /* clear special folders to make sure we don't have invalid references
355            after remove_missing_folder_items */
356         folder->outbox = NULL;
357         folder->draft = NULL;
358         folder->queue = NULL;
359         folder->trash = NULL;
360
361         debug_print("scanning tree %s\n", rootpath);
362         maildir_create_tree(folder);
363         remove_missing_folder_items(folder);
364
365         globpat = g_strconcat(rootpath, G_DIR_SEPARATOR_S ".*", NULL);
366         globbuf.gl_offs = 0;
367         glob(globpat, 0, NULL, &globbuf);
368         g_free(globpat);
369         build_tree(rootnode, &globbuf);
370         globfree(&globbuf);
371
372         return 0;
373 }
374
375 static gchar *get_filename_for_msgdata(MessageData *msgdata)
376 {
377         gchar *filename;
378
379         if (msgdata->info[0])
380                 filename = g_strconcat(msgdata->dir, G_DIR_SEPARATOR_S, 
381                                        msgdata->uniq, ":", msgdata->info, NULL);
382         else
383                 filename = g_strconcat(msgdata->dir, G_DIR_SEPARATOR_S, 
384                                        msgdata->uniq, NULL);
385
386         return filename;
387 }
388
389 static MessageData *get_msgdata_for_filename(const gchar *filename)
390 {
391         MessageData *msgdata;
392         const gchar *tmpfilename;
393         gchar **pathsplit, **namesplit;
394
395         tmpfilename = strrchr(filename, G_DIR_SEPARATOR);
396         if (tmpfilename == NULL || tmpfilename == filename)
397                 return NULL;
398
399         tmpfilename--;
400         while (tmpfilename > filename && tmpfilename[0] != G_DIR_SEPARATOR)
401                 tmpfilename--;
402         if (tmpfilename[0] == G_DIR_SEPARATOR)
403                 tmpfilename++;
404
405         pathsplit = g_strsplit(tmpfilename, G_DIR_SEPARATOR_S, 2);
406         if (pathsplit[1] == NULL) {
407                 g_strfreev(pathsplit);
408                 return NULL;
409         }
410
411         namesplit = g_strsplit(pathsplit[1], ":", 2);
412
413         msgdata = g_new0(MessageData, 1);
414
415         msgdata->dir = g_strdup(pathsplit[0]);
416         msgdata->uniq = g_strdup(namesplit[0]);
417         if (namesplit[1] != NULL)
418                 msgdata->info = g_strdup(namesplit[1]);
419         else
420                 msgdata->info = g_strdup("");
421
422         g_strfreev(namesplit);
423         g_strfreev(pathsplit);
424
425         return msgdata;
426 }
427
428 static guint32 get_uid_for_filename(MaildirFolderItem *item, const gchar *filename)
429 {
430         gchar *uniq, *info;
431         MessageData *msgdata;
432         guint32 uid;
433
434         g_return_val_if_fail(item->db != NULL, 0);
435
436         uniq = strrchr(filename, G_DIR_SEPARATOR);
437         if (uniq == NULL) {
438                 return 0;
439         }
440         uniq++;
441
442         Xstrdup_a(uniq, uniq, return 0);
443         info = strchr(uniq, ':');
444         if (info != NULL)
445                 *info++ = '\0';
446         else
447                 info = "";
448
449         msgdata = uiddb_get_entry_for_uniq(item->db, uniq);
450         if (msgdata == NULL) {
451                 msgdata = get_msgdata_for_filename(filename);
452                 if (msgdata == NULL) {
453                         return 0;
454                 }
455                 msgdata->uid = uiddb_get_new_uid(item->db);
456
457                 uiddb_insert_entry(item->db, msgdata);
458         } else if (strcmp(msgdata->info, info)) {
459                 uiddb_delete_entry(item->db, msgdata->uid);
460
461                 g_free(msgdata->info);
462                 msgdata->info = g_strdup(info);
463
464                 uiddb_insert_entry(item->db, msgdata);
465         }
466
467         uid = msgdata->uid;
468         uiddb_free_msgdata(msgdata);
469
470         return uid;
471 }
472
473 static MessageData *get_msgdata_for_uid(MaildirFolderItem *item, guint32 uid)
474 {
475         MessageData *msgdata;
476         gchar *path, *msgname, *filename;
477         glob_t globbuf;
478
479         g_return_val_if_fail(item->db != NULL, NULL);
480
481         msgdata = uiddb_get_entry_for_uid(item->db, uid);
482         if (msgdata == NULL) {
483                 return NULL;
484         }
485         path = maildir_item_get_path(FOLDER_ITEM(item)->folder, FOLDER_ITEM(item));
486
487         msgname = get_filename_for_msgdata(msgdata);
488         filename = g_strconcat(path, G_DIR_SEPARATOR_S, msgname, NULL);
489         g_free(msgname);
490         
491         if (is_file_exist(filename)) {
492                 g_free(path);
493                 return msgdata;
494         }
495
496         debug_print("researching for %s\n", msgdata->uniq);
497         /* delete old entry */
498         g_free(filename);
499         uiddb_delete_entry(item->db, uid);
500
501         /* try to find file with same uniq and different info */
502         filename = g_strconcat(path, G_DIR_SEPARATOR_S, DIR_NEW, G_DIR_SEPARATOR_S, msgdata->uniq, NULL);
503         if (!is_file_exist(filename)) {
504                 g_free(filename);
505
506                 filename = g_strconcat(path, G_DIR_SEPARATOR_S, DIR_CUR, G_DIR_SEPARATOR_S, msgdata->uniq, ":*", NULL);
507                 glob(filename, 0, NULL, &globbuf);
508                 g_free(filename);
509
510                 g_free(path);
511         
512                 filename = NULL;
513                 if (globbuf.gl_pathc > 0)
514                         filename = g_strdup(globbuf.gl_pathv[0]);
515                 globfree(&globbuf);
516         }
517         uiddb_free_msgdata(msgdata);
518         msgdata = NULL;
519
520         /* if found: update database and return new entry */
521         if (filename != NULL) {
522                 debug_print("found %s\n", filename);
523                 
524                 msgdata = get_msgdata_for_filename(filename);
525                 msgdata->uid = uid;
526
527                 uiddb_insert_entry(item->db, msgdata);
528         }
529
530         return msgdata;
531 }
532
533 static gchar *get_filepath_for_msgdata(MaildirFolderItem *item, MessageData *msgdata)
534 {
535         gchar *path, *msgname, *filename;
536
537         path = maildir_item_get_path(FOLDER_ITEM(item)->folder, FOLDER_ITEM(item));
538         msgname = get_filename_for_msgdata(msgdata);
539         filename = g_strconcat(path, G_DIR_SEPARATOR_S, msgname, NULL);
540         g_free(msgname);
541         g_free(path);
542         
543         return filename;
544 }
545
546 static gchar *get_filepath_for_uid(MaildirFolderItem *item, guint32 uid)
547 {
548         MessageData *msgdata;
549         gchar *filename;
550
551         g_return_val_if_fail(item->db != NULL, NULL);
552
553         msgdata = get_msgdata_for_uid(item, uid);
554         if (msgdata == NULL) {
555                 return NULL;
556         }
557         filename = get_filepath_for_msgdata(item, msgdata);
558         uiddb_free_msgdata(msgdata);
559
560         return filename;
561 }
562
563 static gint maildir_uid_compare(gconstpointer a, gconstpointer b)
564 {
565         guint gint_a = GPOINTER_TO_INT(a);
566         guint gint_b = GPOINTER_TO_INT(b);
567         
568         return (gint_a - gint_b);
569 }
570
571 static gint maildir_get_num_list(Folder *folder, FolderItem *item,
572                                  MsgNumberList ** list, gboolean *old_uids_valid)
573 {
574         gchar *path, *globpattern;
575         glob_t globbuf;
576         int i;
577         MsgNumberList * tail;
578
579         g_return_val_if_fail(open_database(MAILDIR_FOLDERITEM(item)) == 0, -1);
580
581         *old_uids_valid = TRUE;
582
583         globbuf.gl_offs = 0;
584         path = maildir_item_get_path(folder, item);
585
586         globpattern = g_strconcat(path, G_DIR_SEPARATOR_S, DIR_CUR, G_DIR_SEPARATOR_S, "*", NULL);
587         glob(globpattern, GLOB_NOSORT, NULL, &globbuf);
588         g_free(globpattern);
589
590         globpattern = g_strconcat(path, G_DIR_SEPARATOR_S, DIR_NEW, G_DIR_SEPARATOR_S, "*", NULL);
591         glob(globpattern, GLOB_NOSORT | GLOB_APPEND, NULL, &globbuf);
592         g_free(globpattern);
593
594         g_free(path);
595
596         tail = g_slist_last(*list);
597         
598         for (i = 0; i < globbuf.gl_pathc; i++) {
599                 guint32 uid;
600
601                 uid = get_uid_for_filename((MaildirFolderItem *) item, globbuf.gl_pathv[i]);
602                 if (uid != 0) {
603                         tail = g_slist_append(tail, GINT_TO_POINTER(uid));
604                         tail = g_slist_last(tail);
605                         if (!*list) *list = tail;
606                 }
607         }
608         globfree(&globbuf);
609
610         *list = g_slist_sort(*list, maildir_uid_compare);
611
612         uiddb_delete_entries_not_in_list(((MaildirFolderItem *) item)->db, *list);
613
614         close_database(MAILDIR_FOLDERITEM(item));
615         return g_slist_length(*list);
616 }
617
618 static MsgInfo *maildir_parse_msg(const gchar *file, FolderItem *item)
619 {
620         MsgInfo *msginfo;
621         MsgFlags flags;
622
623         flags.perm_flags = MSG_NEW|MSG_UNREAD;
624         flags.tmp_flags = 0;
625
626         g_return_val_if_fail(item != NULL, NULL);
627         g_return_val_if_fail(file != NULL, NULL);
628
629         if (item->stype == F_QUEUE) {
630                 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
631         } else if (item->stype == F_DRAFT) {
632                 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
633         }
634
635         msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
636         if (!msginfo) return NULL;
637
638         msginfo->msgnum = atoi(file);
639         msginfo->folder = item;
640
641 #if 0
642         /* this is already done by procheader_parse_file */
643         if (stat(file, &s) < 0) {
644                 FILE_OP_ERROR(file, "stat");
645                 msginfo->size = 0;
646                 msginfo->mtime = 0;
647         } else {
648                 msginfo->size = (goffset)s.st_size;
649                 msginfo->mtime = s.st_mtime;
650         }
651 #endif
652
653         return msginfo;
654 }
655
656 static MsgInfo *maildir_get_msginfo(Folder * folder,
657                                     FolderItem * item, gint num)
658 {
659         MsgInfo *msginfo;
660         gchar *file;
661
662         g_return_val_if_fail(item != NULL, NULL);
663         g_return_val_if_fail(num > 0, NULL);
664
665         file = maildir_fetch_msg(folder, item, num);
666         if (!file) return NULL;
667
668         msginfo = maildir_parse_msg(file, item);
669         if (msginfo)
670                 msginfo->msgnum = num;
671
672         g_free(file);
673
674         return msginfo;
675 }
676
677 static gchar *maildir_fetch_msg(Folder * folder, FolderItem * item,
678                                 gint num)
679 {
680         gchar *filename;
681         
682         g_return_val_if_fail(open_database(MAILDIR_FOLDERITEM(item)) == 0, NULL);
683         filename = get_filepath_for_uid((MaildirFolderItem *) item, num);
684         close_database(MAILDIR_FOLDERITEM(item));
685         
686         return filename;
687 }
688
689 static gchar *generate_uniq()
690 {
691         gchar hostname[32], *strptr;
692         static gint q = 1;
693         struct timeval tv;
694
695         gethostname(hostname, 32);
696         hostname[31] = '\0';
697
698         strptr = &hostname[0];
699         while (*strptr != '\0') {
700                 if (*strptr == '/')
701                         *strptr = '\057';
702                 if (*strptr == ':')
703                         *strptr = '\072';
704                 strptr++;
705         }
706
707         gettimeofday(&tv, NULL);
708
709         return g_strdup_printf("%d.P%dQ%dM%d.%s", (int) tv.tv_sec, getpid(), q++, (int) tv.tv_usec, hostname);
710 }
711
712 static gchar *get_infostr(MsgPermFlags permflags)
713 {
714         if (permflags & MSG_NEW)
715                 return g_strdup("");
716
717         return g_strconcat("2,",
718                   permflags & MSG_MARKED    ? "F" : "",
719                   permflags & MSG_FORWARDED ? "P" : "",
720                   permflags & MSG_REPLIED   ? "R" : "",
721                 !(permflags & MSG_UNREAD)   ? "S" : "",
722                 NULL);
723 }
724
725 static gint add_file_to_maildir(MaildirFolderItem *item, const gchar *file, MsgFlags *flags)
726 {
727         MessageData *msgdata;
728         gchar *tmpname, *destname;
729         gint uid = -1;
730
731         g_return_val_if_fail(item != NULL, -1);
732         g_return_val_if_fail(open_database(MAILDIR_FOLDERITEM(item)) == 0, -1);
733
734         msgdata = g_new0(MessageData, 1);
735         msgdata->uniq = generate_uniq();
736         if (flags != NULL)
737                 msgdata->info = get_infostr(flags->perm_flags);
738         else
739                 msgdata->info = g_strdup("");
740         msgdata->uid = uiddb_get_new_uid(item->db);
741
742         msgdata->dir = DIR_TMP;
743         tmpname = get_filepath_for_msgdata(item, msgdata);
744
745         if (flags != NULL)
746                 msgdata->dir = g_strdup(flags->perm_flags & MSG_NEW ? DIR_NEW : DIR_CUR);
747         else
748                 msgdata->dir = g_strdup(DIR_NEW);
749
750         if (copy_file(file, tmpname, FALSE) < 0) {
751                 goto exit;
752         }
753
754         destname = get_filepath_for_msgdata(item, msgdata);
755         if (rename(tmpname, destname) < 0) {
756                 g_free(destname);
757                 goto exit;
758         }
759
760         uiddb_insert_entry(item->db, msgdata);
761
762         uid = msgdata->uid;
763         
764  exit:
765         uiddb_free_msgdata(msgdata);
766         g_free(tmpname);
767         close_database(MAILDIR_FOLDERITEM(item));
768         return uid;
769 }
770
771 static gint maildir_add_msg(Folder *folder, FolderItem *_dest, const gchar *file, MsgFlags *flags)
772 {
773         MaildirFolderItem *dest = MAILDIR_FOLDERITEM(_dest);
774
775         g_return_val_if_fail(folder != NULL, -1);
776         g_return_val_if_fail(dest != NULL, -1);
777         g_return_val_if_fail(file != NULL, -1);
778
779         return add_file_to_maildir(dest, file, flags);
780 }
781
782 static gint maildir_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
783 {
784         gchar *srcfile;
785         gint ret = -1;
786         gboolean delsrc = FALSE;
787
788         g_return_val_if_fail(folder != NULL, -1);
789         g_return_val_if_fail(dest != NULL, -1);
790         g_return_val_if_fail(msginfo != NULL, -1);
791
792         srcfile = procmsg_get_message_file(msginfo);
793         if (srcfile == NULL)
794                 return -1;
795
796         if ((MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags))
797             && dest->stype != F_QUEUE && dest->stype != F_DRAFT) {
798                 gchar *tmpfile;
799
800                 tmpfile = get_tmp_file();
801                 if (procmsg_remove_special_headers(srcfile, tmpfile) != 0) {
802                         g_free(srcfile);
803                         g_free(tmpfile);
804                         return -1;
805                 }               
806                 g_free(srcfile);
807                 srcfile = tmpfile;
808                 delsrc = TRUE;
809         }
810
811         ret = add_file_to_maildir(MAILDIR_FOLDERITEM(dest), srcfile, &msginfo->flags);
812
813         if (delsrc)
814                 unlink(srcfile);
815         g_free(srcfile);
816
817         return ret;
818 }
819
820 static gint maildir_remove_msg(Folder *folder, FolderItem *_item, gint num)
821 {
822         MaildirFolderItem *item = MAILDIR_FOLDERITEM(_item);
823         gchar *filename;
824         gint ret;
825
826         g_return_val_if_fail(folder != NULL, -1);
827         g_return_val_if_fail(item != NULL, -1);
828         g_return_val_if_fail(num > 0, -1);
829
830         g_return_val_if_fail(open_database(MAILDIR_FOLDERITEM(item)) == 0, -1);
831         
832         filename = get_filepath_for_uid(item, num);
833         if (filename == NULL) {
834                 ret = -1;
835                 goto close;
836         }
837
838         ret = unlink(filename); 
839         if (ret == 0)
840                 uiddb_delete_entry(item->db, num);
841
842         g_free(filename);
843         
844  close:
845         close_database(MAILDIR_FOLDERITEM(item));
846         return ret;
847 }
848
849 static void maildir_change_flags(Folder *folder, FolderItem *_item, MsgInfo *msginfo, MsgPermFlags newflags)
850 {
851         MaildirFolderItem *item = MAILDIR_FOLDERITEM(_item);
852         MessageData *msgdata;
853         gchar *oldname, *newinfo, *newdir;
854         gboolean renamefile = FALSE;
855
856         g_return_if_fail(open_database(MAILDIR_FOLDERITEM(item)) == 0);
857
858         msgdata = get_msgdata_for_uid(item, msginfo->msgnum);
859         if (msgdata == NULL)
860                 goto fail;
861         
862         oldname = get_filepath_for_msgdata(item, msgdata);
863
864         newinfo = get_infostr(newflags);
865         if (strcmp(msgdata->info, newinfo)) {
866                 g_free(msgdata->info);
867                 msgdata->info = newinfo;
868                 renamefile = TRUE;
869         } else
870                 g_free(newinfo);
871
872         newdir = g_strdup(newflags & MSG_NEW ? DIR_NEW : DIR_CUR);
873         if (strcmp(msgdata->dir, newdir)) {
874                 g_free(msgdata->dir);
875                 msgdata->dir = newdir;
876                 renamefile = TRUE;
877         } else
878                 g_free(newdir);
879
880         if (renamefile) {
881                 gchar *newname;
882
883                 newname = get_filepath_for_msgdata(item, msgdata);
884                 if (rename(oldname, newname) == 0) {
885                         uiddb_delete_entry(item->db, msgdata->uid);
886                         uiddb_insert_entry(item->db, msgdata);
887                         msginfo->flags.perm_flags = newflags;
888                 }
889                 g_free(newname);
890         } else {
891                 msginfo->flags.perm_flags = newflags;
892         }
893
894         g_free(oldname);
895         uiddb_free_msgdata(msgdata);
896         
897         close_database(MAILDIR_FOLDERITEM(item));
898         
899         if (renamefile) {
900                 MainWindow *mainwin = mainwindow_get_mainwindow();
901                 SummaryView *summaryview = mainwin->summaryview;
902                 gint displayed_msgnum = -1;
903                 if (summaryview->displayed)
904                         displayed_msgnum = summary_get_msgnum(summaryview,
905                                                       summaryview->displayed);
906                 if (displayed_msgnum == msginfo->msgnum
907                 && summaryview->folder_item == msginfo->folder)
908                         messageview_show(
909                                 summaryview->messageview, 
910                                 msginfo,
911                                 summaryview->messageview->all_headers);
912         }
913         
914         return;
915         
916  fail:
917         close_database(MAILDIR_FOLDERITEM(item));
918 }
919
920 static gboolean setup_new_folder(const gchar * path, gboolean subfolder)
921 {
922         gchar *curpath, *newpath, *tmppath, *maildirfolder;
923         gboolean failed = FALSE;
924
925         g_return_val_if_fail(path != NULL, TRUE);
926
927         curpath = g_strconcat(path, G_DIR_SEPARATOR_S, DIR_CUR, NULL);
928         newpath = g_strconcat(path, G_DIR_SEPARATOR_S, DIR_NEW, NULL);
929         tmppath = g_strconcat(path, G_DIR_SEPARATOR_S, DIR_TMP, NULL);
930
931         if (!is_dir_exist(path))
932                 if (mkdir(path, DIR_PERMISSION) != 0)
933                         failed = TRUE;
934         if (!is_dir_exist(curpath))
935                 if (mkdir(curpath, DIR_PERMISSION) != 0)
936                         failed = TRUE;
937         if (!is_dir_exist(newpath))
938                 if (mkdir(newpath, DIR_PERMISSION) != 0)
939                         failed = TRUE;
940         if (!is_dir_exist(tmppath))
941                 if (mkdir(tmppath, DIR_PERMISSION) != 0)
942                         failed = TRUE;
943
944         if (subfolder) {
945                 int res;
946                 maildirfolder = g_strconcat(path, G_DIR_SEPARATOR_S, "maildirfolder", NULL);
947                 res = open(maildirfolder, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY,
948                            S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
949                 if (res != -1)
950                         close(res);
951                 else
952                         failed = TRUE;
953         }
954
955         if (failed) {
956                 rmdir(tmppath);
957                 rmdir(newpath);
958                 rmdir(curpath);
959                 rmdir(path);
960         }
961             
962         g_free(tmppath);
963         g_free(newpath);
964         g_free(curpath);
965
966         return failed;
967 }
968
969 static FolderItem *maildir_create_folder(Folder * folder,
970                                          FolderItem * parent,
971                                          const gchar * name)
972 {
973         gchar *folder_path, *path, *real_path;
974         FolderItem *newitem = NULL;
975         gboolean failed = FALSE;
976
977         g_return_val_if_fail(folder != NULL, NULL);
978         g_return_val_if_fail(parent != NULL, NULL);
979         g_return_val_if_fail(name != NULL, NULL);
980
981         folder_path = g_strdup(LOCAL_FOLDER(folder)->rootpath);
982         g_return_val_if_fail(folder_path != NULL, NULL);
983
984         if (g_path_is_absolute(folder_path)) {
985                 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
986                                    parent->path != NULL ? parent->path : "",
987                                    ".", name, NULL);
988         } else {
989                 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
990                                    folder_path, G_DIR_SEPARATOR_S,
991                                    parent->path != NULL ? parent->path : "",
992                                    ".", name, NULL);
993         }
994         g_free(folder_path);
995
996         debug_print("creating new maildir folder: %s\n", path);
997
998         real_path = filename_from_utf8(path);
999         g_free(path);
1000
1001         failed = setup_new_folder(real_path, TRUE);
1002         g_free(real_path);
1003
1004         if (failed)
1005                 return NULL;
1006
1007         path = g_strconcat((parent->path != NULL) ? parent->path : "", ".", name, NULL);
1008         newitem = folder_item_new(folder, name, path);
1009         folder_item_append(parent, newitem);
1010         g_free(path);
1011
1012         return newitem;
1013 }
1014
1015 static gint maildir_create_tree(Folder *folder)
1016 {
1017         gchar *rootpath, *real_rootpath, *folder_path, *path;
1018
1019         g_return_val_if_fail(folder != NULL, -1);
1020
1021         folder_path = g_strdup(LOCAL_FOLDER(folder)->rootpath);
1022         g_return_val_if_fail(folder_path != NULL, -1);
1023
1024         if (g_path_is_absolute(folder_path)) {
1025                 rootpath = g_strdup(folder_path);
1026         } else {
1027                 rootpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1028                                    folder_path, NULL);
1029         }
1030         g_free(folder_path);
1031
1032         real_rootpath = filename_from_utf8(rootpath);
1033         g_free(rootpath);
1034
1035         debug_print("creating new maildir tree: %s\n", real_rootpath);
1036         if (!is_dir_exist(real_rootpath)) {
1037                 if (is_file_exist(real_rootpath)) {
1038                         g_warning("File `%s' already exists.\n"
1039                                     "Can't create folder.", real_rootpath);
1040                         return -1;
1041                 }
1042                 if (make_dir_hier(real_rootpath) < 0)
1043                         return -1;
1044         }
1045
1046         if (setup_new_folder(real_rootpath, FALSE)) { /* create INBOX */
1047                 g_free(real_rootpath);
1048                 return -1;
1049         }
1050         path = g_strconcat(real_rootpath, G_DIR_SEPARATOR_S, "." OUTBOX_DIR, NULL);
1051         if (setup_new_folder(path, TRUE)) {
1052                 g_free(path);
1053         g_free(real_rootpath);
1054                         return -1;
1055         }
1056         g_free(path);
1057         path = g_strconcat(real_rootpath, G_DIR_SEPARATOR_S, "." QUEUE_DIR, NULL);
1058         if (setup_new_folder(path, TRUE)) {
1059                 g_free(path);
1060                 g_free(real_rootpath);
1061                         return -1;
1062         }
1063         g_free(path);
1064         path = g_strconcat(real_rootpath, G_DIR_SEPARATOR_S, "." DRAFT_DIR, NULL);
1065         if (setup_new_folder(path, TRUE)) {
1066                 g_free(path);
1067                 g_free(real_rootpath);
1068                         return -1;
1069         }
1070         g_free(path);
1071         path = g_strconcat(real_rootpath, G_DIR_SEPARATOR_S, "." TRASH_DIR, NULL);
1072         if (setup_new_folder(path, TRUE)) {
1073                 g_free(path);
1074                 g_free(real_rootpath);
1075                         return -1;
1076         }
1077         g_free(path);
1078         g_free(real_rootpath);
1079
1080         return 0;
1081 }
1082
1083 static gboolean remove_missing_folder_items_func(GNode *node, gpointer data)
1084 {
1085         FolderItem *item;
1086         gchar *path;
1087
1088         g_return_val_if_fail(node->data != NULL, FALSE);
1089
1090         if (G_NODE_IS_ROOT(node))
1091                 return FALSE;
1092
1093         item = FOLDER_ITEM(node->data);
1094
1095         if (item->stype == F_INBOX)
1096                 return FALSE;
1097
1098         path = folder_item_get_path(item);
1099         if (!is_dir_exist(path)) {
1100                 debug_print("folder '%s' not found. removing...\n", path);
1101                 folder_item_remove(item);
1102         }
1103         g_free(path);
1104
1105         return FALSE;
1106 }
1107
1108 static void remove_missing_folder_items(Folder *folder)
1109 {
1110         g_return_if_fail(folder != NULL);
1111
1112         debug_print("searching missing folders...\n");
1113
1114         g_node_traverse(folder->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
1115                         remove_missing_folder_items_func, folder);
1116 }
1117
1118 static gboolean remove_folder_func(GNode *node, gpointer data)
1119 {
1120         FolderItem *item;
1121         gchar *path;
1122
1123         g_return_val_if_fail(node->data != NULL, FALSE);
1124
1125         if (G_NODE_IS_ROOT(node))
1126                 return FALSE;
1127
1128         item = FOLDER_ITEM(node->data);
1129
1130         if (item->stype != F_NORMAL)
1131                 return FALSE;
1132
1133         path = folder_item_get_path(item);
1134         debug_print("removing directory %s\n", path);
1135         if (remove_dir_recursive(path) < 0) {
1136                 g_warning("can't remove directory `%s'\n", path);
1137                 g_free(path);
1138                 *(gint*)data = -1;
1139                 return TRUE;
1140         }
1141         g_free(path);
1142
1143         folder_item_remove(item);
1144
1145         return FALSE;
1146 }
1147
1148 static gint maildir_remove_folder(Folder *folder, FolderItem *item)
1149 {
1150         gint res=0;
1151
1152         g_return_val_if_fail(folder != NULL, -1);
1153         g_return_val_if_fail(item != NULL, -1);
1154         g_return_val_if_fail(item->path != NULL, -1);
1155         g_return_val_if_fail(item->stype == F_NORMAL, -1);
1156
1157         debug_print("removing folder %s\n", item->path);
1158
1159         g_node_traverse(item->node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
1160                         remove_folder_func, &res);
1161
1162         return res;
1163 }
1164
1165 struct RenameData
1166 {
1167         gint     oldprefixlen;
1168         gchar   *newprefix;
1169 };
1170
1171 static gboolean rename_folder_func(GNode *node, gpointer data)
1172 {
1173         FolderItem *item;
1174         gchar *oldpath, *newpath, *newitempath;
1175         gchar *suffix, *real_path, *real_rootpath;
1176         struct RenameData *renamedata = data;
1177
1178         g_return_val_if_fail(node->data != NULL, FALSE);
1179
1180         if (G_NODE_IS_ROOT(node))
1181                 return FALSE;
1182
1183         item = FOLDER_ITEM(node->data);
1184
1185         if (item->stype != F_NORMAL)
1186                 return FALSE;
1187
1188         real_rootpath = filename_from_utf8(LOCAL_FOLDER(item->folder)->rootpath);
1189         real_path = filename_from_utf8(item->path);
1190         suffix = real_path + renamedata->oldprefixlen;
1191
1192         oldpath = folder_item_get_path(item);
1193         newitempath = g_strconcat(renamedata->newprefix, suffix, NULL);
1194         newpath = g_strconcat(real_rootpath, G_DIR_SEPARATOR_S, newitempath, NULL);
1195         g_free(real_path);
1196         g_free(real_rootpath);
1197
1198         debug_print("renaming directory %s to %s\n", oldpath, newpath);
1199
1200         if (rename(oldpath, newpath) < 0) {
1201                 FILE_OP_ERROR(oldpath, "rename");
1202         } else {
1203                 g_free(item->path);
1204                 item->path = filename_to_utf8(newitempath);
1205         }
1206
1207         g_free(newitempath);
1208         g_free(oldpath);
1209         g_free(newpath);
1210
1211         return FALSE;
1212 }
1213
1214 static gint maildir_rename_folder(Folder *folder, FolderItem *item,
1215                              const gchar *name)
1216 {
1217         struct RenameData renamedata;
1218         gchar *p, *real_path, *real_name;
1219
1220         g_return_val_if_fail(folder != NULL, -1);
1221         g_return_val_if_fail(item != NULL, -1);
1222         g_return_val_if_fail(item->path != NULL, -1);
1223         g_return_val_if_fail(name != NULL, -1);
1224
1225         debug_print("renaming folder %s to %s\n", item->path, name);
1226
1227         g_free(item->name);
1228         item->name = g_strdup(name);
1229
1230         real_path = filename_from_utf8(item->path);
1231         real_name = filename_from_utf8(name);
1232
1233         renamedata.oldprefixlen = strlen(real_path);
1234         p = strrchr(real_path, '.');
1235         if (p)
1236                 p = g_strndup(real_path, p - real_path + 1);
1237         else
1238                 p = g_strdup(".");
1239         renamedata.newprefix = g_strconcat(p, real_name, NULL);
1240         g_free(p);
1241         g_free(real_name);
1242         g_free(real_path);
1243
1244         g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1245                         rename_folder_func, &renamedata);
1246
1247         g_free(renamedata.newprefix);
1248
1249         return 0;
1250 }
1251
1252 static gint get_flags_for_msgdata(MessageData *msgdata, MsgPermFlags *flags)
1253 {
1254         gint    i;
1255
1256         g_return_val_if_fail(msgdata != NULL, -1);
1257         g_return_val_if_fail(msgdata->info != NULL, -1);
1258
1259         if ((msgdata->info[0] != '2') && (msgdata->info[1] != ','))
1260                 return -1;
1261
1262         *flags = MSG_UNREAD;
1263         for (i = 2; i < strlen(msgdata->info); i++) {
1264                 switch (msgdata->info[i]) {
1265                         case 'F':
1266                                   *flags |= MSG_MARKED;
1267                                   break;
1268                         case 'P':
1269                                   *flags |= MSG_FORWARDED;
1270                                   break;
1271                         case 'R':
1272                                   *flags |= MSG_REPLIED;
1273                                   break;
1274                         case 'S':
1275                                   *flags &= ~MSG_UNREAD;
1276                                   break;
1277                 }
1278         }
1279
1280         return 0;
1281 }
1282
1283 static gint maildir_get_flags (Folder *folder,  FolderItem *item,
1284                                MsgInfoList *msglist, GHashTable *msgflags)
1285 {
1286         MsgInfoList     *elem;
1287         MsgInfo         *msginfo;
1288         MessageData     *msgdata;
1289         MsgPermFlags    flags;
1290
1291         g_return_val_if_fail(folder != NULL, -1);
1292         g_return_val_if_fail(item != NULL, -1);
1293         g_return_val_if_fail(msglist != NULL, -1);
1294         g_return_val_if_fail(msgflags != NULL, -1);
1295         g_return_val_if_fail(open_database(MAILDIR_FOLDERITEM(item)) == 0, -1);
1296
1297         for (elem = msglist; elem != NULL; elem = g_slist_next(elem)) {
1298                 msginfo = (MsgInfo*) elem->data;
1299                 msgdata = uiddb_get_entry_for_uid(MAILDIR_FOLDERITEM(item)->db, msginfo->msgnum);
1300                 if (msgdata == NULL)
1301                         break;
1302
1303                 if (get_flags_for_msgdata(msgdata, &flags) < 0)
1304                         break;
1305
1306                 flags = flags | (msginfo->flags.perm_flags & 
1307                         ~(MSG_MARKED | MSG_FORWARDED | MSG_REPLIED | MSG_UNREAD | ((flags & MSG_UNREAD) == 0 ? MSG_NEW : 0)));
1308                 g_hash_table_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
1309
1310                 uiddb_free_msgdata(msgdata);
1311         }
1312
1313         close_database(MAILDIR_FOLDERITEM(item));
1314         return 0;
1315 }
1316
1317 static gchar *filename_from_utf8(const gchar *path)
1318 {
1319         gchar *real_path = g_filename_from_utf8(path, -1, NULL, NULL, NULL);
1320
1321         if (!real_path) {
1322                 g_warning("filename_from_utf8: failed to convert character set\n");
1323                 real_path = g_strdup(path);
1324         }
1325
1326         return real_path;
1327 }
1328
1329 static gchar *filename_to_utf8(const gchar *path)
1330 {
1331         gchar *utf8path = g_filename_to_utf8(path, -1, NULL, NULL, NULL);
1332         if (!utf8path) {
1333                 g_warning("filename_to_utf8: failed to convert character set\n");
1334                 utf8path = g_strdup(path);
1335         }
1336
1337         return utf8path;
1338 }