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