Contents of /trunk/src/geotoad.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 252 - (show annotations)
Thu Feb 4 20:15:19 2010 UTC (14 years, 3 months ago) by harbaum
File MIME type: text/plain
File size: 19211 byte(s)
Geotoad fixes
1 /*
2 * Copyright (C) 2009 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 #define __USE_GNU
23 #include <string.h>
24
25 #include <fcntl.h>
26 #include <sys/types.h>
27 #include <sys/wait.h>
28 #include <errno.h>
29 #include <math.h>
30
31 #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR >= 5)
32 #include <hildon/hildon-entry.h>
33 #endif
34
35 #define GEOTOAD "/usr/bin/geotoad"
36
37 #define COLOR_ERR "red"
38 #define COLOR_OK "darkgreen"
39 #define COLOR_SYSTEM "darkblue"
40
41 #define BUFFER_SIZE 256
42
43 typedef enum {
44 GT_STATE_ILLEGAL = 0,
45 GT_STATE_STARTED,
46 GT_STATE_PREMATURE_END,
47 GT_STATE_SAVE_FOUND,
48 } gt_state_t;
49
50 typedef struct {
51 appdata_t *appdata;
52
53 GPtrArray *argv;
54 gchar *info, *color;
55 GMutex *update_mutex;
56 GCond *update_cond;
57
58 gt_state_t state;
59
60 GtkWidget *dialog;
61
62 long pid;
63
64 struct log_s {
65 GtkTextBuffer *buffer;
66 GtkWidget *view;
67 } log;
68
69 GtkWidget *username, *password;
70 GtkWidget *lat, *lon, *dst, *no_of;
71 char *filename;
72
73 int use_cnt;
74
75 } gt_context_t;
76
77 static void arg_free(gpointer data, gpointer user_data) {
78 if(data) g_free(data);
79 }
80
81 static void context_free(gt_context_t *context) {
82 context->use_cnt--;
83
84 if(context->use_cnt > 0)
85 printf("still in use by %d, keeping context\n", context->use_cnt);
86 else {
87 printf("freeing context\n");
88
89 if(context->info) g_free(context->info);
90
91 if(context->argv) {
92 g_ptr_array_foreach(context->argv, arg_free, NULL);
93 g_ptr_array_free (context->argv, TRUE);
94 }
95
96 if(context->filename) g_free(context->filename);
97
98 g_free(context);
99 }
100 }
101
102 static void appendf(struct log_s *log, char *colname,
103 const char *fmt, ...) {
104 va_list args;
105 va_start( args, fmt );
106 char *buf = g_strdup_vprintf(fmt, args);
107 va_end( args );
108
109 // printf("append: %s", buf);
110
111 GtkTextTag *tag = NULL;
112 if(colname)
113 tag = gtk_text_buffer_create_tag(log->buffer, NULL,
114 "foreground", colname,
115 NULL);
116
117 GtkTextIter end;
118 gtk_text_buffer_get_end_iter(log->buffer, &end);
119 if(tag)
120 gtk_text_buffer_insert_with_tags(log->buffer, &end, buf, -1, tag, NULL);
121 else
122 gtk_text_buffer_insert(log->buffer, &end, buf, -1);
123
124 g_free(buf);
125
126 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(log->view),
127 &end, 0.0, TRUE, 1.0, 1.0);
128 }
129
130 // This function receives a requst from a worker thread asking to
131 // update the gui with the required info.
132 gboolean cb_update_job(gt_context_t *context) {
133 if (context->info) {
134
135 /* check if client reports "saved to ..." */
136 if(strstr(context->info, "Saved to "))
137 context->state = GT_STATE_SAVE_FOUND;
138
139 appendf(&context->log, context->color, context->info);
140
141 g_free(context->info);
142 context->info = NULL;
143 }
144
145 // Indicate that the update is done
146 g_mutex_lock(context->update_mutex);
147 g_cond_signal(context->update_cond);
148 g_mutex_unlock(context->update_mutex);
149
150 return FALSE;
151 }
152
153 // A helper function run in the job thread receiving the string that
154 // should be displayed in the textview.
155 void append_parentf(gt_context_t *context, char *colname,
156 const char *fmt, ...) {
157 va_list args;
158 va_start( args, fmt );
159 context->info = g_strdup_vprintf(fmt, args);
160 va_end( args );
161
162 if(colname)
163 context->color = colname;
164 else
165 context->color = NULL;
166
167 // Lock mutex to make sure that we will receive the condition signal
168 g_mutex_lock(context->update_mutex);
169
170 g_idle_add((GSourceFunc)cb_update_job, context);
171
172 // Wait for cb_update_job to tell me that the update is done
173 g_cond_wait(context->update_cond, context->update_mutex);
174 g_mutex_unlock(context->update_mutex);
175 }
176
177 /* custom version of popen to get access to the pid and to be able */
178 /* to slaughter the child process */
179 static FILE *gt_popen(gt_context_t *context, char **args,
180 const char *type, long *tid) {
181 int p[2];
182 FILE *fp;
183
184 if (*type != 'r' && *type != 'w')
185 return NULL;
186
187 if (pipe(p) < 0)
188 return NULL;
189
190 if ((*tid = fork()) > 0) { /* then we are the parent */
191 if (*type == 'r') {
192 close(p[1]);
193 fp = fdopen(p[0], type);
194 } else {
195 close(p[0]);
196 fp = fdopen(p[1], type);
197 }
198
199 return fp;
200 } else if (*tid == 0) { /* we're the child */
201
202 /* make our thread id the process group leader */
203 setpgid(0, 0);
204
205 if (*type == 'r') {
206 fflush(stdout);
207 fflush(stderr);
208 close(1);
209 if (dup(p[1]) < 0)
210 perror("dup of write side of pipe failed");
211 close(2);
212 if (dup(p[1]) < 0)
213 perror("dup of write side of pipe failed");
214 } else {
215 close(0);
216 if (dup(p[0]) < 0)
217 perror("dup of read side of pipe failed");
218 }
219
220 close(p[0]); /* close since we dup()'ed what we needed */
221 close(p[1]);
222
223 execve(args[0], args, NULL);
224
225 /* this printf will actually be redirected through the pipe */
226 printf("Error: Failed to execute!\n");
227 exit(1);
228 } else { /* we're having major problems... */
229 close(p[0]);
230 close(p[1]);
231
232 /* this printf will actually be redirected through the pipe??? No! */
233 printf("Error: Failed to fork!\n");
234 }
235
236 return NULL;
237 }
238
239 // The thread entry point. It will do the job, send the data to the
240 // GUI and self destruct when it is done.
241 static gpointer thread_worker(gt_context_t *context)
242 {
243 FILE *fh;
244 GIOStatus status;
245 GError *error = NULL;
246 gsize length;
247 gsize terminator_pos;
248 gchar *str_return;
249
250 fh = gt_popen(context, (char**)(context->argv->pdata),"r", &context->pid);
251 if(!fh) {
252 printf("fail free\n");
253 context_free(context);
254 // g_thread_exit(NULL);
255 return NULL;
256 }
257
258 /* the client is running */
259 printf("client inc use_cnt\n");
260 context->use_cnt++;
261
262 /* switch to line buffered mode */
263 if(setvbuf(fh, NULL, _IOLBF, 0))
264 perror("setvbuf(_IOLBF)");
265
266 GIOChannel *gh = g_io_channel_unix_new(fileno(fh));
267
268 while( (status = g_io_channel_read_line(gh,
269 &str_return,
270 &length,
271 &terminator_pos,
272 &error)) == G_IO_STATUS_NORMAL) {
273 char *color = NULL;
274 if(strstr(str_return, "Saved to ")) color = COLOR_OK;
275 if(strcasestr(str_return, "error")) color = COLOR_ERR;
276
277 append_parentf(context, color, str_return);
278 g_free(str_return);
279 }
280
281 g_io_channel_unref(gh);
282 pclose(fh);
283 append_parentf(context, COLOR_SYSTEM, "Job done!");
284
285 context_free(context);
286
287 g_thread_exit(NULL);
288 return NULL;
289 }
290
291
292 static void arg_dsp(gpointer data, gpointer user_data) {
293 gt_context_t *context = (gt_context_t*)user_data;
294
295 if(data)
296 appendf(&context->log, COLOR_SYSTEM, "%s\n", data);
297 }
298
299 static void run(gt_context_t *context) {
300 GError *error = NULL;
301
302 /* build list of arguments to call geotoad */
303 context->argv = g_ptr_array_new();
304 g_ptr_array_add (context->argv, g_strdup_printf(GEOTOAD));
305 g_ptr_array_add (context->argv,
306 g_strdup_printf("--distanceMax=%u",
307 (int)ceil(context->appdata->gt.distance)));
308 g_ptr_array_add (context->argv,
309 g_strdup_printf("--output=%s", context->appdata->gt.filename));
310 g_ptr_array_add (context->argv,
311 g_strdup_printf("--password=%s", context->appdata->gt.password));
312 g_ptr_array_add (context->argv,
313 g_strdup_printf("--queryType=coord"));
314 g_ptr_array_add (context->argv,
315 g_strdup_printf("--user=%s", context->appdata->username));
316
317 if(context->appdata->gt.no_owned_found) {
318 g_ptr_array_add (context->argv,
319 g_strdup_printf("--userExclude=%s", context->appdata->username));
320 g_ptr_array_add (context->argv,
321 g_strdup_printf("--ownerExclude=%s", context->appdata->username));
322 }
323
324 /* check if we need to add proxy config */
325 char *proxy = NULL;
326
327 if(context->appdata->proxy && context->appdata->proxy->host) {
328 if(context->appdata->proxy->use_authentication &&
329 context->appdata->proxy->authentication_user &&
330 context->appdata->proxy->authentication_password)
331 proxy = g_strdup_printf("--proxy=http://%s:%s@%s:%d",
332 context->appdata->proxy->authentication_user,
333 context->appdata->proxy->authentication_password,
334 context->appdata->proxy->host,
335 context->appdata->proxy->port);
336 else
337 proxy = g_strdup_printf("--proxy=http://%s:%d",
338 context->appdata->proxy->host,
339 context->appdata->proxy->port);
340
341 } else {
342 /* use environment settings if preset (for scratchbox) */
343 const char *proxy_env = g_getenv("http_proxy");
344 if(proxy_env)
345 proxy = g_strdup_printf("--proxy=%s", proxy_env);
346 }
347
348 if(proxy)
349 g_ptr_array_add (context->argv, proxy);
350
351 /* convert coordinates into simple ascii format */
352 char n = (context->appdata->gt.lat >= 0)?'N':'S';
353 char e = (context->appdata->gt.lon >= 0)?'E':'W';
354 float lat = fabs(context->appdata->gt.lat);
355 float lon = fabs(context->appdata->gt.lon);
356 float lat_mint, lat_int, lat_frac = modff(lat, &lat_int);
357 float lon_mint, lon_int, lon_frac = modff(lon, &lon_int);
358 lat_frac = modff(lat_frac*60.0, &lat_mint);
359 lon_frac = modff(lon_frac*60.0, &lon_mint);
360
361 g_ptr_array_add (context->argv,
362 g_strdup_printf("%c%02u %02u.%03u %c%03u %02u.%03u",
363 n, (int)lat_int, (int)lat_mint, (int)(lat_frac*1000.0+0.5),
364 e, (int)lon_int, (int)lon_mint, (int)(lon_frac*1000.0+0.5)));
365
366 g_ptr_array_add (context->argv, NULL);
367
368 /* show all entries */
369 g_ptr_array_foreach(context->argv, arg_dsp, context);
370
371 g_thread_create((GThreadFunc)thread_worker, context, FALSE, &error);
372 if (error) {
373 appendf(&context->log, COLOR_ERR, "Error: %s\n", error->message);
374 g_error_free(error);
375 }
376 }
377
378 /* show text window and display output of running geotoad */
379 static void gui_run(gt_context_t *context) {
380 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("GeoToad - Run"),
381 GTK_WINDOW(context->appdata->window),
382 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
383 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
384 NULL);
385
386 gtk_window_set_default_size(GTK_WINDOW(dialog), 640, 480);
387
388 #ifndef USE_PANNABLE_AREA
389 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
390 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
391 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
392 #else
393 GtkWidget *pannable_area = hildon_pannable_area_new();
394 #endif
395
396 context->log.buffer = gtk_text_buffer_new(NULL);
397
398 #ifndef USE_HILDON_TEXT_VIEW
399 context->log.view = gtk_text_view_new_with_buffer(context->log.buffer);
400 #else
401 context->log.view = hildon_text_view_new();
402 hildon_text_view_set_buffer(HILDON_TEXT_VIEW(context->log.view),
403 context->log.buffer);
404 #endif
405
406 #ifndef USE_PANNABLE_AREA
407 gtk_container_add(GTK_CONTAINER(scrolled_window), context->log.view);
408 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW(scrolled_window),
409 GTK_SHADOW_IN);
410
411 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
412 scrolled_window, TRUE, TRUE, 0);
413 #else
414 gtk_container_add(GTK_CONTAINER(pannable_area), context->log.view);
415 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
416 pannable_area, TRUE, TRUE, 0);
417 #endif
418
419 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
420
421 gtk_widget_show_all(dialog);
422
423 appendf(&context->log, COLOR_SYSTEM, "Running GeoToad\n");
424 run(context);
425
426 gtk_dialog_run(GTK_DIALOG(dialog));
427
428 gtk_widget_destroy(dialog);
429 }
430
431 static void table_attach(GtkWidget *table, GtkWidget *child, int left, int top) {
432 gtk_table_attach_defaults(GTK_TABLE(table), child, left, left+1, top, top+1);
433 }
434
435
436 static gboolean gui_setup(gt_context_t *context) {
437 appdata_t *appdata = context->appdata;
438 gboolean ok = FALSE;
439
440 /* if no filename has been setup yet, create one */
441 if(!appdata->gt.filename && appdata->path) {
442 printf("creating path\n");
443 appdata->gt.filename =
444 g_strdup_printf("%s/gtoad.gpx", appdata->path);
445 }
446
447 if(appdata->gt.filename)
448 context->filename = g_strdup(appdata->gt.filename);
449
450 context->dialog = gtk_dialog_new_with_buttons(_("GeoToad - Setup"),
451 GTK_WINDOW(appdata->window),
452 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
453 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
454 GTK_STOCK_OK, GTK_RESPONSE_OK,
455 NULL);
456
457 GtkWidget *table = gtk_table_new(5, 2, FALSE);
458
459 /* ------------------- Coordinates ------------------------- */
460
461 table_attach(table, left_label_new(_("Position:")), 0, 0);
462 table_attach(table, left_label_new(_("Distance:")), 0, 1);
463
464 /* setup default positions */
465 pos_t *refpos = get_pos(appdata);
466 if((isnan(appdata->gt.lat) || isnan(appdata->gt.lat)) && refpos) {
467 appdata->gt.lat = refpos->lat;
468 appdata->gt.lon = refpos->lon;
469 }
470
471 GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
472 context->lat = lat_entry_new(appdata->gt.lat);
473 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->lat);
474 context->lon = lon_entry_new(appdata->gt.lon);
475 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->lon);
476 GtkWidget *popup = preset_coordinate_picker(appdata, context->lat, context->lon);
477 gtk_box_pack_start_defaults(GTK_BOX(hbox), popup);
478 table_attach(table, hbox, 1, 0);
479
480 hbox = gtk_hbox_new(FALSE, 0);
481 float dst = appdata->gt.distance; // distance is given in miles
482 if(!appdata->imperial) dst *= 1.609344;
483 context->dst = dist_entry_new(dst, appdata->imperial);
484 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->dst);
485
486 context->no_of = check_button_new_with_label(_("No owned/found"));
487 check_button_set_active(context->no_of, appdata->gt.no_owned_found);
488 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->no_of);
489
490 table_attach(table, hbox, 1, 1);
491
492 /* ------------------- Username/Password ------------------------- */
493 table_attach(table, left_label_new(_("Username:")), 0, 2);
494 table_attach(table, left_label_new(_("Password:")), 0, 3);
495
496 context->username = entry_new();
497 context->password = entry_new();
498 gtk_entry_set_visibility(GTK_ENTRY(context->password), FALSE);
499
500 /* set saved defaults */
501 if(appdata->username)
502 gtk_entry_set_text(GTK_ENTRY(context->username),
503 appdata->username);
504
505 if(appdata->gt.password)
506 gtk_entry_set_text(GTK_ENTRY(context->password),
507 appdata->gt.password);
508
509 table_attach(table, context->username, 1, 2);
510 table_attach(table, context->password, 1, 3);
511
512 /* ------------------- file name ------------------------- */
513 gtk_table_attach_defaults(GTK_TABLE(table),
514 export_file(_("Save GPX file"), &context->filename),
515 0, 2, 4, 5);
516
517 /* ---------------------------------------------------------------- */
518 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
519 table);
520
521 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
522 GTK_RESPONSE_OK);
523
524 gtk_widget_show_all(context->dialog);
525
526 if(gtk_dialog_run(GTK_DIALOG(context->dialog)) == GTK_RESPONSE_OK) {
527
528 /* parse coordinates */
529 appdata->gt.lat = lat_entry_get(context->lat);
530 appdata->gt.lon = lon_entry_get(context->lon);
531
532 /* save values */
533 if(appdata->username) g_free(appdata->username);
534 appdata->username =
535 g_strdup(gtk_entry_get_text(GTK_ENTRY(context->username)));
536
537 if(appdata->gt.password) g_free(appdata->gt.password);
538 appdata->gt.password =
539 g_strdup(gtk_entry_get_text(GTK_ENTRY(context->password)));
540
541 if(appdata->gt.filename) g_free(appdata->gt.filename);
542 if(context->filename)
543 appdata->gt.filename = g_strdup(context->filename);
544
545 /* get distance in miles */
546 appdata->gt.distance = dist_entry_get(context->dst, TRUE);
547
548 /* get "no owned/found" state */
549 appdata->gt.no_owned_found = check_button_get_active(context->no_of);
550
551 /* check for valid entries */
552 if(isnan(appdata->gt.lat) || isnan(appdata->gt.lon) ||
553 isnan(appdata->gt.distance) || !appdata->gt.filename ||
554 !appdata->username || !appdata->gt.password)
555 errorf(_("The GeoToad setup is not complete."));
556 else
557 ok = TRUE;
558 }
559
560 gtk_widget_destroy(context->dialog);
561
562 return ok;
563 }
564
565 void geotoad(appdata_t *appdata) {
566 if(!geotoad_available()) {
567 errorf(_("GeoToad is not installed on this device.\n"
568 "You need to install it in order to be able to use it."));
569 return;
570 }
571
572 gt_context_t *context = g_new0(gt_context_t, 1);
573 context->appdata = appdata;
574 context->use_cnt++; // parent still uses this
575
576 context->update_mutex = g_mutex_new();
577 context->update_cond = g_cond_new();
578
579 if(gui_setup(context))
580 gui_run(context);
581
582 /* continue to process if something has actually been saved */
583 if(context->state == GT_STATE_SAVE_FOUND) {
584 /* download seems to be successful. Make sure the GUI is */
585 /* updated if required */
586
587 gpx_t **gpx = &(appdata->gpx);
588 while(*gpx && strcmp((*gpx)->filename, appdata->gt.filename))
589 gpx = &(*gpx)->next;
590
591 /* return main GUI to GPX list */
592 #ifdef USE_BREAD_CRUMB_TRAIL
593 while(appdata->cur_gpx)
594 hildon_bread_crumb_trail_pop(HILDON_BREAD_CRUMB_TRAIL(appdata->bct));
595 #elif defined(BCT)
596 while(appdata->cur_gpx)
597 bct_pop(appdata->bct);
598 #else
599 HildonWindowStack *stack = hildon_window_stack_get_default();
600 gint num = hildon_window_stack_size(stack)-1;
601 while(num--) {
602 GtkWidget *top = hildon_window_stack_peek(stack);
603 gtk_widget_destroy(top);
604 }
605 #endif
606
607 /* replace an existing entry or add to end of list */
608 if(*gpx) {
609 GtkTreeIter iter;
610 g_assert(gpxlist_find(appdata, &iter, *gpx));
611
612 gpx_t *next = (*gpx)->next;
613
614 gpx_free(*gpx);
615 *gpx = gpx_parse(NULL, appdata->gt.filename, appdata->username);
616 (*gpx)->next = next;
617
618 /* update gpxlist */
619 gpxlist_set(appdata->gpxstore, &iter, *gpx);
620
621 /* select that row */
622 GtkTreeSelection *selection =
623 gtk_tree_view_get_selection(GTK_TREE_VIEW(appdata->gpxview));
624 gtk_tree_selection_select_iter(selection, &iter);
625 GtkTreePath *path =
626 gtk_tree_model_get_path(GTK_TREE_MODEL(appdata->gpxstore), &iter);
627 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(appdata->gpxview),
628 path, NULL, TRUE, 0.0, 0.0);
629 gtk_tree_path_free(path);
630 } else {
631 gpx_t *new = gpx_parse(NULL, appdata->gt.filename, appdata->username);
632 if(new) gpxlist_add(appdata, new);
633 }
634 }
635
636 printf("main context free\n");
637 context_free(context);
638 }
639
640 gboolean geotoad_available(void) {
641 /* before doing anything make sure geotoad is installed */
642 return g_file_test(GEOTOAD, G_FILE_TEST_IS_EXECUTABLE);
643 }
644
645