• 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
datamanagementmodel.cpp
Go to the documentation of this file.
1 /*
2  This file is part of the Nepomuk KDE project.
3  Copyright (C) 2010-2012 Sebastian Trueg <trueg@kde.org>
4  Copyright (C) 2011-2013 Vishesh Handa <handa.vish@gmail.com>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Lesser General Public
8  License as published by the Free Software Foundation; either
9  version 2.1 of the License, or (at your option) version 3, or any
10  later version accepted by the membership of KDE e.V. (or its
11  successor approved by the membership of KDE e.V.), which shall
12  act as a proxy defined in Section 6 of version 3 of the license.
13 
14  This library is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  Lesser General Public License for more details.
18 
19  You should have received a copy of the GNU Lesser General Public
20  License along with this library. If not, see <http://www.gnu.org/licenses/>.
21 */
22 
23 #include "datamanagement.h"
24 #include "datamanagementmodel.h"
25 #include "classandpropertytree.h"
26 #include "resourcemerger.h"
27 #include "resourceidentifier.h"
28 #include "simpleresourcegraph.h"
29 #include "simpleresource.h"
30 #include "resourcewatchermanager.h"
31 #include "syncresource.h"
32 #include "nepomuktools.h"
33 #include "typecache.h"
34 
35 #include <Soprano/Vocabulary/NRL>
36 #include <Soprano/Vocabulary/NAO>
37 #include <Soprano/Vocabulary/RDF>
38 #include <Soprano/Vocabulary/RDFS>
39 
40 #include <Soprano/Graph>
41 #include <Soprano/QueryResultIterator>
42 #include <Soprano/StatementIterator>
43 #include <Soprano/NodeIterator>
44 #include <Soprano/Error/ErrorCode>
45 #include <Soprano/Parser>
46 #include <Soprano/Serializer>
47 #include <Soprano/PluginManager>
48 #include <Soprano/Util/SimpleStatementIterator>
49 
50 #include <QtCore/QHash>
51 #include <QtCore/QCache>
52 #include <QtCore/QUrl>
53 #include <QtCore/QVariant>
54 #include <QtCore/QDateTime>
55 #include <QtCore/QUuid>
56 #include <QtCore/QSet>
57 #include <QtCore/QPair>
58 #include <QtCore/QFileInfo>
59 
60 #include "nie.h"
61 #include "nfo.h"
62 #include "pimo.h"
63 
64 #include <KDebug>
65 #include <KService>
66 #include <KServiceTypeTrader>
67 #include <KProtocolInfo>
68 
69 #include <KIO/NetAccess>
70 
71 #define STRIGI_INDEX_GRAPH_FOR "http://www.strigi.org/fields#indexGraphFor"
72 
73 using namespace Nepomuk2::Vocabulary;
74 using namespace Soprano::Vocabulary;
75 
79 
80 namespace {
81 
82  QStringList nodesToN3(const QSet<Soprano::Node>& nodes) {
83  QStringList n3;
84  Q_FOREACH(const Soprano::Node& node, nodes) {
85  n3 << node.toN3();
86  }
87  return n3;
88  }
89 
90  QStringList nodesToN3(const QList<Soprano::Node>& nodes) {
91  QStringList n3;
92  Q_FOREACH(const Soprano::Node& node, nodes) {
93  n3 << node.toN3();
94  }
95  return n3;
96  }
97 
98  QStringList urlListToN3(const QList<QUrl>& uris) {
99  QStringList n3;
100  Q_FOREACH(const QUrl& uri, uris) {
101  n3 << Soprano::Node::resourceToN3(uri);
102  }
103  return n3;
104  }
105 
106  QStringList urlSetToN3(const QSet<QUrl>& uris) {
107  QStringList n3;
108  Q_FOREACH(const QUrl& uri, uris) {
109  n3 << Soprano::Node::resourceToN3(uri);
110  }
111  return n3;
112  }
113 
114  template<typename T> QString createResourceFilter(const T& resources, const QString& var, bool exclude = true) {
115  QString filter = QString::fromLatin1("%1 in (%2)").arg(var, Nepomuk2::resourcesToN3(resources).join(QLatin1String(",")));
116  if(exclude) {
117  filter = QString::fromLatin1("!(%1)").arg(filter);
118  }
119  return filter;
120  }
121 
122  /*
123  * Creates a filter (without the "FILTER" keyword) which either excludes the \p propVar
124  * as once of the metadata properties or forces it to be one.
125  */
126  QString createResourceMetadataPropertyFilter(const QString& propVar, bool exclude = true) {
127  return createResourceFilter(QList<QUrl>()
128  << NAO::created()
129  << NAO::lastModified()
130  << NAO::creator()
131  << NAO::userVisible()
132  << NIE::url(),
133  propVar,
134  exclude);
135  }
136 
137  enum UriState {
139  ExistingFileUrl,
141  NonExistingFileUrl,
143  SupportedUrl,
145  NepomukUri,
147  BlankUri,
149  OntologyUri,
151  OtherUri
152  };
153 
155  inline UriState uriState(const QUrl& uri, bool statLocalFiles = true) {
156  if(uri.scheme() == QLatin1String("nepomuk")) {
157  return NepomukUri;
158  }
159  else if(uri.scheme() == QLatin1String("file")) {
160  if(!statLocalFiles ||
161  QFile::exists(uri.toLocalFile())) {
162  return ExistingFileUrl;
163  }
164  else {
165  return NonExistingFileUrl;
166  }
167  }
168  else if(Nepomuk2::ClassAndPropertyTree::self()->contains(uri)) {
169  return OntologyUri;
170  }
171  else if(uri.toString().startsWith("_:") ) {
172  return BlankUri;
173  }
174  // if supported by kio
175  else if( KProtocolInfo::isKnownProtocol(uri) ) {
176  return SupportedUrl;
177  }
178  else {
179  return OtherUri;
180  }
181  }
182 
183  inline Soprano::Node convertIfBlankUri(const QUrl &uri) {
184  if( uri.toString().startsWith(QLatin1String("_:")) )
185  return Soprano::Node( uri.toString().mid(2) );
186  else
187  return Soprano::Node( uri );
188  }
189 }
190 
191 class Nepomuk2::DataManagementModel::Private
192 {
193 public:
194  ClassAndPropertyTree* m_classAndPropertyTree;
195  ResourceWatcherManager* m_watchManager;
196 
198  QSet<QUrl> m_protectedProperties;
199 
200  QCache<QString, QUrl> m_appCache;
201  QMutex m_appCacheMutex;
202 
203  typedef QPair<QString, bool> GraphCacheItem;
204  QCache<GraphCacheItem, QUrl> m_graphCache;
205  QMutex m_graphCacheMutex;
206 
207  TypeCache* m_typeCache;
208  QUrl m_nepomukGraph;
209 };
210 
211 Nepomuk2::DataManagementModel::DataManagementModel(Nepomuk2::ClassAndPropertyTree* tree, Soprano::Model* model, QObject *parent)
212  : Soprano::FilterModel(model),
213  d(new Private())
214 {
215  d->m_classAndPropertyTree = tree;
216  d->m_watchManager = new ResourceWatcherManager(this);
217  d->m_typeCache = new TypeCache(this);
218  d->m_appCache.setMaxCost( 10 );
219 
220  setParent(parent);
221 
222  // meta data properties are protected. This means they cannot be removed. But they
223  // can be set.
224  d->m_protectedProperties.insert(NAO::created());
225  d->m_protectedProperties.insert(NAO::creator());
226  d->m_protectedProperties.insert(NAO::lastModified());
227  d->m_protectedProperties.insert(NAO::userVisible());
228  d->m_protectedProperties.insert(NIE::url());
229 
230  // Create a "Nepomuk" application graph
231  d->m_nepomukGraph = fetchGraph(QLatin1String("nepomuk"));
232 
233  // Specially add <nepomuk:/me> cause the clients cannot
234  // TODO: Add the fullname, email and other details
235  if( !containsAnyStatement( QUrl("nepomuk:/me"), Soprano::Node(), Soprano::Node() ) ) {
236  addStatement( QUrl("nepomuk:/me"), RDF::type(), PIMO::Person(), d->m_nepomukGraph );
237  }
238 
239  // Enable auto-commit after each statement change
240  // This is required because multiple parallel calls to storeResources and removeResources
241  // can often cause transaction failures due to deadlocks in virtuoso
242  // function documentation - http://docs.openlinksw.com/virtuoso/fn_log_enable.html
243  QLatin1String command("log_enable( 3 )");
244  executeQuery( command, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
245 }
246 
247 void Nepomuk2::DataManagementModel::clearCache()
248 {
249  QMutexLocker lock2( &d->m_graphCacheMutex );
250  d->m_graphCache.clear();
251  lock2.unlock();
252 
253  QMutexLocker lock( &d->m_appCacheMutex );
254  d->m_appCache.clear();
255  lock.unlock();
256 
257  d->m_typeCache->clear();
258  d->m_nepomukGraph = fetchGraph(QLatin1String("nepomuk"));
259 
260  // Specially add <nepomuk:/me> cause the clients cannot
261  // TODO: Add the fullname, email and other details
262  if( !containsAnyStatement( QUrl("nepomuk:/me"), Soprano::Node(), Soprano::Node() ) ) {
263  addStatement( QUrl("nepomuk:/me"), RDF::type(), PIMO::Person(), d->m_nepomukGraph );
264  }
265 }
266 
267 
268 Nepomuk2::DataManagementModel::~DataManagementModel()
269 {
270  delete d->m_typeCache;
271  delete d;
272 }
273 
274 
275 Soprano::Error::ErrorCode Nepomuk2::DataManagementModel::updateModificationDate(const QUrl& resource,
276  const QDateTime& date)
277 {
278  return updateModificationDate(QSet<QUrl>() << resource, date);
279 
280 }
281 
282 Soprano::Error::ErrorCode Nepomuk2::DataManagementModel::updateModificationDate(const QSet<QUrl>& resources,
283  const QDateTime& date)
284 {
285  if(resources.isEmpty()) {
286  return Soprano::Error::ErrorNone;
287  }
288 
289  // FIXME: It would be awesome if we could use 'using graph delete {} insert {} where {}'
290  // But virtuoso does not seem to insert the new statement if old one does not exist
291  // which might actually be okay, but lets stick with 2 statements for now
292  //
293  const QString graphN3 = Soprano::Node::resourceToN3(d->m_nepomukGraph);
294  const QStringList allResN3 = urlSetToN3( resources );
295 
296  // Do them 30 resources at a time, we do not want to send too large queries
297  for(int i=0; i<allResN3.size(); i+=30) {
298  const QStringList resN3 = allResN3.mid( i, 30 );
299 
300  QString delQ = QString::fromLatin1("sparql DELETE from %1 { ?res nao:lastModified ?mod . } "
301  "WHERE { ?res nao:lastModified ?mod . "
302  " FILTER(?res in (%3)) ."
303  "}")
304  .arg( graphN3, resN3.join(",") );
305 
306  executeQuery( delQ, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
307 
308  const QString dt = Soprano::Node::literalToN3(date);
309 
310  QString iq = QString::fromLatin1("sparql insert into %1 {").arg( graphN3 );
311  foreach(const QString& res, resN3) {
312  iq += QString::fromLatin1(" %1 nao:lastModified %2 .").arg( res, dt );
313  }
314  iq += QLatin1String("} ");
315 
316  executeQuery( iq, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
317  }
318  return Soprano::Error::ErrorNone;
319 }
320 
321 void Nepomuk2::DataManagementModel::addProperty(const QList<QUrl> &resources, const QUrl &property, const QVariantList &values, const QString &app)
322 {
323  //
324  // Check parameters
325  //
326  if(app.isEmpty()) {
327  setError(QLatin1String("addProperty: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument);
328  return;
329  }
330  if(resources.isEmpty()) {
331  setError(QLatin1String("addProperty: No resource specified."), Soprano::Error::ErrorInvalidArgument);
332  return;
333  }
334  foreach( const QUrl & res, resources ) {
335  if(res.isEmpty()) {
336  setError(QLatin1String("addProperty: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument);
337  return;
338  }
339  }
340  if(property.isEmpty()) {
341  setError(QLatin1String("addProperty: Property needs to be specified."), Soprano::Error::ErrorInvalidArgument);
342  return;
343  }
344  if(values.isEmpty()) {
345  setError(QLatin1String("addProperty: Values needs to be specified."), Soprano::Error::ErrorInvalidArgument);
346  return;
347  }
348  if(d->m_protectedProperties.contains(property)) {
349  setError(QString::fromLatin1("addProperty: %1 is a protected property which can only be set.").arg(property.toString()),
350  Soprano::Error::ErrorInvalidArgument);
351  return;
352  }
353 
354 
355  //
356  // Convert all values to RDF nodes. This includes the property range check and conversion of local file paths to file URLs
357  //
358  const QSet<Soprano::Node> nodes = d->m_classAndPropertyTree->variantListToNodeSet(values, property);
359  if(nodes.isEmpty()) {
360  setError(d->m_classAndPropertyTree->lastError());
361  return;
362  }
363 
364  clearError();
365 
366 
367  //
368  // Hash to keep mapping from provided URL/URI to resource URIs
369  //
370  QList<Soprano::Node> resolvedNodes;
371 
372 
373  if(property == NIE::url()) {
374  if(resources.count() != 1) {
375  setError(QLatin1String("addProperty: no two resources can have the same nie:url."), Soprano::Error::ErrorInvalidArgument);
376  return;
377  }
378  else if(nodes.count() > 1) {
379  setError(QLatin1String("addProperty: One resource can only have one nie:url."), Soprano::Error::ErrorInvalidArgument);
380  return;
381  }
382 
383  if(!nodes.isEmpty()) {
384  // check if another resource already uses the URL - no two resources can have the same URL at the same time
385  // CAUTION: There is one theoretical situation in which this breaks (more than this actually):
386  // A file is moved and before the nie:url is updated data is added to the file in the new location.
387  // At this point the file is there twice and the data should ideally be merged. But how to decide that
388  // and how to distiguish between that situation and a file overwrite?
389  if(containsAnyStatement(Soprano::Node(), NIE::url(), *nodes.constBegin())) {
390  setError(QLatin1String("addProperty: No two resources can have the same nie:url at the same time."), Soprano::Error::ErrorInvalidArgument);
391  return;
392  }
393  else if(containsAnyStatement(resources.first(), NIE::url(), Soprano::Node())) {
394  // TODO: this can be removed as soon as nie:url gets a max cardinality of 1
395  // vHanda: nie:url as of KDE 4.6 has a max cardinality of 1. This is due to the
396  // nepomuk resource identification ontology
397  setError(QLatin1String("addProperty: One resource can only have one nie:url."), Soprano::Error::ErrorInvalidArgument);
398  return;
399  }
400 
401  // nie:url is the only property for which we do not want to resolve URLs
402  resolvedNodes << *nodes.constBegin();
403  }
404  }
405  else {
406  resolvedNodes = resolveNodes(nodes, app);
407  if(lastError()) {
408  return;
409  }
410  }
411 
412 
413  //
414  // Resolve local file URLs (we need to hash the values since we do not want to write anything yet)
415  //
416  QList<QUrl> resolvedUris = resolveUrls(resources, app);
417  if(lastError()) {
418  return;
419  }
420 
421 
422  //
423  // Check cardinality conditions
424  //
425  const int maxCardinality = d->m_classAndPropertyTree->maxCardinality(property);
426  if( maxCardinality == 1 ) {
427  // check if any of the resources already has a value set which differs from the one we want to add
428 
429  // an empty hashed value means that the resource for a file URL does not exist yet. Thus, there is
430  // no need to filter it out. We basically only need to check if any value exists.
431  QString valueFilter;
432  if(resolvedNodes.first().isValid()) {
433  valueFilter = QString::fromLatin1("FILTER(?v!=%3) . ")
434  .arg(resolvedNodes.first().toN3());
435  }
436 
437  QStringList terms;
438  Q_FOREACH(const QUrl& res, resolvedUris) {
439  terms << QString::fromLatin1("%1 %2 ?v . %3")
440  .arg(Soprano::Node::resourceToN3(res),
441  Soprano::Node::resourceToN3(property),
442  valueFilter);
443  }
444 
445  const QString cardinalityQuery = QString::fromLatin1("ask where { { %1 } }")
446  .arg(terms.join(QLatin1String("} UNION {")));
447 
448  if(executeQuery(cardinalityQuery, Soprano::Query::QueryLanguageSparql).boolValue()) {
449  setError(QString::fromLatin1("%1 has cardinality of 1. At least one of the resources already has a value set that differs from the new one.")
450  .arg(Soprano::Node::resourceToN3(property)), Soprano::Error::ErrorInvalidArgument);
451  return;
452  }
453  }
454 
455 
456  //
457  // Do the actual work
458  //
459  addProperty(resolvedUris, property, resolvedNodes, app, true /* signal propertyChanged */);
460 }
461 
462 
463 // setting a property can be implemented by way of addProperty. All we have to do before calling addProperty is to remove all
464 // the values that are not in the setProperty call
465 void Nepomuk2::DataManagementModel::setProperty(const QList<QUrl> &resources, const QUrl &property, const QVariantList &values, const QString &app)
466 {
467  //
468  // Special case: setting to the empty list
469  //
470  if(values.isEmpty()) {
471  removeProperties(resources, QList<QUrl>() << property, app);
472  return;
473  }
474 
475  //
476  // Check parameters
477  //
478  if(app.isEmpty()) {
479  setError(QLatin1String("setProperty: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument);
480  return;
481  }
482  if(resources.isEmpty()) {
483  setError(QLatin1String("setProperty: No resource specified."), Soprano::Error::ErrorInvalidArgument);
484  return;
485  }
486  foreach( const QUrl & res, resources ) {
487  if(res.isEmpty()) {
488  setError(QLatin1String("setProperty: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument);
489  return;
490  }
491  }
492  if(property.isEmpty()) {
493  setError(QLatin1String("setProperty: Property needs to be specified."), Soprano::Error::ErrorInvalidArgument);
494  return;
495  }
496 
497 
498  //
499  // Convert all values to RDF nodes. This includes the property range check and conversion of local file paths to file URLs
500  //
501  const QSet<Soprano::Node> nodes = d->m_classAndPropertyTree->variantListToNodeSet(values, property);
502  if(nodes.isEmpty()) {
503  setError(d->m_classAndPropertyTree->lastError());
504  return;
505  }
506 
507  clearError();
508 
509 
510  //
511  // Hash to keep mapping from provided URL/URI to resource URIs
512  //
513  QList<Soprano::Node> resolvedNodes;
514 
515  //
516  // Setting nie:url on file resources also means updating nfo:fileName and possibly nie:isPartOf
517  //
518  if(property == NIE::url()) {
519  if(resources.count() != 1) {
520  setError(QLatin1String("setProperty: no two resources can have the same nie:url."), Soprano::Error::ErrorInvalidArgument);
521  return;
522  }
523  else if(nodes.count() > 1) {
524  setError(QLatin1String("setProperty: One resource can only have one nie:url."), Soprano::Error::ErrorInvalidArgument);
525  return;
526  }
527 
528  // check if another resource already uses the URL - no two resources can have the same URL at the same time
529  // CAUTION: There is one theoretical situation in which this breaks (more than this actually):
530  // A file is moved and before the nie:url is updated data is added to the file in the new location.
531  // At this point the file is there twice and the data should ideally be merged. But how to decide that
532  // and how to distiguish between that situation and a file overwrite?
533  QString query = QString::fromLatin1("select ?r where { ?r nie:url %1 . }")
534  .arg( nodes.constBegin()->toN3() );
535 
536  Soprano::QueryResultIterator it = executeQuery( query, Soprano::Query::QueryLanguageSparqlNoInference );
537  if( it.next() ) {
538  if( it[0] != resources.first() ) {
539  setError(QLatin1String("setProperty: No two resources can have the same nie:url at the same time."), Soprano::Error::ErrorInvalidArgument);
540  return;
541  }
542  }
543 
544  if(updateNieUrlOnLocalFile(resources.first(), nodes.constBegin()->uri())) {
545  return;
546  }
547 
548  // nie:url is the only property for which we do not want to resolve URLs
549  resolvedNodes << *nodes.constBegin();
550  }
551  else {
552  resolvedNodes = resolveNodes(nodes, app);
553  if(lastError()) {
554  return;
555  }
556  }
557 
558  //
559  // Resolve local file URLs
560  //
561  QList<QUrl> resolvedUris = resolveUrls(resources, app);
562  if(lastError()) {
563  return;
564  }
565 
566  //
567  // Remove values that are not wanted anymore
568  //
569  const QStringList uriHashN3 = urlListToN3(resolvedUris);
570  if(!uriHashN3.isEmpty()) {
571  QHash<QUrl, QList<Soprano::Node> > valuesToRemove;
572  const QSet<Soprano::Node> setValues = QSet<Soprano::Node>::fromList(resolvedNodes);
573  QList<Soprano::BindingSet> existing
574  = executeQuery(QString::fromLatin1("select ?r ?v where { ?r %1 ?v . FILTER(?r in (%2)) . }")
575  .arg(Soprano::Node::resourceToN3(property),
576  uriHashN3.join(QLatin1String(","))),
577  Soprano::Query::QueryLanguageSparql).allBindings();
578  Q_FOREACH(const Soprano::BindingSet& binding, existing) {
579  if(!setValues.contains(binding["v"])) {
580  removeAllStatements(binding["r"], property, binding["v"]);
581  valuesToRemove[binding["r"].uri()].append(binding["v"]);
582  }
583  }
584 
585  // construct the lists of old and new values
586  QHash<QUrl, QList<Soprano::Node> > addedValues;
587 
588  //
589  // And finally add the rest of the statements (only if there is anything to add)
590  //
591  if(!nodes.isEmpty()) {
592  addedValues = addProperty(resolvedUris, property, resolvedNodes, app);
593  }
594 
595  //
596  // Inform interested parties about the changes
597  //
598  foreach(const QUrl& res, resolvedUris) {
599  const QList<Soprano::Node> added = addedValues.value(res);
600  const QList<Soprano::Node> removed = valuesToRemove.value(res);
601  if(!added.isEmpty() || !removed.isEmpty()) {
602  d->m_watchManager->changeProperty(res,
603  property,
604  added,
605  removed);
606  }
607  }
608  if(!resolvedUris.isEmpty()) {
609  d->m_watchManager->changeSomething();
610  }
611  }
612  else {
613  addProperty(resolvedUris, property, resolvedNodes, app, true);
614  }
615 }
616 
617 void Nepomuk2::DataManagementModel::removeProperty(const QList<QUrl> &resources, const QUrl &property, const QVariantList &values, const QString &app)
618 {
619  // 1. remove the triples
620  // 2. remove trailing graphs
621  // 3. update resource mtime only if we actually change anything on the resource
622 
623  //
624  // Check arguments
625  //
626  if(app.isEmpty()) {
627  setError(QLatin1String("removeProperty: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument);
628  return;
629  }
630  if(resources.isEmpty()) {
631  setError(QLatin1String("removeProperty: No resource specified."), Soprano::Error::ErrorInvalidArgument);
632  return;
633  }
634  foreach( const QUrl & res, resources ) {
635  if(res.isEmpty()) {
636  setError(QLatin1String("removeProperty: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument);
637  return;
638  }
639  }
640  if(property.isEmpty()) {
641  setError(QLatin1String("removeProperty: Property needs to be specified."), Soprano::Error::ErrorInvalidArgument);
642  return;
643  }
644  if(values.isEmpty()) {
645  setError(QLatin1String("removeProperty: Values needs to be specified."), Soprano::Error::ErrorInvalidArgument);
646  return;
647  }
648  if(d->m_protectedProperties.contains(property)) {
649  setError(QString::fromLatin1("removeProperty: %1 is a protected property which can only be changed by the data management service itself.").arg(property.toString()),
650  Soprano::Error::ErrorInvalidArgument);
651  return;
652  }
653 
654  const QSet<Soprano::Node> valueNodes = d->m_classAndPropertyTree->variantListToNodeSet(values, property);
655  if(valueNodes.isEmpty()) {
656  setError(d->m_classAndPropertyTree->lastError());
657  return;
658  }
659 
660 
661  clearError();
662 
663 
664  //
665  // Resolve file URLs
666  //
667  QSet<QUrl> resolvedResources = QSet<QUrl>::fromList(resolveUrls(resources, app, false));
668  if(resolvedResources.isEmpty() || lastError()) {
669  return;
670  }
671 
672  QList<Soprano::Node> resolvedNodes = resolveNodes(valueNodes, app);
673  if(resolvedNodes.isEmpty() || lastError()) {
674  return;
675  }
676 
677  //
678  // Actually change data
679  //
680  foreach( const QUrl & res, resolvedResources ) {
681  QString query = QString::fromLatin1("select distinct ?v where { %1 %2 ?v . FILTER(?v in (%3)). }")
682  .arg( Soprano::Node::resourceToN3(res),
683  Soprano::Node::resourceToN3(property),
684  nodesToN3(resolvedNodes).join(QLatin1String(",")) );
685 
686  const QList<Soprano::BindingSet> valueGraphs
687  = executeQuery( query, Soprano::Query::QueryLanguageSparqlNoInference ).allBindings();
688 
689  QList<Soprano::Node> removedValues;
690  foreach(const Soprano::BindingSet& binding, valueGraphs) {
691  const Soprano::Node v = binding["v"];
692  removeAllStatements( res, property, v );
693  removedValues << v;
694  }
695 
696  if(!removedValues.isEmpty()) {
697  d->m_watchManager->changeProperty( res, property, QList<Soprano::Node>(), removedValues );
698  }
699 
700  // we only update the mtime in case we actually remove anything
701  if(!valueGraphs.isEmpty()) {
702  // If the resource is now empty we remove it completely
703  if(!doesResourceExist(res)) {
704  removeResources(QList<QUrl>() << res, NoRemovalFlags, app);
705  }
706  else {
707  updateModificationDate(res);
708  }
709  }
710  }
711 }
712 
713 void Nepomuk2::DataManagementModel::removeProperties(const QList<QUrl> &resources, const QList<QUrl> &properties, const QString &app)
714 {
715  // 1. remove the triples
716  // 2. remove trailing graphs
717  // 3. update resource mtime
718 
719  //
720  // Check arguments
721  //
722  if(app.isEmpty()) {
723  setError(QLatin1String("removeProperties: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument);
724  return;
725  }
726  if(resources.isEmpty()) {
727  setError(QLatin1String("removeProperties: No resource specified."), Soprano::Error::ErrorInvalidArgument);
728  return;
729  }
730  foreach( const QUrl & res, resources ) {
731  if(res.isEmpty()) {
732  setError(QLatin1String("removeProperties: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument);
733  return;
734  }
735  }
736  if(properties.isEmpty()) {
737  setError(QLatin1String("removeProperties: No properties specified."), Soprano::Error::ErrorInvalidArgument);
738  return;
739  }
740  foreach(const QUrl& property, properties) {
741  if(property.isEmpty()) {
742  setError(QLatin1String("removeProperties: Encountered empty property URI."), Soprano::Error::ErrorInvalidArgument);
743  return;
744  }
745  else if(d->m_protectedProperties.contains(property)) {
746  setError(QString::fromLatin1("removeProperties: %1 is a protected property which can only be changed by the data management service itself.").arg(property.toString()),
747  Soprano::Error::ErrorInvalidArgument);
748  return;
749  }
750  }
751 
752 
753  clearError();
754 
755 
756  //
757  // Resolve file URLs, we can simply ignore the non-existing file resources which are reflected by empty resolved URIs
758  //
759  QSet<QUrl> resolvedResources = QSet<QUrl>::fromList(resolveUrls(resources, app, false));
760  if(resolvedResources.isEmpty() || lastError()) {
761  return;
762  }
763 
764 
765  //
766  // Actually change data
767  //
768  foreach( const QUrl & res, resolvedResources ) {
769  QSet<Soprano::Node> propertiesToRemove;
770  QMultiHash<QUrl, Soprano::Node> propertyValues;
771  Soprano::QueryResultIterator it
772  = executeQuery(QString::fromLatin1("select distinct ?p ?v where { %1 ?p ?v . FILTER(?p in (%2)) . }")
773  .arg(Soprano::Node::resourceToN3(res),
774  resourcesToN3(properties).join(QLatin1String(","))),
775  Soprano::Query::QueryLanguageSparql);
776  while(it.next()) {
777  propertiesToRemove.insert(it["p"]);
778  propertyValues.insert( it["p"].uri(), it["v"] );
779  }
780 
781  // remove the data
782  foreach(const Soprano::Node& property, propertiesToRemove) {
783  removeAllStatements( res, property, Soprano::Node() );
784  }
785 
786  // inform interested parties
787  for(QMultiHash<QUrl, Soprano::Node>::const_iterator it = propertyValues.constBegin();
788  it != propertyValues.constEnd(); ) {
789  QList<Soprano::Node> values;
790  values << it.value();
791 
792  const QUrl property = it.key();
793  for( ++it; it != propertyValues.constEnd() && it.key() == property; ++it ) {
794  values << it.value();
795  }
796 
797  d->m_watchManager->changeProperty(res, property, QList<Soprano::Node>(), values);
798  }
799 
800  // we only update the mtime in case we actually remove anything
801  if(!propertiesToRemove.isEmpty()) {
802  // If the resource is now empty we remove it completely
803  if(!doesResourceExist(res)) {
804  removeResources(QList<QUrl>() << res, NoRemovalFlags, app);
805  }
806  else {
807  updateModificationDate(res);
808  }
809  }
810  }
811 }
812 
813 
814 QUrl Nepomuk2::DataManagementModel::createResource(const QList<QUrl> &types, const QString &label, const QString &description, const QString &app)
815 {
816  // 1. create an new graph
817  // 2. simplify the types
818  // 3. check if the app exists, if not create it in the new graph
819  // 4. create the new resource in the new graph
820  // 5. return the resource's URI
821 
822  //
823  // Check parameters
824  //
825  if(app.isEmpty()) {
826  setError(QLatin1String("createResource: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument);
827  return QUrl();
828  }
829  QSet<QUrl> newTypes = types.toSet();
830  QMutableSetIterator<QUrl> iterator( newTypes );
831  while( iterator.hasNext() ) {
832  const QUrl type = iterator.next();
833  if(type.isEmpty()) {
834  iterator.remove();
835  continue;
836  }
837 
838  if(!d->m_classAndPropertyTree->isKnownClass(type)) {
839  QString error = QString::fromLatin1("createResource: Encountered invalid type URI %1").arg( Soprano::Node::resourceToN3(type) );
840  setError(error, Soprano::Error::ErrorInvalidArgument);
841  return QUrl();
842  }
843  }
844 
845  if( newTypes.isEmpty() ) {
846  newTypes << RDFS::Resource();
847  }
848 
849  clearError();
850 
851  // Simplify the types
852  QSetIterator<QUrl> it( newTypes );
853  while( it.hasNext() ) {
854  const QUrl &type = it.next();
855  QSet<QUrl> superTypes = d->m_classAndPropertyTree->allParents( type );
856 
857  foreach( const QUrl& parent, superTypes ) {
858  QSet< QUrl >::iterator iter = newTypes.find( parent );
859  if( iter != newTypes.end() ) {
860  newTypes.erase( iter );
861  }
862  }
863  }
864 
865  // create new URIs and a new graph
866  const QUrl graph = fetchGraph(app);
867  const QUrl resUri = createUri(ResourceUri);
868  const QString resN3 = Soprano::Node::resourceToN3(resUri);
869 
870  // add provided metadata
871  QString command = QString::fromLatin1("sparql insert into %1 { %2 ")
872  .arg( Soprano::Node::resourceToN3(graph),
873  resN3 );
874 
875  if(!label.isEmpty()) {
876  Soprano::LiteralValue lv = Soprano::LiteralValue::createPlainLiteral(label);
877  command += QString::fromLatin1(" nao:prefLabel %1 ;")
878  .arg( Soprano::Node::literalToN3(lv) );
879  }
880  if(!description.isEmpty()) {
881  Soprano::LiteralValue lv = Soprano::LiteralValue::createPlainLiteral(description);
882  command += QString::fromLatin1(" nao:description %1 ;")
883  .arg( Soprano::Node::literalToN3(lv) );
884  }
885 
886  command += QString::fromLatin1("a %1 . }").arg( urlSetToN3(newTypes).join(",") );
887  executeQuery( command, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
888 
889  // add basic metadata to the new resource in the nepomuk graph
890  command = QString::fromLatin1("sparql insert into %1 { %2 ")
891  .arg( Soprano::Node::resourceToN3(d->m_nepomukGraph), resN3 );
892 
893  Soprano::LiteralValue now( QDateTime::currentDateTime() );
894  command += QString::fromLatin1(" nao:lastModified %1 ; nao:created %1 . }")
895  .arg( Soprano::Node::literalToN3(now) );
896 
897  executeQuery( command, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
898 
899  // inform interested parties
900  QSet<QUrl> allTypes = d->m_classAndPropertyTree->allParents( types );
901  d->m_watchManager->createResource(resUri, allTypes);
902  d->m_watchManager->changeSomething();
903 
904  return resUri;
905 }
906 
907 void Nepomuk2::DataManagementModel::removeResources(const QList<QUrl> &resources, RemovalFlags flags, const QString &app)
908 {
909  // 1. get all sub-resources and check if they are used by some other resource (not in the list of resources to remove)
910  // for the latter one can use a bif:exists and a !filter(?s in <s>, <s>, ...) - based on the value of force
911  // 2. remove the resources and the sub-resources
912  // 3. drop trailing graphs (could be optimized by enumerating the graphs up front)
913 
914  //
915  // Check parameters
916  //
917  if(app.isEmpty()) {
918  setError(QLatin1String("removeResources: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument);
919  return;
920  }
921  if(resources.isEmpty()) {
922  setError(QLatin1String("removeResources: No resource specified."), Soprano::Error::ErrorInvalidArgument);
923  return;
924  }
925  foreach( const QUrl & res, resources ) {
926  if(res.isEmpty()) {
927  setError(QLatin1String("removeResources: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument);
928  return;
929  }
930  }
931 
932  clearError();
933 
934  //
935  // Resolve file URLs, we can simply ignore the non-existing file resources which are reflected by empty resolved URIs
936  //
937  QSet<QUrl> resolvedResources = QSet<QUrl>::fromList(resolveUrls(resources, app, false));
938  if(resolvedResources.isEmpty() || lastError()) {
939  return;
940  }
941 
942  //
943  // Actually remove the data
944  //
945  removeAllResources(resolvedResources, flags);
946 }
947 
948 void Nepomuk2::DataManagementModel::removeDataByApplication(const QList<QUrl> &resources, RemovalFlags flags, const QString &app)
949 {
950  //
951  // Check parameters
952  //
953  if(app.isEmpty()) {
954  setError(QLatin1String("removeDataByApplication: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument);
955  return;
956  }
957  foreach( const QUrl & res, resources ) {
958  if(res.isEmpty()) {
959  setError(QLatin1String("removeDataByApplication: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument);
960  return;
961  }
962  }
963 
964 
965  clearError();
966 
967  const QUrl appRes = findApplicationResource(app, false);
968  if(appRes.isEmpty()) {
969  return;
970  }
971 
972  // Fetch all the graphs maintained by the app. This should at most be 2
973  // one discardable and one normal
974  //
975  QString query = QString::fromLatin1("select distinct ?g where { ?g nao:maintainedBy %1 . }")
976  .arg( Soprano::Node::resourceToN3(appRes) );
977 
978  Soprano::QueryResultIterator it = executeQuery(query, Soprano::Query::QueryLanguageSparqlNoInference);
979  QList<QUrl> graphs;
980  while( it.next() ) {
981  graphs << it[0].uri();
982  }
983 
984  if( graphs.isEmpty() )
985  return;
986 
987  //
988  // Resolve file URLs, we can simply ignore the non-existing file resources which are reflected by empty resolved URIs
989  //
990  QSet<QUrl> resolvedResources = QSet<QUrl>::fromList(resolveUrls(resources, app, false));
991  if(resolvedResources.isEmpty() || lastError()) {
992  return;
993  }
994 
995 
996  QList<QUrl> allGraphs( graphs );
997  allGraphs << d->m_nepomukGraph;
998 
999  if( flags & RemoveSubResoures ) {
1000  //
1001  // Handle the sub-resources: we can delete all sub-resources of the deleted ones that
1002  // are entirely defined by our app and are not related by other resources.
1003  // this has to be done before deleting the resouces in resolvedResources.
1004  // Otherwise the nao:hasSubResource relationships are already gone!
1005  //
1006  // Explanation of the query:
1007  // The query selects all subresources of the resources in resolvedResources.
1008  //
1009  QSet<QUrl> subResources;
1010  QSet<QUrl> currentResources = resolvedResources;
1011  int resCount = 0;
1012  do {
1013  resCount = resolvedResources.count();
1014 
1015  QString query = QString::fromLatin1("select distinct ?r where { graph ?g { ?r ?p ?o. }"
1016  "?parent nao:hasSubResource ?r . "
1017  "FILTER(?parent in (%1)) ."
1018  "FILTER(?g in (%2)) . "
1019  "FILTER NOT EXISTS { graph ?g2 { ?r ?p ?o. } FILTER(!(?g2 in (%3))) . }"
1020  " } ")
1021  .arg( resourcesToN3(currentResources).join(","),
1022  resourcesToN3(graphs).join(","),
1023  resourcesToN3(allGraphs).join(",") );
1024 
1025  Soprano::QueryResultIterator it = executeQuery( query, Soprano::Query::QueryLanguageSparqlNoInference );
1026  currentResources.clear();
1027  while(it.next()) {
1028  currentResources << it[0].uri();
1029  }
1030  resolvedResources += currentResources;
1031  subResources += currentResources;
1032  } while(resCount < resolvedResources.count());
1033 
1034  //
1035  // Now subResources contains the list of all possible sub-resources to delete.
1036  // We now have to remove all those which have links from the "outside", ie. resources
1037  // that are not in our resolvedResources.
1038  // We do this iterative until nothing changes anymore by removing the sub-resources
1039  // have are linked from the "outside".
1040  //
1041  QSet<QUrl> excludedSubResources = resolvedResources;
1042  bool firstIteration = true;
1043  while(!subResources.isEmpty() && !excludedSubResources.isEmpty()) {
1044  Soprano::QueryResultIterator it
1045  = executeQuery(QString::fromLatin1("select distinct ?r where { "
1046  "?r2 ?p ?r ."
1047  "FILTER(%1) . "
1048  "FILTER(%2) . }" )
1049  .arg(createResourceFilter(subResources, QLatin1String("?r"), false),
1050  createResourceFilter(excludedSubResources, QLatin1String("?r2"), firstIteration)),
1051  Soprano::Query::QueryLanguageSparqlNoInference);
1052  excludedSubResources.clear();
1053  while(it.next()) {
1054  excludedSubResources << it[0].uri();
1055  }
1056  subResources -= excludedSubResources;
1057  resolvedResources -= excludedSubResources;
1058 
1059  // in all subsequent queries we are only interested in links coming from the excluded sub-resources
1060  // rather than coming from anything but our resolved resources.
1061  firstIteration = false;
1062  }
1063  }
1064 
1065 
1066  //
1067  // Shorten the list of resources to the ones that are actually in our graphs
1068  //
1069  QString graphN3 = urlListToN3(graphs).join(QLatin1String(","));
1070  QString resN3 = urlSetToN3(resolvedResources).join(QLatin1String(","));
1071 
1072  query = QString::fromLatin1("select distinct ?r where { graph ?g { ?r ?p ?o. } FILTER(?g in (%1)) ."
1073  "FILTER(?r in (%2)) . }")
1074  .arg( graphN3, resN3 );
1075 
1076  QList<QUrl> finalResourcesList;
1077  it = executeQuery( query, Soprano::Query::QueryLanguageSparql );
1078  while( it.next() )
1079  finalResourcesList << it[0].uri();
1080 
1081  resolvedResources = finalResourcesList.toSet();
1082  resN3 = urlListToN3(finalResourcesList).join(QLatin1String(","));
1083 
1084  //
1085  // Get modified resources
1086  //
1087  QString notInGraphs = graphN3;
1088  notInGraphs += QString::fromLatin1(",%1").arg(Soprano::Node::resourceToN3(d->m_nepomukGraph));
1089 
1090  query = QString::fromLatin1("select distinct ?r where { "
1091  " graph ?g1 { ?r ?p ?o. } "
1092  " graph ?g2 { ?r ?p2 ?o2 . } "
1093  " FILTER(?g1 in (%1)) . FILTER(?r in (%2)) . "
1094  " FILTER(!(?g2 in (%3))) . }")
1095  .arg( graphN3, resN3, notInGraphs );
1096 
1097  it = executeQuery( query, Soprano::Query::QueryLanguageSparqlNoInference );
1098  QSet<QUrl> modifiedResources;
1099  while( it.next() ) {
1100  modifiedResources << it[0].uri();
1101  }
1102 
1103  query = QString::fromLatin1("select distinct ?r2 where { "
1104  " graph ?g { ?r2 ?p ?r . FILTER(?r in (%1)) .}"
1105  " FILTER(?g in (%2)) }")
1106  .arg( resN3, graphN3 );
1107 
1108  it = executeQuery( query, Soprano::Query::QueryLanguageSparqlNoInference );
1109  while( it.next() ) {
1110  QUrl uri = it[0].uri();
1111  if( !resolvedResources.contains(uri) )
1112  modifiedResources << uri;
1113  }
1114  updateModificationDate(modifiedResources);
1115 
1116  foreach(const QUrl& graph, graphs) {
1117  QString deleteCommand = QString::fromLatin1("sparql delete from %1 { ?r ?p ?o. } where { "
1118  "?r ?p ?o. FILTER(?r in (%2)). }")
1119  .arg( Soprano::Node::resourceToN3(graph), resN3 );
1120 
1121  executeQuery( deleteCommand, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
1122 
1123  deleteCommand = QString::fromLatin1("sparql delete from %1 { ?o ?p ?r. } where { "
1124  "?o ?p ?r. FILTER(?r in (%2)). }")
1125  .arg( Soprano::Node::resourceToN3(graph), resN3 );
1126 
1127  executeQuery( deleteCommand, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
1128  }
1129 
1130  // From the nepomuk graph only remove the resources which should be deleted completely
1131  resolvedResources.subtract( modifiedResources );
1132 
1133  if( resolvedResources.count() ) {
1134  QString deleteCommand = QString::fromLatin1("sparql delete from %1 { ?r ?p ?o. } where { "
1135  "?r ?p ?o. FILTER(?r in (%2)). }")
1136  .arg( Soprano::Node::resourceToN3(d->m_nepomukGraph),
1137  urlSetToN3(resolvedResources).join(",") );
1138  executeQuery( deleteCommand, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
1139  }
1140 }
1141 
1142 
1143 void Nepomuk2::DataManagementModel::removeDataByApplication(RemovalFlags flags, const QString &app)
1144 {
1145  Q_UNUSED( flags );
1146  //
1147  // Check parameters
1148  //
1149  if(app.isEmpty()) {
1150  setError(QLatin1String("removeDataByApplication: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument);
1151  return;
1152  }
1153 
1154  clearError();
1155 
1156  const QUrl appRes = findApplicationResource(app, false);
1157  if(appRes.isEmpty()) {
1158  return;
1159  }
1160 
1161  QString query = QString::fromLatin1("select ?g where { ?g nao:maintainedBy %1 . }")
1162  .arg( Soprano::Node::resourceToN3(appRes) );
1163 
1164  Soprano::QueryResultIterator it = executeQuery(query, Soprano::Query::QueryLanguageSparqlNoInference);
1165  QList<QUrl> graphs;
1166  while( it.next() ) {
1167  graphs << it[0].uri();
1168  }
1169 
1170  // TODO: Find a faster way to update all the nao:lastModified of all the resources in the graph
1171  QSet<QUrl> modifiedResources;
1172 
1173  QString graphN3 = urlListToN3(graphs).join(QLatin1String(","));
1174  QString notInGraphs = graphN3;
1175  notInGraphs += QString::fromLatin1(",%1").arg(Soprano::Node::resourceToN3(d->m_nepomukGraph));
1176 
1177  // Fetch resources to modify
1178  query = QString::fromLatin1("select distinct ?r where { graph ?g1 { ?r ?p ?o. } "
1179  " graph ?g2 { ?r ?p2 ?o2 . } "
1180  " FILTER(?g1 in (%1)) . "
1181  " FILTER(!(?g2 in (%2))) . }")
1182  .arg( graphN3, notInGraphs );
1183 
1184  it = executeQuery( query, Soprano::Query::QueryLanguageSparqlNoInference );
1185  while( it.next() ) {
1186  modifiedResources << it[0].uri();
1187  }
1188 
1189  updateModificationDate( modifiedResources );
1190 
1191  QSet<QUrl> resourcesToRemove;
1192  foreach(const QUrl& graph, graphs) {
1193  QString query = QString::fromLatin1("select distinct ?r where { graph %1 { ?r ?p ?o. } }")
1194  .arg( Soprano::Node::resourceToN3(graph) );
1195 
1196  Soprano::QueryResultIterator it = executeQuery( query, Soprano::Query::QueryLanguageSparqlNoInference );
1197  while( it.next() )
1198  resourcesToRemove << it[0].uri();
1199 
1200  QString command = QString::fromLatin1("clear graph %1").arg(Soprano::Node::resourceToN3(graph));
1201  executeQuery( command, Soprano::Query::QueryLanguageSparqlNoInference );
1202  }
1203 
1204  resourcesToRemove.subtract( modifiedResources );
1205  if( resourcesToRemove.count() ) {
1206  query = QString::fromLatin1("delete from %2 { ?r ?p ?o . } where { ?r ?p ?o. FILTER(?r in (%1)) . }")
1207  .arg( urlSetToN3(resourcesToRemove).join(","),
1208  Soprano::Node::resourceToN3(d->m_nepomukGraph) );
1209 
1210  executeQuery( query, Soprano::Query::QueryLanguageSparqlNoInference );
1211  }
1212 }
1213 
1214 
1215 namespace {
1216  using namespace Nepomuk2;
1222  void mergeDuplicateSyncResources( QList<Sync::SyncResource>& syncResources ) {
1223  QHash<Soprano::Node, Soprano::Node> duplicateResources;
1224  do {
1225  duplicateResources.clear();
1226 
1227  QList<Sync::SyncResource> finalList;
1228  QHash< uint, const Sync::SyncResource* > syncResHash;
1229  foreach( const Sync::SyncResource& syncRes, syncResources ) {
1230 
1231  // Do not check for duplicates in non blank nodes
1232  if( syncRes.uri().url().startsWith("_:") ) {
1233 
1234  //WARNING: The SyncResource qHash function does not use the uri while preparing the hash.
1235  // If it did, then this would fail miserably.
1236  uint hash = qHash( syncRes );
1237  QHash< uint, const Sync::SyncResource* >::iterator it = syncResHash.find( hash );
1238  if( it != syncResHash.end() ) {
1239  // hash collision : Check the entire contents
1240  const Sync::SyncResource r = *it.value();
1241 
1242  // We can't use SyncResource::operator== cause that would check the uri as well
1243  // and we don't care about the uri
1244  if( r.QHash<KUrl,Soprano::Node>::operator==(syncRes) ) {
1245  kDebug() << "Adding: " << syncRes.uri() << " " << it.value()->uri();
1246  duplicateResources.insert( convertIfBlankUri( syncRes.uri() ),
1247  convertIfBlankUri( it.value()->uri() ) );
1248  continue;
1249  }
1250  }
1251 
1252  syncResHash.insert( hash, &syncRes );
1253  }
1254 
1255  finalList << syncRes;
1256  }
1257 
1258  if( !duplicateResources.isEmpty() ) {
1259  // Resolve all duplicates in the finalList
1260  QMutableListIterator<Sync::SyncResource> it( finalList );
1261  while( it.hasNext() ) {
1262  it.next();
1263 
1264  QMutableHashIterator<KUrl, Soprano::Node> iter( it.value() );
1265  while( iter.hasNext() ) {
1266  iter.next();
1267 
1268  const Soprano::Node node = iter.value();
1269  if( node.isLiteral() || node.isEmpty() )
1270  continue;
1271  QHash< Soprano::Node, Soprano::Node >::const_iterator fit = duplicateResources.constFind( node );
1272  if( fit != duplicateResources.constEnd() ) {
1273  iter.setValue( fit.value() );
1274  }
1275  }
1276  }
1277 
1278  syncResources = finalList;
1279  }
1280  } while( !duplicateResources.isEmpty() );
1281  }
1282 }
1283 
1285 QHash<QUrl, QUrl> DataManagementModel::storeResources(const Nepomuk2::SimpleResourceGraph &resources,
1286  const QString &app,
1287  Nepomuk2::StoreIdentificationMode identificationMode,
1288  Nepomuk2::StoreResourcesFlags flags,
1289  const QHash<QUrl, QVariant> &additionalMetadata)
1290 {
1291  bool discardable = false;
1292  if( QMultiHash<QUrl, QVariant>(additionalMetadata).contains(RDF::type(), NRL::DiscardableInstanceBase()) ) {
1293  discardable = true;
1294  }
1295 
1296  return storeResources(resources, app, discardable, identificationMode, flags);
1297 }
1298 
1299 QHash<QUrl, QUrl> DataManagementModel::storeResources(const SimpleResourceGraph& resources,
1300  const QString& app,
1301  bool discardable,
1302  StoreIdentificationMode identificationMode,
1303  StoreResourcesFlags flags)
1304 {
1305  QTime timer;
1306  timer.start();
1307 
1308  if(app.isEmpty()) {
1309  setError(QLatin1String("storeResources: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument);
1310  return QHash<QUrl, QUrl>();
1311  }
1312  if(resources.isEmpty()) {
1313  clearError();
1314  return QHash<QUrl, QUrl>();
1315  }
1316 
1318  QHash<QUrl, QUrl> resolvedNodes;
1319 
1320  //
1321  // Resolve the nie URLs which are present as resource uris
1322  //
1323  QSet<QUrl> blankResources;
1324  SimpleResourceGraph resGraph( resources );
1325  QList<SimpleResource> resGraphList = resGraph.toList();
1326  QMutableListIterator<SimpleResource> iter( resGraphList );
1327  while( iter.hasNext() ) {
1328  SimpleResource & res = iter.next();
1329 
1330  if( !res.isValid() ) {
1331  QString error = QString::fromLatin1("The resource with URI %1 is invalid.")
1332  .arg( res.uri().toString() );
1333  setError(error, Soprano::Error::ErrorInvalidArgument);
1334  return QHash<QUrl, QUrl>();
1335  }
1336 
1337  const UriState state = uriState(res.uri());
1338  if(state == NepomukUri) {
1339  continue;
1340  }
1341  else if(state == BlankUri) {
1342  blankResources << res.uri();
1343  }
1344  // Handle nie urls
1345  else if(state == NonExistingFileUrl) {
1346  setError(QString::fromLatin1("Cannot store information about non-existing local files. File '%1' does not exist.").arg(res.uri().toLocalFile()), Soprano::Error::ErrorInvalidArgument);
1347  return QHash<QUrl, QUrl>();
1348  }
1349  else if(state == ExistingFileUrl || state == SupportedUrl) {
1350  const QUrl nieUrl = res.uri();
1351  QUrl newResUri = resolveUrl( nieUrl );
1352  if( lastError() )
1353  return QHash<QUrl, QUrl>();
1354 
1355  if( newResUri.isEmpty() ) {
1356  // Resolution of one url failed. Assign it a random blank uri
1357  newResUri = SimpleResource().uri(); // HACK: improveme
1358 
1359  if( state == ExistingFileUrl ) {
1360  res.addProperty( RDF::type(), NFO::FileDataObject() );
1361  if( QFileInfo( nieUrl.toLocalFile() ).isDir() )
1362  res.addProperty( RDF::type(), NFO::Folder() );
1363  }
1364  }
1365 
1366  res.addProperty( NIE::url(), nieUrl );
1367 
1368  resolvedNodes.insert( nieUrl, newResUri );
1369 
1370  res.setUri( newResUri );
1371  }
1372  else if( state == OtherUri ) {
1373  // Legacy support - Sucks but we need it
1374  const QUrl legacyUri = resolveUrl( res.uri() );
1375  if( lastError() )
1376  return QHash<QUrl, QUrl>();
1377  }
1378  else if( state == OntologyUri ) {
1379  setError(QLatin1String("It is not allowed to add classes or properties through this API."), Soprano::Error::ErrorInvalidArgument);
1380  return QHash<QUrl, QUrl>();
1381  }
1382  }
1383  resGraph = resGraphList;
1384 
1385  ResourceIdentifier resIdent( identificationMode, this );
1386  QList<Sync::SyncResource> syncResources;
1387 
1388  //
1389  // Resolve URLs in property values and prepare the resource identifier
1390  //
1391  foreach( const SimpleResource& res, resGraph.toList() ) {
1392  // Convert to a Sync::SyncResource
1393  //
1394  Sync::SyncResource syncRes( res.uri() );
1395  QHashIterator<QUrl, QVariant> hit( res.properties() );
1396  while( hit.hasNext() ) {
1397  hit.next();
1398 
1399  Soprano::Node n = d->m_classAndPropertyTree->variantToNode( hit.value(), hit.key() );
1400  // The ClassAndPropertyTree returns blank nodes as URIs. It does not understand the
1401  // concept of blank nodes, and since only storeResources requires blank nodes,
1402  // it's easier to keep the blank node logic outside.
1403  if( n.isResource() )
1404  n = convertIfBlankUri( n.uri() );
1405 
1406  const Soprano::Error::Error error = d->m_classAndPropertyTree->lastError();
1407  if( error ) {
1408  setError( error );
1409  return QHash<QUrl, QUrl>();
1410  }
1411  syncRes.insert( hit.key(), n );
1412  }
1413 
1414  // Temporarily remove the nie:url, we will add it back later
1415  const QUrl nieUrl = syncRes.take( NIE::url() ).uri();
1416 
1417  QMutableHashIterator<KUrl, Soprano::Node> it( syncRes );
1418  while( it.hasNext() ) {
1419  it.next();
1420 
1421  const Soprano::Node object = it.value();
1422  if( object.isBlank() ) {
1423  //
1424  // Extra checks it is a blank node -
1425  // If it is a blank node make sure it was present as the subject
1426  QSet<QUrl>::const_iterator fit = blankResources.constFind( QUrl(object.toN3()) );
1427  if( fit == blankResources.constEnd() ) {
1428  QString error = QString::fromLatin1("%1 does not exist in the graph. In statement (%2, %3, %4)")
1429  .arg( object.toN3(),
1430  syncRes.uri().url(),
1431  it.key().url(),
1432  it.value().toN3() );
1433  setError( error, Soprano::Error::ErrorInvalidArgument );
1434  return QHash<QUrl, QUrl>();
1435  }
1436  else {
1437  continue;
1438  }
1439  }
1440 
1441  else if( object.isResource() ) {
1442  const UriState state = uriState(object.uri());
1443  if(state==NepomukUri || state == OntologyUri) {
1444  continue;
1445  }
1446  else if(state == NonExistingFileUrl) {
1447  setError(QString::fromLatin1("Cannot store information about non-existing local files. File '%1' does not exist.").arg(object.uri().toLocalFile()),
1448  Soprano::Error::ErrorInvalidArgument);
1449  return QHash<QUrl, QUrl>();
1450  }
1451  else if(state == ExistingFileUrl || state==SupportedUrl) {
1452  const QUrl nieUrl = object.uri();
1453  // Need to resolve it
1454  QHash< QUrl, QUrl >::const_iterator findIter = resolvedNodes.constFind( nieUrl );
1455  if( findIter != resolvedNodes.constEnd() ) {
1456  it.setValue( convertIfBlankUri(findIter.value()) );
1457  }
1458  else {
1459  Sync::SyncResource newRes;
1460 
1461  // It doesn't exist, create it
1462  QUrl resolvedUri = resolveUrl( nieUrl );
1463  if( resolvedUri.isEmpty() ) {
1464  resolvedUri = SimpleResource().uri(); // HACK: improveme
1465 
1466  newRes.insert( NIE::url(), nieUrl );
1467  if( state == ExistingFileUrl ) {
1468  newRes.insert( RDF::type(), NFO::FileDataObject() );
1469  if( QFileInfo( nieUrl.toLocalFile() ).isDir() )
1470  newRes.insert( RDF::type(), NFO::Folder() );
1471  }
1472 
1473  newRes.setUri( resolvedUri );
1474  syncResources << newRes;
1475  }
1476 
1477  resolvedNodes.insert( nieUrl, resolvedUri );
1478  it.setValue( convertIfBlankUri(resolvedUri) );
1479  }
1480  }
1481  else if(state == OtherUri) {
1482  // We use resolveUrl to check if the otherUri exists. If it doesn't exist,
1483  // then resolveUrl which set the last error
1484  // trueg: seems like a waste to not use the resolved uri here!
1485  const QUrl legacyUri = resolveUrl( object.uri() );
1486  if( lastError() )
1487  return QHash<QUrl, QUrl>();
1488 
1489  // It apparently exists, so we must support it
1490  }
1491  } // if object.isResurce
1492  } // while( it.hasNext() )
1493 
1494  // Check if the nie:url exists
1495  if( nieUrl.scheme() == QLatin1String("file") && !QFile::exists(nieUrl.toLocalFile()) ) {
1496  setError(QString::fromLatin1("Cannot store information about non-existing local files. File '%1' does not exist.").arg(nieUrl.toLocalFile()),
1497  Soprano::Error::ErrorInvalidArgument);
1498  return QHash<QUrl, QUrl>();
1499  }
1500 
1501  // We had removed the nie:url in the begining, we must insert it again
1502  if( !nieUrl.isEmpty() )
1503  syncRes.insert( NIE::url(), nieUrl );
1504 
1505  // The resource is now ready.
1506  syncResources << syncRes;
1507  }
1508 
1509  blankResources.clear();
1510 
1511  if( flags & MergeDuplicateResources ) {
1512  mergeDuplicateSyncResources( syncResources );
1513  }
1514 
1515  // Push it into the Resource Identifier
1516  foreach( const Sync::SyncResource& syncRes, syncResources ) {
1517  QList< Soprano::Statement > stList = syncRes.toStatementList();
1518 
1519  if(stList.isEmpty()) {
1520  setError(QString::fromLatin1("storeResources: Encountered empty sync resource (%1). This is a bug. Please report.").arg(syncRes.uri().url()));
1521  return QHash<QUrl, QUrl>();
1522  }
1523 
1524  if( !syncRes.isValid() ) {
1525  setError(QLatin1String("storeResources: Contains invalid resources."), Soprano::Error::ErrorParsingFailed);
1526  return QHash<QUrl, QUrl>();
1527  }
1528  resIdent.addSyncResource( syncRes );
1529  }
1530 
1531  //
1532  // Perform the actual identification
1533  //
1534  QTime identificationTimer;
1535  identificationTimer.start();
1536 
1537  resIdent.identifyAll();
1538 
1539  int identificationTime = identificationTimer.elapsed();
1540  QTime mergingTimer;
1541  mergingTimer.start();
1542 
1543  ResourceMerger merger( this, app, flags, discardable );
1544  merger.setMappings( resIdent.mappings() );
1545 
1546  if( !merger.merge( resIdent.resourceHash() ) ) {
1547  kDebug() << " MERGING FAILED! ";
1548  kDebug() << "Setting error!" << merger.lastError();
1549  setError( merger.lastError() );
1550  return QHash<QUrl, QUrl>();
1551  }
1552 
1553  kDebug() << "Identification:" << identificationTime << "Merging:" << mergingTimer.elapsed();
1554  kDebug() << "TIME TAKEN -------- " << timer.elapsed();
1555  return merger.mappings();
1556 }
1557 
1558 void Nepomuk2::DataManagementModel::importResources(const QUrl &url,
1559  const QString &app,
1560  Soprano::RdfSerialization serialization,
1561  const QString &userSerialization,
1562  Nepomuk2::StoreIdentificationMode identificationMode,
1563  Nepomuk2::StoreResourcesFlags flags,
1564  const QHash<QUrl, QVariant>& additionalMetadata)
1565 {
1566  // download the file
1567  QString tmpFileName;
1568  if(!KIO::NetAccess::download(url, tmpFileName, 0)) {
1569  setError(QString::fromLatin1("Failed to download '%1'.").arg(url.toString()));
1570  return;
1571  }
1572 
1573  // guess the serialization
1574  if(serialization == Soprano::SerializationUnknown) {
1575  const QString extension = KUrl(url).fileName().section('.', -1).toLower();
1576  if(extension == QLatin1String("trig"))
1577  serialization = Soprano::SerializationTrig;
1578  else if(extension == QLatin1String("n3"))
1579  serialization = Soprano::SerializationNTriples;
1580  else if(extension == QLatin1String("xml"))
1581  serialization = Soprano::SerializationRdfXml;
1582  }
1583 
1584  const Soprano::Parser* parser = Soprano::PluginManager::instance()->discoverParserForSerialization(serialization, userSerialization);
1585  if(!parser) {
1586  setError(QString::fromLatin1("Failed to create parser for serialization '%1'").arg(Soprano::serializationMimeType(serialization, userSerialization)));
1587  }
1588  else {
1589  SimpleResourceGraph graph;
1590  Soprano::StatementIterator it = parser->parseFile(tmpFileName, QUrl(), serialization, userSerialization);
1591  while(it.next()) {
1592  graph.addStatement(*it);
1593  }
1594  if(parser->lastError()) {
1595  setError(parser->lastError());
1596  }
1597  else if(it.lastError()) {
1598  setError(it.lastError());
1599  }
1600  else {
1601  storeResources(graph, app, identificationMode, flags, additionalMetadata);
1602  }
1603  }
1604 
1605  KIO::NetAccess::removeTempFile(tmpFileName);
1606 }
1607 
1608 void Nepomuk2::DataManagementModel::mergeResources(const QList<QUrl>& resources, const QString& app)
1609 {
1610  if(app.isEmpty()) {
1611  setError(QLatin1String("mergeResources: Empty application specified. This is not supported."),
1612  Soprano::Error::ErrorInvalidArgument);
1613  return;
1614  }
1615  QSet<QUrl> resSet = resources.toSet();
1616  if(resSet.size() <= 1) {
1617  setError(QLatin1String("mergeResources: Need to provide more than 1 resource to merge"),
1618  Soprano::Error::ErrorInvalidArgument);
1619  return;
1620  }
1621 
1622  // Virtuoso cannot handle very large number of resources cause of the complicated queries below
1623  // so, we do them in multiple sets of 10
1624  if( resources.size() > 10 ) {
1625  QList<QUrl> first10Resources = resources.mid( 0, 10 );
1626  QList<QUrl> remainingResources = resources.mid( 10 );
1627 
1628  mergeResources( first10Resources, app );
1629  if( lastError() )
1630  return;
1631 
1632  mergeResources( remainingResources, app );
1633  return;
1634  }
1635 
1636  foreach(const QUrl& uri, resSet) {
1637  if(uri.isEmpty()) {
1638  setError(QLatin1String("mergeResources: Encountered empty resource URI."),
1639  Soprano::Error::ErrorInvalidArgument);
1640  return;
1641  }
1642  }
1643 
1644  clearError();
1645 
1646  // TODO: Is it correct that all metadata stays the same?
1647 
1648  const QUrl resUri = resources.first();
1649  resSet.remove( resUri );
1650  //
1651  // Copy all property values of res2 that are not also defined for res1
1652  //
1653  QString query = QString::fromLatin1("select ?g ?p ?v (select count(distinct ?v2) where { %2 ?p ?v2 . }) as ?c"
1654  " where { graph ?g { ?r ?p ?v } "
1655  " FILTER(?r in (%1)) ."
1656  " FILTER NOT EXISTS { %2 ?p ?v .} }")
1657  .arg( resourcesToN3(resSet).join(","),
1658  Soprano::Node::resourceToN3(resUri) );
1659 
1660  const QList<Soprano::BindingSet> resProperties
1661  = executeQuery(query, Soprano::Query::QueryLanguageSparqlNoInference).allBindings();
1662 
1663  foreach(const Soprano::BindingSet& binding, resProperties) {
1664  const QUrl& prop = binding["p"].uri();
1665  // if the property has no cardinality of 1 or no value is defined yet we can simply convert the value to res1
1666  if(d->m_classAndPropertyTree->maxCardinality(prop) != 1 ||
1667  binding["c"].literal().toInt() == 0) {
1668  const Soprano::Node v = binding["v"];
1669  addStatement(resUri, prop, v, binding["g"]);
1670  d->m_watchManager->changeProperty( resUri, prop, QList<Soprano::Node>() << v,
1671  QList<Soprano::Node>() );
1672  }
1673  }
1674 
1675 
1676  //
1677  // Copy all backlinks from the other resources that are not valid for resUri
1678  //
1679  const QList<Soprano::BindingSet> res2Backlinks
1680  = executeQuery(QString::fromLatin1("select ?g ?p ?r where { graph ?g { ?r ?p ?r2 . } . "
1681  "FILTER(?r2 in (%1)) ."
1682  "FILTER(?r!=%2) . "
1683  "FILTER NOT EXISTS { ?r ?p %2. } }")
1684  .arg(resourcesToN3(resSet).join(","),
1685  Soprano::Node::resourceToN3(resUri)),
1686  Soprano::Query::QueryLanguageSparqlNoInference).allBindings();
1687  foreach(const Soprano::BindingSet& binding, res2Backlinks) {
1688  addStatement(binding["r"], binding["p"], resUri, binding["g"]);
1689  }
1690 
1691 
1692  //
1693  // Finally delete the other resources as they have been merged
1694  //
1695  removeResources(resSet.toList(), NoRemovalFlags, app);
1696 }
1697 
1698 
1699 
1700 namespace {
1701 QVariant nodeToVariant(const Soprano::Node& node) {
1702  if(node.isResource())
1703  return node.uri();
1704  else if(node.isBlank())
1705  return QUrl(QLatin1String("_:") + node.identifier());
1706  else
1707  return node.literal().variant();
1708 }
1709 }
1710 
1711 Nepomuk2::SimpleResourceGraph Nepomuk2::DataManagementModel::describeResources(const QList<QUrl> &resources,
1712  DescribeResourcesFlags flags,
1713  const QList<QUrl>& targetParties)
1714 {
1715  //
1716  // check parameters
1717  //
1718  foreach( const QUrl & res, resources ) {
1719  if(res.isEmpty()) {
1720  setError(QLatin1String("describeResources: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument);
1721  return SimpleResourceGraph();
1722  }
1723  }
1724 
1725  if(!targetParties.isEmpty()) {
1726  setError(QLatin1String("Permission handling has not been implemented yet."));
1727  return SimpleResourceGraph();
1728  }
1729 
1730 
1731  clearError();
1732 
1733 
1734  //
1735  // Resolve all URLs - for now we ignore non-existing resources
1736  //
1737  QSet<QUrl> resolvedResources(QSet<QUrl>::fromList(resolveUrls(resources, QString(), false /* do not stat local files - it would be a waste of resources */)));
1738  if(resolvedResources.isEmpty()) {
1739  setError(QLatin1String("describeResources: No useful resource specified."), Soprano::Error::ErrorInvalidArgument);
1740  return SimpleResourceGraph();
1741  }
1742 
1743 
1744  //
1745  // In case we need to exclude discardable data we make sure that metadata properties are
1746  // exported in any case as those are maintained by us and are only in the specific graph
1747  // "by chance".
1748  //
1749  const QString discardableDataExcludeFilter
1750  = (flags & ExcludeDiscardableData
1751  ? QString::fromLatin1("FILTER(!bif:exists((select (1) where { ?g a %1 . })) || %2) . ")
1752  .arg(Soprano::Node::resourceToN3(NRL::DiscardableInstanceBase()),
1753  createResourceMetadataPropertyFilter(QLatin1String("?p"), false))
1754  : QString());
1755 
1756  //
1757  // Get all sub-resources and add them to the list of resources to describe
1758  //
1759  {
1760  QSet<QUrl> subResources = resolvedResources;
1761  int resCount = 0;
1762  do {
1763  resCount = resolvedResources.count();
1764  // FIXME: as soon as the issue with the empty result list is fixed change this loop back into a single query
1765  // (email sent to Virtuoso ml: http://sourceforge.net/mailarchive/forum.php?thread_name=4E36D5ED.9020904%40kde.org&forum_name=virtuoso-users)
1766  QSet<QUrl> tmp(subResources);
1767  subResources.clear();
1768  foreach(const QUrl& res, tmp) {
1769  Soprano::QueryResultIterator it
1770  = executeQuery(QString::fromLatin1("select ?r where { "
1771  "graph ?g { ?parent %2 ?r . "
1772  "FILTER(?parent in (%1)) . } . "
1773  "%3"
1774  "}")
1775  .arg(/*resourcesToN3(subResources).join(QLatin1String(","))*/Soprano::Node::resourceToN3(res),
1776  Soprano::Node::resourceToN3(NAO::hasSubResource()),
1777  discardableDataExcludeFilter),
1778  Soprano::Query::QueryLanguageSparqlNoInference);
1779  subResources.clear();
1780  while(it.next()) {
1781  subResources << it[0].uri();
1782  }
1783  resolvedResources += subResources;
1784  }
1785  } while(resCount < resolvedResources.count());
1786  }
1787 
1788  //
1789  // Build the basic graph consisting of the requested resources and all sub-resources
1790  //
1791  SimpleResourceGraph graph;
1792  QSet<QUrl> relatedResourcesToFetch;
1793  // FIXME: as soon as the issue with the empty result list is fixed change this loop back into a single query
1794  // (email sent to Virtuoso ml: http://sourceforge.net/mailarchive/forum.php?thread_name=4E36D5ED.9020904%40kde.org&forum_name=virtuoso-users)
1795  foreach(const QUrl& res, resolvedResources) {
1796  Soprano::QueryResultIterator it
1797  = executeQuery(QString::fromLatin1("select distinct ?s ?p ?o where { "
1798  "?s ?p ?o . "
1799  "FILTER(?s in (%1)) . "
1800  "%2"
1801  "}")
1802  .arg(/*resourcesToN3(resolvedResources).join(QLatin1String(","))*/Soprano::Node::resourceToN3(res),
1803  discardableDataExcludeFilter),
1804  Soprano::Query::QueryLanguageSparqlNoInference);
1805  while(it.next()) {
1806  const Soprano::Node r = it["s"];
1807  const Soprano::Node p = it["p"];
1808  const Soprano::Node o = it["o"];
1809 
1810  if(!o.isResource() ||
1811  !(flags & ExcludeRelatedResources) ||
1812  d->m_classAndPropertyTree->isDefiningProperty(p.uri()) ||
1813  p == NIE::url()) {
1814  graph.addStatement(r, p, o);
1815  }
1816 
1817  if(o.isResource() &&
1818  (!(flags & ExcludeRelatedResources) ||
1819  d->m_classAndPropertyTree->isDefiningProperty(p.uri())) &&
1820  !d->m_classAndPropertyTree->isChildOf(p.uri(), NAO::hasSubResource()) &&
1821  p != NIE::url() &&
1822  p != RDF::type()) {
1823  relatedResourcesToFetch << o.uri();
1824  }
1825  }
1826  if(it.lastError()) {
1827  setError(it.lastError());
1828  return SimpleResourceGraph();
1829  }
1830  }
1831 
1832 
1833  //
1834  // Now fetch related resources and their defining properties,
1835  // ignoring anything that comes from an ontology.
1836  //
1837  // For related resources we only take the defining properties since
1838  // that is all that is required to describe them.
1839  //
1840  QSet<QUrl> currentRelatedResources(relatedResourcesToFetch);
1841  while(!currentRelatedResources.isEmpty()) {
1842  // FIXME: as soon as the issue with the empty result list is fixed change this loop back into a single query
1843  // (email sent to Virtuoso ml: http://sourceforge.net/mailarchive/forum.php?thread_name=4E36D5ED.9020904%40kde.org&forum_name=virtuoso-users)
1844  QSet<QUrl> tmp(currentRelatedResources);
1845  currentRelatedResources.clear();
1846  foreach(const QUrl& res, tmp) {
1847  Soprano::QueryResultIterator it
1848  = executeQuery(QString::fromLatin1("select distinct ?s ?p ?o where { "
1849  "graph ?g { ?s ?p ?o . "
1850  "FILTER(?s in (%1)) . "
1851  "FILTER(!(?o in (%2))) . } . "
1852  "FILTER(!bif:exists((select (1) where { ?g a %3 }))) . "
1853  "%4"
1854  "}")
1855  .arg(/*resourcesToN3(currentRelatedResources).join(QLatin1String(","))*/Soprano::Node::resourceToN3(res),
1856  resourcesToN3(graph.allResourceUris()).join(QLatin1String(",")),
1857  Soprano::Node::resourceToN3(NRL::Ontology()),
1858  discardableDataExcludeFilter),
1859  Soprano::Query::QueryLanguageSparqlNoInference);
1860  //currentRelatedResources.clear();
1861  while(it.next()) {
1862  const Soprano::Node r = it["s"];
1863  const Soprano::Node p = it["p"];
1864  const Soprano::Node o = it["o"];
1865  if(d->m_classAndPropertyTree->isDefiningProperty(p.uri())) {
1866  graph.addStatement(r, p, o);
1867  if(o.isResource()) {
1868  currentRelatedResources << o.uri();
1869  }
1870  }
1871  }
1872  if(it.lastError()) {
1873  setError(it.lastError());
1874  return SimpleResourceGraph();
1875  }
1876  }
1877  }
1878 
1879 
1880  //
1881  // Throw out all relations to related resources which were not fetched.
1882  // FIXME: do we really want this??
1883  //
1884  foreach(const QUrl& res, relatedResourcesToFetch + resolvedResources) {
1885  if(!graph.contains(res)) {
1886  graph.removeAll(QUrl(), QUrl(), res);
1887  }
1888  }
1889 
1890 
1891  //
1892  // Final cleanup: remove all resources that do only have metadata defined.
1893  //
1894  foreach(const QUrl& res, graph.allResourceUris()) {
1895  bool hasNonMetadataProps = false;
1896  foreach(const QUrl& prop, graph[res].properties().keys()) {
1897  if(!d->m_protectedProperties.contains(prop)) {
1898  hasNonMetadataProps = true;
1899  break;
1900  }
1901  }
1902  if(!hasNonMetadataProps) {
1903  graph.remove(res);
1904  }
1905  }
1906 
1907 
1908  return graph;
1909 }
1910 
1911 namespace {
1912  Soprano::Node anonymizeUri(const Soprano::Node& node, QHash<Soprano::Node, Soprano::Node>& blankNodes) {
1913  QHash<Soprano::Node, Soprano::Node>::const_iterator it = blankNodes.constFind(node);
1914  if(it == blankNodes.constEnd()) {
1915  Soprano::Node blank(QString::fromLatin1("b%1").arg(blankNodes.count()));
1916  blankNodes.insert(node, blank);
1917  return blank;
1918  }
1919  else {
1920  return it.value();
1921  }
1922  }
1923 }
1924 
1925 QString Nepomuk2::DataManagementModel::exportResources(const QList<QUrl> &resources,
1926  Soprano::RdfSerialization serialization,
1927  const QString &userSerialization,
1928  Nepomuk2::DescribeResourcesFlags flags,
1929  const QList<QUrl> &targetParties)
1930 {
1931  // try to get a serializer. Without it there is no point in doing any other work
1932  const Soprano::Serializer* serializer = Soprano::PluginManager::instance()->discoverSerializerForSerialization(serialization, userSerialization);
1933  if(!serializer) {
1934  setError(QString::fromLatin1("Could not find serializer plugin for serialization '%1'").arg(Soprano::serializationMimeType(serialization, userSerialization)));
1935  return QString();
1936  }
1937 
1938  // fetch the actual data
1939  SimpleResourceGraph graph = describeResources(resources, flags, targetParties);
1940  if(lastError()) {
1941  return QString();
1942  }
1943 
1944  QList<Soprano::Statement> statements = graph.toStatementGraph().toList();
1945 
1946  if(flags & AnonymizeNepomukUris) {
1947  QHash<Soprano::Node, Soprano::Node> blankNodes;
1948  for(QList<Soprano::Statement>::iterator it = statements.begin();
1949  it != statements.end(); ++it) {
1950  if(it->subject().uri().scheme() == QLatin1String("nepomuk")) {
1951  it->setSubject(anonymizeUri(it->subject(), blankNodes));
1952  }
1953  if(it->object().isResource() && it->object().uri().scheme() == QLatin1String("nepomuk")) {
1954  it->setObject(anonymizeUri(it->object(), blankNodes));
1955  }
1956  }
1957  }
1958 
1959  // serialilze the statements
1960  Soprano::Util::SimpleStatementIterator it(statements);
1961  QString result;
1962  QTextStream s(&result);
1963  if(serializer->serialize(it, s, serialization, userSerialization)) {
1964  clearError();
1965  return result;
1966  }
1967  else {
1968  setError(serializer->lastError());
1969  return QString();
1970  }
1971 }
1972 
1973 
1974 QUrl Nepomuk2::DataManagementModel::createGraph(const QString& app, const QMultiHash< QUrl, Soprano::Node >& additionalMetadata)
1975 {
1976  QHash<QUrl, Soprano::Node> graphMetaData = additionalMetadata;
1977 
1978  // determine the graph type
1979  bool haveGraphType = false;
1980  bool hasNaoCreated = false;
1981 
1982  QHash< QUrl, Soprano::Node >::const_iterator it = additionalMetadata.constFind( RDF::type() );
1983  if( it != additionalMetadata.constEnd() ) {
1984  // check if it is a valid type
1985  if(!it.value().isResource()) {
1986  setError(QString::fromLatin1("rdf:type has resource range. '%1' does not have a resource type.").arg(it.value().toN3()), Soprano::Error::ErrorInvalidArgument);
1987  return QUrl();
1988  }
1989  else {
1990  if(d->m_classAndPropertyTree->isChildOf(it.value().uri(), NRL::Graph()))
1991  haveGraphType = true;
1992  }
1993  }
1994 
1995  it = additionalMetadata.constFind( NAO::created() );
1996  if( it != additionalMetadata.constEnd() ) {
1997  if(!it.value().literal().isDateTime()) {
1998  setError(QString::fromLatin1("nao:created has xsd:dateTime range. '%1' is not convertable to a dateTime.").arg(it.value().toN3()), Soprano::Error::ErrorInvalidArgument);
1999  return QUrl();
2000  }
2001  hasNaoCreated = true;
2002  }
2003 
2004  // FIXME: check property, domain, and range
2005  // Reuse code from ResourceMerger::checkGraphMetadata
2006 
2007  // add missing metadata
2008  if(!haveGraphType) {
2009  graphMetaData.insert(RDF::type(), NRL::InstanceBase());
2010  }
2011  if(!hasNaoCreated) {
2012  graphMetaData.insert(NAO::created(), Soprano::LiteralValue(QDateTime::currentDateTime()));
2013  }
2014  if(!graphMetaData.contains(NAO::maintainedBy()) && !app.isEmpty()) {
2015  graphMetaData.insert(NAO::maintainedBy(), findApplicationResource(app));
2016  }
2017 
2018  const QUrl graph = createUri(GraphUri);
2019  const QUrl metadatagraph = createUri(GraphUri);
2020 
2021  // add metadata graph itself
2022  const QString metaN3 = Soprano::Node::resourceToN3( metadatagraph );
2023  const QString graphN3 = Soprano::Node::resourceToN3( graph );
2024 
2025  QString query = QString::fromLatin1("sparql insert into %1 { %1 nrl:coreGraphMetadataFor %2 ;"
2026  " rdf:type nrl:GraphMetadata . %2 ")
2027  .arg( metaN3, graphN3 );
2028 
2029 
2030  for(QHash<QUrl, Soprano::Node>::const_iterator it = graphMetaData.constBegin();
2031  it != graphMetaData.constEnd(); ++it) {
2032 
2033  query += QString::fromLatin1(" %1 %2 ;")
2034  .arg( Soprano::Node::resourceToN3(it.key()), it.value().toN3() );
2035  }
2036  query[ query.length() - 1 ] = QChar::fromLatin1('.');
2037  query.append( QLatin1Char('}') );
2038 
2039  executeQuery( query, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
2040 
2041  return graph;
2042 }
2043 
2044 QUrl DataManagementModel::fetchGraph(const QString& app, bool discardable)
2045 {
2046  QPair<QString, bool> cacheItem(app, discardable);
2047  QMutexLocker lock( &d->m_graphCacheMutex );
2048  QUrl* uri = d->m_graphCache.object( cacheItem );
2049  if( uri ) {
2050  return *uri;
2051  }
2052 
2053  QLatin1String type("nrl:InstanceBase");
2054  if( discardable )
2055  type = QLatin1String("nrl:DiscardableInstanceBase");
2056 
2057  const QString query = QString::fromLatin1("select ?g where { ?g a %1 ; nao:maintainedBy ?agent ."
2058  " ?agent nao:identifier %2 . } LIMIT 1")
2059  .arg( type, Soprano::Node::literalToN3(app) );
2060 
2061  Soprano::QueryResultIterator it = executeQuery(query, Soprano::Query::QueryLanguageSparqlNoInference);
2062  if( it.next() ) {
2063  QUrl uri = it[0].uri();
2064  d->m_graphCache.insert( cacheItem, new QUrl(uri) );
2065 
2066  return uri;
2067  }
2068  else {
2069  if( app == QLatin1String("nepomuk") )
2070  return createNepomukGraph();
2071 
2072  QMultiHash<QUrl, Soprano::Node> hash;
2073  if( discardable )
2074  hash.insert( RDF::type(), NRL::DiscardableInstanceBase() );
2075 
2076  QUrl uri = createGraph(app, hash);
2077  d->m_graphCache.insert( cacheItem, new QUrl(uri) );
2078 
2079  return uri;
2080  }
2081 }
2082 
2083 
2084 QUrl Nepomuk2::DataManagementModel::findApplicationResource(const QString &app, bool create)
2085 {
2086  QMutexLocker lock( &d->m_appCacheMutex );
2087  QUrl* uri = d->m_appCache.object( app );
2088  if( uri ) {
2089  return *uri;
2090  }
2091 
2092  Soprano::QueryResultIterator it =
2093  executeQuery(QString::fromLatin1("select ?r where { ?r a nao:Agent . ?r nao:identifier %1 . } LIMIT 1")
2094  .arg( Soprano::Node::literalToN3(app) ),
2095  Soprano::Query::QueryLanguageSparql);
2096  if(it.next()) {
2097  const QUrl newUri = it[0].uri();
2098  d->m_appCache.insert( app, new QUrl(newUri) );
2099 
2100  return newUri;
2101  }
2102  else if(create) {
2103  const QUrl graph = d->m_nepomukGraph;
2104  const QUrl uri = createUri(ResourceUri);
2105 
2106  // the app itself
2107  addStatement( uri, RDF::type(), NAO::Agent(), graph );
2108  addStatement( uri, NAO::identifier(), Soprano::LiteralValue(app), graph );
2109 
2110  KService::List services = KServiceTypeTrader::self()->query(QLatin1String("Application"),
2111  QString::fromLatin1("DesktopEntryName=='%1'").arg(app));
2112  if(services.count() == 1) {
2113  addStatement(uri, NAO::prefLabel(), Soprano::LiteralValue(services.first()->name()), graph);
2114  }
2115 
2116  d->m_appCache.insert( app, new QUrl(uri) );
2117  return uri;
2118  }
2119  else {
2120  return QUrl();
2121  }
2122 }
2123 
2124 QUrl DataManagementModel::createNepomukGraph()
2125 {
2126  // The Nepomuk Graph is special since it contains Nepomuk agent
2127  const QUrl graph = createGraph( QString(), QMultiHash<QUrl, Soprano::Node>() );
2128  const QUrl uri = createUri(ResourceUri);
2129 
2130  // the app itself
2131  addStatement( uri, RDF::type(), NAO::Agent(), graph );
2132  addStatement( uri, NAO::identifier(), Soprano::LiteralValue(QLatin1String("nepomuk")), graph );
2133 
2134  // Fetch the meta graph
2135  QString query = QString::fromLatin1("select ?g where { ?g nrl:coreGraphMetadataFor %1 . }")
2136  .arg( Soprano::Node::resourceToN3(graph) );
2137 
2138  Soprano::QueryResultIterator it = executeQuery( query, Soprano::Query::QueryLanguageSparqlNoInference );
2139  it.next();
2140  addStatement( graph, NAO::maintainedBy(), uri, it[0] );
2141 
2142  return graph;
2143 }
2144 
2145 
2146 QUrl Nepomuk2::DataManagementModel::createUri(Nepomuk2::DataManagementModel::UriType type)
2147 {
2148  QString typeToken;
2149  if(type == GraphUri)
2150  typeToken = QLatin1String("ctx");
2151  else
2152  typeToken = QLatin1String("res");
2153 
2154  while( 1 ) {
2155  QString uuid = QUuid::createUuid().toString();
2156  uuid = uuid.mid(1, uuid.length()-2);
2157 
2158  QString uriString = QString::fromLatin1("nepomuk:/%1/%2").arg( typeToken, uuid );
2159  return QUrl( uriString );
2160  }
2161 }
2162 
2163 
2164 QHash< QUrl, QList< Soprano::Node > > DataManagementModel::addProperty(const QList< QUrl >& resources, const QUrl& property, const QList<Soprano::Node>& nodes, const QString& app, bool signalPropertyChanged)
2165 {
2166  kDebug() << resources << property << nodes << app;
2167  Q_ASSERT(!resources.isEmpty());
2168  Q_ASSERT(!nodes.isEmpty());
2169  Q_ASSERT(!property.isEmpty());
2170 
2171  //
2172  // Check cardinality conditions
2173  //
2174  const int maxCardinality = d->m_classAndPropertyTree->maxCardinality(property);
2175  if( maxCardinality == 1 ) {
2176  if( nodes.size() != 1 ) {
2177  setError(QString::fromLatin1("%1 has cardinality of 1. Cannot set more then one value.").arg(property.toString()), Soprano::Error::ErrorInvalidArgument);
2178  return QHash<QUrl, QList<Soprano::Node> >();
2179  }
2180  }
2181 
2182 
2183  clearError();
2184 
2185  QUrl graph;
2186  if( property == NIE::url() )
2187  graph = d->m_nepomukGraph;
2188  else
2189  graph = fetchGraph(app);
2190 
2191  // add all the data
2192  QHash<QUrl, QList<Soprano::Node> > finalValuesPerResource;
2193  QString insertQ = QString::fromLatin1("sparql insert into %1 {").arg( Soprano::Node::resourceToN3(graph) );
2194  const QString propN3 = Soprano::Node::resourceToN3(property);
2195 
2196  foreach(const QUrl& resUri, resources) {
2197  const QString resN3 = Soprano::Node::resourceToN3(resUri);
2198  foreach(const Soprano::Node& node, nodes) {
2199  insertQ += QString::fromLatin1(" %1 %2 %3 . ").arg( resN3, propN3, node.toN3() );
2200  finalValuesPerResource[resUri].append(node);
2201  }
2202  }
2203  insertQ += QLatin1String("}");
2204  executeQuery( insertQ, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
2205 
2206  // inform interested parties
2207  if(signalPropertyChanged) {
2208  for(QHash<QUrl, QList<Soprano::Node> >::const_iterator it = finalValuesPerResource.constBegin(); it != finalValuesPerResource.constEnd(); ++it) {
2209  d->m_watchManager->changeProperty(it.key(), property, it.value(), QList<Soprano::Node>());
2210  }
2211  if(!finalValuesPerResource.isEmpty()) {
2212  d->m_watchManager->changeSomething();
2213  }
2214  }
2215 
2216  // update modification date
2217  // TODO: This can be done when pushing the data above!
2218  QSet<QUrl> finalResources = finalValuesPerResource.keys().toSet();
2219  updateModificationDate( finalResources );
2220 
2221  return finalValuesPerResource;
2222 }
2223 
2224 bool Nepomuk2::DataManagementModel::doesResourceExist(const QUrl &res, const QUrl& graph) const
2225 {
2226  if(graph.isEmpty()) {
2227  return executeQuery(QString::fromLatin1("ask where { %1 ?p ?v . FILTER(%2) . }")
2228  .arg(Soprano::Node::resourceToN3(res),
2229  createResourceMetadataPropertyFilter(QLatin1String("?p"))),
2230  Soprano::Query::QueryLanguageSparql).boolValue();
2231  }
2232  else {
2233  return executeQuery(QString::fromLatin1("ask where { graph %1 { %2 ?p ?v . FILTER(%3) . } . }")
2234  .arg(Soprano::Node::resourceToN3(graph),
2235  Soprano::Node::resourceToN3(res),
2236  createResourceMetadataPropertyFilter(QLatin1String("?p"))),
2237  Soprano::Query::QueryLanguageSparql).boolValue();
2238  }
2239 }
2240 
2241 QList<QUrl> DataManagementModel::resolveUrls(const QList<QUrl>& urls, const QString& app, bool statLocalFiles)
2242 {
2243  QList<QUrl> nieUrls;
2244  QList<QUrl> finalUrls;
2245  finalUrls.reserve( urls.size() );
2246 
2247  Q_FOREACH(const QUrl& url, urls) {
2248  const QUrl resolved = resolveUrl(url, statLocalFiles);
2249  if( lastError() )
2250  return QList<QUrl>();
2251 
2252  if( resolved.isEmpty() )
2253  nieUrls << url;
2254  else
2255  finalUrls << resolved;
2256  }
2257 
2258  // Create the file urls
2259  if( statLocalFiles && !app.isEmpty() ) {
2260  const QUrl graph = fetchGraph( app );
2261  finalUrls.append( createFileResources(nieUrls, graph) );
2262  }
2263 
2264  return finalUrls;
2265 }
2266 
2267 QUrl Nepomuk2::DataManagementModel::resolveUrl(const QUrl &url, bool statLocalFiles)
2268 {
2269  const UriState state = uriState( url, statLocalFiles );
2270 
2271  if( state == NepomukUri || state == OntologyUri ) {
2272  // nothing to resolve over here
2273  return url;
2274  }
2275 
2276  //
2277  // First check if the URL does exists as resource URI
2278  //
2279  else if( executeQuery(QString::fromLatin1("ask where { %1 ?p ?o . }")
2280  .arg(Soprano::Node::resourceToN3(url)),
2281  Soprano::Query::QueryLanguageSparql).boolValue() ) {
2282  return url;
2283  }
2284 
2285  //
2286  // we resolve all URLs except nepomuk:/ URIs. While the DMS does only use nie:url
2287  // on local files libnepomuk used to use it for everything but nepomuk:/ URIs.
2288  // Thus, we need to handle that legacy data by checking if url does exist as nie:url
2289  //
2290  else {
2291  Soprano::QueryResultIterator it
2292  = executeQuery(QString::fromLatin1("select ?r where { ?r %1 %2 . } limit 1")
2293  .arg(Soprano::Node::resourceToN3(NIE::url()),
2294  Soprano::Node::resourceToN3(url)),
2295  Soprano::Query::QueryLanguageSparql);
2296 
2297  // if the URL is used as a nie:url return the corresponding resource URI
2298  if(it.next()) {
2299  return it[0].uri();
2300  }
2301 
2302  // non-existing unsupported URL
2303  else if( state == OtherUri ) {
2304  QString error = QString::fromLatin1("Unknown protocol '%1' encountered in '%2'.")
2305  .arg(url.scheme(), url.toString());
2306  setError(error, Soprano::Error::ErrorInvalidArgument);
2307  return QUrl();
2308  }
2309 
2310  // if there is no existing URI return an empty match for local files (since we do always want to use nie:url here)
2311  else {
2312  // we only throw an error if the file:/ URL points to a non-existing file AND it does not exist in the database.
2313  if(state == NonExistingFileUrl) {
2314  setError(QString::fromLatin1("Cannot store information about non-existing local files. File '%1' does not exist.").arg(url.toLocalFile()),
2315  Soprano::Error::ErrorInvalidArgument);
2316  }
2317 
2318  return QUrl();
2319  }
2320  }
2321 
2322  // fallback
2323  return url;
2324 }
2325 
2326 
2327 QList<Soprano::Node> DataManagementModel::resolveNodes(const QSet< Soprano::Node >& nodes, const QString& app)
2328 {
2329  QList<QUrl> nieUrls;
2330  QList<Soprano::Node> resolvedNodes;
2331  resolvedNodes.reserve( nodes.size() );
2332 
2333  Q_FOREACH(const Soprano::Node& node, nodes) {
2334  if(node.isResource()) {
2335  const QUrl resolved = resolveUrl(node.uri(), true);
2336  if(resolved.isEmpty() && lastError()) {
2337  return QList<Soprano::Node>();
2338  }
2339 
2340  if(resolved.isEmpty())
2341  nieUrls << node.uri();
2342  else
2343  resolvedNodes << resolved;
2344  }
2345  else {
2346  resolvedNodes << node;
2347  }
2348  }
2349 
2350  const QUrl graph = fetchGraph( app );
2351  QList<QUrl> fileResources = createFileResources( nieUrls, graph );
2352  foreach(const QUrl& resUri, fileResources)
2353  resolvedNodes << resUri;
2354 
2355  return resolvedNodes;
2356 }
2357 
2358 // TODO: emit resource watcher resource creation signals
2359 QList<QUrl> DataManagementModel::createFileResources(const QList< QUrl >& nieUrls, const QUrl& graph)
2360 {
2361  if(nieUrls.isEmpty())
2362  return QList<QUrl>();
2363 
2364  QList<QUrl> resUriList;
2365 
2366  QString query = QString::fromLatin1("sparql insert into %1 { ").arg( Soprano::Node::resourceToN3(graph) );
2367  QString query2 = QString::fromLatin1("sparql insert into %1 { ").arg( Soprano::Node::resourceToN3(d->m_nepomukGraph) );
2368 
2369  foreach( const QUrl& nieUrl, nieUrls ) {
2370  const QUrl resUri = createUri( ResourceUri );
2371  const QString resN3 = Soprano::Node::resourceToN3(resUri);
2372  query += resN3;
2373  query2 += resN3;
2374 
2375  resUriList << resUri;
2376 
2377  QFileInfo fileInfo( nieUrl.toLocalFile() );
2378  if( fileInfo.isDir() )
2379  query += QLatin1String(" a nfo:FileDataObject, nfo:Folder . ");
2380  else
2381  query += QLatin1String(" a nfo:FileDataObject . ");
2382 
2383  // Url, lastModified and created will be in the nepomuk graph
2384  query2 += QString::fromLatin1(" nie:url %1 ; ").arg( Soprano::Node::resourceToN3(nieUrl) );
2385 
2386  Soprano::LiteralValue dtNode( QDateTime::currentDateTime() );
2387  query2 += QString::fromLatin1("nao:lastModified %1 ; nao:created %1 . ")
2388  .arg( Soprano::Node::literalToN3( dtNode ) );
2389  }
2390  query += QLatin1Char('}');
2391  query2 += QLatin1Char('}');
2392 
2393  executeQuery( query, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
2394  executeQuery( query2, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
2395  return resUriList;
2396 }
2397 
2398 
2399 bool Nepomuk2::DataManagementModel::updateNieUrlOnLocalFile(const QUrl &resource, const QUrl &nieUrl)
2400 {
2401  if( !nieUrl.isLocalFile() )
2402  return false;
2403 
2404  kDebug() << resource << "->" << nieUrl;
2405 
2406  //
2407  // Since KDE 4.4 we use nepomuk:/res/<UUID> Uris for all resources including files. Thus, moving a file
2408  // means updating two things:
2409  // 1. the nie:url property
2410  // 2. the nie:isPartOf relation (only necessary if the file and not the whole folder was moved)
2411  //
2412 
2413  //
2414  // Now update the nie:url, nfo:fileName, and nie:isPartOf relations.
2415  //
2416  // We do NOT use setProperty to avoid the overhead and data clutter of creating
2417  // new metadata graphs for the changed data.
2418  //
2419 
2420  // remember for url, filename, and parent the graph they are defined in
2421  QUrl resUri, oldNieUrl, oldNieUrlGraph, oldParentResource, oldParentResourceGraph, oldFileNameGraph;
2422  QString oldFileName;
2423 
2424  // we do not use isLocalFileUrl() here since we also handle already moved files
2425  if(resource.scheme() == QLatin1String("file")) {
2426  oldNieUrl = resource;
2427  Soprano::QueryResultIterator it
2428  = executeQuery(QString::fromLatin1("select distinct ?gu ?gf ?gp ?r ?f ?p where { "
2429  "graph ?gu { ?r %2 %1 . } . "
2430  "OPTIONAL { graph ?gf { ?r %3 ?f . } . } . "
2431  "OPTIONAL { graph ?gp { ?r %4 ?p . } . } . "
2432  "} LIMIT 1")
2433  .arg(Soprano::Node::resourceToN3(resource),
2434  Soprano::Node::resourceToN3(NIE::url()),
2435  Soprano::Node::resourceToN3(NFO::fileName()),
2436  Soprano::Node::resourceToN3(NIE::isPartOf())),
2437  Soprano::Query::QueryLanguageSparql);
2438  if(it.next()) {
2439  resUri= it["r"].uri();
2440  oldNieUrlGraph = it["gu"].uri();
2441  oldParentResource = it["p"].uri();
2442  oldParentResourceGraph = it["gp"].uri();
2443  oldFileName = it["f"].toString();
2444  oldFileNameGraph = it["gf"].uri();
2445  }
2446  }
2447  else {
2448  resUri = resource;
2449  Soprano::QueryResultIterator it
2450  = executeQuery(QString::fromLatin1("select distinct ?gu ?gf ?gp ?u ?f ?p where { "
2451  "graph ?gu { %1 %2 ?u . } . "
2452  "OPTIONAL { graph ?gf { %1 %3 ?f . } . } . "
2453  "OPTIONAL { graph ?gp { %1 %4 ?p . } . } . "
2454  "} LIMIT 1")
2455  .arg(Soprano::Node::resourceToN3(resource),
2456  Soprano::Node::resourceToN3(NIE::url()),
2457  Soprano::Node::resourceToN3(NFO::fileName()),
2458  Soprano::Node::resourceToN3(NIE::isPartOf())),
2459  Soprano::Query::QueryLanguageSparql);
2460  if(it.next()) {
2461  oldNieUrl = it["u"].uri();
2462  oldNieUrlGraph = it["gu"].uri();
2463  oldParentResource = it["p"].uri();
2464  oldParentResourceGraph = it["gp"].uri();
2465  oldFileName = it["f"].toString();
2466  oldFileNameGraph = it["gf"].uri();
2467  }
2468  }
2469 
2470  if (!oldNieUrlGraph.isEmpty()) {
2471  const QString oldBasePath = KUrl(oldNieUrl).path(KUrl::AddTrailingSlash);
2472  const QString newBasePath = KUrl(nieUrl).path(KUrl::AddTrailingSlash);
2473 
2474  if( oldBasePath == newBasePath )
2475  return true;
2476 
2477  removeStatement(resUri, NIE::url(), oldNieUrl, oldNieUrlGraph);
2478  addStatement(resUri, NIE::url(), nieUrl, oldNieUrlGraph);
2479 
2480  d->m_watchManager->changeProperty( resource, NIE::url(),
2481  QList<Soprano::Node>() << nieUrl,
2482  QList<Soprano::Node>() << oldNieUrl );
2483 
2484  if (!oldFileNameGraph.isEmpty()) {
2485  // we only update the filename if it actually changed
2486  if(KUrl(oldNieUrl).fileName() != KUrl(nieUrl).fileName()) {
2487  Soprano::Node oldN = Soprano::LiteralValue(oldFileName);
2488  Soprano::Node newN = Soprano::LiteralValue(KUrl(nieUrl).fileName());
2489 
2490  removeStatement(resUri, NFO::fileName(), oldN, oldFileNameGraph);
2491  addStatement(resUri, NFO::fileName(), newN, oldFileNameGraph);
2492 
2493  d->m_watchManager->changeProperty( resource, NIE::url(),
2494  QList<Soprano::Node>() << newN,
2495  QList<Soprano::Node>() << oldN );
2496  }
2497  }
2498 
2499  if (!oldParentResourceGraph.isEmpty()) {
2500  // we only update the parent folder if it actually changed
2501  const KUrl nieUrlParent = KUrl(nieUrl).directory(KUrl::IgnoreTrailingSlash);
2502  const KUrl oldUrlParent = KUrl(oldNieUrl).directory(KUrl::IgnoreTrailingSlash);
2503  if(nieUrlParent != oldUrlParent) {
2504  removeStatement(resUri, NIE::isPartOf(), oldParentResource, oldParentResourceGraph);
2505  const QUrl newParentRes = resolveUrl(nieUrlParent);
2506  if (!newParentRes.isEmpty()) {
2507  addStatement(resUri, NIE::isPartOf(), newParentRes, oldParentResourceGraph);
2508  }
2509  }
2510  }
2511 
2512  //
2513  // Update all children
2514  // We only need to update the nie:url properties. Filenames and nie:isPartOf relations cannot change
2515  // due to a renaming of the parent folder.
2516  //
2517  // CAUTION: The trailing slash on the from URL is essential! Otherwise we might match the newly added
2518  // URLs, too (in case a rename only added chars to the name)
2519  //
2520  const QString query = QString::fromLatin1("select distinct ?r ?u where { "
2521  "?r %1 ?u . "
2522  "FILTER(REGEX(STR(?u),'^%2')) . "
2523  "}")
2524  .arg(Soprano::Node::resourceToN3(NIE::url()),
2525  KUrl(oldNieUrl).url(KUrl::AddTrailingSlash));
2526 
2527  //
2528  // We cannot use one big loop since our updateMetadata calls below can change the iterator
2529  // which could have bad effects like row skipping. Thus, we handle the urls in chunks of
2530  // cached items.
2531  //
2532  forever {
2533  const QList<Soprano::BindingSet> urls = executeQuery(query + QLatin1String( " LIMIT 500" ),
2534  Soprano::Query::QueryLanguageSparql)
2535  .allBindings();
2536  if (urls.isEmpty())
2537  break;
2538 
2539  for (int i = 0; i < urls.count(); ++i) {
2540  const KUrl u = urls[i]["u"].uri();
2541  const QUrl r = urls[i]["r"].uri();
2542 
2543  // now construct the new URL
2544  const QString oldRelativePath = u.path().mid(oldBasePath.length());
2545  const KUrl newUrl(newBasePath + oldRelativePath);
2546 
2547  QString cmd = QString::fromLatin1("sparql with %1 delete { %2 nie:url %3 }"
2548  "insert { %2 nie:url %4 . }")
2549  .arg( Soprano::Node::resourceToN3(d->m_nepomukGraph),
2550  Soprano::Node::resourceToN3(r),
2551  Soprano::Node::resourceToN3(u),
2552  Soprano::Node::resourceToN3(newUrl) );
2553 
2554  executeQuery( cmd, Soprano::Query::QueryLanguageUser, QLatin1String("sql") );
2555  if( lastError() )
2556  return false;
2557  }
2558  }
2559 
2560  return true;
2561  }
2562  else {
2563  // no old nie:url found
2564  return false;
2565  }
2566 }
2567 
2568 //void Nepomuk2::DataManagementModel::insertStatements(const QSet<QUrl> &resources, const QUrl &property, const QSet<Soprano::Node> &values, const QUrl &graph)
2569 //{
2570 // const QString propertyString = Soprano::Node::resourceToN3(property);
2571 
2572 // QString query = QString::fromLatin1("insert into %1 { ")
2573 // .arg(Soprano::Node::resourceToN3(graph));
2574 
2575 // foreach(const QUrl& res, resources) {
2576 // foreach(const Soprano::Node& value, values) {
2577 // query.append(QString::fromLatin1("%1 %2 %3 . ")
2578 // .arg(Soprano::Node::resourceToN3(res),
2579 // propertyString,
2580 // value.toN3()));
2581 // }
2582 // }
2583 // query.append(QLatin1String("}"));
2584 
2585 // executeQuery(query, Soprano::Query::QueryLanguageSparql);
2586 //}
2587 
2588 Nepomuk2::ClassAndPropertyTree* Nepomuk2::DataManagementModel::classAndPropertyTree()
2589 {
2590  return d->m_classAndPropertyTree;
2591 }
2592 
2593 bool Nepomuk2::DataManagementModel::isProtectedProperty(const QUrl &prop) const
2594 {
2595  return d->m_protectedProperties.contains(prop);
2596 }
2597 
2598 Nepomuk2::ResourceWatcherManager* Nepomuk2::DataManagementModel::resourceWatcherManager() const
2599 {
2600  return d->m_watchManager;
2601 }
2602 
2603 TypeCache* DataManagementModel::typeCache()
2604 {
2605  return d->m_typeCache;
2606 }
2607 
2608 QUrl DataManagementModel::nepomukGraph()
2609 {
2610  return d->m_nepomukGraph;
2611 }
2612 
2613 
2614 void Nepomuk2::DataManagementModel::removeAllResources(const QSet< QUrl >& resourceUris, RemovalFlags flags)
2615 {
2616  if( resourceUris.isEmpty() )
2617  return;
2618 
2619  QSet<QUrl> resolvedResources(resourceUris);
2620 
2621  if( flags & RemoveSubResoures ) {
2622  //
2623  // Handle the sub-resources:
2624  // this has to be done before deleting the resouces in resolvedResources. Otherwise the nao:hasSubResource relationships are already gone!
2625  //
2626  // Explanation of the query:
2627  // The query selects all subresources of the resources in resolvedResources.
2628  // It then filters out the sub-resources that are related from other resources that are not the ones being deleted.
2629  //
2630  QSet<QUrl> subResources = resolvedResources;
2631  int resCount = 0;
2632  do {
2633  resCount = resolvedResources.count();
2634 
2635  QString q = QString::fromLatin1("select ?r where { ?r ?p ?o . "
2636  "?parent nao:hasSubResource ?r . "
2637  "FILTER(?parent in (%1)) . "
2638  "FILTER NOT EXISTS { ?r2 ?p3 ?r . FILTER(%2) . "
2639  " FILTER NOT EXISTS { ?x nao:hasSubResource ?r2 . FILTER(?x in (%1)) . } "
2640  "} }")
2641  .arg(resourcesToN3(subResources).join(QLatin1String(",")),
2642  createResourceFilter(resolvedResources, QLatin1String("?r2")));
2643 
2644  Soprano::QueryResultIterator it = executeQuery( q, Soprano::Query::QueryLanguageSparqlNoInference );
2645  subResources.clear();
2646  while(it.next()) {
2647  subResources << it[0].uri();
2648  }
2649  resolvedResources += subResources;
2650  } while(resCount < resolvedResources.count());
2651  }
2652 
2653 
2654  // Filter out all the resource uris that don't actually exist
2655  QSet<QUrl> actuallyRemovedResources;
2656  Soprano::QueryResultIterator it
2657  = executeQuery(QString::fromLatin1("select distinct ?r where { ?r ?p ?o . FILTER(?r in (%1)) . }")
2658  .arg(resourcesToN3(resolvedResources).join(QLatin1String(","))),
2659  Soprano::Query::QueryLanguageSparqlNoInference);
2660  while(it.next()) {
2661  actuallyRemovedResources << it[0].uri();
2662  }
2663 
2664  // get the resources that we modify by removing relations to one of the deleted ones
2665  QSet<QUrl> modifiedResources;
2666  QList<Soprano::Statement> removedStatements;
2667  foreach(const QUrl& resUri, resolvedResources) {
2668  it = executeQuery(QString::fromLatin1("select distinct ?o ?p where { ?o ?p %1 . }")
2669  .arg(Soprano::Node::resourceToN3(resUri)),
2670  Soprano::Query::QueryLanguageSparqlNoInference);
2671  while(it.next()) {
2672  modifiedResources << it[0].uri();
2673  removedStatements << Soprano::Statement(it[0].uri(), it[1].uri(), resUri);
2674  }
2675  }
2676  modifiedResources -= actuallyRemovedResources;
2677 
2678  // remove the resources and inform interested parties
2679  foreach(const Soprano::Node& res, actuallyRemovedResources) {
2680  // The WatcherManaager fill automatically fetch the types
2681  d->m_watchManager->removeResource(res.uri(), QList<QUrl>());
2682 
2683  removeAllStatements(res, Soprano::Node(), Soprano::Node());
2684  removeAllStatements(Soprano::Node(), Soprano::Node(), res);
2685  }
2686  updateModificationDate(modifiedResources);
2687 
2688  foreach(const Soprano::Statement& st, removedStatements) {
2689  d->m_watchManager->changeProperty( st.subject().uri(), st.predicate().uri(),
2690  QList<Soprano::Node>(),
2691  QList<Soprano::Node>() << st.object() );
2692  }
2693 
2694  if(!actuallyRemovedResources.isEmpty()) {
2695  d->m_watchManager->changeSomething();
2696  }
2697 }
2698 
2699 #include "datamanagementmodel.moc"
Nepomuk2::StoreIdentificationMode
StoreIdentificationMode
The identification mode used by storeResources().
Definition: datamanagement.h:349
QMultiHash
Nepomuk2::SimpleResourceGraph::addStatement
void addStatement(const Soprano::Statement &statement)
Definition: simpleresourcegraph.cpp:261
Nepomuk2::ErrorCode
ErrorCode
Definition: resource.h:44
Nepomuk2::ResourceMerger
Definition: resourcemerger.h:45
Nepomuk2::MergeDuplicateResources
When this is enabled each SimpleResource will be checked to make sure a duplicate of it does not alre...
Definition: datamanagement.h:385
Nepomuk2::TypeCache
Definition: typecache.h:34
Nepomuk2::ExcludeRelatedResources
Exclude related resources, only include literal properties.
Definition: datamanagement.h:402
Nepomuk2::addProperty
KJob * addProperty(const QList< QUrl > &resources, const QUrl &property, const QVariantList &values, const KComponentData &component=KGlobal::mainComponent())
Add one or more property values to one or more resources.
Definition: datamanagement.cpp:36
Nepomuk2::DataManagementModel::storeResources
QHash< QUrl, QUrl > storeResources(const SimpleResourceGraph &resources, const QString &app, Nepomuk2::StoreIdentificationMode identificationMode=Nepomuk2::IdentifyNew, Nepomuk2::StoreResourcesFlags flags=Nepomuk2::NoStoreResourcesFlags, const QHash< QUrl, QVariant > &additionalMetadata=(QHash< QUrl, QVariant >()))
Definition: datamanagementmodel.cpp:1285
resourceidentifier.h
Nepomuk2::ExcludeDiscardableData
Exclude discardable data, ie. data which can be re-generated.
Definition: datamanagement.h:399
Nepomuk2::DataManagementModel::removeResources
void removeResources(const QList< QUrl > &resources, Nepomuk2::RemovalFlags flags, const QString &app)
Remove resources from the database.
Definition: datamanagementmodel.cpp:907
Nepomuk2::SimpleResourceGraph::toStatementGraph
Soprano::Graph toStatementGraph() const
Definition: simpleresourcegraph.cpp:274
Nepomuk2::SimpleResource
Represents a snapshot of one Nepomuk resource.
Definition: simpleresource.h:46
Nepomuk2::DataManagementModel::mergeResources
void mergeResources(const QList< QUrl > &resources, const QString &app)
Merges all the resources into one.
Definition: datamanagementmodel.cpp:1608
Nepomuk2::SimpleResource::properties
PropertyHash properties() const
Definition: simpleresource.cpp:155
Nepomuk2::DataManagementModel::~DataManagementModel
~DataManagementModel()
Definition: datamanagementmodel.cpp:268
Nepomuk2::mergeResources
KJob * mergeResources(const QUrl &resource1, const QUrl &resource2, const KComponentData &component=KGlobal::mainComponent())
Merge two resources into one.
Definition: datamanagement.cpp:125
Nepomuk2::DataManagementModel::removeProperty
void removeProperty(const QList< QUrl > &resources, const QUrl &property, const QVariantList &values, const QString &app)
Remove the property property with values from each resource in resources.
Definition: datamanagementmodel.cpp:617
QHash
QObject
Nepomuk2::Sync::SyncResource::isValid
bool isValid() const
Definition: syncresource.cpp:239
Nepomuk2::SimpleResourceGraph::isEmpty
bool isEmpty() const
Definition: simpleresourcegraph.cpp:207
resourcewatchermanager.h
Nepomuk2::DataManagementModel::setProperty
void setProperty(const QList< QUrl > &resources, const QUrl &property, const QVariantList &values, const QString &app)
Set, ie.
Definition: datamanagementmodel.cpp:465
Nepomuk2::DataManagementModel::exportResources
QString exportResources(const QList< QUrl > &resources, Soprano::RdfSerialization serialization, const QString &userSerialization=QString(), DescribeResourcesFlags flags=NoDescribeResourcesFlags, const QList< QUrl > &targetParties=QList< QUrl >())
Export a set of resources, i.e.
Definition: datamanagementmodel.cpp:1925
Nepomuk2::ClassAndPropertyTree
Definition: classandpropertytree.h:45
Nepomuk2::ClassAndPropertyTree::contains
bool contains(const QUrl &uri) const
Returns true if the uri is a Class or a Property.
Definition: classandpropertytree.cpp:522
Nepomuk2::SimpleResource::addProperty
void addProperty(const QUrl &property, const QVariant &value)
Add a property.
Definition: simpleresource.cpp:206
Nepomuk2::SimpleResourceGraph
Definition: simpleresourcegraph.h:48
Nepomuk2::describeResources
DescribeResourcesJob * describeResources(const QList< QUrl > &resources, DescribeResourcesFlags flags=NoDescribeResourcesFlags, const QList< QUrl > &targetParties=QList< QUrl >())
Retrieve all information about a set of resources.
Definition: datamanagement.cpp:171
Nepomuk2::DataManagementModel::createResource
QUrl createResource(const QList< QUrl > &types, const QString &label, const QString &description, const QString &app)
Create a new resource with several types.
Definition: datamanagementmodel.cpp:814
Nepomuk2::SimpleResource::isValid
bool isValid() const
Definition: simpleresource.cpp:132
Nepomuk2::SimpleResourceGraph::remove
void remove(const QUrl &uri)
Definition: simpleresourcegraph.cpp:98
Nepomuk2::AnonymizeNepomukUris
Replaces the resouce URIs which are specific to this instance of Nepomuk with blank nodes...
Definition: datamanagement.h:406
Nepomuk2::DataManagementModel::describeResources
SimpleResourceGraph describeResources(const QList< QUrl > &resources, DescribeResourcesFlags flags=NoDescribeResourcesFlags, const QList< QUrl > &targetParties=QList< QUrl >())
Describe a set of resources, i.e.
Definition: datamanagementmodel.cpp:1711
classandpropertytree.h
typecache.h
Nepomuk2::Sync::SyncResource::toStatementList
QList< Soprano::Statement > toStatementList() const
Definition: syncresource.cpp:75
Nepomuk2::DataManagementModel::addProperty
void addProperty(const QList< QUrl > &resources, const QUrl &property, const QVariantList &values, const QString &app)
Add property with values to each resource from resources.
Definition: datamanagementmodel.cpp:321
Nepomuk2::ClassAndPropertyTree::self
static ClassAndPropertyTree * self()
Definition: classandpropertytree.cpp:591
Nepomuk2::SimpleResourceGraph::removeAll
void removeAll(const QUrl &uri, const QUrl &property, const QVariant &value=QVariant())
Remove all properties matching the provided parameters.
Definition: simpleresourcegraph.cpp:131
Nepomuk2::DataManagementModel::importResources
void importResources(const QUrl &url, const QString &app, Soprano::RdfSerialization serialization, const QString &userSerialization=QString(), Nepomuk2::StoreIdentificationMode identificationMode=Nepomuk2::IdentifyNew, Nepomuk2::StoreResourcesFlags flags=Nepomuk2::NoStoreResourcesFlags, const QHash< QUrl, QVariant > &additionalMetadata=(QHash< QUrl, QVariant >()))
Import an RDF graph from a URL.
Definition: datamanagementmodel.cpp:1558
Nepomuk2::DataManagementModel::DataManagementModel
DataManagementModel(ClassAndPropertyTree *tree, Soprano::Model *model, QObject *parent=0)
Definition: datamanagementmodel.cpp:211
Nepomuk2::Sync::SyncResource::uri
KUrl uri() const
Definition: syncresource.cpp:127
Nepomuk2::storeResources
StoreResourcesJob * storeResources(const Nepomuk2::SimpleResourceGraph &resources, Nepomuk2::StoreIdentificationMode identificationMode=Nepomuk2::IdentifyNew, Nepomuk2::StoreResourcesFlags flags=Nepomuk2::NoStoreResourcesFlags, const QHash< QUrl, QVariant > &additionalMetadata=QHash< QUrl, QVariant >(), const KComponentData &component=KGlobal::mainComponent())
Store many resources at once.
Definition: datamanagement.cpp:144
resourcemerger.h
Nepomuk2::DataManagementModel::removeDataByApplication
void removeDataByApplication(const QList< QUrl > &resources, RemovalFlags flags, const QString &app)
Remove all information about resources from the database which have been created by a specific applic...
Definition: datamanagementmodel.cpp:948
Nepomuk2::DataManagementModel::clearCache
void clearCache()
Clear the internal cache present in the model.
Definition: datamanagementmodel.cpp:247
Nepomuk2::ResourceWatcherManager
Definition: resourcewatchermanager.h:38
simpleresource.h
Nepomuk2::DataManagementModel::nepomukGraph
QUrl nepomukGraph()
Definition: datamanagementmodel.cpp:2608
datamanagement.h
simpleresourcegraph.h
UriState
UriState
Definition: datamanagementmodel.cpp:137
Nepomuk2::ResourceIdentifier
Definition: resourceidentifier.h:31
Nepomuk2::DataManagementModel::resourceWatcherManager
ResourceWatcherManager * resourceWatcherManager() const
used by the unit tests
Definition: datamanagementmodel.cpp:2598
Nepomuk2::removeProperties
KJob * removeProperties(const QList< QUrl > &resources, const QList< QUrl > &properties, const KComponentData &component=KGlobal::mainComponent())
Remove one or more properties from one or more resources.
Definition: datamanagement.cpp:74
Nepomuk2::SimpleResourceGraph::allResourceUris
QList< QUrl > allResourceUris() const
Get a list of the URIs of all resources in this graph.
Definition: simpleresourcegraph.cpp:197
Nepomuk2::qHash
uint qHash(const SimpleResource &res)
Definition: simpleresource.cpp:297
Nepomuk2::Sync::SyncResource::setUri
void setUri(const Soprano::Node &node)
If node is resource node the uri is set to the node's uri Otherwise if node is a blank node then the ...
Definition: syncresource.cpp:117
Nepomuk2::removeResources
KJob * removeResources(const QList< QUrl > &resources, Nepomuk2::RemovalFlags flags=Nepomuk2::NoRemovalFlags, const KComponentData &component=KGlobal::mainComponent())
Completely remove resources from the database.
Nepomuk2::SimpleResource::uri
QUrl uri() const
Definition: simpleresource.cpp:90
Nepomuk2::resourcesToN3
QStringList resourcesToN3(const T &urls)
Convert a list or set or QUrls into a list of N3 formatted strings.
Definition: nepomuktools.h:35
datamanagementmodel.h
Nepomuk2::SimpleResourceGraph::contains
bool contains(const SimpleResource &res) const
Definition: simpleresourcegraph.cpp:161
nepomuktools.h
Nepomuk2::DataManagementModel::removeProperties
void removeProperties(const QList< QUrl > &resources, const QList< QUrl > &properties, const QString &app)
Remove all statements involving any proerty from properties from all resources in resources...
Definition: datamanagementmodel.cpp:713
Nepomuk2::RemoveSubResoures
Remove sub resources of the resources specified in the parameters.
Definition: datamanagement.h:245
syncresource.h
Nepomuk2::Sync::SyncResource
A SyncResource is a convenient way of storing a set of properties and objects for a common subject...
Definition: syncresource.h:53
Nepomuk2::NoRemovalFlags
No flags - default behaviour.
Definition: datamanagement.h:237
Nepomuk2::DataManagementModel::typeCache
TypeCache * typeCache()
Definition: datamanagementmodel.cpp:2603
Nepomuk2::SimpleResource::setUri
void setUri(const QUrl &uri)
Setting an invalid/empty uri will create a new random ID.
Definition: simpleresource.cpp:95
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:48:08 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