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>
18 #include <KPluginLoader>
19 #include <KPluginMetaData>
23 VersionControlObserver::VersionControlObserver(QObject
* parent
) :
25 m_pendingItemStatesUpdate(false),
26 m_silentUpdate(false),
29 m_dirVerificationTimer(nullptr),
30 m_pluginsInitialized(false),
32 m_updateItemStatesThread(nullptr)
34 // The verification timer specifies the timeout until the shown directory
35 // is checked whether it is versioned. Per default it is assumed that users
36 // don't iterate through versioned directories and a high timeout is used
37 // The timeout will be decreased as soon as a versioned directory has been
38 // found (see verifyDirectory()).
39 m_dirVerificationTimer
= new QTimer(this);
40 m_dirVerificationTimer
->setSingleShot(true);
41 m_dirVerificationTimer
->setInterval(500);
42 connect(m_dirVerificationTimer
, &QTimer::timeout
,
43 this, &VersionControlObserver::verifyDirectory
);
46 VersionControlObserver::~VersionControlObserver()
49 m_plugin
->disconnect(this);
54 void VersionControlObserver::setModel(KFileItemModel
* model
)
57 disconnect(m_model
, &KFileItemModel::itemsInserted
,
58 this, &VersionControlObserver::delayedDirectoryVerification
);
59 disconnect(m_model
, &KFileItemModel::itemsChanged
,
60 this, &VersionControlObserver::slotItemsChanged
);
66 connect(m_model
, &KFileItemModel::itemsInserted
,
67 this, &VersionControlObserver::delayedDirectoryVerification
);
68 connect(m_model
, &KFileItemModel::itemsChanged
,
69 this, &VersionControlObserver::slotItemsChanged
);
73 KFileItemModel
* VersionControlObserver::model() const
78 void VersionControlObserver::setView(DolphinView
* view
)
81 disconnect(m_view
, &DolphinView::activated
,
82 this, &VersionControlObserver::delayedDirectoryVerification
);
88 connect(m_view
, &DolphinView::activated
,
89 this, &VersionControlObserver::delayedDirectoryVerification
);
93 DolphinView
* VersionControlObserver::view() const
98 QList
<QAction
*> VersionControlObserver::actions(const KFileItemList
& items
) const
100 bool hasNullItems
= false;
101 for (const KFileItem
& item
: items
) {
103 qCWarning(DolphinDebug
) << "Requesting version-control-actions for empty items";
109 if (!m_model
|| hasNullItems
) {
113 if (isVersionControlled()) {
114 return m_plugin
->versionControlActions(items
);
116 QList
<QAction
*> actions
;
117 for (const QPointer
<KVersionControlPlugin
> &plugin
: qAsConst(m_plugins
)) {
118 actions
<< plugin
->outOfVersionControlActions(items
);
124 void VersionControlObserver::delayedDirectoryVerification()
126 m_silentUpdate
= false;
127 m_dirVerificationTimer
->start();
130 void VersionControlObserver::silentDirectoryVerification()
132 m_silentUpdate
= true;
133 m_dirVerificationTimer
->start();
136 void VersionControlObserver::slotItemsChanged(const KItemRangeList
& itemRanges
, const QSet
<QByteArray
>& roles
)
140 // Because "version" role is emitted by VCS plugin (ourselfs) we don't need to
141 // analyze it and update directory item states information. So lets check if
142 // there is only "version".
143 if ( !(roles
.count() == 1 && roles
.contains("version")) ) {
144 delayedDirectoryVerification();
148 void VersionControlObserver::verifyDirectory()
154 const KFileItem rootItem
= m_model
->rootItem();
155 if (rootItem
.isNull() || !rootItem
.url().isLocalFile()) {
159 if (m_plugin
!= nullptr) {
160 if (!rootItem
.url().path().startsWith(m_localRepoRoot
) || !QFile::exists(m_localRepoRoot
+ '/' + m_plugin
->fileName())) {
163 // The directory is not versioned. Reset the verification timer to a higher
164 // value, so that browsing through non-versioned directories is not slown down
165 // by an immediate verification.
166 m_dirVerificationTimer
->setInterval(500);
168 // View was versionned but should not be anymore
171 } else if ((m_plugin
= searchPlugin(rootItem
.url()))) {
172 // The directory is versioned. Assume that the user will further browse through
173 // versioned directories and decrease the verification timer.
174 m_dirVerificationTimer
->setInterval(100);
179 void VersionControlObserver::slotThreadFinished()
181 UpdateItemStatesThread
* thread
= m_updateItemStatesThread
;
182 m_updateItemStatesThread
= nullptr; // The thread deletes itself automatically (see updateItemStates())
184 if (!m_plugin
|| !thread
) {
188 const QMap
<QString
, QVector
<ItemState
> >& itemStates
= thread
->itemStates();
189 QMap
<QString
, QVector
<ItemState
> >::const_iterator it
= itemStates
.constBegin();
190 for (; it
!= itemStates
.constEnd(); ++it
) {
191 const QVector
<ItemState
>& items
= it
.value();
193 for (const ItemState
& item
: items
) {
194 const KFileItem
& fileItem
= item
.first
;
195 const KVersionControlPlugin::ItemVersion version
= item
.second
;
196 QHash
<QByteArray
, QVariant
> values
;
197 values
.insert("version", QVariant(version
));
198 m_model
->setData(m_model
->index(fileItem
), values
);
202 if (!m_silentUpdate
) {
203 // Using an empty message results in clearing the previously shown information message and showing
204 // the default status bar information. This is useful as the user already gets feedback that the
205 // operation has been completed because of the icon emblems.
206 Q_EMIT
operationCompletedMessage(QString());
209 if (m_pendingItemStatesUpdate
) {
210 m_pendingItemStatesUpdate
= false;
215 void VersionControlObserver::updateItemStates()
218 if (m_updateItemStatesThread
) {
219 // An update is currently ongoing. Wait until the thread has finished
220 // the update (see slotThreadFinished()).
221 m_pendingItemStatesUpdate
= true;
225 QMap
<QString
, QVector
<ItemState
> > itemStates
;
226 createItemStatesList(itemStates
);
228 if (!itemStates
.isEmpty()) {
229 if (!m_silentUpdate
) {
230 Q_EMIT
infoMessage(i18nc("@info:status", "Updating version information..."));
232 m_updateItemStatesThread
= new UpdateItemStatesThread(m_plugin
, itemStates
);
233 connect(m_updateItemStatesThread
, &UpdateItemStatesThread::finished
,
234 this, &VersionControlObserver::slotThreadFinished
);
235 connect(m_updateItemStatesThread
, &UpdateItemStatesThread::finished
,
236 m_updateItemStatesThread
, &UpdateItemStatesThread::deleteLater
);
238 m_updateItemStatesThread
->start(); // slotThreadFinished() is called when finished
242 int VersionControlObserver::createItemStatesList(QMap
<QString
, QVector
<ItemState
> >& itemStates
,
243 const int firstIndex
)
245 const int itemCount
= m_model
->count();
246 const int currentExpansionLevel
= m_model
->expandedParentsCount(firstIndex
);
248 QVector
<ItemState
> items
;
249 items
.reserve(itemCount
- firstIndex
);
252 for (index
= firstIndex
; index
< itemCount
; ++index
) {
253 const int expansionLevel
= m_model
->expandedParentsCount(index
);
255 if (expansionLevel
== currentExpansionLevel
) {
257 itemState
.first
= m_model
->fileItem(index
);
258 itemState
.second
= KVersionControlPlugin::UnversionedVersion
;
260 items
.append(itemState
);
261 } else if (expansionLevel
> currentExpansionLevel
) {
263 index
+= createItemStatesList(itemStates
, index
) - 1;
269 if (!items
.isEmpty()) {
270 const QUrl
& url
= items
.first().first
.url();
271 itemStates
.insert(url
.adjusted(QUrl::RemoveFilename
).path(), items
);
274 return index
- firstIndex
; // number of processed items
277 void VersionControlObserver::initPlugins()
279 if (!m_pluginsInitialized
) {
280 // No searching for plugins has been done yet. Query the KServiceTypeTrader for
281 // all fileview version control plugins and remember them in 'plugins'.
282 const QStringList enabledPlugins
= VersionControlSettings::enabledPlugins();
284 const QVector
<KPluginMetaData
> plugins
= KPluginLoader::findPlugins(QStringLiteral("dolphin/vcs"));
286 QSet
<QString
> loadedPlugins
;
288 for (const auto &p
: plugins
) {
289 if (enabledPlugins
.contains(p
.name())) {
290 KPluginLoader
loader(p
.fileName());
291 KPluginFactory
*factory
= loader
.factory();
292 KVersionControlPlugin
*plugin
= factory
->create
<KVersionControlPlugin
>();
294 m_plugins
.append(plugin
);
295 loadedPlugins
+= p
.name();
300 // Deprecated: load plugins using KService. This mechanism will be removed with KF6
301 const KService::List pluginServices
= KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin"));
302 for (KService::List::ConstIterator it
= pluginServices
.constBegin(); it
!= pluginServices
.constEnd(); ++it
) {
303 if (loadedPlugins
.contains((*it
)->property("Name", QVariant::String
).toString())) {
306 if (enabledPlugins
.contains((*it
)->name())) {
307 KPluginLoader
pluginLoader(*(*it
));
308 // Need to cast to int, because pluginVersion() returns -1 as
309 // an unsigned int for plugins without versions.
310 if (int(pluginLoader
.pluginVersion()) < 2) {
311 qCWarning(DolphinDebug
) << "Can't load old plugin" << (*it
)->name();
314 KVersionControlPlugin
* plugin
= (*it
)->createInstance
<KVersionControlPlugin
>(this);
316 m_plugins
.append(plugin
);
321 for (auto &plugin
: qAsConst(m_plugins
)) {
322 connect(plugin
, &KVersionControlPlugin::itemVersionsChanged
,
323 this, &VersionControlObserver::silentDirectoryVerification
);
324 connect(plugin
, &KVersionControlPlugin::infoMessage
,
325 this, &VersionControlObserver::infoMessage
);
326 connect(plugin
, &KVersionControlPlugin::errorMessage
,
327 this, &VersionControlObserver::errorMessage
);
328 connect(plugin
, &KVersionControlPlugin::operationCompletedMessage
,
329 this, &VersionControlObserver::operationCompletedMessage
);
332 m_pluginsInitialized
= true;
336 KVersionControlPlugin
* VersionControlObserver::searchPlugin(const QUrl
& directory
)
340 // Verify whether the current directory is under a version system
341 for (const QPointer
<KVersionControlPlugin
> &plugin
: qAsConst(m_plugins
)) {
346 // first naively check if we are at working copy root
347 const QString fileName
= directory
.path() + '/' + plugin
->fileName();
348 if (QFile::exists(fileName
)) {
349 m_localRepoRoot
= directory
.path();
352 const QString root
= plugin
->localRepositoryRoot(directory
.path());
353 if (!root
.isEmpty()) {
354 m_localRepoRoot
= root
;
361 bool VersionControlObserver::isVersionControlled() const
363 return m_plugin
!= nullptr;