1 /*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
5 *
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 * for more details.
10 *
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
13 *
14 *
15 * Authors:
16 * Chris Toshok <toshok@ximian.com>
17 * Dan Vratil <dvratil@redhat.com>
18 *
19 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
20 *
21 */
22
23 #include "evolution-config.h"
24
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <locale.h>
30
31 #include <gtk/gtk.h>
32 #include <glib/gi18n.h>
33
34 #include "shell/e-shell.h"
35
36 #include "eab-gui-util.h"
37 #include "util/eab-book-util.h"
38 #include "eab-contact-merging.h"
39
40 /* we link to camel for decoding quoted printable email addresses */
41 #include <camel/camel.h>
42
43 /* Template tags for address format localization */
44 #define ADDRESS_REALNAME "%n" /* this is not used intentionally */
45 #define ADDRESS_REALNAME_UPPER "%N" /* this is not used intentionally */
46 #define ADDRESS_COMPANY "%m"
47 #define ADDRESS_COMPANY_UPPER "%M"
48 #define ADDRESS_POBOX "%p"
49 #define ADDRESS_STREET "%s"
50 #define ADDRESS_STREET_UPPER "%S"
51 #define ADDRESS_ZIPCODE "%z"
52 #define ADDRESS_LOCATION "%l"
53 #define ADDRESS_LOCATION_UPPER "%L"
54 #define ADDRESS_REGION "%r"
55 #define ADDRESS_REGION_UPPER "%R"
56 #define ADDRESS_CONDCOMMA "%," /* Conditional comma is removed when a surrounding tag is evaluated to zero */
57 #define ADDRESS_CONDWHITE "%w" /* Conditional whitespace is removed when a surrounding tag is evaluated to zero */
58 #define ADDRESS_COND_PURGEEMPTY "%0" /* Purge empty has following syntax: %0(...) and is removed when no tag within () is evaluated non-zero */
59
60 /* Fallback formats */
61 #define ADDRESS_DEFAULT_FORMAT "%0(%n\n)%0(%m\n)%0(%s\n)%0(PO BOX %p\n)%0(%l%w%r)%,%z"
62 #define ADDRESS_DEFAULT_COUNTRY_POSITION "below"
63
64 enum {
65 LOCALES_LANGUAGE = 0,
66 LOCALES_COUNTRY = 1
67 };
68
69 typedef enum {
70 ADDRESS_FORMAT_HOME = 0,
71 ADDRESS_FORMAT_BUSINESS = 1
72 } AddressFormat;
73
74 void
eab_error_dialog(EAlertSink * alert_sink,GtkWindow * parent,const gchar * msg,const GError * error)75 eab_error_dialog (EAlertSink *alert_sink,
76 GtkWindow *parent,
77 const gchar *msg,
78 const GError *error)
79 {
80 if (error && error->message) {
81 if (alert_sink)
82 e_alert_submit (
83 alert_sink,
84 "addressbook:generic-error",
85 msg, error->message, NULL);
86 else {
87 if (!parent)
88 parent = e_shell_get_active_window (NULL);
89
90 e_alert_run_dialog_for_args (
91 parent,
92 "addressbook:generic-error",
93 msg, error->message, NULL);
94 }
95 }
96 }
97
98 void
eab_load_error_dialog(GtkWidget * parent,EAlertSink * alert_sink,ESource * source,const GError * error)99 eab_load_error_dialog (GtkWidget *parent,
100 EAlertSink *alert_sink,
101 ESource *source,
102 const GError *error)
103 {
104 ESourceBackend *extension;
105 gchar *label_string, *label = NULL;
106 gboolean can_detail_error = TRUE;
107 const gchar *backend_name;
108 const gchar *extension_name;
109
110 g_return_if_fail (source != NULL);
111
112 extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
113 extension = e_source_get_extension (source, extension_name);
114 backend_name = e_source_backend_get_backend_name (extension);
115
116 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_OFFLINE_UNAVAILABLE)) {
117 can_detail_error = FALSE;
118 label_string =
119 _("This address book cannot be opened. This either "
120 "means this book is not marked for offline usage "
121 "or not yet downloaded for offline usage. Please "
122 "load the address book once in online mode to "
123 "download its contents.");
124 }
125
126 else if (g_strcmp0 (backend_name, "local") == 0) {
127 const gchar *user_data_dir;
128 const gchar *uid;
129 gchar *path;
130
131 uid = e_source_get_uid (source);
132 user_data_dir = e_get_user_data_dir ();
133
134 path = g_build_filename (
135 user_data_dir, "addressbook", uid, NULL);
136
137 label = g_strdup_printf (
138 _("This address book cannot be opened. Please check that the "
139 "path %s exists and that permissions are set to access it."), path);
140
141 g_free (path);
142 label_string = label;
143 }
144
145 #ifndef HAVE_LDAP
146 else if (g_strcmp0 (backend_name, "ldap") == 0) {
147 /* special case for ldap: contact folders so we can tell the user about openldap */
148
149 can_detail_error = FALSE;
150 label_string =
151 _("This version of Evolution does not have LDAP support "
152 "compiled in to it. To use LDAP in Evolution "
153 "an LDAP-enabled Evolution package must be installed.");
154
155 }
156 #endif
157 else {
158 /* other network folders (or if ldap is enabled and server is unreachable) */
159 label_string =
160 _("This address book cannot be opened. This either "
161 "means that an incorrect URI was entered, or the server "
162 "is unreachable.");
163 }
164
165 if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE)) {
166 /* Do not show a detailed message; too generic. */
167 } else if (can_detail_error && error != NULL) {
168 label = g_strconcat (
169 label_string, "\n\n",
170 _("Detailed error message:"),
171 " ", error->message, NULL);
172 label_string = label;
173 }
174
175 if (alert_sink) {
176 e_alert_submit (
177 alert_sink, "addressbook:load-error",
178 e_source_get_display_name (source),
179 label_string, NULL);
180 } else {
181 GtkWidget *dialog;
182
183 dialog = e_alert_dialog_new_for_args (
184 (GtkWindow *) parent,
185 "addressbook:load-error",
186 e_source_get_display_name (source),
187 label_string, NULL);
188 g_signal_connect (
189 dialog, "response",
190 G_CALLBACK (gtk_widget_destroy), NULL);
191 gtk_widget_show (dialog);
192 }
193
194 g_free (label);
195 }
196
197 void
eab_search_result_dialog(EAlertSink * alert_sink,const GError * error)198 eab_search_result_dialog (EAlertSink *alert_sink,
199 const GError *error)
200 {
201 gchar *str = NULL;
202
203 if (!error)
204 return;
205
206 if (error->domain == E_CLIENT_ERROR) {
207 switch (error->code) {
208 case E_CLIENT_ERROR_SEARCH_SIZE_LIMIT_EXCEEDED:
209 str = _("More cards matched this query than either the server is \n"
210 "configured to return or Evolution is configured to display.\n"
211 "Please make your search more specific or raise the result limit in\n"
212 "the directory server preferences for this address book.");
213 str = g_strdup (str);
214 break;
215 case E_CLIENT_ERROR_SEARCH_TIME_LIMIT_EXCEEDED:
216 str = _("The time to execute this query exceeded the server limit or the limit\n"
217 "configured for this address book. Please make your search\n"
218 "more specific or raise the time limit in the directory server\n"
219 "preferences for this address book.");
220 str = g_strdup (str);
221 break;
222 case E_CLIENT_ERROR_INVALID_QUERY:
223 /* Translators: %s is replaced with a detailed error message, or an empty string, if not provided */
224 str = _("The backend for this address book was unable to parse this query. %s");
225 str = g_strdup_printf (str, error->message);
226 break;
227 case E_CLIENT_ERROR_QUERY_REFUSED:
228 /* Translators: %s is replaced with a detailed error message, or an empty string, if not provided */
229 str = _("The backend for this address book refused to perform this query. %s");
230 str = g_strdup_printf (str, error->message);
231 break;
232 case E_CLIENT_ERROR_OTHER_ERROR:
233 default:
234 /* Translators: %s is replaced with a detailed error message, or an empty string, if not provided */
235 str = _("This query did not complete successfully. %s");
236 str = g_strdup_printf (str, error->message);
237 break;
238 }
239 } else {
240 /* Translators: %s is replaced with a detailed error message, or an empty string, if not provided */
241 str = _("This query did not complete successfully. %s");
242 str = g_strdup_printf (str, error->message);
243 }
244
245 e_alert_submit (alert_sink, "addressbook:search-error", str, NULL);
246
247 g_free (str);
248 }
249
250 gint
eab_prompt_save_dialog(GtkWindow * parent)251 eab_prompt_save_dialog (GtkWindow *parent)
252 {
253 return e_alert_run_dialog_for_args (parent, "addressbook:prompt-save", NULL);
254 }
255
256 static gchar *
make_safe_filename(gchar * name)257 make_safe_filename (gchar *name)
258 {
259 gchar *safe;
260
261 if (!name) {
262 /* This is a filename. Translators take note. */
263 name = _("card.vcf");
264 }
265
266 if (!g_strrstr (name, ".vcf"))
267 safe = g_strdup_printf ("%s%s", name, ".vcf");
268 else
269 safe = g_strdup (name);
270
271 e_util_make_safe_filename (safe);
272
273 return safe;
274 }
275
276 static void
source_selection_changed_cb(ESourceSelector * selector,GtkWidget * ok_button)277 source_selection_changed_cb (ESourceSelector *selector,
278 GtkWidget *ok_button)
279 {
280 ESource *except_source = NULL, *selected;
281 gboolean sensitive;
282
283 except_source = g_object_get_data (G_OBJECT (ok_button), "except-source");
284 selected = e_source_selector_ref_primary_selection (selector);
285
286 sensitive = (selected != NULL && selected != except_source);
287 gtk_widget_set_sensitive (ok_button, sensitive);
288
289 if (selected != NULL)
290 g_object_unref (selected);
291 }
292
293 ESource *
eab_select_source(ESourceRegistry * registry,ESource * except_source,const gchar * title,const gchar * message,const gchar * select_uid,GtkWindow * parent)294 eab_select_source (ESourceRegistry *registry,
295 ESource *except_source,
296 const gchar *title,
297 const gchar *message,
298 const gchar *select_uid,
299 GtkWindow *parent)
300 {
301 ESource *source;
302 GtkWidget *content_area;
303 GtkWidget *dialog;
304 GtkWidget *ok_button;
305 /* GtkWidget *label; */
306 GtkWidget *selector;
307 GtkWidget *scrolled_window;
308 const gchar *extension_name;
309 gint response;
310
311 g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
312
313 dialog = gtk_dialog_new_with_buttons (
314 _("Select Address Book"), parent,
315 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
316 _("_Cancel"), GTK_RESPONSE_CANCEL,
317 _("_OK"), GTK_RESPONSE_ACCEPT,
318 NULL);
319 gtk_window_set_default_size (GTK_WINDOW (dialog), 350, 300);
320
321 gtk_dialog_set_response_sensitive (
322 GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT, FALSE);
323
324 /* label = gtk_label_new (message); */
325
326 extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
327 selector = e_source_selector_new (registry, extension_name);
328 e_source_selector_set_show_toggles (
329 E_SOURCE_SELECTOR (selector), FALSE);
330
331 ok_button = gtk_dialog_get_widget_for_response (
332 GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
333
334 if (except_source)
335 g_object_set_data (
336 G_OBJECT (ok_button), "except-source", except_source);
337
338 g_signal_connect (
339 selector, "primary_selection_changed",
340 G_CALLBACK (source_selection_changed_cb), ok_button);
341
342 if (select_uid) {
343 source = e_source_registry_ref_source (registry, select_uid);
344 if (source != NULL) {
345 e_source_selector_set_primary_selection (
346 E_SOURCE_SELECTOR (selector), source);
347 g_object_unref (source);
348 }
349 }
350
351 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
352 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN);
353 gtk_container_add (GTK_CONTAINER (scrolled_window), selector);
354
355 content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
356 gtk_box_pack_start (GTK_BOX (content_area), scrolled_window, TRUE, TRUE, 4);
357
358 gtk_widget_show_all (dialog);
359 response = gtk_dialog_run (GTK_DIALOG (dialog));
360
361 if (response == GTK_RESPONSE_ACCEPT)
362 source = e_source_selector_ref_primary_selection (
363 E_SOURCE_SELECTOR (selector));
364 else
365 source = NULL;
366
367 gtk_widget_destroy (dialog);
368
369 /* XXX Return a borrowed reference for backward-compatibility. */
370 if (source != NULL)
371 g_object_unref (source);
372
373 return source;
374 }
375
376 gchar *
eab_suggest_filename(const GSList * contact_list)377 eab_suggest_filename (const GSList *contact_list)
378 {
379 gchar *res = NULL;
380
381 g_return_val_if_fail (contact_list != NULL, NULL);
382
383 if (!contact_list->next) {
384 EContact *contact = E_CONTACT (contact_list->data);
385 gchar *string;
386
387 string = e_contact_get (contact, E_CONTACT_FILE_AS);
388 if (string == NULL)
389 string = e_contact_get (contact, E_CONTACT_FULL_NAME);
390 if (string != NULL)
391 res = make_safe_filename (string);
392 g_free (string);
393 }
394
395 if (res == NULL)
396 res = make_safe_filename (_("list"));
397
398 return res;
399 }
400
401 typedef struct ContactCopyProcess_ ContactCopyProcess;
402
403 struct ContactCopyProcess_ {
404 gint count;
405 gboolean book_status;
406 GSList *contacts;
407 EBookClient *source;
408 EBookClient *destination;
409 ESourceRegistry *registry;
410 gboolean delete_from_source;
411 EAlertSink *alert_sink;
412 };
413
414 static void process_unref (ContactCopyProcess *process);
415
416 static void
remove_contact_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)417 remove_contact_ready_cb (GObject *source_object,
418 GAsyncResult *result,
419 gpointer user_data)
420 {
421 EBookClient *book_client = E_BOOK_CLIENT (source_object);
422 ContactCopyProcess *process = user_data;
423 GError *error = NULL;
424
425 e_book_client_remove_contact_by_uid_finish (book_client, result, &error);
426
427 if (error != NULL) {
428 g_warning (
429 "%s: Remove contact by uid failed: %s",
430 G_STRFUNC, error->message);
431 g_error_free (error);
432 }
433
434 process_unref (process);
435 }
436
437 static void
do_delete_from_source(gpointer data,gpointer user_data)438 do_delete_from_source (gpointer data,
439 gpointer user_data)
440 {
441 ContactCopyProcess *process = user_data;
442 EContact *contact = data;
443 const gchar *id;
444 EBookClient *book_client = process->source;
445
446 id = e_contact_get_const (contact, E_CONTACT_UID);
447 g_return_if_fail (id != NULL);
448 g_return_if_fail (book_client != NULL);
449
450 process->count++;
451 e_book_client_remove_contact_by_uid (book_client, id, E_BOOK_OPERATION_FLAG_NONE, NULL, remove_contact_ready_cb, process);
452 }
453
454 static void
delete_contacts(ContactCopyProcess * process)455 delete_contacts (ContactCopyProcess *process)
456 {
457 if (process->book_status == TRUE) {
458 g_slist_foreach (process->contacts,
459 do_delete_from_source,
460 process);
461 }
462 }
463
464 static void
process_unref(ContactCopyProcess * process)465 process_unref (ContactCopyProcess *process)
466 {
467 process->count--;
468 if (process->count == 0) {
469 if (process->delete_from_source) {
470 delete_contacts (process);
471 /* to not repeate this again */
472 process->delete_from_source = FALSE;
473
474 if (process->count > 0)
475 return;
476 }
477 g_slist_free_full (
478 process->contacts,
479 (GDestroyNotify) g_object_unref);
480 g_object_unref (process->source);
481 g_object_unref (process->destination);
482 g_object_unref (process->registry);
483 g_slice_free (ContactCopyProcess, process);
484 }
485 }
486
487 static void
contact_added_cb(EBookClient * book_client,const GError * error,const gchar * id,gpointer user_data)488 contact_added_cb (EBookClient *book_client,
489 const GError *error,
490 const gchar *id,
491 gpointer user_data)
492 {
493 ContactCopyProcess *process = user_data;
494
495 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
496 process->book_status = FALSE;
497 } else if (error != NULL) {
498 process->book_status = FALSE;
499 eab_error_dialog (
500 process->alert_sink, NULL,
501 _("Error adding contact"), error);
502 } else {
503 /* success */
504 process->book_status = TRUE;
505 }
506
507 process_unref (process);
508 }
509
510 static void
do_copy(gpointer data,gpointer user_data)511 do_copy (gpointer data,
512 gpointer user_data)
513 {
514 EBookClient *book_client;
515 EContact *contact;
516 ContactCopyProcess *process;
517
518 process = user_data;
519 contact = data;
520
521 book_client = process->destination;
522 e_contact_inline_local_photos (contact, NULL);
523
524 process->count++;
525 eab_merging_book_add_contact (
526 process->registry, book_client,
527 contact, contact_added_cb, process);
528 }
529
530 static void
book_client_connect_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)531 book_client_connect_cb (GObject *source_object,
532 GAsyncResult *result,
533 gpointer user_data)
534 {
535 ContactCopyProcess *process = user_data;
536 EClient *client;
537 GError *error = NULL;
538
539 client = e_book_client_connect_finish (result, &error);
540
541 /* Sanity check. */
542 g_return_if_fail (
543 ((client != NULL) && (error == NULL)) ||
544 ((client == NULL) && (error != NULL)));
545
546 if (error != NULL) {
547 g_warning ("%s: %s", G_STRFUNC, error->message);
548 g_error_free (error);
549 goto exit;
550 }
551
552 process->destination = E_BOOK_CLIENT (client);
553 process->book_status = TRUE;
554 g_slist_foreach (process->contacts, do_copy, process);
555
556 exit:
557 process_unref (process);
558 }
559
560 void
eab_transfer_contacts(ESourceRegistry * registry,EBookClient * source_client,GSList * contacts,gboolean delete_from_source,EAlertSink * alert_sink)561 eab_transfer_contacts (ESourceRegistry *registry,
562 EBookClient *source_client,
563 GSList *contacts /* adopted */,
564 gboolean delete_from_source,
565 EAlertSink *alert_sink)
566 {
567 ESource *source;
568 ESource *destination;
569 static gchar *last_uid = NULL;
570 ContactCopyProcess *process;
571 gchar *desc;
572 GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (alert_sink)));
573
574 g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
575 g_return_if_fail (E_IS_BOOK_CLIENT (source_client));
576
577 if (contacts == NULL)
578 return;
579
580 if (last_uid == NULL)
581 last_uid = g_strdup ("");
582
583 if (contacts->next == NULL) {
584 if (delete_from_source)
585 desc = _("Move contact to");
586 else
587 desc = _("Copy contact to");
588 } else {
589 if (delete_from_source)
590 desc = _("Move contacts to");
591 else
592 desc = _("Copy contacts to");
593 }
594
595 source = e_client_get_source (E_CLIENT (source_client));
596
597 destination = eab_select_source (
598 registry, source, desc, NULL, last_uid, window);
599
600 if (!destination)
601 return;
602
603 if (strcmp (last_uid, e_source_get_uid (destination)) != 0) {
604 g_free (last_uid);
605 last_uid = g_strdup (e_source_get_uid (destination));
606 }
607
608 process = g_slice_new0 (ContactCopyProcess);
609 process->count = 1;
610 process->book_status = FALSE;
611 process->source = g_object_ref (source_client);
612 process->contacts = contacts;
613 process->destination = NULL;
614 process->registry = g_object_ref (registry);
615 process->alert_sink = alert_sink;
616 process->delete_from_source = delete_from_source;
617
618 e_book_client_connect (
619 destination, 30, NULL, book_client_connect_cb, process);
620 }
621
622 /*
623 * eab_format_address helper function
624 *
625 * Splits locales from en_US to array "en","us",NULL. When
626 * locales don't have the second part (for example "C"),
627 * the output array is "c",NULL
628 */
629 static gchar **
get_locales(void)630 get_locales (void)
631 {
632 gchar *locale, *l_locale;
633 gchar *dot;
634 gchar **split;
635
636 #ifdef LC_ADDRESS
637 locale = g_strdup (setlocale (LC_ADDRESS, NULL));
638 #else
639 locale = NULL;
640 #endif
641 if (!locale)
642 return NULL;
643
644 l_locale = g_utf8_strdown (locale, -1);
645 g_free (locale);
646
647 dot = strchr (l_locale, '.');
648 if (dot != NULL) {
649 gchar *p = l_locale;
650 l_locale = g_strndup (l_locale, dot - l_locale);
651 g_free (p);
652 }
653
654 split = g_strsplit (l_locale, "_", 2);
655
656 g_free (l_locale);
657 return split;
658
659 }
660
661 static gchar *
get_locales_str(void)662 get_locales_str (void)
663 {
664 gchar *ret = NULL;
665 gchar **loc = get_locales ();
666
667 if (!loc)
668 return g_strdup ("C");
669
670 if (!loc[0] ||
671 (loc[0] && !loc[1])) /* We don't care about language now, we need a country at first! */
672 ret = g_strdup ("C");
673 else if (loc[0] && loc[1]) {
674 if (*loc[0])
675 ret = g_strconcat (loc[LOCALES_COUNTRY], "_", loc[LOCALES_LANGUAGE], NULL);
676 else
677 ret = g_strdup (loc[LOCALES_COUNTRY]);
678 }
679
680 g_strfreev (loc);
681 return ret;
682 }
683
684 /*
685 * Reads countrytransl.map file, which contains map of localized
686 * country names and their ISO codes and tries to find matching record
687 * for given country. The search is case insensitive.
688 * When no record is found (country is probably in untranslated language), returns
689 * code of local computer country (from locales)
690 */
691 static gchar *
country_to_ISO(const gchar * country)692 country_to_ISO (const gchar *country)
693 {
694 FILE *file = fopen (EVOLUTION_PRIVDATADIR "/countrytransl.map", "r");
695 gchar buffer[100];
696 gint length = 100;
697 gchar **pair;
698 gchar *res;
699 gchar *l_country = g_utf8_strdown (country, -1);
700
701 if (!file) {
702 gchar **loc;
703 g_warning ("%s: Failed to open countrytransl.map. Check your installation.", G_STRFUNC);
704 loc = get_locales ();
705 res = g_strdup (loc ? loc[LOCALES_COUNTRY] : NULL);
706 g_free (l_country);
707 g_strfreev (loc);
708 return res;
709 }
710
711 while (fgets (buffer, length, file) != NULL) {
712 gchar *low = NULL;
713 pair = g_strsplit (buffer, "\t", 2);
714
715 if (pair[0]) {
716 low = g_utf8_strdown (pair[0], -1);
717 if (g_utf8_collate (low, l_country) == 0) {
718 gchar *ret = g_strdup (pair[1]);
719 gchar *pos;
720 /* Remove trailing newline character */
721 if ((pos = g_strrstr (ret, "\n")) != NULL)
722 pos[0] = '\0';
723 fclose (file);
724 g_strfreev (pair);
725 g_free (low);
726 g_free (l_country);
727 return ret;
728 }
729 }
730
731 g_strfreev (pair);
732 g_free (low);
733 }
734
735 /* If we get here, then no match was found in the map file and we
736 * fallback to local system locales */
737 fclose (file);
738
739 pair = get_locales ();
740 res = g_strdup (pair ? pair[LOCALES_COUNTRY] : NULL);
741 g_strfreev (pair);
742 g_free (l_country);
743 return res;
744 }
745
746 /*
747 * Tries to find given key in "country_LANGUAGE" group. When fails to find
748 * such group, then fallbacks to "country" group. When such group does not
749 * exist either, NULL is returned
750 */
751 static gchar *
get_key_file_locale_string(GKeyFile * key_file,const gchar * key,const gchar * locale)752 get_key_file_locale_string (GKeyFile *key_file,
753 const gchar *key,
754 const gchar *locale)
755 {
756 gchar *result;
757 gchar *group;
758
759 g_return_val_if_fail (locale, NULL);
760
761 /* Default locale is in "country_lang", but such group may not exist. In such case use group "country" */
762 if (g_key_file_has_group (key_file, locale))
763 group = g_strdup (locale);
764 else {
765 gchar **locales = g_strsplit (locale, "_", 0);
766 group = g_strdup (locales[LOCALES_COUNTRY]);
767 g_strfreev (locales);
768 }
769
770 /* When group or key does not exist, returns NULL and fallback string will be used */
771 result = g_key_file_get_string (key_file, group, key, NULL);
772 g_free (group);
773 return result;
774 }
775
776 static void
get_address_format(AddressFormat address_format,const gchar * locale,gchar ** format,gchar ** country_position)777 get_address_format (AddressFormat address_format,
778 const gchar *locale,
779 gchar **format,
780 gchar **country_position)
781 {
782 GKeyFile *key_file;
783 GError *error;
784 gchar *loc;
785 const gchar *addr_key, *country_key;
786
787 if (address_format == ADDRESS_FORMAT_HOME) {
788 addr_key = "AddressFormat";
789 country_key = "CountryPosition";
790 } else if (address_format == ADDRESS_FORMAT_BUSINESS) {
791 addr_key = "BusinessAddressFormat";
792 country_key = "BusinessCountryPosition";
793 } else {
794 return;
795 }
796
797 if (locale == NULL)
798 loc = get_locales_str ();
799 else
800 loc = g_strdup (locale);
801
802 error = NULL;
803 key_file = g_key_file_new ();
804 g_key_file_load_from_file (key_file, EVOLUTION_PRIVDATADIR "/address_formats.dat", 0, &error);
805 if (error != NULL) {
806 g_warning ("%s: Failed to load address_formats.dat file: %s", G_STRFUNC, error->message);
807 if (format)
808 *format = g_strdup (ADDRESS_DEFAULT_FORMAT);
809 if (country_position)
810 *country_position = g_strdup (ADDRESS_DEFAULT_COUNTRY_POSITION);
811 g_key_file_free (key_file);
812 g_free (loc);
813 g_error_free (error);
814 return;
815 }
816
817 if (format) {
818 g_free (*format);
819 *format = get_key_file_locale_string (key_file, addr_key, loc);
820 if (!*format && address_format == ADDRESS_FORMAT_HOME) {
821 *format = g_strdup (ADDRESS_DEFAULT_FORMAT);
822 } else if (!*format && address_format == ADDRESS_FORMAT_BUSINESS)
823 get_address_format (ADDRESS_FORMAT_HOME, loc, format, NULL);
824 }
825
826 if (country_position) {
827 g_free (*country_position);
828 *country_position = get_key_file_locale_string (key_file, country_key, loc);
829 if (!*country_position && address_format == ADDRESS_FORMAT_HOME)
830 *country_position = g_strdup (ADDRESS_DEFAULT_COUNTRY_POSITION);
831 else if (!*country_position && address_format == ADDRESS_FORMAT_BUSINESS)
832 get_address_format (ADDRESS_FORMAT_HOME, loc, NULL, country_position);
833 }
834
835 g_free (loc);
836 g_key_file_free (key_file);
837 }
838
839 static const gchar *
find_balanced_bracket(const gchar * str)840 find_balanced_bracket (const gchar *str)
841 {
842 gint balance_counter = 0;
843 gint i = 0;
844
845 do {
846 if (str[i] == '(')
847 balance_counter++;
848
849 if (str[i] == ')')
850 balance_counter--;
851
852 i++;
853
854 } while ((balance_counter > 0) && (str[i]));
855
856 if (balance_counter > 0)
857 return str;
858
859 return str + i;
860 }
861
862 static GString *
string_append_upper(GString * str,const gchar * c)863 string_append_upper (GString *str,
864 const gchar *c)
865 {
866 gchar *up_c;
867
868 g_return_val_if_fail (str, NULL);
869
870 if (!c || !*c)
871 return str;
872
873 up_c = g_utf8_strup (c, -1);
874 g_string_append (str, up_c);
875 g_free (up_c);
876
877 return str;
878 }
879
880 static gboolean
parse_address_template_section(const gchar * format,const gchar * realname,const gchar * org_name,EContactAddress * address,gchar ** result)881 parse_address_template_section (const gchar *format,
882 const gchar *realname,
883 const gchar *org_name,
884 EContactAddress *address,
885 gchar **result)
886
887 {
888 const gchar *pos, *old_pos;
889 gboolean ret = FALSE; /* Indicates, wheter at least something was replaced */
890
891 GString *res = g_string_new ("");
892
893 pos = format;
894 old_pos = pos;
895 while ((pos = strchr (pos, '%')) != NULL) {
896
897 if (old_pos != pos)
898 g_string_append_len (res, old_pos, pos - old_pos);
899
900 switch (pos[1]) {
901 case 'n':
902 if (realname && *realname) {
903 g_string_append (res, realname);
904 ret = TRUE;
905 }
906 pos += 2; /* Jump behind the modifier, see what's next */
907 break;
908 case 'N':
909 if (realname && *realname) {
910 string_append_upper (res, realname);
911 ret = TRUE;
912 }
913 pos += 2;
914 break;
915 case 'm':
916 if (org_name && *org_name) {
917 g_string_append (res, org_name);
918 ret = TRUE;
919 }
920 pos += 2;
921 break;
922 case 'M':
923 if (org_name && *org_name) {
924 string_append_upper (res, org_name);
925 ret = TRUE;
926 }
927 pos += 2;
928 break;
929 case 'p':
930 if (address->po && *(address->po)) {
931 g_string_append (res, address->po);
932 ret = TRUE;
933 }
934 pos += 2;
935 break;
936 case 's':
937 if (address->street && *(address->street)) {
938 g_string_append (res, address->street);
939 if (address->ext && *(address->ext))
940 g_string_append_printf (
941 res, "\n%s",
942 address->ext);
943 ret = TRUE;
944 }
945 pos += 2;
946 break;
947 case 'S':
948 if (address->street && *(address->street)) {
949 string_append_upper (res, address->street);
950 if (address->ext && *(address->ext)) {
951 g_string_append_c (res, '\n');
952 string_append_upper (res, address->ext);
953 }
954 ret = TRUE;
955 }
956 pos += 2;
957 break;
958 case 'z':
959 if (address->code && *(address->code)) {
960 g_string_append (res, address->code);
961 ret = TRUE;
962 }
963 pos += 2;
964 break;
965 case 'l':
966 if (address->locality && *(address->locality)) {
967 g_string_append (res, address->locality);
968 ret = TRUE;
969 }
970 pos += 2;
971 break;
972 case 'L':
973 if (address->locality && *(address->locality)) {
974 string_append_upper (res, address->locality);
975 ret = TRUE;
976 }
977 pos += 2;
978 break;
979 case 'r':
980 if (address->region && *(address->region)) {
981 g_string_append (res, address->region);
982 ret = TRUE;
983 }
984 pos += 2;
985 break;
986 case 'R':
987 if (address->region && *(address->region)) {
988 string_append_upper (res, address->region);
989 ret = TRUE;
990 }
991 pos += 2;
992 break;
993 case ',':
994 if (ret && (pos >= format + 2) && /* If there's something before %, */
995 (g_ascii_strcasecmp (pos - 2, "\n") != 0) && /* And if it is not a newline */
996 (g_ascii_strcasecmp (pos - 2, "%w") != 0)) /* Nor whitespace */
997 g_string_append (res, ", ");
998 pos += 2;
999 break;
1000 case 'w':
1001 if (ret && (pos >= format + 2) &&
1002 (g_ascii_strcasecmp (pos - 2, "\n") != 0) &&
1003 (g_ascii_strcasecmp (pos - 1, " ") != 0))
1004 g_string_append_c (res, ' ');
1005 pos += 2;
1006 break;
1007 case '0': {
1008 const gchar *bpos1, *bpos2;
1009 gchar *inner;
1010 gchar *ires;
1011 gboolean replaced;
1012
1013 bpos1 = pos + 2;
1014 bpos2 = find_balanced_bracket (bpos1);
1015
1016 inner = g_strndup (bpos1 + 1, bpos2 - bpos1 - 2); /* Get inner content of the %0 (...) */
1017 replaced = parse_address_template_section (inner, realname, org_name, address, &ires);
1018 if (replaced)
1019 g_string_append (res, ires);
1020
1021 g_free (ires);
1022 g_free (inner);
1023
1024 ret = replaced;
1025 pos += (bpos2 - bpos1 + 2);
1026 } break;
1027 }
1028
1029 old_pos = pos;
1030 }
1031 g_string_append (res, old_pos);
1032
1033 *result = g_string_free (res, FALSE);
1034
1035 return ret;
1036 }
1037
1038 gchar *
eab_format_address(EContact * contact,EContactField address_type)1039 eab_format_address (EContact *contact,
1040 EContactField address_type)
1041 {
1042 gchar *result;
1043 gchar *format = NULL;
1044 gchar *country_position = NULL;
1045 gchar *locale;
1046 EContactAddress *addr = e_contact_get (contact, address_type);
1047
1048 if (!addr)
1049 return NULL;
1050
1051 if (!addr->po && !addr->ext && !addr->street && !addr->locality && !addr->region &&
1052 !addr->code && !addr->country) {
1053 e_contact_address_free (addr);
1054 return NULL;
1055 }
1056
1057 if (addr->country) {
1058 gchar *cntry = country_to_ISO (addr->country);
1059 gchar **loc = get_locales ();
1060 locale = g_strconcat (loc ? loc[LOCALES_LANGUAGE] : "C", "_", cntry, NULL);
1061 g_strfreev (loc);
1062 g_free (cntry);
1063 } else
1064 locale = get_locales_str ();
1065
1066 if (address_type == E_CONTACT_ADDRESS_HOME)
1067 get_address_format (ADDRESS_FORMAT_HOME, locale, &format, &country_position);
1068 else if (address_type == E_CONTACT_ADDRESS_WORK)
1069 get_address_format (ADDRESS_FORMAT_BUSINESS, locale, &format, &country_position);
1070 else {
1071 e_contact_address_free (addr);
1072 g_free (locale);
1073 return NULL;
1074 }
1075
1076 /* Expand all the variables in format.
1077 * Don't display organization in home address;
1078 * and skip full names, as it's part of the EContact itself,
1079 * check this bug for reason: https://bugzilla.gnome.org/show_bug.cgi?id=667912
1080 */
1081 parse_address_template_section (
1082 format,
1083 NULL,
1084 (address_type == E_CONTACT_ADDRESS_WORK) ?
1085 e_contact_get_const (contact, E_CONTACT_ORG) : NULL,
1086 addr,
1087 &result);
1088
1089 /* Add the country line. In some countries, the address can be located above the
1090 * rest of the address */
1091 if (addr->country && country_position) {
1092 gchar *country_upper = g_utf8_strup (addr->country, -1);
1093 gchar *p = result;
1094 if (g_strcmp0 (country_position, "BELOW") == 0) {
1095 result = g_strconcat (p, "\n\n", country_upper, NULL);
1096 g_free (p);
1097 } else if (g_strcmp0 (country_position, "below") == 0) {
1098 result = g_strconcat (p, "\n\n", addr->country, NULL);
1099 g_free (p);
1100 } else if (g_strcmp0 (country_position, "ABOVE") == 0) {
1101 result = g_strconcat (country_upper, "\n\n", p, NULL);
1102 g_free (p);
1103 } else if (g_strcmp0 (country_position, "above") == 0) {
1104 result = g_strconcat (addr->country, "\n\n", p, NULL);
1105 g_free (p);
1106 }
1107 g_free (country_upper);
1108 }
1109
1110 e_contact_address_free (addr);
1111 g_free (locale);
1112 g_free (format);
1113 g_free (country_position);
1114
1115 return result;
1116 }
1117
1118 gboolean
eab_fullname_matches_nickname(EContact * contact)1119 eab_fullname_matches_nickname (EContact *contact)
1120 {
1121 gchar *nickname, *fullname;
1122 gboolean same;
1123
1124 g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
1125
1126 nickname = e_contact_get (contact, E_CONTACT_NICKNAME);
1127 fullname = e_contact_get (contact, E_CONTACT_FULL_NAME);
1128 same = g_strcmp0 (nickname && *nickname ? nickname : NULL,
1129 fullname && *fullname ? fullname : NULL) == 0;
1130
1131 g_free (nickname);
1132 g_free (fullname);
1133
1134 return same;
1135 }
1136