You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kdirstat/kdirstat/kdirtree.cpp

1637 lines
32 KiB

/*
* File name: kdirtree.cpp
* Summary: Support classes for KDirStat
* License: LGPL - See file COPYING.LIB for details.
* Author: Stefan Hundhammer <sh@suse.de>
*
* Updated: 2005-01-07
*/
#include "config.h"
#include <string.h>
#include <sys/errno.h>
#include <tqtimer.h>
#include <kapp.h>
#include <tdelocale.h>
#include <tdeconfig.h>
#include "kdirtree.h"
#include "kdirtreeiterators.h"
#include "kdirtreeview.h"
#include "kdirsaver.h"
#include "tdeio/job.h"
#include "tdeio/netaccess.h"
#define HAVE_STUPID_COMPILER 0
using namespace KDirStat;
KFileInfo::KFileInfo( KDirTree * tree,
KDirInfo * parent,
const char * name )
: _parent( parent )
, _next( 0 )
, _tree( tree )
{
_isLocalFile = true;
_isSparseFile = false;
_name = name ? name : "";
_device = 0;
_mode = 0;
_links = 0;
_size = 0;
_blocks = 0;
_mtime = 0;
}
KFileInfo::KFileInfo( const TQString & filenameWithoutPath,
struct stat * statInfo,
KDirTree * tree,
KDirInfo * parent )
: _parent( parent )
, _next( 0 )
, _tree( tree )
{
TQ_CHECK_PTR( statInfo );
_isLocalFile = true;
_name = filenameWithoutPath;
_device = statInfo->st_dev;
_mode = statInfo->st_mode;
_links = statInfo->st_nlink;
_mtime = statInfo->st_mtime;
if ( isSpecial() )
{
_size = 0;
_blocks = 0;
_isSparseFile = false;
}
else
{
_size = statInfo->st_size;
_blocks = statInfo->st_blocks;
_isSparseFile = isFile() && ( allocatedSize() < _size );
if ( _isSparseFile )
{
kdDebug() << "Found sparse file: " << this
<< " Byte size: " << formatSize( byteSize() )
<< " Allocated: " << formatSize( allocatedSize() )
<< endl;
}
#if 0
if ( isFile() && _links > 1 )
{
kdDebug() << _links << " hard links: " << this << endl;
}
#endif
}
#if 0
#warning Debug mode: Huge sizes
_size <<= 10;
#endif
}
KFileInfo::KFileInfo( const KFileItem * fileItem,
KDirTree * tree,
KDirInfo * parent )
: _parent( parent )
, _next( 0 )
, _tree( tree )
{
TQ_CHECK_PTR( fileItem );
_isLocalFile = fileItem->isLocalFile();
_name = parent ? fileItem->name() : fileItem->url().url();
_device = 0;
_mode = fileItem->mode();
_links = 1;
if ( isSpecial() )
{
_size = 0;
_blocks = 0;
_isSparseFile = false;
}
else
{
_size = fileItem->size();
// Since KFileItem does not return any information about allocated disk
// blocks, calculate that information artificially so callers don't
// need to bother with special cases depending on how this object was
// constructed.
_blocks = _size / blockSize();
if ( ( _size % blockSize() ) > 0 )
_blocks++;
// There is no way to find out via KFileInfo if this is a sparse file.
_isSparseFile = false;
}
_mtime = fileItem->time( TDEIO::UDS_MODIFICATION_TIME );
}
KFileInfo::~KFileInfo()
{
// NOP
/**
* The destructor should also take care about unlinking this object from
* its parent's children list, but regrettably that just doesn't work: At
* this point (within the destructor) parts of the object are already
* destroyed, e.g., the virtual table - virtual methods don't work any
* more. Thus, somebody from outside must call deletingChild() just prior
* to the actual "delete".
*
* This sucks, but it's the C++ standard.
**/
}
KFileSize
KFileInfo::allocatedSize() const
{
return blocks() * blockSize();
}
KFileSize
KFileInfo::size() const
{
KFileSize sz = _isSparseFile ? allocatedSize() : _size;
if ( _links > 1 )
sz /= _links;
return sz;
}
TQString
KFileInfo::url() const
{
if ( _parent )
{
TQString parentUrl = _parent->url();
if ( isDotEntry() ) // don't append "/." for dot entries
return parentUrl;
if ( parentUrl == "/" ) // avoid duplicating slashes
return parentUrl + _name;
else
return parentUrl + "/" + _name;
}
else
return _name;
}
TQString
KFileInfo::debugUrl() const
{
return url() + ( isDotEntry() ? "/<Files>" : "" );
}
TQString
KFileInfo::urlPart( int targetLevel ) const
{
int level = treeLevel(); // Cache this - it's expensive!
if ( level < targetLevel )
{
kdError() << k_funcinfo << "URL level " << targetLevel
<< " requested, this is level " << level << endl;
return "";
}
const KFileInfo *item = this;
while ( level > targetLevel )
{
level--;
item = item->parent();
}
return item->name();
}
int
KFileInfo::treeLevel() const
{
int level = 0;
KFileInfo * parent = _parent;
while ( parent )
{
level++;
parent = parent->parent();
}
return level;
if ( _parent )
return _parent->treeLevel() + 1;
else
return 0;
}
bool
KFileInfo::hasChildren() const
{
return firstChild() || dotEntry();
}
bool
KFileInfo::isInSubtree( const KFileInfo *subtree ) const
{
const KFileInfo * ancestor = this;
while ( ancestor )
{
if ( ancestor == subtree )
return true;
ancestor = ancestor->parent();
}
return false;
}
KFileInfo *
KFileInfo::locate( TQString url, bool findDotEntries )
{
if ( ! url.startsWith( _name ) )
return 0;
else // URL starts with this node's name
{
url.remove( 0, _name.length() ); // Remove leading name of this node
if ( url.length() == 0 ) // Nothing left?
return this; // Hey! That's us!
if ( url.startsWith( "/" ) ) // If the next thing a path delimiter,
url.remove( 0, 1 ); // remove that leading delimiter.
else // No path delimiter at the beginning
{
if ( _name.right(1) != "/" && // and this is not the root directory
! isDotEntry() ) // or a dot entry:
return 0; // This can't be any of our children.
}
// Search all children
KFileInfo *child = firstChild();
while ( child )
{
KFileInfo *foundChild = child->locate( url, findDotEntries );
if ( foundChild )
return foundChild;
else
child = child->next();
}
// Special case: The dot entry is requested.
if ( findDotEntries && dotEntry() && url == "<Files>" )
return dotEntry();
// Search the dot entry if there is one - but only if there is no more
// path delimiter left in the URL. The dot entry contains files only,
// and their names may not contain the path delimiter, nor can they
// have children. This check is not strictly necessary, but it may
// speed up things a bit if we don't search the non-directory children
// if the rest of the URL consists of several pathname components.
if ( dotEntry() &&
url.find ( "/" ) < 0 ) // No (more) "/" in this URL
{
return dotEntry()->locate( url, findDotEntries );
}
}
return 0;
}
KDirInfo::KDirInfo( KDirTree * tree,
KDirInfo * parent,
bool asDotEntry )
: KFileInfo( tree, parent )
{
init();
if ( asDotEntry )
{
_isDotEntry = true;
_dotEntry = 0;
_name = ".";
}
else
{
_isDotEntry = false;
_dotEntry = new KDirInfo( tree, this, true );
}
}
KDirInfo::KDirInfo( const TQString & filenameWithoutPath,
struct stat * statInfo,
KDirTree * tree,
KDirInfo * parent )
: KFileInfo( filenameWithoutPath,
statInfo,
tree,
parent )
{
init();
_dotEntry = new KDirInfo( tree, this, true );
}
KDirInfo::KDirInfo( const KFileItem * fileItem,
KDirTree * tree,
KDirInfo * parent )
: KFileInfo( fileItem,
tree,
parent )
{
init();
_dotEntry = new KDirInfo( tree, this, true );
}
void
KDirInfo::init()
{
_isDotEntry = false;
_pendingReadJobs = 0;
_dotEntry = 0;
_firstChild = 0;
_totalSize = _size;
_totalBlocks = _blocks;
_totalItems = 0;
_totalSubDirs = 0;
_totalFiles = 0;
_latestMtime = _mtime;
_isMountPoint = false;
_summaryDirty = false;
_beingDestroyed = false;
_readState = KDirQueued;
}
KDirInfo::~KDirInfo()
{
_beingDestroyed = true;
KFileInfo *child = _firstChild;
// Recursively delete all children.
while ( child )
{
KFileInfo * nextChild = child->next();
delete child;
child = nextChild;
}
// Delete the dot entry.
if ( _dotEntry )
{
delete _dotEntry;
}
}
void
KDirInfo::recalc()
{
// kdDebug() << k_funcinfo << this << endl;
_totalSize = _size;
_totalBlocks = _blocks;
_totalItems = 0;
_totalSubDirs = 0;
_totalFiles = 0;
_latestMtime = _mtime;
KFileInfoIterator it( this, KDotEntryAsSubDir );
while ( *it )
{
_totalSize += (*it)->totalSize();
_totalBlocks += (*it)->totalBlocks();
_totalItems += (*it)->totalItems() + 1;
_totalSubDirs += (*it)->totalSubDirs();
_totalFiles += (*it)->totalFiles();
if ( (*it)->isDir() )
_totalSubDirs++;
if ( (*it)->isFile() )
_totalFiles++;
time_t childLatestMtime = (*it)->latestMtime();
if ( childLatestMtime > _latestMtime )
_latestMtime = childLatestMtime;
++it;
}
_summaryDirty = false;
}
void
KDirInfo::setMountPoint( bool isMountPoint )
{
_isMountPoint = isMountPoint;
}
KFileSize
KDirInfo::totalSize()
{
if ( _summaryDirty )
recalc();
return _totalSize;
}
KFileSize
KDirInfo::totalBlocks()
{
if ( _summaryDirty )
recalc();
return _totalBlocks;
}
int
KDirInfo::totalItems()
{
if ( _summaryDirty )
recalc();
return _totalItems;
}
int
KDirInfo::totalSubDirs()
{
if ( _summaryDirty )
recalc();
return _totalSubDirs;
}
int
KDirInfo::totalFiles()
{
if ( _summaryDirty )
recalc();
return _totalFiles;
}
time_t
KDirInfo::latestMtime()
{
if ( _summaryDirty )
recalc();
return _latestMtime;
}
bool
KDirInfo::isFinished()
{
return ! isBusy();
}
void KDirInfo::setReadState( KDirReadState newReadState )
{
// "aborted" has higher priority than "finished"
if ( _readState == KDirAborted && newReadState == KDirFinished )
return;
_readState = newReadState;
}
bool
KDirInfo::isBusy()
{
if ( _pendingReadJobs > 0 && _readState != KDirAborted )
return true;
if ( readState() == KDirReading ||
readState() == KDirQueued )
return true;
return false;
}
void
KDirInfo::insertChild( KFileInfo *newChild )
{
TQ_CHECK_PTR( newChild );
if ( newChild->isDir() || _dotEntry == 0 || _isDotEntry )
{
/**
* Only directories are stored directly in pure directory nodes -
* unless something went terribly wrong, e.g. there is no dot entry to use.
* If this is a dot entry, store everything it gets directly within it.
*
* In any of those cases, insert the new child in the children list.
*
* We don't bother with this list's order - it's explicitly declared to
* be unordered, so be warned! We simply insert this new child at the
* list head since this operation can be performed in constant time
* without the need for any additional lastChild etc. pointers or -
* even worse - seeking the correct place for insertion first. This is
* none of our business; the corresponding "view" object for this tree
* will take care of such niceties.
**/
newChild->setNext( _firstChild );
_firstChild = newChild;
newChild->setParent( this ); // make sure the parent pointer is correct
childAdded( newChild ); // update summaries
}
else
{
/*
* If the child is not a directory, don't store it directly here - use
* this entry's dot entry instead.
*/
_dotEntry->insertChild( newChild );
}
}
void
KDirInfo::childAdded( KFileInfo *newChild )
{
if ( ! _summaryDirty )
{
_totalSize += newChild->size();
_totalBlocks += newChild->blocks();
_totalItems++;
if ( newChild->isDir() )
_totalSubDirs++;
if ( newChild->isFile() )
_totalFiles++;
if ( newChild->mtime() > _latestMtime )
_latestMtime = newChild->mtime();
}
else
{
// NOP
/*
* Don't bother updating the summary fields if the summary is dirty
* (i.e. outdated) anyway: As soon as anybody wants to know some exact
* value a complete recalculation of the entire subtree will be
* triggered. On the other hand, if nobody wants to know (which is very
* likely) we can save this effort.
*/
}
if ( _parent )
_parent->childAdded( newChild );
}
void
KDirInfo::deletingChild( KFileInfo *deletedChild )
{
/**
* When children are deleted, things go downhill: Marking the summary
* fields as dirty (i.e. outdated) is the only thing that can be done here.
*
* The accumulated sizes could be updated (by subtracting this deleted
* child's values from them), but the latest mtime definitely has to be
* recalculated: The child now being deleted might just be the one with the
* latest mtime, and figuring out the second-latest cannot easily be
* done. So we merely mark the summary as dirty and wait until a recalc()
* will be triggered from outside - which might as well never happen when
* nobody wants to know some summary field anyway.
**/
_summaryDirty = true;
if ( _parent )
_parent->deletingChild( deletedChild );
if ( ! _beingDestroyed && deletedChild->parent() == this )
{
/**
* Unlink the child from the children's list - but only if this doesn't
* happen recursively in the destructor of this object: No use
* bothering about the validity of the children's list if this will all
* be history anyway in a moment.
**/
unlinkChild( deletedChild );
}
}
void
KDirInfo::unlinkChild( KFileInfo *deletedChild )
{
if ( deletedChild->parent() != this )
{
kdError() << deletedChild << " is not a child of " << this
<< " - cannot unlink from children list!" << endl;
return;
}
if ( deletedChild == _firstChild )
{
// kdDebug() << "Unlinking first child " << deletedChild << endl;
_firstChild = deletedChild->next();
return;
}
KFileInfo *child = firstChild();
while ( child )
{
if ( child->next() == deletedChild )
{
// kdDebug() << "Unlinking " << deletedChild << endl;
child->setNext( deletedChild->next() );
return;
}
child = child->next();
}
kdError() << "Couldn't unlink " << deletedChild << " from "
<< this << " children list" << endl;
}
void
KDirInfo::readJobAdded()
{
_pendingReadJobs++;
if ( _parent )
_parent->readJobAdded();
}
void
KDirInfo::readJobFinished()
{
_pendingReadJobs--;
if ( _parent )
_parent->readJobFinished();
}
void
KDirInfo::readJobAborted()
{
_readState = KDirAborted;
if ( _parent )
_parent->readJobAborted();
}
void
KDirInfo::finalizeLocal()
{
cleanupDotEntries();
}
KDirReadState
KDirInfo::readState() const
{
if ( _isDotEntry && _parent )
return _parent->readState();
else
return _readState;
}
void
KDirInfo::cleanupDotEntries()
{
if ( ! _dotEntry || _isDotEntry )
return;
// Reparent dot entry children if there are no subdirectories on this level
if ( ! _firstChild )
{
// kdDebug() << "Removing solo dot entry " << this << " " << endl;
KFileInfo *child = _dotEntry->firstChild();
_firstChild = child; // Move the entire children chain here.
_dotEntry->setFirstChild( 0 ); // _dotEntry will be deleted below.
while ( child )
{
child->setParent( this );
child = child->next();
}
}
// Delete dot entries without any children
if ( ! _dotEntry->firstChild() )
{
// kdDebug() << "Removing empty dot entry " << this << endl;
delete _dotEntry;
_dotEntry = 0;
}
}
KDirReadJob::KDirReadJob( KDirTree * tree,
KDirInfo * dir )
: _tree( tree )
, _dir( dir )
{
_dir->readJobAdded();
}
KDirReadJob::~KDirReadJob()
{
_dir->readJobFinished();
}
void
KDirReadJob::childAdded( KFileInfo *newChild )
{
_tree->childAddedNotify( newChild );
}
void
KDirReadJob::deletingChild( KFileInfo *deletedChild )
{
_tree->deletingChildNotify( deletedChild );
}
KLocalDirReadJob::KLocalDirReadJob( KDirTree * tree,
KDirInfo * dir )
: KDirReadJob( tree, dir )
, _diskDir( 0 )
{
}
KLocalDirReadJob::~KLocalDirReadJob()
{
}
void
KLocalDirReadJob::startReading()
{
struct dirent * entry;
struct stat statInfo;
TQString dirName = _dir->url();
if ( ( _diskDir = opendir( dirName.local8Bit() ) ) )
{
_tree->sendProgressInfo( dirName );
_dir->setReadState( KDirReading );
while ( ( entry = readdir( _diskDir ) ) )
{
TQString entryName = entry->d_name;
if ( entryName != "." &&
entryName != ".." )
{
TQString fullName = dirName + "/" + entryName;
if ( lstat( fullName.local8Bit(), &statInfo ) == 0 ) // lstat() OK
{
if ( S_ISDIR( statInfo.st_mode ) ) // directory child?
{
KDirInfo *subDir = new KDirInfo( entryName, &statInfo, _tree, _dir );
_dir->insertChild( subDir );
childAdded( subDir );
if ( subDir->dotEntry() )
childAdded( subDir->dotEntry() );
if ( _dir->device() == subDir->device() ) // normal case
{
_tree->addJob( new KLocalDirReadJob( _tree, subDir ) );
}
else // The subdirectory we just found is a mount point.
{
// kdDebug() << "Found mount point " << subDir << endl;
subDir->setMountPoint();
if ( _tree->crossFileSystems() )
{
_tree->addJob( new KLocalDirReadJob( _tree, subDir ) );
}
else
{
subDir->setReadState( KDirOnRequestOnly );
_tree->sendFinalizeLocal( subDir );
subDir->finalizeLocal();
}
}
}
else // non-directory child
{
KFileInfo *child = new KFileInfo( entryName, &statInfo, _tree, _dir );
_dir->insertChild( child );
childAdded( child );
}
}
else // lstat() error
{
// kdWarning() << "lstat(" << fullName << ") failed: " << strerror( errno ) << endl;
/*
* Not much we can do when lstat() didn't work; let's at
* least create an (almost empty) entry as a placeholder.
*/
KDirInfo *child = new KDirInfo( _tree, _dir, entry->d_name );
child->setReadState( KDirError );
_dir->insertChild( child );
childAdded( child );
}
}
}
closedir( _diskDir );
// kdDebug() << "Finished reading " << _dir << endl;
_dir->setReadState( KDirFinished );
_tree->sendFinalizeLocal( _dir );
_dir->finalizeLocal();
}
else
{
_dir->setReadState( KDirError );
_tree->sendFinalizeLocal( _dir );
_dir->finalizeLocal();
// kdWarning() << k_funcinfo << "opendir(" << dirName << ") failed" << endl;
// opendir() doesn't set 'errno' according to POSIX :-(
}
_tree->jobFinishedNotify( this );
// Don't add anything after _tree->jobFinishedNotify()
// since this deletes this job!
}
KFileInfo *
KLocalDirReadJob::stat( const KURL & url,
KDirTree * tree,
KDirInfo * parent )
{
struct stat statInfo;
if ( lstat( url.path().local8Bit(), &statInfo ) == 0 ) // lstat() OK
{
TQString name = parent ? url.filename() : url.path();
if ( S_ISDIR( statInfo.st_mode ) ) // directory?
{
KDirInfo * dir = new KDirInfo( name, &statInfo, tree, parent );
if ( dir && parent && dir->device() != parent->device() )
dir->setMountPoint();
return dir;
}
else // no directory
return new KFileInfo( name, &statInfo, tree, parent );
}
else // lstat() failed
return 0;
}
KAnyDirReadJob::KAnyDirReadJob( KDirTree * tree,
KDirInfo * dir )
: TQObject()
, KDirReadJob( tree, dir )
{
_job = 0;
}
KAnyDirReadJob::~KAnyDirReadJob()
{
#if 0
if ( _job )
_job->kill( true ); // quietly
#endif
}
void
KAnyDirReadJob::startReading()
{
KURL url( _dir->url() );
if ( ! url.isValid() )
{
kdWarning() << k_funcinfo << "URL malformed: " << _dir->url() << endl;
}
_job = TDEIO::listDir( url,
false ); // showProgressInfo
connect( _job, TQ_SIGNAL( entries( TDEIO::Job *, const TDEIO::UDSEntryList& ) ),
this, TQ_SLOT ( entries( TDEIO::Job *, const TDEIO::UDSEntryList& ) ) );
connect( _job, TQ_SIGNAL( result ( TDEIO::Job * ) ),
this, TQ_SLOT ( finished( TDEIO::Job * ) ) );
connect( _job, TQ_SIGNAL( canceled( TDEIO::Job * ) ),
this, TQ_SLOT ( finished( TDEIO::Job * ) ) );
}
void
KAnyDirReadJob::entries ( TDEIO::Job * job,
const TDEIO::UDSEntryList & entryList )
{
NOT_USED( job );
KURL url( _dir->url() ); // Cache this - it's expensive!
if ( ! url.isValid() )
{
kdWarning() << k_funcinfo << "URL malformed: " << _dir->url() << endl;
}
TDEIO::UDSEntryListConstIterator it = entryList.begin();
while ( it != entryList.end() )
{
KFileItem entry( *it,
url,
true, // determineMimeTypeOnDemand
true ); // URL is parent directory
if ( entry.name() != "." &&
entry.name() != ".." )
{
// kdDebug() << "Found " << entry.url().url() << endl;
if ( entry.isDir() && // Directory child
! entry.isLink() ) // and not a symlink?
{
KDirInfo *subDir = new KDirInfo( &entry, _tree, _dir );
_dir->insertChild( subDir );
childAdded( subDir );
if ( subDir->dotEntry() )
childAdded( subDir->dotEntry() );
_tree->addJob( new KAnyDirReadJob( _tree, subDir ) );
}
else // non-directory child
{
KFileInfo *child = new KFileInfo( &entry, _tree, _dir );
_dir->insertChild( child );
childAdded( child );
}
}
++it;
}
}
void
KAnyDirReadJob::finished( TDEIO::Job * job )
{
if ( job->error() )
_dir->setReadState( KDirError );
else
_dir->setReadState( KDirFinished );
_tree->sendFinalizeLocal( _dir );
_dir->finalizeLocal();
_job = 0; // The job deletes itself after this signal!
_tree->jobFinishedNotify( this );
// Don't add anything after _tree->jobFinishedNotify()
// since this deletes this job!
}
KFileInfo *
KAnyDirReadJob::stat( const KURL & url,
KDirTree * tree,
KDirInfo * parent )
{
TDEIO::UDSEntry uds_entry;
if ( TDEIO::NetAccess::stat( url, uds_entry, tqApp->mainWidget() ) ) // remote stat() OK?
{
KFileItem entry( uds_entry, url,
true, // determine MIME type on demand
false ); // URL specifies parent directory
return entry.isDir() ? new KDirInfo ( &entry, tree, parent ) : new KFileInfo( &entry, tree, parent );
}
else // remote stat() failed
return 0;
#if HAVE_STUPID_COMPILER
/**
* This is stupid, but GCC 2.95.3 claims that "control reaches end of
* non-void function" without this - so let him have this stupid "return".
*
* Sigh.
**/
return 0;
#endif
}
TQString
KAnyDirReadJob::owner( KURL url )
{
TDEIO::UDSEntry uds_entry;
if ( TDEIO::NetAccess::stat( url, uds_entry, tqApp->mainWidget() ) ) // remote stat() OK?
{
KFileItem entry( uds_entry, url,
true, // determine MIME type on demand
false ); // URL specifies parent directory
return entry.user();
}
return TQString();
}
KDirTree::KDirTree()
: TQObject()
{
_root = 0;
_selection = 0;
_isFileProtocol = false;
_isBusy = false;
_readMethod = KDirReadUnknown;
_jobQueue.setAutoDelete( true ); // Delete queued jobs automatically when destroyed
readConfig();
}
KDirTree::~KDirTree()
{
selectItem( 0 );
// Jobs still in the job queue are automatically deleted along with the
// queue since autoDelete is set.
//
// However, the queue needs to be cleared first before the entire tree is
// deleted, otherwise the dir pointers in each read job becomes invalid too
// early.
_jobQueue.clear();
if ( _root )
delete _root;
}
void
KDirTree::readConfig()
{
TDEConfig * config = kapp->config();
config->setGroup( "Directory Reading" );
_crossFileSystems = config->readBoolEntry( "CrossFileSystems", false );
_enableLocalDirReader = config->readBoolEntry( "EnableLocalDirReader", true );
}
void
KDirTree::startReading( const KURL & url )
{
// kdDebug() << k_funcinfo << " " << url.url() << endl;
#if 0
kdDebug() << "url: " << url.url() << endl;
kdDebug() << "path: " << url.path() << endl;
kdDebug() << "filename: " << url.filename() << endl;
kdDebug() << "protocol: " << url.protocol() << endl;
kdDebug() << "isValid: " << url.isValid() << endl;
kdDebug() << "isMalformed: " << url.isMalformed() << endl;
kdDebug() << "isLocalFile: " << url.isLocalFile() << endl;
#endif
_isBusy = true;
emit startingReading();
if ( _root )
{
// Clean up leftover stuff
selectItem( 0 );
emit deletingChild( _root );
// kdDebug() << "Deleting root prior to reading" << endl;
delete _root;
_root = 0;
emit childDeleted();
}
readConfig();
_isFileProtocol = url.isLocalFile();
if ( _isFileProtocol && _enableLocalDirReader )
{
// kdDebug() << "Using local directory reader for " << url.url() << endl;
_readMethod = KDirReadLocal;
_root = KLocalDirReadJob::stat( url, this );
}
else
{
// kdDebug() << "Using TDEIO methods for " << url.url() << endl;
KURL cleanUrl( url );
cleanUrl.cleanPath(); // Resolve relative paths, get rid of multiple '/'
_readMethod = KDirReadKIO;
_root = KAnyDirReadJob::stat( cleanUrl, this );
}
if ( _root )
{
childAddedNotify( _root );
if ( _root->isDir() )
{
KDirInfo *dir = (KDirInfo *) _root;
if ( _readMethod == KDirReadLocal )
addJob( new KLocalDirReadJob( this, dir ) );
else
addJob( new KAnyDirReadJob( this, dir ) );
}
else
{
_isBusy = false;
emit finished();
}
}
else // stat() failed
{
// kdWarning() << "stat(" << url.url() << ") failed" << endl;
_isBusy = false;
emit finished();
emit finalizeLocal( 0 );
}
if ( ! _jobQueue.isEmpty() )
TQTimer::singleShot( 0, this, TQ_SLOT( timeSlicedRead() ) );
}
void
KDirTree::refresh( KFileInfo *subtree )
{
if ( ! _root )
return;
if ( ! subtree || ! subtree->parent() ) // Refresh all (from root)
{
startReading( fixedUrl( _root->url() ) );
}
else // Refresh subtree
{
// Save some values from the old subtree.
KURL url = subtree->url();
KDirInfo * parent = subtree->parent();
// Select nothing if the current selection is to be deleted
if ( _selection && _selection->isInSubtree( subtree ) )
selectItem( 0 );
// Get rid of the old subtree.
emit deletingChild( subtree );
// kdDebug() << "Deleting subtree " << subtree << endl;
/**
* This may sound stupid, but the parent must be told to unlink its
* child from the children list. The child cannot simply do this by
* itself in its destructor since at this point important parts of the
* object may already be destroyed, e.g., the virtual table -
* i.e. virtual methods won't work any more.
*
* I just found that out the hard way by several hours of debugging. ;-}
**/
parent->deletingChild( subtree );
delete subtree;
emit childDeleted();
// Create new subtree root.
subtree = ( _readMethod == KDirReadLocal ) ?
KLocalDirReadJob::stat( url, this, parent ) : KAnyDirReadJob::stat( url, this, parent );
// kdDebug() << "New subtree: " << subtree << endl;
if ( subtree )
{
// Insert new subtree root into the tree hierarchy.
parent->insertChild( subtree );
childAddedNotify( subtree );
if ( subtree->isDir() )
{
// Prepare reading this subtree's contents.
KDirInfo *dir = (KDirInfo *) subtree;
if ( _readMethod == KDirReadLocal )
addJob( new KLocalDirReadJob( this, dir ) );
else
addJob( new KAnyDirReadJob( this, dir ) );
}
else
{
_isBusy = false;
emit finished();
}
// Trigger reading as soon as the event loop continues.
if ( ! _jobQueue.isEmpty() )
TQTimer::singleShot( 0, this, TQ_SLOT( timeSlicedRead() ) );
}
}
}
void
KDirTree::timeSlicedRead()
{
if ( ! _jobQueue.isEmpty() )
_jobQueue.head()->startReading();
}
void
KDirTree::abortReading()
{
if ( _jobQueue.isEmpty() )
return;
while ( ! _jobQueue.isEmpty() )
{
_jobQueue.head()->dir()->readJobAborted();
_jobQueue.dequeue();
}
_isBusy = false;
emit aborted();
}
void
KDirTree::jobFinishedNotify( KDirReadJob *job )
{
// Get rid of the old (finished) job.
_jobQueue.dequeue();
delete job;
// Look for a new job.
if ( _jobQueue.isEmpty() ) // No new job available - we're done.
{
_isBusy = false;
emit finished();
}
else // There is a new job
{
// Set up zero-duration timer for the new job.
TQTimer::singleShot( 0, this, TQ_SLOT( timeSlicedRead() ) );
}
}
void
KDirTree::childAddedNotify( KFileInfo *newChild )
{
emit childAdded( newChild );
if ( newChild->dotEntry() )
emit childAdded( newChild->dotEntry() );
}
void
KDirTree::deletingChildNotify( KFileInfo *deletedChild )
{
emit deletingChild( deletedChild );
// Only now check for selection and root: Give connected objects
// (i.e. views) a chance to change either while handling the signal.
if ( _selection && _selection->isInSubtree( deletedChild ) )
selectItem( 0 );
if ( deletedChild == _root )
_root = 0;
}
void
KDirTree::childDeletedNotify()
{
emit childDeleted();
}
void
KDirTree::deleteSubtree( KFileInfo *subtree )
{
// kdDebug() << "Deleting subtree " << subtree << endl;
KDirInfo *parent = subtree->parent();
if ( parent )
{
// Give the parent of the child to be deleted a chance to unlink the
// child from its children list and take care of internal summary
// fields
parent->deletingChild( subtree );
}
// Send notification to anybody interested (e.g., to attached views)
deletingChildNotify( subtree );
if ( parent )
{
if ( parent->isDotEntry() && ! parent->hasChildren() )
// This was the last child of a dot entry
{
// Get rid of that now empty and useless dot entry
if ( parent->parent() )
{
if ( parent->parent()->isFinished() )
{
// kdDebug() << "Removing empty dot entry " << parent << endl;
deletingChildNotify( parent );
parent->parent()->setDotEntry( 0 );
delete parent;
}
}
else // no parent - this should never happen (?)
{
kdError() << "Internal error: Killing dot entry without parent " << parent << endl;
// Better leave that dot entry alone - we shouldn't have come
// here in the first place. Who knows what will happen if this
// thing is deleted now?!
//
// Intentionally NOT calling:
// delete parent;
}
}
}
delete subtree;
emit childDeleted();
}
void
KDirTree::addJob( KDirReadJob * job )
{
TQ_CHECK_PTR( job );
_jobQueue.enqueue( job );
}
void
KDirTree::sendProgressInfo( const TQString &infoLine )
{
emit progressInfo( infoLine );
}
void
KDirTree::sendFinalizeLocal( KDirInfo *dir )
{
emit finalizeLocal( dir );
}
void
KDirTree::selectItem( KFileInfo *newSelection )
{
if ( newSelection == _selection )
return;
#if 0
if ( newSelection )
kdDebug() << k_funcinfo << " selecting " << newSelection << endl;
else
kdDebug() << k_funcinfo << " selecting nothing" << endl;
#endif
_selection = newSelection;
emit selectionChanged( _selection );
}
KURL
KDirStat::fixedUrl( const TQString & dirtyUrl )
{
KURL url = dirtyUrl;
if ( ! url.isValid() ) // Maybe it's just a path spec?
{
url = KURL(); // Start over with an empty, but valid URL
url.setPath( dirtyUrl ); // and use just the path part.
}
else
{
url.cleanPath(); // Resolve relative paths, get rid of multiple slashes.
}
// Strip off the rightmost slash - some tdeioslaves (e.g. 'tar') can't handle that.
TQString path = url.path();
if ( path.length() > 1 && path.right(1) == "/" )
{
path = path.left( path.length()-1 );
url.setPath( path );
}
if ( url.isLocalFile() )
{
// Make a relative path an absolute path
KDirSaver dir( url.path() );
url.setPath( dir.currentDirPath() );
}
return url;
}
TQString
KDirStat::formatSize( KFileSize lSize )
{
TQString sizeString;
double size;
TQString unit;
if ( lSize < 1024 )
{
sizeString.setNum( (long) lSize );
unit = i18n( "Bytes" );
}
else
{
size = lSize / 1024.0; // kB
if ( size < 1024.0 )
{
sizeString.sprintf( "%.1f", size );
unit = i18n( "kB" );
}
else
{
size /= 1024.0; // MB
if ( size < 1024.0 )
{
sizeString.sprintf( "%.1f", size );
unit = i18n ( "MB" );
}
else
{
size /= 1024.0; // GB - we won't go any further...
sizeString.sprintf( "%.2f", size );
unit = i18n ( "GB" );
}
}
}
if ( ! unit.isEmpty() )
{
sizeString += " " + unit;
}
return sizeString;
}
#include "kdirtree.moc"
// EOF