From b0ac109d0c95ec554253956da2149054b0fdfc5c Mon Sep 17 00:00:00 2001 From: Kaj-Michael Lang Date: Mon, 3 Sep 2007 12:16:56 +0300 Subject: [PATCH] Start to split map downloading functions from map.c here. --- src/map-download.c | 516 +++++++++++++++++++++++++++++++++++++++++++++ src/map-download.h | 23 ++ 2 files changed, 539 insertions(+) create mode 100644 src/map-download.c create mode 100644 src/map-download.h diff --git a/src/map-download.c b/src/map-download.c new file mode 100644 index 0000000..6b32e90 --- /dev/null +++ b/src/map-download.c @@ -0,0 +1,516 @@ +#include "config.h" + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hildon-mapper.h" + +#include "utils.h" +#include "map.h" +#include "osm.h" +#include "db.h" +#include "osm-db.h" +#include "poi.h" +#include "route.h" +#include "gps.h" +#include "bt.h" +#include "mapper-types.h" +#include "ui-common.h" +#include "settings.h" +#include "latlon.h" +#include "gpx.h" +#include "map-download.h" + +guint _num_downloads=0; +guint _curr_download=0; + +static gchar *map_construct_url(guint tilex, guint tiley, guint zoom); + +static gboolean +get_next_pui(gpointer key, gpointer value, ProgressUpdateInfo ** data) +{ +*data = key; +return TRUE; +} + +/** + * Free a ProgressUpdateInfo data structure that was allocated during the + * auto-map-download process. + */ +static void +progress_update_info_free(ProgressUpdateInfo * pui) +{ +g_free(pui->src_str); +g_free(pui->dest_str); +g_slice_free(ProgressUpdateInfo, pui); +} + + +gboolean +map_download_timeout() +{ +static guint destroy_counter = 50; +gint num_transfers = 0, num_msgs = 0; +gint deletes_left = 50; /* only do 50 deletes at a time. */ +CURLMsg *msg; +vprintf("%s()\n", __PRETTY_FUNCTION__); + +if (_curl_multi && CURLM_CALL_MULTI_PERFORM == curl_multi_perform(_curl_multi, &num_transfers)) + return TRUE; /* Give UI a chance first. */ + +while (_curl_multi && (msg = curl_multi_info_read(_curl_multi, &num_msgs))) { + if (msg->msg == CURLMSG_DONE) { + if (msg->easy_handle == _autoroute_data.curl_easy) { + /* This is the autoroute download. */ + /* Now, parse the autoroute and update the display. */ + if (_autoroute_data.enabled + && parse_gpx(&_route, + _autoroute_data.rdl_data.bytes, + _autoroute_data.rdl_data.bytes_read, 0)) { + /* Find the nearest route point, if we're connected. */ + route_find_nearest_point(); + map_force_redraw(); + } + cancel_autoroute(TRUE); /* We're done. Clean up. */ + } else { + ProgressUpdateInfo *pui = g_hash_table_lookup(_pui_by_easy, msg->easy_handle); + g_queue_push_head(_curl_easy_queue, msg->easy_handle); + g_hash_table_remove(_pui_by_easy, msg->easy_handle); + fclose(pui->file); + if (msg->data.result != CURLE_OK) + g_unlink(pui->dest_str); /* Delete so we try again. */ + curl_multi_remove_handle(_curl_multi, msg->easy_handle); + g_idle_add_full(G_PRIORITY_HIGH_IDLE, + (GSourceFunc)map_download_idle_refresh, pui, NULL); + } + } +} + +/* Up to 1 transfer per tile. */ +while (num_transfers < (BUF_WIDTH_TILES * BUF_HEIGHT_TILES) && g_tree_nnodes(_pui_tree)) { + ProgressUpdateInfo *pui; + g_tree_foreach(_pui_tree, (GTraverseFunc) get_next_pui, &pui); + + if (pui->retries) { + /* This is a download. */ + FILE *f; + g_tree_steal(_pui_tree, pui); + g_tree_insert(_downloading_tree, pui, pui); + + pui->src_str = map_construct_url(pui->tilex, pui->tiley, pui->zoom); + pui->dest_str = g_strdup_printf("%s/%u/%u/%u.jpg", + pui->repo->cache_dir, pui->zoom, + pui->tilex, pui->tiley); + + if (!pui->src_str) { + /* Failed to generate URL. */ + g_idle_add_full(G_PRIORITY_HIGH_IDLE,(GSourceFunc)map_download_idle_refresh, pui, NULL); + continue; + } + + /* Check to see if we need to overwrite. */ + if (pui->retries > 0) { + /* We're not updating - check if file already exists. */ + if (g_file_test(pui->dest_str, G_FILE_TEST_EXISTS)) { + g_idle_add_full(G_PRIORITY_HIGH_IDLE, + (GSourceFunc)map_download_idle_refresh, pui, NULL); + continue; + } + } + + /* Attempt to open the file for writing. */ + if (!(f = g_fopen(pui->dest_str, "w")) + && errno == ENOENT) { + /* Directory doesn't exist yet - create it, then we'll retry */ + gchar buffer[BUFFER_SIZE]; + snprintf(buffer, sizeof(buffer), "%s/%u/%u", + pui->repo->cache_dir, pui->zoom, pui->tilex); + g_mkdir_with_parents(buffer, 0775); + f = g_fopen(pui->dest_str, "w"); + } + + if (f) { + CURL *curl_easy; + pui->file = f; + curl_easy = g_queue_pop_tail(_curl_easy_queue); + if (!curl_easy) { + /* Need a new curl_easy. */ + MACRO_CURL_EASY_INIT(curl_easy); + } + curl_easy_setopt(curl_easy, CURLOPT_URL, pui->src_str); + curl_easy_setopt(curl_easy, CURLOPT_WRITEDATA, f); + g_hash_table_insert(_pui_by_easy, curl_easy, pui); + if (!_curl_multi) { + /* Initialize CURL. */ + _curl_multi = curl_multi_init(); + /*curl_multi_setopt(_curl_multi, CURLMOPT_PIPELINING, 1); */ + } + curl_multi_add_handle(_curl_multi, curl_easy); + num_transfers++; + } else { + /* Unable to download file. */ + gchar buffer[BUFFER_SIZE]; + snprintf(buffer, sizeof(buffer), "%s:\n%s", + _("Failed to open file for writing"), + pui->dest_str); + MACRO_BANNER_SHOW_INFO(_window, buffer); + g_idle_add_full(G_PRIORITY_HIGH_IDLE, + (GSourceFunc)map_download_idle_refresh, pui, + NULL); + continue; + } + } else if (--deletes_left) { + /* This is a delete. */ + gchar buffer[BUFFER_SIZE]; + g_tree_steal(_pui_tree, pui); + g_tree_insert(_downloading_tree, pui, pui); + + snprintf(buffer, sizeof(buffer), "%s/%u/%u/%u.jpg", + pui->repo->cache_dir, pui->zoom, pui->tilex, + pui->tiley); + g_unlink(buffer); + g_idle_add_full(G_PRIORITY_HIGH_IDLE, + (GSourceFunc)map_download_idle_refresh, + pui, NULL); + } else + break; +} + +if (!(num_transfers || g_tree_nnodes(_pui_tree))) { + /* Destroy curl after 50 counts (5 seconds). */ + if (--destroy_counter) { + /* Clean up curl. */ + CURL *curr; + while ((curr = g_queue_pop_tail(_curl_easy_queue))) + curl_easy_cleanup(curr); + + curl_multi_cleanup(_curl_multi); + _curl_multi = NULL; + + _curl_sid = 0; + vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__); + return FALSE; + } +} else + destroy_counter = 50; + +vprintf("%s(): return TRUE (%d, %d)\n", __PRETTY_FUNCTION__, + num_transfers, g_tree_nnodes(_pui_tree)); +return TRUE; +} + +/** + * Given a wms uri pattern, compute the coordinate transformation and + * trimming. + * 'proj' is used for the conversion + */ +static gchar * +map_convert_wms_to_wms(gint tilex, gint tiley, gint zoomlevel, gchar * uri) +{ +gint system_retcode; +gchar cmd[BUFFER_SIZE], srs[BUFFER_SIZE]; +gchar *ret = NULL; +FILE *in; +gdouble lon1, lat1, lon2, lat2; + +gchar *widthstr = strcasestr(uri, "WIDTH="); +gchar *heightstr = strcasestr(uri, "HEIGHT="); +gchar *srsstr = strcasestr(uri, "SRS=EPSG"); +gchar *srsstre = strchr(srsstr, '&'); + +/* missing: test if found */ +strcpy(srs, "epsg"); +strncpy(srs + 4, srsstr + 8, 256); +/* missing: test srsstre-srsstr < 526 */ +srs[srsstre - srsstr - 4] = 0; +/* convert to lower, as WMC is EPSG and cs2cs is epsg */ + +gint dwidth = widthstr ? atoi(widthstr + 6) - TILE_SIZE_PIXELS : 0; +gint dheight = heightstr ? atoi(heightstr + 7) - TILE_SIZE_PIXELS : 0; + +unit2latlon(tile2zunit(tilex, zoomlevel) + - pixel2zunit(dwidth / 2, zoomlevel), + tile2zunit(tiley + 1, zoomlevel) + + pixel2zunit((dheight + 1) / 2, zoomlevel), lat1, lon1); + +unit2latlon(tile2zunit(tilex + 1, zoomlevel) + + pixel2zunit((dwidth + 1) / 2, zoomlevel), + tile2zunit(tiley, zoomlevel) + - pixel2zunit(dheight / 2, zoomlevel), lat2, lon2); + +setlocale(LC_NUMERIC, "C"); + +snprintf(cmd, sizeof(cmd), + "(echo \"%.6f %.6f\"; echo \"%.6f %.6f\") | " + "/usr/bin/cs2cs +proj=longlat +datum=WGS84 +to +init=%s -f %%.6f " + " > /tmp/tmpcs2cs ", lon1, lat1, lon2, lat2, srs); +vprintf("Running command: %s\n", cmd); +system_retcode = system(cmd); + +if (system_retcode) + g_printerr("cs2cs returned error code %d\n", + WEXITSTATUS(system_retcode)); +else if (!(in = g_fopen("/tmp/tmpcs2cs", "r"))) + g_printerr("Cannot open results of conversion\n"); +else if (5 != + fscanf(in, "%lf %lf %s %lf %lf", &lon1, &lat1, cmd, &lon2, &lat2)) { + g_printerr("Wrong conversion\n"); + fclose(in); +} else { + fclose(in); + ret = g_strdup_printf(uri, lon1, lat1, lon2, lat2); +} + +setlocale(LC_NUMERIC, ""); + +return ret; +} + + +/** + * Given the xyz coordinates of our map coordinate system, write the qrst + * quadtree coordinates to buffer. + */ +static void +map_convert_coords_to_quadtree_string(gint x, gint y, gint zoomlevel, + gchar * buffer, const gchar initial, + const gchar * const quadrant) +{ +gchar *ptr = buffer; +gint n; + +if (initial) + *ptr++ = initial; + +for (n = 16 - zoomlevel; n >= 0; n--) { + gint xbit = (x >> n) & 1; + gint ybit = (y >> n) & 1; + *ptr++ = quadrant[xbit + 2 * ybit]; +} +*ptr++ = '\0'; +} + +/** + * Construct the URL that we should fetch, based on the current URI format. + * This method works differently depending on if a "%s" string is present in + * the URI format, since that would indicate a quadtree-based map coordinate + * system. + */ +static gchar * +map_construct_url(guint tilex, guint tiley, guint zoom) +{ +switch (_curr_repo->type) { +case REPOTYPE_XYZ: + return g_strdup_printf(_curr_repo->url, tilex, tiley, zoom); + +case REPOTYPE_XYZ_INV: + return g_strdup_printf(_curr_repo->url, 17 - zoom, tilex, tiley); + +case REPOTYPE_QUAD_QRST: + { + gchar location[MAX_ZOOM + 2]; + map_convert_coords_to_quadtree_string(tilex, tiley, zoom, location, 't', "qrts"); + return g_strdup_printf(_curr_repo->url, location); + } + +case REPOTYPE_QUAD_ZERO: + { + /* This is a zero-based quadtree URI. */ + gchar location[MAX_ZOOM + 2]; + map_convert_coords_to_quadtree_string(tilex, tiley, zoom, location, '\0', "0123"); + return g_strdup_printf(_curr_repo->url, location); + } + +case REPOTYPE_WMS: + return map_convert_wms_to_wms(tilex, tiley, zoom, _curr_repo->url); + +default: + return NULL; +} +return ""; +} + + +gboolean +map_download_idle_refresh(ProgressUpdateInfo * pui) +{ +/* Test if download succeeded (only if retries != 0). */ +if (!pui->retries || g_file_test(pui->dest_str, G_FILE_TEST_EXISTS)) { + gint zoom_diff = pui->zoom - _zoom; + /* Only refresh at same or "lower" (more detailed) zoom level. */ + if (zoom_diff >= 0) { + /* If zoom has changed since we first put in the request for + * this tile, then we may have to update more than one tile. */ + guint tilex, tiley, tilex_end, tiley_end; + for (tilex = pui->tilex << zoom_diff, + tilex_end = tilex + (1 << zoom_diff); + tilex < tilex_end; tilex++) { + for (tiley = pui->tiley << zoom_diff, + tiley_end = tiley + (1 << zoom_diff); + tiley < tiley_end; tiley++) { + if ((tilex - _base_tilex) < BUF_WIDTH_TILES && (tiley - _base_tiley) < BUF_HEIGHT_TILES) { + map_render_tile(tilex, tiley, + ((tilex - _base_tilex) << TILE_SIZE_P2), + ((tiley - _base_tiley) << TILE_SIZE_P2), + TRUE); + map_render_data(); + gtk_widget_queue_draw_area + (_map_widget, + ((tilex - _base_tilex) << TILE_SIZE_P2) - _offsetx, + ((tiley - _base_tiley) << TILE_SIZE_P2) - _offsety, + TILE_SIZE_PIXELS, TILE_SIZE_PIXELS); + } + } + } + } +} +/* Else the download failed. Update retries and maybe try again. */ +else { + if (pui->retries > 0) + --pui->retries; + else if (pui->retries < 0) + ++pui->retries; + + if (pui->retries) { + /* removal automatically calls progress_update_info_free(). */ + g_tree_steal(_downloading_tree, pui); + g_tree_insert(_pui_tree, pui, pui); +#ifdef WITH_OSSO + if (iap_is_connected() && !_curl_sid) +#endif + _curl_sid = g_timeout_add(100,(GSourceFunc)map_download_timeout, NULL); + /* Don't do anything else. */ + return FALSE; + } else { + /* No more retries left - something must be wrong. */ + MACRO_BANNER_SHOW_INFO(_window, _("Error in download. Check internet connection" + " and/or Map Repository URL Format.")); + } +} + +/* removal automatically calls progress_update_info_free(). */ +g_tree_remove(_downloading_tree, pui); + +if (++_curr_download == _num_downloads) { +#ifdef WITH_HILDON + gtk_widget_destroy(_download_banner); + _download_banner = NULL; +#else + gtk_widget_hide(GTK_WIDGET(_progress_item)); +#endif + _num_downloads = _curr_download = 0; +} else + hildon_banner_set_fraction(HILDON_BANNER(_download_banner), + _curr_download / (double)_num_downloads); + +return FALSE; +} + +/** + * Initiate a download of the given xyz coordinates using the given buffer + * as the URL. If the map already exists on disk, or if we are already + * downloading the map, then this method does nothing. + */ +void +map_initiate_download(guint tilex, guint tiley, guint zoom, gint retries) +{ +ProgressUpdateInfo *pui; + +#ifdef WITH_OSSO +iap_connect(); +#endif + +pui = g_slice_new(ProgressUpdateInfo); +pui->tilex = tilex; +pui->tiley = tiley; +pui->zoom = zoom; +pui->priority = (abs((gint) tilex - unit2tile(_center.unitx)) + + abs((gint) tiley - unit2tile(_center.unity))); +if (!retries) + pui->priority = -pui->priority; /* "Negative" makes them lowest pri. */ +pui->retries = retries; +pui->repo = _curr_repo; + +if (g_tree_lookup(_pui_tree, pui) || g_tree_lookup(_downloading_tree, pui)) { + /* Already downloading. */ + g_slice_free(ProgressUpdateInfo, pui); + return; +} +pui->src_str = NULL; +pui->dest_str = NULL; +pui->file = NULL; + +g_tree_insert(_pui_tree, pui, pui); +#ifdef WITH_OSSO +if (iap_is_connected() && !_curl_sid) +#endif + _curl_sid = g_timeout_add(100, (GSourceFunc) map_download_timeout, NULL); + +if (!_num_downloads++ && !_download_banner) + _download_banner = hildon_banner_show_progress(_window, NULL,_("Downloading maps")); +} + +void +map_download_init(void) +{ +_curl_easy_queue = g_queue_new(); +_pui_tree = g_tree_new_full((GCompareDataFunc) download_comparefunc, NULL, (GDestroyNotify) progress_update_info_free, NULL); +_downloading_tree = g_tree_new_full((GCompareDataFunc) download_comparefunc, NULL, (GDestroyNotify) progress_update_info_free, NULL); +_pui_by_easy = g_hash_table_new(g_direct_hash, g_direct_equal); +} + +void +map_download_deinit(void) +{ +/* Clean up CURL. */ +if (_curl_multi) { + CURL *curr; + CURLMsg *msg; + gint num_transfers, num_msgs; + + /* First, remove all downloads from _pui_tree. */ + g_tree_destroy(_pui_tree); + + /* Finish up all downloads. */ + while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(_curl_multi, &num_transfers) || num_transfers) { + /* XXX: should inform the user why it's taking so damn long... */ + } + + /* Close all finished files. */ + while ((msg = curl_multi_info_read(_curl_multi, &num_msgs))) { + if (msg->msg == CURLMSG_DONE) { + /* This is a map download. */ + ProgressUpdateInfo *pui = g_hash_table_lookup(_pui_by_easy, msg->easy_handle); + g_queue_push_head(_curl_easy_queue, msg->easy_handle); + g_hash_table_remove(_pui_by_easy, msg->easy_handle); + fclose(pui->file); + curl_multi_remove_handle(_curl_multi, msg->easy_handle); + } + } + + while ((curr = g_queue_pop_tail(_curl_easy_queue))) + curl_easy_cleanup(curr); + + curl_multi_cleanup(_curl_multi); + _curl_multi = NULL; + + g_queue_free(_curl_easy_queue); + g_tree_destroy(_downloading_tree); + g_hash_table_destroy(_pui_by_easy); +} + +} diff --git a/src/map-download.h b/src/map-download.h new file mode 100644 index 0000000..d2b6a16 --- /dev/null +++ b/src/map-download.h @@ -0,0 +1,23 @@ +#include "config.h" + +#ifndef _MAPPER_MAP_DOWNLOAD_H +#define _MAPPER_MAP_DOWNLOAD_H + +#include +#include + +CURLM *_curl_multi; +GQueue *_curl_easy_queue; +GTree *_pui_tree; +GTree *_downloading_tree; +GHashTable *_pui_by_easy; + +guint _num_downloads; +guint _curr_download; + +void map_download_init(void); +void map_initiate_download(guint tilex, guint tiley, guint zoom, gint retries); +gboolean map_download_timeout(); +gboolean map_download_idle_refresh(ProgressUpdateInfo * pui); + +#endif -- 2.39.5