Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Fixes: N#120 - I/O should be threaded
This lays the groundwork for a system of worker threads which remove contention from the UI. IOWorkerThread is held by DirModel (at present - this will likely change!). It creates an IORequestWorker, and communicates IORequest instances to it when they should be run.
- Loading branch information
Showing
12 changed files
with
848 additions
and
311 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,292 @@ | ||
| /* | ||
| * Copyright (C) 2012 Robin Burchell <robin+nemo@viroteck.net> | ||
| * | ||
| * You may use this file under the terms of the BSD license as follows: | ||
| * | ||
| * "Redistribution and use in source and binary forms, with or without | ||
| * modification, are permitted provided that the following conditions are | ||
| * met: | ||
| * * Redistributions of source code must retain the above copyright | ||
| * notice, this list of conditions and the following disclaimer. | ||
| * * Redistributions in binary form must reproduce the above copyright | ||
| * notice, this list of conditions and the following disclaimer in | ||
| * the documentation and/or other materials provided with the | ||
| * distribution. | ||
| * * Neither the name of Nemo Mobile nor the names of its contributors | ||
| * may be used to endorse or promote products derived from this | ||
| * software without specific prior written permission. | ||
| * | ||
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||
| */ | ||
|
|
||
| #include <QDirIterator> | ||
| #include <QDir> | ||
| #include <QDebug> | ||
| #include <QDateTime> | ||
| #include <QUrl> | ||
|
|
||
| #include "dirmodel.h" | ||
|
|
||
| class DirListWorker : public IORequest | ||
| { | ||
| Q_OBJECT | ||
| public: | ||
| DirListWorker(const QString &pathName) | ||
| : mPathName(pathName) | ||
| { } | ||
|
|
||
| void run() | ||
| { | ||
| qDebug() << Q_FUNC_INFO << "Running on: " << QThread::currentThreadId(); | ||
|
|
||
| QDir tmpDir = QDir(mPathName); | ||
| QDirIterator it(tmpDir); | ||
| QVector<QFileInfo> directoryContents; | ||
|
|
||
| while (it.hasNext()) { | ||
| it.next(); | ||
|
|
||
| // skip hidden files | ||
| if (it.fileName()[0] == QLatin1Char('.')) | ||
| continue; | ||
|
|
||
| directoryContents.append(it.fileInfo()); | ||
| if (directoryContents.count() >= 50) { | ||
| emit itemsAdded(directoryContents); | ||
|
|
||
| // clear() would force a deallocation, micro-optimisation | ||
| directoryContents.erase(directoryContents.begin(), directoryContents.end()); | ||
| } | ||
| } | ||
|
|
||
| // last batch | ||
| emit itemsAdded(directoryContents); | ||
|
|
||
| //std::sort(directoryContents.begin(), directoryContents.end(), DirModel::fileCompare); | ||
| } | ||
|
|
||
| signals: | ||
| void itemsAdded(const QVector<QFileInfo> &files); | ||
|
|
||
| private: | ||
| QString mPathName; | ||
| }; | ||
|
|
||
| DirModel::DirModel(QObject *parent) : QAbstractListModel(parent) | ||
| { | ||
| QHash<int, QByteArray> roles = roleNames(); | ||
| roles.insert(FileNameRole, QByteArray("fileName")); | ||
| roles.insert(CreationDateRole, QByteArray("creationDate")); | ||
| roles.insert(ModifiedDateRole, QByteArray("modifiedDate")); | ||
| roles.insert(FileSizeRole, QByteArray("fileSize")); | ||
| roles.insert(IconSourceRole, QByteArray("iconSource")); | ||
| roles.insert(FilePathRole, QByteArray("filePath")); | ||
| roles.insert(IsDirRole, QByteArray("isDir")); | ||
| roles.insert(IsFileRole, QByteArray("isFile")); | ||
| setRoleNames(roles); | ||
|
|
||
| // populate reverse mapping | ||
| QHash<int, QByteArray>::ConstIterator it = roles.constBegin(); | ||
| for (;it != roles.constEnd(); ++it) | ||
| mRoleMapping.insert(it.value(), it.key()); | ||
|
|
||
| // make sure we cover all roles | ||
| Q_ASSERT(roles.count() == IsFileRole - FileNameRole); | ||
| } | ||
|
|
||
| QVariant DirModel::data(int row, const QByteArray &stringRole) const | ||
| { | ||
| QHash<QByteArray, int>::ConstIterator it = mRoleMapping.constFind(stringRole); | ||
|
|
||
| if (it == mRoleMapping.constEnd()) | ||
| return QVariant(); | ||
|
|
||
| return data(index(row, 0), *it); | ||
| } | ||
|
|
||
| QVariant DirModel::data(const QModelIndex &index, int role) const | ||
| { | ||
| // make sure we cover all roles | ||
| Q_ASSERT(roles.count() == IsFileRole - FileNameRole); | ||
|
|
||
| if (role < FileNameRole || role > IsFileRole) { | ||
| qWarning() << Q_FUNC_INFO << "Got an out of range role: " << role; | ||
| return QVariant(); | ||
| } | ||
|
|
||
| if (index.row() < 0 || index.row() >= mDirectoryContents.count()) { | ||
| qWarning() << "Attempted to access out of range row: " << index.row(); | ||
| return QVariant(); | ||
| } | ||
|
|
||
| if (index.column() != 0) | ||
| return QVariant(); | ||
|
|
||
| const QFileInfo &fi = mDirectoryContents.at(index.row()); | ||
|
|
||
| switch (role) { | ||
| case FileNameRole: | ||
| return fi.fileName(); | ||
| case CreationDateRole: | ||
| return fi.created(); | ||
| case ModifiedDateRole: | ||
| return fi.lastModified(); | ||
| case FileSizeRole: { | ||
| qint64 kb = fi.size() / 1024; | ||
| if (kb < 1) | ||
| return QString::number(fi.size()) + " bytes"; | ||
| else if (kb < 1024) | ||
| return QString::number(kb) + " kb"; | ||
|
|
||
| kb /= 1024; | ||
| return QString::number(kb) + "mb"; | ||
| } | ||
| case IconSourceRole: { | ||
| const QString &fileName = fi.fileName(); | ||
|
|
||
| if (fileName.endsWith(".jpg") || | ||
| fileName.endsWith(".png")) { | ||
| return QUrl::fromLocalFile(fi.filePath()); | ||
| } | ||
|
|
||
| if (fi.isDir()) | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
rburchell
Author
Contributor
|
||
| return "image://theme/icon-m-common-directory"; | ||
| else | ||
| return "image://theme/icon-m-content-document"; | ||
| return QVariant(); | ||
| } | ||
| case FilePathRole: | ||
| return fi.filePath(); | ||
| case IsDirRole: | ||
| return fi.isDir(); | ||
| case IsFileRole: | ||
| return !fi.isDir(); | ||
| default: | ||
| // this should not happen, ever | ||
| Q_ASSERT(false); | ||
| qWarning() << Q_FUNC_INFO << "Got an unknown role: " << role; | ||
| return QVariant(); | ||
| } | ||
| } | ||
|
|
||
| void DirModel::setPath(const QString &pathName) | ||
| { | ||
| qDebug() << Q_FUNC_INFO << "Changing to " << pathName << " on " << QThread::currentThreadId(); | ||
|
|
||
| beginResetModel(); | ||
| mDirectoryContents.clear(); | ||
| endResetModel(); | ||
|
|
||
| // TODO: we need to set a spinner active before we start getting results from DirListWorker | ||
| DirListWorker *dlw = new DirListWorker(pathName); | ||
| connect(dlw, SIGNAL(itemsAdded(QVector<QFileInfo>)), SLOT(onItemsAdded(QVector<QFileInfo>))); | ||
| mIOWorker.addRequest(dlw); | ||
|
|
||
| mCurrentDir = pathName; | ||
| emit pathChanged(); | ||
| } | ||
|
|
||
| static bool fileCompare(const QFileInfo &a, const QFileInfo &b) | ||
| { | ||
| if (a.isDir() && !b.isDir()) | ||
| return true; | ||
|
|
||
| if (b.isDir() && !a.isDir()) | ||
| return false; | ||
|
|
||
| return QString::localeAwareCompare(a.fileName(), b.fileName()) < 0; | ||
| } | ||
|
|
||
| void DirModel::onItemsAdded(const QVector<QFileInfo> &newFiles) | ||
| { | ||
| // TODO: we need to perform a sorted insert | ||
| qDebug() << Q_FUNC_INFO << "Got new files: " << newFiles.count(); | ||
|
|
||
| foreach (const QFileInfo &fi, newFiles) { | ||
| QVector<QFileInfo>::Iterator it = qLowerBound(mDirectoryContents.begin(), | ||
| mDirectoryContents.end(), | ||
| fi, | ||
| fileCompare); | ||
|
|
||
| if (it == mDirectoryContents.end()) { | ||
| beginInsertRows(QModelIndex(), mDirectoryContents.count(), mDirectoryContents.count()); | ||
| mDirectoryContents.append(fi); | ||
| endInsertRows(); | ||
| } else { | ||
| int idx = it - mDirectoryContents.begin(); | ||
| beginInsertRows(QModelIndex(), idx, idx); | ||
| mDirectoryContents.insert(it, fi); | ||
| endInsertRows(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void DirModel::rm(const QStringList &paths) | ||
| { | ||
| // TODO: handle directory deletions? | ||
| bool error = false; | ||
|
|
||
| foreach (const QString &path, paths) { | ||
| error |= QFile::remove(path); | ||
|
|
||
| if (error) { | ||
| qWarning() << Q_FUNC_INFO << "Failed to remove " << path; | ||
| error = false; | ||
| } | ||
| } | ||
|
|
||
| // TODO: just remove removed items; don't reload the entire model | ||
| refresh(); | ||
| } | ||
|
|
||
| bool DirModel::rename(int row, const QString &newName) | ||
| { | ||
| qDebug() << Q_FUNC_INFO << "Renaming " << row << " to " << newName; | ||
| Q_ASSERT(row >= 0 && row < mDirectoryContents.count()); | ||
| if (row < 0 || row >= mDirectoryContents.count()) { | ||
| qWarning() << Q_FUNC_INFO << "Out of bounds access"; | ||
| return false; | ||
| } | ||
|
|
||
| const QFileInfo &fi = mDirectoryContents.at(row); | ||
|
|
||
| if (!fi.isDir()) { | ||
| QFile f(fi.absoluteFilePath()); | ||
| bool retval = f.rename(fi.absolutePath() + QDir::separator() + newName); | ||
|
|
||
| if (!retval) | ||
| qDebug() << Q_FUNC_INFO << "Rename returned error code: " << f.error() << f.errorString(); | ||
| else | ||
| refresh(); | ||
| // TODO: just change the affected item... ^^ | ||
|
|
||
| return retval; | ||
| } else { | ||
| QDir d(fi.absoluteFilePath()); | ||
| bool retval = d.rename(fi.absoluteFilePath(), fi.absolutePath() + QDir::separator() + newName); | ||
|
|
||
| // QDir has no way to detect what went wrong. woohoo! | ||
|
|
||
| // TODO: just change the affected item... | ||
| refresh(); | ||
|
|
||
| return retval; | ||
| } | ||
|
|
||
| // unreachable (we hope) | ||
| Q_ASSERT(false); | ||
| return false; | ||
| } | ||
|
|
||
| // for dirlistworker | ||
| #include "dirmodel.moc" | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| /* | ||
| * Copyright (C) 2012 Robin Burchell <robin+nemo@viroteck.net> | ||
| * | ||
| * You may use this file under the terms of the BSD license as follows: | ||
| * | ||
| * "Redistribution and use in source and binary forms, with or without | ||
| * modification, are permitted provided that the following conditions are | ||
| * met: | ||
| * * Redistributions of source code must retain the above copyright | ||
| * notice, this list of conditions and the following disclaimer. | ||
| * * Redistributions in binary form must reproduce the above copyright | ||
| * notice, this list of conditions and the following disclaimer in | ||
| * the documentation and/or other materials provided with the | ||
| * distribution. | ||
| * * Neither the name of Nemo Mobile nor the names of its contributors | ||
| * may be used to endorse or promote products derived from this | ||
| * software without specific prior written permission. | ||
| * | ||
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." | ||
| */ | ||
|
|
||
| #ifndef DIRMODEL_H | ||
| #define DIRMODEL_H | ||
|
|
||
| #include <QAbstractListModel> | ||
| #include <QFileInfo> | ||
| #include <QVector> | ||
|
|
||
| #include "iorequest.h" | ||
| #include "ioworkerthread.h" | ||
|
|
||
| class DirModel : public QAbstractListModel | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| enum Roles { | ||
| FileNameRole = Qt::UserRole, | ||
| CreationDateRole, | ||
| ModifiedDateRole, | ||
| FileSizeRole, | ||
| IconSourceRole, | ||
| FilePathRole, | ||
| IsDirRole, | ||
| IsFileRole, | ||
| }; | ||
|
|
||
| public: | ||
| DirModel(QObject *parent = 0); | ||
|
|
||
| int rowCount(const QModelIndex &index) const | ||
| { | ||
| if (index.parent() != QModelIndex()) | ||
| return 0; | ||
|
|
||
| return mDirectoryContents.count(); | ||
| } | ||
|
|
||
| // TODO: this won't be safe if the model can change under the holder of the row | ||
| Q_INVOKABLE QVariant data(int row, const QByteArray &stringRole) const; | ||
|
|
||
| QVariant data(const QModelIndex &index, int role) const; | ||
|
|
||
| Q_INVOKABLE void refresh() | ||
| { | ||
| // just some syntactical sugar really | ||
| setPath(path()); | ||
| } | ||
|
|
||
| Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged); | ||
| inline QString path() const { return mCurrentDir; } | ||
|
|
||
| void setPath(const QString &pathName); | ||
|
|
||
| Q_INVOKABLE void rm(const QStringList &paths); | ||
|
|
||
| Q_INVOKABLE bool rename(int row, const QString &newName); | ||
|
|
||
| public slots: | ||
| void onItemsAdded(const QVector<QFileInfo> &newFiles); | ||
|
|
||
| signals: | ||
| void pathChanged(); | ||
|
|
||
| private: | ||
| QString mCurrentDir; | ||
| QVector<QFileInfo> mDirectoryContents; | ||
| QHash<QByteArray, int> mRoleMapping; | ||
| IOWorkerThread mIOWorker; | ||
| }; | ||
|
|
||
|
|
||
| #endif // DIRMODEL_H |
Oops, something went wrong.
Shouldn't this condition come first?