summaryrefslogtreecommitdiff
path: root/src/currency-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/currency-manager.c')
-rw-r--r--src/currency-manager.c626
1 files changed, 626 insertions, 0 deletions
diff --git a/src/currency-manager.c b/src/currency-manager.c
new file mode 100644
index 0000000..cd57ab6
--- /dev/null
+++ b/src/currency-manager.c
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2008-2011 Robert Ancell.
+ *
+ * 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. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+#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_("UAE 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")},
+ {"CFA", "Fr", N_("CFA Franc")},
+ {"CHF", "Fr", N_("Swiss Franc")},
+ {"CLP", "$", N_("Chilean Peso")},
+ {"CNY", "元", N_("Chinese Yuan")},
+ {"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_("T&T Dollar (TTD)")},
+ {"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;
+static gboolean load_rates(CurrencyManager *manager);
+
+struct CurrencyManagerPrivate
+{
+ GList *currencies;
+};
+
+G_DEFINE_TYPE (CurrencyManager, currency_manager, G_TYPE_OBJECT);
+
+
+enum {
+ UPDATED,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = { 0, };
+
+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;
+}
+
+
+GList *
+currency_manager_get_currencies(CurrencyManager *manager)
+{
+ g_return_val_if_fail(manager != NULL, NULL);
+ return manager->priv->currencies;
+}
+
+
+Currency *
+currency_manager_get_currency(CurrencyManager *manager, const gchar *name)
+{
+ g_return_val_if_fail(manager != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ 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 (),
+ "mate-calc",
+ "rms_five.xls",
+ NULL);
+}
+
+
+static char *
+get_ecb_rate_filepath()
+{
+ return g_build_filename(g_get_user_cache_dir (),
+ "mate-calc",
+ "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)
+{
+ CurrencyManager *manager = 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;
+ load_rates(manager);
+}
+
+
+static void
+download_ecb_cb(GObject *object, GAsyncResult *result, gpointer user_data)
+{
+ CurrencyManager *manager = 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;
+ load_rates(manager);
+}
+
+
+static void
+download_file(CurrencyManager *manager, 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, manager);
+ 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);
+ mp_reciprocal(&value, &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_multiply(&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;
+ 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", "0.06391", eur_rate);
+ set_ecb_fixed_rate(manager, "CFA", "0.152449", eur_rate);
+
+ xmlInitParser();
+ filename = get_ecb_rate_filepath();
+ document = xmlReadFile(filename, NULL, 0);
+ if (!document)
+ g_warning("Couldn't parse ECB rate file %s", filename);
+ g_free (filename);
+ if (!document)
+ return;
+
+ xpath_ctx = xmlXPathNewContext(document);
+ if (xpath_ctx == NULL) {
+ xmlFreeDoc(document);
+ g_warning("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);
+ g_warning("Couldn't create XPath object");
+ 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 gboolean
+load_rates(CurrencyManager *manager)
+{
+ int i;
+
+ /* Already loaded */
+ if (loaded_rates)
+ return TRUE;
+
+ /* In process */
+ if (downloading_imf_rates || downloading_ecb_rates)
+ return FALSE;
+
+ /* 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;
+
+ g_signal_emit(manager, signals[UPDATED], 0);
+
+ return TRUE;
+}
+
+
+const MPNumber *
+currency_manager_get_value(CurrencyManager *manager, const gchar *currency)
+{
+ gchar *path;
+ Currency *c;
+
+ g_return_val_if_fail(manager != NULL, NULL);
+ g_return_val_if_fail(currency != NULL, NULL);
+
+ /* 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(manager, "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(manager, "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml", path, download_ecb_cb);
+ }
+ g_free(path);
+
+ if (!load_rates(manager))
+ return NULL;
+
+ c = currency_manager_get_currency(manager, currency);
+ if (c)
+ return currency_get_value(c);
+ else
+ return NULL;
+}
+
+
+static void
+currency_manager_class_init(CurrencyManagerClass *klass)
+{
+ g_type_class_add_private(klass, sizeof(CurrencyManagerPrivate));
+
+ signals[UPDATED] =
+ g_signal_new("updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CurrencyManagerClass, updated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+
+static void
+currency_manager_init(CurrencyManager *manager)
+{
+ manager->priv = G_TYPE_INSTANCE_GET_PRIVATE(manager, currency_manager_get_type(), CurrencyManagerPrivate);
+}