/*
* 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 <http://www.gnu.org/licenses/>
*
*
* Authors:
* Chris Toshok <toshok@ximian.com>
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#include <config.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <libedataserver/e-data-server-util.h>
#include <libedataserverui/e-source-selector.h>
#include <e-util/e-util.h>
#include "eab-gui-util.h"
#include "util/eab-book-util.h"
#include <libebook/e-destination.h>
#include "e-util/e-alert-dialog.h"
#include "e-util/e-html-utils.h"
#include "shell/e-shell.h"
#include "misc/e-image-chooser.h"
#include <e-util/e-icon-factory.h>
#include "eab-contact-merging.h"
/* we link to camel for decoding quoted printable email addresses */
#include <camel/camel-mime-utils.h>
#include "addressbook/util/addressbook.h"
/* the NULL's in this table correspond to the status codes
that should *never* be generated by a backend */
static const gchar *status_to_string[] = {
/* E_BOOK_ERROR_OK */ N_("Success"),
/* E_BOOK_ERROR_INVALID_ARG */ NULL,
/* E_BOOK_ERROR_BUSY */ N_("Backend busy"),
/* E_BOOK_ERROR_REPOSITORY_OFFLINE */ N_("Repository offline"),
/* E_BOOK_ERROR_NO_SUCH_BOOK */ N_("Address Book does not exist"),
/* E_BOOK_ERROR_NO_SELF_CONTACT */ N_("No Self Contact defined"),
/* E_BOOK_ERROR_URI_NOT_LOADED */ NULL,
/* E_BOOK_ERROR_URI_ALREADY_LOADED */ NULL,
/* E_BOOK_ERROR_PERMISSION_DENIED */ N_("Permission denied"),
/* E_BOOK_ERROR_CONTACT_NOT_FOUND */ N_("Contact not found"),
/* E_BOOK_ERROR_CONTACT_ID_ALREADY_EXISTS */ N_("Contact ID already exists"),
/* E_BOOK_ERROR_PROTOCOL_NOT_SUPPORTED */ N_("Protocol not supported"),
/* E_BOOK_ERROR_CANCELLED */ N_("Canceled"),
/* E_BOOK_ERROR_COULD_NOT_CANCEL */ N_("Could not cancel"),
/* E_BOOK_ERROR_AUTHENTICATION_FAILED */ N_("Authentication Failed"),
/* E_BOOK_ERROR_AUTHENTICATION_REQUIRED */ N_("Authentication Required"),
/* E_BOOK_ERROR_TLS_NOT_AVAILABLE */ N_("TLS not Available"),
/* E_BOOK_ERROR_CORBA_EXCEPTION */ NULL,
/* E_BOOK_ERROR_NO_SUCH_SOURCE */ N_("No such source"),
/* E_BOOK_ERROR_OFFLINE_UNAVAILABLE */ N_("Not available in offline mode"),
/* E_BOOK_ERROR_OTHER_ERROR */ N_("Other error"),
/* E_BOOK_ERROR_INVALID_SERVER_VERSION */ N_("Invalid server version"),
/* E_BOOK_ERROR_UNSUPPORTED_AUTHENTICATION_METHOD */ N_("Unsupported authentication method")
};
void
eab_error_dialog (const gchar *msg, EBookStatus status)
{
const gchar *status_str;
if (status >= G_N_ELEMENTS (status_to_string))
status_str = "Other error";
else
status_str = status_to_string [status];
if (status_str)
e_alert_run_dialog_for_args (e_shell_get_active_window (NULL),
"addressbook:generic-error",
msg, _(status_str), NULL);
}
void
eab_load_error_dialog (GtkWidget *parent, ESource *source, EBookStatus status)
{
gchar *label_string, *label = NULL, *uri;
GtkWidget *dialog;
gboolean can_detail_error = TRUE;
g_return_if_fail (source != NULL);
uri = e_source_get_uri (source);
if (status == E_BOOK_ERROR_OFFLINE_UNAVAILABLE) {
can_detail_error = FALSE;
label_string = _("This address book cannot be opened. This either means this "
"book is not marked for offline usage or not yet downloaded "
"for offline usage. Please load the address book once in online mode "
"to download its contents.");
}
else if (uri && !strncmp (uri, "file:", 5)) {
gchar *path = g_filename_from_uri (uri, NULL, NULL);
label = g_strdup_printf (
_("This address book cannot be opened. Please check that the "
"path %s exists and that permissions are set to access it."), path);
g_free (path);
label_string = label;
}
#ifndef HAVE_LDAP
else if (uri && !strncmp (uri, "ldap:", 5)) {
/* special case for ldap: contact folders so we can tell the user about openldap */
can_detail_error = FALSE;
label_string =
_("This version of Evolution does not have LDAP support "
"compiled in to it. To use LDAP in Evolution "
"an LDAP-enabled Evolution package must be installed.");
}
#endif
else {
/* other network folders (or if ldap is enabled and server is unreachable) */
label_string =
_("This address book cannot be opened. This either "
"means that an incorrect URI was entered, or the server "
"is unreachable.");
}
if (can_detail_error) {
/* do not show repository offline message, it's kind of generic error */
if (status != E_BOOK_ERROR_REPOSITORY_OFFLINE && status > 0 && status < G_N_ELEMENTS (status_to_string) && status_to_string [status]) {
label = g_strconcat (label_string, "\n\n", _("Detailed error message:"), " ", _(status_to_string [status]), NULL);
label_string = label;
}
}
dialog = e_alert_dialog_new_for_args ((GtkWindow *) parent, "addressbook:load-error", label_string, NULL);
g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
gtk_widget_show (dialog);
g_free (label);
g_free (uri);
}
void
eab_search_result_dialog (GtkWidget *parent,
EBookViewStatus status)
{
gchar *str = NULL;
switch (status) {
case E_BOOK_VIEW_STATUS_OK:
return;
case E_BOOK_VIEW_STATUS_SIZE_LIMIT_EXCEEDED:
str = _("More cards matched this query than either the server is \n"
"configured to return or Evolution is configured to display.\n"
"Please make your search more specific or raise the result limit in\n"
"the directory server preferences for this address book.");
break;
case E_BOOK_VIEW_STATUS_TIME_LIMIT_EXCEEDED:
str = _("The time to execute this query exceeded the server limit or the limit\n"
"configured for this address book. Please make your search\n"
"more specific or raise the time limit in the directory server\n"
"preferences for this address book.");
break;
case E_BOOK_VIEW_ERROR_INVALID_QUERY:
str = _("The backend for this address book was unable to parse this query.");
break;
case E_BOOK_VIEW_ERROR_QUERY_REFUSED:
str = _("The backend for this address book refused to perform this query.");
break;
case E_BOOK_VIEW_ERROR_OTHER_ERROR:
str = _("This query did not complete successfully.");
break;
default:
g_return_if_reached ();
}
e_alert_run_dialog_for_args ((GtkWindow *) parent, "addressbook:search-error", str, NULL);
}
gint
eab_prompt_save_dialog (GtkWindow *parent)
{
return e_alert_run_dialog_for_args (parent, "addressbook:prompt-save", NULL);
}
static gchar *
make_safe_filename (gchar *name)
{
gchar *safe;
if (!name) {
/* This is a filename. Translators take note. */
name = _("card.vcf");
}
if (!g_strrstr (name, ".vcf"))
safe = g_strdup_printf ("%s%s", name, ".vcf");
else
safe = g_strdup (name);
e_filename_make_safe (safe);
return safe;
}
static void
source_selection_changed_cb (ESourceSelector *selector, GtkWidget *ok_button)
{
ESource *except_source = NULL, *selected;
except_source = g_object_get_data (G_OBJECT (ok_button), "except-source");
selected = e_source_selector_peek_primary_selection (selector);
gtk_widget_set_sensitive (ok_button, selected && selected != except_source);
}
ESource *
eab_select_source (ESource *except_source, const gchar *title, const gchar *message, const gchar *select_uid, GtkWindow *parent)
{
ESource *source;
ESourceList *source_list;
GtkWidget *dialog;
GtkWidget *ok_button;
/* GtkWidget *label; */
GtkWidget *selector;
GtkWidget *scrolled_window;
gint response;
if (!e_book_get_addressbooks (&source_list, NULL))
return NULL;
dialog = gtk_dialog_new_with_buttons (_("Select Address Book"), parent,
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
NULL);
gtk_window_set_default_size (GTK_WINDOW (dialog), 350, 300);
gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
ok_button = gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
gtk_widget_set_sensitive (ok_button, FALSE);
/* label = gtk_label_new (message); */
selector = e_source_selector_new (source_list);
e_source_selector_show_selection (E_SOURCE_SELECTOR (selector), FALSE);
if (except_source)
g_object_set_data (G_OBJECT (ok_button), "except-source", e_source_list_peek_source_by_uid (source_list, e_source_peek_uid (except_source)));
g_signal_connect (selector, "primary_selection_changed",
G_CALLBACK (source_selection_changed_cb), ok_button);
if (select_uid) {
source = e_source_list_peek_source_by_uid (source_list, select_uid);
if (source)
e_source_selector_set_primary_selection (E_SOURCE_SELECTOR (selector), source);
}
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN);
gtk_container_add (GTK_CONTAINER (scrolled_window), selector);
/* gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), label, FALSE, FALSE, 4); */
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), scrolled_window, TRUE, TRUE, 4);
gtk_widget_show_all (dialog);
response = gtk_dialog_run (GTK_DIALOG (dialog));
if (response == GTK_RESPONSE_ACCEPT)
source = e_source_selector_peek_primary_selection (E_SOURCE_SELECTOR (selector));
else
source = NULL;
gtk_widget_destroy (dialog);
return source;
}
gchar *
eab_suggest_filename (GList *contact_list)
{
gchar *current_name = NULL, *res;
g_return_val_if_fail (contact_list != NULL, NULL);
if (g_list_length (contact_list) == 1) {
EContact *contact = E_CONTACT (contact_list->data);
gchar *string;
string = e_contact_get (contact, E_CONTACT_FILE_AS);
if (string == NULL)
string = e_contact_get (contact, E_CONTACT_FULL_NAME);
if (string != NULL)
current_name = make_safe_filename (string);
g_free (string);
}
if (current_name == NULL)
current_name = make_safe_filename (_("list"));
res = g_strconcat (current_name, ".vcf", NULL);
g_free (current_name);
return res;
}
typedef struct ContactCopyProcess_ ContactCopyProcess;
typedef void (*ContactCopyDone) (ContactCopyProcess *process);
struct ContactCopyProcess_ {
gint count;
gboolean book_status;
GList *contacts;
EBook *source;
EBook *destination;
ContactCopyDone done_cb;
};
static void
do_delete (gpointer data, gpointer user_data)
{
EBook *book = user_data;
EContact *contact = data;
const gchar *id;
id = e_contact_get_const (contact, E_CONTACT_UID);
e_book_remove_contact(book, id, NULL);
}
static void
delete_contacts (ContactCopyProcess *process)
{
if (process->book_status == TRUE) {
g_list_foreach (process->contacts,
do_delete,
process->source);
}
}
static void
process_unref (ContactCopyProcess *process)
{
process->count --;
if (process->count == 0) {
if (process->done_cb)
process->done_cb (process);
g_list_foreach (
process->contacts,
(GFunc) g_object_unref, NULL);
g_list_free (process->contacts);
g_object_unref (process->source);
g_object_unref (process->destination);
g_free (process);
}
}
static void
contact_added_cb (EBook* book, EBookStatus status, const gchar *id, gpointer user_data)
{
ContactCopyProcess *process = user_data;
if (status != E_BOOK_ERROR_OK && status != E_BOOK_ERROR_CANCELLED) {
process->book_status = FALSE;
eab_error_dialog (_("Error adding contact"), status);
}
else if (status == E_BOOK_ERROR_CANCELLED) {
process->book_status = FALSE;
}
else {
/* success */
process->book_status = TRUE;
}
process_unref (process);
}
static void
do_copy (gpointer data, gpointer user_data)
{
EBook *book;
EContact *contact;
ContactCopyProcess *process;
process = user_data;
contact = data;
book = process->destination;
process->count ++;
eab_merging_book_add_contact(book, contact, contact_added_cb, process);
}
static void
got_book_cb (EBook *book, EBookStatus status, gpointer closure)
{
ContactCopyProcess *process;
process = closure;
if (status == E_BOOK_ERROR_OK) {
process->destination = book;
process->book_status = TRUE;
g_object_ref (book);
g_list_foreach (process->contacts,
do_copy,
process);
}
process_unref (process);
}
void
eab_transfer_contacts (EBook *source, GList *contacts /* adopted */, gboolean delete_from_source, GtkWindow *parent_window)
{
EBook *dest;
ESource *destination_source;
static gchar *last_uid = NULL;
ContactCopyProcess *process;
gchar *desc;
if (contacts == NULL)
return;
if (last_uid == NULL)
last_uid = g_strdup ("");
if (contacts->next == NULL) {
if (delete_from_source)
desc = _("Move contact to");
else
desc = _("Copy contact to");
} else {
if (delete_from_source)
desc = _("Move contacts to");
else
desc = _("Copy contacts to");
}
destination_source = eab_select_source (e_book_get_source (source), desc, NULL,
last_uid, parent_window);
if (!destination_source)
return;
if (strcmp (last_uid, e_source_peek_uid (destination_source)) != 0) {
g_free (last_uid);
last_uid = g_strdup (e_source_peek_uid (destination_source));
}
process = g_new (ContactCopyProcess, 1);
process->count = 1;
process->book_status = FALSE;
process->source = source;
g_object_ref (source);
process->contacts = contacts;
process->destination = NULL;
if (delete_from_source)
process->done_cb = delete_contacts;
else
process->done_cb = NULL;
dest = e_book_new (destination_source, NULL);
addressbook_load (dest, got_book_cb, process);
}
/* To parse something like...
=?UTF-8?Q?=E0=A4=95=E0=A4=95=E0=A4=AC=E0=A5=82=E0=A5=8B=E0=A5=87?=\t\n=?UTF-8?Q?=E0=A4=B0?=\t\n<aa@aa.ccom>
and return the decoded representation of name & email parts.
*/
gboolean
eab_parse_qp_email (const gchar *string, gchar **name, gchar **email)
{
struct _camel_header_address *address;
gboolean res = FALSE;
address = camel_header_address_decode (string, "UTF-8");
if (!address)
return FALSE;
/* report success only when we have filled both name and email address */
if (address->type == CAMEL_HEADER_ADDRESS_NAME && address->name && *address->name && address->v.addr && *address->v.addr) {
*name = g_strdup (address->name);
*email = g_strdup (address->v.addr);
res = TRUE;
}
camel_header_address_unref (address);
return res;
}
/* This is only wrapper to parse_qp_mail, it decodes string and if returned TRUE,
then makes one string and returns it, otherwise returns NULL.
Returned string is usable to place directly into GtkHtml stream.
Returned value should be freed with g_free. */
gchar *
eab_parse_qp_email_to_html (const gchar *string)
{
gchar *name = NULL, *mail = NULL;
gchar *html_name, *html_mail;
gchar *value;
if (!eab_parse_qp_email (string, &name, &mail))
return NULL;
html_name = e_text_to_html (name, 0);
html_mail = e_text_to_html (mail, E_TEXT_TO_HTML_CONVERT_ADDRESSES);
value = g_strdup_printf ("%s <%s>", html_name, html_mail);
g_free (html_name);
g_free (html_mail);
g_free (name);
g_free (mail);
return value;
}