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;
							}
							