r54: Drops are now only allowed if the destination is writeable. Copy, move and
[rox-filer/dt.git] / ROX-Filer / src / action.c
blob26b8e3e16285257bfe5c4e8afdefba77018c9e22
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 "support.h"
23 #include "gui_support.h"
24 #include "filer.h"
25 #include "main.h"
27 typedef struct _GUIside GUIside;
28 typedef void ActionChild(FilerWindow *filer_window);
29 typedef void ForDirCB(char *path);
31 struct _GUIside
33 int from_child; /* File descriptor */
34 FILE *to_child;
35 int input_tag; /* gdk_input_add() */
36 GtkWidget *log, *window, *actions;
37 int child; /* Process ID */
40 /* These don't need to be in a structure because we fork() before
41 * using them.
43 static int from_parent = 0;
44 static FILE *to_parent = NULL;
45 static gboolean quiet = FALSE;
46 static GString *message = NULL;
48 /* Static prototypes */
49 static gboolean send();
50 static gboolean send_error();
52 static void for_dir_contents(char *dir, ForDirCB *cb)
54 DIR *d;
55 struct dirent *ent;
56 GSList *list = NULL, *next;
58 d = opendir(dir);
59 if (!d)
61 send_error();
62 return;
65 while ((ent = readdir(d)))
67 if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0'
68 || (ent->d_name[1] == '.' && ent->d_name[2] == '\0')))
69 continue;
70 list = g_slist_append(list, g_strdup(make_path(dir,
71 ent->d_name)->str));
73 closedir(d);
75 if (!list)
76 return;
78 next = list;
80 while (next)
82 cb((char *) next->data);
83 g_string_sprintf(message, "+%s", dir);
84 send();
86 g_free(next->data);
87 next = next->next;
89 g_slist_free(list);
90 return;
93 /* Read this many bytes into the buffer. TRUE on success. */
94 static gboolean read_exact(int source, char *buffer, ssize_t len)
96 while (len > 0)
98 ssize_t got;
99 got = read(source, buffer, len);
100 if (got < 1)
101 return FALSE;
102 len -= got;
103 buffer += got;
105 return TRUE;
108 /* Send 'message' to our parent process. TRUE on success. */
109 static gboolean send()
111 char len_buffer[5];
112 ssize_t len;
114 g_return_val_if_fail(message->len < 0xffff, FALSE);
116 sprintf(len_buffer, "%04x", message->len);
117 fwrite(len_buffer, 1, 4, to_parent);
118 len = fwrite(message->str, 1, message->len, to_parent);
119 fflush(to_parent);
120 return len == message->len;
123 static gboolean send_error()
125 g_string_sprintf(message, "!ERROR: %s\n", g_strerror(errno));
126 return send();
129 static void button_reply(GtkWidget *button, GUIside *gui_side)
131 char *text;
133 text = gtk_object_get_data(GTK_OBJECT(button), "send-code");
134 g_return_if_fail(text != NULL);
135 fputc(*text, gui_side->to_child);
136 fflush(gui_side->to_child);
138 gtk_widget_set_sensitive(gui_side->actions, FALSE);
141 /* Get one char from fd. Quit on error. */
142 static char reply(int fd)
144 ssize_t len;
145 char retval;
147 len = read(fd, &retval, 1);
148 if (len == 1)
149 return retval;
151 fprintf(stderr, "read() error: %s\n", g_strerror(errno));
152 _exit(1); /* Parent died? */
153 return '!';
156 static void destroy_action_window(GtkWidget *widget, gpointer data)
158 GUIside *gui_side = (GUIside *) data;
160 fclose(gui_side->to_child);
161 close(gui_side->from_child);
162 gdk_input_remove(gui_side->input_tag);
164 if (gui_side->child)
165 kill(gui_side->child, SIGTERM);
167 g_free(data);
169 if (--number_of_windows < 1)
170 gtk_main_quit();
173 /* Create two pipes, fork() a child and return a pointer to a GUIside struct
174 * (NULL on failure). The child calls func().
176 static GUIside *start_action(FilerWindow *filer_window, ActionChild *func)
178 int filedes[4]; /* 0 and 2 are for reading */
179 GUIside *gui_side;
180 int child;
181 GtkWidget *vbox, *button, *control;
183 if (pipe(filedes))
185 report_error("ROX-Filer", g_strerror(errno));
186 return NULL;
189 if (pipe(filedes + 2))
191 close(filedes[0]);
192 close(filedes[1]);
193 report_error("ROX-Filer", g_strerror(errno));
194 return NULL;
197 child = fork();
198 switch (child)
200 case -1:
201 report_error("ROX-Filer", g_strerror(errno));
202 return NULL;
203 case 0:
204 /* We are the child */
205 message = g_string_new(NULL);
206 close(filedes[0]);
207 close(filedes[3]);
208 to_parent = fdopen(filedes[1], "wb");
209 from_parent = filedes[2];
210 func(filer_window);
211 _exit(0);
214 /* We are the parent */
215 close(filedes[1]);
216 close(filedes[2]);
217 gui_side = g_malloc(sizeof(GUIside));
218 gui_side->from_child = filedes[0];
219 gui_side->to_child = fdopen(filedes[3], "wb");
220 gui_side->log = NULL;
221 gui_side->child = child;
223 gui_side->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
224 gtk_window_set_default_size(GTK_WINDOW(gui_side->window), 400, 100);
225 gtk_signal_connect(GTK_OBJECT(gui_side->window), "destroy",
226 GTK_SIGNAL_FUNC(destroy_action_window), gui_side);
228 vbox = gtk_vbox_new(FALSE, 4);
229 gtk_container_add(GTK_CONTAINER(gui_side->window), vbox);
231 gui_side->log = gtk_text_new(NULL, NULL);
232 gtk_box_pack_start(GTK_BOX(vbox), gui_side->log, TRUE, TRUE, 0);
234 gui_side->actions = gtk_hbox_new(TRUE, 4);
235 gtk_box_pack_start(GTK_BOX(vbox), gui_side->actions, FALSE, TRUE, 0);
237 control = gtk_hbox_new(TRUE, 4);
238 gtk_box_pack_start(GTK_BOX(vbox), control, FALSE, TRUE, 0);
240 button = gtk_button_new_with_label("Suspend");
241 gtk_box_pack_start(GTK_BOX(control), button, TRUE, TRUE, 0);
243 button = gtk_button_new_with_label("Resume");
244 gtk_widget_set_sensitive(button, FALSE);
245 gtk_box_pack_start(GTK_BOX(control), button, TRUE, TRUE, 0);
247 button = gtk_button_new_with_label("Abort");
248 gtk_box_pack_start(GTK_BOX(control), button, TRUE, TRUE, 0);
249 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
250 gtk_widget_destroy, GTK_OBJECT(gui_side->window));
252 return gui_side;
255 /* And now, the individual actions... */
257 /* Delete one item - may need to recurse, or ask questions */
258 static void do_delete(char *path)
260 struct stat info;
261 gboolean write_prot;
262 char rep;
264 if (lstat(path, &info))
266 send_error();
267 return;
270 write_prot = access(path, W_OK) != 0;
271 if (quiet == 0 || write_prot)
273 g_string_sprintf(message, "?Delete %s'%s'?\n",
274 write_prot ? "WRITE-PROTECTED " : " ",
275 path);
276 send();
277 rep = reply(from_parent);
278 if (rep == 'A')
279 quiet = TRUE;
280 else if (rep != 'Y')
281 return;
283 if (S_ISDIR(info.st_mode))
285 char *safe_path;
286 safe_path = g_strdup(path);
287 g_string_sprintf(message, "'Scanning in '%s'\n", safe_path);
288 send();
289 for_dir_contents(safe_path, do_delete);
290 if (rmdir(safe_path))
292 g_free(safe_path);
293 send_error();
294 return;
296 g_string_assign(message, "'Directory deleted\n");
297 send();
298 g_free(safe_path);
300 else if (unlink(path) == 0)
302 g_string_sprintf(message, "'Deleted '%s'\n", path);
303 send();
305 else
306 send_error();
309 /* The child executes this... */
310 static void delete_cb(FilerWindow *filer_window)
312 Collection *collection = filer_window->collection;
313 FileItem *item;
314 int left = collection->number_selected;
315 int i = -1;
317 while (left > 0)
319 i++;
320 if (!collection->items[i].selected)
321 continue;
322 item = (FileItem *) collection->items[i].data;
323 do_delete(make_path(filer_window->path, item->leafname)->str);
324 g_string_sprintf(message, "+%s", filer_window->path);
325 send();
326 left--;
329 g_string_sprintf(message, "'\nDone\n");
330 send();
331 g_string_sprintf(message, "+%s", filer_window->path);
332 send();
333 sleep(5);
336 /* Called when the child sends us a message */
337 static void got_delete_data(gpointer data,
338 gint source,
339 GdkInputCondition condition)
341 char buf[5];
342 GUIside *gui_side = (GUIside *) data;
343 GtkWidget *log = gui_side->log;
345 if (read_exact(source, buf, 4))
347 ssize_t message_len;
348 char *buffer;
350 buf[4] = '\0';
351 message_len = strtol(buf, NULL, 16);
352 buffer = g_malloc(message_len + 1);
353 if (message_len > 0 && read_exact(source, buffer, message_len))
355 buffer[message_len] = '\0';
356 if (*buffer == '?')
357 gtk_widget_set_sensitive(gui_side->actions,
358 TRUE);
359 else if (*buffer == '+')
361 refresh_dirs(buffer + 1);
362 g_free(buffer);
363 return;
365 gtk_text_insert(GTK_TEXT(log),
366 NULL,
367 NULL, NULL,
368 buffer + 1, message_len - 1);
369 g_free(buffer);
370 return;
372 g_print("Child died in the middle of a message.\n");
375 /* The child is dead */
376 gui_side->child = 0;
377 gtk_widget_destroy(gui_side->window);
380 /* EXTERNAL INTERFACE */
382 /* Deletes all selected items in the window */
383 void action_delete(FilerWindow *filer_window)
385 GUIside *gui_side;
386 Collection *collection;
387 GtkWidget *button;
389 collection = window_with_focus->collection;
391 if (collection->number_selected < 1)
393 report_error("ROX-Filer", "You need to select some items "
394 "first");
395 return;
398 gui_side = start_action(filer_window, delete_cb);
399 if (!gui_side)
400 return;
402 gui_side->input_tag = gdk_input_add(gui_side->from_child,
403 GDK_INPUT_READ,
404 got_delete_data, gui_side);
406 number_of_windows++;
408 button = gtk_button_new_with_label("Always");
409 gtk_object_set_data(GTK_OBJECT(button), "send-code", "A");
410 gtk_box_pack_start(GTK_BOX(gui_side->actions), button, TRUE, TRUE, 0);
411 gtk_signal_connect(GTK_OBJECT(button), "clicked",
412 button_reply, gui_side);
413 button = gtk_button_new_with_label("Yes");
414 gtk_object_set_data(GTK_OBJECT(button), "send-code", "Y");
415 gtk_box_pack_start(GTK_BOX(gui_side->actions), button, TRUE, TRUE, 0);
416 gtk_signal_connect(GTK_OBJECT(button), "clicked",
417 button_reply, gui_side);
418 button = gtk_button_new_with_label("No");
419 gtk_object_set_data(GTK_OBJECT(button), "send-code", "N");
420 gtk_box_pack_start(GTK_BOX(gui_side->actions), button, TRUE, TRUE, 0);
421 gtk_signal_connect(GTK_OBJECT(button), "clicked",
422 button_reply, gui_side);
423 gtk_widget_set_sensitive(gui_side->actions, FALSE);
425 gtk_widget_show_all(gui_side->window);
428 void action_copy(GSList *paths, char *dest)
430 delayed_error("Copy", "Not implemented yet - sorry!");
433 void action_move(GSList *paths, char *dest)
435 delayed_error("Move", "Not implemented yet - sorry!");
438 void action_link(GSList *paths, char *dest)
440 delayed_error("Link", "Not implemented yet - sorry!");