Updated Latvian translation and added Latvian translation for help by Viesturs Ružāns...
[gcalctool.git] / src / currency-manager.c
blob6086b3596a43cf7ddec189b8c4e77966cdd906d3
1 /*
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
8 * license.
9 */
11 #include <time.h>
13 #include <glib.h>
14 #include <glib/gstdio.h>
15 #include <gio/gio.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"
22 #include "mp.h"
24 typedef struct {
25 char *short_name;
26 char *symbol;
27 char *long_name;
28 } CurrencyInfo;
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")},
90 {NULL, NULL}
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
99 GList *currencies;
102 G_DEFINE_TYPE (CurrencyManager, currency_manager, G_TYPE_OBJECT);
105 enum {
106 UPDATED,
107 LAST_SIGNAL
109 static guint signals[LAST_SIGNAL] = { 0, };
111 static CurrencyManager *default_currency_manager = NULL;
114 CurrencyManager *
115 currency_manager_get_default(void)
117 int i;
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;
135 GList *
136 currency_manager_get_currencies(CurrencyManager *manager)
138 g_return_val_if_fail(manager != NULL, NULL);
139 return manager->priv->currencies;
143 Currency *
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);
149 GList *link;
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) ||
158 mp_is_zero(value)) {
159 return NULL;
161 else
162 return c;
165 return NULL;
169 static char *
170 get_imf_rate_filepath()
172 return g_build_filename(g_get_user_cache_dir (),
173 "gcalctool",
174 "rms_five.xls",
175 NULL);
179 static char *
180 get_ecb_rate_filepath()
182 return g_build_filename(g_get_user_cache_dir (),
183 "gcalctool",
184 "eurofxref-daily.xml",
185 NULL);
189 static Currency *
190 add_currency(CurrencyManager *manager, const gchar *short_name)
192 GList *iter;
193 Currency *c;
195 for (iter = manager->priv->currencies; iter; iter = iter->next) {
196 c = iter->data;
197 if (strcmp(short_name, currency_get_name(c)) == 0)
198 return c;
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);
205 return 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.
212 static gboolean
213 file_needs_update(gchar *filename, double max_age)
215 struct stat buf;
217 if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR))
218 return TRUE;
220 if (g_stat(filename, &buf) == -1)
221 return TRUE;
223 if (difftime(time(NULL), buf.st_mtime) > max_age)
224 return TRUE;
226 return FALSE;
230 static void
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");
238 else
239 g_warning("Couldn't download IMF currency rate file: %s", error->message);
240 g_clear_error(&error);
241 downloading_imf_rates = FALSE;
242 load_rates(manager);
246 static void
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");
254 else
255 g_warning("Couldn't download ECB currency rate file: %s", error->message);
256 g_clear_error(&error);
257 downloading_ecb_rates = FALSE;
258 load_rates(manager);
262 static void
263 download_file(CurrencyManager *manager, gchar *uri, gchar *filename, GAsyncReadyCallback callback)
265 gchar *directory;
266 GFile *source, *dest;
268 directory = g_path_get_dirname(filename);
269 g_mkdir_with_parents(directory, 0755);
270 g_free(directory);
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);
281 static void
282 load_imf_rates(CurrencyManager *manager)
284 gchar *filename;
285 gchar *data, **lines;
286 gsize length;
287 GError *error = NULL;
288 int i;
289 gboolean result, in_data = FALSE;
290 struct
292 const gchar *name, *symbol;
293 } name_map[] =
295 {"Euro", "EUR"},
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"},
346 {NULL, NULL}
349 filename = get_imf_rate_filepath();
350 result = g_file_get_contents(filename, &data, &length, &error);
351 g_free(filename);
352 if (!result)
354 g_warning("Failed to read exchange rates: %s", error->message);
355 g_clear_error(&error);
356 return;
359 lines = g_strsplit(data, "\n", 0);
360 g_free(data);
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') {
369 if (!in_data) {
370 in_data = TRUE;
371 continue;
373 else
374 break;
376 if (!in_data)
377 continue;
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')
386 break;
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)
391 break;
393 if (name_map[name_index].name) {
394 Currency *c = currency_manager_get_currency(manager, name_map[name_index].symbol);
395 MPNumber value;
397 if (!c) {
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);
405 else
406 g_warning("Unknown currency '%s'", tokens[0]);
409 g_strfreev(tokens);
411 g_strfreev(lines);
415 static void
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) {
423 if (name)
424 xmlFree(name);
425 name = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute);
426 } else if (strcmp ((char *)attribute->name, "rate") == 0) {
427 if (value)
428 xmlFree(value);
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)) {
435 Currency *c;
436 MPNumber r, v;
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);
446 if (name)
447 xmlFree(name);
448 if (value)
449 xmlFree(value);
453 static void
454 set_ecb_fixed_rate(CurrencyManager *manager, const gchar *name, const gchar *value, Currency *eur_rate)
456 Currency *c;
457 MPNumber r, v;
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);
468 static void
469 load_ecb_rates(CurrencyManager *manager)
471 Currency *eur_rate;
472 char *filename;
473 xmlDocPtr document;
474 xmlXPathContextPtr xpath_ctx;
475 xmlXPathObjectPtr xpath_obj;
476 int i, len;
478 /* Scale rates to the EUR value */
479 eur_rate = currency_manager_get_currency(manager, "EUR");
480 if (!eur_rate) {
481 g_warning("Cannot use ECB rates as don't have EUR rate");
482 return;
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);
489 xmlInitParser();
490 filename = get_ecb_rate_filepath();
491 document = xmlReadFile(filename, NULL, 0);
492 if (!document)
493 g_error("Couldn't parse ECB rate file %s", filename);
494 g_free (filename);
495 if (!document)
496 return;
498 xpath_ctx = xmlXPathNewContext(document);
499 if (xpath_ctx == NULL) {
500 xmlFreeDoc(document);
501 g_error("Couldn't create XPath context");
502 return;
505 xmlXPathRegisterNs(xpath_ctx,
506 BAD_CAST("xref"),
507 BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref"));
508 xpath_obj = xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"),
509 xpath_ctx);
510 if (xpath_obj == NULL) {
511 xmlXPathFreeContext(xpath_ctx);
512 xmlFreeDoc(document);
513 fprintf(stderr, "Couldn't create XPath object\n");
514 return;
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);
529 xmlCleanupParser();
533 static gboolean
534 load_rates(CurrencyManager *manager)
536 int i;
538 /* Already loaded */
539 if (loaded_rates)
540 return TRUE;
542 /* In process */
543 if (downloading_imf_rates || downloading_ecb_rates)
544 return FALSE;
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++) {
551 GList *link;
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)
555 break;
557 if (!link)
558 g_warning("Currency %s is not provided by IMF or ECB", currency_info[i].short_name);
561 g_debug("Rates loaded");
562 loaded_rates = TRUE;
564 g_signal_emit(manager, signals[UPDATED], 0);
566 return TRUE;
570 const MPNumber *
571 currency_manager_get_value(CurrencyManager *manager, const gchar *currency)
573 gchar *path;
574 Currency *c;
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);
586 g_free(path);
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);
593 g_free(path);
595 if (!load_rates(manager))
596 return NULL;
598 c = currency_manager_get_currency(manager, currency);
599 if (c)
600 return currency_get_value(c);
601 else
602 return NULL;
606 static void
607 currency_manager_class_init(CurrencyManagerClass *klass)
609 g_type_class_add_private(klass, sizeof(CurrencyManagerPrivate));
611 signals[UPDATED] =
612 g_signal_new("updated",
613 G_TYPE_FROM_CLASS (klass),
614 G_SIGNAL_RUN_LAST,
615 G_STRUCT_OFFSET (CurrencyManagerClass, updated),
616 NULL, NULL,
617 g_cclosure_marshal_VOID__VOID,
618 G_TYPE_NONE, 0);
622 static void
623 currency_manager_init(CurrencyManager *manager)
625 manager->priv = G_TYPE_INSTANCE_GET_PRIVATE(manager, currency_manager_get_type(), CurrencyManagerPrivate);