1 /***************************************************************************
2 * Copyright (C) 2009 by Peter Penz <peter.penz19@gmail.com> *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
20 #include "versioncontrolobserver.h"
22 #include "dolphin_versioncontrolsettings.h"
24 #include <KLocalizedString>
26 #include "dolphindebug.h"
27 #include <KServiceTypeTrader>
28 #include <kitemviews/kfileitemmodel.h>
30 #include "updateitemstatesthread.h"
35 VersionControlObserver::VersionControlObserver(QObject
* parent
) :
37 m_pendingItemStatesUpdate(false),
38 m_versionedDirectory(false),
39 m_silentUpdate(false),
41 m_dirVerificationTimer(0),
43 m_updateItemStatesThread(0)
45 // The verification timer specifies the timeout until the shown directory
46 // is checked whether it is versioned. Per default it is assumed that users
47 // don't iterate through versioned directories and a high timeout is used
48 // The timeout will be decreased as soon as a versioned directory has been
49 // found (see verifyDirectory()).
50 m_dirVerificationTimer
= new QTimer(this);
51 m_dirVerificationTimer
->setSingleShot(true);
52 m_dirVerificationTimer
->setInterval(500);
53 connect(m_dirVerificationTimer
, &QTimer::timeout
,
54 this, &VersionControlObserver::verifyDirectory
);
57 VersionControlObserver::~VersionControlObserver()
60 m_plugin
->disconnect(this);
65 void VersionControlObserver::setModel(KFileItemModel
* model
)
68 disconnect(m_model
, &KFileItemModel::itemsInserted
,
69 this, &VersionControlObserver::delayedDirectoryVerification
);
70 disconnect(m_model
, &KFileItemModel::itemsChanged
,
71 this, &VersionControlObserver::delayedDirectoryVerification
);
77 connect(m_model
, &KFileItemModel::itemsInserted
,
78 this, &VersionControlObserver::delayedDirectoryVerification
);
79 connect(m_model
, &KFileItemModel::itemsChanged
,
80 this, &VersionControlObserver::delayedDirectoryVerification
);
84 KFileItemModel
* VersionControlObserver::model() const
89 QList
<QAction
*> VersionControlObserver::actions(const KFileItemList
& items
) const
91 bool hasNullItems
= false;
92 foreach (const KFileItem
& item
, items
) {
94 qCWarning(DolphinDebug
) << "Requesting version-control-actions for empty items";
100 if (!m_model
|| hasNullItems
|| !isVersioned()) {
104 return m_plugin
->actions(items
);
107 void VersionControlObserver::delayedDirectoryVerification()
109 m_silentUpdate
= false;
110 m_dirVerificationTimer
->start();
113 void VersionControlObserver::silentDirectoryVerification()
115 m_silentUpdate
= true;
116 m_dirVerificationTimer
->start();
119 void VersionControlObserver::verifyDirectory()
125 const KFileItem rootItem
= m_model
->rootItem();
126 if (rootItem
.isNull() || !rootItem
.url().isLocalFile()) {
131 m_plugin
->disconnect(this);
134 m_plugin
= searchPlugin(rootItem
.url());
136 connect(m_plugin
, &KVersionControlPlugin::itemVersionsChanged
,
137 this, &VersionControlObserver::silentDirectoryVerification
);
138 connect(m_plugin
, &KVersionControlPlugin::infoMessage
,
139 this, &VersionControlObserver::infoMessage
);
140 connect(m_plugin
, &KVersionControlPlugin::errorMessage
,
141 this, &VersionControlObserver::errorMessage
);
142 connect(m_plugin
, &KVersionControlPlugin::operationCompletedMessage
,
143 this, &VersionControlObserver::operationCompletedMessage
);
145 if (!m_versionedDirectory
) {
146 m_versionedDirectory
= true;
148 // The directory is versioned. Assume that the user will further browse through
149 // versioned directories and decrease the verification timer.
150 m_dirVerificationTimer
->setInterval(100);
153 } else if (m_versionedDirectory
) {
154 m_versionedDirectory
= false;
156 // The directory is not versioned. Reset the verification timer to a higher
157 // value, so that browsing through non-versioned directories is not slown down
158 // by an immediate verification.
159 m_dirVerificationTimer
->setInterval(500);
163 void VersionControlObserver::slotThreadFinished()
165 UpdateItemStatesThread
* thread
= m_updateItemStatesThread
;
166 m_updateItemStatesThread
= 0; // The thread deletes itself automatically (see updateItemStates())
168 if (!m_plugin
|| !thread
) {
172 const QMap
<QString
, QVector
<ItemState
> >& itemStates
= thread
->itemStates();
173 QMap
<QString
, QVector
<ItemState
> >::const_iterator it
= itemStates
.constBegin();
174 for (; it
!= itemStates
.constEnd(); ++it
) {
175 const QVector
<ItemState
>& items
= it
.value();
177 foreach (const ItemState
& item
, items
) {
178 const KFileItem
& fileItem
= item
.first
;
179 const KVersionControlPlugin::ItemVersion version
= item
.second
;
180 QHash
<QByteArray
, QVariant
> values
;
181 values
.insert("version", QVariant(version
));
182 m_model
->setData(m_model
->index(fileItem
), values
);
186 if (!m_silentUpdate
) {
187 // Using an empty message results in clearing the previously shown information message and showing
188 // the default status bar information. This is useful as the user already gets feedback that the
189 // operation has been completed because of the icon emblems.
190 emit
operationCompletedMessage(QString());
193 if (m_pendingItemStatesUpdate
) {
194 m_pendingItemStatesUpdate
= false;
199 void VersionControlObserver::updateItemStates()
202 if (m_updateItemStatesThread
) {
203 // An update is currently ongoing. Wait until the thread has finished
204 // the update (see slotThreadFinished()).
205 m_pendingItemStatesUpdate
= true;
209 QMap
<QString
, QVector
<ItemState
> > itemStates
;
210 createItemStatesList(itemStates
);
212 if (!itemStates
.isEmpty()) {
213 if (!m_silentUpdate
) {
214 emit
infoMessage(i18nc("@info:status", "Updating version information..."));
216 m_updateItemStatesThread
= new UpdateItemStatesThread(m_plugin
, itemStates
);
217 connect(m_updateItemStatesThread
, &UpdateItemStatesThread::finished
,
218 this, &VersionControlObserver::slotThreadFinished
);
219 connect(m_updateItemStatesThread
, &UpdateItemStatesThread::finished
,
220 m_updateItemStatesThread
, &UpdateItemStatesThread::deleteLater
);
222 m_updateItemStatesThread
->start(); // slotThreadFinished() is called when finished
226 int VersionControlObserver::createItemStatesList(QMap
<QString
, QVector
<ItemState
> >& itemStates
,
227 const int firstIndex
)
229 const int itemCount
= m_model
->count();
230 const int currentExpansionLevel
= m_model
->expandedParentsCount(firstIndex
);
232 QVector
<ItemState
> items
;
233 items
.reserve(itemCount
- firstIndex
);
236 for (index
= firstIndex
; index
< itemCount
; ++index
) {
237 const int expansionLevel
= m_model
->expandedParentsCount(index
);
239 if (expansionLevel
== currentExpansionLevel
) {
241 itemState
.first
= m_model
->fileItem(index
);
242 itemState
.second
= KVersionControlPlugin::UnversionedVersion
;
244 items
.append(itemState
);
245 } else if (expansionLevel
> currentExpansionLevel
) {
247 index
+= createItemStatesList(itemStates
, index
) - 1;
253 if (items
.count() > 0) {
254 const QUrl
& url
= items
.first().first
.url();
255 itemStates
.insert(url
.adjusted(QUrl::RemoveFilename
).path(), items
);
258 return index
- firstIndex
; // number of processed items
261 KVersionControlPlugin
* VersionControlObserver::searchPlugin(const QUrl
& directory
) const
263 static bool pluginsAvailable
= true;
264 static QList
<KVersionControlPlugin
*> plugins
;
266 if (!pluginsAvailable
) {
267 // A searching for plugins has already been done, but no
268 // plugins are installed
272 if (plugins
.isEmpty()) {
273 // No searching for plugins has been done yet. Query the KServiceTypeTrader for
274 // all fileview version control plugins and remember them in 'plugins'.
275 const QStringList enabledPlugins
= VersionControlSettings::enabledPlugins();
277 const KService::List pluginServices
= KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin"));
278 for (KService::List::ConstIterator it
= pluginServices
.constBegin(); it
!= pluginServices
.constEnd(); ++it
) {
279 if (enabledPlugins
.contains((*it
)->name())) {
280 KVersionControlPlugin
* plugin
= (*it
)->createInstance
<KVersionControlPlugin
>();
282 plugins
.append(plugin
);
286 if (plugins
.isEmpty()) {
287 pluginsAvailable
= false;
292 // We use the number of upUrl() calls to find the best matching plugin
293 // for the given directory. The smaller value, the better it is (0 is best).
294 KVersionControlPlugin
* bestPlugin
= 0;
295 int bestScore
= INT_MAX
;
297 // Verify whether the current directory contains revision information
298 // like .svn, .git, ...
299 foreach (KVersionControlPlugin
* plugin
, plugins
) {
300 const QString fileName
= directory
.path() + '/' + plugin
->fileName();
301 if (QFile::exists(fileName
)) {
302 // The score of this plugin is 0 (best), so we can just return this plugin,
303 // instead of going through the plugin scoring procedure, we can't find a better one ;)
307 // Version control systems like Git provide the version information
308 // file only in the root directory. Check whether the version information file can
309 // be found in one of the parent directories. For performance reasons this
310 // step is only done, if the previous directory was marked as versioned by
311 // m_versionedDirectory. Drawback: Until e. g. Git is recognized, the root directory
312 // must be shown at least once.
313 if (m_versionedDirectory
) {
314 QUrl
dirUrl(directory
);
315 QUrl upUrl
= KIO::upUrl(dirUrl
);
316 int upUrlCounter
= 1;
317 while ((upUrlCounter
< bestScore
) && (upUrl
!= dirUrl
)) {
318 const QString fileName
= dirUrl
.path() + '/' + plugin
->fileName();
319 if (QFile::exists(fileName
)) {
320 if (upUrlCounter
< bestScore
) {
322 bestScore
= upUrlCounter
;
327 upUrl
= KIO::upUrl(dirUrl
);
336 bool VersionControlObserver::isVersioned() const
338 return m_versionedDirectory
&& m_plugin
;