Browser

/* $Id$ */
/* Copyright (c) 2007-2018 Pierre Pronchery <khorben@defora.org> */
/* 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 <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <locale.h>
#include <libintl.h>
#include <gtk/gtk.h>
#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;
}