/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* GMime
* Copyright (C) 2000-2007 Jeffrey Stedfast
*
* 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include "gmime-charset.h"
#include "gmime-iconv.h"
#include "cache.h"
#ifdef ENABLE_WARNINGS
#define w(x) x
#else
#define w(x)
#endif /* ENABLE_WARNINGS */
#define ICONV_CACHE_SIZE (16)
typedef struct {
CacheNode node;
guint32 refcount : 31;
guint32 used : 1;
iconv_t cd;
} IconvCacheNode;
static Cache *iconv_cache = NULL;
static GHashTable *iconv_open_hash = NULL;
#ifdef GMIME_ICONV_DEBUG
static int cache_misses = 0;
static int shutdown = 0;
#define d(x) x
#else
#define d(x)
#endif /* GMIME_ICONV_DEBUG */
#ifdef G_THREADS_ENABLED
static GStaticMutex iconv_cache_lock = G_STATIC_MUTEX_INIT;
#define ICONV_CACHE_LOCK() g_static_mutex_lock (&iconv_cache_lock)
#define ICONV_CACHE_UNLOCK() g_static_mutex_unlock (&iconv_cache_lock)
#else
#define ICONV_CACHE_LOCK()
#define ICONV_CACHE_UNLOCK()
#endif /* G_THREADS_ENABLED */
/* caller *must* hold the iconv_cache_lock to call any of the following functions */
/**
* iconv_cache_node_new:
* @key: cache key
* @cd: iconv descriptor
*
* Creates a new cache node, inserts it into the cache and increments
* the cache size.
*
* Returns a pointer to the newly allocated cache node.
**/
static IconvCacheNode *
iconv_cache_node_new (const char *key, iconv_t cd)
{
IconvCacheNode *node;
#ifdef GMIME_ICONV_DEBUG
cache_misses++;
#endif
node = (IconvCacheNode *) cache_node_insert (iconv_cache, key);
node->refcount = 1;
node->used = TRUE;
node->cd = cd;
return node;
}
static void
iconv_cache_node_free (CacheNode *node)
{
IconvCacheNode *inode = (IconvCacheNode *) node;
#ifdef GMIME_ICONV_DEBUG
if (shutdown) {
fprintf (stderr, "%s: open=%d; used=%s\n", node->key,
inode->refcount, inode->used ? "yes" : "no");
}
#endif
iconv_close (inode->cd);
}
/**
* iconv_cache_node_expire:
* @node: cache node
*
* Decides whether or not a cache node should be expired.
**/
static gboolean
iconv_cache_node_expire (Cache *cache, CacheNode *node)
{
IconvCacheNode *inode = (IconvCacheNode *) node;
if (inode->refcount == 0)
return TRUE;
return FALSE;
}
/**
* g_mime_iconv_shutdown:
*
* Frees internal iconv caches created in g_mime_iconv_init().
**/
void
g_mime_iconv_shutdown (void)
{
if (!iconv_cache)
return;
#ifdef GMIME_ICONV_DEBUG
fprintf (stderr, "There were %d iconv cache misses\n", cache_misses);
fprintf (stderr, "The following %d iconv cache buckets are still open:\n", iconv_cache->size);
shutdown = 1;
#endif
cache_free (iconv_cache);
iconv_cache = NULL;
g_hash_table_destroy (iconv_open_hash);
iconv_open_hash = NULL;
}
/**
* g_mime_iconv_init:
*
* Initialize GMime's iconv cache. This *MUST* be called before any
* gmime-iconv interfaces will work correctly.
**/
void
g_mime_iconv_init (void)
{
if (iconv_cache)
return;
g_mime_charset_map_init ();
iconv_open_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
iconv_cache = cache_new (iconv_cache_node_expire, iconv_cache_node_free,
sizeof (IconvCacheNode), ICONV_CACHE_SIZE);
}
/**
* g_mime_iconv_open:
* @to: charset to convert to
* @from: charset to convert from
*
* Allocates a coversion descriptor suitable for converting byte
* sequences from charset @from to charset @to. The resulting
* descriptor can be used with iconv() (or the g_mime_iconv() wrapper) any
* number of times until closed using g_mime_iconv_close().
*
* Returns a new conversion descriptor for use with g_mime_iconv() on
* success or (iconv_t) %-1 on fail as well as setting an appropriate
* errno value.
**/
iconv_t
g_mime_iconv_open (const char *to, const char *from)
{
IconvCacheNode *node;
iconv_t cd;
char *key;
if (from == NULL || to == NULL) {
errno = EINVAL;
return (iconv_t) -1;
}
if (!g_ascii_strcasecmp (from, "x-unknown"))
from = g_mime_locale_charset ();
from = g_mime_charset_iconv_name (from);
to = g_mime_charset_iconv_name (to);
key = g_alloca (strlen (from) + strlen (to) + 2);
sprintf (key, "%s:%s", from, to);
ICONV_CACHE_LOCK ();
if ((node = (IconvCacheNode *) cache_node_lookup (iconv_cache, key, TRUE))) {
if (node->used) {
if ((cd = iconv_open (to, from)) == (iconv_t) -1)
goto exception;
} else {
/* Apparently iconv on Solaris <= 7 segfaults if you pass in
* NULL for anything but inbuf; work around that. (NULL outbuf
* or NULL *outbuf is allowed by Unix98.)
*/
size_t inleft = 0, outleft = 0;
char *outbuf = NULL;
cd = node->cd;
node->used = TRUE;
/* reset the descriptor */
iconv (cd, NULL, &inleft, &outbuf, &outleft);
}
node->refcount++;
} else {
if ((cd = iconv_open (to, from)) == (iconv_t) -1)
goto exception;
node = iconv_cache_node_new (key, cd);
}
g_hash_table_insert (iconv_open_hash, cd, ((CacheNode *) node)->key);
ICONV_CACHE_UNLOCK ();
return cd;
exception:
ICONV_CACHE_UNLOCK ();
#if w(!)0
if (errno == EINVAL)
g_warning ("Conversion from '%s' to '%s' is not supported", from, to);
else
g_warning ("Could not open converter from '%s' to '%s': %s",
from, to, strerror (errno));
#endif
return cd;
}
/**
* g_mime_iconv_close:
* @cd: iconv conversion descriptor
*
* Closes the iconv descriptor @cd.
*
* Returns %0 on success or %-1 on fail as well as setting an
* appropriate errno value.
**/
int
g_mime_iconv_close (iconv_t cd)
{
IconvCacheNode *node;
const char *key;
if (cd == (iconv_t) -1)
return 0;
ICONV_CACHE_LOCK ();
if ((key = g_hash_table_lookup (iconv_open_hash, cd))) {
g_hash_table_remove (iconv_open_hash, cd);
node = (IconvCacheNode *) cache_node_lookup (iconv_cache, key, FALSE);
g_assert (node);
if (iconv_cache->size > ICONV_CACHE_SIZE) {
/* expire before unreffing this node so that it wont get uncached */
cache_expire_unused (iconv_cache);
}
node->refcount--;
if (cd == node->cd)
node->used = FALSE;
else
iconv_close (cd);
} else {
ICONV_CACHE_UNLOCK ();
d(g_warning ("This iconv context wasn't opened using g_mime_iconv_open()"));
return iconv_close (cd);
}
ICONV_CACHE_UNLOCK ();
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1