┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
path: root/src/versioncontrol
diff options
context:
space:
mode:
authorPeter Penz <[email protected]>2009-11-06 15:43:08 +0000
committerPeter Penz <[email protected]>2009-11-06 15:43:08 +0000
commit896ee459af10c73d20d0ae093b4c02348ffedb18 (patch)
treea84fda9222985379f6e764de495536c62702b14e /src/versioncontrol
parent5114ab58bcf9ccc7fed7cfdd057bffeb87a2ad94 (diff)
* enable the loading of version control plugins (thanks to Aaron for the hint)
* Don't terminate the thread that receives the item states when changing a view. Instead let the thread get finished and ignore the result. svn path=/trunk/KDE/kdebase/apps/; revision=1045674
Diffstat (limited to 'src/versioncontrol')
-rw-r--r--src/versioncontrol/fileviewsvnplugin.cpp362
-rw-r--r--src/versioncontrol/fileviewsvnplugin.desktop30
-rw-r--r--src/versioncontrol/fileviewsvnplugin.h93
-rw-r--r--src/versioncontrol/fileviewversioncontrolplugin.desktop9
-rw-r--r--src/versioncontrol/updateitemstatesthread.cpp94
-rw-r--r--src/versioncontrol/updateitemstatesthread.h68
-rw-r--r--src/versioncontrol/versioncontrolobserver.cpp297
-rw-r--r--src/versioncontrol/versioncontrolobserver.h144
8 files changed, 1097 insertions, 0 deletions
diff --git a/src/versioncontrol/fileviewsvnplugin.cpp b/src/versioncontrol/fileviewsvnplugin.cpp
new file mode 100644
index 000000000..7e56c724c
--- /dev/null
+++ b/src/versioncontrol/fileviewsvnplugin.cpp
@@ -0,0 +1,362 @@
+/***************************************************************************
+ * Copyright (C) 2009 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "fileviewsvnplugin.h"
+
+#include <kaction.h>
+#include <kdemacros.h>
+#include <kdialog.h>
+#include <kfileitem.h>
+#include <kicon.h>
+#include <klocale.h>
+#include <krun.h>
+#include <kshell.h>
+#include <kvbox.h>
+#include <QDir>
+#include <QLabel>
+#include <QProcess>
+#include <QString>
+#include <QStringList>
+#include <QTextEdit>
+#include <QTextStream>
+
+#include <KPluginFactory>
+#include <KPluginLoader>
+K_PLUGIN_FACTORY(FileViewSvnPluginFactory, registerPlugin<FileViewSvnPlugin>();)
+K_EXPORT_PLUGIN(FileViewSvnPluginFactory("fileviewsvnplugin"))
+
+FileViewSvnPlugin::FileViewSvnPlugin(QObject* parent, const QList<QVariant>& args) :
+ KVersionControlPlugin(parent),
+ m_versionInfoHash(),
+ m_versionInfoKeys(),
+ m_updateAction(0),
+ m_showLocalChangesAction(0),
+ m_commitAction(0),
+ m_addAction(0),
+ m_removeAction(0),
+ m_command(),
+ m_errorMsg(),
+ m_operationCompletedMsg(),
+ m_contextDir(),
+ m_contextItems(),
+ m_tempFile()
+{
+ Q_UNUSED(args);
+
+ m_updateAction = new KAction(this);
+ m_updateAction->setIcon(KIcon("view-refresh"));
+ m_updateAction->setText(i18nc("@item:inmenu", "SVN Update"));
+ connect(m_updateAction, SIGNAL(triggered()),
+ this, SLOT(updateFiles()));
+
+ m_showLocalChangesAction = new KAction(this);
+ m_showLocalChangesAction->setIcon(KIcon("view-split-left-right"));
+ m_showLocalChangesAction->setText(i18nc("@item:inmenu", "Show Local SVN Changes"));
+ connect(m_showLocalChangesAction, SIGNAL(triggered()),
+ this, SLOT(showLocalChanges()));
+
+ m_commitAction = new KAction(this);
+ m_commitAction->setText(i18nc("@item:inmenu", "SVN Commit..."));
+ connect(m_commitAction, SIGNAL(triggered()),
+ this, SLOT(commitFiles()));
+
+ m_addAction = new KAction(this);
+ m_addAction->setIcon(KIcon("list-add"));
+ m_addAction->setText(i18nc("@item:inmenu", "SVN Add"));
+ connect(m_addAction, SIGNAL(triggered()),
+ this, SLOT(addFiles()));
+
+ m_removeAction = new KAction(this);
+ m_removeAction->setIcon(KIcon("list-remove"));
+ m_removeAction->setText(i18nc("@item:inmenu", "SVN Delete"));
+ connect(m_removeAction, SIGNAL(triggered()),
+ this, SLOT(removeFiles()));
+}
+
+FileViewSvnPlugin::~FileViewSvnPlugin()
+{
+}
+
+QString FileViewSvnPlugin::fileName() const
+{
+ return ".svn";
+}
+
+bool FileViewSvnPlugin::beginRetrieval(const QString& directory)
+{
+ Q_ASSERT(directory.endsWith('/'));
+
+ QStringList arguments;
+ arguments << "status" << "--show-updates" << directory;
+
+ QProcess process;
+ process.start("svn", arguments);
+ while (process.waitForReadyRead()) {
+ char buffer[1024];
+ while (process.readLine(buffer, sizeof(buffer)) > 0) {
+ VersionState state = NormalVersion;
+ QString filePath(buffer);
+
+ switch (buffer[0]) {
+ case '?': state = UnversionedVersion; break;
+ case 'M': state = LocallyModifiedVersion; break;
+ case 'A': state = AddedVersion; break;
+ case 'D': state = RemovedVersion; break;
+ case 'C': state = ConflictingVersion; break;
+ default:
+ if (filePath.contains('*')) {
+ state = UpdateRequiredVersion;
+ }
+ break;
+ }
+
+ int pos = filePath.indexOf('/');
+ const int length = filePath.length() - pos - 1;
+ filePath = filePath.mid(pos, length);
+ if (!filePath.isEmpty()) {
+ m_versionInfoHash.insert(filePath, state);
+ }
+ }
+ }
+
+ m_versionInfoKeys = m_versionInfoHash.keys();
+ return true;
+}
+
+void FileViewSvnPlugin::endRetrieval()
+{
+}
+
+KVersionControlPlugin::VersionState FileViewSvnPlugin::versionState(const KFileItem& item)
+{
+ const QString itemUrl = item.localPath();
+ if (m_versionInfoHash.contains(itemUrl)) {
+ return m_versionInfoHash.value(itemUrl);
+ }
+
+ if (!item.isDir()) {
+ // files that have not been listed by 'svn status' (= m_versionInfoHash)
+ // are under version control per definition
+ return NormalVersion;
+ }
+
+ // The item is a directory. Check whether an item listed by 'svn status' (= m_versionInfoHash)
+ // is part of this directory. In this case a local modification should be indicated in the
+ // directory already.
+ foreach (const QString& key, m_versionInfoKeys) {
+ if (key.startsWith(itemUrl)) {
+ const VersionState state = m_versionInfoHash.value(key);
+ if (state == LocallyModifiedVersion) {
+ return LocallyModifiedVersion;
+ }
+ }
+ }
+
+ return NormalVersion;
+}
+
+QList<QAction*> FileViewSvnPlugin::contextMenuActions(const KFileItemList& items)
+{
+ Q_ASSERT(!items.isEmpty());
+ foreach (const KFileItem& item, items) {
+ m_contextItems.append(item);
+ }
+ m_contextDir.clear();
+
+ // iterate all items and check the version state to know which
+ // actions can be enabled
+ const int itemsCount = items.count();
+ int versionedCount = 0;
+ int editingCount = 0;
+ foreach (const KFileItem& item, items) {
+ const VersionState state = versionState(item);
+ if (state != UnversionedVersion) {
+ ++versionedCount;
+ }
+
+ switch (state) {
+ case LocallyModifiedVersion:
+ case ConflictingVersion:
+ ++editingCount;
+ break;
+ default:
+ break;
+ }
+ }
+ m_commitAction->setEnabled(editingCount > 0);
+ m_addAction->setEnabled(versionedCount == 0);
+ m_removeAction->setEnabled(versionedCount == itemsCount);
+
+ QList<QAction*> actions;
+ actions.append(m_updateAction);
+ actions.append(m_commitAction);
+ actions.append(m_addAction);
+ actions.append(m_removeAction);
+ return actions;
+}
+
+QList<QAction*> FileViewSvnPlugin::contextMenuActions(const QString& directory)
+{
+ const bool enabled = m_contextItems.isEmpty();
+ if (enabled) {
+ m_contextDir = directory;
+ }
+
+ // Only enable the SVN actions if no SVN commands are
+ // executed currently (see slotOperationCompleted() and
+ // startSvnCommandProcess()).
+ m_updateAction->setEnabled(enabled);
+ m_showLocalChangesAction->setEnabled(enabled);
+ m_commitAction->setEnabled(enabled);
+
+ QList<QAction*> actions;
+ actions.append(m_updateAction);
+ actions.append(m_showLocalChangesAction);
+ actions.append(m_commitAction);
+ return actions;
+}
+
+void FileViewSvnPlugin::updateFiles()
+{
+ execSvnCommand("update",
+ i18nc("@info:status", "Updating SVN repository..."),
+ i18nc("@info:status", "Update of SVN repository failed."),
+ i18nc("@info:status", "Updated SVN repository."));
+}
+
+void FileViewSvnPlugin::showLocalChanges()
+{
+ Q_ASSERT(!m_contextDir.isEmpty());
+ Q_ASSERT(m_contextItems.isEmpty());
+
+ const QString command = "mkfifo /tmp/fifo; svn diff " +
+ KShell::quoteArg(m_contextDir) +
+ " > /tmp/fifo & kompare /tmp/fifo; rm /tmp/fifo";
+ KRun::runCommand(command, 0);
+}
+
+void FileViewSvnPlugin::commitFiles()
+{
+ KDialog dialog(0, Qt::Dialog);
+
+ KVBox* box = new KVBox(&dialog);
+ new QLabel(i18nc("@label", "Description:"), box);
+ QTextEdit* editor = new QTextEdit(box);
+
+ dialog.setMainWidget(box);
+ dialog.setCaption(i18nc("@title:window", "SVN Commit"));
+ dialog.setButtons(KDialog::Ok | KDialog::Cancel);
+ dialog.setDefaultButton(KDialog::Ok);
+ dialog.setButtonText(KDialog::Ok, i18nc("@action:button", "Commit"));
+
+ KConfigGroup dialogConfig(KSharedConfig::openConfig("dolphinrc"),
+ "SvnCommitDialog");
+ dialog.restoreDialogSize(dialogConfig);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ // Write the commit description into a temporary file, so
+ // that it can be read by the command "svn commit -F". The temporary
+ // file must stay alive until slotOperationCompleted() is invoked and will
+ // be destroyed when the version plugin is destructed.
+ if (!m_tempFile.open()) {
+ emit errorMessage(i18nc("@info:status", "Commit of SVN changes failed."));
+ return;
+ }
+
+ QTextStream out(&m_tempFile);
+ const QString fileName = m_tempFile.fileName();
+ out << editor->toPlainText();
+ m_tempFile.close();
+
+ execSvnCommand("commit -F " + KShell::quoteArg(fileName),
+ i18nc("@info:status", "Committing SVN changes..."),
+ i18nc("@info:status", "Commit of SVN changes failed."),
+ i18nc("@info:status", "Committed SVN changes."));
+ }
+
+ dialog.saveDialogSize(dialogConfig, KConfigBase::Persistent);
+}
+
+void FileViewSvnPlugin::addFiles()
+{
+ execSvnCommand("add",
+ i18nc("@info:status", "Adding files to SVN repository..."),
+ i18nc("@info:status", "Adding of files to SVN repository failed."),
+ i18nc("@info:status", "Added files to SVN repository."));
+}
+
+void FileViewSvnPlugin::removeFiles()
+{
+ execSvnCommand("remove",
+ i18nc("@info:status", "Removing files from SVN repository..."),
+ i18nc("@info:status", "Removing of files from SVN repository failed."),
+ i18nc("@info:status", "Removed files from SVN repository."));
+}
+
+void FileViewSvnPlugin::slotOperationCompleted()
+{
+ if (m_contextItems.isEmpty()) {
+ emit operationCompletedMessage(m_operationCompletedMsg);
+ emit versionStatesChanged();
+ } else {
+ startSvnCommandProcess();
+ }
+}
+
+void FileViewSvnPlugin::slotOperationError()
+{
+ emit errorMessage(m_errorMsg);
+
+ // don't do any operation on other items anymore
+ m_contextItems.clear();
+}
+
+void FileViewSvnPlugin::execSvnCommand(const QString& svnCommand,
+ const QString& infoMsg,
+ const QString& errorMsg,
+ const QString& operationCompletedMsg)
+{
+ emit infoMessage(infoMsg);
+
+ m_command = svnCommand;
+ m_errorMsg = errorMsg;
+ m_operationCompletedMsg = operationCompletedMsg;
+
+ startSvnCommandProcess();
+}
+
+void FileViewSvnPlugin::startSvnCommandProcess()
+{
+ QProcess* process = new QProcess(this);
+ connect(process, SIGNAL(finished(int)),
+ this, SLOT(slotOperationCompleted()));
+ connect(process, SIGNAL(error(QProcess::ProcessError)),
+ this, SLOT(slotOperationError()));
+
+ const QString program = "svn " + m_command + ' ';
+ if (!m_contextDir.isEmpty()) {
+ process->start(program + KShell::quoteArg(m_contextDir));
+ m_contextDir.clear();
+ } else {
+ const KFileItem item = m_contextItems.takeLast();
+ process->start(program + KShell::quoteArg(item.localPath()));
+ // the remaining items of m_contextItems will be executed
+ // after the process has finished (see slotOperationFinished())
+ }
+}
diff --git a/src/versioncontrol/fileviewsvnplugin.desktop b/src/versioncontrol/fileviewsvnplugin.desktop
new file mode 100644
index 000000000..62b485b1a
--- /dev/null
+++ b/src/versioncontrol/fileviewsvnplugin.desktop
@@ -0,0 +1,30 @@
+[Desktop Entry]
+Type=Service
+Name=Subversion
+Name[bg]=Subversion
+Name[csb]=Pòdwersëjô
+Name[de]=Subversion
+Name[en_GB]=Subversion
+Name[eo]=Subversion
+Name[et]=Subversion
+Name[gl]=Subversion
+Name[gu]=સબવર્ઝન
+Name[he]=Subversion
+Name[hr]=Subversion
+Name[id]=Subversion
+Name[is]=Subversion
+Name[lt]=Subversion
+Name[ms]=Subversion
+Name[nb]=Subversion
+Name[nds]=Subversion
+Name[nl]=Subversion
+Name[pt]=Subversion
+Name[pt_BR]=Subversion
+Name[sv]=Subversion
+Name[tr]=Subversion
+Name[uk]=Subversion
+Name[x-test]=xxSubversionxx
+Name[zh_TW]=Subversion
+X-KDE-ServiceTypes=FileViewVersionControlPlugin
+MimeType=text/plain;
+X-KDE-Library=fileviewsvnplugin
diff --git a/src/versioncontrol/fileviewsvnplugin.h b/src/versioncontrol/fileviewsvnplugin.h
new file mode 100644
index 000000000..4804971c0
--- /dev/null
+++ b/src/versioncontrol/fileviewsvnplugin.h
@@ -0,0 +1,93 @@
+/***************************************************************************
+ * Copyright (C) 2009 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef FILEVIEWSVNPLUGIN_H
+#define FILEVIEWSVNPLUGIN_H
+
+#include <kfileitem.h>
+#include <kversioncontrolplugin.h>
+#include <QHash>
+#include <QTemporaryFile>
+
+// TODO: This class will be moved to kdevplatform as soon as kdevplatform will
+// be released. Moving it to kdevplatform allows to reuse code for the context
+// menu actions like commit, add, update, ...
+class FileViewSvnPlugin : public KVersionControlPlugin
+{
+ Q_OBJECT
+
+public:
+ FileViewSvnPlugin(QObject* parent, const QList<QVariant>& args);
+ virtual ~FileViewSvnPlugin();
+ virtual QString fileName() const;
+ virtual bool beginRetrieval(const QString& directory);
+ virtual void endRetrieval();
+ virtual KVersionControlPlugin::VersionState versionState(const KFileItem& item);
+ virtual QList<QAction*> contextMenuActions(const KFileItemList& items);
+ virtual QList<QAction*> contextMenuActions(const QString& directory);
+
+private slots:
+ void updateFiles();
+ void showLocalChanges();
+ void commitFiles();
+ void addFiles();
+ void removeFiles();
+
+ void slotOperationCompleted();
+ void slotOperationError();
+
+private:
+ /**
+ * Executes the command "svn {svnCommand}" for the files that have been
+ * set by getting the context menu actions (see contextMenuActions()).
+ * @param infoMsg Message that should be shown before the command is executed.
+ * @param errorMsg Message that should be shown if the execution of the command
+ * has been failed.
+ * @param operationCompletedMsg
+ * Message that should be shown if the execution of the command
+ * has been completed successfully.
+ */
+ void execSvnCommand(const QString& svnCommand,
+ const QString& infoMsg,
+ const QString& errorMsg,
+ const QString& operationCompletedMsg);
+
+ void startSvnCommandProcess();
+
+private:
+ QHash<QString, VersionState> m_versionInfoHash;
+ QList<QString> m_versionInfoKeys; // cache for accessing the keys of the hash
+
+ QAction* m_updateAction;
+ QAction* m_showLocalChangesAction;
+ QAction* m_commitAction;
+ QAction* m_addAction;
+ QAction* m_removeAction;
+
+ QString m_command;
+ QString m_errorMsg;
+ QString m_operationCompletedMsg;
+
+ QString m_contextDir;
+ KFileItemList m_contextItems;
+
+ QTemporaryFile m_tempFile;
+};
+#endif // FILEVIEWSVNPLUGIN_H
+
diff --git a/src/versioncontrol/fileviewversioncontrolplugin.desktop b/src/versioncontrol/fileviewversioncontrolplugin.desktop
new file mode 100644
index 000000000..58abf0350
--- /dev/null
+++ b/src/versioncontrol/fileviewversioncontrolplugin.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=ServiceType
+X-KDE-ServiceType=FileViewVersionControlPlugin
+Comment=Version Control Plugin for File Views
+Comment[nl]=Plugin voor versiecontrole op bestandoverzichten
+Comment[pt]='Plugin' de Controlo de Versões para as Áreas de Ficheiros
+Comment[sv]=Insticksprogram för versionskontroll i filvyer
+Comment[uk]=Додаток керування версіями для панелей перегляду файлів
+Comment[x-test]=xxVersion Control Plugin for File Viewsxx
diff --git a/src/versioncontrol/updateitemstatesthread.cpp b/src/versioncontrol/updateitemstatesthread.cpp
new file mode 100644
index 000000000..7483e056e
--- /dev/null
+++ b/src/versioncontrol/updateitemstatesthread.cpp
@@ -0,0 +1,94 @@
+/***************************************************************************
+ * Copyright (C) 2009 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "updateitemstatesthread.h"
+
+UpdateItemStatesThread::UpdateItemStatesThread(QObject* parent) :
+ QThread(parent),
+ m_retrievedItems(false),
+ m_mutex(QMutex::Recursive),
+ m_itemStates()
+{
+}
+
+UpdateItemStatesThread::~UpdateItemStatesThread()
+{
+}
+
+void UpdateItemStatesThread::setData(KVersionControlPlugin* plugin,
+ const QList<VersionControlObserver::ItemState>& 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 version
+ // plugin requires the root directory for KVersionControlPlugin::beginRetrieval(). Instead
+ // of doing an expensive search, we utilize the knowledge of the implementation of
+ // VersionControlObserver::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_mutex);
+ m_retrievedItems = false;
+ if (m_plugin->beginRetrieval(directory)) {
+ const int count = m_itemStates.count();
+ for (int i = 0; i < count; ++i) {
+ m_itemStates[i].version = m_plugin->versionState(m_itemStates[i].item);
+ }
+ m_plugin->endRetrieval();
+ m_retrievedItems = true;
+ }
+}
+
+bool UpdateItemStatesThread::beginReadItemStates()
+{
+ return m_mutex.tryLock(300);
+}
+
+void UpdateItemStatesThread::endReadItemStates()
+{
+ m_mutex.unlock();
+}
+
+QList<VersionControlObserver::ItemState> UpdateItemStatesThread::itemStates() const
+{
+ return m_itemStates;
+}
+
+bool UpdateItemStatesThread::retrievedItems() const
+{
+ return m_retrievedItems;
+}
+
+void UpdateItemStatesThread::deleteWhenFinished()
+{
+ connect(this, SIGNAL(finished()), this, SLOT(slotFinished()));
+}
+
+void UpdateItemStatesThread::slotFinished()
+{
+ deleteLater();
+}
+
+#include "updateitemstatesthread.moc"
diff --git a/src/versioncontrol/updateitemstatesthread.h b/src/versioncontrol/updateitemstatesthread.h
new file mode 100644
index 000000000..36768d0e9
--- /dev/null
+++ b/src/versioncontrol/updateitemstatesthread.h
@@ -0,0 +1,68 @@
+/***************************************************************************
+ * Copyright (C) 2009 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef UPDATEITEMSTATESTHREAD_H
+#define UPDATEITEMSTATESTHREAD_H
+
+#include <libdolphin_export.h>
+#include <versioncontrol/versioncontrolobserver.h>
+
+#include <QMutex>
+#include <QThread>
+
+class KVersionControlPlugin;
+
+/**
+ * The performance of updating the version 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 LIBDOLPHINPRIVATE_EXPORT UpdateItemStatesThread : public QThread
+{
+ Q_OBJECT
+
+public:
+ UpdateItemStatesThread(QObject* parent);
+ virtual ~UpdateItemStatesThread();
+
+ void setData(KVersionControlPlugin* plugin,
+ const QList<VersionControlObserver::ItemState>& itemStates);
+
+ bool beginReadItemStates();
+ void endReadItemStates();
+ QList<VersionControlObserver::ItemState> itemStates() const;
+
+ bool retrievedItems() const;
+
+ void deleteWhenFinished();
+
+protected:
+ virtual void run();
+
+private slots:
+ void slotFinished();
+
+private:
+ bool m_retrievedItems;
+ KVersionControlPlugin* m_plugin;
+ QMutex m_mutex;
+ QList<VersionControlObserver::ItemState> m_itemStates;
+};
+
+#endif // UPDATEITEMSTATESTHREAD_H
diff --git a/src/versioncontrol/versioncontrolobserver.cpp b/src/versioncontrol/versioncontrolobserver.cpp
new file mode 100644
index 000000000..aea60b28d
--- /dev/null
+++ b/src/versioncontrol/versioncontrolobserver.cpp
@@ -0,0 +1,297 @@
+/***************************************************************************
+ * Copyright (C) 2009 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#include "versioncontrolobserver.h"
+
+#include <dolphinmodel.h>
+
+#include <kdirlister.h>
+#include <klocale.h>
+#include <kservice.h>
+#include <kservicetypetrader.h>
+#include <kversioncontrolplugin.h>
+
+#include "updateitemstatesthread.h"
+
+#include <QAbstractProxyModel>
+#include <QAbstractItemView>
+#include <QMutexLocker>
+#include <QTimer>
+
+VersionControlObserver::VersionControlObserver(QAbstractItemView* view) :
+ QObject(view),
+ m_pendingItemStatesUpdate(false),
+ m_versionedDirectory(false),
+ m_silentUpdate(false),
+ m_view(view),
+ m_dirLister(0),
+ m_dolphinModel(0),
+ m_dirVerificationTimer(0),
+ m_plugin(0),
+ m_updateItemStatesThread(0)
+{
+ Q_ASSERT(view != 0);
+
+ QAbstractProxyModel* proxyModel = qobject_cast<QAbstractProxyModel*>(view->model());
+ m_dolphinModel = (proxyModel == 0) ?
+ qobject_cast<DolphinModel*>(view->model()) :
+ qobject_cast<DolphinModel*>(proxyModel->sourceModel());
+ if (m_dolphinModel != 0) {
+ m_dirLister = m_dolphinModel->dirLister();
+ connect(m_dirLister, SIGNAL(completed()),
+ this, SLOT(delayedDirectoryVerification()));
+
+ // 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
+ // The timeout will be decreased as soon as a versioned directory has been
+ // found (see verifyDirectory()).
+ m_dirVerificationTimer = new QTimer(this);
+ m_dirVerificationTimer->setSingleShot(true);
+ m_dirVerificationTimer->setInterval(500);
+ connect(m_dirVerificationTimer, SIGNAL(timeout()),
+ this, SLOT(verifyDirectory()));
+ }
+}
+
+VersionControlObserver::~VersionControlObserver()
+{
+ if (m_updateItemStatesThread != 0) {
+ disconnect(m_updateItemStatesThread, SIGNAL(finished()),
+ this, SLOT(applyUpdatedItemStates()));
+ m_updateItemStatesThread->deleteWhenFinished();
+ m_updateItemStatesThread = 0;
+ }
+}
+
+QList<QAction*> VersionControlObserver::contextMenuActions(const KFileItemList& items) const
+{
+ QList<QAction*> actions;
+ if (isVersioned() && m_updateItemStatesThread->beginReadItemStates()) {
+ actions = m_plugin->contextMenuActions(items);
+ m_updateItemStatesThread->endReadItemStates();
+ }
+ return actions;
+}
+
+QList<QAction*> VersionControlObserver::contextMenuActions(const QString& directory) const
+{
+ QList<QAction*> actions;
+ if (isVersioned() && m_updateItemStatesThread->beginReadItemStates()) {
+ actions = m_plugin->contextMenuActions(directory);
+ m_updateItemStatesThread->endReadItemStates();
+ }
+
+ return actions;
+}
+
+void VersionControlObserver::delayedDirectoryVerification()
+{
+ m_silentUpdate = false;
+ m_dirVerificationTimer->start();
+}
+
+void VersionControlObserver::silentDirectoryVerification()
+{
+ m_silentUpdate = true;
+ m_dirVerificationTimer->start();
+}
+
+void VersionControlObserver::verifyDirectory()
+{
+ KUrl versionControlUrl = m_dirLister->url();
+ if (!versionControlUrl.isLocalFile()) {
+ return;
+ }
+
+ if (m_plugin != 0) {
+ m_plugin->disconnect();
+ }
+
+ m_plugin = searchPlugin(versionControlUrl);
+ const bool foundVersionInfo = (m_plugin != 0);
+ if (!foundVersionInfo && m_versionedDirectory) {
+ // Version control systems like Git provide the version information
+ // file only in the root directory. Check whether the version information file can
+ // be found in one of the parent directories.
+
+ // TODO...
+ }
+
+ if (foundVersionInfo) {
+ if (!m_versionedDirectory) {
+ m_versionedDirectory = 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<QPair<KFileItem,KFileItem>>&)),
+ this, SLOT(delayedDirectoryVerification()));
+ connect(m_dirLister, SIGNAL(newItems(const KFileItemList&)),
+ this, SLOT(delayedDirectoryVerification()));
+ connect(m_plugin, SIGNAL(versionStatesChanged()),
+ this, SLOT(silentDirectoryVerification()));
+ 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&)));
+ }
+ updateItemStates();
+ } else if (m_versionedDirectory) {
+ m_versionedDirectory = 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);
+ disconnect(m_dirLister, SIGNAL(refreshItems(const QList<QPair<KFileItem,KFileItem>>&)),
+ this, SLOT(delayedDirectoryVerification()));
+ disconnect(m_dirLister, SIGNAL(newItems(const KFileItemList&)),
+ this, SLOT(delayedDirectoryVerification()));
+ }
+}
+
+void VersionControlObserver::applyUpdatedItemStates()
+{
+ if (!m_updateItemStatesThread->retrievedItems()) {
+ // ignore m_silentUpdate for an error message
+ emit errorMessage(i18nc("@info:status", "Update of version 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<ItemState> itemStates = m_updateItemStatesThread->itemStates();
+ foreach (const ItemState& itemState, itemStates) {
+ m_dolphinModel->setData(itemState.index,
+ QVariant(static_cast<int>(itemState.version)),
+ 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();
+ }
+}
+
+void VersionControlObserver::updateItemStates()
+{
+ Q_ASSERT(m_plugin != 0);
+ if (m_updateItemStatesThread == 0) {
+ m_updateItemStatesThread = new UpdateItemStatesThread(this);
+ 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<ItemState> itemStates;
+ addDirectory(QModelIndex(), itemStates);
+ if (!itemStates.isEmpty()) {
+ if (!m_silentUpdate) {
+ emit infoMessage(i18nc("@info:status", "Updating version information..."));
+ }
+ m_updateItemStatesThread->setData(m_plugin, itemStates);
+ m_updateItemStatesThread->start(); // applyUpdatedItemStates() is called when finished
+ }
+}
+
+void VersionControlObserver::addDirectory(const QModelIndex& parentIndex, QList<ItemState>& itemStates)
+{
+ const int rowCount = m_dolphinModel->rowCount(parentIndex);
+ for (int row = 0; row < rowCount; ++row) {
+ const QModelIndex index = m_dolphinModel->index(row, DolphinModel::Version, parentIndex);
+ addDirectory(index, itemStates);
+
+ ItemState itemState;
+ itemState.index = index;
+ itemState.item = m_dolphinModel->itemForIndex(index);
+ itemState.version = KVersionControlPlugin::UnversionedVersion;
+
+ itemStates.append(itemState);
+ }
+}
+
+KVersionControlPlugin* VersionControlObserver::searchPlugin(const KUrl& directory) const
+{
+ static bool pluginsAvailable = true;
+ static QList<KVersionControlPlugin*> plugins;
+
+ if (!pluginsAvailable) {
+ // a searching for plugins has already been done, but no
+ // plugins are installed
+ return 0;
+ }
+
+ if (plugins.isEmpty()) {
+ // No searching for plugins has been done yet. Query the KServiceTypeTrader for
+ // all fileview version control plugins and remember them in 'plugins'.
+ const KService::List pluginServices = KServiceTypeTrader::self()->query("FileViewVersionControlPlugin");
+ for (KService::List::ConstIterator it = pluginServices.constBegin(); it != pluginServices.constEnd(); ++it) {
+ KVersionControlPlugin* plugin = (*it)->createInstance<KVersionControlPlugin>();
+ Q_ASSERT(plugin != 0);
+ plugins.append(plugin);
+ }
+ if (plugins.isEmpty()) {
+ pluginsAvailable = false;
+ return 0;
+ }
+ }
+
+ // verify whether the current directory contains revision information
+ // like .svn, .git, ...
+ foreach (KVersionControlPlugin* plugin, plugins) {
+ KUrl fileUrl = directory;
+ fileUrl.addPath(plugin->fileName());
+ const KFileItem item = m_dirLister->findByUrl(fileUrl);
+ if (!item.isNull()) {
+ return plugin;
+ }
+ }
+
+ return 0;
+}
+
+bool VersionControlObserver::isVersioned() const
+{
+ return m_dolphinModel->hasVersionData() && (m_plugin != 0);
+}
+
+#include "versioncontrolobserver.moc"
diff --git a/src/versioncontrol/versioncontrolobserver.h b/src/versioncontrol/versioncontrolobserver.h
new file mode 100644
index 000000000..2d1cd02b3
--- /dev/null
+++ b/src/versioncontrol/versioncontrolobserver.h
@@ -0,0 +1,144 @@
+/***************************************************************************
+ * Copyright (C) 2009 by Peter Penz <[email protected]> *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
+ ***************************************************************************/
+
+#ifndef REVISIONCONTROLOBSERVER_H
+#define REVISIONCONTROLOBSERVER_H
+
+#include <libdolphin_export.h>
+
+#include <kfileitem.h>
+#include <kversioncontrolplugin.h>
+#include <QList>
+#include <QMutex>
+#include <QObject>
+#include <QPersistentModelIndex>
+#include <QString>
+
+class DolphinModel;
+class KDirLister;
+class KFileItemList;
+class QAbstractItemView;
+class QAction;
+class QTimer;
+class UpdateItemStatesThread;
+
+/**
+ * @brief Observes all version control plugins.
+ *
+ * The item view gets updated automatically if the currently shown
+ * directory is under version control.
+ *
+ * @see VersionControlPlugin
+ */
+class LIBDOLPHINPRIVATE_EXPORT VersionControlObserver : public QObject
+{
+ Q_OBJECT
+
+public:
+ VersionControlObserver(QAbstractItemView* view);
+ virtual ~VersionControlObserver();
+
+ QList<QAction*> contextMenuActions(const KFileItemList& items) const;
+ QList<QAction*> contextMenuActions(const QString& directory) const;
+
+signals:
+ /**
+ * Is emitted if an information message with the content \a msg
+ * should be shown.
+ */
+ void infoMessage(const QString& msg);
+
+ /**
+ * Is emitted if an error message with the content \a msg
+ * should be shown.
+ */
+ void errorMessage(const QString& msg);
+
+ /**
+ * Is emitted if an "operation completed" message with the content \a msg
+ * should be shown.
+ */
+ void operationCompletedMessage(const QString& msg);
+
+private slots:
+ /**
+ * Invokes verifyDirectory() with a small delay. If delayedDirectoryVerification()
+ * is invoked before the delay has been exceeded, the delay will be reset. This
+ * assures that a lot of short requests for directory verification only result
+ * in one (expensive) call.
+ */
+ void delayedDirectoryVerification();
+
+ /**
+ * Invokes verifyDirectory() with a small delay. In opposite to
+ * delayedDirectoryVerification() it and assures that the verification of
+ * the directory is done silently without information messages.
+ */
+ void silentDirectoryVerification();
+
+ void verifyDirectory();
+ void applyUpdatedItemStates();
+
+private:
+ struct ItemState
+ {
+ QPersistentModelIndex index;
+ KFileItem item;
+ KVersionControlPlugin::VersionState version;
+ };
+
+ void updateItemStates();
+
+ /**
+ * Adds recursively all items from the directory \p parentIndex into
+ * the list \p itemStates.
+ */
+ void addDirectory(const QModelIndex& parentIndex, QList<ItemState>& itemStates);
+
+ /**
+ * Returns a matching plugin for the given directory.
+ * 0 is returned, if no matching plugin has been found.
+ */
+ KVersionControlPlugin* searchPlugin(const KUrl& directory) const;
+
+ /**
+ * Returns true, if the directory contains a version control information.
+ */
+ bool isVersioned() const;
+
+private:
+ bool m_pendingItemStatesUpdate;
+ bool m_versionedDirectory;
+ bool m_silentUpdate; // if true, no messages will be send during the update
+ // of version states
+
+ QAbstractItemView* m_view;
+ KDirLister* m_dirLister;
+ DolphinModel* m_dolphinModel;
+
+ QTimer* m_dirVerificationTimer;
+
+ KVersionControlPlugin* m_plugin;
+ UpdateItemStatesThread* m_updateItemStatesThread;
+
+ friend class UpdateItemStatesThread;
+};
+
+#endif // REVISIONCONTROLOBSERVER_H
+