[gcalctool] Make the currency code OO
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gcalctool] Make the currency code OO
- Date: Thu, 27 Jan 2011 02:06:26 +0000 (UTC)
commit 1e2b5410bb26d041c0fb7fb31f442e643f1ffc55
Author: Robert Ancell <robert ancell canonical com>
Date: Thu Jan 27 12:02:21 2011 +1000
Make the currency code OO
src/Makefile.am | 4 +
src/currency-manager.c | 571 ++++++++++++++++++++++++++++++++++++++++++++++++
src/currency-manager.h | 37 +++
src/currency.c | 463 +++------------------------------------
src/currency.h | 102 +++------
src/math-buttons.c | 1 -
src/math-converter.c | 1 -
src/math-equation.c | 14 +-
src/unit-category.c | 1 -
src/unit-manager.c | 11 +-
src/unit.c | 4 +-
11 files changed, 690 insertions(+), 519 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index e653455..f0f79ef 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,6 +11,8 @@ INCLUDES = \
gcalctool_SOURCES = \
currency.c \
currency.h \
+ currency-manager.c \
+ currency-manager.h \
gcalctool.c \
math-buttons.c \
math-buttons.h \
@@ -63,6 +65,8 @@ gcalccmd_SOURCES = \
gcalccmd.c \
currency.c \
currency.h \
+ currency-manager.c \
+ currency-manager.h \
mp.c \
mp-convert.c \
mp-binary.c \
diff --git a/src/currency-manager.c b/src/currency-manager.c
new file mode 100644
index 0000000..ea43ef1
--- /dev/null
+++ b/src/currency-manager.c
@@ -0,0 +1,571 @@
+#include <time.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+#include <glib/gi18n.h>
+
+#include "currency-manager.h"
+#include "mp.h"
+
+typedef struct {
+ char *short_name;
+ char *symbol;
+ char *long_name;
+} CurrencyInfo;
+static const CurrencyInfo currency_info[] = {
+ {"AED", "إ.د", N_("United Arab Emirates dirham")},
+ {"AUD", "$", N_("Australian dollar")},
+ {"BGN", "лв", N_("Bulgarian lev")},
+ {"BHD", ".ب.د", N_("Bahraini dinar")},
+ {"BND", "$", N_("Brunei dollar")},
+ {"BRL", "R$", N_("Brazilian real")},
+ {"BWP", "P", N_("Botswana pula")},
+ {"CAD", "$", N_("Canadian dollar")},
+ {"CHF", "Fr", N_("Swiss franc")},
+ {"CLP", "$", N_("Chilean peso")},
+ {"CNY", "å??", N_("Chinese yuan renminbi")},
+ {"COP", "$", N_("Colombian peso")},
+ {"CZK", "KÄ?", N_("Czech koruna")},
+ {"DKK", "kr", N_("Danish krone")},
+ {"DZD", "ج.د", N_("Algerian dinar")},
+ {"EEK", "KR", N_("Estonian kroon")},
+ {"EUR", "â?¬", N_("Euro")},
+ {"GBP", "£", N_("Pound sterling")},
+ {"HKD", "$", N_("Hong Kong dollar")},
+ {"HRK", "kn", N_("Croatian kuna")},
+ {"HUF", "Ft", N_("Hungarian forint")},
+ {"IDR", "Rp", N_("Indonesian rupiah")},
+ {"ILS", "â?ª", N_("Israeli new shekel")},
+ {"INR", "â?¹", N_("Indian rupee")},
+ {"IRR", "ï·¼", N_("Iranian rial")},
+ {"ISK", "kr", N_("Icelandic krona")},
+ {"JPY", "Â¥", N_("Japanese yen")},
+ {"KRW", "â?©", N_("South Korean won")},
+ {"KWD", "Ù?.د", N_("Kuwaiti dinar")},
+ {"KZT", "â?¸", N_("Kazakhstani tenge")},
+ {"LKR", "Rs", N_("Sri Lankan rupee")},
+ {"LTL", "Lt", N_("Lithuanian litas")},
+ {"LVL", "Ls", N_("Latvian lats")},
+ {"LYD", "د.Ù?", N_("Libyan dinar")},
+ {"MUR", "Rs", N_("Mauritian rupee")},
+ {"MXN", "$", N_("Mexican peso")},
+ {"MYR", "RM", N_("Malaysian ringgit")},
+ {"NOK", "kr", N_("Norwegian krone")},
+ {"NPR", "Rs", N_("Nepalese rupee")},
+ {"NZD", "$", N_("New Zealand dollar")},
+ {"OMR", "ع.ر.", N_("Omani rial")},
+ {"PEN", "S/.", N_("Peruvian nuevo sol")},
+ {"PHP", "â?±", N_("Philippine peso")},
+ {"PKR", "Rs", N_("Pakistani rupee")},
+ {"PLN", "zÅ?", N_("Polish zloty")},
+ {"QAR", "Ù?.ر", N_("Qatari riyal")},
+ {"RON", "L", N_("New Romanian leu")},
+ {"RUB", "Ñ?Ñ?б.", N_("Russian rouble")},
+ {"SAR", "س.ر", N_("Saudi riyal")},
+ {"SEK", "kr", N_("Swedish krona")},
+ {"SGD", "$", N_("Singapore dollar")},
+ {"THB", "฿", N_("Thai baht")},
+ {"TND", "ت.د", N_("Tunisian dinar")},
+ {"TRY", "TL", N_("New Turkish lira")},
+ {"TTD", "$", N_("Trinidad and Tobago dollar")},
+ {"USD", "$", N_("US dollar")},
+ {"UYU", "$", N_("Uruguayan peso")},
+ {"VEF", "Bs F", N_("Venezuelan bolÃvar")},
+ {"ZAR", "R", N_("South African rand")},
+ {NULL, NULL}
+};
+
+static gboolean downloading_imf_rates = FALSE, downloading_ecb_rates = FALSE;
+static gboolean loaded_rates = FALSE;
+
+struct CurrencyManagerPrivate
+{
+ GList *currencies;
+};
+
+G_DEFINE_TYPE (CurrencyManager, currency_manager, G_TYPE_OBJECT);
+
+
+static CurrencyManager *default_currency_manager = NULL;
+
+
+CurrencyManager *
+currency_manager_get_default(void)
+{
+ int i;
+
+ if (default_currency_manager)
+ return default_currency_manager;
+
+ default_currency_manager = g_object_new(currency_manager_get_type(), NULL);
+
+ for (i = 0; currency_info[i].short_name; i++) {
+ Currency *c = currency_new(currency_info[i].short_name, currency_info[i].long_name, currency_info[i].symbol);
+ default_currency_manager->priv->currencies = g_list_append(default_currency_manager->priv->currencies, c);
+ }
+
+ return default_currency_manager;
+}
+
+
+const GList *
+currency_manager_get_currencies(CurrencyManager *manager)
+{
+ return manager->priv->currencies;
+}
+
+
+Currency *
+currency_manager_get_currency(CurrencyManager *manager, const gchar *name)
+{
+ GList *link;
+ for (link = manager->priv->currencies; link; link = link->next) {
+ Currency *c = link->data;
+ const MPNumber *value;
+
+ value = currency_get_value(c);
+
+ if (!strcmp(name, currency_get_name(c))) {
+ if (mp_is_negative(value) ||
+ mp_is_zero(value)) {
+ return NULL;
+ }
+ else
+ return c;
+ }
+ }
+ return NULL;
+}
+
+
+static char *
+get_imf_rate_filepath()
+{
+ return g_build_filename(g_get_user_cache_dir (),
+ "gcalctool",
+ "rms_five.xls",
+ NULL);
+}
+
+
+static char *
+get_ecb_rate_filepath()
+{
+ return g_build_filename(g_get_user_cache_dir (),
+ "gcalctool",
+ "eurofxref-daily.xml",
+ NULL);
+}
+
+
+static Currency *
+add_currency(CurrencyManager *manager, const gchar *short_name)
+{
+ GList *iter;
+ Currency *c;
+
+ for (iter = manager->priv->currencies; iter; iter = iter->next) {
+ c = iter->data;
+ if (strcmp(short_name, currency_get_name(c)) == 0)
+ return c;
+ }
+
+ g_warning("Currency %s is not in the currency table", short_name);
+ c = currency_new(short_name, short_name, short_name);
+ manager->priv->currencies = g_list_append(manager->priv->currencies, c);
+
+ return c;
+}
+
+
+/* A file needs to be redownloaded if it doesn't exist, or is too old.
+ * When an error occur, it probably won't hurt to try to download again.
+ */
+static gboolean
+file_needs_update(gchar *filename, double max_age)
+{
+ struct stat buf;
+
+ if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR))
+ return TRUE;
+
+ if (g_stat(filename, &buf) == -1)
+ return TRUE;
+
+ if (difftime(time(NULL), buf.st_mtime) > max_age)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static void
+download_imf_cb(GObject *object, GAsyncResult *result, gpointer user_data)
+{
+ GError *error = NULL;
+
+ if (g_file_copy_finish(G_FILE(object), result, &error))
+ g_debug("IMF rates updated");
+ else
+ g_warning("Couldn't download IMF currency rate file: %s", error->message);
+ g_clear_error(&error);
+ downloading_imf_rates = FALSE;
+}
+
+
+static void
+download_ecb_cb(GObject *object, GAsyncResult *result, gpointer user_data)
+{
+ GError *error = NULL;
+
+ if (g_file_copy_finish(G_FILE(object), result, &error))
+ g_debug("ECB rates updated");
+ else
+ g_warning("Couldn't download ECB currency rate file: %s", error->message);
+ g_clear_error(&error);
+ downloading_ecb_rates = FALSE;
+}
+
+
+static void
+download_file(gchar *uri, gchar *filename, GAsyncReadyCallback callback)
+{
+ gchar *directory;
+ GFile *source, *dest;
+
+ directory = g_path_get_dirname(filename);
+ g_mkdir_with_parents(directory, 0755);
+ g_free(directory);
+
+ source = g_file_new_for_uri(uri);
+ dest = g_file_new_for_path(filename);
+
+ g_file_copy_async(source, dest, G_FILE_COPY_OVERWRITE, G_PRIORITY_DEFAULT, NULL, NULL, NULL, callback, NULL);
+ g_object_unref(source);
+ g_object_unref(dest);
+}
+
+
+static void
+load_imf_rates(CurrencyManager *manager)
+{
+ gchar *filename;
+ gchar *data, **lines;
+ gsize length;
+ GError *error = NULL;
+ int i;
+ gboolean result, in_data = FALSE;
+ struct
+ {
+ const gchar *name, *symbol;
+ } name_map[] =
+ {
+ {"Euro", "EUR"},
+ {"Japanese Yen", "JPY"},
+ {"U.K. Pound Sterling", "GBP"},
+ {"U.S. Dollar", "USD"},
+ {"Algerian Dinar", "DZD"},
+ {"Australian Dollar", "AUD"},
+ {"Bahrain Dinar", "BHD"},
+ {"Botswana Pula", "BWP"},
+ {"Brazilian Real", "BRL"},
+ {"Brunei Dollar", "BND"},
+ {"Canadian Dollar", "CAD"},
+ {"Chilean Peso", "CLP"},
+ {"Chinese Yuan", "CNY"},
+ {"Colombian Peso", "COP"},
+ {"Czech Koruna", "CZK"},
+ {"Danish Krone", "DKK"},
+ {"Hungarian Forint", "HUF"},
+ {"Icelandic Krona", "ISK"},
+ {"Indian Rupee", "INR"},
+ {"Indonesian Rupiah", "IDR"},
+ {"Iranian Rial", "IRR"},
+ {"Israeli New Sheqel", "ILS"},
+ {"Kazakhstani Tenge", "KZT"},
+ {"Korean Won", "KRW"},
+ {"Kuwaiti Dinar", "KWD"},
+ {"Libyan Dinar", "LYD"},
+ {"Malaysian Ringgit", "MYR"},
+ {"Mauritian Rupee", "MUR"},
+ {"Mexican Peso", "MXN"},
+ {"Nepalese Rupee", "NPR"},
+ {"New Zealand Dollar", "NZD"},
+ {"Norwegian Krone", "NOK"},
+ {"Rial Omani", "OMR"},
+ {"Pakistani Rupee", "PKR"},
+ {"Nuevo Sol", "PEN"},
+ {"Philippine Peso", "PHP"},
+ {"Polish Zloty", "PLN"},
+ {"Qatar Riyal", "QAR"},
+ {"Russian Ruble", "RUB"},
+ {"Saudi Arabian Riyal", "SAR"},
+ {"Singapore Dollar", "SGD"},
+ {"South African Rand", "ZAR"},
+ {"Sri Lanka Rupee", "LKR"},
+ {"Swedish Krona", "SEK"},
+ {"Swiss Franc", "CHF"},
+ {"Thai Baht", "THB"},
+ {"Trinidad And Tobago Dollar", "TTD"},
+ {"Tunisian Dinar", "TND"},
+ {"U.A.E. Dirham", "AED"},
+ {"Peso Uruguayo", "UYU"},
+ {"Bolivar Fuerte", "VEF"},
+ {NULL, NULL}
+ };
+
+ filename = get_imf_rate_filepath();
+ result = g_file_get_contents(filename, &data, &length, &error);
+ g_free(filename);
+ if (!result)
+ {
+ g_warning("Failed to read exchange rates: %s", error->message);
+ g_clear_error(&error);
+ return;
+ }
+
+ lines = g_strsplit(data, "\n", 0);
+ g_free(data);
+
+ for (i = 0; lines[i]; i++) {
+ gchar *line, **tokens;
+
+ line = g_strchug(lines[i]);
+
+ /* Start after first blank line, stop on next */
+ if (line[0] == '\0') {
+ if (!in_data) {
+ in_data = TRUE;
+ continue;
+ }
+ else
+ break;
+ }
+ if (!in_data)
+ continue;
+
+ tokens = g_strsplit(line, "\t", 0);
+ if (strcmp(tokens[0], "Currency") != 0) {
+ gint value_index, name_index;
+
+ for (value_index = 1; tokens[value_index]; value_index++) {
+ gchar *value = g_strchug (tokens[value_index]);
+ if (value[0] != '\0')
+ break;
+ }
+ if (tokens[value_index]) {
+ for (name_index = 0; name_map[name_index].name; name_index++) {
+ if (strcmp(name_map[name_index].name, tokens[0]) == 0)
+ break;
+ }
+ if (name_map[name_index].name) {
+ Currency *c = currency_manager_get_currency(manager, name_map[name_index].symbol);
+ MPNumber value;
+
+ if (!c) {
+ g_debug ("Using IMF rate of %s for %s", tokens[value_index], name_map[name_index].symbol);
+ c = add_currency(manager, name_map[name_index].symbol);
+ }
+ mp_set_from_string(tokens[value_index], 10, &value);
+ currency_set_value(c, &value);
+ }
+ else
+ g_warning("Unknown currency '%s'", tokens[0]);
+ }
+ }
+ g_strfreev(tokens);
+ }
+ g_strfreev(lines);
+}
+
+
+static void
+set_ecb_rate(CurrencyManager *manager, xmlNodePtr node, Currency *eur_rate)
+{
+ xmlAttrPtr attribute;
+ gchar *name = NULL, *value = NULL;
+
+ for (attribute = node->properties; attribute; attribute = attribute->next) {
+ if (strcmp((char *)attribute->name, "currency") == 0) {
+ if (name)
+ xmlFree(name);
+ name = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute);
+ } else if (strcmp ((char *)attribute->name, "rate") == 0) {
+ if (value)
+ xmlFree(value);
+ value = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute);
+ }
+ }
+
+ /* Use data if value and no rate currently defined */
+ if (name && value && !currency_manager_get_currency(manager, name)) {
+ Currency *c;
+ MPNumber r, v;
+
+ g_debug ("Using ECB rate of %s for %s", value, name);
+ c = add_currency(manager, name);
+ mp_set_from_string(value, 10, &r);
+ mp_set_from_mp(currency_get_value(eur_rate), &v);
+ mp_divide(&v, &r, &v);
+ currency_set_value(c, &v);
+ }
+
+ if (name)
+ xmlFree(name);
+ if (value)
+ xmlFree(value);
+}
+
+
+static void
+set_ecb_fixed_rate(CurrencyManager *manager, const gchar *name, const gchar *value, Currency *eur_rate)
+{
+ Currency *c;
+ MPNumber r, v;
+
+ g_debug ("Using ECB fixed rate of %s for %s", value, name);
+ c = add_currency(manager, name);
+ mp_set_from_string(value, 10, &r);
+ mp_set_from_mp(currency_get_value(eur_rate), &v);
+ mp_divide(&v, &r, &v);
+ currency_set_value(c, &v);
+}
+
+
+static void
+load_ecb_rates(CurrencyManager *manager)
+{
+ Currency *eur_rate;
+ char *filename = get_ecb_rate_filepath();
+ xmlDocPtr document;
+ xmlXPathContextPtr xpath_ctx;
+ xmlXPathObjectPtr xpath_obj;
+ int i, len;
+
+ /* Scale rates to the EUR value */
+ eur_rate = currency_manager_get_currency(manager, "EUR");
+ if (!eur_rate) {
+ g_warning("Cannot use ECB rates as don't have EUR rate");
+ return;
+ }
+
+ /* Set some fixed rates */
+ set_ecb_fixed_rate(manager, "EEK", "15.6466", eur_rate);
+
+ xmlInitParser();
+ document = xmlReadFile(filename, NULL, 0);
+ g_free (filename);
+ if (document == NULL) {
+ g_error("Couldn't parse ECB rate file %s", filename);
+ return;
+ }
+
+ xpath_ctx = xmlXPathNewContext(document);
+ if (xpath_ctx == NULL) {
+ xmlFreeDoc(document);
+ g_error("Couldn't create XPath context");
+ return;
+ }
+
+ xmlXPathRegisterNs(xpath_ctx,
+ BAD_CAST("xref"),
+ BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref"));
+ xpath_obj = xmlXPathEvalExpression(BAD_CAST("//xref:Cube[ currency][@rate]"),
+ xpath_ctx);
+ if (xpath_obj == NULL) {
+ xmlXPathFreeContext(xpath_ctx);
+ xmlFreeDoc(document);
+ fprintf(stderr, "Couldn't create XPath object\n");
+ return;
+ }
+ len = (xpath_obj->nodesetval) ? xpath_obj->nodesetval->nodeNr : 0;
+ for (i = 0; i < len; i++) {
+ if (xpath_obj->nodesetval->nodeTab[i]->type == XML_ELEMENT_NODE)
+ set_ecb_rate(manager, xpath_obj->nodesetval->nodeTab[i], eur_rate);
+
+ /* Avoid accessing removed elements */
+ if (xpath_obj->nodesetval->nodeTab[i]->type != XML_NAMESPACE_DECL)
+ xpath_obj->nodesetval->nodeTab[i] = NULL;
+ }
+
+ xmlXPathFreeObject(xpath_obj);
+ xmlXPathFreeContext(xpath_ctx);
+ xmlFreeDoc(document);
+ xmlCleanupParser();
+}
+
+
+static void
+load_rates(CurrencyManager *manager)
+{
+ int i;
+
+ /* Use the IMF provided values and top up with currencies tracked by the ECB and not the IMF */
+ load_imf_rates(manager);
+ load_ecb_rates(manager);
+
+ for (i = 0; currency_info[i].short_name; i++) {
+ GList *link;
+ for (link = manager->priv->currencies; link; link = link->next) {
+ Currency *c = link->data;
+ if (strcmp(currency_get_name(c), currency_info[i].short_name) == 0)
+ break;
+ }
+ if (!link)
+ g_warning("Currency %s is not provided by IMF or ECB", currency_info[i].short_name);
+ }
+
+ g_debug("Rates loaded");
+ loaded_rates = TRUE;
+}
+
+
+const MPNumber *
+currency_manager_get_value(CurrencyManager *manager, const gchar *currency)
+{
+ gchar *path;
+ Currency *c;
+
+ /* Update rates if necessary */
+ path = get_imf_rate_filepath();
+ if (!downloading_imf_rates && file_needs_update(path, 60 * 60 * 24 * 7)) {
+ downloading_imf_rates = TRUE;
+ g_debug("Downloading rates from the IMF...");
+ download_file("http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y", path, download_imf_cb);
+ }
+ g_free(path);
+ path = get_ecb_rate_filepath();
+ if (!downloading_ecb_rates && file_needs_update(path, 60 * 60 * 24 * 7)) {
+ downloading_ecb_rates = TRUE;
+ g_debug("Downloading rates from the ECB...");
+ download_file("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml", path, download_ecb_cb);
+ }
+ g_free(path);
+
+ if (downloading_imf_rates || downloading_ecb_rates)
+ return NULL;
+
+ if (!loaded_rates)
+ load_rates(manager);
+
+ c = currency_manager_get_currency(manager, currency);
+ return currency_get_value(c);
+}
+
+
+static void
+currency_manager_class_init(CurrencyManagerClass *klass)
+{
+ g_type_class_add_private(klass, sizeof(CurrencyManagerPrivate));
+}
+
+
+static void
+currency_manager_init(CurrencyManager *manager)
+{
+ manager->priv = G_TYPE_INSTANCE_GET_PRIVATE(manager, currency_manager_get_type(), CurrencyManagerPrivate);
+}
diff --git a/src/currency-manager.h b/src/currency-manager.h
new file mode 100644
index 0000000..6d3a887
--- /dev/null
+++ b/src/currency-manager.h
@@ -0,0 +1,37 @@
+#ifndef CURRENCY_MANAGER_H
+#define CURRENCY_MANAGER_H
+
+#include "currency.h"
+#include "mp.h"
+
+G_BEGIN_DECLS
+
+#define CURRENCY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), currency_manager_get_type(), CurrencyManager))
+
+typedef struct CurrencyManagerPrivate CurrencyManagerPrivate;
+
+typedef struct
+{
+ GObject parent_instance;
+ CurrencyManagerPrivate *priv;
+} CurrencyManager;
+
+typedef struct
+{
+ GObjectClass parent_class;
+ // FIXME: Should indicate when rates are updated to UI
+} CurrencyManagerClass;
+
+GType currency_manager_get_type(void);
+
+CurrencyManager *currency_manager_get_default(void);
+
+const GList *currency_manager_get_currencies(CurrencyManager *manager);
+
+Currency *currency_manager_get_currency(CurrencyManager *manager, const gchar *name);
+
+const MPNumber *currency_manager_get_value(CurrencyManager *manager, const gchar *currency);
+
+G_END_DECLS
+
+#endif /* CURRENCY_MANAGER_H */
diff --git a/src/currency.c b/src/currency.c
index 883a002..481a2eb 100644
--- a/src/currency.c
+++ b/src/currency.c
@@ -1,466 +1,77 @@
-#include <time.h>
-
-#include <glib.h>
-#include <glib/gstdio.h>
-#include <gio/gio.h>
-#include <libxml/tree.h>
-#include <libxml/xpath.h>
-#include <libxml/xpathInternals.h>
+#include <string.h>
+#include <stdarg.h>
#include "currency.h"
-#include "mp.h"
-
-typedef struct {
- char *short_name;
- MPNumber value;
- const CurrencyInfo *info;
-} Currency;
-
-static GList *currencies = NULL;
-
-static gboolean downloading_imf_rates = FALSE, downloading_ecb_rates = FALSE;
-static gboolean loaded_rates = FALSE;
+#include "mp-serializer.h"
+#include "currency-manager.h" // FIXME: Move out of here
-static char *
-get_imf_rate_filepath()
+struct CurrencyPrivate
{
- return g_build_filename(g_get_user_cache_dir (),
- "gcalctool",
- "rms_five.xls",
- NULL);
-}
-
+ gchar *name;
+ gchar *display_name;
+ gchar *symbol;
+ MPNumber value;
+};
-static char *
-get_ecb_rate_filepath()
-{
- return g_build_filename(g_get_user_cache_dir (),
- "gcalctool",
- "eurofxref-daily.xml",
- NULL);
-}
+G_DEFINE_TYPE (Currency, currency, G_TYPE_OBJECT);
-static Currency *
-add_currency(const gchar *short_name)
+Currency *
+currency_new(const gchar *name, const gchar *display_name, const gchar *symbol)
{
- Currency *c;
- int i;
-
- c = g_malloc0(sizeof(Currency));
- c->short_name = g_strdup(short_name);
- for (i = 0; currency_info[i].short_name; i++) {
- if (strcmp(c->short_name, currency_info[i].short_name) == 0) {
- c->info = ¤cy_info[i];
- break;
- }
- }
- if (!c->info)
- g_warning("Currency %s is not in the currency table", c->short_name);
+ Currency *currency = g_object_new(currency_get_type(), NULL);
- currencies = g_list_append(currencies, c);
+ currency->priv->name = g_strdup(name);
+ currency->priv->display_name = g_strdup(display_name);
+ currency->priv->symbol = g_strdup(symbol);
- return c;
+ return currency;
}
-static Currency *
-get_currency(const gchar *short_name)
+const gchar *
+currency_get_name(Currency *currency)
{
- GList *link;
- for (link = currencies; link; link = link->next) {
- Currency *c = link->data;
- if (!strcmp(short_name, c->short_name)) {
- if (mp_is_negative(&c->value) ||
- mp_is_zero(&c->value)) {
- return NULL;
- }
- else
- return c;
- }
- }
- return NULL;
+ return currency->priv->name;
}
-/* A file needs to be redownloaded if it doesn't exist, or is too old.
- * When an error occur, it probably won't hurt to try to download again.
- */
-static gboolean
-file_needs_update(gchar *filename, double max_age)
+const gchar *
+currency_get_display_name(Currency *currency)
{
- struct stat buf;
-
- if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR))
- return TRUE;
-
- if (g_stat(filename, &buf) == -1)
- return TRUE;
-
- if (difftime(time(NULL), buf.st_mtime) > max_age)
- return TRUE;
-
- return FALSE;
+ return currency->priv->display_name;
}
-
-static void
-download_imf_cb(GObject *object, GAsyncResult *result, gpointer user_data)
+const gchar *
+currency_get_symbol(Currency *currency)
{
- GError *error = NULL;
-
- if (g_file_copy_finish(G_FILE(object), result, &error))
- g_debug("IMF rates updated");
- else
- g_warning("Couldn't download IMF currency rate file: %s", error->message);
- g_clear_error(&error);
- downloading_imf_rates = FALSE;
+ return currency->priv->symbol;
}
-static void
-download_ecb_cb(GObject *object, GAsyncResult *result, gpointer user_data)
+void
+currency_set_value(Currency *currency, MPNumber *value)
{
- GError *error = NULL;
-
- if (g_file_copy_finish(G_FILE(object), result, &error))
- g_debug("ECB rates updated");
- else
- g_warning("Couldn't download ECB currency rate file: %s", error->message);
- g_clear_error(&error);
- downloading_ecb_rates = FALSE;
+ mp_set_from_mp (value, ¤cy->priv->value);
}
-static void
-download_file(gchar *uri, gchar *filename, GAsyncReadyCallback callback)
+const MPNumber *
+currency_get_value(Currency *currency)
{
- gchar *directory;
- GFile *source, *dest;
-
- directory = g_path_get_dirname(filename);
- g_mkdir_with_parents(directory, 0755);
- g_free(directory);
-
- source = g_file_new_for_uri(uri);
- dest = g_file_new_for_path(filename);
-
- g_file_copy_async(source, dest, G_FILE_COPY_OVERWRITE, G_PRIORITY_DEFAULT, NULL, NULL, NULL, callback, NULL);
- g_object_unref(source);
- g_object_unref(dest);
+ return ¤cy->priv->value;
}
static void
-load_imf_rates()
+currency_class_init(CurrencyClass *klass)
{
- gchar *filename;
- gchar *data, **lines;
- gsize length;
- GError *error = NULL;
- int i;
- gboolean result, in_data = FALSE;
- struct
- {
- const gchar *name, *symbol;
- } name_map[] =
- {
- {"Euro", "EUR"},
- {"Japanese Yen", "JPY"},
- {"U.K. Pound Sterling", "GBP"},
- {"U.S. Dollar", "USD"},
- {"Algerian Dinar", "DZD"},
- {"Australian Dollar", "AUD"},
- {"Bahrain Dinar", "BHD"},
- {"Botswana Pula", "BWP"},
- {"Brazilian Real", "BRL"},
- {"Brunei Dollar", "BND"},
- {"Canadian Dollar", "CAD"},
- {"Chilean Peso", "CLP"},
- {"Chinese Yuan", "CNY"},
- {"Colombian Peso", "COP"},
- {"Czech Koruna", "CZK"},
- {"Danish Krone", "DKK"},
- {"Hungarian Forint", "HUF"},
- {"Icelandic Krona", "ISK"},
- {"Indian Rupee", "INR"},
- {"Indonesian Rupiah", "IDR"},
- {"Iranian Rial", "IRR"},
- {"Israeli New Sheqel", "ILS"},
- {"Kazakhstani Tenge", "KZT"},
- {"Korean Won", "KRW"},
- {"Kuwaiti Dinar", "KWD"},
- {"Libyan Dinar", "LYD"},
- {"Malaysian Ringgit", "MYR"},
- {"Mauritian Rupee", "MUR"},
- {"Mexican Peso", "MXN"},
- {"Nepalese Rupee", "NPR"},
- {"New Zealand Dollar", "NZD"},
- {"Norwegian Krone", "NOK"},
- {"Rial Omani", "OMR"},
- {"Pakistani Rupee", "PKR"},
- {"Nuevo Sol", "PEN"},
- {"Philippine Peso", "PHP"},
- {"Polish Zloty", "PLN"},
- {"Qatar Riyal", "QAR"},
- {"Russian Ruble", "RUB"},
- {"Saudi Arabian Riyal", "SAR"},
- {"Singapore Dollar", "SGD"},
- {"South African Rand", "ZAR"},
- {"Sri Lanka Rupee", "LKR"},
- {"Swedish Krona", "SEK"},
- {"Swiss Franc", "CHF"},
- {"Thai Baht", "THB"},
- {"Trinidad And Tobago Dollar", "TTD"},
- {"Tunisian Dinar", "TND"},
- {"U.A.E. Dirham", "AED"},
- {"Peso Uruguayo", "UYU"},
- {"Bolivar Fuerte", "VEF"},
- {NULL, NULL}
- };
-
- filename = get_imf_rate_filepath();
- result = g_file_get_contents(filename, &data, &length, &error);
- g_free(filename);
- if (!result)
- {
- g_warning("Failed to read exchange rates: %s", error->message);
- g_clear_error(&error);
- return;
- }
-
- lines = g_strsplit(data, "\n", 0);
- g_free(data);
-
- for (i = 0; lines[i]; i++) {
- gchar *line, **tokens;
-
- line = g_strchug(lines[i]);
-
- /* Start after first blank line, stop on next */
- if (line[0] == '\0') {
- if (!in_data) {
- in_data = TRUE;
- continue;
- }
- else
- break;
- }
- if (!in_data)
- continue;
-
- tokens = g_strsplit(line, "\t", 0);
- if (strcmp(tokens[0], "Currency") != 0) {
- gint value_index, name_index;
-
- for (value_index = 1; tokens[value_index]; value_index++) {
- gchar *value = g_strchug (tokens[value_index]);
- if (value[0] != '\0')
- break;
- }
- if (tokens[value_index]) {
- for (name_index = 0; name_map[name_index].name; name_index++) {
- if (strcmp(name_map[name_index].name, tokens[0]) == 0)
- break;
- }
- if (name_map[name_index].name) {
- Currency *c = get_currency(name_map[name_index].symbol);
- if (!c) {
- g_debug ("Using IMF rate of %s for %s", tokens[value_index], name_map[name_index].symbol);
- c = add_currency(name_map[name_index].symbol);
- }
- mp_set_from_string(tokens[value_index], 10, &c->value);
- }
- else
- g_warning("Unknown currency '%s'", tokens[0]);
- }
- }
- g_strfreev(tokens);
- }
- g_strfreev(lines);
+ g_type_class_add_private(klass, sizeof(CurrencyPrivate));
}
static void
-set_ecb_rate(xmlNodePtr node, Currency *eur_rate)
+currency_init(Currency *currency)
{
- xmlAttrPtr attribute;
- gchar *name = NULL, *value = NULL;
-
- for (attribute = node->properties; attribute; attribute = attribute->next) {
- if (strcmp((char *)attribute->name, "currency") == 0) {
- if (name)
- xmlFree(name);
- name = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute);
- } else if (strcmp ((char *)attribute->name, "rate") == 0) {
- if (value)
- xmlFree(value);
- value = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute);
- }
- }
-
- /* Use data if value and no rate currently defined */
- if (name && value && !get_currency(name)) {
- Currency *c;
- MPNumber r;
-
- g_debug ("Using ECB rate of %s for %s", value, name);
- c = add_currency(name);
- mp_set_from_string(value, 10, &r);
- mp_set_from_mp(&eur_rate->value, &c->value);
- mp_divide(&c->value, &r, &c->value);
- }
-
- if (name)
- xmlFree(name);
- if (value)
- xmlFree(value);
-}
-
-
-static void
-set_ecb_fixed_rate(const gchar *name, const gchar *value, Currency *eur_rate)
-{
- Currency *c;
- MPNumber r;
-
- g_debug ("Using ECB fixed rate of %s for %s", value, name);
- c = add_currency(name);
- mp_set_from_string(value, 10, &r);
- mp_set_from_mp(&eur_rate->value, &c->value);
- mp_divide(&c->value, &r, &c->value);
-}
-
-
-static void
-load_ecb_rates()
-{
- Currency *eur_rate;
- char *filename = get_ecb_rate_filepath();
- xmlDocPtr document;
- xmlXPathContextPtr xpath_ctx;
- xmlXPathObjectPtr xpath_obj;
- int i, len;
-
- /* Scale rates to the EUR value */
- eur_rate = get_currency("EUR");
- if (!eur_rate) {
- g_warning("Cannot use ECB rates as don't have EUR rate");
- return;
- }
-
- /* Set some fixed rates */
- set_ecb_fixed_rate("EEK", "15.6466", eur_rate);
-
- xmlInitParser();
- document = xmlReadFile(filename, NULL, 0);
- g_free (filename);
- if (document == NULL) {
- g_error("Couldn't parse ECB rate file %s", filename);
- return;
- }
-
- xpath_ctx = xmlXPathNewContext(document);
- if (xpath_ctx == NULL) {
- xmlFreeDoc(document);
- g_error("Couldn't create XPath context");
- return;
- }
-
- xmlXPathRegisterNs(xpath_ctx,
- BAD_CAST("xref"),
- BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref"));
- xpath_obj = xmlXPathEvalExpression(BAD_CAST("//xref:Cube[ currency][@rate]"),
- xpath_ctx);
- if (xpath_obj == NULL) {
- xmlXPathFreeContext(xpath_ctx);
- xmlFreeDoc(document);
- fprintf(stderr, "Couldn't create XPath object\n");
- return;
- }
- len = (xpath_obj->nodesetval) ? xpath_obj->nodesetval->nodeNr : 0;
- for (i = 0; i < len; i++) {
- if (xpath_obj->nodesetval->nodeTab[i]->type == XML_ELEMENT_NODE)
- set_ecb_rate(xpath_obj->nodesetval->nodeTab[i], eur_rate);
-
- /* Avoid accessing removed elements */
- if (xpath_obj->nodesetval->nodeTab[i]->type != XML_NAMESPACE_DECL)
- xpath_obj->nodesetval->nodeTab[i] = NULL;
- }
-
- xmlXPathFreeObject(xpath_obj);
- xmlXPathFreeContext(xpath_ctx);
- xmlFreeDoc(document);
- xmlCleanupParser();
-}
-
-
-static void
-load_rates()
-{
- int i;
-
- /* Use the IMF provided values and top up with currencies tracked by the ECB and not the IMF */
- load_imf_rates();
- load_ecb_rates();
-
- for (i = 0; currency_info[i].short_name; i++) {
- GList *link;
- for (link = currencies; link; link = link->next) {
- Currency *c = link->data;
- if (c->info == ¤cy_info[i])
- break;
- }
- if (!link)
- g_warning("Currency %s is not provided by IMF or ECB", currency_info[i].short_name);
- }
-
- g_debug("Rates loaded");
- loaded_rates = TRUE;
-}
-
-
-const CurrencyInfo *
-currency_get_info(const gchar *name)
-{
- int i = 0;
- while (currency_info[i].short_name && strcmp(name, currency_info[i].short_name) != 0)
- i++;
- if (currency_info[i].short_name)
- return ¤cy_info[i];
- else
- return NULL;
-}
-
-
-MPNumber *
-currency_get_value(const gchar *currency)
-{
- gchar *path;
- Currency *info;
-
- /* Update rates if necessary */
- path = get_imf_rate_filepath();
- if (!downloading_imf_rates && file_needs_update(path, 60 * 60 * 24 * 7)) {
- downloading_imf_rates = TRUE;
- g_debug("Downloading rates from the IMF...");
- download_file("http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y", path, download_imf_cb);
- }
- g_free(path);
- path = get_ecb_rate_filepath();
- if (!downloading_ecb_rates && file_needs_update(path, 60 * 60 * 24 * 7)) {
- downloading_ecb_rates = TRUE;
- g_debug("Downloading rates from the ECB...");
- download_file("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml", path, download_ecb_cb);
- }
- g_free(path);
-
- if (downloading_imf_rates || downloading_ecb_rates)
- return NULL;
-
- if (!loaded_rates)
- load_rates();
-
- info = get_currency(currency);
- return &info->value;
+ currency->priv = G_TYPE_INSTANCE_GET_PRIVATE(currency, currency_get_type(), CurrencyPrivate);
}
diff --git a/src/currency.h b/src/currency.h
index ea45751..f7f761b 100644
--- a/src/currency.h
+++ b/src/currency.h
@@ -1,82 +1,40 @@
#ifndef CURRENCY_H
#define CURRENCY_H
-#include <glib/gi18n.h>
-
+#include <glib-object.h>
#include "mp.h"
-typedef struct {
- char *short_name;
- char *symbol;
- char *long_name;
-} CurrencyInfo;
-static const CurrencyInfo currency_info[] = {
- {"AED", "إ.د", N_("United Arab Emirates dirham")},
- {"AUD", "$", N_("Australian dollar")},
- {"BGN", "лв", N_("Bulgarian lev")},
- {"BHD", ".ب.د", N_("Bahraini dinar")},
- {"BND", "$", N_("Brunei dollar")},
- {"BRL", "R$", N_("Brazilian real")},
- {"BWP", "P", N_("Botswana pula")},
- {"CAD", "$", N_("Canadian dollar")},
- {"CHF", "Fr", N_("Swiss franc")},
- {"CLP", "$", N_("Chilean peso")},
- {"CNY", "å??", N_("Chinese yuan renminbi")},
- {"COP", "$", N_("Colombian peso")},
- {"CZK", "KÄ?", N_("Czech koruna")},
- {"DKK", "kr", N_("Danish krone")},
- {"DZD", "ج.د", N_("Algerian dinar")},
- {"EEK", "KR", N_("Estonian kroon")},
- {"EUR", "â?¬", N_("Euro")},
- {"GBP", "£", N_("Pound sterling")},
- {"HKD", "$", N_("Hong Kong dollar")},
- {"HRK", "kn", N_("Croatian kuna")},
- {"HUF", "Ft", N_("Hungarian forint")},
- {"IDR", "Rp", N_("Indonesian rupiah")},
- {"ILS", "â?ª", N_("Israeli new shekel")},
- {"INR", "â?¹", N_("Indian rupee")},
- {"IRR", "ï·¼", N_("Iranian rial")},
- {"ISK", "kr", N_("Icelandic krona")},
- {"JPY", "Â¥", N_("Japanese yen")},
- {"KRW", "â?©", N_("South Korean won")},
- {"KWD", "Ù?.د", N_("Kuwaiti dinar")},
- {"KZT", "â?¸", N_("Kazakhstani tenge")},
- {"LKR", "Rs", N_("Sri Lankan rupee")},
- {"LTL", "Lt", N_("Lithuanian litas")},
- {"LVL", "Ls", N_("Latvian lats")},
- {"LYD", "د.Ù?", N_("Libyan dinar")},
- {"MUR", "Rs", N_("Mauritian rupee")},
- {"MXN", "$", N_("Mexican peso")},
- {"MYR", "RM", N_("Malaysian ringgit")},
- {"NOK", "kr", N_("Norwegian krone")},
- {"NPR", "Rs", N_("Nepalese rupee")},
- {"NZD", "$", N_("New Zealand dollar")},
- {"OMR", "ع.ر.", N_("Omani rial")},
- {"PEN", "S/.", N_("Peruvian nuevo sol")},
- {"PHP", "â?±", N_("Philippine peso")},
- {"PKR", "Rs", N_("Pakistani rupee")},
- {"PLN", "zÅ?", N_("Polish zloty")},
- {"QAR", "Ù?.ر", N_("Qatari riyal")},
- {"RON", "L", N_("New Romanian leu")},
- {"RUB", "Ñ?Ñ?б.", N_("Russian rouble")},
- {"SAR", "س.ر", N_("Saudi riyal")},
- {"SEK", "kr", N_("Swedish krona")},
- {"SGD", "$", N_("Singapore dollar")},
- {"THB", "฿", N_("Thai baht")},
- {"TND", "ت.د", N_("Tunisian dinar")},
- {"TRY", "TL", N_("New Turkish lira")},
- {"TTD", "$", N_("Trinidad and Tobago dollar")},
- {"USD", "$", N_("US dollar")},
- {"UYU", "$", N_("Uruguayan peso")},
- {"VEF", "Bs F", N_("Venezuelan bolÃvar")},
- {"ZAR", "R", N_("South African rand")},
- {NULL, NULL}
-};
+G_BEGIN_DECLS
+
+#define CURRENCY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), currency_get_type(), Currency))
+
+typedef struct CurrencyPrivate CurrencyPrivate;
+
+typedef struct
+{
+ GObject parent_instance;
+ CurrencyPrivate *priv;
+} Currency;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} CurrencyClass;
+
+GType currency_get_type(void);
+
+Currency *currency_new(const gchar *name, const gchar *display_name, const gchar *symbol);
+
+const gchar *currency_get_name(Currency *currency);
+
+const gchar *currency_get_display_name(Currency *currency);
+
+const gchar *currency_get_symbol(Currency *currency);
-// FIXME: Should indicate when rates are updated to UI
+void currency_set_value(Currency *currency, MPNumber *value);
-const CurrencyInfo *currency_get_info(const gchar *name);
+const MPNumber *currency_get_value(Currency *currency);
-MPNumber *currency_get_value(const gchar *currency);
+G_END_DECLS
#endif /* CURRENCY_H */
diff --git a/src/math-buttons.c b/src/math-buttons.c
index 87eded8..4295ff2 100644
--- a/src/math-buttons.c
+++ b/src/math-buttons.c
@@ -22,7 +22,6 @@
#include "math-converter.h"
#include "math-variable-popup.h"
#include "financial.h"
-#include "currency.h"
#include "mp-serializer.h"
enum {
diff --git a/src/math-converter.c b/src/math-converter.c
index e2279a7..c5021f0 100644
--- a/src/math-converter.c
+++ b/src/math-converter.c
@@ -20,7 +20,6 @@
#include "math-converter.h"
#include "unit-manager.h"
-#include "currency.h"
enum {
CHANGED,
diff --git a/src/math-equation.c b/src/math-equation.c
index c76b774..082a3c1 100644
--- a/src/math-equation.c
+++ b/src/math-equation.c
@@ -24,13 +24,13 @@
#include <math.h>
#include <errno.h>
#include <glib.h>
+#include <glib/gi18n.h>
#include "math-equation.h"
#include "mp.h"
#include "mp-equation.h"
#include "mp-serializer.h"
-#include "currency.h"
#include "math-enums.h"
@@ -648,10 +648,6 @@ math_equation_get_angle_units(MathEquation *equation)
void
math_equation_set_source_currency(MathEquation *equation, const gchar *currency)
{
- // FIXME: Pick based on locale
- if (!currency || currency[0] == '\0')
- currency = currency_info[0].short_name;
-
if (strcmp(equation->priv->source_currency, currency) == 0)
return;
g_free(equation->priv->source_currency);
@@ -670,10 +666,6 @@ math_equation_get_source_currency(MathEquation *equation)
void
math_equation_set_target_currency(MathEquation *equation, const gchar *currency)
{
- // FIXME: Pick based on locale
- if (!currency || currency[0] == '\0')
- currency = currency_info[0].short_name;
-
if (strcmp(equation->priv->target_currency, currency) == 0)
return;
g_free(equation->priv->target_currency);
@@ -1859,8 +1851,8 @@ math_equation_init(MathEquation *equation)
equation->priv->word_size = 32;
equation->priv->angle_units = MP_DEGREES;
// FIXME: Pick based on locale
- equation->priv->source_currency = g_strdup(currency_info[0].short_name);
- equation->priv->target_currency = g_strdup(currency_info[0].short_name);
+ equation->priv->source_currency = g_strdup("");
+ equation->priv->target_currency = g_strdup("");
equation->priv->source_units = g_strdup("");
equation->priv->target_units = g_strdup("");
equation->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 9);
diff --git a/src/unit-category.c b/src/unit-category.c
index 81edd70..aae9e98 100644
--- a/src/unit-category.c
+++ b/src/unit-category.c
@@ -1,7 +1,6 @@
#include <string.h>
#include "unit-category.h"
-#include "currency.h" // FIXME: TEMP
struct UnitCategoryPrivate
{
diff --git a/src/unit-manager.c b/src/unit-manager.c
index b77c99b..4d5e032 100644
--- a/src/unit-manager.c
+++ b/src/unit-manager.c
@@ -2,7 +2,7 @@
#include <glib/gi18n.h> // FIXME: Move out of here
#include "unit-manager.h"
-#include "currency.h" // FIXME: Move out of here
+#include "currency-manager.h" // FIXME: Move out of here
struct UnitManagerPrivate
{
@@ -35,7 +35,7 @@ unit_manager_get_default(void)
{
UnitCategory *category;
MPNumber t;
- int i;
+ const GList *iter;
if (default_unit_manager)
return default_unit_manager;
@@ -105,13 +105,14 @@ unit_manager_get_default(void)
//unit_category_add_unit(category, unit_new("kelvin", _("Kelvin"), "%s days", get_value("86400", &t), "days", "day", NULL));
category = unit_manager_add_category(default_unit_manager, "currency", _("Currency"));
- for (i = 0; currency_info[i].short_name != NULL; i++)
+ for (iter = currency_manager_get_currencies(currency_manager_get_default()); iter; iter = iter->next)
{
+ Currency *currency = iter->data;
gchar *format;
Unit *unit;
- format = g_strdup_printf("%s%%s", currency_info[i].symbol);
- unit = unit_new(currency_info[i].short_name, currency_info[i].short_name, format, NULL, currency_info[i].short_name, NULL);
+ format = g_strdup_printf("%s%%s", currency_get_symbol(currency));
+ unit = unit_new(currency_get_name(currency), currency_get_name(currency), format, NULL, currency_get_name(currency), NULL);
g_free(format);
unit_category_add_unit(category, unit);
diff --git a/src/unit.c b/src/unit.c
index 3a53d74..762b572 100644
--- a/src/unit.c
+++ b/src/unit.c
@@ -3,7 +3,7 @@
#include "unit.h"
#include "mp-serializer.h"
-#include "currency.h" // FIXME: Move out of here
+#include "currency-manager.h" // FIXME: Move out of here
struct UnitPrivate
{
@@ -92,7 +92,7 @@ unit_get_value(Unit *unit)
if (unit->priv->has_value)
return &unit->priv->value;
else
- return currency_get_value(unit->priv->name); // FIXME: Hack to make currency work
+ return currency_manager_get_value(currency_manager_get_default(), unit->priv->name); // FIXME: Hack to make currency work
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]