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