]> cloud.milkyroute.net Git - dolphin.git/blob - src/revisioncontrolobserver.cpp
6ecba64c588c307ea8892947b0d14e7973677c1a
[dolphin.git] / src / revisioncontrolobserver.cpp
1 /***************************************************************************
2 * Copyright (C) 2009 by Peter Penz <peter.penz@gmx.at> *
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 "revisioncontrolobserver.h"
21
22 #include "dolphinmodel.h"
23 #include "revisioncontrolplugin.h"
24
25 #include <kdirlister.h>
26 #include <klocale.h>
27
28 #include <QAbstractProxyModel>
29 #include <QAbstractItemView>
30 #include <QMutexLocker>
31 #include <QTimer>
32
33 /**
34 * The performance of updating the revision state of items depends
35 * on the used plugin. To prevent that Dolphin gets blocked by a
36 * slow plugin, the updating is delegated to a thread.
37 */
38 class UpdateItemStatesThread : public QThread
39 {
40 public:
41 UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex);
42 void setData(RevisionControlPlugin* plugin,
43 const QList<RevisionControlObserver::ItemState>& itemStates);
44 QList<RevisionControlObserver::ItemState> itemStates() const;
45 bool retrievedItems() const;
46
47 protected:
48 virtual void run();
49
50 private:
51 bool m_retrievedItems;
52 RevisionControlPlugin* m_plugin;
53 QMutex* m_pluginMutex;
54 QList<RevisionControlObserver::ItemState> m_itemStates;
55 };
56
57 UpdateItemStatesThread::UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex) :
58 QThread(parent),
59 m_retrievedItems(false),
60 m_pluginMutex(pluginMutex),
61 m_itemStates()
62 {
63 }
64
65 void UpdateItemStatesThread::setData(RevisionControlPlugin* plugin,
66 const QList<RevisionControlObserver::ItemState>& itemStates)
67 {
68 m_plugin = plugin;
69 m_itemStates = itemStates;
70 }
71
72 void UpdateItemStatesThread::run()
73 {
74 Q_ASSERT(!m_itemStates.isEmpty());
75 Q_ASSERT(m_plugin != 0);
76
77 // it is assumed that all items have the same parent directory
78 const QString directory = m_itemStates.first().item.url().directory(KUrl::AppendTrailingSlash);
79
80 QMutexLocker locker(m_pluginMutex);
81 m_retrievedItems = false;
82 if (m_plugin->beginRetrieval(directory)) {
83 const int count = m_itemStates.count();
84 for (int i = 0; i < count; ++i) {
85 m_itemStates[i].revision = m_plugin->revisionState(m_itemStates[i].item);
86 }
87 m_plugin->endRetrieval();
88 m_retrievedItems = true;
89 }
90 }
91
92 QList<RevisionControlObserver::ItemState> UpdateItemStatesThread::itemStates() const
93 {
94 return m_itemStates;
95 }
96
97 bool UpdateItemStatesThread::retrievedItems() const
98 {
99 return m_retrievedItems;
100 }
101
102 // ------------------------------------------------------------------------------------------------
103
104 RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) :
105 QObject(view),
106 m_pendingItemStatesUpdate(false),
107 m_revisionedDirectory(false),
108 m_silentUpdate(false),
109 m_view(view),
110 m_dirLister(0),
111 m_dolphinModel(0),
112 m_dirVerificationTimer(0),
113 m_pluginMutex(QMutex::Recursive),
114 m_plugin(0),
115 m_updateItemStatesThread(0)
116 {
117 Q_ASSERT(view != 0);
118
119 QAbstractProxyModel* proxyModel = qobject_cast<QAbstractProxyModel*>(view->model());
120 m_dolphinModel = (proxyModel == 0) ?
121 qobject_cast<DolphinModel*>(view->model()) :
122 qobject_cast<DolphinModel*>(proxyModel->sourceModel());
123 if (m_dolphinModel != 0) {
124 m_dirLister = m_dolphinModel->dirLister();
125 connect(m_dirLister, SIGNAL(completed()),
126 this, SLOT(delayedDirectoryVerification()));
127
128 // The verification timer specifies the timeout until the shown directory
129 // is checked whether it is versioned. Per default it is assumed that users
130 // don't iterate through versioned directories and a high timeout is used
131 // The timeout will be decreased as soon as a versioned directory has been
132 // found (see verifyDirectory()).
133 m_dirVerificationTimer = new QTimer(this);
134 m_dirVerificationTimer->setSingleShot(true);
135 m_dirVerificationTimer->setInterval(500);
136 connect(m_dirVerificationTimer, SIGNAL(timeout()),
137 this, SLOT(verifyDirectory()));
138 }
139 }
140
141 RevisionControlObserver::~RevisionControlObserver()
142 {
143 if (m_updateItemStatesThread != 0) {
144 m_updateItemStatesThread->terminate();
145 m_updateItemStatesThread->wait();
146 }
147 delete m_plugin;
148 m_plugin = 0;
149 }
150
151 QList<QAction*> RevisionControlObserver::contextMenuActions(const KFileItemList& items) const
152 {
153 if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) {
154 QMutexLocker locker(&m_pluginMutex);
155 return m_plugin->contextMenuActions(items);
156 }
157 return QList<QAction*>();
158 }
159
160 QList<QAction*> RevisionControlObserver::contextMenuActions(const QString& directory) const
161 {
162 if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) {
163 QMutexLocker locker(&m_pluginMutex);
164 return m_plugin->contextMenuActions(directory);
165 }
166
167 return QList<QAction*>();
168 }
169
170 void RevisionControlObserver::delayedDirectoryVerification()
171 {
172 m_silentUpdate = false;
173 m_dirVerificationTimer->start();
174 }
175
176 void RevisionControlObserver::silentDirectoryVerification()
177 {
178 m_silentUpdate = true;
179 m_dirVerificationTimer->start();
180 }
181
182 void RevisionControlObserver::verifyDirectory()
183 {
184 KUrl revisionControlUrl = m_dirLister->url();
185 if (!revisionControlUrl.isLocalFile()) {
186 return;
187 }
188
189 if (m_plugin == 0) {
190 // TODO: just for testing purposes. A plugin approach will be used later.
191 m_plugin = new SubversionPlugin();
192 connect(m_plugin, SIGNAL(infoMessage(const QString&)),
193 this, SIGNAL(infoMessage(const QString&)));
194 connect(m_plugin, SIGNAL(errorMessage(const QString&)),
195 this, SIGNAL(errorMessage(const QString&)));
196 connect(m_plugin, SIGNAL(operationCompletedMessage(const QString&)),
197 this, SIGNAL(operationCompletedMessage(const QString&)));
198 }
199
200 revisionControlUrl.addPath(m_plugin->fileName());
201 const KFileItem item = m_dirLister->findByUrl(revisionControlUrl);
202
203 bool foundRevisionInfo = !item.isNull();
204 if (!foundRevisionInfo && m_revisionedDirectory) {
205 // Revision control systems like Git provide the revision information
206 // file only in the root directory. Check whether the revision information file can
207 // be found in one of the parent directories.
208
209 // TODO...
210 }
211
212 if (foundRevisionInfo) {
213 if (!m_revisionedDirectory) {
214 m_revisionedDirectory = true;
215
216 // The directory is versioned. Assume that the user will further browse through
217 // versioned directories and decrease the verification timer.
218 m_dirVerificationTimer->setInterval(100);
219 connect(m_dirLister, SIGNAL(refreshItems(const QList<QPair<KFileItem,KFileItem>>&)),
220 this, SLOT(delayedDirectoryVerification()));
221 connect(m_dirLister, SIGNAL(newItems(const KFileItemList&)),
222 this, SLOT(delayedDirectoryVerification()));
223 connect(m_plugin, SIGNAL(revisionStatesChanged()),
224 this, SLOT(silentDirectoryVerification()));
225 }
226 updateItemStates();
227 } else if (m_revisionedDirectory) {
228 m_revisionedDirectory = false;
229
230 // The directory is not versioned. Reset the verification timer to a higher
231 // value, so that browsing through non-versioned directories is not slown down
232 // by an immediate verification.
233 m_dirVerificationTimer->setInterval(500);
234 disconnect(m_dirLister, SIGNAL(refreshItems(const QList<QPair<KFileItem,KFileItem>>&)),
235 this, SLOT(delayedDirectoryVerification()));
236 disconnect(m_dirLister, SIGNAL(newItems(const KFileItemList&)),
237 this, SLOT(delayedDirectoryVerification()));
238 disconnect(m_plugin, SIGNAL(revisionStatesChanged()),
239 this, SLOT(silentDirectoryVerification()));
240 }
241 }
242
243 void RevisionControlObserver::applyUpdatedItemStates()
244 {
245 if (!m_updateItemStatesThread->retrievedItems()) {
246 // ignore m_silentUpdate for an error message
247 emit errorMessage(i18nc("@info:status", "Update of revision information failed."));
248 return;
249 }
250
251 // QAbstractItemModel::setData() triggers a bottleneck in combination with QListView
252 // (a detailed description of the root cause is given in the class KFilePreviewGenerator
253 // from kdelibs). To bypass this bottleneck, the signals of the model are temporary blocked.
254 // This works as the update of the data does not require a relayout of the views used in Dolphin.
255 const bool signalsBlocked = m_dolphinModel->signalsBlocked();
256 m_dolphinModel->blockSignals(true);
257
258 const QList<ItemState> itemStates = m_updateItemStatesThread->itemStates();
259 foreach (const ItemState& itemState, itemStates) {
260 m_dolphinModel->setData(itemState.index,
261 QVariant(static_cast<int>(itemState.revision)),
262 Qt::DecorationRole);
263 }
264
265 m_dolphinModel->blockSignals(signalsBlocked);
266 m_view->viewport()->repaint();
267
268 if (!m_silentUpdate) {
269 // Using an empty message results in clearing the previously shown information message and showing
270 // the default status bar information. This is useful as the user already gets feedback that the
271 // operation has been completed because of the icon emblems.
272 emit operationCompletedMessage(QString());
273 }
274
275 if (m_pendingItemStatesUpdate) {
276 m_pendingItemStatesUpdate = false;
277 updateItemStates();
278 }
279 }
280
281 void RevisionControlObserver::updateItemStates()
282 {
283 Q_ASSERT(m_plugin != 0);
284 if (m_updateItemStatesThread == 0) {
285 m_updateItemStatesThread = new UpdateItemStatesThread(this, &m_pluginMutex);
286 connect(m_updateItemStatesThread, SIGNAL(finished()),
287 this, SLOT(applyUpdatedItemStates()));
288 }
289 if (m_updateItemStatesThread->isRunning()) {
290 // An update is currently ongoing. Wait until the thread has finished
291 // the update (see applyUpdatedItemStates()).
292 m_pendingItemStatesUpdate = true;
293 return;
294 }
295
296 const int rowCount = m_dolphinModel->rowCount();
297 if (rowCount > 0) {
298 // Build a list of all items in the current directory and delegate
299 // this list to the thread, which adjusts the revision states.
300 QList<ItemState> itemStates;
301 for (int row = 0; row < rowCount; ++row) {
302 const QModelIndex index = m_dolphinModel->index(row, DolphinModel::Revision);
303
304 ItemState itemState;
305 itemState.index = index;
306 itemState.item = m_dolphinModel->itemForIndex(index);
307 itemState.revision = RevisionControlPlugin::UnversionedRevision;
308
309 itemStates.append(itemState);
310 }
311
312 if (!m_silentUpdate) {
313 emit infoMessage(i18nc("@info:status", "Updating revision information..."));
314 }
315 m_updateItemStatesThread->setData(m_plugin, itemStates);
316 m_updateItemStatesThread->start(); // applyUpdatedItemStates() is called when finished
317 }
318 }
319
320 #include "revisioncontrolobserver.moc"