Files
evolution/mail/mail-vfolder.c
Rodney Dawes e6ee91aa26 New method to copy the selected text in the mail display, to the clipboard
2005-02-18  Rodney Dawes  <dobey@novell.com>

	* mail/em-folder-view.c (emfv_popup_copy_text): New method to copy
	the selected text in the mail display, to the clipboard
	(emfv_popup_items): Restructure this list for the new context menus
	layout that we are moving to, to improve the UI
	(emfv_popup): Add a third argument for whether we are on the mail
	display or not, so that we can pop up the correct portion of the menus
	Pass the on_display argument on to create the popup target
	Only generate the label items if we are on the list and not the display
	(emp_uri_popup_vfolder_{sender,recipient}): New methods to create
	vfolders to and from mailto: addresses from the context menu
	(emfv_enable_menus): Create the target here for clicking on the list
	(em_folder_view_get_popup_target): Add a third argument so that we
	can tell whether we are on the display or not
	Set the appropriate flags on the target for whether we are on the
	message display,, and whether or not there is selected text in it
	(emfv_list_right_click): We aren't rigt-clicking on the display here
	(emfv_popup_menu): We aren't right-clicking on the mail display here
	(emfv_uri_popups): Add the submenu for creating a vfolder from mailto:
	(emfv_format_popup_event): We are right-clickingo nt he display here

	* mail/em-folder-view.h: Add the new flags we need for the mail
	context menus popup target here
	(em_folderiew_get_popup_target): Add the on_display argument

	* mail/em-popup.c (emp_standard_uri_popups): Fix the label and mnemonic
	for the Send New Message popup item for mailto: addresses

	* mail/mail-autofilter.c (rule_from_address):
	(em_vfolder_rule_from_address): New methods to create a vfolder based
	on a CamelInternetAddress object that we get from the mailer

	* mail/mail-vfolder.[ch] (vfolder_gui_add_from_address): New method to
	create a vfolder rule based on the mailto: addresses

	Fixes #23822

svn path=/trunk/; revision=28814
2005-02-18 16:43:55 +00:00

1164 lines
32 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Authors: Michael Zucchi <notzed@ximian.com>
*
* Copyright 2000-2003 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 Street #330, Boston, MA 02111-1307, USA.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib.h>
#include <string.h>
#include <libgnome/gnome-i18n.h>
#include "mail-component.h"
#include "mail-config.h"
#include "mail-vfolder.h"
#include "mail-tools.h"
#include "mail-autofilter.h"
#include "mail-folder-cache.h"
#include "mail-ops.h"
#include "mail-mt.h"
#include "em-utils.h"
#include "e-util/e-account-list.h"
#include "widgets/misc/e-error.h"
#include "camel/camel-vee-folder.h"
#include "camel/camel-vee-store.h"
#include "camel/camel-vtrash-folder.h"
#include "em-vfolder-context.h"
#include "em-vfolder-editor.h"
#define d(x) /*(printf("%s(%d):%s: ", __FILE__, __LINE__, __PRETTY_FUNCTION__), (x))*/
static EMVFolderContext *context; /* context remains open all time */
CamelStore *vfolder_store; /* the 1 static vfolder store */
/* lock for accessing shared resources (below) */
static pthread_mutex_t vfolder_lock = PTHREAD_MUTEX_INITIALIZER;
static GList *source_folders_remote; /* list of source folder uri's - remote ones */
static GList *source_folders_local; /* list of source folder uri's - local ones */
static GHashTable *vfolder_hash;
/* This is a slightly hacky solution to shutting down, we poll this variable in various
loops, and just quit processing if it is set. */
static volatile int shutdown; /* are we shutting down? */
/* more globals ... */
extern CamelSession *session;
static void rule_changed(FilterRule *rule, CamelFolder *folder);
#define LOCK() pthread_mutex_lock(&vfolder_lock);
#define UNLOCK() pthread_mutex_unlock(&vfolder_lock);
/* ********************************************************************** */
struct _setup_msg {
struct _mail_msg msg;
CamelFolder *folder;
char *query;
GList *sources_uri;
GList *sources_folder;
};
static char *
vfolder_setup_desc(struct _mail_msg *mm, int done)
{
struct _setup_msg *m = (struct _setup_msg *)mm;
return g_strdup_printf(_("Setting up vFolder: %s"), m->folder->full_name);
}
static void
vfolder_setup_do(struct _mail_msg *mm)
{
struct _setup_msg *m = (struct _setup_msg *)mm;
GList *l, *list = NULL;
CamelFolder *folder;
d(printf("Setting up vFolder: %s\n", m->folder->full_name));
camel_vee_folder_set_expression((CamelVeeFolder *)m->folder, m->query);
l = m->sources_uri;
while (l && !shutdown) {
d(printf(" Adding uri: %s\n", (char *)l->data));
folder = mail_tool_uri_to_folder (l->data, 0, &mm->ex);
if (folder) {
list = g_list_append(list, folder);
} else {
g_warning("Could not open vfolder source: %s", (char *)l->data);
camel_exception_clear(&mm->ex);
}
l = l->next;
}
l = m->sources_folder;
while (l && !shutdown) {
d(printf(" Adding folder: %s\n", ((CamelFolder *)l->data)->full_name));
camel_object_ref(l->data);
list = g_list_append(list, l->data);
l = l->next;
}
if (!shutdown)
camel_vee_folder_set_folders((CamelVeeFolder *)m->folder, list);
l = list;
while (l) {
camel_object_unref(l->data);
l = l->next;
}
g_list_free(list);
}
static void
vfolder_setup_done(struct _mail_msg *mm)
{
struct _setup_msg *m = (struct _setup_msg *)mm;
m = m;
}
static void
vfolder_setup_free (struct _mail_msg *mm)
{
struct _setup_msg *m = (struct _setup_msg *)mm;
GList *l;
camel_object_unref(m->folder);
g_free(m->query);
l = m->sources_uri;
while (l) {
g_free(l->data);
l = l->next;
}
g_list_free(m->sources_uri);
l = m->sources_folder;
while (l) {
camel_object_unref(l->data);
l = l->next;
}
g_list_free(m->sources_folder);
}
static struct _mail_msg_op vfolder_setup_op = {
vfolder_setup_desc,
vfolder_setup_do,
vfolder_setup_done,
vfolder_setup_free,
};
/* sources_uri should be camel uri's */
static int
vfolder_setup(CamelFolder *folder, const char *query, GList *sources_uri, GList *sources_folder)
{
struct _setup_msg *m;
int id;
m = mail_msg_new(&vfolder_setup_op, NULL, sizeof (*m));
m->folder = folder;
camel_object_ref(folder);
m->query = g_strdup(query);
m->sources_uri = sources_uri;
m->sources_folder = sources_folder;
id = m->msg.seq;
e_thread_put(mail_thread_queued_slow, (EMsg *)m);
return id;
}
/* ********************************************************************** */
struct _adduri_msg {
struct _mail_msg msg;
char *uri;
GList *folders;
int remove;
};
static char *
vfolder_adduri_desc(struct _mail_msg *mm, int done)
{
struct _adduri_msg *m = (struct _adduri_msg *)mm;
char *euri, *desc = NULL;
/* Yuck yuck. Lookup the account name and use that to describe the path */
/* We really need to normalise this across all of camel and evolution :-/ */
euri = em_uri_from_camel(m->uri);
if (euri) {
CamelURL *url = camel_url_new(euri, NULL);
if (url) {
const char *loc = NULL;
if (url->host && !strcmp(url->host, "local")
&& url->user && !strcmp(url->user, "local")) {
loc = _("On This Computer");
} else {
char *uid;
const EAccount *account;
if (url->user == NULL)
uid = g_strdup(url->host);
else
uid = g_strdup_printf("%s@%s", url->user, url->host);
account = e_account_list_find(mail_config_get_accounts(), E_ACCOUNT_FIND_UID, uid);
g_free(uid);
if (account != NULL)
loc = account->name;
}
if (loc && url->path)
desc = g_strdup_printf(_("Updating vFolders for '%s:%s'"), loc, url->path);
camel_url_free(url);
}
g_free(euri);
}
if (desc == NULL)
desc = g_strdup_printf(_("Updating vFolders for '%s'"), m->uri);
return desc;
}
static void
vfolder_adduri_do(struct _mail_msg *mm)
{
struct _adduri_msg *m = (struct _adduri_msg *)mm;
GList *l;
CamelFolder *folder = NULL;
if (shutdown)
return;
d(printf("%s uri to vfolder: %s\n", m->remove?"Removing":"Adding", m->uri));
/* we dont try lookup the cache if we are removing it, its no longer there */
if (!m->remove && !mail_note_get_folder_from_uri(m->uri, &folder)) {
g_warning("Folder '%s' disappeared while I was adding/remove it to/from my vfolder", m->uri);
return;
}
if (folder == NULL)
folder = mail_tool_uri_to_folder (m->uri, 0, &mm->ex);
if (folder != NULL) {
l = m->folders;
while (l && !shutdown) {
if (m->remove)
camel_vee_folder_remove_folder((CamelVeeFolder *)l->data, folder);
else
camel_vee_folder_add_folder((CamelVeeFolder *)l->data, folder);
l = l->next;
}
camel_object_unref(folder);
}
}
static void
vfolder_adduri_done(struct _mail_msg *mm)
{
struct _adduri_msg *m = (struct _adduri_msg *)mm;
m = m;
}
static void
vfolder_adduri_free (struct _mail_msg *mm)
{
struct _adduri_msg *m = (struct _adduri_msg *)mm;
g_list_foreach(m->folders, (GFunc)camel_object_unref, NULL);
g_list_free(m->folders);
g_free(m->uri);
}
static struct _mail_msg_op vfolder_adduri_op = {
vfolder_adduri_desc,
vfolder_adduri_do,
vfolder_adduri_done,
vfolder_adduri_free,
};
/* uri should be a camel uri */
static int
vfolder_adduri(const char *uri, GList *folders, int remove)
{
struct _adduri_msg *m;
int id;
m = mail_msg_new(&vfolder_adduri_op, NULL, sizeof (*m));
m->folders = folders;
m->uri = g_strdup(uri);
m->remove = remove;
id = m->msg.seq;
e_thread_put(mail_thread_queued_slow, (EMsg *)m);
return id;
}
/* ********************************************************************** */
static GList *
mv_find_folder(GList *l, CamelStore *store, const char *uri)
{
while (l) {
if (camel_store_folder_uri_equal(store, l->data, uri))
break;
l = l->next;
}
return l;
}
/* uri is a camel uri */
static int
uri_is_ignore(CamelStore *store, const char *uri)
{
EAccountList *accounts;
EAccount *account;
EIterator *iter;
int found = FALSE;
d(printf("checking '%s' against:\n %s\n %s\n %s\n", uri,
mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_OUTBOX),
mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_SENT),
mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_DRAFTS)));
found = camel_store_folder_uri_equal(store, mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_OUTBOX), uri)
|| camel_store_folder_uri_equal(store, mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_SENT), uri)
|| camel_store_folder_uri_equal(store, mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_DRAFTS), uri);
if (found)
return found;
accounts = mail_config_get_accounts ();
iter = e_list_get_iterator ((EList *) accounts);
while (e_iterator_is_valid (iter)) {
char *curi;
account = (EAccount *) e_iterator_get (iter);
d(printf("checking sent_folder_uri '%s' == '%s'\n",
account->sent_folder_uri ? account->sent_folder_uri : "empty", uri));
if (account->sent_folder_uri) {
curi = em_uri_to_camel(account->sent_folder_uri);
found = camel_store_folder_uri_equal(store, uri, curi);
g_free(curi);
}
if (!found && account->drafts_folder_uri) {
curi = em_uri_to_camel(account->drafts_folder_uri);
found = camel_store_folder_uri_equal(store, uri, curi);
g_free(curi);
}
if (found)
break;
e_iterator_next (iter);
}
g_object_unref (iter);
return found;
}
/* so special we never use it */
static int
uri_is_spethal(CamelStore *store, const char *uri)
{
CamelURL *url;
int res;
/* This is a bit of a hack, but really the only way it can be done at the moment. */
if ((store->flags & (CAMEL_STORE_VTRASH|CAMEL_STORE_VJUNK)) == 0)
return FALSE;
url = camel_url_new(uri, NULL);
if (url == NULL)
return TRUE;
/* don't use strcasecmp here */
if (url->fragment) {
res = (((store->flags & CAMEL_STORE_VTRASH)
&& strcmp(url->fragment, CAMEL_VTRASH_NAME) == 0)
|| ((store->flags & CAMEL_STORE_VJUNK)
&& strcmp(url->fragment, CAMEL_VJUNK_NAME) == 0));
} else {
res = url->path
&& (((store->flags & CAMEL_STORE_VTRASH)
&& strcmp(url->path, "/" CAMEL_VTRASH_NAME) == 0)
|| ((store->flags & CAMEL_STORE_VJUNK)
&& strcmp(url->path, "/" CAMEL_VJUNK_NAME) == 0));
}
camel_url_free(url);
return res;
}
/* called when a new uri becomes (un)available */
void
mail_vfolder_add_uri(CamelStore *store, const char *curi, int remove)
{
FilterRule *rule;
const char *source;
CamelVeeFolder *vf;
GList *folders = NULL, *link;
int remote = (((CamelService *)store)->provider->flags & CAMEL_PROVIDER_IS_REMOTE) != 0;
int is_ignore;
char *uri;
uri = em_uri_from_camel(curi);
if (context == NULL || uri_is_spethal(store, curi)) {
g_free(uri);
return;
}
g_assert(pthread_self() == mail_gui_thread);
is_ignore = uri_is_ignore(store, curi);
LOCK();
d(printf("%s uri to check: %s\n", remove?"Removing":"Adding", uri));
/* maintain the source folders lists for changed rules later on */
if (CAMEL_IS_VEE_STORE(store)) {
is_ignore = TRUE;
} else if (remove) {
if (remote) {
if ((link = mv_find_folder(source_folders_remote, store, curi)) != NULL) {
g_free(link->data);
source_folders_remote = g_list_remove_link(source_folders_remote, link);
}
} else {
if ((link = mv_find_folder(source_folders_local, store, curi)) != NULL) {
g_free(link->data);
source_folders_local = g_list_remove_link(source_folders_local, link);
}
}
} else if (!is_ignore) {
/* we ignore drafts/sent/outbox here */
if (remote) {
if (mv_find_folder(source_folders_remote, store, curi) == NULL)
source_folders_remote = g_list_prepend(source_folders_remote, g_strdup(curi));
} else {
if (mv_find_folder(source_folders_local, store, curi) == NULL)
source_folders_local = g_list_prepend(source_folders_local, g_strdup(curi));
}
}
rule = NULL;
while ((rule = rule_context_next_rule((RuleContext *)context, rule, NULL))) {
int found = FALSE;
if (!rule->name) {
d(printf("invalid rule (%p): rule->name is set to NULL\n", rule));
continue;
}
/* dont auto-add any sent/drafts folders etc, they must be explictly listed as a source */
if (rule->source
&& !is_ignore
&& ((((EMVFolderRule *)rule)->with == EM_VFOLDER_RULE_WITH_LOCAL && !remote)
|| (((EMVFolderRule *)rule)->with == EM_VFOLDER_RULE_WITH_REMOTE_ACTIVE && remote)
|| (((EMVFolderRule *)rule)->with == EM_VFOLDER_RULE_WITH_LOCAL_REMOTE_ACTIVE)))
found = TRUE;
source = NULL;
while (!found && (source = em_vfolder_rule_next_source((EMVFolderRule *)rule, source))) {
char *csource;
csource = em_uri_to_camel(source);
found = camel_store_folder_uri_equal(store, curi, csource);
d(printf(found?" '%s' == '%s'?\n":" '%s' != '%s'\n", curi, csource));
g_free(csource);
}
if (found) {
vf = g_hash_table_lookup(vfolder_hash, rule->name);
g_assert(vf);
camel_object_ref(vf);
folders = g_list_prepend(folders, vf);
}
}
UNLOCK();
if (folders != NULL)
vfolder_adduri(curi, folders, remove);
g_free(uri);
}
/* called when a uri is deleted from a store */
void
mail_vfolder_delete_uri(CamelStore *store, const char *curi)
{
FilterRule *rule;
const char *source;
CamelVeeFolder *vf;
GString *changed;
char *uri;
GList *link;
if (context == NULL || uri_is_spethal(store, curi))
return;
uri = em_uri_from_camel(curi);
d(printf ("Deleting uri to check: %s\n", uri));
g_assert (pthread_self() == mail_gui_thread);
changed = g_string_new ("");
LOCK();
/* see if any rules directly reference this removed uri */
rule = NULL;
while ((rule = rule_context_next_rule ((RuleContext *) context, rule, NULL))) {
source = NULL;
while ((source = em_vfolder_rule_next_source ((EMVFolderRule *) rule, source))) {
char *csource = em_uri_to_camel(source);
/* Remove all sources that match, ignore changed events though
because the adduri call above does the work async */
if (camel_store_folder_uri_equal(store, curi, csource)) {
vf = g_hash_table_lookup (vfolder_hash, rule->name);
g_assert (vf != NULL);
g_signal_handlers_disconnect_matched (rule, G_SIGNAL_MATCH_FUNC|G_SIGNAL_MATCH_DATA, 0,
0, NULL, rule_changed, vf);
em_vfolder_rule_remove_source ((EMVFolderRule *)rule, source);
g_signal_connect (rule, "changed", G_CALLBACK(rule_changed), vf);
g_string_append_printf (changed, " %s\n", rule->name);
source = NULL;
}
g_free(csource);
}
}
if ((link = mv_find_folder(source_folders_remote, store, curi)) != NULL) {
g_free(link->data);
source_folders_remote = g_list_remove_link(source_folders_remote, link);
}
if ((link = mv_find_folder(source_folders_local, store, curi)) != NULL) {
g_free(link->data);
source_folders_local = g_list_remove_link(source_folders_local, link);
}
UNLOCK();
if (changed->str[0]) {
GtkWidget *dialog;
char *user;
dialog = e_error_new(NULL, "mail:vfolder-updated", changed->str, uri, NULL);
g_signal_connect_swapped (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
gtk_widget_show (dialog);
user = g_strdup_printf ("%s/mail/vfolders.xml",
mail_component_peek_base_directory (mail_component_peek ()));
rule_context_save ((RuleContext *) context, user);
g_free (user);
}
g_string_free (changed, TRUE);
g_free(uri);
}
/* called when a uri is renamed in a store */
void
mail_vfolder_rename_uri(CamelStore *store, const char *cfrom, const char *cto)
{
FilterRule *rule;
const char *source;
CamelVeeFolder *vf;
int changed = 0;
char *from, *to;
d(printf("vfolder rename uri: %s to %s\n", from, to));
if (context == NULL || uri_is_spethal(store, cfrom) || uri_is_spethal(store, cto))
return;
g_assert(pthread_self() == mail_gui_thread);
from = em_uri_from_camel(cfrom);
to = em_uri_from_camel(cto);
LOCK();
/* see if any rules directly reference this removed uri */
rule = NULL;
while ( (rule = rule_context_next_rule((RuleContext *)context, rule, NULL)) ) {
source = NULL;
while ( (source = em_vfolder_rule_next_source((EMVFolderRule *)rule, source)) ) {
char *csource = em_uri_to_camel(source);
/* Remove all sources that match, ignore changed events though
because the adduri call above does the work async */
if (camel_store_folder_uri_equal(store, cfrom, csource)) {
d(printf("Vfolder '%s' used '%s' ('%s') now uses '%s'\n", rule->name, source, from, to));
vf = g_hash_table_lookup(vfolder_hash, rule->name);
g_assert(vf);
g_signal_handlers_disconnect_matched(rule, G_SIGNAL_MATCH_FUNC|G_SIGNAL_MATCH_DATA, 0,
0, NULL, rule_changed, vf);
em_vfolder_rule_remove_source((EMVFolderRule *)rule, source);
em_vfolder_rule_add_source((EMVFolderRule *)rule, to);
g_signal_connect(rule, "changed", G_CALLBACK(rule_changed), vf);
changed++;
source = NULL;
}
g_free(csource);
}
}
UNLOCK();
if (changed) {
char *user;
d(printf("Vfolders updated from renamed folder\n"));
user = g_strdup_printf("%s/mail/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ()));
rule_context_save((RuleContext *)context, user);
g_free(user);
}
g_free(from);
g_free(to);
}
/* ********************************************************************** */
static void context_rule_added(RuleContext *ctx, FilterRule *rule);
static void
rule_add_sources(GList *l, GList **sources_folderp, GList **sources_urip)
{
GList *sources_folder = *sources_folderp;
GList *sources_uri = *sources_urip;
CamelFolder *newfolder;
while (l) {
char *curi = em_uri_to_camel(l->data);
if (mail_note_get_folder_from_uri(curi, &newfolder)) {
if (newfolder)
sources_folder = g_list_append(sources_folder, newfolder);
else
sources_uri = g_list_append(sources_uri, g_strdup(curi));
}
g_free(curi);
l = l->next;
}
*sources_folderp = sources_folder;
*sources_urip = sources_uri;
}
static void
rule_changed(FilterRule *rule, CamelFolder *folder)
{
GList *sources_uri = NULL, *sources_folder = NULL;
GString *query;
/* if the folder has changed name, then add it, then remove the old manually */
if (strcmp(folder->full_name, rule->name) != 0) {
char *key, *oldname;
CamelFolder *old;
LOCK();
d(printf("Changing folder name in hash table to '%s'\n", rule->name));
if (g_hash_table_lookup_extended(vfolder_hash, folder->full_name, (void **)&key, (void **)&old)) {
g_hash_table_remove(vfolder_hash, key);
g_free(key);
g_hash_table_insert(vfolder_hash, g_strdup(rule->name), folder);
UNLOCK();
} else {
UNLOCK();
g_warning("couldn't find a vfolder rule in our table? %s", folder->full_name);
}
/* TODO: make the folder->full_name var thread accessible */
oldname = g_strdup(folder->full_name);
camel_store_rename_folder(vfolder_store, oldname, rule->name, NULL);
g_free(oldname);
}
d(printf("Filter rule changed? for folder '%s'!!\n", folder->name));
/* find any (currently available) folders, and add them to the ones to open */
rule_add_sources(((EMVFolderRule *)rule)->sources, &sources_folder, &sources_uri);
LOCK();
if (((EMVFolderRule *)rule)->with == EM_VFOLDER_RULE_WITH_LOCAL || ((EMVFolderRule *)rule)->with == EM_VFOLDER_RULE_WITH_LOCAL_REMOTE_ACTIVE)
rule_add_sources(source_folders_local, &sources_folder, &sources_uri);
if (((EMVFolderRule *)rule)->with == EM_VFOLDER_RULE_WITH_REMOTE_ACTIVE || ((EMVFolderRule *)rule)->with == EM_VFOLDER_RULE_WITH_LOCAL_REMOTE_ACTIVE)
rule_add_sources(source_folders_remote, &sources_folder, &sources_uri);
UNLOCK();
query = g_string_new("");
filter_rule_build_code(rule, query);
vfolder_setup(folder, query->str, sources_uri, sources_folder);
g_string_free(query, TRUE);
}
static void context_rule_added(RuleContext *ctx, FilterRule *rule)
{
CamelFolder *folder;
d(printf("rule added: %s\n", rule->name));
/* this always runs quickly */
folder = camel_store_get_folder(vfolder_store, rule->name, 0, NULL);
if (folder) {
g_signal_connect(rule, "changed", G_CALLBACK(rule_changed), folder);
LOCK();
g_hash_table_insert(vfolder_hash, g_strdup(rule->name), folder);
UNLOCK();
rule_changed(rule, folder);
}
}
static void context_rule_removed(RuleContext *ctx, FilterRule *rule)
{
char *key, *path;
CamelFolder *folder = NULL;
d(printf("rule removed; %s\n", rule->name));
/* TODO: remove from folder info cache? */
/* FIXME: is this even necessary? if we remove the folder from
* the CamelStore, the tree should pick it up auto-magically
* because it listens to CamelStore events... */
path = g_strdup_printf("/%s", rule->name);
mail_component_remove_folder (mail_component_peek (), vfolder_store, path);
g_free(path);
LOCK();
if (g_hash_table_lookup_extended(vfolder_hash, rule->name, (void **)&key, (void **)&folder)) {
g_hash_table_remove(vfolder_hash, key);
g_free(key);
}
UNLOCK();
camel_store_delete_folder(vfolder_store, rule->name, NULL);
/* this must be unref'd after its deleted */
if (folder)
camel_object_unref(folder);
}
static void
store_folder_created(CamelObject *o, void *event_data, void *data)
{
CamelStore *store = (CamelStore *)o;
CamelFolderInfo *info = event_data;
store = store;
info = info;
}
static void
store_folder_deleted(CamelObject *o, void *event_data, void *data)
{
CamelStore *store = (CamelStore *)o;
CamelFolderInfo *info = event_data;
FilterRule *rule;
char *user;
d(printf("Folder deleted: %s\n", info->name));
store = store;
/* Warning not thread safe, but might be enough */
LOCK();
/* delete it from our list */
rule = rule_context_find_rule((RuleContext *)context, info->full_name, NULL);
if (rule) {
/* We need to stop listening to removed events, otherwise we'll try and remove it again */
g_signal_handlers_disconnect_matched(context, G_SIGNAL_MATCH_FUNC|G_SIGNAL_MATCH_DATA, 0,
0, NULL, context_rule_removed, context);
rule_context_remove_rule((RuleContext *)context, rule);
g_object_unref(rule);
g_signal_connect(context, "rule_removed", G_CALLBACK(context_rule_removed), context);
user = g_strdup_printf("%s/mail/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ()));
rule_context_save((RuleContext *)context, user);
g_free(user);
} else {
g_warning("Cannot find rule for deleted vfolder '%s'", info->name);
}
UNLOCK();
}
static void
store_folder_renamed(CamelObject *o, void *event_data, void *data)
{
CamelRenameInfo *info = event_data;
FilterRule *rule;
char *user;
char *key;
CamelFolder *folder;
/* This should be more-or-less thread-safe */
d(printf("Folder renamed to '%s' from '%s'\n", info->new->full_name, info->old_base));
/* Folder is already renamed? */
LOCK();
d(printf("Changing folder name in hash table to '%s'\n", info->new->full_name));
if (g_hash_table_lookup_extended(vfolder_hash, info->old_base, (void **)&key, (void **)&folder)) {
g_hash_table_remove(vfolder_hash, key);
g_free(key);
g_hash_table_insert(vfolder_hash, g_strdup(info->new->full_name), folder);
rule = rule_context_find_rule((RuleContext *)context, info->old_base, NULL);
g_assert(rule);
g_signal_handlers_disconnect_matched(rule, G_SIGNAL_MATCH_FUNC|G_SIGNAL_MATCH_DATA, 0,
0, NULL, rule_changed, folder);
filter_rule_set_name(rule, info->new->full_name);
g_signal_connect(rule, "changed", G_CALLBACK(rule_changed), folder);
user = g_strdup_printf("%s/mail/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ()));
rule_context_save((RuleContext *)context, user);
g_free(user);
UNLOCK();
} else {
UNLOCK();
g_warning("couldn't find a vfolder rule in our table? %s", info->new->full_name);
}
}
void
vfolder_load_storage(void)
{
char *user, *storeuri;
FilterRule *rule;
vfolder_hash = g_hash_table_new(g_str_hash, g_str_equal);
/* first, create the vfolder store, and set it up */
storeuri = g_strdup_printf("vfolder:%s/mail/vfolder", mail_component_peek_base_directory (mail_component_peek ()));
vfolder_store = camel_session_get_store(session, storeuri, NULL);
if (vfolder_store == NULL) {
g_warning("Cannot open vfolder store - no vfolders available");
return;
}
camel_object_hook_event(vfolder_store, "folder_created",
(CamelObjectEventHookFunc)store_folder_created, NULL);
camel_object_hook_event(vfolder_store, "folder_deleted",
(CamelObjectEventHookFunc)store_folder_deleted, NULL);
camel_object_hook_event(vfolder_store, "folder_renamed",
(CamelObjectEventHookFunc)store_folder_renamed, NULL);
d(printf("got store '%s' = %p\n", storeuri, vfolder_store));
mail_component_load_store_by_uri (mail_component_peek (), storeuri, _("vFolders"));
/* load our rules */
user = g_strdup_printf ("%s/mail/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ()));
context = em_vfolder_context_new ();
if (rule_context_load ((RuleContext *)context,
EVOLUTION_PRIVDATADIR "/vfoldertypes.xml", user) != 0) {
g_warning("cannot load vfolders: %s\n", ((RuleContext *)context)->error);
}
g_free (user);
g_signal_connect(context, "rule_added", G_CALLBACK(context_rule_added), context);
g_signal_connect(context, "rule_removed", G_CALLBACK(context_rule_removed), context);
/* and setup the rules we have */
rule = NULL;
while ( (rule = rule_context_next_rule((RuleContext *)context, rule, NULL)) ) {
if (rule->name)
context_rule_added((RuleContext *)context, rule);
else
d(printf("invalid rule (%p) encountered: rule->name is NULL\n", rule));
}
g_free(storeuri);
}
void
vfolder_revert(void)
{
char *user;
d(printf("vfolder_revert\n"));
user = g_strdup_printf("%s/mail/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ()));
rule_context_revert((RuleContext *)context, user);
g_free(user);
}
static GtkWidget *vfolder_editor = NULL;
static void
em_vfolder_editor_response (GtkWidget *dialog, int button, void *data)
{
char *user;
user = g_strdup_printf ("%s/mail/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ()));
switch(button) {
case GTK_RESPONSE_OK:
rule_context_save((RuleContext *)context, user);
break;
default:
rule_context_revert((RuleContext *)context, user);
}
vfolder_editor = NULL;
gtk_widget_destroy(dialog);
g_free (user);
}
void
vfolder_edit (void)
{
if (vfolder_editor) {
gdk_window_raise (GTK_WIDGET (vfolder_editor)->window);
return;
}
vfolder_editor = GTK_WIDGET (em_vfolder_editor_new (context));
gtk_window_set_title (GTK_WINDOW (vfolder_editor), _("vFolders"));
g_signal_connect(vfolder_editor, "response", G_CALLBACK(em_vfolder_editor_response), NULL);
gtk_widget_show (vfolder_editor);
}
static void
edit_rule_response(GtkWidget *w, int button, void *data)
{
if (button == GTK_RESPONSE_OK) {
char *user;
FilterRule *rule = g_object_get_data (G_OBJECT (w), "rule");
FilterRule *orig = g_object_get_data (G_OBJECT (w), "orig");
filter_rule_copy(orig, rule);
user = g_strdup_printf("%s/mail/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ()));
rule_context_save((RuleContext *)context, user);
g_free(user);
}
gtk_widget_destroy(w);
}
void
vfolder_edit_rule(const char *uri)
{
GtkWidget *w;
GtkDialog *gd;
FilterRule *rule, *newrule;
CamelURL *url;
url = camel_url_new(uri, NULL);
if (url && url->fragment
&& (rule = rule_context_find_rule((RuleContext *)context, url->fragment, NULL))) {
g_object_ref((GtkObject *)rule);
newrule = filter_rule_clone(rule);
w = filter_rule_get_widget((FilterRule *)newrule, (RuleContext *)context);
gd = (GtkDialog *)gtk_dialog_new_with_buttons(_("Edit vFolder"), NULL,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL,
GTK_RESPONSE_CANCEL,
GTK_STOCK_OK,
GTK_RESPONSE_OK,
NULL);
gtk_container_set_border_width (GTK_CONTAINER (gd), 6);
gtk_box_set_spacing ((GtkBox *) gd->vbox, 6);
gtk_dialog_set_default_response(gd, GTK_RESPONSE_OK);
g_object_set(gd, "allow_shrink", FALSE, "allow_grow", TRUE, NULL);
gtk_window_set_default_size (GTK_WINDOW (gd), 500, 500);
gtk_box_pack_start((GtkBox *)gd->vbox, w, TRUE, TRUE, 0);
gtk_widget_show((GtkWidget *)gd);
g_object_set_data_full(G_OBJECT(gd), "rule", newrule, (GtkDestroyNotify)g_object_unref);
g_object_set_data_full(G_OBJECT(gd), "orig", rule, (GtkDestroyNotify)g_object_unref);
g_signal_connect(gd, "response", G_CALLBACK(edit_rule_response), NULL);
gtk_widget_show((GtkWidget *)gd);
} else {
/* TODO: we should probably just create it ... */
e_error_run(NULL, "mail:vfolder-notexist", uri, NULL);
}
if (url)
camel_url_free(url);
}
static void
new_rule_clicked(GtkWidget *w, int button, void *data)
{
if (button == GTK_RESPONSE_OK) {
char *user;
FilterRule *rule = g_object_get_data((GObject *)w, "rule");
if (!filter_rule_validate(rule)) {
/* no need to popup a dialog because the validate code does that. */
return;
}
if (rule_context_find_rule ((RuleContext *)context, rule->name, rule->source)) {
e_error_run((GtkWindow *)w, "mail:vfolder-notunique", rule->name, NULL);
return;
}
g_object_ref(rule);
rule_context_add_rule((RuleContext *)context, rule);
user = g_strdup_printf("%s/mail/vfolders.xml", mail_component_peek_base_directory (mail_component_peek ()));
rule_context_save((RuleContext *)context, user);
g_free(user);
}
gtk_widget_destroy(w);
}
FilterPart *
vfolder_create_part(const char *name)
{
return rule_context_create_part((RuleContext *)context, name);
}
/* clones a filter/search rule into a matching vfolder rule (assuming the same system definitions) */
FilterRule *
vfolder_clone_rule(FilterRule *in)
{
FilterRule *rule = (FilterRule *)em_vfolder_rule_new();
xmlNodePtr xml;
xml = filter_rule_xml_encode(in);
filter_rule_xml_decode(rule, xml, (RuleContext *)context);
xmlFreeNodeList(xml);
return rule;
}
/* adds a rule with a gui */
void
vfolder_gui_add_rule(EMVFolderRule *rule)
{
GtkWidget *w;
GtkDialog *gd;
w = filter_rule_get_widget((FilterRule *)rule, (RuleContext *)context);
gd = (GtkDialog *)gtk_dialog_new_with_buttons(_("New vFolder"),
NULL,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL,
GTK_RESPONSE_CANCEL,
GTK_STOCK_OK,
GTK_RESPONSE_OK,
NULL);
gtk_dialog_set_default_response(gd, GTK_RESPONSE_OK);
gtk_container_set_border_width (GTK_CONTAINER (gd), 6);
gtk_box_set_spacing ((GtkBox *) gd->vbox, 6);
g_object_set(gd, "allow_shrink", FALSE, "allow_grow", TRUE, NULL);
gtk_window_set_default_size (GTK_WINDOW (gd), 500, 500);
gtk_box_pack_start((GtkBox *)gd->vbox, w, TRUE, TRUE, 0);
gtk_widget_show((GtkWidget *)gd);
g_object_set_data_full(G_OBJECT(gd), "rule", rule, (GtkDestroyNotify)g_object_unref);
g_signal_connect(gd, "response", G_CALLBACK(new_rule_clicked), NULL);
gtk_widget_show((GtkWidget *)gd);
}
void
vfolder_gui_add_from_message(CamelMimeMessage *msg, int flags, const char *source)
{
EMVFolderRule *rule;
g_return_if_fail (msg != NULL);
rule = (EMVFolderRule*)em_vfolder_rule_from_message(context, msg, flags, source);
vfolder_gui_add_rule(rule);
}
void
vfolder_gui_add_from_address(CamelInternetAddress *addr, int flags, const char *source)
{
EMVFolderRule *rule;
g_return_if_fail (addr != NULL);
rule = (EMVFolderRule*)em_vfolder_rule_from_address(context, addr, flags, source);
vfolder_gui_add_rule(rule);
}
static void
vfolder_foreach_cb (gpointer key, gpointer data, gpointer user_data)
{
CamelFolder *folder = CAMEL_FOLDER (data);
if (folder)
camel_object_unref(folder);
g_free (key);
}
void
mail_vfolder_shutdown (void)
{
shutdown = 1;
g_hash_table_foreach (vfolder_hash, vfolder_foreach_cb, NULL);
g_hash_table_destroy (vfolder_hash);
vfolder_hash = NULL;
if (vfolder_store) {
camel_object_unref (vfolder_store);
vfolder_store = NULL;
}
if (context) {
g_object_unref(context);
context = NULL;
}
}