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)
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
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.
30 #include <sys/types.h>
38 #include "gui_support.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
);
50 int from_child
; /* File descriptor */
52 int input_tag
; /* gdk_input_add() */
53 GtkWidget
*log
, *window
, *actions
;
54 int child
; /* Process ID */
56 gboolean show_info
; /* For Disk Usage */
59 /* These don't need to be in a structure because we fork() before
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
,
78 GdkInputCondition condition
)
81 GUIside
*gui_side
= (GUIside
*) data
;
82 GtkWidget
*log
= gui_side
->log
;
84 if (read_exact(source
, buf
, 4))
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';
96 gtk_widget_set_sensitive(gui_side
->actions
,
98 else if (*buffer
== '+')
100 refresh_dirs(buffer
+ 1);
104 else if (*buffer
== '!')
107 gtk_text_insert(GTK_TEXT(log
),
109 *buffer
== '!' ? &red
: NULL
,
111 buffer
+ 1, message_len
- 1);
115 g_print("Child died in the middle of a message.\n");
118 /* The child is dead */
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
)
128 report
= g_string_new(NULL
);
129 g_string_sprintf(report
, "There %s %d error%s.\n",
130 gui_side
->errors
== 1 ? "was" : "were",
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
)
147 GSList
*list
= NULL
, *next
;
149 d
= opendir(src_dir
);
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')))
161 list
= g_slist_append(list
, g_strdup(make_path(src_dir
,
173 if (cb((char *) next
->data
, dest_path
))
175 g_string_sprintf(message
, "+%s", dest_path
);
186 /* Read this many bytes into the buffer. TRUE on success. */
187 static gboolean
read_exact(int source
, char *buffer
, ssize_t len
)
192 got
= read(source
, buffer
, len
);
201 /* Send 'message' to our parent process. TRUE on success. */
202 static gboolean
send()
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
);
213 return len
== message
->len
;
216 static gboolean
send_error()
218 g_string_sprintf(message
, "!ERROR: %s\n", g_strerror(errno
));
222 static void button_reply(GtkWidget
*button
, GUIside
*gui_side
)
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
)
240 len
= read(fd
, &retval
, 1);
244 fprintf(stderr
, "read() error: %s\n", g_strerror(errno
));
245 _exit(1); /* Parent died? */
249 static void destroy_action_window(GtkWidget
*widget
, gpointer data
)
251 GUIside
*gui_side
= (GUIside
*) data
;
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
);
263 if (--number_of_windows
< 1)
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 */
275 GtkWidget
*vbox
, *button
, *control
, *hbox
, *scrollbar
;
279 report_error("ROX-Filer", g_strerror(errno
));
283 if (pipe(filedes
+ 2))
287 report_error("ROX-Filer", g_strerror(errno
));
295 report_error("ROX-Filer", g_strerror(errno
));
298 /* We are the child */
299 message
= g_string_new(NULL
);
302 to_parent
= fdopen(filedes
[1], "wb");
303 from_parent
= filedes
[2];
308 /* We are the parent */
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
,
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
)
391 if (lstat(src_path
, &info
))
393 g_string_sprintf(message
, "'%s:\n", src_path
);
399 if (S_ISREG(info
.st_mode
))
400 size_tally
+= info
.st_size
;
401 else if (S_ISDIR(info
.st_mode
))
404 safe_path
= g_strdup(src_path
);
405 g_string_sprintf(message
, "'Scanning in '%s'\n", safe_path
);
407 for_dir_contents(do_usage
, safe_path
, safe_path
);
414 /* dest_path is the dir containing src_path */
415 static gboolean
do_delete(char *src_path
, char *dest_path
)
421 if (lstat(src_path
, &info
))
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 " : " ",
435 rep
= reply(from_parent
);
441 if (S_ISDIR(info
.st_mode
))
444 safe_path
= g_strdup(src_path
);
445 g_string_sprintf(message
, "'Scanning in '%s'\n", safe_path
);
447 for_dir_contents(do_delete
, safe_path
, safe_path
);
448 if (rmdir(safe_path
))
454 g_string_assign(message
, "'Directory deleted\n");
458 else if (unlink(src_path
) == 0)
460 g_string_sprintf(message
, "'Deleted '%s'\n", src_path
);
472 static gboolean
do_copy(char *path
, char *dest
)
477 gboolean retval
= TRUE
;
479 leaf
= strrchr(path
, '/');
481 leaf
= path
; /* Error? */
485 dest_path
= make_path(dest
, leaf
)->str
;
487 g_string_sprintf(message
, "'Copying %s as %s\n", path
, dest_path
);
490 if (access(dest_path
, F_OK
) == 0)
493 g_string_sprintf(message
, "?'%s' already exists - overwrite?\n",
497 rep
= reply(from_parent
);
504 if (lstat(path
, &info
))
510 if (S_ISDIR(info
.st_mode
))
512 char *safe_path
, *safe_dest
;
513 struct stat dest_info
;
516 /* (we will do the update ourselves now, rather than
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
))
538 /* (just been created then) */
539 g_string_sprintf(message
, "+%s", dest
);
543 g_string_sprintf(message
, "'Scanning in '%s'\n",
546 for_dir_contents(do_copy
, safe_path
, safe_dest
);
547 /* Note: dest_path now invalid... */
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",
568 static gboolean
do_move(char *path
, char *dest
)
572 gboolean retval
= TRUE
;
574 leaf
= strrchr(path
, '/');
576 leaf
= path
; /* Error? */
580 dest_path
= make_path(dest
, leaf
)->str
;
582 g_string_sprintf(message
, "'Moving %s into %s...\n", path
, dest
);
585 if (access(dest_path
, F_OK
) == 0)
590 g_string_sprintf(message
, "?'%s' already exists - overwrite?\n",
594 rep
= reply(from_parent
);
600 if (lstat(dest_path
, &info
))
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",
615 for_dir_contents(do_move
, safe_path
, safe_dest
);
617 if (rmdir(safe_path
))
623 g_string_assign(message
, "'Directory deleted\n");
626 g_string_sprintf(message
, "+%s", path
);
627 g_string_truncate(message
, leaf
- path
);
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
);
642 g_string_sprintf(message
, "!ERROR: Failed to move %s as %s\n",
651 static gboolean
do_link(char *path
, char *dest
)
656 leaf
= strrchr(path
, '/');
658 leaf
= path
; /* Error? */
662 dest_path
= make_path(dest
, leaf
)->str
;
664 if (symlink(path
, dest_path
))
671 g_string_sprintf(message
, "'Symlinked %s as %s\n",
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
;
688 int left
= collection
->number_selected
;
690 off_t total_size
= 0;
695 if (!collection
->items
[i
].selected
)
697 item
= (DirItem
*) collection
->items
[i
].data
;
699 do_usage(make_path(filer_window
->path
,
700 item
->leafname
)->str
,
702 g_string_sprintf(message
, "'%s: %s\n",
704 format_size((unsigned long) size_tally
));
706 total_size
+= size_tally
;
710 g_string_sprintf(message
, "'\nTotal: %s\n",
711 format_size((unsigned long) total_size
));
715 static void delete_cb(gpointer data
)
717 FilerWindow
*filer_window
= (FilerWindow
*) data
;
718 Collection
*collection
= filer_window
->collection
;
720 int left
= collection
->number_selected
;
726 if (!collection
->items
[i
].selected
)
728 item
= (DirItem
*) collection
->items
[i
].data
;
729 if (do_delete(make_path(filer_window
->path
,
730 item
->leafname
)->str
,
733 g_string_sprintf(message
, "+%s", filer_window
->path
);
739 g_string_sprintf(message
, "'\nDone\n");
743 static void list_cb(gpointer data
)
745 GSList
*paths
= (GSList
*) data
;
749 if (action_do_func((char *) paths
->data
, action_dest
))
751 g_string_sprintf(message
, "+%s", action_dest
);
758 g_string_sprintf(message
, "'\nDone\n");
762 /* EXTERNAL INTERFACE */
764 /* Count disk space used by selected items */
765 void action_usage(FilerWindow
*filer_window
)
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 "
779 gui_side
= start_action(filer_window
, usage_cb
);
783 gui_side
->show_info
= TRUE
;
785 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Disk Usage");
787 gtk_widget_show_all(gui_side
->window
);
790 /* Deletes all selected items in the window */
791 void action_delete(FilerWindow
*filer_window
)
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 "
805 gui_side
= start_action(filer_window
, delete_cb
);
809 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Delete");
811 gtk_widget_show_all(gui_side
->window
);
814 void action_copy(GSList
*paths
, char *dest
)
819 action_do_func
= do_copy
;
820 gui_side
= start_action(paths
, list_cb
);
824 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Copy");
826 gtk_widget_show_all(gui_side
->window
);
829 void action_move(GSList
*paths
, char *dest
)
834 action_do_func
= do_move
;
835 gui_side
= start_action(paths
, list_cb
);
839 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Move");
841 gtk_widget_show_all(gui_side
->window
);
844 void action_link(GSList
*paths
, char *dest
)
849 action_do_func
= do_link
;
850 gui_side
= start_action(paths
, list_cb
);
854 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Link");
856 gtk_widget_show_all(gui_side
->window
);