]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/versioncontrol/versioncontrolobserver.cpp
GIT_SILENT Sync po/docbooks with svn
[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_currentPlugin(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_currentPlugin) {
46 m_currentPlugin->disconnect(this);
47 }
48 if (m_updateItemStatesThread) {
49 m_updateItemStatesThread->requestInterruption();
50 m_updateItemStatesThread->wait();
51 m_updateItemStatesThread->deleteLater();
52 }
53
54 if (m_currentPlugin) {
55 delete m_currentPlugin;
56 m_currentPlugin = nullptr;
57 }
58 m_plugins.clear();
59 }
60
61 void VersionControlObserver::setModel(KFileItemModel *model)
62 {
63 if (m_model) {
64 if (m_currentPlugin) {
65 delete m_currentPlugin;
66 m_currentPlugin = nullptr;
67 }
68 if (m_updateItemStatesThread) {
69 m_updateItemStatesThread->requestInterruption();
70 }
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);
74 }
75
76 m_model = model;
77
78 if (model) {
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);
82 }
83 }
84
85 KFileItemModel *VersionControlObserver::model() const
86 {
87 return m_model;
88 }
89
90 void VersionControlObserver::setView(DolphinView *view)
91 {
92 if (m_view) {
93 disconnect(m_view, &DolphinView::activated, this, &VersionControlObserver::delayedDirectoryVerification);
94 }
95
96 m_view = view;
97
98 if (m_view) {
99 connect(m_view, &DolphinView::activated, this, &VersionControlObserver::delayedDirectoryVerification);
100 }
101 }
102
103 DolphinView *VersionControlObserver::view() const
104 {
105 return m_view;
106 }
107
108 QList<QAction *> VersionControlObserver::actions(const KFileItemList &items) const
109 {
110 bool hasNullItems = false;
111 for (const KFileItem &item : items) {
112 if (item.isNull()) {
113 qCWarning(DolphinDebug) << "Requesting version-control-actions for empty items";
114 hasNullItems = true;
115 break;
116 }
117 }
118
119 if (!m_model || hasNullItems) {
120 return {};
121 }
122
123 if (isVersionControlled()) {
124 return m_currentPlugin->versionControlActions(items);
125 } else {
126 QList<QAction *> actions;
127 for (const KVersionControlPlugin *plugin : std::as_const(m_plugins)) {
128 actions << plugin->outOfVersionControlActions(items);
129 }
130 return actions;
131 }
132 }
133
134 void VersionControlObserver::delayedDirectoryVerification()
135 {
136 if (!isVersionControlled()) {
137 m_dirVerificationTimer->stop();
138 return;
139 }
140
141 m_silentUpdate = false;
142 m_dirVerificationTimer->start();
143 }
144
145 void VersionControlObserver::silentDirectoryVerification()
146 {
147 if (!isVersionControlled()) {
148 m_dirVerificationTimer->stop();
149 return;
150 }
151
152 m_silentUpdate = true;
153 m_dirVerificationTimer->start();
154 }
155
156 void VersionControlObserver::slotItemsChanged(const KItemRangeList &itemRanges, const QSet<QByteArray> &roles)
157 {
158 Q_UNUSED(itemRanges)
159
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();
165 }
166 }
167
168 void VersionControlObserver::verifyDirectory()
169 {
170 if (!m_model) {
171 return;
172 }
173
174 const KFileItem rootItem = m_model->rootItem();
175 if (rootItem.isNull() || !rootItem.url().isLocalFile()) {
176 return;
177 }
178
179 if (m_currentPlugin && rootItem.url().path().startsWith(m_localRepoRoot) && QFile::exists(m_localRepoRoot + '/' + m_currentPlugin->fileName())) {
180 // current directory is still versionned
181 updateItemStates();
182 return;
183 }
184
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);
189 updateItemStates();
190 return;
191 }
192
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);
197 }
198
199 void VersionControlObserver::slotThreadFinished()
200 {
201 UpdateItemStatesThread *thread = m_updateItemStatesThread;
202 m_updateItemStatesThread = nullptr; // The thread deletes itself automatically (see updateItemStates())
203
204 if (!m_currentPlugin || !thread) {
205 return;
206 }
207
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();
212
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);
219 }
220 }
221
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());
227 }
228
229 if (m_pendingItemStatesUpdate) {
230 m_pendingItemStatesUpdate = false;
231 updateItemStates();
232 }
233 }
234
235 void VersionControlObserver::updateItemStates()
236 {
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;
242 return;
243 }
244
245 QMap<QString, QVector<ItemState>> itemStates;
246 createItemStatesList(itemStates);
247
248 if (!itemStates.isEmpty()) {
249 if (!m_silentUpdate) {
250 Q_EMIT infoMessage(i18nc("@info:status", "Updating version information…"));
251 }
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);
255
256 m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished
257 }
258 }
259
260 int VersionControlObserver::createItemStatesList(QMap<QString, QVector<ItemState>> &itemStates, const int firstIndex)
261 {
262 const int itemCount = m_model->count();
263 const int currentExpansionLevel = m_model->expandedParentsCount(firstIndex);
264
265 QVector<ItemState> items;
266 items.reserve(itemCount - firstIndex);
267
268 int index;
269 for (index = firstIndex; index < itemCount; ++index) {
270 const int expansionLevel = m_model->expandedParentsCount(index);
271
272 if (expansionLevel == currentExpansionLevel) {
273 ItemState itemState;
274 itemState.first = m_model->fileItem(index);
275 itemState.second = KVersionControlPlugin::UnversionedVersion;
276
277 items.append(itemState);
278 } else if (expansionLevel > currentExpansionLevel) {
279 // Sub folder
280 index += createItemStatesList(itemStates, index) - 1;
281 } else {
282 break;
283 }
284 }
285
286 if (!items.isEmpty()) {
287 const QUrl &url = items.first().first.url();
288 itemStates.insert(url.adjusted(QUrl::RemoveFilename).path(), items);
289 }
290
291 return index - firstIndex; // number of processed items
292 }
293
294 void VersionControlObserver::initPlugins()
295 {
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();
300
301 const QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("dolphin/vcs"));
302
303 for (const auto &p : plugins) {
304 if (enabledPlugins.contains(p.name())) {
305 auto plugin = KPluginFactory::instantiatePlugin<KVersionControlPlugin>(p, parent()).plugin;
306 if (plugin) {
307 m_plugins.append(plugin);
308 }
309 }
310 }
311
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);
317 }
318
319 m_pluginsInitialized = true;
320 }
321 }
322
323 KVersionControlPlugin *VersionControlObserver::searchPlugin(const QUrl &directory)
324 {
325 initPlugins();
326
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();
333 return plugin;
334 }
335
336 const QString root = plugin->localRepositoryRoot(directory.path());
337 if (!root.isEmpty()) {
338 m_localRepoRoot = root;
339 return plugin;
340 }
341 }
342 return nullptr;
343 }
344
345 bool VersionControlObserver::isVersionControlled() const
346 {
347 return m_currentPlugin != nullptr;
348 }
349
350 #include "moc_versioncontrolobserver.cpp"