/***************************************************************************
                        kmountman.cpp
                     -------------------
copyright            : (C) 2000 by Shie Erlich & Rafi Yanai
e-mail               : krusader@users.sourceforge.net
web site             : http://krusader.sourceforge.net
---------------------------------------------------------------------------

A 

db   dD d8888b. db    db .d8888.  .d8b.  d8888b. d88888b d8888b.
88 ,8P' 88  `8D 88    88 88'  YP d8' `8b 88  `8D 88'     88  `8D
88,8P   88oobY' 88    88 `8bo.   88ooo88 88   88 88ooooo 88oobY'
88`8b   88`8b   88    88   `Y8b. 88~~~88 88   88 88~~~~~ 88`8b
88 `88. 88 `88. 88b  d88 db   8D 88   88 88  .8D 88.     88 `88.
YP   YD 88   YD ~Y8888P' `8888Y' YP   YP Y8888D' Y88888P 88   YD

                                             S o u r c e    F i l e

***************************************************************************
*                                                                         *
*   This program 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 2 of the License, or     *
*   (at your option) any later version.                                   *
*                                                                         *
***************************************************************************/


#include <sys/param.h>
#include <time.h>
#include "kmountman.h" 
// KDE includes
#include <kmessagebox.h>
#include <kprocess.h>
#include <klocale.h>
#include <kpopupmenu.h>
#include <kdebug.h>

// Krusader includes
#include "../krusader.h"
#include "../defaults.h"
#include "../Dialogs/krdialogs.h"
#include "../krservices.h"
#include "kmountmangui.h"
#include <unistd.h>
#include "../Dialogs/krprogress.h"
#include "../VFS/krpermhandler.h"

#ifdef _OS_SOLARIS_
#define FSTAB "/etc/vfstab"
#else
#define FSTAB "/etc/fstab"
#endif

static int __delayedIdx; // ugly: pass the processEvents deadlock

KMountMan::KMountMan() : TQObject(), Operational( false ), waiting(false), mountManGui( 0 ) {
   _actions = 0L;

	// added as a precaution, although we use kde services now
	if( !KrServices::cmdExist( "df" ) || !KrServices::cmdExist( "mount" ) ) {
    	Operational = false;
	} else { 
		Operational = true;
	}

	// list of FS that we don't manage at all
	invalid_fs << "swap" << "/dev/pts" << "tmpfs" << "devpts" << "sysfs" << "rpc_pipefs" << "usbfs" << "binfmt_misc";
#if defined(BSD)
	invalid_fs << "procfs";
#else
	invalid_fs << "proc";
#endif

	// list of FS that we don't allow to mount/unmount
	nonmount_fs << "supermount";
	{
		KConfigGroupSaver saver(krConfig, "Advanced");
		TQStringList nonmount = TQStringList::split(",", krConfig->readEntry("Nonmount Points", _NonMountPoints));
		nonmount_fs_mntpoint += nonmount;
		// simplify the white space
		for ( TQStringList::Iterator it = nonmount_fs_mntpoint.begin(); it != nonmount_fs_mntpoint.end(); ++it ) {
			*it = (*it).simplifyWhiteSpace();
		}
	}
	
}

KMountMan::~KMountMan() {}

bool KMountMan::invalidFilesystem(TQString type) {
	return (invalid_fs.contains(type) > 0);
}

// this is an ugly hack, but type can actually be a mountpoint. oh well...
bool KMountMan::nonmountFilesystem(TQString type, TQString mntPoint) {
	return((nonmount_fs.contains(type) > 0) || (nonmount_fs_mntpoint.contains(mntPoint) > 0));
}

void KMountMan::mainWindow() {
   mountManGui = new KMountManGUI();
   delete mountManGui;   /* as KMountManGUI is modal, we can now delete it */
   mountManGui = 0; /* for sanity */
}

KMountPoint *KMountMan::findInListByMntPoint(KMountPoint::List &lst, TQString value) {
	KMountPoint *m;
	for (KMountPoint::List::iterator it = lst.begin(); it != lst.end(); ++it) {
		m = *it;
		if (m->mountPoint() == value)
			return m;
	}
	
	return 0;
}

void KMountMan::jobResult(KIO::Job *job) {
	waiting = false;
	if ( job->error() )
		job->showErrorDialog( 0 );
}

void KMountMan::mount( TQString mntPoint, bool blocking ) {
	KMountPoint::List possible = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions);
	KMountPoint *m = findInListByMntPoint(possible, mntPoint);
	if (!m) return;
	if (blocking)
	   waiting = true; // prepare to block
	KIO::SimpleJob *job = KIO::mount(false, m->mountType().local8Bit(), m->mountedFrom(), m->mountPoint(), false);
	new KrProgress(job);
	connect(job, TQT_SIGNAL(result(KIO::Job* )), this, TQT_SLOT(jobResult(KIO::Job* )));
	while (blocking && waiting) {
		tqApp->processEvents();
		usleep( 1000 );
	}
}

void KMountMan::unmount( TQString mntPoint, bool blocking ) {
	if (blocking)
	   waiting = true; // prepare to block
	KIO::SimpleJob *job = KIO::unmount(mntPoint, false);
	new KrProgress(job);
	connect(job, TQT_SIGNAL(result(KIO::Job* )), this, TQT_SLOT(jobResult(KIO::Job* )));
	while (blocking && waiting) {
		tqApp->processEvents();
		usleep( 1000 );
	}
}

KMountMan::mntStatus KMountMan::getStatus( TQString mntPoint ) {	
	KMountPoint::List::iterator it;
   KMountPoint *m;
	
	// 1: is it already mounted
	KMountPoint::List current = KMountPoint::currentMountPoints();
	m = findInListByMntPoint(current, mntPoint);
	if (m) 
		return MOUNTED;
	
	// 2: is it a mount point but not mounted?
	KMountPoint::List possible = KMountPoint::possibleMountPoints();
	m = findInListByMntPoint(possible, mntPoint);
	if (m) 
		return NOT_MOUNTED;
	
	// 3: unknown
	return DOESNT_EXIST;
}


void KMountMan::toggleMount( TQString mntPoint ) {
	mntStatus status = getStatus(mntPoint);
	switch (status) {
		case MOUNTED:
			unmount(mntPoint);
			break;
		case NOT_MOUNTED:
			mount(mntPoint);
			break;
		case DOESNT_EXIST:
			// do nothing: no-op to make the compiler quiet ;-)
			break;
	}
}

void KMountMan::autoMount( TQString path ) {
   if ( getStatus( path ) == NOT_MOUNTED )
      mount( path );
}

void KMountMan::eject( TQString mntPoint ) {
   KShellProcess proc;
   proc << KrServices::fullPathName( "eject" ) << "'" + mntPoint + "'";
   proc.start( KProcess::Block );
   if ( !proc.normalExit() || proc.exitStatus() != 0 )         // if we failed with eject
      KMessageBox::information( 0, i18n( "Error ejecting device! You need to have 'eject' in your path." ), i18n( "Error" ), "CantExecuteEjectWarning" );
}

// returns true if the path is an ejectable mount point (at the moment CDROM and DVD)
bool KMountMan::ejectable( TQString path ) {
#if !defined(BSD) && !defined(_OS_SOLARIS_)
	KMountPoint::List possible = KMountPoint::possibleMountPoints();
	KMountPoint *m = findInListByMntPoint(possible, path);
	if (m && (m->mountType()=="iso9660" || m->mountedFrom().left(7)=="/dev/cd" || m->mountedFrom().left(8)=="/dev/dvd"))
			return KrServices::cmdExist( "eject" );
#endif

   return false;
}


// a mountMan special version of KIO::convertSize, which deals
// with large filesystems ==> >4GB, it actually recieve size in
// a minimum block of 1024 ==> data is KB not bytes
TQString KMountMan::convertSize( KIO::filesize_t size ) {
   float fsize;
   TQString s;
   // Tera-byte
   if ( size >= 1073741824 ) {
      fsize = ( float ) size / ( float ) 1073741824;
      if ( fsize > 1024 )         // no name for something bigger than tera byte
         // let's call it Zega-Byte, who'll ever find out? :-)
         s = i18n( "%1 ZB" ).arg( KGlobal::locale() ->formatNumber( fsize / ( float ) 1024, 1 ) );
      else
         s = i18n( "%1 TB" ).arg( KGlobal::locale() ->formatNumber( fsize, 1 ) );
   }
   // Giga-byte
   else if ( size >= 1048576 ) {
      fsize = ( float ) size / ( float ) 1048576;
      s = i18n( "%1 GB" ).arg( KGlobal::locale() ->formatNumber( fsize, 1 ) );
   }
   // Mega-byte
   else if ( size > 1024 ) {
      fsize = ( float ) size / ( float ) 1024;
      s = i18n( "%1 MB" ).arg( KGlobal::locale() ->formatNumber( fsize, 1 ) );
   }
   // Kilo-byte
   else {
      fsize = ( float ) size;
      s = i18n( "%1 KB" ).arg( KGlobal::locale() ->formatNumber( fsize, 0 ) );
   }
   return s;
}


// populate the pop-up menu of the mountman tool-button with actions
void KMountMan::quickList() {
   if ( !Operational ) {
      KMessageBox::error( 0, i18n( "MountMan is not operational. Sorry" ) );
      return ;
   }

   // clear the popup menu
   ( ( KToolBarPopupAction* ) krMountMan ) ->popupMenu() ->clear();

   // create lists of current and possible mount points
   KMountPoint::List current = KMountPoint::currentMountPoints();
   KMountPoint::List possible = KMountPoint::possibleMountPoints();

   // create a popupmenu, displaying mountpoints with possible actions
   // also, populate a small array with the actions
   if ( _actions )
      delete[] _actions;
   _actions = new TQString[ possible.size() ];

   KMountPoint::List::iterator it;
   KMountPoint *m;
   int idx;
   for ( it = possible.begin(), idx = 0; it != possible.end(); ++it, ++idx ) {
      m = *it;
		// skip nonmountable file systems
		if (nonmountFilesystem(m->mountType(), m->mountPoint()) || invalidFilesystem(m->mountType()))
			continue;
      // does the mountpoint exist in current list? if so, it can only
      // be umounted, otherwise, it can be mounted
      bool needUmount = false;
      KMountPoint::List::iterator otherIt;
      for ( otherIt = current.begin(); otherIt != current.end(); ++otherIt ) {
         if ( ( *otherIt ) ->mountPoint() == m->mountPoint() ) { // found it, in needs umount
            needUmount = true;
            break;
         }
      }
      // add the item to the menu
      _actions[ idx ] = TQString( needUmount ? "_U_" : "_M_" ) + m->mountPoint();
      TQString text = TQString( ( needUmount ? i18n( "Unmount" ) : i18n( "Mount" ) ) ) + " " + m->mountPoint() +
                     " (" + m->mountedFrom() + ")";


      ( ( KToolBarPopupAction* ) krMountMan ) ->popupMenu() ->insertItem( text, idx );
   }
   connect( ( ( KToolBarPopupAction* ) krMountMan ) ->popupMenu(), TQT_SIGNAL( activated( int ) ),
            this, TQT_SLOT( delayedPerformAction( int ) ) );

}

void KMountMan::delayedPerformAction( int idx ) {
   __delayedIdx = idx;
   TQTimer::singleShot(0, this, TQT_SLOT(performAction(int)));   
}

void KMountMan::performAction( int idx ) {
   while ( tqApp->hasPendingEvents() )
      tqApp->processEvents();

   // ugly !!! take idx from the value put there by delayedPerformAction so 
   // as to NOT DIE because of a processEvents deadlock!!! @#$@!@
   idx = __delayedIdx;
   
   if ( idx < 0 )
      return ;
   bool domount = _actions[ idx ].left( 3 ) == "_M_";
   TQString mountPoint = _actions[ idx ].mid( 3 );
   if ( !domount ) { // umount
      unmount( mountPoint);
   } else { // mount
      mount( mountPoint);
   }

   // free memory
   delete[] _actions;
   _actions = 0L;
   disconnect( ( ( KToolBarPopupAction* ) krMountMan ) ->popupMenu(), TQT_SIGNAL( activated( int ) ), 0, 0 );
}

#include "kmountman.moc"
