/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see
*
*
* Authors:
* Jeffrey Stedfast
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "e-util/e-util.h"
#include "em-vfolder-editor-rule.h"
#include "libemail-engine/e-mail-folder-utils.h"
#include "libemail-engine/e-mail-session.h"
#include "libemail-engine/e-mail-store-utils.h"
#include "libemail-engine/e-mail-utils.h"
#include "libemail-engine/mail-folder-cache.h"
#include "libemail-engine/mail-mt.h"
#include "libemail-engine/mail-ops.h"
#include "libemail-engine/mail-tools.h"
#include "e-mail-ui-session.h"
#include "em-utils.h"
#include "em-folder-tree.h"
#include "em-folder-tree-model.h"
#include "em-folder-utils.h"
#include "em-folder-selector.h"
#include "em-folder-properties.h"
#include "mail-vfolder-ui.h"
#define d(x)
typedef struct _AsyncContext AsyncContext;
struct _AsyncContext {
EMFolderTree *folder_tree;
gchar *folder_uri;
};
static void
async_context_free (AsyncContext *context)
{
if (context->folder_tree != NULL)
g_object_unref (context->folder_tree);
g_free (context->folder_uri);
g_slice_free (AsyncContext, context);
}
static gboolean
emfu_is_special_local_folder (const gchar *name)
{
return (!strcmp (name, "Drafts") ||
!strcmp (name, "Inbox") ||
!strcmp (name, "Outbox") ||
!strcmp (name, "Sent") ||
!strcmp (name, "Templates"));
}
struct _EMCopyFolders {
MailMsg base;
/* input data */
CamelStore *fromstore;
CamelStore *tostore;
gchar *frombase;
gchar *tobase;
gint delete;
};
static gchar *
emft_copy_folders__desc (struct _EMCopyFolders *m,
gint complete)
{
if (m->delete)
return g_strdup_printf (_("Moving folder %s"), m->frombase);
else
return g_strdup_printf (_("Copying folder %s"), m->frombase);
}
static void
emft_copy_folders__exec (struct _EMCopyFolders *m,
GCancellable *cancellable,
GError **error)
{
guint32 flags;
GList *pending = NULL, *deleting = NULL, *l;
GString *fromname, *toname;
CamelFolderInfo *fi;
const gchar *tmp;
gint fromlen;
flags = CAMEL_STORE_FOLDER_INFO_FAST |
CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
/* If we're copying, then we need to copy every subfolder. If we're
* *moving*, though, then we only need to rename the top-level folder */
if (!m->delete)
flags |= CAMEL_STORE_FOLDER_INFO_RECURSIVE;
fi = camel_store_get_folder_info_sync (
m->fromstore, m->frombase, flags, cancellable, error);
if (fi == NULL)
return;
pending = g_list_append (pending, fi);
toname = g_string_new ("");
fromname = g_string_new ("");
tmp = strrchr (m->frombase, '/');
if (tmp == NULL)
fromlen = 0;
else
fromlen = tmp - m->frombase + 1;
d (printf ("top name is '%s'\n", fi->full_name));
while (pending) {
CamelFolderInfo *info = pending->data;
pending = g_list_remove_link (pending, pending);
while (info) {
CamelFolder *fromfolder, *tofolder;
GPtrArray *uids;
gint deleted = 0;
/* We still get immediate children even without the
* CAMEL_STORE_FOLDER_INFO_RECURSIVE flag. But we only
* want to process the children too if we're *copying * */
if (info->child && !m->delete)
pending = g_list_append (pending, info->child);
if (m->tobase[0])
g_string_printf (
toname, "%s/%s", m->tobase,
info->full_name + fromlen);
else
g_string_printf (
toname, "%s",
info->full_name + fromlen);
d (printf (
"Copying from '%s' to '%s'\n",
info->full_name, toname->str));
/* This makes sure we create the same tree,
* e.g. from a nonselectable source. */
/* Not sure if this is really the 'right thing',
* e.g. for spool stores, but it makes the ui work. */
if ((info->flags & CAMEL_FOLDER_NOSELECT) == 0) {
d (printf ("this folder is selectable\n"));
if (m->tostore == m->fromstore && m->delete) {
camel_store_rename_folder_sync (
m->fromstore,
info->full_name,
toname->str,
cancellable, error);
if (error && *error)
goto exception;
/* this folder no longer exists, unsubscribe it */
if (CAMEL_IS_SUBSCRIBABLE (m->fromstore))
camel_subscribable_unsubscribe_folder_sync (
CAMEL_SUBSCRIBABLE (m->fromstore),
info->full_name, NULL, NULL);
deleted = 1;
} else {
fromfolder = camel_store_get_folder_sync (
m->fromstore, info->full_name, 0,
cancellable, error);
if (fromfolder == NULL)
goto exception;
tofolder = camel_store_get_folder_sync (
m->tostore, toname->str,
CAMEL_STORE_FOLDER_CREATE,
cancellable, error);
if (tofolder == NULL) {
g_object_unref (fromfolder);
goto exception;
}
uids = camel_folder_get_uids (fromfolder);
camel_folder_transfer_messages_to_sync (
fromfolder, uids, tofolder,
m->delete, NULL,
cancellable, error);
camel_folder_free_uids (fromfolder, uids);
if (m->delete && (!error || !*error))
camel_folder_synchronize_sync (
fromfolder, TRUE,
NULL, NULL);
g_object_unref (fromfolder);
g_object_unref (tofolder);
}
}
if (error && *error)
goto exception;
else if (m->delete && !deleted)
deleting = g_list_prepend (deleting, info);
/* subscribe to the new folder if appropriate */
if (CAMEL_IS_SUBSCRIBABLE (m->tostore)
&& !camel_subscribable_folder_is_subscribed (
CAMEL_SUBSCRIBABLE (m->tostore),
toname->str))
camel_subscribable_subscribe_folder_sync (
CAMEL_SUBSCRIBABLE (m->tostore),
toname->str, NULL, NULL);
info = info->next;
}
}
/* Delete the folders in reverse order from how we copied them,
* if we are deleting any. */
l = deleting;
while (l) {
CamelFolderInfo *info = l->data;
d (printf ("deleting folder '%s'\n", info->full_name));
/* FIXME: we need to do something with the exception
* since otherwise the users sees a failed operation
* with no error message or even any warnings */
if (CAMEL_IS_SUBSCRIBABLE (m->fromstore))
camel_subscribable_unsubscribe_folder_sync (
CAMEL_SUBSCRIBABLE (m->fromstore),
info->full_name, NULL, NULL);
camel_store_delete_folder_sync (
m->fromstore, info->full_name, NULL, NULL);
l = l->next;
}
exception:
camel_store_free_folder_info (m->fromstore, fi);
g_list_free (deleting);
g_string_free (toname, TRUE);
g_string_free (fromname, TRUE);
}
static void
emft_copy_folders__free (struct _EMCopyFolders *m)
{
g_object_unref (m->fromstore);
g_object_unref (m->tostore);
g_free (m->frombase);
g_free (m->tobase);
}
static MailMsgInfo copy_folders_info = {
sizeof (struct _EMCopyFolders),
(MailMsgDescFunc) emft_copy_folders__desc,
(MailMsgExecFunc) emft_copy_folders__exec,
(MailMsgDoneFunc) NULL,
(MailMsgFreeFunc) emft_copy_folders__free
};
gint
em_folder_utils_copy_folders (CamelStore *fromstore,
const gchar *frombase,
CamelStore *tostore,
const gchar *tobase,
gint delete)
{
struct _EMCopyFolders *m;
gint seq;
m = mail_msg_new (©_folders_info);
g_object_ref (fromstore);
m->fromstore = fromstore;
g_object_ref (tostore);
m->tostore = tostore;
m->frombase = g_strdup (frombase);
m->tobase = g_strdup (tobase);
m->delete = delete;
seq = m->base.seq;
mail_msg_unordered_push (m);
return seq;
}
struct _copy_folder_data {
CamelStore *source_store;
gchar *source_folder_name;
gboolean delete;
};
static void
emfu_copy_folder_selected (EMailSession *session,
EAlertSink *alert_sink,
const gchar *uri,
gpointer data)
{
struct _copy_folder_data *cfd = data;
CamelProvider *provider, *toprovider;
CamelStore *tostore = NULL;
CamelService *service, *toservice;
gboolean store_is_local, tostore_is_local, session_is_online;
const gchar *display_name, *todisplay_name;
gchar *tobase = NULL;
gchar *folder_name = NULL, *tofolder_name = NULL;
GError *local_error = NULL;
if (uri == NULL)
goto fail;
session_is_online = camel_session_get_online (CAMEL_SESSION (session));
service = CAMEL_SERVICE (cfd->source_store);
provider = camel_service_get_provider (service);
store_is_local = (provider->flags & CAMEL_PROVIDER_IS_LOCAL) != 0;
display_name = camel_service_get_display_name (service);
folder_name = g_strdup_printf ("%s: %s", display_name, cfd->source_folder_name);
e_mail_folder_uri_parse (
CAMEL_SESSION (session), uri,
&tostore, &tobase, &local_error);
if (local_error != NULL) {
e_alert_submit (
alert_sink, cfd->delete ?
"mail:no-move-folder-to-nostore" :
"mail:no-copy-folder-to-nostore",
folder_name, uri,
local_error->message, NULL);
goto fail;
}
g_return_if_fail (CAMEL_IS_STORE (service));
toservice = CAMEL_SERVICE (tostore);
toprovider = camel_service_get_provider (toservice);
tostore_is_local = (toprovider->flags & CAMEL_PROVIDER_IS_LOCAL) != 0;
todisplay_name = camel_service_get_display_name (toservice);
tofolder_name = g_strdup_printf ("%s: %s", todisplay_name, tobase);
if (!session_is_online && (!store_is_local || !tostore_is_local)) {
e_alert_submit (
alert_sink,
"mail:online-operation",
store_is_local ? tofolder_name : folder_name,
NULL);
goto fail;
}
camel_service_connect_sync (service, NULL, &local_error);
if (local_error != NULL) {
e_alert_submit (
alert_sink, cfd->delete ?
"mail:no-move-folder-nostore" :
"mail:no-copy-folder-nostore",
folder_name, tofolder_name,
local_error->message, NULL);
goto fail;
}
if (cfd->delete && store_is_local &&
emfu_is_special_local_folder (cfd->source_folder_name)) {
e_alert_submit (
alert_sink,
"mail:no-rename-special-folder",
folder_name, NULL);
goto fail;
}
camel_service_connect_sync (toservice, NULL, &local_error);
if (local_error != NULL) {
e_alert_submit (
alert_sink, cfd->delete ?
"mail:no-move-folder-to-nostore" :
"mail:no-copy-folder-to-nostore",
folder_name, tofolder_name,
local_error->message, NULL);
goto fail;
}
g_return_if_fail (CAMEL_IS_STORE (tostore));
em_folder_utils_copy_folders (
cfd->source_store, cfd->source_folder_name,
tostore, tobase ? tobase : "", cfd->delete);
fail:
g_clear_error (&local_error);
g_object_unref (cfd->source_store);
g_free (cfd->source_folder_name);
g_free (cfd);
if (tostore)
g_object_unref (tostore);
g_free (tobase);
g_free (folder_name);
g_free (tofolder_name);
}
/* tree here is the 'destination' selector, not 'self' */
static gboolean
emfu_copy_folder_exclude (EMFolderTree *tree,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
struct _copy_folder_data *cfd = data;
CamelStore *store;
const gchar *uid;
gint fromvfolder, tovfolder;
guint flags;
/* handles moving to/from vfolders */
uid = camel_service_get_uid (CAMEL_SERVICE (cfd->source_store));
fromvfolder = (g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0);
gtk_tree_model_get (
model, iter,
COL_UINT_FLAGS, &flags,
COL_POINTER_CAMEL_STORE, &store, -1);
uid = camel_service_get_uid (CAMEL_SERVICE (store));
tovfolder = (g_strcmp0 (uid, E_MAIL_SESSION_VFOLDER_UID) == 0);
/* moving from vfolder to normal- not allowed */
if (fromvfolder && !tovfolder && cfd->delete)
return FALSE;
/* copy/move from normal folder to vfolder - not allowed */
if (!fromvfolder && tovfolder)
return FALSE;
/* copying to vfolder - not allowed */
if (tovfolder && !cfd->delete)
return FALSE;
return (flags & EMFT_EXCLUDE_NOINFERIORS) == 0;
}
void
em_folder_utils_copy_folder (GtkWindow *parent,
EMailSession *session,
EAlertSink *alert_sink,
const gchar *folder_uri,
gint delete)
{
GtkWidget *dialog;
EMFolderSelector *selector;
EMFolderTree *folder_tree;
EMFolderTreeModel *model;
const gchar *label;
const gchar *title;
struct _copy_folder_data *cfd;
GError *error = NULL;
g_return_if_fail (E_IS_MAIL_SESSION (session));
g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
g_return_if_fail (folder_uri != NULL);
cfd = g_malloc (sizeof (*cfd));
cfd->delete = delete;
e_mail_folder_uri_parse (
CAMEL_SESSION (session), folder_uri,
&cfd->source_store, &cfd->source_folder_name, &error);
if (error != NULL) {
e_notice (parent, GTK_MESSAGE_ERROR, "%s", error->message);
g_error_free (error);
g_free (cfd);
return;
}
label = delete ? _("_Move") : _("C_opy");
title = delete ? _("Move Folder To") : _("Copy Folder To");
model = em_folder_tree_model_get_default ();
dialog = em_folder_selector_new (
parent, model,
EM_FOLDER_SELECTOR_CAN_CREATE,
title, NULL, label);
selector = EM_FOLDER_SELECTOR (dialog);
folder_tree = em_folder_selector_get_folder_tree (selector);
em_folder_tree_set_excluded_func (
folder_tree, emfu_copy_folder_exclude, cfd);
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
const gchar *uri;
uri = em_folder_selector_get_selected_uri (selector);
emfu_copy_folder_selected (session, alert_sink, uri, cfd);
}
gtk_widget_destroy (dialog);
}
static void
new_folder_created_cb (CamelStore *store,
GAsyncResult *result,
AsyncContext *context)
{
GError *error = NULL;
e_mail_store_create_folder_finish (store, result, &error);
/* FIXME Use an EActivity here. */
if (error != NULL) {
e_notice (NULL, GTK_MESSAGE_ERROR, "%s", error->message);
g_error_free (error);
} else if (context->folder_tree != NULL) {
gpointer data;
gboolean expand_only;
/* XXX What in the hell kind of lazy hack is this? */
data = g_object_get_data (
G_OBJECT (context->folder_tree), "select");
expand_only = GPOINTER_TO_INT (data) ? FALSE : TRUE;
em_folder_tree_set_selected (
context->folder_tree,
context->folder_uri, expand_only);
}
async_context_free (context);
}
void
em_folder_utils_create_folder (GtkWindow *parent,
EMailSession *session,
EMFolderTree *emft,
const gchar *initial_uri)
{
EMFolderSelector *selector;
EMFolderTree *folder_tree;
EMFolderTreeModel *model;
EMailAccountStore *account_store;
CamelStore *store = NULL;
gchar *folder_name = NULL;
GtkWidget *dialog;
GQueue queue = G_QUEUE_INIT;
GError *error = NULL;
g_return_if_fail (GTK_IS_WINDOW (parent));
g_return_if_fail (E_IS_MAIL_SESSION (session));
model = em_folder_tree_model_new ();
em_folder_tree_model_set_session (model, session);
account_store = e_mail_ui_session_get_account_store (E_MAIL_UI_SESSION (session));
e_mail_account_store_queue_enabled_services (account_store, &queue);
while (!g_queue_is_empty (&queue)) {
CamelService *service;
CamelStoreFlags flags;
service = g_queue_pop_head (&queue);
g_warn_if_fail (CAMEL_IS_STORE (service));
flags = CAMEL_STORE (service)->flags;
if ((flags & CAMEL_STORE_CAN_EDIT_FOLDERS) == 0)
continue;
em_folder_tree_model_add_store (model, CAMEL_STORE (service));
}
dialog = em_folder_selector_create_new (
parent, model, 0,
_("Create Folder"),
_("Specify where to create the folder:"));
g_object_unref (model);
selector = EM_FOLDER_SELECTOR (dialog);
folder_tree = em_folder_selector_get_folder_tree (selector);
if (initial_uri != NULL)
em_folder_tree_set_selected (folder_tree, initial_uri, FALSE);
if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK)
goto exit;
if (em_folder_tree_store_root_selected (folder_tree, &store)) {
const gchar *folder_uri;
folder_uri = em_folder_selector_get_selected_uri (selector);
if (!folder_uri || !strrchr (folder_uri, '/'))
g_set_error (
&error, CAMEL_FOLDER_ERROR,
CAMEL_FOLDER_ERROR_INVALID,
_("Invalid folder URI '%s'"),
folder_uri ? folder_uri : "null");
else
folder_name = g_strdup (strrchr (folder_uri, '/'));
} else {
const gchar *folder_uri;
folder_uri = em_folder_selector_get_selected_uri (selector);
e_mail_folder_uri_parse (
CAMEL_SESSION (session), folder_uri,
&store, &folder_name, &error);
}
/* XXX This is unlikely to fail since the URI comes straight from
* EMFolderSelector, but leave a breadcrumb if it does fail. */
if (error != NULL) {
g_warn_if_fail (store == NULL);
g_warn_if_fail (folder_name == NULL);
e_notice (parent, GTK_MESSAGE_ERROR, "%s", error->message);
g_error_free (error);
goto exit;
}
g_return_if_fail (folder_name != NULL);
/* HACK: we need to create vfolders using the vfolder editor */
if (CAMEL_IS_VEE_STORE (store)) {
EFilterRule *rule;
const gchar *skip_slash;
if (*folder_name == '/')
skip_slash = folder_name + 1;
else
skip_slash = folder_name;
rule = em_vfolder_editor_rule_new (session);
e_filter_rule_set_name (rule, skip_slash);
vfolder_gui_add_rule (EM_VFOLDER_RULE (rule));
} else {
AsyncContext *context;
context = g_slice_new0 (AsyncContext);
context->folder_uri = e_mail_folder_uri_build (store, folder_name);
if (EM_IS_FOLDER_TREE (emft))
context->folder_tree = g_object_ref (emft);
/* FIXME Not passing a GCancellable. */
e_mail_store_create_folder (
store, folder_name, G_PRIORITY_DEFAULT, NULL,
(GAsyncReadyCallback) new_folder_created_cb,
context);
}
g_free (folder_name);
g_object_unref (store);
exit:
gtk_widget_destroy (dialog);
}
const gchar *
em_folder_utils_get_icon_name (guint32 flags)
{
const gchar *icon_name;
switch (flags & CAMEL_FOLDER_TYPE_MASK) {
case CAMEL_FOLDER_TYPE_INBOX:
icon_name = "mail-inbox";
break;
case CAMEL_FOLDER_TYPE_OUTBOX:
icon_name = "mail-outbox";
break;
case CAMEL_FOLDER_TYPE_TRASH:
icon_name = "user-trash";
break;
case CAMEL_FOLDER_TYPE_JUNK:
icon_name = "mail-mark-junk";
break;
case CAMEL_FOLDER_TYPE_SENT:
icon_name = "mail-sent";
break;
case CAMEL_FOLDER_TYPE_CONTACTS:
icon_name = "x-office-address-book";
break;
case CAMEL_FOLDER_TYPE_EVENTS:
icon_name = "x-office-calendar";
break;
case CAMEL_FOLDER_TYPE_MEMOS:
icon_name = "evolution-memos";
break;
case CAMEL_FOLDER_TYPE_TASKS:
icon_name = "evolution-tasks";
break;
default:
if (flags & CAMEL_FOLDER_SHARED_TO_ME)
icon_name = "stock_shared-to-me";
else if (flags & CAMEL_FOLDER_SHARED_BY_ME)
icon_name = "stock_shared-by-me";
else if (flags & CAMEL_FOLDER_VIRTUAL)
icon_name = "folder-saved-search";
else
icon_name = "folder";
}
return icon_name;
}