2 * Copyright (C) 2008-2012 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
11 static bool downloading_imf_rates
= false;
12 static bool downloading_ecb_rates
= false;
13 static bool loaded_rates
= false;
14 private static CurrencyManager? default_currency_manager
= null;
16 public class CurrencyManager
: Object
18 private List
<Currency
> currencies
;
19 public signal void updated ();
21 public static CurrencyManager
get_default ()
23 if (default_currency_manager
!= null)
24 return default_currency_manager
;
26 default_currency_manager
= new
CurrencyManager ();
28 default_currency_manager
.currencies
.append (new
Currency ("AED", _("UAE Dirham"), "إ.د"));
29 default_currency_manager
.currencies
.append (new
Currency ("AUD", _("Australian Dollar"), "$"));
30 default_currency_manager
.currencies
.append (new
Currency ("BGN", _("Bulgarian Lev"), "лв"));
31 default_currency_manager
.currencies
.append (new
Currency ("BHD", _("Bahraini Dinar"), ".ب.د"));
32 default_currency_manager
.currencies
.append (new
Currency ("BND", _("Brunei Dollar"), "$"));
33 default_currency_manager
.currencies
.append (new
Currency ("BRL", _("Brazilian Real"), "R$"));
34 default_currency_manager
.currencies
.append (new
Currency ("BWP", _("Botswana Pula"), "P"));
35 default_currency_manager
.currencies
.append (new
Currency ("CAD", _("Canadian Dollar"), "$"));
36 default_currency_manager
.currencies
.append (new
Currency ("CFA", _("CFA Franc"), "Fr"));
37 default_currency_manager
.currencies
.append (new
Currency ("CHF", _("Swiss Franc"), "Fr"));
38 default_currency_manager
.currencies
.append (new
Currency ("CLP", _("Chilean Peso"), "$"));
39 default_currency_manager
.currencies
.append (new
Currency ("CNY", _("Chinese Yuan"), "元"));
40 default_currency_manager
.currencies
.append (new
Currency ("COP", _("Colombian Peso"), "$"));
41 default_currency_manager
.currencies
.append (new
Currency ("CZK", _("Czech Koruna"), "Kč"));
42 default_currency_manager
.currencies
.append (new
Currency ("DKK", _("Danish Krone"), "kr"));
43 default_currency_manager
.currencies
.append (new
Currency ("DZD", _("Algerian Dinar"), "ج.د"));
44 default_currency_manager
.currencies
.append (new
Currency ("EEK", _("Estonian Kroon"), "KR"));
45 default_currency_manager
.currencies
.append (new
Currency ("EUR", _("Euro"), "€"));
46 default_currency_manager
.currencies
.append (new
Currency ("GBP", _("Pound Sterling"), "£"));
47 default_currency_manager
.currencies
.append (new
Currency ("HKD", _("Hong Kong Dollar"), "$"));
48 default_currency_manager
.currencies
.append (new
Currency ("HRK", _("Croatian Kuna"), "kn"));
49 default_currency_manager
.currencies
.append (new
Currency ("HUF", _("Hungarian Forint"), "Ft"));
50 default_currency_manager
.currencies
.append (new
Currency ("IDR", _("Indonesian Rupiah"), "Rp"));
51 default_currency_manager
.currencies
.append (new
Currency ("ILS", _("Israeli New Shekel"), "₪"));
52 default_currency_manager
.currencies
.append (new
Currency ("INR", _("Indian Rupee"), "₹"));
53 default_currency_manager
.currencies
.append (new
Currency ("IRR", _("Iranian Rial"), "﷼"));
54 default_currency_manager
.currencies
.append (new
Currency ("ISK", _("Icelandic Krona"), "kr"));
55 default_currency_manager
.currencies
.append (new
Currency ("JPY", _("Japanese Yen"), "¥"));
56 default_currency_manager
.currencies
.append (new
Currency ("KRW", _("South Korean Won"), "₩"));
57 default_currency_manager
.currencies
.append (new
Currency ("KWD", _("Kuwaiti Dinar"), "ك.د"));
58 default_currency_manager
.currencies
.append (new
Currency ("KZT", _("Kazakhstani Tenge"), "₸"));
59 default_currency_manager
.currencies
.append (new
Currency ("LKR", _("Sri Lankan Rupee"), "Rs"));
60 default_currency_manager
.currencies
.append (new
Currency ("LTL", _("Lithuanian Litas"), "Lt"));
61 default_currency_manager
.currencies
.append (new
Currency ("LVL", _("Latvian Lats"), "Ls"));
62 default_currency_manager
.currencies
.append (new
Currency ("LYD", _("Libyan Dinar"), "د.ل"));
63 default_currency_manager
.currencies
.append (new
Currency ("MUR", _("Mauritian Rupee"), "Rs"));
64 default_currency_manager
.currencies
.append (new
Currency ("MXN", _("Mexican Peso"), "$"));
65 default_currency_manager
.currencies
.append (new
Currency ("MYR", _("Malaysian Ringgit"), "RM"));
66 default_currency_manager
.currencies
.append (new
Currency ("NOK", _("Norwegian Krone"), "kr"));
67 default_currency_manager
.currencies
.append (new
Currency ("NPR", _("Nepalese Rupee"), "Rs"));
68 default_currency_manager
.currencies
.append (new
Currency ("NZD", _("New Zealand Dollar"), "$"));
69 default_currency_manager
.currencies
.append (new
Currency ("OMR", _("Omani Rial"), "ع.ر."));
70 default_currency_manager
.currencies
.append (new
Currency ("PEN", _("Peruvian Nuevo Sol"), "S/."));
71 default_currency_manager
.currencies
.append (new
Currency ("PHP", _("Philippine Peso"), "₱"));
72 default_currency_manager
.currencies
.append (new
Currency ("PKR", _("Pakistani Rupee"), "Rs"));
73 default_currency_manager
.currencies
.append (new
Currency ("PLN", _("Polish Zloty"), "zł"));
74 default_currency_manager
.currencies
.append (new
Currency ("QAR", _("Qatari Riyal"), "ق.ر"));
75 default_currency_manager
.currencies
.append (new
Currency ("RON", _("New Romanian Leu"), "L"));
76 default_currency_manager
.currencies
.append (new
Currency ("RUB", _("Russian Rouble"), "руб."));
77 default_currency_manager
.currencies
.append (new
Currency ("SAR", _("Saudi Riyal"), "س.ر"));
78 default_currency_manager
.currencies
.append (new
Currency ("SEK", _("Swedish Krona"), "kr"));
79 default_currency_manager
.currencies
.append (new
Currency ("SGD", _("Singapore Dollar"), "$"));
80 default_currency_manager
.currencies
.append (new
Currency ("THB", _("Thai Baht"), "฿"));
81 default_currency_manager
.currencies
.append (new
Currency ("TND", _("Tunisian Dinar"), "ت.د"));
82 default_currency_manager
.currencies
.append (new
Currency ("TRY", _("New Turkish Lira"), "TL"));
83 default_currency_manager
.currencies
.append (new
Currency ("TTD", _("T&T Dollar (TTD)"), "$"));
84 default_currency_manager
.currencies
.append (new
Currency ("USD", _("US Dollar"), "$"));
85 default_currency_manager
.currencies
.append (new
Currency ("UYU", _("Uruguayan Peso"), "$"));
86 default_currency_manager
.currencies
.append (new
Currency ("VEF", _("Venezuelan Bolívar"), "Bs F"));
87 default_currency_manager
.currencies
.append (new
Currency ("ZAR", _("South African Rand"), "R"));
89 return default_currency_manager
;
92 public List
<Currency
> get_currencies ()
94 var r
= new List
<Currency
> ();
95 foreach (var c
in currencies
)
100 public Currency?
get_currency (string name
)
102 foreach (var c
in currencies
)
106 var value
= c
.get_value ();
107 if (value
== null || value
.is_negative () || value
.is_zero ())
117 private string get_imf_rate_filepath ()
119 return Path
.build_filename (Environment
.get_user_cache_dir (), "gcalctool", "rms_five.xls");
122 private string get_ecb_rate_filepath ()
124 return Path
.build_filename (Environment
.get_user_cache_dir (), "gcalctool", "eurofxref-daily.xml");
127 private Currency
add_currency (string short_name
)
129 foreach (var c
in currencies
)
130 if (c
.name
== short_name
)
133 warning ("Currency %s is not in the currency table", short_name
);
134 var c
= new
Currency (short_name
, short_name
, short_name
);
135 currencies
.append (c
);
140 /* A file needs to be redownloaded if it doesn't exist, or is too old.
141 * When an error occur, it probably won't hurt to try to download again.
143 private bool file_needs_update (string filename
, double max_age
)
145 if (!FileUtils
.test (filename
, FileTest
.IS_REGULAR
))
148 var buf
= Posix
.Stat ();
149 if (Posix
.stat (filename
, out buf
) == -1)
152 var modify_time
= buf
.st_mtime
;
154 if (now
- modify_time
> max_age
)
160 private void load_imf_rates ()
162 var name_map
= new HashTable
<string, string> (str_hash
, str_equal
);
163 name_map
.insert ("Euro", "EUR");
164 name_map
.insert ("Japanese Yen", "JPY");
165 name_map
.insert ("U.K. Pound Sterling", "GBP");
166 name_map
.insert ("U.S. Dollar", "USD");
167 name_map
.insert ("Algerian Dinar", "DZD");
168 name_map
.insert ("Australian Dollar", "AUD");
169 name_map
.insert ("Bahrain Dinar", "BHD");
170 name_map
.insert ("Botswana Pula", "BWP");
171 name_map
.insert ("Brazilian Real", "BRL");
172 name_map
.insert ("Brunei Dollar", "BND");
173 name_map
.insert ("Canadian Dollar", "CAD");
174 name_map
.insert ("Chilean Peso", "CLP");
175 name_map
.insert ("Chinese Yuan", "CNY");
176 name_map
.insert ("Colombian Peso", "COP");
177 name_map
.insert ("Czech Koruna", "CZK");
178 name_map
.insert ("Danish Krone", "DKK");
179 name_map
.insert ("Hungarian Forint", "HUF");
180 name_map
.insert ("Icelandic Krona", "ISK");
181 name_map
.insert ("Indian Rupee", "INR");
182 name_map
.insert ("Indonesian Rupiah", "IDR");
183 name_map
.insert ("Iranian Rial", "IRR");
184 name_map
.insert ("Israeli New Sheqel", "ILS");
185 name_map
.insert ("Kazakhstani Tenge", "KZT");
186 name_map
.insert ("Korean Won", "KRW");
187 name_map
.insert ("Kuwaiti Dinar", "KWD");
188 name_map
.insert ("Libyan Dinar", "LYD");
189 name_map
.insert ("Malaysian Ringgit", "MYR");
190 name_map
.insert ("Mauritian Rupee", "MUR");
191 name_map
.insert ("Mexican Peso", "MXN");
192 name_map
.insert ("Nepalese Rupee", "NPR");
193 name_map
.insert ("New Zealand Dollar", "NZD");
194 name_map
.insert ("Norwegian Krone", "NOK");
195 name_map
.insert ("Rial Omani", "OMR");
196 name_map
.insert ("Pakistani Rupee", "PKR");
197 name_map
.insert ("Nuevo Sol", "PEN");
198 name_map
.insert ("Philippine Peso", "PHP");
199 name_map
.insert ("Polish Zloty", "PLN");
200 name_map
.insert ("Qatar Riyal", "QAR");
201 name_map
.insert ("Russian Ruble", "RUB");
202 name_map
.insert ("Saudi Arabian Riyal", "SAR");
203 name_map
.insert ("Singapore Dollar", "SGD");
204 name_map
.insert ("South African Rand", "ZAR");
205 name_map
.insert ("Sri Lanka Rupee", "LKR");
206 name_map
.insert ("Swedish Krona", "SEK");
207 name_map
.insert ("Swiss Franc", "CHF");
208 name_map
.insert ("Thai Baht", "THB");
209 name_map
.insert ("Trinidad And Tobago Dollar", "TTD");
210 name_map
.insert ("Tunisian Dinar", "TND");
211 name_map
.insert ("U.A.E. Dirham", "AED");
212 name_map
.insert ("Peso Uruguayo", "UYU");
213 name_map
.insert ("Bolivar Fuerte", "VEF");
215 var filename
= get_imf_rate_filepath ();
219 FileUtils
.get_contents (filename
, out data
);
223 warning ("Failed to read exchange rates: %s", e
.message
);
227 var lines
= data
.split ("\n", 0);
230 foreach (var line
in lines
)
234 /* Start after first blank line, stop on next */
248 var tokens
= line
.split ("\t", 0);
249 if (tokens
[0] != "Currency")
252 for (value_index
= 1; value_index
< tokens
.length
; value_index
++)
254 var value
= tokens
[value_index
].chug ();
259 if (value_index
< tokens
.length
)
261 var symbol
= name_map
.lookup (tokens
[0]);
264 var c
= get_currency (symbol
);
267 debug ("Using IMF rate of %s for %s", tokens
[value_index
], symbol
);
268 c
= add_currency (symbol
);
270 var value
= mp_set_from_string (tokens
[value_index
]);
271 value
= value
.reciprocal ();
275 warning ("Unknown currency '%s'", tokens
[0]);
281 private void set_ecb_rate (Xml
.Node node
, Currency eur_rate
)
283 string? name
= null, value
= null;
285 for (var attribute
= node
.properties
; attribute
!= null; attribute
= attribute
->next
)
287 var n
= (Xml
.Node
*) attribute
;
288 if (attribute
->name
== "currency")
289 name
= n
->get_content ();
290 else if (attribute
->name
== "rate")
291 value
= n
->get_content ();
294 /* Use data if value and no rate currently defined */
295 if (name
!= null && value
!= null && get_currency (name
) == null)
297 debug ("Using ECB rate of %s for %s", value
, name
);
298 var c
= add_currency (name
);
299 var r
= mp_set_from_string (value
);
300 var v
= eur_rate
.get_value ();
306 private void set_ecb_fixed_rate (string name
, string value
, Currency eur_rate
)
308 debug ("Using ECB fixed rate of %s for %s", value
, name
);
309 var c
= add_currency (name
);
310 var r
= mp_set_from_string (value
);
311 var v
= eur_rate
.get_value ();
316 private void load_ecb_rates ()
318 /* Scale rates to the EUR value */
319 var eur_rate
= get_currency ("EUR");
320 if (eur_rate
== null)
322 warning ("Cannot use ECB rates as don't have EUR rate");
326 /* Set some fixed rates */
327 set_ecb_fixed_rate ("EEK", "0.06391", eur_rate
);
328 set_ecb_fixed_rate ("CFA", "0.152449", eur_rate
);
331 var filename
= get_ecb_rate_filepath ();
332 var document
= Xml
.Parser
.read_file (filename
);
333 if (document
== null)
335 warning ("Couldn't parse ECB rate file %s", filename
);
339 var xpath_ctx
= new Xml
.XPath
.Context (document
);
340 if (xpath_ctx
== null)
342 warning ("Couldn't create XPath context");
346 xpath_ctx
.register_ns ("xref", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
347 var xpath_obj
= xpath_ctx
.eval_expression ("//xref:Cube[@currency][@rate]");
348 if (xpath_obj
== null)
350 warning ("Couldn't create XPath object");
353 var len
= (xpath_obj
->nodesetval
!= null) ? xpath_obj
->nodesetval
->length () : 0;
354 for (var i
= 0; i
< len
; i
++)
356 var node
= xpath_obj
->nodesetval
->item (i
);
358 if (node
->type
== Xml
.ElementType
.ELEMENT_NODE
)
359 set_ecb_rate (node
, eur_rate
);
361 /* Avoid accessing removed elements */
362 if (node
->type
!= Xml
.ElementType
.NAMESPACE_DECL
)
366 Xml
.Parser
.cleanup ();
369 private bool load_rates ()
376 if (downloading_imf_rates
|| downloading_ecb_rates
)
379 /* Use the IMF provided values and top up with currencies tracked by the ECB and not the IMF */
383 /* Check if we couldn't find out a currency */
384 foreach (var c
in currencies
)
385 if (c
.get_value ().is_zero ())
386 warning ("Currency %s is not provided by IMF or ECB", c
.name
);
388 debug ("Rates loaded");
396 public Number?
get_value (string currency
)
398 /* Update rates if necessary */
399 var path
= get_imf_rate_filepath ();
400 if (!downloading_imf_rates
&& file_needs_update (path
, 60 * 60 * 24 * 7))
402 downloading_imf_rates
= true;
403 debug ("Downloading rates from the IMF...");
404 download_file ("http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y", path
, download_imf_cb
);
406 path
= get_ecb_rate_filepath ();
407 if (!downloading_ecb_rates
&& file_needs_update (path
, 60 * 60 * 24 * 7))
409 downloading_ecb_rates
= true;
410 debug ("Downloading rates from the ECB...");
411 download_file ("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml", path
, download_ecb_cb
);
417 var c
= get_currency (currency
);
419 return c
.get_value ();
424 private void download_file (string uri
, string filename
, AsyncReadyCallback
callback)
426 var directory
= Path
.get_dirname (filename
);
427 DirUtils
.create_with_parents (directory
, 0755);
429 var source
= File
.new_for_uri (uri
);
430 var dest
= File
.new_for_path (filename
);
432 source
.copy_async
.begin (dest
, FileCopyFlags
.OVERWRITE
, GLib
.Priority
.DEFAULT
, null, null, callback);
435 private void download_imf_cb (Object?
object, AsyncResult result
)
437 var f
= object as File
;
441 f
.copy_async
.end (result
);
442 debug ("IMF rates updated");
446 warning ("Couldn't download IMF currency rate file: %s", e
.message
);
449 downloading_imf_rates
= false;
453 private void download_ecb_cb (Object?
object, AsyncResult result
)
455 var f
= object as File
;
458 f
.copy_async
.end (result
);
459 debug ("ECB rates updated");
463 warning ("Couldn't download ECB currency rate file: %s", e
.message
);
465 downloading_ecb_rates
= false;
470 public class Currency
: Object
472 private Number value
;
474 private string _name
;
475 public string name
{ owned
get { return _name
; } }
477 private string _display_name
;
478 public string display_name
{ owned
get { return _display_name
; } }
480 private string _symbol
;
481 public string symbol
{ owned
get { return _symbol
; } }
483 public Currency (string name
, string display_name
, string symbol
)
486 _display_name
= display_name
;
490 public void set_value (Number value
)
495 public Number
get_value ()