]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/versioncontrol/versioncontrolobserver.cpp
Add clang-format and format code as in Frameworks
[dolphin.git] / src / views / versioncontrol / versioncontrolobserver.cpp
1 /*
2 * SPDX-FileCopyrightText: 2009 Peter Penz <peter.penz19@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "versioncontrolobserver.h"
8
9 #include "dolphin_versioncontrolsettings.h"
10 #include "dolphindebug.h"
11 #include "kitemviews/kfileitemmodel.h"
12 #include "updateitemstatesthread.h"
13 #include "views/dolphinview.h"
14
15 #include <KLocalizedString>
16 #include <KPluginFactory>
17 #include <KPluginMetaData>
18
19 #include <QTimer>
20
21 VersionControlObserver::VersionControlObserver(QObject *parent)
22 : QObject(parent)
23 , m_pendingItemStatesUpdate(false)
24 , m_silentUpdate(false)
25 , m_view(nullptr)
26 , m_model(nullptr)
27 , m_dirVerificationTimer(nullptr)
28 , m_pluginsInitialized(false)
29 , m_plugin(nullptr)
30 , m_updateItemStatesThread(nullptr)
31 {
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);
41 }
42
43 VersionControlObserver::~VersionControlObserver()
44 {
45 if (m_plugin) {
46 m_plugin->disconnect(this);
47 m_plugin = nullptr;
48 }
49 }
50
51 void VersionControlObserver::setModel(KFileItemModel *model)
52 {
53 if (m_model) {
54 disconnect(m_model, &KFileItemModel::itemsInserted, this, &VersionControlObserver::delayedDirectoryVerification);
55 disconnect(m_model, &KFileItemModel::itemsChanged, this, &VersionControlObserver::slotItemsChanged);
56 }
57
58 m_model = model;
59
60 if (model) {
61 connect(m_model, &KFileItemModel::itemsInserted, this, &VersionControlObserver::delayedDirectoryVerification);
62 connect(m_model, &KFileItemModel::itemsChanged, this, &VersionControlObserver::slotItemsChanged);
63 }
64 }
65
66 KFileItemModel *VersionControlObserver::model() const
67 {
68 return m_model;
69 }
70
71 void VersionControlObserver::setView(DolphinView *view)
72 {
73 if (m_view) {
74 disconnect(m_view, &DolphinView::activated, this, &VersionControlObserver::delayedDirectoryVerification);
75 }
76
77 m_view = view;
78
79 if (m_view) {
80 connect(m_view, &DolphinView::activated, this, &VersionControlObserver::delayedDirectoryVerification);
81 }
82 }
83
84 DolphinView *VersionControlObserver::view() const
85 {
86 return m_view;
87 }
88
89 QList<QAction *> VersionControlObserver::actions(const KFileItemList &items) const
90 {
91 bool hasNullItems = false;
92 for (const KFileItem &item : items) {
93 if (item.isNull()) {
94 qCWarning(DolphinDebug) << "Requesting version-control-actions for empty items";
95 hasNullItems = true;
96 break;
97 }
98 }
99
100 if (!m_model || hasNullItems) {
101 return {};
102 }
103
104 if (isVersionControlled()) {
105 return m_plugin->versionControlActions(items);
106 } else {
107 QList<QAction *> actions;
108 for (const QPointer<KVersionControlPlugin> &plugin : qAsConst(m_plugins)) {
109 actions << plugin->outOfVersionControlActions(items);
110 }
111 return actions;
112 }
113 }
114
115 void VersionControlObserver::delayedDirectoryVerification()
116 {
117 m_silentUpdate = false;
118 m_dirVerificationTimer->start();
119 }
120
121 void VersionControlObserver::silentDirectoryVerification()
122 {
123 m_silentUpdate = true;
124 m_dirVerificationTimer->start();
125 }
126
127 void VersionControlObserver::slotItemsChanged(const KItemRangeList &itemRanges, const QSet<QByteArray> &roles)
128 {
129 Q_UNUSED(itemRanges)
130
131 // Because "version" role is emitted by VCS plugin (ourselves) we don't need to
132 // analyze it and update directory item states information. So lets check if
133 // there is only "version".
134 if (!(roles.count() == 1 && roles.contains("version"))) {
135 delayedDirectoryVerification();
136 }
137 }
138
139 void VersionControlObserver::verifyDirectory()
140 {
141 if (!m_model) {
142 return;
143 }
144
145 const KFileItem rootItem = m_model->rootItem();
146 if (rootItem.isNull() || !rootItem.url().isLocalFile()) {
147 return;
148 }
149
150 if (m_plugin != nullptr) {
151 if (!rootItem.url().path().startsWith(m_localRepoRoot) || !QFile::exists(m_localRepoRoot + '/' + m_plugin->fileName())) {
152 m_plugin = nullptr;
153
154 // The directory is not versioned. Reset the verification timer to a higher
155 // value, so that browsing through non-versioned directories is not slown down
156 // by an immediate verification.
157 m_dirVerificationTimer->setInterval(500);
158 } else {
159 // View was versioned but should not be anymore
160 updateItemStates();
161 }
162 } else if ((m_plugin = searchPlugin(rootItem.url()))) {
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);
166 updateItemStates();
167 }
168 }
169
170 void VersionControlObserver::slotThreadFinished()
171 {
172 UpdateItemStatesThread *thread = m_updateItemStatesThread;
173 m_updateItemStatesThread = nullptr; // The thread deletes itself automatically (see updateItemStates())
174
175 if (!m_plugin || !thread) {
176 return;
177 }
178
179 const QMap<QString, QVector<ItemState>> &itemStates = thread->itemStates();
180 QMap<QString, QVector<ItemState>>::const_iterator it = itemStates.constBegin();
181 for (; it != itemStates.constEnd(); ++it) {
182 const QVector<ItemState> &items = it.value();
183
184 for (const ItemState &item : items) {
185 const KFileItem &fileItem = item.first;
186 const KVersionControlPlugin::ItemVersion version = item.second;
187 QHash<QByteArray, QVariant> values;
188 values.insert("version", QVariant(version));
189 m_model->setData(m_model->index(fileItem), values);
190 }
191 }
192
193 if (!m_silentUpdate) {
194 // Using an empty message results in clearing the previously shown information message and showing
195 // the default status bar information. This is useful as the user already gets feedback that the
196 // operation has been completed because of the icon emblems.
197 Q_EMIT operationCompletedMessage(QString());
198 }
199
200 if (m_pendingItemStatesUpdate) {
201 m_pendingItemStatesUpdate = false;
202 updateItemStates();
203 }
204 }
205
206 void VersionControlObserver::updateItemStates()
207 {
208 Q_ASSERT(m_plugin);
209 if (m_updateItemStatesThread) {
210 // An update is currently ongoing. Wait until the thread has finished
211 // the update (see slotThreadFinished()).
212 m_pendingItemStatesUpdate = true;
213 return;
214 }
215
216 QMap<QString, QVector<ItemState>> itemStates;
217 createItemStatesList(itemStates);
218
219 if (!itemStates.isEmpty()) {
220 if (!m_silentUpdate) {
221 Q_EMIT infoMessage(i18nc("@info:status", "Updating version information..."));
222 }
223 m_updateItemStatesThread = new UpdateItemStatesThread(m_plugin, itemStates);
224 connect(m_updateItemStatesThread, &UpdateItemStatesThread::finished, this, &VersionControlObserver::slotThreadFinished);
225 connect(m_updateItemStatesThread, &UpdateItemStatesThread::finished, m_updateItemStatesThread, &UpdateItemStatesThread::deleteLater);
226
227 m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished
228 }
229 }
230
231 int VersionControlObserver::createItemStatesList(QMap<QString, QVector<ItemState>> &itemStates, const int firstIndex)
232 {
233 const int itemCount = m_model->count();
234 const int currentExpansionLevel = m_model->expandedParentsCount(firstIndex);
235
236 QVector<ItemState> items;
237 items.reserve(itemCount - firstIndex);
238
239 int index;
240 for (index = firstIndex; index < itemCount; ++index) {
241 const int expansionLevel = m_model->expandedParentsCount(index);
242
243 if (expansionLevel == currentExpansionLevel) {
244 ItemState itemState;
245 itemState.first = m_model->fileItem(index);
246 itemState.second = KVersionControlPlugin::UnversionedVersion;
247
248 items.append(itemState);
249 } else if (expansionLevel > currentExpansionLevel) {
250 // Sub folder
251 index += createItemStatesList(itemStates, index) - 1;
252 } else {
253 break;
254 }
255 }
256
257 if (!items.isEmpty()) {
258 const QUrl &url = items.first().first.url();
259 itemStates.insert(url.adjusted(QUrl::RemoveFilename).path(), items);
260 }
261
262 return index - firstIndex; // number of processed items
263 }
264
265 void VersionControlObserver::initPlugins()
266 {
267 if (!m_pluginsInitialized) {
268 // No searching for plugins has been done yet. Query all fileview version control
269 // plugins and remember them in 'plugins'.
270 const QStringList enabledPlugins = VersionControlSettings::enabledPlugins();
271
272 const QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("dolphin/vcs"));
273
274 QSet<QString> loadedPlugins;
275
276 for (const auto &p : plugins) {
277 if (enabledPlugins.contains(p.name())) {
278 auto plugin = KPluginFactory::instantiatePlugin<KVersionControlPlugin>(p, parent()).plugin;
279 if (plugin) {
280 m_plugins.append(plugin);
281 loadedPlugins += p.name();
282 }
283 }
284 }
285
286 for (auto &plugin : qAsConst(m_plugins)) {
287 connect(plugin, &KVersionControlPlugin::itemVersionsChanged, this, &VersionControlObserver::silentDirectoryVerification);
288 connect(plugin, &KVersionControlPlugin::infoMessage, this, &VersionControlObserver::infoMessage);
289 connect(plugin, &KVersionControlPlugin::errorMessage, this, &VersionControlObserver::errorMessage);
290 connect(plugin, &KVersionControlPlugin::operationCompletedMessage, this, &VersionControlObserver::operationCompletedMessage);
291 }
292
293 m_pluginsInitialized = true;
294 }
295 }
296
297 KVersionControlPlugin *VersionControlObserver::searchPlugin(const QUrl &directory)
298 {
299 initPlugins();
300
301 // Verify whether the current directory is under a version system
302 for (const QPointer<KVersionControlPlugin> &plugin : qAsConst(m_plugins)) {
303 if (!plugin) {
304 continue;
305 }
306
307 // first naively check if we are at working copy root
308 const QString fileName = directory.path() + '/' + plugin->fileName();
309 if (QFile::exists(fileName)) {
310 m_localRepoRoot = directory.path();
311 return plugin;
312 }
313 const QString root = plugin->localRepositoryRoot(directory.path());
314 if (!root.isEmpty()) {
315 m_localRepoRoot = root;
316 return plugin;
317 }
318 }
319 return nullptr;
320 }
321
322 bool VersionControlObserver::isVersionControlled() const
323 {
324 return m_plugin != nullptr;
325 }