talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

addrbook.c (60213B)


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