/* $Id: e2p_unpack.c 2097 2010-05-28 00:08:41Z tpgww $

Copyright (C) 2003-2010 tooar <tooar@emelfm2.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, see http://www.gnu.org/licenses.
*/

/**
@file plugins/e2p_unpack.c
@brief plugin for interfacting with several archive managers, to unpack selected item(s)
*/

#include "emelfm2.h"
#include <string.h>
#include "e2_plugins.h"
#include "e2_dialog.h"
#include "e2_task.h"
#include "e2_filelist.h"
//same enum as in pack plugin, though it need not be so
enum { TAR_GZ, TAR_BZ2, TAR_LZMA, TAR_XZ, TAR, /*DEB, RPM, */ZIP, Z7Z, RAR, ARJ, ZOO, MAXTYPES };

typedef struct _E2P_Unpackdata
{
	gchar *package;	//absolute path of source archive, UTF-8 string
	gchar *workdir;	//absolute path of dir used to unpack this archive,
					//== unpack_tmp or a tmp variant of that, UTF-8, no trailer
	gchar *last_dir;	//dir to go back to after starting a repack
	glong thispid;	//id of process which is re-packing an archive
//	guint chdir_id;	//id of timer which checks for whether to ask user what to do
	guint pack_id;	//id of timer which checks for repack completion
	E2_CDType cd_completed;	//flag set when cd to temp dir is completed
	gchar *command;	//the upack command to be run, UTF-8 string
	gboolean departing;	//TRUE when the temp dir is waiting to be processed after
						//the user selects from a what-to-do dialog
						//also used to block reentrant use of the hook function
} E2P_Unpackdata;

static gboolean _e2p_unpack_delete_dir (E2P_Unpackdata *data);
static gboolean _e2p_unpack_change_dir_hook (gchar *path, E2P_Unpackdata *data);

static gchar *unpack_tmp;	//absolute path of 'base working' directory (no trailer), utf-8 string

/**
@brief cleanup plugin data
This is executed with BGL open or closed
@param data pointer to plugin data

@return
*/
static void _e2p_unpack_cleanup (E2P_Unpackdata *data)
{
	g_free (data->package);
	g_free (data->workdir);
	g_free (data->command);
	if (data->last_dir != NULL)
		g_free (data->last_dir);
	DEALLOCATE (E2P_Unpackdata, data);
}
/**
@brief select the relevant archive-enumerator corresponding to to @a localpath

@param localpath pointer to archive data, with localised path

@return the number, or -1 upon no match
*/
static gint _e2p_unpack_match_type (VPATH *localpath)
{
	gint typecode, count;
/*	//FIXME use a better way to distinguish filetypes eg magic number
	//e.g mimetype
	file string needs to be localised VPATH *
	file(1) cannot detect all these mimetypes
*/
	gchar *thismime = e2_utils_get_mimetype (localpath);
	if (thismime != NULL)
	{
		const gchar *mimes [] =
		{
			"x-gzip",
			"x-bzip2",
			//CHECKME not sure about lzma mimetype yet
			"x-lzma-compressed-tar",
			//this may be version-specific i.e. NOT for LZMA_Alone tool in LZMA SDK <= v.4.43
			"x-lzma",
			"x-xz"
			"x-tar",
//			"x-deb",
//			"x-rpm",
			"zip",
			"x-7z", //"x-7z-compressed" ?
			"x-rar", //"x-rar-compressed" ?
			"x-arj", //"arj" ?
			"x-zoo"
		};
		gint mimecodes [] =
		{
			TAR_GZ,
			TAR_BZ2,
			TAR_LZMA,	//2 alternates for lzma
			TAR_LZMA,
			TAR_XZ,
			TAR,
	//		DEB,
	//		RPM,
			ZIP,
			Z7Z,
			RAR,
			ARJ,
			ZOO,
		};
		if (g_str_has_prefix (thismime, "application/"))
		{
			gchar *s = thismime + sizeof (gchar) * 12;
			count = sizeof (mimes) / sizeof (gchar *);
			for (typecode = 0; typecode < count; typecode++)
			{
				if (strcmp (s, mimes [typecode]) == 0)
				{
					typecode = mimecodes [typecode];
					break;
				}
			}
			if (typecode == count)
				typecode = -1;
		}
		else
			typecode = -1;

		g_free (thismime);
	}
	else //mime-check by xdg-utils(1) or file(1) not available, try some other approach
		typecode = -1;

	if (typecode == -1)
	{
		const gchar *extensions [] =
		{
			".tar.gz", ".tgz", //compress(1) does ".taz", ".tar.z",
			".tar.bz2", ".tbz2",
			".tar.lzma", ".tlz",
			".tar.xz",
			".tar",
	//		".deb",
	//		".rpm".
			".zip",
			".7z",
			".rar",
			".arj",
			".zoo",
		};
		//codes corresponding to extensions array, above
		gint extcodes [] =
		{
			TAR_GZ, TAR_GZ,
			TAR_BZ2, TAR_BZ2,
			TAR_LZMA, TAR_LZMA,
			TAR_XZ,
			TAR,
	//		DEB,
	//		RPM,
			ZIP,
			Z7Z,
			RAR,
			ARJ,
			ZOO,
		};

		//CHECKME matched string not at end of name - ok ?
		count = sizeof (extensions) / sizeof (gchar *);
		for (typecode = 0; typecode < count; typecode++)
		{
			if (g_str_has_suffix (VPSTR (localpath), extensions[typecode]))
			{
				typecode = extcodes [typecode]; //must be < count
				break;
			}
		}

		if (typecode == count)
			typecode = -1;
	}
	return typecode;
}
/**
@brief timer callback to resume idle checking

@param data pointer to unpack data struct

@return FALSE always
*/
static gboolean _e2p_unpack_pause (E2P_Unpackdata *data)
{
	data->pack_id = g_idle_add_full (G_PRIORITY_LOW,
		(GSourceFunc)_e2p_unpack_delete_dir, data, NULL);
	return FALSE;
}
/**
@brief idle callback to check whether temp-dir deletion can proceed safely
We do this check at an idle time to reduce risk of conflict, but interpose a timer
between idle callbacks to reduce their frequency
@param data pointer to unpack data struct

@return FALSE always
*/
static gboolean _e2p_unpack_delete_dir (E2P_Unpackdata *data)
{
	LISTS_LOCK
	//conservative approach - no deletion while any chance of the temp-dir being used
	if (g_atomic_int_get (&curr_view->listcontrols.cd_working) ||
		g_atomic_int_get (&curr_view->listcontrols.refresh_working) || (
#ifdef E2_VFS
		curr_view->spacedata == NULL &&	//local temp dirs
#endif
		g_str_has_prefix (curr_view->dir, data->workdir)))
	{
		LISTS_UNLOCK
		//wait before checking for an idle again
		data->pack_id = g_timeout_add (500, (GSourceFunc)_e2p_unpack_pause, data);
		return FALSE;
	}
	if (g_atomic_int_get (&other_view->listcontrols.cd_working) ||
		g_atomic_int_get (&other_view->listcontrols.refresh_working) || (
#ifdef E2_VFS
		other_view->spacedata == NULL &&	//local temp dirs
#endif
		g_str_has_prefix (other_view->dir, data->workdir)))
	{
		LISTS_UNLOCK
		//wait before checking for an idle again
		data->pack_id = g_timeout_add (500, (GSourceFunc)_e2p_unpack_pause, data);
		return FALSE;
	}
	LISTS_UNLOCK
	//kill the idle now
//	if (data->pack_id > 0)
//		g_source_remove (data->pack_id);
//	else
//		while (g_source_remove_by_user_data (data)) {}
	//now we're ready for cleanup
	e2_filelist_disable_refresh ();
	gchar *local = F_FILENAME_TO_LOCALE (data->workdir);
#ifdef E2_VFS
	VPATH ddata = { local, NULL };	//local unpacking only
	if (e2_fs_access2 (&ddata E2_ERR_NONE()) == 0)
		e2_task_backend_delete (&ddata);
#else
	if (e2_fs_access2 (local E2_ERR_NONE()) == 0)
		e2_task_backend_delete (local);
#endif

	e2_filelist_enable_refresh ();
	F_FREE (local, data->workdir);
	_e2p_unpack_cleanup (data);
	//FIXME different refresh approach with E2_ASYNC
	//in case a pane shows parent of temp dir
#ifdef E2_FAM
	e2_filelist_request_refresh (curr_view->dir, FALSE);
	e2_filelist_request_refresh (other_view->dir, TRUE);
#else
	e2_filelist_check_dirty (GINT_TO_POINTER (1));
#endif
	return FALSE;
}
/**
@brief delete the temp dir
This is executed with BGL open or closed
@param data pointer to plugin data

@return
*/
static void _e2p_unpack_clear (E2P_Unpackdata *data)
{
	//ensure BGL is open and manage any race with refresh function
	data->pack_id = g_idle_add_full (G_PRIORITY_LOW,
		(GSourceFunc)_e2p_unpack_delete_dir, data, NULL);
}
/* *
@brief timer callback to periodically check whether a repack is completed
Needed if repack command does not include cleanup
@param data pointer to plugin data struct

@return TRUE if the repack process is still running
*/
/*static gboolean _e2p_unpack_clean_dir (E2P_Unpackdata *data)
{
	if (e2_command_find_process ((guint)data->thispid))
		return TRUE;	//wait some more
	//FIXME only delete dir if repack command succeeded, or else don't delete
	//original archive until repack succeeded

	//now we're ready for cleanup
//	g_source_remove (data->pack_id);
//	if (_e2p_unpack_delete_dir (data))
		//keep trying until the deletion is done
		g_idle_add_full (G_PRIORITY_LOW,
			(GSourceFunc)_e2p_unpack_delete_dir, data, NULL);
	return FALSE;
}
*/
/**
@brief repack the temp dir
This is executed inside a callback with BGL closed
@param data pointer to plugin data
@param from the dialog where the action was initiated

@return
*/
static void _e2p_unpack_repack (E2P_Unpackdata *data, gpointer from)
{
#ifdef E2_VFSTMP
	if (curr_view->spacedata != NULL)	//CHECKME fuse ok?
	{
		_e2p_unpack_cleanup (data);
		return;
	}
#endif
	const gchar *pack_str [MAXTYPES] =
	{	//these command strings are in same order as enum
		//they are all designed to be executed from the unpack temp dir, and
		//on all its contents (recursive if appropriate)
		//CHECKME "." always remains correct during repack execution ?
		">tar cf - . | gzip - > %s",
		">tar cf - . | bzip2 - > %s",	//default block size -9 is not significantly better, maybe use -3 instead ?
		">tar cf - . | lzma - > %s",	//default compresssion -7, maybe use -2 instead ?
		">tar cf - . | xz - > %s",
		"tar cf %s .",
//		deb 'pack' command ?
//		rpm 'pack' command does not exist
		">zip -r - . > %s",
		">7za a -t7z -so . > %s",	//CHECKME
		//updates of archives probably don't capture renames,deletes
		"rar u -as -ol -tl -r %s .", //OK

		"arj a -u -r -s -a -2s %s .", //CHECKME
		"zoo unP %s ."	//CHECKME replace, not update ? deletions ?
	};
/*
compress ANSI files: 7za a -tzip archive.zip file1 file2 ... fileN
compress ANSI dir:   7za a -tzip archive.zip dirnametocompress\
compress UNICODE files: 7za a -t7z archive.7z file1 file2 ... fileN
compress UNICODE dir:   7za a -t7z archive.7z dirnametocompress\
To backup directories you must use tar :
   - to backup a directory : tar cf - directory | 7za a -si directory.tar.7z
   - to restore your backup : 7za x -so directory.tar.7z | tar xf -

ar -x package-file.deb
a deb top-level package contains a small file with a version number,
and two tar archives - one contains the package description, MD5 checksums and
install/uninstall scripts, the other the files to be installed.
*/
	gchar *package = data->package;
#ifdef E2_VFSTMP
	VPATH ddata;
	ddata.path = F_FILENAME_TO_LOCALE (package);
	ddata.spacedata = curr_view->spacedata; //FIXME
	gint index = _e2p_unpack_match_type (&ddata);
#else
	gchar *local = F_FILENAME_TO_LOCALE (package);
	gint index = _e2p_unpack_match_type ((VPATH*)local);
#endif
	gchar *fmt, *qp;
	g_free (data->command);

	switch (index)
	{
		case TAR_GZ:
		case TAR_BZ2:
		case TAR_LZMA:
		case TAR_XZ:
		case TAR:
		case ZIP:
		case Z7Z:
			//these types of archive are overwritten
			fmt = g_strconcat (pack_str [index], " && mv -f %s %s && rm -rfd %s", NULL);
			qp = e2_utils_quote_string (package);
			gchar *tmp = e2_utils_get_tempname
#ifdef E2_VFSTMP
				(ddata.path);
#else
				(local);
#endif
			gchar *utftmp = F_FILENAME_FROM_LOCALE (tmp);
//tag E2_BADQUOTES
			gchar *qpt = e2_utils_quote_string (utftmp);
			g_free (tmp);
			F_FREE (utftmp, tmp);
			data->command = g_strdup_printf (fmt, qpt, qpt, qp, data->workdir);
			g_free (qpt);
			break;
		case RAR:
		case ARJ:
		case ZOO:
			//these types of archive are just updated
			fmt = g_strconcat (pack_str [index], " && rm -rfd %s", NULL);
			qp = e2_utils_quote_string (package);
			data->command = g_strdup_printf (fmt, qp, data->workdir, NULL);
//			printd (DEBUG, "repack command %s", data->command);
			break;
		default:
		//case -1:	//should never happen
			fmt = qp = NULL;	//warning prevention
			_e2p_unpack_cleanup (data);
			return;
	}

	g_free (fmt);
	g_free (qp);

#ifdef E2_VFSTMP
	F_FREE (ddata.path, package);
#else
	F_FREE (local, package);
#endif

	gint res = e2_command_run_at (data->command, data->workdir,
		E2_COMMAND_RANGE_DEFAULT, from
#ifdef E2_COMMANDQ
		, T/F ?
#endif
	);
	if (res == 0)
	{
/* use this if command doesn't include cleanups i.e. rm workdir
	//FIXME race here if something else is run at a bad time, so find the pid
	//with matching command-string instead
		E2_TaskRuntime *td = e2_task_find_last_running_child (TRUE);
		data->thispid = (td != NULL) ? td->pid : 0;
		//periodically check whether re-build finished, when so, cleanup the temp dir
		//CHECKME make this timer cancellable at session end
		data->pack_id = g_timeout_add (500, (GSourceFunc) _e2p_unpack_clean_dir, data);
		//CHECKME refreshing etc may move CWD away from the temp dir while the repack is underway
*/
		_e2p_unpack_cleanup (data); //or else, just cleanup data
	}
	else
	{
//		data->thispid = 0;
		printd (WARN, "repack command cannot be run");
		//WARN user ?
		_e2p_unpack_cleanup (data);
	}
}
/**
@brief callback for "what-to-do" dialog's "response" signal

@param dialog UNUSED the dialog where the response was triggered
@param response the response for the clicked button
@param rt pointer to data for dialog

@return
*/
static void _e2p_unpack_response_decode_cb (GtkDialog *dialog, gint response,
	E2P_Unpackdata *data)
{
	gtk_widget_destroy (GTK_WIDGET(dialog));
	//do this outside of current hook func, as we need to check both panes and data
	e2_hook_unregister (&app.pane1.hook_change_dir,
		(HookFunc)_e2p_unpack_change_dir_hook, data, TRUE);
	e2_hook_unregister (&app.pane2.hook_change_dir,
		(HookFunc)_e2p_unpack_change_dir_hook, data, TRUE);

	switch (response)
	{
		case E2_RESPONSE_USER1:	//repack the temp dir
			_e2p_unpack_repack (data, (gpointer)dialog);
			break;
		case E2_RESPONSE_USER2: //keep the unpacked archive
			_e2p_unpack_cleanup (data);
		//	case GTK_RESPONSE_CANCEL:
			break;
		//case E2_RESPONSE_REMOVE:
		default: //this will pick up GTK_RESPONSE_NONE or GTK_RESPONSE_DELETE_EVENT
			_e2p_unpack_clear (data);
			break;
	}
}
/**
@brief hook function for cd in either pane (app.paneX.hook_change_dir)
This is initiated from a cd thread, and with BGL open/off
@param path UNUSED path of an opened directory, utf-8 string
@param data pointer to operation data struct
@return TRUE always so hook remains active
*/
static gboolean _e2p_unpack_change_dir_hook (gchar *path, E2P_Unpackdata *data)
{
	if (data->departing)
		return TRUE;	//a callback has begun, ignore this other cd
	data->departing = TRUE;	//temp block on nested checking
	/* this hookfunc is called toward the end of a cd process, there's no need
	   to check for various "busy" flags before proceeding
	   the first cd will be into the temp dir, so path will be that dir with
	   trailing separator */
	if (
#ifdef E2_VFSTMP
		//FIXME dirs when not mounted local
		curr_view->spacedata == NULL //local temp dirs
		other_view->spacedata == NULL //local temp dirs
#else
		( g_str_has_prefix (curr_view->dir, data->workdir)
		|| g_str_has_prefix (other_view->dir, data->workdir))
#endif
	   )
	{
		data->departing = FALSE;	//unblock
		return TRUE;
	}

	//user changed dir, now neither pane is for anywhere in unpack-dir tree
	printd (DEBUG, "ready to cleanup unpack dir");

	//ask user what to do with the unpacked items
	GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_QUESTION,
		_("What do you want to do with the unpacked item(s) ?"), NULL,
		(ResponseFunc)_e2p_unpack_response_decode_cb, data);

	e2_dialog_add_simple_button (dialog, GTK_STOCK_CLEAR,
		_("Re_pack"), E2_RESPONSE_USER1);
	e2_dialog_add_simple_button (dialog, GTK_STOCK_APPLY,
		_("_Retain"), E2_RESPONSE_USER2);
	GtkWidget *button = e2_dialog_add_simple_button (dialog, GTK_STOCK_DELETE,
		_("_Delete"), E2_RESPONSE_REMOVE);

	gdk_threads_enter ();
	e2_dialog_setup (dialog, app.main_window);
	e2_dialog_run (dialog, NULL, 0);
	gtk_widget_grab_focus (button);
	gtk_window_present (GTK_WINDOW (dialog));
	gdk_threads_leave ();

	return TRUE; 	//no hook cleanup here
					//done in response cb, as we need to check both panes and data
}
/**
@brief unpack plugin action : unpack a supported archive into a temp dir
Best if refreshing is disabled when this action is initiated
@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_unpack (gpointer from, E2_ActionRuntime *art)
{
	//these unpack-command strings are in same order as enum
	//all are executed from the temp dir (as some can only do that)
	//all are ascii (no conversion to utf8 before execution)
	static gchar *cmd_str [MAXTYPES] =
	{
		//each unpack goes into a separate newly-created work dir, so there's
		//no need to worry manage over-writing when unpacking with these commands
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(darwin)
		//tar decompression options -z, -j, --lzma are GNU extensions
		//ditto for --overwrite, and for -C, if that's wanted
		">gzip -cd %s | tar -xpf -",
		">bzip2 -cd %s | tar -xpf -",
		">lzma -cd %s | tar -xpf -",
		">xz -cd %s | tar -xpf -",
		"tar -xpf %s",
#else
		//note: an --atime-preserve in these tar commands prevents the
		//file-monitoring process from noticing anything in the temp dir
		//--overwrite not needed
		"tar -xpzf %s",
		"tar -xpjf %s",
		"tar --lzma -xpf %s",
		"tar --xz -xpf %s",
		"tar -xpf %s",
#endif
		//.deb unpack command by Martin Zelaia (has GNU-specific tar options)
		//">>mkdir ./DEBIAN ./CONTENTS;ar -x %s | tar -xfz control.tar.gz -C ./DEBIAN | tar -xfz data.tar.gz -C ./CONTENTS; rm control.tar.gz data.tar.gz;cp ./DEBIAN/control ./INFO;rm ./debian-binary";
		//">rpm2cpio %s | cpio -id"  this is ok, but no spec data from a non-src file, and there's no re-pack command available
		"unzip -o %s",  //or "unzip -o -d %s %s"
/*
decompress ANSI:    7za x archive.zip -odirname -aoa
decompress UNICODE: 7za x archive.7z -odirname -aoa
*/
		"7za x %s -aoa", //or ??
		"rar x -o+ %s", //rar will only extract to current dir
		"arj x -y %s", //or "arj x -y "UNPACKPATH" "UNPACKPATH, //NOTE swapped order of archive & path
		"zoo xO %s"	//zoo will only extract to current dir
	};

	//CHECKME 	e2_filelist_disable_refresh ();
	FileInfo *info = e2_fileview_get_selected_first_local (curr_view, FALSE);
	if (info == NULL)
	{
//		e2_filelist_enable_refresh ();
		return FALSE;	//nothing selected
	}

#ifdef E2_VFSTMP
	VPATH ddata;
	ddata.path = e2_utils_dircat (curr_view, info->filename, TRUE);
	ddata.spacedata = curr_view->spacedata;
	gint index = _e2p_unpack_match_type (&ddata);
	g_free (ddata.path);
#else
	gchar *local = e2_utils_dircat (curr_view, info->filename, TRUE);
	gint index = _e2p_unpack_match_type ((VPATH*)local);
	g_free (local);
#endif
	if (index == -1)
	{
		e2_output_print_error (_("Selected item is not a supported archive"), FALSE);
//		e2_filelist_enable_refresh ();
		return FALSE;
	}

	//the current temp dir may be deleted when the user exits that dir, so it
	//would be very bad to open an archive inside the temp dir
	//CHECKME recursive unpacking would be ok (with different temp dirs for each
	//archive) if there's a way to handle (prevent?) repacking when the 'parent'
	//temp dir is alredy gone
#ifdef E2_VFSTMP
	//FIXME handle space-change too
	if (curr_view->spacedata == ? || strstr (curr_view->dir, unpack_tmp) != NULL)
#else
	if (strstr (curr_view->dir, unpack_tmp) != NULL)
#endif
	{
		e2_output_print_error (_("Recursive unpack is not supported"), FALSE);
//		e2_filelist_enable_refresh ();
		return FALSE;
	}

	gchar *converted = F_FILENAME_TO_LOCALE (unpack_tmp);
	gchar *workdir = e2_utils_get_tempname (converted);
#ifdef E2_VFS
	VPATH ddata = { workdir, NULL };	//local unpacking only
#endif
	F_FREE (converted, unpack_tmp);
	//(re)make it
#ifdef E2_VFS
	if (e2_fs_recurse_mkdir (&ddata, 0777 E2_ERR_NONE()))
#else
	if (e2_fs_recurse_mkdir (workdir, 0777 E2_ERR_NONE()))
#endif
	{
		converted = F_DISPLAYNAME_FROM_LOCALE (workdir);
		gchar *msg = g_strdup_printf ("Could not create working directory '%s'",
			converted);
		e2_output_print_error (msg, TRUE);
		F_FREE (converted, workdir);
		g_free (workdir);
//		e2_filelist_enable_refresh ();
		return FALSE;
	}

	E2P_Unpackdata *data = ALLOCATE0 (E2P_Unpackdata);
	CHECKALLOCATEDWARN (data, return FALSE;)
	data->workdir = D_FILENAME_FROM_LOCALE (workdir);
	g_free (workdir);

	converted = F_FILENAME_FROM_LOCALE (info->filename); //not much race chance, don't D_...
#ifdef E2_VFSTMP
	FIXME dir when not mounted local
#else
	data->package = e2_utils_strcat (curr_view->dir, converted);  //dir has trailing /
	F_FREE (converted, info->filename);
//tag E2_BADQUOTES
	gchar *qp = e2_utils_quote_string (data->package);
#endif
	//need no conversion of command encoding
	data->command = g_strdup_printf (cmd_str [index], qp); //CHECKME append 1>/dev/null
	g_free (qp);
	e2_window_set_cursor (GDK_WATCH);
	//unpack the archive into the temp directory
	gint result = e2_command_run_at (data->command, data->workdir,
		E2_COMMAND_RANGE_DEFAULT, from
#ifdef E2_COMMANDQ
		, T/F ?
#endif
		);
	e2_window_set_cursor (GDK_LEFT_PTR);
	if (result != 0)
	{
		workdir = F_FILENAME_TO_LOCALE (data->workdir);
#ifdef E2_VFS
		ddata.path = workdir;
		e2_task_backend_delete (&ddata);
#else
		e2_task_backend_delete (workdir);
#endif
		F_FREE (workdir, data->workdir);
		_e2p_unpack_cleanup (data);
//		e2_filelist_enable_refresh ();
		return FALSE;
	}
	//now go see it
	e2_pane_change_dir (NULL, data->workdir);
	//setup to clean when temp dir not open anymore
	e2_hook_register (&app.pane1.hook_change_dir,
		(HookFunc)_e2p_unpack_change_dir_hook, data);
	e2_hook_register (&app.pane2.hook_change_dir,
		(HookFunc)_e2p_unpack_change_dir_hook, data);

//	e2_filelist_enable_refresh ();
	return TRUE;
}

//aname must be confined to this module
static gchar *aname;
/**
@brief plugin initialization function, called by main program

@param p ptr to plugin data struct

@return TRUE if the initialization succeeds, else FALSE
*/
gboolean init_plugin (Plugin *p)
{
	//once-only, setup the working dir name
	unpack_tmp = e2_utils_get_temp_path ("-unpack");
	//strip the trailing ".tmp~0" as we will append a suffix like that for each unpack
	gchar *s = strrchr (unpack_tmp, '.');
	*s = '\0';

#define ANAME "unpack"
#ifdef E2_VFS
	aname = _("unpack_with_plugin");
#else
	aname = _A(104);
#endif
	p->signature = ANAME VERSION;
	p->menu_name = _("_Unpack");
	p->description = _("Unpack archive (tar, tar.gz, tar.bz2, zip, 7z, rar, arj, zoo) into a temporary directory");
	p->icon = "plugin_"ANAME E2ICONTB;  //use icon file pathname if appropriate

	if (p->action == NULL)
	{
		//don't free name string here
		E2_Action plugact =
		{g_strconcat (_A(6),".",aname,NULL),_e2p_unpack,FALSE,E2_ACTION_TYPE_ITEM,0,NULL,NULL};
		p->action = e2_plugins_action_register (&plugact);
		if G_LIKELY((p->action != NULL))
			return TRUE;
		g_free (plugact.name);
	}
	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)
{
	//FIXME prevent unload when an unpack is underway
	//clear any current hook(s)
	while (e2_hook_unregister (&app.pane1.hook_change_dir,
		(HookFunc)_e2p_unpack_change_dir_hook, NULL, FALSE)) {}
	while (e2_hook_unregister (&app.pane2.hook_change_dir,
		(HookFunc)_e2p_unpack_change_dir_hook, NULL, FALSE)) {}

	gchar *action_name = g_strconcat (_A(6),".",aname,NULL);
	gboolean ret = e2_plugins_action_unregister (action_name);
	g_free (action_name);
	g_free (unpack_tmp);
	return ret;
}
