]> err.no Git - mapper/commitdiff
Start to split map downloading functions from map.c here.
authorKaj-Michael Lang <milang@onion.tal.org>
Mon, 3 Sep 2007 09:16:56 +0000 (12:16 +0300)
committerKaj-Michael Lang <milang@onion.tal.org>
Mon, 3 Sep 2007 09:16:56 +0000 (12:16 +0300)
src/map-download.c [new file with mode: 0644]
src/map-download.h [new file with mode: 0644]

diff --git a/src/map-download.c b/src/map-download.c
new file mode 100644 (file)
index 0000000..6b32e90
--- /dev/null
@@ -0,0 +1,516 @@
+#include "config.h"
+
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <stddef.h>
+#include <locale.h>
+#include <math.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <glib/gstdio.h>
+#include <fcntl.h>
+#include <curl/multi.h>
+#include <libintl.h>
+#include <locale.h>
+
+#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 (file)
index 0000000..d2b6a16
--- /dev/null
@@ -0,0 +1,23 @@
+#include "config.h"
+
+#ifndef _MAPPER_MAP_DOWNLOAD_H
+#define _MAPPER_MAP_DOWNLOAD_H
+
+#include <curl/multi.h>
+#include <glib.h>
+
+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