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