/* * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client * Copyright (C) 1999-2001 Hiroyuki Yamamoto & The Sylpheed Claws Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include "mbox_folder.h" #include "folder.h" #include "procmsg.h" #include "procheader.h" #include "utils.h" #include "intl.h" #define MSGBUFSIZE 8192 static gboolean mbox_write_data(FILE * mbox_fp, FILE * new_fp, gchar * new_filename, gint size); static gboolean mbox_rewrite(gchar * mbox); static gboolean mbox_purge_deleted(gchar * mbox); static gchar * mbox_get_new_path(FolderItem * parent, gchar * name); static gchar * mbox_get_folderitem_name(gchar * name); static gchar * mbox_folder_create_parent(const gchar * path) { if (!is_file_exist(path)) { gchar * new_path; new_path = g_dirname(path); if (new_path[strlen(new_path) - 1] == G_DIR_SEPARATOR) new_path[strlen(new_path) - 1] = '\0'; if (!is_dir_exist(new_path)) make_dir_hier(new_path); g_free(new_path); } } static gchar *mbox_folder_get_path(FolderItem *item) { gchar *folder_path; gchar *path; g_return_val_if_fail(item != NULL, NULL); if (item->path && item->path[0] == G_DIR_SEPARATOR) { mbox_folder_create_parent(item->path); return g_strdup(item->path); } folder_path = g_strdup(LOCAL_FOLDER(item->folder)->rootpath); g_return_val_if_fail(folder_path != NULL, NULL); if (folder_path[0] == G_DIR_SEPARATOR) { if (item->path) { path = g_strconcat(folder_path, G_DIR_SEPARATOR_S, item->path, NULL); } else path = g_strdup(folder_path); } else { if (item->path) path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, folder_path, G_DIR_SEPARATOR_S, item->path, NULL); else path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, folder_path, NULL); } g_free(folder_path); mbox_folder_create_parent(path); return path; } /**********************************************************/ /* */ /* file lock */ /* */ /**********************************************************/ static GSList * file_lock = NULL; static gboolean mbox_file_lock_file(gchar * base) { gchar *lockfile, *locklink; gint retry = 0; FILE *lockfp; lockfile = g_strdup_printf("%s.%d", base, getpid()); if ((lockfp = fopen(lockfile, "w")) == NULL) { FILE_OP_ERROR(lockfile, "fopen"); g_warning(_("can't create lock file %s\n"), lockfile); g_warning(_("use 'flock' instead of 'file' if possible.\n")); g_free(lockfile); return FALSE; } fprintf(lockfp, "%d\n", getpid()); fclose(lockfp); locklink = g_strconcat(base, ".lock", NULL); while (link(lockfile, locklink) < 0) { FILE_OP_ERROR(lockfile, "link"); if (retry >= 5) { g_warning(_("can't create %s\n"), lockfile); unlink(lockfile); g_free(lockfile); return -1; } if (retry == 0) g_warning(_("mailbox is owned by another" " process, waiting...\n")); retry++; sleep(5); } unlink(lockfile); g_free(lockfile); return TRUE; } static gboolean mbox_fcntl_lockwrite_file(FILE * fp) { struct flock lck; lck.l_type = F_WRLCK; lck.l_whence = 0; lck.l_start = 0; lck.l_len = 0; if (fcntl(fileno(fp), F_SETLK, &lck) < 0) return FALSE; else return TRUE; } static gboolean mbox_fcntl_lockread_file(FILE * fp) { struct flock lck; lck.l_type = F_RDLCK; lck.l_whence = 0; lck.l_start = 0; lck.l_len = 0; if (fcntl(fileno(fp), F_SETLK, &lck) < 0) return FALSE; else return TRUE; } static gboolean mbox_fcntl_unlock_file(FILE * fp) { struct flock lck; lck.l_type = F_UNLCK; lck.l_whence = 0; lck.l_start = 0; lck.l_len = 0; if (fcntl(fileno(fp), F_SETLK, &lck) < 0) return FALSE; else return TRUE; } static gboolean mbox_file_unlock_file(gchar * base) { gchar *lockfile; lockfile = g_strdup_printf("%s.lock", base); unlink(lockfile); g_free(lockfile); return TRUE; } static gboolean mbox_lockread_file(FILE * fp, gchar * base) { gboolean result; result = mbox_fcntl_lockread_file(fp); if (!result) { if ((result = mbox_file_lock_file(base)) == TRUE) { file_lock = g_slist_append(file_lock, g_strdup(base)); debug_print("lockfile lock %s.\n", base); } else g_warning(_("could not lock read file %s\n"), base); } else debug_print("fcntl lock %s.\n", base); return result; } static gboolean mbox_lockwrite_file(FILE * fp, gchar * base) { gboolean result; result = mbox_fcntl_lockwrite_file(fp); if (!result) { if ((result = mbox_file_lock_file(base)) == TRUE) { file_lock = g_slist_append(file_lock, g_strdup(base)); debug_print("lockfile lock %s.\n", base); } else g_warning(_("could not lock write file %s\n"), base); } else debug_print("fcntl lock %s.\n", base); return result; } static gboolean mbox_unlock_file(FILE * fp, gchar * base) { gboolean result = FALSE; GSList * l; gboolean unlocked = FALSE; for(l = file_lock ; l != NULL ; l = g_slist_next(l)) { gchar * data = l->data; if (strcmp(data, base) == 0) { file_lock = g_slist_remove(file_lock, data); g_free(data); result = mbox_file_unlock_file(base); unlocked = TRUE; debug_print("lockfile unlock - %s.\n", base); break; } } if (!unlocked) { result = mbox_fcntl_unlock_file(fp); debug_print("fcntl unlock - %s.\n", base); } return result; } /**********************************************************/ /* */ /* mbox parsing */ /* */ /**********************************************************/ #define MAILFILE_ERROR_NO_ERROR 0x000 #define MAILFILE_ERROR_FILE_NOT_FOUND 0x001 #define MAILFILE_ERROR_MEMORY 0x002 #define MAILFILE_ERROR_MESSAGE_NOT_FOUND 0x003 static int mailfile_error = MAILFILE_ERROR_NO_ERROR; #define STATE_BEGIN 0x000 #define STATE_TEXT_READ 0x001 #define STATE_FROM_READ 0x002 #define STATE_FIELD_READ 0x003 #define STATE_END 0x004 #define STATE_END_OF_FILE 0x005 #define STATE_MEM_ERROR 0x006 #define STATE_TEXT_BEGIN 0x007 #define STATE_MASK 0x0FF /* filter state from functions */ #define STATE_RESTORE_POS 0x100 /* go back while reading */ typedef struct _mailfile mailfile; struct _mailfile { gint count; gchar * filename; GList * msg_list; }; struct _message { int msgnum; int offset; int header; int content; int end; int marked; gchar * messageid; gchar * fromspace; MsgFlags flags; MsgFlags old_flags; gboolean fetched; }; #define MSG_IS_INVALID(msg) \ ((msg).perm_flags == (msg).tmp_flags && (msg).tmp_flags == -1) #define MSG_SET_INVALID(msg) \ ((msg).perm_flags = (msg).tmp_flags = -1) static int startFrom(char * s) { return (strncmp(s, "From ", 5) == 0); } static int startSpace(char * s) { return ((*s == ' ') || (*s == '\t')); } static int startEmpty(char * s) { return (*s == '\n'); } static void free_msg_list(GList * l) { GList * elt = g_list_first(l); while (elt) { g_free(elt->data); elt = g_list_next(elt); } g_list_free(l); } static mailfile * mailfile_init_from_file(FILE * f, gchar * filename) { int state; GList * msg_list = NULL; char * r = NULL; char s[256]; int lastpos = 0; int former_pos = 0; int ignore_next = 0; int msgnum = 0; struct _message * data = NULL; mailfile * mf; state = STATE_BEGIN; while (state != STATE_END_OF_FILE) { if ((state & STATE_RESTORE_POS) == 0) { former_pos = lastpos; lastpos = ftell(f); r = fgets(s, 256, f); if (r != NULL && *r) ignore_next = (s[strlen(s) - 1] != '\n'); else ignore_next = 0; } switch(state & 0x0F) { case STATE_BEGIN: if (r == NULL) state = STATE_END_OF_FILE; else if (startFrom(s)) { state = STATE_FROM_READ; data = g_new0(struct _message, 1); if (data == NULL) { free_msg_list(msg_list); return NULL; } msgnum ++; data->msgnum = msgnum; data->offset = lastpos; data->header = lastpos; data->end = 0; data->content = 0; data->messageid = NULL; data->fromspace = NULL; MSG_SET_INVALID(data->flags); MSG_SET_INVALID(data->old_flags); data->fetched = FALSE; msg_list = g_list_append(msg_list, (gpointer) data); } else state = STATE_BEGIN; break; case STATE_TEXT_READ: if (r == NULL) state = STATE_END; else if (startFrom(s)) state = STATE_END | STATE_RESTORE_POS; else state = STATE_TEXT_READ; break; case STATE_TEXT_BEGIN: data->content = lastpos; if (r == NULL) state = STATE_END; else if (startFrom(s)) { state = STATE_END | STATE_RESTORE_POS; } else { state = STATE_TEXT_READ; } break; case STATE_FROM_READ: data->content = lastpos; if (r == NULL) state = STATE_END; else if (startSpace(s)) state = STATE_FROM_READ; else if (startEmpty(s)) state = STATE_TEXT_READ; else state = STATE_FIELD_READ; break; case STATE_FIELD_READ: data->content = lastpos; if (r == NULL) state = STATE_END; else if (startSpace(s)) state = STATE_FIELD_READ; else if (startEmpty(s)) { state = STATE_TEXT_BEGIN; } else state = STATE_FIELD_READ; break; } if ((state & STATE_MASK) == STATE_END) { state = STATE_BEGIN | (state & STATE_RESTORE_POS); data->end = lastpos; } if (ignore_next) { do { r = fgets(s, 256, f); if (r == NULL || *r == '\0') break; } while (s[strlen(s) - 1] != '\n'); } } mf = (mailfile *) g_new0(struct _mailfile, 1); if (mf == NULL) { free_msg_list(msg_list); mailfile_error = MAILFILE_ERROR_MEMORY; return NULL; } mf->msg_list = g_list_first(msg_list); mf->filename = g_strdup(filename); mf->count = msgnum; mailfile_error = MAILFILE_ERROR_NO_ERROR; return mf; } static mailfile * mailfile_init(char * filename) { FILE * f; mailfile * mf; f = fopen(filename, "r"); if (f == NULL) { mailfile_error = MAILFILE_ERROR_FILE_NOT_FOUND; return NULL; } mbox_lockread_file(f, filename); mf = mailfile_init_from_file(f, filename); mbox_unlock_file(f, filename); fclose(f); return mf; } static void mailfile_done(mailfile * f) { free_msg_list(f->msg_list); g_free(f->filename); g_free(f); } /* #define MAX_READ 4096 static char * readfile(char * filename, int offset, int max_offset) { char * message; int size; int pos; int max; int bread; FILE * handle; handle = fopen(filename, "r"); if (handle == NULL) { mailfile_error = MAILFILE_ERROR_FILE_NOT_FOUND; return NULL; } size = max_offset - offset; message = (char *) malloc(size + 1); if (message == NULL) { fclose(handle); mailfile_error = MAILFILE_ERROR_MEMORY; return NULL; } fseek(handle, offset, SEEK_SET); pos = 0; while (pos < size) { if ((size - pos) > MAX_READ) max = MAX_READ; else max = (size - pos); bread = fread(message + pos, 1, max, handle); if (bread != -1) pos += bread; if (bread < max) break; } message[pos] = 0; fclose(handle); return message; } static char * mailfile_readmsg(mailfile f, int index) { GList * nth; int max_offset; int offset; char * message; struct _message * msginfo; nth = g_list_nth(f->msg_list, index); if (!nth) { mailfile_error = MAILFILE_ERROR_MESSAGE_NOT_FOUND; return NULL; } msginfo = (struct _message *)nth->data; offset = msginfo->offset; max_offset = msginfo->end; message = readfile(f->filename, offset, max_offset); mailfile_error = MAILFILE_ERROR_NO_ERROR; return message; } static char * mailfile_readheader(mailfile f, int index) { GList * nth; int max_offset; int offset; char * message; struct _message * msginfo; nth = g_list_nth(f->msg_list, index); if (!nth) { mailfile_error = MAILFILE_ERROR_MESSAGE_NOT_FOUND; return NULL; } msginfo = (struct _message *)nth->data; offset = msginfo->offset; max_offset = msginfo->content; message = readfile(f->filename, offset, max_offset); mailfile_error = MAILFILE_ERROR_NO_ERROR; return message; } static int mailfile_count(mailfile * f) { return g_list_length(f->msg_list); } static int mailfile_find_deleted(mailfile f, char * filename) { FILE * handle; handle = fopen(filename, "r"); while (elt) { struct _message m = elt->data; n = fread(&m.deleted, sizeof(int), 1, handle); if (!n) break; elt = g_list_next(elt); } fclose(handle); } */ /**********************************************************/ /* */ /* mbox cache operations */ /* */ /**********************************************************/ struct _mboxcache { gchar * filename; mailfile * mf; GPtrArray * tab_mf; gint mtime; gboolean modification; }; typedef struct _mboxcache mboxcache; static GHashTable * mbox_cache_table = NULL; static MsgInfo *mbox_parse_msg(FILE * fp, struct _message * msg, FolderItem *item) { MsgInfo *msginfo; MsgFlags flags = { 0, 0 }; MSG_SET_PERM_FLAGS(flags, MSG_NEW | MSG_UNREAD); g_return_val_if_fail(fp != NULL, NULL); if (item != NULL) { if (item->stype == F_QUEUE) { MSG_SET_TMP_FLAGS(flags, MSG_QUEUED); } else if (item->stype == F_DRAFT) { MSG_SET_TMP_FLAGS(flags, MSG_DRAFT); } } msginfo = procheader_file_parse(fp, flags, FALSE); if (!msginfo) return NULL; if (item != NULL) { msginfo->msgnum = msg->msgnum; msginfo->folder = item; } return msginfo; } static void mbox_cache_init() { mbox_cache_table = g_hash_table_new(g_str_hash, g_str_equal); } static void mbox_cache_done() { g_hash_table_destroy(mbox_cache_table); } static void mbox_cache_free_mbox(mboxcache * cache) { g_hash_table_remove(mbox_cache_table, cache->filename); if (cache->mf) mailfile_done(cache->mf); if (cache->tab_mf) g_ptr_array_free(cache->tab_mf, FALSE); if (cache->filename) g_free(cache->filename); g_free(cache); } static void mbox_cache_get_msginfo_from_file(FILE * fp, GList * msg_list) { GList * l; MsgInfo * msginfo; for(l = msg_list ; l != NULL ; l = g_list_next(l)) { struct _message * msg; msg = (struct _message *) l->data; fseek(fp, msg->header, SEEK_SET); msginfo = mbox_parse_msg(fp, msg, NULL); if (msginfo) { if (msginfo->msgid) msg->messageid = g_strdup(msginfo->msgid); if (msginfo->fromspace) msg->fromspace = g_strdup(msginfo->fromspace); msg->flags = msginfo->flags; msg->old_flags = msginfo->flags; procmsg_msginfo_free(msginfo); } } } static void mbox_cache_get_msginfo(gchar * filename, GList * msg_list) { FILE * fp; fp = fopen(filename, "r"); if (fp == NULL) return; mbox_cache_get_msginfo_from_file(fp, msg_list); fclose(fp); } static mboxcache * mbox_cache_read_mbox(gchar * filename) { mboxcache * cache; struct stat s; mailfile * mf; GList * l; if (stat(filename, &s) < 0) return NULL; mf = mailfile_init(filename); if (mf == NULL) return NULL; cache = g_new0(mboxcache, 1); cache->mtime = s.st_mtime; cache->mf = mf; cache->filename = g_strdup(filename); cache->modification = FALSE; cache->tab_mf = g_ptr_array_new(); for(l = mf->msg_list ; l != NULL ; l = g_list_next(l)) g_ptr_array_add(cache->tab_mf, l->data); mbox_cache_get_msginfo(filename, mf->msg_list); debug_print(_("read mbox - %s\n"), filename); return cache; } static mboxcache * mbox_cache_read_mbox_from_file(FILE * fp, gchar * filename) { mboxcache * cache; struct stat s; mailfile * mf; GList * l; if (stat(filename, &s) < 0) return NULL; mf = mailfile_init_from_file(fp, filename); if (mf == NULL) return NULL; cache = g_new0(mboxcache, 1); cache->mtime = s.st_mtime; cache->mf = mf; cache->filename = g_strdup(filename); cache->tab_mf = g_ptr_array_new(); for(l = mf->msg_list ; l != NULL ; l = g_list_next(l)) g_ptr_array_add(cache->tab_mf, l->data); mbox_cache_get_msginfo_from_file(fp, mf->msg_list); debug_print(_("read mbox from file - %s\n"), filename); return cache; } static void mbox_cache_insert_mbox(mboxcache * data) { if (mbox_cache_table == NULL) mbox_cache_init(); g_hash_table_insert(mbox_cache_table, data->filename, data); } static mboxcache * mbox_cache_get_mbox(gchar * filename) { if (mbox_cache_table == NULL) mbox_cache_init(); return g_hash_table_lookup(mbox_cache_table, filename); } static gint mbox_cache_get_count(gchar * filename) { mboxcache * cache; cache = mbox_cache_get_mbox(filename); if (cache == NULL) return -1; if (cache->mf == NULL) return -1; return cache->mf->count; } static gint mbox_cache_get_mtime(gchar * filename) { mboxcache * cache; cache = mbox_cache_get_mbox(filename); if (cache == NULL) return -1; return cache->mtime; } static GList * mbox_cache_get_msg_list(gchar * filename) { mboxcache * cache; cache = mbox_cache_get_mbox(filename); if (cache == NULL) return NULL; if (cache->mf == NULL) return NULL; return cache->mf->msg_list; } static void mbox_cache_synchronize_lists(GList * old_msg_list, GList * new_msg_list) { GList * l; GList * l2; for(l2 = old_msg_list ; l2 != NULL ; l2 = g_list_next(l2)) { struct _message * msg2 = l2->data; if ((msg2->messageid == NULL) || (msg2->fromspace == NULL)) continue; for(l = new_msg_list ; l != NULL ; l = g_list_next(l)) { struct _message * msg = l->data; if ((msg->messageid == NULL) || (msg->fromspace == NULL)) continue; if ((strcmp(msg->messageid, msg2->messageid) == 0) && (strcmp(msg->fromspace, msg2->fromspace) == 0)) { if (msg2->flags.perm_flags != msg2->old_flags.perm_flags) { msg->flags = msg2->flags; break; } } } } } static void mbox_cache_synchronize(gchar * filename, gboolean sync) { mboxcache * new_cache; mboxcache * old_cache; gboolean scan_new = TRUE; struct stat s; old_cache = mbox_cache_get_mbox(filename); if (old_cache != NULL) { if (stat(filename, &s) < 0) { FILE_OP_ERROR(filename, "stat"); } else if (old_cache->mtime == s.st_mtime) { debug_print("Folder is not modified.\n"); scan_new = FALSE; } } if (scan_new) { GList * l; /* if (strstr(filename, "trash") == 0) printf("old_cache: %p %s\n", old_cache, filename); if (old_cache) { printf("begin old\n"); for(l = old_cache->mf->msg_list ; l != NULL ; l = g_list_next(l)) { struct _message * msg = l->data; printf("%p\n", msg); } printf("end old\n"); } */ new_cache = mbox_cache_read_mbox(filename); /* if (strstr(filename, "trash") == 0) printf("new_cache: %p %s\n", new_cache, filename); if (new_cache) { printf("begin new\n"); for(l = new_cache->mf->msg_list ; l != NULL ; l = g_list_next(l)) { struct _message * msg = l->data; printf("%p\n", msg); } printf("end new\n"); } */ if (!new_cache) return; if (sync && new_cache && old_cache) mbox_cache_synchronize_lists(old_cache->mf->msg_list, new_cache->mf->msg_list); if (old_cache != NULL) mbox_cache_free_mbox(old_cache); if (new_cache) { mbox_cache_insert_mbox(new_cache); /* printf("insert %p %s\n", new_cache, new_cache->filename); printf("inserted %s %p\n", filename, mbox_cache_get_mbox(filename)); */ } } } static void mbox_cache_synchronize_from_file(FILE * fp, gchar * filename, gboolean sync) { mboxcache * new_cache; mboxcache * old_cache; gboolean scan_new = TRUE; struct stat s; old_cache = mbox_cache_get_mbox(filename); if (old_cache != NULL) { if (stat(filename, &s) < 0) { FILE_OP_ERROR(filename, "stat"); } else if (old_cache->mtime == s.st_mtime) { debug_print("Folder is not modified.\n"); scan_new = FALSE; } } if (scan_new) { /* GList * l; if (strstr(filename, "trash") == 0) printf("old_cache: %p %s\n", old_cache, filename); if (old_cache) { printf("begin old\n"); for(l = old_cache->mf->msg_list ; l != NULL ; l = g_list_next(l)) { struct _message * msg = l->data; printf("%p\n", msg); } printf("end old\n"); } */ new_cache = mbox_cache_read_mbox_from_file(fp, filename); /* if (strstr(filename, "trash") == 0) printf("new_cache: %p %s\n", new_cache, filename); if (new_cache) { printf("begin new\n"); for(l = new_cache->mf->msg_list ; l != NULL ; l = g_list_next(l)) { struct _message * msg = l->data; printf("%p\n", msg); } printf("end new\n"); } */ if (!new_cache) return; if (sync && new_cache && old_cache) mbox_cache_synchronize_lists(old_cache->mf->msg_list, new_cache->mf->msg_list); if (old_cache != NULL) mbox_cache_free_mbox(old_cache); if (new_cache) { mbox_cache_insert_mbox(new_cache); /* printf("insert %p %s\n", new_cache, new_cache->filename); printf("inserted %s %p\n", filename, mbox_cache_get_mbox(filename)); */ } } } gboolean mbox_cache_msg_fetched(gchar * filename, gint num) { struct _message * msg; mboxcache * cache; cache = mbox_cache_get_mbox(filename); if (cache == NULL) return FALSE; msg = (struct _message *) g_ptr_array_index(cache->tab_mf, num - 1); if (msg == NULL) return FALSE; return msg->fetched; } void mbox_cache_msg_set_fetched(gchar * filename, gint num) { struct _message * msg; mboxcache * cache; cache = mbox_cache_get_mbox(filename); if (cache == NULL) return; msg = (struct _message *) g_ptr_array_index(cache->tab_mf, num - 1); if (msg == NULL) return; msg->fetched = TRUE; } struct _message * mbox_cache_get_msg(gchar * filename, gint num) { mboxcache * cache; cache = mbox_cache_get_mbox(filename); if (cache == NULL) { return NULL; } return (struct _message *) g_ptr_array_index(cache->tab_mf, num - 1); } /**********************************************************/ /* */ /* mbox operations */ /* */ /**********************************************************/ GSList *mbox_get_msg_list(Folder *folder, FolderItem *item, gboolean use_cache) { GSList *mlist; MsgInfo * msginfo; GList * l; FILE * fp; gchar * mbox_path; #ifdef MEASURE_TIME struct timeval tv_before, tv_after, tv_result; gettimeofday(&tv_before, NULL); #endif mlist = NULL; mbox_path = mbox_folder_get_path(item); if (mbox_path == NULL) return NULL; mbox_purge_deleted(mbox_path); fp = fopen(mbox_path, "r"); if (fp == NULL) { g_free(mbox_path); return NULL; } mbox_lockread_file(fp, mbox_path); mbox_cache_synchronize_from_file(fp, mbox_path, TRUE); item->last_num = mbox_cache_get_count(mbox_path); for(l = mbox_cache_get_msg_list(mbox_path) ; l != NULL ; l = g_list_next(l)) { struct _message * msg; msg = (struct _message *) l->data; if (MSG_IS_INVALID(msg->flags) || !MSG_IS_REALLY_DELETED(msg->flags)) { fseek(fp, msg->header, SEEK_SET); msginfo = mbox_parse_msg(fp, msg, item); if (!MSG_IS_INVALID(msg->flags)) msginfo->flags = msg->flags; else { msg->old_flags = msginfo->flags; msg->flags = msginfo->flags; } mlist = g_slist_append(mlist, msginfo); } else { MSG_SET_PERM_FLAGS(msg->flags, MSG_REALLY_DELETED); } } mbox_unlock_file(fp, mbox_path); g_free(mbox_path); fclose(fp); #ifdef MEASURE_TIME gettimeofday(&tv_after, NULL); timersub(&tv_after, &tv_before, &tv_result); g_print("mbox_get_msg_list: %s: elapsed time: %ld.%06ld sec\n", mbox_path, tv_result.tv_sec, tv_result.tv_usec); #endif return mlist; } static gboolean mbox_extract_msg(FolderItem * item, gint msgnum, gchar * dest_filename) { struct _message * msg; gint offset; gint max_offset; gint size; FILE * src; FILE * dest; gboolean err; /* GList * msg_list;*/ gboolean already_fetched; gchar * mbox_path; mbox_path = mbox_folder_get_path(item); if (mbox_path == NULL) return FALSE; src = fopen(mbox_path, "r"); if (src == NULL) { g_free(mbox_path); return FALSE; } mbox_lockread_file(src, mbox_path); mbox_cache_synchronize_from_file(src, mbox_path, TRUE); already_fetched = mbox_cache_msg_fetched(mbox_path, msgnum); if (already_fetched) { mbox_unlock_file(src, mbox_path); fclose(src); g_free(mbox_path); return TRUE; } msg = mbox_cache_get_msg(mbox_path, msgnum); if (msg == NULL) { mbox_unlock_file(src, mbox_path); fclose(src); g_free(mbox_path); return FALSE; } offset = msg->offset; max_offset = msg->end; size = max_offset - offset; fseek(src, offset, SEEK_SET); dest = fopen(dest_filename, "w"); if (dest == NULL) { mbox_unlock_file(src, mbox_path); fclose(src); g_free(mbox_path); return FALSE; } if (change_file_mode_rw(dest, dest_filename) < 0) { FILE_OP_ERROR(dest_filename, "chmod"); g_warning(_("can't change file mode\n")); } if (!mbox_write_data(src, dest, dest_filename, size)) { mbox_unlock_file(src, mbox_path); fclose(dest); fclose(src); unlink(dest_filename); g_free(mbox_path); return FALSE; } err = FALSE; if (ferror(src)) { FILE_OP_ERROR(mbox_path, "fread"); err = TRUE; } mbox_cache_msg_set_fetched(mbox_path, msgnum); if (fclose(dest) == -1) { FILE_OP_ERROR(dest_filename, "fclose"); err = TRUE; } mbox_unlock_file(src, mbox_path); if (fclose(src) == -1) { FILE_OP_ERROR(mbox_path, "fclose"); err = TRUE; } g_free(mbox_path); if (err) { unlink(dest_filename); return FALSE; } return TRUE; } gchar *mbox_fetch_msg(Folder *folder, FolderItem *item, gint num) { gchar *path; gchar *filename; g_return_val_if_fail(item != NULL, NULL); g_return_val_if_fail(num > 0 && num <= item->last_num, NULL); path = folder_item_get_path(item); if (!is_dir_exist(path)) make_dir_hier(path); filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL); g_free(path); if (!mbox_extract_msg(item, num, filename)) { g_free(filename); return NULL; } return filename; } gint mbox_add_msg(Folder *folder, FolderItem *dest, const gchar *file, gboolean remove_source) { FILE * src_fp; FILE * dest_fp; gchar buf[BUFSIZ]; gint old_size; gint n_read; gboolean err; gchar * mbox_path; gchar from_line[MSGBUFSIZE]; if (dest->last_num < 0) { mbox_scan_folder(folder, dest); if (dest->last_num < 0) return -1; } src_fp = fopen(file, "r"); if (src_fp == NULL) { return -1; } mbox_path = mbox_folder_get_path(dest); if (mbox_path == NULL) return -1; dest_fp = fopen(mbox_path, "a"); if (dest_fp == NULL) { fclose(src_fp); g_free(mbox_path); return -1; } if (change_file_mode_rw(dest_fp, mbox_path) < 0) { FILE_OP_ERROR(mbox_path, "chmod"); g_warning(_("can't change file mode\n")); } old_size = ftell(dest_fp); mbox_lockwrite_file(dest_fp, mbox_path); if (fgets(from_line, sizeof(from_line), src_fp) == NULL) { mbox_unlock_file(dest_fp, mbox_path); g_warning(_("unvalid file - %s.\n"), file); fclose(dest_fp); fclose(src_fp); g_free(mbox_path); return -1; } if (strncmp(from_line, "From ", 5) != 0) { struct stat s; if (stat(file, &s) < 0) { mbox_unlock_file(dest_fp, mbox_path); g_warning(_("unvalid file - %s.\n"), file); fclose(dest_fp); fclose(src_fp); g_free(mbox_path); return -1; } fprintf(dest_fp, "From - %s", ctime(&s.st_mtime)); } fputs(from_line, dest_fp); while (1) { n_read = fread(buf, 1, sizeof(buf), src_fp); if ((n_read < (gint) sizeof(buf)) && ferror(src_fp)) break; if (fwrite(buf, n_read, 1, dest_fp) < 1) { mbox_unlock_file(dest_fp, mbox_path); g_warning(_("writing to %s failed.\n"), mbox_path); ftruncate(fileno(dest_fp), old_size); fclose(dest_fp); fclose(src_fp); g_free(mbox_path); return -1; } if (n_read < (gint) sizeof(buf)) break; } err = FALSE; if (ferror(src_fp)) { FILE_OP_ERROR(mbox_path, "fread"); } mbox_unlock_file(dest_fp, mbox_path); if (fclose(src_fp) == -1) { FILE_OP_ERROR(file, "fclose"); err = TRUE; } if (fclose(dest_fp) == -1) { FILE_OP_ERROR(mbox_path, "fclose"); g_free(mbox_path); return -1; } if (err) { ftruncate(fileno(dest_fp), old_size); g_free(mbox_path); return -1; } if (remove_source) { if (unlink(file) < 0) FILE_OP_ERROR(file, "unlink"); } g_free(mbox_path); dest->last_num++; return dest->last_num; } gint mbox_remove_msg(Folder *folder, FolderItem *item, gint num) { struct _message * msg; gchar * mbox_path; mbox_path = mbox_folder_get_path(item); if (mbox_path == NULL) return -1; mbox_cache_synchronize(mbox_path, TRUE); msg = mbox_cache_get_msg(mbox_path, num); g_free(mbox_path); if (msg != NULL) MSG_SET_PERM_FLAGS(msg->flags, MSG_REALLY_DELETED); return 0; } gint mbox_remove_all_msg(Folder *folder, FolderItem *item) { FILE * fp; gchar * mbox_path; mbox_path = mbox_folder_get_path(item); if (mbox_path == NULL) return -1; fp = fopen(mbox_path, "w"); if (fp == NULL) { g_free(mbox_path); return -1; } fclose(fp); g_free(mbox_path); return 0; } /* gint mbox_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo) { gchar * filename; gint msgnum; filename = mbox_fetch_msg(folder, msginfo->folder, msginfo->msgnum); if (filename == NULL) return -1; msgnum = mbox_add_msg(folder, dest, filename, TRUE); if (msgnum != -1) { MSG_SET_FLAGS(msginfo->flags, MSG_REALLY_DELETED); mbox_change_flags(folder, msginfo->folder, msginfo); } return msgnum; } gint mbox_move_msgs_with_dest(Folder *folder, FolderItem *dest, GSList *msglist) { GSList * l; gchar * mbox_path = NULL; for(l = msglist ; l != NULL ; l = g_slist_next(l)) { MsgInfo * msginfo = (MsgInfo *) l->data; if (msginfo->folder && mbox_path == NULL) mbox_path = mbox_folder_get_path(msginfo->folder); mbox_move_msg(folder, dest, msginfo); } if (mbox_path) { mbox_cache_synchronize(mbox_path); g_free(mbox_path); } mbox_path = mbox_folder_get_path(dest); mbox_cache_synchronize(mbox_path); g_free(mbox_path); return dest->last_num; } */ /* gint mbox_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo) { gchar * filename; gint msgnum; filename = mbox_fetch_msg(folder, msginfo->folder, msginfo->msgnum); if (filename == NULL) return -1; msgnum = mbox_add_msg(folder, dest, filename, FALSE); return msgnum; } gint mbox_copy_msgs_with_dest(Folder *folder, FolderItem *dest, GSList *msglist) { GSList * l; gchar * mbox_path = NULL; for(l = msglist ; l != NULL ; l = g_slist_next(l)) { MsgInfo * msginfo = (MsgInfo *) l->data; if (msginfo->folder && mbox_path == NULL) mbox_path = mbox_folder_get_path(msginfo->folder); mbox_copy_msg(folder, dest, msginfo); } if (mbox_path) { mbox_cache_synchronize(mbox_path); g_free(mbox_path); } mbox_path = mbox_folder_get_path(dest); mbox_cache_synchronize(mbox_path); g_free(mbox_path); return dest->last_num; } */ struct _copy_flags_info { gint num; MsgFlags flags; }; typedef struct _copy_flags_info CopyFlagsInfo; GSList * copy_flags_data = NULL; gint mbox_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo) { Folder * src_folder; gchar * filename; gint num; gchar * destdir; gchar * mbox_path; struct _message * msg; CopyFlagsInfo * flags_info; src_folder = msginfo->folder->folder; g_return_val_if_fail(src_folder->fetch_msg != NULL, -1); /* mbox_path = mbox_folder_get_path(msginfo->folder); mbox_rewrite(mbox_path); g_free(mbox_path); */ filename = src_folder->fetch_msg(src_folder, msginfo->folder, msginfo->msgnum); if (filename == NULL) return -1; num = folder->add_msg(folder, dest, filename, FALSE); /* mbox_path = mbox_folder_get_path(dest); msg = mbox_cache_get_msg(mbox_path, num); if (msg != NULL) msg->flags = msginfo->flags; g_free(mbox_path); */ if (num == -1) return -1; flags_info = g_new0(CopyFlagsInfo, 1); flags_info->num = num; flags_info->flags = msginfo->flags; copy_flags_data = g_slist_append(copy_flags_data, flags_info); return num; } void mbox_finished_copy(Folder *folder, FolderItem *dest) { gchar * mbox_path; GSList * l; mboxcache * cache; mbox_path = mbox_folder_get_path(dest); if (mbox_path == NULL) return; mbox_cache_synchronize(mbox_path, TRUE); for(l = copy_flags_data ; l != NULL ; l = g_slist_next(l)) { CopyFlagsInfo * flags_info = l->data; struct _message * msg; msg = mbox_cache_get_msg(mbox_path, flags_info->num); if (msg != NULL) msg->flags = flags_info->flags; g_free(flags_info); } if (copy_flags_data != NULL) { cache = mbox_cache_get_mbox(mbox_path); cache->modification = TRUE; } g_slist_free(copy_flags_data); copy_flags_data = NULL; mbox_rewrite(mbox_path); g_free(mbox_path); } void mbox_scan_folder(Folder *folder, FolderItem *item) { gchar *mbox_path; gint n_msg; mboxcache * cached; GList * l; mbox_path = mbox_folder_get_path(item); if (mbox_path == NULL) return; mbox_cache_synchronize(mbox_path, TRUE); cached = mbox_cache_get_mbox(mbox_path); if (cached == NULL) { item->new = 0; item->unread = 0; item->total = 0; item->last_num = 0; g_free(mbox_path); return; } n_msg = mbox_cache_get_count(mbox_path); if (n_msg == 0) { item->new = item->unread = item->total = 0; } else { gint new = 0; gint unread = 0; gint total = 0; for(l = mbox_cache_get_msg_list(mbox_path) ; l != NULL ; l = g_list_next(l)) { struct _message * msg = (struct _message *) l->data; if (!MSG_IS_REALLY_DELETED(msg->flags)) total ++; if (MSG_IS_NEW(msg->flags) && !MSG_IS_IGNORE_THREAD(msg->flags)) new ++; if (MSG_IS_UNREAD(msg->flags) && !MSG_IS_IGNORE_THREAD(msg->flags)) unread ++; } item->new = new; item->unread = unread; item->total = total; } debug_print(_("Last number in dir %s = %d\n"), mbox_path, item->total); item->last_num = n_msg; g_free(mbox_path); } gchar * mbox_get_virtual_path(FolderItem * item) { if (item == NULL) return NULL; if (item->parent == NULL) { return NULL; } else { gchar * parent_path; gchar * result_path; parent_path = mbox_get_virtual_path(item->parent); if (parent_path == NULL) result_path = g_strdup(item->name); else result_path = g_strconcat(parent_path, G_DIR_SEPARATOR_S, item->name, NULL); g_free(parent_path); return result_path; } } static gboolean mbox_write_data(FILE * mbox_fp, FILE * new_fp, gchar * new_filename, gint size) { gint n_read; gint pos; gchar buf[BUFSIZ]; gint max; pos = 0; while (pos < size) { if ((size - pos) > (gint) sizeof(buf)) max = sizeof(buf); else max = (size - pos); n_read = fread(buf, 1, max, mbox_fp); if (n_read < max && ferror(mbox_fp)) { return FALSE; } if (fwrite(buf, n_read, 1, new_fp) < 1) { g_warning(_("writing to %s failed.\n"), new_filename); return FALSE; } if (n_read != -1) pos += n_read; if (n_read < max) break; } return TRUE; } static gboolean mbox_write_message(FILE * mbox_fp, FILE * new_fp, gchar * new_filename, struct _message * msg) { gint size; GPtrArray * headers; gint i; fseek(mbox_fp, msg->header, SEEK_SET); headers = procheader_get_header_array_asis(mbox_fp); for (i = 0; i < (gint) headers->len; i++) { Header * h = g_ptr_array_index(headers, i); if (!procheader_headername_equal(h->name, "Status") && !procheader_headername_equal(h->name, "X-Status")) { fwrite(h->name, strlen(h->name), 1, new_fp); if (h->name[strlen(h->name) - 1] != ' ') fwrite(" ", 1, 1, new_fp); fwrite(h->body, strlen(h->body), 1, new_fp); fwrite("\n", 1, 1, new_fp); } procheader_header_free(h); g_ptr_array_remove_index(headers, i); i--; } g_ptr_array_free(headers, FALSE); if (!MSG_IS_INVALID(msg->flags)) { /* Status header */ fwrite("Status: ", strlen("Status: "), 1, new_fp); if (!MSG_IS_UNREAD(msg->flags)) fwrite("R", 1, 1, new_fp); fwrite("O", 1, 1, new_fp); fwrite("\n", 1, 1, new_fp); /* X-Status header */ if (MSG_IS_REALLY_DELETED(msg->flags) || MSG_IS_MARKED(msg->flags) || MSG_IS_DELETED(msg->flags) || MSG_IS_REPLIED(msg->flags) || MSG_IS_FORWARDED(msg->flags)) { fwrite("X-Status: ", strlen("X-Status: "), 1, new_fp); if (MSG_IS_REALLY_DELETED(msg->flags)) fwrite("D", 1, 1, new_fp); /* really deleted */ else { if (MSG_IS_MARKED(msg->flags)) fwrite("F", 1, 1, new_fp); if (MSG_IS_DELETED(msg->flags)) fwrite("d", 1, 1, new_fp); if (MSG_IS_REPLIED(msg->flags)) fwrite("r", 1, 1, new_fp); if (MSG_IS_FORWARDED(msg->flags)) fwrite("f", 1, 1, new_fp); } fwrite("\n", 1, 1, new_fp); } } fwrite("\n", 1, 1, new_fp); size = msg->end - msg->content; fseek(mbox_fp, msg->content, SEEK_SET); return mbox_write_data(mbox_fp, new_fp, new_filename, size); } void mbox_update_mark(Folder * folder, FolderItem * item) { gchar * mbox_path; mbox_path = mbox_folder_get_path(item); if (mbox_path == NULL) return; mbox_rewrite(mbox_path); g_free(mbox_path); } void mbox_change_flags(Folder * folder, FolderItem * item, MsgInfo * info) { struct _message * msg; mboxcache * cache; gchar * mbox_path; mbox_path = mbox_folder_get_path(item); if (mbox_path == NULL) return; msg = mbox_cache_get_msg(mbox_path, info->msgnum); cache = mbox_cache_get_mbox(mbox_path); g_free(mbox_path); if ((msg == NULL) || (cache == NULL)) return; msg->flags = info->flags; cache->modification = TRUE; } static gboolean mbox_rewrite(gchar * mbox) { FILE * mbox_fp; FILE * new_fp; gchar * new; GList * l; gboolean result; GList * msg_list; gint count; mboxcache * cache; msg_list = mbox_cache_get_msg_list(mbox); cache = mbox_cache_get_mbox(mbox); if (cache == NULL) return FALSE; if (!cache->modification) { debug_print(_("no modification - %s\n"), mbox); return FALSE; } debug_print(_("save modification - %s\n"), mbox); mbox_fp = fopen(mbox, "r+"); mbox_lockwrite_file(mbox_fp, mbox); mbox_cache_synchronize_from_file(mbox_fp, mbox, TRUE); new = g_strconcat(mbox, ".", itos((int) mbox), NULL); new_fp = fopen(new, "w"); if (change_file_mode_rw(new_fp, new) < 0) { FILE_OP_ERROR(new, "chmod"); g_warning(_("can't change file mode\n")); } mbox_lockwrite_file(new_fp, new); result = TRUE; count = 0; msg_list = mbox_cache_get_msg_list(mbox); for(l = msg_list ; l != NULL ; l = g_list_next(l)) { struct _message * msg = (struct _message *) l->data; if (!mbox_write_message(mbox_fp, new_fp, new, msg)) { result = FALSE; break; } count ++; } unlink(mbox); if (rename(new, mbox) == -1) { g_warning(_("can't rename %s to %s\n"), new, mbox); mbox_unlock_file(new_fp, new); fclose(new_fp); mbox_unlock_file(mbox_fp, mbox); fclose(mbox_fp); return -1; } if (change_file_mode_rw(new_fp, mbox) < 0) { FILE_OP_ERROR(new, "chmod"); g_warning(_("can't change file mode\n")); } mbox_unlock_file(new_fp, new); fclose(new_fp); mbox_unlock_file(mbox_fp, mbox); fclose(mbox_fp); debug_print(_("%i messages written - %s\n"), count, mbox); cache = mbox_cache_get_mbox(mbox); if (cache != NULL) cache->mtime = -1; mbox_cache_synchronize(mbox, FALSE); return result; } static gboolean mbox_purge_deleted(gchar * mbox) { FILE * mbox_fp; FILE * new_fp; gchar * new; GList * l; gboolean result; gboolean modification = FALSE; GList * msg_list; gint count; mbox_cache_synchronize(mbox, TRUE); msg_list = mbox_cache_get_msg_list(mbox); for(l = msg_list ; l != NULL ; l = g_list_next(l)) { struct _message * msg = (struct _message *) l->data; if (MSG_IS_INVALID(msg->flags) && MSG_IS_REALLY_DELETED(msg->flags)) { modification = TRUE; break; } } if (!modification) { debug_print(_("no deleted messages - %s\n"), mbox); return FALSE; } debug_print(_("purge deleted messages - %s\n"), mbox); mbox_fp = fopen(mbox, "r+"); mbox_lockwrite_file(mbox_fp, mbox); mbox_cache_synchronize_from_file(mbox_fp, mbox, TRUE); new = g_strconcat(mbox, ".", itos((int) mbox), NULL); new_fp = fopen(new, "w"); if (change_file_mode_rw(new_fp, new) < 0) { FILE_OP_ERROR(new, "chmod"); g_warning(_("can't change file mode\n")); } mbox_lockwrite_file(new_fp, new); result = TRUE; count = 0; msg_list = mbox_cache_get_msg_list(mbox); for(l = msg_list ; l != NULL ; l = g_list_next(l)) { struct _message * msg = (struct _message *) l->data; if (MSG_IS_INVALID(msg->flags) || !MSG_IS_REALLY_DELETED(msg->flags)) { if (!mbox_write_message(mbox_fp, new_fp, new, msg)) { result = FALSE; break; } count ++; } } unlink(mbox); if (rename(new, mbox) == -1) { g_warning(_("can't rename %s to %s\n"), new, mbox); mbox_unlock_file(new_fp, new); fclose(new_fp); mbox_unlock_file(mbox_fp, mbox); fclose(mbox_fp); return -1; } if (change_file_mode_rw(new_fp, mbox) < 0) { FILE_OP_ERROR(new, "chmod"); g_warning(_("can't change file mode\n")); } mbox_unlock_file(new_fp, new); fclose(new_fp); mbox_unlock_file(mbox_fp, mbox); fclose(mbox_fp); debug_print(_("%i messages written - %s\n"), count, mbox); mbox_cache_synchronize(mbox, FALSE); return result; } #define MAKE_DIR_IF_NOT_EXIST(dir) \ { \ if (!is_dir_exist(dir)) { \ if (is_file_exist(dir)) { \ g_warning(_("File `%s' already exists.\n" \ "Can't create folder."), dir); \ return -1; \ } \ if (mkdir(dir, S_IRWXU) < 0) { \ FILE_OP_ERROR(dir, "mkdir"); \ return -1; \ } \ if (chmod(dir, S_IRWXU) < 0) \ FILE_OP_ERROR(dir, "chmod"); \ } \ } gint mbox_create_tree(Folder *folder) { gchar *rootpath; g_return_val_if_fail(folder != NULL, -1); CHDIR_RETURN_VAL_IF_FAIL(get_home_dir(), -1); rootpath = LOCAL_FOLDER(folder)->rootpath; MAKE_DIR_IF_NOT_EXIST(rootpath); CHDIR_RETURN_VAL_IF_FAIL(rootpath, -1); return 0; } #undef MAKE_DIR_IF_NOT_EXIST static gchar * mbox_get_new_path(FolderItem * parent, gchar * name) { gchar * path; if (strchr(name, '/') == NULL) { if (parent->path != NULL) path = g_strconcat(parent->path, ".sbd", G_DIR_SEPARATOR_S, name, NULL); else path = g_strdup(name); } else path = g_strdup(name); return path; } static gchar * mbox_get_folderitem_name(gchar * name) { gchar * foldername; foldername = g_strdup(g_basename(name)); return foldername; } FolderItem *mbox_create_folder(Folder *folder, FolderItem *parent, const gchar *name) { gchar * path; FolderItem *new_item; gchar * foldername; g_return_val_if_fail(folder != NULL, NULL); g_return_val_if_fail(parent != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); path = mbox_get_new_path(parent, (gchar *) name); foldername = mbox_get_folderitem_name((gchar *) name); new_item = folder_item_new(foldername, path); folder_item_append(parent, new_item); if (!strcmp(name, "inbox")) { new_item->stype = F_INBOX; new_item->folder->inbox = new_item; } else if (!strcmp(name, "outbox")) { new_item->stype = F_OUTBOX; new_item->folder->outbox = new_item; } else if (!strcmp(name, "draft")) { new_item->stype = F_DRAFT; new_item->folder->draft = new_item; } else if (!strcmp(name, "queue")) { new_item->stype = F_QUEUE; new_item->folder->queue = new_item; } else if (!strcmp(name, "trash")) { new_item->stype = F_TRASH; new_item->folder->trash = new_item; } g_free(foldername); g_free(path); return new_item; } gint mbox_rename_folder(Folder *folder, FolderItem *item, const gchar *name) { gchar * path; gchar * foldername; g_return_val_if_fail(folder != NULL, -1); g_return_val_if_fail(item != NULL, -1); g_return_val_if_fail(item->path != NULL, -1); g_return_val_if_fail(name != NULL, -1); path = mbox_get_new_path(item->parent, (gchar *) name); foldername = mbox_get_folderitem_name((gchar *) name); if (rename(item->path, path) == -1) { g_free(foldername); g_free(path); g_warning(_("Cannot rename folder item")); return -1; } else { g_free(item->name); g_free(item->path); item->path = path; item->name = foldername; return 0; } } gint mbox_remove_folder(Folder *folder, FolderItem *item) { g_return_val_if_fail(folder != NULL, -1); g_return_val_if_fail(item != NULL, -1); g_return_val_if_fail(item->path != NULL, -1); folder_item_remove(item); return 0; }