/* $Id$ */ /* Copyright (c) 2007-2018 Pierre Pronchery */ /* This file is part of DeforaOS Desktop Browser */ /* Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY ITS AUTHORS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include "Browser/vfs.h" #include "../config.h" #define _(string) gettext(string) /* constants */ #ifndef PROGNAME_DELETE # define PROGNAME_DELETE "delete" #endif #ifndef PREFIX # define PREFIX "/usr/local" #endif #ifndef DATADIR # define DATADIR PREFIX "/share" #endif #ifndef LOCALEDIR # define LOCALEDIR DATADIR "/locale" #endif /* Delete */ /* private */ /* types */ typedef int Prefs; #define PREFS_f 0x1 #define PREFS_i 0x2 #define PREFS_R 0x4 typedef struct _DeleteDir DeleteDir; typedef enum _DeleteMode { DM_COUNT = 0, DM_DELETE } DeleteMode; typedef struct _Delete { DeleteMode mode; Prefs * prefs; unsigned int filec; char ** filev; unsigned int file_cur; size_t count_cnt; size_t count_cur; struct timeval count_tv; struct dirent * de; DeleteDir ** dirv; size_t dirv_cnt; /* widgets */ guint idle; guint timeout; GtkWidget * window; GtkWidget * label; GtkWidget * hbox; GtkWidget * entry; GtkWidget * progress; } Delete; struct _DeleteDir { DIR * dir; char * filename; }; /* prototypes */ static int _delete_error(Delete * delete, char const * message, int ret); static int _delete_filename_error(Delete * delete, char const * filename, int ret); static void _delete_refresh(Delete * delete, char const * filename); /* functions */ /* delete */ /* callbacks */ static void _delete_on_cancel(gpointer data); static gboolean _delete_on_closex(gpointer data); static gboolean _delete_idle(gpointer data); static gboolean _delete_timeout(gpointer data); static int _delete(Prefs * prefs, unsigned int filec, char * filev[]) { static Delete delete; GtkWidget * vbox; GtkWidget * hbox; GtkWidget * widget; PangoFontDescription * bold; if(filec < 1 || filev == NULL) return 1; delete.mode = DM_COUNT; delete.prefs = prefs; delete.filec = filec; delete.filev = filev; delete.file_cur = 0; delete.count_cnt = 0; delete.count_cur = 0; delete.de = NULL; delete.dirv = NULL; delete.dirv_cnt = 0; /* graphical interface */ delete.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_icon_name(GTK_WINDOW(delete.window), "stock_delete"); gtk_window_set_resizable(GTK_WINDOW(delete.window), FALSE); gtk_window_set_title(GTK_WINDOW(delete.window), _("Delete file(s)")); g_signal_connect_swapped(delete.window, "delete-event", G_CALLBACK( _delete_on_closex), &delete); vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4); /* counter */ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); widget = gtk_image_new_from_icon_name("stock_delete", GTK_ICON_SIZE_DIALOG); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0); delete.label = gtk_label_new(_("Counting files...")); #if GTK_CHECK_VERSION(3, 0, 0) g_object_set(delete.label, "halign", GTK_ALIGN_START, NULL); #else gtk_misc_set_alignment(GTK_MISC(delete.label), 0.0, 0.5); #endif gtk_box_pack_start(GTK_BOX(hbox), delete.label, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); /* current argument */ delete.hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); hbox = delete.hbox; gtk_widget_set_no_show_all(hbox, TRUE); widget = gtk_label_new(_("File: ")); bold = pango_font_description_new(); pango_font_description_set_weight(bold, PANGO_WEIGHT_BOLD); #if GTK_CHECK_VERSION(3, 0, 0) gtk_widget_override_font(widget, bold); #else gtk_widget_modify_font(widget, bold); #endif pango_font_description_free(bold); gtk_widget_show(widget); gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0); delete.entry = gtk_entry_new(); gtk_editable_set_editable(GTK_EDITABLE(delete.entry), FALSE); gtk_widget_show(delete.entry); gtk_box_pack_start(GTK_BOX(hbox), delete.entry, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); /* progress bar */ delete.progress = gtk_progress_bar_new(); #if GTK_CHECK_VERSION(3, 0, 0) gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(delete.progress), TRUE); #endif gtk_progress_bar_set_text(GTK_PROGRESS_BAR(delete.progress), ""); gtk_box_pack_start(GTK_BOX(vbox), delete.progress, TRUE, TRUE, 0); hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); widget = gtk_button_new_from_stock(GTK_STOCK_CANCEL); g_signal_connect_swapped(widget, "clicked", G_CALLBACK( _delete_on_cancel), &delete); gtk_box_pack_end(GTK_BOX(hbox), widget, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(delete.window), 4); gtk_container_add(GTK_CONTAINER(delete.window), vbox); #ifdef DEBUG delete.idle = g_timeout_add(10, _delete_idle, &delete); #else delete.idle = g_idle_add(_delete_idle, &delete); #endif delete.timeout = g_timeout_add(500, _delete_timeout, &delete); _delete_refresh(&delete, ""); gtk_widget_show_all(delete.window); return 0; } static void _delete_on_cancel(gpointer data) { Delete * delete = data; size_t i; gtk_widget_hide(delete->window); for(i = delete->dirv_cnt; i >= 1; i--) { if(delete->dirv[i - 1]->dir != NULL) browser_vfs_closedir(delete->dirv[i - 1]->dir); free(delete->dirv[i - 1]->filename); free(delete->dirv[i - 1]); } free(delete->dirv); if(delete->idle != 0) g_source_remove(delete->idle); if(delete->timeout != 0) g_source_remove(delete->timeout); gtk_main_quit(); } static gboolean _delete_on_closex(gpointer data) { Delete * delete = data; _delete_on_cancel(delete); return FALSE; } static gboolean _idle_count(Delete * delete); static gboolean _idle_delete(Delete * delete); static int _idle_do(Delete * delete); static gboolean _delete_idle(gpointer data) { gboolean ret = FALSE; Delete * delete = data; _idle_do(delete); switch(delete->mode) { case DM_COUNT: ret = _idle_count(delete); break; case DM_DELETE: ret = _idle_delete(delete); break; } if(ret == FALSE) gtk_main_quit(); return ret; } static gboolean _idle_count(Delete * delete) { if(delete->file_cur == delete->filec) { delete->mode = DM_DELETE; delete->file_cur = 0; gtk_label_set_text(GTK_LABEL(delete->label), _("Deleting files...")); gtk_widget_show(delete->hbox); } return TRUE; } static gboolean _idle_delete(Delete * delete) { return (delete->file_cur == delete->filec) ? FALSE : TRUE; } static int _idle_do_file(Delete * delete, char const * filename); static int _idle_do_readdir(Delete * delete); static int _idle_do_closedir(Delete * delete); static int _idle_ask_recursive(Delete * delete, char const * filename); static int _idle_ask(Delete * delete, char const * filename); static int _idle_do_opendir(Delete * delete, char const * filename); static int _idle_do(Delete * delete) { int ret; if(delete->dirv_cnt > 0) { ret = _idle_do_readdir(delete); if(delete->de != NULL) return ret; return _idle_do_closedir(delete); } ret = _idle_do_file(delete, delete->filev[delete->file_cur]); if(delete->dirv_cnt == 0) delete->file_cur++; return ret; } static int _idle_do_file_count(Delete * delete); static int _idle_do_file_delete(Delete * delete, char const * filename); static int _idle_do_file(Delete * delete, char const * filename) { struct stat st; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, filename); #endif _delete_refresh(delete, filename); if(browser_vfs_lstat(filename, &st) != 0 && errno == ENOENT) { if(!(*(delete->prefs) & PREFS_f)) return _delete_filename_error(delete, filename, 1); return 0; } if(S_ISDIR(st.st_mode)) { if(!(*(delete->prefs) & PREFS_R)) { errno = EISDIR; return _delete_filename_error(delete, filename, 1); } else if((*(delete->prefs) & PREFS_f) || _idle_ask_recursive(delete, filename) == 0) return _idle_do_opendir(delete, filename); } else switch(delete->mode) { case DM_COUNT: return _idle_do_file_count(delete); case DM_DELETE: return _idle_do_file_delete(delete, filename); } return 0; } static int _idle_do_file_count(Delete * delete) { delete->count_cnt++; return 0; } static int _idle_do_file_delete(Delete * delete, char const * filename) { delete->count_cur++; if((*(delete->prefs) & PREFS_f) || _idle_ask(delete, filename) == 0) #ifdef DEBUG fprintf(stderr, "DEBUG: unlink(\"%s\")\n", filename); #else if(unlink(filename) != 0) return _delete_filename_error(delete, filename, 1); #endif return 0; } static int _idle_do_readdir(Delete * delete) { int ret = 0; DIR * dir = delete->dirv[delete->dirv_cnt - 1]->dir; char const * parent; size_t len; char * p; if((delete->de = readdir(dir)) == NULL) return 0; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, delete->de->d_name); #endif if(strcmp(delete->de->d_name, ".") == 0 || strcmp(delete->de->d_name, "..") == 0) return 0; parent = delete->dirv[delete->dirv_cnt - 1]->filename; len = strlen(parent) + strlen(delete->de->d_name) + 2; if((p = malloc(len)) == NULL) return _delete_filename_error(delete, parent, 1); snprintf(p, len, "%s/%s", parent, delete->de->d_name); ret = _idle_do_file(delete, p); free(p); return ret; } static int _idle_do_closedir_count(Delete * delete); static int _idle_do_closedir_delete(Delete * delete, DeleteDir * dd); static int _idle_do_closedir(Delete * delete) { int ret = 0; DeleteDir * dd = delete->dirv[delete->dirv_cnt - 1]; browser_vfs_closedir(dd->dir); switch(delete->mode) { case DM_COUNT: ret = _idle_do_closedir_count(delete); break; case DM_DELETE: ret = _idle_do_closedir_delete(delete, dd); break; } free(dd->filename); free(dd); if(--delete->dirv_cnt == 0) { free(delete->dirv); delete->dirv = NULL; delete->file_cur++; } return ret; } static int _idle_do_closedir_count(Delete * delete) { delete->count_cnt++; return 0; } static int _idle_do_closedir_delete(Delete * delete, DeleteDir * dd) { delete->count_cur++; #ifdef DEBUG fprintf(stderr, "DEBUG: rmdir(\"%s\")\n", dd->filename); #else if(rmdir(dd->filename) != 0) _delete_filename_error(delete, dd->filename, 1); #endif return 0; } static int _idle_ask_recursive(Delete * delete, char const * filename) { char * p; GtkWidget * dialog; int res; switch(delete->mode) { case DM_COUNT: return 0; case DM_DELETE: break; } #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, filename); #endif if((p = g_filename_to_utf8(filename, -1, NULL, NULL, NULL)) != NULL) filename = p; dialog = gtk_message_dialog_new(GTK_WINDOW(delete->window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, #if GTK_CHECK_VERSION(2, 6, 0) "%s", _("Question")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), #endif _("%s is a directory.\nRecursively delete?"), filename); gtk_window_set_title(GTK_WINDOW(dialog), _("Question")); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Yes to all"), 1); res = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); free(p); if(res == 1) { *(delete->prefs) = (*(delete->prefs) & ~PREFS_i) | PREFS_f; return 0; } return (res == GTK_RESPONSE_YES) ? 0 : 1; } static int _idle_ask(Delete * delete, char const * filename) { int ret; char * p; GtkWidget * dialog; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, filename); #endif if((p = g_filename_to_utf8(filename, -1, NULL, NULL, NULL)) != NULL) filename = p; dialog = gtk_message_dialog_new(GTK_WINDOW(delete->window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, #if GTK_CHECK_VERSION(2, 6, 0) "%s", _("Question")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), #endif _("%s will be permanently deleted.\nContinue?"), filename); gtk_window_set_title(GTK_WINDOW(dialog), _("Question")); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Yes to all"), 1); ret = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); free(p); if(ret == 1) { *(delete->prefs) = (*(delete->prefs) & ~PREFS_i) | PREFS_f; return 0; } return (ret == GTK_RESPONSE_YES) ? 0 : 1; } static int _idle_do_opendir(Delete * delete, char const * filename) { DeleteDir * dd; DeleteDir ** d; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, filename); #endif if((dd = malloc(sizeof(*dd))) == NULL) return _delete_filename_error(delete, filename, 1); if((d = realloc(delete->dirv, sizeof(*d) * (delete->dirv_cnt + 1))) == NULL) { free(dd); return _delete_filename_error(delete, filename, 1); } delete->dirv = d; d = &delete->dirv[delete->dirv_cnt]; if((dd->filename = strdup(filename)) == NULL) { free(dd); return _delete_filename_error(delete, filename, 1); } if((dd->dir = browser_vfs_opendir(filename, NULL)) == NULL) { free(dd); return _delete_filename_error(delete, filename, 1); } *d = dd; delete->dirv_cnt++; return 0; } static gboolean _delete_timeout(gpointer data) { Delete * delete = data; switch(delete->mode) { case DM_COUNT: gtk_progress_bar_pulse(GTK_PROGRESS_BAR( delete->progress)); return TRUE; case DM_DELETE: break; } delete->timeout = 0; return FALSE; } /* delete_error */ static int _error_text(char const * message, int ret); static int _delete_error(Delete * delete, char const * message, int ret) { GtkWidget * dialog; char const * error = strerror(errno); if(delete == NULL) return _error_text(message, ret); dialog = gtk_message_dialog_new(GTK_WINDOW(delete->window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", #if GTK_CHECK_VERSION(2, 6, 0) _("Error")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s: %s", message, #endif error); gtk_window_set_title(GTK_WINDOW(dialog), _("Error")); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return ret; } static int _error_text(char const * message, int ret) { fputs(PROGNAME_DELETE ": ", stderr); perror(message); return ret; } /* delete_filename_error */ static int _delete_filename_error(Delete * delete, char const * filename, int ret) { char * p; int error = errno; if((p = g_filename_to_utf8(filename, -1, NULL, NULL, NULL)) != NULL) filename = p; errno = error; ret = _delete_error(delete, filename, ret); free(p); return ret; } /* delete_refresh */ static void _refresh_delete(Delete * delete, char const * filename); static void _delete_refresh(Delete * delete, char const * filename) { switch(delete->mode) { case DM_COUNT: break; case DM_DELETE: _refresh_delete(delete, filename); break; } } static void _refresh_delete(Delete * delete, char const * filename) { char * p; char buf[64]; double fraction; if((p = g_filename_to_utf8(filename, -1, NULL, NULL, NULL)) != NULL) filename = p; gtk_entry_set_text(GTK_ENTRY(delete->entry), filename); free(p); snprintf(buf, sizeof(buf), _("File %u of %u"), delete->file_cur + 1, delete->filec); fraction = MIN(delete->count_cur, delete->count_cnt); fraction /= MAX(delete->count_cnt, 1); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() %f\n", __func__, fraction); #endif gtk_progress_bar_set_text(GTK_PROGRESS_BAR(delete->progress), buf); gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(delete->progress), fraction); } /* usage */ static int _usage(void) { fprintf(stderr, _("Usage: %s [-fiRr] file...\n\ -f Do not prompt for confirmation and ignore errors\n\ -i Prompt for confirmation\n\ -R Remove file hierarchies\n\ -r Equivalent to -R\n"), PROGNAME_DELETE); return 1; } /* main */ int main(int argc, char * argv[]) { Prefs prefs; int o; if(setlocale(LC_ALL, "") == NULL) _delete_error(NULL, "setlocale", 1); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); memset(&prefs, 0, sizeof(prefs)); gtk_init(&argc, &argv); while((o = getopt(argc, argv, "fiRr")) != -1) switch(o) { case 'f': prefs -= prefs & PREFS_i; prefs |= PREFS_f; break; case 'i': prefs -= prefs & PREFS_f; prefs |= PREFS_i; break; case 'R': case 'r': prefs |= PREFS_R; break; default: return _usage(); } if(optind == argc) return _usage(); _delete(&prefs, argc - optind, &argv[optind]); gtk_main(); return 0; }