2
* Copyright (C) 2008-2011 Robert Ancell.
4
* This program is free software: you can redistribute it and/or modify it under
5
* the terms of the GNU General Public License as published by the Free Software
6
* Foundation, either version 2 of the License, or (at your option) any later
7
* version. See http://www.gnu.org/copyleft/gpl.html the full text of the
14
#include <glib/gstdio.h>
16
#include <libxml/tree.h>
17
#include <libxml/xpath.h>
18
#include <libxml/xpathInternals.h>
19
#include <glib/gi18n.h>
21
#include "currency-manager.h"
29
static const CurrencyInfo currency_info[] = {
30
{"AED", "إ.د", N_("UAE Dirham")},
31
{"AUD", "$", N_("Australian Dollar")},
32
{"BGN", "лв", N_("Bulgarian Lev")},
33
{"BHD", ".ب.د", N_("Bahraini Dinar")},
34
{"BND", "$", N_("Brunei Dollar")},
35
{"BRL", "R$", N_("Brazilian Real")},
36
{"BWP", "P", N_("Botswana Pula")},
37
{"CAD", "$", N_("Canadian Dollar")},
38
{"CFA", "Fr", N_("CFA Franc")},
39
{"CHF", "Fr", N_("Swiss Franc")},
40
{"CLP", "$", N_("Chilean Peso")},
41
{"CNY", "元", N_("Chinese Yuan")},
42
{"COP", "$", N_("Colombian Peso")},
43
{"CZK", "Kč", N_("Czech Koruna")},
44
{"DKK", "kr", N_("Danish Krone")},
45
{"DZD", "ج.د", N_("Algerian Dinar")},
46
{"EEK", "KR", N_("Estonian Kroon")},
47
{"EUR", "€", N_("Euro")},
48
{"GBP", "£", N_("Pound Sterling")},
49
{"HKD", "$", N_("Hong Kong Dollar")},
50
{"HRK", "kn", N_("Croatian Kuna")},
51
{"HUF", "Ft", N_("Hungarian Forint")},
52
{"IDR", "Rp", N_("Indonesian Rupiah")},
53
{"ILS", "₪", N_("Israeli New Shekel")},
54
{"INR", "₹", N_("Indian Rupee")},
55
{"IRR", "﷼", N_("Iranian Rial")},
56
{"ISK", "kr", N_("Icelandic Krona")},
57
{"JPY", "¥", N_("Japanese Yen")},
58
{"KRW", "₩", N_("South Korean Won")},
59
{"KWD", "ك.د", N_("Kuwaiti Dinar")},
60
{"KZT", "₸", N_("Kazakhstani Tenge")},
61
{"LKR", "Rs", N_("Sri Lankan Rupee")},
62
{"LTL", "Lt", N_("Lithuanian Litas")},
63
{"LVL", "Ls", N_("Latvian Lats")},
64
{"LYD", "د.ل", N_("Libyan Dinar")},
65
{"MUR", "Rs", N_("Mauritian Rupee")},
66
{"MXN", "$", N_("Mexican Peso")},
67
{"MYR", "RM", N_("Malaysian Ringgit")},
68
{"NOK", "kr", N_("Norwegian Krone")},
69
{"NPR", "Rs", N_("Nepalese Rupee")},
70
{"NZD", "$", N_("New Zealand Dollar")},
71
{"OMR", "ع.ر.", N_("Omani Rial")},
72
{"PEN", "S/.", N_("Peruvian Nuevo Sol")},
73
{"PHP", "₱", N_("Philippine Peso")},
74
{"PKR", "Rs", N_("Pakistani Rupee")},
75
{"PLN", "zł", N_("Polish Zloty")},
76
{"QAR", "ق.ر", N_("Qatari Riyal")},
77
{"RON", "L", N_("New Romanian Leu")},
78
{"RUB", "руб.", N_("Russian Rouble")},
79
{"SAR", "س.ر", N_("Saudi Riyal")},
80
{"SEK", "kr", N_("Swedish Krona")},
81
{"SGD", "$", N_("Singapore Dollar")},
82
{"THB", "฿", N_("Thai Baht")},
83
{"TND", "ت.د", N_("Tunisian Dinar")},
84
{"TRY", "TL", N_("New Turkish Lira")},
85
{"TTD", "$", N_("T&T Dollar (TTD)")},
86
{"USD", "$", N_("US Dollar")},
87
{"UYU", "$", N_("Uruguayan Peso")},
88
{"VEF", "Bs F", N_("Venezuelan Bolívar")},
89
{"ZAR", "R", N_("South African Rand")},
93
static gboolean downloading_imf_rates = FALSE, downloading_ecb_rates = FALSE;
94
static gboolean loaded_rates = FALSE;
95
static gboolean load_rates(CurrencyManager *manager);
97
struct CurrencyManagerPrivate
102
G_DEFINE_TYPE (CurrencyManager, currency_manager, G_TYPE_OBJECT);
109
static guint signals[LAST_SIGNAL] = { 0, };
111
static CurrencyManager *default_currency_manager = NULL;
115
currency_manager_get_default(void)
119
if (default_currency_manager)
120
return default_currency_manager;
122
default_currency_manager = g_object_new(currency_manager_get_type(), NULL);
124
for (i = 0; currency_info[i].short_name; i++) {
125
Currency *c = currency_new(currency_info[i].short_name,
126
_(currency_info[i].long_name),
127
currency_info[i].symbol);
128
default_currency_manager->priv->currencies = g_list_append(default_currency_manager->priv->currencies, c);
131
return default_currency_manager;
136
currency_manager_get_currencies(CurrencyManager *manager)
138
g_return_val_if_fail(manager != NULL, NULL);
139
return manager->priv->currencies;
144
currency_manager_get_currency(CurrencyManager *manager, const gchar *name)
146
g_return_val_if_fail(manager != NULL, NULL);
147
g_return_val_if_fail(name != NULL, NULL);
150
for (link = manager->priv->currencies; link; link = link->next) {
151
Currency *c = link->data;
152
const MPNumber *value;
154
value = currency_get_value(c);
156
if (!strcmp(name, currency_get_name(c))) {
157
if (mp_is_negative(value) ||
170
get_imf_rate_filepath()
172
return g_build_filename(g_get_user_cache_dir (),
180
get_ecb_rate_filepath()
182
return g_build_filename(g_get_user_cache_dir (),
184
"eurofxref-daily.xml",
190
add_currency(CurrencyManager *manager, const gchar *short_name)
195
for (iter = manager->priv->currencies; iter; iter = iter->next) {
197
if (strcmp(short_name, currency_get_name(c)) == 0)
201
g_warning("Currency %s is not in the currency table", short_name);
202
c = currency_new(short_name, short_name, short_name);
203
manager->priv->currencies = g_list_append(manager->priv->currencies, c);
209
/* A file needs to be redownloaded if it doesn't exist, or is too old.
210
* When an error occur, it probably won't hurt to try to download again.
213
file_needs_update(gchar *filename, double max_age)
217
if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR))
220
if (g_stat(filename, &buf) == -1)
223
if (difftime(time(NULL), buf.st_mtime) > max_age)
231
download_imf_cb(GObject *object, GAsyncResult *result, gpointer user_data)
233
CurrencyManager *manager = user_data;
234
GError *error = NULL;
236
if (g_file_copy_finish(G_FILE(object), result, &error))
237
g_debug("IMF rates updated");
239
g_warning("Couldn't download IMF currency rate file: %s", error->message);
240
g_clear_error(&error);
241
downloading_imf_rates = FALSE;
247
download_ecb_cb(GObject *object, GAsyncResult *result, gpointer user_data)
249
CurrencyManager *manager = user_data;
250
GError *error = NULL;
252
if (g_file_copy_finish(G_FILE(object), result, &error))
253
g_debug("ECB rates updated");
255
g_warning("Couldn't download ECB currency rate file: %s", error->message);
256
g_clear_error(&error);
257
downloading_ecb_rates = FALSE;
263
download_file(CurrencyManager *manager, gchar *uri, gchar *filename, GAsyncReadyCallback callback)
266
GFile *source, *dest;
268
directory = g_path_get_dirname(filename);
269
g_mkdir_with_parents(directory, 0755);
272
source = g_file_new_for_uri(uri);
273
dest = g_file_new_for_path(filename);
275
g_file_copy_async(source, dest, G_FILE_COPY_OVERWRITE, G_PRIORITY_DEFAULT, NULL, NULL, NULL, callback, manager);
276
g_object_unref(source);
277
g_object_unref(dest);
282
load_imf_rates(CurrencyManager *manager)
285
gchar *data, **lines;
287
GError *error = NULL;
289
gboolean result, in_data = FALSE;
292
const gchar *name, *symbol;
296
{"Japanese Yen", "JPY"},
297
{"U.K. Pound Sterling", "GBP"},
298
{"U.S. Dollar", "USD"},
299
{"Algerian Dinar", "DZD"},
300
{"Australian Dollar", "AUD"},
301
{"Bahrain Dinar", "BHD"},
302
{"Botswana Pula", "BWP"},
303
{"Brazilian Real", "BRL"},
304
{"Brunei Dollar", "BND"},
305
{"Canadian Dollar", "CAD"},
306
{"Chilean Peso", "CLP"},
307
{"Chinese Yuan", "CNY"},
308
{"Colombian Peso", "COP"},
309
{"Czech Koruna", "CZK"},
310
{"Danish Krone", "DKK"},
311
{"Hungarian Forint", "HUF"},
312
{"Icelandic Krona", "ISK"},
313
{"Indian Rupee", "INR"},
314
{"Indonesian Rupiah", "IDR"},
315
{"Iranian Rial", "IRR"},
316
{"Israeli New Sheqel", "ILS"},
317
{"Kazakhstani Tenge", "KZT"},
318
{"Korean Won", "KRW"},
319
{"Kuwaiti Dinar", "KWD"},
320
{"Libyan Dinar", "LYD"},
321
{"Malaysian Ringgit", "MYR"},
322
{"Mauritian Rupee", "MUR"},
323
{"Mexican Peso", "MXN"},
324
{"Nepalese Rupee", "NPR"},
325
{"New Zealand Dollar", "NZD"},
326
{"Norwegian Krone", "NOK"},
327
{"Rial Omani", "OMR"},
328
{"Pakistani Rupee", "PKR"},
329
{"Nuevo Sol", "PEN"},
330
{"Philippine Peso", "PHP"},
331
{"Polish Zloty", "PLN"},
332
{"Qatar Riyal", "QAR"},
333
{"Russian Ruble", "RUB"},
334
{"Saudi Arabian Riyal", "SAR"},
335
{"Singapore Dollar", "SGD"},
336
{"South African Rand", "ZAR"},
337
{"Sri Lanka Rupee", "LKR"},
338
{"Swedish Krona", "SEK"},
339
{"Swiss Franc", "CHF"},
340
{"Thai Baht", "THB"},
341
{"Trinidad And Tobago Dollar", "TTD"},
342
{"Tunisian Dinar", "TND"},
343
{"U.A.E. Dirham", "AED"},
344
{"Peso Uruguayo", "UYU"},
345
{"Bolivar Fuerte", "VEF"},
349
filename = get_imf_rate_filepath();
350
result = g_file_get_contents(filename, &data, &length, &error);
354
g_warning("Failed to read exchange rates: %s", error->message);
355
g_clear_error(&error);
359
lines = g_strsplit(data, "\n", 0);
362
for (i = 0; lines[i]; i++) {
363
gchar *line, **tokens;
365
line = g_strchug(lines[i]);
367
/* Start after first blank line, stop on next */
368
if (line[0] == '\0') {
379
tokens = g_strsplit(line, "\t", 0);
380
if (strcmp(tokens[0], "Currency") != 0) {
381
gint value_index, name_index;
383
for (value_index = 1; tokens[value_index]; value_index++) {
384
gchar *value = g_strchug (tokens[value_index]);
385
if (value[0] != '\0')
388
if (tokens[value_index]) {
389
for (name_index = 0; name_map[name_index].name; name_index++) {
390
if (strcmp(name_map[name_index].name, tokens[0]) == 0)
393
if (name_map[name_index].name) {
394
Currency *c = currency_manager_get_currency(manager, name_map[name_index].symbol);
398
g_debug ("Using IMF rate of %s for %s", tokens[value_index], name_map[name_index].symbol);
399
c = add_currency(manager, name_map[name_index].symbol);
401
mp_set_from_string(tokens[value_index], 10, &value);
402
mp_reciprocal(&value, &value);
403
currency_set_value(c, &value);
406
g_warning("Unknown currency '%s'", tokens[0]);
416
set_ecb_rate(CurrencyManager *manager, xmlNodePtr node, Currency *eur_rate)
418
xmlAttrPtr attribute;
419
gchar *name = NULL, *value = NULL;
421
for (attribute = node->properties; attribute; attribute = attribute->next) {
422
if (strcmp((char *)attribute->name, "currency") == 0) {
425
name = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute);
426
} else if (strcmp ((char *)attribute->name, "rate") == 0) {
429
value = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute);
433
/* Use data if value and no rate currently defined */
434
if (name && value && !currency_manager_get_currency(manager, name)) {
438
g_debug ("Using ECB rate of %s for %s", value, name);
439
c = add_currency(manager, name);
440
mp_set_from_string(value, 10, &r);
441
mp_set_from_mp(currency_get_value(eur_rate), &v);
442
mp_multiply(&v, &r, &v);
443
currency_set_value(c, &v);
454
set_ecb_fixed_rate(CurrencyManager *manager, const gchar *name, const gchar *value, Currency *eur_rate)
459
g_debug ("Using ECB fixed rate of %s for %s", value, name);
460
c = add_currency(manager, name);
461
mp_set_from_string(value, 10, &r);
462
mp_set_from_mp(currency_get_value(eur_rate), &v);
463
mp_divide(&v, &r, &v);
464
currency_set_value(c, &v);
469
load_ecb_rates(CurrencyManager *manager)
474
xmlXPathContextPtr xpath_ctx;
475
xmlXPathObjectPtr xpath_obj;
478
/* Scale rates to the EUR value */
479
eur_rate = currency_manager_get_currency(manager, "EUR");
481
g_warning("Cannot use ECB rates as don't have EUR rate");
485
/* Set some fixed rates */
486
set_ecb_fixed_rate(manager, "EEK", "0.06391", eur_rate);
487
set_ecb_fixed_rate(manager, "CFA", "0.152449", eur_rate);
490
filename = get_ecb_rate_filepath();
491
document = xmlReadFile(filename, NULL, 0);
493
g_warning("Couldn't parse ECB rate file %s", filename);
498
xpath_ctx = xmlXPathNewContext(document);
499
if (xpath_ctx == NULL) {
500
xmlFreeDoc(document);
501
g_warning("Couldn't create XPath context");
505
xmlXPathRegisterNs(xpath_ctx,
507
BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref"));
508
xpath_obj = xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"),
510
if (xpath_obj == NULL) {
511
xmlXPathFreeContext(xpath_ctx);
512
xmlFreeDoc(document);
513
g_warning("Couldn't create XPath object");
516
len = (xpath_obj->nodesetval) ? xpath_obj->nodesetval->nodeNr : 0;
517
for (i = 0; i < len; i++) {
518
if (xpath_obj->nodesetval->nodeTab[i]->type == XML_ELEMENT_NODE)
519
set_ecb_rate(manager, xpath_obj->nodesetval->nodeTab[i], eur_rate);
521
/* Avoid accessing removed elements */
522
if (xpath_obj->nodesetval->nodeTab[i]->type != XML_NAMESPACE_DECL)
523
xpath_obj->nodesetval->nodeTab[i] = NULL;
526
xmlXPathFreeObject(xpath_obj);
527
xmlXPathFreeContext(xpath_ctx);
528
xmlFreeDoc(document);
534
load_rates(CurrencyManager *manager)
543
if (downloading_imf_rates || downloading_ecb_rates)
546
/* Use the IMF provided values and top up with currencies tracked by the ECB and not the IMF */
547
load_imf_rates(manager);
548
load_ecb_rates(manager);
550
for (i = 0; currency_info[i].short_name; i++) {
552
for (link = manager->priv->currencies; link; link = link->next) {
553
Currency *c = link->data;
554
if (strcmp(currency_get_name(c), currency_info[i].short_name) == 0)
558
g_warning("Currency %s is not provided by IMF or ECB", currency_info[i].short_name);
561
g_debug("Rates loaded");
564
g_signal_emit(manager, signals[UPDATED], 0);
571
currency_manager_get_value(CurrencyManager *manager, const gchar *currency)
576
g_return_val_if_fail(manager != NULL, NULL);
577
g_return_val_if_fail(currency != NULL, NULL);
579
/* Update rates if necessary */
580
path = get_imf_rate_filepath();
581
if (!downloading_imf_rates && file_needs_update(path, 60 * 60 * 24 * 7)) {
582
downloading_imf_rates = TRUE;
583
g_debug("Downloading rates from the IMF...");
584
download_file(manager, "http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y", path, download_imf_cb);
587
path = get_ecb_rate_filepath();
588
if (!downloading_ecb_rates && file_needs_update(path, 60 * 60 * 24 * 7)) {
589
downloading_ecb_rates = TRUE;
590
g_debug("Downloading rates from the ECB...");
591
download_file(manager, "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml", path, download_ecb_cb);
595
if (!load_rates(manager))
598
c = currency_manager_get_currency(manager, currency);
600
return currency_get_value(c);
607
currency_manager_class_init(CurrencyManagerClass *klass)
609
g_type_class_add_private(klass, sizeof(CurrencyManagerPrivate));
612
g_signal_new("updated",
613
G_TYPE_FROM_CLASS (klass),
615
G_STRUCT_OFFSET (CurrencyManagerClass, updated),
617
g_cclosure_marshal_VOID__VOID,
623
currency_manager_init(CurrencyManager *manager)
625
manager->priv = G_TYPE_INSTANCE_GET_PRIVATE(manager, currency_manager_get_type(), CurrencyManagerPrivate);