2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2002-2012 Match Grun and the Claws Mail team
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.
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, see <http://www.gnu.org/licenses/>.
21 * Functions necessary to access Pine address book file.
34 #include "addrcache.h"
35 #include "file-utils.h"
37 #define PINE_HOME_FILE ".addressbook"
38 #define PINEBUFSIZE 2048
39 #define CHAR_QUOTE '\"'
40 #define CHAR_APOS '\''
41 #define CHAR_COMMA ','
47 PineFile
*pine_create() {
49 pineFile
= g_new0( PineFile
, 1 );
50 pineFile
->path
= NULL
;
51 pineFile
->file
= NULL
;
52 pineFile
->retVal
= MGU_SUCCESS
;
53 pineFile
->uniqTable
= g_hash_table_new( g_str_hash
, g_str_equal
);
54 pineFile
->cbProgress
= NULL
;
61 void pine_set_file( PineFile
* pineFile
, const gchar
*value
) {
62 cm_return_if_fail( pineFile
!= NULL
);
63 pineFile
->path
= mgu_replace_string( pineFile
->path
, value
);
64 g_strstrip( pineFile
->path
);
70 static gint
pine_free_table_vis( gpointer key
, gpointer value
, gpointer data
) {
76 * Free up object by releasing internal memory.
78 void pine_free( PineFile
*pineFile
) {
79 cm_return_if_fail( pineFile
!= NULL
);
82 if( pineFile
->file
) claws_fclose( pineFile
->file
);
84 /* Free internal stuff */
85 g_free( pineFile
->path
);
87 /* Free unique address table */
88 g_hash_table_foreach_remove( pineFile
->uniqTable
, pine_free_table_vis
, NULL
);
89 g_hash_table_destroy( pineFile
->uniqTable
);
92 pineFile
->file
= NULL
;
93 pineFile
->path
= NULL
;
94 pineFile
->retVal
= MGU_SUCCESS
;
95 pineFile
->uniqTable
= NULL
;
96 pineFile
->cbProgress
= NULL
;
98 /* Now release file object */
103 * Open file for read.
104 * Enter: pineFile File object.
105 * return: TRUE if file opened successfully.
107 static gint
pine_open_file( PineFile
* pineFile
) {
108 if( pineFile
->path
) {
109 pineFile
->file
= claws_fopen( pineFile
->path
, "rb" );
110 if( ! pineFile
->file
) {
111 pineFile
->retVal
= MGU_OPEN_FILE
;
112 return pineFile
->retVal
;
116 /* g_print( "file not specified\n" ); */
117 pineFile
->retVal
= MGU_NO_FILE
;
118 return pineFile
->retVal
;
121 /* Setup a buffer area */
122 pineFile
->retVal
= MGU_SUCCESS
;
123 return pineFile
->retVal
;
128 * Enter: pineFile File object.
130 static void pine_close_file( PineFile
*pineFile
) {
131 cm_return_if_fail( pineFile
!= NULL
);
132 if( pineFile
->file
) claws_fclose( pineFile
->file
);
133 pineFile
->file
= NULL
;
137 * Read line of text from file.
138 * Enter: pineFile File object.
139 * Return: Copy of buffer. Should be g_free'd when done.
141 static gchar
*pine_read_line( PineFile
*pineFile
) {
142 gchar buf
[ PINEBUFSIZE
];
146 if( claws_feof( pineFile
->file
) )
149 while( i
< PINEBUFSIZE
-1 ) {
150 c
= fgetc( pineFile
->file
);
170 /* Copy into private buffer */
171 return g_strdup( buf
);
175 * Parsed address data.
177 typedef struct _Pine_ParsedRec_ Pine_ParsedRec
;
178 struct _Pine_ParsedRec_
{
191 * Enter: rec Data record.
193 static void pine_free_rec( Pine_ParsedRec
*rec
) {
195 g_free( rec
->nickName
);
197 g_free( rec
->address
);
199 g_free( rec
->comments
);
200 g_slist_free_full( rec
->listName
, g_free
);
201 g_slist_free_full( rec
->listAddr
, g_free
);
202 rec
->nickName
= NULL
;
206 rec
->comments
= NULL
;
207 rec
->isGroup
= FALSE
;
215 static void pine_clean_name( Pine_ParsedRec
*rec
) {
219 if( p
== NULL
) return;
220 if( *p
== '\0' ) return;
222 g_strstrip( rec
->name
);
223 if( *p
== CHAR_APOS
|| *p
== CHAR_QUOTE
) {
227 /* If embedded comma present, surround match with quotes */
229 if( *p
== CHAR_COMMA
) {
230 p
= g_strdup_printf( "\"%s\"", rec
->name
);
240 * Parse pine address record.
241 * Enter: buf Address record buffer.
242 * Return: Data record.
244 static Pine_ParsedRec
*pine_parse_record( gchar
*buf
) {
250 for( i
= 0; i
< 5; i
++ )
253 /* Extract tab separated values */
261 tmp
[ pos
] = g_strndup( f
, len
);
270 /* Extract last value */
273 tmp
[ pos
++ ] = g_strndup( f
, len
);
276 /* Populate record */
278 rec
= g_new0( Pine_ParsedRec
, 1 );
279 rec
->isGroup
= FALSE
;
280 for( i
= 0; i
< pos
; i
++ ) {
285 if( i
== 0 ) rec
->nickName
= f
;
286 else if( i
== 1 ) rec
->name
= f
;
287 else if( i
== 2 ) rec
->address
= f
;
288 else if( i
== 3 ) rec
->fcc
= f
;
289 else if( i
== 4 ) rec
->comments
= f
;
293 if( rec
->address
!= NULL
) {
294 /* Strip leading/trailing parens */
297 len
= strlen( p
) - 1;
309 * Parse name from email address string.
310 * Enter: buf Start address of buffer to process (not modified).
311 * atp Pointer to email at (@) character.
312 * ap Pointer to start of email address returned.
313 * ep Pointer to end of email address returned.
314 * Return: Parsed name or NULL if not present. This should be g_free'd
317 static gchar
*pine_parse_name(
318 const gchar
*buf
, const gchar
*atp
, const gchar
**ap
,
331 /* Find first non-separator char */
334 if( strchr( ",; \n\r", *bp
) == NULL
) break;
338 /* Search back for start of name */
344 /* Found start of address/end of name part */
345 ilen
= -1 + ( size_t ) ( pos
- bp
);
346 name
= g_strndup( bp
, ilen
+ 1 );
347 *(name
+ ilen
+ 1) = '\0';
349 /* Remove leading trailing quotes and spaces */
350 mgu_str_ltc2space( name
, '\"', '\"' );
351 mgu_str_ltc2space( name
, '\'', '\'' );
352 mgu_str_ltc2space( name
, '\"', '\"' );
353 mgu_str_unescape( name
);
361 /* Search forward for end of address */
368 if( strchr( ",; \'\n\r", *pos
) ) break;
377 * Parse address list.
378 * Enter: pineFile Pine control data.
379 * cache Address cache.
382 static void pine_parse_address( PineFile
*pineFile
, AddressCache
*cache
, Pine_ParsedRec
*rec
) {
384 gchar addr
[ PINEBUFSIZE
];
391 cm_return_if_fail( rec
->address
!= NULL
);
394 while((atCh
= strchr( buf
, CHAR_AT
)) != NULL
) {
395 name
= pine_parse_name( buf
, atCh
, &bp
, &ep
);
396 len
= ( size_t ) ( ep
- bp
);
397 strncpy( addr
, bp
, len
);
399 extract_address( addr
);
401 if( name
== NULL
) name
= g_strdup( "" );
402 rec
->listName
= g_slist_append( rec
->listName
, name
);
403 rec
->listAddr
= g_slist_append( rec
->listAddr
, g_strdup( addr
) );
413 * Insert person and address into address cache.
414 * Enter: pineFile Pine control data.
415 * cache Address cache.
416 * address E-Mail address.
419 * Return: E-Mail object, either inserted or found in hash table.
421 static ItemEMail
*pine_insert_table(
422 PineFile
*pineFile
, AddressCache
*cache
, gchar
*address
,
423 gchar
*name
, gchar
*remarks
)
429 cm_return_val_if_fail( address
!= NULL
, NULL
);
431 /* create an entry with empty name if needed */
435 /* Test whether address already in hash table */
436 key
= g_utf8_strdown( address
, -1 );
437 email
= g_hash_table_lookup( pineFile
->uniqTable
, key
);
439 if( email
== NULL
) {
440 /* No - create person */
441 person
= addritem_create_item_person();
442 addritem_person_set_common_name( person
, name
);
443 addrcache_id_person( cache
, person
);
444 addrcache_add_person( cache
, person
);
446 /* Add email for person */
447 email
= addritem_create_item_email();
448 addritem_email_set_address( email
, address
);
449 addritem_email_set_remarks( email
, remarks
);
450 addrcache_id_email( cache
, email
);
451 addrcache_person_add_email( cache
, person
, email
);
454 g_hash_table_insert( pineFile
->uniqTable
, key
, email
);
457 /* Yes - update person with longest name */
458 person
= ( ItemPerson
* ) ADDRITEM_PARENT(email
);
459 if( strlen( name
) > strlen( ADDRITEM_NAME(person
) ) ) {
460 addritem_person_set_common_name( person
, name
);
471 * Parse address line adn build address items.
472 * Enter: pineFile Pine control data.
473 * cache Address cache to load.
474 * line Address record.
476 static void pine_build_items( PineFile
*pineFile
, AddressCache
*cache
, gchar
*line
) {
478 GSList
*nodeAddr
, *nodeName
;
482 rec
= pine_parse_record( line
);
484 pine_clean_name( rec
);
485 pine_parse_address( pineFile
, cache
, rec
);
486 /* pine_print_rec( rec, stdout ); */
487 /* g_print( "=========\n" ); */
491 group
= addritem_create_item_group();
492 addritem_group_set_name( group
, rec
->nickName
);
493 addrcache_id_group( cache
, group
);
494 addrcache_add_group( cache
, group
);
496 /* Add email to group */
497 nodeName
= rec
->listName
;
498 nodeAddr
= rec
->listAddr
;
500 email
= pine_insert_table(
501 pineFile
, cache
, nodeAddr
->data
,
502 nodeName
->data
, "" );
504 /* Add email to group */
505 addritem_group_add_email( group
, email
);
507 nodeAddr
= g_slist_next( nodeAddr
);
508 nodeName
= g_slist_next( nodeName
);
513 pineFile
, cache
, rec
->address
,
514 rec
->name
, rec
->comments
);
517 pine_free_rec( rec
);
522 * Read file data into address cache.
523 * Enter: pineFile Pine control data.
524 * cache Address cache to load.
526 static void pine_read_file( PineFile
*pineFile
, AddressCache
*cache
) {
527 GSList
*listValue
= NULL
;
528 gboolean flagEOF
= FALSE
, flagProc
= FALSE
, flagDone
= FALSE
;
529 gchar
*line
= NULL
, *lineValue
= NULL
;
533 /* Find EOF for progress indicator */
534 fseek( pineFile
->file
, 0L, SEEK_END
);
535 posEnd
= ftell( pineFile
->file
);
536 fseek( pineFile
->file
, 0L, SEEK_SET
);
539 while( ! flagDone
) {
545 line
= pine_read_line( pineFile
);
548 posCur
= ftell( pineFile
->file
);
549 if( pineFile
->cbProgress
) {
550 /* Call progress indicator */
551 ( pineFile
->cbProgress
) ( pineFile
, & posEnd
, & posCur
);
554 /* Add line to list */
559 /* Check for continuation line (1 space only) */
562 listValue
= g_slist_append(
563 listValue
, g_strdup( line
) );
572 if( listValue
!= NULL
) {
574 lineValue
= mgu_list_coalesce( listValue
);
577 pineFile
, cache
, lineValue
);
581 g_slist_free_full( listValue
, g_free
);
586 listValue
= g_slist_append(
587 listValue
, g_strdup( line
) );
596 g_slist_free_full( listValue
, g_free
);
601 * ============================================================================================
602 * Read file into list. Main entry point
603 * Enter: pineFile Pine control data.
604 * cache Address cache to load.
605 * Return: Status code.
606 * ============================================================================================
608 gint
pine_import_data( PineFile
*pineFile
, AddressCache
*cache
) {
609 cm_return_val_if_fail( pineFile
!= NULL
, MGU_BAD_ARGS
);
610 cm_return_val_if_fail( cache
!= NULL
, MGU_BAD_ARGS
);
612 pineFile
->retVal
= MGU_SUCCESS
;
613 addrcache_clear( cache
);
614 cache
->dataRead
= FALSE
;
615 pine_open_file( pineFile
);
616 if( pineFile
->retVal
== MGU_SUCCESS
) {
617 /* Read data into the cache */
618 pine_read_file( pineFile
, cache
);
619 pine_close_file( pineFile
);
622 cache
->modified
= FALSE
;
623 cache
->dataRead
= TRUE
;
625 return pineFile
->retVal
;
628 #define WORK_BUFLEN 1024
631 * Attempt to find a Pine addressbook file.
632 * Return: Filename, or home directory if not found, or empty string if
633 * no home. Filename should be g_free() when done.
635 gchar
*pine_find_file( void ) {
636 const gchar
*homedir
;
637 gchar str
[ WORK_BUFLEN
+ 1 ];
641 homedir
= get_home_dir();
642 if( ! homedir
) return g_strdup( "" );
644 strncpy( str
, homedir
, WORK_BUFLEN
);
647 if( str
[ len
-1 ] != G_DIR_SEPARATOR
) {
648 str
[ len
] = G_DIR_SEPARATOR
;
652 strncat( str
, PINE_HOME_FILE
, WORK_BUFLEN
- strlen(str
) );
654 /* Attempt to open */
655 if( ( fp
= claws_fopen( str
, "rb" ) ) != NULL
) {
659 /* Truncate filename */
662 return g_strdup( str
);