2005-10-25 [wwp] 1.9.15cvs102
[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         gchar *ptr, *lptr;
163
164         *flagCont = FALSE;
165         if( feof( muttFile->file ) ) return NULL;
166
167         ptr = buf;
168         lch = '\0';
169         lptr = NULL;
170         while( TRUE ) {
171                 *ptr = '\0';
172                 ch = fgetc( muttFile->file );
173                 if( ch == '\0' || ch == EOF ) {
174                         if( *buf == '\0' ) return NULL;
175                         break;
176                 }
177                 if( ch == '\n' ) {
178                         if( lch == '\\' ) {
179                                 /* Replace backslash with NULL */
180                                 if( lptr ) *lptr = '\0';
181                                 *flagCont = TRUE;
182                         }
183                         break;
184                 }
185                 *ptr = ch;
186                 lptr = ptr;
187                 lch = ch;
188                 ptr++;
189         }
190
191         /* Copy into private buffer */
192         return g_strdup( buf );
193 }
194
195 /*
196  * Parsed address data.
197  */
198 typedef struct _Mutt_ParsedRec_ Mutt_ParsedRec;
199 struct _Mutt_ParsedRec_ {
200         gchar *address;
201         gchar *name;
202 };
203
204 /*
205  * Free data record.
206  * Enter: rec Data record.
207  */
208 static void mutt_free_rec( Mutt_ParsedRec *rec ) {
209         if( rec ) {
210                 g_free( rec->address );
211                 g_free( rec->name );
212                 rec->address = NULL;
213                 rec->name = NULL;
214                 g_free( rec );
215         }
216 }
217
218 /*
219  * Print data record.
220  * Enter: rec    Data record.
221  *        stream File.
222  */void mutt_print_rec( Mutt_ParsedRec *rec, FILE *stream ) {
223         fprintf( stream, "\taddr: %s\tname: %s\n", rec->address, rec->name );
224 }
225
226 /*
227 * Parse recipient list for each address.
228 * Enter: rcpList   Recipients extracted from file.
229 *        addrCount Updated with recipient count.
230 * Return: Linked list of recipients.
231 */
232 static GSList *mutt_parse_rcplist( gchar *rcpList, gint *addrCount ) {
233         gchar *ptr, *pStart, *pEnd, *pAddr, *pName, *address, *name;
234         gchar ch;
235         GSList *list;
236         Mutt_ParsedRec *rec;
237         gint  cnt;
238
239         list = NULL;
240         cnt = 0;
241         pStart = rcpList;
242         while( *pStart ) {
243                 ptr = pStart;
244                 address = name = NULL;
245                 pName = pAddr = NULL;
246                 /* Chew up spaces */
247                 while( *ptr ) {
248                         if( ! isspace( *ptr ) ) break;
249                         ptr++;
250                 }
251
252                 /* Find address */
253                 while( *ptr ) {
254                         ch = *ptr;
255                         if( ch == '(' ) {
256                                 pAddr = pName = ptr;
257                                 break;
258                         }
259                         if( ch == ',' ) {
260                                 pAddr = ptr;
261                                 ptr++;
262                                 break;
263                         }
264                         if( isspace( ch ) ) {
265                                 pAddr = ptr;
266                                 break;
267                         }
268                         ptr++;
269                 }
270
271                 /* Extract address */
272                 if( pAddr ) {
273                         address = g_strndup( pStart, pAddr - pStart );
274                 }
275                 else {
276                         address = g_strdup( pStart );
277                 }
278                 g_strstrip( address );
279
280                 /* Chew up spaces */
281                 while( *ptr ) {
282                         ch = *ptr;
283                         if( ch == '(' ) {
284                                 pName = ptr;
285                                 break;
286                         }
287                         if( ch == ',' ) {
288                                 ptr++;
289                                 break;
290                         }
291                         ptr++;
292                         if( isspace( ch ) ) continue;
293                 }
294                 pStart = ptr;
295         
296                 /* Extract name (if any) */
297                 if( pName ) {
298                         /* Look for closing parens */
299                         pName++;
300                         pEnd = NULL;
301                         ptr = pName;
302                         while( *ptr ) {
303                                 if( *ptr == ')' ) {
304                                         pEnd = ptr;
305                                         break;
306                                 }
307                                 ptr++;
308                         }
309                         if( pEnd ) {
310                                 name = g_strndup( pName, pEnd - pName );
311                                 pEnd++;
312                                 if( *pEnd ) pEnd++;
313                         }
314                         else {
315                                 name = g_strdup( pName );
316                         }
317                         g_strstrip( name );
318                         pStart = pEnd;
319                 }
320                 else {
321                         name = g_strdup( "" );
322                 }
323
324                 /* New record */
325                 rec = g_new0( Mutt_ParsedRec, 1 );
326                 rec->address = address;
327                 rec->name = name;
328                 list = g_slist_append( list, rec );
329                 cnt++;
330
331                 /* mutt_print_rec( rec, stdout ); */
332         }
333         *addrCount = cnt;
334         return list;
335 }
336
337 /*
338  * Insert person and address into address cache.
339  * Enter: muttFile MUTT control data.
340  *        cache    Address cache.
341  *        address  E-Mail address.
342  *        name     Name.
343  * Return: E-Mail object, either inserted or found in hash table.
344  */
345 static ItemEMail *mutt_insert_table(
346                 MuttFile *muttFile, AddressCache *cache, gchar *address,
347                 gchar *name )
348 {
349         ItemPerson *person;
350         ItemEMail *email;
351         gchar *key;
352
353         /* Test whether address already in hash table */
354         key = g_strdup( address );
355         g_strdown( key );
356         email = g_hash_table_lookup( muttFile->uniqTable, key );
357
358         if( email == NULL ) {
359                 /* No - create person */
360                 person = addritem_create_item_person();
361                 addritem_person_set_common_name( person, name );
362                 addrcache_id_person( cache, person );
363                 addrcache_add_person( cache, person );
364
365                 /* Add email for person */
366                 email = addritem_create_item_email();
367                 addritem_email_set_address( email, address );
368                 addrcache_id_email( cache, email );
369                 addrcache_person_add_email( cache, person, email );
370
371                 /* Insert entry */
372                 g_hash_table_insert( muttFile->uniqTable, key, email );
373         }
374         else {
375                 /* Yes - update person with longest name */
376                 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
377                 if( strlen( name ) > strlen( ADDRITEM_NAME(person) ) ) {
378                         addritem_person_set_common_name( person, name );
379                 }
380
381                 /* Free up */
382                 g_free( key );
383         }
384
385         return email;
386 }
387
388 /*
389  * Build address book entries.
390  * Enter: muttFile  MUTT control data.
391  *        cache     Address cache.
392  *        aliasName Alias,
393  *        listAddr  List of address items.
394  *        addrCount Address list count.
395  */
396 static void mutt_build_address(
397                 MuttFile *muttFile, AddressCache *cache,
398                 gchar *aliasName, GSList *listAddr, gint addrCount )
399 {
400         GSList *node = NULL;
401         ItemEMail *email;
402         ItemGroup *group;
403         Mutt_ParsedRec *rec;
404
405         group = NULL;
406         if( listAddr != NULL && addrCount > 1 ) {
407                 group = addritem_create_item_group();
408                 addritem_group_set_name( group, aliasName );
409                 addrcache_id_group( cache, group );
410                 addrcache_add_group( cache, group );
411         }
412
413         email = NULL;
414         node = listAddr;
415         while( node ) {
416                 rec = node->data;
417
418                 /* Insert person/email */
419                 email = mutt_insert_table(
420                                 muttFile, cache, rec->address, rec->name );
421
422                 /* Add email to group */
423                 if( group ) {
424                         addritem_group_add_email( group, email );
425                 }
426
427                 mutt_free_rec( rec );
428                 node = g_slist_next( node );
429         }
430 }
431
432 /*
433  * Parse address line adn build address items.
434  * Enter: muttFile MUTT control data.
435  *        cache    Address cache.
436  *        line     Data record.
437  */
438 static void mutt_build_items( MuttFile *muttFile, AddressCache *cache, gchar *line ) {
439         GList *list, *node;
440         gint tCount, aCount;
441         gchar *aliasTag, *aliasName, *recipient;
442         GSList *addrList;
443
444         /* printf( "\nBUILD >%s<\n", line ); */
445         list = mgu_parse_string( line,  3, &tCount );
446         if( tCount < 3 ) {
447                 if( list ) {
448                         mgu_free_dlist( list );
449                         list = NULL;
450                 }
451                 return;
452         }
453
454         aliasTag = list->data;
455         node = g_list_next( list );
456         aliasName = node->data;
457         node = g_list_next( node );
458         recipient = node->data;
459
460         addrList = NULL;
461         if( strcmp( aliasTag, MUTT_TAG_ALIAS ) == 0 ) {
462                 aCount = 0;
463                 /* printf( "aliasName :%s:\n", aliasName ); */
464                 /* printf( "recipient :%s:\n", recipient ); */
465                 addrList = mutt_parse_rcplist( recipient, &aCount );
466                 /* printf( "---\n" ); */
467                 mutt_build_address( muttFile, cache, aliasName, addrList, aCount );
468         }
469
470         mgu_free_dlist( list );
471         list = NULL;
472
473 }
474
475 /*
476  * Read file data into address cache.
477  * Enter: muttFile MUTT control data.
478  *        cache Address cache.
479  */
480 static void mutt_read_file( MuttFile *muttFile, AddressCache *cache ) {
481         GSList *listValue = NULL;
482         gboolean flagEOF = FALSE, flagCont = FALSE, lastCont = FALSE;
483         gchar *line =  NULL, *lineValue = NULL;
484         long posEnd = 0L;
485         long posCur = 0L;
486
487         /* Find EOF for progress indicator */
488         fseek( muttFile->file, 0L, SEEK_END );
489         posEnd = ftell( muttFile->file );
490         fseek( muttFile->file, 0L, SEEK_SET );
491
492         while( ! flagEOF ) {
493                 flagCont = FALSE;
494                 line =  mutt_get_line( muttFile, &flagCont );
495
496                 posCur = ftell( muttFile->file );
497                 if( muttFile->cbProgress ) {
498                         /* Call progress indicator */
499                         ( muttFile->cbProgress ) ( muttFile, & posEnd, & posCur );
500                 }
501
502                 if( line == NULL ) flagEOF = TRUE;
503                 if( ! lastCont ) {
504                         /* Save data */
505                         lineValue = mgu_list_coalesce( listValue );
506                         if( lineValue ) {
507                                 mutt_build_items( muttFile, cache, lineValue );
508                         }
509                         g_free( lineValue );
510                         lineValue = NULL;
511                         mgu_free_list( listValue );
512                         listValue = NULL;
513                 }
514                 lastCont = flagCont;
515
516                 /* Add line to list */
517                 listValue = g_slist_append( listValue, g_strdup( line ) );
518
519                 g_free( line );
520                 line = NULL;
521         }
522
523         /* Release data */
524         mgu_free_list( listValue );
525         listValue = NULL;
526 }
527
528 /*
529 * ============================================================================================
530 * Read file into list. Main entry point
531 * Enter:  muttFile MUTT control data.
532 *         cache    Address cache to load.
533 * Return: Status code.
534 * ============================================================================================
535 */
536 gint mutt_import_data( MuttFile *muttFile, AddressCache *cache ) {
537         g_return_val_if_fail( muttFile != NULL, MGU_BAD_ARGS );
538         g_return_val_if_fail( cache != NULL, MGU_BAD_ARGS );
539         muttFile->retVal = MGU_SUCCESS;
540         addrcache_clear( cache );
541         cache->dataRead = FALSE;
542         mutt_open_file( muttFile );
543         if( muttFile->retVal == MGU_SUCCESS ) {
544                 /* Read data into the cache */
545                 mutt_read_file( muttFile, cache );
546                 mutt_close_file( muttFile );
547
548                 /* Mark cache */
549                 cache->modified = FALSE;
550                 cache->dataRead = TRUE;
551         }
552         return muttFile->retVal;
553 }
554
555 #define WORK_BUFLEN 1024
556
557 /*
558 * Attempt to find a Mutt file.
559 * Return: Filename, or home directory if not found, or empty string if
560 * no home. Filename should be g_free() when done.
561 */
562 gchar *mutt_find_file( void ) {
563         const gchar *homedir;
564         gchar str[ WORK_BUFLEN ];
565         gint len;
566         FILE *fp;
567
568         homedir = g_get_home_dir();
569         if( ! homedir ) return g_strdup( "" );
570
571         strcpy( str, homedir );
572         len = strlen( str );
573         if( len > 0 ) {
574                 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
575                         str[ len ] = G_DIR_SEPARATOR;
576                         str[ ++len ] = '\0';
577                 }
578         }
579         strcat( str, MUTT_HOME_FILE );
580
581         /* Attempt to open */
582         if( ( fp = g_fopen( str, "rb" ) ) != NULL ) {
583                 fclose( fp );
584         }
585         else {
586                 /* Truncate filename */
587                 str[ len ] = '\0';
588         }
589         return g_strdup( str );
590 }
591
592 /*
593 * End of Source.
594 */
595