]> err.no Git - mapper/blob - src/map-download.c
Map download:
[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 #include <unistd.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <strings.h>
32 #include <stddef.h>
33 #include <locale.h>
34 #include <math.h>
35 #include <errno.h>
36 #include <sys/wait.h>
37 #include <glib/gstdio.h>
38 #include <fcntl.h>
39 #include <curl/multi.h>
40 #include <libintl.h>
41 #include <locale.h>
42
43 #include "hildon-mapper.h"
44
45 #include "utils.h"
46 #include "map.h"
47 #include "osm.h"
48 #include "db.h"
49 #include "osm-db.h"
50 #include "poi.h"
51 #include "route.h"
52 #include "gps.h"
53 #include "mapper-types.h"
54 #include "ui-common.h"
55 #include "settings.h"
56 #include "latlon.h"
57 #include "gpx.h"
58 #include "map-download.h"
59 #include "iap.h"
60 #include "map-repo.h"
61
62 static guint _num_downloads=0;
63 static guint _curr_download=0;
64
65 static GQueue *curl_easy_queue=NULL;
66 static GTree *pui_tree=NULL;
67 static GTree *downloading_tree=NULL;
68 static GHashTable *pui_by_easy=NULL;
69
70 static gchar *map_construct_url(guint tilex, guint tiley, guint zoom);
71
72 static gboolean 
73 get_next_pui(gpointer key, gpointer value, ProgressUpdateInfo ** data)
74 {
75 *data = key;
76 return TRUE;
77 }
78
79 static gint
80 download_comparefunc(const ProgressUpdateInfo * a, const ProgressUpdateInfo * b, gpointer user_data)
81 {
82 gint diff = (a->priority - b->priority);
83 if (diff)
84         return diff;
85 diff = (a->tilex - b->tilex);
86 if (diff)
87         return diff;
88 diff = (a->tiley - b->tiley);
89 if (diff)
90         return diff;
91 diff = (a->zoom - b->zoom);
92 if (diff)
93         return diff;
94 diff = (a->repo - b->repo);
95 if (diff)
96         return diff;
97 /* Otherwise, deletes are "greatest" (least priority). */
98 if (!a->retries)
99         return (b->retries ? -1 : 0);
100 else if (!b->retries)
101         return (a->retries ? 1 : 0);
102 /* Do updates after non-updates (because they'll both be done anyway). */
103 return (a->retries - b->retries);
104 }
105
106 /**
107  * Free a ProgressUpdateInfo data structure that was allocated during the
108  * auto-map-download process.
109  */
110 static void 
111 progress_update_info_free(ProgressUpdateInfo * pui)
112 {
113 g_free(pui->src_str);
114 g_free(pui->dest_str);
115 g_slice_free(ProgressUpdateInfo, pui);
116 }
117
118 gboolean 
119 map_download_timeout()
120 {
121 static guint destroy_counter = 50;
122 gint num_transfers = 0, num_msgs = 0;
123 gint deletes_left = 50; /* only do 50 deletes at a time. */
124 CURLMsg *msg;
125
126 if (_curl_multi && CURLM_CALL_MULTI_PERFORM == curl_multi_perform(_curl_multi, &num_transfers))
127         return TRUE;    /* Give UI a chance first. */
128
129 while (_curl_multi && (msg = curl_multi_info_read(_curl_multi, &num_msgs))) {
130         if (msg->msg == CURLMSG_DONE) {
131                 if (msg->easy_handle == _autoroute_data.curl_easy) {
132                         /* This is the autoroute download. */
133                         /* Now, parse the autoroute and update the display. */
134                         if (_autoroute_data.enabled && gpx_parse(&_route, _autoroute_data.rdl_data.bytes, _autoroute_data.rdl_data.bytes_read, 0)) {
135                                 /* Find the nearest route point, if we're connected. */
136                                 route_find_nearest_point(_route);
137                                 map_force_redraw();
138                         }
139                         route_cancel_autoroute(_route, TRUE);   /* We're done. Clean up. */
140                 } else {
141                         ProgressUpdateInfo *pui = g_hash_table_lookup(pui_by_easy, msg->easy_handle);
142                         g_queue_push_head(curl_easy_queue, msg->easy_handle);
143                         g_hash_table_remove(pui_by_easy, msg->easy_handle);
144                         fclose(pui->file);
145                         if (msg->data.result != CURLE_OK)
146                                 g_unlink(pui->dest_str);        /* Delete so we try again. */
147                         curl_multi_remove_handle(_curl_multi, msg->easy_handle);
148                         g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)map_download_idle_refresh, pui, NULL);
149                 }
150         }
151 }
152
153 /* Up to 1 transfer per tile. */
154 while (num_transfers < (4*4) && g_tree_nnodes(pui_tree)) {
155         ProgressUpdateInfo *pui;
156         g_tree_foreach(pui_tree, (GTraverseFunc)get_next_pui, &pui);
157
158         if (pui->retries) {
159                 /* This is a download. */
160                 FILE *f;
161                 g_tree_steal(pui_tree, pui);
162                 g_tree_insert(downloading_tree, pui, pui);
163
164                 pui->src_str=map_construct_url(pui->tilex, pui->tiley, pui->zoom);
165
166                 if (!pui->src_str) {
167                         /* Failed to generate URL. */
168                         g_debug("Failed to generate tile download URL");
169                         g_idle_add_full(G_PRIORITY_HIGH_IDLE,(GSourceFunc)map_download_idle_refresh, pui, NULL);
170                         continue;
171                 }
172
173                 pui->dest_str=g_strdup_printf("%s/%u/%u/%u.jpg", pui->repo->cache_dir, pui->zoom, pui->tilex, pui->tiley);
174
175                 /* Check to see if we need to overwrite. */
176                 if (pui->retries > 0) {
177                         /* We're not updating - check if file already exists. */
178                         if (g_file_test(pui->dest_str, G_FILE_TEST_EXISTS)) {
179                                 g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)map_download_idle_refresh, pui, NULL);
180                                 continue;
181                         }
182                 }
183
184                 /* Attempt to open the file for writing. */
185                 if (!(f=g_fopen(pui->dest_str, "w")) && errno == ENOENT) {
186                         /* Directory doesn't exist yet - create it, then we'll retry */
187                         gchar buffer[BUFFER_SIZE];
188                         g_snprintf(buffer, sizeof(buffer), "%s/%u/%u", pui->repo->cache_dir, pui->zoom, pui->tilex);
189                         g_mkdir_with_parents(buffer, 0775);
190                         f=g_fopen(pui->dest_str, "w");
191                 }
192
193                 if (f) {
194                         CURL *curl_easy;
195                         pui->file = f;
196                         curl_easy = g_queue_pop_tail(curl_easy_queue);
197                         if (!curl_easy) {
198                                 /* Need a new curl_easy. */
199                                 MACRO_CURL_EASY_INIT(curl_easy);
200                         }
201                         curl_easy_setopt(curl_easy, CURLOPT_URL, pui->src_str);
202                         curl_easy_setopt(curl_easy, CURLOPT_WRITEDATA, f);
203                         g_hash_table_insert(pui_by_easy, curl_easy, pui);
204                         curl_multi_add_handle(_curl_multi, curl_easy);
205                         num_transfers++;
206                 } else {
207                         /* Unable to open tile file for writing. */
208                         gchar buffer[BUFFER_SIZE];
209                         g_snprintf(buffer, sizeof(buffer), "%s:\n%s", _("Failed to open file for writing"), pui->dest_str);
210                         MACRO_BANNER_SHOW_INFO(_window, buffer);
211                         g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)map_download_idle_refresh, pui, NULL);
212                         continue;
213                 }
214         } else if (--deletes_left) {
215                 /* This is a delete. */
216                 gchar buffer[BUFFER_SIZE];
217                 g_tree_steal(pui_tree, pui);
218                 g_tree_insert(downloading_tree, pui, pui);
219
220                 g_snprintf(buffer, sizeof(buffer), "%s/%u/%u/%u.jpg", pui->repo->cache_dir, pui->zoom, pui->tilex, pui->tiley);
221                 g_unlink(buffer);
222                 g_idle_add_full(G_PRIORITY_HIGH_IDLE,(GSourceFunc)map_download_idle_refresh, pui, NULL);
223         } else
224                 break;
225 }
226
227 if (!(num_transfers || g_tree_nnodes(pui_tree))) {
228         /* Destroy curl after 50 counts (5 seconds). */
229         if (--destroy_counter) {
230                 /* Clean up curl. */
231                 CURL *curr;
232                 while ((curr = g_queue_pop_tail(curl_easy_queue)))
233                         curl_easy_cleanup(curr);
234
235                 _curl_sid = 0;
236                 return FALSE;
237         }
238 } else
239         destroy_counter = 50;
240
241 return TRUE;
242 }
243
244 /**
245  * Given a wms uri pattern, compute the coordinate transformation and
246  * trimming.
247  * 'proj' is used for the conversion
248  */
249 static gchar *
250 map_convert_wms_to_wms(gint tilex, gint tiley, gint zoomlevel, gchar * uri)
251 {
252 gint system_retcode;
253 gchar cmd[BUFFER_SIZE], srs[BUFFER_SIZE];
254 gchar *ret = NULL;
255 FILE *in;
256 gdouble lon1, lat1, lon2, lat2;
257
258 gchar *widthstr = strcasestr(uri, "WIDTH=");
259 gchar *heightstr = strcasestr(uri, "HEIGHT=");
260 gchar *srsstr = strcasestr(uri, "SRS=EPSG");
261 gchar *srsstre = strchr(srsstr, '&');
262
263 /* missing: test if found */
264 strcpy(srs, "epsg");
265 strncpy(srs + 4, srsstr + 8, 256);
266 /* missing: test srsstre-srsstr < 526 */
267 srs[srsstre - srsstr - 4] = 0;
268 /* convert to lower, as WMC is EPSG and cs2cs is epsg */
269
270 gint dwidth = widthstr ? atoi(widthstr + 6) - TILE_SIZE_PIXELS : 0;
271 gint dheight = heightstr ? atoi(heightstr + 7) - TILE_SIZE_PIXELS : 0;
272
273 unit2latlon(tile2zunit(tilex, zoomlevel) - pixel2zunit(dwidth / 2, zoomlevel),
274             tile2zunit(tiley + 1, zoomlevel) + pixel2zunit((dheight + 1) / 2, zoomlevel), lat1, lon1);
275
276 unit2latlon(tile2zunit(tilex + 1, zoomlevel) + pixel2zunit((dwidth + 1) / 2, zoomlevel), 
277                 tile2zunit(tiley, zoomlevel) - pixel2zunit(dheight / 2, zoomlevel), lat2, lon2);
278
279 setlocale(LC_NUMERIC, "C");
280
281 g_snprintf(cmd, sizeof(cmd),
282          "(echo \"%.6f %.6f\"; echo \"%.6f %.6f\") | "
283          "/usr/bin/cs2cs +proj=longlat +datum=WGS84 +to +init=%s -f %%.6f "
284          " > /tmp/tmpcs2cs ", lon1, lat1, lon2, lat2, srs);
285 vprintf("Running command: %s\n", cmd);
286 system_retcode = system(cmd);
287
288 if (system_retcode)
289         g_printerr("cs2cs returned error code %d\n", WEXITSTATUS(system_retcode));
290 else if (!(in = g_fopen("/tmp/tmpcs2cs", "r")))
291         g_printerr("Cannot open results of conversion\n");
292 else if (5 != fscanf(in, "%lf %lf %s %lf %lf", &lon1, &lat1, cmd, &lon2, &lat2)) {
293         g_printerr("Wrong conversion\n");
294         fclose(in);
295 } else {
296         fclose(in);
297         ret = g_strdup_printf(uri, lon1, lat1, lon2, lat2);
298 }
299
300 setlocale(LC_NUMERIC, "");
301
302 return ret;
303 }
304
305
306 /**
307  * Given the xyz coordinates of our map coordinate system, write the qrst
308  * quadtree coordinates to buffer.
309  */
310 static void
311 map_convert_coords_to_quadtree_string(gint x, gint y, gint zoomlevel,
312                                       gchar * buffer, const gchar initial,
313                                       const gchar * const quadrant)
314 {
315 gchar *ptr = buffer;
316 gint n;
317
318 if (initial)
319         *ptr++ = initial;
320
321 for (n = 16 - zoomlevel; n >= 0; n--) {
322         gint xbit = (x >> n) & 1;
323         gint ybit = (y >> n) & 1;
324         *ptr++ = quadrant[xbit + 2 * ybit];
325 }
326 *ptr++ = '\0';
327 }
328
329 /**
330  * Construct the URL that we should fetch, based on the current URI format.
331  * This method works differently depending on if a "%s" string is present in
332  * the URI format, since that would indicate a quadtree-based map coordinate
333  * system.
334  */
335 static gchar *
336 map_construct_url(guint tilex, guint tiley, guint zoom)
337 {
338 switch (_curr_repo->type) {
339 case REPOTYPE_XYZ:
340         return g_strdup_printf(_curr_repo->url, tilex, tiley, zoom);
341
342 case REPOTYPE_XYZ_INV:
343         return g_strdup_printf(_curr_repo->url, 17 - zoom, tilex, tiley);
344
345 case REPOTYPE_QUAD_QRST:
346         {
347                 gchar location[MAX_ZOOM + 2];
348                 map_convert_coords_to_quadtree_string(tilex, tiley, zoom, location, 't', "qrts");
349                 return g_strdup_printf(_curr_repo->url, location);
350         }
351
352 case REPOTYPE_QUAD_ZERO:
353         {
354                 /* This is a zero-based quadtree URI. */
355                 gchar location[MAX_ZOOM + 2];
356                 map_convert_coords_to_quadtree_string(tilex, tiley, zoom, location, '\0', "0123");
357                 return g_strdup_printf(_curr_repo->url, location);
358         }
359
360 case REPOTYPE_WMS:
361         return map_convert_wms_to_wms(tilex, tiley, zoom, _curr_repo->url);
362
363 default:
364         return NULL;
365 }
366 return "";
367 }
368
369 void 
370 map_download_progress_clear(void) 
371 {
372 g_assert(_progress_item);
373 gtk_progress_bar_set_fraction(_progress_item, 0.0);
374 gtk_progress_bar_set_text(_progress_item, "");
375 }
376
377 void 
378 map_download_progress_set(gint currd, gint numd)
379 {
380 gchar buffer[64];
381
382 g_assert(_progress_item);
383 g_snprintf(buffer, sizeof(buffer), _("Downloading maps (%d/%d)"), currd, numd);
384 gtk_progress_bar_set_text(_progress_item, buffer);      
385 gtk_progress_bar_set_fraction(_progress_item, currd/(double)numd);
386 }
387
388 gboolean 
389 map_download_idle_refresh(ProgressUpdateInfo * pui)
390 {
391 guint tmp=0;
392 /* Test if download succeeded (only if retries != 0). */
393 if (!pui->retries || g_file_test(pui->dest_str, G_FILE_TEST_EXISTS)) {
394         gint zoom_diff = pui->zoom - _zoom;
395
396         /* Only refresh at same or "lower" (more detailed) zoom level. */
397         if (zoom_diff >= 0) {
398                 guint tilex, tiley, tilex_end, tiley_end;
399
400                 /* If zoom has changed since we first put in the request for
401                  * this tile, then we may have to update more than one tile. */
402                 for (tilex = pui->tilex << zoom_diff, tilex_end = tilex + (1 << zoom_diff); tilex < tilex_end; tilex++) {
403                         for (tiley = pui->tiley << zoom_diff, tiley_end = tiley + (1 << zoom_diff); tiley < tiley_end; tiley++) {
404                                 tmp++;
405                                 if (map_render_tile(tilex, tiley, ((tilex - _base_tilex) << TILE_SIZE_P2), ((tiley - _base_tiley) << TILE_SIZE_P2), TRUE)==TRUE) {
406                                         map_render_data();
407                                         gtk_widget_queue_draw_area(_map_widget,
408                                              ((tilex - _base_tilex) << TILE_SIZE_P2) - _offsetx,
409                                              ((tiley - _base_tiley) << TILE_SIZE_P2) - _offsety,
410                                              TILE_SIZE_PIXELS, TILE_SIZE_PIXELS);
411                                 }
412                         }
413                 }
414         g_debug("*** MDIR LOOP: %u", tmp);
415         }
416 }
417 /* Else the download failed. Update retries and maybe try again. */
418 else {
419         if (pui->retries > 0)
420                 --pui->retries;
421         else if (pui->retries < 0)
422                 ++pui->retries;
423
424         if (pui->retries) {
425                 /* removal automatically calls progress_update_info_free(). */
426                 g_tree_steal(downloading_tree, pui);
427                 g_tree_insert(pui_tree, pui, pui);
428                 if (iap_is_connected() && !_curl_sid)
429                         _curl_sid = g_timeout_add(100,(GSourceFunc)map_download_timeout, NULL);
430                 /* Don't do anything else. */
431                 return FALSE;
432         } else {
433                 /* No more retries left - something must be wrong. */
434                 MACRO_BANNER_SHOW_INFO(_window, _("Error in download. Check internet connection and/or Map Repository URL Format."));
435         }
436 }
437
438 /* removal automatically calls progress_update_info_free(). */
439 g_tree_remove(downloading_tree, pui);
440
441 if (++_curr_download == _num_downloads) {
442         _num_downloads = _curr_download = 0;
443         map_download_progress_clear();
444 } else {
445         map_download_progress_set(_curr_download, _num_downloads);
446 }
447 return FALSE;
448 }
449
450 /**
451  * Initiate a download of the given xyz coordinates using the given buffer
452  * as the URL.  If the map already exists on disk, or if we are already
453  * downloading the map, then this method does nothing.
454  */
455 void 
456 map_initiate_download(guint tilex, guint tiley, guint zoom, gint retries)
457 {
458 ProgressUpdateInfo *pui;
459
460 iap_connect();
461
462 pui = g_slice_new(ProgressUpdateInfo);
463 pui->tilex = tilex;
464 pui->tiley = tiley;
465 pui->zoom = zoom;
466 pui->priority = (abs((gint) tilex - unit2tile(_center.unitx)) + abs((gint) tiley - unit2tile(_center.unity)));
467 if (!retries)
468         pui->priority = -pui->priority; /* "Negative" makes them lowest pri. */
469 pui->retries = retries;
470 pui->repo = _curr_repo;
471
472 if (g_tree_lookup(pui_tree, pui) || g_tree_lookup(downloading_tree, pui)) {
473         /* Already downloading. */
474         g_slice_free(ProgressUpdateInfo, pui);
475         return;
476 }
477 pui->src_str = NULL;
478 pui->dest_str = NULL;
479 pui->file = NULL;
480
481 g_tree_insert(pui_tree, pui, pui);
482 if (iap_is_connected() && !_curl_sid)
483         _curl_sid = g_timeout_add(100, (GSourceFunc) map_download_timeout, NULL);
484
485 if (!_num_downloads++)
486         gtk_progress_bar_set_text(_progress_item, _("Downloading maps..."));    
487 }
488
489 void
490 map_download_init(void)
491 {
492 curl_easy_queue = g_queue_new();
493 pui_tree = g_tree_new_full((GCompareDataFunc)download_comparefunc, NULL, (GDestroyNotify) progress_update_info_free, NULL);
494 downloading_tree = g_tree_new_full((GCompareDataFunc)download_comparefunc, NULL, (GDestroyNotify) progress_update_info_free, NULL);
495 pui_by_easy = g_hash_table_new(g_direct_hash, g_direct_equal);
496 _curl_multi = curl_multi_init();
497 /* curl_multi_setopt(_curl_multi, CURLMOPT_PIPELINING, 1); */
498 }
499
500 gboolean
501 map_download_stop(void)
502 {
503 if (_curl_multi) {
504         CURL *curr;
505         CURLMsg *msg;
506         gint num_msgs;
507
508         /* Clear the tree and create it again */
509         if (pui_tree) {
510                 g_tree_destroy(pui_tree);
511                 pui_tree = g_tree_new_full((GCompareDataFunc)download_comparefunc, NULL, (GDestroyNotify) progress_update_info_free, NULL);
512         }
513
514         /* Close all finished files. */
515         while ((msg = curl_multi_info_read(_curl_multi, &num_msgs))) {
516                 if (msg->msg == CURLMSG_DONE) {
517                         /* This is a map download. */
518                         ProgressUpdateInfo *pui = g_hash_table_lookup(pui_by_easy, msg->easy_handle);
519                         g_queue_push_head(curl_easy_queue, msg->easy_handle);
520                         g_hash_table_remove(pui_by_easy, msg->easy_handle);
521                         if (pui->file)
522                                 fclose(pui->file);
523                         curl_multi_remove_handle(_curl_multi, msg->easy_handle);
524                 }
525         }
526
527         g_debug("Stopping downloads");
528         while ((curr = g_queue_pop_tail(curl_easy_queue)))
529                 curl_easy_cleanup(curr);
530         return TRUE;
531 }
532 g_debug("No downloads to stop");
533 return FALSE;
534 }
535
536 /*
537  * Clean up CURL and structures
538  */
539 void
540 map_download_deinit(void) 
541 {
542 if (_curl_multi) {
543         CURL *curr;
544         gint num_transfers;
545
546         /* Finish up all downloads. */
547         while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(_curl_multi, &num_transfers) || num_transfers) {
548                 g_debug("NT: %d", num_transfers);
549         }
550
551         /* Stop all transfers and cleanup */
552         map_download_stop();
553
554         if (pui_tree)
555                 g_tree_destroy(pui_tree);
556         if (downloading_tree)
557                 g_tree_destroy(downloading_tree);
558         if (pui_by_easy)
559                 g_hash_table_destroy(pui_by_easy);
560         if (curl_easy_queue)
561                 g_queue_free(curl_easy_queue);
562         curl_multi_cleanup(_curl_multi);
563         _curl_multi = NULL;
564 }
565
566 if (_curl_sid) {
567     g_source_remove(_curl_sid);
568     _curl_sid = 0;
569 }
570 }