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