Changed mind about translating short currency names, instead use the full name in...
[gcalctool.git] / src / currency-manager.c
blobecbefc104a8df52123033a33702d5ee1c672dbdb
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_("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")},
79 {NULL, NULL}
82 static gboolean downloading_imf_rates = FALSE, downloading_ecb_rates = FALSE;
83 static gboolean loaded_rates = FALSE;
85 struct CurrencyManagerPrivate
87 GList *currencies;
90 G_DEFINE_TYPE (CurrencyManager, currency_manager, G_TYPE_OBJECT);
93 static CurrencyManager *default_currency_manager = NULL;
96 CurrencyManager *
97 currency_manager_get_default(void)
99 int i;
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;
117 const GList *
118 currency_manager_get_currencies(CurrencyManager *manager)
120 return manager->priv->currencies;
124 Currency *
125 currency_manager_get_currency(CurrencyManager *manager, const gchar *name)
127 GList *link;
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) ||
136 mp_is_zero(value)) {
137 return NULL;
139 else
140 return c;
143 return NULL;
147 static char *
148 get_imf_rate_filepath()
150 return g_build_filename(g_get_user_cache_dir (),
151 "gcalctool",
152 "rms_five.xls",
153 NULL);
157 static char *
158 get_ecb_rate_filepath()
160 return g_build_filename(g_get_user_cache_dir (),
161 "gcalctool",
162 "eurofxref-daily.xml",
163 NULL);
167 static Currency *
168 add_currency(CurrencyManager *manager, const gchar *short_name)
170 GList *iter;
171 Currency *c;
173 for (iter = manager->priv->currencies; iter; iter = iter->next) {
174 c = iter->data;
175 if (strcmp(short_name, currency_get_name(c)) == 0)
176 return c;
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);
183 return 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.
190 static gboolean
191 file_needs_update(gchar *filename, double max_age)
193 struct stat buf;
195 if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR))
196 return TRUE;
198 if (g_stat(filename, &buf) == -1)
199 return TRUE;
201 if (difftime(time(NULL), buf.st_mtime) > max_age)
202 return TRUE;
204 return FALSE;
208 static void
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");
215 else
216 g_warning("Couldn't download IMF currency rate file: %s", error->message);
217 g_clear_error(&error);
218 downloading_imf_rates = FALSE;
222 static void
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");
229 else
230 g_warning("Couldn't download ECB currency rate file: %s", error->message);
231 g_clear_error(&error);
232 downloading_ecb_rates = FALSE;
236 static void
237 download_file(gchar *uri, gchar *filename, GAsyncReadyCallback callback)
239 gchar *directory;
240 GFile *source, *dest;
242 directory = g_path_get_dirname(filename);
243 g_mkdir_with_parents(directory, 0755);
244 g_free(directory);
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);
255 static void
256 load_imf_rates(CurrencyManager *manager)
258 gchar *filename;
259 gchar *data, **lines;
260 gsize length;
261 GError *error = NULL;
262 int i;
263 gboolean result, in_data = FALSE;
264 struct
266 const gchar *name, *symbol;
267 } name_map[] =
269 {"Euro", "EUR"},
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"},
320 {NULL, NULL}
323 filename = get_imf_rate_filepath();
324 result = g_file_get_contents(filename, &data, &length, &error);
325 g_free(filename);
326 if (!result)
328 g_warning("Failed to read exchange rates: %s", error->message);
329 g_clear_error(&error);
330 return;
333 lines = g_strsplit(data, "\n", 0);
334 g_free(data);
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') {
343 if (!in_data) {
344 in_data = TRUE;
345 continue;
347 else
348 break;
350 if (!in_data)
351 continue;
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')
360 break;
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)
365 break;
367 if (name_map[name_index].name) {
368 Currency *c = currency_manager_get_currency(manager, name_map[name_index].symbol);
369 MPNumber value;
371 if (!c) {
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);
378 else
379 g_warning("Unknown currency '%s'", tokens[0]);
382 g_strfreev(tokens);
384 g_strfreev(lines);
388 static void
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) {
396 if (name)
397 xmlFree(name);
398 name = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute);
399 } else if (strcmp ((char *)attribute->name, "rate") == 0) {
400 if (value)
401 xmlFree(value);
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)) {
408 Currency *c;
409 MPNumber r, v;
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);
419 if (name)
420 xmlFree(name);
421 if (value)
422 xmlFree(value);
426 static void
427 set_ecb_fixed_rate(CurrencyManager *manager, const gchar *name, const gchar *value, Currency *eur_rate)
429 Currency *c;
430 MPNumber r, v;
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);
441 static void
442 load_ecb_rates(CurrencyManager *manager)
444 Currency *eur_rate;
445 char *filename = get_ecb_rate_filepath();
446 xmlDocPtr document;
447 xmlXPathContextPtr xpath_ctx;
448 xmlXPathObjectPtr xpath_obj;
449 int i, len;
451 /* Scale rates to the EUR value */
452 eur_rate = currency_manager_get_currency(manager, "EUR");
453 if (!eur_rate) {
454 g_warning("Cannot use ECB rates as don't have EUR rate");
455 return;
458 /* Set some fixed rates */
459 set_ecb_fixed_rate(manager, "EEK", "15.6466", eur_rate);
461 xmlInitParser();
462 document = xmlReadFile(filename, NULL, 0);
463 g_free (filename);
464 if (document == NULL) {
465 g_error("Couldn't parse ECB rate file %s", filename);
466 return;
469 xpath_ctx = xmlXPathNewContext(document);
470 if (xpath_ctx == NULL) {
471 xmlFreeDoc(document);
472 g_error("Couldn't create XPath context");
473 return;
476 xmlXPathRegisterNs(xpath_ctx,
477 BAD_CAST("xref"),
478 BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref"));
479 xpath_obj = xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"),
480 xpath_ctx);
481 if (xpath_obj == NULL) {
482 xmlXPathFreeContext(xpath_ctx);
483 xmlFreeDoc(document);
484 fprintf(stderr, "Couldn't create XPath object\n");
485 return;
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);
500 xmlCleanupParser();
504 static void
505 load_rates(CurrencyManager *manager)
507 int i;
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++) {
514 GList *link;
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)
518 break;
520 if (!link)
521 g_warning("Currency %s is not provided by IMF or ECB", currency_info[i].short_name);
524 g_debug("Rates loaded");
525 loaded_rates = TRUE;
529 const MPNumber *
530 currency_manager_get_value(CurrencyManager *manager, const gchar *currency)
532 gchar *path;
533 Currency *c;
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);
542 g_free(path);
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);
549 g_free(path);
551 if (downloading_imf_rates || downloading_ecb_rates)
552 return NULL;
554 if (!loaded_rates)
555 load_rates(manager);
557 c = currency_manager_get_currency(manager, currency);
558 return currency_get_value(c);
562 static void
563 currency_manager_class_init(CurrencyManagerClass *klass)
565 g_type_class_add_private(klass, sizeof(CurrencyManagerPrivate));
569 static void
570 currency_manager_init(CurrencyManager *manager)
572 manager->priv = G_TYPE_INSTANCE_GET_PRIVATE(manager, currency_manager_get_type(), CurrencyManagerPrivate);