/* $Id: e2p_mvbar.c 773 2007-11-30 21:19:14Z tpgww $
**************************************

Emelmovebar - a plugin for emelFM2
by Florian Zaehringer
with help from tooar and tom to port it to Gtk+2

source of source-code and inspiration:
emelFM by Michael Clark

**************************************
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
*/

/**
@file plugins/e2p_mvbar.c
@brief plugin for moving selected items, with a progress-bar
*/

#include "emelfm2.h"
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include "e2_plugins.h"
#include "e2_dialog.h"
#include "e2_option.h"
#include "e2_filelist.h"
#include "e2_task.h"

//max. no. of _bytes_, at the end of a copied item's source-path
//and dest-path, shown in the progress dialog
//paths longer than this will be 'ellipsized'
#define MAX_CHAR 55
//size of buffer for progress dialog
//make this >= (MAX_CHAR+4)*2 + space for rest of message text
//#define MAX_MSG 220
//interval (usec) between progress window updates
//for items < and > 10MB resepcively
#define MIN_UPDATE_INTERVAL 100000
#define MAX_UPDATE_INTERVAL 200000

//include things to support pause and resume
#ifndef IS_PAUSABLE
#define IS_PAUSABLE
#endif
#ifdef IS_PAUSABLE
typedef enum
{
	E2_BARTASK_STOPPED   = 1,
	E2_BARTASK_PAUSEREQ  = 1 << 1,	//pause requested
	E2_BARTASK_PAUSED    = 1 << 2,	//actually paused
	E2_BARTASK_COMPLETED = 1 << 3,
	E2_BARTASK_SUCCEEDED = 1 << 4,
} E2_BarFlags;
#endif

typedef struct _E2_BarData
{
	guint64 count;	//count of items to be processed, in active pane
	guint64 totalsize;	//aggregate apparent size of items to be processed
} E2_BarData;

typedef struct _E2_ProgressData
{
	pthread_mutex_t mutex;  //protects access to these data
	pthread_cond_t cond;   //signals change
	VPATH *dlocal;	//includes localised destination path (maybe temp) of item moved
	guint64 done_size;
	guint64 refresh_interval;
} E2_ProgressData;

typedef struct _E2_ActionData
{
//	pthread_mutex_t mutex;  //protects access to these data
	E2_FileTaskMode flags;	//flags for features of current copy
	VPATH *slocal;	//includes localised source path of item copied
	VPATH *dlocal;	//includes localised destination path of item copied
	gboolean completed;	//set TRUE when the backend task is completed (valid or not)
	gboolean result;	//value returned by backend task function
} E2_ActionData;

typedef struct _E2_BarWindowData
{
	GtkWidget *dialog;
	GtkWidget *label;
//	GtkWidget *label2;
	GtkWidget *progbar;
#ifdef IS_PAUSABLE
	GtkWidget *pause_btn;
	GtkWidget *resume_btn;
	GtkWidget *stop_btn;
	E2_BarFlags flags;
#endif
} E2_BarWindowData;

static gboolean _e2p_mvbarQ (E2_ActionTaskData *qed);

/**
@brief update items' count and total size
This is a callback for a treewalk function
Error message expects BGL to be open
@param localpath absolute path of item reported by the walker, localised string
@param statptr pointer to struct stat with data about @a localpath
@param status code from the walker, indicating what type of report it is
@param twdata pointer to tw data struct

@return E2TW_CONTINUE always
*/
static E2_TwResult
_e2p_mvbar_twcb (VPATH *localpath, const struct stat *statptr,
	E2_TwStatus status, E2_BarData *twdata)
{
	switch (status)
	{
		case E2TW_F:	//not dir or link
		case E2TW_SL:	//symbolic link
		case E2TW_SLN:	//symbolic link naming non-existing file
			twdata->totalsize += statptr->st_size;
		case E2TW_DL:	//dir, not opened due to tree-depth limit (reported upstream)
		case E2TW_DM:	//dir, not opened due to different file system (reported upstream)
		case E2TW_D:	//dir (don't care about its reported size)
		case E2TW_DRR:	//dir now readable
		case E2TW_DNR:	//unreadable dir (reported upstream)
		case E2TW_NS:	//un-statable item (reported upstream)
			twdata->count++;
//		case E2TW_DP:	//dir, finished
		default:
			break;
	}
	return E2TW_CONTINUE;
}
/**
@brief 'shorten' string @a string if it is longer than allowed
@a dots is set to "..." if @a string is too long, or else ""

@param string utf string which may need to be shortened
@param dots pointer to store for utf string which will preceed @a string, when it is displayed
@param string_diff pointer to store for the no. of bytes to be omitted from the start of @a string, when it is displayed

@return
*/
/*static void
_e2p_mvbar_shorten (gchar *string, gchar **dots, gint *string_diff)
{
	if (strlen (string) > MAX_CHAR)
	{
		//nominally we will drop this many bytes off the start of string
		gint trunc = strlen (string) - MAX_CHAR;
		//but make sure we don't cut off in the middle of a char
		gchar *c = g_utf8_find_next_char (string+trunc-1, NULL);
		*string_diff = c - string;
		*dots = "...";
	}
	else
	{
		*string_diff = 0;
		*dots = "";
	}
	return;
} */
/**
@brief thread function to determine how much progress has been made on the current item

This provides data for progressive updates during the course of moving an item.
The thread doesn't end of its own accord, but continues until it is cancelled
by the main thread
BGL will be open

@param data pointer to struct with data which is needed here

@return NULL (never happens)
*/
static void *
_e2p_mvbar_progress (E2_ProgressData *data)
{
	gchar *localpath;
	struct stat sb;
#ifdef E2_VFS
	VPATH ddata;
#endif
//	printd (DEBUG, "monitor thread underway");

	e2_utils_block_thread_signals ();	//block all allowed signals to this thread

#ifdef E2_VFS
	ddata.spacedata = data->dlocal->spacedata;
#endif
	while (TRUE)
	{
		pthread_testcancel ();	//has a cancel-instruction been issued ?
		//prevent the main thread from setting the cancel signal now
		if (pthread_mutex_lock (&(data->mutex)))
		{
			printd (WARN, "Lock error!");
		}
		//get a local copy of the present item's destination path
		//(that will be NULL if the action thread has finished already)
		if (data->dlocal == NULL)
			localpath = NULL;
		else
			localpath = g_strdup (VPSTR (data->dlocal));

		if (pthread_mutex_unlock (&(data->mutex)))
		{
			printd (WARN,"Unlock error!");
		}

		if (localpath != NULL)
		{	//we're still underway
			//determine how much of current item has been moved
			E2_BarData pdata;
			pdata.totalsize = 0;
//			pdata.count = 0;
#ifdef E2_VFS
			ddata.localpath = localpath;
			if (!e2_fs_lstat (&ddata, &sb E2_ERR_NONE()))
#else
			if (!e2_fs_lstat (localpath, &sb E2_ERR_NONE()))
#endif
			{
				if (S_ISDIR (sb.st_mode))
				{
					//if (!
#ifdef E2_VFS
					e2_fs_tw (&ddata,
#else
					e2_fs_tw (localpath,
#endif
					_e2p_mvbar_twcb, &pdata, -1, E2TW_PHYS E2_ERR_NONE());
					//)
					//{
						//FIXME handle error
					//}
				}
				else
//			{
				pdata.totalsize = sb.st_size;
//				pdata.count = 1;
//			}
			}
			g_free (localpath);

			if (pthread_mutex_lock (&(data->mutex)))
			{
				printd (WARN, "Lock error!\n");
			}
			//update size data for the main thread to use
			data->done_size = pdata.totalsize;
//			printd (DEBUG, "progress is %li", pdata.totalsize);

			if (pthread_cond_signal (&data->cond))
			{
				printd (WARN,"Signal error!");
			}

			if (pthread_mutex_unlock (&(data->mutex)))
			{
				printd (WARN,"Unlock error!");
			}
		}
		//usleep is another cancellation point
		usleep (data->refresh_interval);	//delay between updates
	}
	return NULL;
}
/**
@brief perform the move

This is the thread function which performs the move task.
The thread simply exits when completed

@param data pointer to struct with data which is needed here

@return NULL (irrelevant pointer)
*/
static void *
_e2p_mvbar_action (E2_ActionData *data)
{
//	printd (DEBUG, "action thread underway for %s", data->slocal);
	e2_utils_block_thread_signals ();	//block all allowed signals to this thread

	data->result = e2_task_backend_move	//no background-move option in this context
		(data->slocal, data->dlocal);
	data->completed = TRUE;
	return NULL;
}
/**
@brief update progress window details, and move item @a src to @a dest
This is called with BGL open
@param slocal path of item to be moved, localised string
@param dlocal new path of moved item, localised string
@param realmove TRUE if this is a move between devices or partititions
@param breakflag pointer to store for abort-flag
@param flags bitflags indicating task parameters
@param bdata pointer to bar data struct holding progress data
@param tdata pointer to bar data struct holding totals data
@param wdata pointer to info-window data struct

@return
*/
static void _e2p_mvbar_exec (VPATH *slocal, VPATH *dlocal, gboolean realmove,
#ifndef IS_PAUSABLE
	gboolean *breakflag,
#endif
	E2_FileTaskMode flags,
	E2_BarData *bdata, E2_BarData *tdata, E2_BarWindowData *wdata)
{
	gchar progresstext[64];	//utf-8 string, middle line of progress dialog
	//localised strings
	gchar *src = F_FILENAME_FROM_LOCALE (VPSTR (slocal));
//	gchar *dest = F_FILENAME_FROM_LOCALE (VPSTR (dlocal));
	gchar *dest_dir = g_path_get_dirname (VPSTR (dlocal));

	//before move starts, get size of item to be moved
	E2_BarData pdata = { 0,0 };
	//if (!
		e2_fs_tw (slocal, _e2p_mvbar_twcb, &pdata, -1, E2TW_PHYS E2_ERR_NONE());
	//)
	//{
		//FIXME handle error
	//}

    //create action thread
	//work with a temp name so that the backend doesn't create its own
	//(which would prevent destination-monitoring)
	gchar *templocal = e2_utils_get_tempname (VPSTR (dlocal));
#ifdef E2_VFS
	VPATH tempdata = { templocal, dlocal->spacedata };
	E2_ActionData a_data = { flags, slocal , &tempdata, FALSE, FALSE };
#else
	E2_ActionData a_data = { flags, slocal, templocal, FALSE, FALSE };
#endif
	pthread_t action_thread_id;
	gint status = pthread_create (&action_thread_id, NULL,
		(void *) _e2p_mvbar_action, &a_data);
	if (status != 0)
	{
		printd (WARN,"action-thread-create error!");
//		if (errno == EAGAIN)
//			printd (WARN, "not enough system resources");
		g_free (templocal);
		return;
	}

	//wait for task to initiate, at least
	guint waitup;
	if (!realmove)
		waitup = 1000;  //shorter delay if the move is logical (same device)
	else
		waitup = 50000;  //longer delay if the move needs to be physical
	g_usleep (waitup);

	//create progress monitor, if task not finished already
	//NOTE since we're processing 1 item at a time, this monitoring
	//could probably be handled just as well in the main thread
	if (!a_data.completed)
	{
		//create monitor thread
		E2_ProgressData m_data;
		pthread_mutex_init (&(m_data.mutex), NULL);
		pthread_cond_init (&(m_data.cond), NULL);
		//get the temp name now in use for moving the item
#ifdef E2_VFS
		m_data.dlocal = &tempdata;
#else
		m_data.dlocal = templocal;
#endif
		m_data.done_size = 0;
		//rough approach to setting the reporting interval (usec)
//		m_data.refresh_interval = pdata.totalsize * 10;
//		if (m_data.refresh_interval > MAX_UPDATE_INTERVAL)
		if (pdata.totalsize < 10000000)
			m_data.refresh_interval = MIN_UPDATE_INTERVAL;
		else
			m_data.refresh_interval = MAX_UPDATE_INTERVAL;	//with a cap
		pthread_t mon_thread_id;
		status = pthread_create (&mon_thread_id, NULL,
			(gpointer) _e2p_mvbar_progress, &m_data);
		if (status != 0)
		{
			printd (WARN,"monitor-thread-create error!");
			g_free (templocal);
			return;
		}

		if (!GTK_WIDGET_VISIBLE (wdata->dialog))
		{
			gdk_threads_enter ();
			gtk_widget_show (wdata->dialog);
			gdk_threads_leave ();
		}

		gchar *shortsrc = e2_utils_str_shorten (src, MAX_CHAR, E2_DOTS_START);
		gchar *shortdest = e2_utils_str_shorten (dest_dir, MAX_CHAR, E2_DOTS_START);
		gchar *num1 = g_strdup_printf ("%"PRIu64, bdata->count);	//gettext workaround
		gchar *num2 = g_strdup_printf ("%"PRIu64, tdata->count);
		gchar *labeltext = g_strdup_printf (
			_("moving %s\nto %s\nthis is item %s of %s"),
			shortsrc, shortdest, num1, num2);
		gdk_threads_enter ();
		gtk_label_set_text (GTK_LABEL (wdata->label), labeltext);
		gdk_threads_leave ();
		g_free (shortsrc);
		g_free (shortdest);
		g_free (num1);
		g_free (num2);
		g_free (labeltext);

		gchar *progress_format = _("%.2f MB of %.2f MB  (%.0f\%%)");
		gfloat fraction;

		//update progress when the monitor thread reports
		guint64 progress;
		while (!a_data.completed)	//loop until the action thread is completed
		{
			if (pthread_mutex_lock (&(m_data.mutex)))
			{
				printd (WARN, "Lock error!");
			}
			while (m_data.done_size == 0)
			{
				status = pthread_cond_wait (&(m_data.cond), &(m_data.mutex));
			}
			if (status != 0)
			{
				printd (WARN,"Wait error!");
			}

			progress = m_data.done_size + bdata->totalsize;
			m_data.done_size = 0;	//reset

			if (pthread_mutex_unlock (&(m_data.mutex)))
			{
				printd (WARN,"Unlock error!");
			}

#ifdef IS_PAUSABLE
			if (wdata->flags & E2_BARTASK_STOPPED)	//user wants to abort
#else
			if (*breakflag) //user wants to abort
#endif
			{
				if (pthread_cancel (mon_thread_id))
				{
					printd (WARN,"Thread cancel error!\n");
				}
				//cancel the task thread
				if (pthread_cancel (action_thread_id))
				{
					printd (WARN,"Thread cancel error!\n");
				}
				//cleanup anything part-moved
#ifdef E2_VFS
				e2_task_backend_delete (&tempdata);
#else
				e2_task_backend_delete (templocal);
#endif
				g_free (templocal);
				return;
			}

			fraction = (gdouble) progress / tdata->totalsize;
			//deal with rounding errors
			if (fraction > 1.0)
				fraction = 1.0;
			g_snprintf (progresstext, sizeof (progresstext), progress_format,
				progress / 1048576.0, tdata->totalsize / 1048576.0,
				fraction * 100.0);
			gdk_threads_enter ();
			gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wdata->progbar), progresstext);
			gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wdata->progbar), fraction);
			//CHECKME some sort of wait here ??
			gdk_threads_leave ();
		}

		//immediate signal to monitor thread to stop processing
		if (pthread_mutex_lock (&(m_data.mutex)))
		{
			printd (WARN, "Lock error!");
		}
		m_data.dlocal = NULL;
		if (pthread_mutex_unlock (&(m_data.mutex)))
		{
			printd (WARN,"Unlock error!");
		}

		//show the full-time score ASAP
		if (a_data.result) //move succeeded
		{
			progress = pdata.totalsize + bdata->totalsize;
			g_snprintf (progresstext, sizeof (progresstext), progress_format,
				progress / 1048576.0, tdata->totalsize / 1048576.0,
				100.0);
			gdk_threads_enter ();
			gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wdata->progbar), progresstext);
			gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wdata->progbar),
				(gdouble) progress / tdata->totalsize);
			//CHECKME some sort of wait here ??
			gdk_threads_leave ();
			//update progressive total
			bdata->totalsize = progress;
		}

		//cancel the monitor thread
		if (pthread_cancel (mon_thread_id))
		{
			printd (WARN,"Thread cancel error!\n");
		}
		//make sure things are finished before we move on
		pthread_join (action_thread_id, NULL);
		pthread_join (mon_thread_id, NULL);

		//CHECKME some sort of wait here ??
		usleep (100000);	//small delay to (sort of) show the completion status ?
		//need to slow down ...
		//this approach is empirical, needs to be validated for various systems
//		if (realmove && bdata->count > 300 && bdata->count%10 == 0)
//		g_usleep (50000);	//small delay to allow filesystem to complete its business
#ifdef IS_PAUSABLE
//		if (!(wdata->flags & E2_BARTASK_PAUSED))
#endif
//		gtk_widget_hide (wdata->dialog);
	}
	else
		bdata->totalsize += pdata.totalsize;

	if (a_data.result) //move succeeded
	{
#ifdef E2_VFS
		e2_task_backend_rename (&tempdata, dlocal);
#else
		e2_task_backend_rename (templocal, dlocal);
#endif
		//update the progressive totals
//		bdata->count += pdata.count;
/* final display now done earlier
		bdata->count++;
		bdata->totalsize += pdata.totalsize;
		fraction =  (gdouble) bdata->totalsize / tdata->totalsize;
		//deal with rounding errors
		if (fraction > 1.0)
			fraction = 1.0;
		//and show them
		g_snprintf (progresstext, sizeof (progresstext), progress_format,
			bdata->totalsize / 1048576.0, tdata->totalsize / 1048576.0,
			fraction * 100.0);
		gdk_threads_enter ();
		gtk_label_set_text (GTK_LABEL (wdata->label2), progresstext);
//		gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wdata->progbar), progresstext);
		gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wdata->progbar), fraction);
		//CHECKME some sort of wait here ??
		gdk_threads_leave ();
*/
	}
	else
#ifdef E2_VFS
		e2_task_backend_delete (&tempdata); //cleanup anything part-moved
#else
		e2_task_backend_delete (templocal); //cleanup anything part-moved
#endif

	g_free (templocal);

#ifdef IS_PAUSABLE
	if (wdata->flags & E2_BARTASK_PAUSEREQ)
	{	//if it was paused, the dialog must be still visible
		wdata->flags &= ~E2_BARTASK_PAUSEREQ;
		wdata->flags |= E2_BARTASK_PAUSED;
		e2_filelist_enable_refresh ();
		gdk_threads_enter ();
		gtk_main ();
		gdk_threads_leave ();
	}
#endif
	return;
}
#ifdef IS_PAUSABLE
/**
@brief callback for stop-button press and window-close event

@param widget the activated widget which triggered the callback
@param flag pointer to store for flag which will abort the process when set to TRUE;

@return TRUE always
*/
static gboolean
_e2p_mvbar_break_cb (GtkWidget *widget, E2_BarWindowData *wdata)
{
	wdata->flags |= E2_BARTASK_STOPPED;
	//handle paused moving
	if (wdata->flags & E2_BARTASK_PAUSED)
	{
		wdata->flags &= ~E2_BARTASK_PAUSED;
		e2_filelist_disable_refresh ();
		gtk_main_quit ();
		//FIXME = cleanups
		// pthread_cancel ();
	}

	return TRUE;
}
/**
@brief callback for pause-button press

@param widget the button widget which triggered the callback
@param resbutton pointer to paired button widget

@return TRUE always
*/
static gboolean
_e2p_mvbar_pause_cb (GtkWidget *widget, E2_BarWindowData *wdata)
{
	gtk_widget_set_sensitive (widget, FALSE);
	gtk_widget_set_sensitive (wdata->resume_btn, TRUE);
	gtk_widget_grab_focus (wdata->resume_btn);
	wdata->flags |= E2_BARTASK_PAUSEREQ;
	//actual pause is started in the monitor loop
	return TRUE;
}
/**
@brief callback for resume-button press

@param widget the button widget which triggered the callback
@param resbutton pointer to paired button widget

@return TRUE unless the process is not paused
*/
static gboolean
_e2p_mvbar_resume_cb (GtkWidget *widget, E2_BarWindowData *wdata)
{
	if (!(wdata->flags & E2_BARTASK_PAUSED))
		return FALSE;
	gtk_widget_set_sensitive (widget, FALSE);
	gtk_widget_set_sensitive (wdata->pause_btn, TRUE);
	gtk_widget_grab_focus (wdata->pause_btn);
	wdata->flags &= ~E2_BARTASK_PAUSED;
	e2_filelist_disable_refresh ();
	gtk_main_quit ();
	return TRUE;
}
#else //ndef IS_PAUSABLE
/**
@brief callback for stop-button press and window-close event

@param widget the activated widget which triggered the callback
@param flag pointer to store for flag which will abort the process when set to TRUE;

@return TRUE always
*/
static gboolean
_e2p_mvbar_break_cb (GtkWidget *widget,
#ifdef IS_PAUSABLE
	E2_BarFlags *flags
#else
	gboolean *flag
#endif
	)
{
#ifdef IS_PAUSABLE
	*flags |= E2_BARTASK_STOPPED;
#else
	*flag = TRUE;
#endif
	return TRUE;
}
#endif
/**
@brief mvbar action
This sets up progress display window, and processes each selected item

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

@return TRUE if action completed successfully, else FALSE
*/
static gboolean
_e2p_mvbar (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_MOVE, art, from,
		_e2p_mvbarQ, e2_task_refresh_lists));
}
static gboolean
_e2p_mvbarQ (E2_ActionTaskData *qed)
{
	//handle case of specific data instead of selection??
	if (g_str_equal (qed->currdir, qed->othrdir))
	{
		//display some message ??
		return FALSE;
	}
	E2_ERR_DECLARE
	//FIXME change dir permission if possible ?
#ifdef E2_VFS
	VPATH sdata;
	sdata.localpath = qed->currdir;
	VPATH ddata = { qed->othrdir, qed->othrspace };
	if (e2_fs_access (&ddata, W_OK E2_ERR_PTR()))
#else
	if (e2_fs_access (qed->othrdir, W_OK E2_ERR_PTR()))
#endif
	{
		e2_fs_error_local (_("Cannot put anything in %s"),
#ifdef E2_VFS
			&ddata E2_ERR_MSGL());
#else
			qed->othrdir E2_ERR_MSGL());
#endif
		E2_ERR_CLEAR
		return FALSE;
	}

	GPtrArray *names = qed->names;
	GtkWidget *vbox;	//, *hbox;
	GString *src = g_string_sized_new (1024);
	GString *dest = g_string_sized_new (1024);

	//determine whether move will be physical or logical
	gboolean realmove;
#ifdef E2_VFS
	if (qed->currspace != qed->othrspace)
		realmove = TRUE;
	else
	{
#endif
		dev_t device;
		struct stat statbuf;
#ifdef E2_VFS
		sdata.spacedata = qed->currspace;
		if (!e2_fs_stat (&sdata, &statbuf E2_ERR_NONE()))
#else
		if (!e2_fs_stat (qed->currdir, &statbuf E2_ERR_NONE()))
#endif
			device = statbuf.st_dev;
		else
			device = -1;
#ifdef E2_VFS
		if (!e2_fs_stat (&ddata, &statbuf E2_ERR_NONE()))
#else
		if (!e2_fs_stat (qed->othrdir, &statbuf E2_ERR_NONE()))
#endif
			realmove = (statbuf.st_dev != device);
		else
			realmove = TRUE;
#ifdef E2_VFS
	}
#endif
	printd (DEBUG, (realmove) ? "physical move":"logical move");

	//setup the information window
	E2_BarWindowData windowdata;
#ifdef IS_PAUSABLE
	windowdata.flags = 0;
#else
	gboolean breakflag = FALSE;	//this is for aborting the process
#endif
	windowdata.dialog = e2_dialog_create (NULL, NULL, _("moving"), NULL, NULL);
	e2_dialog_setup (windowdata.dialog, app.main_window);
//CHECKME permit Esc-key aborting ?
#ifdef IS_PAUSABLE
	g_signal_connect (G_OBJECT (windowdata.dialog), "delete-event",
		G_CALLBACK (_e2p_mvbar_break_cb), &windowdata);
#else
	g_signal_connect (G_OBJECT (windowdata.dialog), "delete-event",
		G_CALLBACK (_e2p_mvbar_break_cb), &breakflag);
#endif
	gtk_dialog_set_has_separator (GTK_DIALOG (windowdata.dialog), FALSE);

	vbox = GTK_DIALOG (windowdata.dialog)->vbox;
	windowdata.label = e2_widget_add_mid_label (vbox, "", 0.0, FALSE, 0);

//	hbox = e2_widget_add_box (vbox, FALSE, 0, TRUE, FALSE, 0);
//	windowdata.label2 = e2_widget_add_mid_label (hbox, "", 0.5, TRUE, 0);

	windowdata.progbar = gtk_progress_bar_new ();
	gtk_box_pack_start (GTK_BOX (vbox), windowdata.progbar, TRUE, TRUE, E2_PADDING_LARGE);
	gtk_widget_show (windowdata.progbar);
#ifdef IS_PAUSABLE
	//buttons in reverse order
	windowdata.resume_btn =
	e2_dialog_add_undefined_button_custom (windowdata.dialog, FALSE,
		E2_RESPONSE_USER1,_("_Resume"), GTK_STOCK_MEDIA_PLAY,
		_("Resume moving after pause"), _e2p_mvbar_resume_cb, &windowdata);
	//this one is disabled for now
	gtk_widget_set_sensitive (windowdata.resume_btn, FALSE);
	windowdata.pause_btn =
	e2_dialog_add_undefined_button_custom (windowdata.dialog, FALSE,
		E2_RESPONSE_USER2,_("_Pause"), GTK_STOCK_MEDIA_PAUSE,
		_("Suspend moving, after the current item"), _e2p_mvbar_pause_cb,
		&windowdata);
	windowdata.stop_btn =
	e2_dialog_add_undefined_button_custom (windowdata.dialog, TRUE,
		E2_RESPONSE_NOTOALL,_("_Stop"), GTK_STOCK_STOP,
		_("Abort the moving"), _e2p_mvbar_break_cb, &windowdata);
#else
	e2_dialog_add_undefined_button_custom (windowdata.dialog, TRUE,
		E2_RESPONSE_NOTOALL,_("_Stop"), GTK_STOCK_STOP,
		_("Abort the moving"), _e2p_mvbar_break_cb, &breakflag);
#endif

	//accumulate non-recursed count and total size of src item(s)
	E2_BarData totaldata = { 0,0 };
	guint count;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	for (count=0; count < names->len; count++, iterator++)
	{
		g_string_printf (src, "%s%s", qed->currdir, (*iterator)->filename);  //separator comes with dir
#ifdef E2_VFS
		sdata.localpath = src->str;
#endif
		//if (!
#ifdef E2_VFS
		e2_fs_tw (&sdata, _e2p_mvbar_twcb, &totaldata, -1,
#else
		e2_fs_tw (src->str, _e2p_mvbar_twcb, &totaldata, -1,
#endif
			E2TW_PHYS E2_ERR_NONE());
		//)
		//{
			//FIXME handle error
		//}
	}
	totaldata.count = names->len;	//not interested in nested count

	gboolean check = e2_option_bool_get ("confirm-overwrite")
#ifdef E2_VFS
		&& qed->currspace == qed->othrspace
#endif
	;
	E2_BarData progressdata = { 1,0 };
	OW_ButtonFlags extras = (totaldata.count > 1) ? BOTHALL : NONE;
	iterator = (E2_SelectedItemInfo **) names->pdata;

	e2_filelist_disable_refresh ();

	for (count=0; count < names->len; count++, iterator++)
	{
#ifdef IS_PAUSABLE
		if (windowdata.flags & E2_BARTASK_STOPPED)
#else
		if (breakflag)
#endif
			break;	//user pressed stop btn or closed info window
		//src_dir, dest_dir have trailing "/"
		g_string_printf (src, "%s%s", qed->currdir, (*iterator)->filename);  //separator comes with dir
		g_string_printf (dest, "%s%s", qed->othrdir, (*iterator)->filename);

#ifdef E2_VFS
		sdata.localpath = src->str;
		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:
#ifdef E2_VFS
					_e2p_mvbar_exec (&sdata, &ddata,
#else
					_e2p_mvbar_exec (src->str, dest->str,
#endif
						realmove,
#ifndef IS_PAUSABLE
						&breakflag,
#endif
						GPOINTER_TO_INT (qed->action->data),
						&progressdata, &totaldata, &windowdata);
					break;
				case CANCEL:
					break;
				default:
					result = NO_TO_ALL;
					break;
			}
			if (result == NO_TO_ALL)
			{
				break;
			}
		}
		else  //no overwrite, or don't care
		{
#ifdef E2_VFS
			_e2p_mvbar_exec (&sdata, &ddata,
#else
			_e2p_mvbar_exec (src->str, dest->str,
#endif
				realmove,
#ifndef IS_PAUSABLE
				&breakflag,
#endif
				GPOINTER_TO_INT (qed->action->data),
				&progressdata, &totaldata, &windowdata);
		}
		progressdata.count++;
	}
	gdk_threads_enter ();
	gtk_widget_destroy (windowdata.dialog);
	gdk_threads_leave ();

	g_string_free (src, TRUE);
	g_string_free (dest, TRUE);
#ifdef E2_FAM
	e2_filelist_request_refresh (other_view->dir, FALSE);
	e2_filelist_request_refresh (curr_view->dir, TRUE);
#else
	e2_filelist_check_dirty (GINT_TO_POINTER (1));
#endif
	e2_filelist_enable_refresh ();
	return TRUE;
}

//aname must be confined to this module
static gchar *aname;
/**
@brief initialize this plugin

@param p pointer to plugin data struct to be populated

@return TRUE if the intialization succeeded
*/
gboolean
init_plugin (Plugin *p)
{
#define ANAME "mvbar"
	aname = _("mvbar");

	p->signature = ANAME VERSION;
	p->menu_name = _("_Move");
	p->description = _("Move selected item(s), with displayed progress details");
	p->icon = "plugin_move_"E2IP".png";  //use icon file pathname if appropriate

	if (p->action == NULL)
	{
		//no need to free this
		gchar *action_name = g_strconcat (_A(5),".",aname,NULL);
		p->action = e2_plugins_action_register
		(action_name, E2_ACTION_TYPE_ITEM, _e2p_mvbar, NULL, FALSE, 0, NULL);
		return TRUE;
	}
	return FALSE;
}
/**
@brief cleanup transient things for this plugin

@param p pointer to data struct for the plugin

@return TRUE if all cleanups were completed
*/
gboolean clean_plugin (Plugin *p)
{
  gchar *action_name = g_strconcat (_A(5),".",aname,NULL);
  gboolean ret = e2_plugins_action_unregister (action_name);
  g_free (action_name);
  return ret;
}

#undef IS_PAUSABLE
