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