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