]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/versioncontrol/versioncontrolobserver.cpp
c794408825c62cf962d9c97ca72ba34a10a22a1f
[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
24 #include <KLocalizedString>
25 #include <KService>
26 #include "dolphindebug.h"
27 #include <KServiceTypeTrader>
28 #include <kitemviews/kfileitemmodel.h>
29
30 #include "updateitemstatesthread.h"
31
32 #include <QFile>
33 #include <QTimer>
34
35 VersionControlObserver::VersionControlObserver(QObject* parent) :
36 QObject(parent),
37 m_pendingItemStatesUpdate(false),
38 m_versionedDirectory(false),
39 m_silentUpdate(false),
40 m_model(0),
41 m_dirVerificationTimer(0),
42 m_plugin(0),
43 m_updateItemStatesThread(0)
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 = 0;
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 QList<QAction*> VersionControlObserver::actions(const KFileItemList& items) const
90 {
91 bool hasNullItems = false;
92 foreach (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 || !isVersioned()) {
101 return {};
102 }
103
104 return m_plugin->actions(items);
105 }
106
107 void VersionControlObserver::delayedDirectoryVerification()
108 {
109 m_silentUpdate = false;
110 m_dirVerificationTimer->start();
111 }
112
113 void VersionControlObserver::silentDirectoryVerification()
114 {
115 m_silentUpdate = true;
116 m_dirVerificationTimer->start();
117 }
118
119 void VersionControlObserver::verifyDirectory()
120 {
121 if (!m_model) {
122 return;
123 }
124
125 const KFileItem rootItem = m_model->rootItem();
126 if (rootItem.isNull() || !rootItem.url().isLocalFile()) {
127 return;
128 }
129
130 if (m_plugin) {
131 m_plugin->disconnect(this);
132 }
133
134 m_plugin = searchPlugin(rootItem.url());
135 if (m_plugin) {
136 connect(m_plugin, &KVersionControlPlugin::itemVersionsChanged,
137 this, &VersionControlObserver::silentDirectoryVerification);
138 connect(m_plugin, &KVersionControlPlugin::infoMessage,
139 this, &VersionControlObserver::infoMessage);
140 connect(m_plugin, &KVersionControlPlugin::errorMessage,
141 this, &VersionControlObserver::errorMessage);
142 connect(m_plugin, &KVersionControlPlugin::operationCompletedMessage,
143 this, &VersionControlObserver::operationCompletedMessage);
144
145 if (!m_versionedDirectory) {
146 m_versionedDirectory = true;
147
148 // The directory is versioned. Assume that the user will further browse through
149 // versioned directories and decrease the verification timer.
150 m_dirVerificationTimer->setInterval(100);
151 }
152 updateItemStates();
153 } else if (m_versionedDirectory) {
154 m_versionedDirectory = false;
155
156 // The directory is not versioned. Reset the verification timer to a higher
157 // value, so that browsing through non-versioned directories is not slown down
158 // by an immediate verification.
159 m_dirVerificationTimer->setInterval(500);
160 }
161 }
162
163 void VersionControlObserver::slotThreadFinished()
164 {
165 UpdateItemStatesThread* thread = m_updateItemStatesThread;
166 m_updateItemStatesThread = 0; // The thread deletes itself automatically (see updateItemStates())
167
168 if (!m_plugin || !thread) {
169 return;
170 }
171
172 const QMap<QString, QVector<ItemState> >& itemStates = thread->itemStates();
173 QMap<QString, QVector<ItemState> >::const_iterator it = itemStates.constBegin();
174 for (; it != itemStates.constEnd(); ++it) {
175 const QVector<ItemState>& items = it.value();
176
177 foreach (const ItemState& item, items) {
178 const KFileItem& fileItem = item.first;
179 const KVersionControlPlugin::ItemVersion version = item.second;
180 QHash<QByteArray, QVariant> values;
181 values.insert("version", QVariant(version));
182 m_model->setData(m_model->index(fileItem), values);
183 }
184 }
185
186 if (!m_silentUpdate) {
187 // Using an empty message results in clearing the previously shown information message and showing
188 // the default status bar information. This is useful as the user already gets feedback that the
189 // operation has been completed because of the icon emblems.
190 emit operationCompletedMessage(QString());
191 }
192
193 if (m_pendingItemStatesUpdate) {
194 m_pendingItemStatesUpdate = false;
195 updateItemStates();
196 }
197 }
198
199 void VersionControlObserver::updateItemStates()
200 {
201 Q_ASSERT(m_plugin);
202 if (m_updateItemStatesThread) {
203 // An update is currently ongoing. Wait until the thread has finished
204 // the update (see slotThreadFinished()).
205 m_pendingItemStatesUpdate = true;
206 return;
207 }
208
209 QMap<QString, QVector<ItemState> > itemStates;
210 createItemStatesList(itemStates);
211
212 if (!itemStates.isEmpty()) {
213 if (!m_silentUpdate) {
214 emit infoMessage(i18nc("@info:status", "Updating version information..."));
215 }
216 m_updateItemStatesThread = new UpdateItemStatesThread(m_plugin, itemStates);
217 connect(m_updateItemStatesThread, &UpdateItemStatesThread::finished,
218 this, &VersionControlObserver::slotThreadFinished);
219 connect(m_updateItemStatesThread, &UpdateItemStatesThread::finished,
220 m_updateItemStatesThread, &UpdateItemStatesThread::deleteLater);
221
222 m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished
223 }
224 }
225
226 int VersionControlObserver::createItemStatesList(QMap<QString, QVector<ItemState> >& itemStates,
227 const int firstIndex)
228 {
229 const int itemCount = m_model->count();
230 const int currentExpansionLevel = m_model->expandedParentsCount(firstIndex);
231
232 QVector<ItemState> items;
233 items.reserve(itemCount - firstIndex);
234
235 int index;
236 for (index = firstIndex; index < itemCount; ++index) {
237 const int expansionLevel = m_model->expandedParentsCount(index);
238
239 if (expansionLevel == currentExpansionLevel) {
240 ItemState itemState;
241 itemState.first = m_model->fileItem(index);
242 itemState.second = KVersionControlPlugin::UnversionedVersion;
243
244 items.append(itemState);
245 } else if (expansionLevel > currentExpansionLevel) {
246 // Sub folder
247 index += createItemStatesList(itemStates, index) - 1;
248 } else {
249 break;
250 }
251 }
252
253 if (items.count() > 0) {
254 const QUrl& url = items.first().first.url();
255 itemStates.insert(url.adjusted(QUrl::RemoveFilename).path(), items);
256 }
257
258 return index - firstIndex; // number of processed items
259 }
260
261 KVersionControlPlugin* VersionControlObserver::searchPlugin(const QUrl& directory) const
262 {
263 static bool pluginsAvailable = true;
264 static QList<KVersionControlPlugin*> plugins;
265
266 if (!pluginsAvailable) {
267 // A searching for plugins has already been done, but no
268 // plugins are installed
269 return 0;
270 }
271
272 if (plugins.isEmpty()) {
273 // No searching for plugins has been done yet. Query the KServiceTypeTrader for
274 // all fileview version control plugins and remember them in 'plugins'.
275 const QStringList enabledPlugins = VersionControlSettings::enabledPlugins();
276
277 const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin"));
278 for (KService::List::ConstIterator it = pluginServices.constBegin(); it != pluginServices.constEnd(); ++it) {
279 if (enabledPlugins.contains((*it)->name())) {
280 KVersionControlPlugin* plugin = (*it)->createInstance<KVersionControlPlugin>();
281 if (plugin) {
282 plugins.append(plugin);
283 }
284 }
285 }
286 if (plugins.isEmpty()) {
287 pluginsAvailable = false;
288 return 0;
289 }
290 }
291
292 // We use the number of upUrl() calls to find the best matching plugin
293 // for the given directory. The smaller value, the better it is (0 is best).
294 KVersionControlPlugin* bestPlugin = 0;
295 int bestScore = INT_MAX;
296
297 // Verify whether the current directory contains revision information
298 // like .svn, .git, ...
299 foreach (KVersionControlPlugin* plugin, plugins) {
300 const QString fileName = directory.path() + '/' + plugin->fileName();
301 if (QFile::exists(fileName)) {
302 // The score of this plugin is 0 (best), so we can just return this plugin,
303 // instead of going through the plugin scoring procedure, we can't find a better one ;)
304 return plugin;
305 }
306
307 // Version control systems like Git provide the version information
308 // file only in the root directory. Check whether the version information file can
309 // be found in one of the parent directories. For performance reasons this
310 // step is only done, if the previous directory was marked as versioned by
311 // m_versionedDirectory. Drawback: Until e. g. Git is recognized, the root directory
312 // must be shown at least once.
313 if (m_versionedDirectory) {
314 QUrl dirUrl(directory);
315 QUrl upUrl = KIO::upUrl(dirUrl);
316 int upUrlCounter = 1;
317 while ((upUrlCounter < bestScore) && (upUrl != dirUrl)) {
318 const QString fileName = dirUrl.path() + '/' + plugin->fileName();
319 if (QFile::exists(fileName)) {
320 if (upUrlCounter < bestScore) {
321 bestPlugin = plugin;
322 bestScore = upUrlCounter;
323 }
324 break;
325 }
326 dirUrl = upUrl;
327 upUrl = KIO::upUrl(dirUrl);
328 ++upUrlCounter;
329 }
330 }
331 }
332
333 return bestPlugin;
334 }
335
336 bool VersionControlObserver::isVersioned() const
337 {
338 return m_versionedDirectory && m_plugin;
339 }
340