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