Don't assume docbook2pdf variable is set
[claws.git] / src / pine.c
blobae00b7ad1630b68fc364d020558013cdc5f22bc4
1 /*
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.
24 #include "config.h"
26 #include <sys/stat.h>
27 #include <glib.h>
28 #include <string.h>
30 #include "utils.h"
31 #include "mgutils.h"
32 #include "pine.h"
33 #include "addritem.h"
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 ','
42 #define CHAR_AT '@'
45 * Create new object.
47 PineFile *pine_create() {
48 PineFile *pineFile;
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;
55 return pineFile;
59 * Properties...
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 );
68 * Free key in table.
70 static gint pine_free_table_vis( gpointer key, gpointer value, gpointer data ) {
71 g_free( key );
72 return TRUE;
76 * Free up object by releasing internal memory.
78 void pine_free( PineFile *pineFile ) {
79 cm_return_if_fail( pineFile != NULL );
81 /* Close file */
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 );
91 /* Clear pointers */
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 */
99 g_free( pineFile );
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;
115 else {
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;
127 * Close file.
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 ];
143 int c, i = 0;
144 gchar ch;
146 if( claws_feof( pineFile->file ) )
147 return NULL;
149 while( i < PINEBUFSIZE-1 ) {
150 c = fgetc( pineFile->file );
151 if( c == EOF ) {
152 if( i == 0 )
153 return NULL;
154 break;
156 ch = (gchar) c;
157 if( ch == '\0' ) {
158 if( i == 0 )
159 return NULL;
160 break;
162 if( ch == '\n' ) {
163 break;
165 buf[i] = ch;
166 i++;
168 buf[i] = '\0';
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_ {
179 gchar *nickName;
180 gchar *name;
181 gchar *address;
182 gchar *fcc;
183 gchar *comments;
184 gboolean isGroup;
185 GSList *listName;
186 GSList *listAddr;
190 * Free data record.
191 * Enter: rec Data record.
193 static void pine_free_rec( Pine_ParsedRec *rec ) {
194 if( rec ) {
195 g_free( rec->nickName );
196 g_free( rec->name );
197 g_free( rec->address );
198 g_free( rec->fcc );
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;
203 rec->name = NULL;
204 rec->address = NULL;
205 rec->fcc = NULL;
206 rec->comments = NULL;
207 rec->isGroup = FALSE;
208 g_free( rec );
213 * Clean name.
215 static void pine_clean_name( Pine_ParsedRec *rec ) {
216 gchar *p;
218 p = rec->name;
219 if( p == NULL ) return;
220 if( *p == '\0' ) return;
222 g_strstrip( rec->name );
223 if( *p == CHAR_APOS || *p == CHAR_QUOTE ) {
224 return;
227 /* If embedded comma present, surround match with quotes */
228 while( *p ) {
229 if( *p == CHAR_COMMA ) {
230 p = g_strdup_printf( "\"%s\"", rec->name );
231 g_free( rec->name );
232 rec->name = p;
233 return;
235 p++;
240 * Parse pine address record.
241 * Enter: buf Address record buffer.
242 * Return: Data record.
244 static Pine_ParsedRec *pine_parse_record( gchar *buf ) {
245 Pine_ParsedRec *rec;
246 gchar *p, *f;
247 gint pos, len, i;
248 gchar *tmp[5];
250 for( i = 0; i < 5; i++ )
251 tmp[i] = NULL;
253 /* Extract tab separated values */
254 rec = NULL;
255 pos = 0;
256 p = f = buf;
257 while( *p ) {
258 if( *p == '\t' ) {
259 len = p - f;
260 if( len > 0 ) {
261 tmp[ pos ] = g_strndup( f, len );
262 f = p;
263 f++;
265 pos++;
267 p++;
270 /* Extract last value */
271 len = p - f;
272 if( len > 0 ) {
273 tmp[ pos++ ] = g_strndup( f, len );
276 /* Populate record */
277 if( pos > 0 ) {
278 rec = g_new0( Pine_ParsedRec, 1 );
279 rec->isGroup = FALSE;
280 for( i = 0; i < pos; i++ ) {
281 f = tmp[i];
282 if( f ) {
283 g_strstrip( f );
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;
290 tmp[i] = NULL;
293 if( rec->address != NULL ) {
294 /* Strip leading/trailing parens */
295 p = rec->address;
296 if( *p == '(' ) {
297 len = strlen( p ) - 1;
298 *p = ' ';
299 *(p + len) = ' ';
300 rec->isGroup = TRUE;
305 return rec;
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
315 * when done.
317 static gchar *pine_parse_name(
318 const gchar *buf, const gchar *atp, const gchar **ap,
319 const gchar **ep )
321 gchar *name;
322 const gchar *pos;
323 const gchar *tmp;
324 const gchar *bp;
325 gint ilen;
327 name = NULL;
328 *ap = NULL;
329 *ep = NULL;
331 /* Find first non-separator char */
332 bp = buf;
333 while( TRUE ) {
334 if( strchr( ",; \n\r", *bp ) == NULL ) break;
335 bp++;
338 /* Search back for start of name */
339 tmp = atp;
340 pos = atp;
341 while( pos >= bp ) {
342 tmp = pos;
343 if( *pos == '<' ) {
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 );
354 g_strstrip( name );
355 break;
357 pos--;
359 *ap = tmp;
361 /* Search forward for end of address */
362 pos = atp + 1;
363 while( TRUE ) {
364 if( *pos == '>' ) {
365 pos++;
366 break;
368 if( strchr( ",; \'\n\r", *pos ) ) break;
369 pos++;
371 *ep = pos;
373 return name;
377 * Parse address list.
378 * Enter: pineFile Pine control data.
379 * cache Address cache.
380 * rec Data record.
382 static void pine_parse_address( PineFile *pineFile, AddressCache *cache, Pine_ParsedRec *rec ) {
383 const gchar *buf;
384 gchar addr[ PINEBUFSIZE ];
385 const gchar *bp;
386 const gchar *ep;
387 gchar *atCh;
388 gchar *name;
389 gint len;
391 cm_return_if_fail( rec->address != NULL );
393 buf = rec->address;
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 );
398 addr[ len ] = '\0';
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 ) );
405 buf = ep;
406 if( atCh == ep ) {
407 buf++;
413 * Insert person and address into address cache.
414 * Enter: pineFile Pine control data.
415 * cache Address cache.
416 * address E-Mail address.
417 * name Name.
418 * remarks Remarks.
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 )
425 ItemPerson *person;
426 ItemEMail *email;
427 gchar *key;
429 cm_return_val_if_fail( address != NULL, NULL );
431 /* create an entry with empty name if needed */
432 if ( name == NULL )
433 name = "";
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 );
453 /* Insert entry */
454 g_hash_table_insert( pineFile->uniqTable, key, email );
456 else {
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 );
463 /* Free up */
464 g_free( key );
467 return email;
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 ) {
477 Pine_ParsedRec *rec;
478 GSList *nodeAddr, *nodeName;
479 ItemGroup *group;
480 ItemEMail *email;
482 rec = pine_parse_record( line );
483 if( rec ) {
484 pine_clean_name( rec );
485 pine_parse_address( pineFile, cache, rec );
486 /* pine_print_rec( rec, stdout ); */
487 /* g_print( "=========\n" ); */
489 if( rec->isGroup ) {
490 /* Create group */
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;
499 while( nodeAddr ) {
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 );
511 else {
512 pine_insert_table(
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;
530 long posEnd = 0L;
531 long posCur = 0L;
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 );
538 flagProc = FALSE;
539 while( ! flagDone ) {
540 if( flagEOF ) {
541 flagDone = TRUE;
542 flagProc = TRUE;
544 else {
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 */
555 if( line == NULL ) {
556 flagEOF = TRUE;
558 else {
559 /* Check for continuation line (1 space only) */
560 if( *line == ' ' ) {
561 g_strchug( line );
562 listValue = g_slist_append(
563 listValue, g_strdup( line ) );
564 flagProc = FALSE;
566 else {
567 flagProc = TRUE;
571 if( flagProc ) {
572 if( listValue != NULL ) {
573 /* Process list */
574 lineValue = mgu_list_coalesce( listValue );
575 if( lineValue ) {
576 pine_build_items(
577 pineFile, cache, lineValue );
579 g_free( lineValue );
580 lineValue = NULL;
581 g_slist_free_full( listValue, g_free );
582 listValue = NULL;
584 if( line != NULL ) {
585 /* Append to list */
586 listValue = g_slist_append(
587 listValue, g_strdup( line ) );
591 g_free( line );
592 line = NULL;
595 /* Release data */
596 g_slist_free_full( listValue, g_free );
597 listValue = NULL;
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 );
621 /* Mark cache */
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 ];
638 gint len;
639 FILE *fp;
641 homedir = get_home_dir();
642 if( ! homedir ) return g_strdup( "" );
644 strncpy( str, homedir, WORK_BUFLEN );
645 len = strlen( str );
646 if( len > 0 ) {
647 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
648 str[ len ] = G_DIR_SEPARATOR;
649 str[ ++len ] = '\0';
652 strncat( str, PINE_HOME_FILE, WORK_BUFLEN - strlen(str) );
654 /* Attempt to open */
655 if( ( fp = claws_fopen( str, "rb" ) ) != NULL ) {
656 claws_fclose( fp );
658 else {
659 /* Truncate filename */
660 str[ len ] = '\0';
662 return g_strdup( str );
666 * End of Source.