529 lines
15 KiB
C
529 lines
15 KiB
C
/*
|
|
* 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.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 *content_area;
|
|
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);
|
|
|
|
content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
|
|
gtk_box_pack_start (GTK_BOX (content_area), 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;
|
|
}
|