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_error("Couldn't parse ECB rate file %s", filename
);
498 xpath_ctx
= xmlXPathNewContext(document
);
499 if (xpath_ctx
== NULL
) {
500 xmlFreeDoc(document
);
501 g_error("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 fprintf(stderr
, "Couldn't create XPath object\n");
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
);