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