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