• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdelibs API Reference
  • KDE Home
  • Contact Us
 

Nepomuk-Core

  • sources
  • kde-4.12
  • kdelibs
  • nepomuk-core
  • services
  • storage
resourcewatchermanager.cpp
Go to the documentation of this file.
1 /*
2  This file is part of the Nepomuk KDE project.
3  Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com>
4  Copyright (C) 2011-2012 Sebastian Trueg <trueg@kde.org>
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License along
17  with this program; if not, write to the Free Software Foundation, Inc.,
18  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20 
21 
22 #include "resourcewatchermanager.h"
23 #include "resourcewatcherconnection.h"
24 #include "datamanagementmodel.h"
25 #include "typecache.h"
26 
27 #include <Soprano/Statement>
28 #include <Soprano/StatementIterator>
29 #include <Soprano/NodeIterator>
30 #include <Soprano/Vocabulary/RDF>
31 
32 #include <QtDBus/QDBusMessage>
33 
34 #include <KUrl>
35 #include <KDebug>
36 #include <kdbusconnectionpool.h>
37 
38 #include <QtCore/QStringList>
39 #include <QtCore/QSet>
40 
41 
42 using namespace Soprano::Vocabulary;
43 
44 namespace {
45 QVariant nodeToVariant(const Soprano::Node& node) {
46  if(node.isResource()) {
47  return QVariant(node.uri().toString());
48  }
49  else {
50  return QVariant(node.literal().variant());
51  }
52 }
53 
54 template<typename T> QVariantList nodeListToVariantList(const T &nodes) {
55  QVariantList list;
56  list.reserve(nodes.size());
57  foreach( const Soprano::Node &n, nodes ) {
58  list << nodeToVariant(n);
59  }
60 
61  return list;
62 }
63 
64 QString convertUri(const QUrl& uri) {
65  return KUrl(uri).url();
66 }
67 
68 template<typename T> QStringList convertUris(const T& uris) {
69  QStringList sl;
70  foreach(const QUrl& uri, uris)
71  sl << convertUri(uri);
72  return sl;
73 }
74 
75 QUrl convertUri(const QString& s) {
76  return KUrl(s);
77 }
78 
79 QList<QUrl> convertUris(const QStringList& uris) {
80  QList<QUrl> sl;
81  foreach(const QString& uri, uris)
82  sl << convertUri(uri);
83  return sl;
84 }
85 
89 bool hashContainsAtLeastOneOf(Nepomuk2::ResourceWatcherConnection* con, const QSet<QUrl>& candidates, const QMultiHash<QUrl, Nepomuk2::ResourceWatcherConnection*>& hash) {
90  for(QSet<QUrl>::const_iterator it = candidates.constBegin();
91  it != candidates.constEnd(); ++it) {
92  if(hash.contains(*it, con)) {
93  return true;
94  }
95  }
96  return false;
97 }
98 }
99 
100 
101 Nepomuk2::ResourceWatcherManager::ResourceWatcherManager(DataManagementModel* parent)
102  : QObject(parent),
103  m_model(parent),
104  m_mutex( QMutex::Recursive ),
105  m_connectionCount(0)
106 {
107  QDBusConnection con = KDBusConnectionPool::threadConnection();
108  con.registerObject("/resourcewatcher", this, QDBusConnection::ExportScriptableSlots|QDBusConnection::ExportScriptableSignals);
109 }
110 
111 Nepomuk2::ResourceWatcherManager::~ResourceWatcherManager()
112 {
113  // the connections call removeConnection() from their descrutors. Thus,
114  // we need to clean them up before we are deleted ourselves
115  QMutexLocker locker( &m_mutex );
116  QSet<ResourceWatcherConnection*> allConnections
117  = QSet<ResourceWatcherConnection*>::fromList(m_resHash.values())
118  + QSet<ResourceWatcherConnection*>::fromList(m_propHash.values())
119  + QSet<ResourceWatcherConnection*>::fromList(m_typeHash.values())
120  + m_watchAllConnections;
121  qDeleteAll(allConnections);
122 }
123 
124 
125 void Nepomuk2::ResourceWatcherManager::changeProperty(const QUrl &res, const QUrl &property, const QList<Soprano::Node> &addedValues, const QList<Soprano::Node> &removedValues)
126 {
127  QMutexLocker locker( &m_mutex );
128 // kDebug() << res << property << addedValues << removedValues;
129 
130  //
131  // We only need the resource types if any connections are watching types.
132  //
133  QList<QUrl> types;
134  if(!m_typeHash.isEmpty()) {
135  types = m_model->typeCache()->types( res );
136  }
137 
138 
139  //
140  // special case: rdf:type
141  //
142  if(property == RDF::type()) {
143  QSet<QUrl> addedTypes, removedTypes;
144  for(QList<Soprano::Node>::const_iterator it = addedValues.constBegin();
145  it != addedValues.constEnd(); ++it) {
146  addedTypes << it->uri();
147  }
148  for(QList<Soprano::Node>::const_iterator it = removedValues.constBegin();
149  it != removedValues.constEnd(); ++it) {
150  removedTypes << it->uri();
151  }
152  changeTypes(res, types.toSet(), addedTypes, removedTypes);
153  }
154 
155 
156  // first collect all the connections we need to emit the signals for
157  QSet<ResourceWatcherConnection*> connections(m_watchAllConnections);
158 
159  //
160  // Emit signals for all the connections that are only watching specific resources
161  //
162  foreach( ResourceWatcherConnection* con, m_resHash.values( res ) ) {
163  if( m_propHash.contains(property, con) ||
164  !m_propHash.values().contains(con) ) {
165  connections << con;
166  }
167  }
168 
169 
170  //
171  // Emit signals for the connections that are watching specific resources and properties
172  //
173  foreach( ResourceWatcherConnection* con, m_propHash.values( property ) ) {
174  //
175  // Emit for those connections which watch the property and either no
176  // type or once of the types of the resource.
177  // Only query the types if we have any type watching connections.
178  //
179  bool conIsWatchingResType = !m_typeHash.values().contains(con);
180  foreach(const QUrl& type, types) {
181  if(m_typeHash.contains(type, con)) {
182  conIsWatchingResType = true;
183  break;
184  }
185  }
186 
187  if( !m_resHash.values().contains(con) && conIsWatchingResType ) {
188  connections << con;
189  }
190  }
191 
192 
193 
194  //
195  // Emit signals for all connections which watch one of the types of the resource
196  // but no properties (that is handled above).
197  //
198  foreach(const QUrl& type, types) {
199  foreach(ResourceWatcherConnection* con, m_typeHash.values(type)) {
200  if(!m_propHash.values(property).contains(con)) {
201  connections << con;
202  }
203  }
204  }
205 
206 
207  //
208  // Finally emit the signals for all connections
209  //
210  foreach(ResourceWatcherConnection* con, connections) {
211  // make sure we emit from the correct thread through a queued connection
212  QMetaObject::invokeMethod(con,
213  "propertyChanged",
214  Q_ARG(QString, convertUri(res)),
215  Q_ARG(QString, convertUri(property)),
216  Q_ARG(QVariantList, nodeListToVariantList(addedValues)),
217  Q_ARG(QVariantList, nodeListToVariantList(removedValues)));
218  }
219 }
220 
221 void Nepomuk2::ResourceWatcherManager::changeProperty(const QMultiHash< QUrl, Soprano::Node >& oldValues,
222  const QUrl& property,
223  const QList<Soprano::Node>& nodes)
224 {
225  QMutexLocker locker( &m_mutex );
226  QList<QUrl> uniqueKeys = oldValues.keys();
227  foreach( const QUrl resUri, uniqueKeys ) {
228  const QList<Soprano::Node> old = oldValues.values( resUri );
229  changeProperty(resUri, property, old, nodes);
230  }
231 }
232 
233 void Nepomuk2::ResourceWatcherManager::createResource(const QUrl& uri, const QSet<QUrl>& types)
234 {
235  QMutexLocker locker( &m_mutex );
236  QSet<ResourceWatcherConnection*> connections(m_watchAllConnections);
237  foreach(const QUrl& type, types) {
238  foreach(ResourceWatcherConnection* con, m_typeHash.values( type )) {
239  connections += con;
240  }
241  }
242 
243  foreach(ResourceWatcherConnection* con, connections) {
244  // make sure we emit from the correct thread through a queued connection
245  QMetaObject::invokeMethod(con,
246  "resourceCreated",
247  Q_ARG(QString, convertUri(uri)),
248  Q_ARG(QStringList, convertUris(types)));
249  }
250 }
251 
252 void Nepomuk2::ResourceWatcherManager::removeResource(const QUrl &res, const QList<QUrl>& _types)
253 {
254  QMutexLocker locker( &m_mutex );
255  QList<QUrl> types(_types);
256  if(!m_typeHash.isEmpty()) {
257  types = m_model->typeCache()->types( res );
258  }
259 
260  QSet<ResourceWatcherConnection*> connections(m_watchAllConnections);
261  foreach(const QUrl& type, types) {
262  foreach(ResourceWatcherConnection* con, m_typeHash.values( type )) {
263  connections += con;
264  }
265  }
266  foreach(ResourceWatcherConnection* con, m_resHash.values( res )) {
267  connections += con;
268  }
269 
270  foreach(ResourceWatcherConnection* con, connections) {
271  // make sure we emit from the correct thread through a queued connection
272  QMetaObject::invokeMethod(con,
273  "resourceRemoved",
274  Q_ARG(QString, convertUri(res)),
275  Q_ARG(QStringList, convertUris(types)));
276  }
277 }
278 
279 void Nepomuk2::ResourceWatcherManager::changeSomething()
280 {
281  QMutexLocker locker( &m_mutex );
282  // make sure we emit from the correct thread through a queued connection
283  QMetaObject::invokeMethod(this, "somethingChanged");
284 }
285 
286 Nepomuk2::ResourceWatcherConnection* Nepomuk2::ResourceWatcherManager::createConnection(const QList<QUrl> &resources,
287  const QList<QUrl> &properties,
288  const QList<QUrl> &types)
289 {
290  QMutexLocker locker( &m_mutex );
291  kDebug() << resources << properties << types;
292 
293  ResourceWatcherConnection* con = new ResourceWatcherConnection( this );
294  foreach( const QUrl& res, resources ) {
295  m_resHash.insert(res, con);
296  }
297 
298  foreach( const QUrl& prop, properties ) {
299  m_propHash.insert(prop, con);
300  }
301 
302  foreach( const QUrl& type, types ) {
303  m_typeHash.insert(type, con);
304  }
305 
306  if(resources.isEmpty() && properties.isEmpty() && types.isEmpty()) {
307  m_watchAllConnections.insert(con);
308  }
309 
310  return con;
311 }
312 
313 QDBusObjectPath Nepomuk2::ResourceWatcherManager::watch(const QStringList& resources,
314  const QStringList& properties,
315  const QStringList& types)
316 {
317  QMutexLocker locker( &m_mutex );
318  kDebug() << resources << properties << types;
319 
320  if(ResourceWatcherConnection* con = createConnection(convertUris(resources), convertUris(properties), convertUris(types))) {
321  return con->registerDBusObject(message().service(), ++m_connectionCount);
322  }
323  else {
324  QDBusConnection bus = KDBusConnectionPool::threadConnection();
325  bus.send(message().createErrorReply(QDBusError::InvalidArgs, QLatin1String("Failed to create watch for given arguments.")));
326  return QDBusObjectPath();
327  }
328 }
329 
330 namespace {
331  void removeConnectionFromHash( QMultiHash<QUrl, Nepomuk2::ResourceWatcherConnection*> & hash,
332  const Nepomuk2::ResourceWatcherConnection * con )
333  {
334  QMutableHashIterator<QUrl, Nepomuk2::ResourceWatcherConnection*> it( hash );
335  while( it.hasNext() ) {
336  if( it.next().value() == con )
337  it.remove();
338  }
339  }
340 }
341 
342 void Nepomuk2::ResourceWatcherManager::removeConnection(Nepomuk2::ResourceWatcherConnection *con)
343 {
344  QMutexLocker locker( &m_mutex );
345  removeConnectionFromHash( m_resHash, con );
346  removeConnectionFromHash( m_propHash, con );
347  removeConnectionFromHash( m_typeHash, con );
348  m_watchAllConnections.remove(con);
349 }
350 
351 void Nepomuk2::ResourceWatcherManager::setResources(Nepomuk2::ResourceWatcherConnection *conn, const QStringList &resources)
352 {
353  QMutexLocker locker( &m_mutex );
354  const QSet<QUrl> newRes = convertUris(resources).toSet();
355  const QSet<QUrl> oldRes = m_resHash.keys(conn).toSet();
356 
357  foreach(const QUrl& res, newRes - oldRes) {
358  m_resHash.insert(res, conn);
359  }
360  foreach(const QUrl& res, oldRes - newRes) {
361  m_resHash.remove(res, conn);
362  }
363 
364  if(resources.isEmpty()) {
365  if(!m_propHash.values().contains(conn) &&
366  !m_typeHash.values().contains(conn)) {
367  m_watchAllConnections << conn;
368  }
369  }
370  else {
371  m_watchAllConnections.remove(conn);
372  }
373 }
374 
375 void Nepomuk2::ResourceWatcherManager::addResource(Nepomuk2::ResourceWatcherConnection *conn, const QString &resource)
376 {
377  QMutexLocker locker( &m_mutex );
378  m_resHash.insert(convertUri(resource), conn);
379  m_watchAllConnections.remove(conn);
380 }
381 
382 void Nepomuk2::ResourceWatcherManager::removeResource(Nepomuk2::ResourceWatcherConnection *conn, const QString &resource)
383 {
384  QMutexLocker locker( &m_mutex );
385  m_resHash.remove(convertUri(resource), conn);
386  if(!m_resHash.values().contains(conn) &&
387  !m_propHash.values().contains(conn) &&
388  !m_typeHash.values().contains(conn)) {
389  m_watchAllConnections << conn;
390  }
391 }
392 
393 void Nepomuk2::ResourceWatcherManager::setProperties(Nepomuk2::ResourceWatcherConnection *conn, const QStringList &properties)
394 {
395  QMutexLocker locker( &m_mutex );
396  const QSet<QUrl> newprop = convertUris(properties).toSet();
397  const QSet<QUrl> oldprop = m_propHash.keys(conn).toSet();
398 
399  foreach(const QUrl& prop, newprop - oldprop) {
400  m_propHash.insert(prop, conn);
401  }
402  foreach(const QUrl& prop, oldprop - newprop) {
403  m_propHash.remove(prop, conn);
404  }
405 
406  if(properties.isEmpty()) {
407  if(!m_resHash.values().contains(conn) &&
408  !m_typeHash.values().contains(conn)) {
409  m_watchAllConnections << conn;
410  }
411  }
412  else {
413  m_watchAllConnections.remove(conn);
414  }
415 }
416 
417 void Nepomuk2::ResourceWatcherManager::addProperty(Nepomuk2::ResourceWatcherConnection *conn, const QString &property)
418 {
419  QMutexLocker locker( &m_mutex );
420  m_propHash.insert(convertUri(property), conn);
421  m_watchAllConnections.remove(conn);
422 }
423 
424 void Nepomuk2::ResourceWatcherManager::removeProperty(Nepomuk2::ResourceWatcherConnection *conn, const QString &property)
425 {
426  QMutexLocker locker( &m_mutex );
427  m_propHash.remove(convertUri(property), conn);
428  if(!m_resHash.values().contains(conn) &&
429  !m_propHash.values().contains(conn) &&
430  !m_typeHash.values().contains(conn)) {
431  m_watchAllConnections << conn;
432  }
433 }
434 
435 void Nepomuk2::ResourceWatcherManager::setTypes(Nepomuk2::ResourceWatcherConnection *conn, const QStringList &types)
436 {
437  QMutexLocker locker( &m_mutex );
438  const QSet<QUrl> newtype = convertUris(types).toSet();
439  const QSet<QUrl> oldtype = m_typeHash.keys(conn).toSet();
440 
441  foreach(const QUrl& type, newtype - oldtype) {
442  m_typeHash.insert(type, conn);
443  }
444  foreach(const QUrl& type, oldtype - newtype) {
445  m_typeHash.remove(type, conn);
446  }
447 
448  if(types.isEmpty()) {
449  if(!m_resHash.values().contains(conn) &&
450  !m_propHash.values().contains(conn)) {
451  m_watchAllConnections << conn;
452  }
453  }
454  else {
455  m_watchAllConnections.remove(conn);
456  }
457 }
458 
459 void Nepomuk2::ResourceWatcherManager::addType(Nepomuk2::ResourceWatcherConnection *conn, const QString &type)
460 {
461  QMutexLocker locker( &m_mutex );
462  m_typeHash.insert(convertUri(type), conn);
463  m_watchAllConnections.remove(conn);
464 }
465 
466 void Nepomuk2::ResourceWatcherManager::removeType(Nepomuk2::ResourceWatcherConnection *conn, const QString &type)
467 {
468  QMutexLocker locker( &m_mutex );
469  m_typeHash.remove(convertUri(type), conn);
470  if(!m_resHash.values().contains(conn) &&
471  !m_propHash.values().contains(conn) &&
472  !m_typeHash.values().contains(conn)) {
473  m_watchAllConnections << conn;
474  }
475 }
476 
477 // FIXME: also take super-classes into account
478 void Nepomuk2::ResourceWatcherManager::changeTypes(const QUrl &res, const QSet<QUrl>& resTypes, const QSet<QUrl> &addedTypes, const QSet<QUrl> &removedTypes)
479 {
480  QMutexLocker locker( &m_mutex );
481  // first collect all the connections we need to emit the signals for
482  QSet<ResourceWatcherConnection*> addConnections(m_watchAllConnections), removeConnections(m_watchAllConnections);
483 
484  // all connections watching the resource and not a special property
485  // and no special type or one of the changed types
486  foreach( ResourceWatcherConnection* con, m_resHash.values( res ) ) {
487  if( m_propHash.contains(RDF::type(), con) ||
488  !m_propHash.values().contains(con) ) {
489  if(!addedTypes.isEmpty() &&
490  connectionWatchesOneType(con, addedTypes)) {
491  addConnections << con;
492  }
493  if(!removedTypes.isEmpty() &&
494  connectionWatchesOneType(con, removedTypes)) {
495  removeConnections << con;
496  }
497  }
498  }
499 
500  // all connections watching one of the types and no special resource or property
501  if(!addedTypes.isEmpty()) {
502  foreach(const QUrl& type, addedTypes + resTypes) {
503  foreach(ResourceWatcherConnection* con, m_typeHash.values(type)) {
504  if(!m_resHash.values().contains(con) &&
505  !m_propHash.values().contains(con)) {
506  addConnections << con;
507  }
508  }
509  }
510  }
511  if(!removedTypes.isEmpty()) {
512  foreach(const QUrl& type, removedTypes + resTypes) {
513  foreach(ResourceWatcherConnection* con, m_typeHash.values(type)) {
514  if(!m_resHash.values().contains(con) &&
515  !m_propHash.values().contains(con)) {
516  removeConnections << con;
517  }
518  }
519  }
520  }
521 
522  // all connections watching rdf:type
523  foreach(ResourceWatcherConnection* con, m_propHash.values(RDF::type())) {
524  if(!m_resHash.values().contains(con) ) {
525  if(connectionWatchesOneType(con, addedTypes + resTypes)) {
526  addConnections << con;
527  }
528  if(connectionWatchesOneType(con, removedTypes + resTypes)) {
529  removeConnections << con;
530  }
531  }
532  }
533 
534  // finally emit the actual signals
535  if(!addedTypes.isEmpty()) {
536  foreach(ResourceWatcherConnection* con, addConnections) {
537  // make sure we emit from the correct thread through a queued connection
538  QMetaObject::invokeMethod(con,
539  "resourceTypesAdded",
540  Q_ARG(QString, convertUri(res)),
541  Q_ARG(QStringList, convertUris(addedTypes)));
542  }
543  }
544  if(!removedTypes.isEmpty()) {
545  foreach(ResourceWatcherConnection* con, removeConnections) {
546  // make sure we emit from the correct thread through a queued connection
547  QMetaObject::invokeMethod(con,
548  "resourceTypesRemoved",
549  Q_ARG(QString, convertUri(res)),
550  Q_ARG(QStringList, convertUris(removedTypes)));
551  }
552  }
553 }
554 
555 bool Nepomuk2::ResourceWatcherManager::connectionWatchesOneType(Nepomuk2::ResourceWatcherConnection *con, const QSet<QUrl> &types) const
556 {
557  QMutexLocker locker( &m_mutex );
558  return !m_typeHash.values().contains(con) || hashContainsAtLeastOneOf(con, types, m_typeHash);
559 }
560 
561 #include "resourcewatchermanager.moc"
QMultiHash
Nepomuk2::ResourceWatcherConnection
Definition: resourcewatcherconnection.h:34
Nepomuk2::ResourceWatcherManager::createConnection
ResourceWatcherConnection * createConnection(const QList< QUrl > &resources, const QList< QUrl > &properties, const QList< QUrl > &types)
Used internally by watch() and by the unit tests to create watcher connections.
Definition: resourcewatchermanager.cpp:286
Nepomuk2::ResourceWatcherConnection::registerDBusObject
QDBusObjectPath registerDBusObject(const QString &dbusClient, int id)
Definition: resourcewatcherconnection.cpp:41
Nepomuk2::ResourceWatcherManager::~ResourceWatcherManager
~ResourceWatcherManager()
Definition: resourcewatchermanager.cpp:111
Nepomuk2::ResourceWatcherManager::changeSomething
void changeSomething()
to be called whenever something changes (preferably after calling any of the above) ...
Definition: resourcewatchermanager.cpp:279
QObject
resourcewatchermanager.h
Nepomuk2::ResourceWatcherManager::watch
Q_SCRIPTABLE QDBusObjectPath watch(const QStringList &resources, const QStringList &properties, const QStringList &types)
The main DBus methods exposed by the ResourceWatcher.
Definition: resourcewatchermanager.cpp:313
typecache.h
Nepomuk2::ResourceWatcherManager::createResource
void createResource(const QUrl &uri, const QSet< QUrl > &types)
Definition: resourcewatchermanager.cpp:233
Nepomuk2::ResourceWatcherManager::removeResource
void removeResource(const QUrl &uri, const QList< QUrl > &types)
Definition: resourcewatchermanager.cpp:252
Nepomuk2::DBus::convertUri
QString convertUri(const QUrl &uri)
Definition: dbustypes.cpp:33
Nepomuk2::ResourceWatcherManager::ResourceWatcherManager
ResourceWatcherManager(DataManagementModel *parent=0)
Definition: resourcewatchermanager.cpp:101
Nepomuk2::ResourceWatcherManager::changeProperty
void changeProperty(const QUrl &res, const QUrl &property, const QList< Soprano::Node > &addedValues, const QList< Soprano::Node > &removedValues)
Definition: resourcewatchermanager.cpp:125
datamanagementmodel.h
resourcewatcherconnection.h
Nepomuk2::DataManagementModel
Definition: datamanagementmodel.h:39
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:48:09 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

Nepomuk-Core

Skip menu "Nepomuk-Core"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Modules
  • Related Pages

kdelibs API Reference

Skip menu "kdelibs API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  • kjsembed
  •   WTF
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Nepomuk-Core
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal