Improve file tree population/update performance

Use simple tokenization instead of splitting the file path into
QStringList for each item. Don't expand each added item separetely
during population/update. Don't expand all the items but just up to the
point where parent has more than one expandable child.

Also, fix items order by sorting items after population/update. Fix
sorting by size on systems where uint64_t != quint64.
This commit is contained in:
Mike Gelfand
2015-07-12 20:48:54 +00:00
parent b9adf279ca
commit 5296570476
4 changed files with 115 additions and 24 deletions

View File

@@ -119,7 +119,7 @@ FileTreeItem::data (int column, int role) const
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)
value.setValue (sizeString()); value.setValue (sizeString());
else else
value.setValue (size ()); value.setValue<quint64> (size ());
break; break;
case FileTreeModel::COL_PROGRESS: case FileTreeModel::COL_PROGRESS:

View File

@@ -9,11 +9,81 @@
#include <cassert> #include <cassert>
#include <QStringList>
#include "FileTreeItem.h" #include "FileTreeItem.h"
#include "FileTreeModel.h" #include "FileTreeModel.h"
namespace
{
class PathIteratorBase
{
protected:
PathIteratorBase(const QString& path, int slashIndex):
myPath (path),
mySlashIndex (slashIndex),
myToken ()
{
myToken.reserve (path.size () / 2);
}
protected:
const QString& myPath;
int mySlashIndex;
QString myToken;
static const QChar SlashChar;
};
const QChar PathIteratorBase::SlashChar = QLatin1Char ('/');
class ForwardPathIterator: public PathIteratorBase
{
public:
ForwardPathIterator (const QString& path):
PathIteratorBase (path, path.size () - 1)
{
}
bool hasNext () const
{
return mySlashIndex > 0;
}
const QString& next ()
{
int newSlashIndex = myPath.lastIndexOf (SlashChar, mySlashIndex);
myToken.truncate (0);
myToken += myPath.midRef (newSlashIndex + 1, mySlashIndex - newSlashIndex);
mySlashIndex = newSlashIndex - 1;
return myToken;
}
};
class BackwardPathIterator: public PathIteratorBase
{
public:
BackwardPathIterator (const QString& path):
PathIteratorBase (path, 0)
{
}
bool hasNext () const
{
return mySlashIndex < myPath.size ();
}
const QString& next ()
{
int newSlashIndex = myPath.indexOf (SlashChar, mySlashIndex);
if (newSlashIndex == -1)
newSlashIndex = myPath.size ();
myToken.truncate (0);
myToken += myPath.midRef (mySlashIndex, newSlashIndex - mySlashIndex);
mySlashIndex = newSlashIndex + 1;
return myToken;
}
};
}
FileTreeModel::FileTreeModel (QObject * parent, bool isEditable): FileTreeModel::FileTreeModel (QObject * parent, bool isEditable):
QAbstractItemModel(parent), QAbstractItemModel(parent),
myIsEditable (isEditable), myIsEditable (isEditable),
@@ -221,26 +291,25 @@ FileTreeModel::findItemForFileIndex (int fileIndex) const
} }
void void
FileTreeModel::addFile (int fileIndex, FileTreeModel::addFile (int fileIndex,
const QString & filename, const QString& filename,
bool wanted, bool wanted,
int priority, int priority,
uint64_t totalSize, uint64_t totalSize,
uint64_t have, uint64_t have,
QList<QModelIndex> & rowsAdded, bool updateFields)
bool updateFields)
{ {
FileTreeItem * item; FileTreeItem * item;
QStringList tokens = filename.split (QChar::fromLatin1('/'));
item = findItemForFileIndex (fileIndex); item = findItemForFileIndex (fileIndex);
if (item) // this file is already in the tree, we've added this if (item) // this file is already in the tree, we've added this
{ {
QModelIndex indexWithChangedParents; QModelIndex indexWithChangedParents;
while (!tokens.isEmpty()) ForwardPathIterator filenameIt (filename);
while (filenameIt.hasNext ())
{ {
const QString token = tokens.takeLast(); const QString& token = filenameIt.next ();
const std::pair<int,int> changed = item->update (token, wanted, priority, have, updateFields); const std::pair<int,int> changed = item->update (token, wanted, priority, have, updateFields);
if (changed.first >= 0) if (changed.first >= 0)
{ {
@@ -260,9 +329,10 @@ FileTreeModel::addFile (int fileIndex,
bool added = false; bool added = false;
item = myRootItem; item = myRootItem;
while (!tokens.isEmpty()) BackwardPathIterator filenameIt (filename);
while (filenameIt.hasNext ())
{ {
const QString token = tokens.takeFirst(); const QString& token = filenameIt.next ();
FileTreeItem * child(item->child(token)); FileTreeItem * child(item->child(token));
if (!child) if (!child)
{ {
@@ -271,14 +341,12 @@ FileTreeModel::addFile (int fileIndex,
const int n (item->childCount()); const int n (item->childCount());
beginInsertRows (parentIndex, n, n); beginInsertRows (parentIndex, n, n);
if (tokens.isEmpty()) if (!filenameIt.hasNext ())
child = new FileTreeItem (token, fileIndex, totalSize); child = new FileTreeItem (token, fileIndex, totalSize);
else else
child = new FileTreeItem (token); child = new FileTreeItem (token);
item->appendChild (child); item->appendChild (child);
endInsertRows (); endInsertRows ();
rowsAdded.append (indexOf(child, 0));
} }
item = child; item = child;
} }

View File

@@ -53,7 +53,6 @@ class FileTreeModel: public QAbstractItemModel
void addFile (int index, const QString& filename, void addFile (int index, const QString& filename,
bool wanted, int priority, bool wanted, int priority,
uint64_t size, uint64_t have, uint64_t size, uint64_t have,
QList<QModelIndex>& rowsAdded,
bool torrentChanged); bool torrentChanged);
QModelIndex parent (const QModelIndex& child, int column) const; QModelIndex parent (const QModelIndex& child, int column) const;

View File

@@ -169,13 +169,37 @@ FileTreeView::keyPressEvent (QKeyEvent * event)
void void
FileTreeView::update (const FileList& files, bool updateFields) FileTreeView::update (const FileList& files, bool updateFields)
{ {
const bool modelWasEmpty = myProxy->rowCount () == 0;
for (const TorrentFile& file: files) for (const TorrentFile& file: files)
myModel->addFile (file.index, file.filename, file.wanted, file.priority, file.size, file.have, updateFields);
if (modelWasEmpty)
{ {
QList<QModelIndex> added; // expand up until the item with more than one expandable child
myModel->addFile (file.index, file.filename, file.wanted, file.priority, file.size, file.have, added, updateFields); for (QModelIndex index = myProxy->index (0, 0); index.isValid ();)
for (const QModelIndex& i: added) {
expand (myProxy->mapFromSource(i)); const QModelIndex oldIndex = index;
expand (oldIndex);
index = QModelIndex ();
for (int i = 0, count = myProxy->rowCount (oldIndex); i < count; ++i)
{
const QModelIndex newIndex = myProxy->index (i, 0, oldIndex);
if (myProxy->rowCount (newIndex) == 0)
continue;
if (index.isValid ())
{
index = QModelIndex ();
break;
}
index = newIndex;
}
}
} }
myProxy->sort (header ()->sortIndicatorSection (), header ()->sortIndicatorOrder ());
} }
void void