]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/versioncontrol/versioncontrolobserver.cpp
[versioncontrolobserver] Do not use static plugin objects
[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 "kitemviews/kfileitemmodel.h"
25 #include "updateitemstatesthread.h"
26
27 #include <KLocalizedString>
28 #include <KService>
29 #include <KServiceTypeTrader>
30
31 #include <QTimer>
32
33 VersionControlObserver::VersionControlObserver(QObject* parent) :
34 QObject(parent),
35 m_pendingItemStatesUpdate(false),
36 m_versionedDirectory(false),
37 m_silentUpdate(false),
38 m_model(nullptr),
39 m_dirVerificationTimer(nullptr),
40 m_pluginsInitialized(false),
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)
261 {
262 if (!m_pluginsInitialized) {
263 // No searching for plugins has been done yet. Query the KServiceTypeTrader for
264 // all fileview version control plugins and remember them in 'plugins'.
265 const QStringList enabledPlugins = VersionControlSettings::enabledPlugins();
266
267 const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin"));
268 for (KService::List::ConstIterator it = pluginServices.constBegin(); it != pluginServices.constEnd(); ++it) {
269 if (enabledPlugins.contains((*it)->name())) {
270 KVersionControlPlugin* plugin = (*it)->createInstance<KVersionControlPlugin>(this);
271 if (plugin) {
272 m_plugins.append(plugin);
273 }
274 }
275 }
276 m_pluginsInitialized = true;
277 }
278
279 if (m_plugins.empty()) {
280 // A searching for plugins has already been done, but no
281 // plugins are installed
282 return nullptr;
283 }
284
285 // We use the number of upUrl() calls to find the best matching plugin
286 // for the given directory. The smaller value, the better it is (0 is best).
287 KVersionControlPlugin* bestPlugin = nullptr;
288 int bestScore = INT_MAX;
289
290 // Verify whether the current directory contains revision information
291 // like .svn, .git, ...
292 foreach (KVersionControlPlugin* plugin, m_plugins) {
293 const QString fileName = directory.path() + '/' + plugin->fileName();
294 if (QFile::exists(fileName)) {
295 // The score of this plugin is 0 (best), so we can just return this plugin,
296 // instead of going through the plugin scoring procedure, we can't find a better one ;)
297 return plugin;
298 }
299
300 // Version control systems like Git provide the version information
301 // file only in the root directory. Check whether the version information file can
302 // be found in one of the parent directories. For performance reasons this
303 // step is only done, if the previous directory was marked as versioned by
304 // m_versionedDirectory. Drawback: Until e. g. Git is recognized, the root directory
305 // must be shown at least once.
306 if (m_versionedDirectory) {
307 QUrl dirUrl(directory);
308 QUrl upUrl = KIO::upUrl(dirUrl);
309 int upUrlCounter = 1;
310 while ((upUrlCounter < bestScore) && (upUrl != dirUrl)) {
311 const QString fileName = dirUrl.path() + '/' + plugin->fileName();
312 if (QFile::exists(fileName)) {
313 if (upUrlCounter < bestScore) {
314 bestPlugin = plugin;
315 bestScore = upUrlCounter;
316 }
317 break;
318 }
319 dirUrl = upUrl;
320 upUrl = KIO::upUrl(dirUrl);
321 ++upUrlCounter;
322 }
323 }
324 }
325
326 return bestPlugin;
327 }
328
329 bool VersionControlObserver::isVersioned() const
330 {
331 return m_versionedDirectory && m_plugin;
332 }
333