2013-02-27 [mones] 3.9.0cvs104
[claws.git] / src / pine.c
blob355e6367f5b72d3b6692d9aa988254a04bfe33d4
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"
34 #define PINE_HOME_FILE ".addressbook"
35 #define PINEBUFSIZE 2048
36 #define CHAR_QUOTE '\"'
37 #define CHAR_APOS '\''
38 #define CHAR_COMMA ','
39 #define CHAR_AT '@'
42 * Create new object.
44 PineFile *pine_create() {
45 PineFile *pineFile;
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;
52 return pineFile;
56 * Properties...
58 void pine_set_file( PineFile* pineFile, const gchar *value ) {
59 cm_return_if_fail( pineFile != NULL );
60 pineFile->path = mgu_replace_string( pineFile->path, value );
61 g_strstrip( pineFile->path );
65 * Free key in table.
67 static gint pine_free_table_vis( gpointer key, gpointer value, gpointer data ) {
68 g_free( key );
69 key = NULL;
70 value = NULL;
71 return TRUE;
75 * Free up object by releasing internal memory.
77 void pine_free( PineFile *pineFile ) {
78 cm_return_if_fail( pineFile != NULL );
80 /* Close file */
81 if( pineFile->file ) fclose( pineFile->file );
83 /* Free internal stuff */
84 g_free( pineFile->path );
86 /* Free unique address table */
87 g_hash_table_foreach_remove( pineFile->uniqTable, pine_free_table_vis, NULL );
88 g_hash_table_destroy( pineFile->uniqTable );
90 /* Clear pointers */
91 pineFile->file = NULL;
92 pineFile->path = NULL;
93 pineFile->retVal = MGU_SUCCESS;
94 pineFile->uniqTable = NULL;
95 pineFile->cbProgress = NULL;
97 /* Now release file object */
98 g_free( pineFile );
102 * Open file for read.
103 * Enter: pineFile File object.
104 * return: TRUE if file opened successfully.
106 static gint pine_open_file( PineFile* pineFile ) {
107 if( pineFile->path ) {
108 pineFile->file = g_fopen( pineFile->path, "rb" );
109 if( ! pineFile->file ) {
110 pineFile->retVal = MGU_OPEN_FILE;
111 return pineFile->retVal;
114 else {
115 /* g_print( "file not specified\n" ); */
116 pineFile->retVal = MGU_NO_FILE;
117 return pineFile->retVal;
120 /* Setup a buffer area */
121 pineFile->retVal = MGU_SUCCESS;
122 return pineFile->retVal;
126 * Close file.
127 * Enter: pineFile File object.
129 static void pine_close_file( PineFile *pineFile ) {
130 cm_return_if_fail( pineFile != NULL );
131 if( pineFile->file ) fclose( pineFile->file );
132 pineFile->file = NULL;
136 * Read line of text from file.
137 * Enter: pineFile File object.
138 * Return: Copy of buffer. Should be g_free'd when done.
140 static gchar *pine_read_line( PineFile *pineFile ) {
141 gchar buf[ PINEBUFSIZE ];
142 int c, i = 0;
143 gchar ch;
145 if( feof( pineFile->file ) )
146 return NULL;
148 while( i < PINEBUFSIZE-1 ) {
149 c = fgetc( pineFile->file );
150 if( c == EOF ) {
151 if( i == 0 )
152 return NULL;
153 break;
155 ch = (gchar) c;
156 if( ch == '\0' ) {
157 if( i == 0 )
158 return NULL;
159 break;
161 if( ch == '\n' ) {
162 break;
164 buf[i] = ch;
165 i++;
167 buf[i] = '\0';
169 /* Copy into private buffer */
170 return g_strdup( buf );
174 * Parsed address data.
176 typedef struct _Pine_ParsedRec_ Pine_ParsedRec;
177 struct _Pine_ParsedRec_ {
178 gchar *nickName;
179 gchar *name;
180 gchar *address;
181 gchar *fcc;
182 gchar *comments;
183 gboolean isGroup;
184 GSList *listName;
185 GSList *listAddr;
189 * Free data record.
190 * Enter: rec Data record.
192 static void pine_free_rec( Pine_ParsedRec *rec ) {
193 if( rec ) {
194 g_free( rec->nickName );
195 g_free( rec->name );
196 g_free( rec->address );
197 g_free( rec->fcc );
198 g_free( rec->comments );
199 mgu_clear_slist( rec->listName );
200 mgu_clear_slist( rec->listAddr );
201 g_slist_free( rec->listName );
202 g_slist_free( rec->listAddr );
203 rec->nickName = NULL;
204 rec->name = NULL;
205 rec->address = NULL;
206 rec->fcc = NULL;
207 rec->comments = NULL;
208 rec->isGroup = FALSE;
209 g_free( rec );
214 * Clean name.
216 static void pine_clean_name( Pine_ParsedRec *rec ) {
217 gchar *p;
219 p = rec->name;
220 if( p == NULL ) return;
221 if( *p == '\0' ) return;
223 g_strstrip( rec->name );
224 if( *p == CHAR_APOS || *p == CHAR_QUOTE ) {
225 return;
228 /* If embedded comma present, surround match with quotes */
229 while( *p ) {
230 if( *p == CHAR_COMMA ) {
231 p = g_strdup_printf( "\"%s\"", rec->name );
232 g_free( rec->name );
233 rec->name = p;
234 return;
236 p++;
241 * Parse pine address record.
242 * Enter: buf Address record buffer.
243 * Return: Data record.
245 static Pine_ParsedRec *pine_parse_record( gchar *buf ) {
246 Pine_ParsedRec *rec;
247 gchar *p, *f;
248 gint pos, len, i;
249 gchar *tmp[5];
251 for( i = 0; i < 5; i++ )
252 tmp[i] = NULL;
254 /* Extract tab separated values */
255 rec = NULL;
256 pos = 0;
257 p = f = buf;
258 while( *p ) {
259 if( *p == '\t' ) {
260 len = p - f;
261 if( len > 0 ) {
262 tmp[ pos ] = g_strndup( f, len );
263 f = p;
264 f++;
266 pos++;
268 p++;
271 /* Extract last value */
272 len = p - f;
273 if( len > 0 ) {
274 tmp[ pos++ ] = g_strndup( f, len );
277 /* Populate record */
278 if( pos > 0 ) {
279 rec = g_new0( Pine_ParsedRec, 1 );
280 rec->isGroup = FALSE;
281 for( i = 0; i < pos; i++ ) {
282 f = tmp[i];
283 if( f ) {
284 g_strstrip( f );
286 if( i == 0 ) rec->nickName = f;
287 else if( i == 1 ) rec->name = f;
288 else if( i == 2 ) rec->address = f;
289 else if( i == 3 ) rec->fcc = f;
290 else if( i == 4 ) rec->comments = f;
291 tmp[i] = NULL;
294 if( rec->address != NULL ) {
295 /* Strip leading/trailing parens */
296 p = rec->address;
297 if( *p == '(' ) {
298 len = strlen( p ) - 1;
299 *p = ' ';
300 *(p + len) = ' ';
301 rec->isGroup = TRUE;
306 return rec;
310 * Parse name from email address string.
311 * Enter: buf Start address of buffer to process (not modified).
312 * atp Pointer to email at (@) character.
313 * ap Pointer to start of email address returned.
314 * ep Pointer to end of email address returned.
315 * Return: Parsed name or NULL if not present. This should be g_free'd
316 * when done.
318 static gchar *pine_parse_name(
319 const gchar *buf, const gchar *atp, const gchar **ap,
320 const gchar **ep )
322 gchar *name;
323 const gchar *pos;
324 const gchar *tmp;
325 const gchar *bp;
326 gint ilen;
328 name = NULL;
329 *ap = NULL;
330 *ep = NULL;
332 /* Find first non-separator char */
333 bp = buf;
334 while( TRUE ) {
335 if( strchr( ",; \n\r", *bp ) == NULL ) break;
336 bp++;
339 /* Search back for start of name */
340 tmp = atp;
341 pos = atp;
342 while( pos >= bp ) {
343 tmp = pos;
344 if( *pos == '<' ) {
345 /* Found start of address/end of name part */
346 ilen = -1 + ( size_t ) ( pos - bp );
347 name = g_strndup( bp, ilen + 1 );
348 *(name + ilen + 1) = '\0';
350 /* Remove leading trailing quotes and spaces */
351 mgu_str_ltc2space( name, '\"', '\"' );
352 mgu_str_ltc2space( name, '\'', '\'' );
353 mgu_str_ltc2space( name, '\"', '\"' );
354 mgu_str_unescape( name );
355 g_strstrip( name );
356 break;
358 pos--;
360 *ap = tmp;
362 /* Search forward for end of address */
363 pos = atp + 1;
364 while( TRUE ) {
365 if( *pos == '>' ) {
366 pos++;
367 break;
369 if( strchr( ",; \'\n\r", *pos ) ) break;
370 pos++;
372 *ep = pos;
374 return name;
378 * Parse address list.
379 * Enter: pineFile Pine control data.
380 * cache Address cache.
381 * rec Data record.
383 static void pine_parse_address( PineFile *pineFile, AddressCache *cache, Pine_ParsedRec *rec ) {
384 const gchar *buf;
385 gchar addr[ PINEBUFSIZE ];
386 const gchar *bp;
387 const gchar *ep;
388 gchar *atCh;
389 gchar *name;
390 gint len;
392 cm_return_if_fail( rec->address != NULL );
394 buf = rec->address;
395 while((atCh = strchr( buf, CHAR_AT )) != NULL) {
396 name = pine_parse_name( buf, atCh, &bp, &ep );
397 len = ( size_t ) ( ep - bp );
398 strncpy( addr, bp, len );
399 addr[ len ] = '\0';
400 extract_address( addr );
402 if( name == NULL ) name = g_strdup( "" );
403 rec->listName = g_slist_append( rec->listName, name );
404 rec->listAddr = g_slist_append( rec->listAddr, g_strdup( addr ) );
406 buf = ep;
407 if( atCh == ep ) {
408 buf++;
414 * Insert person and address into address cache.
415 * Enter: pineFile Pine control data.
416 * cache Address cache.
417 * address E-Mail address.
418 * name Name.
419 * remarks Remarks.
420 * Return: E-Mail object, either inserted or found in hash table.
422 static ItemEMail *pine_insert_table(
423 PineFile *pineFile, AddressCache *cache, gchar *address,
424 gchar *name, gchar *remarks )
426 ItemPerson *person;
427 ItemEMail *email;
428 gchar *key;
430 cm_return_val_if_fail( address != NULL, NULL );
432 /* create an entry with empty name if needed */
433 if ( name == NULL )
434 name = "";
436 /* Test whether address already in hash table */
437 key = g_utf8_strdown( address, -1 );
438 email = g_hash_table_lookup( pineFile->uniqTable, key );
440 if( email == NULL ) {
441 /* No - create person */
442 person = addritem_create_item_person();
443 addritem_person_set_common_name( person, name );
444 addrcache_id_person( cache, person );
445 addrcache_add_person( cache, person );
447 /* Add email for person */
448 email = addritem_create_item_email();
449 addritem_email_set_address( email, address );
450 addritem_email_set_remarks( email, remarks );
451 addrcache_id_email( cache, email );
452 addrcache_person_add_email( cache, person, email );
454 /* Insert entry */
455 g_hash_table_insert( pineFile->uniqTable, key, email );
457 else {
458 /* Yes - update person with longest name */
459 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
460 if( strlen( name ) > strlen( ADDRITEM_NAME(person) ) ) {
461 addritem_person_set_common_name( person, name );
464 /* Free up */
465 g_free( key );
468 return email;
472 * Parse address line adn build address items.
473 * Enter: pineFile Pine control data.
474 * cache Address cache to load.
475 * line Address record.
477 static void pine_build_items( PineFile *pineFile, AddressCache *cache, gchar *line ) {
478 Pine_ParsedRec *rec;
479 GSList *nodeAddr, *nodeName;
480 ItemGroup *group;
481 ItemEMail *email;
483 rec = pine_parse_record( line );
484 if( rec ) {
485 pine_clean_name( rec );
486 pine_parse_address( pineFile, cache, rec );
487 /* pine_print_rec( rec, stdout ); */
488 /* g_print( "=========\n" ); */
490 if( rec->isGroup ) {
491 /* Create group */
492 group = addritem_create_item_group();
493 addritem_group_set_name( group, rec->nickName );
494 addrcache_id_group( cache, group );
495 addrcache_add_group( cache, group );
497 /* Add email to group */
498 nodeName = rec->listName;
499 nodeAddr = rec->listAddr;
500 while( nodeAddr ) {
501 email = pine_insert_table(
502 pineFile, cache, nodeAddr->data,
503 nodeName->data, "" );
505 /* Add email to group */
506 addritem_group_add_email( group, email );
508 nodeAddr = g_slist_next( nodeAddr );
509 nodeName = g_slist_next( nodeName );
512 else {
513 email = pine_insert_table(
514 pineFile, cache, rec->address,
515 rec->name, rec->comments );
518 pine_free_rec( rec );
523 * Read file data into address cache.
524 * Enter: pineFile Pine control data.
525 * cache Address cache to load.
527 static void pine_read_file( PineFile *pineFile, AddressCache *cache ) {
528 GSList *listValue = NULL;
529 gboolean flagEOF = FALSE, flagProc = FALSE, flagDone = FALSE;
530 gchar *line = NULL, *lineValue = NULL;
531 long posEnd = 0L;
532 long posCur = 0L;
534 /* Find EOF for progress indicator */
535 fseek( pineFile->file, 0L, SEEK_END );
536 posEnd = ftell( pineFile->file );
537 fseek( pineFile->file, 0L, SEEK_SET );
539 flagProc = FALSE;
540 while( ! flagDone ) {
541 if( flagEOF ) {
542 flagDone = TRUE;
543 flagProc = TRUE;
545 else {
546 line = pine_read_line( pineFile );
549 posCur = ftell( pineFile->file );
550 if( pineFile->cbProgress ) {
551 /* Call progress indicator */
552 ( pineFile->cbProgress ) ( pineFile, & posEnd, & posCur );
555 /* Add line to list */
556 if( line == NULL ) {
557 flagEOF = TRUE;
559 else {
560 /* Check for continuation line (1 space only) */
561 if( *line == ' ' ) {
562 g_strchug( line );
563 listValue = g_slist_append(
564 listValue, g_strdup( line ) );
565 flagProc = FALSE;
567 else {
568 flagProc = TRUE;
572 if( flagProc ) {
573 if( listValue != NULL ) {
574 /* Process list */
575 lineValue = mgu_list_coalesce( listValue );
576 if( lineValue ) {
577 pine_build_items(
578 pineFile, cache, lineValue );
580 g_free( lineValue );
581 lineValue = NULL;
582 mgu_free_list( listValue );
583 listValue = NULL;
585 if( line != NULL ) {
586 /* Append to list */
587 listValue = g_slist_append(
588 listValue, g_strdup( line ) );
592 g_free( line );
593 line = NULL;
596 /* Release data */
597 mgu_free_list( listValue );
598 listValue = NULL;
602 * ============================================================================================
603 * Read file into list. Main entry point
604 * Enter: pineFile Pine control data.
605 * cache Address cache to load.
606 * Return: Status code.
607 * ============================================================================================
609 gint pine_import_data( PineFile *pineFile, AddressCache *cache ) {
610 cm_return_val_if_fail( pineFile != NULL, MGU_BAD_ARGS );
611 cm_return_val_if_fail( cache != NULL, MGU_BAD_ARGS );
613 pineFile->retVal = MGU_SUCCESS;
614 addrcache_clear( cache );
615 cache->dataRead = FALSE;
616 pine_open_file( pineFile );
617 if( pineFile->retVal == MGU_SUCCESS ) {
618 /* Read data into the cache */
619 pine_read_file( pineFile, cache );
620 pine_close_file( pineFile );
622 /* Mark cache */
623 cache->modified = FALSE;
624 cache->dataRead = TRUE;
626 return pineFile->retVal;
629 #define WORK_BUFLEN 1024
632 * Attempt to find a Pine addressbook file.
633 * Return: Filename, or home directory if not found, or empty string if
634 * no home. Filename should be g_free() when done.
636 gchar *pine_find_file( void ) {
637 const gchar *homedir;
638 gchar str[ WORK_BUFLEN + 1 ];
639 gint len;
640 FILE *fp;
642 homedir = get_home_dir();
643 if( ! homedir ) return g_strdup( "" );
645 strncpy( str, homedir, WORK_BUFLEN );
646 len = strlen( str );
647 if( len > 0 ) {
648 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
649 str[ len ] = G_DIR_SEPARATOR;
650 str[ ++len ] = '\0';
653 strncat( str, PINE_HOME_FILE, WORK_BUFLEN - strlen(str) );
655 /* Attempt to open */
656 if( ( fp = g_fopen( str, "rb" ) ) != NULL ) {
657 fclose( fp );
659 else {
660 /* Truncate filename */
661 str[ len ] = '\0';
663 return g_strdup( str );
667 * End of Source.