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