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