#include #include #include #include #include #include #include #include "currency.h" #include "mp.h" typedef struct { char* short_name; MPNumber value; } currency; static currency* currencies = NULL; static int currency_count = 0; static gboolean downloading_rates = FALSE; static gboolean loaded_rates = FALSE; static char* get_rate_filepath() { return g_build_filename(g_get_user_cache_dir(), "mate-calc", "eurofxref-daily.xml", NULL); } static int currency_get_index(const char *short_name) { int i; for (i = 0; i < currency_count; i++) { if (!strcmp(short_name, currencies[i].short_name)) { if (mp_is_negative(¤cies[i].value) || mp_is_zero(¤cies[i].value)) { return -1; } else { return i; } } } return -1; } /* A file needs to be redownloaded if it doesn't exist, or every 7 days. * When an error occur, it probably won't hurt to try to download again. */ static int currency_rates_needs_update() { gchar* filename = get_rate_filepath(); struct stat buf; if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { g_free(filename); return 1; } if (g_stat(filename, &buf) == -1) { g_free(filename); return 1; } g_free(filename); if (difftime(time(NULL), buf.st_mtime) > (60 * 60 * 24 * 7)) { return 1; } return 0; } static void download_cb(GObject* object, GAsyncResult* result, gpointer user_data) { GError* error = NULL; if (g_file_copy_finish(G_FILE(object), result, &error)) { g_debug("Rates updated"); } else { g_warning("Couldn't download currency file: %s", error->message); } g_clear_error(&error); downloading_rates = FALSE; } static void currency_download_rates() { gchar* filename; gchar* directory; GFile* source; GFile* dest; downloading_rates = TRUE; g_debug("Downloading rates..."); filename = get_rate_filepath(); directory = g_path_get_dirname(filename); g_mkdir_with_parents(directory, 0755); g_free(directory); /* HACK: It is safe? */ source = g_file_new_for_uri("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"); dest = g_file_new_for_path(filename); g_free(filename); g_file_copy_async(source, dest, G_FILE_COPY_OVERWRITE, G_PRIORITY_DEFAULT, NULL, NULL, NULL, download_cb, NULL); g_object_unref(source); g_object_unref(dest); } static void set_rate(xmlNodePtr node, currency* cur) { xmlAttrPtr attribute; for (attribute = node->properties; attribute; attribute = attribute->next) { if (strcmp((char*) attribute->name, "currency") == 0) { cur->short_name = (char*) xmlNodeGetContent((xmlNodePtr) attribute); } else if (strcmp ((char*) attribute->name, "rate") == 0) { char* val = (char*) xmlNodeGetContent((xmlNodePtr) attribute); mp_set_from_string(val, 10, &(cur->value)); xmlFree(val); } } } static void currency_load_rates() { char* filename = get_rate_filepath(); xmlDocPtr document; xmlXPathContextPtr xpath_ctx; xmlXPathObjectPtr xpath_obj; int i; int len; g_return_if_fail(g_file_test(filename, G_FILE_TEST_IS_REGULAR)); xmlInitParser(); document = xmlReadFile(filename, NULL, 0); g_free (filename); if (document == NULL) { fprintf(stderr, "Couldn't parse data file\n"); return; } xpath_ctx = xmlXPathNewContext(document); if (xpath_ctx == NULL) { xmlFreeDoc(document); fprintf(stderr, "Couldn't create XPath context\n"); return; } xmlXPathRegisterNs(xpath_ctx, BAD_CAST("xref"), BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref")); xpath_obj = xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"), xpath_ctx); if (xpath_obj == NULL) { xmlXPathFreeContext(xpath_ctx); xmlFreeDoc(document); fprintf(stderr, "Couldn't create XPath object\n"); return; } len = (xpath_obj->nodesetval) ? xpath_obj->nodesetval->nodeNr : 0; currency_count = len + 1; currencies = g_slice_alloc0(sizeof(currency) * currency_count); for (i = 0; i < len; i++) { if (xpath_obj->nodesetval->nodeTab[i]->type == XML_ELEMENT_NODE) { set_rate(xpath_obj->nodesetval->nodeTab[i], ¤cies[i]); } // Avoid accessing removed elements if (xpath_obj->nodesetval->nodeTab[i]->type != XML_NAMESPACE_DECL) { xpath_obj->nodesetval->nodeTab[i] = NULL; } } currencies[len].short_name = g_strdup("EUR"); MPNumber foo; mp_set_from_integer(1, &foo); currencies[len].value = foo; xmlXPathFreeObject(xpath_obj); xmlXPathFreeContext(xpath_ctx); xmlFreeDoc(document); xmlCleanupParser(); g_debug("Rates loaded"); loaded_rates = TRUE; } gboolean currency_convert(const MPNumber* from_amount, const char* source_currency, const char* target_currency, MPNumber* to_amount) { int from_index, to_index; if (downloading_rates) { return FALSE; } /* Update currency if necessary */ if (currency_rates_needs_update()) { currency_download_rates(); return FALSE; } if (!loaded_rates) { currency_load_rates(); } from_index = currency_get_index(source_currency); to_index = currency_get_index(target_currency); if (from_index < 0 || to_index < 0) { return FALSE; } if (mp_is_zero(¤cies[from_index].value) || mp_is_zero(¤cies[to_index].value)) { mp_set_from_integer(0, to_amount); return FALSE; } mp_divide(from_amount, ¤cies[from_index].value, to_amount); mp_multiply(to_amount, ¤cies[to_index].value, to_amount); return TRUE; } void currency_free_resources() { int i; for (i = 0; i < currency_count; i++) { if (currencies[i].short_name != NULL) { xmlFree(currencies[i].short_name); } } g_slice_free1(currency_count * sizeof(currency), currencies); currencies = NULL; currency_count = 0; }