/***************************************************************************
	  	                        vfs.cpp
  	                    -------------------
    copyright            : (C) 2000 by Shie Erlich & Rafi Yanai
    e-mail               : krusader@users.sourceforge.net
    web site             : http://krusader.sourceforge.net
  ------------------------------------------------------------------------
   the vfs class is an extendable class which by itself does (almost)
   nothing. other VFSs like the normal_vfs inherits from this class and
   make it possible to use a consistent API for all types of VFSs.

 ***************************************************************************

   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 <unistd.h>
#include <time.h>
#include <tqeventloop.h>
#include <tdeapplication.h>
#include <kdirsize.h>
#include <klargefile.h>
#include <tqdir.h>
#include "vfs.h"
#include "../krusader.h"
#include "../defaults.h"

vfs::vfs(TQObject* panel, bool quiet): vfs_busy(false), quietMode(quiet),disableRefresh(false),postponedRefreshURL(),
                                      invalidated(true),panelConnected(false),vfs_tempFilesP(0),vfileIterator(0),deletePossible( true ),
                                      deleteRequested( false ) {
		

	setVfsFilesP( new vfileDict() );
	if ( panel ){
		panelConnected = true;
		connect(this,TQ_SIGNAL(startUpdate()),panel,TQ_SLOT(slotStartUpdate()));
		connect(this,TQ_SIGNAL(incrementalRefreshFinished( const KURL& )),panel,TQ_SLOT(slotGetStats( const KURL& )));
	}
	else quietMode = true;
}

vfs::~vfs() {
	if( !deletePossible )
		fprintf( stderr, "INTERNAL ERROR: trying to delete vfs while it is used! This may cause crash. Hoping the best...\n" );
	clear(); // please don't remove this line. This informs the view about deleting the references
	delete vfs_filesP;
}

TDEIO::filesize_t vfs::vfs_totalSize(){
	TDEIO::filesize_t temp=0;
	class vfile* vf=vfs_getFirstFile();
		
	while (vf!=0){
		if ( (vf->vfile_getName() != ".") && ( vf->vfile_getName() != "..")  
		     && !(vf->vfile_isDir()) )
				temp+=vf->vfile_getSize();
		vf=vfs_getNextFile();
	}
	return temp;                                                     
}

bool vfs::vfs_refresh(TDEIO::Job* job){
  if(job && job->error()){
		job->showErrorDialog(krApp);
	}
	return vfs_refresh(vfs_origin);
}

KURL vfs::fromPathOrURL( const TQString &originIn )
{
  TQString password, loginName, origin = originIn;
  bool bugfix = false;
  
  if ( originIn.contains( ":/" ) && !originIn.startsWith( "/" ) )
  {
    // breakdown the url;
    /* FIXME: untill KDE fixes the bug we have to check for
       passwords and users with @ in them... */
    bugfix = origin.find("@") != origin.findRev("@");
    if(bugfix){
      if(origin.find(":") != origin.findRev(":", origin.findRev("@") )){
        int passStart = origin.find( ":",origin.find(":")+1 )+1;
        int passLen = origin.findRev("@")-passStart;
        password = origin.mid(passStart,passLen);
        origin = origin.remove(passStart-1,passLen+1);
      }
      if(origin.find("@") != origin.findRev("@")){
        int usrStart = origin.find( "/" )+1;
        if(origin.at(usrStart) == '/') ++usrStart;
        int usrLen = origin.findRev("@")-usrStart;
        loginName = origin.mid(usrStart,usrLen);
        origin = origin.remove(usrStart,usrLen+1);
      }
    }
  }
  KURL url = KURL::fromPathOrURL( origin );
  if(loginName.isEmpty()) loginName = url.user();
  if(password.isEmpty())  password  = url.pass();
  if(bugfix){
    url.setPass(password);
    url.setUser(loginName);
  }

  return url;
}

TQString vfs::pathOrURL( const KURL &originIn, int trailingSlash )
{
  if( originIn.isLocalFile() )
    return originIn.path( trailingSlash );
  return originIn.prettyURL( trailingSlash );
}  

void vfs::setVfsFilesP(vfileDict* dict){
	vfs_filesP=dict;
	vfs_tempFilesP = new vfileDict();
	vfs_tempFilesP->setAutoDelete( true );
	dict->setAutoDelete(true);
	if( vfileIterator ) delete vfileIterator;
	vfileIterator = new TQDictIterator<vfile>(*dict);
}

bool vfs::vfs_refresh(){ 
	if( vfs_busy )
		return false;
	
	if( invalidated ) // invalidated fs requires total refresh
		return vfs_refresh( vfs_getOrigin() );
	
	if( disableRefresh )
	{
		postponedRefreshURL = vfs_getOrigin();
		return false;
	}
	
	vfs_busy = true;
	// and populate it
	krConfig->setGroup("Advanced");
	int maxIncrementalRefreshFileNr = krConfig->readNumEntry("Max Incremental Refresh File Nr", 50);
	krConfig->setGroup("Look&Feel");
	bool showHidden = krConfig->readBoolEntry("Show Hidden",_ShowHidden);
	bool res = populateVfsList(vfs_getOrigin(),showHidden);

	TQString name;
	if( res ){
		// check if the maximum incremental refresh number is achieved
		int diff = vfs_filesP->count() - vfs_tempFilesP->count();
		if( diff < 0 )
			diff = -diff;
		if( diff >= maxIncrementalRefreshFileNr )
		{
			// total filesystem refresh is cheaper than incremental refresh for many files
			clear();
			delete vfs_filesP;
			setVfsFilesP( vfs_tempFilesP );
			vfs_busy = false;
			
			emit startUpdate();
			return true;
		}

		// compare the two list emiting signals when needed;;
		for( vfile* vf = vfs_getFirstFile(); vf ;  ){
			name = vf->vfile_getName();
			vfile* newVf = (*vfs_tempFilesP)[name];
			if( !newVf ){
				// the file was deleted..
				emit deletedVfile(name);
				vfs_filesP->remove(name);
				// the remove() advance our iterator ! 
				vf = vfileIterator->current();
			} else {
				if( *vf != *newVf ){
					// the file was changed..
					*vf = *newVf;
					emit updatedVfile(vf);
				}
				vf=vfs_getNextFile();
			}
			vfs_tempFilesP->remove(name);
		} 
		// everything thats left is a new file
		TQDictIterator<vfile> it(*vfs_tempFilesP);
		for(vfile* vf=it.toFirst(); vf; vf=(++it)){
			// sanity checking
			if( !vf || (*vfs_filesP)[vf->vfile_getName()] ) continue;
			
			vfile* newVf = new vfile();
			*newVf = *vf;
			vfs_filesP->insert(newVf->vfile_getName(),newVf);
			emit addedVfile(newVf);
		}
	}
	
	// delete the temporary vfiles
	vfs_tempFilesP->clear();
	vfs_busy = false;
	
	emit incrementalRefreshFinished( vfs_origin );
	
	return res; 
}

bool vfs::vfs_refresh(const KURL& origin){
	if( vfs_busy )
		return false;
	
	if( disableRefresh )
	{
		postponedRefreshURL = origin;
		return true;
	}

	if( !invalidated && origin.equals(vfs_getOrigin(),true) ) return vfs_refresh();
	
	vfs_busy = true;
	
	krConfig->setGroup("Look&Feel");
	bool showHidden = krConfig->readBoolEntry("Show Hidden",_ShowHidden);

	vfs_tempFilesP->clear();
	// and re-populate it
	if (!populateVfsList(origin,showHidden) ) 
	{
		vfs_busy = false;
		return false;
	}
	
	clear();
	delete vfs_filesP;
	setVfsFilesP( vfs_tempFilesP );
	vfs_busy = false;
	
	emit startUpdate();
	
	invalidated = false;
	return true;
}

void vfs::vfs_enableRefresh(bool enable){
	if (vfs_type != NORMAL) return;
	if (disableRefresh == !enable) return; // if gets called twice by mistake
	disableRefresh = quietMode = !enable;
	if( enable && !postponedRefreshURL.isEmpty() ) vfs_refresh( postponedRefreshURL );
	postponedRefreshURL = KURL();
}

void vfs::clear()
{
	emit cleared();
	vfs_filesP->clear();
}

bool vfs::vfs_processEvents() {
	if( deleteRequested )
		return false;        
	deletePossible = false;
	tqApp->eventLoop() ->processEvents( TQEventLoop::AllEvents | TQEventLoop::WaitForMore );
	deletePossible = true;        
	if( deleteRequested ) {
		emit deleteAllowed();
		return false;
	}        
	return true;                
}

void vfs::vfs_requestDelete() {
	if( deletePossible )
		emit deleteAllowed();
	deleteRequested = true;
}

void vfs::slotKdsResult( TDEIO::Job* job){
	if( job && !job->error() ){
		KDirSize* kds = static_cast<KDirSize*>(job);
		*kds_totalSize += kds->totalSize();
		*kds_totalFiles += kds->totalFiles();
		*kds_totalDirs += kds->totalSubdirs();
	}
	*kds_busy = true;
}

void vfs::vfs_calcSpace( TQString name , TDEIO::filesize_t* totalSize, unsigned long* totalFiles, unsigned long* totalDirs, bool* stop ) {
	calculateURLSize( vfs_getFile( name ), totalSize, totalFiles, totalDirs, stop );
}        
        
void vfs::calculateURLSize( KURL url,  TDEIO::filesize_t* totalSize, unsigned long* totalFiles, unsigned long* totalDirs, bool* stop ) {
	if ( stop && *stop ) return ;        
	kds_busy = stop;
	kds_totalSize  = totalSize ;
	kds_totalFiles = totalFiles;
	kds_totalDirs  = totalDirs;

	if( url.isLocalFile() ) {
		vfs_calcSpaceLocal( url.path(-1), totalSize, totalFiles, totalDirs, stop );
		return;
	} else {
		stat_busy = true;
		TDEIO::StatJob* statJob = TDEIO::stat( url, false );
		connect( statJob, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotStatResultArrived( TDEIO::Job* ) ) );
		while ( !(*stop) && stat_busy ) {usleep(1000);}
		if( entry.isEmpty()  ) return; // statJob failed
		KFileItem kfi(entry, url, true );        
		if( kfi.isFile() || kfi.isLink() ) {
			*totalFiles++;
			*totalSize += kfi.size();
			return;
		}
	}
	
	KDirSize* kds  = KDirSize::dirSizeJob( url );
	connect( kds, TQ_SIGNAL( result( TDEIO::Job* ) ), this, TQ_SLOT( slotKdsResult( TDEIO::Job* ) ) );
	while ( !(*stop) ){ 
		// we are in a sepetate thread - so sleeping is OK
		usleep(1000);
	}
}

void vfs::vfs_calcSpaceLocal(TQString name ,TDEIO::filesize_t *totalSize,unsigned long *totalFiles,unsigned long *totalDirs, bool* stop){
  if ( *stop ) return;
  if (!name.contains("/")) name = vfs_workingDir()+"/"+name;
  if (name == "/proc") return;

  KDE_struct_stat stat_p;                // KDE lstat is necessary as TQFileInfo and KFileItem 
  KDE_lstat(name.local8Bit(),&stat_p);   //         reports wrong size for a symbolic link
  
  if( S_ISLNK(stat_p.st_mode) || !S_ISDIR(stat_p.st_mode) ) { // single files are easy : )
    ++(*totalFiles);
    (*totalSize) += stat_p.st_size;
  }
  else{  // handle directories
    // avoid a nasty crash on un-readable dirs
    bool readable = ::access( name.local8Bit(), R_OK | X_OK ) == 0;
    if( !readable )
      return;
      
    TQDir dir(name);    
    if ( !dir.exists() ) return;
    
    ++(*totalDirs);
    dir.setFilter(TQDir::All | TQDir::System | TQDir::Hidden);
    dir.setSorting(TQDir::Name | TQDir::DirsFirst);

    // recurse on all the files in the directory
    TQFileInfoList* fileList = const_cast<TQFileInfoList*>(dir.entryInfoList());
    for (TQFileInfo* qfiP = fileList->first(); qfiP != 0; qfiP = fileList->next()){
      if ( *stop ) return;
      if (qfiP->fileName() != "." && qfiP->fileName() != "..")
        vfs_calcSpaceLocal(name+"/"+qfiP->fileName(),totalSize,totalFiles,totalDirs,stop);
    }
  }
}

        
void vfs::slotStatResultArrived( TDEIO::Job* job ) {
	if( !job || job->error() ) entry = TDEIO::UDSEntry();
	else entry = static_cast<TDEIO::StatJob*>(job)->statResult();
	stat_busy = false;
}
        

TQValueList<vfile*> vfs::vfs_search(const KRQuery& filter) {
	TQValueList<vfile*> result;
	for ( vfile *vf = vfs_getFirstFile(); vf != 0 ; vf = vfs_getNextFile() )
		if (filter.match(vf)) 
			result.append(vf);
	return result;
}

#include "vfs.moc"
