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