c06d71787c6a02d09c819fcc002d01428bced5ee
[claws.git] / src / addrharvest.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2002 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 for an E-Mail address harvester.
22  * Code still needs some work. Address parsing not strictly correct.
23  */
24
25 #include <sys/stat.h>
26 #include <dirent.h>
27 #include <glib.h>
28 #include <string.h>
29
30 #include "utils.h"
31 #include "mgutils.h"
32 #include "addrharvest.h"
33 #include "addritem.h"
34
35 /* Mail header names of interest */
36 static gchar *_headerFrom_     = HEADER_FROM;
37 static gchar *_headerReplyTo_  = HEADER_REPLY_TO;
38 static gchar *_headerSender_   = HEADER_SENDER;
39 static gchar *_headerErrorsTo_ = HEADER_ERRORS_TO;
40 static gchar *_headerCC_       = HEADER_CC;
41 static gchar *_headerTo_       = HEADER_TO;
42
43 #define ADDR_BUFFSIZE    1024
44 #define MSG_BUFFSIZE     2048
45 #define MSGNUM_BUFFSIZE  32
46 #define DFL_FOLDER_SIZE  20
47
48 /* Noise strings included by some other E-Mail clients */
49 #define REM_NAME_STRING  "(E-mail)"
50 #define REM_NAME_STRING2 "(E-mail 2)"
51
52 /*
53  * Header entry.
54  */
55 typedef struct _HeaderEntry HeaderEntry;
56 struct _HeaderEntry {
57         gchar      *header;
58         gboolean   selected;
59         ItemFolder *folder;
60         gint       count;
61 };
62
63 /*
64  * Build header table entry.
65  * Enter: harvester Harvester object.
66  *        name      Header name.
67  */
68 static void *addrharvest_build_entry(
69                 AddressHarvester* harvester, gchar *name )
70 {
71         HeaderEntry *entry;
72
73         entry = g_new0( HeaderEntry, 1 );
74         entry->header = name;
75         entry->selected = FALSE;
76         entry->folder = NULL;
77         entry->count = 0;
78         harvester->headerTable = g_list_append( harvester->headerTable, entry );
79 }
80
81 static void addrharvest_print_hdrentry( HeaderEntry *entry, FILE *stream ) {
82         fprintf( stream, "Header Entry\n" );
83         fprintf( stream, "    name : %s\n", entry->header );
84         fprintf( stream, "selected : %s\n", entry->selected ? "yes" : "no" );
85 }
86
87 /*
88  * Free key in table.
89  */
90 static gint addrharvest_free_table_vis( gpointer key, gpointer value, gpointer data ) {
91         g_free( key );
92         key = NULL;
93         value = NULL;
94         return TRUE;
95 }
96
97 /*
98  * Free lookup table.
99  */
100 static void addrharvest_free_table( AddressHarvester* harvester ) {
101         GList *node;
102         HeaderEntry *entry;
103
104         /* Free header list */
105         node = harvester->headerTable;
106         while( node ) {
107                 entry = ( HeaderEntry * ) node->data;
108                 entry->header = NULL;
109                 entry->selected = FALSE;
110                 entry->folder = NULL;
111                 entry->count = 0;
112                 g_free( entry );
113                 node = g_list_next( node );
114         }
115         g_list_free( harvester->headerTable );
116         harvester->headerTable = NULL;
117
118         /* Free duplicate table */
119         g_hash_table_freeze( harvester->dupTable );
120         g_hash_table_foreach_remove( harvester->dupTable, addrharvest_free_table_vis, NULL );
121         g_hash_table_thaw( harvester->dupTable );
122         g_hash_table_destroy( harvester->dupTable );
123         harvester->dupTable = NULL;
124 }
125
126 /*
127 * Create new object.
128 * Return: Harvester.
129 */
130 AddressHarvester *addrharvest_create( void ) {
131         AddressHarvester *harvester;
132
133         harvester = g_new0( AddressHarvester, 1 );
134         harvester->path = NULL;
135         harvester->dupTable = g_hash_table_new( g_str_hash, g_str_equal );
136         harvester->folderSize = DFL_FOLDER_SIZE;
137         harvester->retVal = MGU_SUCCESS;
138
139         /* Build header table */
140         harvester->headerTable = NULL;
141         addrharvest_build_entry( harvester, _headerFrom_ );
142         addrharvest_build_entry( harvester, _headerReplyTo_ );
143         addrharvest_build_entry( harvester, _headerSender_ );
144         addrharvest_build_entry( harvester, _headerErrorsTo_ );
145         addrharvest_build_entry( harvester, _headerCC_ );
146         addrharvest_build_entry( harvester, _headerTo_ );
147
148         return harvester;
149 }
150
151 /*
152 * Properties...
153 */
154 /*
155  * Specify path to folder that will be harvested.
156  * Entry: harvester Harvester object.
157  *        value     Full directory path.
158  */
159 void addrharvest_set_path( AddressHarvester* harvester, const gchar *value ) {
160         g_return_if_fail( harvester != NULL );
161         harvester->path = mgu_replace_string( harvester->path, value );
162         g_strstrip( harvester->path );
163 }
164
165 /*
166  * Specify maximum folder size.
167  * Entry: harvester Harvester object.
168  *        value     Folder size.
169  */
170 void addrharvest_set_folder_size(
171         AddressHarvester* harvester, const gint value )
172 {
173         g_return_if_fail( harvester != NULL );
174         if( value > 0 ) {
175                 harvester->folderSize = value;
176         }
177 }
178
179 /*
180  * Search (case insensitive) for header entry with specified name.
181  * Enter: harvester Harvester.
182  *        name      Header name.
183  * Return: Header, or NULL if not found.
184  */
185 static HeaderEntry *addrharvest_find( 
186         AddressHarvester* harvester, const gchar *name ) {
187         HeaderEntry *retVal;
188         GList *node;
189
190         retVal = NULL;
191         node = harvester->headerTable;
192         while( node ) {
193                 HeaderEntry *entry;
194
195                 entry = node->data;
196                 if( g_strcasecmp( entry->header, name ) == 0 ) {
197                         retVal = entry;
198                         break;
199                 }
200                 node = g_list_next( node );
201         }
202         return retVal;
203 }
204
205 /*
206  * Set selection for specified heaader.
207  * Enter: harvester Harvester.
208  *        name      Header name.
209  *        value     Value to set.
210  */
211 void addrharvest_set_header(
212         AddressHarvester* harvester, const gchar *name, const gboolean value )
213 {
214         HeaderEntry *entry;
215
216         g_return_if_fail( harvester != NULL );
217         entry = addrharvest_find( harvester, name );
218         if( entry != NULL ) {
219                 entry->selected = value;
220         }
221 }
222
223 /*
224  * Get address count
225  * Enter: harvester Harvester.
226  *        name      Header name.
227  * Return: Address count, or -1 if header not found.
228  */
229 gint addrharvest_get_count( AddressHarvester* harvester, const gchar *name ) {
230         HeaderEntry *entry;
231         gint count;
232
233         count = -1;
234         g_return_val_if_fail( harvester != NULL, count );
235         entry = addrharvest_find( harvester, name );
236         if( entry != NULL ) {
237                 count = entry->count;
238         }
239         return count;
240 }
241
242 /*
243 * Free up object by releasing internal memory.
244 * Enter: harvester Harvester.
245 */
246 void addrharvest_free( AddressHarvester *harvester ) {
247         g_return_if_fail( harvester != NULL );
248
249         /* Free internal stuff */
250         addrharvest_free_table( harvester );
251         g_free( harvester->path );
252
253         /* Clear pointers */
254         harvester->path = NULL;
255         harvester->retVal = MGU_SUCCESS;
256         harvester->headerTable = NULL;
257
258         harvester->folderSize = 0;
259
260         /* Now release object */
261         g_free( harvester );
262 }
263
264 /*
265 * Display object to specified stream.
266 * Enter: harvester Harvester.
267 *        stream    Output stream.
268 */
269 void addrharvest_print( AddressHarvester *harvester, FILE *stream ) {
270         GList *node;
271         HeaderEntry *entry;
272
273         g_return_if_fail( harvester != NULL );
274         fprintf( stream, "Address Harvester:\n" );
275         fprintf( stream, " file path: '%s'\n", harvester->path );
276         fprintf( stream, "max folder: %d'\n", harvester->folderSize );
277
278         node = harvester->headerTable;
279         while( node ) {
280                 entry = node->data;
281                 fprintf( stream, "   header: %s", entry->header );
282                 fprintf( stream, "\t: %s", entry->selected ? "yes" : "no" );
283                 fprintf( stream, "\t: %d\n", entry->count );
284                 node = g_list_next( node );
285         }
286         fprintf( stream, "  ret val: %d\n", harvester->retVal );
287 }
288
289 /*
290  * Insert address into cache.
291  * Enter: harvester Harvester object.
292  *        entry     Header object.
293  *        cache     Address cache to load.
294  *        name      Name.
295  *        address   eMail address.
296  */
297 static void addrharvest_insert_cache(
298                 AddressHarvester *harvester, HeaderEntry *entry,
299                 AddressCache *cache, const gchar *name,
300                 const gchar *address )
301 {
302         ItemPerson *person;
303         ItemFolder *folder;
304         gchar *folderName;
305         gboolean newFolder;
306         gint cnt;
307         gchar *key, *value;
308
309         newFolder = FALSE;
310         folder = entry->folder;
311         if( folder == NULL ) {
312                 newFolder = TRUE;       /* No folder yet */
313         }
314         if( entry->count % harvester->folderSize == 0 ) {
315                 newFolder = TRUE;       /* Folder is full */
316         }
317
318         /* Insert address */
319         key = g_strdup( address );
320         g_strdown( key );
321         person = g_hash_table_lookup( harvester->dupTable, key );
322         if( person ) {
323                 /* Update existing person to use longest name */
324                 value = ADDRITEM_NAME(person);
325                 if( strlen( name ) > strlen( value ) ) {
326                         addritem_person_set_common_name( person, name );
327                 }
328                 g_free( key );
329         }
330         else {
331                 /* Folder if required */
332                 if( newFolder ) {
333                         cnt = 1 + ( entry->count / harvester->folderSize );
334                         folderName =g_strdup_printf( "%s (%d)",
335                                         entry->header, cnt );
336                         folder = addritem_create_item_folder();
337                         addritem_folder_set_name( folder, folderName );
338                         addritem_folder_set_remarks( folder, "" );
339                         addrcache_id_folder( cache, folder );
340                         addrcache_add_folder( cache, folder );
341                         entry->folder = folder;
342                         g_free( folderName );
343                 }
344
345                 /* Insert entry */
346                 person = addrcache_add_contact(
347                                 cache, folder, name, address, "" );
348                 g_hash_table_insert( harvester->dupTable, key, person );
349                 entry->count++;
350         }
351         addritem_parse_first_last( person );
352 }
353
354 /*
355  * Remove specified string from name.
356  * Enter: name Name.
357  *        str  String to remove.
358  */
359 static void addrharvest_del_email( gchar *name, gchar *str ) {
360         gchar *p;
361         gint lenn, lenr;
362
363         lenr = strlen( str );
364         while( p = strcasestr( name, str )  ) {
365                 lenn = strlen( p );
366                 memmove( p, p + lenr, lenn );
367         }
368 }
369
370 /*
371  * Find position of at (@) character in buffer.
372  * Enter:  buffer Start of buffer.
373  * Return: Position of at character, or NULL if not found.
374  * Note: This function searches for the last occurrence of an 'at' character
375  * prior to a valid delimiter character for the end of address. This enables
376  * an address to be found where it is also used as the name of the
377  * recipient. For example:
378  *     "axle.rose@netscape.com" <axle.rose@netscape.com>
379  * The last occurrence of the at character is detected.
380  */
381 static gchar *addrharvest_find_at( const gchar *buffer ) {
382         gchar *atCh;
383         gchar *p;
384
385         atCh = strchr( buffer, '@' );
386         if( atCh ) {
387                 /* Search forward for another one */
388                 p = atCh + 1;
389                 while( *p ) {
390                         if( *p == '>' ) {
391                                 break;
392                         }
393                         if( *p == ',' ) {
394                                 break;
395                         }
396                         if( *p == '\n' ) {
397                                 break;
398                         }
399                         if( *p == '@' ) {
400                                 atCh = p;
401                                 break;
402                         }
403                         p++;
404                 }
405         }
406         return atCh;
407 }
408
409 /*
410  * Find start and end of address string.
411  * Enter: buf Start address of buffer to process (not modified).
412  *        atp Pointer to email at (@) character.
413  *        bp  Pointer to start of email address (returned).
414  *        ep  Pointer to end of email address (returned).
415  */
416 static void addrharvest_find_address(
417                 const gchar *buf, const gchar *atp, const gchar **bp,
418                 const gchar **ep )
419 {
420         const gchar *p;
421
422         /* Find first non-separator char */
423         *bp = NULL;
424         p = buf;
425         while( TRUE ) {
426                 if( strchr( ",; \n\r", *p ) == NULL ) break;
427                 p++;
428         }
429         *bp = p;
430
431         /* Search forward for end of address */
432         *ep = NULL;
433         p = atp + 1;
434         while( TRUE ) {
435                 if( strchr( ",;", *p ) ) break;
436                 p++;
437         }
438         *ep = p;
439 }
440
441 /*
442  * Extract E-Mail address from buffer. If found, address is removed from
443  * buffer.
444  * Enter:  buffer Address buffer.
445  * Return: E-Mail address, or NULL if none found. Must g_free() when done.
446  */
447 static gchar *addrharvest_extract_address( gchar *buffer ) {
448         gchar *addr;
449         gchar *atCh, *p, *bp, *ep;
450         gint len;
451
452         addr = NULL;
453         atCh = addrharvest_find_at( buffer );
454         if( atCh ) {
455                 /* Search back for start of address */
456                 bp = NULL;
457                 p = atCh;
458                 while( p >= buffer ) {
459                         bp = p;
460                         if( *p == '<' ) {
461                                 *p = ' ';
462                                 bp++;
463                                 break;
464                         }
465                         p--;
466                 }
467
468                 /* Search fwd for end */
469                 ep = NULL;
470                 ep = p = atCh;
471                 while( *p ) {
472                         if( *p == '>' ) {
473                                 *p = ' ';
474                                 break;
475                         }
476                         else if( *p == ' ' ) {
477                                 break;
478                         }
479                         ep = p;
480                         p++;
481                 }
482
483                 /* Extract email */
484                 if( bp != NULL ) {
485                         len = ( ep - bp );
486                         if( len > 0 ) {
487                                 addr = g_strndup( bp, len + 1 );
488                                 memmove( bp, ep, len );
489                                 *bp = ' ';
490                         }
491                 }       
492         }
493         return addr;
494 }
495
496 /*
497  * Parse address from header buffer creating address in cache.
498  * Enter: harvester Harvester object.
499  *        entry     Header object.
500  *        cache     Address cache to load.
501  *        hdrBuf    Pointer to header buffer.
502  */
503 static void addrharvest_parse_address(
504                 AddressHarvester *harvester, HeaderEntry *entry,
505                 AddressCache *cache, const gchar *hdrBuf )
506 {
507         gchar buffer[ ADDR_BUFFSIZE + 2 ];
508         const gchar *bp;
509         const gchar *ep;
510         gchar *atCh, *email, *name;
511         gint bufLen;
512
513         /* Search for an address */
514         while( atCh = addrharvest_find_at( hdrBuf ) ) {
515                 /* Find addres string */
516                 addrharvest_find_address( hdrBuf, atCh, &bp, &ep );
517
518                 /* Copy into buffer */
519                 bufLen = ( size_t ) ( ep - bp );
520                 if( bufLen > ADDR_BUFFSIZE ) {
521                         bufLen = ADDR_BUFFSIZE;
522                 }
523                 strncpy( buffer, bp, bufLen );
524                 buffer[ bufLen ] = '\0';
525                 buffer[ bufLen + 1 ] = '\0';
526                 buffer[ bufLen + 2 ] = '\0';
527
528                 /* Extract address from buffer */
529                 email = addrharvest_extract_address( buffer );
530                 if( email ) {
531                         /* Unescape characters */
532                         mgu_str_unescape( buffer );
533
534                         /* Remove noise characaters */
535                         addrharvest_del_email( buffer, REM_NAME_STRING );
536                         addrharvest_del_email( buffer, REM_NAME_STRING2 );
537
538                         /* Remove leading trailing quotes and spaces */
539                         mgu_str_ltc2space( buffer, '\"', '\"' );
540                         mgu_str_ltc2space( buffer, '\'', '\'' );
541                         mgu_str_ltc2space( buffer, '\"', '\"' );
542                         mgu_str_ltc2space( buffer, '(', ')' );
543                         g_strstrip( buffer );
544
545                         if( g_strcasecmp( buffer, email ) == 0 ) {
546                                 name = "";
547                         }
548                         else {
549                                 name = buffer;
550                         }
551
552                         /* Insert into address book */
553                         addrharvest_insert_cache(
554                                 harvester, entry, cache, name, email );
555                         g_free( email );
556                 }
557                 hdrBuf = ep;
558         }
559 }
560
561 /*
562  * Test whether buffer contains a header that appears in header list.
563  * Enter: listHdr Header list.
564  *        buf     Header buffer.
565  * Return: TRUE if header in list.
566  */
567 static gboolean addrharvest_check_hdr( GList *listHdr, gchar *buf ) {
568         gboolean retVal;
569         GList *node;
570         gchar *p, *hdr, *nhdr;
571         gint len;
572
573         retVal = FALSE;
574         p = strchr( buf, ':' );
575         if( p ) {
576                 len = ( size_t ) ( p - buf );
577                 hdr = g_strndup( buf, len );
578                 node = listHdr;
579                 while( node ) {
580                         nhdr = node->data;
581                         if( g_strcasecmp( nhdr, hdr ) == 0 ) {
582                                 retVal = TRUE;
583                                 break;
584                         }
585                         node = g_list_next( node );
586                 }
587                 g_free( hdr );
588         }
589         return retVal;
590 }
591
592 /*
593  * Read header into a linked list of lines.
594  * Enter:  fp      File to read.
595  *         listHdr List of header lines of interest.
596  *         done    End of headers or end of file reached.
597  * Return: Linked list of lines.
598  */
599 static GSList *addrharvest_get_header( FILE *fp, GList *listHdr, gboolean *done ) {
600         GSList *list;
601         gchar buf[ MSG_BUFFSIZE + 2 ];
602         gint ch;
603         gboolean foundHdr;
604
605         list = NULL;
606
607         /* Read line */
608         if( fgets( buf, MSG_BUFFSIZE, fp ) == NULL ) {
609                 *done = TRUE;
610                 return list;
611         }
612
613         /* Test for end of headers */
614         if( buf[0] == '\r' || buf[0] == '\n' ) {
615                 *done = TRUE;
616                 return list;
617         }
618
619         /* Test whether required header */
620         foundHdr = addrharvest_check_hdr( listHdr, buf );
621
622         /* Read all header lines. Only add reqd ones to list */
623         while( TRUE ) {
624                 gchar *p;
625
626                 if( foundHdr ) {
627                         p = g_strdup( buf );
628                         list = g_slist_append( list, p );
629                 }
630
631                 /* Read first character */
632                 ch = fgetc( fp );
633                 if( ch == ' ' || ch == '\t' ) {
634                         /* Continuation character - read into buffer */
635                         if( fgets( buf, MSG_BUFFSIZE, fp ) == NULL ) {
636                                 break;
637                         }
638                 }
639                 else {
640                         if( ch == EOF ) {
641                                 *done = TRUE;
642                         }
643                         else {
644                                 /* Push back character for next header */
645                                 ungetc( ch, fp );
646                         }
647                         break;
648                 }
649         }
650
651         return list;
652 }
653
654 /*
655  * Read specified file into address book.
656  * Enter:  harvester Harvester object.
657  *         fileName  File to read.
658  *         cache     Address cache to load.
659  * Return: Status.
660  */
661 static gint addrharvest_readfile(
662                 AddressHarvester *harvester, const gchar *fileName,
663                 AddressCache *cache, GList *listHdr )
664 {
665         gint retVal;
666         FILE *msgFile;
667         gchar *buf, *addr, *p;
668         HeaderEntry *entry;
669         GSList *list;
670         gboolean done;
671
672         msgFile = fopen( fileName, "rb" );
673         if( ! msgFile ) {
674                 /* Cannot open file */
675                 retVal = MGU_OPEN_FILE;
676                 return retVal;
677         }
678
679         done = FALSE;
680         while( TRUE ) {
681                 list = addrharvest_get_header( msgFile, listHdr, &done );
682                 if( done ) break;
683
684                 if( list == NULL ) {
685                         continue;
686                 }
687
688                 buf = mgu_list_coalesce( list );
689                 mgu_free_list( list );
690
691                 if(( p = strchr( buf, ':' ) ) != NULL ) {
692                         addr = p + 1;
693                         *p = '\0';
694
695                         entry = addrharvest_find( harvester, buf );
696                         if( entry && entry->selected ) {
697                                 /* Sanitize control characters */
698                                 p = addr;
699                                 while( *p ) {
700                                         if( *p == '\r' || *p == '\n' || *p == '\t' )
701                                                 *p = ' ';
702                                         p++;
703                                 }
704                                 addrharvest_parse_address(
705                                         harvester, entry, cache, addr );
706                         }
707                 }
708                 g_free( buf );
709         }
710
711         fclose( msgFile );
712         return MGU_SUCCESS;
713 }
714
715 /*
716  * ============================================================================
717  * Read all files in specified directory into address book.
718  * Enter:  harvester Harvester object.
719  *         cache     Address cache to load.
720  *         msgList   List of message numbers, or NULL to process folder.
721  * Return: Status.
722  * ============================================================================
723  */
724 gint addrharvest_harvest(
725         AddressHarvester *harvester, AddressCache *cache, GList *msgList )
726 {
727         gint retVal;
728         DIR *dp;
729         struct dirent *d;
730         struct stat s;
731         gint num;
732         GList *node;
733         GList *listHdr;
734         gchar msgNum[ MSGNUM_BUFFSIZE ];
735
736         retVal = MGU_BAD_ARGS;
737         g_return_val_if_fail( harvester != NULL, retVal );
738         g_return_val_if_fail( cache != NULL, retVal );
739         g_return_val_if_fail( harvester->path != NULL, retVal );
740
741         /* Clear cache */
742         addrcache_clear( cache );
743         cache->dataRead = FALSE;
744
745         if( chdir( harvester->path ) < 0 ) {
746                 /* printf( "Error changing dir\n" ); */
747                 return retVal;
748         }
749
750         if( ( dp = opendir( harvester->path ) ) == NULL ) {
751                 /* printf( "Error opening dir\n" ); */
752                 return retVal;
753         }
754
755         /* Build list of headers of interest */
756         listHdr = NULL;
757         node = harvester->headerTable;
758         while( node ) {
759                 HeaderEntry *entry;
760
761                 entry = node->data;
762                 if( entry->selected ) {
763                         gchar *p;
764
765                         p = g_strdup( entry->header );
766                         g_strdown( p );
767                         listHdr = g_list_append( listHdr, p );
768                 }
769                 node = g_list_next( node );
770         }
771
772         if( msgList == NULL ) {
773                 /* Process directory */
774                 while( ( d = readdir( dp ) ) != NULL ) {
775                         stat( d->d_name, &s );
776                         if( S_ISREG( s.st_mode ) ) {
777                                 if( ( num = to_number( d->d_name ) ) >= 0 ) {
778                                         addrharvest_readfile(
779                                                 harvester, d->d_name, cache, listHdr );
780                                 }
781                         }
782                 }
783         }
784         else {
785                 /* Process message list */
786                 node = msgList;
787                 while( node ) {
788                         num = GPOINTER_TO_UINT( node->data );
789                         sprintf( msgNum, "%d", num );
790                         addrharvest_readfile(
791                                 harvester, msgNum, cache, listHdr );
792                         node = g_list_next( node );
793                 }
794         }
795         mgu_free_dlist( listHdr );
796
797         closedir( dp );
798
799         /* Mark cache */
800         cache->modified = FALSE;
801         cache->dataRead = TRUE;
802
803         return retVal;
804 }
805
806 /*
807  * ============================================================================
808  * Test whether any headers have been selected for processing.
809  * Enter:  harvester Harvester object.
810  * Return: TRUE if a header was selected, FALSE if none were selected.
811  * ============================================================================
812  */
813 gboolean addrharvest_check_header( AddressHarvester *harvester ) {
814         gboolean retVal;
815         GList *node;
816
817         retVal = FALSE;
818         g_return_val_if_fail( harvester != NULL, retVal );
819
820         node = harvester->headerTable;
821         while( node ) {
822                 HeaderEntry *entry;
823
824                 entry = ( HeaderEntry * ) node->data;
825                 if( entry->selected ) return TRUE;
826                 node = g_list_next( node );
827         }
828         return retVal;
829 }
830
831 /*
832  * ============================================================================
833  * End of Source.
834  * ============================================================================
835  */
836
837