Contents of /trunk/src/geotoad.c

Parent Directory Parent Directory | Revision Log Revision Log


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