claws-mail/src/msgcache.c
Colin Leroy fdbfac5415 2004-07-13 [colin] 0.9.12cvs18.3
* src/pop.c
	* src/pop.h
	* src/messageview.c
		Refactor API (to avoid passing 5+ params and code
		duplication)
	* src/folder.c
	* src/msgcache.c
	* src/procheader.c
	* src/common/defs.h
		Add planned_download to cache
		Don't mess uidl file when moving partially downloaded
		mails (they change folder, we should change it in the
		uidl list too or we'll remove an incorrect message)
		(this also helps removing hacks when changing
		 planned_download in messageview.c)
2004-07-13 11:55:36 +00:00

530 lines
13 KiB
C

/*
* Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
* Copyright (C) 1999-2001 Hiroyuki Yamamoto
*
* 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 "defs.h"
#include <glib.h>
#include <time.h>
#include "intl.h"
#include "msgcache.h"
#include "utils.h"
#include "procmsg.h"
typedef enum
{
DATA_READ,
DATA_WRITE,
DATA_APPEND
} DataOpenMode;
struct _MsgCache {
GHashTable *msgnum_table;
GHashTable *msgid_table;
guint memusage;
time_t last_access;
};
MsgCache *msgcache_new(void)
{
MsgCache *cache;
cache = g_new0(MsgCache, 1),
cache->msgnum_table = g_hash_table_new(g_int_hash, g_int_equal);
cache->msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
cache->last_access = time(NULL);
return cache;
}
static gboolean msgcache_msginfo_free_func(gpointer num, gpointer msginfo, gpointer user_data)
{
procmsg_msginfo_free((MsgInfo *)msginfo);
return TRUE;
}
void msgcache_destroy(MsgCache *cache)
{
g_return_if_fail(cache != NULL);
g_hash_table_foreach_remove(cache->msgnum_table, msgcache_msginfo_free_func, NULL);
g_hash_table_destroy(cache->msgid_table);
g_hash_table_destroy(cache->msgnum_table);
g_free(cache);
}
void msgcache_add_msg(MsgCache *cache, MsgInfo *msginfo)
{
MsgInfo *newmsginfo;
g_return_if_fail(cache != NULL);
g_return_if_fail(msginfo != NULL);
newmsginfo = procmsg_msginfo_new_ref(msginfo);
g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
if(newmsginfo->msgid != NULL)
g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
cache->memusage += procmsg_msginfo_memusage(msginfo);
cache->last_access = time(NULL);
debug_print("Cache size: %d messages, %d bytes\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
}
void msgcache_remove_msg(MsgCache *cache, guint msgnum)
{
MsgInfo *msginfo;
g_return_if_fail(cache != NULL);
g_return_if_fail(msgnum > 0);
msginfo = (MsgInfo *) g_hash_table_lookup(cache->msgnum_table, &msgnum);
if(!msginfo)
return;
cache->memusage -= procmsg_msginfo_memusage(msginfo);
if(msginfo->msgid)
g_hash_table_remove(cache->msgid_table, msginfo->msgid);
g_hash_table_remove(cache->msgnum_table, &msginfo->msgnum);
procmsg_msginfo_free(msginfo);
cache->last_access = time(NULL);
debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
}
void msgcache_update_msg(MsgCache *cache, MsgInfo *msginfo)
{
MsgInfo *oldmsginfo, *newmsginfo;
g_return_if_fail(cache != NULL);
g_return_if_fail(msginfo != NULL);
oldmsginfo = g_hash_table_lookup(cache->msgnum_table, &msginfo->msgnum);
if(msginfo) {
g_hash_table_remove(cache->msgid_table, oldmsginfo->msgid);
g_hash_table_remove(cache->msgnum_table, &oldmsginfo->msgnum);
procmsg_msginfo_free(oldmsginfo);
}
cache->memusage -= procmsg_msginfo_memusage(oldmsginfo);
newmsginfo = procmsg_msginfo_new_ref(msginfo);
g_hash_table_insert(cache->msgnum_table, &newmsginfo->msgnum, newmsginfo);
if(newmsginfo->msgid)
g_hash_table_insert(cache->msgid_table, newmsginfo->msgid, newmsginfo);
cache->memusage += procmsg_msginfo_memusage(newmsginfo);
cache->last_access = time(NULL);
debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
return;
}
MsgInfo *msgcache_get_msg(MsgCache *cache, guint num)
{
MsgInfo *msginfo;
g_return_val_if_fail(cache != NULL, NULL);
msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
if(!msginfo)
return NULL;
cache->last_access = time(NULL);
return procmsg_msginfo_new_ref(msginfo);
}
MsgInfo *msgcache_get_msg_by_id(MsgCache *cache, const gchar *msgid)
{
MsgInfo *msginfo;
g_return_val_if_fail(cache != NULL, NULL);
g_return_val_if_fail(msgid != NULL, NULL);
msginfo = g_hash_table_lookup(cache->msgid_table, msgid);
if(!msginfo)
return NULL;
cache->last_access = time(NULL);
return procmsg_msginfo_new_ref(msginfo);
}
static void msgcache_get_msg_list_func(gpointer key, gpointer value, gpointer user_data)
{
MsgInfoList **listptr = user_data;
MsgInfo *msginfo = value;
*listptr = g_slist_prepend(*listptr, procmsg_msginfo_new_ref(msginfo));
}
MsgInfoList *msgcache_get_msg_list(MsgCache *cache)
{
MsgInfoList *msg_list = NULL;
g_return_val_if_fail(cache != NULL, NULL);
g_hash_table_foreach((GHashTable *)cache->msgnum_table, msgcache_get_msg_list_func, (gpointer)&msg_list);
cache->last_access = time(NULL);
msg_list = g_slist_reverse(msg_list);
return msg_list;
}
time_t msgcache_get_last_access_time(MsgCache *cache)
{
g_return_val_if_fail(cache != NULL, 0);
return cache->last_access;
}
gint msgcache_get_memory_usage(MsgCache *cache)
{
g_return_val_if_fail(cache != NULL, 0);
return cache->memusage;
}
/*
* Cache saving functions
*/
#define READ_CACHE_DATA(data, fp) \
{ \
if (msgcache_read_cache_data_str(fp, &data) < 0) { \
procmsg_msginfo_free(msginfo); \
error = TRUE; \
break; \
} \
}
#define READ_CACHE_DATA_INT(n, fp) \
{ \
guint32 idata; \
\
if (fread(&idata, sizeof(idata), 1, fp) != 1) { \
g_warning("Cache data is corrupted\n"); \
procmsg_msginfo_free(msginfo); \
error = TRUE; \
break; \
} else \
n = idata;\
}
#define WRITE_CACHE_DATA_INT(n, fp) \
{ \
guint32 idata; \
\
idata = (guint32)n; \
fwrite(&idata, sizeof(idata), 1, fp); \
}
#define WRITE_CACHE_DATA(data, fp) \
{ \
size_t len; \
if (data == NULL) \
len = 0; \
else \
len = strlen(data); \
WRITE_CACHE_DATA_INT(len, fp); \
if (len > 0) { \
fwrite(data, len, 1, fp); \
} \
}
static FILE *msgcache_open_data_file(const gchar *file, gint version,
DataOpenMode mode,
gchar *buf, size_t buf_size)
{
FILE *fp;
gint data_ver;
g_return_val_if_fail(file != NULL, NULL);
if (mode == DATA_WRITE) {
if ((fp = fopen(file, "wb")) == NULL) {
FILE_OP_ERROR(file, "fopen");
return NULL;
}
if (change_file_mode_rw(fp, file) < 0)
FILE_OP_ERROR(file, "chmod");
WRITE_CACHE_DATA_INT(version, fp);
return fp;
}
/* check version */
if ((fp = fopen(file, "rb")) == NULL)
debug_print("Mark/Cache file not found\n");
else {
if (buf && buf_size > 0)
setvbuf(fp, buf, _IOFBF, buf_size);
if (fread(&data_ver, sizeof(data_ver), 1, fp) != 1 ||
version != data_ver) {
debug_print("Mark/Cache version is different (%d != %d). "
"Discarding it.\n", data_ver, version);
fclose(fp);
fp = NULL;
}
}
if (mode == DATA_READ)
return fp;
if (fp) {
/* reopen with append mode */
fclose(fp);
if ((fp = fopen(file, "ab")) == NULL)
FILE_OP_ERROR(file, "fopen");
} else {
/* open with overwrite mode if mark file doesn't exist or
version is different */
fp = msgcache_open_data_file(file, version, DATA_WRITE, buf,
buf_size);
}
return fp;
}
static gint msgcache_read_cache_data_str(FILE *fp, gchar **str)
{
gchar buf[BUFFSIZE];
gint ret = 0;
guint32 len;
if (fread(&len, sizeof(len), 1, fp) == 1) {
if (len > G_MAXINT)
ret = -1;
else {
gchar *tmp = NULL;
while (len > 0) {
size_t size = MIN(len, BUFFSIZE - 1);
if (fread(buf, size, 1, fp) != 1) {
ret = -1;
if (tmp) g_free(tmp);
*str = NULL;
break;
}
buf[size] = '\0';
if (tmp) {
*str = g_strconcat(tmp, buf, NULL);
g_free(tmp);
tmp = *str;
} else
tmp = *str = g_strdup(buf);
len -= size;
}
}
} else
ret = -1;
if (ret < 0)
g_warning("Cache data is corrupted\n");
return ret;
}
MsgCache *msgcache_read_cache(FolderItem *item, const gchar *cache_file)
{
MsgCache *cache;
FILE *fp;
MsgInfo *msginfo;
MsgTmpFlags tmp_flags = 0;
gchar file_buf[BUFFSIZE];
guint num;
gboolean error = FALSE;
g_return_val_if_fail(cache_file != NULL, NULL);
g_return_val_if_fail(item != NULL, NULL);
if ((fp = msgcache_open_data_file
(cache_file, CACHE_VERSION, DATA_READ, file_buf, sizeof(file_buf))) == NULL)
return NULL;
debug_print("\tReading message cache from %s...\n", cache_file);
if (item->stype == F_QUEUE) {
tmp_flags |= MSG_QUEUED;
} else if (item->stype == F_DRAFT) {
tmp_flags |= MSG_DRAFT;
}
cache = msgcache_new();
g_hash_table_freeze(cache->msgnum_table);
while (fread(&num, sizeof(num), 1, fp) == 1) {
msginfo = procmsg_msginfo_new();
msginfo->msgnum = num;
READ_CACHE_DATA_INT(msginfo->size, fp);
READ_CACHE_DATA_INT(msginfo->mtime, fp);
READ_CACHE_DATA_INT(msginfo->date_t, fp);
READ_CACHE_DATA_INT(msginfo->flags.tmp_flags, fp);
READ_CACHE_DATA(msginfo->fromname, fp);
READ_CACHE_DATA(msginfo->date, fp);
READ_CACHE_DATA(msginfo->from, fp);
READ_CACHE_DATA(msginfo->to, fp);
READ_CACHE_DATA(msginfo->cc, fp);
READ_CACHE_DATA(msginfo->newsgroups, fp);
READ_CACHE_DATA(msginfo->subject, fp);
READ_CACHE_DATA(msginfo->msgid, fp);
READ_CACHE_DATA(msginfo->inreplyto, fp);
READ_CACHE_DATA(msginfo->references, fp);
READ_CACHE_DATA(msginfo->xref, fp);
READ_CACHE_DATA_INT(msginfo->planned_download, fp);
msginfo->folder = item;
msginfo->flags.tmp_flags |= tmp_flags;
g_hash_table_insert(cache->msgnum_table, &msginfo->msgnum, msginfo);
if(msginfo->msgid)
g_hash_table_insert(cache->msgid_table, msginfo->msgid, msginfo);
cache->memusage += procmsg_msginfo_memusage(msginfo);
}
fclose(fp);
g_hash_table_thaw(cache->msgnum_table);
if(error) {
msgcache_destroy(cache);
return NULL;
}
cache->last_access = time(NULL);
debug_print("done. (%d items read)\n", g_hash_table_size(cache->msgnum_table));
debug_print("Cache size: %d messages, %d byte\n", g_hash_table_size(cache->msgnum_table), cache->memusage);
return cache;
}
void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
{
FILE *fp;
MsgInfo *msginfo;
MsgPermFlags perm_flags;
guint num;
if ((fp = msgcache_open_data_file(mark_file, MARK_VERSION, DATA_READ, NULL, 0)) == NULL)
return;
debug_print("\tReading message marks from %s...\n", mark_file);
while (fread(&num, sizeof(num), 1, fp) == 1) {
if (fread(&perm_flags, sizeof(perm_flags), 1, fp) != 1) break;
msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
if(msginfo) {
msginfo->flags.perm_flags = perm_flags;
}
}
fclose(fp);
}
void msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
{
MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
WRITE_CACHE_DATA_INT(msginfo->size, fp);
WRITE_CACHE_DATA_INT(msginfo->mtime, fp);
WRITE_CACHE_DATA_INT(msginfo->date_t, fp);
WRITE_CACHE_DATA_INT(flags, fp);
WRITE_CACHE_DATA(msginfo->fromname, fp);
WRITE_CACHE_DATA(msginfo->date, fp);
WRITE_CACHE_DATA(msginfo->from, fp);
WRITE_CACHE_DATA(msginfo->to, fp);
WRITE_CACHE_DATA(msginfo->cc, fp);
WRITE_CACHE_DATA(msginfo->newsgroups, fp);
WRITE_CACHE_DATA(msginfo->subject, fp);
WRITE_CACHE_DATA(msginfo->msgid, fp);
WRITE_CACHE_DATA(msginfo->inreplyto, fp);
WRITE_CACHE_DATA(msginfo->references, fp);
WRITE_CACHE_DATA(msginfo->xref, fp);
WRITE_CACHE_DATA_INT(msginfo->planned_download, fp);
}
static void msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
{
MsgPermFlags flags = msginfo->flags.perm_flags;
WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
WRITE_CACHE_DATA_INT(flags, fp);
}
struct write_fps
{
FILE *cache_fp;
FILE *mark_fp;
};
static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
{
MsgInfo *msginfo;
struct write_fps *write_fps;
msginfo = (MsgInfo *)value;
write_fps = user_data;
msgcache_write_cache(msginfo, write_fps->cache_fp);
msgcache_write_flags(msginfo, write_fps->mark_fp);
}
gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *cache)
{
struct write_fps write_fps;
g_return_val_if_fail(cache_file != NULL, -1);
g_return_val_if_fail(mark_file != NULL, -1);
g_return_val_if_fail(cache != NULL, -1);
write_fps.cache_fp = msgcache_open_data_file(cache_file, CACHE_VERSION,
DATA_WRITE, NULL, 0);
if (write_fps.cache_fp == NULL)
return -1;
write_fps.mark_fp = msgcache_open_data_file(mark_file, MARK_VERSION,
DATA_WRITE, NULL, 0);
if (write_fps.mark_fp == NULL) {
fclose(write_fps.cache_fp);
return -1;
}
debug_print("\tWriting message cache to %s and %s...\n", cache_file, mark_file);
if (change_file_mode_rw(write_fps.cache_fp, cache_file) < 0)
FILE_OP_ERROR(cache_file, "chmod");
g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
fclose(write_fps.cache_fp);
fclose(write_fps.mark_fp);
cache->last_access = time(NULL);
debug_print("done.\n");
return 0;
}