Disable localized digits (Bug #644980)
[gcalctool.git] / src / currency-manager.c
blobaa783f9538a2ea80d4ed1e5f7f9e96095b52138b
1 #include <time.h>
3 #include <glib.h>
4 #include <glib/gstdio.h>
5 #include <gio/gio.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"
12 #include "mp.h"
14 typedef struct {
15 char *short_name;
16 char *symbol;
17 char *long_name;
18 } CurrencyInfo;
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")},
79 {NULL, NULL}
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
88 GList *currencies;
91 G_DEFINE_TYPE (CurrencyManager, currency_manager, G_TYPE_OBJECT);
94 enum {
95 UPDATED,
96 LAST_SIGNAL
98 static guint signals[LAST_SIGNAL] = { 0, };
100 static CurrencyManager *default_currency_manager = NULL;
103 CurrencyManager *
104 currency_manager_get_default(void)
106 int i;
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;
124 GList *
125 currency_manager_get_currencies(CurrencyManager *manager)
127 return manager->priv->currencies;
131 Currency *
132 currency_manager_get_currency(CurrencyManager *manager, const gchar *name)
134 GList *link;
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) ||
143 mp_is_zero(value)) {
144 return NULL;
146 else
147 return c;
150 return NULL;
154 static char *
155 get_imf_rate_filepath()
157 return g_build_filename(g_get_user_cache_dir (),
158 "gcalctool",
159 "rms_five.xls",
160 NULL);
164 static char *
165 get_ecb_rate_filepath()
167 return g_build_filename(g_get_user_cache_dir (),
168 "gcalctool",
169 "eurofxref-daily.xml",
170 NULL);
174 static Currency *
175 add_currency(CurrencyManager *manager, const gchar *short_name)
177 GList *iter;
178 Currency *c;
180 for (iter = manager->priv->currencies; iter; iter = iter->next) {
181 c = iter->data;
182 if (strcmp(short_name, currency_get_name(c)) == 0)
183 return c;
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);
190 return 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.
197 static gboolean
198 file_needs_update(gchar *filename, double max_age)
200 struct stat buf;
202 if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR))
203 return TRUE;
205 if (g_stat(filename, &buf) == -1)
206 return TRUE;
208 if (difftime(time(NULL), buf.st_mtime) > max_age)
209 return TRUE;
211 return FALSE;
215 static void
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");
223 else
224 g_warning("Couldn't download IMF currency rate file: %s", error->message);
225 g_clear_error(&error);
226 downloading_imf_rates = FALSE;
227 load_rates(manager);
231 static void
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");
239 else
240 g_warning("Couldn't download ECB currency rate file: %s", error->message);
241 g_clear_error(&error);
242 downloading_ecb_rates = FALSE;
243 load_rates(manager);
247 static void
248 download_file(CurrencyManager *manager, gchar *uri, gchar *filename, GAsyncReadyCallback callback)
250 gchar *directory;
251 GFile *source, *dest;
253 directory = g_path_get_dirname(filename);
254 g_mkdir_with_parents(directory, 0755);
255 g_free(directory);
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);
266 static void
267 load_imf_rates(CurrencyManager *manager)
269 gchar *filename;
270 gchar *data, **lines;
271 gsize length;
272 GError *error = NULL;
273 int i;
274 gboolean result, in_data = FALSE;
275 struct
277 const gchar *name, *symbol;
278 } name_map[] =
280 {"Euro", "EUR"},
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"},
331 {NULL, NULL}
334 filename = get_imf_rate_filepath();
335 result = g_file_get_contents(filename, &data, &length, &error);
336 g_free(filename);
337 if (!result)
339 g_warning("Failed to read exchange rates: %s", error->message);
340 g_clear_error(&error);
341 return;
344 lines = g_strsplit(data, "\n", 0);
345 g_free(data);
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') {
354 if (!in_data) {
355 in_data = TRUE;
356 continue;
358 else
359 break;
361 if (!in_data)
362 continue;
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')
371 break;
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)
376 break;
378 if (name_map[name_index].name) {
379 Currency *c = currency_manager_get_currency(manager, name_map[name_index].symbol);
380 MPNumber value;
382 if (!c) {
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);
390 else
391 g_warning("Unknown currency '%s'", tokens[0]);
394 g_strfreev(tokens);
396 g_strfreev(lines);
400 static void
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) {
408 if (name)
409 xmlFree(name);
410 name = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute);
411 } else if (strcmp ((char *)attribute->name, "rate") == 0) {
412 if (value)
413 xmlFree(value);
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)) {
420 Currency *c;
421 MPNumber r, v;
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);
431 if (name)
432 xmlFree(name);
433 if (value)
434 xmlFree(value);
438 static void
439 set_ecb_fixed_rate(CurrencyManager *manager, const gchar *name, const gchar *value, Currency *eur_rate)
441 Currency *c;
442 MPNumber r, v;
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);
453 static void
454 load_ecb_rates(CurrencyManager *manager)
456 Currency *eur_rate;
457 char *filename = get_ecb_rate_filepath();
458 xmlDocPtr document;
459 xmlXPathContextPtr xpath_ctx;
460 xmlXPathObjectPtr xpath_obj;
461 int i, len;
463 /* Scale rates to the EUR value */
464 eur_rate = currency_manager_get_currency(manager, "EUR");
465 if (!eur_rate) {
466 g_warning("Cannot use ECB rates as don't have EUR rate");
467 return;
470 /* Set some fixed rates */
471 set_ecb_fixed_rate(manager, "EEK", "0.06391", eur_rate);
473 xmlInitParser();
474 document = xmlReadFile(filename, NULL, 0);
475 g_free (filename);
476 if (document == NULL) {
477 g_error("Couldn't parse ECB rate file %s", filename);
478 return;
481 xpath_ctx = xmlXPathNewContext(document);
482 if (xpath_ctx == NULL) {
483 xmlFreeDoc(document);
484 g_error("Couldn't create XPath context");
485 return;
488 xmlXPathRegisterNs(xpath_ctx,
489 BAD_CAST("xref"),
490 BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref"));
491 xpath_obj = xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"),
492 xpath_ctx);
493 if (xpath_obj == NULL) {
494 xmlXPathFreeContext(xpath_ctx);
495 xmlFreeDoc(document);
496 fprintf(stderr, "Couldn't create XPath object\n");
497 return;
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);
512 xmlCleanupParser();
516 static gboolean
517 load_rates(CurrencyManager *manager)
519 int i;
521 /* Already loaded */
522 if (loaded_rates)
523 return TRUE;
525 /* In process */
526 if (downloading_imf_rates || downloading_ecb_rates)
527 return FALSE;
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++) {
534 GList *link;
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)
538 break;
540 if (!link)
541 g_warning("Currency %s is not provided by IMF or ECB", currency_info[i].short_name);
544 g_debug("Rates loaded");
545 loaded_rates = TRUE;
547 g_signal_emit(manager, signals[UPDATED], 0);
549 return TRUE;
553 const MPNumber *
554 currency_manager_get_value(CurrencyManager *manager, const gchar *currency)
556 gchar *path;
557 Currency *c;
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);
566 g_free(path);
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);
573 g_free(path);
575 if (!load_rates(manager))
576 return NULL;
578 c = currency_manager_get_currency(manager, currency);
579 return currency_get_value(c);
583 static void
584 currency_manager_class_init(CurrencyManagerClass *klass)
586 g_type_class_add_private(klass, sizeof(CurrencyManagerPrivate));
588 signals[UPDATED] =
589 g_signal_new("updated",
590 G_TYPE_FROM_CLASS (klass),
591 G_SIGNAL_RUN_LAST,
592 G_STRUCT_OFFSET (CurrencyManagerClass, updated),
593 NULL, NULL,
594 g_cclosure_marshal_VOID__VOID,
595 G_TYPE_NONE, 0);
599 static void
600 currency_manager_init(CurrencyManager *manager)
602 manager->priv = G_TYPE_INSTANCE_GET_PRIVATE(manager, currency_manager_get_type(), CurrencyManagerPrivate);