claws-mail/src/pine.c
2003-10-05 10:10:30 +00:00

695 lines
15 KiB
C

/*
* Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
* Copyright (C) 2002 Match Grun
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
* Functions necessary to access Pine address book file.
*/
#include <sys/stat.h>
#include <glib.h>
#include <string.h>
#include "utils.h"
#include "mgutils.h"
#include "pine.h"
#include "addritem.h"
#include "addrcache.h"
#define PINE_HOME_FILE ".addressbook"
#define PINEBUFSIZE 2048
#define CHAR_QUOTE '\"'
#define CHAR_APOS '\''
#define CHAR_COMMA ','
#define CHAR_AT '@'
/*
* Create new object.
*/
PineFile *pine_create() {
PineFile *pineFile;
pineFile = g_new0( PineFile, 1 );
pineFile->path = NULL;
pineFile->file = NULL;
pineFile->retVal = MGU_SUCCESS;
pineFile->uniqTable = g_hash_table_new( g_str_hash, g_str_equal );
pineFile->cbProgress = NULL;
return pineFile;
}
/*
* Properties...
*/
void pine_set_file( PineFile* pineFile, const gchar *value ) {
g_return_if_fail( pineFile != NULL );
pineFile->path = mgu_replace_string( pineFile->path, value );
g_strstrip( pineFile->path );
}
/*
* Register a callback function. When called, the function will be passed
* the following arguments:
* PineFile object,
* File size (long),
* Current position (long)
* This can be used for a progress indicator.
*/
void pine_set_callback( PineFile *pineFile, void *func ) {
pineFile->cbProgress = func;
}
/*
* Free key in table.
*/
static gint pine_free_table_vis( gpointer key, gpointer value, gpointer data ) {
g_free( key );
key = NULL;
value = NULL;
return TRUE;
}
/*
* Free up object by releasing internal memory.
*/
void pine_free( PineFile *pineFile ) {
g_return_if_fail( pineFile != NULL );
/* Close file */
if( pineFile->file ) fclose( pineFile->file );
/* Free internal stuff */
g_free( pineFile->path );
/* Free unique address table */
g_hash_table_freeze( pineFile->uniqTable );
g_hash_table_foreach_remove( pineFile->uniqTable, pine_free_table_vis, NULL );
g_hash_table_thaw( pineFile->uniqTable );
g_hash_table_destroy( pineFile->uniqTable );
/* Clear pointers */
pineFile->file = NULL;
pineFile->path = NULL;
pineFile->retVal = MGU_SUCCESS;
pineFile->uniqTable = NULL;
pineFile->cbProgress = NULL;
/* Now release file object */
g_free( pineFile );
}
/*
* Display object to specified stream.
* Enter: pineFile File object.
* stream File..
*/
void pine_print_file( PineFile *pineFile, FILE *stream ) {
g_return_if_fail( pineFile != NULL );
fprintf( stream, "Pine File:\n" );
fprintf( stream, "file spec: '%s'\n", pineFile->path );
fprintf( stream, " ret val: %d\n", pineFile->retVal );
}
/*
* Open file for read.
* Enter: pineFile File object.
* return: TRUE if file opened successfully.
*/
static gint pine_open_file( PineFile* pineFile ) {
if( pineFile->path ) {
pineFile->file = fopen( pineFile->path, "rb" );
if( ! pineFile->file ) {
pineFile->retVal = MGU_OPEN_FILE;
return pineFile->retVal;
}
}
else {
/* printf( "file not specified\n" ); */
pineFile->retVal = MGU_NO_FILE;
return pineFile->retVal;
}
/* Setup a buffer area */
pineFile->retVal = MGU_SUCCESS;
return pineFile->retVal;
}
/*
* Close file.
* Enter: pineFile File object.
*/
static void pine_close_file( PineFile *pineFile ) {
g_return_if_fail( pineFile != NULL );
if( pineFile->file ) fclose( pineFile->file );
pineFile->file = NULL;
}
/*
* Read line of text from file.
* Enter: pineFile File object.
* Return: Copy of buffer. Should be g_free'd when done.
*/
static gchar *pine_read_line( PineFile *pineFile ) {
gchar buf[ PINEBUFSIZE ];
int c;
gchar ch;
gchar *ptr;
if( feof( pineFile->file ) ) return NULL;
ptr = buf;
while( TRUE ) {
*ptr = '\0';
c = fgetc( pineFile->file );
if( c == EOF ) {
if( *buf == '\0' ) return NULL;
break;
}
ch = (gchar) c;
if( ch == '\0' ) {
if( *buf == '\0' ) return NULL;
break;
}
if( ch == '\n' ) {
break;
}
*ptr = ch;
ptr++;
}
/* Copy into private buffer */
return g_strdup( buf );
}
/*
* Parsed address data.
*/
typedef struct _Pine_ParsedRec_ Pine_ParsedRec;
struct _Pine_ParsedRec_ {
gchar *nickName;
gchar *name;
gchar *address;
gchar *fcc;
gchar *comments;
gboolean isGroup;
GSList *listName;
GSList *listAddr;
};
/*
* Free data record.
* Enter: rec Data record.
*/
static void pine_free_rec( Pine_ParsedRec *rec ) {
if( rec ) {
g_free( rec->nickName );
g_free( rec->name );
g_free( rec->address );
g_free( rec->fcc );
g_free( rec->comments );
mgu_clear_slist( rec->listName );
mgu_clear_slist( rec->listAddr );
g_slist_free( rec->listName );
g_slist_free( rec->listAddr );
rec->nickName = NULL;
rec->name = NULL;
rec->address = NULL;
rec->fcc = NULL;
rec->comments = NULL;
rec->isGroup = FALSE;
g_free( rec );
}
}
/*
* Clean name.
*/
static void pine_clean_name( Pine_ParsedRec *rec ) {
gchar *p;
p = rec->name;
if( p == NULL ) return;
if( *p == '\0' ) return;
g_strstrip( rec->name );
if( *p == CHAR_APOS || *p == CHAR_QUOTE ) {
return;
}
/* If embedded comma present, surround match with quotes */
while( *p ) {
if( *p == CHAR_COMMA ) {
p = g_strdup_printf( "\"%s\"", rec->name );
g_free( rec->name );
rec->name = p;
return;
}
p++;
}
}
/*
* Parse pine address record.
* Enter: buf Address record buffer.
* Return: Data record.
*/
static Pine_ParsedRec *pine_parse_record( gchar *buf ) {
Pine_ParsedRec *rec;
gchar *p, *f;
gint pos, len, i;
gchar *tmp[5];
for( i = 0; i < 5; i++ )
tmp[i] = NULL;
/* Extract tab separated values */
rec = NULL;
pos = 0;
p = f = buf;
while( *p ) {
if( *p == '\t' ) {
len = p - f;
if( len > 0 ) {
tmp[ pos ] = g_strndup( f, len );
f = p;
f++;
}
pos++;
}
p++;
}
/* Extract last value */
len = p - f;
if( len > 0 ) {
tmp[ pos++ ] = g_strndup( f, len );
}
/* Populate record */
if( pos > 0 ) {
rec = g_new0( Pine_ParsedRec, 1 );
rec->isGroup = FALSE;
for( i = 0; i < pos; i++ ) {
f = tmp[i];
if( f ) {
g_strstrip( f );
}
if( i == 0 ) rec->nickName = f;
else if( i == 1 ) rec->name = f;
else if( i == 2 ) rec->address = f;
else if( i == 3 ) rec->fcc = f;
else if( i == 4 ) rec->comments = f;
tmp[i] = NULL;
}
if( rec->address != NULL ) {
/* Strip leading/trailing parens */
p = rec->address;
if( *p == '(' ) {
len = strlen( p ) - 1;
*p = ' ';
*(p + len) = ' ';
rec->isGroup = TRUE;
}
}
}
return rec;
}
/*
* Parse name from email address string.
* Enter: buf Start address of buffer to process (not modified).
* atp Pointer to email at (@) character.
* ap Pointer to start of email address returned.
* ep Pointer to end of email address returned.
* Return: Parsed name or NULL if not present. This should be g_free'd
* when done.
*/
static gchar *pine_parse_name(
const gchar *buf, const gchar *atp, const gchar **ap,
const gchar **ep )
{
gchar *name;
const gchar *pos;
const gchar *tmp;
const gchar *bp;
gint ilen;
name = NULL;
*ap = NULL;
*ep = NULL;
/* Find first non-separator char */
bp = buf;
while( TRUE ) {
if( strchr( ",; \n\r", *bp ) == NULL ) break;
bp++;
}
/* Search back for start of name */
tmp = atp;
pos = atp;
while( pos >= bp ) {
tmp = pos;
if( *pos == '<' ) {
/* Found start of address/end of name part */
ilen = -1 + ( size_t ) ( pos - bp );
name = g_strndup( bp, ilen + 1 );
*(name + ilen + 1) = '\0';
/* Remove leading trailing quotes and spaces */
mgu_str_ltc2space( name, '\"', '\"' );
mgu_str_ltc2space( name, '\'', '\'' );
mgu_str_ltc2space( name, '\"', '\"' );
mgu_str_unescape( name );
g_strstrip( name );
break;
}
pos--;
}
*ap = tmp;
/* Search forward for end of address */
pos = atp + 1;
while( TRUE ) {
if( *pos == '>' ) {
pos++;
break;
}
if( strchr( ",; \'\n\r", *pos ) ) break;
pos++;
}
*ep = pos;
return name;
}
/*
* Parse address list.
* Enter: pineFile Pine control data.
* cache Address cache.
* rec Data record.
*/
static void pine_parse_address( PineFile *pineFile, AddressCache *cache, Pine_ParsedRec *rec ) {
const gchar *buf;
gchar addr[ PINEBUFSIZE ];
const gchar *bp;
const gchar *ep;
gchar *atCh;
gchar *name;
gint len;
g_return_if_fail( rec->address != NULL );
buf = rec->address;
while((atCh = strchr( buf, CHAR_AT )) != NULL) {
name = pine_parse_name( buf, atCh, &bp, &ep );
len = ( size_t ) ( ep - bp );
strncpy( addr, bp, len );
addr[ len ] = '\0';
extract_address( addr );
if( name == NULL ) name = g_strdup( "" );
rec->listName = g_slist_append( rec->listName, name );
rec->listAddr = g_slist_append( rec->listAddr, g_strdup( addr ) );
buf = ep;
if( atCh == ep ) {
buf++;
}
}
}
/*
* Insert person and address into address cache.
* Enter: pineFile Pine control data.
* cache Address cache.
* address E-Mail address.
* name Name.
* remarks Remarks.
* Return: E-Mail object, either inserted or found in hash table.
*/
static ItemEMail *pine_insert_table(
PineFile *pineFile, AddressCache *cache, gchar *address,
gchar *name, gchar *remarks )
{
ItemPerson *person;
ItemEMail *email;
gchar *key;
g_return_val_if_fail( address != NULL, NULL );
/* create an entry with empty name if needed */
if ( name == NULL )
name = "";
/* Test whether address already in hash table */
key = g_strdup( address );
g_strdown( key );
email = g_hash_table_lookup( pineFile->uniqTable, key );
if( email == NULL ) {
/* No - create person */
person = addritem_create_item_person();
addritem_person_set_common_name( person, name );
addrcache_id_person( cache, person );
addrcache_add_person( cache, person );
/* Add email for person */
email = addritem_create_item_email();
addritem_email_set_address( email, address );
addritem_email_set_remarks( email, remarks );
addrcache_id_email( cache, email );
addrcache_person_add_email( cache, person, email );
/* Insert entry */
g_hash_table_insert( pineFile->uniqTable, key, email );
}
else {
/* Yes - update person with longest name */
person = ( ItemPerson * ) ADDRITEM_PARENT(email);
if( strlen( name ) > strlen( ADDRITEM_NAME(person) ) ) {
addritem_person_set_common_name( person, name );
}
/* Free up */
g_free( key );
}
return email;
}
/*
* Parse address line adn build address items.
* Enter: pineFile Pine control data.
* cache Address cache to load.
* line Address record.
*/
static void pine_build_items( PineFile *pineFile, AddressCache *cache, gchar *line ) {
Pine_ParsedRec *rec;
GSList *nodeAddr, *nodeName;
ItemGroup *group;
ItemEMail *email;
rec = pine_parse_record( line );
if( rec ) {
pine_clean_name( rec );
pine_parse_address( pineFile, cache, rec );
/* pine_print_rec( rec, stdout ); */
/* printf( "=========\n" ); */
if( rec->isGroup ) {
/* Create group */
group = addritem_create_item_group();
addritem_group_set_name( group, rec->nickName );
addrcache_id_group( cache, group );
addrcache_add_group( cache, group );
/* Add email to group */
nodeName = rec->listName;
nodeAddr = rec->listAddr;
while( nodeAddr ) {
email = pine_insert_table(
pineFile, cache, nodeAddr->data,
nodeName->data, "" );
/* Add email to group */
addritem_group_add_email( group, email );
nodeAddr = g_slist_next( nodeAddr );
nodeName = g_slist_next( nodeName );
}
}
else {
email = pine_insert_table(
pineFile, cache, rec->address,
rec->name, rec->comments );
}
pine_free_rec( rec );
}
}
/*
* Read file data into address cache.
* Enter: pineFile Pine control data.
* cache Address cache to load.
*/
static void pine_read_file( PineFile *pineFile, AddressCache *cache ) {
GSList *listValue = NULL;
gboolean flagEOF = FALSE, flagProc = FALSE, flagDone = FALSE;
gchar *line = NULL, *lineValue = NULL;
long posEnd = 0L;
long posCur = 0L;
/* Find EOF for progress indicator */
fseek( pineFile->file, 0L, SEEK_END );
posEnd = ftell( pineFile->file );
fseek( pineFile->file, 0L, SEEK_SET );
flagProc = FALSE;
while( ! flagDone ) {
if( flagEOF ) {
flagDone = TRUE;
flagProc = TRUE;
}
else {
line = pine_read_line( pineFile );
}
posCur = ftell( pineFile->file );
if( pineFile->cbProgress ) {
/* Call progress indicator */
( pineFile->cbProgress ) ( pineFile, & posEnd, & posCur );
}
/* Add line to list */
if( line == NULL ) {
flagEOF = TRUE;
}
else {
/* Check for continuation line (1 space only) */
if( *line == ' ' ) {
g_strchug( line );
listValue = g_slist_append(
listValue, g_strdup( line ) );
flagProc = FALSE;
}
else {
flagProc = TRUE;
}
}
if( flagProc ) {
if( listValue != NULL ) {
/* Process list */
lineValue = mgu_list_coalesce( listValue );
if( lineValue ) {
pine_build_items(
pineFile, cache, lineValue );
}
g_free( lineValue );
lineValue = NULL;
mgu_free_list( listValue );
listValue = NULL;
}
if( line != NULL ) {
/* Append to list */
listValue = g_slist_append(
listValue, g_strdup( line ) );
}
}
g_free( line );
line = NULL;
}
/* Release data */
mgu_free_list( listValue );
listValue = NULL;
}
/*
* ============================================================================================
* Read file into list. Main entry point
* Enter: pineFile Pine control data.
* cache Address cache to load.
* Return: Status code.
* ============================================================================================
*/
gint pine_import_data( PineFile *pineFile, AddressCache *cache ) {
g_return_val_if_fail( pineFile != NULL, MGU_BAD_ARGS );
g_return_val_if_fail( cache != NULL, MGU_BAD_ARGS );
pineFile->retVal = MGU_SUCCESS;
addrcache_clear( cache );
cache->dataRead = FALSE;
pine_open_file( pineFile );
if( pineFile->retVal == MGU_SUCCESS ) {
/* Read data into the cache */
pine_read_file( pineFile, cache );
pine_close_file( pineFile );
/* Mark cache */
cache->modified = FALSE;
cache->dataRead = TRUE;
}
return pineFile->retVal;
}
#define WORK_BUFLEN 1024
/*
* Attempt to find a Pine addressbook file.
* Return: Filename, or home directory if not found, or empty string if
* no home. Filename should be g_free() when done.
*/
gchar *pine_find_file( void ) {
const gchar *homedir;
gchar str[ WORK_BUFLEN ];
gint len;
FILE *fp;
homedir = g_get_home_dir();
if( ! homedir ) return g_strdup( "" );
strcpy( str, homedir );
len = strlen( str );
if( len > 0 ) {
if( str[ len-1 ] != G_DIR_SEPARATOR ) {
str[ len ] = G_DIR_SEPARATOR;
str[ ++len ] = '\0';
}
}
strcat( str, PINE_HOME_FILE );
/* Attempt to open */
if( ( fp = fopen( str, "rb" ) ) != NULL ) {
fclose( fp );
}
else {
/* Truncate filename */
str[ len ] = '\0';
}
return g_strdup( str );
}
/*
* End of Source.
*/