/* $Id: e2_task.c 853 2008-04-07 10:38:17Z tpgww $

Copyright (C) 2003-2008 tooar <tooar@gmx.net>

This file is part of emelFM2.
emelFM2 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

emelFM2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with emelFM2; see the file GPL. If not, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/* TODO 25/10/04
some functions do not need TreeRowReferences
 (as of now, none do - no on-the-fly store content changes)
when stability confirmed, move surrounding dis/enable refresh to e2_dialog_ow_check() itself ?
*/

#include "e2_task.h"
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include "e2_dialog.h"
#include "e2_option.h"
#include "e2_ownership_dialog.h"
#include "e2_filelist.h"
#include "e2_filetype.h"

GList *open_history = NULL;  //history list for open-with dialog
extern gint refresh_refcount;

pthread_mutex_t task_mutex = PTHREAD_MUTEX_INITIALIZER; //PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP not needed
pthread_t athreadID = 0;	//action thread ID, 0 when stopped
pthread_t mthreadID = 0;	//monitor thread ID, 0 when stopped
//pointers for passing customised data to Q store func
GPtrArray *names_array;	//this array will not be copied for Q data
gchar *srcdir_local;	//localised source dir, not copied for Q data
gchar *destdir_local;	//ditto for dest

static gpointer _e2_task_progress_monitor (E2_TaskRuntime *rt);
static gboolean _e2_task_move (E2_ActionTaskData *qed, gchar *trashpath);
static gboolean _e2_task_copyQ (E2_ActionTaskData *qed);
static gboolean _e2_task_copy_asQ (E2_ActionTaskData *qed);
static gboolean _e2_task_trashitQ (E2_ActionTaskData *qed);
static gboolean _e2_task_moveQ (E2_ActionTaskData *qed);
static gboolean _e2_task_move_asQ (E2_ActionTaskData *qed);
static gboolean _e2_task_symlinkQ (E2_ActionTaskData *qed);
static gboolean _e2_task_symlink_asQ (E2_ActionTaskData *qed);
static gboolean _e2_task_deleteQ (E2_ActionTaskData *qed);
static gboolean _e2_task_renameQ (E2_ActionTaskData *qed);
static gboolean _e2_task_permissionsQ (E2_ActionTaskData *qed);
static gboolean _e2_task_ownershipQ (E2_ActionTaskData *qed);
static gboolean _e2_task_file_infoQ (E2_ActionTaskData *qed);
static gboolean _e2_task_viewQ (E2_ActionTaskData *qed);
static gboolean _e2_task_editQ (E2_ActionTaskData *qed);
static gboolean _e2_task_openQ (E2_ActionTaskData *qed);
static gboolean _e2_task_open_withQ (E2_ActionTaskData *qed);
//CHECKME need this ?static gboolean _e2_task_open_inotherQ (E2_ActionTaskData *qed);



#ifdef E2_INCLIST
/**
@brief

@param ref pointer to reference in current view

@return
*/
static void _e2_task_treeview_line_remove (GtkTreeRowReference *ref)
{
	GtkTreeIter iter;
	GtkTreePath *path = gtk_tree_row_reference_get_path (ref);
	if (path != NULL && gtk_tree_model_get_iter (curr_view->model, &iter, path))
		gtk_list_store_remove (curr_view->store, &iter);
}
/**
@brief

@param ref pointer to reference in current view
@param newname utf-8 string with replacement name for an item

@return
*/
static void _e2_task_treeview_line_rename (GtkTreeRowReference *ref, gchar *newname)
{
	gchar *savename;
	gchar *local = F_FILENAME_TO_LOCALE (newname);
	if (e2_fs_is_dir3 (local E2_ERR_NONE()))
		savename = g_strconcat (newname, G_DIR_SEPARATOR_S, NULL);
	else
		savename = newname;

	GtkTreeIter iter;
	GtkTreePath *path = gtk_tree_row_reference_get_path (ref);
	if (path != NULL && gtk_tree_model_get_iter (curr_view->model, &iter, path))
		gtk_list_store_set (curr_view->store, &iter, FILENAME, savename, -1);

	F_FREE (local);
	if (savename != newname)
		g_free (savename);
}
//FIXME add fn to change permissions field in a row
#endif	//def E2_INCLIST

  /***********************/
 /** support functions **/
/***********************/

/**
@brief find the data for a running or paused task with pid @a id
This does not distinguish between commands and actions
@param pid the id of the process we're looking for
@return the data struct for the task, or NULL if not found
*/
E2_TaskRuntime *e2_task_find_running_task (glong pid)
{
	E2_TaskRuntime *rt;
	GList *member;
	pthread_mutex_lock (&task_mutex);
	member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		rt = (E2_TaskRuntime *)member->data;
		if (rt != NULL && rt->pid == pid &&
			(rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED))
			return rt;
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
	return NULL;
}
/**
@brief find the pid of the last-started child process that's still running
This doesn't distinguish between commands and actions
@param anytab TRUE to find a match in any tab, FALSE in the currently-displayed tab
@return pointer to task data or NULL if not found
*/
E2_TaskRuntime *e2_task_find_last_running_child (gboolean anytab)
{
	E2_TaskRuntime *rt;
	GList *member;
	pthread_mutex_lock (&task_mutex);
	member = g_list_last (app.taskhistory);
	pthread_mutex_unlock (&task_mutex);
	//backwards scan, don't bother locking at end of loop
	for (; member != NULL; member = member->prev)
	{
		rt = (E2_TaskRuntime *)member->data;
		if (rt != NULL && rt->status == E2_TASK_RUNNING && !rt->action
			&& (anytab || rt->current_tab == &app.tab))
			return rt;
	}
	return NULL;
}
/**
@brief setup task data for a command or action
This is called for a command just commenced (async) or about to commence (sync)
or for a file-action about to be commenced or queued.
Task data is added to the task Q if not already there, and initialised as
appropriate for @a mode.
@a mode = one of:
 E2_TASKTYPE_ASYNC when called from _e2_command_run_async()
 E2_TASKTYPE_SYNC when called from _e2_command_run_sync()
 E2_TASKTYPE_ACTION when called from e2_task_enqueue_task()

@param pid the id of the process we're starting (0 if not yet known)
@param mode task type enumerator
@param command executed command string or NULL for an action
@return pointer to command data struct, or NULL if this fn is busy, or 1 on error
*/
E2_TaskRuntime *e2_task_set_data (glong pid, E2_TaskDataType mode, gchar *command)
{
	static gboolean blocked = FALSE;
	if (blocked)
		return NULL;
	else
		blocked = TRUE;

	E2_TaskRuntime *rt = e2_task_find_running_task (pid);
	if (rt == NULL)
	{
		printd (DEBUG, "e2_task_set_data (pid:%d,mode:%d,command:%s)",
			pid, mode, command);
		rt = ALLOCATE (E2_TaskRuntime);	//FIXME never deallocated
		CHECKALLOCATEDWARN (rt, )
		if (rt == NULL)
		{
			printd (WARN, "not enough memory for task history data");
			blocked = FALSE;
			return GINT_TO_POINTER (1);
		}
		if (mode == E2_TASKTYPE_ASYNC || mode == E2_TASKTYPE_SYNC)
		{
			rt->action = FALSE;
#ifndef E2_NEW_COMMAND
			rt->pid = pid;
#endif
			rt->ex.command.command = g_strdup (command);
//E2_VFSTMPOK
			rt->ex.command.currdir = g_strdup (curr_view->dir);
#ifdef E2_COMMANDQ
			rt->ex.command.othrdir = g_strdup (other_view->dir);
# ifdef E2_VFS
			rt->ex.command.currspace = curr_view->spacedata;
			rt->ex.command.othrspace = other_view->spacedata;
# endif
			rt->ex.command.names = ;	//active pane selected items array
			rt->ex.command.othernames = ;	//inactive pane selected items array
//			rt->ex.command.range = SET EXTERNALLY;
#endif
#ifndef E2_NEW_COMMAND
			if (mode == E2_TASKTYPE_ASYNC)
			{
				rt->pidstr = g_strdup_printf ("%ld", rt->pid);
//				rt->status = E2_TASK_RUNNING; see below
			}
			else	//(mode == E2_TASKTYPE_SYNC)
			{
				//pid will be -ve, a fake assigned by e2, not the os, as we never
				//get the real one, nor ever get opportunity to kill the sync process
				rt->pidstr = g_strdup_printf ("s%ld", -pid);
			}
			rt->status = E2_TASK_RUNNING;
#endif
		}
		else if (mode == E2_TASKTYPE_ACTION)
		{
			rt->action = TRUE;
			rt->status = E2_TASK_NONE;	//prevent immediate Q processing
			//setup for on-the-fly status changes coz often just rt->ex.action is known
			rt->ex.action.status = &rt->status;
		}
		else
		{
			printd (WARN, "tried to register a process with bad mode flag");
			DEALLOCATE (E2_TaskRuntime, rt);
			blocked = FALSE;
			return GINT_TO_POINTER (1);
		}

		//setup for foreground running
		rt->current_tab = &app.tab;
		rt->background_tab = curr_tab;

		pthread_mutex_lock (&task_mutex);
		app.taskhistory = g_list_append (app.taskhistory, rt);
		pthread_mutex_unlock (&task_mutex);
	}
	else
	{	//found an entry with matching pid
		printd (DEBUG, "tried to re-register a process");
		blocked = FALSE;
		return GINT_TO_POINTER (1);
	}

	blocked = FALSE;
	return rt;
}
/**
@brief get name(s) of item(s) to process, from @a arg

@a arg comprises (notionally at least) one or more concatenated filenames
or filepaths, with or without quotes, to be processed.
Such string is converted into array form, with source path set from the
first item.

@param arg utf8 string (action rt data)
@param path store for source path (if any) retrieved from 1st item in @a arg

@return array of E2_SelectedItemInfo's, or NULL if nothing relevant is available
*/
static GPtrArray *_e2_task_get_names (gchar *arg, gchar **path)
{
	GPtrArray *names;
	E2_SelectedItemInfo *seldata;
	gint len;
	gchar *s, *name, *local;
	gchar *item = e2_utils_get_first_arg (arg, FALSE);
	if (item == NULL)
		return NULL;

	//check first argument for a source path
	s = g_path_get_dirname (item);
	if (!g_str_equal (s, "."))
	{
		if (g_str_equal (s, G_DIR_SEPARATOR_S))
			*path = g_strdup (G_DIR_SEPARATOR_S);
		else
		{
			local = F_FILENAME_TO_LOCALE (s);
			*path = e2_utils_strcat (local, G_DIR_SEPARATOR_S);
			F_FREE (local);
		}
	}
	g_free (item);
	g_free (s);
	//now get names of all items
	names = g_ptr_array_new ();
	s = arg;
	while (s != NULL)
	{
		item = e2_utils_get_first_arg (s, FALSE);
		if (item == NULL)
			break;
		seldata = ALLOCATE (E2_SelectedItemInfo);
		//CHECKME brackets inside macro
		CHECKALLOCATEDWARN (seldata, g_ptr_array_free (names, TRUE);g_free (item);return NULL;);
		name = g_path_get_basename (item);
		local = F_FILENAME_TO_LOCALE (name);
		//strip any trailer
		len = strlen (local);
		if (*(local+len-1) == G_DIR_SEPARATOR)
			*(local+len-1) = '\0';
		g_strlcpy (seldata->filename, local, sizeof (seldata->filename));
		g_ptr_array_add (names, seldata);
		g_free (name);
		F_FREE (local);
		//point after the current argument
		len = strlen (item);
		//this might be not quite correct (preceeding whitespace or quotes)
		s += len;
		s = e2_utils_find_whitespace (s);
		g_free (item);
	}

	return names;
}
/**
@brief thread function to process tasks-pending queue
Data are (in general) not cleaned when a task is finished - so we have a
history. The selection names are cleared (lot of storage & not yet used for
history purposes)
Any completion-report function is called with BGL open/off !
@param data UNUSED ptr to data assigned when thread created

@return NULL
*/
static gpointer _e2_task_processQ (gpointer data)
{
	e2_utils_block_thread_signals ();	//block all allowed signals to this thread

	//this is a workaround for systems that run this thread before the
	//thread-creation function sets athreadID
	//but don't adjust athreadID yet
	pthread_t thisID = pthread_self ();

	E2_TaskRuntime *rt;
	E2_ActionTaskData *qed;
	GList *member;
	pthread_mutex_lock (&task_mutex);
	//FIXME move the scan-start position to ignore completed items
	member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		rt = (E2_TaskRuntime *)member->data;
		qed = &rt->ex.action;
		if (rt->action && rt->status == E2_TASK_QUEUED)
		{
			if (e2_option_bool_get ("task-timeout-checks"))
			{
				//create progress monitor thread, providing a mechanism to abort
				//slow activities
//				qed->timelimit = time (NULL) + e2_option_int_get ("task-timeout-interval");
//				printd (DEBUG,"task period is %d seconds", data->timeout);
				pthread_attr_t attr;
				//we don't use glib thread funcs, as we may need to kill threads
				pthread_attr_init (&attr);
				pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
				if (pthread_create (&mthreadID, &attr,
					(gpointer) _e2_task_progress_monitor, rt) == 0)
				{
					printd (DEBUG,"monitor-thread (ID=%lu) started", mthreadID);
					rt->dialog = NULL;
				}
				else
				{
					//FIXME message to user
					printd (WARN,"monitor-thread-create error!");
					mthreadID = 0;
				}
			}
			else
				mthreadID = 0;

			rt->pid = (glong) thisID;	//all tasks in the Q will get same pid
			rt->pidstr = g_strdup_printf ("%lu", (gulong) thisID);

			gboolean (*actionfunc) (E2_ActionTaskData *) = qed->taskfunc;

			rt->status = E2_TASK_RUNNING;	//FIXME enable downstream status change for pauses
//			printd (DEBUG,"queued action starts");
			//do it
			qed->result = (*actionfunc) (qed);
			rt->status = E2_TASK_COMPLETED;
			//promptly signal completion to monitor thread
			if (mthreadID > 0)
			{
				pthread_mutex_lock (&task_mutex);
				if (rt->dialog != NULL)
				{
					gdk_threads_enter ();
					gtk_widget_destroy (rt->dialog);
					gdk_threads_leave ();
					rt->dialog = NULL;	//not really necessary
				}
				pthread_mutex_unlock (&task_mutex);

				pthread_cancel (mthreadID);
				printd (DEBUG,"monitor-thread (ID=%lu) stopped by action-thread", mthreadID);
				mthreadID = 0;
			}
			//report as required (BGL open)
			if (qed->callback != NULL && qed->callback != e2_task_refresh_lists)
			{
				gboolean (*cb) (gboolean) = qed->callback;
				(*cb) (qed->result);
			}
			//setup refresh Q as required
			if (qed->callback == e2_task_refresh_lists)
			{
				e2_task_refresh_lists (qed);
			}
			//just 1 cleanup
			if (qed->names != NULL)
				e2_fileview_clean_selected (qed->names);
		}
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
	pthread_mutex_lock (&task_mutex);
	if (athreadID == 0)	//we're finishing before the thread-starter has returned
		athreadID = -1;	//set quick-finish signal for the starter
	else
		athreadID = 0;	//just clear it
	pthread_mutex_unlock (&task_mutex);
	printd (DEBUG, "task-Q-process-thread finished");
	return NULL;
}
/**
@brief timer callback to initiate a queued-task thread with BGL definitely off/open

@param rt pointer to data for added task, or NULL when just restarting

@return FALSE to stop the timer
*/
static gboolean _e2_task_run_processQ (E2_TaskRuntime *rt)
{
	pthread_attr_t attr;
	pthread_attr_init (&attr);
	pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
	pthread_mutex_lock (&task_mutex);
	gboolean not_running = (athreadID == 0);
	pthread_mutex_unlock (&task_mutex);
	if (not_running)
	{
		//NOTE in some systems the created thread will run and maybe even finish,
		//before this func returns !!
		pthread_t thisID;
		if (pthread_create (&thisID, &attr,
			(gpointer) _e2_task_processQ, NULL) == 0)	//no data ?
		{
			pthread_mutex_lock (&task_mutex);
			if (athreadID == -1)	//the task thread has finished already
			{
				athreadID = 0;
				printd (DEBUG,"Q-process-thread (ID=%lu) finished immediately", (gulong) thisID);
			}
			else
			{
				athreadID = thisID;
				printd (DEBUG,"Q-process-thread (ID=%lu) started", (gulong) thisID);
			}
			pthread_mutex_unlock (&task_mutex);
		}
		else
		{
	/*		gdk_threads_enter ();
			e2_output_print_error (_("Cannot create thread to run task"), FALSE);
			gdk_threads_leave ();
	*/
			printd (WARN,"Q-process-thread creation error!");
		}
	}
	else if (rt != NULL)	//adding a task
	{	//task start may be deferred, advise user
#ifdef E2_COMMANDQ
		gchar *s, *p;
		if (rt->action)
		{
			s = rt->ex.action.action->name;
			p = NULL;
		}
		else
		{
			s = rt->ex.command.command;
			p = e2_utils_find_whitespace (s);
			if (p != NULL)
				*p = '\0';
		}
#else
		gchar *s = rt->ex.action.action->name;
#endif
		gchar *msg = g_strdup_printf (_("%s added to tasks queue"), s);
		gdk_threads_enter ();
		e2_output_print (&app.tab, msg, NULL, TRUE, "blue", "bold", NULL);
		gdk_threads_leave ();
		g_free (msg);
#ifdef E2_COMMANDQ
		if (p != NULL)
			*p = ' ';	//too bad if it was a tab before
#endif
		//if message not visible, make a noise
		if (app.window.output_paned_ratio > 0.99)
			e2_utils_beep ();
	}

	return FALSE;
}
/**
@brief thread function to process immediate task
Data are (in general) not cleaned when a task is finished - so we have a
history. The selection names are cleared (lot of storage & not yet used for
history purposes)
@param rt ptr to data struct for the task to be performed

@return NULL
*/
static gpointer _e2_task_processnow (E2_TaskRuntime *rt)
{
	e2_utils_block_thread_signals ();	//block all allowed signals to this thread

	rt->pid = (glong) pthread_self ();
	rt->pidstr = g_strdup_printf ("%lu", (gulong)rt->pid);
	E2_ActionTaskData *qed = &rt->ex.action;
	gboolean (*actionfunc) (E2_ActionTaskData *) = qed->taskfunc;

	rt->status = E2_TASK_RUNNING;
//	printd (DEBUG,"immediate action starts");
	//do it
	qed->result = (*actionfunc) (qed);
	rt->status = E2_TASK_COMPLETED;

	//report as required
	if (qed->callback != NULL && qed->callback != e2_task_refresh_lists)
	{
		gboolean (*cb) (gboolean) = qed->callback;
		(*cb) (qed->result);
	}
	//setup refresh Q as required
	if (qed->callback == e2_task_refresh_lists)
	{
		e2_task_refresh_lists (qed);
	}
	//just 1 cleanup
	if (qed->names != NULL)
		e2_fileview_clean_selected (qed->names);

	printd (DEBUG,"immediate-action-thread (ID=%lu) finished", rt->pid);
	return NULL;
}
/**
@brief timer callback to initiate an immediate-task thread with BGL definitely off/open

@param rt pointer to task data struct

@return FALSE to stop the timer
*/
static gboolean _e2_task_run_processnow (E2_TaskRuntime *rt)
{
	//no glib thread funcs here, as we generally need the option to cancel thread(s)
	pthread_t threadID;
	pthread_attr_t attr;
	pthread_attr_init (&attr);
	pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
	if (pthread_create (&threadID, &attr,
		(gpointer) _e2_task_processnow, rt) == 0)
		printd (DEBUG,"immediate-action-thread (ID=%lu) started", (gulong) threadID);
	else
	{
/*		gdk_threads_enter ();
		e2_output_print_error (_("Cannot create thread to run task"), FALSE);
		gdk_threads_leave ();
*/
		printd (WARN,"action-thread creation error!");
	}
	return FALSE;
}
/**
@brief terminate execution of current or all queued tasks

@param all TRUE to terminate pending tasks as well as current

@return
*/
void e2_task_abort (gboolean all)
{
	//shutdown any monitor thread
	if (mthreadID > 0)
	{
		pthread_cancel (mthreadID);
		mthreadID = 0;
	}
	//shutdown any action thread
	if (athreadID > 0)
	{
		pthread_cancel (athreadID);
		//clear ID after finished checking all tasks
	}
	//cleanups
	gboolean killchildren = !e2_option_bool_get ("command-persist");
	E2_TaskRuntime *rt;
	GList *member;
	//Q-threads are stopped, no need for mutex
	for (member = app.taskhistory; member != NULL; member = member->next)
	{
		rt = (E2_TaskRuntime *)member->data;
		if (rt != NULL &&
			(rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED))
		{
			if (!rt->action)
			{
				if (killchildren)
					kill (-rt->pid, SIGTERM);	//cleanup commands
				/*else
				 allowing running children to continue is consistent with other
				 FM's, but the child's stdin, stdout, stderr will be disabled
				 FIND A WAY TO REVERT CHILD'S STDIO FD'S TO DEFAULTS 0,1,2 */
			}
			else if (rt->pid != (glong) athreadID)
			{ //action may still be running in an immediate task thread
				pthread_cancel ((pthread_t) rt->pid);
			}
			rt->status = E2_TASK_INCOMPLETE;
			if (rt->action)
			{
				E2_ActionTaskData *qed = &rt->ex.action;
				qed->result = FALSE;
				if (qed->callback != NULL && qed->callback != e2_task_refresh_lists)
				{
					gboolean (*cb) (gboolean) = qed->callback;
					(*cb) (FALSE);
				}
				if (rt->dialog != NULL)	//too-slow dialog still in play
					gtk_widget_destroy (rt->dialog);	//CHECKME gdk mutex state ?
				//just 1 cleanup
				e2_fileview_clean_selected (qed->names);
			}
		}
	}

	athreadID = 0;
	e2_filelist_reset_refresh ();

	if (all)
	{
		//make sure Q is clear
		e2_command_clear_pending (NULL, NULL);
	}
	else
	{
		//restart action thread on next item in Q, if any
		g_timeout_add_full (G_PRIORITY_HIGH, 0,
			(GSourceFunc) _e2_task_run_processQ, NULL, NULL);
	}
}
/**
@brief thread function to block while a task is running or paused

@param status ptr to store of monitored status-flag

@return NULL
*/
/*static gpointer _e2_task_watch_status (E2_TaskRuntime *rt)
{
	e2_utils_block_thread_signals ();	//block all allowed signals to this thread

	while (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)
	{
		usleep (66000);	//15Hz checks for status-change
	}
	return NULL;
} */
/**
@brief wait for a task to complete, and return its completion status
This is for task-functions that must return the actual task status
There is no timeout check performed here.
@param rt ptr to data for the task to monitor, NULL to use task at end of Q

@return TRUE if the task completed successfully
*/
static gboolean _e2_task_wait (E2_TaskRuntime *rt)
{
	if (rt == NULL)
	{
		GList *member;
		pthread_mutex_lock (&task_mutex);
		member = g_list_last (app.taskhistory);
		pthread_mutex_unlock (&task_mutex);
		if (member != NULL)
			rt = (E2_TaskRuntime *)member->data;
	}
	if (rt == NULL)
		return FALSE;

/*	pthread_t wthreadID;
	if (pthread_create (&wthreadID, NULL, (gpointer) _e2_task_watch_status, rt) == 0)
		printd (DEBUG,"watch-thread (ID=%lu) started", wthreadID);
	else
	{
		//FIXME warn the user
		printd (WARN,"watch-thread creation error!");
		return FALSE;
	}
	pthread_join (wthreadID, NULL);
*/
	//poll for status-change (15Hz to miss other timer callbacks)
	//FIXME do this with less impact, handle failure
	while (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)
		usleep (66000);

	gboolean retval;
	if (rt->status == E2_TASK_COMPLETED)
		retval = (rt->action) ? rt->ex.action.result : (rt->ex.command.exit == 0);
	else
		retval = FALSE;
	printd (DEBUG,"wait finished, task status is %s", (retval) ? "true" : "false");
	return retval;
}
/**
@brief callback for responses from too-slow-dialog @a dialog
This approach eliminates gtk_main() (which hates being aborted) from the dialog
@param dialog the dialog from which the response was initiated
@param response the response enumerator
@param rt task-data specified when the callback was connected
@return
*/
static void _e2_task_progress_monitor_slow_response_cb (GtkDialog *dialog,
	gint response, E2_TaskRuntime *rt)
{
	switch (response)
	{
		case GTK_RESPONSE_NO:	//abort the operation
		case E2_RESPONSE_YESTOALL:	//no more reminders
			pthread_mutex_lock (&task_mutex);
			if (rt->dialog != NULL)	//race-management
			{
				gtk_widget_destroy (rt->dialog);	//== (GTK_WIDGET(dialog)
//				WAIT_FOR_EVENTS;	//make the window repaint work
				rt->dialog = NULL;
			}
			pthread_mutex_unlock (&task_mutex);
			if (mthreadID > 0) //race-management
			{
				pthread_cancel (mthreadID);
				//if the racy action-thread ends about now, signal to it that it
				//does not need to abort this thread
				mthreadID = 0;
			}
			if (response == GTK_RESPONSE_NO	//not just cancelling reminders
				&& athreadID > 0)	//operation still not finished
			{
				//shutdown action thread
				pthread_cancel (athreadID);
				athreadID = 0;

				rt->status = E2_TASK_INCOMPLETE;
				E2_ActionTaskData *qed = &rt->ex.action;
				qed->result = FALSE;

				gchar *msg = g_strdup_printf
					(_("%s operation incomplete - time limit exceeded"),
						rt->ex.action.action->name);
				e2_output_print_error (msg, TRUE);

				//refreshing may be disabled or not, depending on the task
				//and its embedded dialog(s)
				//so reset the refresh process after this interruption
				e2_filelist_reset_refresh ();
				e2_window_clear_status_message ();

				if (qed->callback != NULL && qed->callback != e2_task_refresh_lists)
				{
					gboolean (*cb) (gboolean) = qed->callback;
					//conform BGL state to main thread when that's doing a report
					gdk_threads_leave ();
					(*cb) (FALSE);
					gdk_threads_enter ();
				}
				//setup refresh Q as required
				if (qed->callback == e2_task_refresh_lists)
				{
					e2_task_refresh_lists (qed);
				}

				//just 1 cleanup
				e2_fileview_clean_selected (qed->names);

				//restart action thread on next item in Q, if any
				g_timeout_add_full (G_PRIORITY_HIGH, 0,
					(GSourceFunc) _e2_task_run_processQ, NULL, NULL);
				}
		default:
//		case GTK_RESPONSE_YES:	//keep waiting
			pthread_mutex_lock (&task_mutex);
			if (rt->dialog != NULL)	//race-management
			{
				gtk_widget_hide (rt->dialog);
//				WAIT_FOR_EVENTS;	//make the hide work
			}
			pthread_mutex_unlock (&task_mutex);
			break;
	}
}
/**
@brief thread function to enforce timeout on processing a queued task
Expects valid athreadID for the action thread
@param rt ptr to data for the currently-running task

@return NULL
*/
static gpointer _e2_task_progress_monitor (E2_TaskRuntime *rt)
{
	e2_utils_block_thread_signals ();	//block all allowed signals to this thread

	//on some systems this func may be called before the thread-creation
	//function sets mthreadID, but that should not matter ...
	rt->dialog = NULL;

	while (TRUE)	//loop until user kills this thread (via dialog) or the
					//action-thread kills this thread
	{
		gchar *msg;
//		gboolean refreshed = FALSE;
		gint seconds = e2_option_int_get ("task-timeout-interval");
		if (seconds < 1)	//should never happen, but ...
			seconds = 1;
		//1 Hz check for thread stopped
		while (seconds > 0)
		{
			sleep (1);
			if (rt->status == E2_TASK_RUNNING)
				seconds--;
		}
		//if the task is still paused, probably a dialog is showing,
		//and we don't want to interfere with that (BUT potential hang ?!)
		while (rt->status == E2_TASK_PAUSED)
			sleep (1);
		//if we get to here, it's timed out
		if (athreadID > 0)
		{
			printd (DEBUG,"action-thread (ID=%lu) timed out", (gulong) athreadID);
			if (rt->dialog == NULL)
			{
				//once-only, create the dialog
				msg = g_strdup_printf (_("The current operation (%s)"),
					rt->ex.action.action->name);
				rt->dialog = e2_dialog_slow (msg, _("operation"),
					_e2_task_progress_monitor_slow_response_cb, rt);
				g_free (msg);
			}
			//if dialog was hidden from last iteration, show it again
			if (rt->dialog != NULL && !GTK_WIDGET_VISIBLE (rt->dialog))
			{
				gdk_threads_enter ();
				gtk_widget_show (rt->dialog);
				gtk_window_present (GTK_WINDOW (rt->dialog));
				gdk_threads_leave ();
			}
		}
	}
	return NULL;	//should never get to here
}
/**
@brief initialise immediate or queued task
If @a pane_data is TRUE, for file.* actions the current-pane selected items
are logged only if art->data is NULL or ""
If @a pane_data is FALSE, static pointers: names_array, srcdir_local,
destdir_local are used instead of defaults
@param type code indicating the type of task to be performed
@param art pointer to action runtime data struct sent to the action
@param from the widget activated to trigger the action, possibly NULL
@param taskfunc function to be called to perform the task
@param callback function to be called when task completed (may be NULL)
@param immediate TRUE to run the task in its own thread, FALSE to Q the task
@param pane_data TRUE to create array of items to process

@return TRUE if setup was successful
*/
gboolean e2_task_run_task (E2_TaskType type, E2_ActionRuntime *art,
	gpointer from, gpointer taskfunc, gpointer callback, gboolean immediate,
	gboolean pane_data)
{
	GPtrArray *names;
	gchar *specific_path = NULL;
	if (pane_data)
	{
		if (g_str_has_prefix (art->action->name, _A(5)))
		{	//this is an action potentially applying to selected item(s)
			if (art->data != NULL && *((gchar *)art->data) != '\0')
				 //use items in argument string
				names = _e2_task_get_names ((gchar *)art->data, &specific_path);
			else
				//no specific data, use selected items
				names = e2_fileview_get_selected (curr_view);
			if (names == NULL)
				return FALSE;
		}
		else	//e.g. command.mkdir
			names = NULL;
	}
	else	//use supplied names array
		names = names_array;

	E2_TaskRuntime *rt = NULL;

	//keep trying this until data set fn not busy
	//the real pid is set elsewhere, when the task is performed
	while ((rt = e2_task_set_data (0, E2_TASKTYPE_ACTION, NULL)) == NULL);

	if (rt == GINT_TO_POINTER (1))
	{
		//FIXME error message for user
		//FIXME names_aray leak
		return FALSE;
	}

	E2_ActionTaskData *qed = &rt->ex.action;
	qed->tasktype = type;
	qed->result = FALSE;
	//all item-name strings are localized
	if (pane_data)
	{
		qed->currdir = (specific_path == NULL) ?
			D_FILENAME_TO_LOCALE (curr_view->dir) : specific_path;
		qed->othrdir = D_FILENAME_TO_LOCALE (other_view->dir);
	}
	else
	{
		qed->currdir = srcdir_local;
		qed->othrdir = destdir_local;
	}
#ifdef E2_VFS
	qed->currspace = curr_view->spacedata;
	qed->othrspace = other_view->spacedata;
#endif
	qed->names = names;
	qed->action = art->action;
	qed->rt_data = (art->data == NULL) ? NULL : g_strdup (art->data);
	qed->initiator = (GtkWidget *)from;
	qed->taskfunc = taskfunc;
	qed->callback = callback;

	if (immediate)
	{	//process this one independently
		g_timeout_add_full (G_PRIORITY_HIGH, 0,
			(GSourceFunc) _e2_task_run_processnow, rt, NULL);
	}
	else
	{	//process as part of Q
		rt->status = E2_TASK_QUEUED;	//enable processing by a running thread
		//Q-thread needs to be started with BGL off
		g_timeout_add_full (G_PRIORITY_HIGH, 0,
			(GSourceFunc) _e2_task_run_processQ, rt, NULL);
	}
	return TRUE;
}
/**
@brief refresh both filelists
This is primarily for post-task filelists update.
Both displayed filelists will be refreshed as soon as any in-progress
re-list (cd or refresh) is completed
@param qed pointer to data struct for the task being processed

@return
*/
void e2_task_refresh_lists (E2_ActionTaskData *qed)
{
//	printd (DEBUG, "initiate refresh of both filelists");
#ifdef E2_FAM
	gchar *utf;
	utf = F_FILENAME_FROM_LOCALE (qed->currdir);
	e2_filelist_request_refresh (utf, FALSE);
	F_FREE (utf);
	utf = F_FILENAME_FROM_LOCALE (qed->othrdir);
	e2_filelist_request_refresh (utf, TRUE);
	F_FREE (utf);
#else
	e2_filelist_check_dirty (GINT_TO_POINTER (1));
#endif
}
/**
@brief refresh content of both pane views
Downstream functions expect gtk's BGL to be closed
@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if the request was logged
*/
gboolean e2_task_refresh (gpointer from, E2_ActionRuntime *art)
{
#ifdef E2_FAM
# ifdef E2_VFSTMP
	gboolean withother = other_view->spacedata == NULL || other_view->spacedata->monitored;
	gboolean retval =
		(curr_view->spacedata == NULL || curr_view->spacedata->monitored) ?
		e2_filelist_request_refresh (curr_view->dir, !withother) : TRUE;
	if (withother)
		retval = e2_filelist_request_refresh (other_view->dir, TRUE) && retval;
# else
	//2 tests, to lessen race due to dir-swap during first call
	gboolean retval = e2_filelist_request_refresh (curr_view->dir, FALSE);
	retval = e2_filelist_request_refresh (other_view->dir, TRUE) || retval;
# endif
	return retval;
#else
	e2_filelist_check_dirty (GINT_TO_POINTER (1));
	return TRUE;
#endif
}
/**
@brief show statusline message about task-in-progress
@return
*/
void e2_task_advise (void)
{
	e2_window_show_status_message (_("File operation in progress"));
}
/**
@brief get temp item name if needed
@param localpath localised path of item to check
@return pointer to name string, = @a path, or newly-allocated replacement
*/
gchar *e2_task_tempname (const gchar *localpath)
{
	struct stat dest_sb;
	E2_ERR_DECLARE
#ifdef E2_VFS
	VPATH data = { localpath, NULL };

	if (e2_fs_lstat (&data, &dest_sb E2_ERR_PTR()) && E2_ERR_IS (ENOENT))
#else
	if (e2_fs_lstat (localpath, &dest_sb E2_ERR_PTR()) && E2_ERR_IS (ENOENT))
#endif
	{
		E2_ERR_CLEAR
		return (gchar*)localpath;
	}
	E2_ERR_CLEAR
	return (e2_utils_get_tempname (localpath));
}
/**
@brief dialog response callback
@param dialog the dialog which generated the response signal
@param response the response enumerator
@param dialog_result pointer to store for @a response
@return
*/
static void _e2_task_response_cb (GtkDialog *dialog, gint response,
	gint *dialog_result)
{
	*dialog_result = response;
	gtk_main_quit ();
}
/**
@brief helper function for checking recursive merge item-count
This is a callback for a treewalk function
@param local_name path of item reported by the walker, localised string
@param statbuf pointer to struct stat with data about @a local_name
@param status code from the walker, indicating what type of item is being reported
@param count pointer to store for items count
@return E2TW_CONTINUE until @a count is > 1, then E2TW_STOP
*/
static E2_TwResult _e2_task_count_twcb (VPATH *local_name,
	const struct stat *statbuf, E2_TwStatus status, guint *count)
{
	E2_TwResult result;
	switch (status)
	{
		case E2TW_F:	//not directory or link
		case E2TW_SL:	//symbolic link
		case E2TW_SLN:	//symbolic link naming non-existing file
			(*count)++;
			result = (*count > 1) ? E2TW_STOP : E2TW_CONTINUE ;
			break;
		default:
			result = E2TW_CONTINUE;
			break;
	}
	return result;
}
/**
@brief check whether a recursive merge task will process multiple items
This counts only non-dirs, i.e ignores dirs that are missing from the
destination tree
Assumes BGL is open
@param sdir_local path of dir containing items to be processed, localised string
@param names_local array of localised item-names
@return TRUE if multiple items will be processed
*/
static gboolean _e2_task_count_recursive (VPATH *sdir_local, GPtrArray *names_local)
{
	guint count, items = 0;
#ifdef E2_VFS
	VPATH sdata;
	sdata.spacedata = sdir_local->spacedata;
#endif
	GString *src = g_string_sized_new (1024);
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names_local->pdata;
	for (count=0; count < names_local->len; count++, iterator++)
	{
		//if (!
#ifdef E2_VFS
		g_string_printf (src, "%s%s", VPSTR(sdir_local), (*iterator)->filename);
		sdata.localpath = src->str;
		e2_fs_tw (&sdata, _e2_task_count_twcb, &items, -1,
#else
		g_string_printf (src, "%s%s", sdir_local, (*iterator)->filename);
		e2_fs_tw (src->str, _e2_task_count_twcb, &items, -1,
#endif
			E2TW_PHYS | E2TW_NODIR E2_ERR_NONE());
		//)
		//{
			//FIXME handle error
		//}
		if (items > 1)
			break;
	}
	return (items > 1);
}

/**
@brief check whether a move|copy task would put a dir into any of its own descendants
@param srcpath local path of source item
@param destpath local path of source item
Links can't be checked as the name doesn't exist yet, but its parent does
@return TRUE if the operation is NOT ok
*/
static gboolean _e2_task_circular (
#ifdef E2_VFS
	VPATH *sdata, VPATH *ddata
#else
	const gchar *srcpath, const gchar *destpath
#endif
)
{
#ifdef E2_VFS
	if (sdata->space_data != ddata->space_data
		|| !e2_fs_is_dir3 (sdata E2_ERR_NONE()))
		return FALSE;
#else
	if (!e2_fs_is_dir3 (srcpath E2_ERR_NONE()))
		return FALSE;
#endif
	//get real paths of dirs
#ifdef E2_VFSTMP
	FIXME
#else
	gchar *real_src = g_strdup (srcpath);
	e2_fs_walk_link (&real_src);
	if (!g_str_has_suffix (real_src, G_DIR_SEPARATOR_S))
	{
		gchar *freeme = real_src;
		real_src = g_strconcat (real_src, G_DIR_SEPARATOR_S, NULL);
		g_free (freeme);
	}
	gchar *real_dest = g_strdup (destpath);
	e2_fs_walk_link (&real_dest);
#endif
	gboolean retval = (strncmp (real_src, real_dest, strlen (real_src)) == 0);
	if (retval)
		e2_output_print_error (_("Operation not permitted - Circular directories"), FALSE);
	g_free (real_src);
	g_free (real_dest);
	return retval;
}

  /************************/
 /**** task functions ****/
/************************/

/**
@brief copy selected items from active to inactive pane

Overwrite checking is performed, if that option is enabled
The actual operation is performed by a separate back-end function

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_copy (gpointer from, E2_ActionRuntime *art)
{
//	printd (DEBUG, "task: copy");
	return (e2_task_enqueue_task (E2_TASK_COPY, art, from,
		_e2_task_copyQ, e2_task_refresh_lists));
}
static gboolean _e2_task_copyQ (E2_ActionTaskData *qed)
{
//	printd (DEBUG, "task: copyQ");
	if (g_str_equal (qed->currdir, qed->othrdir))
	{
		//display some message ??
		return FALSE;
	}
	GPtrArray *names = qed->names;
	gchar *curr_local = qed->currdir;
	gchar *other_local = qed->othrdir;
#ifdef E2_VFS
	VPATH sdata = { NULL, qed->currspace };
	VPATH ddata = { NULL, qed->othrspace };
#endif
	E2_FileTaskMode mode = GPOINTER_TO_INT (qed->action->data);
	GString *src = g_string_sized_new (1024);
	GString *dest = g_string_sized_new (1024);
	guint count;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	gboolean check = e2_option_bool_get ("confirm-overwrite")
#ifdef E2_VFS
		&& qed->currspace == qed->othrspace
#endif
	;
	//setup for tailoring over-write dialog
	gboolean multisrc;
	if (!check)
		multisrc = FALSE;
	else
	{
#ifdef E2_VFS
		sdata.localpath = curr_local;
#endif
		multisrc = (mode & E2_FTM_MERGE) ?
#ifdef E2_VFS
			_e2_task_count_recursive (&sdata, names)
#else
			_e2_task_count_recursive (curr_local, names)
#endif
		: names->len > 1;
	}
	OW_ButtonFlags extras = (multisrc) ? BOTHALL : NONE;

	//no window desensitize ...
	e2_task_advise ();
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, copy task");
#endif
	e2_filelist_disable_refresh ();  //prevent pauses in the copy process

	for (count=0; count < names->len; count++, iterator++)
	{
		//".." entries filtered when names compiled;
		g_string_printf (src, "%s%s", curr_local, (*iterator)->filename);  //separator comes with dir
		g_string_printf (dest, "%s%s", other_local, (*iterator)->filename);

#ifdef E2_VFS
		sdata.localpath = src->str;
		ddata.localpath = dest->str;
		if (_e2_task_circular (&sdata, &ddata))
			continue;
		if ((!(mode & E2_FTM_MERGE)	//normal copy
				|| !e2_fs_is_dir3 (&sdata E2_ERR_NONE())
				|| !e2_fs_is_dir3 (&ddata E2_ERR_NONE())	//not merging 2 dirs
			   )
			&& check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0
		   )
#else
		if (_e2_task_circular (src->str, dest->str))
			continue;
		if ((!(mode & E2_FTM_MERGE)	//normal copy
				|| !e2_fs_is_dir3 (src->str E2_ERR_NONE())
				|| !e2_fs_is_dir3 (dest->str E2_ERR_NONE())	//not merging 2 dirs
			   )
			&& check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0
		   )
#endif
		{
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, copy dialog");
#endif
			e2_filelist_enable_refresh ();  //allow updates while we wait
			gdk_threads_enter ();
			*qed->status = E2_TASK_PAUSED;
			DialogButtons result = e2_dialog_ow_check (src->str, dest->str, extras);
			*qed->status = E2_TASK_RUNNING;
			gdk_threads_leave ();
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, copy dialog");
#endif
			e2_filelist_disable_refresh ();
			switch (result)
			{
				case YES_TO_ALL:
					check = FALSE;
				case OK:
					if (mode & E2_FTM_MERGE)
					{	//pass the check flag to backend, in case there is no
						//confirmation at top level
						if (check)
							mode |= E2_FTM_CHECK;
						else
							mode &= ~E2_FTM_CHECK;
					}
					e2_task_backend_copy
#ifdef E2_VFS
					(&sdata, &ddata, mode);
#else
					(src->str, dest->str, mode);
#endif
					//update local copy of check flag
					if ((mode & E2_FTM_MERGE) && check && !(mode & E2_FTM_CHECK))
						check = FALSE;
				case CANCEL:
					break;
				default:
					result = NO_TO_ALL;
					break;
			}
			if (result == NO_TO_ALL)
				break;
		}
		else  //file doesn't exist, or don't care
		{
			if (mode & E2_FTM_MERGE)
			{	//pass the check flag to backend, in case there is no
				//confirmation at top level
				if (check)
					mode |= E2_FTM_CHECK;
				else
					mode &= ~E2_FTM_CHECK;
			}
			e2_task_backend_copy
#ifdef E2_VFS
			(&sdata, &ddata, mode);
#else
			(src->str, dest->str, mode);
#endif
			if ((mode & E2_FTM_MERGE) && check && !(mode & E2_FTM_CHECK))
				check = FALSE;
		}
	}
	g_string_free (src, TRUE);
	g_string_free (dest, TRUE);
	e2_window_clear_status_message ();
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, copy task");
#endif
	e2_filelist_enable_refresh ();
//	printd (DEBUG, "task: copyQ FINISHED");
	return TRUE;
}
/**
@brief copy selected items from active to inactive pane, with renaming

Overwrite checking is performed, if that option is enabled
The actual operation is performed by a separate back-end function

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_copy_as (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_COPYAS, art, from,
		_e2_task_copy_asQ, e2_task_refresh_lists));
}
static gboolean _e2_task_copy_asQ (E2_ActionTaskData *qed)
{
	GPtrArray *names = qed->names;
	gchar *curr_local = qed->currdir;
	gchar *other_local = qed->othrdir;
#ifdef E2_VFS
	VPATH sdata = { NULL, qed->currspace };
	VPATH ddata = { NULL, qed->othrspace };
#endif
	GString *prompt = g_string_sized_new (NAME_MAX+64);
	GString *src = g_string_sized_new (1024);
	GString *dest = g_string_sized_new (1024);
	gchar *converted, *new_name, *public;
	gboolean check = e2_option_bool_get ("confirm-overwrite")
#ifdef E2_VFS
		&& qed->currspace == qed->othrspace
#endif
	;
	guint count;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	//setup for tailoring over-write dialog
	gboolean multisrc =  (check) ? names->len > 1 : FALSE;
	// setup for showing stop button in rename dialog
	OW_ButtonFlags extras = (multisrc)? NOALL : NONE;

#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, copy as task");
#endif
	e2_filelist_disable_refresh ();
	e2_task_advise ();

	for (count=0; count < names->len; count++, iterator++)
	{
		//".." entries filtered when names compiled
		converted = F_FILENAME_FROM_LOCALE ((*iterator)->filename);
		public = g_markup_escape_text (converted, -1);
		g_string_printf (prompt, "%s: <b>%s</b>", _("Enter new name for"), public);
		g_free (public);
		DialogButtons result = 0;
	//FIXME = SPEED UP DIALOG CREATION OUTSIDE LOOP ?
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, copy as dialog");
#endif
		e2_filelist_enable_refresh ();  //allow updates while we wait
		gdk_threads_enter ();
		*qed->status = E2_TASK_PAUSED;
		DialogButtons result2 = e2_dialog_line_input (_("copy"), prompt->str,
			converted, extras, FALSE, &new_name);
		*qed->status = E2_TASK_RUNNING;
		gdk_threads_leave ();
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, copy as dialog");
#endif
		F_FREE (converted);
		e2_filelist_disable_refresh ();

		if (result2 == OK)
		{
			g_string_printf (src, "%s%s", curr_local, (*iterator)->filename);  //separator comes with dir
			converted = F_FILENAME_TO_LOCALE (new_name);
			g_string_printf (dest, "%s%s", other_local, converted);
			g_free (new_name);
			F_FREE (converted);
			if (strcmp(src->str, dest->str) == 0) continue;

#ifdef E2_VFS
			sdata.localpath = src->str;
			ddata.localpath = dest->str;
			if (_e2_task_circular (&sdata, &ddata))
				continue;
			if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0)
#else
			if (_e2_task_circular (src->str, dest->str))
				continue;
			if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0)
#endif
			{
#ifdef E2_REFRESH_DEBUG
				printd (DEBUG, "enable refresh, copy as dialog");
#endif
				e2_filelist_enable_refresh ();  //allow updates while we wait
				gdk_threads_enter ();
				*qed->status = E2_TASK_PAUSED;
				result = e2_dialog_ow_check (NULL, dest->str, extras);
				*qed->status = E2_TASK_RUNNING;
				gdk_threads_leave ();
				e2_filelist_disable_refresh ();
				if (result == OK)
					e2_task_backend_copy
#ifdef E2_VFS
					(&sdata, &ddata, E2_FTM_NORMAL);
#else
					(src->str, dest->str, E2_FTM_NORMAL);
#endif
/*				else if (result == YES_TO_ALL)
				{
					do something smart about multiple-renames
				} */
				else if (result == NO_TO_ALL)
					break;
			}
			else
				e2_task_backend_copy
#ifdef E2_VFS
				(&sdata, &ddata, E2_FTM_NORMAL);
#else
				(src->str, dest->str, E2_FTM_NORMAL);
#endif
		}
		else if (result2 == NO_TO_ALL)
			break;
	}
	g_string_free (prompt,TRUE);
	g_string_free (src,TRUE);
	g_string_free (dest,TRUE);
	e2_window_clear_status_message ();
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, copy as task");
#endif
	e2_filelist_enable_refresh ();
	return TRUE;
}
/**
@brief move selected items from active pane to trash location
THIS IS OUT-OF-DATE
Selected item(s) are moved to the last-used trash dir, which will be:
if it exists, folder named '.Trash' in current dir, or
if it exists, ~/.Trash, or
if it exists, config dir/Trash, or
not moved, and error msg is printed
No overwrite checking is performed.
The actual operation is performed by a separate back-end function

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_trashit (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_TRASH, art, from,
		_e2_task_trashitQ, e2_task_refresh_lists));
}
static gboolean _e2_task_trashitQ (E2_ActionTaskData *qed)
{
	//	printd (DEBUG, "task: trash");
	const gchar *trashpath = e2_utils_get_trash_path ();
	if (trashpath == NULL
		//do nothing if we're in the local trash place already
		|| (g_str_equal (trashpath, curr_view->dir)
#ifdef E2_VFS
			&& curr_view->spacedata == NULL
#endif
		))
	{
		//display some message
		return FALSE;
	}
	else
		return (_e2_task_move (qed, (gchar *) trashpath));
}
/**
@brief move selected items from active to inactive pane

Overwrite checking is performed, if that option is enabled
The actual operation is performed by a separate back-end function

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_move (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_MOVE, art, from,
		_e2_task_moveQ, e2_task_refresh_lists));
}
static gboolean _e2_task_moveQ (E2_ActionTaskData *qed)
{
//	printd (DEBUG, "task: move");
	if (g_str_equal (qed->currdir, qed->othrdir))
	{
		//display some message
		return FALSE;
	}
	else
		return (_e2_task_move (qed, NULL));
}
/**
@brief move selected items

@param from the button, menu item etc which was activated
@param art action runtime data
@param trashpath string with path to trash dir, or NULL if moving to inactive pane

@return TRUE if task completed successfully, else FALSE
*/
static gboolean _e2_task_move (E2_ActionTaskData *qed, gchar *trashpath)
{
	GPtrArray *names = qed->names;
	GString *src = g_string_sized_new (1024);
	GString *dest = g_string_sized_new (1024);
	gboolean trash = (trashpath != NULL);
	//curr_view->dir, other_view->dir are utf-8
	gchar *curr_local = qed->currdir;
	gchar *other_local = (trash) ?
		F_FILENAME_TO_LOCALE (trashpath) :	//CHECKME trailer
		qed->othrdir;
#ifdef E2_VFS
	VPATH sdata = { NULL, qed->currspace };
	VPATH ddata;
	ddata.spacedata = (trash) ? NULL : qed->othrspace;	//local trash only
#endif
	guint count;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	gboolean check = (trash) ? FALSE : e2_option_bool_get ("confirm-overwrite")
#ifdef E2_VFS
		&& qed->currspace == qed->othrspace
#endif
	;
	//setup for tailoring over-write dialog
	gboolean multisrc = (check) ? names->len > 1 : FALSE;
	OW_ButtonFlags extras = (multisrc) ? BOTHALL : NONE;

	e2_task_advise ();
	e2_filelist_disable_refresh ();  //avoid pauses in the move process

	for (count=0; count < names->len; count++, iterator++)
	{
		//".." entries filtered when names compiled
		g_string_printf (src, "%s%s", curr_local, (*iterator)->filename);  //separator comes with dir
		g_string_printf (dest, "%s%s", other_local, (*iterator)->filename);

#ifdef E2_VFS
		sdata.localpath = src->str;
		ddata.localpath = dest->str;
		if (_e2_task_circular (&sdata, &ddata))
			continue;
		if (trash && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0)
#else
		if (_e2_task_circular (src->str, dest->str))
			continue;
		if (trash && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0)
#endif
		{
			//trash without destroying existing file there
			gchar *tlocal = e2_utils_get_tempname (dest->str);
			g_string_assign (dest, tlocal);
			g_free (tlocal);
		}
#ifdef E2_VFS
		ddata.localpath = dest->str;
		if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0)
#else
		if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0)
#endif
		{
			e2_filelist_enable_refresh ();  //allow updates while we wait
			gdk_threads_enter ();
			*qed->status = E2_TASK_PAUSED;
			DialogButtons result = e2_dialog_ow_check (src->str, dest->str, extras);
			*qed->status = E2_TASK_RUNNING;
			gdk_threads_leave ();
			e2_filelist_disable_refresh ();
			switch (result)
			{
				case YES_TO_ALL:
					check = FALSE;
				case OK:
					e2_task_backend_move
#ifdef E2_VFS
					(&sdata, &ddata);
#else
					(src->str, dest->str);
#endif
/* full compliance with fdo trash spec requires adding to $trash/info directory
an “information file” for every file and directory in $trash/files.
This file MUST have exactly the same name as the file or directory in $trash/files,
plus the extension “.trashinfo”
The format of this file is like:
[Trash Info]
Path=foo/bar/meow.bow-wow
DeletionDate=20040831T22:32:08
The path string is localised (as used by the filesystem, with characters escaped
as in URLs (as defined by RFC 2396, section 2).
The date string is the date and time when the item was trashed, in
YYYY-MM-DDThh:mm:ss format (see RFC 3339). The time zone is the user's (or
filesystem's) local time.
*/
				case CANCEL:
					break;
				default:
					result = NO_TO_ALL;
					break;
			}
			if (result == NO_TO_ALL)
				break;
		}
		else  //file doesn't exist, or don't care
		{
			e2_task_backend_move
#ifdef E2_VFS
			(&sdata, &ddata);
#else
			(src->str, dest->str);
#endif
		}
	}
	g_string_free (src, TRUE);
	g_string_free (dest, TRUE);
	e2_window_clear_status_message ();
	e2_filelist_enable_refresh ();

	return TRUE;
}
/**
@brief move selected items from active to inactive pane, with renaming

Overwrite checking is performed, if that option is enabled
The actual operation is performed by a separate back-end function

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_move_as (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_MOVEAS, art, from,
		_e2_task_move_asQ, e2_task_refresh_lists));
}
static gboolean _e2_task_move_asQ (E2_ActionTaskData *qed)
{
	GPtrArray *names = qed->names;
	gchar *curr_local = qed->currdir;
	gchar *other_local = qed->othrdir;
#ifdef E2_VFS
	VPATH sdata = { NULL, qed->currspace };
	VPATH ddata = { NULL, qed->othrspace };
#endif
	GString *prompt = g_string_sized_new (NAME_MAX+64);
	GString *src = g_string_sized_new (1024);
	GString *dest = g_string_sized_new (1024);
	gchar *converted, *new_name, *public;
	gboolean check = e2_option_bool_get ("confirm-overwrite")
#ifdef E2_VFS
		&& qed->currspace == qed->othrspace
#endif
	;
	guint count;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	gboolean multisrc =  (check) ? names->len > 1 : FALSE;
	OW_ButtonFlags extras = (multisrc)? NOALL : NONE;

	e2_task_advise ();
	e2_filelist_disable_refresh ();

	for (count=0; count < names->len; count++, iterator++)
	{
		//".." entries filtered when names compiled
		converted = F_FILENAME_FROM_LOCALE ((*iterator)->filename);
		public = g_markup_escape_text (converted, -1);
		g_string_printf (prompt, "%s: <b>%s</b>", _("Enter new name for"), public);
		g_free (public);
		DialogButtons result = 0;
		e2_filelist_enable_refresh ();  //allow updates while we wait
		gdk_threads_enter ();
		*qed->status = E2_TASK_PAUSED;
		DialogButtons result2 = e2_dialog_line_input (_("move"), prompt->str,
			converted, extras, FALSE, &new_name);
		*qed->status = E2_TASK_RUNNING;
		gdk_threads_leave ();
		F_FREE (converted);
		e2_filelist_disable_refresh ();

		if (result2 == OK)
		{
			g_string_printf (src, "%s%s", curr_local, (*iterator)->filename);  //separator comes with dir
			converted = F_FILENAME_TO_LOCALE (new_name);
			g_string_printf (dest, "%s%s", other_local, converted);
			g_free (new_name);
			F_FREE (converted);
			if (strcmp (src->str, dest->str) == 0) continue;

#ifdef E2_VFS
			sdata.localpath = src->str;
			ddata.localpath = dest->str;
			if (_e2_task_circular (&sdata, &ddata))
				continue;
			if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0)
#else
			if (_e2_task_circular (src->str, dest->str))
				continue;
			if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0)
#endif
			{
				e2_filelist_enable_refresh ();  //allow updates while we wait
				gdk_threads_enter ();
				*qed->status = E2_TASK_PAUSED;
				result = e2_dialog_ow_check (NULL, dest->str, extras);
				*qed->status = E2_TASK_RUNNING;
				gdk_threads_leave ();
				e2_filelist_enable_refresh ();
				if (result == OK)
				{
					e2_task_backend_move
#ifdef E2_VFS
					(&sdata, &ddata);
#else
					(src->str, dest->str);
#endif
				}
/*				else if (result == YES_TO_ALL)
				{
					do something smart about multiple-renames ??
				} */
				else if (result == NO_TO_ALL)
					break;
			}
			else
			{
				e2_task_backend_move
#ifdef E2_VFS
				(&sdata, &ddata);
#else
				(src->str, dest->str);
#endif
			}
		}
		else if (result2 == NO_TO_ALL)
		{
//			result = NO_TO_ALL;
			break;
		}
	}
	g_string_free (prompt,TRUE);
	g_string_free (src,TRUE);
	g_string_free (dest,TRUE);
	e2_window_clear_status_message ();
	e2_filelist_enable_refresh ();

	return TRUE;
}
/**
@brief symlink selected items in active pane to the inactive pane

Overwrite checking is performed, if that option is enabled
The actual operation is performed by a separate back-end function

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_symlink (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_LINK, art, from,
		_e2_task_symlinkQ, e2_task_refresh_lists));
}
static gboolean _e2_task_symlinkQ (E2_ActionTaskData *qed)
{
	if (g_str_equal (qed->currdir, qed->othrdir))
	{
		//display some message
		return FALSE;
	}
	GPtrArray *names = qed->names;
	gchar *curr_local = qed->currdir;
	gchar *other_local = qed->othrdir;
#ifdef E2_VFS
	VPATH sdata = { NULL, qed->currspace };
	VPATH ddata = { NULL, qed->othrspace };
#endif
	GString *src = g_string_sized_new (1024);
	GString *dest = g_string_sized_new (1024);

	guint count;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	gboolean check = e2_option_bool_get ("confirm-overwrite")
#ifdef E2_VFS
		&& qed->currspace == qed->othrspace
#endif
	;
	//setup for tailoring over-write dialog
	gboolean multisrc = (check) ? names->len > 1 : FALSE;
	OW_ButtonFlags extras = (multisrc) ? BOTHALL : NONE;

	//no window desensitize ...
	e2_task_advise ();
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, copy task");
#endif
	e2_filelist_disable_refresh ();  //prevent pauses in the copy process

	for (count=0; count < names->len; count++, iterator++)
	{
		//".." entries filtered when names compiled;
		g_string_printf (src, "%s%s", curr_local, (*iterator)->filename);  //separator comes with dir
		g_string_printf (dest, "%s%s", other_local, (*iterator)->filename);

#ifdef E2_VFS
		sdata.locapath = src->str;
		ddata.localpath = dest->str;
//		if (_e2_task_circular (&sdata, &ddata)) FIXME parent of name
//			continue;
		if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0)
#else
//		if (_e2_task_circular (src->str, dest->str))
//			continue;
		if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0)
#endif
		{
			e2_filelist_enable_refresh ();  //allow updates while we wait
			gdk_threads_enter ();
			*qed->status = E2_TASK_PAUSED;
			DialogButtons result = e2_dialog_ow_check
				(src->str, dest->str, extras);
			*qed->status = E2_TASK_RUNNING;
			gdk_threads_leave ();
			e2_filelist_disable_refresh ();
			switch (result)
			{
				case YES_TO_ALL:
					check = FALSE;
				case OK:
					e2_task_backend_link
#ifdef E2_VFS
					(&sdata, &ddata);
#else
					(src->str, dest->str);
#endif
				case CANCEL:
					break;
				default:
					result = NO_TO_ALL;
					break;
			}
			if (result == NO_TO_ALL)
				break;
		}
		else  //file doesn't exist, or don't care
		{
			e2_task_backend_link
#ifdef E2_VFS
			(&sdata, &ddata);
#else
			(src->str, dest->str);
#endif
		}
	}
	g_string_free (src, TRUE);
	g_string_free (dest, TRUE);
	e2_window_clear_status_message ();
	e2_filelist_enable_refresh ();
	return TRUE;
}
/**
@brief symlink selected items in active pane to the inactive pane, with renaming

Overwrite checking is performed, if that option is enabled
The actual operation is performed by a separate back-end function

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_symlink_as (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_LINKAS, art, from,
		_e2_task_symlink_asQ, e2_task_refresh_lists));
}
static gboolean _e2_task_symlink_asQ (E2_ActionTaskData *qed)
{
	GPtrArray *names = qed->names;
	gchar *curr_local = qed->currdir;
	gchar *other_local = qed->othrdir;
#ifdef E2_VFS
	VPATH sdata = { NULL, qed->currspace };
	VPATH ddata = { NULL, qed->othrspace };
#endif
	GString *prompt = g_string_sized_new (NAME_MAX+64);
	GString *src = g_string_sized_new (1024);
	GString *dest = g_string_sized_new (1024);
	gchar *converted, *new_name, *public;
	gboolean check = e2_option_bool_get ("confirm-overwrite")
#ifdef E2_VFS
		&& qed->currspace == qed->othrspace
#endif
	;
	guint count;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	//setup for tailoring over-write dialog
	gboolean multisrc =  (check) ? names->len > 1 : FALSE;
	// setup for showing stop button in rename dialog
	OW_ButtonFlags extras = (multisrc)? NOALL : NONE;

	e2_filelist_disable_refresh ();
	e2_task_advise ();

	for (count=0; count < names->len; count++, iterator++)
	{
		//".." entries filtered when names compiled
		converted = F_FILENAME_FROM_LOCALE ((*iterator)->filename);
		public = g_markup_escape_text (converted, -1);
		g_string_printf (prompt, "%s: <b>%s</b>", _("Enter new name for"), public);
		g_free (public);
		DialogButtons result = 0;
		e2_filelist_enable_refresh ();  //allow updates while we wait
		gdk_threads_enter ();
		*qed->status = E2_TASK_PAUSED;
		DialogButtons result2 = e2_dialog_line_input (_("link"), prompt->str,
			converted, extras, FALSE, &new_name);
		*qed->status = E2_TASK_RUNNING;
		gdk_threads_leave ();
		F_FREE (converted);
		e2_filelist_disable_refresh ();

		if (result2 == OK)
		{
			g_string_printf (src, "%s%s", curr_local, (*iterator)->filename);  //separator comes with dir
			converted = F_FILENAME_TO_LOCALE (new_name);
			g_string_printf (dest, "%s%s", other_local, converted);
			g_free (new_name);
			F_FREE (converted);
			if (strcmp(src->str, dest->str) == 0) continue;

#ifdef E2_VFS
			sdata.localpath = src->str;
			ddata.localpath = dest->str;
//			if (_e2_task_circular (&sdata, &ddata)) FIXME parent of name
//				continue;
			if (check && e2_fs_access2 (&ddata E2_ERR_NONE()) == 0)
#else
//			if (_e2_task_circular (src->str, dest->str))
//				continue;
			if (check && e2_fs_access2 (dest->str E2_ERR_NONE()) == 0)
#endif
			{
#ifdef E2_REFRESH_DEBUG
				printd (DEBUG, "enable refresh, copy as dialog");
#endif
				e2_filelist_enable_refresh ();  //allow updates while we wait
				gdk_threads_enter ();
				*qed->status = E2_TASK_PAUSED;
				result = e2_dialog_ow_check (NULL, dest->str, extras);
				*qed->status = E2_TASK_RUNNING;
				gdk_threads_leave ();
				e2_filelist_disable_refresh ();
				if (result == OK)
				{
					e2_task_backend_link
#ifdef E2_VFS
					(&sdata, &ddata);
#else
					(src->str, dest->str);
#endif
				}
/*				else if (result == YES_TO_ALL)
				{
					do something smart about multiple-renames
				} */
				else if (result == NO_TO_ALL)
					break;
			}
			else
			{
				e2_task_backend_link
#ifdef E2_VFS
				(&sdata, &ddata);
#else
				(src->str, dest->str);
#endif
			}
		}
		else if (result2 == NO_TO_ALL)
		{
			break;
		}
	}
	g_string_free (prompt,TRUE);
	g_string_free (src,TRUE);
	g_string_free (dest,TRUE);
	e2_window_clear_status_message ();
	e2_filelist_enable_refresh ();
	return TRUE;
}
/**
@brief delete selected items in the active pane

Confirmation is sought, if that option is enabled
The actual operation is performed by a separate back-end function

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_delete (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_DELETE, art, from,
		_e2_task_deleteQ, e2_task_refresh_lists));
}
static gboolean _e2_task_deleteQ (E2_ActionTaskData *qed)
{
	printd (DEBUG, "task: delete");
	GPtrArray *names = qed->names;
	gchar *curr_local = qed->currdir;
#ifdef E2_VFS
	VPATH sdata = { NULL, qed->currspace };
#endif
	GString *prompt = g_string_sized_new (NAME_MAX + 64);
	GString *src = g_string_sized_new (NAME_MAX);
	gchar *utf, *public;

	guint count;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	gboolean check = e2_option_bool_get ("confirm-delete");
	gboolean multisrc =  (check) ? names->len > 1 : FALSE;
	gint result = 0;

	e2_filelist_disable_refresh ();
	e2_task_advise ();

	for (count=0; count < names->len; count++, iterator++)
	{
		//".." entries filtered when names compiled
		g_string_printf (src, "%s%s", curr_local, (*iterator)->filename);

		if (check)
		{
			utf = F_DISPLAYNAME_FROM_LOCALE ((*iterator)->filename);
			public = g_markup_escape_text (utf, -1);
			g_string_printf (prompt, _("Are you sure you want to delete <b>%s</b>?"), public);
			F_FREE (utf);
			g_free (public);
			GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION,
				prompt->str, _("confirm"), _e2_task_response_cb, &result);
			if (multisrc)
				e2_dialog_set_negative_response (dialog, E2_RESPONSE_NOTOALL);
			else
			{
				E2_BUTTON_NOTOALL.showflags |= E2_BTN_GREY;
				E2_BUTTON_YESTOALL.showflags |= E2_BTN_GREY;
			}
			E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT;
			E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT;
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, delete dialog");
#endif
			e2_filelist_enable_refresh ();  //allow updates while we wait
			gdk_threads_enter ();
			*qed->status = E2_TASK_PAUSED;
			e2_dialog_show (dialog, app.main_window,
				0,
				&E2_BUTTON_NOTOALL, &E2_BUTTON_YESTOALL,
				&E2_BUTTON_CANCEL, &E2_BUTTON_OK, NULL);
			gtk_main ();
			gtk_widget_destroy (dialog);
			*qed->status = E2_TASK_RUNNING;
			gdk_threads_leave ();
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, delete dialog");
#endif
			e2_filelist_disable_refresh ();

			switch (result)
			{
				case E2_RESPONSE_YESTOALL:
					check = FALSE;
				case GTK_RESPONSE_OK:
#ifdef E2_VFS
					sdata.localpath = src->str;
					e2_task_backend_delete (&sdata);
#else
					e2_task_backend_delete (src->str);
#endif
				case GTK_RESPONSE_CANCEL:
					break;
//				case E2_RESPONSE_NOTOALL:
				default:
					result = E2_RESPONSE_NOTOALL;
					break;
			}
		}
		else  //no checking
		{
#ifdef E2_VFS
			sdata.localpath = src->str;
			e2_task_backend_delete (&sdata);
#else
			e2_task_backend_delete (src->str);
#endif
		}
		if (result == E2_RESPONSE_NOTOALL)
			break;
	}
	g_string_free (prompt, TRUE);
	g_string_free (src, TRUE);
	e2_window_clear_status_message ();
	e2_filelist_enable_refresh ();

	return TRUE;
}
/**
@brief delete all items from trash location
There are no warnings for any deletion, here
Assumes BGL is closed on arrival
@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_trashempty (gpointer from, E2_ActionRuntime *art)
{
//	printd (DEBUG, "task: empty trash");
	const gchar *trashpath = e2_utils_get_trash_path ();	//has trailer
	if (trashpath != NULL)
	{
		GList *entries, *member;
		gchar *dlocal, *localpath, *itemname;
		e2_filelist_disable_refresh ();
		dlocal = F_FILENAME_TO_LOCALE ((gchar *)trashpath);
#ifdef E2_VFS
		VPATH ddata;
		ddata.spacedata = NULL;	//only local spaces for trash
#endif

#ifdef E2_VFS
		ddata.localpath = dlocal;
		gdk_threads_leave ();
 		entries = (GList *)e2_fs_dir_foreach (&ddata,
#else
		gdk_threads_leave ();
 		entries = (GList *)e2_fs_dir_foreach (dlocal,
#endif
			E2_DIRWATCH_NO,	//trash is local, fast read
			NULL, NULL, NULL  E2_ERR_NONE());

		if (E2DREAD_FAILED (entries))
		{
			gdk_threads_enter ();
			//FIXME warn user about error
			e2_filelist_enable_refresh ();
			F_FREE (dlocal);
			return FALSE;
		}
		for (member = entries; member != NULL; member=member->next)
		{
			itemname = (gchar *)member->data;
			if (!g_str_equal (itemname, ".."))
			{
				localpath = e2_utils_strcat (dlocal, itemname);
#ifdef E2_VFS
				ddata.localpath = localpath;
#endif
				gdk_threads_leave ();	//downstream error messages manage BGL locally
#ifdef E2_VFS
				e2_task_backend_delete (&ddata);
#else
				e2_task_backend_delete (localpath);
#endif
				gdk_threads_enter ();
				g_free (localpath);
			}
			g_free (itemname);
		}
		if (entries != NULL)
			g_list_free (entries);
		F_FREE (dlocal);
		//there may also be a fdo-compliant data dir to clean
		localpath = g_strrstr (trashpath, "/files");	//no need for utf8 scan
		if (localpath != NULL) //should always be TRUE
		{
			gsize len = localpath - trashpath + sizeof(gchar);	//include the separator
			gchar *dup = g_strndup (trashpath, len);
			gchar *sp = g_strconcat (dup, "info"G_DIR_SEPARATOR_S, NULL);	//no translation needed
			dlocal = F_FILENAME_TO_LOCALE (sp);
#ifdef E2_VFS
			ddata.localpath = dlocal;
			if (!e2_fs_access (&ddata, W_OK E2_ERR_NONE()))
#else
			if (!e2_fs_access (dlocal, W_OK E2_ERR_NONE()))
#endif
			{
#ifdef E2_VFS
				entries = (GList *)e2_fs_dir_foreach (&ddata,
#else
				entries = (GList *)e2_fs_dir_foreach (dlocal,
#endif
					E2_DIRWATCH_NO,	//trash is local, fast read
					NULL, NULL, NULL E2_ERR_NONE());
				if (!E2DREAD_FAILED (entries))
				{
					for (member = entries; member != NULL; member=member->next)
					{
						itemname = (gchar *)member->data;
						if (!g_str_equal (itemname, ".."))
						{
							localpath = e2_utils_strcat (dlocal, itemname);
#ifdef E2_VFS
							ddata.localpath = localpath;
#endif
#ifdef E2_VFS
							e2_task_backend_delete (&ddata);
#else
							e2_task_backend_delete (localpath);	//self-manages BGL
#endif
							g_free (localpath);
						}
						g_free (itemname);
					}
					if (entries != NULL)
						g_list_free (entries);
				}
			}
			g_free (dup);
			g_free (sp);
			F_FREE (dlocal);
		}
		gdk_threads_enter ();
		e2_filelist_enable_refresh ();
		return TRUE;
	}
	//FIXME warn user about error
	return FALSE;
}
/**
@brief rename selected items in active pane to the inactive pane

Overwrite checking is performed, if that option is enabled
The actual operation is performed by a separate back-end function

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_rename (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_RENAME, art, from,
		_e2_task_renameQ, e2_task_refresh_lists));
}
static gboolean _e2_task_renameQ (E2_ActionTaskData *qed)
{
	GPtrArray *names = qed->names;
	gchar *curr_local = qed->currdir;
#ifdef E2_VFS
	VPATH sdata = { NULL, qed->currspace };
	VPATH ddata = { NULL, qed->currspace };
	VPATH tdata = { NULL, qed->currspace };
#endif
	GString *prompt = g_string_sized_new (NAME_MAX+64);
	GString *src = g_string_sized_new (1024);
	GString *dest = g_string_sized_new (1024);
	gchar *converted, *new_name, *public;
	gboolean check = e2_option_bool_get ("confirm-overwrite");
	guint count;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	//setup for tailoring over-write dialog
	gboolean multisrc =  (check) ? names->len > 1 : FALSE;
	// setup for showing stop button in rename dialog
	OW_ButtonFlags extras = (multisrc)? NOALL : NONE;

	e2_filelist_disable_refresh ();
	e2_task_advise ();

	for (count=0; count < names->len; count++, iterator++)
	{
		//".." entries filtered when names compiled
		converted = F_FILENAME_FROM_LOCALE ((*iterator)->filename);
		public = g_markup_escape_text (converted, -1);
		g_string_printf (prompt, "%s: <b>%s</b>", _("Enter new name for"), public);
		g_free (public);
		DialogButtons result = 0;
		e2_filelist_enable_refresh ();  //allow updates while we wait
		gdk_threads_enter ();
		*qed->status = E2_TASK_PAUSED;
		DialogButtons result2 = e2_dialog_line_input (_("rename"), prompt->str,
			converted, extras, FALSE, &new_name);
		*qed->status = E2_TASK_RUNNING;
		gdk_threads_leave ();
		e2_filelist_disable_refresh ();

		if (result2 == OK)
		{
			if (g_str_equal (converted, new_name))
			{
				g_free (new_name);
				F_FREE (converted);
				continue;
			}
			g_string_printf (src, "%s%s", curr_local, (*iterator)->filename);  //separator comes with dir
			gchar *local = F_FILENAME_TO_LOCALE (new_name);
			g_string_printf (dest, "%s%s", curr_local, local);
			F_FREE (local);

#ifdef E2_VFS
			sdata.localpath = src->str;
			ddata.localpath = dest->str;
			gboolean exists = !e2_fs_access2 (&ddata E2_ERR_NONE());
#else
			gboolean exists = !e2_fs_access2 (dest->str E2_ERR_NONE());
#endif
			if (exists)
			{	//maybe new name exists, or maybe it's just the old name with some different case
				//FIXME use curr_view->case_sensitive - but view may have altered now
				gchar *old_name_lc = g_utf8_casefold (converted, -1);
				gchar *new_name_lc = g_utf8_casefold (new_name, -1);
				gboolean case_change = g_str_equal (old_name_lc, new_name_lc);
				g_free (old_name_lc);
				g_free (new_name_lc);
				if (case_change)
				{	//we have just a case-difference that looks the same to the kernel
					//do interim name change to check, & also to ensure case-change happens
					gchar *tempname = e2_utils_get_tempname (src->str);
//					if (e2_task_backend_rename (slocal, tempname)
//						&& e2_task_backend_rename (tempname, dlocal))
#ifdef E2_VFS
					tdata.localpath = tempname;
					if (e2_task_backend_rename (&sdata, &tdata))
#else
					if (e2_task_backend_rename (src->str, tempname))
#endif
					{
#ifdef E2_VFS
						exists = !e2_fs_access2 (&ddata E2_ERR_NONE());
#else
						exists = !e2_fs_access2 (dest->str E2_ERR_NONE());
#endif
						if (exists)
						{	//the new name really exists
							//revert and process normally
#ifdef E2_VFS
							e2_task_backend_rename (&tdata, &sdata);
#else
							e2_task_backend_rename (tempname, src->str);
#endif
							g_free (tempname);
						}
						else
						{	//the former detection was fake, just do the rest of the rename
							//CHECKME this may be irrelevant
#ifdef E2_VFS
							e2_task_backend_rename (&tdata, &ddata);
#else
							e2_task_backend_rename (tempname, dest->str);
#endif
/*#ifdef E2_INCLIST
							_e2_task_treeview_line_rename ((*iterator)->ref, NOTFREEDnew_name);
#else
							gtk_tree_selection_unselect_path (selection, (*iterator)->path);
#endif */
							g_free (tempname);
							g_free (new_name);
							F_FREE (converted);
							continue;
						}
					}
					else
					{
#ifdef E2_VFS
						sdata.localpath = (*iterator)->filename;
#endif
						e2_fs_error_simple (_("Cannot rename %s"),
#ifdef E2_VFS
							&sdata);
#else
							(*iterator)->filename);
#endif
						g_free (tempname);
						g_free (new_name);
						F_FREE (converted);
						continue;
					}
				}
			}
			if (check && exists)
			{
				e2_filelist_enable_refresh ();  //allow updates while we wait
				gdk_threads_enter ();
				*qed->status = E2_TASK_PAUSED;
				result = e2_dialog_ow_check (src->str, dest->str, extras);
				*qed->status = E2_TASK_RUNNING;
				gdk_threads_leave ();
				e2_filelist_disable_refresh ();
				if (result == OK)
				{
#ifdef E2_VFS
					e2_task_backend_rename (&sdata, &ddata);
#else
					e2_task_backend_rename (src->str, dest->str);
#endif
				}
				else if (result == NO_TO_ALL)
				{
					g_free (new_name);
					F_FREE (converted);
					break;
				}
			}
			else
			{
#ifdef E2_VFS
				e2_task_backend_rename (&sdata, &ddata);
#else
				e2_task_backend_rename (src->str, dest->str);
#endif
			}
			g_free (new_name);
			F_FREE (converted);
		}
		else
		{
			F_FREE (converted);
			if (result2 == NO_TO_ALL)
				break;
		}
	}
	g_string_free (prompt,TRUE);
	g_string_free (src,TRUE);
	g_string_free (dest,TRUE);
	e2_window_clear_status_message ();
	e2_filelist_enable_refresh ();

	return TRUE;
}
/**
@brief show/change permission of selected items in active pane

The actual operation is performed by a separate back-end function
Directories are treated separately, to support recursive changes to
directories only
View is not updated immediately as the change does not trigger an auto-refresh  FIXME

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_permissions (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_CHMOD, art, from,
		_e2_task_permissionsQ, e2_task_refresh_lists));
}
static gboolean _e2_task_permissionsQ (E2_ActionTaskData *qed)
{
//	printd (DEBUG, "task: permissions");
	GPtrArray *names = qed->names;
	gchar *curr_local = qed->currdir;
#ifdef E2_VFS
	VPATH sdata;
	sdata.spacedata = qed->currspace;
#endif
	gchar *modestring = NULL;
	guint count;
	gint myuid = -1;
	gboolean multisrc = names->len > 1;
	gboolean all = FALSE;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	GString *path = g_string_sized_new (1024);

/* out-of-loop setup = FIXME
	GtkWidget *dialog;
	dialog = e2_permissions_dialog_setup (info, &recurse);
	e2_dialog_add_buttons_simple (dialog, buttons ..., NULL);
*/

#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, permissions task");
#endif
	e2_filelist_disable_refresh ();
	e2_task_advise ();

	for (count=0; count < names->len; count++, iterator++)
	{
		DialogButtons choice;
		E2_RecurseType recurse;
		gboolean permission;
		//".." entries filtered when names compiled
//FIXME for single-setup: instead of the following, adjust file details in dialog, reset default button
		g_string_printf (path, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir
#ifdef E2_VFS
		sdata.localpath = path->str;
#endif
		if (all)
		{
			//check if we have permission to change this item
			//this tests _all_ W permissions, access() is not so sophisticated ...
#ifdef E2_VFS
 			permission = e2_fs_check_write_permission (&sdata E2_ERR_NONE());
#else
 			permission = e2_fs_check_write_permission (path->str E2_ERR_NONE());
#endif
			if (permission)
				choice = OK;
			else
			{
#ifdef E2_VFS
				sdata.localpath = (*iterator)->filename;
#endif
				e2_fs_error_simple (
					_("You do not have authority to change permission(s) of %s"),
#ifdef E2_VFS
					&sdata);
#else
					(*iterator)->filename);
#endif
				choice = CANCEL;
			}
		}
		else
		{
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, permissions dialog");
#endif
			e2_filelist_enable_refresh ();  //allow updates while we wait
			*qed->status = E2_TASK_PAUSED;
#ifdef E2_VFS
			choice = e2_permissions_dialog_run (&sdata,
#else
			choice = e2_permissions_dialog_run (path->str,
#endif
				&modestring, &recurse, &permission, multisrc);
			*qed->status = E2_TASK_RUNNING;
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, permissions dialog");
#endif
			e2_filelist_disable_refresh ();
		}

		switch (choice)
		{
		  case YES_TO_ALL:
			all = TRUE;
			myuid = getuid ();  //do this once, for speed
			choice = OK;
		  case OK:
			if (permission && modestring != NULL)
			{
#ifdef E2_INCLIST
				if (e2_task_backend_chmod (path->str, modestring, recurse))
				{
					//FIXME update line in treeview
				}
#else
# ifdef E2_VFS
				sdata.localpath = path->str;
# endif
# ifdef E2_FAM
#  ifdef E2_VFS
				e2_task_backend_chmod (&sdata, modestring, recurse);
#  else
				e2_task_backend_chmod (path->str, modestring, recurse);
#  endif
# else
#  ifdef E2_VFS
				if (e2_task_backend_chmod (&sdata, modestring, recurse))
				{
					if (sdata.spacedata == NULL)
					{
						sdata.localpath = qed->currdir;
						e2_fs_touchnow (&sdata E2_ERR_NONE());
					}
				}
#  else
				if (e2_task_backend_chmod (path->str, modestring, recurse))
					//make the file-list refresher notice successful change
					e2_fs_touchnow (qed->currdir E2_ERR_NONE());
#  endif
# endif
#endif
				if (!all)
				{
					g_free (modestring);
					modestring = NULL;
				}
			}
		  case CANCEL:
			break;
		  default:
			choice = NO_TO_ALL;  // break flag;
			break;
		}
		if (choice == NO_TO_ALL)
			break;
	}

	if (modestring != NULL)
		g_free (modestring);

	g_string_free (path, TRUE);
	e2_window_clear_status_message ();
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, permissions task");
#endif
	e2_filelist_enable_refresh ();

	return TRUE;
}
/**
@brief show/change user and/or group of selected items in active pane

The actual operation is performed by a separate back-end function

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_ownership (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_CHOWN, art, from,
		_e2_task_ownershipQ, e2_task_refresh_lists));
}
static gboolean _e2_task_ownershipQ (E2_ActionTaskData *qed)
{
	GPtrArray *names = qed->names;
	gchar *curr_local = qed->currdir;
#ifdef E2_VFS
	VPATH sdata;
	sdata.spacedata = qed->currspace;
#endif
	GString *path = g_string_sized_new (1024);
	guint count;
	DialogButtons choice;
	gint myuid = -1;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	gboolean multisrc = names->len > 1;
	gboolean all = FALSE;

	e2_filelist_disable_refresh ();
	e2_task_advise ();

	for (count=0; count < names->len; count++, iterator++)
	{
		gboolean permission;
		uid_t owner_id;
		gid_t group_id;
		gboolean recurse;
		//".." entries filtered when names compiled
		g_string_printf (path, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir
#ifdef E2_VFS
		sdata.localpath = path->str;
#endif
		if (all)
		{
			//check if we have permission to change this item
			permission = e2_fs_check_write_permission (
#ifdef E2_VFS
 			&sdata E2_ERR_NONE());
#else
 			path->str E2_ERR_NONE());
#endif
			if (!permission)
			{
#ifdef E2_VFS
				sdata.localpath = (*iterator)->filename;
#endif
				e2_fs_error_simple (
					_("You do not have authority to change owner(s) of %s"),
#ifdef E2_VFS
					&sdata);
#else
					(*iterator)->filename);
#endif
			}
			choice = (permission) ? OK : CANCEL;
		}
		else
		{
			owner_id=-1;  //CHECKME = should these be set outside loop ?
			group_id=-1;
			permission = TRUE;
			recurse = FALSE;
			//leave dialog open as long as possible to prevent 'shadow' prior to screen refresh
			e2_filelist_enable_refresh ();  //allow updates while we wait

			*qed->status = E2_TASK_PAUSED;
			choice = e2_ownership_dialog_run (
#ifdef E2_VFS
			&sdata,
#else
			path->str,
#endif
				&owner_id, &group_id, &recurse, &permission, multisrc);
			*qed->status = E2_TASK_RUNNING;

			e2_filelist_disable_refresh ();
		}

		switch (choice)
		{
		  case YES_TO_ALL:
			all = TRUE;
			myuid = getuid ();
			choice = OK;
		  case OK:
			if (permission)
			{
//				printd (DEBUG, "ownership task: ok");
//				printd (DEBUG, "new owner id is: %d",  owner_id);
#ifdef E2_INCLIST
				if (e2_task_backend_chown (path->str, owner_id, group_id, recurse))
				{
					//FIXME update line in treeview
				}
#else
# ifdef E2_FAM
#  ifdef E2_VFS
				e2_task_backend_chown (&sdata, owner_id, group_id, recurse);
#  else
				e2_task_backend_chown (path->str, owner_id, group_id, recurse);
#  endif
# else
#  ifdef E2_VFS
				if (e2_task_backend_chown (&sdata, owner_id, group_id, recurse))
				{
					if (sdata.spacedata == NULL)
					{
						sdata.localpath = qed->currdir;
						e2_fs_touchnow (&sdata E2_ERR_NONE());
					}
				}
#  else
				if (e2_task_backend_chown (path->str, owner_id, group_id, recurse))
					//make the file-list refresher notice successful change
					e2_fs_touchnow (qed->currdir E2_ERR_NONE());
#  endif
# endif
#endif
			}
		  case CANCEL:
			break;
		  default:
			choice = NO_TO_ALL;  // break flag;
			break;
		}
		if (choice == NO_TO_ALL)
			break;
	}
	g_string_free (path,TRUE);
	e2_window_clear_status_message ();
	e2_filelist_enable_refresh ();

	return TRUE;
}
/**
@brief execute the operation specified from the drag menu, on all dragged items

It is assumed all items have the same source path, @a s_local.
Nothing will be done if @a s_local and @a d_local are the same.
Item overwrite checks are performed if that option is in force.
Processing of dropped items stops if any problem occurs
This is called from inside gtk's BGL
@param type code for function to be performed on dragged items
@param s_local source path, localised string with trailing /
@param d_local destination path, localised string with trailing /
@param names_local array of localised item names to process with @a func

@return TRUE if the items were all processed successfully (this must be synchronous)
*/
gboolean e2_task_drop (E2_TaskType type, gchar *s_local, gchar *d_local,
	GPtrArray *names_local)
{
//	printd (DEBUG, "callback: drop task");
	if (strcmp (s_local, d_local) == 0)
		return FALSE;
	//store the data for use by setup function
	//these data are not copied, and so must not be cleared in the calling function
	srcdir_local = s_local;
	destdir_local = d_local;
	names_array = names_local;

	gchar *action_name;
	gboolean result;
	gpointer taskfunc;
	switch (type)
	{
		case E2_TASK_LINK:
			action_name = g_strconcat (_A(5),".",_A(91),NULL);
			taskfunc = _e2_task_symlinkQ;
			break;
		case E2_TASK_MOVE:
			action_name = g_strconcat (_A(5),".",_A(58),NULL);
			taskfunc = _e2_task_moveQ;
			break;
		default:
//		case E2_TASK_COPY:
			action_name = g_strconcat (_A(5),".",_A(33),NULL);
			taskfunc = _e2_task_copyQ;
			break;
	}

	E2_Action *action = e2_action_get (action_name);
	E2_ActionRuntime *art = e2_action_pack_runtime (action, NULL, NULL);

	result = e2_task_run_task (type, art, NULL, taskfunc, e2_task_refresh_lists,
		FALSE, FALSE);

	g_free (action_name);
	e2_action_free_runtime (art);

	if (!result)
		return FALSE;

	result = _e2_task_wait (NULL);
	return result;
}
/**
@brief change 'other' pane to be the same as 'current' pane
Downstream function expects BGL to be closed
@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE
*/
gboolean e2_task_sync_dirs (gpointer from, E2_ActionRuntime *art)
{
	e2_pane_change_dir (other_pane, curr_pane->path); //FIXME Q this
	return TRUE;
}
/**
@brief show info about each select item in active pane

NB the active pane directory is not necessarily the current file system
dir, so the path is added to the filename
FIXME on this ?

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_file_info (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_do_task (E2_TASK_INFO, art, from, _e2_task_file_infoQ, NULL));
}
static gboolean _e2_task_file_infoQ (E2_ActionTaskData *qed)
{
	GPtrArray *names = qed->names;
	gchar *curr_local = qed->currdir;
#ifdef E2_VFS
	VPATH sdata;
	sdata.spacedata = qed->currspace;
#endif
	guint count;
	gboolean multi;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;

	e2_filelist_disable_refresh ();	//prevent access-related refresh

	for (count=0; count < names->len; count++, iterator++)
	{
		//".." entries filtered when names compiled
		gchar *localpath = e2_utils_strcat (curr_local, (*iterator)->filename);
#ifdef E2_VFS
		sdata.localpath = localpath;
		if (!e2_fs_access2 (&sdata E2_ERR_NONE()))
#else
		if (!e2_fs_access2 (localpath E2_ERR_NONE()))
#endif
		{
			multi = (names->len > 1 && count < (names->len - 1));
			*qed->status = E2_TASK_PAUSED;
#ifdef E2_VFS
			DialogButtons choice = e2_file_info_dialog_run (&sdata, multi);
#else
			DialogButtons choice = e2_file_info_dialog_run (localpath, multi);
#endif
			*qed->status = E2_TASK_RUNNING;
			if (multi && (choice == NO_TO_ALL))
				break;
		}
		g_free (localpath);
	}

	e2_filelist_enable_refresh ();

	return TRUE;
}
/**
@brief view content of nomimated file or 1st selected item in active pane
ATM assume that non-local files can also be edited
The actual operation is performed by internal or extenal viewer,
depending on the option specfied

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_view (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_do_task (E2_TASK_VIEW, art, from, _e2_task_viewQ, NULL));
}
static gboolean _e2_task_viewQ (E2_ActionTaskData *qed)
{
//	printd (DEBUG, "task: view");
	gboolean retval;
	GPtrArray *names = qed->names;
	if (names == NULL)
		return FALSE;

	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	//".." entries filtered when names compiled
	gchar *local = e2_utils_strcat (qed->currdir, (*iterator)->filename);
#ifdef E2_VFS
	VPATH sdata = { local, qed->currspace };
#endif

	//grab mutex in case of error or other output message
	gdk_threads_enter ();
#ifdef E2_VFS
	retval = e2_task_backend_view (&sdata);
#else
	retval = e2_task_backend_view (local);
#endif
	gdk_threads_leave ();

	g_free (local);
	return retval;
}
/**
@brief edit nomimated file or 1st selected item in active pane
ATM assume that non-local files can also be edited
@param action_data ptr to data assigned when action struct created at session start
@param rt_data ptr to data sent to this command, NULL to act on selection
@param from the button, menu item etc which was activated

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_edit (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_do_task (E2_TASK_EDIT, art, from, _e2_task_editQ, NULL));
}
static gboolean _e2_task_editQ (E2_ActionTaskData *qed)
{
//	printd (DEBUG, "task: edit");
	gboolean retval;
	GPtrArray *names = qed->names;
	if (names == NULL)
		return FALSE;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	//".." entries filtered when names compiled
	gchar *local = e2_utils_strcat (qed->currdir, (*iterator)->filename);

	if (e2_option_bool_get ("use-external-editor"))
	{
		gchar *editor = e2_option_str_get ("command-editor");
		if (*editor != '\0')
		{
			gchar *edit_this = F_FILENAME_FROM_LOCALE (local);
			gchar *command = e2_utils_replace_name (editor, edit_this);
			if (command == NULL)
				command = g_strdup_printf ("%s '%s'", editor, edit_this);

			//grab mutex in case of error or other output message
			gdk_threads_enter ();
			gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT
#ifdef E2_COMMANDQ
			, TRUE
#endif
			);
			gdk_threads_leave ();

			F_FREE (edit_this);
			g_free (command);
			retval = (res == 0);
		}
		else
			retval = FALSE;
	}
	else
	{
#ifdef E2_VFS
		VPATH sdata = { local, qed->currspace };
#endif
		gdk_threads_enter ();
		retval = e2_edit_dialog_create (
#ifdef E2_VFS
			&sdata, NULL);
#else
			local, NULL);
#endif
		gdk_threads_leave ();
	}

	g_free (local);
	return retval;
}
/**
@brief perform the default action for nominated item, or the first selected item in active pane

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_open (gpointer from, E2_ActionRuntime *art)
{
	//need to support opening a specified item, maybe with path != %d
	if (from == (gpointer) curr_view->treeview || from == (gpointer) other_view->treeview)
	{
		ViewInfo *view = (from == (gpointer) curr_view->treeview) ? curr_view : other_view;
		FileInfo *infoptr = e2_fileview_get_selected_first_local (view, TRUE);
		if (infoptr == NULL)
			return FALSE;
		//parse ".." here (for cd ".." and similar), as ".." is excluded from
		//normal selections
		if (g_str_equal (infoptr->filename, "..")
	//		&& gtk_tree_selection_count_selected_rows (view->selection) == 1
		)
		{
			if (art->data != NULL)
				g_free (art->data);
//E2_VFSTMPOK
			art->data = g_strdup (view->dir);
			gint len = strlen ((gchar *)art->data);
			if (len > 1)
			{
				*((gchar *)art->data + len - 1) = '\0';	//strip trailing separator
				gchar *s = strrchr ((gchar *)art->data, G_DIR_SEPARATOR);
				if (s != NULL)
				{
					if (s > (gchar *)art->data)
						*s = '\0';
					else	//at start of string, so parent is root dir
						*(s+sizeof(gchar)) = '\0';
				}
			}
		}
	}
#ifdef E2_VFSTMP
	FIXME may be any space if a specific item was nominated
#endif
	return (e2_task_do_task (E2_TASK_OPEN, art, from, _e2_task_openQ, NULL));
}
static gboolean _e2_task_openQ (E2_ActionTaskData *qed)
{
//	printd (DEBUG, "task: open");
	gboolean retval;
	GPtrArray *names = qed->names;
	if (names == NULL)
		return FALSE;

	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	//".." entries filtered when names compiled, unless ".." is the only item selected
	//when opening a specific item, the statbuf will not be completed
	gchar *local = e2_utils_strcat (qed->currdir, (*iterator)->filename);
#ifdef E2_VFS
	VPATH sdata = { local, qed->currspace };
# ifdef E2_VFSTMP
	FIXME may be any space if a specific item was nominated
# endif
#endif

	gdk_threads_enter ();
#ifdef E2_VFS
	retval = e2_task_backend_open (&sdata, TRUE);
#else
	retval = e2_task_backend_open (local, TRUE);
#endif
	gdk_threads_leave ();

	g_free (local);

	return retval;
}
/**
@brief open selected item(s) in active pane with a chosen application

If the specified command does not include "%f", all selected items
are concatenated, and sent as a single argument to the application.
Otherwise, the entered command is simply executed

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_open_with (gpointer from, E2_ActionRuntime *art)
{
#ifdef E2_VFSTMP
	FIXME may be any space if a specific item was nominated
#endif
	return (e2_task_do_task (E2_TASK_OPENWITH, art, from, _e2_task_open_withQ, NULL));
}
static gboolean _e2_task_open_withQ (E2_ActionTaskData *qed)
{
	gchar *command, *defcmd, *ext, *localpath;
	GPtrArray *names = qed->names;
	if (names == NULL)
		return FALSE;
	//determine a suggested command, the default for first-selected item
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
#ifdef E2_VFS
	//".." entries filtered when names compiled
	localpath = e2_utils_strcat (qed->currdir, (*iterator)->filename);
	VPATH sdata = { localpath, qed->currspace };
	//checking a virtual dir in the process of being mounted may cause bad delay
	//so we fake a rough guess at what is intended ...
	//FIXME how ?? (also needed in _e2_pane_change_dir())
	printd (DEBUG, "NEED fake check whether %s is a directory", localpath);
	if (
	//	1 ||
		e2_fs_is_dir3 (&sdata E2_ERR_NONE()))
#else
	//".." entries filtered when names compiled
	localpath = e2_utils_strcat (qed->currdir, (*iterator)->filename);
	if (e2_fs_is_dir3 (localpath E2_ERR_NONE()))
#endif
		ext = _("<directory>");
#ifdef E2_VFS
	else if (!e2_fs_access (&sdata, X_OK E2_ERR_NONE()))
#else
	else if (!e2_fs_access (localpath, X_OK E2_ERR_NONE()))
#endif
		ext = _("<executable>");
	else
	{
		ext = strchr ((*iterator)->filename, '.');	//assumes '.' is ascii
		if (ext == NULL //no extension
			|| ext == (*iterator)->filename) //hidden file
		{
#ifdef E2_VFS
			if (e2_fs_is_text (&sdata E2_ERR_NONE()))
#else
			if (e2_fs_is_text (localpath E2_ERR_NONE()))
#endif
				ext = "txt"; //too bad if this is not a recognised text extension in filetypes
			else
				ext = NULL;	//clear value from hidden file
		}
		else
		{
			do
			{
				//skip leading dot "."
				ext++;	//NCHR(ext); ascii '.'. always single char
				if (*ext == '\0')
				{
					ext = NULL;
					break;
				}
				if (e2_filetype_get_actions (ext) != NULL)
					break;
			} while ((ext = strchr (ext, '.')) != NULL);	//always ascii '.', don't need g_utf8_strchr()
		}
	}
	g_free (localpath);
	command = (ext == NULL) ? NULL : (gchar*)e2_filetype_get_default_action (ext);	//maybe NULL
	if (command != NULL)
	{
		defcmd = strchr (command, '@');
		if (defcmd != NULL)
			defcmd++;
		else
			defcmd = command;
		//exclude commands which start by opening another dialog
		if (g_str_has_prefix (defcmd, "%{") && strchr (defcmd+2, '}') != NULL)
			defcmd = NULL;
	}
	else
		defcmd = NULL;

	gdk_threads_enter ();
	*qed->status = E2_TASK_PAUSED;
	DialogButtons result = e2_dialog_combo_input (_("open with"),
		_("Enter command:"), defcmd, 0, &open_history, &command);
	*qed->status = E2_TASK_RUNNING;
	gdk_threads_leave ();

	gboolean retval;
	if (result == OK)
	{
		gchar *freeme;
		if (strstr (command, "%f") == NULL && strstr (command, "%p") == NULL)
		{
			freeme = command;
			command = g_strconcat (command, " %f", NULL);
			g_free (freeme);
		}
//E2_VFSTMP how to specify namespace in command arg(s) ?
		gchar *utf = F_FILENAME_FROM_LOCALE (qed->currdir);
		freeme = command;
		command = e2_utils_replace_name2 (command, utf, names, FALSE);
		F_FREE (utf);
		if (command == NULL)
		{
			g_free (freeme);
			return FALSE;
		}

		//grab mutex in case of error or other output message
		gdk_threads_enter ();
#ifdef E2_COMMANDQ
		gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, TRUE);
#else
		gint res = e2_command_run (command, E2_COMMAND_RANGE_DEFAULT);
#endif
		gdk_threads_leave ();
		retval = (res == 0);

		g_free (command);
	}
	else
		retval = FALSE;

	return retval;
}
/**
@brief open directory which is currently selected, in the other pane
Downstream function expects BGL to be closed
@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE
*/
gboolean e2_task_open_in_other_pane (gpointer from, E2_ActionRuntime *art)
{
	//handle case of specific data instead of selection??
	//FIXME do this with localised data
	//FIXME check item is a dir
//	The last-activated row in the other pane is focused - this is not
//	necessarily sensible
	//CHECKME Q this
	e2_filelist_disable_refresh ();
	gchar *name = e2_fileview_get_row_name (curr_view, curr_view->row);
	if (name == NULL)
	{
		e2_filelist_enable_refresh ();
		return FALSE;  //the model got mixed up, somehow
	}
	gchar *path = e2_utils_strcat (curr_view->dir, name); //separator comes with dir
#ifdef E2_VFS
	if (other_view->spacedata != curr_view->spacedata)
	{
		VPATH ddata = { path, curr_view->spacedata };	//utf8 path expected
		e2_pane_change_space (other_pane, &ddata);	//should never fail
	}
	else
#endif
		e2_pane_change_dir (other_pane, path);
	g_free (name);
	g_free (path);
	e2_filelist_enable_refresh ();
	return TRUE;
}
/**
@brief toggle tag on selected item in active pane

Deleted after discussion with tooar

@return
*/
/*void __e2_task_toggle_tag_cb (void)
{
//FIXME = logic of this is silly !!
	GList *tmp = e2_fileview_get_selection (curr_view);
	//deprecated
	FileInfo *info;
	gint row;

//	g_signal_emit_by_name (G_OBJECT (curr_view->clist), "end-selection");

	for (; tmp != NULL; tmp = tmp->next)
	{
/ *		row = GPOINTER_TO_INT (tmp->data);
		info = gtk_clist_get_row_data(GTK_CLIST(curr_view->clist), row);
		if (g_list_find (curr_view->tagged, info) != NULL)
		{
			curr_view->tagged = g_list_remove(curr_view->tagged, info);
			gtk_clist_set_background(GTK_CLIST(curr_view->clist), row, &LIST_COLOR);
		}
		else
		{
			curr_view->tagged = g_list_append(curr_view->tagged, info);
			gtk_clist_set_background(GTK_CLIST(curr_view->clist), row, e2_option_color_get ("color-tag"));
		}  * /
	//FIXME
	}
	e2_fileview_focus_row(curr_view, curr_view->row+1, FALSE, TRUE, TRUE, TRUE);
} */
/**
@brief revert all configuration settings to defailt values

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE
*/
gboolean e2_task_configure_default (gpointer from, E2_ActionRuntime *art)
{
	//dump old data, don't re-read the config file, recreate screen
	e2_option_refresh (FALSE, TRUE);
	return TRUE;
}
/**
@brief initiate configuration change

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE
*/
gboolean e2_task_configure (gpointer from, E2_ActionRuntime *art)
{  //put this in config dialog file ?
//	gtk_widget_set_sensitive (app.main_window, FALSE);
	gchar *page = (gchar *)art->data;
	e2_config_dialog_create (page);
//	gtk_widget_set_sensitive (app.main_window, TRUE);
	return TRUE;
}
/**
@brief find and select named item in active pane
This differs from treeview type-ahead searching because the supplied pattern
does not need to be at the start of the line
The search starts from the current line, forward to end, then cycles
back to the start

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if task completed successfully, else FALSE
*/
gboolean e2_task_find (gpointer from, E2_ActionRuntime *art)
{
	gchar *s, *pattern;
	gint i, last, rowcount;

	//find the no. of rows in the store
	rowcount = gtk_tree_model_iter_n_children (curr_view->model, NULL);
	if (rowcount < 1)
		return FALSE;  //no message ...

	e2_window_set_cursor (GDK_WATCH);
	//FIXME nicer to use a dialog that stays open until specifically closed,
	//with this stuff in its "find" callback
	DialogButtons choice = e2_dialog_line_input (_("find"),
		_("Enter a name or partial name to find:"), curr_view->last_find, 0,
		TRUE, &s);

	if (choice == OK)
	{
		g_strlcpy (curr_view->last_find, s, sizeof(curr_view->last_find));
		pattern = g_strdup_printf ("*%s*", s);

		GtkTreeModel *model = curr_view->model;
		GtkTreePath *path;
		GtkTreeIter iter;
		if (gtk_tree_model_iter_nth_child (model, &iter, NULL, curr_view->row))
			path = gtk_tree_model_get_path (model, &iter);
		else
			//try fallback to the row of the current cursor position
			gtk_tree_view_get_cursor (GTK_TREE_VIEW (curr_view->treeview),
				&path, NULL);

		if (path == NULL)
		{
			last = rowcount - 1;
			//no defined start pos, start from beginning
			i = 0;
		}
		else
		{
			last = *gtk_tree_path_get_indices (path);
			if (last == rowcount - 1)
				i = 0;	//roll around to the start
			else
				i = last + 1;	//start at the next line

			gtk_tree_path_free (path);
		}

		//iterate through the rows
		gchar *thisrowname;
		gboolean matched = FALSE;
		while (i != last)
		{
			thisrowname = e2_fileview_get_row_name (curr_view, i);
			if (g_pattern_match_simple (pattern, thisrowname))
				matched = TRUE;
			g_free (thisrowname);
			if (matched)
				break;
			if (++i >= rowcount)
				i = 0;  //cycle to start
		}
		if (i == last)
		{	//nothing found sofar, but maybe it's in the "last" row, where the scan started
			thisrowname = e2_fileview_get_row_name (curr_view, i);
			if (g_pattern_match_simple (pattern, thisrowname))
				matched = TRUE;
			g_free (thisrowname);
		}
		if (matched)
		{
			gtk_tree_model_iter_nth_child (model, &iter, NULL, i);
			path = gtk_tree_model_get_path (model, &iter);
			gtk_tree_view_set_cursor (GTK_TREE_VIEW (curr_view->treeview),
				path, NULL, FALSE);
		}

		g_free(pattern);
		g_free(s);
	}

	e2_window_set_cursor (GDK_LEFT_PTR);
//	gtk_widget_grab_focus (curr_view->treeview);
	return TRUE;
}
/**
@brief register task-related actions

@return
*/
void e2_task_actions_register (void)
{
	E2_Action task_actions[] =
	{	//we don't bother explicitly setting each element's last-3 parameters
		{ NULL, e2_task_open, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_open_in_other_pane, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_open_with, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_view, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_edit, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_file_info, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_permissions, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_ownership, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_rename, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_copy, FALSE, E2_ACTION_TYPE_ITEM, 0 },	//data == E2_FTM_NORMAL
		{ NULL, e2_task_copy_as, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_move, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_move_as, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_symlink, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_symlink_as, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_delete, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_find, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_trashit, FALSE, E2_ACTION_TYPE_ITEM, 0 },
		{ NULL, e2_task_trashempty, FALSE, E2_ACTION_TYPE_ITEM, 0 }
	};
	task_actions[0].name = g_strconcat(_A(5),".",_A(60),NULL);
	task_actions[1].name = g_strconcat(_A(5),".",_A(61),NULL);
	task_actions[2].name = g_strconcat(_A(5),".",_A(62),NULL);
	task_actions[3].name = g_strconcat(_A(5),".",_A(101),NULL);
	task_actions[4].name = g_strconcat(_A(5),".",_A(39),NULL);
	task_actions[5].name = g_strconcat(_A(5),".",_A(52),NULL);
	task_actions[6].name = g_strconcat(_A(5),".",_A(67),NULL);
	task_actions[7].name = g_strconcat(_A(5),".",_A(63),NULL);
	task_actions[8].name = g_strconcat(_A(5),".",_A(73),NULL);
	task_actions[9].name = g_strconcat(_A(5),".",_A(33),NULL);
	task_actions[10].name = g_strconcat(_A(5),".",_A(34),NULL);
	task_actions[11].name = g_strconcat(_A(5),".",_A(58),NULL);
	task_actions[12].name = g_strconcat(_A(5),".",_A(59),NULL);
	task_actions[13].name = g_strconcat(_A(5),".",_A(91),NULL);
	task_actions[14].name = g_strconcat(_A(5),".",_A(92),NULL);
	task_actions[15].name = g_strconcat(_A(5),".",_A(38),NULL);
	task_actions[16].name = g_strconcat(_A(5),".",_A(6),NULL);
	task_actions[17].name = g_strconcat(_A(5),".",_A(97),NULL);
	task_actions[18].name = g_strconcat(_A(1),".",_A(98),NULL);	//not applied to selected items

	gint actions_count = sizeof (task_actions) / sizeof (E2_Action);
	gint i;
	for (i = 0; i < actions_count; i++)
	{
		e2_action_register (
			task_actions[i].name,
			task_actions[i].type,
			task_actions[i].func,
			NULL,
			task_actions[i].has_arg,
			task_actions[i].exclude,
			NULL);
	}
	//these have action data
	gchar *action = g_strconcat(_A(5),".",_A(35),NULL);
	e2_action_register (action, E2_ACTION_TYPE_ITEM, e2_task_copy,
		GINT_TO_POINTER (E2_FTM_MERGE), FALSE, 0, NULL);
	action = g_strconcat(_A(5),".",_A(36),NULL);
	e2_action_register (action, E2_ACTION_TYPE_ITEM, e2_task_copy,
		GINT_TO_POINTER (E2_FTM_SAMETIME), FALSE, 0, NULL);
}
