summaryrefslogtreecommitdiffstats
path: root/kdirstat/kdirtree.cpp
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-02-10 01:15:27 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-02-10 01:15:27 +0000
commitb6e09a3a8ea5f1338d089a29eb6e08f00f03f1aa (patch)
tree7dee2cbb5c94d3371357f796d42e344e1305ce9c /kdirstat/kdirtree.cpp
downloadkdirstat-b6e09a3a8ea5f1338d089a29eb6e08f00f03f1aa.tar.gz
kdirstat-b6e09a3a8ea5f1338d089a29eb6e08f00f03f1aa.zip
Added abandoned KDE3 version of kdirstat
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kdirstat@1088039 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kdirstat/kdirtree.cpp')
-rw-r--r--kdirstat/kdirtree.cpp1636
1 files changed, 1636 insertions, 0 deletions
diff --git a/kdirstat/kdirtree.cpp b/kdirstat/kdirtree.cpp
new file mode 100644
index 0000000..7bf972a
--- /dev/null
+++ b/kdirstat/kdirtree.cpp
@@ -0,0 +1,1636 @@
+/*
+ * 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 <qtimer.h>
+#include <kapp.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include "kdirtree.h"
+#include "kdirtreeiterators.h"
+#include "kdirtreeview.h"
+#include "kdirsaver.h"
+#include "kio/job.h"
+#include "kio/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 QString & filenameWithoutPath,
+ struct stat * statInfo,
+ KDirTree * tree,
+ KDirInfo * parent )
+ : _parent( parent )
+ , _next( 0 )
+ , _tree( tree )
+{
+ 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 )
+{
+ 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( KIO::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;
+}
+
+
+QString
+KFileInfo::url() const
+{
+ if ( _parent )
+ {
+ QString 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;
+}
+
+
+QString
+KFileInfo::debugUrl() const
+{
+ return url() + ( isDotEntry() ? "/<Files>" : "" );
+}
+
+
+QString
+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( QString 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 QString & 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 )
+{
+ 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;
+ QString dirName = _dir->url();
+
+ if ( ( _diskDir = opendir( dirName ) ) )
+ {
+ _tree->sendProgressInfo( dirName );
+ _dir->setReadState( KDirReading );
+
+ while ( ( entry = readdir( _diskDir ) ) )
+ {
+ QString entryName = entry->d_name;
+
+ if ( entryName != "." &&
+ entryName != ".." )
+ {
+ QString 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
+ {
+ QString 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 )
+ : QObject()
+ , 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 = KIO::listDir( url,
+ false ); // showProgressInfo
+
+ connect( _job, SIGNAL( entries( KIO::Job *, const KIO::UDSEntryList& ) ),
+ this, SLOT ( entries( KIO::Job *, const KIO::UDSEntryList& ) ) );
+
+ connect( _job, SIGNAL( result ( KIO::Job * ) ),
+ this, SLOT ( finished( KIO::Job * ) ) );
+
+ connect( _job, SIGNAL( canceled( KIO::Job * ) ),
+ this, SLOT ( finished( KIO::Job * ) ) );
+}
+
+
+void
+KAnyDirReadJob::entries ( KIO::Job * job,
+ const KIO::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;
+ }
+
+ KIO::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( KIO::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 )
+{
+ KIO::UDSEntry uds_entry;
+
+ if ( KIO::NetAccess::stat( url, uds_entry, qApp->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
+}
+
+
+QString
+KAnyDirReadJob::owner( KURL url )
+{
+ KIO::UDSEntry uds_entry;
+
+ if ( KIO::NetAccess::stat( url, uds_entry, qApp->mainWidget() ) ) // remote stat() OK?
+ {
+ KFileItem entry( uds_entry, url,
+ true, // determine MIME type on demand
+ false ); // URL specifies parent directory
+
+ return entry.user();
+ }
+
+ return QString();
+}
+
+
+
+
+
+
+KDirTree::KDirTree()
+ : QObject()
+{
+ _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()
+{
+ KConfig * 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 KIO 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() )
+ QTimer::singleShot( 0, this, 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() )
+ QTimer::singleShot( 0, this, 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.
+
+ QTimer::singleShot( 0, this, 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 )
+{
+ CHECK_PTR( job );
+ _jobQueue.enqueue( job );
+}
+
+
+void
+KDirTree::sendProgressInfo( const QString &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 QString & 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 kioslaves (e.g. 'tar') can't handle that.
+
+ QString 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;
+}
+
+
+
+
+
+
+QString
+KDirStat::formatSize( KFileSize lSize )
+{
+ QString sizeString;
+ double size;
+ QString 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;
+}
+
+
+
+// EOF