2005-11-29 [colin] 1.9.100cvs42
[claws.git] / src / mutt.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2001 Match Grun
4  *
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 2 of the License, or
8  * (at your option) any later version.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 /*
21  * Functions necessary to access MUTT address book file.
22  */
23
24 #include <sys/stat.h>
25 #include <ctype.h>
26 #include <string.h>
27 #include <glib.h>
28
29 #include "utils.h"
30 #include "mgutils.h"
31 #include "mutt.h"
32 #include "addritem.h"
33 #include "addrcache.h"
34
35 #define MUTT_HOME_FILE  ".muttrc"
36 #define MUTTBUFSIZE     2048
37 #define MUTT_TAG_ALIAS  "alias"
38
39 /*
40 * Create new object.
41 */
42 MuttFile *mutt_create() {
43         MuttFile *muttFile;
44         muttFile = g_new0( MuttFile, 1 );
45         muttFile->path = NULL;
46         muttFile->file = NULL;
47         muttFile->retVal = MGU_SUCCESS;
48         muttFile->uniqTable = g_hash_table_new( g_str_hash, g_str_equal );
49         muttFile->cbProgress = NULL;
50         return muttFile;
51 }
52
53 /*
54 * Properties...
55 */
56 void mutt_set_file( MuttFile* muttFile, const gchar *value ) {
57         g_return_if_fail( muttFile != NULL );
58         muttFile->path = mgu_replace_string( muttFile->path, value );
59         g_strstrip( muttFile->path );
60 }
61
62 /*
63 * Register a callback function. When called, the function will be passed
64 * the following arguments:
65 *       MuttFile object,
66 *       File size (long),
67 *       Current position (long)
68 * This can be used for a progress indicator.
69 */
70 void mutt_set_callback( MuttFile *muttFile, void *func ) {
71         muttFile->cbProgress = func;
72 }
73
74 /*
75  * Free key in table.
76  */
77 static gint mutt_free_table_vis( gpointer key, gpointer value, gpointer data ) {
78         g_free( key );
79         key = NULL;
80         value = NULL;
81         return TRUE;
82 }
83
84 /*
85 * Free up object by releasing internal memory.
86 */
87 void mutt_free( MuttFile *muttFile ) {
88         g_return_if_fail( muttFile != NULL );
89
90         /* Close file */
91         if( muttFile->file ) fclose( muttFile->file );
92
93         /* Free internal stuff */
94         g_free( muttFile->path );
95
96         /* Free unique address table */
97         g_hash_table_foreach_remove( muttFile->uniqTable, mutt_free_table_vis, NULL );
98         g_hash_table_destroy( muttFile->uniqTable );
99
100         /* Clear pointers */
101         muttFile->file = NULL;
102         muttFile->path = NULL;
103         muttFile->retVal = MGU_SUCCESS;
104         muttFile->uniqTable = NULL;
105         muttFile->cbProgress = NULL;
106
107         /* Now release file object */
108         g_free( muttFile );
109 }
110
111 /*
112 * Display object to specified stream.
113 */
114 void mutt_print_file( MuttFile *muttFile, FILE *stream ) {
115         g_return_if_fail( muttFile != NULL );
116         fprintf( stream, "MUTT File:\n" );
117         fprintf( stream, "file spec: '%s'\n", muttFile->path );
118         fprintf( stream, "  ret val: %d\n",   muttFile->retVal );
119 }
120
121 /*
122 * Open file for read.
123 * return: TRUE if file opened successfully.
124 */
125 static gint mutt_open_file( MuttFile* muttFile ) {
126         if( muttFile->path ) {
127                 muttFile->file = g_fopen( muttFile->path, "rb" );
128                 if( ! muttFile->file ) {
129                         muttFile->retVal = MGU_OPEN_FILE;
130                         return muttFile->retVal;
131                 }
132         }
133         else {
134                 /* printf( "file not specified\n" ); */
135                 muttFile->retVal = MGU_NO_FILE;
136                 return muttFile->retVal;
137         }
138
139         /* Setup a buffer area */
140         muttFile->retVal = MGU_SUCCESS;
141         return muttFile->retVal;
142 }
143
144 /*
145 * Close file.
146 */
147 static void mutt_close_file( MuttFile *muttFile ) {
148         g_return_if_fail( muttFile != NULL );
149         if( muttFile->file ) fclose( muttFile->file );
150         muttFile->file = NULL;
151 }
152
153 /*
154 * Read line of text from file.
155 * Enter: muttFile File object.
156 *        flagCont Continuation flag, set if back-slash character at EOL.
157 * Return: ptr to buffer where line starts.
158 */
159 static gchar *mutt_get_line( MuttFile *muttFile, gboolean *flagCont ) {
160         gchar buf[ MUTTBUFSIZE ];
161         int ch, lch;
162         int i = 0, li = 0;
163
164         *flagCont = FALSE;
165         if( feof( muttFile->file ) ) 
166                 return NULL;
167
168         memset(buf, 0, MUTTBUFSIZE);
169
170         lch = '\0';
171         while( i < MUTTBUFSIZE-1 ) {
172                 ch = fgetc( muttFile->file );
173                 if( ch == '\0' || ch == EOF ) {
174                         if( i == 0 ) 
175                                 return NULL;
176                         break;
177                 }
178                 if( ch == '\n' ) {
179                         if( lch == '\\' ) {
180                                 /* Replace backslash with NULL */
181                                 if( li != 0 ) 
182                                         buf[li] = '\0';
183                                 *flagCont = TRUE;
184                         }
185                         break;
186                 }
187                 buf[i] = ch;
188                 li = i;
189                 lch = ch;
190                 i++;
191         }
192         buf[i]='\0';
193
194         /* Copy into private buffer */
195         return g_strdup( buf );
196 }
197
198 /*
199  * Parsed address data.
200  */
201 typedef struct _Mutt_ParsedRec_ Mutt_ParsedRec;
202 struct _Mutt_ParsedRec_ {
203         gchar *address;
204         gchar *name;
205 };
206
207 /*
208  * Free data record.
209  * Enter: rec Data record.
210  */
211 static void mutt_free_rec( Mutt_ParsedRec *rec ) {
212         if( rec ) {
213                 g_free( rec->address );
214                 g_free( rec->name );
215                 rec->address = NULL;
216                 rec->name = NULL;
217                 g_free( rec );
218         }
219 }
220
221 /*
222  * Print data record.
223  * Enter: rec    Data record.
224  *        stream File.
225  */void mutt_print_rec( Mutt_ParsedRec *rec, FILE *stream ) {
226         fprintf( stream, "\taddr: %s\tname: %s\n", rec->address, rec->name );
227 }
228
229 /*
230 * Parse recipient list for each address.
231 * Enter: rcpList   Recipients extracted from file.
232 *        addrCount Updated with recipient count.
233 * Return: Linked list of recipients.
234 */
235 static GSList *mutt_parse_rcplist( gchar *rcpList, gint *addrCount ) {
236         gchar *ptr, *pStart, *pEnd, *pAddr, *pName, *address, *name;
237         gchar ch;
238         GSList *list;
239         Mutt_ParsedRec *rec;
240         gint  cnt;
241
242         list = NULL;
243         cnt = 0;
244         pStart = rcpList;
245         while( *pStart ) {
246                 ptr = pStart;
247                 address = name = NULL;
248                 pName = pAddr = NULL;
249                 /* Chew up spaces */
250                 while( *ptr ) {
251                         if( ! isspace( *ptr ) ) break;
252                         ptr++;
253                 }
254
255                 /* Find address */
256                 while( *ptr ) {
257                         ch = *ptr;
258                         if( ch == '(' ) {
259                                 pAddr = pName = ptr;
260                                 break;
261                         }
262                         if( ch == ',' ) {
263                                 pAddr = ptr;
264                                 ptr++;
265                                 break;
266                         }
267                         if( isspace( ch ) ) {
268                                 pAddr = ptr;
269                                 break;
270                         }
271                         ptr++;
272                 }
273
274                 /* Extract address */
275                 if( pAddr ) {
276                         address = g_strndup( pStart, pAddr - pStart );
277                 }
278                 else {
279                         address = g_strdup( pStart );
280                 }
281                 g_strstrip( address );
282
283                 /* Chew up spaces */
284                 while( *ptr ) {
285                         ch = *ptr;
286                         if( ch == '(' ) {
287                                 pName = ptr;
288                                 break;
289                         }
290                         if( ch == ',' ) {
291                                 ptr++;
292                                 break;
293                         }
294                         ptr++;
295                         if( isspace( ch ) ) continue;
296                 }
297                 pStart = ptr;
298         
299                 /* Extract name (if any) */
300                 if( pName ) {
301                         /* Look for closing parens */
302                         pName++;
303                         pEnd = NULL;
304                         ptr = pName;
305                         while( *ptr ) {
306                                 if( *ptr == ')' ) {
307                                         pEnd = ptr;
308                                         break;
309                                 }
310                                 ptr++;
311                         }
312                         if( pEnd ) {
313                                 name = g_strndup( pName, pEnd - pName );
314                                 pEnd++;
315                                 if( *pEnd ) pEnd++;
316                         }
317                         else {
318                                 name = g_strdup( pName );
319                         }
320                         g_strstrip( name );
321                         pStart = pEnd;
322                 }
323                 else {
324                         name = g_strdup( "" );
325                 }
326
327                 /* New record */
328                 rec = g_new0( Mutt_ParsedRec, 1 );
329                 rec->address = address;
330                 rec->name = name;
331                 list = g_slist_append( list, rec );
332                 cnt++;
333
334                 /* mutt_print_rec( rec, stdout ); */
335         }
336         *addrCount = cnt;
337         return list;
338 }
339
340 /*
341  * Insert person and address into address cache.
342  * Enter: muttFile MUTT control data.
343  *        cache    Address cache.
344  *        address  E-Mail address.
345  *        name     Name.
346  * Return: E-Mail object, either inserted or found in hash table.
347  */
348 static ItemEMail *mutt_insert_table(
349                 MuttFile *muttFile, AddressCache *cache, gchar *address,
350                 gchar *name )
351 {
352         ItemPerson *person;
353         ItemEMail *email;
354         gchar *key;
355
356         /* Test whether address already in hash table */
357         key = g_strdup( address );
358         g_strdown( key );
359         email = g_hash_table_lookup( muttFile->uniqTable, key );
360
361         if( email == NULL ) {
362                 /* No - create person */
363                 person = addritem_create_item_person();
364                 addritem_person_set_common_name( person, name );
365                 addrcache_id_person( cache, person );
366                 addrcache_add_person( cache, person );
367
368                 /* Add email for person */
369                 email = addritem_create_item_email();
370                 addritem_email_set_address( email, address );
371                 addrcache_id_email( cache, email );
372                 addrcache_person_add_email( cache, person, email );
373
374                 /* Insert entry */
375                 g_hash_table_insert( muttFile->uniqTable, key, email );
376         }
377         else {
378                 /* Yes - update person with longest name */
379                 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
380                 if( strlen( name ) > strlen( ADDRITEM_NAME(person) ) ) {
381                         addritem_person_set_common_name( person, name );
382                 }
383
384                 /* Free up */
385                 g_free( key );
386         }
387
388         return email;
389 }
390
391 /*
392  * Build address book entries.
393  * Enter: muttFile  MUTT control data.
394  *        cache     Address cache.
395  *        aliasName Alias,
396  *        listAddr  List of address items.
397  *        addrCount Address list count.
398  */
399 static void mutt_build_address(
400                 MuttFile *muttFile, AddressCache *cache,
401                 gchar *aliasName, GSList *listAddr, gint addrCount )
402 {
403         GSList *node = NULL;
404         ItemEMail *email;
405         ItemGroup *group;
406         Mutt_ParsedRec *rec;
407
408         group = NULL;
409         if( listAddr != NULL && addrCount > 1 ) {
410                 group = addritem_create_item_group();
411                 addritem_group_set_name( group, aliasName );
412                 addrcache_id_group( cache, group );
413                 addrcache_add_group( cache, group );
414         }
415
416         email = NULL;
417         node = listAddr;
418         while( node ) {
419                 rec = node->data;
420
421                 /* Insert person/email */
422                 email = mutt_insert_table(
423                                 muttFile, cache, rec->address, rec->name );
424
425                 /* Add email to group */
426                 if( group ) {
427                         addritem_group_add_email( group, email );
428                 }
429
430                 mutt_free_rec( rec );
431                 node = g_slist_next( node );
432         }
433 }
434
435 /*
436  * Parse address line adn build address items.
437  * Enter: muttFile MUTT control data.
438  *        cache    Address cache.
439  *        line     Data record.
440  */
441 static void mutt_build_items( MuttFile *muttFile, AddressCache *cache, gchar *line ) {
442         GList *list, *node;
443         gint tCount, aCount;
444         gchar *aliasTag, *aliasName, *recipient;
445         GSList *addrList;
446
447         /* printf( "\nBUILD >%s<\n", line ); */
448         list = mgu_parse_string( line,  3, &tCount );
449         if( tCount < 3 ) {
450                 if( list ) {
451                         mgu_free_dlist( list );
452                         list = NULL;
453                 }
454                 return;
455         }
456
457         aliasTag = list->data;
458         node = g_list_next( list );
459         aliasName = node->data;
460         node = g_list_next( node );
461         recipient = node->data;
462
463         addrList = NULL;
464         if( strcmp( aliasTag, MUTT_TAG_ALIAS ) == 0 ) {
465                 aCount = 0;
466                 /* printf( "aliasName :%s:\n", aliasName ); */
467                 /* printf( "recipient :%s:\n", recipient ); */
468                 addrList = mutt_parse_rcplist( recipient, &aCount );
469                 /* printf( "---\n" ); */
470                 mutt_build_address( muttFile, cache, aliasName, addrList, aCount );
471         }
472
473         mgu_free_dlist( list );
474         list = NULL;
475
476 }
477
478 /*
479  * Read file data into address cache.
480  * Enter: muttFile MUTT control data.
481  *        cache Address cache.
482  */
483 static void mutt_read_file( MuttFile *muttFile, AddressCache *cache ) {
484         GSList *listValue = NULL;
485         gboolean flagEOF = FALSE, flagCont = FALSE, lastCont = FALSE;
486         gchar *line =  NULL, *lineValue = NULL;
487         long posEnd = 0L;
488         long posCur = 0L;
489
490         /* Find EOF for progress indicator */
491         fseek( muttFile->file, 0L, SEEK_END );
492         posEnd = ftell( muttFile->file );
493         fseek( muttFile->file, 0L, SEEK_SET );
494
495         while( ! flagEOF ) {
496                 flagCont = FALSE;
497                 line =  mutt_get_line( muttFile, &flagCont );
498
499                 posCur = ftell( muttFile->file );
500                 if( muttFile->cbProgress ) {
501                         /* Call progress indicator */
502                         ( muttFile->cbProgress ) ( muttFile, & posEnd, & posCur );
503                 }
504
505                 if( line == NULL ) flagEOF = TRUE;
506                 if( ! lastCont ) {
507                         /* Save data */
508                         lineValue = mgu_list_coalesce( listValue );
509                         if( lineValue ) {
510                                 mutt_build_items( muttFile, cache, lineValue );
511                         }
512                         g_free( lineValue );
513                         lineValue = NULL;
514                         mgu_free_list( listValue );
515                         listValue = NULL;
516                 }
517                 lastCont = flagCont;
518
519                 /* Add line to list */
520                 listValue = g_slist_append( listValue, g_strdup( line ) );
521
522                 g_free( line );
523                 line = NULL;
524         }
525
526         /* Release data */
527         mgu_free_list( listValue );
528         listValue = NULL;
529 }
530
531 /*
532 * ============================================================================================
533 * Read file into list. Main entry point
534 * Enter:  muttFile MUTT control data.
535 *         cache    Address cache to load.
536 * Return: Status code.
537 * ============================================================================================
538 */
539 gint mutt_import_data( MuttFile *muttFile, AddressCache *cache ) {
540         g_return_val_if_fail( muttFile != NULL, MGU_BAD_ARGS );
541         g_return_val_if_fail( cache != NULL, MGU_BAD_ARGS );
542         muttFile->retVal = MGU_SUCCESS;
543         addrcache_clear( cache );
544         cache->dataRead = FALSE;
545         mutt_open_file( muttFile );
546         if( muttFile->retVal == MGU_SUCCESS ) {
547                 /* Read data into the cache */
548                 mutt_read_file( muttFile, cache );
549                 mutt_close_file( muttFile );
550
551                 /* Mark cache */
552                 cache->modified = FALSE;
553                 cache->dataRead = TRUE;
554         }
555         return muttFile->retVal;
556 }
557
558 #define WORK_BUFLEN 1024
559
560 /*
561 * Attempt to find a Mutt file.
562 * Return: Filename, or home directory if not found, or empty string if
563 * no home. Filename should be g_free() when done.
564 */
565 gchar *mutt_find_file( void ) {
566         const gchar *homedir;
567         gchar str[ WORK_BUFLEN ];
568         gint len;
569         FILE *fp;
570
571         homedir = g_get_home_dir();
572         if( ! homedir ) return g_strdup( "" );
573
574         strcpy( str, homedir );
575         len = strlen( str );
576         if( len > 0 ) {
577                 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
578                         str[ len ] = G_DIR_SEPARATOR;
579                         str[ ++len ] = '\0';
580                 }
581         }
582         strcat( str, MUTT_HOME_FILE );
583
584         /* Attempt to open */
585         if( ( fp = g_fopen( str, "rb" ) ) != NULL ) {
586                 fclose( fp );
587         }
588         else {
589                 /* Truncate filename */
590                 str[ len ] = '\0';
591         }
592         return g_strdup( str );
593 }
594
595 /*
596 * End of Source.
597 */
598