* major code cleanup (part 1)
[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.
1184 */
1185 static gboolean addrbook_free_simple_hash_vis( gpointer *key, gpointer *value, gpointer *data ) {
1186         g_free( key );
1187         key = NULL;
1188         value = NULL;
1189         return TRUE;
1190 }
1191
1192 /*
1193 * Update address book email list for specified person.
1194 * Enter: book      Address book.
1195 *        person    Person to update.
1196 *        listEMail New list of email addresses.
1197 * Note: The existing email addresses are replaced with the new addresses. Any references
1198 * to old addresses in the groups are re-linked to the new addresses. All old addresses
1199 * linked to the person are removed.
1200 */
1201 void addrbook_update_address_list( AddressBookFile *book, ItemPerson *person, GList *listEMail ) {
1202         GList *node;
1203         GList *oldData;
1204         GList *listGroup;
1205
1206         g_return_if_fail( book != NULL );
1207         g_return_if_fail( person != NULL );
1208
1209         /* Remember old list */
1210         oldData = person->listEMail;
1211
1212         /* Attach new address list to person. */
1213         node = listEMail;
1214         while( node ) {
1215                 ItemEMail *email = node->data;
1216                 if( ADDRITEM_ID(email) == NULL ) {
1217                         /* Allocate an ID */
1218                         addrcache_id_email( book->addressCache, email );
1219                 }
1220                 ADDRITEM_PARENT(email) = ADDRITEM_OBJECT(person);
1221                 node = g_list_next( node );
1222         }
1223         person->listEMail = listEMail;
1224
1225         /* Get groups where person's email is listed */
1226         listGroup = addrcache_get_group_for_person( book->addressCache, person );
1227         if( listGroup ) {
1228                 GHashTable *hashEMail;
1229                 GList *nodeGrp;
1230
1231                 /* Load hash table with new address entries */
1232                 hashEMail = g_hash_table_new( g_str_hash, g_str_equal );
1233                 node = listEMail;
1234                 while( node ) {
1235                         ItemEMail *email = node->data;
1236                         gchar *addr = g_strdup( email->address );
1237                         g_strdown( addr );
1238                         if( ! g_hash_table_lookup( hashEMail, addr ) ) {
1239                                 g_hash_table_insert( hashEMail, addr, email );
1240                         }
1241                         node = g_list_next( node );
1242                 }
1243
1244                 /* Re-parent new addresses to existing groups, where email address match. */
1245                 nodeGrp = listGroup;
1246                 while( nodeGrp ) {
1247                         ItemGroup *group = ( ItemGroup * ) nodeGrp->data;
1248                         GList *groupEMail = group->listEMail;
1249                         GList *nodeGrpEM;
1250                         GList *listRemove = NULL;
1251
1252                         /* Process each email item linked to group */
1253                         nodeGrpEM = groupEMail;
1254                         while( nodeGrpEM ) {
1255                                 ItemEMail *emailGrp = ( ItemEMail * ) nodeGrpEM->data;
1256                                 if( ADDRITEM_PARENT(emailGrp) == ADDRITEM_OBJECT(person) ) {
1257                                         /* Found an email address for this person */
1258                                         ItemEMail *emailNew = NULL;
1259                                         gchar *addr = g_strdup( emailGrp->address );
1260                                         g_strdown( addr );
1261                                         emailNew = ( ItemEMail * ) g_hash_table_lookup( hashEMail, addr );
1262                                         g_free( addr );
1263                                         if( emailNew ) {
1264                                                 /* Point to this entry */
1265                                                 nodeGrpEM->data = emailNew;
1266                                         }
1267                                         else {
1268                                                 /* Mark for removal */
1269                                                 listRemove = g_list_append( listRemove, emailGrp );
1270                                         }
1271                                 }
1272                                 /* Move on to next email link */
1273                                 nodeGrpEM = g_list_next( nodeGrpEM );
1274                         }
1275
1276                         /* Process all removed links in current group */
1277                         nodeGrpEM = listRemove;
1278                         while( nodeGrpEM ) {
1279                                 ItemEMail *emailGrp = nodeGrpEM->data;
1280                                 groupEMail = g_list_remove( groupEMail, emailGrp );
1281                                 nodeGrpEM = g_list_next( nodeGrpEM );
1282                         }
1283
1284                         /* Move on to next group */
1285                         nodeGrp = g_list_next( nodeGrp );
1286
1287                 }
1288
1289                 /* Clear hash table */
1290                 g_hash_table_foreach_remove( hashEMail, ( GHRFunc ) addrbook_free_simple_hash_vis, NULL );
1291                 g_hash_table_destroy( hashEMail );
1292                 hashEMail = NULL;
1293                 g_list_free( listGroup );
1294                 listGroup = NULL;
1295         }
1296         addrcache_set_dirty( book->addressCache, TRUE );
1297
1298         /* Free up old data */
1299         addritem_free_list_email( oldData );
1300         oldData = NULL;
1301
1302 }
1303
1304 /*
1305 * Add person and address data to address book.
1306 * Enter: book      Address book.
1307 *        folder    Folder where to add person, or NULL for root folder.
1308 *        listEMail New list of email addresses.
1309 * Return: Person added.
1310 * Note: A new person is created with specified list of email addresses. All objects inserted
1311 * into address book.
1312 */
1313 ItemPerson *addrbook_add_address_list( AddressBookFile *book, ItemFolder *folder, GList *listEMail ) {
1314         ItemPerson *person;
1315         ItemFolder *f = folder;
1316         GList *node;
1317
1318         g_return_val_if_fail( book != NULL, NULL );
1319
1320         if( ! f ) f = book->addressCache->rootFolder;
1321         person = addritem_create_item_person();
1322         addrcache_id_person( book->addressCache, person );
1323         addrcache_folder_add_person( book->addressCache, f, person );
1324
1325         node = listEMail;
1326         while( node ) {
1327                 ItemEMail *email = node->data;
1328                 if( ADDRITEM_ID(email) == NULL ) {
1329                         addrcache_id_email( book->addressCache, email );
1330                 }
1331                 addrcache_person_add_email( book->addressCache, person, email );
1332                 node = g_list_next( node );
1333         }
1334         return person;
1335 }
1336
1337 /*
1338 * Load hash table visitor function.
1339 */
1340 static void addrbook_load_hash_table_email_vis( gpointer key, gpointer value, gpointer data ) {
1341         AddrItemObject *obj = ( AddrItemObject * ) value;
1342
1343         if( ADDRITEM_TYPE(obj) == ITEMTYPE_EMAIL ) {
1344                 GHashTable *table = ( GHashTable * ) data;
1345                 gchar *newKey = g_strdup( key );
1346                 ItemEMail *email = ( ItemEMail * ) obj;
1347                 if( ! g_hash_table_lookup( table, newKey ) ) {
1348                         g_hash_table_insert( table, newKey, email );
1349                 }
1350         }
1351 }
1352
1353 /*
1354 * Build available email list visitor function.
1355 */
1356 static void addrbook_build_avail_email_vis( gpointer key, gpointer value, gpointer data ) {
1357         AddrItemObject *obj = ( AddrItemObject * ) value;
1358
1359         if( ADDRITEM_TYPE(obj) == ITEMTYPE_PERSON ) {
1360                 AddressBookFile *book = data;
1361                 ItemPerson *person = ( ItemPerson * ) obj;
1362                 GList *node = person->listEMail;
1363                 while( node ) {
1364                         ItemEMail *email = node->data;
1365                         /* gchar *newKey = g_strdup( ADDRITEM_ID(email) ); */
1366
1367                         if( ! g_hash_table_lookup( book->tempHash, ADDRITEM_ID(email) ) ) {
1368                                 book->tempList = g_list_append( book->tempList, email );
1369                         }
1370                         node = g_list_next( node );
1371                 }
1372         }
1373 }
1374
1375 /*
1376 * Return link list of available email items (which have not already been linked to
1377 * groups). Note that the list contains references to items and should be g_free()
1378 * when done. Do *NOT* attempt to used the addrbook_free_xxx() functions... this will
1379 * destroy the addressbook data!
1380 * Return: List of items, or NULL if none.
1381 */
1382 GList *addrbook_get_available_email_list( AddressBookFile *book, ItemGroup *group ) {
1383         GList *list = NULL;
1384         GHashTable *table;
1385
1386         g_return_val_if_fail( book != NULL, NULL );
1387
1388         /* Load hash table with group email entries */
1389         table = g_hash_table_new( g_str_hash, g_str_equal );
1390         if( group ) {
1391                 list = group->listEMail;
1392                 while( list ) {
1393                         ItemEMail *email = list->data;
1394                         g_hash_table_insert( table, ADDRITEM_ID(email), email );
1395                         list = g_list_next( list );
1396                 }
1397         }
1398
1399         /* Build list of available email addresses which exclude those already in groups */
1400         book->tempList = NULL;
1401         book->tempHash = table;
1402         g_hash_table_foreach( book->addressCache->itemHash, addrbook_build_avail_email_vis, book );
1403         list = book->tempList;
1404         book->tempList = NULL;
1405         book->tempHash = NULL;
1406
1407         /* Clear hash table */
1408         g_hash_table_destroy( table );
1409         table = NULL;
1410
1411         return list;
1412 }
1413
1414 /*
1415 * Update address book email list for specified group.
1416 * Enter: book      Address book.
1417 *        group     group to update.
1418 *        listEMail New list of email addresses. This should *NOT* be g_free() when done.
1419 * Note: The existing email addresses are replaced with the new addresses. Any references
1420 * to old addresses in the groups are re-linked to the new addresses. All old addresses
1421 * linked to the person are removed.
1422 */
1423 void addrbook_update_group_list( AddressBookFile *book, ItemGroup *group, GList *listEMail ) {
1424         GList *oldData;
1425
1426         g_return_if_fail( book != NULL );
1427         g_return_if_fail( group != NULL );
1428
1429         addrcache_set_dirty( book->addressCache, TRUE );
1430
1431         /* Remember old list */
1432         oldData = group->listEMail;
1433         group->listEMail = listEMail;
1434         mgu_clear_list( oldData );
1435         oldData = NULL;
1436 }
1437
1438 /*
1439 * Add group and email list to address book.
1440 * Enter: book      Address book.
1441 *        folder    Parent folder, or NULL for root folder.
1442 *        listEMail New list of email addresses. This should *NOT* be g_free() when done.
1443 * Return: Group object.
1444 * Note: The existing email addresses are replaced with the new addresses. Any references
1445 * to old addresses in the groups are re-linked to the new addresses. All old addresses
1446 * linked to the person are removed.
1447 */
1448 ItemGroup *addrbook_add_group_list( AddressBookFile *book, ItemFolder *folder, GList *listEMail ) {
1449         ItemGroup *group = NULL;
1450         ItemFolder *f = folder;
1451
1452         g_return_val_if_fail( book != NULL, NULL );
1453
1454         if( ! f ) f = book->addressCache->rootFolder;
1455         group = addritem_create_item_group();
1456         addrcache_id_group( book->addressCache, group );
1457         addrcache_folder_add_group( book->addressCache, f, group );
1458         group->listEMail = listEMail;
1459         return group;
1460 }
1461
1462 /*
1463 * Add new folder to address book.
1464 * Enter: book   Address book.
1465 *        parent Parent folder.
1466 * Return: Folder that was added. This should *NOT* be g_free() when done.
1467 */
1468 ItemFolder *addrbook_add_new_folder( AddressBookFile *book, ItemFolder *parent ) {
1469         ItemFolder *folder = NULL;
1470         ItemFolder *p = parent;
1471
1472         g_return_val_if_fail( book != NULL, NULL );
1473
1474         if( ! p ) p = book->addressCache->rootFolder;
1475         folder = addritem_create_item_folder();
1476         addrcache_id_folder( book->addressCache, folder );
1477         if( addrcache_hash_add_folder( book->addressCache, folder ) ) {
1478                 p->listFolder = g_list_append( p->listFolder, folder );
1479                 ADDRITEM_PARENT(folder) = ADDRITEM_OBJECT(p);
1480                 addrcache_set_dirty( book->addressCache, TRUE );
1481         }
1482         else {
1483                 addritem_free_item_folder( folder );
1484                 folder = NULL;
1485         }
1486         return folder;
1487 }
1488
1489 /*
1490 * Update address book attribute list for specified person.
1491 * Enter: book       Address book.
1492 *        person     Person to update.
1493 *        listAttrib New list of attributes.
1494 * Note: The existing email addresses are replaced with the new addresses. All old attributes
1495 * linked to the person are removed.
1496 */
1497 void addrbook_update_attrib_list( AddressBookFile *book, ItemPerson *person, GList *listAttrib ) {
1498         GList *node;
1499         GList *oldData;
1500
1501         g_return_if_fail( book != NULL );
1502         g_return_if_fail( person != NULL );
1503
1504         /* Remember old list */
1505         oldData = person->listAttrib;
1506
1507         /* Attach new address list to person. */
1508         node = listAttrib;
1509         while( node ) {
1510                 UserAttribute *attrib = node->data;
1511                 if( attrib->uid == NULL ) {
1512                         /* Allocate an ID */
1513                         addrcache_id_attribute( book->addressCache, attrib );
1514                 }
1515                 node = g_list_next( node );
1516         }
1517         person->listAttrib = listAttrib;
1518         addrcache_set_dirty( book->addressCache, TRUE );
1519
1520         /* Free up old data */
1521         addritem_free_list_attribute( oldData );
1522         oldData = NULL;
1523
1524 }
1525
1526 /*
1527 * Add attribute data for person to address book.
1528 * Enter: book       Address book.
1529 *        person     New person object.
1530 *        listAttrib New list of attributes.
1531 * Note: Only attributes are inserted into address book.
1532 */
1533 void addrbook_add_attrib_list( AddressBookFile *book, ItemPerson *person, GList *listAttrib ) {
1534         GList *node;
1535
1536         g_return_if_fail( book != NULL );
1537         g_return_if_fail( person != NULL );
1538
1539         node = listAttrib;
1540         while( node ) {
1541                 UserAttribute *attrib = node->data;
1542                 if( attrib->uid == NULL ) {
1543                         addrcache_id_attribute( book->addressCache, attrib );
1544                 }
1545                 addritem_person_add_attribute( person, attrib );
1546                 node = g_list_next( node );
1547         }
1548         addrcache_set_dirty( book->addressCache, TRUE );
1549 }
1550
1551 /*
1552 * Return address book file for specified object.
1553 * Enter: aio Book item object.
1554 * Return: Address book, or NULL if not found.
1555 */
1556 AddressBookFile *addrbook_item_get_bookfile( AddrItemObject *aio ) {
1557         AddressBookFile *book = NULL;
1558
1559         if( aio ) {
1560                 ItemFolder *parent = NULL;
1561                 ItemFolder *root = NULL;
1562                 if( aio->type == ITEMTYPE_EMAIL ) {
1563                         ItemPerson *person = ( ItemPerson * ) ADDRITEM_PARENT(aio);
1564                         if( person ) {
1565                                 parent = ( ItemFolder * ) ADDRITEM_PARENT(person);
1566                         }
1567                 }
1568                 else {
1569                         parent = ( ItemFolder * ) ADDRITEM_PARENT(aio);
1570                 }
1571                 if( parent ) {
1572                         root = addrcache_find_root_folder( parent );
1573                 }
1574                 if( root ) {
1575                         book = ( AddressBookFile * ) ADDRITEM_PARENT(root);
1576                 }
1577         }
1578         return book;
1579 }
1580
1581 /*
1582 * Remove folder from address book. Children are re-parented to parent folder.
1583 * param: folder Folder to remove.
1584 * return: Folder, or NULL if not found. Note that object should still be freed.
1585 */
1586 ItemFolder *addrbook_remove_folder( AddressBookFile *book, ItemFolder *folder ) {
1587         ItemFolder *f;
1588
1589         g_return_val_if_fail( book != NULL, NULL );
1590
1591         f = addrcache_remove_folder( book->addressCache, folder );
1592         return f;
1593 }
1594
1595 /*
1596 * Remove folder from address book. Children are deleted.
1597 * param: folder Folder to remove.
1598 * return: Folder, or NULL if not found. Note that object should still be freed.
1599 */
1600 ItemFolder *addrbook_remove_folder_delete( AddressBookFile *book, ItemFolder *folder ) {
1601         ItemFolder *f;
1602
1603         g_return_val_if_fail( book != NULL, NULL );
1604
1605         f = addrcache_remove_folder_delete( book->addressCache, folder );
1606         return f;
1607 }
1608
1609 #define WORK_BUFLEN     1024
1610 #define ADDRBOOK_DIGITS "0123456789"
1611
1612 /*
1613 * Return list of existing address book files.
1614 * Enter: book Address book file.
1615 * Return: File list.
1616 */
1617 GList *addrbook_get_bookfile_list( AddressBookFile *book ) {
1618         gchar *adbookdir;
1619         DIR *dp;
1620         struct dirent *entry;
1621         struct stat statbuf;
1622         gchar buf[ WORK_BUFLEN ];
1623         gchar numbuf[ WORK_BUFLEN ];
1624         gint len, lenpre, lensuf, lennum;
1625         long int val, maxval;
1626         GList *fileList = NULL;
1627
1628         g_return_val_if_fail( book != NULL, NULL );
1629
1630         if( book->path == NULL || *book->path == '\0' ) {
1631                 book->retVal = MGU_NO_PATH;
1632                 return NULL;
1633         }
1634
1635         strcpy( buf, book->path );
1636         len = strlen( buf );
1637         if( len > 0 ) {
1638                 if( buf[ len-1 ] != G_DIR_SEPARATOR ) {
1639                         buf[ len ] = G_DIR_SEPARATOR;
1640                         buf[ ++len ] = '\0';
1641                 }
1642         }
1643
1644         adbookdir = g_strdup( buf );
1645         strcat( buf, ADDRBOOK_PREFIX );
1646
1647         if( ( dp = opendir( adbookdir ) ) == NULL ) {
1648                 book->retVal = MGU_OPEN_DIRECTORY;
1649                 g_free( adbookdir );
1650                 return NULL;
1651         }
1652
1653         lenpre = strlen( ADDRBOOK_PREFIX );
1654         lensuf = strlen( ADDRBOOK_SUFFIX );
1655         lennum = FILE_NUMDIGITS + lenpre;
1656         maxval = -1;
1657
1658         while( ( entry = readdir( dp ) ) != NULL ) {
1659                 gchar *endptr = NULL;
1660                 gint i;
1661                 gboolean flg;
1662
1663                 strcpy( buf, adbookdir );
1664                 strcat( buf, entry->d_name );
1665                 stat( buf, &statbuf );
1666                 if( S_IFREG & statbuf.st_mode ) {
1667                         if( strncmp( entry->d_name, ADDRBOOK_PREFIX, lenpre ) == 0 ) {
1668                                 if( strncmp( (entry->d_name) + lennum, ADDRBOOK_SUFFIX, lensuf ) == 0 ) {
1669                                         strncpy( numbuf, (entry->d_name) + lenpre, FILE_NUMDIGITS );
1670                                         numbuf[ FILE_NUMDIGITS ] = '\0';
1671                                         flg = TRUE;
1672                                         for( i = 0; i < FILE_NUMDIGITS; i++ ) {
1673                                                 if( ! strchr( ADDRBOOK_DIGITS, numbuf[i] ) ) {
1674                                                         flg = FALSE;
1675                                                         break;
1676                                                 }
1677                                         }
1678                                         if( flg ) {
1679                                                 /* Get value */
1680                                                 val = strtol( numbuf, &endptr, 10 );
1681                                                 if( endptr  && val > -1 ) {
1682                                                         if( val > maxval ) maxval = val;
1683                                                         fileList = g_list_append( fileList, g_strdup( entry->d_name ) );
1684                                                 }
1685                                         }
1686                                 }
1687                         }
1688                 }
1689         }
1690         closedir( dp );
1691         g_free( adbookdir );
1692
1693         book->maxValue = maxval; 
1694         book->retVal = MGU_SUCCESS;
1695         return fileList;
1696 }
1697
1698 /*
1699 * Return file name for specified file number.
1700 * Enter:  fileNum File number.
1701 * Return: File name, or NULL if file number too large. Should be g_free() when done.
1702 */
1703 gchar *addrbook_gen_new_file_name( gint fileNum ) {
1704         gchar fmt[ 30 ];
1705         gchar buf[ WORK_BUFLEN ];
1706         gint n = fileNum;
1707         long int nmax;
1708
1709         if( n < 1 ) n = 1;
1710         nmax = -1 + (long int) pow( 10, FILE_NUMDIGITS );
1711         if( fileNum > nmax ) return NULL;
1712         sprintf( fmt, "%%s%%0%dd%%s", FILE_NUMDIGITS );
1713         sprintf( buf, fmt, ADDRBOOK_PREFIX, n, ADDRBOOK_SUFFIX );
1714         return g_strdup( buf );
1715 }
1716
1717 /* **********************************************************************
1718 * Address book test functions...
1719 * ***********************************************************************
1720 */
1721
1722 /*
1723 * Test email address list.
1724 */
1725 static void addrbook_chkparse_addr_list( AddressBookFile *book, XMLFile *file ){
1726         guint prev_level;
1727         GList *attr;
1728
1729         for (;;) {
1730                 prev_level = file->level;
1731                 if( xml_parse_next_tag( file ) ) {
1732                         longjmp( book->jumper, 1 );
1733                 }
1734                 if (file->level < prev_level) return;
1735                 attr = xml_get_current_tag_attr(file);
1736                 /* addrbook_show_attribs( attr ); */
1737                 if( xml_compare_tag( file, AB_ELTAG_ADDRESS ) ) {
1738                         addrbook_chkparse_addr_list( book, file );
1739                 }
1740         }
1741 }
1742
1743 /*
1744 * Test user attributes for person.
1745 */
1746 static void addrbook_chkparse_attribute( AddressBookFile *book, XMLFile *file ) {
1747         GList *attr;
1748         gchar *element;
1749
1750         attr = xml_get_current_tag_attr(file);
1751         /* addrbook_show_attribs( attr ); */
1752         element = xml_get_element( file );
1753         /* printf( "\t\tattrib value : %s\n", element ); */
1754 }
1755
1756 /*
1757 * Test attribute list.
1758 */
1759 static void addrbook_chkparse_attr_list( AddressBookFile *book, XMLFile *file ){
1760         guint prev_level;
1761
1762         for (;;) {
1763                 prev_level = file->level;
1764                 if( xml_parse_next_tag( file ) ) {
1765                         longjmp( book->jumper, 1 );
1766                 }
1767                 if (file->level < prev_level) return;
1768                 if( xml_compare_tag( file, AB_ELTAG_ATTRIBUTE ) ) {
1769                         addrbook_chkparse_attribute( book, file );
1770                         addrbook_chkparse_attr_list( book, file );
1771                 }
1772         }
1773 }
1774
1775 /*
1776 * Test person.
1777 */
1778 static void addrbook_chkparse_person( AddressBookFile *book, XMLFile *file ) {
1779         GList *attr;
1780
1781         attr = xml_get_current_tag_attr(file);
1782         /* addrbook_show_attribs( attr ); */
1783         if( xml_parse_next_tag( file ) ) {      /* Consume closing tag */
1784                 longjmp( book->jumper, 1 );
1785         }
1786         if( xml_compare_tag( file, AB_ELTAG_ADDRESS_LIST ) ) {
1787                 addrbook_chkparse_addr_list( book, file );
1788         }
1789         if( xml_parse_next_tag( file ) ) {      /* Consume closing tag */
1790                 longjmp( book->jumper, 1 );
1791         }
1792         if( xml_compare_tag( file, AB_ELTAG_ATTRIBUTE_LIST ) ) {
1793                 addrbook_chkparse_attr_list( book, file );
1794         }
1795 }
1796
1797 /*
1798 * Test group member list.
1799 */
1800 static void addrbook_chkparse_member_list( AddressBookFile *book, XMLFile *file ){
1801         GList *attr;
1802         guint prev_level;
1803
1804         for (;;) {
1805                 prev_level = file->level;
1806                 if( xml_parse_next_tag( file ) ) {
1807                         longjmp( book->jumper, 1 );
1808                 }
1809                 if (file->level < prev_level) return;
1810                 if( xml_compare_tag( file, AB_ELTAG_MEMBER ) ) {
1811                         attr = xml_get_current_tag_attr(file);
1812                         /* addrbook_show_attribs( attr ); */
1813                         addrbook_chkparse_member_list( book, file );
1814                 }
1815                 else {
1816                         attr = xml_get_current_tag_attr( file );
1817                         /* addrbook_show_attribs( attr ); */
1818                 }
1819         }
1820 }
1821
1822 /*
1823 * Test group.
1824 */
1825 static void addrbook_chkparse_group( 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_MEMBER_LIST ) ) {
1834                 addrbook_chkparse_member_list( book, file );
1835         }
1836 }
1837
1838 /*
1839 * Test folder item list.
1840 */
1841 static void addrbook_chkparse_folder_list( AddressBookFile *book, XMLFile *file ){
1842         GList *attr;
1843         guint prev_level;
1844
1845         for (;;) {
1846                 prev_level = file->level;
1847                 if( xml_parse_next_tag( file ) ) {
1848                         longjmp( book->jumper, 1 );
1849                 }
1850                 if (file->level < prev_level) return;
1851                 if( xml_compare_tag( file, AB_ELTAG_ITEM ) ) {
1852                         attr = xml_get_current_tag_attr(file);
1853                         /* addrbook_show_attribs( attr ); */
1854                         addrbook_chkparse_folder_list( book, file );
1855                 }
1856                 else {
1857                         attr = xml_get_current_tag_attr( file );
1858                         /* addrbook_show_attribs( attr ); */
1859                 }
1860         }
1861 }
1862
1863 /*
1864 * Test folder.
1865 */
1866 static void addrbook_chkparse_folder( AddressBookFile *book, XMLFile *file ) {
1867         GList *attr;
1868
1869         attr = xml_get_current_tag_attr(file);
1870         /* addrbook_show_attribs( attr ); */
1871         if( xml_parse_next_tag( file ) ) {      /* Consume closing tag */
1872                 longjmp( book->jumper, 1 );
1873         }
1874         if( xml_compare_tag( file, AB_ELTAG_ITEM_LIST ) ) {
1875                 addrbook_chkparse_folder_list( book, file );
1876         }
1877 }
1878
1879 /*
1880 * Test address book.
1881 */
1882 static gboolean addrbook_chkread_tree( AddressBookFile *book, XMLFile *file ) {
1883         GList *attr;
1884         gboolean retVal;
1885
1886         if( xml_get_dtd( file ) ) {
1887                 return FALSE;
1888         }
1889         if( xml_parse_next_tag( file ) ) {
1890                 return FALSE;
1891         }
1892
1893         if( ! xml_compare_tag( file, AB_ELTAG_ADDRESS_BOOK ) ) {
1894                 return FALSE;
1895         }
1896
1897         attr = xml_get_current_tag_attr(file);
1898         /* addrbook_show_attribs( attr ); */
1899
1900         retVal = TRUE;
1901         for (;;) {
1902                 if (! file->level ) break;
1903                 /* Get item tag */
1904                 if( xml_parse_next_tag( file ) ) {
1905                         longjmp( book->jumper, 1 );
1906                 }
1907                 /* Get next tag (person, group or folder) */
1908                 if( xml_compare_tag( file, AB_ELTAG_PERSON ) ) {
1909                         addrbook_chkparse_person( book, file );
1910                 }
1911                 else if( xml_compare_tag( file, AB_ELTAG_GROUP ) ) {
1912                         addrbook_chkparse_group( book, file );
1913                 }
1914                 else if( xml_compare_tag( file, AB_ELTAG_FOLDER ) ) {
1915                         addrbook_chkparse_folder( book, file );
1916                 }
1917         }
1918         return retVal;
1919 }
1920
1921 /*
1922 * Test address book file by parsing contents.
1923 * Enter: book     Address book file to check.
1924 *        fileName File name to check.
1925 * Return: MGU_SUCCESS if file appears to be valid format.
1926 */
1927 gint addrbook_test_read_file( AddressBookFile *book, gchar *fileName ) {
1928         XMLFile *file = NULL;
1929         gchar *fileSpec = NULL;
1930
1931         g_return_val_if_fail( book != NULL, -1 );
1932
1933         fileSpec = g_strconcat( book->path, G_DIR_SEPARATOR_S, fileName, NULL );
1934         book->retVal = MGU_OPEN_FILE;
1935         file = xml_open_file( fileSpec );
1936         g_free( fileSpec );
1937         if( file ) {
1938                 book->retVal = MGU_BAD_FORMAT;
1939                 if( setjmp( book->jumper ) ) {
1940                         /* printf( "Caught Ya!!!\n" ); */
1941                         xml_close_file( file );
1942                         return book->retVal;
1943                 }
1944                 if( addrbook_chkread_tree( book, file ) ) {
1945                         book->retVal = MGU_SUCCESS;
1946                 }
1947                 xml_close_file( file );
1948         }
1949         return book->retVal;
1950 }
1951
1952 /*
1953 * Return link list of all persons in address book.  Note that the list contains
1954 * references to items. Do *NOT* attempt to use the addrbook_free_xxx() functions...
1955 * this will destroy the addressbook data!
1956 * Return: List of items, or NULL if none.
1957 */
1958 GList *addrbook_get_all_persons( AddressBookFile *book ) {
1959         g_return_val_if_fail( book != NULL, NULL );
1960         return addrcache_get_all_persons( book->addressCache );
1961 }
1962
1963 /*
1964 * Add person and address data to address book.
1965 * Enter: book      Address book.
1966 *        folder    Folder where to add person, or NULL for root folder.
1967 *        name      Common name.
1968 *        address   EMail address.
1969 *        remarks   Remarks.
1970 * Return: Person added. Do not *NOT* to use the addrbook_free_xxx() functions...
1971 * this will destroy the address book data.
1972 */
1973 ItemPerson *addrbook_add_contact( AddressBookFile *book, ItemFolder *folder, const gchar *name,
1974                 const gchar *address, const gchar *remarks )
1975 {
1976         g_return_val_if_fail( book != NULL, NULL );
1977         return addrcache_add_contact( book->addressCache, folder, name, address, remarks );
1978 }
1979
1980 /*
1981  * Return file name for next address book file.
1982  * Enter:  book Address book.
1983  * Return: File name, or NULL if could not create. This should be g_free()
1984  *         when done.
1985  */
1986 gchar *addrbook_guess_next_file( AddressBookFile *book ) {
1987         gchar *newFile = NULL;
1988         GList *fileList = NULL;
1989         gint fileNum = 1;
1990         fileList = addrbook_get_bookfile_list( book );
1991         if( fileList ) {
1992                 fileNum = 1 + book->maxValue;
1993         }
1994         newFile = addrbook_gen_new_file_name( fileNum );
1995         g_list_free( fileList );
1996         fileList = NULL;
1997         return newFile;
1998 }
1999
2000 /*
2001 * End of Source.
2002 */
2003
2004