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