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_("UAE 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")},
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_("T&T Dollar (TTD)")},
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
;
84 static gboolean
load_rates(CurrencyManager
*manager
);
86 struct CurrencyManagerPrivate
91 G_DEFINE_TYPE (CurrencyManager
, currency_manager
, G_TYPE_OBJECT
);
98 static guint signals
[LAST_SIGNAL
] = { 0, };
100 static CurrencyManager
*default_currency_manager
= NULL
;
104 currency_manager_get_default(void)
108 if (default_currency_manager
)
109 return default_currency_manager
;
111 default_currency_manager
= g_object_new(currency_manager_get_type(), NULL
);
113 for (i
= 0; currency_info
[i
].short_name
; i
++) {
114 Currency
*c
= currency_new(currency_info
[i
].short_name
,
115 _(currency_info
[i
].long_name
),
116 currency_info
[i
].symbol
);
117 default_currency_manager
->priv
->currencies
= g_list_append(default_currency_manager
->priv
->currencies
, c
);
120 return default_currency_manager
;
125 currency_manager_get_currencies(CurrencyManager
*manager
)
127 return manager
->priv
->currencies
;
132 currency_manager_get_currency(CurrencyManager
*manager
, const gchar
*name
)
135 for (link
= manager
->priv
->currencies
; link
; link
= link
->next
) {
136 Currency
*c
= link
->data
;
137 const MPNumber
*value
;
139 value
= currency_get_value(c
);
141 if (!strcmp(name
, currency_get_name(c
))) {
142 if (mp_is_negative(value
) ||
155 get_imf_rate_filepath()
157 return g_build_filename(g_get_user_cache_dir (),
165 get_ecb_rate_filepath()
167 return g_build_filename(g_get_user_cache_dir (),
169 "eurofxref-daily.xml",
175 add_currency(CurrencyManager
*manager
, const gchar
*short_name
)
180 for (iter
= manager
->priv
->currencies
; iter
; iter
= iter
->next
) {
182 if (strcmp(short_name
, currency_get_name(c
)) == 0)
186 g_warning("Currency %s is not in the currency table", short_name
);
187 c
= currency_new(short_name
, short_name
, short_name
);
188 manager
->priv
->currencies
= g_list_append(manager
->priv
->currencies
, c
);
194 /* A file needs to be redownloaded if it doesn't exist, or is too old.
195 * When an error occur, it probably won't hurt to try to download again.
198 file_needs_update(gchar
*filename
, double max_age
)
202 if (!g_file_test(filename
, G_FILE_TEST_IS_REGULAR
))
205 if (g_stat(filename
, &buf
) == -1)
208 if (difftime(time(NULL
), buf
.st_mtime
) > max_age
)
216 download_imf_cb(GObject
*object
, GAsyncResult
*result
, gpointer user_data
)
218 CurrencyManager
*manager
= user_data
;
219 GError
*error
= NULL
;
221 if (g_file_copy_finish(G_FILE(object
), result
, &error
))
222 g_debug("IMF rates updated");
224 g_warning("Couldn't download IMF currency rate file: %s", error
->message
);
225 g_clear_error(&error
);
226 downloading_imf_rates
= FALSE
;
232 download_ecb_cb(GObject
*object
, GAsyncResult
*result
, gpointer user_data
)
234 CurrencyManager
*manager
= user_data
;
235 GError
*error
= NULL
;
237 if (g_file_copy_finish(G_FILE(object
), result
, &error
))
238 g_debug("ECB rates updated");
240 g_warning("Couldn't download ECB currency rate file: %s", error
->message
);
241 g_clear_error(&error
);
242 downloading_ecb_rates
= FALSE
;
248 download_file(CurrencyManager
*manager
, gchar
*uri
, gchar
*filename
, GAsyncReadyCallback callback
)
251 GFile
*source
, *dest
;
253 directory
= g_path_get_dirname(filename
);
254 g_mkdir_with_parents(directory
, 0755);
257 source
= g_file_new_for_uri(uri
);
258 dest
= g_file_new_for_path(filename
);
260 g_file_copy_async(source
, dest
, G_FILE_COPY_OVERWRITE
, G_PRIORITY_DEFAULT
, NULL
, NULL
, NULL
, callback
, manager
);
261 g_object_unref(source
);
262 g_object_unref(dest
);
267 load_imf_rates(CurrencyManager
*manager
)
270 gchar
*data
, **lines
;
272 GError
*error
= NULL
;
274 gboolean result
, in_data
= FALSE
;
277 const gchar
*name
, *symbol
;
281 {"Japanese Yen", "JPY"},
282 {"U.K. Pound Sterling", "GBP"},
283 {"U.S. Dollar", "USD"},
284 {"Algerian Dinar", "DZD"},
285 {"Australian Dollar", "AUD"},
286 {"Bahrain Dinar", "BHD"},
287 {"Botswana Pula", "BWP"},
288 {"Brazilian Real", "BRL"},
289 {"Brunei Dollar", "BND"},
290 {"Canadian Dollar", "CAD"},
291 {"Chilean Peso", "CLP"},
292 {"Chinese Yuan", "CNY"},
293 {"Colombian Peso", "COP"},
294 {"Czech Koruna", "CZK"},
295 {"Danish Krone", "DKK"},
296 {"Hungarian Forint", "HUF"},
297 {"Icelandic Krona", "ISK"},
298 {"Indian Rupee", "INR"},
299 {"Indonesian Rupiah", "IDR"},
300 {"Iranian Rial", "IRR"},
301 {"Israeli New Sheqel", "ILS"},
302 {"Kazakhstani Tenge", "KZT"},
303 {"Korean Won", "KRW"},
304 {"Kuwaiti Dinar", "KWD"},
305 {"Libyan Dinar", "LYD"},
306 {"Malaysian Ringgit", "MYR"},
307 {"Mauritian Rupee", "MUR"},
308 {"Mexican Peso", "MXN"},
309 {"Nepalese Rupee", "NPR"},
310 {"New Zealand Dollar", "NZD"},
311 {"Norwegian Krone", "NOK"},
312 {"Rial Omani", "OMR"},
313 {"Pakistani Rupee", "PKR"},
314 {"Nuevo Sol", "PEN"},
315 {"Philippine Peso", "PHP"},
316 {"Polish Zloty", "PLN"},
317 {"Qatar Riyal", "QAR"},
318 {"Russian Ruble", "RUB"},
319 {"Saudi Arabian Riyal", "SAR"},
320 {"Singapore Dollar", "SGD"},
321 {"South African Rand", "ZAR"},
322 {"Sri Lanka Rupee", "LKR"},
323 {"Swedish Krona", "SEK"},
324 {"Swiss Franc", "CHF"},
325 {"Thai Baht", "THB"},
326 {"Trinidad And Tobago Dollar", "TTD"},
327 {"Tunisian Dinar", "TND"},
328 {"U.A.E. Dirham", "AED"},
329 {"Peso Uruguayo", "UYU"},
330 {"Bolivar Fuerte", "VEF"},
334 filename
= get_imf_rate_filepath();
335 result
= g_file_get_contents(filename
, &data
, &length
, &error
);
339 g_warning("Failed to read exchange rates: %s", error
->message
);
340 g_clear_error(&error
);
344 lines
= g_strsplit(data
, "\n", 0);
347 for (i
= 0; lines
[i
]; i
++) {
348 gchar
*line
, **tokens
;
350 line
= g_strchug(lines
[i
]);
352 /* Start after first blank line, stop on next */
353 if (line
[0] == '\0') {
364 tokens
= g_strsplit(line
, "\t", 0);
365 if (strcmp(tokens
[0], "Currency") != 0) {
366 gint value_index
, name_index
;
368 for (value_index
= 1; tokens
[value_index
]; value_index
++) {
369 gchar
*value
= g_strchug (tokens
[value_index
]);
370 if (value
[0] != '\0')
373 if (tokens
[value_index
]) {
374 for (name_index
= 0; name_map
[name_index
].name
; name_index
++) {
375 if (strcmp(name_map
[name_index
].name
, tokens
[0]) == 0)
378 if (name_map
[name_index
].name
) {
379 Currency
*c
= currency_manager_get_currency(manager
, name_map
[name_index
].symbol
);
383 g_debug ("Using IMF rate of %s for %s", tokens
[value_index
], name_map
[name_index
].symbol
);
384 c
= add_currency(manager
, name_map
[name_index
].symbol
);
386 mp_set_from_string(tokens
[value_index
], 10, &value
);
387 mp_reciprocal(&value
, &value
);
388 currency_set_value(c
, &value
);
391 g_warning("Unknown currency '%s'", tokens
[0]);
401 set_ecb_rate(CurrencyManager
*manager
, xmlNodePtr node
, Currency
*eur_rate
)
403 xmlAttrPtr attribute
;
404 gchar
*name
= NULL
, *value
= NULL
;
406 for (attribute
= node
->properties
; attribute
; attribute
= attribute
->next
) {
407 if (strcmp((char *)attribute
->name
, "currency") == 0) {
410 name
= (gchar
*)xmlNodeGetContent((xmlNodePtr
)attribute
);
411 } else if (strcmp ((char *)attribute
->name
, "rate") == 0) {
414 value
= (gchar
*)xmlNodeGetContent((xmlNodePtr
)attribute
);
418 /* Use data if value and no rate currently defined */
419 if (name
&& value
&& !currency_manager_get_currency(manager
, name
)) {
423 g_debug ("Using ECB rate of %s for %s", value
, name
);
424 c
= add_currency(manager
, name
);
425 mp_set_from_string(value
, 10, &r
);
426 mp_set_from_mp(currency_get_value(eur_rate
), &v
);
427 mp_multiply(&v
, &r
, &v
);
428 currency_set_value(c
, &v
);
439 set_ecb_fixed_rate(CurrencyManager
*manager
, const gchar
*name
, const gchar
*value
, Currency
*eur_rate
)
444 g_debug ("Using ECB fixed rate of %s for %s", value
, name
);
445 c
= add_currency(manager
, name
);
446 mp_set_from_string(value
, 10, &r
);
447 mp_set_from_mp(currency_get_value(eur_rate
), &v
);
448 mp_divide(&v
, &r
, &v
);
449 currency_set_value(c
, &v
);
454 load_ecb_rates(CurrencyManager
*manager
)
457 char *filename
= get_ecb_rate_filepath();
459 xmlXPathContextPtr xpath_ctx
;
460 xmlXPathObjectPtr xpath_obj
;
463 /* Scale rates to the EUR value */
464 eur_rate
= currency_manager_get_currency(manager
, "EUR");
466 g_warning("Cannot use ECB rates as don't have EUR rate");
470 /* Set some fixed rates */
471 set_ecb_fixed_rate(manager
, "EEK", "0.06391", eur_rate
);
474 document
= xmlReadFile(filename
, NULL
, 0);
476 if (document
== NULL
) {
477 g_error("Couldn't parse ECB rate file %s", filename
);
481 xpath_ctx
= xmlXPathNewContext(document
);
482 if (xpath_ctx
== NULL
) {
483 xmlFreeDoc(document
);
484 g_error("Couldn't create XPath context");
488 xmlXPathRegisterNs(xpath_ctx
,
490 BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref"));
491 xpath_obj
= xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"),
493 if (xpath_obj
== NULL
) {
494 xmlXPathFreeContext(xpath_ctx
);
495 xmlFreeDoc(document
);
496 fprintf(stderr
, "Couldn't create XPath object\n");
499 len
= (xpath_obj
->nodesetval
) ? xpath_obj
->nodesetval
->nodeNr
: 0;
500 for (i
= 0; i
< len
; i
++) {
501 if (xpath_obj
->nodesetval
->nodeTab
[i
]->type
== XML_ELEMENT_NODE
)
502 set_ecb_rate(manager
, xpath_obj
->nodesetval
->nodeTab
[i
], eur_rate
);
504 /* Avoid accessing removed elements */
505 if (xpath_obj
->nodesetval
->nodeTab
[i
]->type
!= XML_NAMESPACE_DECL
)
506 xpath_obj
->nodesetval
->nodeTab
[i
] = NULL
;
509 xmlXPathFreeObject(xpath_obj
);
510 xmlXPathFreeContext(xpath_ctx
);
511 xmlFreeDoc(document
);
517 load_rates(CurrencyManager
*manager
)
526 if (downloading_imf_rates
|| downloading_ecb_rates
)
529 /* Use the IMF provided values and top up with currencies tracked by the ECB and not the IMF */
530 load_imf_rates(manager
);
531 load_ecb_rates(manager
);
533 for (i
= 0; currency_info
[i
].short_name
; i
++) {
535 for (link
= manager
->priv
->currencies
; link
; link
= link
->next
) {
536 Currency
*c
= link
->data
;
537 if (strcmp(currency_get_name(c
), currency_info
[i
].short_name
) == 0)
541 g_warning("Currency %s is not provided by IMF or ECB", currency_info
[i
].short_name
);
544 g_debug("Rates loaded");
547 g_signal_emit(manager
, signals
[UPDATED
], 0);
554 currency_manager_get_value(CurrencyManager
*manager
, const gchar
*currency
)
559 /* Update rates if necessary */
560 path
= get_imf_rate_filepath();
561 if (!downloading_imf_rates
&& file_needs_update(path
, 60 * 60 * 24 * 7)) {
562 downloading_imf_rates
= TRUE
;
563 g_debug("Downloading rates from the IMF...");
564 download_file(manager
, "http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y", path
, download_imf_cb
);
567 path
= get_ecb_rate_filepath();
568 if (!downloading_ecb_rates
&& file_needs_update(path
, 60 * 60 * 24 * 7)) {
569 downloading_ecb_rates
= TRUE
;
570 g_debug("Downloading rates from the ECB...");
571 download_file(manager
, "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml", path
, download_ecb_cb
);
575 if (!load_rates(manager
))
578 c
= currency_manager_get_currency(manager
, currency
);
579 return currency_get_value(c
);
584 currency_manager_class_init(CurrencyManagerClass
*klass
)
586 g_type_class_add_private(klass
, sizeof(CurrencyManagerPrivate
));
589 g_signal_new("updated",
590 G_TYPE_FROM_CLASS (klass
),
592 G_STRUCT_OFFSET (CurrencyManagerClass
, updated
),
594 g_cclosure_marshal_VOID__VOID
,
600 currency_manager_init(CurrencyManager
*manager
)
602 manager
->priv
= G_TYPE_INSTANCE_GET_PRIVATE(manager
, currency_manager_get_type(), CurrencyManagerPrivate
);