]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/versioncontrol/versioncontrolobserver.cpp
2d801686e259099a249a301b613f49109d41ca03
[dolphin.git] / src / views / versioncontrol / versioncontrolobserver.cpp
1 /***************************************************************************
2 * Copyright (C) 2009 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "versioncontrolobserver.h"
21
22 #include "dolphin_versioncontrolsettings.h"
23 #include "dolphindebug.h"
24 #include "views/dolphinview.h"
25 #include "kitemviews/kfileitemmodel.h"
26 #include "updateitemstatesthread.h"
27
28 #include <KLocalizedString>
29 #include <KService>
30 #include <KServiceTypeTrader>
31
32 #include <QTimer>
33
34 VersionControlObserver::VersionControlObserver(QObject* parent) :
35 QObject(parent),
36 m_pendingItemStatesUpdate(false),
37 m_versionedDirectory(false),
38 m_silentUpdate(false),
39 m_view(nullptr),
40 m_model(nullptr),
41 m_dirVerificationTimer(nullptr),
42 m_pluginsInitialized(false),
43 m_plugin(nullptr),
44 m_updateItemStatesThread(nullptr)
45 {
46 // The verification timer specifies the timeout until the shown directory
47 // is checked whether it is versioned. Per default it is assumed that users
48 // don't iterate through versioned directories and a high timeout is used
49 // The timeout will be decreased as soon as a versioned directory has been
50 // found (see verifyDirectory()).
51 m_dirVerificationTimer = new QTimer(this);
52 m_dirVerificationTimer->setSingleShot(true);
53 m_dirVerificationTimer->setInterval(500);
54 connect(m_dirVerificationTimer, &QTimer::timeout,
55 this, &VersionControlObserver::verifyDirectory);
56 }
57
58 VersionControlObserver::~VersionControlObserver()
59 {
60 if (m_plugin) {
61 m_plugin->disconnect(this);
62 m_plugin = nullptr;
63 }
64 }
65
66 void VersionControlObserver::setModel(KFileItemModel* model)
67 {
68 if (m_model) {
69 disconnect(m_model, &KFileItemModel::itemsInserted,
70 this, &VersionControlObserver::delayedDirectoryVerification);
71 disconnect(m_model, &KFileItemModel::itemsChanged,
72 this, &VersionControlObserver::slotItemsChanged);
73 }
74
75 m_model = model;
76
77 if (model) {
78 connect(m_model, &KFileItemModel::itemsInserted,
79 this, &VersionControlObserver::delayedDirectoryVerification);
80 connect(m_model, &KFileItemModel::itemsChanged,
81 this, &VersionControlObserver::slotItemsChanged);
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,
94 this, &VersionControlObserver::delayedDirectoryVerification);
95 }
96
97 m_view = view;
98
99 if (m_view) {
100 connect(m_view, &DolphinView::activated,
101 this, &VersionControlObserver::delayedDirectoryVerification);
102 }
103 }
104
105 DolphinView* VersionControlObserver::view() const
106 {
107 return m_view;
108 }
109
110 QList<QAction*> VersionControlObserver::actions(const KFileItemList& items) const
111 {
112 bool hasNullItems = false;
113 foreach (const KFileItem& item, items) {
114 if (item.isNull()) {
115 qCWarning(DolphinDebug) << "Requesting version-control-actions for empty items";
116 hasNullItems = true;
117 break;
118 }
119 }
120
121 if (!m_model || hasNullItems || !isVersioned()) {
122 return {};
123 }
124
125 return m_plugin->actions(items);
126 }
127
128 void VersionControlObserver::delayedDirectoryVerification()
129 {
130 m_silentUpdate = false;
131 m_dirVerificationTimer->start();
132 }
133
134 void VersionControlObserver::silentDirectoryVerification()
135 {
136 m_silentUpdate = true;
137 m_dirVerificationTimer->start();
138 }
139
140 void VersionControlObserver::slotItemsChanged(const KItemRangeList& itemRanges, const QSet<QByteArray>& roles)
141 {
142 Q_UNUSED(itemRanges)
143
144 // Because "version" role is emitted by VCS plugin (ourselfs) we don't need to
145 // analyze it and update directory item states information. So lets check if
146 // there is only "version".
147 if ( !(roles.count() == 1 && roles.contains("version")) ) {
148 delayedDirectoryVerification();
149 }
150 }
151
152 void VersionControlObserver::verifyDirectory()
153 {
154 if (!m_model) {
155 return;
156 }
157
158 const KFileItem rootItem = m_model->rootItem();
159 if (rootItem.isNull() || !rootItem.url().isLocalFile()) {
160 return;
161 }
162
163 if (m_plugin) {
164 m_plugin->disconnect(this);
165 }
166
167 m_plugin = searchPlugin(rootItem.url());
168 if (m_plugin) {
169 connect(m_plugin, &KVersionControlPlugin::itemVersionsChanged,
170 this, &VersionControlObserver::silentDirectoryVerification);
171 connect(m_plugin, &KVersionControlPlugin::infoMessage,
172 this, &VersionControlObserver::infoMessage);
173 connect(m_plugin, &KVersionControlPlugin::errorMessage,
174 this, &VersionControlObserver::errorMessage);
175 connect(m_plugin, &KVersionControlPlugin::operationCompletedMessage,
176 this, &VersionControlObserver::operationCompletedMessage);
177
178 if (!m_versionedDirectory) {
179 m_versionedDirectory = true;
180
181 // The directory is versioned. Assume that the user will further browse through
182 // versioned directories and decrease the verification timer.
183 m_dirVerificationTimer->setInterval(100);
184 }
185 updateItemStates();
186 } else if (m_versionedDirectory) {
187 m_versionedDirectory = false;
188
189 // The directory is not versioned. Reset the verification timer to a higher
190 // value, so that browsing through non-versioned directories is not slown down
191 // by an immediate verification.
192 m_dirVerificationTimer->setInterval(500);
193 }
194 }
195
196 void VersionControlObserver::slotThreadFinished()
197 {
198 UpdateItemStatesThread* thread = m_updateItemStatesThread;
199 m_updateItemStatesThread = nullptr; // The thread deletes itself automatically (see updateItemStates())
200
201 if (!m_plugin || !thread) {
202 return;
203 }
204
205 const QMap<QString, QVector<ItemState> >& itemStates = thread->itemStates();
206 QMap<QString, QVector<ItemState> >::const_iterator it = itemStates.constBegin();
207 for (; it != itemStates.constEnd(); ++it) {
208 const QVector<ItemState>& items = it.value();
209
210 foreach (const ItemState& item, items) {
211 const KFileItem& fileItem = item.first;
212 const KVersionControlPlugin::ItemVersion version = item.second;
213 QHash<QByteArray, QVariant> values;
214 values.insert("version", QVariant(version));
215 m_model->setData(m_model->index(fileItem), values);
216 }
217 }
218
219 if (!m_silentUpdate) {
220 // Using an empty message results in clearing the previously shown information message and showing
221 // the default status bar information. This is useful as the user already gets feedback that the
222 // operation has been completed because of the icon emblems.
223 emit operationCompletedMessage(QString());
224 }
225
226 if (m_pendingItemStatesUpdate) {
227 m_pendingItemStatesUpdate = false;
228 updateItemStates();
229 }
230 }
231
232 void VersionControlObserver::updateItemStates()
233 {
234 Q_ASSERT(m_plugin);
235 if (m_updateItemStatesThread) {
236 // An update is currently ongoing. Wait until the thread has finished
237 // the update (see slotThreadFinished()).
238 m_pendingItemStatesUpdate = true;
239 return;
240 }
241
242 QMap<QString, QVector<ItemState> > itemStates;
243 createItemStatesList(itemStates);
244
245 if (!itemStates.isEmpty()) {
246 if (!m_silentUpdate) {
247 emit infoMessage(i18nc("@info:status", "Updating version information..."));
248 }
249 m_updateItemStatesThread = new UpdateItemStatesThread(m_plugin, itemStates);
250 connect(m_updateItemStatesThread, &UpdateItemStatesThread::finished,
251 this, &VersionControlObserver::slotThreadFinished);
252 connect(m_updateItemStatesThread, &UpdateItemStatesThread::finished,
253 m_updateItemStatesThread, &UpdateItemStatesThread::deleteLater);
254
255 m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished
256 }
257 }
258
259 int VersionControlObserver::createItemStatesList(QMap<QString, QVector<ItemState> >& itemStates,
260 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 KVersionControlPlugin* VersionControlObserver::searchPlugin(const QUrl& directory)
295 {
296 if (!m_pluginsInitialized) {
297 // No searching for plugins has been done yet. Query the KServiceTypeTrader for
298 // all fileview version control plugins and remember them in 'plugins'.
299 const QStringList enabledPlugins = VersionControlSettings::enabledPlugins();
300
301 const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin"));
302 for (KService::List::ConstIterator it = pluginServices.constBegin(); it != pluginServices.constEnd(); ++it) {
303 if (enabledPlugins.contains((*it)->name())) {
304 KVersionControlPlugin* plugin = (*it)->createInstance<KVersionControlPlugin>(this);
305 if (plugin) {
306 m_plugins.append( qMakePair(plugin, plugin->fileName()) );
307 }
308 }
309 }
310 m_pluginsInitialized = true;
311 }
312
313 if (m_plugins.empty()) {
314 // A searching for plugins has already been done, but no
315 // plugins are installed
316 return nullptr;
317 }
318
319 // We use the number of upUrl() calls to find the best matching plugin
320 // for the given directory. The smaller value, the better it is (0 is best).
321 KVersionControlPlugin* bestPlugin = nullptr;
322 int bestScore = INT_MAX;
323
324 // Verify whether the current directory contains revision information
325 // like .svn, .git, ...
326 for (const auto &it : qAsConst(m_plugins)) {
327 const QString fileName = directory.path() + '/' + it.second;
328 if (QFile::exists(fileName)) {
329 // The score of this plugin is 0 (best), so we can just return this plugin,
330 // instead of going through the plugin scoring procedure, we can't find a better one ;)
331 return it.first;
332 }
333
334 // Version control systems like Git provide the version information
335 // file only in the root directory. Check whether the version information file can
336 // be found in one of the parent directories. For performance reasons this
337 // step is only done, if the previous directory was marked as versioned by
338 // m_versionedDirectory. Drawback: Until e. g. Git is recognized, the root directory
339 // must be shown at least once.
340 if (m_versionedDirectory) {
341 QUrl dirUrl(directory);
342 QUrl upUrl = KIO::upUrl(dirUrl);
343 int upUrlCounter = 1;
344 while ((upUrlCounter < bestScore) && (upUrl != dirUrl)) {
345 const QString fileName = dirUrl.path() + '/' + it.second;
346 if (QFile::exists(fileName)) {
347 if (upUrlCounter < bestScore) {
348 bestPlugin = it.first;
349 bestScore = upUrlCounter;
350 }
351 break;
352 }
353 dirUrl = upUrl;
354 upUrl = KIO::upUrl(dirUrl);
355 ++upUrlCounter;
356 }
357 }
358 }
359
360 return bestPlugin;
361 }
362
363 bool VersionControlObserver::isVersioned() const
364 {
365 return m_versionedDirectory && m_plugin;
366 }
367