update Bulgarian translation
[claws.git] / src / addrbook.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2001-2002 Match Grun and the Sylpheed-Claws team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 /* General functions for accessing address book files */
21
22 #include <glib.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <dirent.h>
26 #include <sys/stat.h>
27 #include <math.h>
28 #include <setjmp.h>
29
30 #include "xml.h"
31 #include "mgutils.h"
32 #include "addritem.h"
33 #include "addrcache.h"
34 #include "addrbook.h"
35 #include "adbookbase.h"
36
37 #ifndef DEV_STANDALONE
38 #include "prefs_gtk.h"
39 #include "codeconv.h"
40 #endif
41
42 #define ADDRBOOK_MAX_SEARCH_COUNT 1000
43 #define ADDRBOOK_PREFIX           "addrbook-"
44 #define ADDRBOOK_SUFFIX           ".xml"
45 #define FILE_NUMDIGITS            6
46
47 #define ID_TIME_OFFSET            998000000
48
49 /* Create new address book */
50 AddressBookFile *addrbook_create_book()
51 {
52         AddressBookFile *book;
53
54         book = g_new0(AddressBookFile, 1);
55         book->type = ADBOOKTYPE_BOOK;
56         book->addressCache = addrcache_create();
57         book->retVal = MGU_SUCCESS;
58         book->path = NULL;
59         book->fileName = NULL;
60         book->maxValue = 0;
61         book->tempList = NULL;
62         book->tempHash = NULL;
63         book->addressCache->modified = TRUE;
64         return book;
65 }
66
67 /* Specify name to be used */
68 void addrbook_set_name(AddressBookFile *book, const gchar *value)
69 {
70         g_return_if_fail(book != NULL);
71         addrcache_set_name(book->addressCache, value);
72 }
73
74 gchar *addrbook_get_name(AddressBookFile *book)
75 {
76         g_return_val_if_fail(book != NULL, NULL);
77         return addrcache_get_name(book->addressCache);
78 }
79
80 void addrbook_set_path(AddressBookFile *book, const gchar *value)
81 {
82         g_return_if_fail(book != NULL);
83         book->path = mgu_replace_string(book->path, value);
84         addrcache_set_dirty(book->addressCache, TRUE);
85 }
86
87 void addrbook_set_file(AddressBookFile *book, const gchar *value)
88 {
89         g_return_if_fail(book != NULL);
90         book->fileName = mgu_replace_string(book->fileName, value);
91         addrcache_set_dirty(book->addressCache, TRUE);
92 }
93
94 gboolean addrbook_get_modified(AddressBookFile *book)
95 {
96         g_return_val_if_fail(book != NULL, FALSE);
97         return book->addressCache->modified;
98 }
99
100 void addrbook_set_modified(AddressBookFile *book, const gboolean value)
101 {
102         g_return_if_fail(book != NULL);
103         book->addressCache->modified = value;
104 }
105
106 gboolean addrbook_get_accessed(AddressBookFile *book)
107 {
108         g_return_val_if_fail(book != NULL, FALSE);
109         return book->addressCache->accessFlag;
110 }
111
112 void addrbook_set_accessed(AddressBookFile *book, const gboolean value)
113 {
114         g_return_if_fail(book != NULL);
115         book->addressCache->accessFlag = value;
116 }
117
118 gboolean addrbook_get_read_flag(AddressBookFile *book)
119 {
120         g_return_val_if_fail(book != NULL, FALSE);
121         return book->addressCache->dataRead;
122 }
123
124 void addrbook_set_read_flag(AddressBookFile *book, const gboolean value)
125 {
126         g_return_if_fail(book != NULL);
127         book->addressCache->dataRead = value;
128 }
129
130 gint addrbook_get_status(AddressBookFile *book)
131 {
132         g_return_val_if_fail(book != NULL, -1);
133         return book->retVal;
134 }
135
136 ItemFolder *addrbook_get_root_folder(AddressBookFile *book)
137 {
138         g_return_val_if_fail(book != NULL, NULL);
139         return addrcache_get_root_folder(book->addressCache);
140 }
141
142 GList *addrbook_get_list_folder(AddressBookFile *book)
143 {
144         g_return_val_if_fail(book != NULL, NULL);
145         return addrcache_get_list_folder(book->addressCache);
146 }
147
148 GList *addrbook_get_list_person(AddressBookFile *book)
149 {
150         g_return_val_if_fail(book != NULL, NULL);
151         return addrcache_get_list_person(book->addressCache);
152 }
153
154 gboolean addrbook_get_dirty(AddressBookFile *book)
155 {
156         g_return_val_if_fail(book != NULL, FALSE);
157         return addrcache_get_dirty(book->addressCache);
158 }
159
160 void addrbook_set_dirty(AddressBookFile *book, const gboolean value)
161 {
162         g_return_if_fail(book != NULL);
163         addrcache_set_dirty(book->addressCache, value);
164 }
165
166 /* Empty address book */
167 void addrbook_empty_book(AddressBookFile *book)
168 {
169         g_return_if_fail(book != NULL);
170
171         /* Free up internal objects */
172         addrcache_clear(book->addressCache);
173         addrcache_set_dirty(book->addressCache, FALSE);
174         g_list_free(book->tempList);
175
176         /* Reset to initial state */
177         book->tempList = NULL;
178         book->tempHash = NULL;
179         book->addressCache->dataRead = FALSE;
180         book->addressCache->modified = FALSE;
181         book->addressCache->accessFlag = FALSE;
182         book->retVal = MGU_SUCCESS;
183 }
184
185 /* Free address book */
186 void addrbook_free_book(AddressBookFile *book)
187 {
188         g_return_if_fail(book != NULL);
189
190         /* Clear cache */
191         addrcache_clear(book->addressCache);
192         addrcache_free(book->addressCache);
193
194         /* Free up internal objects */
195         g_free(book->path);
196         g_free(book->fileName);
197         g_list_free(book->tempList);
198
199         book->path = NULL;
200         book->fileName = NULL;
201         book->maxValue = 0;
202         book->tempList = NULL;
203         book->tempHash = NULL;
204
205         book->type = ADBOOKTYPE_NONE;
206         book->addressCache = NULL;
207         book->retVal = MGU_SUCCESS;
208
209         g_free(book);
210 }
211
212 /* Print list of items */
213 void addrbook_print_item_list(GList *list, FILE *stream)
214 {
215         GList *node = list;
216
217         while(node) {
218                 AddrItemObject *obj = node->data;
219                 if (ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON) 
220                         addritem_print_item_person((ItemPerson *) obj, stream); 
221                 else if (ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP)
222                         addritem_print_item_group((ItemGroup *) obj, stream); 
223                 else if (ADDRITEM_TYPE(obj) == ITEMTYPE_FOLDER) 
224                         addritem_print_item_folder((ItemFolder *) obj, stream);
225                 node = g_list_next(node);
226         }
227         fprintf(stream, "\t---\n");
228 }
229
230 /* Print address book */
231 void addrbook_print_book(AddressBookFile *book, FILE *stream)
232 {
233         g_return_if_fail(book != NULL);
234
235         fprintf(stream, "AddressBook:\n");
236         fprintf(stream, "\tpath : '%s'\n", book->path);
237         fprintf(stream, "\tfile : '%s'\n", book->fileName);
238         fprintf(stream, "\tstatus : %d : '%s'\n", book->retVal,
239                 mgu_error2string(book->retVal));
240         addrcache_print(book->addressCache, stream);
241 }
242
243 /* Dump entire address book traversing folders */
244 void addrbook_dump_book(AddressBookFile *book, FILE *stream)
245 {
246         ItemFolder *folder;
247
248         g_return_if_fail(book != NULL);
249
250         addrbook_print_book(book, stream);
251         folder = book->addressCache->rootFolder;
252         addritem_print_item_folder(folder, stream);
253 }
254
255 /* Remove group from address book.
256    param: group Group to remove.
257    return: Group, or NULL if not found. 
258    Note that object should still be freed */
259 ItemGroup *addrbook_remove_group(AddressBookFile *book, ItemGroup *group)
260 {
261         g_return_val_if_fail(book != NULL, NULL);
262         return addrcache_remove_group(book->addressCache, group);
263 }
264
265 /* Remove specified person from address book.
266    param: person        Person to remove.
267   return: Person, or NULL if not found. 
268   Note that object should still be freed */
269 ItemPerson *addrbook_remove_person(AddressBookFile *book, ItemPerson *person)
270 {
271         g_return_val_if_fail(book != NULL, NULL);
272         return addrcache_remove_person(book->addressCache, person);
273 }
274
275 /* Remove email address in address book for specified person.
276    param: person        Person.
277           email         EMail to remove.
278   return: EMail object, or NULL if not found. 
279   Note that object should still be freed */
280 ItemEMail *addrbook_person_remove_email(AddressBookFile *book,
281                                         ItemPerson *person, ItemEMail *email)
282 {
283         g_return_val_if_fail(book != NULL, NULL);
284         return addrcache_person_remove_email(book->addressCache, person, email);
285 }
286
287 /* **********************************************************************
288 * Read/Write XML data file...
289 * ===========================
290 * Notes:
291 * 1)    The address book is structured as follows:
292 *
293 *               address-book
294 *                       person
295 *                               address-list
296 *                                       address
297 *                               attribute-list
298 *                                       attribute
299 *                       group
300 *                               member-list
301 *                                       member
302 *                       folder
303 *                               item-list
304 *                                       item
305 *
306 * 2)    This sequence of elements was chosen so that the most important
307 *       elements (person and their email addresses) appear first.
308 *
309 * 3)    Groups then appear. When groups are loaded, person's email
310 *       addresses have already been loaded and can be found.
311 *
312 * 4)    Finally folders are loaded. Any forward and backward references
313 *       to folders, groups and persons in the folders are resolved after
314 *       loading.
315 *
316 * ***********************************************************************
317 */
318
319 /* Element tag names */
320 #define AB_ELTAG_ADDRESS         "address"
321 #define AB_ELTAG_ATTRIBUTE       "attribute"
322 #define AB_ELTAG_ATTRIBUTE_LIST  "attribute-list"
323 #define AB_ELTAG_ADDRESS_LIST    "address-list"
324 #define AB_ELTAG_MEMBER          "member"
325 #define AB_ELTAG_MEMBER_LIST     "member-list"
326 #define AB_ELTAG_ITEM            "item"
327 #define AB_ELTAG_ITEM_LIST       "item-list"
328 #define AB_ELTAG_ADDRESS_BOOK    "address-book"
329 #define AB_ELTAG_PERSON          "person"
330 #define AB_ELTAG_GROUP           "group"
331 #define AB_ELTAG_FOLDER          "folder"
332
333 /* Attribute tag names */
334 #define AB_ATTAG_TYPE            "type"
335 #define AB_ATTAG_UID             "uid"
336 #define AB_ATTAG_NAME            "name"
337 #define AB_ATTAG_REMARKS         "remarks"
338 #define AB_ATTAG_FIRST_NAME      "first-name"
339 #define AB_ATTAG_LAST_NAME       "last-name"
340 #define AB_ATTAG_NICK_NAME       "nick-name"
341 #define AB_ATTAG_COMMON_NAME     "cn"
342 #define AB_ATTAG_ALIAS           "alias"
343 #define AB_ATTAG_EMAIL           "email"
344 #define AB_ATTAG_EID             "eid"
345 #define AB_ATTAG_PID             "pid"
346
347 /* Attribute values */
348 #define AB_ATTAG_VAL_PERSON      "person"
349 #define AB_ATTAG_VAL_GROUP       "group"
350 #define AB_ATTAG_VAL_FOLDER      "folder"
351
352 /* Parse address item for person */
353 static void addrbook_parse_address(AddressBookFile *book, XMLFile *file, 
354                                    ItemPerson *person)
355 {
356         GList *attr;
357         gchar *name, *value;
358         ItemEMail *email = NULL;
359
360         attr = xml_get_current_tag_attr(file);
361         while (attr) {
362                 name = ((XMLAttr *)attr->data)->name;
363                 value = ((XMLAttr *)attr->data)->value;
364                 if (!email)
365                         email = addritem_create_item_email();
366                 if (strcmp(name, AB_ATTAG_UID) == 0)
367                         ADDRITEM_ID(email) = g_strdup(value);
368                 else if (strcmp(name, AB_ATTAG_ALIAS) == 0)
369                         ADDRITEM_NAME(email) = g_strdup(value);
370                 else if (strcmp(name, AB_ATTAG_EMAIL) == 0)
371                         email->address = g_strdup(value);
372                 else if (strcmp(name, AB_ATTAG_REMARKS) == 0)
373                         email->remarks = g_strdup(value);
374                 attr = g_list_next(attr);
375         }
376         if (email) {
377                 if (person) {
378                         addrcache_person_add_email(book->addressCache, person,
379                                                    email);
380                 }
381                 else {
382                         addritem_free_item_email(email);
383                         email = NULL;
384                 }
385         }
386 }
387
388 /* Parse email address list */
389 static void addrbook_parse_addr_list(AddressBookFile *book, XMLFile *file, 
390                                      ItemPerson *person)
391 {
392         GList *attr;
393         guint prev_level;
394
395         for (;;) {
396                 prev_level = file->level;
397                 if (xml_parse_next_tag(file)) {
398                         longjmp(book->jumper, 1);
399                 }
400                 if (file->level < prev_level) return;
401                 if (xml_compare_tag(file, AB_ELTAG_ADDRESS)) {
402                         attr = xml_get_current_tag_attr(file);
403                         addrbook_parse_address(book, file, person);
404                         addrbook_parse_addr_list(book, file, person);
405                 }
406         }
407 }
408
409 /* Parse attibute for person */
410 static void addrbook_parse_attribute(XMLFile *file, ItemPerson *person)
411 {
412         GList *attr;
413         gchar *name, *value;
414         gchar *element;
415         UserAttribute *uAttr = NULL;
416
417         attr = xml_get_current_tag_attr(file);
418         while (attr) {
419                 name = ((XMLAttr *)attr->data)->name;
420                 value = ((XMLAttr *)attr->data)->value;
421                 if (!uAttr) uAttr = addritem_create_attribute();
422                 if (strcmp(name, AB_ATTAG_UID) == 0)
423                         addritem_attrib_set_id(uAttr, value);
424                 else if (strcmp(name, AB_ATTAG_NAME) == 0) 
425                         addritem_attrib_set_name(uAttr, value);
426                 attr = g_list_next(attr);
427         }
428
429         element = xml_get_element(file);
430         addritem_attrib_set_value(uAttr, element);
431
432         if (uAttr) {
433                 if (person) {
434                         addritem_person_add_attribute(person, uAttr);
435                 }
436                 else {
437                         addritem_free_attribute(uAttr);
438                         uAttr = NULL;
439                 }
440         }
441 }
442
443 /* Parse attribute list */
444 static void addrbook_parse_attr_list(AddressBookFile *book, XMLFile *file, 
445                                      ItemPerson *person)
446 {
447         GList *attr;
448         guint prev_level;
449
450         for (;;) {
451                 prev_level = file->level;
452                 if (xml_parse_next_tag(file)) {
453                         longjmp( book->jumper, 1 );
454                 }
455                 if (file->level < prev_level) return;
456                 if (xml_compare_tag(file, AB_ELTAG_ATTRIBUTE)) {
457                         attr = xml_get_current_tag_attr(file);
458                         addrbook_parse_attribute(file, person);
459                         addrbook_parse_attr_list(book, file, person);
460                 }
461         }
462 }
463
464 /* Parse person */
465 static void addrbook_parse_person(AddressBookFile *book, XMLFile *file)
466 {
467         GList *attr;
468         gchar *name, *value;
469         ItemPerson *person = NULL;
470
471         attr = xml_get_current_tag_attr(file);
472         while (attr) {
473                 name = ((XMLAttr *)attr->data)->name;
474                 value = ((XMLAttr *)attr->data)->value;
475                 if (!person) 
476                         person = addritem_create_item_person();
477                 if (strcmp(name, AB_ATTAG_UID) == 0)
478                         ADDRITEM_ID(person) = g_strdup(value);
479                 else if (strcmp(name, AB_ATTAG_FIRST_NAME) == 0)
480                         person->firstName = g_strdup(value);
481                 else if (strcmp(name, AB_ATTAG_LAST_NAME) == 0)
482                         person->lastName = g_strdup(value);
483                 else if (strcmp(name, AB_ATTAG_NICK_NAME) == 0)
484                         person->nickName = g_strdup(value);
485                 else if (strcmp(name, AB_ATTAG_COMMON_NAME) == 0)
486                         ADDRITEM_NAME(person) = g_strdup(value);
487                 attr = g_list_next(attr);
488         }
489         if (xml_parse_next_tag(file)) { /* Consume closing tag */
490                 longjmp(book->jumper, 1);
491         }
492         if (xml_compare_tag(file, AB_ELTAG_ADDRESS_LIST)) {
493                 addrbook_parse_addr_list(book, file, person);
494                 if (person) {
495                         addrcache_hash_add_person(book->addressCache, person);
496                 }
497         }
498         if (xml_parse_next_tag(file)) { /* Consume closing tag */
499                 longjmp(book->jumper, 1);
500         }
501         if (xml_compare_tag(file, AB_ELTAG_ATTRIBUTE_LIST)) {
502                 addrbook_parse_attr_list(book, file, person);
503         }
504 }
505
506 /* Parse group member */
507 static void addrbook_parse_member(AddressBookFile *book, XMLFile *file, 
508                                   ItemGroup *group)
509 {
510         GList *attr;
511         gchar *name, *value;
512         gchar *pid = NULL, *eid = NULL;
513         ItemEMail *email = NULL;
514
515         attr = xml_get_current_tag_attr(file);
516         while (attr) {
517                 name = ((XMLAttr *)attr->data)->name;
518                 value = ((XMLAttr *)attr->data)->value;
519                 if (strcmp(name, AB_ATTAG_PID) == 0)
520                         pid = g_strdup(value);
521                 else if (strcmp(name, AB_ATTAG_EID) == 0)
522                         eid = g_strdup(value);
523                 attr = g_list_next(attr);
524         }
525         /* email = addrcache_get_email( book->addressCache, pid, eid ); */
526         email = addrcache_get_email(book->addressCache, eid);
527         if (email) {
528                 if (group) {
529                         addrcache_group_add_email(book->addressCache, group, 
530                                                   email);
531                 }
532                 else {
533                         addritem_free_item_email(email);
534                         email = NULL;
535                 }
536         }
537 }
538
539 /* Parse group member list */
540 static void addrbook_parse_member_list(AddressBookFile *book, XMLFile *file, 
541                                        ItemGroup *group)
542 {
543         GList *attr;
544         guint prev_level;
545
546         for (;;) {
547                 prev_level = file->level;
548                 if (xml_parse_next_tag(file)) {
549                         longjmp(book->jumper, 1);
550                 }
551                 if (file->level < prev_level)
552                         return;
553                 if (xml_compare_tag(file, AB_ELTAG_MEMBER)) {
554                         attr = xml_get_current_tag_attr(file);
555                         addrbook_parse_member(book, file, group);
556                         addrbook_parse_member_list(book, file, group);
557                 }
558                 else {
559                         attr = xml_get_current_tag_attr(file);
560                 }
561         }
562 }
563
564 /* Parse group */
565 static void addrbook_parse_group(AddressBookFile *book, XMLFile *file)
566 {
567         GList *attr;
568         gchar *name, *value;
569         ItemGroup *group = NULL;
570
571         attr = xml_get_current_tag_attr(file);
572         while (attr) {
573                 name = ((XMLAttr *)attr->data)->name;
574                 value = ((XMLAttr *)attr->data)->value;
575                 if (!group) 
576                         group = addritem_create_item_group();
577                 if (strcmp(name, AB_ATTAG_UID) == 0) 
578                         ADDRITEM_ID(group) = g_strdup(value);
579                 else if (strcmp(name, AB_ATTAG_NAME) == 0)
580                         ADDRITEM_NAME(group) = g_strdup(value);
581                 else if (strcmp(name, AB_ATTAG_REMARKS) == 0)
582                         group->remarks = g_strdup(value);
583                 attr = g_list_next(attr);
584         }
585         if (xml_parse_next_tag(file)) { /* Consume closing tag */
586                 longjmp(book->jumper, 1);
587         }
588         if (xml_compare_tag(file, AB_ELTAG_MEMBER_LIST)) {
589                 if (group) {
590                         addrcache_hash_add_group(book->addressCache, group);
591                 }
592                 addrbook_parse_member_list(book, file, group);
593         }
594 }
595
596 /* Parse folder item */
597 static void addrbook_parse_folder_item(AddressBookFile *book, XMLFile *file, 
598                                        ItemFolder *folder)
599 {
600         GList *attr;
601         gchar *name, *value;
602         gchar *uid = NULL;
603
604         attr = xml_get_current_tag_attr(file);
605         while (attr) {
606                 name = ((XMLAttr *)attr->data)->name;
607                 value = ((XMLAttr *)attr->data)->value;
608                 if (strcmp(name, AB_ATTAG_UID) == 0) {
609                         uid = g_strdup(value);
610                 }
611                 attr = g_list_next(attr);
612         }
613         if (folder) {
614                 if (uid) {
615                         folder->listItems = g_list_append(folder->listItems, uid);
616                 }
617         }
618 }
619
620 /* Parse folder item list */
621 static void addrbook_parse_folder_list(AddressBookFile *book, XMLFile *file,
622                                        ItemFolder *folder)
623 {
624         GList *attr;
625         guint prev_level;
626
627         for (;;) {
628                 prev_level = file->level;
629                 if (xml_parse_next_tag(file)) {
630                         longjmp(book->jumper, 1);
631                 }
632                 if (file->level < prev_level)
633                         return;
634                 if (xml_compare_tag(file, AB_ELTAG_ITEM)) {
635                         attr = xml_get_current_tag_attr(file);
636                         addrbook_parse_folder_item(book, file, folder);
637                         addrbook_parse_folder_list(book, file, folder);
638                 }
639                 else {
640                         attr = xml_get_current_tag_attr(file);
641                 }
642         }
643 }
644
645 /* Parse folder */
646 static void addrbook_parse_folder(AddressBookFile *book, XMLFile *file) 
647 {
648         GList *attr;
649         gchar *name, *value;
650         ItemFolder *folder = NULL;
651
652         attr = xml_get_current_tag_attr(file);
653         while (attr) {
654                 name = ((XMLAttr *)attr->data)->name;
655                 value = ((XMLAttr *)attr->data)->value;
656                 if (!folder)
657                         folder = addritem_create_item_folder();
658                 if (strcmp(name, AB_ATTAG_UID) == 0)
659                         ADDRITEM_ID(folder) = g_strdup(value);
660                 else if (strcmp(name, AB_ATTAG_NAME) == 0)
661                         ADDRITEM_NAME(folder) = g_strdup(value);
662                 else if (strcmp(name, AB_ATTAG_REMARKS) == 0)
663                         folder->remarks = g_strdup(value);
664                 attr = g_list_next(attr);
665         }
666         if (xml_parse_next_tag(file)) { /* Consume closing tag */
667                 longjmp(book->jumper, 1);
668         }
669         if (xml_compare_tag(file, AB_ELTAG_ITEM_LIST)) {
670                 if (folder) {
671                         if (addrcache_hash_add_folder(book->addressCache, 
672                                                       folder)) {
673                                 book->tempList = g_list_append(book->tempList, 
674                                                                folder);
675                                 /* We will resolve folder later */
676                                 ADDRITEM_PARENT(folder) = NULL;
677                         }
678                 }
679                 addrbook_parse_folder_list(book, file, folder);
680         }
681 }
682
683 /* Parse address book.
684    Return: TRUE if data read successfully, FALSE if error reading data */
685 static gboolean addrbook_read_tree(AddressBookFile *book, XMLFile *file)
686 {
687         gboolean retVal;
688         GList *attr;
689         gchar *name, *value;
690
691         book->retVal = MGU_BAD_FORMAT;
692         if (xml_get_dtd(file))
693                 return FALSE;
694         if (xml_parse_next_tag(file))
695                 longjmp(book->jumper, 1);
696         if (!xml_compare_tag(file, AB_ELTAG_ADDRESS_BOOK))
697                 return FALSE;
698
699         attr = xml_get_current_tag_attr(file);
700         while (attr) {
701                 name = ((XMLAttr *)attr->data)->name;
702                 value = ((XMLAttr *)attr->data)->value;
703                 if (strcmp( name, AB_ATTAG_NAME) == 0)
704                         addrbook_set_name( book, value );
705                 attr = g_list_next( attr );
706         }
707
708         retVal = TRUE;
709         for (;;) {
710                 if (!file->level)
711                         break;
712                 /* Get next item tag (person, group or folder) */
713                 if (xml_parse_next_tag(file))
714                         longjmp( book->jumper, 1 );
715                         
716                 if (xml_compare_tag(file, AB_ELTAG_PERSON))
717                         addrbook_parse_person(book, file);
718                 else if (xml_compare_tag(file, AB_ELTAG_GROUP))
719                         addrbook_parse_group(book, file);
720                 else if (xml_compare_tag(file, AB_ELTAG_FOLDER))
721                         addrbook_parse_folder(book, file);
722         }
723         if (retVal) book->retVal = MGU_SUCCESS;
724                 return retVal;
725 }
726
727 /* Resolve folder items visitor function */
728 static void addrbook_res_items_vis(gpointer key, gpointer value, gpointer data)
729 {
730         AddressBookFile *book = data;
731         AddrItemObject *obj = (AddrItemObject *) value;
732         ItemFolder *rootFolder = book->addressCache->rootFolder;
733         if (obj->parent == NULL) {
734                 if (ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON) {
735                         rootFolder->listPerson = g_list_append(rootFolder->listPerson,
736                                                                obj);
737                         ADDRITEM_PARENT(obj) = ADDRITEM_OBJECT(rootFolder);
738                 }
739                 else if (ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP) {
740                         rootFolder->listGroup = g_list_append(rootFolder->listGroup,
741                                                               obj);
742                         ADDRITEM_PARENT(obj) = ADDRITEM_OBJECT(rootFolder);
743                 }
744         }
745 }
746
747 /* Resolve folder items. Lists of UID's are replaced with pointers to 
748    data items */
749 static void addrbook_resolve_folder_items(AddressBookFile *book)
750 {
751         GList *nodeFolder = NULL;
752         GList *listRemove = NULL;
753         GList *node = NULL;
754         ItemFolder *rootFolder = book->addressCache->rootFolder;
755         nodeFolder = book->tempList;
756         
757         while (nodeFolder) {
758                 ItemFolder *folder = nodeFolder->data;
759                 listRemove = NULL;
760                 node = folder->listItems;
761                 while (node) {
762                         gchar *uid = node->data;
763                         AddrItemObject *aio = addrcache_get_object(book->addressCache, 
764                                                                    uid);
765                         if (aio) {
766                                 if (aio->type == ITEMTYPE_FOLDER) {
767                                         ItemFolder *item = (ItemFolder *) aio;
768                                         folder->listFolder = g_list_append(folder->listFolder, item);
769                                         ADDRITEM_PARENT(item) = ADDRITEM_OBJECT(folder);
770                                         addrcache_hash_add_folder(book->addressCache, folder);
771                                 }
772                                 else if (aio->type == ITEMTYPE_PERSON) {
773                                         ItemPerson *item = (ItemPerson *) aio;
774                                         folder->listPerson = g_list_append(folder->listPerson, item);
775                                         ADDRITEM_PARENT(item) = ADDRITEM_OBJECT(folder);
776                                 }
777                                 else if (aio->type == ITEMTYPE_GROUP) {
778                                         ItemGroup *item = (ItemGroup *) aio;
779                                         folder->listGroup = g_list_append(folder->listGroup, item);
780                                         ADDRITEM_PARENT(item) = ADDRITEM_OBJECT(folder);
781                                 }
782                                 /* Replace data with pointer to item */
783                                 g_free(uid);
784                                 node->data = aio;
785                         }
786                         else { /* Not found, append to remove list. */
787                                 listRemove = g_list_append(listRemove, uid);
788                         }
789                         node = g_list_next(node);
790                 }
791                 rootFolder->listFolder = g_list_append(rootFolder->listFolder, 
792                                                        folder);
793                 /* Process remove list */
794                 node = listRemove;
795                 while (node) {
796                         gchar *uid = node->data;
797                         folder->listItems = g_list_remove(folder->listItems,
798                                                           uid);
799                         g_free(uid);
800                         node = g_list_next(node);
801                 }
802                 g_list_free(listRemove);
803                 nodeFolder = g_list_next(nodeFolder);
804         }
805         /* Remove folders with parents. */
806         listRemove = NULL;
807         node = rootFolder->listFolder;
808         while (node) {
809                 ItemFolder *folder = (ItemFolder *) node->data;
810                 if (ADDRITEM_PARENT(folder))
811                         /* Remove folders with parents */
812                         listRemove = g_list_append(listRemove, folder);
813                 else /* Add to root folder */
814                         ADDRITEM_PARENT(folder) = ADDRITEM_OBJECT(book->addressCache->rootFolder);
815
816                 node = g_list_next( node );
817         }
818         /* Process remove list */
819         node = listRemove;
820         while (node) {
821                 rootFolder->listFolder = g_list_remove(rootFolder->listFolder, 
822                                                        node->data);
823                 node = g_list_next(node);
824         }
825         g_list_free(listRemove);
826
827         /* Move all unparented persons and groups into root folder */
828         g_hash_table_foreach(book->addressCache->itemHash, 
829                              addrbook_res_items_vis, book);
830
831         /* Free up some more */
832         nodeFolder = book->tempList;
833         while (nodeFolder) {
834                 ItemFolder *folder = nodeFolder->data;
835                 g_list_free(folder->listItems);
836                 folder->listItems = NULL;
837                 nodeFolder = g_list_next(nodeFolder);
838         }
839         g_list_free(book->tempList);
840         book->tempList = NULL;
841 }
842
843 /* Read address book file */
844 gint addrbook_read_data(AddressBookFile *book)
845 {
846         XMLFile *file = NULL;
847         gchar *fileSpec = NULL;
848
849         g_return_val_if_fail(book != NULL, -1);
850
851         /*
852         printf( "...addrbook_read_data :%s:\t:%s:\n", book->fileName,
853                 addrcache_get_name( book->addressCache ) );
854         */
855
856         fileSpec = g_strconcat(book->path, G_DIR_SEPARATOR_S, 
857                                book->fileName, NULL);
858         book->retVal = MGU_OPEN_FILE;
859         addrcache_clear(book->addressCache);
860         book->addressCache->modified = FALSE;
861         book->addressCache->accessFlag = FALSE;
862         file = xml_open_file(fileSpec);
863         g_free(fileSpec);
864         if (file) {
865                 book->tempList = NULL;
866                 /* Trap for parsing errors. */
867                 if (setjmp( book->jumper)) {
868                         xml_close_file(file);
869                         return book->retVal;
870                 }
871                 addrbook_read_tree(book, file);
872                 xml_close_file(file);
873                 /* Resolve folder items */
874                 addrbook_resolve_folder_items(book);
875                 book->tempList = NULL;
876                 book->addressCache->modified = FALSE;
877                 book->addressCache->dataRead = TRUE;
878                 addrcache_set_dirty(book->addressCache, FALSE);
879         }
880         return book->retVal;
881 }
882
883 static void addrbook_write_elem_s(FILE *fp, gint lvl, gchar *name)
884 {
885         gint i;
886         for (i = 0; i < lvl; i++) 
887                 fputs("  ", fp);
888         fputs("<", fp);
889         fputs(name, fp);
890 }
891
892 static void addrbook_write_elem_e(FILE *fp, gint lvl, gchar *name)
893 {
894         gint i;
895         for(i = 0; i < lvl; i++)
896                 fputs("  ", fp);
897         fputs("</", fp);
898         fputs(name, fp);
899         fputs(">\n", fp);
900 }
901
902 static void addrbook_write_attr(FILE *fp, gchar *name, gchar *value)
903 {
904         fputs(" ", fp);
905         fputs(name, fp);
906         fputs("=\"", fp);
907         xml_file_put_escape_str(fp, value);
908         fputs("\"", fp);
909 }
910
911 /* Write file hash table visitor function */
912 static void addrbook_write_item_person_vis(gpointer key, gpointer value, 
913                                            gpointer data)
914 {
915         AddrItemObject *obj = (AddrItemObject *) value;
916         FILE *fp = (FILE *) data;
917         GList *node;
918
919         if (!obj)
920                 return;
921         if (ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON) {
922                 ItemPerson *person = (ItemPerson *) value;
923                 if (person) {
924                         addrbook_write_elem_s(fp, 1, AB_ELTAG_PERSON);
925                         addrbook_write_attr(fp, AB_ATTAG_UID, ADDRITEM_ID(person));
926                         addrbook_write_attr(fp, AB_ATTAG_FIRST_NAME, person->firstName);
927                         addrbook_write_attr(fp, AB_ATTAG_LAST_NAME, person->lastName);
928                         addrbook_write_attr(fp, AB_ATTAG_NICK_NAME, person->nickName);
929                         addrbook_write_attr(fp, AB_ATTAG_COMMON_NAME, ADDRITEM_NAME(person));
930                         fputs(" >\n", fp);
931
932                         /* Output email addresses */
933                         addrbook_write_elem_s(fp, 2, AB_ELTAG_ADDRESS_LIST);
934                         fputs(">\n", fp);
935                         node = person->listEMail;
936                         while (node) {
937                                 ItemEMail *email = node->data;
938                                 addrbook_write_elem_s(fp, 3, AB_ELTAG_ADDRESS);
939                                 addrbook_write_attr(fp, AB_ATTAG_UID, ADDRITEM_ID(email));
940                                 addrbook_write_attr(fp, AB_ATTAG_ALIAS, ADDRITEM_NAME(email));
941                                 addrbook_write_attr(fp, AB_ATTAG_EMAIL, email->address);
942                                 addrbook_write_attr(fp, AB_ATTAG_REMARKS, email->remarks);
943                                 fputs(" />\n", fp);
944                                 node = g_list_next(node);
945                         }
946                         addrbook_write_elem_e(fp, 2, AB_ELTAG_ADDRESS_LIST);
947
948                         /* Output user attributes */
949                         addrbook_write_elem_s(fp, 2, AB_ELTAG_ATTRIBUTE_LIST);
950                         fputs(">\n", fp);
951                         node = person->listAttrib;
952                         while (node) {
953                                 UserAttribute *attrib = node->data;
954                                 addrbook_write_elem_s(fp, 3, AB_ELTAG_ATTRIBUTE);
955                                 addrbook_write_attr(fp, AB_ATTAG_UID, attrib->uid);
956                                 addrbook_write_attr(fp, AB_ATTAG_NAME, attrib->name);
957                                 fputs(" >", fp);
958                                 xml_file_put_escape_str(fp, attrib->value);
959                                 addrbook_write_elem_e(fp, 0, AB_ELTAG_ATTRIBUTE);
960                                 node = g_list_next(node);
961                         }
962                         addrbook_write_elem_e(fp, 2, AB_ELTAG_ATTRIBUTE_LIST);
963                         addrbook_write_elem_e(fp, 1, AB_ELTAG_PERSON);
964                 }
965         }
966 }
967
968 /* Write file hash table visitor function */
969 static void addrbook_write_item_group_vis(gpointer key, gpointer value, 
970                                           gpointer data)
971 {
972         AddrItemObject *obj = (AddrItemObject *) value;
973         FILE *fp = (FILE *) data;
974         GList *node;
975
976         if (!obj)
977                 return;
978         if (ADDRITEM_TYPE(obj) == ITEMTYPE_GROUP) {
979                 ItemGroup *group = (ItemGroup *) value;
980                 if (group) {
981                         addrbook_write_elem_s(fp, 1, AB_ELTAG_GROUP);
982                         addrbook_write_attr(fp, AB_ATTAG_UID, ADDRITEM_ID(group));
983                         addrbook_write_attr(fp, AB_ATTAG_NAME, ADDRITEM_NAME(group));
984                         addrbook_write_attr(fp, AB_ATTAG_REMARKS, group->remarks);
985                         fputs(" >\n", fp);
986
987                         /* Output email address links */
988                         addrbook_write_elem_s(fp, 2, AB_ELTAG_MEMBER_LIST);
989                         fputs(">\n", fp);
990                         node = group->listEMail;
991                         while (node) {
992                                 ItemEMail *email = node->data;
993                                 ItemPerson *person = (ItemPerson *) ADDRITEM_PARENT(email);
994                                 addrbook_write_elem_s(fp, 3, AB_ELTAG_MEMBER);
995                                 addrbook_write_attr(fp, AB_ATTAG_PID, ADDRITEM_ID(person));
996                                 addrbook_write_attr(fp, AB_ATTAG_EID, ADDRITEM_ID(email));
997                                 fputs(" />\n", fp);
998                                 node = g_list_next(node);
999                         }
1000                         addrbook_write_elem_e(fp, 2, AB_ELTAG_MEMBER_LIST);
1001                         addrbook_write_elem_e(fp, 1, AB_ELTAG_GROUP);
1002                 }
1003         }
1004 }
1005
1006 /* Write file hash table visitor function */
1007 static void addrbook_write_item_folder_vis(gpointer key, gpointer value, 
1008                                            gpointer data)
1009 {
1010         AddrItemObject *obj = (AddrItemObject *) value;
1011         FILE *fp = (FILE *) data;
1012         GList *node;
1013
1014         if (!obj)
1015                 return;
1016         if (ADDRITEM_TYPE(obj) == ITEMTYPE_FOLDER) {
1017                 ItemFolder *folder = (ItemFolder *) value;
1018                 if (folder) {
1019                         addrbook_write_elem_s(fp, 1, AB_ELTAG_FOLDER);
1020                         addrbook_write_attr(fp, AB_ATTAG_UID, ADDRITEM_ID(folder));
1021                         addrbook_write_attr(fp, AB_ATTAG_NAME, ADDRITEM_NAME(folder));
1022                         addrbook_write_attr(fp, AB_ATTAG_REMARKS, folder->remarks);
1023                         fputs(" >\n", fp);
1024                         addrbook_write_elem_s(fp, 2, AB_ELTAG_ITEM_LIST);
1025                         fputs(">\n", fp);
1026
1027                         /* Output persons */
1028                         node = folder->listPerson;
1029                         while (node) {
1030                                 ItemPerson *item = node->data;
1031                                 addrbook_write_elem_s(fp, 3, AB_ELTAG_ITEM);
1032                                 addrbook_write_attr(fp, AB_ATTAG_TYPE,  AB_ATTAG_VAL_PERSON);
1033                                 addrbook_write_attr(fp, AB_ATTAG_UID, ADDRITEM_ID(item));
1034                                 fputs(" />\n", fp);
1035                                 node = g_list_next(node);
1036                         }
1037
1038                         /* Output groups */
1039                         node = folder->listGroup;
1040                         while (node) {
1041                                 ItemGroup *item = node->data;
1042                                 addrbook_write_elem_s(fp, 3, AB_ELTAG_ITEM);
1043                                 addrbook_write_attr(fp, AB_ATTAG_TYPE, AB_ATTAG_VAL_GROUP);
1044                                 addrbook_write_attr(fp, AB_ATTAG_UID, ADDRITEM_ID(item));
1045                                 fputs(" />\n", fp);
1046                                 node = g_list_next(node);
1047                         }
1048
1049                         /* Output folders */
1050                         node = folder->listFolder;
1051                         while (node) {
1052                                 ItemFolder *item = node->data;
1053                                 addrbook_write_elem_s(fp, 3, AB_ELTAG_ITEM);
1054                                 addrbook_write_attr(fp, AB_ATTAG_TYPE, AB_ATTAG_VAL_FOLDER);
1055                                 addrbook_write_attr(fp, AB_ATTAG_UID, ADDRITEM_ID(item));
1056                                 fputs(" />\n", fp);
1057                                 node = g_list_next(node);
1058                         }
1059                         addrbook_write_elem_e(fp, 2, AB_ELTAG_ITEM_LIST);
1060                         addrbook_write_elem_e(fp, 1, AB_ELTAG_FOLDER);
1061                 }
1062         }
1063 }
1064
1065 /* Output address book data to specified file.
1066   return: Status code */
1067 gint addrbook_write_to(AddressBookFile *book, gchar *newFile)
1068 {
1069         FILE *fp;
1070         gchar *fileSpec;
1071 #ifndef DEV_STANDALONE
1072         PrefFile *pfile;
1073 #endif
1074
1075         g_return_val_if_fail(book != NULL, -1);
1076         g_return_val_if_fail(newFile != NULL, -1);
1077
1078         fileSpec = g_strconcat(book->path, G_DIR_SEPARATOR_S, newFile, NULL);
1079
1080         book->retVal = MGU_OPEN_FILE;
1081 #ifdef DEV_STANDALONE
1082         fp = fopen(fileSpec, "wb");
1083         g_free(fileSpec);
1084         if (fp) {
1085                 fputs("<?xml version=\"1.0\" ?>\n", fp);
1086 #else
1087         pfile = prefs_write_open(fileSpec);
1088         g_free(fileSpec);
1089         if (pfile) {
1090                 fp = pfile->fp;
1091                 fprintf(fp, "<?xml version=\"1.0\" encoding=\"%s\" ?>\n",
1092                         conv_get_current_charset_str());
1093 #endif
1094                 addrbook_write_elem_s(fp, 0, AB_ELTAG_ADDRESS_BOOK);
1095                 addrbook_write_attr(fp, AB_ATTAG_NAME,
1096                                     addrcache_get_name(book->addressCache));
1097                 fputs(" >\n", fp);
1098
1099                 /* Output all persons */
1100                 g_hash_table_foreach(book->addressCache->itemHash, 
1101                                      addrbook_write_item_person_vis, fp);
1102
1103                 /* Output all groups */
1104                 g_hash_table_foreach(book->addressCache->itemHash, 
1105                                      addrbook_write_item_group_vis, fp);
1106
1107                 /* Output all folders */
1108                 g_hash_table_foreach(book->addressCache->itemHash, 
1109                                      addrbook_write_item_folder_vis, fp);
1110
1111                 addrbook_write_elem_e(fp, 0, AB_ELTAG_ADDRESS_BOOK);
1112                 book->retVal = MGU_SUCCESS;
1113 #ifdef DEV_STANDALONE
1114                 fclose(fp);
1115 #else
1116                 if (prefs_write_close( pfile ) < 0)
1117                         book->retVal = MGU_ERROR_WRITE;
1118 #endif
1119         }
1120
1121         fileSpec = NULL;
1122         return book->retVal;
1123 }
1124
1125 /* Output address book data to original file.
1126    return: Status code */
1127 gint addrbook_save_data(AddressBookFile *book)
1128 {
1129         g_return_val_if_fail(book != NULL, -1);
1130
1131         book->retVal = MGU_NO_FILE;
1132         if (book->fileName == NULL || *book->fileName == '\0') 
1133                 return book->retVal;
1134         if (book->path == NULL || *book->path == '\0')
1135                 return book->retVal;
1136
1137         addrbook_write_to(book, book->fileName);
1138         if (book->retVal == MGU_SUCCESS)
1139                 addrcache_set_dirty(book->addressCache, FALSE);
1140         return book->retVal;
1141 }
1142
1143 /* **********************************************************************
1144    Address book edit interface functions...
1145    ***********************************************************************
1146   Move person's email item.
1147   param: book       Address book.
1148          person     Person.
1149          itemMove   Item to move.
1150          itemTarget Target item before which to move item */
1151 ItemEMail *addrbook_move_email_before(AddressBookFile *book, ItemPerson *person,
1152                                       ItemEMail *itemMove, ItemEMail *itemTarget)
1153 {
1154         ItemEMail *email = NULL;
1155
1156         g_return_val_if_fail(book != NULL, NULL);
1157
1158         email = addritem_move_email_before(person, itemMove, itemTarget);
1159         if (email) 
1160                 addrcache_set_dirty(book->addressCache, TRUE);
1161         return email;
1162 }
1163
1164 /* Move person's email item.
1165    param: book       Address book.
1166           person     Person.
1167          itemMove   Item to move.
1168          itemTarget Target item after which to move item */
1169 ItemEMail *addrbook_move_email_after(AddressBookFile *book, ItemPerson *person,
1170                                      ItemEMail *itemMove, ItemEMail *itemTarget)
1171 {
1172         ItemEMail *email = NULL;
1173
1174         g_return_val_if_fail(book != NULL, NULL);
1175
1176         email = addritem_move_email_after(person, itemMove, itemTarget);
1177         if (email)
1178                 addrcache_set_dirty(book->addressCache, TRUE);
1179         return email;
1180 }
1181
1182 /* Hash table visitor function for deletion of hashtable entries */
1183 static gboolean addrbook_free_simple_hash_vis(gpointer *key, gpointer *value, 
1184                                               gpointer *data)
1185 {
1186         g_free(key);
1187         key = NULL;
1188         value = NULL;
1189         return TRUE;
1190 }
1191
1192 /* Update address book email list for specified person.
1193    Enter: book      Address book.
1194           person    Person to update.
1195           listEMail List of new email addresses.
1196   Note: The existing email addresses are replaced with the new addresses. Any references
1197   to old addresses in the groups are re-linked to the new addresses. All old addresses
1198   linked to the person are removed */
1199 void addrbook_update_address_list(AddressBookFile *book, ItemPerson *person, 
1200                                   GList *listEMail)
1201 {
1202         GList *node;
1203         GList *listDelete;
1204         GList *listGroup;
1205
1206         g_return_if_fail(book != NULL);
1207         g_return_if_fail(person != NULL);
1208
1209         /* Get groups where person's existing email addresses are listed */
1210         listGroup = addrcache_get_group_for_person(book->addressCache, person);
1211         if (listGroup) {
1212                 GHashTable *hashEMail;
1213                 GHashTable *hashEMailAlias;
1214                 GList *nodeGrp;
1215
1216                 /* Load hash table with new address entries */
1217                 hashEMail = g_hash_table_new(g_str_hash, g_str_equal);
1218                 hashEMailAlias = g_hash_table_new(g_str_hash, g_str_equal);
1219                 node = listEMail;
1220                 while (node) {
1221                         ItemEMail *email = node->data;
1222                         gchar *addr = g_strdup(email->address);
1223                         gchar *alias = email->obj.name ;
1224                         g_strdown(addr);
1225                         if (!g_hash_table_lookup(hashEMail, addr)) {
1226                                 g_hash_table_insert(hashEMail, addr, email);
1227                         }
1228                         if (*alias != '\0' && ! g_hash_table_lookup(hashEMailAlias,
1229                             alias)) 
1230                                 g_hash_table_insert(hashEMailAlias, alias, email);
1231
1232                         node = g_list_next(node);
1233                 }
1234
1235                 /* Re-parent new addresses to existing groups, where email address match. */
1236                 nodeGrp = listGroup;
1237                 while (nodeGrp) {
1238                         ItemGroup *group = (ItemGroup *) nodeGrp->data;
1239                         GList *groupEMail = group->listEMail;
1240                         GList *nodeGrpEM;
1241                         GList *listRemove = NULL;
1242
1243                         /* Process each email item linked to group */
1244                         nodeGrpEM = groupEMail;
1245                         while (nodeGrpEM) {
1246                                 ItemEMail *emailGrp = (ItemEMail *) nodeGrpEM->data;
1247
1248                                 if (ADDRITEM_PARENT(emailGrp) == ADDRITEM_OBJECT(person)) {
1249                                         /* Found an email address for this person */
1250                                         ItemEMail *emailNew = NULL;
1251                                         gchar *addr = g_strdup(emailGrp->address);
1252                                         gchar *alias = emailGrp->obj.name;
1253                                         g_strdown(addr);
1254                                         emailNew = (ItemEMail *)
1255                                                 g_hash_table_lookup(hashEMail, addr);
1256                                         g_free( addr );
1257                                         /* If no match by e-mail, try to match by e-mail alias */
1258                                         if (!emailNew && *alias != '\0') {
1259                                                 emailNew = (ItemEMail *)
1260                                                         g_hash_table_lookup(hashEMailAlias, alias);
1261                                         }
1262                                         
1263                                         if (emailNew)
1264                                                 /* Point to this entry */
1265                                                 nodeGrpEM->data = emailNew;
1266                                         else if (g_hash_table_size(hashEMail)==1)
1267                                                 /* If the person has just one e-mail address, then 
1268                                                    change e-mail address in group list */
1269                                                 nodeGrpEM->data = listEMail->data;
1270                                         else 
1271                                                 /* Mark for removal */
1272                                                 listRemove = g_list_append(listRemove, emailGrp);
1273                                 }
1274                                 /* Move on to next email link */
1275                                 nodeGrpEM = g_list_next(nodeGrpEM);
1276                         }
1277
1278                         /* Process all removed links in current group */
1279                         nodeGrpEM = listRemove;
1280                         while (nodeGrpEM) {
1281                                 ItemEMail *emailGrp = nodeGrpEM->data;
1282                                 groupEMail = g_list_remove(groupEMail, emailGrp);
1283                                 nodeGrpEM = g_list_next(nodeGrpEM);
1284                         }
1285
1286                         g_list_free(listRemove);
1287
1288                         /* Move on to next group */
1289                         nodeGrp = g_list_next(nodeGrp);
1290
1291                 }
1292                 /* Clear hash table */
1293                 g_hash_table_foreach_remove(hashEMail, (GHRFunc) 
1294                                             addrbook_free_simple_hash_vis, NULL);
1295                 g_hash_table_destroy(hashEMail);
1296                 hashEMail = NULL;
1297                 g_hash_table_destroy(hashEMailAlias);
1298                 hashEMailAlias = NULL;
1299                 g_list_free(listGroup);
1300                 listGroup = NULL;
1301         }
1302         /* Remove old addresses from person and cache */
1303         listDelete = NULL;
1304         node = person->listEMail;
1305         while (node) {
1306                 ItemEMail *email = node->data;
1307
1308                 if (addrcache_person_remove_email(book->addressCache, person, email))
1309                         addrcache_remove_email(book->addressCache, email);
1310
1311                 listDelete = g_list_append(listDelete, email);
1312                 node = person->listEMail;
1313         }
1314         /* Add new address entries */
1315         node = listEMail;
1316         while (node) {
1317                 ItemEMail *email = node->data;
1318
1319                 if (ADDRITEM_ID(email) == NULL)
1320                         /* Allocate an ID for new address */
1321                         addrcache_id_email(book->addressCache, email);
1322                         
1323                 addrcache_person_add_email( book->addressCache, person, email );
1324                 node = g_list_next( node );
1325         }
1326
1327         addrcache_set_dirty(book->addressCache, TRUE);
1328
1329         /* Free up memory */
1330         g_list_free(listEMail);
1331         listEMail = NULL;
1332
1333         node = listDelete;
1334         while (node) {
1335                 ItemEMail *email = node->data;
1336
1337                 addritem_free_item_email(email);
1338                 node = g_list_next(node);
1339         }
1340         g_list_free(listDelete);
1341         listDelete = NULL;
1342
1343 }
1344
1345 /* Add person and address data to address book.
1346    Enter: book      Address book.
1347           folder    Folder where to add person, or NULL for root folder.
1348           listEMail New list of email addresses.
1349    Return: Person added.
1350    Note: A new person is created with specified list of email addresses. All objects inserted
1351    into address book */
1352 ItemPerson *addrbook_add_address_list(AddressBookFile *book, ItemFolder *folder,
1353                                       GList *listEMail)
1354 {
1355         ItemPerson *person;
1356         ItemFolder *f = folder;
1357         GList *node;
1358
1359         g_return_val_if_fail(book != NULL, NULL);
1360
1361         if (!f) 
1362                 f = book->addressCache->rootFolder;
1363         person = addritem_create_item_person();
1364         addrcache_id_person(book->addressCache, person);
1365         addrcache_folder_add_person(book->addressCache, f, person);
1366
1367         node = listEMail;
1368         while (node) {
1369                 ItemEMail *email = node->data;
1370                 if (ADDRITEM_ID(email) == NULL)
1371                         addrcache_id_email(book->addressCache, email);
1372
1373                 addrcache_person_add_email(book->addressCache, person, email);
1374                 node = g_list_next(node);
1375         }
1376         return person;
1377 }
1378
1379 /* Build available email list visitor function */
1380 static void addrbook_build_avail_email_vis(gpointer key, gpointer value, 
1381                                            gpointer data)
1382 {
1383         AddrItemObject *obj = (AddrItemObject *) value;
1384
1385         if (ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON) {
1386                 AddressBookFile *book = data;
1387                 ItemPerson *person = (ItemPerson *) obj;
1388                 GList *node = person->listEMail;
1389                 while (node) {
1390                         ItemEMail *email = node->data;
1391                         /* gchar *newKey = g_strdup( ADDRITEM_ID(email) ); */
1392
1393                         if (!g_hash_table_lookup(book->tempHash,
1394                                                  ADDRITEM_ID(email)))
1395                                 book->tempList = g_list_append(book->tempList, email);
1396
1397                         node = g_list_next(node);
1398                 }
1399         }
1400 }
1401
1402 /* Return link list of available email items (which have not already been linked to
1403    groups). Note that the list contains references to items and should be g_free()
1404    when done. Do *NOT* attempt to used the addrbook_free_xxx() functions... this will
1405    destroy the addressbook data!
1406    Return: List of items, or NULL if none */
1407 GList *addrbook_get_available_email_list(AddressBookFile *book, ItemGroup *group)
1408 {
1409         GList *list = NULL;
1410         GHashTable *table;
1411
1412         g_return_val_if_fail(book != NULL, NULL);
1413
1414         /* Load hash table with group email entries */
1415         table = g_hash_table_new(g_str_hash, g_str_equal);
1416         if (group) {
1417                 list = group->listEMail;
1418                 while (list) {
1419                         ItemEMail *email = list->data;
1420                         g_hash_table_insert(table, ADDRITEM_ID(email), email);
1421                         list = g_list_next(list);
1422                 }
1423         }
1424
1425         /* Build list of available email addresses which exclude those already in groups */
1426         book->tempList = NULL;
1427         book->tempHash = table;
1428         g_hash_table_foreach(book->addressCache->itemHash, 
1429                              addrbook_build_avail_email_vis, book);
1430         list = book->tempList;
1431         book->tempList = NULL;
1432         book->tempHash = NULL;
1433
1434         /* Clear hash table */
1435         g_hash_table_destroy(table);
1436         table = NULL;
1437
1438         return list;
1439 }
1440
1441 /* Update address book email list for specified group.
1442    Enter: book      Address book.
1443           group     group to update.
1444           listEMail New list of email addresses. This should *NOT* be g_free() when done.
1445   Note: The existing email addresses are replaced with the new addresses. Any references
1446   to old addresses in the groups are re-linked to the new addresses. All old addresses
1447   linked to the person are removed */
1448 void addrbook_update_group_list(AddressBookFile *book, ItemGroup *group, 
1449                                 GList *listEMail)
1450 {
1451         GList *oldData;
1452
1453         g_return_if_fail(book != NULL);
1454         g_return_if_fail(group != NULL);
1455
1456         addrcache_set_dirty(book->addressCache, TRUE);
1457
1458         /* Remember old list */
1459         oldData = group->listEMail;
1460         group->listEMail = listEMail;
1461         mgu_clear_list(oldData);
1462         oldData = NULL;
1463 }
1464
1465 /* Add group and email list to address book.
1466    Enter: book      Address book.
1467           folder    Parent folder, or NULL for root folder.
1468           listEMail New list of email addresses. This should *NOT* be g_free() when done.
1469    Return: Group object.
1470    Note: The existing email addresses are replaced with the new addresses. Any references
1471    to old addresses in the groups are re-linked to the new addresses. All old addresses
1472    linked to the person are removed */
1473 ItemGroup *addrbook_add_group_list(AddressBookFile *book, ItemFolder *folder,
1474                                    GList *listEMail)
1475 {
1476         ItemGroup *group = NULL;
1477         ItemFolder *f = folder;
1478
1479         g_return_val_if_fail(book != NULL, NULL);
1480
1481         if (!f)
1482                 f = book->addressCache->rootFolder;
1483         group = addritem_create_item_group();
1484         addrcache_id_group(book->addressCache, group);
1485         addrcache_folder_add_group(book->addressCache, f, group);
1486         group->listEMail = listEMail;
1487         return group;
1488 }
1489
1490 /* Add new folder to address book.
1491    Enter: book   Address book.
1492           parent        Parent folder.
1493    Return: Folder that was added. This should *NOT* be g_free() when done */
1494 ItemFolder *addrbook_add_new_folder(AddressBookFile *book, ItemFolder *parent)
1495 {
1496         ItemFolder *folder = NULL;
1497         ItemFolder *p = parent;
1498
1499         g_return_val_if_fail(book != NULL, NULL);
1500
1501         if (!p)
1502                 p = book->addressCache->rootFolder;
1503         folder = addritem_create_item_folder();
1504         addrcache_id_folder(book->addressCache, folder);
1505         if (addrcache_hash_add_folder(book->addressCache, folder)) {
1506                 p->listFolder = g_list_append(p->listFolder, folder);
1507                 ADDRITEM_PARENT(folder) = ADDRITEM_OBJECT(p);
1508                 addrcache_set_dirty(book->addressCache, TRUE);
1509         }
1510         else {
1511                 addritem_free_item_folder(folder);
1512                 folder = NULL;
1513         }
1514         return folder;
1515 }
1516
1517 /* Update address book attribute list for specified person.
1518    Enter: book       Address book.
1519           person     Person to update.
1520           listAttrib New list of attributes.
1521    Note: The existing email addresses are replaced with the new addresses. All old attributes
1522    linked to the person are removed */
1523 void addrbook_update_attrib_list(AddressBookFile *book, ItemPerson *person,
1524                                  GList *listAttrib)
1525 {
1526         GList *node;
1527         GList *oldData;
1528
1529         g_return_if_fail(book != NULL);
1530         g_return_if_fail(person != NULL);
1531
1532         /* Remember old list */
1533         oldData = person->listAttrib;
1534
1535         /* Attach new address list to person. */
1536         node = listAttrib;
1537         while (node) {
1538                 UserAttribute *attrib = node->data;
1539                 if (attrib->uid == NULL) {
1540                         /* Allocate an ID */
1541                         addrcache_id_attribute(book->addressCache, attrib);
1542                 }
1543                 node = g_list_next(node);
1544         }
1545         person->listAttrib = listAttrib;
1546         addrcache_set_dirty(book->addressCache, TRUE);
1547
1548         /* Free up old data */
1549         addritem_free_list_attribute(oldData);
1550         oldData = NULL;
1551 }
1552
1553 /*
1554 * Add attribute data for person to address book.
1555 * Enter: book       Address book.
1556 *        person     New person object.
1557 *        listAttrib New list of attributes.
1558 * Note: Only attributes are inserted into address book.
1559 */
1560 void addrbook_add_attrib_list( AddressBookFile *book, ItemPerson *person, GList *listAttrib ) {
1561         GList *node;
1562
1563         g_return_if_fail( book != NULL );
1564         g_return_if_fail( person != NULL );
1565
1566         node = listAttrib;
1567         while( node ) {
1568                 UserAttribute *attrib = node->data;
1569                 if( attrib->uid == NULL ) {
1570                         addrcache_id_attribute( book->addressCache, attrib );
1571                 }
1572                 addritem_person_add_attribute( person, attrib );
1573                 node = g_list_next( node );
1574         }
1575         addrcache_set_dirty( book->addressCache, TRUE );
1576 }
1577
1578 /* Return address book file for specified object.
1579    Enter: aio Book item object.
1580    Return: Address book, or NULL if not found */
1581 AddressBookFile *addrbook_item_get_bookfile(AddrItemObject *aio)
1582 {
1583         AddressBookFile *book = NULL;
1584
1585         if (aio) {
1586                 ItemFolder *parent = NULL;
1587                 ItemFolder *root = NULL;
1588                 if (aio->type == ITEMTYPE_EMAIL) {
1589                         ItemPerson *person = (ItemPerson *)ADDRITEM_PARENT(aio);
1590                         if( person ) 
1591                                 parent = (ItemFolder *)ADDRITEM_PARENT(person);
1592                 }
1593                 else {
1594                         parent = (ItemFolder *)ADDRITEM_PARENT(aio);
1595                 }
1596                 if (parent) 
1597                         root = addrcache_find_root_folder(parent);
1598                 if (root)
1599                         book = (AddressBookFile *)ADDRITEM_PARENT(root);
1600         }
1601         return book;
1602 }
1603
1604 /* Remove folder from address book. Children are re-parented to parent folder.
1605    param: folder Folder to remove.
1606    return: Folder, or NULL if not found. Note that object should still be freed */
1607 ItemFolder *addrbook_remove_folder(AddressBookFile *book, ItemFolder *folder)
1608 {
1609         ItemFolder *f;
1610
1611         g_return_val_if_fail(book != NULL, NULL);
1612
1613         f = addrcache_remove_folder(book->addressCache, folder);
1614         return f;
1615 }
1616
1617 /* Remove folder from address book. Children are deleted.
1618    param: folder Folder to remove.
1619    return: Folder, or NULL if not found. Note that object should still be freed */
1620 ItemFolder *addrbook_remove_folder_delete(AddressBookFile *book, 
1621                                           ItemFolder *folder)
1622 {
1623         ItemFolder *f;
1624
1625         g_return_val_if_fail(book != NULL, NULL);
1626
1627         f = addrcache_remove_folder_delete(book->addressCache, folder);
1628         return f;
1629 }
1630
1631 #define WORK_BUFLEN     1024
1632 #define ADDRBOOK_DIGITS "0123456789"
1633
1634 /* Return list of existing address book files.
1635    Enter: book Address book file.
1636    Return: File list */
1637 GList *addrbook_get_bookfile_list(AddressBookFile *book) {
1638         gchar *adbookdir;
1639         DIR *dp;
1640         struct dirent *entry;
1641         struct stat statbuf;
1642         gchar buf[WORK_BUFLEN];
1643         gchar numbuf[WORK_BUFLEN];
1644         gint len, lenpre, lensuf, lennum;
1645         long int val, maxval;
1646         GList *fileList = NULL;
1647
1648         g_return_val_if_fail(book != NULL, NULL);
1649
1650         if (book->path == NULL || *book->path == '\0') {
1651                 book->retVal = MGU_NO_PATH;
1652                 return NULL;
1653         }
1654
1655         strcpy(buf, book->path);
1656         len = strlen(buf);
1657         if (len > 0) {
1658                 if (buf[len-1] != G_DIR_SEPARATOR) {
1659                         buf[len] = G_DIR_SEPARATOR;
1660                         buf[++len] = '\0';
1661                 }
1662         }
1663
1664         adbookdir = g_strdup(buf);
1665         strcat(buf, ADDRBOOK_PREFIX);
1666
1667         if ((dp = opendir(adbookdir)) == NULL) {
1668                 book->retVal = MGU_OPEN_DIRECTORY;
1669                 g_free(adbookdir);
1670                 return NULL;
1671         }
1672
1673         lenpre = strlen(ADDRBOOK_PREFIX);
1674         lensuf = strlen(ADDRBOOK_SUFFIX);
1675         lennum = FILE_NUMDIGITS + lenpre;
1676         maxval = -1;
1677
1678         while ((entry = readdir(dp)) != NULL) {
1679                 gchar *endptr = NULL;
1680                 gint i;
1681                 gboolean flg;
1682
1683                 strcpy(buf, adbookdir);
1684                 strcat(buf, entry->d_name);
1685                 stat(buf, &statbuf);
1686                 if (S_IFREG & statbuf.st_mode) {
1687                         if (strncmp(entry->d_name, ADDRBOOK_PREFIX, lenpre) == 0) {
1688                                 if (strncmp((entry->d_name) + lennum, ADDRBOOK_SUFFIX, lensuf) == 0) {
1689                                         strncpy(numbuf, (entry->d_name) + lenpre, FILE_NUMDIGITS);
1690                                         numbuf[FILE_NUMDIGITS] = '\0';
1691                                         flg = TRUE;
1692                                         for(i = 0; i < FILE_NUMDIGITS; i++) {
1693                                                 if(!strchr(ADDRBOOK_DIGITS, numbuf[i])) {
1694                                                         flg = FALSE;
1695                                                         break;
1696                                                 }
1697                                         }
1698                                         if (flg) {
1699                                                 /* Get value */
1700                                                 val = strtol(numbuf, &endptr, 10);
1701                                                 if (endptr  && val > -1) {
1702                                                         if (val > maxval) maxval = val;
1703                                                         fileList = g_list_append(fileList, g_strdup(entry->d_name));
1704                                                 }
1705                                         }
1706                                 }
1707                         }
1708                 }
1709         }
1710         closedir(dp);
1711         g_free(adbookdir);
1712
1713         book->maxValue = maxval; 
1714         book->retVal = MGU_SUCCESS;
1715         return fileList;
1716 }
1717
1718 /* Return file name for specified file number.
1719    Enter:  fileNum File number.
1720    Return: File name, or NULL if file number too large. Should be g_free() when done */
1721 gchar *addrbook_gen_new_file_name(gint fileNum) {
1722         gchar fmt[30];
1723         gchar buf[WORK_BUFLEN];
1724         gint n = fileNum;
1725         long int nmax;
1726
1727         if (n < 1) 
1728                 n = 1;
1729         nmax = -1 + (long int) pow(10, FILE_NUMDIGITS);
1730         if (fileNum > nmax)
1731                 return NULL;
1732         g_snprintf(fmt, sizeof(fmt), "%%s%%0%dd%%s", FILE_NUMDIGITS);
1733         g_snprintf(buf, sizeof(buf), fmt, ADDRBOOK_PREFIX, n, ADDRBOOK_SUFFIX);
1734         return g_strdup(buf);
1735 }
1736
1737 /* **********************************************************************
1738    Address book test functions...
1739    **********************************************************************
1740 */
1741
1742 /*
1743 * Test email address list.
1744 */
1745 static void addrbook_chkparse_addr_list( AddressBookFile *book, XMLFile *file ){
1746         guint prev_level;
1747         GList *attr;
1748
1749         for (;;) {
1750                 prev_level = file->level;
1751                 if (xml_parse_next_tag(file))
1752                         longjmp(book->jumper, 1);
1753                 if (file->level < prev_level)
1754                         return;
1755                 attr = xml_get_current_tag_attr(file);
1756                 /* addrbook_show_attribs( attr ); */
1757                 if (xml_compare_tag(file, AB_ELTAG_ADDRESS))
1758                         addrbook_chkparse_addr_list(book, file);
1759         }
1760 }
1761
1762 /* Test user attributes for person */
1763 static void addrbook_chkparse_attribute(AddressBookFile *book, XMLFile *file)
1764 {
1765         GList *attr;
1766         gchar *element;
1767
1768         attr = xml_get_current_tag_attr(file);
1769         /* addrbook_show_attribs( attr ); */
1770         element = xml_get_element(file);
1771         /* printf( "\t\tattrib value : %s\n", element ); */
1772 }
1773
1774 /* Test attribute list */
1775 static void addrbook_chkparse_attr_list(AddressBookFile *book, XMLFile *file)
1776 {
1777         guint prev_level;
1778
1779         for (;;) {
1780                 prev_level = file->level;
1781                 if (xml_parse_next_tag(file))
1782                         longjmp(book->jumper, 1);
1783                 if (file->level < prev_level) 
1784                         return;
1785                 if (xml_compare_tag(file, AB_ELTAG_ATTRIBUTE)) {
1786                         addrbook_chkparse_attribute(book, file);
1787                         addrbook_chkparse_attr_list(book, file);
1788                 }
1789         }
1790 }
1791
1792 /* Test person */
1793 static void addrbook_chkparse_person(AddressBookFile *book, XMLFile *file)
1794 {
1795         GList *attr;
1796
1797         attr = xml_get_current_tag_attr(file);
1798         /* addrbook_show_attribs( attr ); */
1799         if (xml_parse_next_tag(file)) /* Consume closing tag */
1800                 longjmp(book->jumper, 1);
1801
1802         if (xml_compare_tag(file, AB_ELTAG_ADDRESS_LIST))
1803                 addrbook_chkparse_addr_list(book, file);
1804
1805         if (xml_parse_next_tag(file))   /* Consume closing tag */
1806                 longjmp(book->jumper, 1);
1807
1808         if (xml_compare_tag(file, AB_ELTAG_ATTRIBUTE_LIST))
1809                 addrbook_chkparse_attr_list(book, file);
1810 }
1811
1812 /* Test group member list */
1813 static void addrbook_chkparse_member_list(AddressBookFile *book, XMLFile *file)
1814 {
1815         GList *attr;
1816         guint prev_level;
1817
1818         for (;;) {
1819                 prev_level = file->level;
1820                 if (xml_parse_next_tag(file)) 
1821                         longjmp(book->jumper, 1);
1822
1823                 if (file->level < prev_level)
1824                         return;
1825                         
1826                 if (xml_compare_tag(file, AB_ELTAG_MEMBER)) {
1827                         attr = xml_get_current_tag_attr(file);
1828                         /* addrbook_show_attribs( attr ); */
1829                         addrbook_chkparse_member_list(book, file);
1830                 }
1831                 else {
1832                         attr = xml_get_current_tag_attr(file);
1833                         /* addrbook_show_attribs( attr ); */
1834                 }
1835         }
1836 }
1837
1838 /* Test group */
1839 static void addrbook_chkparse_group(AddressBookFile *book, XMLFile *file)
1840 {
1841         GList *attr;
1842
1843         attr = xml_get_current_tag_attr(file);
1844         /* addrbook_show_attribs( attr ); */
1845         if (xml_parse_next_tag(file))   /* Consume closing tag */
1846                 longjmp(book->jumper, 1);
1847
1848         if (xml_compare_tag(file, AB_ELTAG_MEMBER_LIST))
1849                 addrbook_chkparse_member_list(book, file);
1850 }
1851
1852 /* Test folder item list */
1853 static void addrbook_chkparse_folder_list(AddressBookFile *book, XMLFile *file)
1854 {
1855         GList *attr;
1856         guint prev_level;
1857
1858         for (;;) {
1859                 prev_level = file->level;
1860                 if (xml_parse_next_tag(file))
1861                         longjmp(book->jumper, 1);
1862
1863                 if (file->level < prev_level)
1864                         return;
1865                         
1866                 if (xml_compare_tag(file, AB_ELTAG_ITEM)) {
1867                         attr = xml_get_current_tag_attr(file);
1868                         /* addrbook_show_attribs( attr ); */
1869                         addrbook_chkparse_folder_list(book, file);
1870                 }
1871                 else {
1872                         attr = xml_get_current_tag_attr(file);
1873                         /* addrbook_show_attribs( attr ); */
1874                 }
1875         }
1876 }
1877
1878 /* Test folder */
1879 static void addrbook_chkparse_folder(AddressBookFile *book, XMLFile *file)
1880 {
1881         GList *attr;
1882
1883         attr = xml_get_current_tag_attr(file);
1884         /* addrbook_show_attribs( attr ); */
1885         if (xml_parse_next_tag(file))   /* Consume closing tag */
1886                 longjmp(book->jumper, 1);
1887
1888         if (xml_compare_tag(file, AB_ELTAG_ITEM_LIST))
1889                 addrbook_chkparse_folder_list(book, file);
1890 }
1891
1892 /* Test address book */
1893 static gboolean addrbook_chkread_tree(AddressBookFile *book, XMLFile *file)
1894 {
1895         GList *attr;
1896         gboolean retVal;
1897
1898         if (xml_get_dtd(file))
1899                 return FALSE;
1900
1901         if (xml_parse_next_tag(file))
1902                 return FALSE;
1903
1904         if (!xml_compare_tag(file, AB_ELTAG_ADDRESS_BOOK))
1905                 return FALSE;
1906
1907         attr = xml_get_current_tag_attr(file);
1908         /* addrbook_show_attribs( attr ); */
1909
1910         retVal = TRUE;
1911         for (;;) {
1912                 if (!file->level) 
1913                         break;
1914                 /* Get item tag */
1915                 if (xml_parse_next_tag(file))
1916                         longjmp(book->jumper, 1);
1917                         
1918                 /* Get next tag (person, group or folder) */
1919                 if (xml_compare_tag(file, AB_ELTAG_PERSON))
1920                         addrbook_chkparse_person( book, file );
1921                 else if (xml_compare_tag(file, AB_ELTAG_GROUP))
1922                         addrbook_chkparse_group(book, file);
1923                 else if (xml_compare_tag(file, AB_ELTAG_FOLDER))
1924                         addrbook_chkparse_folder(book, file);
1925         }
1926         return retVal;
1927 }
1928
1929 /* Test address book file by parsing contents.
1930    Enter: book     Address book file to check.
1931           fileName File name to check.
1932    Return: MGU_SUCCESS if file appears to be valid format */
1933 gint addrbook_test_read_file(AddressBookFile *book, gchar *fileName)
1934 {
1935         XMLFile *file = NULL;
1936         gchar *fileSpec = NULL;
1937
1938         g_return_val_if_fail(book != NULL, -1);
1939
1940         fileSpec = g_strconcat(book->path, G_DIR_SEPARATOR_S, fileName, NULL);
1941         book->retVal = MGU_OPEN_FILE;
1942         file = xml_open_file(fileSpec);
1943         g_free(fileSpec);
1944         if (file) {
1945                 book->retVal = MGU_BAD_FORMAT;
1946                 if (setjmp(book->jumper)) {
1947                         /* printf( "Caught Ya!!!\n" ); */
1948                         xml_close_file(file);
1949                         return book->retVal;
1950                 }
1951                 if (addrbook_chkread_tree(book, file))
1952                         book->retVal = MGU_SUCCESS;
1953
1954                 xml_close_file( file );
1955         }
1956         return book->retVal;
1957 }
1958
1959 /* Return link list of all persons in address book.  Note that the list contains
1960    references to items. Do *NOT* attempt to use the addrbook_free_xxx() functions...
1961    this will destroy the addressbook data!
1962    Return: List of items, or NULL if none */
1963 GList *addrbook_get_all_persons(AddressBookFile *book)
1964 {
1965         g_return_val_if_fail(book != NULL, NULL);
1966         return addrcache_get_all_persons(book->addressCache);
1967 }
1968
1969 /* Add person and address data to address book.
1970    Enter: book      Address book.
1971           folder    Folder where to add person, or NULL for root folder.
1972           name      Common name.
1973           address   EMail address.
1974           remarks   Remarks.
1975    Return: Person added. Do not *NOT* to use the addrbook_free_xxx() functions...
1976    this will destroy the address book data */
1977 ItemPerson *addrbook_add_contact(AddressBookFile *book, ItemFolder *folder, 
1978                                  const gchar *name,const gchar *address, 
1979                                  const gchar *remarks)
1980 {
1981         g_return_val_if_fail(book != NULL, NULL);
1982         return addrcache_add_contact(book->addressCache, folder, name, address, 
1983                                      remarks);
1984 }
1985
1986 /* Return file name for next address book file.
1987    Enter:  book Address book.
1988    Return: File name, or NULL if could not create. This should be g_free()
1989            when done */
1990 gchar *addrbook_guess_next_file(AddressBookFile *book)
1991 {
1992         gchar *newFile = NULL;
1993         GList *fileList = NULL;
1994         gint fileNum = 1;
1995         fileList = addrbook_get_bookfile_list(book);
1996         if (fileList)
1997                 fileNum = 1 + book->maxValue;
1998         
1999         newFile = addrbook_gen_new_file_name(fileNum);
2000         g_list_free(fileList);
2001         fileList = NULL;
2002         return newFile;
2003 }