I've cleaned up the code, and added some comments. It works perfectly. It would be really nice if somebody (hint Trueg) could review the code.
I'm posting a short summary of what the code does -* PROBLEM:* The Strigi analyzer on analyzing a file creates additional metadata which is linked to the file's metadata. Example - When indexing an a audio file, say, "Coldplay - Yellow" from the Album 'X&Y'. It will create 2 additional resources one of type nco:Contact and the other of type nmm:MusicAlbum. It will do that for every indexed song that has the artist 'Coldplay' and album 'X&Y'. Nepomuk simply adds all the data to the database without checking if similar contacts or albums exist. This leads to multiple contacts, albums .. with the same names, and makes queries harder to perform ( and longer ). Additionally, some files may not contain totally accurate Metadata. For example - I have a song whose metadata says that it has 2 artists both of whom are called "Coldplay" (exact same spelling) The Strigi analyzer creates 2 different resources for both of these identical contacts. They should be merged. Additionally, all the metadata created ( even the contacts, albums, etc ) were contained in the same discardable graph. So when the file was deleted the additional metadata was deleted as well. * SOLUTION:* The Nepomuk Indexer ( kdebase/runtime/nepomuk/strigibackend/indexerwriter.* ) now contains an additional thread, which takes all the statements from the IndexWriter, resolves duplicates and merges them. It has been done in a separate thread so that the indexing speed does not suffer. The current patch checks for blank Nodes in the object / subject of the file's metadata, and tries to find them or creates them if not present. The patch reverts to a the original behavior if any of the additionally generated metadata ( contacts, albums) contain any blank nodes. in order to fix this, a full blown dependency resolution algorithm would be required. I don't think that it is currently required. The patch also creates a different graph ( discardable ) for each individual resource. *Problem not fixed :* This will only work on newly indexed files and does not affect the files which have already been indexed. We'll need some kind of merger to do that. It's a lot simpler to just re-index the files, but I don't think the end users would like that. *A New Problem : *Since the additional metadata now has it's own graph. It will not be deleted if the file is deleted. We need some kind of cleaner which cleans resources which are no longer in use. And, that's about it. - Vishesh Handa On Tue, Jul 13, 2010 at 7:42 PM, Vishesh Handa <[email protected]> wrote: > Yes, I finally implemented it. :-D > > Please note that this is just the initial design. If you don't like the API > design, or anything in particular, please tell me! > > I've debugged it, and it seems to running okay, but I'll test it more > thoroughly, and benchmark it later. For what it's worth, it seems to be > somewhat faster. > > There is one obvious bug in the implementation which I've highlighted. > There are ways to fix it, but that would make the code messier than it > already is, and AFIAK it currently isn't a problem, but it could be in the > future. > > - Vishesh Handa > >
Index: strigifeeder.h =================================================================== --- strigifeeder.h (revision 0) +++ strigifeeder.h (revision 0) @@ -0,0 +1,94 @@ +/* + Copyright (C) 2010 Vishesh Handa <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#ifndef STRIGIFEEDER_H +#define STRIGIFEEDER_H + +#include <QtCore/QThread> +#include <QtCore/QMutex> +#include <QtCore/QWaitCondition> +#include <QtCore/QUrl> +#include <QtCore/QQueue> +#include <QtCore/QStack> +#include <QtCore/QSet> + +namespace Soprano { + class Model; + class Statement; + class Node; +} + +namespace Nepomuk { + class StrigiFeeder : public QThread + { + Q_OBJECT + public: + StrigiFeeder( Soprano::Model* model, QObject* parent = 0); + virtual ~StrigiFeeder(); + + void stop(); + void run(); + + public Q_SLOTS: + void begin( const QUrl & uri ); + + /** + * Adds \p st to the list of statements to be added. + * \p st may contain Blank Nodes.The context is ignored. + * Should be called between begin and end + * + * \sa begin end + */ + void addStatement( const Soprano::Statement & st ); + + /** + * Adds the subject, predicate, object to the list of statements + * to be added. The Subject or Object may contain Blank Nodes + * Should be called between begin and end + * + * \sa begin end + */ + void addStatement( const Soprano::Node & subject, + const Soprano::Node & predicate, + const Soprano::Node & object ); + + void end(); + + private: + struct Request { + QUrl uri; + QSet<Soprano::Statement> statements; + }; + QQueue<Request> m_queue; + QStack<Request> m_stack; + + Soprano::Model* m_model; + + QMutex m_queueMutex; + QWaitCondition m_queueWaiter; + bool m_stopped; + + /// Generates a discardable graph for \p resourceUri + QUrl generateGraph( const QUrl& resourceUri ); + }; + +} + +#endif // STRIGIFEEDER_H Index: nepomukindexwriter.cpp =================================================================== --- nepomukindexwriter.cpp (revision 1149492) +++ nepomukindexwriter.cpp (working copy) @@ -1,5 +1,6 @@ /* Copyright (C) 2007-2010 Sebastian Trueg <[email protected]> + Copyright (C) 2010 Vishesh Handa <[email protected]> This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -22,6 +23,7 @@ #include "nfo.h" #include "nie.h" #include "nrl.h" +#include "strigifeeder.h" #include <Soprano/Soprano> #include <Soprano/Vocabulary/RDF> @@ -118,6 +120,25 @@ namespace { return uri; } + class RegisteredFieldData + { + public: + RegisteredFieldData( const QUrl& prop, QVariant::Type t ) + : property( prop ), + dataType( t ), + isRdfType( prop == Vocabulary::RDF::type() ) { + } + + /// The actual property URI + QUrl property; + + /// the literal range of the property (if applicable) + QVariant::Type dataType; + + /// caching QUrl comparison + bool isRdfType; + }; + /** * Data objects that are used to store information relative to one * indexing run. @@ -128,7 +149,7 @@ namespace { FileMetaData( const Strigi::AnalysisResult* idx ); /// stores basic data including the nie:url and the nrl:GraphMetadata in \p model - void storeBasicData( Soprano::Model* model ); + void storeBasicData( Nepomuk::StrigiFeeder* feeder ); /// map a blank node to a resource QUrl mapNode( const std::string& s ); @@ -142,37 +163,12 @@ namespace { /// The file info - saved to prevent multiple stats QFileInfo fileInfo; - /// The URI of the graph that contains all indexed statements - QUrl context; - /// a buffer for all plain-text content generated by strigi std::string content; private: /// The Strigi result const Strigi::AnalysisResult* m_analysisResult; - - /// mapping from blank nodes used in addTriplet to our urns - QMap<std::string, QUrl> m_blankNodeMap; - }; - - class RegisteredFieldData - { - public: - RegisteredFieldData( const QUrl& prop, QVariant::Type t ) - : property( prop ), - dataType( t ), - isRdfType( prop == Vocabulary::RDF::type() ) { - } - - /// The actual property URI - QUrl property; - - /// the literal range of the property (if applicable) - QVariant::Type dataType; - - /// caching QUrl comparison - bool isRdfType; }; FileMetaData::FileMetaData( const Strigi::AnalysisResult* idx ) @@ -185,92 +181,64 @@ namespace { // this will automatically find previous uses of the file in question // with backwards compatibility resourceUri = Nepomuk::Resource( fileUrl ).resourceUri(); - - // use a new random context URI - context = Nepomuk::ResourceManager::instance()->generateUniqueUri( "ctx" ); - } - - QUrl FileMetaData::mapNode( const std::string& s ) - { - if ( s[0] == ':' ) { - if( m_blankNodeMap.contains( s ) ) { - return m_blankNodeMap[s]; - } - else { - QUrl urn = Nepomuk::ResourceManager::instance()->generateUniqueUri( QString() ); - m_blankNodeMap.insert( s, urn ); - return urn; - } - } - // special case to properly handle nie:isPartOf relations created for containers - else if ( s == m_analysisResult->path() ) { - return resourceUri; - } - else { - return QUrl::fromEncoded( s.c_str() ); - } } - void FileMetaData::storeBasicData( Soprano::Model* model ) + void FileMetaData::storeBasicData( Nepomuk::StrigiFeeder * feeder ) { - model->addStatement( resourceUri, Nepomuk::Vocabulary::NIE::url(), fileUrl, context ); + feeder->addStatement( resourceUri, Nepomuk::Vocabulary::NIE::url(), fileUrl ); // Strigi only indexes files and extractors mostly (if at all) store the nie:DataObject type (i.e. the contents) // Thus, here we go the easy way and mark each indexed file as a nfo:FileDataObject. - model->addStatement( resourceUri, + feeder->addStatement( resourceUri, Vocabulary::RDF::type(), - Nepomuk::Vocabulary::NFO::FileDataObject(), - context ); + Nepomuk::Vocabulary::NFO::FileDataObject() ); if ( fileInfo.isDir() ) { - model->addStatement( resourceUri, + feeder->addStatement( resourceUri, Vocabulary::RDF::type(), - Nepomuk::Vocabulary::NFO::Folder(), - context ); + Nepomuk::Vocabulary::NFO::Folder() ); } - - - // create the provedance data for the data graph - // TODO: add more data at some point when it becomes of interest - QUrl metaDataContext = Nepomuk::ResourceManager::instance()->generateUniqueUri( "ctx" ); - model->addStatement( context, - Vocabulary::RDF::type(), - Nepomuk::Vocabulary::NRL::DiscardableInstanceBase(), - metaDataContext ); - model->addStatement( context, - Vocabulary::NAO::created(), - LiteralValue( QDateTime::currentDateTime() ), - metaDataContext ); - model->addStatement( context, - Strigi::Ontology::indexGraphFor(), - resourceUri, - metaDataContext ); - model->addStatement( metaDataContext, - Vocabulary::RDF::type(), - Nepomuk::Vocabulary::NRL::GraphMetadata(), - metaDataContext ); - model->addStatement( metaDataContext, - Nepomuk::Vocabulary::NRL::coreGraphMetadataFor(), - context, - metaDataContext ); } FileMetaData* fileDataForResult( const Strigi::AnalysisResult* idx ) { return static_cast<FileMetaData*>( idx->writerData() ); } + + Soprano::Node createNode( const std::string & str ) { + QString identifier = QString::fromUtf8( str.c_str() ); + + if( !identifier.isEmpty() && identifier[0] == ':' ) { + identifier.remove( 0, 1 ); + return Soprano::Node::createBlankNode( identifier ); + } + + //Not a blank node + return Soprano::Node( QUrl(identifier) ); + } } class Strigi::NepomukIndexWriter::Private { public: - Private() + Private( Soprano::Model * model ) + : repository( model ) { literalTypes[FieldRegister::stringType] = QVariant::String; literalTypes[FieldRegister::floatType] = QVariant::Double; literalTypes[FieldRegister::integerType] = QVariant::Int; literalTypes[FieldRegister::binaryType] = QVariant::ByteArray; literalTypes[FieldRegister::datetimeType] = QVariant::DateTime; // Strigi encodes datetime as unsigned integer, i.e. addValue( ..., uint ) + + feeder = new Nepomuk::StrigiFeeder( model ); + feeder->start(); + } + + ~Private() + { + feeder->stop(); + feeder->wait(); + delete feeder; } QVariant::Type literalType( const Strigi::FieldProperties& strigiType ) { @@ -310,6 +278,8 @@ public: QStack<const Strigi::AnalysisResult*> currentResultStack; + Nepomuk::StrigiFeeder* feeder; + private: QHash<std::string, QVariant::Type> literalTypes; }; @@ -318,8 +288,7 @@ private: Strigi::NepomukIndexWriter::NepomukIndexWriter( Soprano::Model* model ) : Strigi::IndexWriter() { - d = new Private; - d->repository = model; + d = new Private( model ); Util::storeStrigiMiniOntology( d->repository ); } @@ -387,8 +356,12 @@ void Strigi::NepomukIndexWriter::startAn if ( data->resourceUri.isEmpty() ) data->resourceUri = Nepomuk::ResourceManager::instance()->generateUniqueUri( QString() ); + // Start the feeder + kDebug() << "Starting the feeder"; + d->feeder->begin( data->resourceUri ); + // store initial data to make sure newly created URIs are reused directly by libnepomuk - data->storeBasicData( d->repository ); + data->storeBasicData( d->feeder ); // remember the file data idx->setWriterData( data ); @@ -419,7 +392,7 @@ void Strigi::NepomukIndexWriter::addValu RegisteredFieldData* rfd = reinterpret_cast<RegisteredFieldData*>( field->writerData() ); // the statement we will create, we will determine the object below - Soprano::Statement statement( md->resourceUri, rfd->property, Soprano::Node(), md->context ); + Soprano::Statement statement( md->resourceUri, rfd->property, Soprano::Node() ); // // Strigi uses rdf:type improperly since it stores the value as a string. We have to @@ -461,12 +434,12 @@ void Strigi::NepomukIndexWriter::addValu if ( value[0] == ':' ) { Nepomuk::Types::Property property( rfd->property ); if ( property.range().isValid() ) { - statement.setObject( md->mapNode( value ) ); + statement.setObject( createNode( value ) ); } } } - d->repository->addStatement( statement ); + d->feeder->addStatement( statement ); } } @@ -504,10 +477,7 @@ void Strigi::NepomukIndexWriter::addValu val = QDateTime::fromTime_t( value ); } - d->repository->addStatement( Statement( md->resourceUri, - rfd->property, - val, - md->context) ); + d->feeder->addStatement( md->resourceUri, rfd->property, val); } @@ -522,10 +492,7 @@ void Strigi::NepomukIndexWriter::addValu FileMetaData* md = fileDataForResult( idx ); RegisteredFieldData* rfd = reinterpret_cast<RegisteredFieldData*>( field->writerData() ); - d->repository->addStatement( Statement( md->resourceUri, - rfd->property, - LiteralValue( value ), - md->context) ); + d->repository->addStatement( md->resourceUri, rfd->property, LiteralValue( value ) ); } @@ -540,10 +507,7 @@ void Strigi::NepomukIndexWriter::addValu FileMetaData* md = fileDataForResult( idx ); RegisteredFieldData* rfd = reinterpret_cast<RegisteredFieldData*>( field->writerData() ); - d->repository->addStatement( Statement( md->resourceUri, - rfd->property, - LiteralValue( value ), - md->context) ); + d->repository->addStatement( md->resourceUri, rfd->property, LiteralValue( value ) ); } @@ -555,17 +519,17 @@ void Strigi::NepomukIndexWriter::addTrip return; } - FileMetaData* md = fileDataForResult( d->currentResultStack.top() ); + //FileMetaData* md = fileDataForResult( d->currentResultStack.top() ); - QUrl subject = md->mapNode( s ); - Nepomuk::Types::Property property( md->mapNode( p ) ); + Soprano::Node subject( createNode( s ) ); + Nepomuk::Types::Property property( QUrl( QString::fromUtf8(p.c_str()) ) ); // Was mapped earlier Soprano::Node object; if ( property.range().isValid() ) - object = md->mapNode( o ); + object = Soprano::Node( createNode( o ) ); else object = Soprano::LiteralValue::fromString( QString::fromUtf8( o.c_str() ), property.literalRangeType().dataTypeUri() ); - d->repository->addStatement( subject, property.uri(), object, md->context ); + d->feeder->addStatement( subject, property.uri(), object ); } @@ -582,17 +546,17 @@ void Strigi::NepomukIndexWriter::finishA // store the full text of the file if ( md->content.length() > 0 ) { - d->repository->addStatement( Statement( md->resourceUri, + d->feeder->addStatement( md->resourceUri, Nepomuk::Vocabulary::NIE::plainTextContent(), - LiteralValue( QString::fromUtf8( md->content.c_str() ) ), - md->context ) ); - if ( d->repository->lastError() ) - kDebug() << "Failed to add" << md->resourceUri << "as text" << QString::fromUtf8( md->content.c_str() ); + LiteralValue( QString::fromUtf8( md->content.c_str() ) ) ); } // cleanup delete md; idx->setWriterData( 0 ); + + // Handle the feeder + d->feeder->end(); } Index: CMakeLists.txt =================================================================== --- CMakeLists.txt (revision 1149492) +++ CMakeLists.txt (working copy) @@ -11,6 +11,7 @@ set( strigi_nepomuk_indexer_SRCS nepomukindexmanager.cpp nepomukindexreader.cpp nepomukindexwriter.cpp + strigifeeder.cpp util.cpp ) Index: strigifeeder.cpp =================================================================== --- strigifeeder.cpp (revision 0) +++ strigifeeder.cpp (revision 0) @@ -0,0 +1,290 @@ +/* + Copyright (C) 2010 Vishesh Handa <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#include "strigifeeder.h" +#include "nrl.h" +#include "util.h" + +#include <QtCore/QDateTime> + +#include <Soprano/Model> +#include <Soprano/Statement> +#include <Soprano/QueryResultIterator> +#include <Soprano/Vocabulary/RDF> +#include <Soprano/Vocabulary/NAO> + +#include <Nepomuk/ResourceManager> +#include <Nepomuk/Resource> + +#include <KDebug> + + +Nepomuk::StrigiFeeder::StrigiFeeder(Soprano::Model* model, QObject* parent) + : QThread( parent ), + m_model( model ) +{ + m_stopped = false; +} + + +Nepomuk::StrigiFeeder::~StrigiFeeder() +{ +} + + +void Nepomuk::StrigiFeeder::begin( const QUrl & url ) +{ + //kDebug() << "BEGINING"; + Request req; + req.uri = url; + + m_stack.push( req ); +} + + +void Nepomuk::StrigiFeeder::addStatement(const Soprano::Statement& st) +{ + Q_ASSERT( !m_stack.isEmpty() ); + Request & req = m_stack.top(); + + // Since we are adding them to a set, duplicates are automatically resolved + req.statements.insert( st ); +} + + +void Nepomuk::StrigiFeeder::addStatement(const Soprano::Node& subject, const Soprano::Node& predicate, const Soprano::Node& object) +{ + addStatement( Soprano::Statement( subject, predicate, object, Soprano::Node() ) ); +} + + +void Nepomuk::StrigiFeeder::end() +{ + if( m_stack.isEmpty() ) + return; + //kDebug() << "ENDING"; + + Request req = m_stack.pop(); + + m_queueMutex.lock(); + m_queue.enqueue( req ); + + m_queueMutex.unlock(); + m_queueWaiter.wakeAll(); +} + + +void Nepomuk::StrigiFeeder::stop() +{ + QMutexLocker lock( &m_queueMutex ); + m_stopped = true; + m_queueWaiter.wakeAll(); +} + +namespace { + + struct ResourceStruct { + QUrl uri; + QMultiHash<QUrl, Soprano::Node> propHash; + }; + + // Maps the uri to the ResourceStuct + typedef QHash<QUrl, ResourceStruct> ResourceHash; + + ResourceHash convertToResourceHash(const QSet<Soprano::Statement> & set ) { + ResourceHash hash; + + foreach( const Soprano::Statement & st, set ) { + //kDebug() << st; + const Soprano::Node & n = st.subject(); + QUrl uriOrId; + if( n.isResource() ) + uriOrId = n.uri(); + else if( n.isBlank() ) + uriOrId = n.identifier(); + + if( !hash.contains( uriOrId ) ) { + ResourceStruct rs; + if( n.isResource() ) + rs.uri = n.uri(); + + hash.insert( uriOrId, rs ); + } + + ResourceStruct & rs = hash[ uriOrId ]; + rs.propHash.insert( st.predicate().uri(), st.object() ); + } + return hash; + } + + /** + * Creates a sparql query which returns 1 resource which matches all the properties, + * and objects present in the propHash of the ResourceStruct + */ + QString toSparql( const ResourceStruct & rs ) { + QString query = QString::fromLatin1("select distinct ?r where { "); + + QList<QUrl> keys = rs.propHash.uniqueKeys(); + foreach( const QUrl & prop, keys ) { + const QList<Soprano::Node>& values = rs.propHash.values( prop ); + + foreach( const Soprano::Node & n, values ) { + query += " ?r " + Soprano::Node::resourceToN3( prop ) + " " + n.toN3() + " . "; + } + } + query += " } LIMIT 1"; + return query; + } + + /** + * Adds all the statements present in the ResourceStruct to the \p model. + * The contex is \p context + */ + void add( Soprano::Model * model, const ResourceStruct &rs, const QUrl & context ) { + QHashIterator<QUrl, Soprano::Node> iter( rs.propHash ); + while( iter.hasNext() ) { + iter.next(); + + Soprano::Statement st( rs.uri, iter.key(), iter.value(), context ); + //kDebug() << "ADDING : " << st; + model->addStatement( st ); + } + } +} + +//BUG: When indexing a file, there is one main uri ( in Request ) and other additional uris +// If there is a statement connecting the main uri with the additional ones, it will be +// resolved correctly, but not if one of the additional one links to another additional one. +void Nepomuk::StrigiFeeder::run() +{ + m_stopped = false; + while( !m_stopped ) { + + // lock for initial iteration + m_queueMutex.lock(); + + // work the queue + while( !m_queue.isEmpty() ) { + Request request = m_queue.dequeue(); + + // unlock after queue utilization + m_queueMutex.unlock(); + + //kDebug() << " Converting to ResourceHash .."; + // Convert to Resource Hash + ResourceHash hash = convertToResourceHash( request.statements ); + + // Search for the resources or create them + //kDebug() << " Searching for duplicates or creating them ... "; + QMutableHashIterator<QUrl, ResourceStruct> it( hash ); + while( it.hasNext() ) { + it.next(); + + // If it already exists + ResourceStruct & rs = it.value(); + if( !rs.uri.isEmpty() ) + continue; + + QString query = toSparql( rs ); + //kDebug() << query; + Soprano::QueryResultIterator it = m_model->executeQuery( query, Soprano::Query::QueryLanguageSparql ); + + if( it.next() ) { + //kDebug() << "Found exact match " << rs.uri << " " << it[0].uri(); + rs.uri = it[0].uri(); + } + else { + //kDebug() << "Creating .."; + rs.uri = ResourceManager::instance()->generateUniqueUri( QString() ); + } + + // Add to the repository + QUrl context = generateGraph( rs.uri ); + add( m_model, rs, context ); + } + + // Fix links for main + ResourceStruct & rs = hash[ request.uri ]; + QMutableHashIterator<QUrl, Soprano::Node> iter( rs.propHash ); + while( iter.hasNext() ) { + iter.next(); + Soprano::Node & n = iter.value(); + + if( n.isEmpty() ) + continue; + + if( n.isBlank() ) { + const QString & id = n.identifier(); + if( !hash.contains( id ) ) + continue; + QUrl newUri = hash.value( id ).uri; + //kDebug() << id << " ---> " << newUri; + iter.value() = Soprano::Node( newUri ); + } + } + + // Add main file to the repository + QUrl context = generateGraph( rs.uri ); + add( m_model, rs, context ); + + // lock for next iteration + m_queueMutex.lock(); + } + + // wait for more input + kDebug() << "Waiting..."; + m_queueWaiter.wait( &m_queueMutex ); + m_queueMutex.unlock(); + kDebug() << "Woke up."; + + } +} + + +QUrl Nepomuk::StrigiFeeder::generateGraph( const QUrl & resourceUri ) +{ + QUrl context = Nepomuk::ResourceManager::instance()->generateUniqueUri( "ctx" ); + + // create the provedance data for the data graph + // TODO: add more data at some point when it becomes of interest + QUrl metaDataContext = Nepomuk::ResourceManager::instance()->generateUniqueUri( "ctx" ); + m_model->addStatement( context, + Soprano::Vocabulary::RDF::type(), + Nepomuk::Vocabulary::NRL::DiscardableInstanceBase(), + metaDataContext ); + m_model->addStatement( context, + Soprano::Vocabulary::NAO::created(), + Soprano::LiteralValue( QDateTime::currentDateTime() ), + metaDataContext ); + m_model->addStatement( context, + Strigi::Ontology::indexGraphFor(), + resourceUri, + metaDataContext ); + m_model->addStatement( metaDataContext, + Soprano::Vocabulary::RDF::type(), + Nepomuk::Vocabulary::NRL::GraphMetadata(), + metaDataContext ); + m_model->addStatement( metaDataContext, + Nepomuk::Vocabulary::NRL::coreGraphMetadataFor(), + context, + metaDataContext ); + + return context; +}
_______________________________________________ Nepomuk mailing list [email protected] https://mail.kde.org/mailman/listinfo/nepomuk
