/***************************************************************************
                          kbeardeletejob.cpp  -  description
                             -------------------
    begin                : lör apr 27 2002
    copyright            : (C) 2003 by Björn Sahlström
    email                : kbjorn@users.sourceforge.net
 ***************************************************************************/

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

//////////////////////////////////////////////////////////////////////
// Qt specific include files
#include <qfile.h>
//////////////////////////////////////////////////////////////////////
// KDE specific include files
#include <dcopclient.h>
#include <kio/observer.h>
#include <kio/scheduler.h>
#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <kdirwatch.h>
#include <kprotocolinfo.h>
#include <kapplication.h>
#include <kmessagebox.h>
//////////////////////////////////////////////////////////////////////
// Application specific include files
#include "kbeardeletejob.h"
#include "kbearlistjob.h"
#include "connectionmanager.h"
#include <assert.h>
#define REPORT_TIMEOUT 200

#define KIO_ARGS QByteArray packedArgs; QDataStream stream( packedArgs, IO_WriteOnly ); stream

using namespace KBear;
//-----------------------------------------------
KBearDeleteJob::KBearDeleteJob( const KURL::List& src, bool shred, bool showProgressInfo )
	:	KIO::Job(showProgressInfo), m_totalSize( 0 ),
		m_processedSize( 0 ), m_fileProcessedSize( 0 ),
		m_processedFiles( 0 ), m_processedDirs( 0 ),
		m_totalFilesDirs( 0 ),  m_srcList(src),
		m_currentStat(m_srcList.begin()),
		m_shred(shred), m_reportTimer(0)
{
	if( showProgressInfo ) {
		connect( this, SIGNAL( totalFiles( KIO::Job*, unsigned long ) ),
					Observer::self(), SLOT( slotTotalFiles( KIO::Job*, unsigned long ) ) );
		connect( this, SIGNAL( totalDirs( KIO::Job*, unsigned long ) ),
					Observer::self(), SLOT( slotTotalDirs( KIO::Job*, unsigned long ) ) );

		m_reportTimer=new QTimer(this);
		connect(m_reportTimer,SIGNAL(timeout()),this,SLOT(slotReport()));
		// we disconnect this, and call it from slotInfoMessage instead, since we don't want to flood
		// Observer with all log info
		disconnect( this, SIGNAL( infoMessage( KIO::Job*, const QString & ) ),
                 Observer::self(), SLOT( slotInfoMessage( KIO::Job*, const QString & ) ) );
/*
		connect( this, SIGNAL( infoMessage( KIO::Job*, const QString & ) ),
                 this, SLOT( slotInfoMessage( KIO::Job*, const QString & ) ) );
*/
		//this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
		m_reportTimer->start(REPORT_TIMEOUT,false);
	}
/* will be called by connectionManager
 	QTimer::singleShot(0, this, SLOT(slotStart()));
*/
}
//-----------------------------------------------
KBearDeleteJob::~KBearDeleteJob() {
}
//-----------------------------------------------
void KBearDeleteJob::slotInfoMessage( KIO::Job* job, const QString& message ) {
	static const QString& respString = KGlobal::staticQString( "resp" );
	static const QString& commandString = KGlobal::staticQString( "command" );
	static const QString& multiString = KGlobal::staticQString( "multi-line" );
	static const QString& internalString = KGlobal::staticQString( "internal" );
	// Only emit for non log messages
	if( message.left( respString.length() ) != respString
		&& message.left( commandString.length() ) !=commandString
		&& message.left( multiString.length() ) != multiString
		&& message.left( internalString.length() ) != internalString )
	{
		Observer::self()->slotInfoMessage( job, message );
	}

	emit infoMessage( job, message );
}
//-----------------------------------------------
void KBearDeleteJob::start( int ID) {
	// First thing to do is to notify that we started.
	// this is used to disable gui on filesys part if single connection
	// is selected.
	m_ID = ID;
	statNextSrc();
}
//-----------------------------------------------
//this is called often, so calling the functions
//from Observer here directly might improve the performance a little bit
//aleXXX
void KBearDeleteJob::slotReport()
{
   if (m_progressId==0)
      return;

   Observer * observer = Observer::self();

   emit deleting( this, m_currentURL );
   observer->slotDeleting(this,m_currentURL);

   switch( state ) {
        case STATE_STATING:
        case STATE_LISTING:
            emit totalSize( this, m_totalSize );
            emit totalFiles( this, files.count() );
            emit totalDirs( this, dirs.count() );
            break;
        case STATE_DELETING_DIRS:
            emit processedDirs( this, m_processedDirs );
            observer->slotProcessedDirs(this,m_processedDirs);
            emitPercent( m_processedFiles + m_processedDirs, m_totalFilesDirs );
            break;
        case STATE_DELETING_FILES:
            observer->slotProcessedFiles(this,m_processedFiles);
            emit processedFiles( this, m_processedFiles );
            if (!m_shred)
               emitPercent( m_processedFiles, m_totalFilesDirs );
            break;
   }
}
//-----------------------------------------------
void KBearDeleteJob::slotEntries(KIO::Job* job, const KIO::UDSEntryList& list) {
	KIO::UDSEntryListConstIterator it = list.begin();
	KIO::UDSEntryListConstIterator end = list.end();
	for( ; it != end; ++it ) {
		KIO::UDSEntry::ConstIterator it2 = (*it).begin();
		bool bDir = false;
		bool bLink = false;
		QString relName;
		int atomsFound(0);
		for( ; it2 != (*it).end(); it2++ ) {
			switch ((*it2).m_uds) {
				case KIO::UDS_FILE_TYPE:
					bDir = S_ISDIR((*it2).m_long);
					atomsFound++;
					break;
				case KIO::UDS_NAME:
					relName = ((*it2).m_str);
					atomsFound++;
					break;
				case KIO::UDS_LINK_DEST:
					bLink = !(*it2).m_str.isEmpty();
					atomsFound++;
					break;
				case KIO::UDS_SIZE:
					m_totalSize += (off_t)((*it2).m_long);
					atomsFound++;
					break;
				default:
					break;
			}
			if( atomsFound == 4 )
				break;
		}
		assert(!relName.isEmpty());
		if( relName != ".." && relName != ".") {
			KURL url = ((KIO::SimpleJob *)job)->url(); // assumed to be a dir
			url.addPath( relName );
			//kdDebug(7007) << "KBearDeleteJob::slotEntries " << relName << " (" << url.prettyURL() << ")" << endl;
			if( bLink )
				symlinks.append( url );
			else if( bDir )
				dirs.append( url );
			else
				files.append( url );
		}
	}
}


void KBearDeleteJob::statNextSrc() {
	//kdDebug(7007) << "statNextSrc" << endl;
	if( m_currentStat != m_srcList.end() ) {
		m_currentURL = (*m_currentStat);

		// if the file system doesn't support deleting, we do not even stat
		if(!KProtocolInfo::supportsDeleting(m_currentURL)) {
			KMessageBox::information( 0, KIO::buildErrorString(KIO::ERR_CANNOT_DELETE, m_currentURL.prettyURL()));
			++m_currentStat;
			statNextSrc(); // we could use a loop instead of a recursive call :)
			return;
		}
		// Stat it
		state = STATE_STATING;
		KIO::SimpleJob * job = KIO::stat( m_currentURL, true, 1, false );
		ConnectionManager::getInstance()->attachJob( m_ID, job );
		//kdDebug(7007) << "KIO::stat (DeleteJob) " << m_currentURL.prettyURL() << endl;
		addSubjob(job);
		//if ( m_progressId ) // Did we get an ID from the observer ?
		//  Observer::self()->slotDeleting( this, *it ); // show asap
	}
	else {
		m_totalFilesDirs = files.count()+symlinks.count() + dirs.count();
		slotReport();
		// Now we know which dirs hold the files we're going to delete.
		// To speed things up and prevent double-notification, we disable KDirWatch
		// on those dirs temporarily (using KDirWatch::self, that's the instanced
		// used by e.g. kdirlister).
		for( QStringList::Iterator it = m_parentDirs.begin() ; it != m_parentDirs.end() ; ++it )
			KDirWatch::self()->stopDirScan( *it );
		state = STATE_DELETING_FILES;
		deleteNextFile();
	}
}
//-----------------------------------------------
void KBearDeleteJob::deleteNextFile() {
	//kdDebug(7007) << "deleteNextFile" << endl;
	if( ! files.isEmpty() || ! symlinks.isEmpty() ) {
		KIO::SimpleJob *job;
		do {
			// Take first file to delete out of list
			KURL::List::Iterator it = files.begin();
			bool isLink = false;
			if( it == files.end() ) { // No more files
				it = symlinks.begin(); // Pick up a symlink to delete
				isLink = true;
			}
			// Use shredding ?
			if( m_shred && (*it).isLocalFile() && !isLink ) {
				// KShred your KTie
				KIO_ARGS << int(3) << (*it).path();
				job = KIO::special(KURL("file:/"), packedArgs, false /*no GUI*/);
				KIO::Scheduler::scheduleJob(job);
				m_currentURL=(*it);
				connect( job, SIGNAL( processedSize( KIO::Job*, KIO::filesize_t ) ),
							this, SLOT( slotProcessedSize( KIO::Job*, KIO::filesize_t ) ) );
			}
			else {
				// Normal deletion
				// If local file, try do it directly
				if( (*it).isLocalFile() && unlink( QFile::encodeName((*it).path()) ) == 0 ) {
					job = 0;
					m_processedFiles++;
					if( m_processedFiles % 300 == 0 ) { // update progress info every 300 files
						m_currentURL = *it;
						slotReport();
					}
				}
				else { // if remote - or if unlink() failed (we'll use the job's error handling in that case)
					job = KIO::file_delete( *it, false /*no GUI*/);
					ConnectionManager::getInstance()->attachJob( m_ID, job );
					m_currentURL=(*it);
				}
			}
			if( isLink )
				symlinks.remove( it );
			else
				files.remove( it );
			if( job ) {
				addSubjob( job );
				return;
			}
			// loop only if direct deletion worked (job=0) and there is something else to delete
		} while ( ! job && ( ! files.isEmpty() || ! symlinks.isEmpty() ) );
	}
	state = STATE_DELETING_DIRS;
	deleteNextDir();
}
//-----------------------------------------------
void KBearDeleteJob::deleteNextDir() {
	if( ! dirs.isEmpty() ) { // some dirs to delete ?
		do {
			// Take first dir to delete out of list - last ones first !
			KURL::List::Iterator it = dirs.fromLast();
			// If local dir, try to rmdir it directly
			if( (*it).isLocalFile() && ::rmdir( QFile::encodeName((*it).path()) ) == 0 ) {
				m_processedDirs++;
				if( m_processedDirs % 100 == 0 ) { // update progress info every 100 dirs
					m_currentURL = *it;
					slotReport();
				}
			}
			else {
				KIO::SimpleJob *job = KIO::rmdir( *it );
				ConnectionManager::getInstance()->attachJob( m_ID, job );
				dirs.remove(it);
				addSubjob( job );
				return;
			}
			dirs.remove(it);
		} while ( !dirs.isEmpty() );
	}

	// Re-enable watching on the dirs that held the deleted files
	for( QStringList::Iterator it = m_parentDirs.begin() ; it != m_parentDirs.end() ; ++it )
		KDirWatch::self()->restartDirScan( *it );

	// Finished - tell the world
	if( !m_srcList.isEmpty() ) {
		QByteArray data;
		QDataStream arg( data, IO_WriteOnly );
		arg << m_srcList;
		kapp->dcopClient()->send( "*", "KDirNotify", "FilesRemoved(const KURL::List&)", data );
    }
    if (m_reportTimer!=0)
       m_reportTimer->stop();
    emitResult();
}
//-----------------------------------------------
void KBearDeleteJob::slotProcessedSize( KIO::Job*, KIO::filesize_t data_size ) {
	// Note: this is the same implementation as CopyJob::slotProcessedSize but
	// it's different from FileCopyJob::slotProcessedSize - which is why this
	// is not in Job.

	m_fileProcessedSize = data_size;
	//kdDebug(7007) << "KBearDeleteJob::slotProcessedSize " << (unsigned int) (m_processedSize + m_fileProcessedSize) << endl;
	emit processedSize( this, m_processedSize + m_fileProcessedSize );
     // calculate percents
	unsigned long ipercent = m_percent;

	if( m_totalSize == 0 )
		m_percent = 100;
	else
		m_percent = (unsigned long)(( (float)(m_processedSize + m_fileProcessedSize) / (float)m_totalSize ) * 100.0);

	if( m_percent > ipercent ) {
		emit percent( this, m_percent );
		//kdDebug(7007) << "KBearDeleteJob::slotProcessedSize - percent =  " << (unsigned int) m_percent << endl;
	}
}

void KBearDeleteJob::slotResult( Job *job )
{
   switch ( state )
   {
   case STATE_STATING:
      {
         // Was there an error while stating ?
         if (job->error() )
         {
            // Probably : doesn't exist
            Job::slotResult( job ); // will set the error and emit result(this)
            return;
         }

         // Is it a file or a dir ?
         KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
         bool bDir = false;
         bool bLink = false;
         KIO::filesize_t size = (KIO::filesize_t)-1;
         KIO::UDSEntry::ConstIterator it2 = entry.begin();
         int atomsFound(0);
         for( ; it2 != entry.end(); it2++ )
         {
            if ( ((*it2).m_uds) == KIO::UDS_FILE_TYPE )
            {
               bDir = S_ISDIR( (mode_t)(*it2).m_long );
               atomsFound++;
            }
            else if ( ((*it2).m_uds) == KIO::UDS_LINK_DEST )
            {
               bLink = !((*it2).m_str.isEmpty());
               atomsFound++;
            }
            else if ( ((*it2).m_uds) == KIO::UDS_SIZE )
            {
               size = (*it2).m_long;
               atomsFound++;
            };
            if (atomsFound==3) break;
         }

         KURL url = ((KIO::SimpleJob*)job)->url();

         subjobs.remove( job );
         assert( subjobs.isEmpty() );

         if (bDir && !bLink)
         {
            // Add toplevel dir in list of dirs
            dirs.append( url );
            if ( url.isLocalFile() && !m_parentDirs.contains( url.path(-1) ) )
                m_parentDirs.append( url.path(-1) );

            //kdDebug(7007) << " Target is a directory " << endl;
            // List it
            state = STATE_LISTING;
            KBearListJob *newjob = KBearListJob::listRecursive( m_ID, url, false, true );
            ConnectionManager::getInstance()->attachJob( m_ID, newjob );

            connect(newjob, SIGNAL(entries( KIO::Job *,
                                            const KIO::UDSEntryList& )),
                    SLOT( slotEntries( KIO::Job*,
                                       const KIO::UDSEntryList& )));
            addSubjob(newjob);
         }
         else
         {
            if ( bLink ) {
                //kdDebug(7007) << " Target is a symlink" << endl;
                symlinks.append( url );
            } else {
                //kdDebug(7007) << " Target is a file" << endl;
                files.append( url );
            }
            if ( url.isLocalFile() && !m_parentDirs.contains( url.directory(-1) ) )
                m_parentDirs.append( url.directory(-1) );
            ++m_currentStat;
            statNextSrc();
         }
      }
      break;
   case STATE_LISTING:
      if ( job->error() )
      {
         // Try deleting nonetheless, it may be empty (and non-listable)
      }
      subjobs.remove( job );
      assert( subjobs.isEmpty() );
      ++m_currentStat;
      statNextSrc();
      break;
   case STATE_DELETING_FILES:
      if ( job->error() )
      {
         Job::slotResult( job ); // will set the error and emit result(this)
         return;
      }
      subjobs.remove( job );
      assert( subjobs.isEmpty() );
      m_processedFiles++;

      deleteNextFile();
      break;
   case STATE_DELETING_DIRS:
      if ( job->error() )
      {
         Job::slotResult( job ); // will set the error and emit result(this)
         return;
      }
      subjobs.remove( job );
      assert( subjobs.isEmpty() );
      m_processedDirs++;
      //emit processedDirs( this, m_processedDirs );
      //if (!m_shred)
         //emitPercent( m_processedFiles + m_processedDirs, m_totalFilesDirs );

      deleteNextDir();
      break;
   default:
      assert(0);
   }
}
//-----------------------------------------------
KBearDeleteJob* KBearDeleteJob::del( const KURL::List& src, bool shred, bool showProgressInfo ) {
	return new KBearDeleteJob( src, shred, showProgressInfo );
}
//-----------------------------------------------
#ifndef NO_INCLUDE_MOCFILES
#include "kbeardeletejob.moc"
#endif
