
2001-10-19 <NotZed@Ximian.com> * mail-folder-cache.c (store_finalised): Free folders_uri. (real_folder_deleted): If folder is deleted, remove it from the hashtables. * subscribe-dialog.c (get_short_folderinfo_get): Remove the register/unregister, they're already done above us. * mail-vfolder.c (mail_vfolder_delete_uri): Dont do any work to remove the actual folder from the vfolder (we'd have to look it up first), let the vfolder remove it itself. Just update the rules. svn path=/trunk/; revision=13787
556 lines
16 KiB
C
556 lines
16 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Authors: Peter Williams <peterw@ximian.com>
|
|
* Michael Zucchi <notzed@ximian.com>
|
|
*
|
|
* Copyright 2000,2001 Ximian, Inc. (www.ximian.com)
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef G_LOG_DOMAIN
|
|
#undef G_LOG_DOMAIN
|
|
#endif
|
|
#define G_LOG_DOMAIN "folder tree"
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <bonobo/bonobo-exception.h>
|
|
#include <camel/camel-store.h>
|
|
#include <camel/camel-folder.h>
|
|
#include <camel/camel-vtrash-folder.h>
|
|
|
|
#include "mail-mt.h"
|
|
#include "mail-folder-cache.h"
|
|
#include "mail-ops.h"
|
|
#include "mail-vfolder.h"
|
|
|
|
#define d(x)
|
|
|
|
/* note that many things are effectively serialised by having them run in
|
|
the main loop thread which they need to do because of corba/gtk calls */
|
|
#define LOCK(x) pthread_mutex_lock(&x)
|
|
#define UNLOCK(x) pthread_mutex_unlock(&x)
|
|
|
|
static pthread_mutex_t info_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
struct _folder_info {
|
|
struct _store_info *store_info; /* 'parent' link */
|
|
|
|
char *path; /* shell path */
|
|
char *full_name; /* full name of folder/folderinfo */
|
|
char *uri; /* uri of folder */
|
|
|
|
CamelFolder *folder; /* if known */
|
|
};
|
|
|
|
struct _store_info {
|
|
GHashTable *folders; /* by full_name */
|
|
GHashTable *folders_uri; /* by uri */
|
|
|
|
CamelStore *store; /* the store for these folders */
|
|
|
|
/* only 1 should be set */
|
|
EvolutionStorage *storage;
|
|
GNOME_Evolution_Storage corba_storage;
|
|
MailAsyncEvent *async_event;
|
|
};
|
|
|
|
static GHashTable *stores;
|
|
|
|
static void free_folder_info(char *path, struct _folder_info *mfi, void *data);
|
|
static void unset_folder_info(struct _folder_info *mfi, int delete);
|
|
|
|
|
|
/* This is how unread counts work (and don't work):
|
|
*
|
|
* camel_folder_unread_message_count() only gives a correct answer if
|
|
* the store is paying attention to the folder. (Some stores always
|
|
* pay attention to all folders, but IMAP can only pay attention to
|
|
* one folder at a time.) But it doesn't have any way to know when
|
|
* it's lying, so it's only safe to call it when you know for sure
|
|
* that the store is paying attention to the folder, such as when it's
|
|
* just been created, or you get a folder_changed or message_changed
|
|
* signal on it.
|
|
*
|
|
* camel_store_get_folder_info() always gives correct answers for the
|
|
* folders it checks, but it can also return -1 for a folder, meaning
|
|
* it didn't check, and so you should stick with your previous answer.
|
|
*
|
|
* update_1folder is called from three places: with info != NULL when
|
|
* the folder is created (or get_folder_info), with info == NULL when
|
|
* a folder changed event is emitted.
|
|
*
|
|
* So if info is NULL, camel_folder_unread_message_count is correct,
|
|
* and if it's not NULL and its unread_message_count isn't -1, then
|
|
* it's correct. */
|
|
|
|
static void
|
|
update_1folder(struct _folder_info *mfi, CamelFolderInfo *info)
|
|
{
|
|
struct _store_info *si;
|
|
CamelFolder *folder;
|
|
int unread = -1;
|
|
CORBA_Environment ev;
|
|
extern CamelFolder *outbox_folder, *sent_folder;
|
|
|
|
si = mfi->store_info;
|
|
|
|
LOCK(info_lock);
|
|
folder = mfi->folder;
|
|
if (folder) {
|
|
if (CAMEL_IS_VTRASH_FOLDER (folder) || folder == outbox_folder || folder == sent_folder) {
|
|
unread = camel_folder_get_message_count(folder);
|
|
} else {
|
|
if (info)
|
|
unread = info->unread_message_count;
|
|
else
|
|
unread = camel_folder_get_unread_message_count (folder);
|
|
}
|
|
} else if (info)
|
|
unread = info->unread_message_count;
|
|
UNLOCK(info_lock);
|
|
if (unread == -1)
|
|
return;
|
|
|
|
if (si->storage == NULL) {
|
|
d(printf("Updating existing (local) folder: %s (%d unread) folder=%p\n", mfi->path, unread, folder));
|
|
CORBA_exception_init(&ev);
|
|
GNOME_Evolution_Storage_updateFolder(si->corba_storage, mfi->path, unread, &ev);
|
|
CORBA_exception_free(&ev);
|
|
} else {
|
|
d(printf("Updating existing folder: %s (%d unread)\n", mfi->path, unread));
|
|
evolution_storage_update_folder(si->storage, mfi->path, unread);
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_folder(CamelFolderInfo *fi, struct _store_info *si)
|
|
{
|
|
struct _folder_info *mfi;
|
|
char *type;
|
|
CamelStore *store;
|
|
|
|
LOCK(info_lock);
|
|
mfi = g_hash_table_lookup(si->folders, fi->full_name);
|
|
if (mfi) {
|
|
UNLOCK(info_lock);
|
|
update_1folder(mfi, fi);
|
|
} else {
|
|
/* always 'add it', but only 'add it' to non-local stores */
|
|
d(printf("Adding new folder: %s (%s) %d unread\n", fi->path, fi->url, fi->unread_message_count));
|
|
mfi = g_malloc0(sizeof(*mfi));
|
|
mfi->path = g_strdup(fi->path);
|
|
mfi->full_name = g_strdup(fi->full_name);
|
|
mfi->uri = g_strdup(fi->url);
|
|
mfi->store_info = si;
|
|
g_hash_table_insert(si->folders, mfi->full_name, mfi);
|
|
g_hash_table_insert(si->folders_uri, mfi->uri, mfi);
|
|
store = si->store;
|
|
camel_object_ref((CamelObject *)store);
|
|
UNLOCK(info_lock);
|
|
|
|
if (si->storage != NULL) {
|
|
int unread = (fi->unread_message_count==-1)?0:fi->unread_message_count;
|
|
|
|
type = (strncmp(fi->url, "vtrash:", 7)==0)?"vtrash":"mail";
|
|
evolution_storage_new_folder(si->storage, mfi->path, fi->name, type,
|
|
fi->url, fi->name, unread);
|
|
}
|
|
|
|
if (strstr(fi->url, ";noselect") == NULL)
|
|
mail_vfolder_add_uri(store, fi->url, FALSE);
|
|
|
|
camel_object_unref((CamelObject *)store);
|
|
}
|
|
}
|
|
|
|
static void
|
|
real_folder_changed(CamelFolder *folder, void *event_data, void *data)
|
|
{
|
|
struct _folder_info *mfi = data;
|
|
|
|
update_1folder(mfi, NULL);
|
|
camel_object_unref((CamelObject *)folder);
|
|
}
|
|
|
|
static void
|
|
folder_changed(CamelObject *o, gpointer event_data, gpointer user_data)
|
|
{
|
|
struct _folder_info *mfi = user_data;
|
|
|
|
if (mfi->folder != CAMEL_FOLDER(o))
|
|
return;
|
|
|
|
d(printf("Fodler changed!\n"));
|
|
camel_object_ref((CamelObject *)o);
|
|
mail_async_event_emit(mfi->store_info->async_event, (CamelObjectEventHookFunc)real_folder_changed, o, NULL, mfi);
|
|
}
|
|
|
|
static void
|
|
folder_finalised(CamelObject *o, gpointer event_data, gpointer user_data)
|
|
{
|
|
struct _folder_info *mfi = user_data;
|
|
|
|
d(printf("Folder finalised '%s'!\n", ((CamelFolder *)o)->full_name));
|
|
mfi->folder = NULL;
|
|
}
|
|
|
|
static void
|
|
folder_deleted(CamelObject *o, gpointer event_data, gpointer user_data)
|
|
{
|
|
struct _folder_info *mfi = user_data;
|
|
|
|
d(printf("Folder deleted '%s'!\n", ((CamelFolder *)o)->full_name));
|
|
mfi->folder = NULL;
|
|
}
|
|
|
|
static void
|
|
real_note_folder(CamelFolder *folder, void *event_data, void *data)
|
|
{
|
|
struct _folder_info *mfi = event_data;
|
|
|
|
update_1folder(mfi, NULL);
|
|
camel_object_unref((CamelObject *)folder);
|
|
}
|
|
|
|
void mail_note_folder(CamelFolder *folder)
|
|
{
|
|
CamelStore *store = folder->parent_store;
|
|
struct _store_info *si;
|
|
struct _folder_info *mfi;
|
|
|
|
if (stores == NULL) {
|
|
g_warning("Adding a folder `%s' to a store which hasn't been added yet?\n", folder->full_name);
|
|
return;
|
|
}
|
|
|
|
LOCK(info_lock);
|
|
si = g_hash_table_lookup(stores, store);
|
|
if (si == NULL) {
|
|
/*g_warning("Adding a folder `%s' to a store %p which hasn't been added yet?", folder->full_name, store);*/
|
|
UNLOCK(info_lock);
|
|
return;
|
|
}
|
|
|
|
mfi = g_hash_table_lookup(si->folders, folder->full_name);
|
|
if (mfi == NULL) {
|
|
g_warning("Adding a folder `%s' that I dont know about yet?", folder->full_name);
|
|
UNLOCK(info_lock);
|
|
return;
|
|
}
|
|
|
|
/* dont do anything if we already have this */
|
|
if (mfi->folder == folder) {
|
|
UNLOCK(info_lock);
|
|
return;
|
|
}
|
|
|
|
mfi->folder = folder;
|
|
|
|
camel_object_hook_event((CamelObject *)folder, "folder_changed", folder_changed, mfi);
|
|
camel_object_hook_event((CamelObject *)folder, "message_changed", folder_changed, mfi);
|
|
camel_object_hook_event((CamelObject *)folder, "deleted", folder_deleted, mfi);
|
|
camel_object_hook_event((CamelObject *)folder, "finalize", folder_finalised, mfi);
|
|
|
|
camel_object_ref((CamelObject *)folder);
|
|
|
|
UNLOCK(info_lock);
|
|
|
|
mail_async_event_emit(si->async_event, (CamelObjectEventHookFunc)real_note_folder, (CamelObject *)folder, (void *)mfi, NULL);
|
|
}
|
|
|
|
static void
|
|
real_folder_created(CamelStore *store, struct _store_info *si, CamelFolderInfo *fi)
|
|
{
|
|
setup_folder(fi, si);
|
|
camel_object_unref((CamelObject *)store);
|
|
camel_folder_info_free(fi);
|
|
}
|
|
|
|
static void
|
|
store_folder_subscribed(CamelObject *o, void *event_data, void *data)
|
|
{
|
|
struct _store_info *si;
|
|
|
|
LOCK(info_lock);
|
|
si = g_hash_table_lookup(stores, o);
|
|
if (si)
|
|
camel_object_ref(o);
|
|
UNLOCK(info_lock);
|
|
|
|
if (si)
|
|
mail_async_event_emit(si->async_event,
|
|
(CamelObjectEventHookFunc)real_folder_created, o, si,
|
|
camel_folder_info_clone(event_data));
|
|
}
|
|
|
|
static void
|
|
store_folder_created(CamelObject *o, void *event_data, void *data)
|
|
{
|
|
/* we only want created events to do more work if we dont support subscriptions */
|
|
if (!camel_store_supports_subscriptions(CAMEL_STORE(o)))
|
|
store_folder_subscribed(o, event_data, data);
|
|
}
|
|
|
|
|
|
static void
|
|
real_folder_deleted(CamelStore *store, struct _store_info *si, CamelFolderInfo *fi)
|
|
{
|
|
struct _folder_info *mfi;
|
|
|
|
d(printf("real_folder_deleted: %s (%s)\n", fi->full_name, fi->url));
|
|
|
|
LOCK(info_lock);
|
|
mfi = g_hash_table_lookup(si->folders, fi->full_name);
|
|
if (mfi) {
|
|
g_hash_table_remove(si->folders, mfi->full_name);
|
|
g_hash_table_remove(si->folders_uri, mfi->uri);
|
|
unset_folder_info(mfi, TRUE);
|
|
free_folder_info(NULL, mfi, NULL);
|
|
}
|
|
UNLOCK(info_lock);
|
|
|
|
camel_object_unref((CamelObject *)store);
|
|
camel_folder_info_free(fi);
|
|
}
|
|
|
|
static void
|
|
store_folder_unsubscribed(CamelObject *o, void *event_data, void *data)
|
|
{
|
|
struct _store_info *si;
|
|
|
|
LOCK(info_lock);
|
|
si = g_hash_table_lookup(stores, o);
|
|
if (si)
|
|
camel_object_ref(o);
|
|
UNLOCK(info_lock);
|
|
|
|
if (si)
|
|
mail_async_event_emit(si->async_event,
|
|
(CamelObjectEventHookFunc)real_folder_deleted, o, si,
|
|
camel_folder_info_clone(event_data));
|
|
}
|
|
|
|
static void
|
|
store_folder_deleted(CamelObject *o, void *event_data, void *data)
|
|
{
|
|
/* we only want deleted events to do more work if we dont support subscriptions */
|
|
if (!camel_store_supports_subscriptions(CAMEL_STORE(o)))
|
|
store_folder_unsubscribed(o, event_data, data);
|
|
}
|
|
|
|
static void
|
|
unset_folder_info(struct _folder_info *mfi, int delete)
|
|
{
|
|
if (mfi->folder) {
|
|
CamelFolder *folder = mfi->folder;
|
|
|
|
camel_object_unhook_event((CamelObject *)folder, "folder_changed", folder_changed, mfi);
|
|
camel_object_unhook_event((CamelObject *)folder, "message_changed", folder_changed, mfi);
|
|
camel_object_unhook_event((CamelObject *)folder, "deleted", folder_deleted, mfi);
|
|
camel_object_unhook_event((CamelObject *)folder, "finalize", folder_finalised, mfi);
|
|
}
|
|
|
|
if (strstr(mfi->uri, ";noselect") == NULL) {
|
|
if (delete)
|
|
mail_vfolder_delete_uri(mfi->store_info->store, mfi->uri);
|
|
else
|
|
mail_vfolder_add_uri(mfi->store_info->store, mfi->uri, TRUE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
unset_folder_info_hash(char *path, struct _folder_info *mfi, void *data)
|
|
{
|
|
unset_folder_info(mfi, FALSE);
|
|
}
|
|
|
|
|
|
static void
|
|
free_folder_info(char *path, struct _folder_info *mfi, void *data)
|
|
{
|
|
g_free(mfi->path);
|
|
g_free(mfi->full_name);
|
|
g_free(mfi->uri);
|
|
}
|
|
|
|
static void
|
|
store_finalised(CamelObject *o, void *event_data, void *data)
|
|
{
|
|
CamelStore *store = (CamelStore *)o;
|
|
struct _store_info *si;
|
|
|
|
d(printf("store finalised!!\n"));
|
|
LOCK(info_lock);
|
|
si = g_hash_table_lookup(stores, store);
|
|
if (si) {
|
|
g_hash_table_remove(stores, store);
|
|
|
|
camel_object_unhook_event((CamelObject *)store, "folder_created", store_folder_created, NULL);
|
|
camel_object_unhook_event((CamelObject *)store, "folder_deleted", store_folder_deleted, NULL);
|
|
camel_object_unhook_event((CamelObject *)store, "folder_subscribed", store_folder_subscribed, NULL);
|
|
camel_object_unhook_event((CamelObject *)store, "folder_unsubscribed", store_folder_unsubscribed, NULL);
|
|
camel_object_unhook_event((CamelObject *)store, "finalize", store_finalised, NULL);
|
|
|
|
g_hash_table_foreach(si->folders, (GHFunc)unset_folder_info_hash, NULL);
|
|
UNLOCK(info_lock);
|
|
mail_async_event_destroy(si->async_event);
|
|
LOCK(info_lock);
|
|
g_hash_table_foreach(si->folders, (GHFunc)free_folder_info, NULL);
|
|
g_hash_table_destroy(si->folders);
|
|
g_hash_table_destroy(si->folders_uri);
|
|
g_free(si);
|
|
}
|
|
UNLOCK(info_lock);
|
|
}
|
|
|
|
static void
|
|
create_folders(CamelFolderInfo *fi, struct _store_info *si)
|
|
{
|
|
d(printf("Setup new folder: %s\n", fi->url));
|
|
|
|
setup_folder(fi, si);
|
|
|
|
if (fi->child)
|
|
create_folders(fi->child, si);
|
|
if (fi->sibling)
|
|
create_folders(fi->sibling, si);
|
|
}
|
|
|
|
struct _update_data {
|
|
struct _store_info *si;
|
|
void (*done)(CamelStore *store, CamelFolderInfo *info, void *data);
|
|
void *data;
|
|
};
|
|
|
|
static void
|
|
update_folders(CamelStore *store, CamelFolderInfo *info, void *data)
|
|
{
|
|
struct _update_data *ud = data;
|
|
|
|
if (info) {
|
|
if (ud->si->storage)
|
|
gtk_object_set_data (GTK_OBJECT (ud->si->storage), "connected", GINT_TO_POINTER (TRUE));
|
|
create_folders(info, ud->si);
|
|
}
|
|
if (ud->done)
|
|
ud->done(store, info, ud->data);
|
|
g_free(ud);
|
|
}
|
|
|
|
void
|
|
mail_note_store_remove(CamelStore *store)
|
|
{
|
|
g_assert(CAMEL_IS_STORE(store));
|
|
|
|
if (stores == NULL)
|
|
return;
|
|
|
|
/* same action */
|
|
store_finalised((CamelObject *)store, NULL, NULL);
|
|
}
|
|
|
|
void
|
|
mail_note_store(CamelStore *store, EvolutionStorage *storage, GNOME_Evolution_Storage corba_storage,
|
|
void (*done)(CamelStore *store, CamelFolderInfo *info, void *data), void *data)
|
|
{
|
|
struct _store_info *si;
|
|
struct _update_data *ud;
|
|
|
|
g_assert(CAMEL_IS_STORE(store));
|
|
g_assert(pthread_self() == mail_gui_thread);
|
|
g_assert(storage != NULL || corba_storage != CORBA_OBJECT_NIL);
|
|
|
|
LOCK(info_lock);
|
|
|
|
if (stores == NULL)
|
|
stores = g_hash_table_new(NULL, NULL);
|
|
|
|
si = g_hash_table_lookup(stores, store);
|
|
if (si == NULL) {
|
|
|
|
d(printf("Noting a new store: %p: %s\n", store, camel_url_to_string(((CamelService *)store)->url, 0)));
|
|
|
|
/* FIXME: Need to ref the storages & store or something?? */
|
|
|
|
si = g_malloc0(sizeof(*si));
|
|
si->folders = g_hash_table_new(g_str_hash, g_str_equal);
|
|
si->folders_uri = g_hash_table_new(CAMEL_STORE_CLASS(CAMEL_OBJECT_GET_CLASS(store))->hash_folder_name,
|
|
CAMEL_STORE_CLASS(CAMEL_OBJECT_GET_CLASS(store))->compare_folder_name);
|
|
si->storage = storage;
|
|
si->corba_storage = corba_storage;
|
|
si->store = store;
|
|
g_hash_table_insert(stores, store, si);
|
|
si->async_event = mail_async_event_new();
|
|
|
|
camel_object_hook_event((CamelObject *)store, "folder_created", store_folder_created, NULL);
|
|
camel_object_hook_event((CamelObject *)store, "folder_deleted", store_folder_deleted, NULL);
|
|
camel_object_hook_event((CamelObject *)store, "folder_subscribed", store_folder_subscribed, NULL);
|
|
camel_object_hook_event((CamelObject *)store, "folder_unsubscribed", store_folder_unsubscribed, NULL);
|
|
camel_object_hook_event((CamelObject *)store, "finalize", store_finalised, NULL);
|
|
}
|
|
|
|
UNLOCK(info_lock);
|
|
|
|
ud = g_malloc(sizeof(*ud));
|
|
ud->si = si;
|
|
ud->done = done;
|
|
ud->data = data;
|
|
|
|
mail_get_folderinfo(store, update_folders, ud);
|
|
}
|
|
|
|
struct _find_info {
|
|
const char *uri;
|
|
struct _folder_info *fi;
|
|
};
|
|
|
|
/* look up on each storeinfo using proper hash function for that stores uri's */
|
|
static void storeinfo_find_folder_info(CamelStore *store, struct _store_info *si, struct _find_info *fi)
|
|
{
|
|
if (fi->fi == NULL)
|
|
fi->fi = g_hash_table_lookup(si->folders_uri, fi->uri);
|
|
}
|
|
|
|
/* returns TRUE if the uri is available, folderp is set to a
|
|
reffed folder if the folder has also already been opened */
|
|
int mail_note_get_folder_from_uri(const char *uri, CamelFolder **folderp)
|
|
{
|
|
struct _find_info fi = { uri, NULL };
|
|
|
|
if (stores == NULL)
|
|
return FALSE;
|
|
|
|
LOCK(info_lock);
|
|
g_hash_table_foreach(stores, (GHFunc)storeinfo_find_folder_info, &fi);
|
|
if (folderp) {
|
|
if (fi.fi && fi.fi->folder) {
|
|
*folderp = fi.fi->folder;
|
|
camel_object_ref((CamelObject *)*folderp);
|
|
} else {
|
|
*folderp = NULL;
|
|
}
|
|
}
|
|
UNLOCK(info_lock);
|
|
|
|
return fi.fi != NULL;
|
|
}
|