Add cache for Solid devices to speed up KFilePlacesModel

This commit is contained in:
Dan Vrátil 2012-11-29 16:10:08 +01:00
parent 601681bc18
commit 04893e481a
2 changed files with 410 additions and 0 deletions

View File

@ -0,0 +1,406 @@
diff --git a/kfile/CMakeLists.txt b/kfile/CMakeLists.txt
index ceae140..c7c4d3d 100644
--- a/kfile/CMakeLists.txt
+++ b/kfile/CMakeLists.txt
@@ -20,6 +20,7 @@ set(kfile_LIB_SRCS
kfilefiltercombo.cpp
kfiletreeview.cpp
kfilewidget.cpp
+ kfileplacesdevicecache.cpp
kfileplacesitem.cpp
kfileplacesmodel.cpp
kfileplacessharedbookmarks.cpp
@@ -63,6 +64,7 @@ install( FILES
kdirselectdialog.h
kdirsortfilterproxymodel.h
kfilefiltercombo.h
+ kfileplacesdevicecache.h
kfileplacesmodel.h
kfileplacesview.h
kfilepreviewgenerator.h
diff --git a/kfile/kfileplacesdevicecache.cpp b/kfile/kfileplacesdevicecache.cpp
new file mode 100644
index 0000000..40f7242
--- /dev/null
+++ b/kfile/kfileplacesdevicecache.cpp
@@ -0,0 +1,174 @@
+/*
+ Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kfileplacesdevicecache.h"
+
+#include <QMutex>
+#include <QtConcurrentRun>
+#include <QFutureWatcher>
+#include <QTimer>
+
+#include <kprotocolinfo.h>
+#include <solid/predicate.h>
+#include <solid/device.h>
+#include <solid/devicenotifier.h>
+#include <solid/genericinterface.h>
+
+#include <kdebug.h>
+
+KFilePlacesDeviceCache* KFilePlacesDeviceCache::s_instance = 0;
+
+class KFilePlacesDeviceCache::Private
+{
+ public:
+ Private(KFilePlacesDeviceCache *parent):
+ queryRunning(false),
+ q(parent)
+ { }
+
+ ~Private()
+ { }
+
+ /* This method runs asynchronously in thread */
+ QSet<QString> listSolidDevicesAsync()
+ {
+ QSet<QString> udis;
+
+ kDebug() << "Querying Solid devices...";
+ const QList<Solid::Device>& deviceList = Solid::Device::listFromQuery(solidPredicate);
+ kDebug() << "Retrieved" << deviceList.count() << "devices";
+
+ Q_FOREACH (const Solid::Device& device, deviceList) {
+ if (solidPredicate.matches(device)) {
+ udis << device.udi();
+ }
+ }
+
+ return udis;
+ }
+
+ void _k_slotDeviceAdded(const QString &udi)
+ {
+ devicesCache << udi;
+
+ Q_EMIT q->deviceAdded(udi);
+ }
+
+ void _k_slotDeviceRemoved(const QString &udi)
+ {
+ if (!devicesCache.contains(udi)) {
+ return;
+ }
+
+ devicesCache.remove(udi);
+
+ Q_EMIT q->deviceRemoved(udi);
+ }
+
+ void _k_listSolidDevicesFinished()
+ {
+ Q_FOREACH (const QString& device, futureWatcher->result()) {
+ _k_slotDeviceAdded(device);
+ }
+
+ delete futureWatcher;
+ futureWatcher = 0;
+
+ queryRunning = false;
+ }
+
+ Solid::Predicate solidPredicate;
+
+ QFutureWatcher< QSet<QString> >* futureWatcher;
+
+ /* Static */
+ QSet<QString> devicesCache;
+ bool queryRunning;
+
+ KFilePlacesDeviceCache *q;
+};
+
+KFilePlacesDeviceCache::KFilePlacesDeviceCache():
+ QObject(),
+ d(new Private(this))
+{
+ Solid::DeviceNotifier* notifier = Solid::DeviceNotifier::instance();
+ connect(notifier, SIGNAL(deviceAdded(QString)), this, SLOT(_k_slotDeviceAdded(QString)));
+ connect(notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(_k_slotDeviceRemoved(QString)));
+
+ QString predicate("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]"
+ " OR "
+ "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]"
+ " OR "
+ "OpticalDisc.availableContent & 'Audio' ]"
+ " OR "
+ "StorageAccess.ignored == false ]");
+
+ if (KProtocolInfo::isKnownProtocol("mtp")) {
+ predicate.prepend("[");
+ predicate.append(" OR PortableMediaPlayer.supportedProtocols == 'mtp']");
+ }
+
+ d->solidPredicate = Solid::Predicate::fromString(predicate);
+}
+
+KFilePlacesDeviceCache* KFilePlacesDeviceCache::self()
+{
+ static QMutex mutex;
+
+ mutex.lock();
+ if (s_instance == 0) {
+ s_instance = new KFilePlacesDeviceCache();
+ }
+ mutex.unlock();
+
+ return s_instance;
+}
+
+KFilePlacesDeviceCache::~KFilePlacesDeviceCache()
+{
+ Solid::DeviceNotifier* notifier = Solid::DeviceNotifier::instance();
+ disconnect(notifier, SIGNAL(deviceAdded(QString)));
+ disconnect(notifier, SIGNAL(deviceRemoved(QString)));
+
+ delete d;
+}
+
+const Solid::Predicate& KFilePlacesDeviceCache::predicate() const
+{
+ return d->solidPredicate;
+}
+
+const QSet<QString>& KFilePlacesDeviceCache::devices() const
+{
+ kDebug();
+ if (d->devicesCache.isEmpty() && !d->queryRunning) {
+ d->queryRunning = true;
+ d->futureWatcher = new QFutureWatcher< QSet<QString> >;
+ connect(d->futureWatcher, SIGNAL(finished()), this, SLOT(_k_listSolidDevicesFinished()));
+
+ QFuture< QSet<QString> > future = QtConcurrent::run(d, &Private::listSolidDevicesAsync);
+ d->futureWatcher->setFuture(future);
+ }
+
+ return d->devicesCache;
+}
+
+
+
+#include "kfileplacesdevicecache.moc"
diff --git a/kfile/kfileplacesdevicecache.h b/kfile/kfileplacesdevicecache.h
new file mode 100644
index 0000000..7293d03
--- /dev/null
+++ b/kfile/kfileplacesdevicecache.h
@@ -0,0 +1,96 @@
+/*
+ Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFILEPLACESDEVICECACHE_H
+#define KFILEPLACESDEVICECACHE_H
+
+#include <kfile_export.h>
+
+#include <QObject>
+#include <QSet>
+
+#include <solid/predicate.h>
+
+/**
+ * @short Asynchronous cache for Solid devices
+ *
+ * Purpose of this cache is to load Solid devices asynchronously, because
+ * udisks2 backend can take quite a lot of time to enumerate devices, and
+ * since Solid does not have async API, the UI thread is blocked for too long.
+ *
+ * When libsolid2 with asynchronous API is available, this class can go away.
+ *
+ * The cache keeps itself up-to-date and notifies listeners when a
+ * new devices is added or removed.
+ */
+class KFILE_EXPORT KFilePlacesDeviceCache : public QObject
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Returns a global instance of the cache
+ */
+ static KFilePlacesDeviceCache* self();
+
+ /**
+ * Returns list of Solid devices.
+ *
+ * This method always returns immediatelly. When there are no
+ * devices in the cache, it returns an empty list and will asynchronously
+ * query Solid for devices and will notify listeners by emitting
+ * deviceAdded() signal for each devices loaded.
+ */
+ const QSet<QString>& devices() const;
+
+ /**
+ * Returns Solid::Predicate used to obtain devices from Solid
+ */
+ const Solid::Predicate& predicate() const;
+
+Q_SIGNALS:
+ /**
+ * Emitted whenever a new device is discovered.
+ *
+ * @param udi UDI (Universal Device ID) of the newly discovered device
+ */
+ void deviceAdded(const QString& udi);
+
+ /**
+ * Emitted whenever a device is removed from system
+ *
+ * @param udi UDI (Universal Device ID) of the removed device
+ */
+ void deviceRemoved(const QString& udi);
+
+private:
+ Q_PRIVATE_SLOT(d, void _k_listSolidDevicesFinished())
+ Q_PRIVATE_SLOT(d, void _k_slotDeviceAdded(const QString&))
+ Q_PRIVATE_SLOT(d, void _k_slotDeviceRemoved(const QString&))
+
+ class Private;
+ Private * const d;
+ friend class Private;
+
+ explicit KFilePlacesDeviceCache();
+ virtual ~KFilePlacesDeviceCache();
+
+ static KFilePlacesDeviceCache* s_instance;
+};
+
+#endif // KFILEPLACESDEVICECACHE_H
diff --git a/kfile/kfileplacesmodel.cpp b/kfile/kfileplacesmodel.cpp
index 0192926..e0b01c6 100644
--- a/kfile/kfileplacesmodel.cpp
+++ b/kfile/kfileplacesmodel.cpp
@@ -20,6 +20,7 @@
#include "kfileplacesmodel.h"
#include "kfileplacesitem_p.h"
#include "kfileplacessharedbookmarks_p.h"
+#include "kfileplacesdevicecache.h"
#ifdef _WIN32_WCE
#include "Windows.h"
@@ -49,14 +50,12 @@
#include <kio/netaccess.h>
#include <kprotocolinfo.h>
-#include <solid/devicenotifier.h>
#include <solid/storageaccess.h>
#include <solid/storagedrive.h>
#include <solid/storagevolume.h>
#include <solid/opticaldrive.h>
#include <solid/opticaldisc.h>
#include <solid/portablemediaplayer.h>
-#include <solid/predicate.h>
class KFilePlacesModel::Private
{
@@ -74,7 +73,6 @@ public:
QSet<QString> availableDevices;
QMap<QObject*, QPersistentModelIndex> setupInProgress;
- Solid::Predicate predicate;
KBookmarkManager *bookmarkManager;
KFilePlacesSharedBookmarks * sharedBookmarks;
@@ -149,30 +147,12 @@ KFilePlacesModel::KFilePlacesModel(QObject *parent)
// create after, so if we have own places, they are added afterwards, in case of equal priorities
d->sharedBookmarks = new KFilePlacesSharedBookmarks(d->bookmarkManager);
- QString predicate("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]"
- " OR "
- "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]"
- " OR "
- "OpticalDisc.availableContent & 'Audio' ]"
- " OR "
- "StorageAccess.ignored == false ]");
-
- if (KProtocolInfo::isKnownProtocol("mtp")) {
- predicate.prepend("[");
- predicate.append(" OR PortableMediaPlayer.supportedProtocols == 'mtp']");
- }
-
- d->predicate = Solid::Predicate::fromString(predicate);
-
- Q_ASSERT(d->predicate.isValid());
-
connect(d->bookmarkManager, SIGNAL(changed(QString,QString)),
this, SLOT(_k_reloadBookmarks()));
connect(d->bookmarkManager, SIGNAL(bookmarksChanged(QString)),
this, SLOT(_k_reloadBookmarks()));
- d->_k_reloadBookmarks();
- QTimer::singleShot(0, this, SLOT(_k_initDeviceList()));
+ d->_k_initDeviceList();
}
KFilePlacesModel::~KFilePlacesModel()
@@ -313,30 +293,21 @@ QModelIndex KFilePlacesModel::closestItem(const KUrl &url) const
void KFilePlacesModel::Private::_k_initDeviceList()
{
- Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance();
-
- connect(notifier, SIGNAL(deviceAdded(QString)),
+ KFilePlacesDeviceCache *cache = KFilePlacesDeviceCache::self();
+ connect(cache, SIGNAL(deviceAdded(QString)),
q, SLOT(_k_deviceAdded(QString)));
- connect(notifier, SIGNAL(deviceRemoved(QString)),
+ connect(cache, SIGNAL(deviceRemoved(QString)),
q, SLOT(_k_deviceRemoved(QString)));
- const QList<Solid::Device> &deviceList = Solid::Device::listFromQuery(predicate);
-
- foreach(const Solid::Device &device, deviceList) {
- availableDevices << device.udi();
- }
+ availableDevices = cache->devices();
_k_reloadBookmarks();
}
void KFilePlacesModel::Private::_k_deviceAdded(const QString &udi)
{
- Solid::Device d(udi);
-
- if (predicate.matches(d)) {
- availableDevices << udi;
- _k_reloadBookmarks();
- }
+ availableDevices << udi;
+ _k_reloadBookmarks();
}
void KFilePlacesModel::Private::_k_deviceRemoved(const QString &udi)

View File

@ -124,6 +124,9 @@ Patch45: kdelibs-4.7.3-halectomy.patch
# udisks2 Solid backend
Patch47: kdelibs-udisks2-backend.patch
# cache to improve performance of apps using Solid with udisks2 (#868530)
Patch48: kdelibs-udisks2-kfileplacesdevicecache.patch
## upstreamable
# knewstuff2 variant of:
# https://git.reviewboard.kde.org/r/102439/
@ -317,6 +320,7 @@ sed -i -e "s|@@VERSION_RELEASE@@|%{version}-%{release}|" kio/kio/kprotocolmanage
%if "%{?udisks}" == "udisks2"
%patch47 -p1 -b .udisks2backend
%patch48 -p1 -b .kfileplacesdevicescache
%else
%patch45 -p1 -b .halectomy
%endif