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