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 "kitemviews/kfileitemmodel.h"
12 #include "updateitemstatesthread.h"
13 #include "views/dolphinview.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)
29 , m_currentPlugin(nullptr)
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
, this, &VersionControlObserver::verifyDirectory
);
43 VersionControlObserver::~VersionControlObserver()
45 if (m_currentPlugin
) {
46 m_currentPlugin
->disconnect(this);
48 if (m_updateItemStatesThread
) {
49 m_updateItemStatesThread
->requestInterruption();
50 m_updateItemStatesThread
->wait();
51 m_updateItemStatesThread
->deleteLater();
54 if (m_currentPlugin
) {
55 delete m_currentPlugin
;
56 m_currentPlugin
= nullptr;
61 void VersionControlObserver::setModel(KFileItemModel
*model
)
64 if (m_currentPlugin
) {
65 delete m_currentPlugin
;
66 m_currentPlugin
= nullptr;
68 if (m_updateItemStatesThread
) {
69 m_updateItemStatesThread
->requestInterruption();
71 disconnect(m_model
, &KFileItemModel::itemsInserted
, this, &VersionControlObserver::delayedDirectoryVerification
);
72 disconnect(m_model
, &KFileItemModel::itemsChanged
, this, &VersionControlObserver::slotItemsChanged
);
73 disconnect(m_model
, &KFileItemModel::directoryLoadingCompleted
, this, &VersionControlObserver::verifyDirectory
);
79 connect(m_model
, &KFileItemModel::itemsInserted
, this, &VersionControlObserver::delayedDirectoryVerification
);
80 connect(m_model
, &KFileItemModel::itemsChanged
, this, &VersionControlObserver::slotItemsChanged
);
81 connect(m_model
, &KFileItemModel::directoryLoadingCompleted
, this, &VersionControlObserver::verifyDirectory
);
85 KFileItemModel
*VersionControlObserver::model() const
90 void VersionControlObserver::setView(DolphinView
*view
)
93 disconnect(m_view
, &DolphinView::activated
, this, &VersionControlObserver::delayedDirectoryVerification
);
99 connect(m_view
, &DolphinView::activated
, this, &VersionControlObserver::delayedDirectoryVerification
);
103 DolphinView
*VersionControlObserver::view() const
108 QList
<QAction
*> VersionControlObserver::actions(const KFileItemList
&items
) const
110 bool hasNullItems
= false;
111 for (const KFileItem
&item
: items
) {
113 qCWarning(DolphinDebug
) << "Requesting version-control-actions for empty items";
119 if (!m_model
|| hasNullItems
) {
123 if (isVersionControlled()) {
124 return m_currentPlugin
->versionControlActions(items
);
126 QList
<QAction
*> actions
;
127 for (const KVersionControlPlugin
*plugin
: std::as_const(m_plugins
)) {
128 actions
<< plugin
->outOfVersionControlActions(items
);
134 void VersionControlObserver::delayedDirectoryVerification()
136 if (!isVersionControlled()) {
137 m_dirVerificationTimer
->stop();
141 m_silentUpdate
= false;
142 m_dirVerificationTimer
->start();
145 void VersionControlObserver::silentDirectoryVerification()
147 if (!isVersionControlled()) {
148 m_dirVerificationTimer
->stop();
152 m_silentUpdate
= true;
153 m_dirVerificationTimer
->start();
156 void VersionControlObserver::slotItemsChanged(const KItemRangeList
&itemRanges
, const QSet
<QByteArray
> &roles
)
160 // Because "version" role is emitted by VCS plugin (ourselves) we don't need to
161 // analyze it and update directory item states information. So lets check if
162 // there is only "version".
163 if (!(roles
.count() == 1 && roles
.contains("version"))) {
164 delayedDirectoryVerification();
168 void VersionControlObserver::verifyDirectory()
174 const KFileItem rootItem
= m_model
->rootItem();
175 if (rootItem
.isNull() || !rootItem
.url().isLocalFile()) {
179 if (m_currentPlugin
&& rootItem
.url().path().startsWith(m_localRepoRoot
) && QFile::exists(m_localRepoRoot
+ '/' + m_currentPlugin
->fileName())) {
180 // current directory is still versionned
185 if ((m_currentPlugin
= searchPlugin(rootItem
.url()))) {
186 // The directory is versioned. Assume that the user will further browse through
187 // versioned directories and decrease the verification timer.
188 m_dirVerificationTimer
->setInterval(100);
193 // The directory is not versioned. Reset the verification timer to a higher
194 // value, so that browsing through non-versioned directories is not slown down
195 // by an immediate verification.
196 m_dirVerificationTimer
->setInterval(500);
199 void VersionControlObserver::slotThreadFinished()
201 UpdateItemStatesThread
*thread
= m_updateItemStatesThread
;
202 m_updateItemStatesThread
= nullptr; // The thread deletes itself automatically (see updateItemStates())
204 if (!m_currentPlugin
|| !thread
) {
208 const QMap
<QString
, QVector
<ItemState
>> &itemStates
= thread
->itemStates();
209 QMap
<QString
, QVector
<ItemState
>>::const_iterator it
= itemStates
.constBegin();
210 for (; it
!= itemStates
.constEnd(); ++it
) {
211 const QVector
<ItemState
> &items
= it
.value();
213 for (const ItemState
&item
: items
) {
214 const KFileItem
&fileItem
= item
.first
;
215 const KVersionControlPlugin::ItemVersion version
= item
.second
;
216 QHash
<QByteArray
, QVariant
> values
;
217 values
.insert("version", QVariant(version
));
218 m_model
->setData(m_model
->index(fileItem
), values
);
222 if (!m_silentUpdate
) {
223 // Using an empty message results in clearing the previously shown information message and showing
224 // the default status bar information. This is useful as the user already gets feedback that the
225 // operation has been completed because of the icon emblems.
226 Q_EMIT
operationCompletedMessage(QString());
229 if (m_pendingItemStatesUpdate
) {
230 m_pendingItemStatesUpdate
= false;
235 void VersionControlObserver::updateItemStates()
237 Q_ASSERT(m_currentPlugin
);
238 if (m_updateItemStatesThread
) {
239 // An update is currently ongoing. Wait until the thread has finished
240 // the update (see slotThreadFinished()).
241 m_pendingItemStatesUpdate
= true;
245 QMap
<QString
, QVector
<ItemState
>> itemStates
;
246 createItemStatesList(itemStates
);
248 if (!itemStates
.isEmpty()) {
249 if (!m_silentUpdate
) {
250 Q_EMIT
infoMessage(i18nc("@info:status", "Updating version information…"));
252 m_updateItemStatesThread
= new UpdateItemStatesThread(m_currentPlugin
, itemStates
);
253 connect(m_updateItemStatesThread
, &UpdateItemStatesThread::finished
, this, &VersionControlObserver::slotThreadFinished
);
254 connect(m_updateItemStatesThread
, &UpdateItemStatesThread::finished
, m_updateItemStatesThread
, &UpdateItemStatesThread::deleteLater
);
256 m_updateItemStatesThread
->start(); // slotThreadFinished() is called when finished
260 int VersionControlObserver::createItemStatesList(QMap
<QString
, QVector
<ItemState
>> &itemStates
, const int firstIndex
)
262 const int itemCount
= m_model
->count();
263 const int currentExpansionLevel
= m_model
->expandedParentsCount(firstIndex
);
265 QVector
<ItemState
> items
;
266 items
.reserve(itemCount
- firstIndex
);
269 for (index
= firstIndex
; index
< itemCount
; ++index
) {
270 const int expansionLevel
= m_model
->expandedParentsCount(index
);
272 if (expansionLevel
== currentExpansionLevel
) {
274 itemState
.first
= m_model
->fileItem(index
);
275 itemState
.second
= KVersionControlPlugin::UnversionedVersion
;
277 items
.append(itemState
);
278 } else if (expansionLevel
> currentExpansionLevel
) {
280 index
+= createItemStatesList(itemStates
, index
) - 1;
286 if (!items
.isEmpty()) {
287 const QUrl
&url
= items
.first().first
.url();
288 itemStates
.insert(url
.adjusted(QUrl::RemoveFilename
).path(), items
);
291 return index
- firstIndex
; // number of processed items
294 void VersionControlObserver::initPlugins()
296 if (!m_pluginsInitialized
) {
297 // No searching for plugins has been done yet. Query all fileview version control
298 // plugins and remember them in 'plugins'.
299 const QStringList enabledPlugins
= VersionControlSettings::enabledPlugins();
301 const QVector
<KPluginMetaData
> plugins
= KPluginMetaData::findPlugins(QStringLiteral("dolphin/vcs"));
303 for (const auto &p
: plugins
) {
304 if (enabledPlugins
.contains(p
.name())) {
305 auto plugin
= KPluginFactory::instantiatePlugin
<KVersionControlPlugin
>(p
, parent()).plugin
;
307 m_plugins
.append(plugin
);
312 for (const auto *plugin
: std::as_const(m_plugins
)) {
313 connect(plugin
, &KVersionControlPlugin::itemVersionsChanged
, this, &VersionControlObserver::silentDirectoryVerification
);
314 connect(plugin
, &KVersionControlPlugin::infoMessage
, this, &VersionControlObserver::infoMessage
);
315 connect(plugin
, &KVersionControlPlugin::errorMessage
, this, &VersionControlObserver::errorMessage
);
316 connect(plugin
, &KVersionControlPlugin::operationCompletedMessage
, this, &VersionControlObserver::operationCompletedMessage
);
319 m_pluginsInitialized
= true;
323 KVersionControlPlugin
*VersionControlObserver::searchPlugin(const QUrl
&directory
)
327 // Verify whether the current directory is under a version system
328 for (KVersionControlPlugin
*plugin
: std::as_const(m_plugins
)) {
329 // first naively check if we are at working copy root
330 const QString fileName
= directory
.path() + '/' + plugin
->fileName();
331 if (QFile::exists(fileName
)) {
332 m_localRepoRoot
= directory
.path();
336 const QString root
= plugin
->localRepositoryRoot(directory
.path());
337 if (!root
.isEmpty()) {
338 m_localRepoRoot
= root
;
345 bool VersionControlObserver::isVersionControlled() const
347 return m_currentPlugin
!= nullptr;
350 #include "moc_versioncontrolobserver.cpp"