X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/fa4680cb38028aceb68d41e1937d27c71d1f121b..79a6e75b6567e8cf2ef677cea6bb2c34075d07c7:/src/revisioncontrolobserver.cpp diff --git a/src/revisioncontrolobserver.cpp b/src/revisioncontrolobserver.cpp index 99fd61b67..87f0a3aa7 100644 --- a/src/revisioncontrolobserver.cpp +++ b/src/revisioncontrolobserver.cpp @@ -23,33 +23,111 @@ #include "revisioncontrolplugin.h" #include +#include #include #include +#include #include +/** + * The performance of updating the revision state of items depends + * on the used plugin. To prevent that Dolphin gets blocked by a + * slow plugin, the updating is delegated to a thread. + */ +class UpdateItemStatesThread : public QThread +{ +public: + UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex); + void setData(RevisionControlPlugin* plugin, + const QList& itemStates); + QList itemStates() const; + bool retrievedItems() const; + +protected: + virtual void run(); + +private: + bool m_retrievedItems; + RevisionControlPlugin* m_plugin; + QMutex* m_pluginMutex; + QList m_itemStates; +}; + +UpdateItemStatesThread::UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex) : + QThread(parent), + m_retrievedItems(false), + m_pluginMutex(pluginMutex), + m_itemStates() +{ +} + +void UpdateItemStatesThread::setData(RevisionControlPlugin* plugin, + const QList& itemStates) +{ + m_plugin = plugin; + m_itemStates = itemStates; +} + +void UpdateItemStatesThread::run() +{ + Q_ASSERT(!m_itemStates.isEmpty()); + Q_ASSERT(m_plugin != 0); + + // The items from m_itemStates may be located in different directory levels. The revision + // plugin requires the root directory for RevisionControlPlugin::beginRetrieval(). Instead + // of doing an expensive search, we utilize the knowledge of the implementation of + // RevisionControlObserver::addDirectory() to be sure that the last item contains the root. + const QString directory = m_itemStates.last().item.url().directory(KUrl::AppendTrailingSlash); + + QMutexLocker locker(m_pluginMutex); + m_retrievedItems = false; + if (m_plugin->beginRetrieval(directory)) { + const int count = m_itemStates.count(); + for (int i = 0; i < count; ++i) { + m_itemStates[i].revision = m_plugin->revisionState(m_itemStates[i].item); + } + m_plugin->endRetrieval(); + m_retrievedItems = true; + } +} + +QList UpdateItemStatesThread::itemStates() const +{ + return m_itemStates; +} + +bool UpdateItemStatesThread::retrievedItems() const +{ + return m_retrievedItems; +} + +// ------------------------------------------------------------------------------------------------ + RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) : QObject(view), + m_pendingItemStatesUpdate(false), + m_revisionedDirectory(false), + m_silentUpdate(false), m_view(view), m_dirLister(0), m_dolphinModel(0), m_dirVerificationTimer(0), - m_plugin(0) + m_pluginMutex(QMutex::Recursive), + m_plugin(0), + m_updateItemStatesThread(0) { Q_ASSERT(view != 0); QAbstractProxyModel* proxyModel = qobject_cast(view->model()); m_dolphinModel = (proxyModel == 0) ? - qobject_cast(view->model()) : - qobject_cast(proxyModel->sourceModel()); + qobject_cast(view->model()) : + qobject_cast(proxyModel->sourceModel()); if (m_dolphinModel != 0) { m_dirLister = m_dolphinModel->dirLister(); connect(m_dirLister, SIGNAL(completed()), this, SLOT(delayedDirectoryVerification())); - // TODO: - // connect(m_dirLister, SIGNAL(refreshItems(const QList>&)), - // this, SLOT(refreshItems())); - + // The verification timer specifies the timeout until the shown directory // is checked whether it is versioned. Per default it is assumed that users // don't iterate through versioned directories and a high timeout is used @@ -65,12 +143,42 @@ RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) : RevisionControlObserver::~RevisionControlObserver() { + if (m_updateItemStatesThread != 0) { + m_updateItemStatesThread->terminate(); + m_updateItemStatesThread->wait(); + } delete m_plugin; m_plugin = 0; } +QList RevisionControlObserver::contextMenuActions(const KFileItemList& items) const +{ + if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) { + QMutexLocker locker(&m_pluginMutex); + return m_plugin->contextMenuActions(items); + } + return QList(); +} + +QList RevisionControlObserver::contextMenuActions(const QString& directory) const +{ + if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) { + QMutexLocker locker(&m_pluginMutex); + return m_plugin->contextMenuActions(directory); + } + + return QList(); +} + void RevisionControlObserver::delayedDirectoryVerification() { + m_silentUpdate = false; + m_dirVerificationTimer->start(); +} + +void RevisionControlObserver::silentDirectoryVerification() +{ + m_silentUpdate = true; m_dirVerificationTimer->start(); } @@ -84,19 +192,91 @@ void RevisionControlObserver::verifyDirectory() if (m_plugin == 0) { // TODO: just for testing purposes. A plugin approach will be used later. m_plugin = new SubversionPlugin(); + connect(m_plugin, SIGNAL(infoMessage(const QString&)), + this, SIGNAL(infoMessage(const QString&))); + connect(m_plugin, SIGNAL(errorMessage(const QString&)), + this, SIGNAL(errorMessage(const QString&))); + connect(m_plugin, SIGNAL(operationCompletedMessage(const QString&)), + this, SIGNAL(operationCompletedMessage(const QString&))); } revisionControlUrl.addPath(m_plugin->fileName()); - KFileItem item = m_dirLister->findByUrl(revisionControlUrl); - if (item.isNull()) { + const KFileItem item = m_dirLister->findByUrl(revisionControlUrl); + + bool foundRevisionInfo = !item.isNull(); + if (!foundRevisionInfo && m_revisionedDirectory) { + // Revision control systems like Git provide the revision information + // file only in the root directory. Check whether the revision information file can + // be found in one of the parent directories. + + // TODO... + } + + if (foundRevisionInfo) { + if (!m_revisionedDirectory) { + m_revisionedDirectory = true; + + // The directory is versioned. Assume that the user will further browse through + // versioned directories and decrease the verification timer. + m_dirVerificationTimer->setInterval(100); + connect(m_dirLister, SIGNAL(refreshItems(const QList>&)), + this, SLOT(delayedDirectoryVerification())); + connect(m_dirLister, SIGNAL(newItems(const KFileItemList&)), + this, SLOT(delayedDirectoryVerification())); + connect(m_plugin, SIGNAL(revisionStatesChanged()), + this, SLOT(silentDirectoryVerification())); + } + updateItemStates(); + } else if (m_revisionedDirectory) { + m_revisionedDirectory = false; + // The directory is not versioned. Reset the verification timer to a higher // value, so that browsing through non-versioned directories is not slown down // by an immediate verification. m_dirVerificationTimer->setInterval(500); - } else { - // The directory is versioned. Assume that the user will further browse through - // versioned directories and decrease the verification timer. - m_dirVerificationTimer->setInterval(100); + disconnect(m_dirLister, SIGNAL(refreshItems(const QList>&)), + this, SLOT(delayedDirectoryVerification())); + disconnect(m_dirLister, SIGNAL(newItems(const KFileItemList&)), + this, SLOT(delayedDirectoryVerification())); + disconnect(m_plugin, SIGNAL(revisionStatesChanged()), + this, SLOT(silentDirectoryVerification())); + } +} + +void RevisionControlObserver::applyUpdatedItemStates() +{ + if (!m_updateItemStatesThread->retrievedItems()) { + // ignore m_silentUpdate for an error message + emit errorMessage(i18nc("@info:status", "Update of revision information failed.")); + return; + } + + // QAbstractItemModel::setData() triggers a bottleneck in combination with QListView + // (a detailed description of the root cause is given in the class KFilePreviewGenerator + // from kdelibs). To bypass this bottleneck, the signals of the model are temporary blocked. + // This works as the update of the data does not require a relayout of the views used in Dolphin. + const bool signalsBlocked = m_dolphinModel->signalsBlocked(); + m_dolphinModel->blockSignals(true); + + const QList itemStates = m_updateItemStatesThread->itemStates(); + foreach (const ItemState& itemState, itemStates) { + m_dolphinModel->setData(itemState.index, + QVariant(static_cast(itemState.revision)), + Qt::DecorationRole); + } + + m_dolphinModel->blockSignals(signalsBlocked); + m_view->viewport()->repaint(); + + if (!m_silentUpdate) { + // Using an empty message results in clearing the previously shown information message and showing + // the default status bar information. This is useful as the user already gets feedback that the + // operation has been completed because of the icon emblems. + emit operationCompletedMessage(QString()); + } + + if (m_pendingItemStatesUpdate) { + m_pendingItemStatesUpdate = false; updateItemStates(); } } @@ -104,21 +284,43 @@ void RevisionControlObserver::verifyDirectory() void RevisionControlObserver::updateItemStates() { Q_ASSERT(m_plugin != 0); - const KUrl directory = m_dirLister->url(); - if (!m_plugin->beginRetrieval(directory.toLocalFile(KUrl::AddTrailingSlash))) { + if (m_updateItemStatesThread == 0) { + m_updateItemStatesThread = new UpdateItemStatesThread(this, &m_pluginMutex); + connect(m_updateItemStatesThread, SIGNAL(finished()), + this, SLOT(applyUpdatedItemStates())); + } + if (m_updateItemStatesThread->isRunning()) { + // An update is currently ongoing. Wait until the thread has finished + // the update (see applyUpdatedItemStates()). + m_pendingItemStatesUpdate = true; return; } + + QList itemStates; + addDirectory(QModelIndex(), itemStates); + if (!itemStates.isEmpty()) { + if (!m_silentUpdate) { + emit infoMessage(i18nc("@info:status", "Updating revision information...")); + } + m_updateItemStatesThread->setData(m_plugin, itemStates); + m_updateItemStatesThread->start(); // applyUpdatedItemStates() is called when finished + } +} - const int rowCount = m_dolphinModel->rowCount(); +void RevisionControlObserver::addDirectory(const QModelIndex& parentIndex, QList& itemStates) +{ + const int rowCount = m_dolphinModel->rowCount(parentIndex); for (int row = 0; row < rowCount; ++row) { - const QModelIndex index = m_dolphinModel->index(row, DolphinModel::Revision); - const KFileItem item = m_dolphinModel->itemForIndex(index); - const RevisionControlPlugin::RevisionState revision = m_plugin->revisionState(item.name()); - m_dolphinModel->setData(index, QVariant(static_cast(revision)), Qt::DecorationRole); - } - m_view->viewport()->repaint(); // TODO: this should not be necessary, as DolphinModel::setData() calls dataChanged() + const QModelIndex index = m_dolphinModel->index(row, DolphinModel::Revision, parentIndex); + addDirectory(index, itemStates); + + ItemState itemState; + itemState.index = index; + itemState.item = m_dolphinModel->itemForIndex(index); + itemState.revision = RevisionControlPlugin::UnversionedRevision; - m_plugin->endRetrieval(); + itemStates.append(itemState); + } } #include "revisioncontrolobserver.moc"