/*
 *   File name:	kcleanup.cpp
 *   Summary:	Support classes for KDirStat
 *   License:	LGPL - See file COPYING.LIB for details.
 *   Author:	Stefan Hundhammer <sh@suse.de>
 *
 *   Updated:	2004-11-23
 */


#include <stdlib.h>
#include <tqapplication.h>
#include <tqregexp.h>

#include <kapp.h>
#include <kprocess.h>
#include <kdebug.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <kglobalsettings.h>

#include "kcleanup.h"
#include "kdirsaver.h"

#define VERBOSE_RUN_COMMAND	1
#define SIMULATE_COMMAND	0

using namespace KDirStat;


KCleanup::KCleanup( TQString		id,
		    TQString		command,
		    TQString		title,
		    KActionCollection *	parent	)
    
    : KAction( title,
	       0,	// accel
	       parent,
	       id )
    
    , _id	( id	  )
    , _command	( command )
    , _title	( title	  )
{
    _selection		= 0;
    _enabled		= true;
    _worksForDir	= true;
    _worksForFile	= false;
    _worksForDotEntry	= false;
    _worksLocalOnly	= true;
    _recurse		= false;
    _askForConfirmation	= false;
    _refreshPolicy	= noRefresh;
    
    KAction::setEnabled( false );
}


KCleanup::KCleanup( const KCleanup &src )
    : KAction()
{
    copy( src );
}


KCleanup &
KCleanup::operator= ( const KCleanup &src )
{
    copy( src );
    
    return *this;
}


void
KCleanup::copy( const KCleanup &src )
{
    setTitle( src.title() );
    _selection		= src.selection();
    _id			= src.id();
    _command		= src.command();
    _enabled		= src.enabled();
    _worksForDir	= src.worksForDir();
    _worksForFile	= src.worksForFile();
    _worksForDotEntry	= src.worksForDotEntry();
    _worksLocalOnly	= src.worksLocalOnly();
    _recurse		= src.recurse();
    _askForConfirmation	= src.askForConfirmation();
    _refreshPolicy	= src.refreshPolicy();
}


void
KCleanup::setTitle( const TQString &title )
{
    _title = title;
    KAction::setText( _title );
}


bool
KCleanup::worksFor( KFileInfo *item ) const
{
    if ( ! _enabled || ! item )
	return false;

    if ( worksLocalOnly() && ! item->tree()->isFileProtocol() )
	return false;

    if	( item->isDotEntry() )	return worksForDotEntry();
    if	( item->isDir() )	return worksForDir();
    
    return worksForFile();
}


void
KCleanup::selectionChanged( KFileInfo *selection )
{
    bool enabled = false;
    _selection = selection;

    if ( selection )
    {
	enabled = worksFor( selection );

	if ( ! selection->isFinished() )
	{
	    // This subtree isn't finished reading yet

	    switch ( _refreshPolicy )
	    {
		// Refresh policies that would cause this subtree to be deleted
		case refreshThis:
		case refreshParent:
		case assumeDeleted:

		    // Prevent premature deletion of this tree - this would
		    // cause a core dump for sure. 
		    enabled = false;
		    break;
			
		default:
		    break;
	    }
	    
	}
    }
    
    KAction::setEnabled( enabled );
}


void
KCleanup::executeWithSelection()
{
    if ( _selection )
	execute( _selection );
}


bool
KCleanup::confirmation( KFileInfo * item )
{
    TQString msg;

    if ( item->isDir() || item->isDotEntry() )
    {
	msg = i18n( "%1\nin directory %2" ).arg( cleanTitle() ).arg( item->url() );
    }
    else
    {
	msg = i18n( "%1\nfor file %2" ).arg( cleanTitle() ).arg( item->url() );
    }

    if ( KMessageBox::warningContinueCancel( 0,				// parentWidget
					     msg,			// message
					     i18n( "Please Confirm" ),	// caption
					     i18n( "Confirm" )		// confirmButtonLabel
					     ) == KMessageBox::Continue )
	return true;
    else
	return false;
}


void
KCleanup::execute( KFileInfo *item )
{
    if ( worksFor( item ) )
    {
	if ( _askForConfirmation && ! confirmation( item ) )
	    return;
	    
	KDirTree  * tree = item->tree();
	
	executeRecursive( item );
	
	switch ( _refreshPolicy )
	{
	    case noRefresh:
		// Do nothing.
		break;


	    case refreshThis:
		tree->refresh( item );
		break;


	    case refreshParent:
		tree->refresh( item->parent() );
		break;


	    case assumeDeleted:

		// Assume the cleanup action has deleted the item.
		// Modify the KDirTree accordingly.

		tree->deleteSubtree( item );

		// Don't try to figure out a reasonable next selection - the
		// views have to do that while handling the subtree
		// deletion. Only the views have any knowledge about a
		// reasonable strategy for choosing a next selection. Unlike
		// the view items, the KFileInfo items don't have an order that
		// makes any sense to the user.

		break;
	}
    }

    emit executed();
}


void
KCleanup::executeRecursive( KFileInfo *item )
{
    if ( worksFor( item ) )
    {
	if ( _recurse )
	{
	    // Recurse into all subdirectories.

	    KFileInfo * subdir = item->firstChild();

	    while ( subdir )
	    {
		if ( subdir->isDir() )
		{
		    /**
		     * Recursively execute in this subdirectory, but only if it
		     * really is a directory: File children might have been
		     * reparented to the directory (normally, they reside in
		     * the dot entry) if there are no real subdirectories on
		     * this directory level.
		     **/
		    executeRecursive( subdir );
		}
		subdir = subdir->next();
	    }
	}


	// Perform cleanup for this directory.

	runCommand( item, _command );
    }
}


const TQString
KCleanup::itemDir( const KFileInfo *item ) const
{
    TQString dir = item->url();

    if ( ! item->isDir() && ! item->isDotEntry() )
    {
	dir.replace ( TQRegExp ( "/[^/]*$" ), "" );
    }

    return dir;
}


TQString
KCleanup::cleanTitle() const
{
    // Use the cleanup action's title, if possible.
   
    TQString title = _title;

    if ( title.isEmpty() )
    {
	title = _id;
    }

    // Get rid of any "&" characters in the text that denote keyboard
    // shortcuts in menus.
    title.replace( TQRegExp( "&" ), "" );

    return title;
}


TQString
KCleanup::expandVariables( const KFileInfo *	item,
			   const TQString &	unexpanded ) const
{
    TQString expanded = unexpanded;

    expanded.replace( TQRegExp( "%p" ),
		      "\"" + TQString::fromLocal8Bit( item->url() )  + "\"" );
    expanded.replace( TQRegExp( "%n" ),
		      "\"" + TQString::fromLocal8Bit( item->name() ) + "\"" );

    if ( KDE::versionMajor() >= 3 && KDE::versionMinor() >= 4 )
	expanded.replace( TQRegExp( "%t" ), "trash:/" );
    else
	expanded.replace( TQRegExp( "%t" ), KGlobalSettings::trashPath() );

    return expanded;
}

#include <tqtextcodec.h>
void
KCleanup::runCommand ( const KFileInfo *	item,
		       const TQString &		command ) const
{
    KProcess	proc;
    KDirSaver	dir( itemDir( item ) );
    TQString	cmd( expandVariables( item, command ));

#if VERBOSE_RUN_COMMAND
    printf( "\ncd " );
    fflush( stdout );
    system( "pwd" );
    TQTextCodec * codec = TQTextCodec::codecForLocale();
    printf( "%s\n", (const char *) codec->fromUnicode( cmd ) );
    fflush( stdout );
#endif

#if ! SIMULATE_COMMAND
    proc << "sh";
    proc << "-c";
    proc << cmd;

    switch ( _refreshPolicy )
    {
	case noRefresh:
	case assumeDeleted:

	    // In either case it is no use waiting for the command to
	    // finish, so we are starting the command as a pure
	    // background process.

	    proc.start( KProcess::DontCare );
	    break;


	case refreshThis:
	case refreshParent:

	    // If a display refresh is due after the command, we need to
	    // wait for the command to be finished in order to avoid
	    // performing the update prematurely, so we are starting this
	    // process in blocking mode.

	    TQApplication::setOverrideCursor( waitCursor );
	    proc.start( KProcess::Block );
	    TQApplication::restoreOverrideCursor();
	    break;
    }

#endif
}


void
KCleanup::readConfig()
{
    KConfig *config = kapp->config();
    KConfigGroupSaver saver( config, _id );

    bool valid = config->readBoolEntry( "valid", false	);

    // If the config section requested exists, it should contain a
    // "valid" field with a true value. If not, there is no such
    // section within the config file. In this case, just leave this
    // cleanup action undisturbed - we'd rather have a good default
    // value (as provided - hopefully - by our application upon
    // startup) than a generic empty cleanup action.
   
    if ( valid )
    {
	_command		= config->readEntry	( "command"		);
	_enabled		= config->readBoolEntry ( "enabled"		);
	_worksForDir		= config->readBoolEntry ( "worksForDir"		);
	_worksForFile		= config->readBoolEntry ( "worksForFile"	);
	_worksForDotEntry	= config->readBoolEntry ( "worksForDotEntry"	);
	_worksLocalOnly		= config->readBoolEntry ( "worksLocalOnly"	);
	_recurse		= config->readBoolEntry ( "recurse"		, false	);
	_askForConfirmation	= config->readBoolEntry ( "askForConfirmation"	, false	);
	_refreshPolicy		= (KCleanup::RefreshPolicy) config->readNumEntry( "refreshPolicy" );
	setTitle( config->readEntry( "title" ) );
    }
}


void
KCleanup::saveConfig() const
{
    KConfig *config = kapp->config();
    KConfigGroupSaver saver( config, _id );

    config->writeEntry( "valid",		true			);
    config->writeEntry( "command",		_command		);
    config->writeEntry( "title",		_title			);
    config->writeEntry( "enabled",		_enabled		);
    config->writeEntry( "worksForDir",		_worksForDir		);
    config->writeEntry( "worksForFile",		_worksForFile		);
    config->writeEntry( "worksForDotEntry",	_worksForDotEntry	);
    config->writeEntry( "worksLocalOnly",	_worksLocalOnly		);
    config->writeEntry( "recurse",		_recurse		);
    config->writeEntry( "askForConfirmation",	_askForConfirmation	);
    config->writeEntry( "refreshPolicy",	(int) _refreshPolicy	);
}


// EOF
