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