]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/versioncontrol/versioncontrolobserver.cpp
[versioncontrolobserver] Update working directory on tab activation
[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_model(nullptr),
40 m_dirVerificationTimer(nullptr),
41 m_pluginsInitialized(false),
42 m_plugin(nullptr),
43 m_updateItemStatesThread(nullptr)
44 {
45 // The verification timer specifies the timeout until the shown directory
46 // is checked whether it is versioned. Per default it is assumed that users
47 // don't iterate through versioned directories and a high timeout is used
48 // The timeout will be decreased as soon as a versioned directory has been
49 // found (see verifyDirectory()).
50 m_dirVerificationTimer = new QTimer(this);
51 m_dirVerificationTimer->setSingleShot(true);
52 m_dirVerificationTimer->setInterval(500);
53 connect(m_dirVerificationTimer, &QTimer::timeout,
54 this, &VersionControlObserver::verifyDirectory);
55 }
56
57 VersionControlObserver::~VersionControlObserver()
58 {
59 if (m_plugin) {
60 m_plugin->disconnect(this);
61 m_plugin = nullptr;
62 }
63 }
64
65 void VersionControlObserver::setModel(KFileItemModel* model)
66 {
67 if (m_model) {
68 disconnect(m_model, &KFileItemModel::itemsInserted,
69 this, &VersionControlObserver::delayedDirectoryVerification);
70 disconnect(m_model, &KFileItemModel::itemsChanged,
71 this, &VersionControlObserver::delayedDirectoryVerification);
72 }
73
74 m_model = model;
75
76 if (model) {
77 connect(m_model, &KFileItemModel::itemsInserted,
78 this, &VersionControlObserver::delayedDirectoryVerification);
79 connect(m_model, &KFileItemModel::itemsChanged,
80 this, &VersionControlObserver::delayedDirectoryVerification);
81 }
82 }
83
84 KFileItemModel* VersionControlObserver::model() const
85 {
86 return m_model;
87 }
88
89 void VersionControlObserver::setView(DolphinView* view)
90 {
91 if (m_view) {
92 disconnect(m_view, &DolphinView::activated,
93 this, &VersionControlObserver::delayedDirectoryVerification);
94 }
95
96 m_view = view;
97
98 if (m_view) {
99 connect(m_view, &DolphinView::activated,
100 this, &VersionControlObserver::delayedDirectoryVerification);
101 }
102 }
103
104 DolphinView* VersionControlObserver::view() const
105 {
106 return m_view;
107 }
108
109 QList<QAction*> VersionControlObserver::actions(const KFileItemList& items) const
110 {
111 bool hasNullItems = false;
112 foreach (const KFileItem& item, items) {
113 if (item.isNull()) {
114 qCWarning(DolphinDebug) << "Requesting version-control-actions for empty items";
115 hasNullItems = true;
116 break;
117 }
118 }
119
120 if (!m_model || hasNullItems || !isVersioned()) {
121 return {};
122 }
123
124 return m_plugin->actions(items);
125 }
126
127 void VersionControlObserver::delayedDirectoryVerification()
128 {
129 m_silentUpdate = false;
130 m_dirVerificationTimer->start();
131 }
132
133 void VersionControlObserver::silentDirectoryVerification()
134 {
135 m_silentUpdate = true;
136 m_dirVerificationTimer->start();
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) {
151 m_plugin->disconnect(this);
152 }
153
154 m_plugin = searchPlugin(rootItem.url());
155 if (m_plugin) {
156 connect(m_plugin, &KVersionControlPlugin::itemVersionsChanged,
157 this, &VersionControlObserver::silentDirectoryVerification);
158 connect(m_plugin, &KVersionControlPlugin::infoMessage,
159 this, &VersionControlObserver::infoMessage);
160 connect(m_plugin, &KVersionControlPlugin::errorMessage,
161 this, &VersionControlObserver::errorMessage);
162 connect(m_plugin, &KVersionControlPlugin::operationCompletedMessage,
163 this, &VersionControlObserver::operationCompletedMessage);
164
165 if (!m_versionedDirectory) {
166 m_versionedDirectory = true;
167
168 // The directory is versioned. Assume that the user will further browse through
169 // versioned directories and decrease the verification timer.
170 m_dirVerificationTimer->setInterval(100);
171 }
172 updateItemStates();
173 } else if (m_versionedDirectory) {
174 m_versionedDirectory = false;
175
176 // The directory is not versioned. Reset the verification timer to a higher
177 // value, so that browsing through non-versioned directories is not slown down
178 // by an immediate verification.
179 m_dirVerificationTimer->setInterval(500);
180 }
181 }
182
183 void VersionControlObserver::slotThreadFinished()
184 {
185 UpdateItemStatesThread* thread = m_updateItemStatesThread;
186 m_updateItemStatesThread = nullptr; // The thread deletes itself automatically (see updateItemStates())
187
188 if (!m_plugin || !thread) {
189 return;
190 }
191
192 const QMap<QString, QVector<ItemState> >& itemStates = thread->itemStates();
193 QMap<QString, QVector<ItemState> >::const_iterator it = itemStates.constBegin();
194 for (; it != itemStates.constEnd(); ++it) {
195 const QVector<ItemState>& items = it.value();
196
197 foreach (const ItemState& item, items) {
198 const KFileItem& fileItem = item.first;
199 const KVersionControlPlugin::ItemVersion version = item.second;
200 QHash<QByteArray, QVariant> values;
201 values.insert("version", QVariant(version));
202 m_model->setData(m_model->index(fileItem), values);
203 }
204 }
205
206 if (!m_silentUpdate) {
207 // Using an empty message results in clearing the previously shown information message and showing
208 // the default status bar information. This is useful as the user already gets feedback that the
209 // operation has been completed because of the icon emblems.
210 emit operationCompletedMessage(QString());
211 }
212
213 if (m_pendingItemStatesUpdate) {
214 m_pendingItemStatesUpdate = false;
215 updateItemStates();
216 }
217 }
218
219 void VersionControlObserver::updateItemStates()
220 {
221 Q_ASSERT(m_plugin);
222 if (m_updateItemStatesThread) {
223 // An update is currently ongoing. Wait until the thread has finished
224 // the update (see slotThreadFinished()).
225 m_pendingItemStatesUpdate = true;
226 return;
227 }
228
229 QMap<QString, QVector<ItemState> > itemStates;
230 createItemStatesList(itemStates);
231
232 if (!itemStates.isEmpty()) {
233 if (!m_silentUpdate) {
234 emit infoMessage(i18nc("@info:status", "Updating version information..."));
235 }
236 m_updateItemStatesThread = new UpdateItemStatesThread(m_plugin, itemStates);
237 connect(m_updateItemStatesThread, &UpdateItemStatesThread::finished,
238 this, &VersionControlObserver::slotThreadFinished);
239 connect(m_updateItemStatesThread, &UpdateItemStatesThread::finished,
240 m_updateItemStatesThread, &UpdateItemStatesThread::deleteLater);
241
242 m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished
243 }
244 }
245
246 int VersionControlObserver::createItemStatesList(QMap<QString, QVector<ItemState> >& itemStates,
247 const int firstIndex)
248 {
249 const int itemCount = m_model->count();
250 const int currentExpansionLevel = m_model->expandedParentsCount(firstIndex);
251
252 QVector<ItemState> items;
253 items.reserve(itemCount - firstIndex);
254
255 int index;
256 for (index = firstIndex; index < itemCount; ++index) {
257 const int expansionLevel = m_model->expandedParentsCount(index);
258
259 if (expansionLevel == currentExpansionLevel) {
260 ItemState itemState;
261 itemState.first = m_model->fileItem(index);
262 itemState.second = KVersionControlPlugin::UnversionedVersion;
263
264 items.append(itemState);
265 } else if (expansionLevel > currentExpansionLevel) {
266 // Sub folder
267 index += createItemStatesList(itemStates, index) - 1;
268 } else {
269 break;
270 }
271 }
272
273 if (items.count() > 0) {
274 const QUrl& url = items.first().first.url();
275 itemStates.insert(url.adjusted(QUrl::RemoveFilename).path(), items);
276 }
277
278 return index - firstIndex; // number of processed items
279 }
280
281 KVersionControlPlugin* VersionControlObserver::searchPlugin(const QUrl& directory)
282 {
283 if (!m_pluginsInitialized) {
284 // No searching for plugins has been done yet. Query the KServiceTypeTrader for
285 // all fileview version control plugins and remember them in 'plugins'.
286 const QStringList enabledPlugins = VersionControlSettings::enabledPlugins();
287
288 const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin"));
289 for (KService::List::ConstIterator it = pluginServices.constBegin(); it != pluginServices.constEnd(); ++it) {
290 if (enabledPlugins.contains((*it)->name())) {
291 KVersionControlPlugin* plugin = (*it)->createInstance<KVersionControlPlugin>(this);
292 if (plugin) {
293 m_plugins.append(plugin);
294 }
295 }
296 }
297 m_pluginsInitialized = true;
298 }
299
300 if (m_plugins.empty()) {
301 // A searching for plugins has already been done, but no
302 // plugins are installed
303 return nullptr;
304 }
305
306 // We use the number of upUrl() calls to find the best matching plugin
307 // for the given directory. The smaller value, the better it is (0 is best).
308 KVersionControlPlugin* bestPlugin = nullptr;
309 int bestScore = INT_MAX;
310
311 // Verify whether the current directory contains revision information
312 // like .svn, .git, ...
313 foreach (KVersionControlPlugin* plugin, m_plugins) {
314 const QString fileName = directory.path() + '/' + plugin->fileName();
315 if (QFile::exists(fileName)) {
316 // The score of this plugin is 0 (best), so we can just return this plugin,
317 // instead of going through the plugin scoring procedure, we can't find a better one ;)
318 return plugin;
319 }
320
321 // Version control systems like Git provide the version information
322 // file only in the root directory. Check whether the version information file can
323 // be found in one of the parent directories. For performance reasons this
324 // step is only done, if the previous directory was marked as versioned by
325 // m_versionedDirectory. Drawback: Until e. g. Git is recognized, the root directory
326 // must be shown at least once.
327 if (m_versionedDirectory) {
328 QUrl dirUrl(directory);
329 QUrl upUrl = KIO::upUrl(dirUrl);
330 int upUrlCounter = 1;
331 while ((upUrlCounter < bestScore) && (upUrl != dirUrl)) {
332 const QString fileName = dirUrl.path() + '/' + plugin->fileName();
333 if (QFile::exists(fileName)) {
334 if (upUrlCounter < bestScore) {
335 bestPlugin = plugin;
336 bestScore = upUrlCounter;
337 }
338 break;
339 }
340 dirUrl = upUrl;
341 upUrl = KIO::upUrl(dirUrl);
342 ++upUrlCounter;
343 }
344 }
345 }
346
347 return bestPlugin;
348 }
349
350 bool VersionControlObserver::isVersioned() const
351 {
352 return m_versionedDirectory && m_plugin;
353 }
354