]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/versioncontrol/versioncontrolobserver.cpp
a773aef6b397be9e4e76f5a1075a6e585e12abc0
[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 "views/dolphinview.h"
12 #include "kitemviews/kfileitemmodel.h"
13 #include "updateitemstatesthread.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,
41 this, &VersionControlObserver::verifyDirectory);
42 }
43
44 VersionControlObserver::~VersionControlObserver()
45 {
46 if (m_plugin) {
47 m_plugin->disconnect(this);
48 m_plugin = nullptr;
49 }
50 }
51
52 void VersionControlObserver::setModel(KFileItemModel* model)
53 {
54 if (m_model) {
55 disconnect(m_model, &KFileItemModel::itemsInserted,
56 this, &VersionControlObserver::delayedDirectoryVerification);
57 disconnect(m_model, &KFileItemModel::itemsChanged,
58 this, &VersionControlObserver::slotItemsChanged);
59 }
60
61 m_model = model;
62
63 if (model) {
64 connect(m_model, &KFileItemModel::itemsInserted,
65 this, &VersionControlObserver::delayedDirectoryVerification);
66 connect(m_model, &KFileItemModel::itemsChanged,
67 this, &VersionControlObserver::slotItemsChanged);
68 }
69 }
70
71 KFileItemModel* VersionControlObserver::model() const
72 {
73 return m_model;
74 }
75
76 void VersionControlObserver::setView(DolphinView* view)
77 {
78 if (m_view) {
79 disconnect(m_view, &DolphinView::activated,
80 this, &VersionControlObserver::delayedDirectoryVerification);
81 }
82
83 m_view = view;
84
85 if (m_view) {
86 connect(m_view, &DolphinView::activated,
87 this, &VersionControlObserver::delayedDirectoryVerification);
88 }
89 }
90
91 DolphinView* VersionControlObserver::view() const
92 {
93 return m_view;
94 }
95
96 QList<QAction*> VersionControlObserver::actions(const KFileItemList& items) const
97 {
98 bool hasNullItems = false;
99 for (const KFileItem& item : items) {
100 if (item.isNull()) {
101 qCWarning(DolphinDebug) << "Requesting version-control-actions for empty items";
102 hasNullItems = true;
103 break;
104 }
105 }
106
107 if (!m_model || hasNullItems) {
108 return {};
109 }
110
111 if (isVersionControlled()) {
112 return m_plugin->versionControlActions(items);
113 } else {
114 QList<QAction*> actions;
115 for (const QPointer<KVersionControlPlugin> &plugin : qAsConst(m_plugins)) {
116 actions << plugin->outOfVersionControlActions(items);
117 }
118 return actions;
119 }
120 }
121
122 void VersionControlObserver::delayedDirectoryVerification()
123 {
124 m_silentUpdate = false;
125 m_dirVerificationTimer->start();
126 }
127
128 void VersionControlObserver::silentDirectoryVerification()
129 {
130 m_silentUpdate = true;
131 m_dirVerificationTimer->start();
132 }
133
134 void VersionControlObserver::slotItemsChanged(const KItemRangeList& itemRanges, const QSet<QByteArray>& roles)
135 {
136 Q_UNUSED(itemRanges)
137
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();
143 }
144 }
145
146 void VersionControlObserver::verifyDirectory()
147 {
148 if (!m_model) {
149 return;
150 }
151
152 const KFileItem rootItem = m_model->rootItem();
153 if (rootItem.isNull() || !rootItem.url().isLocalFile()) {
154 return;
155 }
156
157 if (m_plugin != nullptr) {
158 if (!rootItem.url().path().startsWith(m_localRepoRoot) || !QFile::exists(m_localRepoRoot + '/' + m_plugin->fileName())) {
159 m_plugin = nullptr;
160
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);
165 } else {
166 // View was versioned but should not be anymore
167 updateItemStates();
168 }
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);
173 updateItemStates();
174 }
175 }
176
177 void VersionControlObserver::slotThreadFinished()
178 {
179 UpdateItemStatesThread* thread = m_updateItemStatesThread;
180 m_updateItemStatesThread = nullptr; // The thread deletes itself automatically (see updateItemStates())
181
182 if (!m_plugin || !thread) {
183 return;
184 }
185
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();
190
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);
197 }
198 }
199
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());
205 }
206
207 if (m_pendingItemStatesUpdate) {
208 m_pendingItemStatesUpdate = false;
209 updateItemStates();
210 }
211 }
212
213 void VersionControlObserver::updateItemStates()
214 {
215 Q_ASSERT(m_plugin);
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;
220 return;
221 }
222
223 QMap<QString, QVector<ItemState> > itemStates;
224 createItemStatesList(itemStates);
225
226 if (!itemStates.isEmpty()) {
227 if (!m_silentUpdate) {
228 Q_EMIT infoMessage(i18nc("@info:status", "Updating version information..."));
229 }
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);
235
236 m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished
237 }
238 }
239
240 int VersionControlObserver::createItemStatesList(QMap<QString, QVector<ItemState> >& itemStates,
241 const int firstIndex)
242 {
243 const int itemCount = m_model->count();
244 const int currentExpansionLevel = m_model->expandedParentsCount(firstIndex);
245
246 QVector<ItemState> items;
247 items.reserve(itemCount - firstIndex);
248
249 int index;
250 for (index = firstIndex; index < itemCount; ++index) {
251 const int expansionLevel = m_model->expandedParentsCount(index);
252
253 if (expansionLevel == currentExpansionLevel) {
254 ItemState itemState;
255 itemState.first = m_model->fileItem(index);
256 itemState.second = KVersionControlPlugin::UnversionedVersion;
257
258 items.append(itemState);
259 } else if (expansionLevel > currentExpansionLevel) {
260 // Sub folder
261 index += createItemStatesList(itemStates, index) - 1;
262 } else {
263 break;
264 }
265 }
266
267 if (!items.isEmpty()) {
268 const QUrl& url = items.first().first.url();
269 itemStates.insert(url.adjusted(QUrl::RemoveFilename).path(), items);
270 }
271
272 return index - firstIndex; // number of processed items
273 }
274
275 void VersionControlObserver::initPlugins()
276 {
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();
281
282 const QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("dolphin/vcs"));
283
284 QSet<QString> loadedPlugins;
285
286 for (const auto &p : plugins) {
287 if (enabledPlugins.contains(p.name())) {
288 auto plugin = KPluginFactory::instantiatePlugin<KVersionControlPlugin>(p).plugin;
289 if (plugin) {
290 m_plugins.append(plugin);
291 loadedPlugins += p.name();
292 }
293 }
294 }
295
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);
305 }
306
307 m_pluginsInitialized = true;
308 }
309 }
310
311 KVersionControlPlugin* VersionControlObserver::searchPlugin(const QUrl& directory)
312 {
313 initPlugins();
314
315 // Verify whether the current directory is under a version system
316 for (const QPointer<KVersionControlPlugin> &plugin : qAsConst(m_plugins)) {
317 if (!plugin) {
318 continue;
319 }
320
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();
325 return plugin;
326 }
327 const QString root = plugin->localRepositoryRoot(directory.path());
328 if (!root.isEmpty()) {
329 m_localRepoRoot = root;
330 return plugin;
331 }
332 }
333 return nullptr;
334 }
335
336 bool VersionControlObserver::isVersionControlled() const
337 {
338 return m_plugin != nullptr;
339 }
340