2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2002 Match Grun
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.
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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * Functions necessary to access Pine address book file.
32 #include "addrcache.h"
34 #define PINE_HOME_FILE ".addressbook"
35 #define PINEBUFSIZE 2048
36 #define CHAR_QUOTE '\"'
37 #define CHAR_APOS '\''
38 #define CHAR_COMMA ','
44 PineFile *pine_create() {
46 pineFile = g_new0( PineFile, 1 );
47 pineFile->path = NULL;
48 pineFile->file = NULL;
49 pineFile->retVal = MGU_SUCCESS;
50 pineFile->uniqTable = g_hash_table_new( g_str_hash, g_str_equal );
51 pineFile->cbProgress = NULL;
58 void pine_set_file( PineFile* pineFile, const gchar *value ) {
59 g_return_if_fail( pineFile != NULL );
60 pineFile->path = mgu_replace_string( pineFile->path, value );
61 g_strstrip( pineFile->path );
65 * Register a callback function. When called, the function will be passed
66 * the following arguments:
69 * Current position (long)
70 * This can be used for a progress indicator.
72 void pine_set_callback( PineFile *pineFile, void *func ) {
73 pineFile->cbProgress = func;
79 static gint pine_free_table_vis( gpointer key, gpointer value, gpointer data ) {
87 * Free up object by releasing internal memory.
89 void pine_free( PineFile *pineFile ) {
90 g_return_if_fail( pineFile != NULL );
93 if( pineFile->file ) fclose( pineFile->file );
95 /* Free internal stuff */
96 g_free( pineFile->path );
98 /* Free unique address table */
99 g_hash_table_freeze( pineFile->uniqTable );
100 g_hash_table_foreach_remove( pineFile->uniqTable, pine_free_table_vis, NULL );
101 g_hash_table_thaw( pineFile->uniqTable );
102 g_hash_table_destroy( pineFile->uniqTable );
105 pineFile->file = NULL;
106 pineFile->path = NULL;
107 pineFile->retVal = MGU_SUCCESS;
108 pineFile->uniqTable = NULL;
109 pineFile->cbProgress = NULL;
111 /* Now release file object */
116 * Display object to specified stream.
117 * Enter: pineFile File object.
120 void pine_print_file( PineFile *pineFile, FILE *stream ) {
121 g_return_if_fail( pineFile != NULL );
122 fprintf( stream, "Pine File:\n" );
123 fprintf( stream, "file spec: '%s'\n", pineFile->path );
124 fprintf( stream, " ret val: %d\n", pineFile->retVal );
128 * Open file for read.
129 * Enter: pineFile File object.
130 * return: TRUE if file opened successfully.
132 static gint pine_open_file( PineFile* pineFile ) {
133 if( pineFile->path ) {
134 pineFile->file = fopen( pineFile->path, "rb" );
135 if( ! pineFile->file ) {
136 pineFile->retVal = MGU_OPEN_FILE;
137 return pineFile->retVal;
141 /* printf( "file not specified\n" ); */
142 pineFile->retVal = MGU_NO_FILE;
143 return pineFile->retVal;
146 /* Setup a buffer area */
147 pineFile->retVal = MGU_SUCCESS;
148 return pineFile->retVal;
153 * Enter: pineFile File object.
155 static void pine_close_file( PineFile *pineFile ) {
156 g_return_if_fail( pineFile != NULL );
157 if( pineFile->file ) fclose( pineFile->file );
158 pineFile->file = NULL;
162 * Read line of text from file.
163 * Enter: pineFile File object.
164 * Return: Copy of buffer. Should be g_free'd when done.
166 static gchar *pine_read_line( PineFile *pineFile ) {
167 gchar buf[ PINEBUFSIZE ];
172 if( feof( pineFile->file ) ) return NULL;
177 c = fgetc( pineFile->file );
179 if( *buf == '\0' ) return NULL;
184 if( *buf == '\0' ) return NULL;
194 /* Copy into private buffer */
195 return g_strdup( buf );
199 * Parsed address data.
201 typedef struct _Pine_ParsedRec_ Pine_ParsedRec;
202 struct _Pine_ParsedRec_ {
215 * Enter: rec Data record.
217 static void pine_free_rec( Pine_ParsedRec *rec ) {
219 g_free( rec->nickName );
221 g_free( rec->address );
223 g_free( rec->comments );
224 mgu_clear_slist( rec->listName );
225 mgu_clear_slist( rec->listAddr );
226 g_slist_free( rec->listName );
227 g_slist_free( rec->listAddr );
228 rec->nickName = NULL;
232 rec->comments = NULL;
233 rec->isGroup = FALSE;
241 static void pine_clean_name( Pine_ParsedRec *rec ) {
245 if( p == NULL ) return;
246 if( *p == '\0' ) return;
248 g_strstrip( rec->name );
249 if( *p == CHAR_APOS || *p == CHAR_QUOTE ) {
253 /* If embedded comma present, surround match with quotes */
255 if( *p == CHAR_COMMA ) {
256 p = g_strdup_printf( "\"%s\"", rec->name );
266 * Parse pine address record.
267 * Enter: buf Address record buffer.
268 * Return: Data record.
270 static Pine_ParsedRec *pine_parse_record( gchar *buf ) {
276 for( i = 0; i < 5; i++ )
279 /* Extract tab separated values */
287 tmp[ pos ] = g_strndup( f, len );
296 /* Extract last value */
299 tmp[ pos++ ] = g_strndup( f, len );
302 /* Populate record */
304 rec = g_new0( Pine_ParsedRec, 1 );
305 rec->isGroup = FALSE;
306 for( i = 0; i < pos; i++ ) {
311 if( i == 0 ) rec->nickName = f;
312 else if( i == 1 ) rec->name = f;
313 else if( i == 2 ) rec->address = f;
314 else if( i == 3 ) rec->fcc = f;
315 else if( i == 4 ) rec->comments = f;
319 if( rec->address != NULL ) {
320 /* Strip leading/trailing parens */
323 len = strlen( p ) - 1;
335 * Parse name from email address string.
336 * Enter: buf Start address of buffer to process (not modified).
337 * atp Pointer to email at (@) character.
338 * ap Pointer to start of email address returned.
339 * ep Pointer to end of email address returned.
340 * Return: Parsed name or NULL if not present. This should be g_free'd
343 static gchar *pine_parse_name(
344 const gchar *buf, const gchar *atp, const gchar **ap,
357 /* Find first non-separator char */
360 if( strchr( ",; \n\r", *bp ) == NULL ) break;
364 /* Search back for start of name */
370 /* Found start of address/end of name part */
371 ilen = -1 + ( size_t ) ( pos - bp );
372 name = g_strndup( bp, ilen + 1 );
373 *(name + ilen + 1) = '\0';
375 /* Remove leading trailing quotes and spaces */
376 mgu_str_ltc2space( name, '\"', '\"' );
377 mgu_str_ltc2space( name, '\'', '\'' );
378 mgu_str_ltc2space( name, '\"', '\"' );
379 mgu_str_unescape( name );
387 /* Search forward for end of address */
394 if( strchr( ",; \'\n\r", *pos ) ) break;
403 * Parse address list.
404 * Enter: pineFile Pine control data.
405 * cache Address cache.
408 static void pine_parse_address( PineFile *pineFile, AddressCache *cache, Pine_ParsedRec *rec ) {
410 gchar addr[ PINEBUFSIZE ];
417 g_return_if_fail( rec->address != NULL );
420 while((atCh = strchr( buf, CHAR_AT )) != NULL) {
421 name = pine_parse_name( buf, atCh, &bp, &ep );
422 len = ( size_t ) ( ep - bp );
423 strncpy( addr, bp, len );
425 extract_address( addr );
427 if( name == NULL ) name = g_strdup( "" );
428 rec->listName = g_slist_append( rec->listName, name );
429 rec->listAddr = g_slist_append( rec->listAddr, g_strdup( addr ) );
439 * Insert person and address into address cache.
440 * Enter: pineFile Pine control data.
441 * cache Address cache.
442 * address E-Mail address.
445 * Return: E-Mail object, either inserted or found in hash table.
447 static ItemEMail *pine_insert_table(
448 PineFile *pineFile, AddressCache *cache, gchar *address,
449 gchar *name, gchar *remarks )
455 g_return_val_if_fail( address != NULL, NULL );
457 /* create an entry with empty name if needed */
461 /* Test whether address already in hash table */
462 key = g_strdup( address );
464 email = g_hash_table_lookup( pineFile->uniqTable, key );
466 if( email == NULL ) {
467 /* No - create person */
468 person = addritem_create_item_person();
469 addritem_person_set_common_name( person, name );
470 addrcache_id_person( cache, person );
471 addrcache_add_person( cache, person );
473 /* Add email for person */
474 email = addritem_create_item_email();
475 addritem_email_set_address( email, address );
476 addritem_email_set_remarks( email, remarks );
477 addrcache_id_email( cache, email );
478 addrcache_person_add_email( cache, person, email );
481 g_hash_table_insert( pineFile->uniqTable, key, email );
484 /* Yes - update person with longest name */
485 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
486 if( strlen( name ) > strlen( ADDRITEM_NAME(person) ) ) {
487 addritem_person_set_common_name( person, name );
498 * Parse address line adn build address items.
499 * Enter: pineFile Pine control data.
500 * cache Address cache to load.
501 * line Address record.
503 static void pine_build_items( PineFile *pineFile, AddressCache *cache, gchar *line ) {
505 GSList *nodeAddr, *nodeName;
509 rec = pine_parse_record( line );
511 pine_clean_name( rec );
512 pine_parse_address( pineFile, cache, rec );
513 /* pine_print_rec( rec, stdout ); */
514 /* printf( "=========\n" ); */
518 group = addritem_create_item_group();
519 addritem_group_set_name( group, rec->nickName );
520 addrcache_id_group( cache, group );
521 addrcache_add_group( cache, group );
523 /* Add email to group */
524 nodeName = rec->listName;
525 nodeAddr = rec->listAddr;
527 email = pine_insert_table(
528 pineFile, cache, nodeAddr->data,
529 nodeName->data, "" );
531 /* Add email to group */
532 addritem_group_add_email( group, email );
534 nodeAddr = g_slist_next( nodeAddr );
535 nodeName = g_slist_next( nodeName );
539 email = pine_insert_table(
540 pineFile, cache, rec->address,
541 rec->name, rec->comments );
544 pine_free_rec( rec );
549 * Read file data into address cache.
550 * Enter: pineFile Pine control data.
551 * cache Address cache to load.
553 static void pine_read_file( PineFile *pineFile, AddressCache *cache ) {
554 GSList *listValue = NULL;
555 gboolean flagEOF = FALSE, flagProc = FALSE, flagDone = FALSE;
556 gchar *line = NULL, *lineValue = NULL;
560 /* Find EOF for progress indicator */
561 fseek( pineFile->file, 0L, SEEK_END );
562 posEnd = ftell( pineFile->file );
563 fseek( pineFile->file, 0L, SEEK_SET );
566 while( ! flagDone ) {
572 line = pine_read_line( pineFile );
575 posCur = ftell( pineFile->file );
576 if( pineFile->cbProgress ) {
577 /* Call progress indicator */
578 ( pineFile->cbProgress ) ( pineFile, & posEnd, & posCur );
581 /* Add line to list */
586 /* Check for continuation line (1 space only) */
589 listValue = g_slist_append(
590 listValue, g_strdup( line ) );
599 if( listValue != NULL ) {
601 lineValue = mgu_list_coalesce( listValue );
604 pineFile, cache, lineValue );
608 mgu_free_list( listValue );
613 listValue = g_slist_append(
614 listValue, g_strdup( line ) );
623 mgu_free_list( listValue );
628 * ============================================================================================
629 * Read file into list. Main entry point
630 * Enter: pineFile Pine control data.
631 * cache Address cache to load.
632 * Return: Status code.
633 * ============================================================================================
635 gint pine_import_data( PineFile *pineFile, AddressCache *cache ) {
636 g_return_val_if_fail( pineFile != NULL, MGU_BAD_ARGS );
637 g_return_val_if_fail( cache != NULL, MGU_BAD_ARGS );
639 pineFile->retVal = MGU_SUCCESS;
640 addrcache_clear( cache );
641 cache->dataRead = FALSE;
642 pine_open_file( pineFile );
643 if( pineFile->retVal == MGU_SUCCESS ) {
644 /* Read data into the cache */
645 pine_read_file( pineFile, cache );
646 pine_close_file( pineFile );
649 cache->modified = FALSE;
650 cache->dataRead = TRUE;
652 return pineFile->retVal;
655 #define WORK_BUFLEN 1024
658 * Attempt to find a Pine addressbook file.
659 * Return: Filename, or home directory if not found, or empty string if
660 * no home. Filename should be g_free() when done.
662 gchar *pine_find_file( void ) {
664 gchar str[ WORK_BUFLEN ];
668 homedir = g_get_home_dir();
669 if( ! homedir ) return g_strdup( "" );
671 strcpy( str, homedir );
674 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
675 str[ len ] = G_DIR_SEPARATOR;
679 strcat( str, PINE_HOME_FILE );
681 /* Attempt to open */
682 if( ( fp = fopen( str, "rb" ) ) != NULL ) {
686 /* Truncate filename */
689 return g_strdup( str );