]> err.no Git - mapper/blob - src/map-download.c
A bunch of small and large code changes:
[mapper] / src / map-download.c
1 /*
2  * This file is part of mapper
3  *
4  * Copyright (C) 2007 Kaj-Michael Lang
5  * Copyright (C) 2006-2007 John Costigan.
6  *
7  * POI and GPS-Info code originally written by Cezary Jackiewicz.
8  *
9  * Default map data provided by http://www.openstreetmap.org/
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License along
22  * with this program; if not, write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24  */
25
26 #include "config.h"
27
28 #define _GNU_SOURCE
29
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <stddef.h>
35 #include <locale.h>
36 #include <math.h>
37 #include <errno.h>
38 #include <sys/wait.h>
39 #include <glib/gstdio.h>
40 #include <fcntl.h>
41 #include <curl/multi.h>
42 #include <libintl.h>
43 #include <locale.h>
44
45 #include "hildon-mapper.h"
46
47 #include "utils.h"
48 #include "map.h"
49 #include "osm.h"
50 #include "db.h"
51 #include "osm-db.h"
52 #include "poi.h"
53 #include "route.h"
54 #include "gps.h"
55 #include "bt.h"
56 #include "mapper-types.h"
57 #include "ui-common.h"
58 #include "settings.h"
59 #include "latlon.h"
60 #include "gpx.h"
61 #include "map-download.h"
62 #include "iap.h"
63
64 static guint _num_downloads=0;
65 static guint _curr_download=0;
66
67 static gchar *map_construct_url(guint tilex, guint tiley, guint zoom);
68
69 static gboolean 
70 get_next_pui(gpointer key, gpointer value, ProgressUpdateInfo ** data)
71 {
72 *data = key;
73 return TRUE;
74 }
75
76 /**
77  * Free a ProgressUpdateInfo data structure that was allocated during the
78  * auto-map-download process.
79  */
80 static void 
81 progress_update_info_free(ProgressUpdateInfo * pui)
82 {
83 g_free(pui->src_str);
84 g_free(pui->dest_str);
85 g_slice_free(ProgressUpdateInfo, pui);
86 }
87
88 gboolean 
89 map_download_timeout()
90 {
91 static guint destroy_counter = 50;
92 gint num_transfers = 0, num_msgs = 0;
93 gint deletes_left = 50; /* only do 50 deletes at a time. */
94 CURLMsg *msg;
95
96 if (_curl_multi && CURLM_CALL_MULTI_PERFORM == curl_multi_perform(_curl_multi, &num_transfers))
97         return TRUE;    /* Give UI a chance first. */
98
99 while (_curl_multi && (msg = curl_multi_info_read(_curl_multi, &num_msgs))) {
100         if (msg->msg == CURLMSG_DONE) {
101                 if (msg->easy_handle == _autoroute_data.curl_easy) {
102                         /* This is the autoroute download. */
103                         /* Now, parse the autoroute and update the display. */
104                         if (_autoroute_data.enabled && parse_gpx(&_route,
105                                          _autoroute_data.rdl_data.bytes,
106                                          _autoroute_data.rdl_data.bytes_read, 0)) {
107                                 /* Find the nearest route point, if we're connected. */
108                                 route_find_nearest_point();
109                                 map_force_redraw();
110                         }
111                         cancel_autoroute(TRUE); /* We're done. Clean up. */
112                 } else {
113                         ProgressUpdateInfo *pui = g_hash_table_lookup(_pui_by_easy, msg->easy_handle);
114                         g_queue_push_head(_curl_easy_queue, msg->easy_handle);
115                         g_hash_table_remove(_pui_by_easy, msg->easy_handle);
116                         fclose(pui->file);
117                         if (msg->data.result != CURLE_OK)
118                                 g_unlink(pui->dest_str);        /* Delete so we try again. */
119                         curl_multi_remove_handle(_curl_multi, msg->easy_handle);
120                         g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)map_download_idle_refresh, pui, NULL);
121                 }
122         }
123 }
124
125 /* Up to 1 transfer per tile. */
126 while (num_transfers < (BUF_WIDTH_TILES * BUF_HEIGHT_TILES) && g_tree_nnodes(_pui_tree)) {
127         ProgressUpdateInfo *pui;
128         g_tree_foreach(_pui_tree, (GTraverseFunc) get_next_pui, &pui);
129
130         if (pui->retries) {
131                 /* This is a download. */
132                 FILE *f;
133                 g_tree_steal(_pui_tree, pui);
134                 g_tree_insert(_downloading_tree, pui, pui);
135
136                 pui->src_str = map_construct_url(pui->tilex, pui->tiley, pui->zoom);
137                 pui->dest_str = g_strdup_printf("%s/%u/%u/%u.jpg",
138                                     pui->repo->cache_dir, pui->zoom,
139                                     pui->tilex, pui->tiley);
140
141                 if (!pui->src_str) {
142                         /* Failed to generate URL. */
143                         g_idle_add_full(G_PRIORITY_HIGH_IDLE,(GSourceFunc)map_download_idle_refresh, pui, NULL);
144                         continue;
145                 }
146
147                 /* Check to see if we need to overwrite. */
148                 if (pui->retries > 0) {
149                         /* We're not updating - check if file already exists. */
150                         if (g_file_test(pui->dest_str, G_FILE_TEST_EXISTS)) {
151                                 g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)map_download_idle_refresh, pui, NULL);
152                                 continue;
153                         }
154                 }
155
156                 /* Attempt to open the file for writing. */
157                 if (!(f = g_fopen(pui->dest_str, "w")) && errno == ENOENT) {
158                         /* Directory doesn't exist yet - create it, then we'll retry */
159                         gchar buffer[BUFFER_SIZE];
160                         g_snprintf(buffer, sizeof(buffer), "%s/%u/%u",
161                                  pui->repo->cache_dir, pui->zoom, pui->tilex);
162                         g_mkdir_with_parents(buffer, 0775);
163                         f = g_fopen(pui->dest_str, "w");
164                 }
165
166                 if (f) {
167                         CURL *curl_easy;
168                         pui->file = f;
169                         curl_easy = g_queue_pop_tail(_curl_easy_queue);
170                         if (!curl_easy) {
171                                 /* Need a new curl_easy. */
172                                 MACRO_CURL_EASY_INIT(curl_easy);
173                         }
174                         curl_easy_setopt(curl_easy, CURLOPT_URL, pui->src_str);
175                         curl_easy_setopt(curl_easy, CURLOPT_WRITEDATA, f);
176                         g_hash_table_insert(_pui_by_easy, curl_easy, pui);
177                         if (!_curl_multi) {
178                                 /* Initialize CURL. */
179                                 _curl_multi = curl_multi_init();
180                                 /*curl_multi_setopt(_curl_multi, CURLMOPT_PIPELINING, 1); */
181                         }
182                         curl_multi_add_handle(_curl_multi, curl_easy);
183                         num_transfers++;
184                 } else {
185                         /* Unable to open tile file for writing. */
186                         gchar buffer[BUFFER_SIZE];
187                         g_snprintf(buffer, sizeof(buffer), "%s:\n%s",
188                                  _("Failed to open file for writing"),
189                                  pui->dest_str);
190                         MACRO_BANNER_SHOW_INFO(_window, buffer);
191                         g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)map_download_idle_refresh, pui, NULL);
192                         continue;
193                 }
194         } else if (--deletes_left) {
195                 /* This is a delete. */
196                 gchar buffer[BUFFER_SIZE];
197                 g_tree_steal(_pui_tree, pui);
198                 g_tree_insert(_downloading_tree, pui, pui);
199
200                 g_snprintf(buffer, sizeof(buffer), "%s/%u/%u/%u.jpg",
201                         pui->repo->cache_dir, pui->zoom, 
202                         pui->tilex,     pui->tiley);
203                 g_unlink(buffer);
204                 g_idle_add_full(G_PRIORITY_HIGH_IDLE,(GSourceFunc)map_download_idle_refresh, pui, NULL);
205         } else
206                 break;
207 }
208
209 if (!(num_transfers || g_tree_nnodes(_pui_tree))) {
210         /* Destroy curl after 50 counts (5 seconds). */
211         if (--destroy_counter) {
212                 /* Clean up curl. */
213                 CURL *curr;
214                 while ((curr = g_queue_pop_tail(_curl_easy_queue)))
215                         curl_easy_cleanup(curr);
216
217                 curl_multi_cleanup(_curl_multi);
218                 _curl_multi = NULL;
219
220                 _curl_sid = 0;
221                 return FALSE;
222         }
223 } else
224         destroy_counter = 50;
225
226 return TRUE;
227 }
228
229 /**
230  * Given a wms uri pattern, compute the coordinate transformation and
231  * trimming.
232  * 'proj' is used for the conversion
233  */
234 static gchar *
235 map_convert_wms_to_wms(gint tilex, gint tiley, gint zoomlevel, gchar * uri)
236 {
237 gint system_retcode;
238 gchar cmd[BUFFER_SIZE], srs[BUFFER_SIZE];
239 gchar *ret = NULL;
240 FILE *in;
241 gdouble lon1, lat1, lon2, lat2;
242
243 gchar *widthstr = strcasestr(uri, "WIDTH=");
244 gchar *heightstr = strcasestr(uri, "HEIGHT=");
245 gchar *srsstr = strcasestr(uri, "SRS=EPSG");
246 gchar *srsstre = strchr(srsstr, '&');
247
248 /* missing: test if found */
249 strcpy(srs, "epsg");
250 strncpy(srs + 4, srsstr + 8, 256);
251 /* missing: test srsstre-srsstr < 526 */
252 srs[srsstre - srsstr - 4] = 0;
253 /* convert to lower, as WMC is EPSG and cs2cs is epsg */
254
255 gint dwidth = widthstr ? atoi(widthstr + 6) - TILE_SIZE_PIXELS : 0;
256 gint dheight = heightstr ? atoi(heightstr + 7) - TILE_SIZE_PIXELS : 0;
257
258 unit2latlon(tile2zunit(tilex, zoomlevel) - pixel2zunit(dwidth / 2, zoomlevel),
259             tile2zunit(tiley + 1, zoomlevel) + pixel2zunit((dheight + 1) / 2, zoomlevel), lat1, lon1);
260
261 unit2latlon(tile2zunit(tilex + 1, zoomlevel) + pixel2zunit((dwidth + 1) / 2, zoomlevel), 
262                 tile2zunit(tiley, zoomlevel) - pixel2zunit(dheight / 2, zoomlevel), lat2, lon2);
263
264 setlocale(LC_NUMERIC, "C");
265
266 g_snprintf(cmd, sizeof(cmd),
267          "(echo \"%.6f %.6f\"; echo \"%.6f %.6f\") | "
268          "/usr/bin/cs2cs +proj=longlat +datum=WGS84 +to +init=%s -f %%.6f "
269          " > /tmp/tmpcs2cs ", lon1, lat1, lon2, lat2, srs);
270 vprintf("Running command: %s\n", cmd);
271 system_retcode = system(cmd);
272
273 if (system_retcode)
274         g_printerr("cs2cs returned error code %d\n", WEXITSTATUS(system_retcode));
275 else if (!(in = g_fopen("/tmp/tmpcs2cs", "r")))
276         g_printerr("Cannot open results of conversion\n");
277 else if (5 != fscanf(in, "%lf %lf %s %lf %lf", &lon1, &lat1, cmd, &lon2, &lat2)) {
278         g_printerr("Wrong conversion\n");
279         fclose(in);
280 } else {
281         fclose(in);
282         ret = g_strdup_printf(uri, lon1, lat1, lon2, lat2);
283 }
284
285 setlocale(LC_NUMERIC, "");
286
287 return ret;
288 }
289
290
291 /**
292  * Given the xyz coordinates of our map coordinate system, write the qrst
293  * quadtree coordinates to buffer.
294  */
295 static void
296 map_convert_coords_to_quadtree_string(gint x, gint y, gint zoomlevel,
297                                       gchar * buffer, const gchar initial,
298                                       const gchar * const quadrant)
299 {
300 gchar *ptr = buffer;
301 gint n;
302
303 if (initial)
304         *ptr++ = initial;
305
306 for (n = 16 - zoomlevel; n >= 0; n--) {
307         gint xbit = (x >> n) & 1;
308         gint ybit = (y >> n) & 1;
309         *ptr++ = quadrant[xbit + 2 * ybit];
310 }
311 *ptr++ = '\0';
312 }
313
314 /**
315  * Construct the URL that we should fetch, based on the current URI format.
316  * This method works differently depending on if a "%s" string is present in
317  * the URI format, since that would indicate a quadtree-based map coordinate
318  * system.
319  */
320 static gchar *
321 map_construct_url(guint tilex, guint tiley, guint zoom)
322 {
323 switch (_curr_repo->type) {
324 case REPOTYPE_XYZ:
325         return g_strdup_printf(_curr_repo->url, tilex, tiley, zoom);
326
327 case REPOTYPE_XYZ_INV:
328         return g_strdup_printf(_curr_repo->url, 17 - zoom, tilex, tiley);
329
330 case REPOTYPE_QUAD_QRST:
331         {
332                 gchar location[MAX_ZOOM + 2];
333                 map_convert_coords_to_quadtree_string(tilex, tiley, zoom, location, 't', "qrts");
334                 return g_strdup_printf(_curr_repo->url, location);
335         }
336
337 case REPOTYPE_QUAD_ZERO:
338         {
339                 /* This is a zero-based quadtree URI. */
340                 gchar location[MAX_ZOOM + 2];
341                 map_convert_coords_to_quadtree_string(tilex, tiley, zoom, location, '\0', "0123");
342                 return g_strdup_printf(_curr_repo->url, location);
343         }
344
345 case REPOTYPE_WMS:
346         return map_convert_wms_to_wms(tilex, tiley, zoom, _curr_repo->url);
347
348 default:
349         return NULL;
350 }
351 return "";
352 }
353
354
355 gboolean 
356 map_download_idle_refresh(ProgressUpdateInfo * pui)
357 {
358 /* Test if download succeeded (only if retries != 0). */
359 if (!pui->retries || g_file_test(pui->dest_str, G_FILE_TEST_EXISTS)) {
360         gint zoom_diff = pui->zoom - _zoom;
361         /* Only refresh at same or "lower" (more detailed) zoom level. */
362         if (zoom_diff >= 0) {
363                 /* If zoom has changed since we first put in the request for
364                  * this tile, then we may have to update more than one tile. */
365                 guint tilex, tiley, tilex_end, tiley_end;
366                 for (tilex = pui->tilex << zoom_diff, tilex_end = tilex + (1 << zoom_diff); tilex < tilex_end; tilex++) {
367                         for (tiley = pui->tiley << zoom_diff, tiley_end = tiley + (1 << zoom_diff); tiley < tiley_end; tiley++) {
368                                 if ((tilex - _base_tilex) < BUF_WIDTH_TILES && (tiley - _base_tiley) < BUF_HEIGHT_TILES) {
369                                         map_render_tile(tilex, tiley, ((tilex - _base_tilex) << TILE_SIZE_P2), ((tiley - _base_tiley) << TILE_SIZE_P2), TRUE);
370                                         map_render_data();
371                                         gtk_widget_queue_draw_area
372                                             (_map_widget,
373                                              ((tilex - _base_tilex) << TILE_SIZE_P2) - _offsetx,
374                                              ((tiley - _base_tiley) << TILE_SIZE_P2) - _offsety,
375                                              TILE_SIZE_PIXELS, TILE_SIZE_PIXELS);
376                                 }
377                         }
378                 }
379         }
380 }
381 /* Else the download failed. Update retries and maybe try again. */
382 else {
383         if (pui->retries > 0)
384                 --pui->retries;
385         else if (pui->retries < 0)
386                 ++pui->retries;
387
388         if (pui->retries) {
389                 /* removal automatically calls progress_update_info_free(). */
390                 g_tree_steal(_downloading_tree, pui);
391                 g_tree_insert(_pui_tree, pui, pui);
392                 if (iap_is_connected() && !_curl_sid)
393                         _curl_sid = g_timeout_add(100,(GSourceFunc)map_download_timeout, NULL);
394                 /* Don't do anything else. */
395                 return FALSE;
396         } else {
397                 /* No more retries left - something must be wrong. */
398                 MACRO_BANNER_SHOW_INFO(_window, _("Error in download. Check internet connection and/or Map Repository URL Format."));
399         }
400 }
401
402 /* removal automatically calls progress_update_info_free(). */
403 g_tree_remove(_downloading_tree, pui);
404
405 if (++_curr_download == _num_downloads) {
406 #ifdef WITH_HILDON
407         gtk_widget_destroy(_download_banner);
408         _download_banner = NULL;
409 #endif
410         _num_downloads = _curr_download = 0;
411 } else {
412         hildon_banner_set_fraction(HILDON_BANNER(_download_banner), _curr_download / (double)_num_downloads);
413 }
414 return FALSE;
415 }
416
417 /**
418  * Initiate a download of the given xyz coordinates using the given buffer
419  * as the URL.  If the map already exists on disk, or if we are already
420  * downloading the map, then this method does nothing.
421  */
422 void 
423 map_initiate_download(guint tilex, guint tiley, guint zoom, gint retries)
424 {
425 ProgressUpdateInfo *pui;
426
427 iap_connect();
428
429 pui = g_slice_new(ProgressUpdateInfo);
430 pui->tilex = tilex;
431 pui->tiley = tiley;
432 pui->zoom = zoom;
433 pui->priority = (abs((gint) tilex - unit2tile(_center.unitx)) + abs((gint) tiley - unit2tile(_center.unity)));
434 if (!retries)
435         pui->priority = -pui->priority; /* "Negative" makes them lowest pri. */
436 pui->retries = retries;
437 pui->repo = _curr_repo;
438
439 if (g_tree_lookup(_pui_tree, pui) || g_tree_lookup(_downloading_tree, pui)) {
440         /* Already downloading. */
441         g_slice_free(ProgressUpdateInfo, pui);
442         return;
443 }
444 pui->src_str = NULL;
445 pui->dest_str = NULL;
446 pui->file = NULL;
447
448 g_tree_insert(_pui_tree, pui, pui);
449 if (iap_is_connected() && !_curl_sid)
450         _curl_sid = g_timeout_add(100, (GSourceFunc) map_download_timeout, NULL);
451
452 if (!_num_downloads++ && !_download_banner) {
453         _download_banner = hildon_banner_show_progress(_window, NULL, _("Downloading maps..."));
454 }
455 }
456
457 void
458 map_download_init(void)
459 {
460 _curl_easy_queue = g_queue_new();
461 _pui_tree = g_tree_new_full((GCompareDataFunc) download_comparefunc, NULL, (GDestroyNotify) progress_update_info_free, NULL);
462 _downloading_tree = g_tree_new_full((GCompareDataFunc) download_comparefunc, NULL, (GDestroyNotify) progress_update_info_free, NULL);
463 _pui_by_easy = g_hash_table_new(g_direct_hash, g_direct_equal);
464 }
465
466 void
467 map_download_deinit(void) 
468 {
469 /* Clean up CURL. */
470 if (_curl_multi) {
471         CURL *curr;
472         CURLMsg *msg;
473         gint num_transfers, num_msgs;
474
475         /* First, remove all downloads from _pui_tree. */
476         g_tree_destroy(_pui_tree);
477
478         /* Finish up all downloads. */
479         while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(_curl_multi, &num_transfers) || num_transfers) {
480                 /* XXX: should inform the user why it's taking so damn long... */
481         }
482
483         /* Close all finished files. */
484         while ((msg = curl_multi_info_read(_curl_multi, &num_msgs))) {
485                 if (msg->msg == CURLMSG_DONE) {
486                         /* This is a map download. */
487                         ProgressUpdateInfo *pui = g_hash_table_lookup(_pui_by_easy, msg->easy_handle);
488                         g_queue_push_head(_curl_easy_queue, msg->easy_handle);
489                         g_hash_table_remove(_pui_by_easy, msg->easy_handle);
490                         fclose(pui->file);
491                         curl_multi_remove_handle(_curl_multi, msg->easy_handle);
492                 }
493         }
494
495         while ((curr = g_queue_pop_tail(_curl_easy_queue)))
496                 curl_easy_cleanup(curr);
497
498         curl_multi_cleanup(_curl_multi);
499         _curl_multi = NULL;
500
501         g_queue_free(_curl_easy_queue);
502         g_tree_destroy(_downloading_tree);
503         g_hash_table_destroy(_pui_by_easy);
504 }
505
506 }