2 * Sylpheed -- 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.
32 #include "addrcache.h"
33 #include "file-utils.h"
35 #define PINE_HOME_FILE ".addressbook"
36 #define PINEBUFSIZE 2048
37 #define CHAR_QUOTE '\"'
38 #define CHAR_APOS '\''
39 #define CHAR_COMMA ','
45 PineFile
*pine_create() {
47 pineFile
= g_new0( PineFile
, 1 );
48 pineFile
->path
= NULL
;
49 pineFile
->file
= NULL
;
50 pineFile
->retVal
= MGU_SUCCESS
;
51 pineFile
->uniqTable
= g_hash_table_new( g_str_hash
, g_str_equal
);
52 pineFile
->cbProgress
= NULL
;
59 void pine_set_file( PineFile
* pineFile
, const gchar
*value
) {
60 cm_return_if_fail( pineFile
!= NULL
);
61 pineFile
->path
= mgu_replace_string( pineFile
->path
, value
);
62 g_strstrip( pineFile
->path
);
68 static gint
pine_free_table_vis( gpointer key
, gpointer value
, gpointer data
) {
74 * Free up object by releasing internal memory.
76 void pine_free( PineFile
*pineFile
) {
77 cm_return_if_fail( pineFile
!= NULL
);
80 if( pineFile
->file
) claws_fclose( pineFile
->file
);
82 /* Free internal stuff */
83 g_free( pineFile
->path
);
85 /* Free unique address table */
86 g_hash_table_foreach_remove( pineFile
->uniqTable
, pine_free_table_vis
, NULL
);
87 g_hash_table_destroy( pineFile
->uniqTable
);
90 pineFile
->file
= NULL
;
91 pineFile
->path
= NULL
;
92 pineFile
->retVal
= MGU_SUCCESS
;
93 pineFile
->uniqTable
= NULL
;
94 pineFile
->cbProgress
= NULL
;
96 /* Now release file object */
101 * Open file for read.
102 * Enter: pineFile File object.
103 * return: TRUE if file opened successfully.
105 static gint
pine_open_file( PineFile
* pineFile
) {
106 if( pineFile
->path
) {
107 pineFile
->file
= claws_fopen( pineFile
->path
, "rb" );
108 if( ! pineFile
->file
) {
109 pineFile
->retVal
= MGU_OPEN_FILE
;
110 return pineFile
->retVal
;
114 /* g_print( "file not specified\n" ); */
115 pineFile
->retVal
= MGU_NO_FILE
;
116 return pineFile
->retVal
;
119 /* Setup a buffer area */
120 pineFile
->retVal
= MGU_SUCCESS
;
121 return pineFile
->retVal
;
126 * Enter: pineFile File object.
128 static void pine_close_file( PineFile
*pineFile
) {
129 cm_return_if_fail( pineFile
!= NULL
);
130 if( pineFile
->file
) claws_fclose( pineFile
->file
);
131 pineFile
->file
= NULL
;
135 * Read line of text from file.
136 * Enter: pineFile File object.
137 * Return: Copy of buffer. Should be g_free'd when done.
139 static gchar
*pine_read_line( PineFile
*pineFile
) {
140 gchar buf
[ PINEBUFSIZE
];
144 if( claws_feof( pineFile
->file
) )
147 while( i
< PINEBUFSIZE
-1 ) {
148 c
= fgetc( pineFile
->file
);
168 /* Copy into private buffer */
169 return g_strdup( buf
);
173 * Parsed address data.
175 typedef struct _Pine_ParsedRec_ Pine_ParsedRec
;
176 struct _Pine_ParsedRec_
{
189 * Enter: rec Data record.
191 static void pine_free_rec( Pine_ParsedRec
*rec
) {
193 g_free( rec
->nickName
);
195 g_free( rec
->address
);
197 g_free( rec
->comments
);
198 g_slist_free_full( rec
->listName
, g_free
);
199 g_slist_free_full( rec
->listAddr
, g_free
);
200 rec
->nickName
= NULL
;
204 rec
->comments
= NULL
;
205 rec
->isGroup
= FALSE
;
213 static void pine_clean_name( Pine_ParsedRec
*rec
) {
217 if( p
== NULL
) return;
218 if( *p
== '\0' ) return;
220 g_strstrip( rec
->name
);
221 if( *p
== CHAR_APOS
|| *p
== CHAR_QUOTE
) {
225 /* If embedded comma present, surround match with quotes */
227 if( *p
== CHAR_COMMA
) {
228 p
= g_strdup_printf( "\"%s\"", rec
->name
);
238 * Parse pine address record.
239 * Enter: buf Address record buffer.
240 * Return: Data record.
242 static Pine_ParsedRec
*pine_parse_record( gchar
*buf
) {
248 for( i
= 0; i
< 5; i
++ )
251 /* Extract tab separated values */
259 tmp
[ pos
] = g_strndup( f
, len
);
268 /* Extract last value */
271 tmp
[ pos
++ ] = g_strndup( f
, len
);
274 /* Populate record */
276 rec
= g_new0( Pine_ParsedRec
, 1 );
277 rec
->isGroup
= FALSE
;
278 for( i
= 0; i
< pos
; i
++ ) {
283 if( i
== 0 ) rec
->nickName
= f
;
284 else if( i
== 1 ) rec
->name
= f
;
285 else if( i
== 2 ) rec
->address
= f
;
286 else if( i
== 3 ) rec
->fcc
= f
;
287 else if( i
== 4 ) rec
->comments
= f
;
291 if( rec
->address
!= NULL
) {
292 /* Strip leading/trailing parens */
295 len
= strlen( p
) - 1;
307 * Parse name from email address string.
308 * Enter: buf Start address of buffer to process (not modified).
309 * atp Pointer to email at (@) character.
310 * ap Pointer to start of email address returned.
311 * ep Pointer to end of email address returned.
312 * Return: Parsed name or NULL if not present. This should be g_free'd
315 static gchar
*pine_parse_name(
316 const gchar
*buf
, const gchar
*atp
, const gchar
**ap
,
329 /* Find first non-separator char */
332 if( strchr( ",; \n\r", *bp
) == NULL
) break;
336 /* Search back for start of name */
342 /* Found start of address/end of name part */
343 ilen
= -1 + ( size_t ) ( pos
- bp
);
344 name
= g_strndup( bp
, ilen
+ 1 );
345 *(name
+ ilen
+ 1) = '\0';
347 /* Remove leading trailing quotes and spaces */
348 mgu_str_ltc2space( name
, '\"', '\"' );
349 mgu_str_ltc2space( name
, '\'', '\'' );
350 mgu_str_ltc2space( name
, '\"', '\"' );
351 mgu_str_unescape( name
);
359 /* Search forward for end of address */
366 if( strchr( ",; \'\n\r", *pos
) ) break;
375 * Parse address list.
376 * Enter: pineFile Pine control data.
377 * cache Address cache.
380 static void pine_parse_address( PineFile
*pineFile
, AddressCache
*cache
, Pine_ParsedRec
*rec
) {
382 gchar addr
[ PINEBUFSIZE
];
389 cm_return_if_fail( rec
->address
!= NULL
);
392 while((atCh
= strchr( buf
, CHAR_AT
)) != NULL
) {
393 name
= pine_parse_name( buf
, atCh
, &bp
, &ep
);
394 len
= ( size_t ) ( ep
- bp
);
395 strncpy( addr
, bp
, len
);
397 extract_address( addr
);
399 if( name
== NULL
) name
= g_strdup( "" );
400 rec
->listName
= g_slist_append( rec
->listName
, name
);
401 rec
->listAddr
= g_slist_append( rec
->listAddr
, g_strdup( addr
) );
411 * Insert person and address into address cache.
412 * Enter: pineFile Pine control data.
413 * cache Address cache.
414 * address E-Mail address.
417 * Return: E-Mail object, either inserted or found in hash table.
419 static ItemEMail
*pine_insert_table(
420 PineFile
*pineFile
, AddressCache
*cache
, gchar
*address
,
421 gchar
*name
, gchar
*remarks
)
427 cm_return_val_if_fail( address
!= NULL
, NULL
);
429 /* create an entry with empty name if needed */
433 /* Test whether address already in hash table */
434 key
= g_utf8_strdown( address
, -1 );
435 email
= g_hash_table_lookup( pineFile
->uniqTable
, key
);
437 if( email
== NULL
) {
438 /* No - create person */
439 person
= addritem_create_item_person();
440 addritem_person_set_common_name( person
, name
);
441 addrcache_id_person( cache
, person
);
442 addrcache_add_person( cache
, person
);
444 /* Add email for person */
445 email
= addritem_create_item_email();
446 addritem_email_set_address( email
, address
);
447 addritem_email_set_remarks( email
, remarks
);
448 addrcache_id_email( cache
, email
);
449 addrcache_person_add_email( cache
, person
, email
);
452 g_hash_table_insert( pineFile
->uniqTable
, key
, email
);
455 /* Yes - update person with longest name */
456 person
= ( ItemPerson
* ) ADDRITEM_PARENT(email
);
457 if( strlen( name
) > strlen( ADDRITEM_NAME(person
) ) ) {
458 addritem_person_set_common_name( person
, name
);
469 * Parse address line adn build address items.
470 * Enter: pineFile Pine control data.
471 * cache Address cache to load.
472 * line Address record.
474 static void pine_build_items( PineFile
*pineFile
, AddressCache
*cache
, gchar
*line
) {
476 GSList
*nodeAddr
, *nodeName
;
480 rec
= pine_parse_record( line
);
482 pine_clean_name( rec
);
483 pine_parse_address( pineFile
, cache
, rec
);
484 /* pine_print_rec( rec, stdout ); */
485 /* g_print( "=========\n" ); */
489 group
= addritem_create_item_group();
490 addritem_group_set_name( group
, rec
->nickName
);
491 addrcache_id_group( cache
, group
);
492 addrcache_add_group( cache
, group
);
494 /* Add email to group */
495 nodeName
= rec
->listName
;
496 nodeAddr
= rec
->listAddr
;
498 email
= pine_insert_table(
499 pineFile
, cache
, nodeAddr
->data
,
500 nodeName
->data
, "" );
502 /* Add email to group */
503 addritem_group_add_email( group
, email
);
505 nodeAddr
= g_slist_next( nodeAddr
);
506 nodeName
= g_slist_next( nodeName
);
511 pineFile
, cache
, rec
->address
,
512 rec
->name
, rec
->comments
);
515 pine_free_rec( rec
);
520 * Read file data into address cache.
521 * Enter: pineFile Pine control data.
522 * cache Address cache to load.
524 static void pine_read_file( PineFile
*pineFile
, AddressCache
*cache
) {
525 GSList
*listValue
= NULL
;
526 gboolean flagEOF
= FALSE
, flagProc
= FALSE
, flagDone
= FALSE
;
527 gchar
*line
= NULL
, *lineValue
= NULL
;
531 /* Find EOF for progress indicator */
532 fseek( pineFile
->file
, 0L, SEEK_END
);
533 posEnd
= ftell( pineFile
->file
);
534 fseek( pineFile
->file
, 0L, SEEK_SET
);
537 while( ! flagDone
) {
543 line
= pine_read_line( pineFile
);
546 posCur
= ftell( pineFile
->file
);
547 if( pineFile
->cbProgress
) {
548 /* Call progress indicator */
549 ( pineFile
->cbProgress
) ( pineFile
, & posEnd
, & posCur
);
552 /* Add line to list */
557 /* Check for continuation line (1 space only) */
560 listValue
= g_slist_append(
561 listValue
, g_strdup( line
) );
570 if( listValue
!= NULL
) {
572 lineValue
= mgu_list_coalesce( listValue
);
575 pineFile
, cache
, lineValue
);
579 g_slist_free_full( listValue
, g_free
);
584 listValue
= g_slist_append(
585 listValue
, g_strdup( line
) );
594 g_slist_free_full( listValue
, g_free
);
599 * ============================================================================================
600 * Read file into list. Main entry point
601 * Enter: pineFile Pine control data.
602 * cache Address cache to load.
603 * Return: Status code.
604 * ============================================================================================
606 gint
pine_import_data( PineFile
*pineFile
, AddressCache
*cache
) {
607 cm_return_val_if_fail( pineFile
!= NULL
, MGU_BAD_ARGS
);
608 cm_return_val_if_fail( cache
!= NULL
, MGU_BAD_ARGS
);
610 pineFile
->retVal
= MGU_SUCCESS
;
611 addrcache_clear( cache
);
612 cache
->dataRead
= FALSE
;
613 pine_open_file( pineFile
);
614 if( pineFile
->retVal
== MGU_SUCCESS
) {
615 /* Read data into the cache */
616 pine_read_file( pineFile
, cache
);
617 pine_close_file( pineFile
);
620 cache
->modified
= FALSE
;
621 cache
->dataRead
= TRUE
;
623 return pineFile
->retVal
;
626 #define WORK_BUFLEN 1024
629 * Attempt to find a Pine addressbook file.
630 * Return: Filename, or home directory if not found, or empty string if
631 * no home. Filename should be g_free() when done.
633 gchar
*pine_find_file( void ) {
634 const gchar
*homedir
;
635 gchar str
[ WORK_BUFLEN
+ 1 ];
639 homedir
= get_home_dir();
640 if( ! homedir
) return g_strdup( "" );
642 strncpy( str
, homedir
, WORK_BUFLEN
);
645 if( str
[ len
-1 ] != G_DIR_SEPARATOR
) {
646 str
[ len
] = G_DIR_SEPARATOR
;
650 strncat( str
, PINE_HOME_FILE
, WORK_BUFLEN
- strlen(str
) );
652 /* Attempt to open */
653 if( ( fp
= claws_fopen( str
, "rb" ) ) != NULL
) {
657 /* Truncate filename */
660 return g_strdup( str
);