1 /* This file is part of the KDE Project
2 Copyright (c) 2007-2008 Sebastian Trueg <trueg@kde.org>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
19 #include "nepomukfilewatch.h"
21 #include <QtCore/QTimer>
22 #include <QtCore/QDir>
23 #include <QtCore/QRegExp>
24 #include <QtCore/QFileInfo>
25 #include <QtDBus/QDBusConnection>
29 #include <kmessagebox.h>
31 #include <KPluginFactory>
32 #include <kio/netaccess.h>
34 #include <Soprano/Model>
35 #include <Soprano/StatementIterator>
36 #include <Soprano/Statement>
37 #include <Soprano/Node>
38 #include <Soprano/NodeIterator>
39 #include <Soprano/QueryResultIterator>
40 #include <Soprano/Vocabulary/Xesam>
42 // Restrictions and TODO:
43 // ----------------------
45 // * KIO slaves that do change the local file system may emit stuff like
46 // file:///foo/bar -> xyz://foobar while the file actually ends up in
47 // the local file system again. This is not handled here. It is maybe
48 // necessary to use KFileItem::mostLocalUrl to determine local paths
49 // before deciding to call updateMetaDataForResource.
51 // * Only operations done through KIO are caught
54 using namespace Soprano
;
57 NEPOMUK_EXPORT_SERVICE( Nepomuk::FileWatch
, "nepomukfilewatch")
61 Soprano::QueryResultIterator
queryChildren( Model
* model
, const QString
& path
)
63 // escape special chars
64 QString
regexpPath( path
);
65 if ( regexpPath
[regexpPath
.length()-1] != '/' ) {
68 regexpPath
.replace( QRegExp( "([\\.\\?\\*\\\\+\\(\\)\\\\\\|\\[\\]{}])" ), "\\\\\\1" );
70 // kDebug() << "query:" << QString( "select ?r ?p where { ?r <http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#fileUrl> ?p FILTER(REGEX(STR(?p), '^%1')) . }" ).arg( regexpPath );
72 // query for all files that
73 return model
->executeQuery( QString( "prefix xesam: <http://freedesktop.org/standards/xesam/1.0/core#> "
74 "select ?r ?p where { ?r xesam:url ?p . FILTER(REGEX(STR(?p), '^%1')) . }" ).arg( regexpPath
),
75 Soprano::Query::QueryLanguageSparql
);
81 Nepomuk::FileWatch::FileWatch( QObject
* parent
, const QList
<QVariant
>& )
83 m_strigiParentUrlUri( "http://strigi.sf.net/ontologies/0.9#parentUrl" )
85 // monitor KIO for changes
86 QDBusConnection::sessionBus().connect( QString(), QString(), "org.kde.KDirNotify", "FileMoved",
87 this, SIGNAL( fileMoved( const QString
&, const QString
& ) ) );
88 QDBusConnection::sessionBus().connect( QString(), QString(), "org.kde.KDirNotify", "FilesRemoved",
89 this, SIGNAL( filesDeleted( const QStringList
& ) ) );
91 // async connection to the actual slots
92 // FIXME: is the signal delivery order guranteed? If not, we need a queue!
93 connect( this, SIGNAL( fileMoved( QString
, QString
) ), this, SLOT( slotFileMoved( QString
, QString
) ), Qt::QueuedConnection
);
94 connect( this, SIGNAL( filesDeleted( QStringList
) ), this, SLOT( slotFilesDeleted( QStringList
) ), Qt::QueuedConnection
);
98 Nepomuk::FileWatch::~FileWatch()
103 void Nepomuk::FileWatch::slotFileMoved( const QString
& urlFrom
, const QString
& urlTo
)
105 KUrl
from( urlFrom
);
108 kDebug() << from
<< to
;
110 if ( from
.isEmpty() || to
.isEmpty() ) {
111 kDebug() << "empty path. Looks like a bug somewhere...";
116 // We do NOT get deleted messages for overwritten files! Thus, we have to remove all metadata for overwritten files
117 // first. We do that now.
118 removeMetaData( to
);
120 // and finally update the old statements
121 updateMetaData( from
, to
);
123 // update children files in case from is a folder
124 QString fromPath
= from
.path();
125 QList
<Soprano::BindingSet
> children
= queryChildren( mainModel(), fromPath
).allBindings();
126 foreach( const Soprano::BindingSet
& bs
, children
) {
127 QString path
= to
.path();
128 if ( !path
.endsWith( '/' ) )
130 path
+= bs
[1].toString().mid( fromPath
.endsWith( '/' ) ? fromPath
.length() : fromPath
.length()+1 );
131 updateMetaData( bs
[1].toString(), path
); // FIXME: reuse the URI we already have
134 // TODO: optionally create a xesam:url property in case a file was moved from a remote URL to a local one
135 // still disabled since we also need a new context and that is much easier with a proper NRLModel which
136 // we will hopefully have in Soprano 2.2
137 // if ( to.isLocalFile() ) {
138 // if ( !mainModel()->containsAnyStatement( to, Soprano::Vocabulary::Xesam::url(), Soprano::Node() ) ) {
139 // mainModel()->addStatement( to, Soprano::Vocabulary::Xesam::url(), Soprano::LiteralValue( to.path() ) );
144 kDebug() << "Could not contact Nepomuk server.";
149 void Nepomuk::FileWatch::slotFilesDeleted( const QStringList
& paths
)
151 foreach( const QString
& path
, paths
) {
152 slotFileDeleted( path
);
157 void Nepomuk::FileWatch::slotFileDeleted( const QString
& urlString
)
159 KUrl
url( urlString
);
164 removeMetaData( url
);
166 // remove child annotations in case it is a local folder
167 foreach( Soprano::Node node
, queryChildren( mainModel(), url
.path() ).iterateBindings( 0 ).allNodes() ) {
168 mainModel()->removeAllStatements( Statement( node
, Node(), Node() ) );
172 kDebug() << "Could not contact Nepomuk server.";
177 void Nepomuk::FileWatch::removeMetaData( const KUrl
& url
)
181 if ( url
.isEmpty() ) {
182 kDebug() << "empty path. Looks like a bug somewhere...";
186 mainModel()->removeAllStatements( Statement( url
, Node(), Node() ) );
188 // FIXME: what about the triples that have our uri as object?
192 void Nepomuk::FileWatch::updateMetaData( const KUrl
& from
, const KUrl
& to
)
194 kDebug() << from
<< "->" << to
;
197 // Nepomuk allows annotating of remote files. These files do not necessarily have a xesam:url property
198 // since it would not be of much use in the classic sense: we cannot use it to locate the file on the hd
200 // Thus, when remote files are moved through KIO and we get the notification, we simply change all triples
201 // referring to the original file to use the new URL
204 Soprano::Node oldResource
= from
;
205 Soprano::Node newResource
= to
;
207 // update the resource itself
208 // -----------------------------------------------
209 if ( mainModel()->containsAnyStatement( Soprano::Statement( oldResource
, Soprano::Node(), Soprano::Node() ) ) ) {
211 QList
<Soprano::Statement
> sl
= mainModel()->listStatements( Soprano::Statement( oldResource
,
213 Soprano::Node() ) ).allStatements();
214 Q_FOREACH( Soprano::Statement s
, sl
) {
215 if ( s
.predicate() == Soprano::Vocabulary::Xesam::url() ) {
216 mainModel()->addStatement( Soprano::Statement( newResource
,
218 Soprano::LiteralValue( to
.path() ),
221 else if ( s
.predicate() == m_strigiParentUrlUri
) {
222 mainModel()->addStatement( Soprano::Statement( newResource
,
224 Soprano::LiteralValue( to
.directory( KUrl::IgnoreTrailingSlash
) ),
228 mainModel()->addStatement( Soprano::Statement( newResource
,
235 mainModel()->removeStatements( sl
);
236 // -----------------------------------------------
239 // update resources relating to it
240 // -----------------------------------------------
241 sl
= mainModel()->listStatements( Statement( Node(),
243 oldResource
) ).allStatements();
244 Q_FOREACH( Soprano::Statement s
, sl
) {
245 mainModel()->addStatement( Soprano::Statement( s
.subject(),
250 mainModel()->removeStatements( sl
);
251 // -----------------------------------------------
255 #include "nepomukfilewatch.moc"