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