r69: Tidied up the options system a bit. Added drag-and-drop section (with
[rox-filer.git] / ROX-Filer / src / action.c
blob32898eefde427256f348c1c18e8f7c67c09b91fc
1 /* vi: set cindent:
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * By Thomas Leonard, <tal197@ecs.soton.ac.uk>.
6 */
8 /* action.c - code for handling the filer action windows.
9 * These routines generally fork() and talk to us via pipes.
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <errno.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <signal.h>
19 #include <dirent.h>
21 #include "action.h"
22 #include "string.h"
23 #include "support.h"
24 #include "gui_support.h"
25 #include "filer.h"
26 #include "main.h"
28 static GdkColor red = {0, 0xffff, 0, 0};
30 typedef struct _GUIside GUIside;
31 typedef void ActionChild(gpointer data);
32 typedef void ForDirCB(char *path);
34 struct _GUIside
36 int from_child; /* File descriptor */
37 FILE *to_child;
38 int input_tag; /* gdk_input_add() */
39 GtkWidget *log, *window, *actions;
40 int child; /* Process ID */
41 int errors;
44 /* These don't need to be in a structure because we fork() before
45 * using them again.
47 static int from_parent = 0;
48 static FILE *to_parent = NULL;
49 static gboolean quiet = FALSE;
50 static GString *message = NULL;
51 static char *action_dest = NULL;
52 static void (*action_do_func)(char *source, char *dest);
54 /* Static prototypes */
55 static gboolean send();
56 static gboolean send_error();
57 static gboolean read_exact(int source, char *buffer, ssize_t len);
59 /* Called when the child sends us a message */
60 static void message_from_child(gpointer data,
61 gint source,
62 GdkInputCondition condition)
64 char buf[5];
65 GUIside *gui_side = (GUIside *) data;
66 GtkWidget *log = gui_side->log;
68 if (read_exact(source, buf, 4))
70 ssize_t message_len;
71 char *buffer;
73 buf[4] = '\0';
74 message_len = strtol(buf, NULL, 16);
75 buffer = g_malloc(message_len + 1);
76 if (message_len > 0 && read_exact(source, buffer, message_len))
78 buffer[message_len] = '\0';
79 if (*buffer == '?')
80 gtk_widget_set_sensitive(gui_side->actions,
81 TRUE);
82 else if (*buffer == '+')
84 refresh_dirs(buffer + 1);
85 g_free(buffer);
86 return;
88 else if (*buffer == '!')
89 gui_side->errors++;
91 gtk_text_insert(GTK_TEXT(log),
92 NULL,
93 *buffer == '!' ? &red : NULL,
94 NULL,
95 buffer + 1, message_len - 1);
96 g_free(buffer);
97 return;
99 g_print("Child died in the middle of a message.\n");
102 /* The child is dead */
103 gui_side->child = 0;
105 fclose(gui_side->to_child);
106 close(gui_side->from_child);
107 gdk_input_remove(gui_side->input_tag);
109 if (gui_side->errors)
111 GString *report;
112 report = g_string_new(NULL);
113 g_string_sprintf(report, "There %s %d error%s.\n",
114 gui_side->errors == 1 ? "was" : "were",
115 gui_side->errors,
116 gui_side->errors == 1 ? "" : "s");
117 gtk_text_insert(GTK_TEXT(log), NULL, &red, NULL,
118 report->str, report->len);
120 g_string_free(report, TRUE);
122 else
123 gtk_widget_destroy(gui_side->window);
126 static void for_dir_contents(char *dir, ForDirCB *cb)
128 DIR *d;
129 struct dirent *ent;
130 GSList *list = NULL, *next;
132 d = opendir(dir);
133 if (!d)
135 send_error();
136 return;
139 while ((ent = readdir(d)))
141 if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0'
142 || (ent->d_name[1] == '.' && ent->d_name[2] == '\0')))
143 continue;
144 list = g_slist_append(list, g_strdup(make_path(dir,
145 ent->d_name)->str));
147 closedir(d);
149 if (!list)
150 return;
152 next = list;
154 while (next)
156 cb((char *) next->data);
157 g_string_sprintf(message, "+%s", dir);
158 send();
160 g_free(next->data);
161 next = next->next;
163 g_slist_free(list);
164 return;
167 /* Read this many bytes into the buffer. TRUE on success. */
168 static gboolean read_exact(int source, char *buffer, ssize_t len)
170 while (len > 0)
172 ssize_t got;
173 got = read(source, buffer, len);
174 if (got < 1)
175 return FALSE;
176 len -= got;
177 buffer += got;
179 return TRUE;
182 /* Send 'message' to our parent process. TRUE on success. */
183 static gboolean send()
185 char len_buffer[5];
186 ssize_t len;
188 g_return_val_if_fail(message->len < 0xffff, FALSE);
190 sprintf(len_buffer, "%04x", message->len);
191 fwrite(len_buffer, 1, 4, to_parent);
192 len = fwrite(message->str, 1, message->len, to_parent);
193 fflush(to_parent);
194 return len == message->len;
197 static gboolean send_error()
199 g_string_sprintf(message, "!ERROR: %s\n", g_strerror(errno));
200 return send();
203 static void button_reply(GtkWidget *button, GUIside *gui_side)
205 char *text;
207 text = gtk_object_get_data(GTK_OBJECT(button), "send-code");
208 g_return_if_fail(text != NULL);
209 fputc(*text, gui_side->to_child);
210 fflush(gui_side->to_child);
212 gtk_widget_set_sensitive(gui_side->actions, FALSE);
215 /* Get one char from fd. Quit on error. */
216 static char reply(int fd)
218 ssize_t len;
219 char retval;
221 len = read(fd, &retval, 1);
222 if (len == 1)
223 return retval;
225 fprintf(stderr, "read() error: %s\n", g_strerror(errno));
226 _exit(1); /* Parent died? */
227 return '!';
230 static void destroy_action_window(GtkWidget *widget, gpointer data)
232 GUIside *gui_side = (GUIside *) data;
234 if (gui_side->child)
236 kill(gui_side->child, SIGTERM);
237 fclose(gui_side->to_child);
238 close(gui_side->from_child);
239 gdk_input_remove(gui_side->input_tag);
242 g_free(data);
244 if (--number_of_windows < 1)
245 gtk_main_quit();
248 /* Create two pipes, fork() a child and return a pointer to a GUIside struct
249 * (NULL on failure). The child calls func().
251 static GUIside *start_action(gpointer data, ActionChild *func)
253 int filedes[4]; /* 0 and 2 are for reading */
254 GUIside *gui_side;
255 int child;
256 GtkWidget *vbox, *button, *control;
258 if (pipe(filedes))
260 report_error("ROX-Filer", g_strerror(errno));
261 return NULL;
264 if (pipe(filedes + 2))
266 close(filedes[0]);
267 close(filedes[1]);
268 report_error("ROX-Filer", g_strerror(errno));
269 return NULL;
272 child = fork();
273 switch (child)
275 case -1:
276 report_error("ROX-Filer", g_strerror(errno));
277 return NULL;
278 case 0:
279 /* We are the child */
280 message = g_string_new(NULL);
281 close(filedes[0]);
282 close(filedes[3]);
283 to_parent = fdopen(filedes[1], "wb");
284 from_parent = filedes[2];
285 func(data);
286 _exit(0);
289 /* We are the parent */
290 close(filedes[1]);
291 close(filedes[2]);
292 gui_side = g_malloc(sizeof(GUIside));
293 gui_side->from_child = filedes[0];
294 gui_side->to_child = fdopen(filedes[3], "wb");
295 gui_side->log = NULL;
296 gui_side->child = child;
297 gui_side->errors = 0;
299 gui_side->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
300 gtk_window_set_default_size(GTK_WINDOW(gui_side->window), 500, 100);
301 gtk_signal_connect(GTK_OBJECT(gui_side->window), "destroy",
302 GTK_SIGNAL_FUNC(destroy_action_window), gui_side);
304 vbox = gtk_vbox_new(FALSE, 4);
305 gtk_container_add(GTK_CONTAINER(gui_side->window), vbox);
307 gui_side->log = gtk_text_new(NULL, NULL);
308 gtk_box_pack_start(GTK_BOX(vbox), gui_side->log, TRUE, TRUE, 0);
310 gui_side->actions = gtk_hbox_new(TRUE, 4);
311 gtk_box_pack_start(GTK_BOX(vbox), gui_side->actions, FALSE, TRUE, 0);
313 button = gtk_button_new_with_label("Always");
314 gtk_object_set_data(GTK_OBJECT(button), "send-code", "A");
315 gtk_box_pack_start(GTK_BOX(gui_side->actions), button, TRUE, TRUE, 0);
316 gtk_signal_connect(GTK_OBJECT(button), "clicked",
317 button_reply, gui_side);
318 button = gtk_button_new_with_label("Yes");
319 gtk_object_set_data(GTK_OBJECT(button), "send-code", "Y");
320 gtk_box_pack_start(GTK_BOX(gui_side->actions), button, TRUE, TRUE, 0);
321 gtk_signal_connect(GTK_OBJECT(button), "clicked",
322 button_reply, gui_side);
323 button = gtk_button_new_with_label("No");
324 gtk_object_set_data(GTK_OBJECT(button), "send-code", "N");
325 gtk_box_pack_start(GTK_BOX(gui_side->actions), button, TRUE, TRUE, 0);
326 gtk_signal_connect(GTK_OBJECT(button), "clicked",
327 button_reply, gui_side);
328 gtk_widget_set_sensitive(gui_side->actions, FALSE);
330 control = gtk_hbox_new(TRUE, 4);
331 gtk_box_pack_start(GTK_BOX(vbox), control, FALSE, TRUE, 0);
333 button = gtk_button_new_with_label("Suspend");
334 gtk_box_pack_start(GTK_BOX(control), button, TRUE, TRUE, 0);
336 button = gtk_button_new_with_label("Resume");
337 gtk_widget_set_sensitive(button, FALSE);
338 gtk_box_pack_start(GTK_BOX(control), button, TRUE, TRUE, 0);
340 button = gtk_button_new_with_label("Abort");
341 gtk_box_pack_start(GTK_BOX(control), button, TRUE, TRUE, 0);
342 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
343 gtk_widget_destroy, GTK_OBJECT(gui_side->window));
345 gui_side->input_tag = gdk_input_add(gui_side->from_child,
346 GDK_INPUT_READ,
347 message_from_child,
348 gui_side);
350 return gui_side;
353 /* ACTIONS ON ONE ITEM */
355 /* These may call themselves recursively, or ask questions, etc */
357 static void do_delete(char *path)
359 struct stat info;
360 gboolean write_prot;
361 char rep;
363 if (lstat(path, &info))
365 send_error();
366 return;
369 write_prot = access(path, W_OK) != 0;
370 if (quiet == 0 || write_prot)
372 g_string_sprintf(message, "?Delete %s'%s'?\n",
373 write_prot ? "WRITE-PROTECTED " : " ",
374 path);
375 send();
376 rep = reply(from_parent);
377 if (rep == 'A')
378 quiet = TRUE;
379 else if (rep != 'Y')
380 return;
382 if (S_ISDIR(info.st_mode))
384 char *safe_path;
385 safe_path = g_strdup(path);
386 g_string_sprintf(message, "'Scanning in '%s'\n", safe_path);
387 send();
388 for_dir_contents(safe_path, do_delete);
389 if (rmdir(safe_path))
391 g_free(safe_path);
392 send_error();
393 return;
395 g_string_assign(message, "'Directory deleted\n");
396 send();
397 g_free(safe_path);
399 else if (unlink(path) == 0)
401 g_string_sprintf(message, "'Deleted '%s'\n", path);
402 send();
404 else
405 send_error();
408 static void do_copy(char *path, char *dest)
410 char *dest_path;
411 char *leaf;
413 leaf = strrchr(path, '/');
414 if (!leaf)
415 leaf = path; /* Error? */
416 else
417 leaf++;
419 dest_path = make_path(dest, leaf)->str;
421 if (access(dest_path, F_OK) == 0)
423 char rep;
424 g_string_sprintf(message, "?'%s' already exists - overwrite?\n",
425 dest_path);
426 send();
428 rep = reply(from_parent);
429 if (rep == 'A')
430 quiet = TRUE;
431 else if (rep != 'Y')
432 return;
435 g_string_sprintf(message, "cp -a %s %s", path, dest_path);
436 if (system(message->str) == 0)
437 g_string_sprintf(message, "'Copied %s as %s\n",
438 path, dest_path);
439 else
440 g_string_sprintf(message, "!ERROR: Failed to copy %s as %s\n",
441 path, dest_path);
442 send();
445 static void do_move(char *path, char *dest)
447 char *dest_path;
448 char *leaf;
450 leaf = strrchr(path, '/');
451 if (!leaf)
452 leaf = path; /* Error? */
453 else
454 leaf++;
456 dest_path = make_path(dest, leaf)->str;
458 if (access(dest_path, F_OK) == 0)
460 char rep;
461 g_string_sprintf(message, "?'%s' already exists - overwrite?\n",
462 dest_path);
463 send();
465 rep = reply(from_parent);
466 if (rep == 'A')
467 quiet = TRUE;
468 else if (rep != 'Y')
469 return;
472 g_string_sprintf(message, "mv -f %s %s", path, dest_path);
473 if (system(message->str) == 0)
475 g_string_sprintf(message, "+%s", path);
476 g_string_truncate(message, leaf - path);
477 send();
478 g_string_sprintf(message, "'Moved %s as %s\n",
479 path, dest_path);
481 else
482 g_string_sprintf(message, "!ERROR: Failed to move %s as %s\n",
483 path, dest_path);
484 send();
487 static void do_link(char *path, char *dest)
489 char *dest_path;
490 char *leaf;
492 leaf = strrchr(path, '/');
493 if (!leaf)
494 leaf = path; /* Error? */
495 else
496 leaf++;
498 dest_path = make_path(dest, leaf)->str;
500 if (symlink(path, dest_path))
501 send_error();
502 else
504 g_string_sprintf(message, "'Symlinked %s as %s\n",
505 path, dest_path);
506 send();
510 /* CHILD MAIN LOOPS */
512 /* After forking, the child calls one of these functions */
514 static void delete_cb(gpointer data)
516 FilerWindow *filer_window = (FilerWindow *) data;
517 Collection *collection = filer_window->collection;
518 FileItem *item;
519 int left = collection->number_selected;
520 int i = -1;
522 while (left > 0)
524 i++;
525 if (!collection->items[i].selected)
526 continue;
527 item = (FileItem *) collection->items[i].data;
528 do_delete(make_path(filer_window->path, item->leafname)->str);
529 g_string_sprintf(message, "+%s", filer_window->path);
530 send();
531 left--;
534 g_string_sprintf(message, "'\nDone\n");
535 send();
536 sleep(1);
539 static void list_cb(gpointer data)
541 GSList *paths = (GSList *) data;
543 while (paths)
545 action_do_func((char *) paths->data, action_dest);
546 g_string_sprintf(message, "+%s", action_dest);
547 send();
549 paths = paths->next;
552 g_string_sprintf(message, "'\nDone\n");
553 send();
554 sleep(1);
557 /* EXTERNAL INTERFACE */
559 /* Deletes all selected items in the window */
560 void action_delete(FilerWindow *filer_window)
562 GUIside *gui_side;
563 Collection *collection;
565 collection = window_with_focus->collection;
567 if (collection->number_selected < 1)
569 report_error("ROX-Filer", "You need to select some items "
570 "first");
571 return;
574 gui_side = start_action(filer_window, delete_cb);
575 if (!gui_side)
576 return;
578 number_of_windows++;
579 gtk_widget_show_all(gui_side->window);
582 void action_copy(GSList *paths, char *dest)
584 GUIside *gui_side;
586 action_dest = dest;
587 action_do_func = do_copy;
588 gui_side = start_action(paths, list_cb);
589 if (!gui_side)
590 return;
592 number_of_windows++;
593 gtk_widget_show_all(gui_side->window);
596 void action_move(GSList *paths, char *dest)
598 GUIside *gui_side;
600 action_dest = dest;
601 action_do_func = do_move;
602 gui_side = start_action(paths, list_cb);
603 if (!gui_side)
604 return;
606 number_of_windows++;
607 gtk_widget_show_all(gui_side->window);
610 void action_link(GSList *paths, char *dest)
612 GUIside *gui_side;
614 action_dest = dest;
615 action_do_func = do_link;
616 gui_side = start_action(paths, list_cb);
617 if (!gui_side)
618 return;
620 number_of_windows++;
621 gtk_widget_show_all(gui_side->window);