update contributors list
[claws.git] / src / pine.c
blob1e5df3d6b48f297eee4db789d8d7e81deabada05
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 <sys/stat.h>
25 #include <glib.h>
26 #include <string.h>
28 #include "utils.h"
29 #include "mgutils.h"
30 #include "pine.h"
31 #include "addritem.h"
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 ','
40 #define CHAR_AT '@'
43 * Create new object.
45 PineFile *pine_create() {
46 PineFile *pineFile;
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;
53 return pineFile;
57 * Properties...
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 );
66 * Free key in table.
68 static gint pine_free_table_vis( gpointer key, gpointer value, gpointer data ) {
69 g_free( key );
70 return TRUE;
74 * Free up object by releasing internal memory.
76 void pine_free( PineFile *pineFile ) {
77 cm_return_if_fail( pineFile != NULL );
79 /* Close file */
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 );
89 /* Clear pointers */
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 */
97 g_free( pineFile );
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;
113 else {
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;
125 * Close file.
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 ];
141 int c, i = 0;
142 gchar ch;
144 if( claws_feof( pineFile->file ) )
145 return NULL;
147 while( i < PINEBUFSIZE-1 ) {
148 c = fgetc( pineFile->file );
149 if( c == EOF ) {
150 if( i == 0 )
151 return NULL;
152 break;
154 ch = (gchar) c;
155 if( ch == '\0' ) {
156 if( i == 0 )
157 return NULL;
158 break;
160 if( ch == '\n' ) {
161 break;
163 buf[i] = ch;
164 i++;
166 buf[i] = '\0';
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_ {
177 gchar *nickName;
178 gchar *name;
179 gchar *address;
180 gchar *fcc;
181 gchar *comments;
182 gboolean isGroup;
183 GSList *listName;
184 GSList *listAddr;
188 * Free data record.
189 * Enter: rec Data record.
191 static void pine_free_rec( Pine_ParsedRec *rec ) {
192 if( rec ) {
193 g_free( rec->nickName );
194 g_free( rec->name );
195 g_free( rec->address );
196 g_free( rec->fcc );
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;
201 rec->name = NULL;
202 rec->address = NULL;
203 rec->fcc = NULL;
204 rec->comments = NULL;
205 rec->isGroup = FALSE;
206 g_free( rec );
211 * Clean name.
213 static void pine_clean_name( Pine_ParsedRec *rec ) {
214 gchar *p;
216 p = rec->name;
217 if( p == NULL ) return;
218 if( *p == '\0' ) return;
220 g_strstrip( rec->name );
221 if( *p == CHAR_APOS || *p == CHAR_QUOTE ) {
222 return;
225 /* If embedded comma present, surround match with quotes */
226 while( *p ) {
227 if( *p == CHAR_COMMA ) {
228 p = g_strdup_printf( "\"%s\"", rec->name );
229 g_free( rec->name );
230 rec->name = p;
231 return;
233 p++;
238 * Parse pine address record.
239 * Enter: buf Address record buffer.
240 * Return: Data record.
242 static Pine_ParsedRec *pine_parse_record( gchar *buf ) {
243 Pine_ParsedRec *rec;
244 gchar *p, *f;
245 gint pos, len, i;
246 gchar *tmp[5];
248 for( i = 0; i < 5; i++ )
249 tmp[i] = NULL;
251 /* Extract tab separated values */
252 rec = NULL;
253 pos = 0;
254 p = f = buf;
255 while( *p ) {
256 if( *p == '\t' ) {
257 len = p - f;
258 if( len > 0 ) {
259 tmp[ pos ] = g_strndup( f, len );
260 f = p;
261 f++;
263 pos++;
265 p++;
268 /* Extract last value */
269 len = p - f;
270 if( len > 0 ) {
271 tmp[ pos++ ] = g_strndup( f, len );
274 /* Populate record */
275 if( pos > 0 ) {
276 rec = g_new0( Pine_ParsedRec, 1 );
277 rec->isGroup = FALSE;
278 for( i = 0; i < pos; i++ ) {
279 f = tmp[i];
280 if( f ) {
281 g_strstrip( f );
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;
288 tmp[i] = NULL;
291 if( rec->address != NULL ) {
292 /* Strip leading/trailing parens */
293 p = rec->address;
294 if( *p == '(' ) {
295 len = strlen( p ) - 1;
296 *p = ' ';
297 *(p + len) = ' ';
298 rec->isGroup = TRUE;
303 return rec;
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
313 * when done.
315 static gchar *pine_parse_name(
316 const gchar *buf, const gchar *atp, const gchar **ap,
317 const gchar **ep )
319 gchar *name;
320 const gchar *pos;
321 const gchar *tmp;
322 const gchar *bp;
323 gint ilen;
325 name = NULL;
326 *ap = NULL;
327 *ep = NULL;
329 /* Find first non-separator char */
330 bp = buf;
331 while( TRUE ) {
332 if( strchr( ",; \n\r", *bp ) == NULL ) break;
333 bp++;
336 /* Search back for start of name */
337 tmp = atp;
338 pos = atp;
339 while( pos >= bp ) {
340 tmp = pos;
341 if( *pos == '<' ) {
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 );
352 g_strstrip( name );
353 break;
355 pos--;
357 *ap = tmp;
359 /* Search forward for end of address */
360 pos = atp + 1;
361 while( TRUE ) {
362 if( *pos == '>' ) {
363 pos++;
364 break;
366 if( strchr( ",; \'\n\r", *pos ) ) break;
367 pos++;
369 *ep = pos;
371 return name;
375 * Parse address list.
376 * Enter: pineFile Pine control data.
377 * cache Address cache.
378 * rec Data record.
380 static void pine_parse_address( PineFile *pineFile, AddressCache *cache, Pine_ParsedRec *rec ) {
381 const gchar *buf;
382 gchar addr[ PINEBUFSIZE ];
383 const gchar *bp;
384 const gchar *ep;
385 gchar *atCh;
386 gchar *name;
387 gint len;
389 cm_return_if_fail( rec->address != NULL );
391 buf = rec->address;
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 );
396 addr[ len ] = '\0';
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 ) );
403 buf = ep;
404 if( atCh == ep ) {
405 buf++;
411 * Insert person and address into address cache.
412 * Enter: pineFile Pine control data.
413 * cache Address cache.
414 * address E-Mail address.
415 * name Name.
416 * remarks Remarks.
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 )
423 ItemPerson *person;
424 ItemEMail *email;
425 gchar *key;
427 cm_return_val_if_fail( address != NULL, NULL );
429 /* create an entry with empty name if needed */
430 if ( name == NULL )
431 name = "";
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 );
451 /* Insert entry */
452 g_hash_table_insert( pineFile->uniqTable, key, email );
454 else {
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 );
461 /* Free up */
462 g_free( key );
465 return email;
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 ) {
475 Pine_ParsedRec *rec;
476 GSList *nodeAddr, *nodeName;
477 ItemGroup *group;
478 ItemEMail *email;
480 rec = pine_parse_record( line );
481 if( rec ) {
482 pine_clean_name( rec );
483 pine_parse_address( pineFile, cache, rec );
484 /* pine_print_rec( rec, stdout ); */
485 /* g_print( "=========\n" ); */
487 if( rec->isGroup ) {
488 /* Create group */
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;
497 while( nodeAddr ) {
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 );
509 else {
510 pine_insert_table(
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;
528 long posEnd = 0L;
529 long posCur = 0L;
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 );
536 flagProc = FALSE;
537 while( ! flagDone ) {
538 if( flagEOF ) {
539 flagDone = TRUE;
540 flagProc = TRUE;
542 else {
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 */
553 if( line == NULL ) {
554 flagEOF = TRUE;
556 else {
557 /* Check for continuation line (1 space only) */
558 if( *line == ' ' ) {
559 g_strchug( line );
560 listValue = g_slist_append(
561 listValue, g_strdup( line ) );
562 flagProc = FALSE;
564 else {
565 flagProc = TRUE;
569 if( flagProc ) {
570 if( listValue != NULL ) {
571 /* Process list */
572 lineValue = mgu_list_coalesce( listValue );
573 if( lineValue ) {
574 pine_build_items(
575 pineFile, cache, lineValue );
577 g_free( lineValue );
578 lineValue = NULL;
579 g_slist_free_full( listValue, g_free );
580 listValue = NULL;
582 if( line != NULL ) {
583 /* Append to list */
584 listValue = g_slist_append(
585 listValue, g_strdup( line ) );
589 g_free( line );
590 line = NULL;
593 /* Release data */
594 g_slist_free_full( listValue, g_free );
595 listValue = NULL;
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 );
619 /* Mark cache */
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 ];
636 gint len;
637 FILE *fp;
639 homedir = get_home_dir();
640 if( ! homedir ) return g_strdup( "" );
642 strncpy( str, homedir, WORK_BUFLEN );
643 len = strlen( str );
644 if( len > 0 ) {
645 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
646 str[ len ] = G_DIR_SEPARATOR;
647 str[ ++len ] = '\0';
650 strncat( str, PINE_HOME_FILE, WORK_BUFLEN - strlen(str) );
652 /* Attempt to open */
653 if( ( fp = claws_fopen( str, "rb" ) ) != NULL ) {
654 claws_fclose( fp );
656 else {
657 /* Truncate filename */
658 str[ len ] = '\0';
660 return g_strdup( str );
664 * End of Source.