2010-08-23 [pawel] 3.7.6cvs29
[claws.git] / src / mutt.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2001-2009 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         cm_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         cm_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         cm_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_utf8_strdown( address, -1 );
328         email = g_hash_table_lookup( muttFile->uniqTable, key );
329
330         if( email == NULL ) {
331                 /* No - create person */
332                 person = addritem_create_item_person();
333                 addritem_person_set_common_name( person, name );
334                 addrcache_id_person( cache, person );
335                 addrcache_add_person( cache, person );
336
337                 /* Add email for person */
338                 email = addritem_create_item_email();
339                 addritem_email_set_address( email, address );
340                 addrcache_id_email( cache, email );
341                 addrcache_person_add_email( cache, person, email );
342
343                 /* Insert entry */
344                 g_hash_table_insert( muttFile->uniqTable, key, email );
345         }
346         else {
347                 /* Yes - update person with longest name */
348                 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
349                 if( strlen( name ) > strlen( ADDRITEM_NAME(person) ) ) {
350                         addritem_person_set_common_name( person, name );
351                 }
352
353                 /* Free up */
354                 g_free( key );
355         }
356
357         return email;
358 }
359
360 /*
361  * Build address book entries.
362  * Enter: muttFile  MUTT control data.
363  *        cache     Address cache.
364  *        aliasName Alias,
365  *        listAddr  List of address items.
366  *        addrCount Address list count.
367  */
368 static void mutt_build_address(
369                 MuttFile *muttFile, AddressCache *cache,
370                 gchar *aliasName, GSList *listAddr, gint addrCount )
371 {
372         GSList *node = NULL;
373         ItemEMail *email;
374         ItemGroup *group;
375         Mutt_ParsedRec *rec;
376
377         group = NULL;
378         if( listAddr != NULL && addrCount > 1 ) {
379                 group = addritem_create_item_group();
380                 addritem_group_set_name( group, aliasName );
381                 addrcache_id_group( cache, group );
382                 addrcache_add_group( cache, group );
383         }
384
385         email = NULL;
386         node = listAddr;
387         while( node ) {
388                 rec = node->data;
389
390                 /* Insert person/email */
391                 email = mutt_insert_table(
392                                 muttFile, cache, rec->address, rec->name );
393
394                 /* Add email to group */
395                 if( group ) {
396                         addritem_group_add_email( group, email );
397                 }
398
399                 mutt_free_rec( rec );
400                 node = g_slist_next( node );
401         }
402 }
403
404 /*
405  * Parse address line adn build address items.
406  * Enter: muttFile MUTT control data.
407  *        cache    Address cache.
408  *        line     Data record.
409  */
410 static void mutt_build_items( MuttFile *muttFile, AddressCache *cache, gchar *line ) {
411         GList *list, *node;
412         gint tCount, aCount;
413         gchar *aliasTag, *aliasName, *recipient;
414         GSList *addrList;
415
416         /* g_print( "\nBUILD >%s<\n", line ); */
417         list = mgu_parse_string( line,  3, &tCount );
418         if( tCount < 3 ) {
419                 if( list ) {
420                         mgu_free_dlist( list );
421                         list = NULL;
422                 }
423                 return;
424         }
425
426         aliasTag = list->data;
427         node = g_list_next( list );
428         aliasName = node->data;
429         node = g_list_next( node );
430         recipient = node->data;
431
432         addrList = NULL;
433         if( strcmp( aliasTag, MUTT_TAG_ALIAS ) == 0 ) {
434                 aCount = 0;
435                 /* g_print( "aliasName :%s:\n", aliasName ); */
436                 /* g_print( "recipient :%s:\n", recipient ); */
437                 addrList = mutt_parse_rcplist( recipient, &aCount );
438                 /* g_print( "---\n" ); */
439                 mutt_build_address( muttFile, cache, aliasName, addrList, aCount );
440         }
441
442         mgu_free_dlist( list );
443         list = NULL;
444
445 }
446
447 /*
448  * Read file data into address cache.
449  * Enter: muttFile MUTT control data.
450  *        cache Address cache.
451  */
452 static void mutt_read_file( MuttFile *muttFile, AddressCache *cache ) {
453         GSList *listValue = NULL;
454         gboolean flagEOF = FALSE, flagCont = FALSE, lastCont = FALSE;
455         gchar *line =  NULL, *lineValue = NULL;
456         long posEnd = 0L;
457         long posCur = 0L;
458
459         /* Find EOF for progress indicator */
460         fseek( muttFile->file, 0L, SEEK_END );
461         posEnd = ftell( muttFile->file );
462         fseek( muttFile->file, 0L, SEEK_SET );
463
464         while( ! flagEOF ) {
465                 flagCont = FALSE;
466                 line =  mutt_get_line( muttFile, &flagCont );
467
468                 posCur = ftell( muttFile->file );
469                 if( muttFile->cbProgress ) {
470                         /* Call progress indicator */
471                         ( muttFile->cbProgress ) ( muttFile, & posEnd, & posCur );
472                 }
473
474                 if( line == NULL ) flagEOF = TRUE;
475                 if( ! lastCont ) {
476                         /* Save data */
477                         lineValue = mgu_list_coalesce( listValue );
478                         if( lineValue ) {
479                                 mutt_build_items( muttFile, cache, lineValue );
480                         }
481                         g_free( lineValue );
482                         lineValue = NULL;
483                         mgu_free_list( listValue );
484                         listValue = NULL;
485                 }
486                 lastCont = flagCont;
487
488                 /* Add line to list */
489                 listValue = g_slist_append( listValue, g_strdup( line ) );
490
491                 g_free( line );
492                 line = NULL;
493         }
494
495         /* Release data */
496         mgu_free_list( listValue );
497         listValue = NULL;
498 }
499
500 /*
501 * ============================================================================================
502 * Read file into list. Main entry point
503 * Enter:  muttFile MUTT control data.
504 *         cache    Address cache to load.
505 * Return: Status code.
506 * ============================================================================================
507 */
508 gint mutt_import_data( MuttFile *muttFile, AddressCache *cache ) {
509         cm_return_val_if_fail( muttFile != NULL, MGU_BAD_ARGS );
510         cm_return_val_if_fail( cache != NULL, MGU_BAD_ARGS );
511         muttFile->retVal = MGU_SUCCESS;
512         addrcache_clear( cache );
513         cache->dataRead = FALSE;
514         mutt_open_file( muttFile );
515         if( muttFile->retVal == MGU_SUCCESS ) {
516                 /* Read data into the cache */
517                 mutt_read_file( muttFile, cache );
518                 mutt_close_file( muttFile );
519
520                 /* Mark cache */
521                 cache->modified = FALSE;
522                 cache->dataRead = TRUE;
523         }
524         return muttFile->retVal;
525 }
526
527 #define WORK_BUFLEN 1024
528
529 /*
530 * Attempt to find a Mutt file.
531 * Return: Filename, or home directory if not found, or empty string if
532 * no home. Filename should be g_free() when done.
533 */
534 gchar *mutt_find_file( void ) {
535         const gchar *homedir;
536         gchar str[ WORK_BUFLEN ];
537         gint len;
538         FILE *fp;
539
540         homedir = get_home_dir();
541         if( ! homedir ) return g_strdup( "" );
542
543         strcpy( str, homedir );
544         len = strlen( str );
545         if( len > 0 ) {
546                 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
547                         str[ len ] = G_DIR_SEPARATOR;
548                         str[ ++len ] = '\0';
549                 }
550         }
551         strcat( str, MUTT_HOME_FILE );
552
553         /* Attempt to open */
554         if( ( fp = g_fopen( str, "rb" ) ) != NULL ) {
555                 fclose( fp );
556         }
557         else {
558                 /* Truncate filename */
559                 str[ len ] = '\0';
560         }
561         return g_strdup( str );
562 }
563
564 /*
565 * End of Source.
566 */
567