From 1de6dfdef52c7cd9976f93745c78d23bc467c9a6 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Fri, 3 Dec 2004 03:33:06 +0000 Subject: [PATCH] include config.h. 2004-11-15 Not Zed * libedataserver/e-util.c: include config.h. * libedataserver/e-sexp.c: updated from evolution/e-util. * libedataserver/e-memory.c: updated from evolution/e-util. * iconv-detect.c: added iconv format checker. * configure.in: add stftime checks and iconv charset format checks. * libedataserver/e-util.c (e_strftime): copied from gal/e-util.h. * libedataserver/e-time-utils.c (parse_with_strptime): reove e-utf8 depenedncy. * libedataserver/e-iconv.[ch]: Moved from gal/util. * libedataserver/e-trie.[ch]: Moved from evolution/e-util. * libedataserver/e-msgport.[ch]: Moved from evolution/e-util. * libedataserver/e-time-utils.[ch]: Moved from evolution/e-util. --- libedataserver/Makefile.am | 8 + libedataserver/e-iconv.c | 614 ++++++++++++++++++++ libedataserver/e-iconv.h | 49 ++ libedataserver/e-memory.c | 9 +- libedataserver/e-msgport.c | 1250 +++++++++++++++++++++++++++++++++++++++++ libedataserver/e-msgport.h | 111 ++++ libedataserver/e-sexp.c | 8 +- libedataserver/e-time-utils.c | 490 ++++++++++++++++ libedataserver/e-time-utils.h | 58 ++ libedataserver/e-trie.c | 345 ++++++++++++ libedataserver/e-trie.h | 47 ++ libedataserver/e-util.c | 70 +++ libedataserver/e-util.h | 6 + 13 files changed, 3061 insertions(+), 4 deletions(-) create mode 100644 libedataserver/e-iconv.c create mode 100644 libedataserver/e-iconv.h create mode 100644 libedataserver/e-msgport.c create mode 100644 libedataserver/e-msgport.h create mode 100644 libedataserver/e-time-utils.c create mode 100644 libedataserver/e-time-utils.h create mode 100644 libedataserver/e-trie.c create mode 100644 libedataserver/e-trie.h diff --git a/libedataserver/Makefile.am b/libedataserver/Makefile.am index 0ece08b..8784afe 100644 --- a/libedataserver/Makefile.am +++ b/libedataserver/Makefile.am @@ -21,17 +21,21 @@ libedataserver_1_2_la_SOURCES = \ e-dbhash.c \ e-db3-utils.c \ e-file-cache.c \ + e-iconv.c \ e-iterator.c \ e-list.c \ e-list-iterator.c \ e-memory.c \ + e-msgport.c \ e-sexp.c \ e-source-group.c \ e-source-list.c \ e-source.c \ + e-time-utils.c \ e-uid.c \ e-url.c \ e-util.c \ + e-trie.c \ e-xml-hash-utils.c \ md5-utils.c @@ -52,14 +56,18 @@ libedataserverinclude_HEADERS = \ e-db3-utils.h \ e-dbhash.h \ e-file-cache.h \ + e-iconv.h \ e-iterator.h \ e-list.h \ e-list-iterator.h \ e-memory.h \ + e-msgport.h \ e-sexp.h \ e-source-group.h \ e-source-list.h \ e-source.h \ + e-time-utils.h \ + e-trie.h \ e-uid.h \ e-url.h \ e-util.h \ diff --git a/libedataserver/e-iconv.c b/libedataserver/e-iconv.c new file mode 100644 index 0000000..3236521 --- /dev/null +++ b/libedataserver/e-iconv.c @@ -0,0 +1,614 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * e-iconv.c + * Copyright 2000, 2001, Ximian, Inc. + * + * Authors: + * Michael Zucchi + * Jeffery Stedfast + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License, version 2, as published by the Free Software Foundation. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include "e-iconv.h" + +#include + +#ifdef HAVE_CODESET +#include +#endif + +#include "iconv-detect.h" + +#define cd(x) + +#ifdef G_THREADS_ENABLED +static GStaticMutex lock = G_STATIC_MUTEX_INIT; +#define LOCK() g_static_mutex_lock(&lock) +#define UNLOCK() g_static_mutex_unlock(&lock) +#else +#define LOCK() +#define UNLOCK() +#endif + +typedef struct _EDListNode { + struct _EDListNode *next; + struct _EDListNode *prev; +} EDListNode; + +typedef struct _EDList { + struct _EDListNode *head; + struct _EDListNode *tail; + struct _EDListNode *tailpred; +} EDList; + +#define E_DLIST_INITIALISER(l) { (EDListNode *)&l.tail, 0, (EDListNode *)&l.head } + +struct _iconv_cache_node { + struct _iconv_cache_node *next; + struct _iconv_cache_node *prev; + + struct _iconv_cache *parent; + + int busy; + iconv_t ip; +}; + +struct _iconv_cache { + struct _iconv_cache *next; + struct _iconv_cache *prev; + + char *conv; + + EDList open; /* stores iconv_cache_nodes, busy ones up front */ +}; + +#define E_ICONV_CACHE_SIZE (16) + +static EDList iconv_cache_list; +static GHashTable *iconv_cache; +static GHashTable *iconv_cache_open; +static unsigned int iconv_cache_size = 0; + +static GHashTable *iconv_charsets = NULL; +static char *locale_charset = NULL; +static char *locale_lang = NULL; + +struct { + char *charset; + char *iconv_name; +} known_iconv_charsets[] = { +#if 0 + /* charset name, iconv-friendly charset name */ + { "iso-8859-1", "iso-8859-1" }, + { "iso8859-1", "iso-8859-1" }, + /* the above mostly serves as an example for iso-style charsets, + but we have code that will populate the iso-*'s if/when they + show up in e_iconv_charset_name() so I'm + not going to bother putting them all in here... */ + { "windows-cp1251", "cp1251" }, + { "windows-1251", "cp1251" }, + { "cp1251", "cp1251" }, + /* the above mostly serves as an example for windows-style + charsets, but we have code that will parse and convert them + to their cp#### equivalents if/when they show up in + e_iconv_charset_name() so I'm not going to bother + putting them all in here either... */ +#endif + /* charset name (lowercase!), iconv-friendly name (sometimes case sensitive) */ + { "utf-8", "UTF-8" }, + + /* 10646 is a special case, its usually UCS-2 big endian */ + /* This might need some checking but should be ok for solaris/linux */ + { "iso-10646-1", "UCS-2BE" }, + { "iso_10646-1", "UCS-2BE" }, + { "iso10646-1", "UCS-2BE" }, + { "iso-10646", "UCS-2BE" }, + { "iso_10646", "UCS-2BE" }, + { "iso10646", "UCS-2BE" }, + + { "ks_c_5601-1987", "EUC-KR" }, + + /* FIXME: Japanese/Korean/Chinese stuff needs checking */ + { "euckr-0", "EUC-KR" }, + { "5601", "EUC-KR" }, + { "zh_TW-euc", "EUC-TW" }, + { "zh_CN.euc", "gb2312" }, + { "zh_TW-big5", "BIG5" }, + { "euc-cn", "gb2312" }, + { "big5-0", "BIG5" }, + { "big5.eten-0", "BIG5" }, + { "big5hkscs-0", "BIG5HKSCS" }, + { "gb2312-0", "gb2312" }, + { "gb2312.1980-0", "gb2312" }, + { "gb-2312", "gb2312" }, + { "gb18030-0", "gb18030" }, + { "gbk-0", "GBK" }, + + { "eucjp-0", "eucJP" }, + { "ujis-0", "ujis" }, + { "jisx0208.1983-0","SJIS" }, + { "jisx0212.1990-0","SJIS" }, + { "pck", "SJIS" }, + { NULL, NULL } +}; + + + +/* Another copy of this trivial list implementation + Why? This stuff gets called a lot (potentially), should run fast, + and g_list's are f@@#$ed up to make this a hassle */ +static void e_dlist_init(EDList *v) +{ + v->head = (EDListNode *)&v->tail; + v->tail = 0; + v->tailpred = (EDListNode *)&v->head; +} + +static EDListNode *e_dlist_addhead(EDList *l, EDListNode *n) +{ + n->next = l->head; + n->prev = (EDListNode *)&l->head; + l->head->prev = n; + l->head = n; + return n; +} + +static EDListNode *e_dlist_addtail(EDList *l, EDListNode *n) +{ + n->next = (EDListNode *)&l->tail; + n->prev = l->tailpred; + l->tailpred->next = n; + l->tailpred = n; + return n; +} + +static EDListNode *e_dlist_remove(EDListNode *n) +{ + n->next->prev = n->prev; + n->prev->next = n->next; + return n; +} + + +/* fucking glib... */ +static const char * +e_strdown (char *str) +{ + register char *s = str; + + while (*s) { + if (*s >= 'A' && *s <= 'Z') + *s += 0x20; + s++; + } + + return str; +} + +static const char * +e_strup (char *str) +{ + register char *s = str; + + while (*s) { + if (*s >= 'a' && *s <= 'z') + *s -= 0x20; + s++; + } + + return str; +} + + +static void +locale_parse_lang (const char *locale) +{ + char *codeset, *lang; + + if ((codeset = strchr (locale, '.'))) + lang = g_strndup (locale, codeset - locale); + else + lang = g_strdup (locale); + + /* validate the language */ + if (strlen (lang) >= 2) { + if (lang[2] == '-' || lang[2] == '_') { + /* canonicalise the lang */ + e_strdown (lang); + + /* validate the country code */ + if (strlen (lang + 3) > 2) { + /* invalid country code */ + lang[2] = '\0'; + } else { + lang[2] = '-'; + e_strup (lang + 3); + } + } else if (lang[2] != '\0') { + /* invalid language */ + g_free (lang); + lang = NULL; + } + + locale_lang = lang; + } else { + /* invalid language */ + locale_lang = NULL; + g_free (lang); + } +} + +/* NOTE: Owns the lock on return if keep is TRUE ! */ +static void +e_iconv_init(int keep) +{ + char *from, *to, *locale; + int i; + + LOCK(); + + if (iconv_charsets != NULL) { + if (!keep) + UNLOCK(); + return; + } + + iconv_charsets = g_hash_table_new(g_str_hash, g_str_equal); + + for (i = 0; known_iconv_charsets[i].charset != NULL; i++) { + from = g_strdup(known_iconv_charsets[i].charset); + to = g_strdup(known_iconv_charsets[i].iconv_name); + e_strdown (from); + g_hash_table_insert(iconv_charsets, from, to); + } + + e_dlist_init(&iconv_cache_list); + iconv_cache = g_hash_table_new(g_str_hash, g_str_equal); + iconv_cache_open = g_hash_table_new(NULL, NULL); + + locale = setlocale (LC_ALL, NULL); + + if (!locale || !strcmp (locale, "C") || !strcmp (locale, "POSIX")) { + /* The locale "C" or "POSIX" is a portable locale; its + * LC_CTYPE part corresponds to the 7-bit ASCII character + * set. + */ + + locale_charset = NULL; + locale_lang = NULL; + } else { +#ifdef HAVE_CODESET + locale_charset = g_strdup (nl_langinfo (CODESET)); + e_strdown (locale_charset); +#else + /* A locale name is typically of the form language[_terri- + * tory][.codeset][@modifier], where language is an ISO 639 + * language code, territory is an ISO 3166 country code, and + * codeset is a character set or encoding identifier like + * ISO-8859-1 or UTF-8. + */ + char *codeset, *p; + + codeset = strchr (locale, '.'); + if (codeset) { + codeset++; + + /* ; is a hack for debian systems and / is a hack for Solaris systems */ + for (p = codeset; *p && !strchr ("@;/", *p); p++); + locale_charset = g_strndup (codeset, p - codeset); + e_strdown (locale_charset); + } else { + /* charset unknown */ + locale_charset = NULL; + } +#endif + + /* parse the locale lang */ + locale_parse_lang (locale); + + } + + if (!keep) + UNLOCK(); +} + +const char *e_iconv_charset_name(const char *charset) +{ + char *name, *ret, *tmp; + + if (charset == NULL) + return NULL; + + name = g_alloca (strlen (charset) + 1); + strcpy (name, charset); + e_strdown (name); + + e_iconv_init(TRUE); + ret = g_hash_table_lookup(iconv_charsets, name); + if (ret != NULL) { + UNLOCK(); + return ret; + } + + /* Unknown, try canonicalise some basic charset types to something that should work */ + if (strncmp(name, "iso", 3) == 0) { + /* Convert iso-nnnn-n or isonnnn-n or iso_nnnn-n to iso-nnnn-n or isonnnn-n */ + int iso, codepage; + char *p; + + tmp = name + 3; + if (*tmp == '-' || *tmp == '_') + tmp++; + + iso = strtoul (tmp, &p, 10); + + if (iso == 10646) { + /* they all become ICONV_10646 */ + ret = g_strdup (ICONV_10646); + } else { + tmp = p; + if (*tmp == '-' || *tmp == '_') + tmp++; + + codepage = strtoul (tmp, &p, 10); + + if (p > tmp) { + /* codepage is numeric */ +#ifdef __aix__ + if (codepage == 13) + ret = g_strdup ("IBM-921"); + else +#endif /* __aix__ */ + ret = g_strdup_printf (ICONV_ISO_D_FORMAT, iso, codepage); + } else { + /* codepage is a string - probably iso-2022-jp or something */ + ret = g_strdup_printf (ICONV_ISO_S_FORMAT, iso, p); + } + } + } else if (strncmp(name, "windows-", 8) == 0) { + /* Convert windows-nnnnn or windows-cpnnnnn to cpnnnn */ + tmp = name+8; + if (!strncmp(tmp, "cp", 2)) + tmp+=2; + ret = g_strdup_printf("CP%s", tmp); + } else if (strncmp(name, "microsoft-", 10) == 0) { + /* Convert microsoft-nnnnn or microsoft-cpnnnnn to cpnnnn */ + tmp = name+10; + if (!strncmp(tmp, "cp", 2)) + tmp+=2; + ret = g_strdup_printf("CP%s", tmp); + } else { + /* Just assume its ok enough as is, case and all */ + ret = g_strdup(charset); + } + + g_hash_table_insert(iconv_charsets, g_strdup(name), ret); + UNLOCK(); + + return ret; +} + +static void +flush_entry(struct _iconv_cache *ic) +{ + struct _iconv_cache_node *in, *nn; + + in = (struct _iconv_cache_node *)ic->open.head; + nn = in->next; + while (nn) { + if (in->ip != (iconv_t)-1) { + g_hash_table_remove(iconv_cache_open, in->ip); + iconv_close(in->ip); + } + g_free(in); + in = nn; + nn = in->next; + } + g_free(ic->conv); + g_free(ic); +} + +/* This should run pretty quick, its called a lot */ +iconv_t e_iconv_open(const char *oto, const char *ofrom) +{ + const char *to, *from; + char *tofrom; + struct _iconv_cache *ic; + struct _iconv_cache_node *in; + int errnosav; + iconv_t ip; + + if (oto == NULL || ofrom == NULL) { + errno = EINVAL; + return (iconv_t) -1; + } + + to = e_iconv_charset_name (oto); + from = e_iconv_charset_name (ofrom); + tofrom = g_alloca (strlen (to) + strlen (from) + 2); + sprintf(tofrom, "%s%%%s", to, from); + + LOCK(); + + ic = g_hash_table_lookup(iconv_cache, tofrom); + if (ic) { + e_dlist_remove((EDListNode *)ic); + } else { + struct _iconv_cache *last = (struct _iconv_cache *)iconv_cache_list.tailpred; + struct _iconv_cache *prev; + + prev = last->prev; + while (prev && iconv_cache_size > E_ICONV_CACHE_SIZE) { + in = (struct _iconv_cache_node *)last->open.head; + if (in->next && !in->busy) { + cd(printf("Flushing iconv converter '%s'\n", last->conv)); + e_dlist_remove((EDListNode *)last); + g_hash_table_remove(iconv_cache, last->conv); + flush_entry(last); + iconv_cache_size--; + } + last = prev; + prev = last->prev; + } + + iconv_cache_size++; + + ic = g_malloc(sizeof(*ic)); + e_dlist_init(&ic->open); + ic->conv = g_strdup(tofrom); + g_hash_table_insert(iconv_cache, ic->conv, ic); + + cd(printf("Creating iconv converter '%s'\n", ic->conv)); + } + e_dlist_addhead(&iconv_cache_list, (EDListNode *)ic); + + /* If we have a free iconv, use it */ + in = (struct _iconv_cache_node *)ic->open.tailpred; + if (in->prev && !in->busy) { + cd(printf("using existing iconv converter '%s'\n", ic->conv)); + ip = in->ip; + if (ip != (iconv_t)-1) { + /* work around some broken iconv implementations + * that die if the length arguments are NULL + */ + size_t buggy_iconv_len = 0; + char *buggy_iconv_buf = NULL; + + /* resets the converter */ + iconv(ip, &buggy_iconv_buf, &buggy_iconv_len, &buggy_iconv_buf, &buggy_iconv_len); + in->busy = TRUE; + e_dlist_remove((EDListNode *)in); + e_dlist_addhead(&ic->open, (EDListNode *)in); + } + } else { + cd(printf("creating new iconv converter '%s'\n", ic->conv)); + ip = iconv_open(to, from); + in = g_malloc(sizeof(*in)); + in->ip = ip; + in->parent = ic; + e_dlist_addhead(&ic->open, (EDListNode *)in); + if (ip != (iconv_t)-1) { + g_hash_table_insert(iconv_cache_open, ip, in); + in->busy = TRUE; + } else { + errnosav = errno; + g_warning("Could not open converter for '%s' to '%s' charset", from, to); + in->busy = FALSE; + errno = errnosav; + } + } + + UNLOCK(); + + return ip; +} + +size_t e_iconv(iconv_t cd, const char **inbuf, size_t *inbytesleft, char ** outbuf, size_t *outbytesleft) +{ + return iconv(cd, (char **) inbuf, inbytesleft, outbuf, outbytesleft); +} + +void +e_iconv_close(iconv_t ip) +{ + struct _iconv_cache_node *in; + + if (ip == (iconv_t)-1) + return; + + LOCK(); + in = g_hash_table_lookup(iconv_cache_open, ip); + if (in) { + cd(printf("closing iconv converter '%s'\n", in->parent->conv)); + e_dlist_remove((EDListNode *)in); + in->busy = FALSE; + e_dlist_addtail(&in->parent->open, (EDListNode *)in); + } else { + g_warning("trying to close iconv i dont know about: %p", ip); + iconv_close(ip); + } + UNLOCK(); + +} + +const char *e_iconv_locale_charset(void) +{ + e_iconv_init(FALSE); + + return locale_charset; +} + + +const char * +e_iconv_locale_language (void) +{ + e_iconv_init (FALSE); + + return locale_lang; +} + +/* map CJKR charsets to their language code */ +/* NOTE: only support charset names that will be returned by + * e_iconv_charset_name() so that we don't have to keep track of all + * the aliases too. */ +static struct { + char *charset; + char *lang; +} cjkr_lang_map[] = { + { "Big5", "zh" }, + { "BIG5HKSCS", "zh" }, + { "gb2312", "zh" }, + { "gb18030", "zh" }, + { "gbk", "zh" }, + { "euc-tw", "zh" }, + { "iso-2022-jp", "ja" }, + { "sjis", "ja" }, + { "ujis", "ja" }, + { "eucJP", "ja" }, + { "euc-jp", "ja" }, + { "euc-kr", "ko" }, + { "koi8-r", "ru" }, + { "koi8-u", "uk" } +}; + +#define NUM_CJKR_LANGS (sizeof (cjkr_lang_map) / sizeof (cjkr_lang_map[0])) + +const char * +e_iconv_charset_language (const char *charset) +{ + int i; + + if (!charset) + return NULL; + + charset = e_iconv_charset_name (charset); + for (i = 0; i < NUM_CJKR_LANGS; i++) { + if (!strcasecmp (cjkr_lang_map[i].charset, charset)) + return cjkr_lang_map[i].lang; + } + + return NULL; +} diff --git a/libedataserver/e-iconv.h b/libedataserver/e-iconv.h new file mode 100644 index 0000000..14b9385 --- /dev/null +++ b/libedataserver/e-iconv.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * e-iconv.h + * Copyright 2000, 2001, Ximian, Inc. + * + * Authors: + * Michael Zucchi + * Jeffrey Stedfast + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License, version 2, as published by the Free Software Foundation. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef _E_ICONV_H_ +#define _E_ICONV_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +const char *e_iconv_charset_name(const char *charset); +iconv_t e_iconv_open(const char *oto, const char *ofrom); +size_t e_iconv(iconv_t cd, const char **inbuf, size_t *inbytesleft, char ** outbuf, size_t *outbytesleft); +void e_iconv_close(iconv_t ip); +const char *e_iconv_locale_charset(void); + +/* languages */ +const char *e_iconv_locale_language (void); +const char *e_iconv_charset_language (const char *charset); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* !_E_ICONV_H_ */ diff --git a/libedataserver/e-memory.c b/libedataserver/e-memory.c index ae882d7..455e263 100644 --- a/libedataserver/e-memory.c +++ b/libedataserver/e-memory.c @@ -24,6 +24,7 @@ #include /* memset() */ #include /* alloca() */ +#include #define s(x) /* strv debug */ #define p(x) /* poolv debug */ @@ -282,7 +283,7 @@ e_memchunk_clean(MemChunk *m) /* first, setup the tree/list so we can map free block addresses to block addresses */ tree = g_tree_new((GCompareFunc)tree_compare); for (i=0;iblocks->len;i++) { - ci = g_alloca(sizeof(*ci)); + ci = alloca(sizeof(*ci)); ci->count = 0; ci->base = m->blocks->pdata[i]; ci->size = m->blocksize * m->atomsize; @@ -535,7 +536,13 @@ void e_mempool_destroy(MemPool *pool) { if (pool) { e_mempool_flush(pool, 1); +#ifdef G_THREADS_ENABLED + g_static_mutex_lock(&mempool_mutex); +#endif e_memchunk_free(mempool_memchunk, pool); +#ifdef G_THREADS_ENABLED + g_static_mutex_unlock(&mempool_mutex); +#endif } } diff --git a/libedataserver/e-msgport.c b/libedataserver/e-msgport.c new file mode 100644 index 0000000..f360a3f --- /dev/null +++ b/libedataserver/e-msgport.c @@ -0,0 +1,1250 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi + * + * Copyright 2002 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#ifdef HAVE_NSS +#include +#endif + +#include "e-msgport.h" + +#define m(x) /* msgport debug */ +#define t(x) /* thread debug */ +#define c(x) /* cache debug */ + +void e_dlist_init(EDList *v) +{ + v->head = (EDListNode *)&v->tail; + v->tail = 0; + v->tailpred = (EDListNode *)&v->head; +} + +EDListNode *e_dlist_addhead(EDList *l, EDListNode *n) +{ + n->next = l->head; + n->prev = (EDListNode *)&l->head; + l->head->prev = n; + l->head = n; + return n; +} + +EDListNode *e_dlist_addtail(EDList *l, EDListNode *n) +{ + n->next = (EDListNode *)&l->tail; + n->prev = l->tailpred; + l->tailpred->next = n; + l->tailpred = n; + return n; +} + +EDListNode *e_dlist_remove(EDListNode *n) +{ + n->next->prev = n->prev; + n->prev->next = n->next; + return n; +} + +EDListNode *e_dlist_remhead(EDList *l) +{ + EDListNode *n, *nn; + + n = l->head; + nn = n->next; + if (nn) { + nn->prev = n->prev; + l->head = nn; + return n; + } + return NULL; +} + +EDListNode *e_dlist_remtail(EDList *l) +{ + EDListNode *n, *np; + + n = l->tailpred; + np = n->prev; + if (np) { + np->next = n->next; + l->tailpred = np; + return n; + } + return NULL; +} + +int e_dlist_empty(EDList *l) +{ + return (l->head == (EDListNode *)&l->tail); +} + +int e_dlist_length(EDList *l) +{ + EDListNode *n, *nn; + int count = 0; + + n = l->head; + nn = n->next; + while (nn) { + count++; + n = nn; + nn = n->next; + } + + return count; +} + +struct _EMCache { + GMutex *lock; + GHashTable *key_table; + EDList lru_list; + size_t node_size; + int node_count; + time_t timeout; + GFreeFunc node_free; +}; + +/** + * em_cache_new: + * @timeout: + * @nodesize: + * @nodefree: + * + * Setup a new timeout cache. @nodesize is the size of nodes in the + * cache, and @nodefree will be called to free YOUR content. + * + * Return value: + **/ +EMCache * +em_cache_new(time_t timeout, size_t nodesize, GFreeFunc nodefree) +{ + struct _EMCache *emc; + + emc = g_malloc0(sizeof(*emc)); + emc->node_size = nodesize; + emc->key_table = g_hash_table_new(g_str_hash, g_str_equal); + emc->node_free = nodefree; + e_dlist_init(&emc->lru_list); + emc->lock = g_mutex_new(); + emc->timeout = timeout; + + return emc; +} + +/** + * em_cache_destroy: + * @emc: + * + * destroy the cache, duh. + **/ +void +em_cache_destroy(EMCache *emc) +{ + em_cache_clear(emc); + g_mutex_free(emc->lock); + g_free(emc); +} + +/** + * em_cache_lookup: + * @emc: + * @key: + * + * Lookup a cache node. once you're finished with it, you need to + * unref it. + * + * Return value: + **/ +EMCacheNode * +em_cache_lookup(EMCache *emc, const char *key) +{ + EMCacheNode *n; + + g_mutex_lock(emc->lock); + n = g_hash_table_lookup(emc->key_table, key); + if (n) { + e_dlist_remove((EDListNode *)n); + e_dlist_addhead(&emc->lru_list, (EDListNode *)n); + n->stamp = time(0); + n->ref_count++; + } + g_mutex_unlock(emc->lock); + + c(printf("looking up '%s' %s\n", key, n?"found":"not found")); + + return n; +} + +/** + * em_cache_node_new: + * @emc: + * @key: + * + * Create a new key'd cache node. The node will not be added to the + * cache until you insert it. + * + * Return value: + **/ +EMCacheNode * +em_cache_node_new(EMCache *emc, const char *key) +{ + EMCacheNode *n; + + /* this could use memchunks, but its probably overkill */ + n = g_malloc0(emc->node_size); + n->key = g_strdup(key); + + return n; +} + +/** + * em_cache_node_unref: + * @emc: + * @n: + * + * unref a cache node, you can only unref nodes which have been looked + * up. + **/ +void +em_cache_node_unref(EMCache *emc, EMCacheNode *n) +{ + g_mutex_lock(emc->lock); + g_assert(n->ref_count > 0); + n->ref_count--; + g_mutex_unlock(emc->lock); +} + +/** + * em_cache_add: + * @emc: + * @n: + * + * Add a cache node to the cache, once added the memory is owned by + * the cache. If there are conflicts and the old node is still in + * use, then the new node is not added, otherwise it is added and any + * nodes older than the expire time are flushed. + **/ +void +em_cache_add(EMCache *emc, EMCacheNode *n) +{ + EMCacheNode *old, *prev; + EDList old_nodes; + + e_dlist_init(&old_nodes); + + g_mutex_lock(emc->lock); + old = g_hash_table_lookup(emc->key_table, n->key); + if (old != NULL) { + if (old->ref_count == 0) { + g_hash_table_remove(emc->key_table, old->key); + e_dlist_remove((EDListNode *)old); + e_dlist_addtail(&old_nodes, (EDListNode *)old); + goto insert; + } else { + e_dlist_addtail(&old_nodes, (EDListNode *)n); + } + } else { + time_t now; + insert: + now = time(0); + g_hash_table_insert(emc->key_table, n->key, n); + e_dlist_addhead(&emc->lru_list, (EDListNode *)n); + n->stamp = now; + emc->node_count++; + + c(printf("inserting node %s\n", n->key)); + + old = (EMCacheNode *)emc->lru_list.tailpred; + prev = old->prev; + while (prev && old->stamp < now - emc->timeout) { + if (old->ref_count == 0) { + c(printf("expiring node %s\n", old->key)); + g_hash_table_remove(emc->key_table, old->key); + e_dlist_remove((EDListNode *)old); + e_dlist_addtail(&old_nodes, (EDListNode *)old); + } + old = prev; + prev = prev->prev; + } + } + + g_mutex_unlock(emc->lock); + + while ((old = (EMCacheNode *)e_dlist_remhead(&old_nodes))) { + emc->node_free(old); + g_free(old->key); + g_free(old); + } +} + +/** + * em_cache_clear: + * @emc: + * + * clear the cache. just for api completeness. + **/ +void +em_cache_clear(EMCache *emc) +{ + EMCacheNode *node; + EDList old_nodes; + + e_dlist_init(&old_nodes); + g_mutex_lock(emc->lock); + while ((node = (EMCacheNode *)e_dlist_remhead(&emc->lru_list))) + e_dlist_addtail(&old_nodes, (EDListNode *)node); + g_mutex_unlock(emc->lock); + + while ((node = (EMCacheNode *)e_dlist_remhead(&old_nodes))) { + emc->node_free(node); + g_free(node->key); + g_free(node); + } +} + +struct _EMsgPort { + EDList queue; + int condwait; /* how many waiting in condwait */ + union { + int pipe[2]; + struct { + int read; + int write; + } fd; + } pipe; +#ifdef HAVE_NSS + struct { + PRFileDesc *read; + PRFileDesc *write; + } prpipe; +#endif + /* @#@$#$ glib stuff */ + GCond *cond; + GMutex *lock; +}; + +EMsgPort *e_msgport_new(void) +{ + EMsgPort *mp; + + mp = g_malloc(sizeof(*mp)); + e_dlist_init(&mp->queue); + mp->lock = g_mutex_new(); + mp->cond = g_cond_new(); + mp->pipe.fd.read = -1; + mp->pipe.fd.write = -1; +#ifdef HAVE_NSS + mp->prpipe.read = NULL; + mp->prpipe.write = NULL; +#endif + mp->condwait = 0; + + return mp; +} + +void e_msgport_destroy(EMsgPort *mp) +{ + g_mutex_free(mp->lock); + g_cond_free(mp->cond); + if (mp->pipe.fd.read != -1) { + close(mp->pipe.fd.read); + close(mp->pipe.fd.write); + } +#ifdef HAVE_NSS + if (mp->prpipe.read) { + PR_Close(mp->prpipe.read); + PR_Close(mp->prpipe.write); + } +#endif + g_free(mp); +} + +/* get a fd that can be used to wait on the port asynchronously */ +int e_msgport_fd(EMsgPort *mp) +{ + int fd; + + g_mutex_lock(mp->lock); + fd = mp->pipe.fd.read; + if (fd == -1) { + pipe(mp->pipe.pipe); + fd = mp->pipe.fd.read; + } + g_mutex_unlock(mp->lock); + + return fd; +} + +#ifdef HAVE_NSS +PRFileDesc *e_msgport_prfd(EMsgPort *mp) +{ + PRFileDesc *fd; + + g_mutex_lock(mp->lock); + fd = mp->prpipe.read; + if (fd == NULL) { + PR_CreatePipe(&mp->prpipe.read, &mp->prpipe.write); + fd = mp->prpipe.read; + } + g_mutex_unlock(mp->lock); + + return fd; +} +#endif + +void e_msgport_put(EMsgPort *mp, EMsg *msg) +{ + int fd; +#ifdef HAVE_NSS + PRFileDesc *prfd; +#endif + + m(printf("put:\n")); + g_mutex_lock(mp->lock); + e_dlist_addtail(&mp->queue, &msg->ln); + if (mp->condwait > 0) { + m(printf("put: condwait > 0, waking up\n")); + g_cond_signal(mp->cond); + } + fd = mp->pipe.fd.write; +#ifdef HAVE_NSS + prfd = mp->prpipe.write; +#endif + g_mutex_unlock(mp->lock); + + if (fd != -1) { + m(printf("put: have pipe, writing notification to it\n")); + write(fd, "", 1); + } + +#ifdef HAVE_NSS + if (prfd != NULL) { + m(printf("put: have pr pipe, writing notification to it\n")); + PR_Write(prfd, "", 1); + } +#endif + m(printf("put: done\n")); +} + +static void +msgport_cleanlock(void *data) +{ + EMsgPort *mp = data; + + g_mutex_unlock(mp->lock); +} + +EMsg *e_msgport_wait(EMsgPort *mp) +{ + EMsg *msg; + + m(printf("wait:\n")); + g_mutex_lock(mp->lock); + while (e_dlist_empty(&mp->queue)) { + if (mp->pipe.fd.read != -1) { + fd_set rfds; + int retry; + + m(printf("wait: waitng on pipe\n")); + g_mutex_unlock(mp->lock); + do { + FD_ZERO(&rfds); + FD_SET(mp->pipe.fd.read, &rfds); + retry = select(mp->pipe.fd.read+1, &rfds, NULL, NULL, NULL) == -1 && errno == EINTR; + pthread_testcancel(); + } while (retry); + g_mutex_lock(mp->lock); + m(printf("wait: got pipe\n")); +#ifdef HAVE_NSS + } else if (mp->prpipe.read != NULL) { + PRPollDesc polltable[1]; + int retry; + + m(printf("wait: waitng on pr pipe\n")); + g_mutex_unlock(mp->lock); + do { + polltable[0].fd = mp->prpipe.read; + polltable[0].in_flags = PR_POLL_READ|PR_POLL_ERR; + retry = PR_Poll(polltable, 1, PR_INTERVAL_NO_TIMEOUT) == -1 && PR_GetError() == PR_PENDING_INTERRUPT_ERROR; + pthread_testcancel(); + } while (retry); + g_mutex_lock(mp->lock); + m(printf("wait: got pr pipe\n")); +#endif /* HAVE_NSS */ + } else { + m(printf("wait: waiting on condition\n")); + mp->condwait++; + /* if we are cancelled in the cond-wait, then we need to unlock our lock when we cleanup */ + pthread_cleanup_push(msgport_cleanlock, mp); + g_cond_wait(mp->cond, mp->lock); + pthread_cleanup_pop(0); + m(printf("wait: got condition\n")); + mp->condwait--; + } + } + msg = (EMsg *)mp->queue.head; + m(printf("wait: message = %p\n", msg)); + g_mutex_unlock(mp->lock); + m(printf("wait: done\n")); + return msg; +} + +EMsg *e_msgport_get(EMsgPort *mp) +{ + EMsg *msg; + char dummy[1]; + + g_mutex_lock(mp->lock); + msg = (EMsg *)e_dlist_remhead(&mp->queue); + if (msg) { + if (mp->pipe.fd.read != -1) + read(mp->pipe.fd.read, dummy, 1); +#ifdef HAVE_NSS + if (mp->prpipe.read != NULL) { + int c; + c = PR_Read(mp->prpipe.read, dummy, 1); + g_assert(c == 1); + } +#endif + } + m(printf("get: message = %p\n", msg)); + g_mutex_unlock(mp->lock); + + return msg; +} + +void e_msgport_reply(EMsg *msg) +{ + if (msg->reply_port) { + e_msgport_put(msg->reply_port, msg); + } + /* else lost? */ +} + +struct _thread_info { + pthread_t id; + int busy; +}; + +struct _EThread { + struct _EThread *next; + struct _EThread *prev; + + EMsgPort *server_port; + EMsgPort *reply_port; + pthread_mutex_t mutex; + e_thread_t type; + int queue_limit; + + int waiting; /* if we are waiting for a new message, count of waiting processes */ + pthread_t id; /* id of our running child thread */ + GList *id_list; /* if THREAD_NEW, then a list of our child threads in thread_info structs */ + + EThreadFunc destroy; + void *destroy_data; + + EThreadFunc received; + void *received_data; + + EThreadFunc lost; + void *lost_data; +}; + +/* All active threads */ +static EDList ethread_list = E_DLIST_INITIALISER(ethread_list); +static pthread_mutex_t ethread_lock = PTHREAD_MUTEX_INITIALIZER; + +#define E_THREAD_NONE ((pthread_t)~0) +#define E_THREAD_QUIT_REPLYPORT ((struct _EMsgPort *)~0) + +static void thread_destroy_msg(EThread *e, EMsg *m); + +static struct _thread_info *thread_find(EThread *e, pthread_t id) +{ + GList *node; + struct _thread_info *info; + + node = e->id_list; + while (node) { + info = node->data; + if (info->id == id) + return info; + node = node->next; + } + return NULL; +} + +#if 0 +static void thread_remove(EThread *e, pthread_t id) +{ + GList *node; + struct _thread_info *info; + + node = e->id_list; + while (node) { + info = node->data; + if (info->id == id) { + e->id_list = g_list_remove(e->id_list, info); + g_free(info); + } + node = node->next; + } +} +#endif + +EThread *e_thread_new(e_thread_t type) +{ + EThread *e; + + e = g_malloc0(sizeof(*e)); + pthread_mutex_init(&e->mutex, 0); + e->type = type; + e->server_port = e_msgport_new(); + e->id = E_THREAD_NONE; + e->queue_limit = INT_MAX; + + pthread_mutex_lock(ðread_lock); + e_dlist_addtail(ðread_list, (EDListNode *)e); + pthread_mutex_unlock(ðread_lock); + + return e; +} + +/* close down the threads & resources etc */ +void e_thread_destroy(EThread *e) +{ + int busy = FALSE; + EMsg *msg; + struct _thread_info *info; + GList *l; + + /* make sure we soak up all the messages first */ + while ( (msg = e_msgport_get(e->server_port)) ) { + thread_destroy_msg(e, msg); + } + + pthread_mutex_lock(&e->mutex); + + switch(e->type) { + case E_THREAD_QUEUE: + case E_THREAD_DROP: + /* if we have a thread, 'kill' it */ + if (e->id != E_THREAD_NONE) { + pthread_t id = e->id; + + t(printf("Sending thread '%d' quit message\n", id)); + + e->id = E_THREAD_NONE; + + msg = g_malloc0(sizeof(*msg)); + msg->reply_port = E_THREAD_QUIT_REPLYPORT; + e_msgport_put(e->server_port, msg); + + pthread_mutex_unlock(&e->mutex); + t(printf("Joining thread '%d'\n", id)); + pthread_join(id, 0); + t(printf("Joined thread '%d'!\n", id)); + pthread_mutex_lock(&e->mutex); + } + busy = e->id != E_THREAD_NONE; + break; + case E_THREAD_NEW: + /* first, send everyone a quit message */ + l = e->id_list; + while (l) { + info = l->data; + t(printf("Sending thread '%d' quit message\n", info->id)); + msg = g_malloc0(sizeof(*msg)); + msg->reply_port = E_THREAD_QUIT_REPLYPORT; + e_msgport_put(e->server_port, msg); + l = l->next; + } + + /* then, wait for everyone to quit */ + while (e->id_list) { + info = e->id_list->data; + e->id_list = g_list_remove(e->id_list, info); + pthread_mutex_unlock(&e->mutex); + t(printf("Joining thread '%d'\n", info->id)); + pthread_join(info->id, 0); + t(printf("Joined thread '%d'!\n", info->id)); + pthread_mutex_lock(&e->mutex); + g_free(info); + } + busy = g_list_length(e->id_list) != 0; + break; + } + + pthread_mutex_unlock(&e->mutex); + + /* and clean up, if we can */ + if (busy) { + g_warning("threads were busy, leaked EThread"); + return; + } + + pthread_mutex_lock(ðread_lock); + e_dlist_remove((EDListNode *)e); + pthread_mutex_unlock(ðread_lock); + + pthread_mutex_destroy(&e->mutex); + e_msgport_destroy(e->server_port); + g_free(e); +} + +/* set the queue maximum depth, what happens when the queue + fills up depends on the queue type */ +void e_thread_set_queue_limit(EThread *e, int limit) +{ + e->queue_limit = limit; +} + +/* set a msg destroy callback, this can not call any e_thread functions on @e */ +void e_thread_set_msg_destroy(EThread *e, EThreadFunc destroy, void *data) +{ + pthread_mutex_lock(&e->mutex); + e->destroy = destroy; + e->destroy_data = data; + pthread_mutex_unlock(&e->mutex); +} + +/* set a message lost callback, called if any message is discarded */ +void e_thread_set_msg_lost(EThread *e, EThreadFunc lost, void *data) +{ + pthread_mutex_lock(&e->mutex); + e->lost = lost; + e->lost_data = lost; + pthread_mutex_unlock(&e->mutex); +} + +/* set a reply port, if set, then send messages back once finished */ +void e_thread_set_reply_port(EThread *e, EMsgPort *reply_port) +{ + e->reply_port = reply_port; +} + +/* set a received data callback */ +void e_thread_set_msg_received(EThread *e, EThreadFunc received, void *data) +{ + pthread_mutex_lock(&e->mutex); + e->received = received; + e->received_data = data; + pthread_mutex_unlock(&e->mutex); +} + +/* find out if we're busy doing any work, e==NULL, check for all work */ +int e_thread_busy(EThread *e) +{ + int busy = FALSE; + + if (e == NULL) { + pthread_mutex_lock(ðread_lock); + e = (EThread *)ethread_list.head; + while (e->next && !busy) { + busy = e_thread_busy(e); + e = e->next; + } + pthread_mutex_unlock(ðread_lock); + } else { + pthread_mutex_lock(&e->mutex); + switch (e->type) { + case E_THREAD_QUEUE: + case E_THREAD_DROP: + busy = e->waiting != 1 && e->id != E_THREAD_NONE; + break; + case E_THREAD_NEW: + busy = e->waiting != g_list_length(e->id_list); + break; + } + pthread_mutex_unlock(&e->mutex); + } + + return busy; +} + +static void +thread_destroy_msg(EThread *e, EMsg *m) +{ + EThreadFunc func; + void *func_data; + + /* we do this so we never get an incomplete/unmatched callback + data */ + pthread_mutex_lock(&e->mutex); + func = e->destroy; + func_data = e->destroy_data; + pthread_mutex_unlock(&e->mutex); + + if (func) + func(e, m, func_data); +} + +static void +thread_received_msg(EThread *e, EMsg *m) +{ + EThreadFunc func; + void *func_data; + + /* we do this so we never get an incomplete/unmatched callback + data */ + pthread_mutex_lock(&e->mutex); + func = e->received; + func_data = e->received_data; + pthread_mutex_unlock(&e->mutex); + + if (func) + func(e, m, func_data); + else + g_warning("No processing callback for EThread, message unprocessed"); +} + +static void +thread_lost_msg(EThread *e, EMsg *m) +{ + EThreadFunc func; + void *func_data; + + /* we do this so we never get an incomplete/unmatched callback + data */ + pthread_mutex_lock(&e->mutex); + func = e->lost; + func_data = e->lost_data; + pthread_mutex_unlock(&e->mutex); + + if (func) + func(e, m, func_data); +} + +/* the actual thread dispatcher */ +static void * +thread_dispatch(void *din) +{ + EThread *e = din; + EMsg *m; + struct _thread_info *info; + pthread_t self = pthread_self(); + + t(printf("dispatch thread started: %ld\n", pthread_self())); + + while (1) { + pthread_mutex_lock(&e->mutex); + m = e_msgport_get(e->server_port); + if (m == NULL) { + /* nothing to do? If we are a 'new' type thread, just quit. + Otherwise, go into waiting (can be cancelled here) */ + info = NULL; + switch (e->type) { + case E_THREAD_NEW: + case E_THREAD_QUEUE: + case E_THREAD_DROP: + info = thread_find(e, self); + if (info) + info->busy = FALSE; + e->waiting++; + pthread_mutex_unlock(&e->mutex); + e_msgport_wait(e->server_port); + pthread_mutex_lock(&e->mutex); + e->waiting--; + pthread_mutex_unlock(&e->mutex); + break; +#if 0 + case E_THREAD_NEW: + e->id_list = g_list_remove(e->id_list, (void *)pthread_self()); + pthread_mutex_unlock(&e->mutex); + return 0; +#endif + } + + continue; + } else if (m->reply_port == E_THREAD_QUIT_REPLYPORT) { + t(printf("Thread %d got quit message\n", self)); + /* Handle a quit message, say we're quitting, free the message, and break out of the loop */ + info = thread_find(e, self); + if (info) + info->busy = 2; + pthread_mutex_unlock(&e->mutex); + g_free(m); + break; + } else { + info = thread_find(e, self); + if (info) + info->busy = TRUE; + } + pthread_mutex_unlock(&e->mutex); + + t(printf("got message in dispatch thread\n")); + + /* process it */ + thread_received_msg(e, m); + + /* if we have a reply port, send it back, otherwise, lose it */ + if (m->reply_port) { + e_msgport_reply(m); + } else { + thread_destroy_msg(e, m); + } + } + + return NULL; +} + +/* send a message to the thread, start thread if necessary */ +void e_thread_put(EThread *e, EMsg *msg) +{ + pthread_t id; + EMsg *dmsg = NULL; + + pthread_mutex_lock(&e->mutex); + + /* the caller forgot to tell us what to do, well, we can't do anything can we */ + if (e->received == NULL) { + pthread_mutex_unlock(&e->mutex); + g_warning("EThread called with no receiver function, no work to do!"); + thread_destroy_msg(e, msg); + return; + } + + msg->reply_port = e->reply_port; + + switch(e->type) { + case E_THREAD_QUEUE: + /* if the queue is full, lose this new addition */ + if (e_dlist_length(&e->server_port->queue) < e->queue_limit) { + e_msgport_put(e->server_port, msg); + } else { + printf("queue limit reached, dropping new message\n"); + dmsg = msg; + } + break; + case E_THREAD_DROP: + /* if the queue is full, lose the oldest (unprocessed) message */ + if (e_dlist_length(&e->server_port->queue) < e->queue_limit) { + e_msgport_put(e->server_port, msg); + } else { + printf("queue limit reached, dropping old message\n"); + e_msgport_put(e->server_port, msg); + dmsg = e_msgport_get(e->server_port); + } + break; + case E_THREAD_NEW: + /* it is possible that an existing thread can catch this message, so + we might create a thread with no work to do. + but that doesn't matter, the other alternative that it be lost is worse */ + e_msgport_put(e->server_port, msg); + if (e->waiting == 0 + && g_list_length(e->id_list) < e->queue_limit + && pthread_create(&id, NULL, thread_dispatch, e) == 0) { + struct _thread_info *info = g_malloc0(sizeof(*info)); + t(printf("created NEW thread %ld\n", id)); + info->id = id; + info->busy = TRUE; + e->id_list = g_list_append(e->id_list, info); + } + pthread_mutex_unlock(&e->mutex); + return; + } + + /* create the thread, if there is none to receive it yet */ + if (e->id == E_THREAD_NONE) { + int err; + + if ((err = pthread_create(&e->id, NULL, thread_dispatch, e)) != 0) { + g_warning("Could not create dispatcher thread, message queued?: %s", strerror(err)); + e->id = E_THREAD_NONE; + } + } + + pthread_mutex_unlock(&e->mutex); + + if (dmsg) { + thread_lost_msg(e, dmsg); + thread_destroy_msg(e, dmsg); + } +} + +/* yet-another-mutex interface */ +struct _EMutex { + int type; + pthread_t owner; + short waiters; + short depth; + pthread_mutex_t mutex; + pthread_cond_t cond; +}; + +/* sigh, this is just painful to have to need, but recursive + read/write, etc mutexes just aren't very common in thread + implementations */ +/* TODO: Just make it use recursive mutexes if they are available */ +EMutex *e_mutex_new(e_mutex_t type) +{ + struct _EMutex *m; + + m = g_malloc(sizeof(*m)); + m->type = type; + m->waiters = 0; + m->depth = 0; + m->owner = E_THREAD_NONE; + + switch (type) { + case E_MUTEX_SIMPLE: + pthread_mutex_init(&m->mutex, 0); + break; + case E_MUTEX_REC: + pthread_mutex_init(&m->mutex, 0); + pthread_cond_init(&m->cond, 0); + break; + /* read / write ? flags for same? */ + } + + return m; +} + +int e_mutex_destroy(EMutex *m) +{ + int ret = 0; + + switch (m->type) { + case E_MUTEX_SIMPLE: + ret = pthread_mutex_destroy(&m->mutex); + if (ret == -1) + g_warning("EMutex destroy failed: %s", strerror(errno)); + g_free(m); + break; + case E_MUTEX_REC: + ret = pthread_mutex_destroy(&m->mutex); + if (ret == -1) + g_warning("EMutex destroy failed: %s", strerror(errno)); + ret = pthread_cond_destroy(&m->cond); + if (ret == -1) + g_warning("EMutex destroy failed: %s", strerror(errno)); + g_free(m); + + } + return ret; +} + +int e_mutex_lock(EMutex *m) +{ + pthread_t id; + int err; + + switch (m->type) { + case E_MUTEX_SIMPLE: + return pthread_mutex_lock(&m->mutex); + case E_MUTEX_REC: + id = pthread_self(); + if ((err = pthread_mutex_lock(&m->mutex)) != 0) + return err; + while (1) { + if (m->owner == E_THREAD_NONE) { + m->owner = id; + m->depth = 1; + break; + } else if (id == m->owner) { + m->depth++; + break; + } else { + m->waiters++; + if ((err = pthread_cond_wait(&m->cond, &m->mutex)) != 0) + return err; + m->waiters--; + } + } + return pthread_mutex_unlock(&m->mutex); + } + + return EINVAL; +} + +int e_mutex_unlock(EMutex *m) +{ + int err; + + switch (m->type) { + case E_MUTEX_SIMPLE: + return pthread_mutex_unlock(&m->mutex); + case E_MUTEX_REC: + if ((err = pthread_mutex_lock(&m->mutex)) != 0) + return err; + g_assert(m->owner == pthread_self()); + + m->depth--; + if (m->depth == 0) { + m->owner = E_THREAD_NONE; + if (m->waiters > 0) + pthread_cond_signal(&m->cond); + } + return pthread_mutex_unlock(&m->mutex); + } + + errno = EINVAL; + return -1; +} + +void e_mutex_assert_locked(EMutex *m) +{ + g_return_if_fail (m->type == E_MUTEX_REC); + pthread_mutex_lock(&m->mutex); + g_assert(m->owner == pthread_self()); + pthread_mutex_unlock(&m->mutex); +} + +int e_mutex_cond_wait(void *vcond, EMutex *m) +{ + int ret; + pthread_cond_t *cond = vcond; + + switch(m->type) { + case E_MUTEX_SIMPLE: + return pthread_cond_wait(cond, &m->mutex); + case E_MUTEX_REC: + if ((ret = pthread_mutex_lock(&m->mutex)) != 0) + return ret; + g_assert(m->owner == pthread_self()); + ret = pthread_cond_wait(cond, &m->mutex); + g_assert(m->owner == pthread_self()); + pthread_mutex_unlock(&m->mutex); + return ret; + default: + g_return_val_if_reached(-1); + } +} + +#ifdef STANDALONE +EMsgPort *server_port; + + +void *fdserver(void *data) +{ + int fd; + EMsg *msg; + int id = (int)data; + fd_set rfds; + + fd = e_msgport_fd(server_port); + + while (1) { + int count = 0; + + printf("server %d: waiting on fd %d\n", id, fd); + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + select(fd+1, &rfds, NULL, NULL, NULL); + printf("server %d: Got async notification, checking for messages\n", id); + while ((msg = e_msgport_get(server_port))) { + printf("server %d: got message\n", id); + sleep(1); + printf("server %d: replying\n", id); + e_msgport_reply(msg); + count++; + } + printf("server %d: got %d messages\n", id, count); + } +} + +void *server(void *data) +{ + EMsg *msg; + int id = (int)data; + + while (1) { + printf("server %d: waiting\n", id); + msg = e_msgport_wait(server_port); + msg = e_msgport_get(server_port); + if (msg) { + printf("server %d: got message\n", id); + sleep(1); + printf("server %d: replying\n", id); + e_msgport_reply(msg); + } else { + printf("server %d: didn't get message\n", id); + } + } +} + +void *client(void *data) +{ + EMsg *msg; + EMsgPort *replyport; + int i; + + replyport = e_msgport_new(); + msg = g_malloc0(sizeof(*msg)); + msg->reply_port = replyport; + for (i=0;i<10;i++) { + /* synchronous operation */ + printf("client: sending\n"); + e_msgport_put(server_port, msg); + printf("client: waiting for reply\n"); + e_msgport_wait(replyport); + e_msgport_get(replyport); + printf("client: got reply\n"); + } + + printf("client: sleeping ...\n"); + sleep(2); + printf("client: sending multiple\n"); + + for (i=0;i<10;i++) { + msg = g_malloc0(sizeof(*msg)); + msg->reply_port = replyport; + e_msgport_put(server_port, msg); + } + + printf("client: receiving multiple\n"); + for (i=0;i<10;i++) { + e_msgport_wait(replyport); + msg = e_msgport_get(replyport); + g_free(msg); + } + + printf("client: done\n"); +} + +int main(int argc, char **argv) +{ + pthread_t serverid, clientid; + + g_thread_init(NULL); + + server_port = e_msgport_new(); + + /*pthread_create(&serverid, NULL, server, (void *)1);*/ + pthread_create(&serverid, NULL, fdserver, (void *)1); + pthread_create(&clientid, NULL, client, NULL); + + sleep(60); + + return 0; +} +#endif diff --git a/libedataserver/e-msgport.h b/libedataserver/e-msgport.h new file mode 100644 index 0000000..6347564 --- /dev/null +++ b/libedataserver/e-msgport.h @@ -0,0 +1,111 @@ + +#ifndef _E_MSGPORT_H +#define _E_MSGPORT_H + +#include +#include + +/* double-linked list yeah another one, deal */ +typedef struct _EDListNode { + struct _EDListNode *next; + struct _EDListNode *prev; +} EDListNode; + +typedef struct _EDList { + struct _EDListNode *head; + struct _EDListNode *tail; + struct _EDListNode *tailpred; +} EDList; + +#define E_DLIST_INITIALISER(l) { (EDListNode *)&l.tail, 0, (EDListNode *)&l.head } + +void e_dlist_init(EDList *v); +EDListNode *e_dlist_addhead(EDList *l, EDListNode *n); +EDListNode *e_dlist_addtail(EDList *l, EDListNode *n); +EDListNode *e_dlist_remove(EDListNode *n); +EDListNode *e_dlist_remhead(EDList *l); +EDListNode *e_dlist_remtail(EDList *l); +int e_dlist_empty(EDList *l); +int e_dlist_length(EDList *l); + +/* a time-based cache */ +typedef struct _EMCache EMCache; +typedef struct _EMCacheNode EMCacheNode; + +/* subclass this for your data nodes, EMCache is opaque */ +struct _EMCacheNode { + struct _EMCacheNode *next, *prev; + char *key; + int ref_count; + time_t stamp; +}; + +EMCache *em_cache_new(time_t timeout, size_t nodesize, GFreeFunc nodefree); +void em_cache_destroy(EMCache *emc); +EMCacheNode *em_cache_lookup(EMCache *emc, const char *key); +EMCacheNode *em_cache_node_new(EMCache *emc, const char *key); +void em_cache_node_unref(EMCache *emc, EMCacheNode *n); +void em_cache_add(EMCache *emc, EMCacheNode *n); +void em_cache_clear(EMCache *emc); + +/* message ports - a simple inter-thread 'ipc' primitive */ +/* opaque handle */ +typedef struct _EMsgPort EMsgPort; + +/* header for any message */ +typedef struct _EMsg { + EDListNode ln; + EMsgPort *reply_port; +} EMsg; + +EMsgPort *e_msgport_new(void); +void e_msgport_destroy(EMsgPort *mp); +/* get a fd that can be used to wait on the port asynchronously */ +int e_msgport_fd(EMsgPort *mp); +void e_msgport_put(EMsgPort *mp, EMsg *msg); +EMsg *e_msgport_wait(EMsgPort *mp); +EMsg *e_msgport_get(EMsgPort *mp); +void e_msgport_reply(EMsg *msg); +#ifdef HAVE_NSS +struct PRFileDesc *e_msgport_prfd(EMsgPort *mp); +#endif + +/* e threads, a server thread with a message based request-response, and flexible queuing */ +typedef struct _EThread EThread; + +typedef enum { + E_THREAD_QUEUE = 0, /* run one by one, until done, if the queue_limit is reached, discard new request */ + E_THREAD_DROP, /* run one by one, until done, if the queue_limit is reached, discard oldest requests */ + E_THREAD_NEW, /* always run in a new thread, if the queue limit is reached, new requests are + stored in the queue until a thread becomes available for it, creating a thread pool */ +} e_thread_t; + +typedef void (*EThreadFunc)(EThread *, EMsg *, void *data); + +EThread *e_thread_new(e_thread_t type); +void e_thread_destroy(EThread *e); +void e_thread_set_queue_limit(EThread *e, int limit); +void e_thread_set_msg_lost(EThread *e, EThreadFunc destroy, void *data); +void e_thread_set_msg_destroy(EThread *e, EThreadFunc destroy, void *data); +void e_thread_set_reply_port(EThread *e, EMsgPort *reply_port); +void e_thread_set_msg_received(EThread *e, EThreadFunc received, void *data); +void e_thread_put(EThread *e, EMsg *msg); +int e_thread_busy(EThread *e); + +/* sigh, another mutex interface, this one allows different mutex types, portably */ +typedef struct _EMutex EMutex; + +typedef enum _e_mutex_t { + E_MUTEX_SIMPLE, /* == pthread_mutex */ + E_MUTEX_REC, /* recursive mutex */ +} e_mutex_t; + +EMutex *e_mutex_new(e_mutex_t type); +int e_mutex_destroy(EMutex *m); +int e_mutex_lock(EMutex *m); +int e_mutex_unlock(EMutex *m); +void e_mutex_assert_locked(EMutex *m); +/* this uses pthread cond's */ +int e_mutex_cond_wait(void *cond, EMutex *m); + +#endif diff --git a/libedataserver/e-sexp.c b/libedataserver/e-sexp.c index 3e6e661..724859f 100644 --- a/libedataserver/e-sexp.c +++ b/libedataserver/e-sexp.c @@ -701,7 +701,7 @@ e_sexp_term_eval(struct _ESExp *f, struct _ESExpTerm *t) break; case ESEXP_TERM_FUNC: /* first evaluate all arguments to result types */ - argv = g_alloca(sizeof(argv[0]) * t->value.func.termcount); + argv = alloca(sizeof(argv[0]) * t->value.func.termcount); for (i=0;ivalue.func.termcount;i++) { argv[i] = e_sexp_term_eval(f, t->value.func.terms[i]); } @@ -903,7 +903,7 @@ parse_value(ESExp *f) p(printf("got brace, its a list!\n")); return parse_list(f, TRUE); case G_TOKEN_STRING: - p(printf("got string\n")); + p(printf("got string '%s'\n", g_scanner_cur_value(gs).v_string)); t = parse_term_new(f, ESEXP_TERM_STRING); t->value.string = g_strdup(g_scanner_cur_value(gs).v_string); break; @@ -922,7 +922,7 @@ parse_value(ESExp *f) t->value.number = g_scanner_cur_value(gs).v_int; if (negative) t->value.number = -t->value.number; - p(printf("got int\n")); + p(printf("got int %d\n", t->value.number)); break; case '#': { char *str; @@ -947,6 +947,7 @@ parse_value(ESExp *f) break; } case G_TOKEN_SYMBOL: s = g_scanner_cur_value(gs).v_symbol; + p(printf("got symbol '%s'\n", s->name)); switch (s->type) { case ESEXP_TERM_FUNC: case ESEXP_TERM_IFUNC: @@ -965,6 +966,7 @@ parse_value(ESExp *f) } break; case G_TOKEN_IDENTIFIER: + p(printf("got unknown identifider '%s'\n", g_scanner_cur_value(gs).v_identifier)); e_sexp_fatal_error(f, "Unknown identifier: %s", g_scanner_cur_value(gs).v_identifier); break; default: diff --git a/libedataserver/e-time-utils.c b/libedataserver/e-time-utils.c new file mode 100644 index 0000000..4666b80 --- /dev/null +++ b/libedataserver/e-time-utils.c @@ -0,0 +1,490 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Time utility functions + * + * Author: + * Damon Chaplin (damon@ximian.com) + * + * (C) 2001 Ximian, Inc. + */ + +#include + +#ifdef __linux__ +/* We need this to get a prototype for strptime. */ +#define _GNU_SOURCE +#endif /* __linux__ */ + +#include +#include + +#ifdef __linux__ +#undef _GNU_SOURCE +#endif /* __linux__ */ + +#include +#include +#include +#include +#include "e-time-utils.h" +#include "e-util.h" + +/* Returns whether a string is NULL, empty, or full of whitespace */ +static gboolean +string_is_empty (const char *value) +{ + const char *p; + gboolean empty = TRUE; + + if (value) { + p = value; + while (*p) { + if (!isspace (*p)) { + empty = FALSE; + break; + } + p++; + } + } + return empty; +} + + +/* Takes a number of format strings for strptime() and attempts to parse a + * string with them. + */ +static ETimeParseStatus +parse_with_strptime (const char *value, struct tm *result, const char **formats, int n_formats) +{ + const char *parse_end = NULL, *pos; + gchar *locale_str; + gchar *format_str; + ETimeParseStatus parse_ret; + gboolean parsed = FALSE; + int i; + + if (string_is_empty (value)) { + memset (result, 0, sizeof (*result)); + result->tm_isdst = -1; + return E_TIME_PARSE_NONE; + } + + locale_str = g_locale_from_utf8(value, strlen(value), NULL, NULL, NULL); + + pos = (const char *) locale_str; + + /* Skip whitespace */ + while (isspace (*pos)) + pos++; + + /* Try each of the formats in turn */ + + for (i = 0; i < n_formats; i++) { + memset (result, 0, sizeof (*result)); + format_str = g_locale_from_utf8(formats[i], strlen(formats[i]), NULL, NULL, NULL); + parse_end = strptime (pos, format_str, result); + g_free (format_str); + if (parse_end) { + parsed = TRUE; + break; + } + } + + result->tm_isdst = -1; + + parse_ret = E_TIME_PARSE_INVALID; + + /* If we parsed something, make sure we parsed the entire string. */ + if (parsed) { + /* Skip whitespace */ + while (isspace (*parse_end)) + parse_end++; + + if (*parse_end == '\0') + parse_ret = E_TIME_PARSE_OK; + } + + g_free (locale_str); + + return (parse_ret); + +} + + +/* Returns TRUE if the locale has 'am' and 'pm' strings defined, in which + case the user can choose between 12 and 24-hour time formats. */ +static gboolean +locale_supports_12_hour_format (void) +{ + struct tm tmp_tm = { 0 }; + char s[16]; + + e_utf8_strftime (s, sizeof (s), "%p", &tmp_tm); + return s[0] != '\0'; +} + + +/* + * Parses a string containing a date and a time. The date is expected to be + * in a format something like "Wed 3/13/00 14:20:00", though we use gettext + * to support the appropriate local formats and we try to accept slightly + * different formats, e.g. the weekday can be skipped and we can accept 12-hour + * formats with an am/pm string. + * + * Returns E_TIME_PARSE_OK if it could not be parsed, E_TIME_PARSE_NONE if it + * was empty, or E_TIME_PARSE_INVALID if it couldn't be parsed. + */ +ETimeParseStatus +e_time_parse_date_and_time (const char *value, + struct tm *result) +{ + struct tm *today_tm; + time_t t; + const char *format[16]; + int num_formats = 0; + gboolean use_12_hour_formats = locale_supports_12_hour_format (); + ETimeParseStatus status; + + if (string_is_empty (value)) { + memset (result, 0, sizeof (*result)); + result->tm_isdst = -1; + return E_TIME_PARSE_NONE; + } + + /* We'll parse the whole date and time in one go, otherwise we get + into i18n problems. We attempt to parse with several formats, + longest first. Note that we only use the '%p' specifier if the + locale actually has 'am' and 'pm' strings defined, otherwise we + will get incorrect results. Note also that we try to use exactly + the same strings as in e_time_format_date_and_time(), to try to + avoid i18n problems. We also use cut-down versions, so users don't + have to type in the weekday or the seconds, for example. + Note that all these formats include the full date, and the time + will be set to 00:00:00 before parsing, so we don't need to worry + about filling in any missing fields after parsing. */ + + /* + * Try the full times, with the weekday. Then try without seconds, + * and without minutes, and finally with no time at all. + */ + if (use_12_hour_formats) { + /* strptime format of a weekday, a date and a time, + in 12-hour format. */ + format[num_formats++] = _("%a %m/%d/%Y %I:%M:%S %p"); + } + + /* strptime format of a weekday, a date and a time, + in 24-hour format. */ + format[num_formats++] = _("%a %m/%d/%Y %H:%M:%S"); + + if (use_12_hour_formats) { + /* strptime format of a weekday, a date and a time, + in 12-hour format, without seconds. */ + format[num_formats++] = _("%a %m/%d/%Y %I:%M %p"); + } + + /* strptime format of a weekday, a date and a time, + in 24-hour format, without seconds. */ + format[num_formats++] = _("%a %m/%d/%Y %H:%M"); + + if (use_12_hour_formats) { + /* strptime format of a weekday, a date and a time, + in 12-hour format, without minutes or seconds. */ + format[num_formats++] = _("%a %m/%d/%Y %I %p"); + } + + /* strptime format of a weekday, a date and a time, + in 24-hour format, without minutes or seconds. */ + format[num_formats++] = _("%a %m/%d/%Y %H"); + + /* strptime format of a weekday and a date. */ + format[num_formats++] = _("%a %m/%d/%Y"); + + + /* + * Now try all the above formats again, but without the weekday. + */ + if (use_12_hour_formats) { + /* strptime format of a date and a time, in 12-hour format. */ + format[num_formats++] = _("%m/%d/%Y %I:%M:%S %p"); + } + + /* strptime format of a date and a time, in 24-hour format. */ + format[num_formats++] = _("%m/%d/%Y %H:%M:%S"); + + if (use_12_hour_formats) { + /* strptime format of a date and a time, in 12-hour format, + without seconds. */ + format[num_formats++] = _("%m/%d/%Y %I:%M %p"); + } + + /* strptime format of a date and a time, in 24-hour format, + without seconds. */ + format[num_formats++] = _("%m/%d/%Y %H:%M"); + + if (use_12_hour_formats) { + /* strptime format of a date and a time, in 12-hour format, + without minutes or seconds. */ + format[num_formats++] = _("%m/%d/%Y %I %p"); + } + + /* strptime format of a date and a time, in 24-hour format, + without minutes or seconds. */ + format[num_formats++] = _("%m/%d/%Y %H"); + + /* strptime format of a weekday and a date. */ + format[num_formats++] = _("%m/%d/%Y"); + + + status = parse_with_strptime (value, result, format, num_formats); + /* Note that we checked if it was empty already, so it is either OK + or INVALID here. */ + if (status == E_TIME_PARSE_OK) { + /* If a 2-digit year was used we use the current century. */ + if (result->tm_year < 0) { + t = time (NULL); + today_tm = localtime (&t); + + /* This should convert it into a value from 0 to 99. */ + result->tm_year += 1900; + + /* Now add on the century. */ + result->tm_year += today_tm->tm_year + - (today_tm->tm_year % 100); + } + } else { + /* Now we try to just parse a time, assuming the current day.*/ + status = e_time_parse_time (value, result); + if (status == E_TIME_PARSE_OK) { + /* We fill in the current day. */ + t = time (NULL); + today_tm = localtime (&t); + result->tm_mday = today_tm->tm_mday; + result->tm_mon = today_tm->tm_mon; + result->tm_year = today_tm->tm_year; + } + } + + return status; +} + +/** + * e_time_parse_date: + * @value: A date string. + * @result: Return value for the parsed date. + * + * Takes in a date string entered by the user and tries to convert it to + * a struct tm. + * + * Return value: Result code indicating whether the @value was an empty + * string, a valid date, or an invalid date. + **/ +ETimeParseStatus +e_time_parse_date (const char *value, struct tm *result) +{ + const char *format[2]; + struct tm *today_tm; + time_t t; + ETimeParseStatus status; + + g_return_val_if_fail (value != NULL, E_TIME_PARSE_INVALID); + g_return_val_if_fail (result != NULL, E_TIME_PARSE_INVALID); + + /* strptime format of a weekday and a date. */ + format[0] = _("%a %m/%d/%Y"); + + /* This is the preferred date format for the locale. */ + format[1] = _("%m/%d/%Y"); + + status = parse_with_strptime (value, result, format, sizeof (format) / sizeof (format[0])); + if (status == E_TIME_PARSE_OK) { + /* If a 2-digit year was used we use the current century. */ + if (result->tm_year < 0) { + t = time (NULL); + today_tm = localtime (&t); + + /* This should convert it into a value from 0 to 99. */ + result->tm_year += 1900; + + /* Now add on the century. */ + result->tm_year += today_tm->tm_year + - (today_tm->tm_year % 100); + } + } + + return status; +} + + +/* + * Parses a string containing a time. It is expected to be in a format + * something like "14:20:00", though we use gettext to support the appropriate + * local formats and we try to accept slightly different formats, e.g. we can + * accept 12-hour formats with an am/pm string. + * + * Returns E_TIME_PARSE_OK if it could not be parsed, E_TIME_PARSE_NONE if it + * was empty, or E_TIME_PARSE_INVALID if it couldn't be parsed. + */ +ETimeParseStatus +e_time_parse_time (const char *value, struct tm *result) +{ + const char *format[6]; + int num_formats = 0; + gboolean use_12_hour_formats = locale_supports_12_hour_format (); + + if (use_12_hour_formats) { + /* strptime format for a time of day, in 12-hour format. */ + format[num_formats++] = _("%I:%M:%S %p"); + } + + /* strptime format for a time of day, in 24-hour format. */ + format[num_formats++] = _("%H:%M:%S"); + + if (use_12_hour_formats) { + /* strptime format for time of day, without seconds, + in 12-hour format. */ + format[num_formats++] = _("%I:%M %p"); + } + + /* strptime format for time of day, without seconds 24-hour format. */ + format[num_formats++] = _("%H:%M"); + + if (use_12_hour_formats) { + /* strptime format for hour and AM/PM, 12-hour format. */ + format[num_formats++] = _("%I %p"); + } + + /* strptime format for hour, 24-hour format. */ + format[num_formats++] = "%H"; + + return parse_with_strptime (value, result, format, num_formats); +} + + +/* Creates a string representation of a time value and stores it in buffer. + buffer_size should be about 64 to be safe. If show_midnight is FALSE, and + the time is midnight, then we just show the date. If show_zero_seconds + is FALSE, then if the time has zero seconds only the hour and minute are + shown. */ +void +e_time_format_date_and_time (struct tm *date_tm, + gboolean use_24_hour_format, + gboolean show_midnight, + gboolean show_zero_seconds, + char *buffer, + int buffer_size) +{ + char *format; + + if (!show_midnight && date_tm->tm_hour == 0 + && date_tm->tm_min == 0 && date_tm->tm_sec == 0) { + /* strftime format of a weekday and a date. */ + format = _("%a %m/%d/%Y"); + } else if (use_24_hour_format) { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a weekday, a date and a + time, in 24-hour format, without seconds. */ + format = _("%a %m/%d/%Y %H:%M"); + else + /* strftime format of a weekday, a date and a + time, in 24-hour format. */ + format = _("%a %m/%d/%Y %H:%M:%S"); + } else { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a weekday, a date and a + time, in 12-hour format, without seconds. */ + format = _("%a %m/%d/%Y %I:%M %p"); + else + /* strftime format of a weekday, a date and a + time, in 12-hour format. */ + format = _("%a %m/%d/%Y %I:%M:%S %p"); + } + + /* strftime returns 0 if the string doesn't fit, and leaves the buffer + undefined, so we set it to the empty string in that case. */ + if (e_utf8_strftime (buffer, buffer_size, format, date_tm) == 0) + buffer[0] = '\0'; +} + + +/* Creates a string representation of a time value and stores it in buffer. + buffer_size should be about 64 to be safe. */ +void +e_time_format_time (struct tm *date_tm, + gboolean use_24_hour_format, + gboolean show_zero_seconds, + char *buffer, + int buffer_size) +{ + char *format; + + if (use_24_hour_format) { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a time in 24-hour format, + without seconds. */ + format = _("%H:%M"); + else + /* strftime format of a time in 24-hour format. */ + format = _("%H:%M:%S"); + } else { + if (!show_zero_seconds && date_tm->tm_sec == 0) + /* strftime format of a time in 12-hour format, + without seconds. */ + format = _("%I:%M %p"); + else + /* strftime format of a time in 12-hour format. */ + format = _("%I:%M:%S %p"); + } + + /* strftime returns 0 if the string doesn't fit, and leaves the buffer + undefined, so we set it to the empty string in that case. */ + if (e_utf8_strftime (buffer, buffer_size, format, date_tm) == 0) + buffer[0] = '\0'; +} + + +/* Like mktime(3), but assumes UTC instead of local timezone. */ +time_t +e_mktime_utc (struct tm *tm) +{ + time_t tt; + + tm->tm_isdst = -1; + tt = mktime (tm); + +#if defined (HAVE_TM_GMTOFF) + tt += tm->tm_gmtoff; +#elif defined (HAVE_TIMEZONE) + if (tm->tm_isdst > 0) { + #if defined (HAVE_ALTZONE) + tt -= altzone; + #else /* !defined (HAVE_ALTZONE) */ + tt -= (timezone - 3600); + #endif + } else + tt -= timezone; +#endif + + return tt; +} + +/* Like localtime_r(3), but also returns an offset in seconds after UTC. + (Calling gmtime with tt + offset would generate the same tm) */ +void +e_localtime_with_offset (time_t tt, struct tm *tm, int *offset) +{ + localtime_r (&tt, tm); + +#if defined (HAVE_TM_GMTOFF) + *offset = tm->tm_gmtoff; +#elif defined (HAVE_TIMEZONE) + if (tm->tm_isdst > 0) { + #if defined (HAVE_ALTZONE) + *offset = -altzone; + #else /* !defined (HAVE_ALTZONE) */ + *offset = -(timezone - 3600); + #endif + } else + *offset = -timezone; +#endif +} diff --git a/libedataserver/e-time-utils.h b/libedataserver/e-time-utils.h new file mode 100644 index 0000000..0b081da --- /dev/null +++ b/libedataserver/e-time-utils.h @@ -0,0 +1,58 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Time utility functions + * + * Author: + * Damon Chaplin (damon@ximian.com) + * + * (C) 2001 Ximian, Inc. + */ + +#ifndef E_TIME_UTILS +#define E_TIME_UTILS + +#include +#include + +typedef enum { + E_TIME_PARSE_OK, + E_TIME_PARSE_NONE, + E_TIME_PARSE_INVALID +} ETimeParseStatus; + +/* Tries to parse a string containing a date and time. */ +ETimeParseStatus e_time_parse_date_and_time (const char *value, + struct tm *result); + +/* Tries to parse a string containing a date. */ +ETimeParseStatus e_time_parse_date (const char *value, + struct tm *result); + +/* Tries to parse a string containing a time. */ +ETimeParseStatus e_time_parse_time (const char *value, + struct tm *result); + +/* Turns a struct tm into a string like "Wed 3/12/00 12:00:00 AM". */ +void e_time_format_date_and_time (struct tm *date_tm, + gboolean use_24_hour_format, + gboolean show_midnight, + gboolean show_zero_seconds, + char *buffer, + int buffer_size); + +/* Formats a time from a struct tm, e.g. "01:59 PM". */ +void e_time_format_time (struct tm *date_tm, + gboolean use_24_hour_format, + gboolean show_zero_seconds, + char *buffer, + int buffer_size); + + +/* Like mktime(3), but assumes UTC instead of local timezone. */ +time_t e_mktime_utc (struct tm *timeptr); + +/* Like localtime_r(3), but also returns an offset in minutes after UTC. + (Calling gmtime with tt + offset would generate the same tm) */ +void e_localtime_with_offset (time_t tt, struct tm *tm, int *offset); + +#endif /* E_TIME_UTILS */ diff --git a/libedataserver/e-trie.c b/libedataserver/e-trie.c new file mode 100644 index 0000000..2edf94d --- /dev/null +++ b/libedataserver/e-trie.c @@ -0,0 +1,345 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast + * + * Copyright 2002 Ximian, Inc. (www.ximian.com) + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "e-trie.h" +#include "e-memory.h" + +#define d(x) + +struct _trie_state { + struct _trie_state *next; + struct _trie_state *fail; + struct _trie_match *match; + unsigned int final; + int id; +}; + +struct _trie_match { + struct _trie_match *next; + struct _trie_state *state; + gunichar c; +}; + +struct _ETrie { + struct _trie_state root; + GPtrArray *fail_states; + gboolean icase; + + EMemChunk *match_chunks; + EMemChunk *state_chunks; +}; + + +static inline gunichar +trie_utf8_getc (const unsigned char **in, size_t inlen) +{ + register const unsigned char *inptr = *in; + const unsigned char *inend = inptr + inlen; + register unsigned char c, r; + register gunichar u, m; + + if (inlen == 0) + return 0; + + r = *inptr++; + if (r < 0x80) { + *in = inptr; + u = r; + } else if (r < 0xfe) { /* valid start char? */ + u = r; + m = 0x7f80; /* used to mask out the length bits */ + do { + if (inptr >= inend) + return 0; + + c = *inptr++; + if ((c & 0xc0) != 0x80) + goto error; + + u = (u << 6) | (c & 0x3f); + r <<= 1; + m <<= 5; + } while (r & 0x40); + + *in = inptr; + + u &= ~m; + } else { + error: + *in = (*in)+1; + u = 0xfffe; + } + + return u; +} + + +ETrie * +e_trie_new (gboolean icase) +{ + ETrie *trie; + + trie = g_new (ETrie, 1); + trie->root.next = NULL; + trie->root.fail = NULL; + trie->root.match = NULL; + trie->root.final = 0; + + trie->fail_states = g_ptr_array_sized_new (8); + trie->icase = icase; + + trie->match_chunks = e_memchunk_new (8, sizeof (struct _trie_match)); + trie->state_chunks = e_memchunk_new (8, sizeof (struct _trie_state)); + + return trie; +} + +void +e_trie_free (ETrie *trie) +{ + g_ptr_array_free (trie->fail_states, TRUE); + e_memchunk_destroy (trie->match_chunks); + e_memchunk_destroy (trie->state_chunks); + g_free (trie); +} + + + +static struct _trie_match * +g (struct _trie_state *s, gunichar c) +{ + struct _trie_match *m = s->match; + + while (m && m->c != c) + m = m->next; + + return m; +} + +static struct _trie_state * +trie_insert (ETrie *trie, int depth, struct _trie_state *q, gunichar c) +{ + struct _trie_match *m; + + m = e_memchunk_alloc (trie->match_chunks); + m->next = q->match; + m->c = c; + + q->match = m; + q = m->state = e_memchunk_alloc (trie->state_chunks); + q->match = NULL; + q->fail = &trie->root; + q->final = 0; + q->id = -1; + + if (trie->fail_states->len < depth + 1) { + unsigned int size = trie->fail_states->len; + + size = MAX (size + 64, depth + 1); + g_ptr_array_set_size (trie->fail_states, size); + } + + q->next = trie->fail_states->pdata[depth]; + trie->fail_states->pdata[depth] = q; + + return q; +} + + +#if 1 +static void +dump_trie (struct _trie_state *s, int depth) +{ + char *p = g_alloca ((depth * 2) + 1); + struct _trie_match *m; + + memset (p, ' ', depth * 2); + p[depth * 2] = '\0'; + + fprintf (stderr, "%s[state] %p: final=%d; pattern-id=%d; fail=%p\n", + p, s, s->final, s->id, s->fail); + m = s->match; + while (m) { + fprintf (stderr, " %s'%c' -> %p\n", p, m->c, m->state); + if (m->state) + dump_trie (m->state, depth + 1); + + m = m->next; + } +} +#endif + + +/* + * final = empty set + * FOR p = 1 TO #pat + * q = root + * FOR j = 1 TO m[p] + * IF g(q, pat[p][j]) == null + * insert(q, pat[p][j]) + * ENDIF + * q = g(q, pat[p][j]) + * ENDFOR + * final = union(final, q) + * ENDFOR +*/ + +void +e_trie_add (ETrie *trie, const char *pattern, int pattern_id) +{ + const unsigned char *inptr = (const unsigned char *) pattern; + struct _trie_state *q, *q1, *r; + struct _trie_match *m, *n; + int i, depth = 0; + gunichar c; + + /* Step 1: add the pattern to the trie */ + + q = &trie->root; + + while ((c = trie_utf8_getc (&inptr, -1))) { + if (trie->icase) + c = g_unichar_tolower (c); + + m = g (q, c); + if (m == NULL) { + q = trie_insert (trie, depth, q, c); + } else { + q = m->state; + } + + depth++; + } + + q->final = depth; + q->id = pattern_id; + + /* Step 2: compute failure graph */ + + for (i = 0; i < trie->fail_states->len; i++) { + q = trie->fail_states->pdata[i]; + while (q) { + m = q->match; + while (m) { + c = m->c; + q1 = m->state; + r = q->fail; + while (r && (n = g (r, c)) == NULL) + r = r->fail; + + if (r != NULL) { + q1->fail = n->state; + if (q1->fail->final > q1->final) + q1->final = q1->fail->final; + } else { + if ((n = g (&trie->root, c))) + q1->fail = n->state; + else + q1->fail = &trie->root; + } + + m = m->next; + } + + q = q->next; + } + } + + d(fprintf (stderr, "\nafter adding pattern '%s' to trie %p:\n", pattern, trie)); + d(dump_trie (&trie->root, 0)); +} + +/* + * Aho-Corasick + * + * q = root + * FOR i = 1 TO n + * WHILE q != fail AND g(q, text[i]) == fail + * q = h(q) + * ENDWHILE + * IF q == fail + * q = root + * ELSE + * q = g(q, text[i]) + * ENDIF + * IF isElement(q, final) + * RETURN TRUE + * ENDIF + * ENDFOR + * RETURN FALSE + */ + +const char * +e_trie_search (ETrie *trie, const char *buffer, size_t buflen, int *matched_id) +{ + const unsigned char *inptr, *inend, *prev, *pat; + register size_t inlen = buflen; + struct _trie_state *q; + struct _trie_match *m; + gunichar c; + + inptr = (const unsigned char *) buffer; + inend = inptr + buflen; + + q = &trie->root; + pat = prev = inptr; + while ((c = trie_utf8_getc (&inptr, inlen))) { + inlen = (inend - inptr); + + if (c != 0xfffe) { + if (trie->icase) + c = g_unichar_tolower (c); + + while (q != NULL && (m = g (q, c)) == NULL) + q = q->fail; + + if (q == &trie->root) + pat = prev; + + if (q == NULL) { + q = &trie->root; + pat = inptr; + } else if (m != NULL) { + q = m->state; + + if (q->final) { + if (matched_id) + *matched_id = q->id; + + return (const char *) pat; + } + } + } + + prev = inptr; + } + + return NULL; +} diff --git a/libedataserver/e-trie.h b/libedataserver/e-trie.h new file mode 100644 index 0000000..cabf5fc --- /dev/null +++ b/libedataserver/e-trie.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Jeffrey Stedfast + * + * Copyright 2002 Ximian, Inc. (www.ximian.com) + * + * 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 Street #330, Boston, MA 02111-1307, USA. + * + */ + + +#ifndef __E_TRIE_H__ +#define __E_TRIE_H__ + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#include + +typedef struct _ETrie ETrie; + +ETrie *e_trie_new (gboolean icase); +void e_trie_free (ETrie *trie); + +void e_trie_add (ETrie *trie, const char *pattern, int pattern_id); + +const char *e_trie_search (ETrie *trie, const char *buffer, size_t buflen, int *matched_id); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __E_TRIE_H__ */ diff --git a/libedataserver/e-util.c b/libedataserver/e-util.c index 2209c6d..b103af7 100644 --- a/libedataserver/e-util.c +++ b/libedataserver/e-util.c @@ -19,8 +19,13 @@ * Authors: Rodrigo Moya */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include #include +#include #include #include #include @@ -29,6 +34,7 @@ #include #include #include +#include #include "e-util.h" int @@ -1543,3 +1549,67 @@ e_util_utf8_strstrcasedecomp (const gchar *haystack, const gchar *needle) return NULL; } + +size_t e_strftime(char *s, size_t max, const char *fmt, const struct tm *tm) +{ +#ifdef HAVE_LKSTRFTIME + return strftime(s, max, fmt, tm); +#else + char *c, *ffmt, *ff; + size_t ret; + + ffmt = g_strdup(fmt); + ff = ffmt; + while ((c = strstr(ff, "%l")) != NULL) { + c[1] = 'I'; + ff = c; + } + + ff = fmt; + while ((c = strstr(ff, "%k")) != NULL) { + c[1] = 'H'; + ff = c; + } + + ret = strftime(s, max, ffmt, tm); + g_free(ffmt); + return ret; +#endif +} + +size_t +e_utf8_strftime(char *s, size_t max, const char *fmt, const struct tm *tm) +{ + size_t sz, ret; + char *locale_fmt, *buf; + + locale_fmt = g_locale_from_utf8(fmt, -1, NULL, &sz, NULL); + if (!locale_fmt) + return 0; + + ret = e_strftime(s, max, locale_fmt, tm); + if (!ret) { + g_free (locale_fmt); + return 0; + } + + buf = g_locale_to_utf8(s, ret, NULL, &sz, NULL); + if (!buf) { + g_free (locale_fmt); + return 0; + } + + if (sz >= max) { + char *tmp = buf + max - 1; + tmp = g_utf8_find_prev_char(buf, tmp); + if (tmp) + sz = tmp - buf; + else + sz = 0; + } + memcpy(s, buf, sz); + s[sz] = '\0'; + g_free(locale_fmt); + g_free(buf); + return sz; +} diff --git a/libedataserver/e-util.h b/libedataserver/e-util.h index 55340ab..45922bf 100644 --- a/libedataserver/e-util.h +++ b/libedataserver/e-util.h @@ -25,9 +25,12 @@ #include #include #include +#include G_BEGIN_DECLS +struct tm; + int e_util_mkdir_hier (const char *path, mode_t mode); gchar *e_util_strstrcase (const gchar *haystack, const gchar *needle); @@ -35,6 +38,9 @@ gchar *e_util_unicode_get_utf8 (const gchar *text, gunichar *out); const gchar *e_util_utf8_strstrcase (const gchar *s1, const gchar *s2); const gchar *e_util_utf8_strstrcasedecomp (const gchar *haystack, const gchar *needle); +size_t e_utf8_strftime(char *s, size_t max, const char *fmt, const struct tm *tm); +size_t e_strftime(char *s, size_t max, const char *fmt, const struct tm *tm); + G_END_DECLS #endif -- 2.7.4