r135: Added new Directory object and put all the scanning and updating code
[rox-filer/dt.git] / ROX-Filer / src / action.c
blob157ad927b9a6e720ace8e8bd294ac11609b1edcf
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 1999, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* action.c - code for handling the filer action windows.
23 * These routines generally fork() and talk to us via pipes.
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <errno.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <signal.h>
33 #include <dirent.h>
35 #include "action.h"
36 #include "string.h"
37 #include "support.h"
38 #include "gui_support.h"
39 #include "filer.h"
40 #include "main.h"
42 static GdkColor red = {0, 0xffff, 0, 0};
44 typedef struct _GUIside GUIside;
45 typedef void ActionChild(gpointer data);
46 typedef gboolean ForDirCB(char *path, char *dest_path);
48 struct _GUIside
50 int from_child; /* File descriptor */
51 FILE *to_child;
52 int input_tag; /* gdk_input_add() */
53 GtkWidget *log, *window, *actions;
54 int child; /* Process ID */
55 int errors;
56 gboolean show_info; /* For Disk Usage */
59 /* These don't need to be in a structure because we fork() before
60 * using them again.
62 static int from_parent = 0;
63 static FILE *to_parent = NULL;
64 static gboolean quiet = FALSE;
65 static GString *message = NULL;
66 static char *action_dest = NULL;
67 static gboolean (*action_do_func)(char *source, char *dest);
68 static size_t size_tally; /* For Disk Usage */
70 /* Static prototypes */
71 static gboolean send();
72 static gboolean send_error();
73 static gboolean read_exact(int source, char *buffer, ssize_t len);
75 /* Called when the child sends us a message */
76 static void message_from_child(gpointer data,
77 gint source,
78 GdkInputCondition condition)
80 char buf[5];
81 GUIside *gui_side = (GUIside *) data;
82 GtkWidget *log = gui_side->log;
84 if (read_exact(source, buf, 4))
86 ssize_t message_len;
87 char *buffer;
89 buf[4] = '\0';
90 message_len = strtol(buf, NULL, 16);
91 buffer = g_malloc(message_len + 1);
92 if (message_len > 0 && read_exact(source, buffer, message_len))
94 buffer[message_len] = '\0';
95 if (*buffer == '?')
96 gtk_widget_set_sensitive(gui_side->actions,
97 TRUE);
98 else if (*buffer == '+')
100 refresh_dirs(buffer + 1);
101 g_free(buffer);
102 return;
104 else if (*buffer == '!')
105 gui_side->errors++;
107 gtk_text_insert(GTK_TEXT(log),
108 NULL,
109 *buffer == '!' ? &red : NULL,
110 NULL,
111 buffer + 1, message_len - 1);
112 g_free(buffer);
113 return;
115 g_print("Child died in the middle of a message.\n");
118 /* The child is dead */
119 gui_side->child = 0;
121 fclose(gui_side->to_child);
122 close(gui_side->from_child);
123 gdk_input_remove(gui_side->input_tag);
125 if (gui_side->errors)
127 GString *report;
128 report = g_string_new(NULL);
129 g_string_sprintf(report, "There %s %d error%s.\n",
130 gui_side->errors == 1 ? "was" : "were",
131 gui_side->errors,
132 gui_side->errors == 1 ? "" : "s");
133 gtk_text_insert(GTK_TEXT(log), NULL, &red, NULL,
134 report->str, report->len);
136 g_string_free(report, TRUE);
138 else if (gui_side->show_info == FALSE)
139 gtk_widget_destroy(gui_side->window);
142 /* Scans src_dir, updating dest_path whenever cb returns TRUE */
143 static void for_dir_contents(ForDirCB *cb, char *src_dir, char *dest_path)
145 DIR *d;
146 struct dirent *ent;
147 GSList *list = NULL, *next;
149 d = opendir(src_dir);
150 if (!d)
152 send_error();
153 return;
156 while ((ent = readdir(d)))
158 if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0'
159 || (ent->d_name[1] == '.' && ent->d_name[2] == '\0')))
160 continue;
161 list = g_slist_append(list, g_strdup(make_path(src_dir,
162 ent->d_name)->str));
164 closedir(d);
166 if (!list)
167 return;
169 next = list;
171 while (next)
173 if (cb((char *) next->data, dest_path))
175 g_string_sprintf(message, "+%s", dest_path);
176 send();
179 g_free(next->data);
180 next = next->next;
182 g_slist_free(list);
183 return;
186 /* Read this many bytes into the buffer. TRUE on success. */
187 static gboolean read_exact(int source, char *buffer, ssize_t len)
189 while (len > 0)
191 ssize_t got;
192 got = read(source, buffer, len);
193 if (got < 1)
194 return FALSE;
195 len -= got;
196 buffer += got;
198 return TRUE;
201 /* Send 'message' to our parent process. TRUE on success. */
202 static gboolean send()
204 char len_buffer[5];
205 ssize_t len;
207 g_return_val_if_fail(message->len < 0xffff, FALSE);
209 sprintf(len_buffer, "%04x", message->len);
210 fwrite(len_buffer, 1, 4, to_parent);
211 len = fwrite(message->str, 1, message->len, to_parent);
212 fflush(to_parent);
213 return len == message->len;
216 static gboolean send_error()
218 g_string_sprintf(message, "!ERROR: %s\n", g_strerror(errno));
219 return send();
222 static void button_reply(GtkWidget *button, GUIside *gui_side)
224 char *text;
226 text = gtk_object_get_data(GTK_OBJECT(button), "send-code");
227 g_return_if_fail(text != NULL);
228 fputc(*text, gui_side->to_child);
229 fflush(gui_side->to_child);
231 gtk_widget_set_sensitive(gui_side->actions, FALSE);
234 /* Get one char from fd. Quit on error. */
235 static char reply(int fd)
237 ssize_t len;
238 char retval;
240 len = read(fd, &retval, 1);
241 if (len == 1)
242 return retval;
244 fprintf(stderr, "read() error: %s\n", g_strerror(errno));
245 _exit(1); /* Parent died? */
246 return '!';
249 static void destroy_action_window(GtkWidget *widget, gpointer data)
251 GUIside *gui_side = (GUIside *) data;
253 if (gui_side->child)
255 kill(gui_side->child, SIGTERM);
256 fclose(gui_side->to_child);
257 close(gui_side->from_child);
258 gdk_input_remove(gui_side->input_tag);
261 g_free(data);
263 if (--number_of_windows < 1)
264 gtk_main_quit();
267 /* Create two pipes, fork() a child and return a pointer to a GUIside struct
268 * (NULL on failure). The child calls func().
270 static GUIside *start_action(gpointer data, ActionChild *func)
272 int filedes[4]; /* 0 and 2 are for reading */
273 GUIside *gui_side;
274 int child;
275 GtkWidget *vbox, *button, *control, *hbox, *scrollbar;
277 if (pipe(filedes))
279 report_error("ROX-Filer", g_strerror(errno));
280 return NULL;
283 if (pipe(filedes + 2))
285 close(filedes[0]);
286 close(filedes[1]);
287 report_error("ROX-Filer", g_strerror(errno));
288 return NULL;
291 child = fork();
292 switch (child)
294 case -1:
295 report_error("ROX-Filer", g_strerror(errno));
296 return NULL;
297 case 0:
298 /* We are the child */
299 message = g_string_new(NULL);
300 close(filedes[0]);
301 close(filedes[3]);
302 to_parent = fdopen(filedes[1], "wb");
303 from_parent = filedes[2];
304 func(data);
305 _exit(0);
308 /* We are the parent */
309 close(filedes[1]);
310 close(filedes[2]);
311 gui_side = g_malloc(sizeof(GUIside));
312 gui_side->from_child = filedes[0];
313 gui_side->to_child = fdopen(filedes[3], "wb");
314 gui_side->log = NULL;
315 gui_side->child = child;
316 gui_side->errors = 0;
317 gui_side->show_info = FALSE;
319 gui_side->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
320 gtk_window_set_default_size(GTK_WINDOW(gui_side->window), 500, 100);
321 gtk_signal_connect(GTK_OBJECT(gui_side->window), "destroy",
322 GTK_SIGNAL_FUNC(destroy_action_window), gui_side);
324 vbox = gtk_vbox_new(FALSE, 4);
325 gtk_container_add(GTK_CONTAINER(gui_side->window), vbox);
327 hbox = gtk_hbox_new(FALSE, 0);
328 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
330 gui_side->log = gtk_text_new(NULL, NULL);
331 gtk_box_pack_start(GTK_BOX(hbox), gui_side->log, TRUE, TRUE, 0);
332 scrollbar = gtk_vscrollbar_new(GTK_TEXT(gui_side->log)->vadj);
333 gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, TRUE, 0);
335 gui_side->actions = gtk_hbox_new(TRUE, 4);
336 gtk_box_pack_start(GTK_BOX(vbox), gui_side->actions, FALSE, TRUE, 0);
338 button = gtk_button_new_with_label("Always");
339 gtk_object_set_data(GTK_OBJECT(button), "send-code", "A");
340 gtk_box_pack_start(GTK_BOX(gui_side->actions), button, TRUE, TRUE, 0);
341 gtk_signal_connect(GTK_OBJECT(button), "clicked",
342 button_reply, gui_side);
343 button = gtk_button_new_with_label("Yes");
344 gtk_object_set_data(GTK_OBJECT(button), "send-code", "Y");
345 gtk_box_pack_start(GTK_BOX(gui_side->actions), button, TRUE, TRUE, 0);
346 gtk_signal_connect(GTK_OBJECT(button), "clicked",
347 button_reply, gui_side);
348 button = gtk_button_new_with_label("No");
349 gtk_object_set_data(GTK_OBJECT(button), "send-code", "N");
350 gtk_box_pack_start(GTK_BOX(gui_side->actions), button, TRUE, TRUE, 0);
351 gtk_signal_connect(GTK_OBJECT(button), "clicked",
352 button_reply, gui_side);
353 gtk_widget_set_sensitive(gui_side->actions, FALSE);
355 control = gtk_hbox_new(TRUE, 4);
356 gtk_box_pack_start(GTK_BOX(vbox), control, FALSE, TRUE, 0);
358 button = gtk_button_new_with_label("Suspend");
359 gtk_box_pack_start(GTK_BOX(control), button, TRUE, TRUE, 0);
361 button = gtk_button_new_with_label("Resume");
362 gtk_widget_set_sensitive(button, FALSE);
363 gtk_box_pack_start(GTK_BOX(control), button, TRUE, TRUE, 0);
365 button = gtk_button_new_with_label("Abort");
366 gtk_box_pack_start(GTK_BOX(control), button, TRUE, TRUE, 0);
367 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
368 gtk_widget_destroy, GTK_OBJECT(gui_side->window));
370 gui_side->input_tag = gdk_input_add(gui_side->from_child,
371 GDK_INPUT_READ,
372 message_from_child,
373 gui_side);
375 return gui_side;
378 /* ACTIONS ON ONE ITEM */
380 /* These may call themselves recursively, or ask questions, etc.
381 * TRUE iff the directory containing dest_path needs to be rescanned.
384 /* dest_path is the dir containing src_path.
385 * Updates the global size_tally.
387 static gboolean do_usage(char *src_path, char *dest_path)
389 struct stat info;
391 if (lstat(src_path, &info))
393 g_string_sprintf(message, "'%s:\n", src_path);
394 send();
395 send_error();
396 return FALSE;
399 if (S_ISREG(info.st_mode))
400 size_tally += info.st_size;
401 else if (S_ISDIR(info.st_mode))
403 char *safe_path;
404 safe_path = g_strdup(src_path);
405 g_string_sprintf(message, "'Scanning in '%s'\n", safe_path);
406 send();
407 for_dir_contents(do_usage, safe_path, safe_path);
408 g_free(safe_path);
411 return FALSE;
414 /* dest_path is the dir containing src_path */
415 static gboolean do_delete(char *src_path, char *dest_path)
417 struct stat info;
418 gboolean write_prot;
419 char rep;
421 if (lstat(src_path, &info))
423 send_error();
424 return FALSE;
427 write_prot = S_ISLNK(info.st_mode) ? FALSE
428 : access(src_path, W_OK) != 0;
429 if (quiet == 0 || write_prot)
431 g_string_sprintf(message, "?Delete %s'%s'?\n",
432 write_prot ? "WRITE-PROTECTED " : " ",
433 src_path);
434 send();
435 rep = reply(from_parent);
436 if (rep == 'A')
437 quiet = TRUE;
438 else if (rep != 'Y')
439 return FALSE;
441 if (S_ISDIR(info.st_mode))
443 char *safe_path;
444 safe_path = g_strdup(src_path);
445 g_string_sprintf(message, "'Scanning in '%s'\n", safe_path);
446 send();
447 for_dir_contents(do_delete, safe_path, safe_path);
448 if (rmdir(safe_path))
450 g_free(safe_path);
451 send_error();
452 return FALSE;
454 g_string_assign(message, "'Directory deleted\n");
455 send();
456 g_free(safe_path);
458 else if (unlink(src_path) == 0)
460 g_string_sprintf(message, "'Deleted '%s'\n", src_path);
461 send();
463 else
465 send_error();
466 return FALSE;
469 return TRUE;
472 static gboolean do_copy(char *path, char *dest)
474 char *dest_path;
475 char *leaf;
476 struct stat info;
477 gboolean retval = TRUE;
479 leaf = strrchr(path, '/');
480 if (!leaf)
481 leaf = path; /* Error? */
482 else
483 leaf++;
485 dest_path = make_path(dest, leaf)->str;
487 g_string_sprintf(message, "'Copying %s as %s\n", path, dest_path);
488 send();
490 if (access(dest_path, F_OK) == 0)
492 char rep;
493 g_string_sprintf(message, "?'%s' already exists - overwrite?\n",
494 dest_path);
495 send();
497 rep = reply(from_parent);
498 if (rep == 'A')
499 quiet = TRUE;
500 else if (rep != 'Y')
501 return FALSE;
504 if (lstat(path, &info))
506 send_error();
507 return FALSE;
510 if (S_ISDIR(info.st_mode))
512 char *safe_path, *safe_dest;
513 struct stat dest_info;
514 gboolean exists;
516 /* (we will do the update ourselves now, rather than
517 * afterwards)
519 retval = FALSE;
521 safe_path = g_strdup(path);
522 safe_dest = g_strdup(dest_path);
524 exists = !lstat(dest_path, &dest_info);
526 if (exists && !S_ISDIR(dest_info.st_mode))
528 g_string_sprintf(message,
529 "!ERROR: Destination already exists, "
530 "but is not a directory\n");
532 else if (exists == FALSE && mkdir(dest_path, info.st_mode))
533 send_error();
534 else
536 if (!exists)
538 /* (just been created then) */
539 g_string_sprintf(message, "+%s", dest);
540 send();
543 g_string_sprintf(message, "'Scanning in '%s'\n",
544 safe_path);
545 send();
546 for_dir_contents(do_copy, safe_path, safe_dest);
547 /* Note: dest_path now invalid... */
550 g_free(safe_path);
551 g_free(safe_dest);
553 else
555 g_string_sprintf(message, "cp -af %s %s", path, dest_path);
556 if (system(message->str))
558 g_string_sprintf(message, "!ERROR: %s\n",
559 "Copy failed\n");
560 send();
561 retval = FALSE;
565 return retval;
568 static gboolean do_move(char *path, char *dest)
570 char *dest_path;
571 char *leaf;
572 gboolean retval = TRUE;
574 leaf = strrchr(path, '/');
575 if (!leaf)
576 leaf = path; /* Error? */
577 else
578 leaf++;
580 dest_path = make_path(dest, leaf)->str;
582 g_string_sprintf(message, "'Moving %s into %s...\n", path, dest);
583 send();
585 if (access(dest_path, F_OK) == 0)
587 char rep;
588 struct stat info;
590 g_string_sprintf(message, "?'%s' already exists - overwrite?\n",
591 dest_path);
592 send();
594 rep = reply(from_parent);
595 if (rep == 'A')
596 quiet = TRUE;
597 else if (rep != 'Y')
598 return FALSE;
600 if (lstat(dest_path, &info))
602 send_error();
603 return FALSE;
606 if (S_ISDIR(info.st_mode))
608 char *safe_path, *safe_dest;
610 safe_path = g_strdup(path);
611 safe_dest = g_strdup(dest_path);
612 g_string_sprintf(message, "'Scanning in '%s'\n",
613 safe_path);
614 send();
615 for_dir_contents(do_move, safe_path, safe_dest);
616 g_free(safe_dest);
617 if (rmdir(safe_path))
619 g_free(safe_path);
620 send_error();
621 return FALSE;
623 g_string_assign(message, "'Directory deleted\n");
624 send();
625 g_free(safe_path);
626 g_string_sprintf(message, "+%s", path);
627 g_string_truncate(message, leaf - path);
628 send();
629 return TRUE;
633 g_string_sprintf(message, "mv -f %s %s", path, dest);
634 if (system(message->str) == 0)
636 g_string_sprintf(message, "+%s", path);
637 g_string_truncate(message, leaf - path);
638 send();
640 else
642 g_string_sprintf(message, "!ERROR: Failed to move %s as %s\n",
643 path, dest_path);
644 retval = FALSE;
646 send();
648 return retval;
651 static gboolean do_link(char *path, char *dest)
653 char *dest_path;
654 char *leaf;
656 leaf = strrchr(path, '/');
657 if (!leaf)
658 leaf = path; /* Error? */
659 else
660 leaf++;
662 dest_path = make_path(dest, leaf)->str;
664 if (symlink(path, dest_path))
666 send_error();
667 return FALSE;
669 else
671 g_string_sprintf(message, "'Symlinked %s as %s\n",
672 path, dest_path);
673 send();
676 return TRUE;
679 /* CHILD MAIN LOOPS */
681 /* After forking, the child calls one of these functions */
683 static void usage_cb(gpointer data)
685 FilerWindow *filer_window = (FilerWindow *) data;
686 Collection *collection = filer_window->collection;
687 DirItem *item;
688 int left = collection->number_selected;
689 int i = -1;
690 off_t total_size = 0;
692 while (left > 0)
694 i++;
695 if (!collection->items[i].selected)
696 continue;
697 item = (DirItem *) collection->items[i].data;
698 size_tally = 0;
699 do_usage(make_path(filer_window->path,
700 item->leafname)->str,
701 filer_window->path);
702 g_string_sprintf(message, "'%s: %s\n",
703 item->leafname,
704 format_size((unsigned long) size_tally));
705 send();
706 total_size += size_tally;
707 left--;
710 g_string_sprintf(message, "'\nTotal: %s\n",
711 format_size((unsigned long) total_size));
712 send();
715 static void delete_cb(gpointer data)
717 FilerWindow *filer_window = (FilerWindow *) data;
718 Collection *collection = filer_window->collection;
719 DirItem *item;
720 int left = collection->number_selected;
721 int i = -1;
723 while (left > 0)
725 i++;
726 if (!collection->items[i].selected)
727 continue;
728 item = (DirItem *) collection->items[i].data;
729 if (do_delete(make_path(filer_window->path,
730 item->leafname)->str,
731 filer_window->path))
733 g_string_sprintf(message, "+%s", filer_window->path);
734 send();
736 left--;
739 g_string_sprintf(message, "'\nDone\n");
740 send();
743 static void list_cb(gpointer data)
745 GSList *paths = (GSList *) data;
747 while (paths)
749 if (action_do_func((char *) paths->data, action_dest))
751 g_string_sprintf(message, "+%s", action_dest);
752 send();
755 paths = paths->next;
758 g_string_sprintf(message, "'\nDone\n");
759 send();
762 /* EXTERNAL INTERFACE */
764 /* Count disk space used by selected items */
765 void action_usage(FilerWindow *filer_window)
767 GUIside *gui_side;
768 Collection *collection;
770 collection = window_with_focus->collection;
772 if (collection->number_selected < 1)
774 report_error("ROX-Filer", "You need to select some items "
775 "to count");
776 return;
779 gui_side = start_action(filer_window, usage_cb);
780 if (!gui_side)
781 return;
783 gui_side->show_info = TRUE;
785 gtk_window_set_title(GTK_WINDOW(gui_side->window), "Disk Usage");
786 number_of_windows++;
787 gtk_widget_show_all(gui_side->window);
790 /* Deletes all selected items in the window */
791 void action_delete(FilerWindow *filer_window)
793 GUIside *gui_side;
794 Collection *collection;
796 collection = window_with_focus->collection;
798 if (collection->number_selected < 1)
800 report_error("ROX-Filer", "You need to select some items "
801 "to delete");
802 return;
805 gui_side = start_action(filer_window, delete_cb);
806 if (!gui_side)
807 return;
809 gtk_window_set_title(GTK_WINDOW(gui_side->window), "Delete");
810 number_of_windows++;
811 gtk_widget_show_all(gui_side->window);
814 void action_copy(GSList *paths, char *dest)
816 GUIside *gui_side;
818 action_dest = dest;
819 action_do_func = do_copy;
820 gui_side = start_action(paths, list_cb);
821 if (!gui_side)
822 return;
824 gtk_window_set_title(GTK_WINDOW(gui_side->window), "Copy");
825 number_of_windows++;
826 gtk_widget_show_all(gui_side->window);
829 void action_move(GSList *paths, char *dest)
831 GUIside *gui_side;
833 action_dest = dest;
834 action_do_func = do_move;
835 gui_side = start_action(paths, list_cb);
836 if (!gui_side)
837 return;
839 gtk_window_set_title(GTK_WINDOW(gui_side->window), "Move");
840 number_of_windows++;
841 gtk_widget_show_all(gui_side->window);
844 void action_link(GSList *paths, char *dest)
846 GUIside *gui_side;
848 action_dest = dest;
849 action_do_func = do_link;
850 gui_side = start_action(paths, list_cb);
851 if (!gui_side)
852 return;
854 gtk_window_set_title(GTK_WINDOW(gui_side->window), "Link");
855 number_of_windows++;
856 gtk_widget_show_all(gui_side->window);