4 #include <glib/gstdio.h>
6 #include <libxml/tree.h>
7 #include <libxml/xpath.h>
8 #include <libxml/xpathInternals.h>
9 #include <glib/gi18n.h>
11 #include "currency-manager.h"
19 static const CurrencyInfo currency_info
[] = {
20 {"AED", "إ.د", N_("United Arab Emirates dirham")},
21 {"AUD", "$", N_("Australian dollar")},
22 {"BGN", "лв", N_("Bulgarian lev")},
23 {"BHD", ".ب.د", N_("Bahraini dinar")},
24 {"BND", "$", N_("Brunei dollar")},
25 {"BRL", "R$", N_("Brazilian real")},
26 {"BWP", "P", N_("Botswana pula")},
27 {"CAD", "$", N_("Canadian dollar")},
28 {"CHF", "Fr", N_("Swiss franc")},
29 {"CLP", "$", N_("Chilean peso")},
30 {"CNY", "元", N_("Chinese yuan renminbi")},
31 {"COP", "$", N_("Colombian peso")},
32 {"CZK", "Kč", N_("Czech koruna")},
33 {"DKK", "kr", N_("Danish krone")},
34 {"DZD", "ج.د", N_("Algerian dinar")},
35 {"EEK", "KR", N_("Estonian kroon")},
36 {"EUR", "€", N_("Euro")},
37 {"GBP", "£", N_("Pound sterling")},
38 {"HKD", "$", N_("Hong Kong dollar")},
39 {"HRK", "kn", N_("Croatian kuna")},
40 {"HUF", "Ft", N_("Hungarian forint")},
41 {"IDR", "Rp", N_("Indonesian rupiah")},
42 {"ILS", "₪", N_("Israeli new shekel")},
43 {"INR", "₹", N_("Indian rupee")},
44 {"IRR", "﷼", N_("Iranian rial")},
45 {"ISK", "kr", N_("Icelandic krona")},
46 {"JPY", "¥", N_("Japanese yen")},
47 {"KRW", "₩", N_("South Korean won")},
48 {"KWD", "ك.د", N_("Kuwaiti dinar")},
49 {"KZT", "₸", N_("Kazakhstani tenge")},
50 {"LKR", "Rs", N_("Sri Lankan rupee")},
51 {"LTL", "Lt", N_("Lithuanian litas")},
52 {"LVL", "Ls", N_("Latvian lats")},
53 {"LYD", "د.ل", N_("Libyan dinar")},
54 {"MUR", "Rs", N_("Mauritian rupee")},
55 {"MXN", "$", N_("Mexican peso")},
56 {"MYR", "RM", N_("Malaysian ringgit")},
57 {"NOK", "kr", N_("Norwegian krone")},
58 {"NPR", "Rs", N_("Nepalese rupee")},
59 {"NZD", "$", N_("New Zealand dollar")},
60 {"OMR", "ع.ر.", N_("Omani rial")},
61 {"PEN", "S/.", N_("Peruvian nuevo sol")},
62 {"PHP", "₱", N_("Philippine peso")},
63 {"PKR", "Rs", N_("Pakistani rupee")},
64 {"PLN", "zł", N_("Polish zloty")},
65 {"QAR", "ق.ر", N_("Qatari riyal")},
66 {"RON", "L", N_("New Romanian leu")},
67 {"RUB", "руб.", N_("Russian rouble")},
68 {"SAR", "س.ر", N_("Saudi riyal")},
69 {"SEK", "kr", N_("Swedish krona")},
70 {"SGD", "$", N_("Singapore dollar")},
71 {"THB", "฿", N_("Thai baht")},
72 {"TND", "ت.د", N_("Tunisian dinar")},
73 {"TRY", "TL", N_("New Turkish lira")},
74 {"TTD", "$", N_("Trinidad and Tobago dollar")},
75 {"USD", "$", N_("US dollar")},
76 {"UYU", "$", N_("Uruguayan peso")},
77 {"VEF", "Bs F", N_("Venezuelan bolívar")},
78 {"ZAR", "R", N_("South African rand")},
82 static gboolean downloading_imf_rates
= FALSE
, downloading_ecb_rates
= FALSE
;
83 static gboolean loaded_rates
= FALSE
;
85 struct CurrencyManagerPrivate
90 G_DEFINE_TYPE (CurrencyManager
, currency_manager
, G_TYPE_OBJECT
);
93 static CurrencyManager
*default_currency_manager
= NULL
;
97 currency_manager_get_default(void)
101 if (default_currency_manager
)
102 return default_currency_manager
;
104 default_currency_manager
= g_object_new(currency_manager_get_type(), NULL
);
106 for (i
= 0; currency_info
[i
].short_name
; i
++) {
107 Currency
*c
= currency_new(currency_info
[i
].short_name
,
108 currency_info
[i
].long_name
,
109 currency_info
[i
].symbol
);
110 default_currency_manager
->priv
->currencies
= g_list_append(default_currency_manager
->priv
->currencies
, c
);
113 return default_currency_manager
;
118 currency_manager_get_currencies(CurrencyManager
*manager
)
120 return manager
->priv
->currencies
;
125 currency_manager_get_currency(CurrencyManager
*manager
, const gchar
*name
)
128 for (link
= manager
->priv
->currencies
; link
; link
= link
->next
) {
129 Currency
*c
= link
->data
;
130 const MPNumber
*value
;
132 value
= currency_get_value(c
);
134 if (!strcmp(name
, currency_get_name(c
))) {
135 if (mp_is_negative(value
) ||
148 get_imf_rate_filepath()
150 return g_build_filename(g_get_user_cache_dir (),
158 get_ecb_rate_filepath()
160 return g_build_filename(g_get_user_cache_dir (),
162 "eurofxref-daily.xml",
168 add_currency(CurrencyManager
*manager
, const gchar
*short_name
)
173 for (iter
= manager
->priv
->currencies
; iter
; iter
= iter
->next
) {
175 if (strcmp(short_name
, currency_get_name(c
)) == 0)
179 g_warning("Currency %s is not in the currency table", short_name
);
180 c
= currency_new(short_name
, short_name
, short_name
);
181 manager
->priv
->currencies
= g_list_append(manager
->priv
->currencies
, c
);
187 /* A file needs to be redownloaded if it doesn't exist, or is too old.
188 * When an error occur, it probably won't hurt to try to download again.
191 file_needs_update(gchar
*filename
, double max_age
)
195 if (!g_file_test(filename
, G_FILE_TEST_IS_REGULAR
))
198 if (g_stat(filename
, &buf
) == -1)
201 if (difftime(time(NULL
), buf
.st_mtime
) > max_age
)
209 download_imf_cb(GObject
*object
, GAsyncResult
*result
, gpointer user_data
)
211 GError
*error
= NULL
;
213 if (g_file_copy_finish(G_FILE(object
), result
, &error
))
214 g_debug("IMF rates updated");
216 g_warning("Couldn't download IMF currency rate file: %s", error
->message
);
217 g_clear_error(&error
);
218 downloading_imf_rates
= FALSE
;
223 download_ecb_cb(GObject
*object
, GAsyncResult
*result
, gpointer user_data
)
225 GError
*error
= NULL
;
227 if (g_file_copy_finish(G_FILE(object
), result
, &error
))
228 g_debug("ECB rates updated");
230 g_warning("Couldn't download ECB currency rate file: %s", error
->message
);
231 g_clear_error(&error
);
232 downloading_ecb_rates
= FALSE
;
237 download_file(gchar
*uri
, gchar
*filename
, GAsyncReadyCallback callback
)
240 GFile
*source
, *dest
;
242 directory
= g_path_get_dirname(filename
);
243 g_mkdir_with_parents(directory
, 0755);
246 source
= g_file_new_for_uri(uri
);
247 dest
= g_file_new_for_path(filename
);
249 g_file_copy_async(source
, dest
, G_FILE_COPY_OVERWRITE
, G_PRIORITY_DEFAULT
, NULL
, NULL
, NULL
, callback
, NULL
);
250 g_object_unref(source
);
251 g_object_unref(dest
);
256 load_imf_rates(CurrencyManager
*manager
)
259 gchar
*data
, **lines
;
261 GError
*error
= NULL
;
263 gboolean result
, in_data
= FALSE
;
266 const gchar
*name
, *symbol
;
270 {"Japanese Yen", "JPY"},
271 {"U.K. Pound Sterling", "GBP"},
272 {"U.S. Dollar", "USD"},
273 {"Algerian Dinar", "DZD"},
274 {"Australian Dollar", "AUD"},
275 {"Bahrain Dinar", "BHD"},
276 {"Botswana Pula", "BWP"},
277 {"Brazilian Real", "BRL"},
278 {"Brunei Dollar", "BND"},
279 {"Canadian Dollar", "CAD"},
280 {"Chilean Peso", "CLP"},
281 {"Chinese Yuan", "CNY"},
282 {"Colombian Peso", "COP"},
283 {"Czech Koruna", "CZK"},
284 {"Danish Krone", "DKK"},
285 {"Hungarian Forint", "HUF"},
286 {"Icelandic Krona", "ISK"},
287 {"Indian Rupee", "INR"},
288 {"Indonesian Rupiah", "IDR"},
289 {"Iranian Rial", "IRR"},
290 {"Israeli New Sheqel", "ILS"},
291 {"Kazakhstani Tenge", "KZT"},
292 {"Korean Won", "KRW"},
293 {"Kuwaiti Dinar", "KWD"},
294 {"Libyan Dinar", "LYD"},
295 {"Malaysian Ringgit", "MYR"},
296 {"Mauritian Rupee", "MUR"},
297 {"Mexican Peso", "MXN"},
298 {"Nepalese Rupee", "NPR"},
299 {"New Zealand Dollar", "NZD"},
300 {"Norwegian Krone", "NOK"},
301 {"Rial Omani", "OMR"},
302 {"Pakistani Rupee", "PKR"},
303 {"Nuevo Sol", "PEN"},
304 {"Philippine Peso", "PHP"},
305 {"Polish Zloty", "PLN"},
306 {"Qatar Riyal", "QAR"},
307 {"Russian Ruble", "RUB"},
308 {"Saudi Arabian Riyal", "SAR"},
309 {"Singapore Dollar", "SGD"},
310 {"South African Rand", "ZAR"},
311 {"Sri Lanka Rupee", "LKR"},
312 {"Swedish Krona", "SEK"},
313 {"Swiss Franc", "CHF"},
314 {"Thai Baht", "THB"},
315 {"Trinidad And Tobago Dollar", "TTD"},
316 {"Tunisian Dinar", "TND"},
317 {"U.A.E. Dirham", "AED"},
318 {"Peso Uruguayo", "UYU"},
319 {"Bolivar Fuerte", "VEF"},
323 filename
= get_imf_rate_filepath();
324 result
= g_file_get_contents(filename
, &data
, &length
, &error
);
328 g_warning("Failed to read exchange rates: %s", error
->message
);
329 g_clear_error(&error
);
333 lines
= g_strsplit(data
, "\n", 0);
336 for (i
= 0; lines
[i
]; i
++) {
337 gchar
*line
, **tokens
;
339 line
= g_strchug(lines
[i
]);
341 /* Start after first blank line, stop on next */
342 if (line
[0] == '\0') {
353 tokens
= g_strsplit(line
, "\t", 0);
354 if (strcmp(tokens
[0], "Currency") != 0) {
355 gint value_index
, name_index
;
357 for (value_index
= 1; tokens
[value_index
]; value_index
++) {
358 gchar
*value
= g_strchug (tokens
[value_index
]);
359 if (value
[0] != '\0')
362 if (tokens
[value_index
]) {
363 for (name_index
= 0; name_map
[name_index
].name
; name_index
++) {
364 if (strcmp(name_map
[name_index
].name
, tokens
[0]) == 0)
367 if (name_map
[name_index
].name
) {
368 Currency
*c
= currency_manager_get_currency(manager
, name_map
[name_index
].symbol
);
372 g_debug ("Using IMF rate of %s for %s", tokens
[value_index
], name_map
[name_index
].symbol
);
373 c
= add_currency(manager
, name_map
[name_index
].symbol
);
375 mp_set_from_string(tokens
[value_index
], 10, &value
);
376 currency_set_value(c
, &value
);
379 g_warning("Unknown currency '%s'", tokens
[0]);
389 set_ecb_rate(CurrencyManager
*manager
, xmlNodePtr node
, Currency
*eur_rate
)
391 xmlAttrPtr attribute
;
392 gchar
*name
= NULL
, *value
= NULL
;
394 for (attribute
= node
->properties
; attribute
; attribute
= attribute
->next
) {
395 if (strcmp((char *)attribute
->name
, "currency") == 0) {
398 name
= (gchar
*)xmlNodeGetContent((xmlNodePtr
)attribute
);
399 } else if (strcmp ((char *)attribute
->name
, "rate") == 0) {
402 value
= (gchar
*)xmlNodeGetContent((xmlNodePtr
)attribute
);
406 /* Use data if value and no rate currently defined */
407 if (name
&& value
&& !currency_manager_get_currency(manager
, name
)) {
411 g_debug ("Using ECB rate of %s for %s", value
, name
);
412 c
= add_currency(manager
, name
);
413 mp_set_from_string(value
, 10, &r
);
414 mp_set_from_mp(currency_get_value(eur_rate
), &v
);
415 mp_divide(&v
, &r
, &v
);
416 currency_set_value(c
, &v
);
427 set_ecb_fixed_rate(CurrencyManager
*manager
, const gchar
*name
, const gchar
*value
, Currency
*eur_rate
)
432 g_debug ("Using ECB fixed rate of %s for %s", value
, name
);
433 c
= add_currency(manager
, name
);
434 mp_set_from_string(value
, 10, &r
);
435 mp_set_from_mp(currency_get_value(eur_rate
), &v
);
436 mp_divide(&v
, &r
, &v
);
437 currency_set_value(c
, &v
);
442 load_ecb_rates(CurrencyManager
*manager
)
445 char *filename
= get_ecb_rate_filepath();
447 xmlXPathContextPtr xpath_ctx
;
448 xmlXPathObjectPtr xpath_obj
;
451 /* Scale rates to the EUR value */
452 eur_rate
= currency_manager_get_currency(manager
, "EUR");
454 g_warning("Cannot use ECB rates as don't have EUR rate");
458 /* Set some fixed rates */
459 set_ecb_fixed_rate(manager
, "EEK", "15.6466", eur_rate
);
462 document
= xmlReadFile(filename
, NULL
, 0);
464 if (document
== NULL
) {
465 g_error("Couldn't parse ECB rate file %s", filename
);
469 xpath_ctx
= xmlXPathNewContext(document
);
470 if (xpath_ctx
== NULL
) {
471 xmlFreeDoc(document
);
472 g_error("Couldn't create XPath context");
476 xmlXPathRegisterNs(xpath_ctx
,
478 BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref"));
479 xpath_obj
= xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"),
481 if (xpath_obj
== NULL
) {
482 xmlXPathFreeContext(xpath_ctx
);
483 xmlFreeDoc(document
);
484 fprintf(stderr
, "Couldn't create XPath object\n");
487 len
= (xpath_obj
->nodesetval
) ? xpath_obj
->nodesetval
->nodeNr
: 0;
488 for (i
= 0; i
< len
; i
++) {
489 if (xpath_obj
->nodesetval
->nodeTab
[i
]->type
== XML_ELEMENT_NODE
)
490 set_ecb_rate(manager
, xpath_obj
->nodesetval
->nodeTab
[i
], eur_rate
);
492 /* Avoid accessing removed elements */
493 if (xpath_obj
->nodesetval
->nodeTab
[i
]->type
!= XML_NAMESPACE_DECL
)
494 xpath_obj
->nodesetval
->nodeTab
[i
] = NULL
;
497 xmlXPathFreeObject(xpath_obj
);
498 xmlXPathFreeContext(xpath_ctx
);
499 xmlFreeDoc(document
);
505 load_rates(CurrencyManager
*manager
)
509 /* Use the IMF provided values and top up with currencies tracked by the ECB and not the IMF */
510 load_imf_rates(manager
);
511 load_ecb_rates(manager
);
513 for (i
= 0; currency_info
[i
].short_name
; i
++) {
515 for (link
= manager
->priv
->currencies
; link
; link
= link
->next
) {
516 Currency
*c
= link
->data
;
517 if (strcmp(currency_get_name(c
), currency_info
[i
].short_name
) == 0)
521 g_warning("Currency %s is not provided by IMF or ECB", currency_info
[i
].short_name
);
524 g_debug("Rates loaded");
530 currency_manager_get_value(CurrencyManager
*manager
, const gchar
*currency
)
535 /* Update rates if necessary */
536 path
= get_imf_rate_filepath();
537 if (!downloading_imf_rates
&& file_needs_update(path
, 60 * 60 * 24 * 7)) {
538 downloading_imf_rates
= TRUE
;
539 g_debug("Downloading rates from the IMF...");
540 download_file("http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y", path
, download_imf_cb
);
543 path
= get_ecb_rate_filepath();
544 if (!downloading_ecb_rates
&& file_needs_update(path
, 60 * 60 * 24 * 7)) {
545 downloading_ecb_rates
= TRUE
;
546 g_debug("Downloading rates from the ECB...");
547 download_file("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml", path
, download_ecb_cb
);
551 if (downloading_imf_rates
|| downloading_ecb_rates
)
557 c
= currency_manager_get_currency(manager
, currency
);
558 return currency_get_value(c
);
563 currency_manager_class_init(CurrencyManagerClass
*klass
)
565 g_type_class_add_private(klass
, sizeof(CurrencyManagerPrivate
));
570 currency_manager_init(CurrencyManager
*manager
)
572 manager
->priv
= G_TYPE_INSTANCE_GET_PRIVATE(manager
, currency_manager_get_type(), CurrencyManagerPrivate
);