2006-07-08 [colin] 2.3.1cvs74
[claws.git] / src / mbox.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws team
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <fcntl.h>
32 #include <sys/file.h>
33 #include <ctype.h>
34 #include <time.h>
35 #include <errno.h>
36
37 #include "mbox.h"
38 #include "procmsg.h"
39 #include "folder.h"
40 #include "prefs_common.h"
41 #include "prefs_account.h"
42 #include "account.h"
43 #include "utils.h"
44 #include "filtering.h"
45 #include "alertpanel.h"
46 #include "statusbar.h"
47
48 #define MSGBUFSIZE      8192
49
50 #define FPUTS_TO_TMP_ABORT_IF_FAIL(s) \
51 { \
52         lines++; \
53         if (fputs(s, tmp_fp) == EOF) { \
54                 g_warning("can't write to temporary file\n"); \
55                 fclose(tmp_fp); \
56                 fclose(mbox_fp); \
57                 g_unlink(tmp_file); \
58                 g_free(tmp_file); \
59                 return -1; \
60         } \
61 }
62
63 gint proc_mbox(FolderItem *dest, const gchar *mbox, gboolean apply_filter)
64 /* return values: -1 error, >=0 number of msgs added */
65 {
66         FILE *mbox_fp;
67         gchar buf[MSGBUFSIZE];
68         gchar *tmp_file;
69         gint msgs = 0;
70         gint lines;
71         MsgInfo *msginfo;
72         gboolean more;
73         GSList *to_filter = NULL, *to_drop = NULL, *cur, *to_add = NULL;
74         gboolean printed = FALSE;
75         FolderItem *dropfolder;
76
77         g_return_val_if_fail(dest != NULL, -1);
78         g_return_val_if_fail(mbox != NULL, -1);
79
80         debug_print("Getting messages from %s into %s...\n", mbox, dest->path);
81
82         if ((mbox_fp = g_fopen(mbox, "rb")) == NULL) {
83                 FILE_OP_ERROR(mbox, "fopen");
84                 alertpanel_error(_("Could not open mbox file:\n%s\n"), mbox);
85                 return -1;
86         }
87
88         /* ignore empty lines on the head */
89         do {
90                 if (fgets(buf, sizeof(buf), mbox_fp) == NULL) {
91                         g_warning("can't read mbox file.\n");
92                         fclose(mbox_fp);
93                         return -1;
94                 }
95         } while (buf[0] == '\n' || buf[0] == '\r');
96
97         if (strncmp(buf, "From ", 5) != 0) {
98                 g_warning("invalid mbox format: %s\n", mbox);
99                 fclose(mbox_fp);
100                 return -1;
101         }
102
103         tmp_file = get_tmp_file();
104
105         folder_item_update_freeze();
106
107         if (apply_filter)
108                 dropfolder = folder_get_default_processing();
109         else
110                 dropfolder = dest;
111         
112         do {
113                 FILE *tmp_fp;
114                 gint empty_lines;
115                 gint msgnum;
116                 
117                 if (msgs > 0 && msgs%500 == 0) {
118                         if (printed)
119                                 statusbar_pop_all();
120                         statusbar_print_all(_("Importing from mbox... (%d mails imported)"), msgs);
121                         printed=TRUE;
122                         GTK_EVENTS_FLUSH();
123                 }
124         
125                 if ((tmp_fp = g_fopen(tmp_file, "wb")) == NULL) {
126                         FILE_OP_ERROR(tmp_file, "fopen");
127                         g_warning("can't open temporary file\n");
128                         fclose(mbox_fp);
129                         g_free(tmp_file);
130                         return -1;
131                 }
132                 if (change_file_mode_rw(tmp_fp, tmp_file) < 0) {
133                         FILE_OP_ERROR(tmp_file, "chmod");
134                 }
135
136                 empty_lines = 0;
137                 lines = 0;
138                 more = FALSE;
139
140                 /* process all lines from mboxrc file */
141                 while (fgets(buf, sizeof(buf), mbox_fp) != NULL) {
142                         int offset;
143
144                         /* eof not reached, expect more lines */
145                         more = TRUE;
146
147                         /* eat empty lines */
148                         if (buf[0] == '\n' || buf[0] == '\r') {
149                                 empty_lines++;
150                                 continue;
151                         }
152
153                         /* From separator or quoted From */
154                         offset = 0;
155                         /* detect leading '>' char(s) */
156                         while ((buf[offset] == '>')) {
157                                 offset++;
158                         }
159                         if (!strncmp(buf+offset, "From ", 5)) {
160                                 /* From separator: */
161                                 if (offset == 0) {
162                                         /* expect next mbox item */
163                                         break;
164                                 }
165
166                                 /* quoted From: */
167                                 /* flush any eaten empty line */
168                                 if (empty_lines > 0) {
169                                         while (empty_lines-- > 0) {
170                                                 FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
171                                 }
172                                         empty_lines = 0;
173                                 }
174                                 /* store the unquoted line */
175                                 FPUTS_TO_TMP_ABORT_IF_FAIL(buf + 1);
176                                 continue;
177                         }
178
179                         /* other line */
180                         /* flush any eaten empty line */
181                         if (empty_lines > 0) {                  
182                                 while (empty_lines-- > 0) {
183                                         FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
184                         }
185                                 empty_lines = 0;
186                         }
187                         /* store the line itself */
188                                         FPUTS_TO_TMP_ABORT_IF_FAIL(buf);
189                 }
190                 /* end of mbox item or end of mbox */
191
192                 /* flush any eaten empty line (but the last one) */
193                 if (empty_lines > 0) {
194                         while (--empty_lines > 0) {
195                                 FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
196                         }
197                 }
198
199                 /* more emails to expect? */
200                 more = !feof(mbox_fp);
201
202                 /* warn if email part is empty (it's the minimum check 
203                    we can do */
204                 if (lines == 0) {
205                         g_warning("malformed mbox: %s: message %d is empty\n", mbox, msgs);
206                         fclose(tmp_fp);
207                         fclose(mbox_fp);
208                         g_unlink(tmp_file);
209                         return -1;
210                 }
211
212                 if (fclose(tmp_fp) == EOF) {
213                         FILE_OP_ERROR(tmp_file, "fclose");
214                         g_warning("can't write to temporary file\n");
215                         fclose(mbox_fp);
216                         g_unlink(tmp_file);
217                         g_free(tmp_file);
218                         return -1;
219                 }
220
221                 if (apply_filter) {
222                         if ((msgnum = folder_item_add_msg(dropfolder, tmp_file, NULL, TRUE)) < 0) {
223                                 fclose(mbox_fp);
224                                 g_unlink(tmp_file);
225                                 g_free(tmp_file);
226                                 return -1;
227                         }
228                         msginfo = folder_item_get_msginfo(dropfolder, msgnum);
229                         if (!procmsg_msginfo_filter(msginfo))
230                                 to_drop = g_slist_prepend(to_drop, msginfo);
231                         else
232                                 to_filter = g_slist_prepend(to_filter, msginfo);
233                 } else {
234                         MsgFileInfo *finfo = g_new0(MsgFileInfo, 1);
235                         finfo->file = tmp_file;
236                         
237                         to_add = g_slist_prepend(to_add, finfo);
238                         tmp_file = get_tmp_file();
239                         
240                         /* flush every 500 */
241                         if (msgs > 0 && msgs % 500 == 0) {
242                                 folder_item_add_msgs(dropfolder, to_add, TRUE);
243                                 procmsg_message_file_list_free(to_add);
244                                 to_add = NULL;
245                         }
246                 }
247                 msgs++;
248         } while (more);
249
250         if (printed)
251                 statusbar_pop_all();
252
253         if (apply_filter) {
254                 to_drop = g_slist_reverse(to_drop);
255                 folder_item_move_msgs(dest, to_drop);
256                 for (cur = to_drop; cur; cur = g_slist_next(cur)) {
257                         MsgInfo *info = (MsgInfo *)cur->data;
258                         procmsg_msginfo_free(info);
259                 }
260
261                 to_filter = g_slist_reverse(to_filter);
262                 filtering_move_and_copy_msgs(to_filter);
263                 for (cur = to_filter; cur; cur = g_slist_next(cur)) {
264                         MsgInfo *info = (MsgInfo *)cur->data;
265                         procmsg_msginfo_free(info);
266                 }
267
268                 g_slist_free(to_drop);
269                 g_slist_free(to_filter);
270         } else if (to_add) {
271                 folder_item_add_msgs(dropfolder, to_add, TRUE);
272                 procmsg_message_file_list_free(to_add);
273                 to_add = NULL;
274         }
275
276         folder_item_update_thaw();
277         
278         g_free(tmp_file);
279         fclose(mbox_fp);
280         debug_print("%d messages found.\n", msgs);
281
282         return msgs;
283 }
284
285 gint lock_mbox(const gchar *base, LockType type)
286 {
287 #ifdef G_OS_UNIX
288         gint retval = 0;
289
290         if (type == LOCK_FILE) {
291                 gchar *lockfile, *locklink;
292                 gint retry = 0;
293                 FILE *lockfp;
294
295                 lockfile = g_strdup_printf("%s.%d", base, getpid());
296                 if ((lockfp = g_fopen(lockfile, "wb")) == NULL) {
297                         FILE_OP_ERROR(lockfile, "fopen");
298                         g_warning("can't create lock file %s\n", lockfile);
299                         g_warning("use 'flock' instead of 'file' if possible.\n");
300                         g_free(lockfile);
301                         return -1;
302                 }
303
304                 fprintf(lockfp, "%d\n", getpid());
305                 fclose(lockfp);
306
307                 locklink = g_strconcat(base, ".lock", NULL);
308                 while (link(lockfile, locklink) < 0) {
309                         FILE_OP_ERROR(lockfile, "link");
310                         if (retry >= 5) {
311                                 g_warning("can't create %s\n", lockfile);
312                                 g_unlink(lockfile);
313                                 g_free(lockfile);
314                                 return -1;
315                         }
316                         if (retry == 0)
317                                 g_warning("mailbox is owned by another"
318                                             " process, waiting...\n");
319                         retry++;
320                         sleep(5);
321                 }
322                 g_unlink(lockfile);
323                 g_free(lockfile);
324         } else if (type == LOCK_FLOCK) {
325                 gint lockfd;
326                 gboolean fcntled = FALSE;
327 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
328                 struct flock fl;
329                 fl.l_type = F_WRLCK;
330                 fl.l_whence = SEEK_SET;
331                 fl.l_start = 0;
332                 fl.l_len = 0;
333 #endif
334
335 #if HAVE_FLOCK
336                 if ((lockfd = open(base, O_RDWR)) < 0) {
337 #else
338                 if ((lockfd = open(base, O_RDWR)) < 0) {
339 #endif
340                         FILE_OP_ERROR(base, "open");
341                         return -1;
342                 }
343                 
344 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
345                 if (fcntl(lockfd, F_SETLK, &fl) == -1) {
346                         g_warning("can't fnctl %s (%s)", base, strerror(errno));
347                         return -1;
348                 } else {
349                         fcntled = TRUE;
350                 }
351 #endif
352
353 #if HAVE_FLOCK
354                 if (flock(lockfd, LOCK_EX|LOCK_NB) < 0 && !fcntled) {
355                         perror("flock");
356 #else
357 #if HAVE_LOCKF
358                 if (lockf(lockfd, F_TLOCK, 0) < 0 && !fcntled) {
359                         perror("lockf");
360 #else
361                 {
362 #endif
363 #endif /* HAVE_FLOCK */
364                         g_warning("can't lock %s\n", base);
365                         if (close(lockfd) < 0)
366                                 perror("close");
367                         return -1;
368                 }
369                 retval = lockfd;
370         } else {
371                 g_warning("invalid lock type\n");
372                 return -1;
373         }
374
375         return retval;
376 #else
377         return -1;
378 #endif /* G_OS_UNIX */
379 }
380
381 gint unlock_mbox(const gchar *base, gint fd, LockType type)
382 {
383         if (type == LOCK_FILE) {
384                 gchar *lockfile;
385
386                 lockfile = g_strconcat(base, ".lock", NULL);
387                 if (g_unlink(lockfile) < 0) {
388                         FILE_OP_ERROR(lockfile, "unlink");
389                         g_free(lockfile);
390                         return -1;
391                 }
392                 g_free(lockfile);
393
394                 return 0;
395         } else if (type == LOCK_FLOCK) {
396                 gboolean fcntled = FALSE;
397 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
398                 struct flock fl;
399                 fl.l_type = F_UNLCK;
400                 fl.l_whence = SEEK_SET;
401                 fl.l_start = 0;
402                 fl.l_len = 0;
403
404                 if (fcntl(fd, F_SETLK, &fl) == -1) {
405                         g_warning("can't fnctl %s", base);
406                         return -1;
407                 } else {
408                         fcntled = TRUE;
409                 }
410 #endif
411 #if HAVE_FLOCK
412                 if (flock(fd, LOCK_UN) < 0 && !fcntled) {
413                         perror("flock");
414 #else
415 #if HAVE_LOCKF
416                 if (lockf(fd, F_ULOCK, 0) < 0 && !fcntled) {
417                         perror("lockf");
418 #else
419                 {
420 #endif
421 #endif /* HAVE_FLOCK */
422                         g_warning("can't unlock %s\n", base);
423                         if (close(fd) < 0)
424                                 perror("close");
425                         return -1;
426                 }
427
428                 if (close(fd) < 0) {
429                         perror("close");
430                         return -1;
431                 }
432
433                 return 0;
434         }
435
436         g_warning("invalid lock type\n");
437         return -1;
438 }
439
440 gint copy_mbox(const gchar *src, const gchar *dest)
441 {
442         return copy_file(src, dest, TRUE);
443 }
444
445 void empty_mbox(const gchar *mbox)
446 {
447         FILE *fp;
448
449         if ((fp = g_fopen(mbox, "wb")) == NULL) {
450                 FILE_OP_ERROR(mbox, "fopen");
451                 g_warning("can't truncate mailbox to zero.\n");
452                 return;
453         }
454         fclose(fp);
455 }
456
457 gint export_list_to_mbox(GSList *mlist, const gchar *mbox)
458 /* return values: -2 skipped, -1 error, 0 OK */
459 {
460         GSList *cur;
461         MsgInfo *msginfo;
462         FILE *msg_fp;
463         FILE *mbox_fp;
464         gchar buf[BUFFSIZE];
465         gint msgs = 1, total = g_slist_length(mlist);
466         if (g_file_test(mbox, G_FILE_TEST_EXISTS) == TRUE) {
467                 if (alertpanel_full(_("Overwrite mbox file"),
468                                         _("This file already exists. Do you want to overwrite it?"),
469                                         GTK_STOCK_CANCEL, _("Overwrite"), NULL, FALSE,
470                                         NULL, ALERT_WARNING, G_ALERTDEFAULT)
471                                 != G_ALERTALTERNATE) {
472                         return -2;
473                 }
474         }
475
476         if ((mbox_fp = g_fopen(mbox, "wb")) == NULL) {
477                 FILE_OP_ERROR(mbox, "fopen");
478                 alertpanel_error(_("Could not create mbox file:\n%s\n"), mbox);
479                 return -1;
480         }
481
482         statusbar_print_all(_("Exporting to mbox..."));
483         for (cur = mlist; cur != NULL; cur = cur->next) {
484                 msginfo = (MsgInfo *)cur->data;
485                 int len;
486
487                 msg_fp = procmsg_open_message(msginfo);
488                 if (!msg_fp) {
489                         continue;
490                 }
491
492                 strncpy2(buf,
493                          msginfo->from ? msginfo->from :
494                          cur_account && cur_account->address ?
495                          cur_account->address : "unknown",
496                          sizeof(buf));
497                 extract_address(buf);
498
499                 fprintf(mbox_fp, "From %s %s",
500                         buf, ctime(&msginfo->date_t));
501
502                 buf[0] = '\0';
503                 
504                 /* write email to mboxrc */
505                 while (fgets(buf, sizeof(buf), msg_fp) != NULL) {
506                         /* quote any From, >From, >>From, etc., according to mbox format specs */
507                         int offset;
508
509                         offset = 0;
510                         /* detect leading '>' char(s) */
511                         while ((buf[offset] == '>')) {
512                                 offset++;
513                         }
514                         if (!strncmp(buf+offset, "From ", 5))
515                                 fputc('>', mbox_fp);
516                         fputs(buf, mbox_fp);
517                 }
518
519                 /* force last line to end w/ a newline */
520                 len = strlen(buf);
521                 if (len > 0) {
522                         len--;
523                         if ((buf[len] != '\n') && (buf[len] != '\r'))
524                                 fputc('\n', mbox_fp);
525                 }
526
527                 /* add a trailing empty line */
528                 fputc('\n', mbox_fp);
529
530                 fclose(msg_fp);
531                 statusbar_progress_all(msgs++,total, 500);
532                 if (msgs%500 == 0)
533                         GTK_EVENTS_FLUSH();
534         }
535         statusbar_progress_all(0,0,0);
536         statusbar_pop_all();
537
538         fclose(mbox_fp);
539
540         return 0;
541 }
542
543 /* read all messages in SRC, and store them into one MBOX file. */
544 /* return values: -2 skipped, -1 error, 0 OK */
545 gint export_to_mbox(FolderItem *src, const gchar *mbox)
546 {
547         GSList *mlist;
548         gint ret;
549         
550         g_return_val_if_fail(src != NULL, -1);
551         g_return_val_if_fail(src->folder != NULL, -1);
552         g_return_val_if_fail(mbox != NULL, -1);
553
554         debug_print("Exporting messages from %s into %s...\n",
555                     src->path, mbox);
556
557         mlist = folder_item_get_msg_list(src);
558
559         ret = export_list_to_mbox(mlist, mbox);
560
561         procmsg_msg_list_free(mlist);
562
563         return ret;
564 }