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