Fix bug #3574: Template addressing
[claws.git] / src / mutt.c
blob4564347cc59f8a10f061441b97bc41742ed11a1c
1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2001-2012 Match Grun and the Claws Mail team
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.
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.
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/>.
21 * Functions necessary to access MUTT address book file.
24 #include <sys/stat.h>
25 #include <ctype.h>
26 #include <string.h>
27 #include <glib.h>
29 #include "utils.h"
30 #include "mgutils.h"
31 #include "mutt.h"
32 #include "addritem.h"
33 #include "addrcache.h"
35 #define MUTT_HOME_FILE ".muttrc"
36 #define MUTTBUFSIZE 2048
37 #define MUTT_TAG_ALIAS "alias"
40 * Create new object.
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;
54 * Properties...
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 );
63 * Free key in table.
65 static gint mutt_free_table_vis( gpointer key, gpointer value, gpointer data ) {
66 g_free( key );
67 return TRUE;
71 * Free up object by releasing internal memory.
73 void mutt_free( MuttFile *muttFile ) {
74 cm_return_if_fail( muttFile != NULL );
76 /* Close file */
77 if( muttFile->file ) fclose( muttFile->file );
79 /* Free internal stuff */
80 g_free( muttFile->path );
82 /* Free unique address table */
83 g_hash_table_foreach_remove( muttFile->uniqTable, mutt_free_table_vis, NULL );
84 g_hash_table_destroy( muttFile->uniqTable );
86 /* Clear pointers */
87 muttFile->file = NULL;
88 muttFile->path = NULL;
89 muttFile->retVal = MGU_SUCCESS;
90 muttFile->uniqTable = NULL;
91 muttFile->cbProgress = NULL;
93 /* Now release file object */
94 g_free( muttFile );
98 * Open file for read.
99 * return: TRUE if file opened successfully.
101 static gint mutt_open_file( MuttFile* muttFile ) {
102 if( muttFile->path ) {
103 muttFile->file = g_fopen( muttFile->path, "rb" );
104 if( ! muttFile->file ) {
105 muttFile->retVal = MGU_OPEN_FILE;
106 return muttFile->retVal;
109 else {
110 /* g_print( "file not specified\n" ); */
111 muttFile->retVal = MGU_NO_FILE;
112 return muttFile->retVal;
115 /* Setup a buffer area */
116 muttFile->retVal = MGU_SUCCESS;
117 return muttFile->retVal;
121 * Close file.
123 static void mutt_close_file( MuttFile *muttFile ) {
124 cm_return_if_fail( muttFile != NULL );
125 if( muttFile->file ) fclose( muttFile->file );
126 muttFile->file = NULL;
130 * Read line of text from file.
131 * Enter: muttFile File object.
132 * flagCont Continuation flag, set if back-slash character at EOL.
133 * Return: ptr to buffer where line starts.
135 static gchar *mutt_get_line( MuttFile *muttFile, gboolean *flagCont ) {
136 gchar buf[ MUTTBUFSIZE ];
137 int ch, lch;
138 int i = 0, li = 0;
140 *flagCont = FALSE;
141 if( feof( muttFile->file ) )
142 return NULL;
144 memset(buf, 0, MUTTBUFSIZE);
146 lch = '\0';
147 while( i < MUTTBUFSIZE-1 ) {
148 ch = fgetc( muttFile->file );
149 if( ch == '\0' || ch == EOF ) {
150 if( i == 0 )
151 return NULL;
152 break;
154 if( ch == '\n' ) {
155 if( lch == '\\' ) {
156 /* Replace backslash with NULL */
157 if( li != 0 )
158 buf[li] = '\0';
159 *flagCont = TRUE;
161 break;
163 buf[i] = ch;
164 li = i;
165 lch = ch;
166 i++;
168 buf[i]='\0';
170 /* Copy into private buffer */
171 return g_strdup( buf );
175 * Parsed address data.
177 typedef struct _Mutt_ParsedRec_ Mutt_ParsedRec;
178 struct _Mutt_ParsedRec_ {
179 gchar *address;
180 gchar *name;
184 * Free data record.
185 * Enter: rec Data record.
187 static void mutt_free_rec( Mutt_ParsedRec *rec ) {
188 if( rec ) {
189 g_free( rec->address );
190 g_free( rec->name );
191 rec->address = NULL;
192 rec->name = NULL;
193 g_free( rec );
198 * Parse recipient list for each address.
199 * Enter: rcpList Recipients extracted from file.
200 * addrCount Updated with recipient count.
201 * Return: Linked list of recipients.
203 static GSList *mutt_parse_rcplist( gchar *rcpList, gint *addrCount ) {
204 gchar *ptr, *pStart, *pEnd, *pAddr, *pName, *address, *name;
205 gchar ch;
206 GSList *list;
207 Mutt_ParsedRec *rec;
208 gint cnt;
210 list = NULL;
211 cnt = 0;
212 pStart = rcpList;
213 while( pStart && *pStart ) {
214 ptr = pStart;
215 address = NULL;
216 pName = pAddr = NULL;
217 /* Chew up spaces */
218 while( *ptr ) {
219 if( ! isspace( *ptr ) ) break;
220 ptr++;
223 /* Find address */
224 while( *ptr ) {
225 ch = *ptr;
226 if( ch == '(' ) {
227 pAddr = pName = ptr;
228 break;
230 if( ch == ',' ) {
231 pAddr = ptr;
232 ptr++;
233 break;
235 if( isspace( ch ) ) {
236 pAddr = ptr;
237 break;
239 ptr++;
242 /* Extract address */
243 if( pAddr ) {
244 address = g_strndup( pStart, pAddr - pStart );
246 else {
247 address = g_strdup( pStart );
249 g_strstrip( address );
251 /* Chew up spaces */
252 while( *ptr ) {
253 ch = *ptr;
254 if( ch == '(' ) {
255 pName = ptr;
256 break;
258 if( ch == ',' ) {
259 ptr++;
260 break;
262 ptr++;
263 if( isspace( ch ) ) continue;
265 pStart = ptr;
267 /* Extract name (if any) */
268 if( pName ) {
269 /* Look for closing parens */
270 pName++;
271 pEnd = NULL;
272 ptr = pName;
273 while( *ptr ) {
274 if( *ptr == ')' ) {
275 pEnd = ptr;
276 break;
278 ptr++;
280 if( pEnd ) {
281 name = g_strndup( pName, pEnd - pName );
282 pEnd++;
283 if( *pEnd ) pEnd++;
285 else {
286 name = g_strdup( pName );
288 g_strstrip( name );
289 pStart = pEnd;
291 else {
292 name = g_strdup( "" );
295 /* New record */
296 rec = g_new0( Mutt_ParsedRec, 1 );
297 rec->address = address;
298 rec->name = name;
299 list = g_slist_append( list, rec );
300 cnt++;
302 /* mutt_print_rec( rec, stdout ); */
304 *addrCount = cnt;
305 return list;
309 * Insert person and address into address cache.
310 * Enter: muttFile MUTT control data.
311 * cache Address cache.
312 * address E-Mail address.
313 * name Name.
314 * Return: E-Mail object, either inserted or found in hash table.
316 static ItemEMail *mutt_insert_table(
317 MuttFile *muttFile, AddressCache *cache, gchar *address,
318 gchar *name )
320 ItemPerson *person;
321 ItemEMail *email;
322 gchar *key;
324 /* Test whether address already in hash table */
325 key = g_utf8_strdown( address, -1 );
326 email = g_hash_table_lookup( muttFile->uniqTable, key );
328 if( email == NULL ) {
329 /* No - create person */
330 person = addritem_create_item_person();
331 addritem_person_set_common_name( person, name );
332 addrcache_id_person( cache, person );
333 addrcache_add_person( cache, person );
335 /* Add email for person */
336 email = addritem_create_item_email();
337 addritem_email_set_address( email, address );
338 addrcache_id_email( cache, email );
339 addrcache_person_add_email( cache, person, email );
341 /* Insert entry */
342 g_hash_table_insert( muttFile->uniqTable, key, email );
344 else {
345 /* Yes - update person with longest name */
346 person = ( ItemPerson * ) ADDRITEM_PARENT(email);
347 if( strlen( name ) > strlen( ADDRITEM_NAME(person) ) ) {
348 addritem_person_set_common_name( person, name );
351 /* Free up */
352 g_free( key );
355 return email;
359 * Build address book entries.
360 * Enter: muttFile MUTT control data.
361 * cache Address cache.
362 * aliasName Alias,
363 * listAddr List of address items.
364 * addrCount Address list count.
366 static void mutt_build_address(
367 MuttFile *muttFile, AddressCache *cache,
368 gchar *aliasName, GSList *listAddr, gint addrCount )
370 GSList *node = NULL;
371 ItemEMail *email;
372 ItemGroup *group;
373 Mutt_ParsedRec *rec;
375 group = NULL;
376 if( listAddr != NULL && addrCount > 1 ) {
377 group = addritem_create_item_group();
378 addritem_group_set_name( group, aliasName );
379 addrcache_id_group( cache, group );
380 addrcache_add_group( cache, group );
383 email = NULL;
384 node = listAddr;
385 while( node ) {
386 rec = node->data;
388 /* Insert person/email */
389 email = mutt_insert_table(
390 muttFile, cache, rec->address, rec->name );
392 /* Add email to group */
393 if( group ) {
394 addritem_group_add_email( group, email );
397 mutt_free_rec( rec );
398 node = g_slist_next( node );
403 * Parse address line adn build address items.
404 * Enter: muttFile MUTT control data.
405 * cache Address cache.
406 * line Data record.
408 static void mutt_build_items( MuttFile *muttFile, AddressCache *cache, gchar *line ) {
409 GList *list, *node;
410 gint tCount, aCount;
411 gchar *aliasTag, *aliasName, *recipient;
412 GSList *addrList;
414 /* g_print( "\nBUILD >%s<\n", line ); */
415 list = mgu_parse_string( line, 3, &tCount );
416 if( tCount < 3 ) {
417 if( list ) {
418 mgu_free_dlist( list );
419 list = NULL;
421 return;
424 aliasTag = list->data;
425 node = g_list_next( list );
426 aliasName = node->data;
427 node = g_list_next( node );
428 recipient = node->data;
430 addrList = NULL;
431 if( strcmp( aliasTag, MUTT_TAG_ALIAS ) == 0 ) {
432 aCount = 0;
433 /* g_print( "aliasName :%s:\n", aliasName ); */
434 /* g_print( "recipient :%s:\n", recipient ); */
435 addrList = mutt_parse_rcplist( recipient, &aCount );
436 /* g_print( "---\n" ); */
437 mutt_build_address( muttFile, cache, aliasName, addrList, aCount );
440 mgu_free_dlist( list );
441 list = NULL;
446 * Read file data into address cache.
447 * Enter: muttFile MUTT control data.
448 * cache Address cache.
450 static void mutt_read_file( MuttFile *muttFile, AddressCache *cache ) {
451 GSList *listValue = NULL;
452 gboolean flagEOF = FALSE, flagCont = FALSE, lastCont = FALSE;
453 gchar *line = NULL, *lineValue = NULL;
454 long posEnd = 0L;
455 long posCur = 0L;
457 /* Find EOF for progress indicator */
458 fseek( muttFile->file, 0L, SEEK_END );
459 posEnd = ftell( muttFile->file );
460 fseek( muttFile->file, 0L, SEEK_SET );
462 while( ! flagEOF ) {
463 flagCont = FALSE;
464 line = mutt_get_line( muttFile, &flagCont );
466 posCur = ftell( muttFile->file );
467 if( muttFile->cbProgress ) {
468 /* Call progress indicator */
469 ( muttFile->cbProgress ) ( muttFile, & posEnd, & posCur );
472 if( line == NULL ) flagEOF = TRUE;
473 if( ! lastCont ) {
474 /* Save data */
475 lineValue = mgu_list_coalesce( listValue );
476 if( lineValue ) {
477 mutt_build_items( muttFile, cache, lineValue );
479 g_free( lineValue );
480 lineValue = NULL;
481 mgu_free_list( listValue );
482 listValue = NULL;
484 lastCont = flagCont;
486 /* Add line to list */
487 listValue = g_slist_append( listValue, g_strdup( line ) );
489 g_free( line );
490 line = NULL;
493 /* Release data */
494 mgu_free_list( listValue );
495 listValue = NULL;
499 * ============================================================================================
500 * Read file into list. Main entry point
501 * Enter: muttFile MUTT control data.
502 * cache Address cache to load.
503 * Return: Status code.
504 * ============================================================================================
506 gint mutt_import_data( MuttFile *muttFile, AddressCache *cache ) {
507 cm_return_val_if_fail( muttFile != NULL, MGU_BAD_ARGS );
508 cm_return_val_if_fail( cache != NULL, MGU_BAD_ARGS );
509 muttFile->retVal = MGU_SUCCESS;
510 addrcache_clear( cache );
511 cache->dataRead = FALSE;
512 mutt_open_file( muttFile );
513 if( muttFile->retVal == MGU_SUCCESS ) {
514 /* Read data into the cache */
515 mutt_read_file( muttFile, cache );
516 mutt_close_file( muttFile );
518 /* Mark cache */
519 cache->modified = FALSE;
520 cache->dataRead = TRUE;
522 return muttFile->retVal;
525 #define WORK_BUFLEN 1024
528 * Attempt to find a Mutt file.
529 * Return: Filename, or home directory if not found, or empty string if
530 * no home. Filename should be g_free() when done.
532 gchar *mutt_find_file( void ) {
533 const gchar *homedir;
534 gchar str[ WORK_BUFLEN + 1 ];
535 gint len;
536 FILE *fp;
538 homedir = get_home_dir();
539 if( ! homedir ) return g_strdup( "" );
541 strncpy( str, homedir, WORK_BUFLEN );
542 len = strlen( str );
543 if( len > 0 ) {
544 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
545 str[ len ] = G_DIR_SEPARATOR;
546 str[ ++len ] = '\0';
549 strncat( str, MUTT_HOME_FILE, WORK_BUFLEN - strlen(str) );
551 /* Attempt to open */
552 if( ( fp = g_fopen( str, "rb" ) ) != NULL ) {
553 fclose( fp );
555 else {
556 /* Truncate filename */
557 str[ len ] = '\0';
559 return g_strdup( str );
563 * End of Source.