Contents of /trunk/src/geotoad.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 206 - (show annotations)
Tue Nov 24 12:42:31 2009 UTC (14 years, 7 months ago) by harbaum
File MIME type: text/plain
File size: 18560 byte(s)
Some geotoad processing
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;
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, *filename;
70 GtkWidget *lat, *lon, *dst;
71
72 } gt_context_t;
73
74 static void arg_free(gpointer data, gpointer user_data) {
75 if(data) g_free(data);
76 }
77
78 static void context_free(gt_context_t *context) {
79 printf("freeing context\n");
80
81 if(context->info) g_free(context->info);
82
83 g_ptr_array_foreach(context->argv, arg_free, NULL);
84 g_ptr_array_free (context->argv, TRUE);
85
86 g_free(context);
87 }
88
89 static void appendf(struct log_s *log, char *colname,
90 const char *fmt, ...) {
91 va_list args;
92 va_start( args, fmt );
93 char *buf = g_strdup_vprintf(fmt, args);
94 va_end( args );
95
96 // printf("append: %s", buf);
97
98 GtkTextTag *tag = NULL;
99 if(colname)
100 tag = gtk_text_buffer_create_tag(log->buffer, NULL,
101 "foreground", colname,
102 NULL);
103
104 GtkTextIter end;
105 gtk_text_buffer_get_end_iter(log->buffer, &end);
106 if(tag)
107 gtk_text_buffer_insert_with_tags(log->buffer, &end, buf, -1, tag, NULL);
108 else
109 gtk_text_buffer_insert(log->buffer, &end, buf, -1);
110
111 g_free(buf);
112
113 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(log->view),
114 &end, 0.0, TRUE, 0, 1.0);
115 }
116
117 // This function receives a requst from a worker thread asking to
118 // update the gui with the required info.
119 gboolean cb_update_job(gt_context_t *context) {
120 if (context->info) {
121 char *color = NULL;
122 if(strstr(context->info, "Saved to ") != NULL) {
123 context->state = GT_STATE_SAVE_FOUND;
124 color = COLOR_OK;
125 }
126
127 if(strcasestr(context->info, "error") != NULL)
128 color = COLOR_ERR;
129
130 appendf(&context->log, color, context->info);
131
132 g_free(context->info);
133 context->info = NULL;
134 }
135
136 // Indicate that the update is done
137 g_mutex_lock(context->update_mutex);
138 g_cond_signal(context->update_cond);
139 g_mutex_unlock(context->update_mutex);
140
141 return FALSE;
142 }
143
144 // A helper function run in the job thread receiving the string that
145 // should be displayed in the textview.
146 void job_add_to_text_viewer(gt_context_t *context,
147 const char *info)
148 {
149 /* TODO: make this cope with format strings */
150 /* TODO: make this cope with colors */
151 context->info = g_strdup(info);
152
153 // Lock mutex to make sure that we will receive the condition signal
154 g_mutex_lock(context->update_mutex);
155 g_idle_add((GSourceFunc)cb_update_job, context);
156
157 // Wait for cb_update_job to tell me that the update is done
158 g_cond_wait(context->update_cond,
159 context->update_mutex);
160 g_mutex_unlock(context->update_mutex);
161 }
162
163 /* custom version of popen to get access to the pid and to be able */
164 /* to slaughter the child process */
165 static FILE *gt_popen(char **args, const char *type, long *tid) {
166 int p[2];
167 FILE *fp;
168
169 if (*type != 'r' && *type != 'w')
170 return NULL;
171
172 if (pipe(p) < 0)
173 return NULL;
174
175 if ((*tid = fork()) > 0) { /* then we are the parent */
176 if (*type == 'r') {
177 close(p[1]);
178 fp = fdopen(p[0], type);
179 } else {
180 close(p[0]);
181 fp = fdopen(p[1], type);
182 }
183
184 return fp;
185 } else if (*tid == 0) { /* we're the child */
186
187 /* make our thread id the process group leader */
188 setpgid(0, 0);
189
190 if (*type == 'r') {
191 fflush(stdout);
192 fflush(stderr);
193 close(1);
194 if (dup(p[1]) < 0)
195 perror("dup of write side of pipe failed");
196 close(2);
197 if (dup(p[1]) < 0)
198 perror("dup of write side of pipe failed");
199 } else {
200 close(0);
201 if (dup(p[0]) < 0)
202 perror("dup of read side of pipe failed");
203 }
204
205 close(p[0]); /* close since we dup()'ed what we needed */
206 close(p[1]);
207
208 execve(args[0], args, NULL);
209
210 printf("gt_popen(): execve(%s) failed!\n", args[0]);
211 } else { /* we're having major problems... */
212 close(p[0]);
213 close(p[1]);
214 printf("gt_popen(): fork() failure!\n");
215 }
216
217 return NULL;
218 }
219
220 // The thread entry point. It will do the job, send the data to the
221 // GUI and self destruct when it is done.
222 static gpointer thread_worker(gt_context_t *context)
223 {
224 FILE *fh;
225 GIOStatus status;
226 GError *error = NULL;
227 gsize length;
228 gsize terminator_pos;
229 gchar *str_return;
230
231 fh = gt_popen((char**)(context->argv->pdata),"r", &context->pid);
232 if(!fh) {
233 appendf(&context->log, COLOR_ERR, _("Failed to run GeoToad"));
234 return NULL;
235 }
236
237 /* switch to line buffered mode */
238 if(setvbuf(fh, NULL, _IOLBF, 0))
239 perror("setvbuf(_IOLBF)");
240
241 GIOChannel *gh = g_io_channel_unix_new(fileno(fh));
242
243 while( (status = g_io_channel_read_line(gh,
244 &str_return,
245 &length,
246 &terminator_pos,
247 &error)) == G_IO_STATUS_NORMAL) {
248 job_add_to_text_viewer(context, str_return);
249 g_free(str_return);
250 }
251
252 g_io_channel_unref(gh);
253 pclose(fh);
254 job_add_to_text_viewer(context, "Job done!");
255
256 g_thread_exit(NULL);
257 context_free(context);
258
259 return NULL;
260 }
261
262
263 static void arg_dsp(gpointer data, gpointer user_data) {
264 gt_context_t *context = (gt_context_t*)user_data;
265
266 if(data)
267 appendf(&context->log, COLOR_SYSTEM, "%s\n", data);
268 }
269
270 static void run(gt_context_t *context) {
271 GError *error = NULL;
272 char str[8];
273
274 /* build list of arguments to call geotoad */
275 context->argv = g_ptr_array_new();
276 g_ptr_array_add (context->argv, g_strdup_printf(GEOTOAD "a"));
277 g_ascii_dtostr(str, sizeof(str), context->appdata->gt.distance);
278 g_ptr_array_add (context->argv, g_strdup_printf("--distanceMax=%s", str));
279 g_ptr_array_add (context->argv, g_strdup_printf("--output=%s", context->appdata->gt.filename));
280 g_ptr_array_add (context->argv, g_strdup_printf("--password=%s", context->appdata->gt.password));
281 g_ptr_array_add (context->argv, g_strdup_printf("--queryType=coord"));
282 g_ptr_array_add (context->argv, g_strdup_printf("--user=%s", context->appdata->username));
283
284 /* check if we need to add proxy config */
285 char *proxy = NULL;
286 if(context->appdata->proxy && context->appdata->proxy->host) {
287 if(context->appdata->proxy->use_authentication &&
288 context->appdata->proxy->authentication_user &&
289 context->appdata->proxy->authentication_password)
290 proxy = g_strdup_printf("--proxy=http://%s:%s@%s:%d",
291 context->appdata->proxy->authentication_user,
292 context->appdata->proxy->authentication_password,
293 context->appdata->proxy->host,
294 context->appdata->proxy->port);
295 else
296 proxy = g_strdup_printf("--proxy=http://%s:%d",
297 context->appdata->proxy->host,
298 context->appdata->proxy->port);
299
300 g_ptr_array_add (context->argv, proxy);
301 }
302
303 /* convert coordinates into simple ascii format */
304 char n = (context->appdata->gt.lat >= 0)?'N':'S';
305 char e = (context->appdata->gt.lon >= 0)?'E':'W';
306 float lat = fabs(context->appdata->gt.lat);
307 float lon = fabs(context->appdata->gt.lon);
308 float lat_mint, lat_int, lat_frac = modff(lat, &lat_int);
309 float lon_mint, lon_int, lon_frac = modff(lon, &lon_int);
310 lat_frac = modff(lat_frac*60.0, &lat_mint);
311 lon_frac = modff(lon_frac*60.0, &lon_mint);
312
313 g_ptr_array_add (context->argv,
314 g_strdup_printf("%c%02u %02u.%03u %c%03u %02u.%03u",
315 n, (int)lat_int, (int)lat_mint, (int)(lat_frac*1000.0+0.5),
316 e, (int)lon_int, (int)lon_mint, (int)(lon_frac*1000.0+0.5)));
317
318 // g_ptr_array_add (context->argv, g_strdup_printf("2>&1"));
319 g_ptr_array_add (context->argv, NULL);
320
321 /* show all entries */
322 g_ptr_array_foreach(context->argv, arg_dsp, context);
323
324 g_thread_create((GThreadFunc)thread_worker, context, FALSE, &error);
325 if (error) {
326 appendf(&context->log, COLOR_ERR, "Error: %s\n", error->message);
327 g_error_free(error);
328 }
329 }
330
331 /* show text window and display output of running geotoad */
332 static void gui_run(gt_context_t *context) {
333 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("GeoToad - Run"),
334 GTK_WINDOW(context->appdata->window),
335 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
336 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
337 NULL);
338
339 gtk_window_set_default_size(GTK_WINDOW(dialog), 640, 480);
340
341 #ifndef USE_PANNABLE_AREA
342 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
343 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
344 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
345 #else
346 GtkWidget *pannable_area = hildon_pannable_area_new();
347 #endif
348
349 context->log.buffer = gtk_text_buffer_new(NULL);
350
351 #ifndef USE_HILDON_TEXT_VIEW
352 context->log.view = gtk_text_view_new_with_buffer(context->log.buffer);
353 #else
354 context->log.view = hildon_text_view_new();
355 hildon_text_view_set_buffer(HILDON_TEXT_VIEW(context->log.view),
356 context->log.buffer);
357 #endif
358
359 #ifndef USE_PANNABLE_AREA
360 gtk_container_add(GTK_CONTAINER(scrolled_window), context->log.view);
361 gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW(scrolled_window),
362 GTK_SHADOW_IN);
363
364 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
365 scrolled_window, TRUE, TRUE, 0);
366 #else
367 gtk_container_add(GTK_CONTAINER(pannable_area), context->log.view);
368 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
369 pannable_area, TRUE, TRUE, 0);
370 #endif
371
372 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
373
374 gtk_widget_show_all(dialog);
375
376 appendf(&context->log, COLOR_SYSTEM, "Running GeoToad\n");
377 run(context);
378
379 gtk_dialog_run(GTK_DIALOG(dialog));
380
381 gtk_widget_destroy(dialog);
382
383 errorf("Fertig!\n");
384
385 }
386
387 static void on_browse(GtkWidget *widget, gpointer data) {
388 gt_context_t *context = (gt_context_t*)data;
389
390 printf("Browse %p\n", context->dialog);
391
392 #ifdef USE_MAEMO
393 GtkWidget *dialog = hildon_file_chooser_dialog_new(GTK_WINDOW(context->dialog),
394 GTK_FILE_CHOOSER_ACTION_SAVE);
395 #else
396 GtkWidget *dialog = gtk_file_chooser_dialog_new(_("Save GPX file"),
397 GTK_WINDOW(context->dialog),
398 GTK_FILE_CHOOSER_ACTION_SAVE,
399 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
400 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
401 NULL);
402 #endif
403
404 printf("set filename <%s>\n", context->appdata->gt.filename);
405
406 if(!g_file_test(context->appdata->gt.filename, G_FILE_TEST_EXISTS)) {
407 char *last_sep = strrchr(context->appdata->gt.filename, '/');
408 if(last_sep) {
409 *last_sep = 0; // seperate path from file
410
411 /* the user just created a new document */
412 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
413 context->appdata->gt.filename);
414 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), last_sep+1);
415
416 /* restore full filename */
417 *last_sep = '/';
418 }
419 } else
420 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog),
421 context->appdata->gt.filename);
422
423 if (gtk_dialog_run (GTK_DIALOG(dialog)) == GTK_FM_OK) {
424 gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
425 gtk_label_set_text(GTK_LABEL(context->filename), name);
426 }
427
428 gtk_widget_destroy (dialog);
429 }
430
431 static gboolean gui_setup(gt_context_t *context) {
432 appdata_t *appdata = context->appdata;
433 gboolean ok = FALSE;
434
435 /* if no filename has been setup yet, create one */
436 if(!appdata->gt.filename && appdata->path) {
437 printf("creating path\n");
438 appdata->gt.filename =
439 g_strdup_printf("%s/gtoad.gpx", appdata->path);
440 }
441
442 context->dialog = gtk_dialog_new_with_buttons(_("GeoToad - Setup"),
443 GTK_WINDOW(appdata->window),
444 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
445 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
446 GTK_STOCK_OK, GTK_RESPONSE_OK,
447 NULL);
448
449 /* ------------------- Coordinates ------------------------- */
450 GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
451
452 GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
453 gtk_box_pack_start_defaults(GTK_BOX(vbox), left_label_new(_("Position:")));
454 gtk_box_pack_start_defaults(GTK_BOX(vbox), left_label_new(_("Distance:")));
455 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
456
457 /* setup default positions */
458 pos_t *refpos = get_pos(appdata);
459 if((isnan(appdata->gt.lat) || isnan(appdata->gt.lat)) && refpos) {
460 appdata->gt.lat = refpos->lat;
461 appdata->gt.lon = refpos->lon;
462 }
463
464 vbox = gtk_vbox_new(FALSE, 0);
465 GtkWidget *ihbox = gtk_hbox_new(FALSE, 0);
466 context->lat = lat_entry_new(appdata->gt.lat);
467 gtk_box_pack_start_defaults(GTK_BOX(ihbox), context->lat);
468 context->lon = lon_entry_new(appdata->gt.lon);
469 gtk_box_pack_start_defaults(GTK_BOX(ihbox), context->lon);
470 gtk_box_pack_start_defaults(GTK_BOX(vbox), ihbox);
471 float dst = appdata->gt.distance; // distance is given in kilometers
472 if(appdata->imperial) dst /= 1.609344;
473 context->dst = dist_entry_new(dst, appdata->imperial);
474 gtk_box_pack_start_defaults(GTK_BOX(vbox), context->dst);
475 gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox);
476
477 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox), hbox);
478
479 /* ------------------- file name ------------------------- */
480 hbox = gtk_hbox_new(FALSE, 0);
481
482 context->filename = gtk_label_new(appdata->gt.filename);
483 gtk_misc_set_alignment(GTK_MISC(context->filename), 0.f, 0.5f);
484 gtk_label_set_ellipsize(GTK_LABEL(context->filename), PANGO_ELLIPSIZE_MIDDLE);
485 gtk_box_pack_start_defaults(GTK_BOX(hbox), context->filename);
486
487 GtkWidget *button = gtk_button_new_with_label(_("Browse"));
488 #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR == 5)
489 hildon_gtk_widget_set_theme_size(button,
490 (HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH));
491 #endif
492 gtk_signal_connect(GTK_OBJECT(button), "clicked",
493 GTK_SIGNAL_FUNC(on_browse), context);
494 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
495
496 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox), hbox);
497
498
499 /* ------------------- Username/Password ------------------------- */
500 hbox = gtk_hbox_new(FALSE, 0);
501 vbox = gtk_vbox_new(FALSE, 0);
502 gtk_box_pack_start_defaults(GTK_BOX(vbox), left_label_new(_("Username:")));
503 gtk_box_pack_start_defaults(GTK_BOX(vbox), left_label_new(_("Password:")));
504 gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
505
506 vbox = gtk_vbox_new(FALSE, 0);
507 #ifndef FREMANTLE
508 context->username = gtk_entry_new();
509 context->password = gtk_entry_new();
510 #else
511 context->username = hildon_entry_new(HILDON_SIZE_AUTO);
512 hildon_gtk_entry_set_input_mode(GTK_ENTRY(context->username),
513 HILDON_GTK_INPUT_MODE_FULL);
514 context->password = hildon_entry_new(HILDON_SIZE_AUTO);
515 hildon_gtk_entry_set_input_mode(GTK_ENTRY(context->password),
516 HILDON_GTK_INPUT_MODE_FULL);
517 #endif
518 gtk_entry_set_visibility(GTK_ENTRY(context->password), FALSE);
519
520 /* set saved defaults */
521 if(appdata->username)
522 gtk_entry_set_text(GTK_ENTRY(context->username),
523 appdata->username);
524
525 if(appdata->gt.password)
526 gtk_entry_set_text(GTK_ENTRY(context->password),
527 appdata->gt.password);
528
529 gtk_box_pack_start_defaults(GTK_BOX(vbox), context->username);
530 gtk_box_pack_start_defaults(GTK_BOX(vbox), context->password);
531 gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox);
532
533 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox), hbox);
534
535 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog), GTK_RESPONSE_OK);
536
537 gtk_widget_show_all(context->dialog);
538
539 if(gtk_dialog_run(GTK_DIALOG(context->dialog)) == GTK_RESPONSE_OK) {
540
541 /* parse coordinates */
542 appdata->gt.lat = lat_get(context->lat);
543 appdata->gt.lon = lon_get(context->lon);
544
545 /* save values */
546 if(appdata->username) g_free(appdata->username);
547 appdata->username =
548 g_strdup(gtk_entry_get_text(GTK_ENTRY(context->username)));
549
550 if(appdata->gt.password) g_free(appdata->gt.password);
551 appdata->gt.password =
552 g_strdup(gtk_entry_get_text(GTK_ENTRY(context->password)));
553
554 if(appdata->gt.filename) g_free(appdata->gt.filename);
555 appdata->gt.filename =
556 g_strdup(gtk_label_get_text(GTK_LABEL(context->filename)));
557
558 /* get distance in kilometers */
559 appdata->gt.distance = dist_get(context->dst, FALSE);
560
561
562 /* check for valid entries */
563 if(isnan(appdata->gt.lat) || isnan(appdata->gt.lon) ||
564 isnan(appdata->gt.distance) || !appdata->gt.filename ||
565 !appdata->username || !appdata->gt.password)
566 errorf(_("GeoToad setup not complete"));
567 else
568 ok = TRUE;
569 }
570
571 gtk_widget_destroy(context->dialog);
572
573 return ok;
574 }
575
576 void geotoad(appdata_t *appdata) {
577 if(!geotoad_available()) {
578 errorf(_("GeoToad is not installed on this device.\n"
579 "You need to install it in order to be able to use it."));
580 return;
581 }
582
583 gt_context_t *context = g_new0(gt_context_t, 1);
584 context->appdata = appdata;
585
586 context->update_mutex = g_mutex_new();
587 context->update_cond = g_cond_new();
588 // context->mutex_to_run = mutex_to_run;
589
590 printf("geoToad\n");
591
592 if(gui_setup(context))
593 gui_run(context);
594 }
595
596 gboolean geotoad_available(void) {
597 /* before doing anything make sure geotoad is installed */
598 return g_file_test(GEOTOAD, G_FILE_TEST_IS_EXECUTABLE);
599 }
600
601