/* * File name: kdirtree.cpp * Summary: Support classes for KDirStat * License: LGPL - See file COPYING.LIB for details. * Author: Stefan Hundhammer * * Updated: 2005-01-07 */ #include "config.h" #include #include #include #include #include #include #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() ? "/" : "" ); } 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 == "" ) 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 ) ) ) { _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, &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(), &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, TQT_SIGNAL( entries( TDEIO::Job *, const TDEIO::UDSEntryList& ) ), this, TQT_SLOT ( entries( TDEIO::Job *, const TDEIO::UDSEntryList& ) ) ); connect( _job, TQT_SIGNAL( result ( TDEIO::Job * ) ), this, TQT_SLOT ( finished( TDEIO::Job * ) ) ); connect( _job, TQT_SIGNAL( canceled( TDEIO::Job * ) ), this, TQT_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, TQT_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, TQT_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, TQT_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