2 * SPDX-FileCopyrightText: 2009 Peter Penz <peter.penz19@gmail.com>
4 * SPDX-License-Identifier: GPL-2.0-or-later
7 #include "versioncontrolobserver.h"
9 #include "dolphin_versioncontrolsettings.h"
10 #include "dolphindebug.h"
11 #include "views/dolphinview.h"
12 #include "kitemviews/kfileitemmodel.h"
13 #include "updateitemstatesthread.h"
15 #include <KLocalizedString>
17 #include <KServiceTypeTrader>
21 VersionControlObserver::VersionControlObserver(QObject
* parent
) :
23 m_pendingItemStatesUpdate(false),
24 m_versionedDirectory(false),
25 m_silentUpdate(false),
28 m_dirVerificationTimer(nullptr),
29 m_pluginsInitialized(false),
31 m_updateItemStatesThread(nullptr)
33 // The verification timer specifies the timeout until the shown directory
34 // is checked whether it is versioned. Per default it is assumed that users
35 // don't iterate through versioned directories and a high timeout is used
36 // The timeout will be decreased as soon as a versioned directory has been
37 // found (see verifyDirectory()).
38 m_dirVerificationTimer
= new QTimer(this);
39 m_dirVerificationTimer
->setSingleShot(true);
40 m_dirVerificationTimer
->setInterval(500);
41 connect(m_dirVerificationTimer
, &QTimer::timeout
,
42 this, &VersionControlObserver::verifyDirectory
);
45 VersionControlObserver::~VersionControlObserver()
48 m_plugin
->disconnect(this);
53 void VersionControlObserver::setModel(KFileItemModel
* model
)
56 disconnect(m_model
, &KFileItemModel::itemsInserted
,
57 this, &VersionControlObserver::delayedDirectoryVerification
);
58 disconnect(m_model
, &KFileItemModel::itemsChanged
,
59 this, &VersionControlObserver::slotItemsChanged
);
65 connect(m_model
, &KFileItemModel::itemsInserted
,
66 this, &VersionControlObserver::delayedDirectoryVerification
);
67 connect(m_model
, &KFileItemModel::itemsChanged
,
68 this, &VersionControlObserver::slotItemsChanged
);
72 KFileItemModel
* VersionControlObserver::model() const
77 void VersionControlObserver::setView(DolphinView
* view
)
80 disconnect(m_view
, &DolphinView::activated
,
81 this, &VersionControlObserver::delayedDirectoryVerification
);
87 connect(m_view
, &DolphinView::activated
,
88 this, &VersionControlObserver::delayedDirectoryVerification
);
92 DolphinView
* VersionControlObserver::view() const
97 QList
<QAction
*> VersionControlObserver::actions(const KFileItemList
& items
) const
99 bool hasNullItems
= false;
100 foreach (const KFileItem
& item
, items
) {
102 qCWarning(DolphinDebug
) << "Requesting version-control-actions for empty items";
108 if (!m_model
|| hasNullItems
) {
112 if (isVersionControlled()) {
113 return m_plugin
->versionControlActions(items
);
115 QList
<QAction
*> actions
;
116 for (const auto &plugin
: qAsConst(m_plugins
)) {
117 actions
<< plugin
.first
->outOfVersionControlActions(items
);
123 void VersionControlObserver::delayedDirectoryVerification()
125 m_silentUpdate
= false;
126 m_dirVerificationTimer
->start();
129 void VersionControlObserver::silentDirectoryVerification()
131 m_silentUpdate
= true;
132 m_dirVerificationTimer
->start();
135 void VersionControlObserver::slotItemsChanged(const KItemRangeList
& itemRanges
, const QSet
<QByteArray
>& roles
)
139 // Because "version" role is emitted by VCS plugin (ourselfs) we don't need to
140 // analyze it and update directory item states information. So lets check if
141 // there is only "version".
142 if ( !(roles
.count() == 1 && roles
.contains("version")) ) {
143 delayedDirectoryVerification();
147 void VersionControlObserver::verifyDirectory()
153 const KFileItem rootItem
= m_model
->rootItem();
154 if (rootItem
.isNull() || !rootItem
.url().isLocalFile()) {
158 m_plugin
= searchPlugin(rootItem
.url());
160 if (!m_versionedDirectory
) {
161 m_versionedDirectory
= true;
163 // The directory is versioned. Assume that the user will further browse through
164 // versioned directories and decrease the verification timer.
165 m_dirVerificationTimer
->setInterval(100);
168 } else if (m_versionedDirectory
) {
169 m_versionedDirectory
= false;
171 // The directory is not versioned. Reset the verification timer to a higher
172 // value, so that browsing through non-versioned directories is not slown down
173 // by an immediate verification.
174 m_dirVerificationTimer
->setInterval(500);
178 void VersionControlObserver::slotThreadFinished()
180 UpdateItemStatesThread
* thread
= m_updateItemStatesThread
;
181 m_updateItemStatesThread
= nullptr; // The thread deletes itself automatically (see updateItemStates())
183 if (!m_plugin
|| !thread
) {
187 const QMap
<QString
, QVector
<ItemState
> >& itemStates
= thread
->itemStates();
188 QMap
<QString
, QVector
<ItemState
> >::const_iterator it
= itemStates
.constBegin();
189 for (; it
!= itemStates
.constEnd(); ++it
) {
190 const QVector
<ItemState
>& items
= it
.value();
192 foreach (const ItemState
& item
, items
) {
193 const KFileItem
& fileItem
= item
.first
;
194 const KVersionControlPlugin::ItemVersion version
= item
.second
;
195 QHash
<QByteArray
, QVariant
> values
;
196 values
.insert("version", QVariant(version
));
197 m_model
->setData(m_model
->index(fileItem
), values
);
201 if (!m_silentUpdate
) {
202 // Using an empty message results in clearing the previously shown information message and showing
203 // the default status bar information. This is useful as the user already gets feedback that the
204 // operation has been completed because of the icon emblems.
205 emit
operationCompletedMessage(QString());
208 if (m_pendingItemStatesUpdate
) {
209 m_pendingItemStatesUpdate
= false;
214 void VersionControlObserver::updateItemStates()
217 if (m_updateItemStatesThread
) {
218 // An update is currently ongoing. Wait until the thread has finished
219 // the update (see slotThreadFinished()).
220 m_pendingItemStatesUpdate
= true;
224 QMap
<QString
, QVector
<ItemState
> > itemStates
;
225 createItemStatesList(itemStates
);
227 if (!itemStates
.isEmpty()) {
228 if (!m_silentUpdate
) {
229 emit
infoMessage(i18nc("@info:status", "Updating version information..."));
231 m_updateItemStatesThread
= new UpdateItemStatesThread(m_plugin
, itemStates
);
232 connect(m_updateItemStatesThread
, &UpdateItemStatesThread::finished
,
233 this, &VersionControlObserver::slotThreadFinished
);
234 connect(m_updateItemStatesThread
, &UpdateItemStatesThread::finished
,
235 m_updateItemStatesThread
, &UpdateItemStatesThread::deleteLater
);
237 m_updateItemStatesThread
->start(); // slotThreadFinished() is called when finished
241 int VersionControlObserver::createItemStatesList(QMap
<QString
, QVector
<ItemState
> >& itemStates
,
242 const int firstIndex
)
244 const int itemCount
= m_model
->count();
245 const int currentExpansionLevel
= m_model
->expandedParentsCount(firstIndex
);
247 QVector
<ItemState
> items
;
248 items
.reserve(itemCount
- firstIndex
);
251 for (index
= firstIndex
; index
< itemCount
; ++index
) {
252 const int expansionLevel
= m_model
->expandedParentsCount(index
);
254 if (expansionLevel
== currentExpansionLevel
) {
256 itemState
.first
= m_model
->fileItem(index
);
257 itemState
.second
= KVersionControlPlugin::UnversionedVersion
;
259 items
.append(itemState
);
260 } else if (expansionLevel
> currentExpansionLevel
) {
262 index
+= createItemStatesList(itemStates
, index
) - 1;
268 if (!items
.isEmpty()) {
269 const QUrl
& url
= items
.first().first
.url();
270 itemStates
.insert(url
.adjusted(QUrl::RemoveFilename
).path(), items
);
273 return index
- firstIndex
; // number of processed items
276 KVersionControlPlugin
* VersionControlObserver::searchPlugin(const QUrl
& directory
)
278 if (!m_pluginsInitialized
) {
279 // No searching for plugins has been done yet. Query the KServiceTypeTrader for
280 // all fileview version control plugins and remember them in 'plugins'.
281 const QStringList enabledPlugins
= VersionControlSettings::enabledPlugins();
283 const KService::List pluginServices
= KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin"));
284 for (KService::List::ConstIterator it
= pluginServices
.constBegin(); it
!= pluginServices
.constEnd(); ++it
) {
285 if (enabledPlugins
.contains((*it
)->name())) {
286 KVersionControlPlugin
* plugin
= (*it
)->createInstance
<KVersionControlPlugin
>(this);
288 connect(plugin
, &KVersionControlPlugin::itemVersionsChanged
,
289 this, &VersionControlObserver::silentDirectoryVerification
);
290 connect(plugin
, &KVersionControlPlugin::infoMessage
,
291 this, &VersionControlObserver::infoMessage
);
292 connect(plugin
, &KVersionControlPlugin::errorMessage
,
293 this, &VersionControlObserver::errorMessage
);
294 connect(plugin
, &KVersionControlPlugin::operationCompletedMessage
,
295 this, &VersionControlObserver::operationCompletedMessage
);
297 m_plugins
.append( qMakePair(plugin
, plugin
->fileName()) );
301 m_pluginsInitialized
= true;
304 if (m_plugins
.empty()) {
305 // A searching for plugins has already been done, but no
306 // plugins are installed
310 // We use the number of upUrl() calls to find the best matching plugin
311 // for the given directory. The smaller value, the better it is (0 is best).
312 KVersionControlPlugin
* bestPlugin
= nullptr;
313 int bestScore
= INT_MAX
;
315 // Verify whether the current directory contains revision information
316 // like .svn, .git, ...
317 for (const auto &it
: qAsConst(m_plugins
)) {
318 const QString fileName
= directory
.path() + '/' + it
.second
;
319 if (QFile::exists(fileName
)) {
320 // The score of this plugin is 0 (best), so we can just return this plugin,
321 // instead of going through the plugin scoring procedure, we can't find a better one ;)
325 // Version control systems like Git provide the version information
326 // file only in the root directory. Check whether the version information file can
327 // be found in one of the parent directories. For performance reasons this
328 // step is only done, if the previous directory was marked as versioned by
329 // m_versionedDirectory. Drawback: Until e. g. Git is recognized, the root directory
330 // must be shown at least once.
331 if (m_versionedDirectory
) {
332 QUrl
dirUrl(directory
);
333 QUrl upUrl
= KIO::upUrl(dirUrl
);
334 int upUrlCounter
= 1;
335 while ((upUrlCounter
< bestScore
) && (upUrl
!= dirUrl
)) {
336 const QString fileName
= dirUrl
.path() + '/' + it
.second
;
337 if (QFile::exists(fileName
)) {
338 if (upUrlCounter
< bestScore
) {
339 bestPlugin
= it
.first
;
340 bestScore
= upUrlCounter
;
345 upUrl
= KIO::upUrl(dirUrl
);
354 bool VersionControlObserver::isVersionControlled() const
356 return m_versionedDirectory
&& m_plugin
;