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 {"CHF", "Fr", N_("Swiss Franc")},
39 {"CLP", "$", N_("Chilean Peso")},
40 {"CNY", "元", N_("Chinese Yuan")},
41 {"COP", "$", N_("Colombian Peso")},
42 {"CZK", "Kč", N_("Czech Koruna")},
43 {"DKK", "kr", N_("Danish Krone")},
44 {"DZD", "ج.د", N_("Algerian Dinar")},
45 {"EEK", "KR", N_("Estonian Kroon")},
46 {"EUR", "€", N_("Euro")},
47 {"GBP", "£", N_("Pound Sterling")},
48 {"HKD", "$", N_("Hong Kong Dollar")},
49 {"HRK", "kn", N_("Croatian Kuna")},
50 {"HUF", "Ft", N_("Hungarian Forint")},
51 {"IDR", "Rp", N_("Indonesian Rupiah")},
52 {"ILS", "₪", N_("Israeli New Shekel")},
53 {"INR", "₹", N_("Indian Rupee")},
54 {"IRR", "﷼", N_("Iranian Rial")},
55 {"ISK", "kr", N_("Icelandic Krona")},
56 {"JPY", "¥", N_("Japanese Yen")},
57 {"KRW", "₩", N_("South Korean Won")},
58 {"KWD", "ك.د", N_("Kuwaiti Dinar")},
59 {"KZT", "₸", N_("Kazakhstani Tenge")},
60 {"LKR", "Rs", N_("Sri Lankan Rupee")},
61 {"LTL", "Lt", N_("Lithuanian Litas")},
62 {"LVL", "Ls", N_("Latvian Lats")},
63 {"LYD", "د.ل", N_("Libyan Dinar")},
64 {"MUR", "Rs", N_("Mauritian Rupee")},
65 {"MXN", "$", N_("Mexican Peso")},
66 {"MYR", "RM", N_("Malaysian Ringgit")},
67 {"NOK", "kr", N_("Norwegian Krone")},
68 {"NPR", "Rs", N_("Nepalese Rupee")},
69 {"NZD", "$", N_("New Zealand Dollar")},
70 {"OMR", "ع.ر.", N_("Omani Rial")},
71 {"PEN", "S/.", N_("Peruvian Nuevo Sol")},
72 {"PHP", "₱", N_("Philippine Peso")},
73 {"PKR", "Rs", N_("Pakistani Rupee")},
74 {"PLN", "zł", N_("Polish Zloty")},
75 {"QAR", "ق.ر", N_("Qatari Riyal")},
76 {"RON", "L", N_("New Romanian Leu")},
77 {"RUB", "руб.", N_("Russian Rouble")},
78 {"SAR", "س.ر", N_("Saudi Riyal")},
79 {"SEK", "kr", N_("Swedish Krona")},
80 {"SGD", "$", N_("Singapore Dollar")},
81 {"THB", "฿", N_("Thai Baht")},
82 {"TND", "ت.د", N_("Tunisian Dinar")},
83 {"TRY", "TL", N_("New Turkish Lira")},
84 {"TTD", "$", N_("T&T Dollar (TTD)")},
85 {"USD", "$", N_("US Dollar")},
86 {"UYU", "$", N_("Uruguayan Peso")},
87 {"VEF", "Bs F", N_("Venezuelan Bolívar")},
88 {"ZAR", "R", N_("South African Rand")},
92 static gboolean downloading_imf_rates
= FALSE
, downloading_ecb_rates
= FALSE
;
93 static gboolean loaded_rates
= FALSE
;
94 static gboolean
load_rates(CurrencyManager
*manager
);
96 struct CurrencyManagerPrivate
101 G_DEFINE_TYPE (CurrencyManager
, currency_manager
, G_TYPE_OBJECT
);
108 static guint signals
[LAST_SIGNAL
] = { 0, };
110 static CurrencyManager
*default_currency_manager
= NULL
;
114 currency_manager_get_default(void)
118 if (default_currency_manager
)
119 return default_currency_manager
;
121 default_currency_manager
= g_object_new(currency_manager_get_type(), NULL
);
123 for (i
= 0; currency_info
[i
].short_name
; i
++) {
124 Currency
*c
= currency_new(currency_info
[i
].short_name
,
125 _(currency_info
[i
].long_name
),
126 currency_info
[i
].symbol
);
127 default_currency_manager
->priv
->currencies
= g_list_append(default_currency_manager
->priv
->currencies
, c
);
130 return default_currency_manager
;
135 currency_manager_get_currencies(CurrencyManager
*manager
)
137 g_return_val_if_fail(manager
!= NULL
, NULL
);
138 return manager
->priv
->currencies
;
143 currency_manager_get_currency(CurrencyManager
*manager
, const gchar
*name
)
145 g_return_val_if_fail(manager
!= NULL
, NULL
);
146 g_return_val_if_fail(name
!= NULL
, NULL
);
149 for (link
= manager
->priv
->currencies
; link
; link
= link
->next
) {
150 Currency
*c
= link
->data
;
151 const MPNumber
*value
;
153 value
= currency_get_value(c
);
155 if (!strcmp(name
, currency_get_name(c
))) {
156 if (mp_is_negative(value
) ||
169 get_imf_rate_filepath()
171 return g_build_filename(g_get_user_cache_dir (),
179 get_ecb_rate_filepath()
181 return g_build_filename(g_get_user_cache_dir (),
183 "eurofxref-daily.xml",
189 add_currency(CurrencyManager
*manager
, const gchar
*short_name
)
194 for (iter
= manager
->priv
->currencies
; iter
; iter
= iter
->next
) {
196 if (strcmp(short_name
, currency_get_name(c
)) == 0)
200 g_warning("Currency %s is not in the currency table", short_name
);
201 c
= currency_new(short_name
, short_name
, short_name
);
202 manager
->priv
->currencies
= g_list_append(manager
->priv
->currencies
, c
);
208 /* A file needs to be redownloaded if it doesn't exist, or is too old.
209 * When an error occur, it probably won't hurt to try to download again.
212 file_needs_update(gchar
*filename
, double max_age
)
216 if (!g_file_test(filename
, G_FILE_TEST_IS_REGULAR
))
219 if (g_stat(filename
, &buf
) == -1)
222 if (difftime(time(NULL
), buf
.st_mtime
) > max_age
)
230 download_imf_cb(GObject
*object
, GAsyncResult
*result
, gpointer user_data
)
232 CurrencyManager
*manager
= user_data
;
233 GError
*error
= NULL
;
235 if (g_file_copy_finish(G_FILE(object
), result
, &error
))
236 g_debug("IMF rates updated");
238 g_warning("Couldn't download IMF currency rate file: %s", error
->message
);
239 g_clear_error(&error
);
240 downloading_imf_rates
= FALSE
;
246 download_ecb_cb(GObject
*object
, GAsyncResult
*result
, gpointer user_data
)
248 CurrencyManager
*manager
= user_data
;
249 GError
*error
= NULL
;
251 if (g_file_copy_finish(G_FILE(object
), result
, &error
))
252 g_debug("ECB rates updated");
254 g_warning("Couldn't download ECB currency rate file: %s", error
->message
);
255 g_clear_error(&error
);
256 downloading_ecb_rates
= FALSE
;
262 download_file(CurrencyManager
*manager
, gchar
*uri
, gchar
*filename
, GAsyncReadyCallback callback
)
265 GFile
*source
, *dest
;
267 directory
= g_path_get_dirname(filename
);
268 g_mkdir_with_parents(directory
, 0755);
271 source
= g_file_new_for_uri(uri
);
272 dest
= g_file_new_for_path(filename
);
274 g_file_copy_async(source
, dest
, G_FILE_COPY_OVERWRITE
, G_PRIORITY_DEFAULT
, NULL
, NULL
, NULL
, callback
, manager
);
275 g_object_unref(source
);
276 g_object_unref(dest
);
281 load_imf_rates(CurrencyManager
*manager
)
284 gchar
*data
, **lines
;
286 GError
*error
= NULL
;
288 gboolean result
, in_data
= FALSE
;
291 const gchar
*name
, *symbol
;
295 {"Japanese Yen", "JPY"},
296 {"U.K. Pound Sterling", "GBP"},
297 {"U.S. Dollar", "USD"},
298 {"Algerian Dinar", "DZD"},
299 {"Australian Dollar", "AUD"},
300 {"Bahrain Dinar", "BHD"},
301 {"Botswana Pula", "BWP"},
302 {"Brazilian Real", "BRL"},
303 {"Brunei Dollar", "BND"},
304 {"Canadian Dollar", "CAD"},
305 {"Chilean Peso", "CLP"},
306 {"Chinese Yuan", "CNY"},
307 {"Colombian Peso", "COP"},
308 {"Czech Koruna", "CZK"},
309 {"Danish Krone", "DKK"},
310 {"Hungarian Forint", "HUF"},
311 {"Icelandic Krona", "ISK"},
312 {"Indian Rupee", "INR"},
313 {"Indonesian Rupiah", "IDR"},
314 {"Iranian Rial", "IRR"},
315 {"Israeli New Sheqel", "ILS"},
316 {"Kazakhstani Tenge", "KZT"},
317 {"Korean Won", "KRW"},
318 {"Kuwaiti Dinar", "KWD"},
319 {"Libyan Dinar", "LYD"},
320 {"Malaysian Ringgit", "MYR"},
321 {"Mauritian Rupee", "MUR"},
322 {"Mexican Peso", "MXN"},
323 {"Nepalese Rupee", "NPR"},
324 {"New Zealand Dollar", "NZD"},
325 {"Norwegian Krone", "NOK"},
326 {"Rial Omani", "OMR"},
327 {"Pakistani Rupee", "PKR"},
328 {"Nuevo Sol", "PEN"},
329 {"Philippine Peso", "PHP"},
330 {"Polish Zloty", "PLN"},
331 {"Qatar Riyal", "QAR"},
332 {"Russian Ruble", "RUB"},
333 {"Saudi Arabian Riyal", "SAR"},
334 {"Singapore Dollar", "SGD"},
335 {"South African Rand", "ZAR"},
336 {"Sri Lanka Rupee", "LKR"},
337 {"Swedish Krona", "SEK"},
338 {"Swiss Franc", "CHF"},
339 {"Thai Baht", "THB"},
340 {"Trinidad And Tobago Dollar", "TTD"},
341 {"Tunisian Dinar", "TND"},
342 {"U.A.E. Dirham", "AED"},
343 {"Peso Uruguayo", "UYU"},
344 {"Bolivar Fuerte", "VEF"},
348 filename
= get_imf_rate_filepath();
349 result
= g_file_get_contents(filename
, &data
, &length
, &error
);
353 g_warning("Failed to read exchange rates: %s", error
->message
);
354 g_clear_error(&error
);
358 lines
= g_strsplit(data
, "\n", 0);
361 for (i
= 0; lines
[i
]; i
++) {
362 gchar
*line
, **tokens
;
364 line
= g_strchug(lines
[i
]);
366 /* Start after first blank line, stop on next */
367 if (line
[0] == '\0') {
378 tokens
= g_strsplit(line
, "\t", 0);
379 if (strcmp(tokens
[0], "Currency") != 0) {
380 gint value_index
, name_index
;
382 for (value_index
= 1; tokens
[value_index
]; value_index
++) {
383 gchar
*value
= g_strchug (tokens
[value_index
]);
384 if (value
[0] != '\0')
387 if (tokens
[value_index
]) {
388 for (name_index
= 0; name_map
[name_index
].name
; name_index
++) {
389 if (strcmp(name_map
[name_index
].name
, tokens
[0]) == 0)
392 if (name_map
[name_index
].name
) {
393 Currency
*c
= currency_manager_get_currency(manager
, name_map
[name_index
].symbol
);
397 g_debug ("Using IMF rate of %s for %s", tokens
[value_index
], name_map
[name_index
].symbol
);
398 c
= add_currency(manager
, name_map
[name_index
].symbol
);
400 mp_set_from_string(tokens
[value_index
], 10, &value
);
401 mp_reciprocal(&value
, &value
);
402 currency_set_value(c
, &value
);
405 g_warning("Unknown currency '%s'", tokens
[0]);
415 set_ecb_rate(CurrencyManager
*manager
, xmlNodePtr node
, Currency
*eur_rate
)
417 xmlAttrPtr attribute
;
418 gchar
*name
= NULL
, *value
= NULL
;
420 for (attribute
= node
->properties
; attribute
; attribute
= attribute
->next
) {
421 if (strcmp((char *)attribute
->name
, "currency") == 0) {
424 name
= (gchar
*)xmlNodeGetContent((xmlNodePtr
)attribute
);
425 } else if (strcmp ((char *)attribute
->name
, "rate") == 0) {
428 value
= (gchar
*)xmlNodeGetContent((xmlNodePtr
)attribute
);
432 /* Use data if value and no rate currently defined */
433 if (name
&& value
&& !currency_manager_get_currency(manager
, name
)) {
437 g_debug ("Using ECB rate of %s for %s", value
, name
);
438 c
= add_currency(manager
, name
);
439 mp_set_from_string(value
, 10, &r
);
440 mp_set_from_mp(currency_get_value(eur_rate
), &v
);
441 mp_multiply(&v
, &r
, &v
);
442 currency_set_value(c
, &v
);
453 set_ecb_fixed_rate(CurrencyManager
*manager
, const gchar
*name
, const gchar
*value
, Currency
*eur_rate
)
458 g_debug ("Using ECB fixed rate of %s for %s", value
, name
);
459 c
= add_currency(manager
, name
);
460 mp_set_from_string(value
, 10, &r
);
461 mp_set_from_mp(currency_get_value(eur_rate
), &v
);
462 mp_divide(&v
, &r
, &v
);
463 currency_set_value(c
, &v
);
468 load_ecb_rates(CurrencyManager
*manager
)
471 char *filename
= get_ecb_rate_filepath();
473 xmlXPathContextPtr xpath_ctx
;
474 xmlXPathObjectPtr xpath_obj
;
477 /* Scale rates to the EUR value */
478 eur_rate
= currency_manager_get_currency(manager
, "EUR");
480 g_warning("Cannot use ECB rates as don't have EUR rate");
484 /* Set some fixed rates */
485 set_ecb_fixed_rate(manager
, "EEK", "0.06391", eur_rate
);
488 document
= xmlReadFile(filename
, NULL
, 0);
490 if (document
== NULL
) {
491 g_error("Couldn't parse ECB rate file %s", filename
);
495 xpath_ctx
= xmlXPathNewContext(document
);
496 if (xpath_ctx
== NULL
) {
497 xmlFreeDoc(document
);
498 g_error("Couldn't create XPath context");
502 xmlXPathRegisterNs(xpath_ctx
,
504 BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref"));
505 xpath_obj
= xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"),
507 if (xpath_obj
== NULL
) {
508 xmlXPathFreeContext(xpath_ctx
);
509 xmlFreeDoc(document
);
510 fprintf(stderr
, "Couldn't create XPath object\n");
513 len
= (xpath_obj
->nodesetval
) ? xpath_obj
->nodesetval
->nodeNr
: 0;
514 for (i
= 0; i
< len
; i
++) {
515 if (xpath_obj
->nodesetval
->nodeTab
[i
]->type
== XML_ELEMENT_NODE
)
516 set_ecb_rate(manager
, xpath_obj
->nodesetval
->nodeTab
[i
], eur_rate
);
518 /* Avoid accessing removed elements */
519 if (xpath_obj
->nodesetval
->nodeTab
[i
]->type
!= XML_NAMESPACE_DECL
)
520 xpath_obj
->nodesetval
->nodeTab
[i
] = NULL
;
523 xmlXPathFreeObject(xpath_obj
);
524 xmlXPathFreeContext(xpath_ctx
);
525 xmlFreeDoc(document
);
531 load_rates(CurrencyManager
*manager
)
540 if (downloading_imf_rates
|| downloading_ecb_rates
)
543 /* Use the IMF provided values and top up with currencies tracked by the ECB and not the IMF */
544 load_imf_rates(manager
);
545 load_ecb_rates(manager
);
547 for (i
= 0; currency_info
[i
].short_name
; i
++) {
549 for (link
= manager
->priv
->currencies
; link
; link
= link
->next
) {
550 Currency
*c
= link
->data
;
551 if (strcmp(currency_get_name(c
), currency_info
[i
].short_name
) == 0)
555 g_warning("Currency %s is not provided by IMF or ECB", currency_info
[i
].short_name
);
558 g_debug("Rates loaded");
561 g_signal_emit(manager
, signals
[UPDATED
], 0);
568 currency_manager_get_value(CurrencyManager
*manager
, const gchar
*currency
)
573 g_return_val_if_fail(manager
!= NULL
, NULL
);
574 g_return_val_if_fail(currency
!= NULL
, NULL
);
576 /* Update rates if necessary */
577 path
= get_imf_rate_filepath();
578 if (!downloading_imf_rates
&& file_needs_update(path
, 60 * 60 * 24 * 7)) {
579 downloading_imf_rates
= TRUE
;
580 g_debug("Downloading rates from the IMF...");
581 download_file(manager
, "http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y", path
, download_imf_cb
);
584 path
= get_ecb_rate_filepath();
585 if (!downloading_ecb_rates
&& file_needs_update(path
, 60 * 60 * 24 * 7)) {
586 downloading_ecb_rates
= TRUE
;
587 g_debug("Downloading rates from the ECB...");
588 download_file(manager
, "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml", path
, download_ecb_cb
);
592 if (!load_rates(manager
))
595 c
= currency_manager_get_currency(manager
, currency
);
597 return currency_get_value(c
);
604 currency_manager_class_init(CurrencyManagerClass
*klass
)
606 g_type_class_add_private(klass
, sizeof(CurrencyManagerPrivate
));
609 g_signal_new("updated",
610 G_TYPE_FROM_CLASS (klass
),
612 G_STRUCT_OFFSET (CurrencyManagerClass
, updated
),
614 g_cclosure_marshal_VOID__VOID
,
620 currency_manager_init(CurrencyManager
*manager
)
622 manager
->priv
= G_TYPE_INSTANCE_GET_PRIVATE(manager
, currency_manager_get_type(), CurrencyManagerPrivate
);