Parent Directory | Revision Log
File selection fremantleized
1 | /* |
2 | * Copyright (C) 2008 Till Harbaum <till@harbaum.org>. |
3 | * |
4 | * This file is part of GPXView. |
5 | * |
6 | * GPXView is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation, either version 3 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * GPXView is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with GPXView. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include "gpxview.h" |
21 | |
22 | #include <math.h> |
23 | #include <sqlite3.h> |
24 | |
25 | static const char *cache_type_icons[] = { |
26 | "traditional", "multi", "mystery", "virtual", |
27 | "webcam", "event", "letterbox", "earthcache", |
28 | "wherigo", "megaevent", "cito", "" }; |
29 | |
30 | static const char *cache_type_str[] = { |
31 | "Traditional", "Multi", "Mystery","Virtual", "Webcam", "Event", |
32 | "Letterbox", "Earthcache", "Wherigo","Megaevent", "CITO",""}; |
33 | |
34 | static const char *cache_type_desc[] = { |
35 | "Traditional Caches", "Multi-Caches", "Unknown Caches", |
36 | "Virtual Caches", "Webcam Caches", "Event Caches", |
37 | "Letterbox Hybrids", "Earthcaches", "Wherigo Caches", |
38 | "Mega-Event Caches", "Cache In Trash Out Events",""}; |
39 | |
40 | static const char *mode_str[] = { |
41 | "???", "the cache listing", "the list of caches", "all GPX files" }; |
42 | |
43 | static int callback(void *NotUsed, int argc, char **argv, char **azColName){ |
44 | int i; |
45 | NotUsed=0; |
46 | |
47 | for(i=0; i<argc; i++){ |
48 | printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL"); |
49 | } |
50 | printf("\n"); |
51 | return 0; |
52 | } |
53 | |
54 | char *escape(char *str) { |
55 | if(!str) return NULL; |
56 | |
57 | char *result = malloc(strlen(str)+1); |
58 | char *p = result; |
59 | |
60 | while(*str) { |
61 | if(*str != '\'') *p++=*str++; |
62 | else { *p++='`'; str++; } |
63 | } |
64 | *p = 0; |
65 | |
66 | return result; |
67 | } |
68 | |
69 | /* export a single cache to the database */ |
70 | static void cache_export(sqlite3 *db, int id, cache_t *cache) { |
71 | char *zErrMsg = 0; |
72 | char cmd[256]; |
73 | |
74 | // printf("exporting %s: %s\n", cache->id, cache->name); |
75 | |
76 | char *desc = escape(cache->id); // short_description |
77 | if(!desc) desc = strdup(_("<none>")); |
78 | |
79 | char *name = escape(cache->name); |
80 | if(!name) desc = strdup(_("<none>")); |
81 | |
82 | pos_t pos = gpx_cache_pos(cache); |
83 | char strlon[32], strlat[32]; |
84 | g_ascii_dtostr(strlat, sizeof(strlat), pos.lat); |
85 | g_ascii_dtostr(strlon, sizeof(strlon), pos.lon); |
86 | |
87 | snprintf(cmd, sizeof(cmd), |
88 | "insert into poi values (%d,%s,%s,'%s','%s',%d)", |
89 | id, strlat, strlon, name, desc, cache->type+12); |
90 | |
91 | if(sqlite3_exec(db,cmd, callback, 0, &zErrMsg) != SQLITE_OK) |
92 | errorf(_("Creating POI entry:\n\n%s"), zErrMsg); |
93 | |
94 | if(desc) free(desc); |
95 | if(name) free(name); |
96 | } |
97 | |
98 | /* add a cache to the chain of caches to be exported */ |
99 | static void chain_cache(gpx_t *gpx, cache_t *cache) { |
100 | cache_t **cur = &gpx->cache; |
101 | |
102 | /* search end of chain */ |
103 | while(*cur) { |
104 | if(strcmp(cache->id, (*cur)->id) == 0) |
105 | return; |
106 | |
107 | cur = &(*cur)->next; |
108 | } |
109 | |
110 | *cur = malloc(sizeof(cache_t)); |
111 | if(!(*cur)) return; |
112 | |
113 | memcpy(*cur, cache, sizeof(cache_t)); |
114 | (*cur)->next = NULL; |
115 | } |
116 | |
117 | static gpx_t *export_list_create(appdata_t *appdata, int *mode) { |
118 | gpx_t *gpx = g_new0(gpx_t,1); |
119 | *mode = 0; |
120 | |
121 | if(!gpx) return gpx; |
122 | |
123 | #ifdef USE_MAEMO |
124 | if(appdata->cur_cache) { |
125 | printf("cache display, exporting cache only!\n"); |
126 | /* appdata->cur_cache */ |
127 | chain_cache(gpx, appdata->cur_cache); |
128 | *mode = 1; |
129 | } else if(appdata->search_results) { |
130 | printf("search result display, exporting entire search result\n"); |
131 | cache_t *cache = appdata->search_results->cache; |
132 | while(cache) { chain_cache(gpx, cache); cache = cache->next; } |
133 | *mode = 2; |
134 | } else if(appdata->cur_gpx) { |
135 | printf("cachelist display, exporting entire cachelist\n"); |
136 | cache_t *cache = appdata->cur_gpx->cache; |
137 | while(cache) { chain_cache(gpx, cache); cache = cache->next; } |
138 | *mode = 2; |
139 | } else |
140 | #endif |
141 | { |
142 | printf("gpxlist display, exporting all caches\n"); |
143 | gpx_t *lgpx = appdata->gpx; |
144 | while(lgpx) { |
145 | /* make sure notes are imported */ |
146 | if(!lgpx->notes_loaded) { |
147 | notes_load_all(appdata, lgpx); |
148 | lgpx->notes_loaded = TRUE; |
149 | } |
150 | |
151 | cache_t *cache = lgpx->cache; |
152 | while(cache) { chain_cache(gpx, cache); cache = cache->next; } |
153 | lgpx = lgpx->next; |
154 | } |
155 | *mode = 3; |
156 | } |
157 | |
158 | return gpx; |
159 | } |
160 | |
161 | static void export_list_free(gpx_t *gpx) { |
162 | printf("freeing export list\n"); |
163 | |
164 | cache_t *cache = gpx->cache; |
165 | while(cache) { |
166 | cache_t *tmp = cache; |
167 | cache = cache->next; |
168 | free(tmp); |
169 | } |
170 | free(gpx); |
171 | } |
172 | |
173 | static int export_list_count(appdata_t *appdata, gpx_t *gpx) { |
174 | pos_t *refpos = get_pos(appdata); |
175 | int cnt = 0; |
176 | |
177 | cache_t *cache = gpx->cache; |
178 | while(cache) { |
179 | /* no disabled/archived if requested */ |
180 | if(!(appdata->mmpoi_dont_export_disabled && |
181 | (!cache->available || cache->archived))) { |
182 | |
183 | /* no found if requested */ |
184 | if(!(appdata->mmpoi_dont_export_found && |
185 | cache->notes && cache->notes->found)) { |
186 | |
187 | if(appdata->mmpoi_use_radius && !isnan(appdata->mmpoi_radius)) { |
188 | /* count all caches within given radius around current position */ |
189 | if(gpx_pos_get_distance(*refpos, gpx_cache_pos(cache), 0) < |
190 | appdata->mmpoi_radius) |
191 | cnt++; |
192 | } else |
193 | cnt++; |
194 | } |
195 | |
196 | } |
197 | |
198 | cache = cache->next; |
199 | } |
200 | return cnt; |
201 | } |
202 | |
203 | typedef struct { |
204 | GtkWidget *use_radius; |
205 | GtkWidget *no_found; |
206 | GtkWidget *no_disabled; |
207 | GtkWidget *entry; |
208 | GtkWidget *info_label; |
209 | GtkWidget *dialog; |
210 | appdata_t *appdata; |
211 | guint handler_id; |
212 | int mode; |
213 | gpx_t *gpx; |
214 | #ifdef USE_MAEMO |
215 | GtkWidget *launch; |
216 | #endif |
217 | } export_context_t; |
218 | |
219 | static gboolean export_list_update(gpointer data) { |
220 | char str[256]; |
221 | export_context_t *context = (export_context_t*)data; |
222 | |
223 | printf("updating list ...\n"); |
224 | |
225 | int cnum = export_list_count(context->appdata, context->gpx); |
226 | printf("About to export %d caches\n", cnum); |
227 | |
228 | snprintf(str, sizeof(str), |
229 | _("This will export %d caches from %s."), |
230 | cnum, _(mode_str[context->mode])); |
231 | |
232 | gtk_label_set_text(GTK_LABEL(context->info_label), str); |
233 | |
234 | return FALSE; |
235 | } |
236 | |
237 | /* Our usual callback function */ |
238 | static void export_update(GtkWidget *widget, gpointer data) { |
239 | export_context_t *context = (export_context_t *)data; |
240 | |
241 | if(check_button_get_active(context->use_radius)) { |
242 | textbox_enable(context->entry); |
243 | context->appdata->mmpoi_use_radius = TRUE; |
244 | } else { |
245 | textbox_disable(context->entry); |
246 | context->appdata->mmpoi_use_radius = FALSE; |
247 | } |
248 | |
249 | context->appdata->mmpoi_dont_export_found = |
250 | toggle_button_get_active(context->no_found); |
251 | |
252 | context->appdata->mmpoi_dont_export_disabled = |
253 | toggle_button_get_active(context->no_disabled); |
254 | |
255 | export_list_update(context); |
256 | } |
257 | |
258 | static void export_radius_modified(GtkWidget *widget, gpointer data ) { |
259 | export_context_t *context = (export_context_t*)data; |
260 | |
261 | context->appdata->mmpoi_radius = |
262 | dist_entry_get(context->entry, context->appdata->imperial); |
263 | |
264 | printf("Distance is %.2f km\n", context->appdata->mmpoi_radius); |
265 | |
266 | if(context->handler_id) { |
267 | printf("resetting running timer\n"); |
268 | gtk_timeout_remove(context->handler_id); |
269 | context->handler_id = 0; |
270 | } |
271 | |
272 | if(!isnan(context->appdata->mmpoi_radius)) |
273 | context->handler_id = |
274 | gtk_timeout_add(1000, export_list_update, context); |
275 | } |
276 | |
277 | /* Our usual callback function */ |
278 | static void on_cancel(GtkWidget *widget, gpointer data) { |
279 | int *flag = (int*)data; |
280 | *flag = TRUE; |
281 | } |
282 | |
283 | void mmpoi_export(appdata_t *appdata) { |
284 | sqlite3 *db; |
285 | char *zErrMsg = 0; |
286 | char cmd[256]; |
287 | int rc, i; |
288 | char *old_mmpoi_path = strdup(appdata->mmpoi_path); |
289 | |
290 | export_context_t context; |
291 | memset(&context, 0, sizeof(export_context_t)); |
292 | context.appdata = appdata; |
293 | |
294 | printf("export poi\n"); |
295 | |
296 | /* first create a new faked gpx file containing all caches to */ |
297 | /* be exported */ |
298 | context.gpx = export_list_create(appdata, &context.mode); |
299 | if(!context.gpx) { |
300 | errorf(_("Out of memory")); |
301 | return; |
302 | } |
303 | |
304 | /* --------- do confirmation dialog ----------- */ |
305 | context.dialog = gtk_dialog_new_with_buttons(_("Maemo Mapper POI export"), |
306 | GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL, |
307 | GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, |
308 | GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, |
309 | NULL); |
310 | |
311 | #if defined(USE_MAEMO) && defined(HILDON_HELP) |
312 | hildon_help_dialog_help_enable(GTK_DIALOG(context.dialog), |
313 | HELP_ID_EXPORT, appdata->osso_context); |
314 | #endif |
315 | |
316 | GtkWidget *vbox = gtk_vbox_new(FALSE,0); |
317 | |
318 | /* ------------------ radius limit gui ------------------ */ |
319 | |
320 | GtkWidget *label, *hbox = gtk_hbox_new(FALSE,2); |
321 | context.use_radius = check_button_new_with_label( |
322 | _("Limit export radius to:")); |
323 | context.entry = dist_entry_new(appdata->mmpoi_radius, appdata->imperial); |
324 | |
325 | gtk_box_pack_start_defaults(GTK_BOX(hbox), context.use_radius); |
326 | gtk_box_pack_start_defaults(GTK_BOX(hbox), context.entry); |
327 | gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox); |
328 | |
329 | check_button_set_active(context.use_radius, appdata->mmpoi_use_radius); |
330 | |
331 | /* Connect the "clicked" signal of the button to our callback */ |
332 | gtk_signal_connect (GTK_OBJECT(context.use_radius), "clicked", |
333 | GTK_SIGNAL_FUNC(export_update), (gpointer)&context); |
334 | |
335 | g_signal_connect(G_OBJECT(context.entry), "changed", |
336 | G_CALLBACK(export_radius_modified), (gpointer)&context); |
337 | |
338 | /* ------------------ don't export found/disabled/archived -------------- */ |
339 | hbox = gtk_hbox_new(FALSE,2); |
340 | |
341 | label = gtk_label_new(_("Don't export")); |
342 | gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f); |
343 | GtkWidget *ihbox = gtk_hbox_new(FALSE,0); |
344 | context.no_found = toggle_button_new_with_label(_("found")); |
345 | context.no_disabled = toggle_button_new_with_label(_("disabled/archived")); |
346 | |
347 | toggle_button_set_active(context.no_found, |
348 | appdata->mmpoi_dont_export_found); |
349 | |
350 | toggle_button_set_active(context.no_disabled, |
351 | appdata->mmpoi_dont_export_disabled); |
352 | |
353 | gtk_box_pack_start_defaults(GTK_BOX(hbox), label); |
354 | gtk_box_pack_start_defaults(GTK_BOX(ihbox), context.no_found); |
355 | gtk_box_pack_start_defaults(GTK_BOX(ihbox), context.no_disabled); |
356 | gtk_box_pack_start_defaults(GTK_BOX(hbox), ihbox); |
357 | |
358 | gtk_signal_connect (GTK_OBJECT(context.no_found), "clicked", |
359 | GTK_SIGNAL_FUNC(export_update), (gpointer)&context); |
360 | gtk_signal_connect (GTK_OBJECT(context.no_disabled), "clicked", |
361 | GTK_SIGNAL_FUNC(export_update), (gpointer)&context); |
362 | |
363 | gtk_box_pack_start_defaults(GTK_BOX(vbox),hbox); |
364 | |
365 | /* ------------------ info text -------------- */ |
366 | |
367 | context.info_label = gtk_label_new(""); |
368 | gtk_label_set_line_wrap_mode(GTK_LABEL(context.info_label), PANGO_WRAP_WORD); |
369 | gtk_label_set_line_wrap(GTK_LABEL(context.info_label), TRUE); |
370 | gtk_misc_set_alignment(GTK_MISC(context.info_label), 0.f, 0.5f); |
371 | gtk_box_pack_start_defaults(GTK_BOX(vbox), context.info_label); |
372 | |
373 | export_update(NULL, &context); |
374 | |
375 | #ifdef USE_MAEMO |
376 | gtk_box_pack_start_defaults(GTK_BOX(vbox), |
377 | context.launch = check_button_new_with_label( |
378 | _("Launch Maemo Mapper after export"))); |
379 | check_button_set_active(context.launch, !appdata->mmpoi_dontlaunch); |
380 | #endif |
381 | |
382 | /* ------------------ path/file ------------------ */ |
383 | gtk_box_pack_start_defaults(GTK_BOX(vbox), gtk_hseparator_new()); |
384 | |
385 | gtk_box_pack_start_defaults(GTK_BOX(vbox), |
386 | export_file(_("Save POI database"), &appdata->mmpoi_path)); |
387 | |
388 | /* ------------------ info ------------------ */ |
389 | |
390 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context.dialog)->vbox), vbox); |
391 | |
392 | gtk_widget_show_all(context.dialog); |
393 | |
394 | if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(context.dialog))) { |
395 | #ifdef USE_MAEMO |
396 | appdata->mmpoi_dontlaunch = |
397 | !check_button_get_active(context.launch); |
398 | #endif |
399 | |
400 | /* remove existing database */ |
401 | remove(appdata->mmpoi_path); |
402 | |
403 | if(checkdir(appdata->mmpoi_path) != 0) |
404 | errorf(_("Unable to access or create output directory!")); |
405 | else { |
406 | |
407 | /* build a progress dialog which can even be stopped */ |
408 | |
409 | GtkWidget *pbar, *wait_dialog = gtk_dialog_new(); |
410 | gtk_dialog_set_has_separator(GTK_DIALOG(wait_dialog), FALSE); |
411 | gtk_window_set_default_size(GTK_WINDOW(wait_dialog), 250, 100); |
412 | gtk_window_set_title(GTK_WINDOW(wait_dialog), _("Exporting")); |
413 | gtk_window_set_modal(GTK_WINDOW(wait_dialog), TRUE); |
414 | gtk_window_set_transient_for(GTK_WINDOW(wait_dialog), |
415 | GTK_WINDOW(context.dialog)); |
416 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(wait_dialog)->vbox), |
417 | pbar = gtk_progress_bar_new()); |
418 | |
419 | GtkWidget *button = button_new_with_label(_("Cancel")); |
420 | int cancelled = 0; |
421 | gtk_signal_connect(GTK_OBJECT(button), "clicked", |
422 | GTK_SIGNAL_FUNC(on_cancel), (gpointer)&cancelled); |
423 | gtk_container_add(GTK_CONTAINER(GTK_DIALOG(wait_dialog)->action_area), |
424 | button); |
425 | |
426 | gtk_widget_show_all(wait_dialog); |
427 | |
428 | /* ---------------------- copy icons --------------------- */ |
429 | for(i=0;cache_type_icons[i][0];i++) { |
430 | char *src = malloc(strlen(ICONPATH)+strlen(cache_type_icons[i])+ |
431 | strlen("cache_type_.png")+1); |
432 | sprintf(src, ICONPATH "cache_type_%s.png", cache_type_icons[i]); |
433 | if(!g_file_test(src, G_FILE_TEST_EXISTS)) |
434 | sprintf(src, "./icons/cache_type_%s.png", cache_type_icons[i]); |
435 | |
436 | if(g_file_test(src, G_FILE_TEST_EXISTS)) { |
437 | char *dest = malloc(strlen(appdata->mmpoi_path)+ |
438 | strlen(cache_type_icons[i])+strlen(".jpg")+1); |
439 | strcpy(dest, appdata->mmpoi_path); |
440 | *(strrchr(dest, '/')+1) = 0; |
441 | sprintf(dest+strlen(dest), "%s.jpg", cache_type_icons[i]); |
442 | |
443 | /* read source file */ |
444 | FILE *file = fopen(src, "rb"); |
445 | fseek(file, 0, SEEK_END); |
446 | int len = ftell(file); |
447 | fseek(file, 0, SEEK_SET); |
448 | char *buffer = malloc(len); |
449 | fread(buffer, 1l, len, file); |
450 | fclose(file); |
451 | |
452 | /* write destination file */ |
453 | file = fopen(dest, "wb"); |
454 | fwrite(buffer, 1l, len, file); |
455 | fclose(file); |
456 | |
457 | free(buffer); |
458 | free(dest); |
459 | } |
460 | |
461 | free(src); |
462 | } |
463 | |
464 | /* ---------------------- database export --------------------- */ |
465 | |
466 | printf("exporting to %s\n", appdata->mmpoi_path); |
467 | rc = sqlite3_open(appdata->mmpoi_path, &db); |
468 | if( rc ){ |
469 | errorf(_("Can't open SQL database:\n\n%s"), sqlite3_errmsg(db)); |
470 | sqlite3_close(db); |
471 | export_list_free(context.gpx); |
472 | return; |
473 | } |
474 | |
475 | /* create the table */ |
476 | rc = sqlite3_exec(db, "create table category (cat_id integer PRIMARY " |
477 | "KEY, label text, desc text, enabled integer)", |
478 | callback, 0, &zErrMsg); |
479 | if( rc!=SQLITE_OK ) { |
480 | errorf(_("Creating cathegory table:\n\n%s"), zErrMsg); |
481 | sqlite3_close(db); |
482 | export_list_free(context.gpx); |
483 | return; |
484 | } |
485 | |
486 | /* create all types */ |
487 | i = 0; |
488 | while(cache_type_str[i][0]) { |
489 | snprintf(cmd, sizeof(cmd), |
490 | "insert into category values (%d, '%s', '%s', 1)", |
491 | 12+i, cache_type_str[i], cache_type_desc[i]); |
492 | |
493 | rc = sqlite3_exec(db, cmd, callback, 0, &zErrMsg); |
494 | if( rc != SQLITE_OK ){ |
495 | errorf(_("Creating cathegory:\n\n%s"), zErrMsg); |
496 | sqlite3_close(db); |
497 | export_list_free(context.gpx); |
498 | return; |
499 | } |
500 | i++; |
501 | } |
502 | |
503 | rc = sqlite3_exec(db, "create table poi (poi_id integer PRIMARY KEY, " |
504 | "lat real, lon real, label text, desc text, " |
505 | "cat_id integer)", callback, 0, &zErrMsg); |
506 | if( rc!=SQLITE_OK ){ |
507 | errorf(_("Creating POI table:\n\n%s"), zErrMsg); |
508 | sqlite3_close(db); |
509 | export_list_free(context.gpx); |
510 | return; |
511 | } |
512 | |
513 | pos_t *refpos = get_pos(appdata); |
514 | |
515 | /* export the caches */ |
516 | int id = 1; |
517 | cache_t *cache = context.gpx->cache; |
518 | int total = export_list_count(appdata, context.gpx); |
519 | |
520 | while(cache && !cancelled) { |
521 | |
522 | /* no disabled/archived if requested */ |
523 | if(!(appdata->mmpoi_dont_export_disabled && |
524 | (!cache->available || cache->archived))) { |
525 | |
526 | /* no found if requested */ |
527 | if(!(appdata->mmpoi_dont_export_found && |
528 | cache->notes && cache->notes->found)) { |
529 | |
530 | if(appdata->mmpoi_use_radius && !isnan(appdata->mmpoi_radius)) { |
531 | /* count all caches within given radius around current position */ |
532 | if(gpx_pos_get_distance(*refpos, gpx_cache_pos(cache), 0) < |
533 | appdata->mmpoi_radius) { |
534 | printf("%d exporting %s\n", id, cache->id); |
535 | cache_export(db, id++, cache); |
536 | } |
537 | } else { |
538 | printf("%d exporting %s\n", id, cache->id); |
539 | cache_export(db, id++, cache); |
540 | } |
541 | } |
542 | } |
543 | cache = cache->next; |
544 | |
545 | gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pbar), |
546 | (float)(id-2)/(float)total); |
547 | |
548 | while(gtk_events_pending ()) |
549 | gtk_main_iteration (); |
550 | } |
551 | sqlite3_close(db); |
552 | |
553 | gtk_widget_destroy(wait_dialog); |
554 | |
555 | #ifdef USE_MAEMO |
556 | if(!cancelled && !appdata->mmpoi_dontlaunch) |
557 | dbus_mm_set_position(appdata, NULL); |
558 | #endif |
559 | } |
560 | } else { |
561 | /* restore old mmpoi_path, in case it has been altered but not been used */ |
562 | free(appdata->mmpoi_path); |
563 | appdata->mmpoi_path = strdup(old_mmpoi_path); |
564 | } |
565 | |
566 | gtk_widget_destroy(context.dialog); |
567 | |
568 | if(context.handler_id) { |
569 | printf("removing timer\n"); |
570 | gtk_timeout_remove(context.handler_id); |
571 | } |
572 | |
573 | export_list_free(context.gpx); |
574 | |
575 | free(old_mmpoi_path); |
576 | } |