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>
16 #include <KPluginFactory>
17 #include <KPluginMetaData>
21 VersionControlObserver::VersionControlObserver(QObject
* parent
) :
23 m_pendingItemStatesUpdate(false),
24 m_silentUpdate(false),
27 m_dirVerificationTimer(nullptr),
28 m_pluginsInitialized(false),
30 m_updateItemStatesThread(nullptr)
32 // The verification timer specifies the timeout until the shown directory
33 // is checked whether it is versioned. Per default it is assumed that users
34 // don't iterate through versioned directories and a high timeout is used
35 // The timeout will be decreased as soon as a versioned directory has been
36 // found (see verifyDirectory()).
37 m_dirVerificationTimer
= new QTimer(this);
38 m_dirVerificationTimer
->setSingleShot(true);
39 m_dirVerificationTimer
->setInterval(500);
40 connect(m_dirVerificationTimer
, &QTimer::timeout
,
41 this, &VersionControlObserver::verifyDirectory
);
44 VersionControlObserver::~VersionControlObserver()
47 m_plugin
->disconnect(this);
52 void VersionControlObserver::setModel(KFileItemModel
* model
)
55 disconnect(m_model
, &KFileItemModel::itemsInserted
,
56 this, &VersionControlObserver::delayedDirectoryVerification
);
57 disconnect(m_model
, &KFileItemModel::itemsChanged
,
58 this, &VersionControlObserver::slotItemsChanged
);
64 connect(m_model
, &KFileItemModel::itemsInserted
,
65 this, &VersionControlObserver::delayedDirectoryVerification
);
66 connect(m_model
, &KFileItemModel::itemsChanged
,
67 this, &VersionControlObserver::slotItemsChanged
);
71 KFileItemModel
* VersionControlObserver::model() const
76 void VersionControlObserver::setView(DolphinView
* view
)
79 disconnect(m_view
, &DolphinView::activated
,
80 this, &VersionControlObserver::delayedDirectoryVerification
);
86 connect(m_view
, &DolphinView::activated
,
87 this, &VersionControlObserver::delayedDirectoryVerification
);
91 DolphinView
* VersionControlObserver::view() const
96 QList
<QAction
*> VersionControlObserver::actions(const KFileItemList
& items
) const
98 bool hasNullItems
= false;
99 for (const KFileItem
& item
: items
) {
101 qCWarning(DolphinDebug
) << "Requesting version-control-actions for empty items";
107 if (!m_model
|| hasNullItems
) {
111 if (isVersionControlled()) {
112 return m_plugin
->versionControlActions(items
);
114 QList
<QAction
*> actions
;
115 for (const QPointer
<KVersionControlPlugin
> &plugin
: qAsConst(m_plugins
)) {
116 actions
<< plugin
->outOfVersionControlActions(items
);
122 void VersionControlObserver::delayedDirectoryVerification()
124 m_silentUpdate
= false;
125 m_dirVerificationTimer
->start();
128 void VersionControlObserver::silentDirectoryVerification()
130 m_silentUpdate
= true;
131 m_dirVerificationTimer
->start();
134 void VersionControlObserver::slotItemsChanged(const KItemRangeList
& itemRanges
, const QSet
<QByteArray
>& roles
)
138 // Because "version" role is emitted by VCS plugin (ourselves) we don't need to
139 // analyze it and update directory item states information. So lets check if
140 // there is only "version".
141 if ( !(roles
.count() == 1 && roles
.contains("version")) ) {
142 delayedDirectoryVerification();
146 void VersionControlObserver::verifyDirectory()
152 const KFileItem rootItem
= m_model
->rootItem();
153 if (rootItem
.isNull() || !rootItem
.url().isLocalFile()) {
157 if (m_plugin
!= nullptr) {
158 if (!rootItem
.url().path().startsWith(m_localRepoRoot
) || !QFile::exists(m_localRepoRoot
+ '/' + m_plugin
->fileName())) {
161 // The directory is not versioned. Reset the verification timer to a higher
162 // value, so that browsing through non-versioned directories is not slown down
163 // by an immediate verification.
164 m_dirVerificationTimer
->setInterval(500);
166 // View was versioned but should not be anymore
169 } else if ((m_plugin
= searchPlugin(rootItem
.url()))) {
170 // The directory is versioned. Assume that the user will further browse through
171 // versioned directories and decrease the verification timer.
172 m_dirVerificationTimer
->setInterval(100);
177 void VersionControlObserver::slotThreadFinished()
179 UpdateItemStatesThread
* thread
= m_updateItemStatesThread
;
180 m_updateItemStatesThread
= nullptr; // The thread deletes itself automatically (see updateItemStates())
182 if (!m_plugin
|| !thread
) {
186 const QMap
<QString
, QVector
<ItemState
> >& itemStates
= thread
->itemStates();
187 QMap
<QString
, QVector
<ItemState
> >::const_iterator it
= itemStates
.constBegin();
188 for (; it
!= itemStates
.constEnd(); ++it
) {
189 const QVector
<ItemState
>& items
= it
.value();
191 for (const ItemState
& item
: items
) {
192 const KFileItem
& fileItem
= item
.first
;
193 const KVersionControlPlugin::ItemVersion version
= item
.second
;
194 QHash
<QByteArray
, QVariant
> values
;
195 values
.insert("version", QVariant(version
));
196 m_model
->setData(m_model
->index(fileItem
), values
);
200 if (!m_silentUpdate
) {
201 // Using an empty message results in clearing the previously shown information message and showing
202 // the default status bar information. This is useful as the user already gets feedback that the
203 // operation has been completed because of the icon emblems.
204 Q_EMIT
operationCompletedMessage(QString());
207 if (m_pendingItemStatesUpdate
) {
208 m_pendingItemStatesUpdate
= false;
213 void VersionControlObserver::updateItemStates()
216 if (m_updateItemStatesThread
) {
217 // An update is currently ongoing. Wait until the thread has finished
218 // the update (see slotThreadFinished()).
219 m_pendingItemStatesUpdate
= true;
223 QMap
<QString
, QVector
<ItemState
> > itemStates
;
224 createItemStatesList(itemStates
);
226 if (!itemStates
.isEmpty()) {
227 if (!m_silentUpdate
) {
228 Q_EMIT
infoMessage(i18nc("@info:status", "Updating version information..."));
230 m_updateItemStatesThread
= new UpdateItemStatesThread(m_plugin
, itemStates
);
231 connect(m_updateItemStatesThread
, &UpdateItemStatesThread::finished
,
232 this, &VersionControlObserver::slotThreadFinished
);
233 connect(m_updateItemStatesThread
, &UpdateItemStatesThread::finished
,
234 m_updateItemStatesThread
, &UpdateItemStatesThread::deleteLater
);
236 m_updateItemStatesThread
->start(); // slotThreadFinished() is called when finished
240 int VersionControlObserver::createItemStatesList(QMap
<QString
, QVector
<ItemState
> >& itemStates
,
241 const int firstIndex
)
243 const int itemCount
= m_model
->count();
244 const int currentExpansionLevel
= m_model
->expandedParentsCount(firstIndex
);
246 QVector
<ItemState
> items
;
247 items
.reserve(itemCount
- firstIndex
);
250 for (index
= firstIndex
; index
< itemCount
; ++index
) {
251 const int expansionLevel
= m_model
->expandedParentsCount(index
);
253 if (expansionLevel
== currentExpansionLevel
) {
255 itemState
.first
= m_model
->fileItem(index
);
256 itemState
.second
= KVersionControlPlugin::UnversionedVersion
;
258 items
.append(itemState
);
259 } else if (expansionLevel
> currentExpansionLevel
) {
261 index
+= createItemStatesList(itemStates
, index
) - 1;
267 if (!items
.isEmpty()) {
268 const QUrl
& url
= items
.first().first
.url();
269 itemStates
.insert(url
.adjusted(QUrl::RemoveFilename
).path(), items
);
272 return index
- firstIndex
; // number of processed items
275 void VersionControlObserver::initPlugins()
277 if (!m_pluginsInitialized
) {
278 // No searching for plugins has been done yet. Query the KServiceTypeTrader for
279 // all fileview version control plugins and remember them in 'plugins'.
280 const QStringList enabledPlugins
= VersionControlSettings::enabledPlugins();
282 const QVector
<KPluginMetaData
> plugins
= KPluginMetaData::findPlugins(QStringLiteral("dolphin/vcs"));
284 QSet
<QString
> loadedPlugins
;
286 for (const auto &p
: plugins
) {
287 if (enabledPlugins
.contains(p
.name())) {
288 auto plugin
= KPluginFactory::instantiatePlugin
<KVersionControlPlugin
>(p
).plugin
;
290 m_plugins
.append(plugin
);
291 loadedPlugins
+= p
.name();
296 for (auto &plugin
: qAsConst(m_plugins
)) {
297 connect(plugin
, &KVersionControlPlugin::itemVersionsChanged
,
298 this, &VersionControlObserver::silentDirectoryVerification
);
299 connect(plugin
, &KVersionControlPlugin::infoMessage
,
300 this, &VersionControlObserver::infoMessage
);
301 connect(plugin
, &KVersionControlPlugin::errorMessage
,
302 this, &VersionControlObserver::errorMessage
);
303 connect(plugin
, &KVersionControlPlugin::operationCompletedMessage
,
304 this, &VersionControlObserver::operationCompletedMessage
);
307 m_pluginsInitialized
= true;
311 KVersionControlPlugin
* VersionControlObserver::searchPlugin(const QUrl
& directory
)
315 // Verify whether the current directory is under a version system
316 for (const QPointer
<KVersionControlPlugin
> &plugin
: qAsConst(m_plugins
)) {
321 // first naively check if we are at working copy root
322 const QString fileName
= directory
.path() + '/' + plugin
->fileName();
323 if (QFile::exists(fileName
)) {
324 m_localRepoRoot
= directory
.path();
327 const QString root
= plugin
->localRepositoryRoot(directory
.path());
328 if (!root
.isEmpty()) {
329 m_localRepoRoot
= root
;
336 bool VersionControlObserver::isVersionControlled() const
338 return m_plugin
!= nullptr;