diff --git a/nepomuk/CMakeLists.txt b/nepomuk/CMakeLists.txt index 0510b54..c6a1879 100644 --- a/nepomuk/CMakeLists.txt +++ b/nepomuk/CMakeLists.txt @@ -10,14 +10,15 @@ include_directories( ${nepomuk_BINARY_DIR}/common ) +add_subdirectory(ontologies) add_subdirectory(common) add_subdirectory(server) add_subdirectory(kcm) add_subdirectory(services) add_subdirectory(servicestub) add_subdirectory(kioslaves) +add_subdirectory(controller) add_subdirectory(interfaces) -add_subdirectory(ontologies) add_subdirectory(removed-services) #add_subdirectory(test) diff --git a/nepomuk/common/CMakeLists.txt b/nepomuk/common/CMakeLists.txt index 39b400f..57892fc 100644 --- a/nepomuk/common/CMakeLists.txt +++ b/nepomuk/common/CMakeLists.txt @@ -3,6 +3,7 @@ project(nepomuk_common) set(nepomukcommon_SRCS fileexcludefilters.cpp regexpcache.cpp + removablemediacache.cpp ) kde4_add_library(nepomukcommon SHARED ${nepomukcommon_SRCS}) @@ -10,6 +11,7 @@ kde4_add_library(nepomukcommon SHARED ${nepomukcommon_SRCS}) target_link_libraries(nepomukcommon ${QT_QTCORE_LIBRARY} ${KDE4_KDECORE_LIBRARY} + ${KDE4_SOLID_LIBRARY} ${NEPOMUK_LIBRARIES} ) diff --git a/nepomuk/common/nepomuktools.h b/nepomuk/common/nepomuktools.h new file mode 100644 index 0000000..64817d5 --- /dev/null +++ b/nepomuk/common/nepomuktools.h @@ -0,0 +1,61 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef NEPOMUKTOOLS_H +#define NEPOMUKTOOLS_H + +#include <KUrl> +#include <QtCore/QStringList> +#include <Soprano/Node> + +namespace Nepomuk { +const int MAX_SPLIT_LIST_ITEMS = 20; + +/** + * Convert a list or set or QUrls into a list of N3 formatted strings. + */ +template<typename T> QStringList resourcesToN3(const T& urls) { + QStringList n3; + Q_FOREACH(const QUrl& url, urls) { + n3 << Soprano::Node::resourceToN3(url); + } + return n3; +} + +/** + * Split a list into several lists, each not containing more than \p max items + */ +template<typename T> QList<QList<T> > splitList(const QList<T>& list, int max = MAX_SPLIT_LIST_ITEMS) { + QList<QList<T> > splitted; + int i = 0; + QList<T> single; + foreach(const T& item, list) { + single.append(item); + if(++i >= max) { + splitted << single; + single.clear(); + } + } + return splitted; +} +} + +#endif // NEPOMUKTOOLS_H diff --git a/nepomuk/common/removablemediacache.cpp b/nepomuk/common/removablemediacache.cpp new file mode 100644 index 0000000..54afdc2 --- /dev/null +++ b/nepomuk/common/removablemediacache.cpp @@ -0,0 +1,286 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "removablemediacache.h" + +#include <Solid/DeviceNotifier> +#include <Solid/DeviceInterface> +#include <Solid/Block> +#include <Solid/Device> +#include <Solid/StorageDrive> +#include <Solid/StorageVolume> +#include <Solid/StorageAccess> +#include <Solid/NetworkShare> +#include <Solid/OpticalDisc> +#include <Solid/Predicate> + +#include <KDebug> + +#include <QtCore/QMutexLocker> + + +namespace { + bool isUsableVolume( const Solid::Device& dev ) { + if ( dev.is<Solid::StorageAccess>() ) { + if( dev.is<Solid::StorageVolume>() && + dev.parent().is<Solid::StorageDrive>() && + ( dev.parent().as<Solid::StorageDrive>()->isRemovable() || + dev.parent().as<Solid::StorageDrive>()->isHotpluggable() ) ) { + const Solid::StorageVolume* volume = dev.as<Solid::StorageVolume>(); + if ( !volume->isIgnored() && volume->usage() == Solid::StorageVolume::FileSystem ) + return true; + } + else if(dev.is<Solid::NetworkShare>()) { + return !dev.as<Solid::NetworkShare>()->url().isEmpty(); + } + } + + // fallback + return false; + } + + bool isUsableVolume( const QString& udi ) { + Solid::Device dev( udi ); + return isUsableVolume( dev ); + } +} + + +Nepomuk::RemovableMediaCache::RemovableMediaCache(QObject *parent) + : QObject(parent) +{ + initCacheEntries(); + + connect( Solid::DeviceNotifier::instance(), SIGNAL( deviceAdded( const QString& ) ), + this, SLOT( slotSolidDeviceAdded( const QString& ) ) ); + connect( Solid::DeviceNotifier::instance(), SIGNAL( deviceRemoved( const QString& ) ), + this, SLOT( slotSolidDeviceRemoved( const QString& ) ) ); +} + + +Nepomuk::RemovableMediaCache::~RemovableMediaCache() +{ +} + + +void Nepomuk::RemovableMediaCache::initCacheEntries() +{ + QList<Solid::Device> devices + = Solid::Device::listFromQuery(QLatin1String("StorageVolume.usage=='FileSystem'")) + + Solid::Device::listFromType(Solid::DeviceInterface::NetworkShare); + foreach( const Solid::Device& dev, devices ) { + if ( isUsableVolume( dev ) ) { + if(Entry* entry = createCacheEntry( dev )) { + const Solid::StorageAccess* storage = entry->device().as<Solid::StorageAccess>(); + if ( storage && storage->isAccessible() ) + slotAccessibilityChanged( true, dev.udi() ); + } + } + } +} + +QList<const Nepomuk::RemovableMediaCache::Entry *> Nepomuk::RemovableMediaCache::allMedia() const +{ + QList<const Entry*> media; + for(QHash<QString, Entry>::const_iterator it = m_metadataCache.begin(); it != m_metadataCache.end(); ++it) + media.append(&(*it)); + return media; +} + +Nepomuk::RemovableMediaCache::Entry* Nepomuk::RemovableMediaCache::createCacheEntry( const Solid::Device& dev ) +{ + QMutexLocker lock(&m_entryCacheMutex); + + Entry entry(dev); + if(!entry.url().isEmpty()) { + kDebug() << "Usable" << dev.udi(); + + // we only add to this set and never remove. This is no problem as this is a small set + m_usedSchemas.insert(KUrl(entry.url()).scheme()); + + connect( dev.as<Solid::StorageAccess>(), SIGNAL(accessibilityChanged(bool, QString)), + this, SLOT(slotAccessibilityChanged(bool, QString)) ); + + m_metadataCache.insert( dev.udi(), entry ); + + emit deviceAdded(&m_metadataCache[dev.udi()]); + + return &m_metadataCache[dev.udi()]; + } + else { + kDebug() << "Cannot use device due to empty identifier:" << dev.udi(); + return 0; + } +} + + +const Nepomuk::RemovableMediaCache::Entry* Nepomuk::RemovableMediaCache::findEntryByFilePath( const QString& path ) const +{ + QMutexLocker lock(&m_entryCacheMutex); + + for( QHash<QString, Entry>::const_iterator it = m_metadataCache.begin(); + it != m_metadataCache.end(); ++it ) { + const Entry& entry = *it; + if ( entry.device().as<Solid::StorageAccess>()->isAccessible() && + path.startsWith( entry.device().as<Solid::StorageAccess>()->filePath() ) ) + return &entry; + } + + return 0; +} + + +const Nepomuk::RemovableMediaCache::Entry* Nepomuk::RemovableMediaCache::findEntryByUrl(const KUrl &url) const +{ + QMutexLocker lock(&m_entryCacheMutex); + + for( QHash<QString, Entry>::const_iterator it = m_metadataCache.constBegin(); + it != m_metadataCache.constEnd(); ++it ) { + const Entry& entry = *it; + kDebug() << url << entry.url(); + if(url.url().startsWith(entry.url())) { + return &entry; + } + } + + return 0; +} + + +bool Nepomuk::RemovableMediaCache::hasRemovableSchema(const KUrl &url) const +{ + return m_usedSchemas.contains(url.scheme()); +} + + +void Nepomuk::RemovableMediaCache::slotSolidDeviceAdded( const QString& udi ) +{ + kDebug() << udi; + + if ( isUsableVolume( udi ) ) { + createCacheEntry( Solid::Device( udi ) ); + } +} + + +void Nepomuk::RemovableMediaCache::slotSolidDeviceRemoved( const QString& udi ) +{ + kDebug() << udi; + if ( m_metadataCache.contains( udi ) ) { + kDebug() << "Found removable storage volume for Nepomuk undocking:" << udi; + m_metadataCache.remove( udi ); + } +} + + +void Nepomuk::RemovableMediaCache::slotAccessibilityChanged( bool accessible, const QString& udi ) +{ + kDebug() << accessible << udi; + + // + // cache new mount path + // + if ( accessible ) { + QMutexLocker lock(&m_entryCacheMutex); + Entry* entry = &m_metadataCache[udi]; + kDebug() << udi << "accessible at" << entry->device().as<Solid::StorageAccess>()->filePath() << "with identifier" << entry->url(); + emit deviceMounted(entry); + } +} + + +Nepomuk::RemovableMediaCache::Entry::Entry() +{ +} + + +Nepomuk::RemovableMediaCache::Entry::Entry(const Solid::Device& device) + : m_device(device) +{ + if(device.is<Solid::StorageVolume>()) { + const Solid::StorageVolume* volume = m_device.as<Solid::StorageVolume>(); + if(device.is<Solid::OpticalDisc>()) { + // we use the label as is - it is not even close to unique but + // so far we have nothing better + m_urlPrefix = QLatin1String("optical://") + volume->label(); + } + else if(!volume->uuid().isEmpty()) { + // we always use lower-case uuids + m_urlPrefix = QLatin1String("filex://") + volume->uuid().toLower(); + } + } + else if(device.is<Solid::NetworkShare>()) { + m_urlPrefix = device.as<Solid::NetworkShare>()->url().toString(); + } +} + +KUrl Nepomuk::RemovableMediaCache::Entry::constructRelativeUrl( const QString& path ) const +{ + if(const Solid::StorageAccess* sa = m_device.as<Solid::StorageAccess>()) { + if(sa->isAccessible()) { + const QString relativePath = path.mid( sa->filePath().count() ); + return KUrl( m_urlPrefix + relativePath ); + } + } + + // fallback + return KUrl(); +} + + +QString Nepomuk::RemovableMediaCache::Entry::constructLocalPath( const KUrl& filexUrl ) const +{ + if(const Solid::StorageAccess* sa = m_device.as<Solid::StorageAccess>()) { + if(sa->isAccessible()) { + // the base of the path: the mount path + QString path( sa->filePath() ); + if ( path.endsWith( QLatin1String( "/" ) ) ) + path.truncate( path.length()-1 ); + + return path + filexUrl.url().mid(m_urlPrefix.count()); + } + } + + // fallback + return QString(); +} + +QString Nepomuk::RemovableMediaCache::Entry::mountPath() const +{ + if(const Solid::StorageAccess* sa = m_device.as<Solid::StorageAccess>()) { + return sa->filePath(); + } + else { + return QString(); + } +} + +bool Nepomuk::RemovableMediaCache::Entry::isMounted() const +{ + if(const Solid::StorageAccess* sa = m_device.as<Solid::StorageAccess>()) { + return sa->isAccessible(); + } + else { + return false; + } +} + +#include "removablemediacache.moc" diff --git a/nepomuk/common/removablemediacache.h b/nepomuk/common/removablemediacache.h new file mode 100644 index 0000000..25982f0 --- /dev/null +++ b/nepomuk/common/removablemediacache.h @@ -0,0 +1,114 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef NEPOMUK_REMOVABLEMEDIACACHE_H +#define NEPOMUK_REMOVABLEMEDIACACHE_H + +#include "nepomukcommon_export.h" + +#include <QtCore/QObject> +#include <QtCore/QMutex> +#include <QtCore/QSet> + +#include <Solid/Device> + +#include <KUrl> + + +namespace Nepomuk { + +/** + * The removable media cache provides access to all removable + * media that are supported by Nepomuk. It allows to convert + * URLs the way RemovableMediaModel requires it and provides + * more or less unique URIs for each device allowing to store + * device-specific configuration. + */ +class NEPOMUKCOMMON_EXPORT RemovableMediaCache : public QObject +{ + Q_OBJECT + +public: + RemovableMediaCache(QObject *parent = 0); + ~RemovableMediaCache(); + + class Entry { + public: + Entry(); + Entry(const Solid::Device& device); + + KUrl constructRelativeUrl( const QString& path ) const; + QString constructLocalPath( const KUrl& filexUrl ) const; + + Solid::Device device() const { return m_device; } + QString url() const { return m_urlPrefix; } + + bool isMounted() const; + QString mountPath() const; + + private: + Solid::Device m_device; + + /// The prefix to be used for URLs + QString m_urlPrefix; + }; + + const Entry* findEntryByFilePath( const QString& path ) const; + const Entry* findEntryByUrl(const KUrl& url) const; + + QList<const Entry*> allMedia() const; + + /** + * Returns true if the URL might be pointing to a file on a + * removable device as handled by this class, ie. a non-local + * URL which can be converted to a local one. + * This method is primarily used for performance gain. + */ + bool hasRemovableSchema(const KUrl& url) const; + +signals: + void deviceAdded(const Nepomuk::RemovableMediaCache::Entry* entry); + void deviceMounted(const Nepomuk::RemovableMediaCache::Entry* entry); + +private slots: + void slotSolidDeviceAdded(const QString &udi); + void slotSolidDeviceRemoved(const QString &udi); + void slotAccessibilityChanged(bool accessible, const QString &udi); + +private: + void initCacheEntries(); + + Entry* createCacheEntry( const Solid::Device& dev ); + + /// maps Solid UDI to Entry + QHash<QString, Entry> m_metadataCache; + + /// contains all schemas that are used as url prefixes in m_metadataCache + /// this is used to avoid trying to convert each and every resource in + /// convertFilexUrl + QSet<QString> m_usedSchemas; + + mutable QMutex m_entryCacheMutex; +}; + +} // namespace Nepomuk + +#endif // NEPOMUK_REMOVABLEMEDIACACHE_H diff --git a/nepomuk/controller/CMakeLists.txt b/nepomuk/controller/CMakeLists.txt new file mode 100644 index 0000000..76c452d --- /dev/null +++ b/nepomuk/controller/CMakeLists.txt @@ -0,0 +1,41 @@ +project(nepomukcontroller) + +include_directories( + ${QT_INCLUDES} + ${KDE4_INCLUDES} + ${SOPRANO_INCLUDE_DIR} + ${NEPOMUK_INCLUDE_DIR} + ${CMAKE_CURRENT_BUILD_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../kcm + ) + +set(controller_SRCS + main.cpp + systray.cpp + ../kcm/statuswidget.cpp + ) + +qt4_add_dbus_interface(controller_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../interfaces/org.kde.nepomuk.Strigi.xml strigiserviceinterface) +qt4_add_dbus_interface(controller_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../interfaces/org.kde.nepomuk.ServiceControl.xml servicecontrol) + +kde4_add_ui_files(controller_SRCS ../kcm/statuswidget.ui) + +kde4_add_executable(nepomukcontroller ${controller_SRCS}) + +target_link_libraries(nepomukcontroller + ${KDE4_KDEUI_LIBS} + ${KDE4_KIO_LIBS} + ${KDE4_SOLID_LIBS} + ${NEPOMUK_LIBRARIES} + ${SOPRANO_LIBRARIES} + ) + +install( + PROGRAMS nepomukcontroller.desktop + DESTINATION ${XDG_APPS_INSTALL_DIR}) +install( + PROGRAMS nepomukcontroller.desktop + DESTINATION ${AUTOSTART_INSTALL_DIR}) +install( + TARGETS nepomukcontroller + ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/nepomuk/controller/Messages.sh b/nepomuk/controller/Messages.sh new file mode 100644 index 0000000..ea4aa1e --- /dev/null +++ b/nepomuk/controller/Messages.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +$EXTRACTRC `find . -name "*.ui" -o -name "*.kcfg"` >> rc.cpp +$XGETTEXT `find . -name "*.cpp"` -o $podir/nepomukcontroller.pot +rm -rf rc.cpp diff --git a/nepomuk/controller/main.cpp b/nepomuk/controller/main.cpp new file mode 100644 index 0000000..ddd9327 --- /dev/null +++ b/nepomuk/controller/main.cpp @@ -0,0 +1,53 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "systray.h" + +#include <KUniqueApplication> +#include <KCmdLineArgs> +#include <KAboutData> + +int main( int argc, char *argv[] ) +{ + KAboutData aboutData( "nepomukcontroller", + "nepomukcontroller", + ki18n("Nepomuk Controller"), + "1.0", + ki18n("A small tool to monitor and control Nepomuk file indexing"), + KAboutData::License_GPL, + ki18n("(c) 2008-2011, Sebastian Trüg"), + KLocalizedString(), + "http://nepomuk.kde.org" ); + aboutData.addAuthor(ki18n("Sebastian Trüg"),ki18n("Maintainer"), "trueg@kde.org"); + aboutData.setProgramIconName( "nepomuk" ); + + KCmdLineArgs::init( argc, argv, &aboutData ); + KCmdLineOptions options; + KCmdLineArgs::addCmdLineOptions( options ); + + if( KUniqueApplication::start() ) { + KUniqueApplication app; + app.setQuitOnLastWindowClosed(false); + KGlobal::locale()->insertCatalog( QLatin1String( "kcm_nepomuk" )); + (void)new Nepomuk::SystemTray(0); + return app.exec(); + } +} diff --git a/nepomuk/controller/nepomukcontroller.desktop b/nepomuk/controller/nepomukcontroller.desktop new file mode 100644 index 0000000..28cc312 --- /dev/null +++ b/nepomuk/controller/nepomukcontroller.desktop @@ -0,0 +1,104 @@ +[Desktop Entry] +Name=Nepomuk File Indexing Controller +Name[bg]=Управление на файлово индексиране с Nepomuk +Name[bs]=Nepomuk kontrolor indeksiranja datoteka +Name[ca]=Controlador de l'indexador de fitxers Nepomuk +Name[ca@valencia]=Controlador de l'indexador de fitxers Nepomuk +Name[cs]=Správce indexování souborů Nepomuku +Name[da]=Kontrol af Nepomuk filindeksering +Name[de]=Steuerung der Nepomuk-Datei-Indizierung +Name[es]=Controlador de indexado de archivos de Nepomuk +Name[et]=Nepomuki failide indekseerimise kontroller +Name[eu]=Nepomuken fitxategiak indexatzeko kontrolatzailea +Name[fi]=Nepomukin tiedostojen indeksoinnin hallinta +Name[fr]=Contrôleur d'indexation de fichiers de Nepomuk +Name[he]=לוח בקרה לממפתח הקבצים של Nepomuk +Name[hr]=Nepomukov upravljač za indeksiranje datoteka +Name[hu]=Nepomuk fájlindexelő-felügyelő +Name[ia]=Controlator de indicisation de file de Nepomuk +Name[is]=Nepomuk skráaskráningarstýring +Name[it]=Controllore dell'indicizzazione file di Nepomuk +Name[kk]=Nepomuk файл индекстеу контроллері +Name[km]=កម្មវិធីត្រួតពិនិត្យការដាក់លិបិក្រមឯកសាររបស់ Nepomuk +Name[ko]=Nepomuk 파일 인덱싱 컨트롤러 +Name[lt]=Nepomuk failų indeksavimo valdiklis +Name[lv]=Nepomuk failu indeksēšanas kontrolieris +Name[nb]=Styring for Nepomuk filindeksering +Name[nds]=Dateiindizeren för Nepomuk +Name[nl]=Besturing door Nepomuk van bestandenindexering +Name[nn]=Nepomuk filindekseringskontroll +Name[pa]=ਨਿਪੋਮੁਕ ਫਾਇਲ ਇੰਡੈਕਸਿੰਗ ਕੰਟਰੋਲਰ +Name[pl]=Kontroler indeksowania plików Nepomuk +Name[pt]=Controlador de Indexação de Ficheiros do Nepomuk +Name[pt_BR]=Controlador de indexação de arquivos do Nepomuk +Name[ro]=Controlor al indexatorului de fișiere Nepomuk +Name[ru]=Управление индексированием файлов в Nepomuk +Name[sl]=Nadzorovalnik indeksiranja datotek Nepomuk +Name[sr]=Непомуков управљач индексирањем фајлова +Name[sr@ijekavian]=Непомуков управљач индексирањем фајлова +Name[sr@ijekavianlatin]=Nepomukov upravljač indeksiranjem fajlova +Name[sr@latin]=Nepomukov upravljač indeksiranjem fajlova +Name[sv]=Nepomuk-styrning av filindexering +Name[th]=ตัวควบคุมการทำดัชนีแฟ้มของ Nepomuk +Name[tr]=Nepomuk Dosya İndeksleme Kontrolcüsü +Name[ug]=Nepomuk ھۆججەت ئىندېكسلاش تىزگىنلىگۈچ +Name[uk]=Засіб керування індексуванням файлів у Nepomuk +Name[x-test]=xxNepomuk File Indexing Controllerxx +Name[zh_CN]=Nepomuk 文件索引控制器 +Name[zh_TW]=Nepomuk 檔案索引控制器 +Comment=System tray icon to control the behaviour of the Nepomuk file indexer +Comment[bg]=Икона в системния поднос за управление на файлово индексиране с Nepomuk +Comment[bs]=Ikona u sistemskom uglu za kontrolu ponapanja Nepomuk indeksiranja datoteka +Comment[ca]=Icona de la safata del sistema per controlar el comportament de l'indexador de fitxers Nepomuk +Comment[ca@valencia]=Icona de la safata del sistema per controlar el comportament de l'indexador de fitxers Nepomuk +Comment[cs]=Ikona v systémové části panelu pro ovládání chování indexeru souborů Nepomuk +Comment[da]=Statusikon til at kontrollere opførsel af Nepomuk filindeksering +Comment[de]=Symbol im Systemabschnitt der Kontrollleiste, um das Verhalten der Nepomuk-Datei-Indizierung zu steuern +Comment[es]=Icono de la bandeja del sistema para controlar el comportamiento de la indexación de archivos de Nepomuk +Comment[et]=Süsteemisalve ikoon Nepomuki failide indekseerimise käitumise juhtimiseks +Comment[eu]=Sistema erretiluko ikonoa Nepomuken fitxategi indexatzailearen portaera kontrolatzeko +Comment[fi]=Ilmoitusaluekuvake Nepomukin tiedostojen indeksoinnin hallintaan. +Comment[fr]=Icône de la boite à miniature contrôlant le comportement de l'indexeur de fichier Nepomuk +Comment[he]=צלמית במגש המערכת לשליטה על התנהגות הממפתח של Nepomuk +Comment[hr]=Ikona u sistemskom bloku za upravljanje ponašanjem Nepomukovog indeksiranja datoteka +Comment[hu]=Paneltálcaikon a Nepomuk fájlindexelő működésének felügyeléséhez +Comment[ia]=Icone de tabuliero de systema per controlar le comportamento del indicisator de file de Nepomuk +Comment[is]=Táknmynd í kerfisbakka til að stýra hegðun Nepomuk skráaskráningarstýringar +Comment[it]=Icona nel vassoio di sistema che controlla il comportamento dell'indicizzatore di file Nepomuk +Comment[kk]=Nepomuk файл индекстеуін басқару үшін жүйелік сөреде таңбашаны көрсету +Comment[km]=រូបតំណាងថាសប្រព័ន្ធ ដើម្បីត្រួតពិនិត្យឥរិយាបថរបស់កម្មវិធីដាក់លិបិក្រមឯកសាររបស់ Nepomuk +Comment[ko]=Nepomuk 파일 인덱서 제어를 위한 시스템 트레이 아이콘 +Comment[lt]=Sisteminio dėklo ženkliukas Nepomuk failų indeksavimo elgsenos valdymui +Comment[lv]=Sistēmas joslas ikona, ar kuru kontrolēt Nepomuk failu indeksētāja uzvedību +Comment[nb]=Ikon i systemkurven som styrer hvordan Nepomuk filindeksering oppfører seg +Comment[nds]=Systeemafsnitt-Lüttbild för de Kuntrull vun den Nepomuk-Dateiindizeerdeenst +Comment[nl]=Pictogram in het systeemvak om het gedrag van bestandenindexering van Nepomuk te besturen +Comment[nn]=Systemtrauikon for å kontrollera Nepomuk filindeksering +Comment[pa]=ਨਿਪੋਮੁਕ ਫਾਇਲ ਇੰਡੈਕਸਰ ਦਾ ਰਵੱਈਆ ਕੰਟਰੋਲ ਕਰਨ ਲਈ ਸਿਸਟਮ ਟਰੇ ਆਈਕਾਨ +Comment[pl]=Ikona w tacce systemowej do kontroli zachowania indeksowania plików Nepomuk +Comment[pt]=Ícone da bandeja para controlar o comportamento de indexação de ficheiros do Nepomuk +Comment[pt_BR]=Ícone da área de notificação para controlar o comportamento de indexação de arquivos do Nepomuk +Comment[ro]=Pictograma din tava de sistem pentru a controla comportamentul indexatorului de fișiere Nepomuk +Comment[ru]=Значок в системном лотке для управления индексированием в Nepomuk +Comment[sl]=Ikona v sistemski vrstici za nadzorovanje delovanja indeksirnika datotek Nepomuk +Comment[sr]=Икона системске касете за управљање Непомуковим индексаром фајлова +Comment[sr@ijekavian]=Икона системске касете за управљање Непомуковим индексаром фајлова +Comment[sr@ijekavianlatin]=Ikona sistemske kasete za upravljanje Nepomukovim indeksarom fajlova +Comment[sr@latin]=Ikona sistemske kasete za upravljanje Nepomukovim indeksarom fajlova +Comment[sv]=Ikon i systembrickan för att styra beteendet hos Nepomuk-filindexering +Comment[th]=ไอคอนบนถาดระบบสำหรับควบคุมตัวทำดัชนีแฟ้มสำหรับ Nepomuk +Comment[tr]=Nepomuk dosya indeksleyicinin davranışını kontrol eden sistem çekmecesi simgesi +Comment[ug]=سىستېما پەغەز سىنبەلگىسى Nepomuk ھۆججەت ئىندېكىسلىغۇچنىڭ ھەرىكىتىنى تىزگىنلەيدۇ +Comment[uk]=Піктограма системного лотка для керування поведінкою служб індексування файлів Nepomuk +Comment[x-test]=xxSystem tray icon to control the behaviour of the Nepomuk file indexerxx +Comment[zh_CN]=控制 Nepomuk 文件索引器的系统托盘图标 +Comment[zh_TW]=控制 Nepomuk 檔案索引行為的系統匣圖示 +Icon=nepomuk +Exec=nepomukcontroller +Type=Application +Categories=Qt;KDE;System; +X-KDE-autostart-after=panel +X-DBUS-StartupType=Unique +X-KDE-StartupNotify=false +X-KDE-UniqueApplet=true +X-KDE-autostart-condition=nepomukserverrc:Basic Settings:Start Nepomuk:true diff --git a/nepomuk/controller/systray.cpp b/nepomuk/controller/systray.cpp new file mode 100644 index 0000000..920877b --- /dev/null +++ b/nepomuk/controller/systray.cpp @@ -0,0 +1,188 @@ +/* This file is part of the KDE Project + Copyright (c) 2008-2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "systray.h" +#include "statuswidget.h" + +#include <KMenu> +#include <KToggleAction> +#include <KLocale> +#include <KIcon> +#include <KToolInvocation> +#include <KActionCollection> +#include <KStandardAction> + +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusConnectionInterface> +#include <QtDBus/QDBusServiceWatcher> + +#include <QApplication> +#include <QDesktopWidget> + + +Nepomuk::SystemTray::SystemTray( QObject* parent ) + : KStatusNotifierItem( parent ), + m_service( 0 ), + m_suspendedManually( false ), + m_statusWidget( 0 ) +{ + setCategory( SystemServices ); + setIconByName( "nepomuk" ); + setTitle( i18n( "Nepomuk File Indexing" ) ); + + // the status widget + connect(this, SIGNAL(activateRequested(bool,QPoint)), this, SLOT(slotActivateRequested())); + + m_suspendResumeAction = new KToggleAction( i18n( "Suspend File Indexing" ), actionCollection() ); + m_suspendResumeAction->setCheckedState( KGuiItem( i18n( "Suspend File Indexing" ) ) ); + m_suspendResumeAction->setToolTip( i18n( "Suspend or resume the file indexer manually" ) ); + connect( m_suspendResumeAction, SIGNAL( toggled( bool ) ), + this, SLOT( slotSuspend( bool ) ) ); + + KAction* configAction = new KAction( actionCollection() ); + configAction->setText( i18n( "Configure File Indexing" ) ); + configAction->setIcon( KIcon( "configure" ) ); + connect( configAction, SIGNAL( triggered() ), + this, SLOT( slotConfigure() ) ); + + contextMenu()->addAction( m_suspendResumeAction ); + contextMenu()->addAction( configAction ); + + // connect to the strigi service + m_service = new org::kde::nepomuk::Strigi( QLatin1String("org.kde.nepomuk.services.nepomukstrigiservice"), + QLatin1String("/nepomukstrigiservice"), + QDBusConnection::sessionBus(), + this ); + m_serviceControl = new org::kde::nepomuk::ServiceControl( QLatin1String("org.kde.nepomuk.services.nepomukstrigiservice"), + QLatin1String("/servicecontrol"), + QDBusConnection::sessionBus(), + this ); + connect( m_service, SIGNAL( statusChanged() ), this, SLOT( slotUpdateStrigiStatus() ) ); + + // watch for the strigi service to come up and go down + QDBusServiceWatcher* dbusServiceWatcher = new QDBusServiceWatcher( m_service->service(), + QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration, + this ); + connect( dbusServiceWatcher, SIGNAL( serviceRegistered( QString ) ), + this, SLOT( slotUpdateStrigiStatus()) ); + connect( dbusServiceWatcher, SIGNAL( serviceUnregistered( QString ) ), + this, SLOT( slotUpdateStrigiStatus()) ); + + slotUpdateStrigiStatus(); +} + + +Nepomuk::SystemTray::~SystemTray() +{ +} + + +void Nepomuk::SystemTray::slotUpdateStrigiStatus() +{ + // make sure we do not update the systray icon all the time + + ItemStatus newStatus = status(); + const bool strigiServiceInitialized = + QDBusConnection::sessionBus().interface()->isServiceRegistered(m_service->service()) && + m_serviceControl->isInitialized(); + + // a manually suspended service should not be passive + if( strigiServiceInitialized ) + newStatus = m_service->isIndexing() || m_suspendedManually ? Active : Passive; + else + newStatus = Passive; + if ( newStatus != status() ) { + setStatus( newStatus ); + } + + QString statusString; + if( strigiServiceInitialized ) + statusString = m_service->userStatusString(); + else + statusString = i18n("Nepomuk File Indexing Service not running"); + if ( statusString != m_prevStatus ) { + m_prevStatus = statusString; + setToolTip("nepomuk", i18n("Search Service"), statusString ); + } + + m_suspendResumeAction->setEnabled( strigiServiceInitialized ); + m_suspendResumeAction->setChecked( m_service->isSuspended() ); +} + + +void Nepomuk::SystemTray::slotConfigure() +{ + QStringList args; + args << "kcm_nepomuk"; + KToolInvocation::kdeinitExec("kcmshell4", args); +} + + +void Nepomuk::SystemTray::slotSuspend( bool suspended ) +{ + m_suspendedManually = suspended; + if( suspended ) + m_service->suspend(); + else + m_service->resume(); +} + + +// from kdialog.cpp since KDialog::centerOnScreen will simply do nothing on X11! +static QRect screenRect( QWidget *widget, int screen ) +{ + QDesktopWidget *desktop = QApplication::desktop(); + KConfig gc( "kdeglobals", KConfig::NoGlobals ); + KConfigGroup cg(&gc, "Windows" ); + if ( desktop->isVirtualDesktop() && + cg.readEntry( "XineramaEnabled", true ) && + cg.readEntry( "XineramaPlacementEnabled", true ) ) { + + if ( screen < 0 || screen >= desktop->numScreens() ) { + if ( screen == -1 ) + screen = desktop->primaryScreen(); + else if ( screen == -3 ) + screen = desktop->screenNumber( QCursor::pos() ); + else + screen = desktop->screenNumber( widget ); + } + + return desktop->availableGeometry( screen ); + } else + return desktop->geometry(); +} + +void Nepomuk::SystemTray::slotActivateRequested() +{ + if(!m_statusWidget) + m_statusWidget = new Nepomuk::StatusWidget(); + + if(!m_statusWidget->isVisible()) { + m_statusWidget->show(); + + const QRect rect = screenRect( 0, -3 ); + m_statusWidget->move( rect.center().x() - m_statusWidget->width() / 2, + rect.center().y() - m_statusWidget->height() / 2 ); + } + else { + m_statusWidget->hide(); + } +} + +#include "systray.moc" diff --git a/nepomuk/controller/systray.h b/nepomuk/controller/systray.h new file mode 100644 index 0000000..3e2adb6 --- /dev/null +++ b/nepomuk/controller/systray.h @@ -0,0 +1,62 @@ +/* This file is part of the KDE Project + Copyright (c) 2008-2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _NEPOMUK_STRIGI_SYSTRAY_H_ +#define _NEPOMUK_STRIGI_SYSTRAY_H_ + +#include <KStatusNotifierItem> +#include "strigiserviceinterface.h" +#include "servicecontrol.h" + +class KToggleAction; + +namespace Nepomuk { + + class StrigiService; + class StatusWidget; + + class SystemTray : public KStatusNotifierItem + { + Q_OBJECT + + public: + SystemTray( QObject* parent ); + ~SystemTray(); + + private Q_SLOTS: + void slotUpdateStrigiStatus(); + void slotConfigure(); + void slotSuspend( bool suspended ); + + void slotActivateRequested(); + + private: + KToggleAction* m_suspendResumeAction; + + org::kde::nepomuk::Strigi* m_service; + org::kde::nepomuk::ServiceControl* m_serviceControl; + bool m_suspendedManually; + + StatusWidget* m_statusWidget; + + // used to prevent endless status updates + QString m_prevStatus; + }; +} + +#endif diff --git a/nepomuk/interfaces/CMakeLists.txt b/nepomuk/interfaces/CMakeLists.txt index db20fe6..2e14346 100644 --- a/nepomuk/interfaces/CMakeLists.txt +++ b/nepomuk/interfaces/CMakeLists.txt @@ -9,11 +9,11 @@ install(FILES org.kde.nepomuk.Storage.xml org.kde.nepomuk.QueryService.xml org.kde.nepomuk.Query.xml - org.kde.nepomuk.RemovableStorage.xml org.kde.nepomuk.BackupSync.xml org.kde.nepomuk.services.nepomukbackupsync.backupmanager.xml org.kde.nepomuk.services.nepomukbackupsync.syncmanager.xml - org.kde.nepomuk.services.nepomukbackupsync.identifier.xml - org.kde.nepomuk.services.nepomukbackupsync.merger.xml + org.kde.nepomuk.DataManagement.xml + org.kde.nepomuk.ResourceWatcher.xml + org.kde.nepomuk.ResourceWatcherConnection.xml DESTINATION ${DBUS_INTERFACES_INSTALL_DIR} ) diff --git a/nepomuk/interfaces/org.kde.nepomuk.DataManagement.xml b/nepomuk/interfaces/org.kde.nepomuk.DataManagement.xml new file mode 100644 index 0000000..0172373 --- /dev/null +++ b/nepomuk/interfaces/org.kde.nepomuk.DataManagement.xml @@ -0,0 +1,121 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.kde.nepomuk.DataManagement"> + <method name="addProperty"> + <arg name="resources" type="as" direction="in"/> + <arg name="property" type="s" direction="in"/> + <arg name="values" type="av" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="setProperty"> + <arg name="resources" type="as" direction="in"/> + <arg name="property" type="s" direction="in"/> + <arg name="values" type="av" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="removeProperty"> + <arg name="resources" type="as" direction="in"/> + <arg name="property" type="s" direction="in"/> + <arg name="values" type="av" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="removeProperties"> + <arg name="resources" type="as" direction="in"/> + <arg name="properties" type="as" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="createResource"> + <arg type="s" direction="out"/> + <arg name="types" type="as" direction="in"/> + <arg name="label" type="s" direction="in"/> + <arg name="description" type="s" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="removeResources"> + <arg name="resources" type="as" direction="in"/> + <arg name="flags" type="i" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="removeDataByApplication"> + <arg name="resources" type="as" direction="in"/> + <arg name="flags" type="i" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="removeDataByApplication"> + <arg name="flags" type="i" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="storeResources"> + <arg name="resources" type="a(sa{sv})" direction="in"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QList<Nepomuk::SimpleResource>"/> + <arg name="identificationMode" type="i" direction="in"/> + <arg name="flags" type="i" direction="in"/> + <arg name="additionalMetadata" type="a{sv}" direction="in"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In3" value="Nepomuk::PropertyHash"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="importResources"> + <arg name="url" type="s" direction="in"/> + <arg name="serialization" type="s" direction="in"/> + <arg name="identificationMode" type="i" direction="in"/> + <arg name="flags" type="i" direction="in"/> + <arg name="additionalMetadata" type="a{sv}" direction="in"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.In4" value="Nepomuk::PropertyHash"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="importResources"> + <arg name="url" type="s" direction="in"/> + <arg name="serialization" type="s" direction="in"/> + <arg name="identificationMode" type="i" direction="in"/> + <arg name="flags" type="i" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="mergeResources"> + <arg name="resource1" type="s" direction="in"/> + <arg name="resource2" type="s" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="describeResources"> + <arg name="resources" type="as" direction="in"/> + <arg name="includeSubResources" type="b" direction="in"/> + <arg type="a(sa{sv})" direction="out"/> + <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QList<Nepomuk::SimpleResource>"/> + </method> + <method name="setProperty"> + <arg name="resource" type="s" direction="in"/> + <arg name="property" type="s" direction="in"/> + <arg name="value" type="v" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="addProperty"> + <arg name="resource" type="s" direction="in"/> + <arg name="property" type="s" direction="in"/> + <arg name="value" type="v" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="removeProperty"> + <arg name="resource" type="s" direction="in"/> + <arg name="property" type="s" direction="in"/> + <arg name="value" type="v" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="removeProperties"> + <arg name="resource" type="s" direction="in"/> + <arg name="property" type="s" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="createResource"> + <arg type="s" direction="out"/> + <arg name="type" type="s" direction="in"/> + <arg name="label" type="s" direction="in"/> + <arg name="description" type="s" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + <method name="removeResources"> + <arg name="resource" type="s" direction="in"/> + <arg name="flags" type="i" direction="in"/> + <arg name="app" type="s" direction="in"/> + </method> + </interface> +</node> diff --git a/nepomuk/interfaces/org.kde.nepomuk.Query.xml b/nepomuk/interfaces/org.kde.nepomuk.Query.xml index af57cb8..0e444e7 100644 --- a/nepomuk/interfaces/org.kde.nepomuk.Query.xml +++ b/nepomuk/interfaces/org.kde.nepomuk.Query.xml @@ -18,6 +18,10 @@ <signal name="entriesRemoved"> <arg name="entries" type="as" /> </signal> + <signal name="entriesRemoved"> + <arg name="entries" type="a(sda{s(isss)}s)" /> + <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QList<Nepomuk::Query::Result>" /> + </signal> <signal name="resultCount"> <arg name="count" type="i" /> </signal> diff --git a/nepomuk/interfaces/org.kde.nepomuk.RemovableStorage.xml b/nepomuk/interfaces/org.kde.nepomuk.RemovableStorage.xml deleted file mode 100644 index 38a29ea..0000000 --- a/nepomuk/interfaces/org.kde.nepomuk.RemovableStorage.xml +++ /dev/null @@ -1,13 +0,0 @@ -<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" - "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> -<node> - <interface name="org.kde.nepomuk.RemovableStorage"> - <method name="resourceUriFromLocalFileUrl"> - <arg type="s" direction="out"/> - <arg name="url" type="s" direction="in"/> - </method> - <method name="currentlyMountedAndIndexed"> - <arg type="as" direction="out"/> - </method> - </interface> -</node> diff --git a/nepomuk/interfaces/org.kde.nepomuk.ResourceWatcher.xml b/nepomuk/interfaces/org.kde.nepomuk.ResourceWatcher.xml new file mode 100644 index 0000000..7f41444 --- /dev/null +++ b/nepomuk/interfaces/org.kde.nepomuk.ResourceWatcher.xml @@ -0,0 +1,14 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.kde.nepomuk.ResourceWatcher"> + <method name="stopWatcher"> + <arg name="objectName" type="s" direction="in"/> + </method> + <method name="watch"> + <arg name="resources" type="as" direction="in"/> + <arg name="properties" type="as" direction="in"/> + <arg name="types" type="as" direction="in"/> + <arg name="queryobject" type="o" direction="out" /> + </method> + </interface> +</node> diff --git a/nepomuk/interfaces/org.kde.nepomuk.ResourceWatcherConnection.xml b/nepomuk/interfaces/org.kde.nepomuk.ResourceWatcherConnection.xml new file mode 100644 index 0000000..8b175bc --- /dev/null +++ b/nepomuk/interfaces/org.kde.nepomuk.ResourceWatcherConnection.xml @@ -0,0 +1,32 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.kde.nepomuk.ResourceWatcherConnection"> + <signal name="resourceCreated"> + <arg name="uri" type="s" direction="out"/> + <arg name="types" type="as" direction="out"/> + </signal> + <signal name="resourceRemoved"> + <arg name="uri" type="s" direction="out"/> + <arg name="types" type="as" direction="out"/> + </signal> + <signal name="resourceTypeAdded"> + <arg name="resUri" type="s" direction="out"/> + <arg name="type" type="s" direction="out"/> + </signal> + <signal name="resourceTypeRemoved"> + <arg name="resUri" type="s" direction="out"/> + <arg name="type" type="s" direction="out"/> + </signal> + <signal name="propertyAdded"> + <arg name="resource" type="s" direction="out"/> + <arg name="property" type="s" direction="out"/> + <arg name="value" type="v" direction="out"/> + </signal> + <signal name="propertyRemoved"> + <arg name="resource" type="s" direction="out"/> + <arg name="property" type="s" direction="out"/> + <arg name="value" type="v" direction="out"/> + </signal> + <method name="close" /> + </interface> +</node> diff --git a/nepomuk/interfaces/org.kde.nepomuk.Strigi.xml b/nepomuk/interfaces/org.kde.nepomuk.Strigi.xml index 4725958..44b736d 100644 --- a/nepomuk/interfaces/org.kde.nepomuk.Strigi.xml +++ b/nepomuk/interfaces/org.kde.nepomuk.Strigi.xml @@ -32,16 +32,6 @@ <method name="indexFile"> <arg name="path" type="s" direction="in" /> </method> - <method name="analyzeResource" > - <arg name="uri" direction="in" type="s" /> - <arg name="lastModificationDate" direction="in" type="u" /> - <arg name="data" direction="in" type="ay" /> - </method> - <method name="analyzeResourceFromTempFileAndDeleteTempFile" > - <arg name="uri" direction="in" type="s" /> - <arg name="lastModificationDate" direction="in" type="u" /> - <arg name="tmpFileName" direction="in" type="s" /> - </method> <method name="userStatusString"> <arg type="s" direction="out" /> </method> diff --git a/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.backupmanager.xml b/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.backupmanager.xml index e7d3cc1..85b2d12 100644 --- a/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.backupmanager.xml +++ b/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.backupmanager.xml @@ -4,10 +4,6 @@ <method name="backup"> <arg name="url" type="s" direction="in"/> </method> - <method name="restore"> - <arg type="i" direction="out"/> - <arg name="url" type="s" direction="in"/> - </method> <signal name="backupDone"> </signal> </interface> diff --git a/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.identifier.xml b/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.identifier.xml deleted file mode 100644 index 191b685..0000000 --- a/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.identifier.xml +++ /dev/null @@ -1,40 +0,0 @@ -<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> -<node> - <interface name="org.kde.nepomuk.services.nepomukbackupsync.Identifier"> - <signal name="identified"> - <arg name="id" type="i" direction="out"/> - <arg name="oldUri" type="s" direction="out"/> - <arg name="newUri" type="s" direction="out"/> - </signal> - <signal name="identificationDone"> - <arg name="id" type="i" direction="out"/> - <arg name="unidentified" type="i" direction="out"/> - </signal> - <signal name="notIdentified"> - <arg name="id" type="i" direction="out"/> - <arg name="serializedStatements" type="s" direction="out"/> - </signal> - <signal name="completed"> - <arg name="id" type="i" direction="out"/> - <arg name="progress" type="i" direction="out"/> - </signal> - <method name="identify"> - <arg type="b" direction="out"/> - <arg name="id" type="i" direction="in"/> - <arg name="oldUri" type="s" direction="in"/> - <arg name="newUri" type="s" direction="in"/> - </method> - <method name="ignore"> - <arg type="b" direction="out"/> - <arg name="id" type="i" direction="in"/> - <arg name="url" type="s" direction="in"/> - <arg name="ignoreSubDirectories" type="b" direction="in"/> - </method> - <method name="ignoreAll"> - <arg name="id" type="i" direction="in"/> - </method> - <method name="completeIdentification"> - <arg name="id" type="i" direction="in"/> - </method> - </interface> -</node> diff --git a/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.merger.xml b/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.merger.xml deleted file mode 100644 index 71c7812..0000000 --- a/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.merger.xml +++ /dev/null @@ -1,12 +0,0 @@ -<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> -<node> - <interface name="org.kde.nepomuk.services.nepomukbackupsync.Merger"> - <signal name="completed"> - <arg name="percent" type="i" direction="out"/> - </signal> - <signal name="multipleMerge"> - <arg name="uri" type="s" direction="out"/> - <arg name="prop" type="s" direction="out"/> - </signal> - </interface> -</node> diff --git a/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.syncmanager.xml b/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.syncmanager.xml index 8db092e..f8a87f2 100644 --- a/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.syncmanager.xml +++ b/nepomuk/interfaces/org.kde.nepomuk.services.nepomukbackupsync.syncmanager.xml @@ -1,10 +1,6 @@ <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> <node> <interface name="org.kde.nepomuk.services.nepomukbackupsync.SyncManager"> - <method name="sync"> - <arg type="i" direction="out"/> - <arg name="url" type="s" direction="in"/> - </method> <method name="createSyncFile"> <arg name="url" type="s" direction="in"/> <arg name="startTime" type="s" direction="in"/> diff --git a/nepomuk/kcm/kcm_nepomuk.desktop b/nepomuk/kcm/kcm_nepomuk.desktop index 75f6ef0..f03686b 100644 --- a/nepomuk/kcm/kcm_nepomuk.desktop +++ b/nepomuk/kcm/kcm_nepomuk.desktop @@ -17,6 +17,7 @@ Name[be@latin]=Pošuk Name[bg]=Настолно търсене Name[bn]=ডেস্কটপ সন্ধান Name[bn_IN]=ডেস্কটপে অনুসন্ধান +Name[bs]=Pretraga površi Name[ca]=Cerca a l'escriptori Name[ca@valencia]=Cerca a l'escriptori Name[cs]=Vyhledávací služby @@ -33,7 +34,7 @@ Name[fi]=Työpöytähaku Name[fr]=Rechercher sur le bureau Name[fy]=Buroblêd sykaksje Name[ga]=Cuardach Deisce -Name[gl]=Procura no escritorio +Name[gl]=Busca no escritorio Name[gu]=ડેસ્કટોપ શોધ Name[he]=חיפוש בשולחן העבודה Name[hi]=डेस्कटॉप खोज @@ -81,6 +82,7 @@ Name[te]=డెస్క్ టాప్ శోధన Name[tg]=Ҷустуҷӯи мизи корӣ Name[th]=ค้นหาผ่านพื้นที่ทำงาน Name[tr]=Masaüstü Araması +Name[ug]=ئۈستەلئۈستىدە ئىزدەش Name[uk]=Стільничний пошук Name[wa]=Cweraedje sol sicribanne Name[x-test]=xxDesktop Searchxx @@ -94,6 +96,7 @@ Comment[be@latin]=Nałady servera „Nepomuk”/„Strigi” Comment[bg]=Настройване на Nepomuk/Strigi Comment[bn]=নেপোমুক/স্ট্রিগি সার্ভার কনফিগারেশন Comment[bn_IN]=Nepomuk/Strigi সার্ভার কনফিগারেশন +Comment[bs]=Postava servera Nepomuka/Strigija Comment[ca]=Configuració del servidor Nepomuk/Strigi Comment[ca@valencia]=Configuració del servidor Nepomuk/Strigi Comment[cs]=Nastavení serveru Strigi/Nepomuk @@ -130,7 +133,7 @@ Comment[ko]=Nepomuk/Strigi 서버 설정 Comment[ku]=Veavakirinên Pêşkêşkara Nepomuk/Strigi Comment[lt]=Nepomuk/Strigi serverio konfigūravimas Comment[lv]=Nepomuk/Strigi servera konfigurācija -Comment[mai]=नेपोमक/स्ट्रीगी सर्वर विन्यास +Comment[mai]=नेपोमक/स्ट्रीगी सर्वर विन्यस्तेशन Comment[mk]=Конфигурација на Nepomuk/Strigi сервер Comment[ml]=നെപ്പോമുക്ക്/സ്ട്രിഗി സെര്വര് ക്രമീകരണം Comment[mr]=Nepomuk/Strigi सर्वर संयोजना @@ -159,6 +162,7 @@ Comment[te]=Nepomuk/Strigi సేవిక ఆకృతీకరణ Comment[tg]=Танзимоти хидматгоҳи Nepomuk/Strigi Comment[th]=ปรับแต่งบริการ Nepomuk/Strigi Comment[tr]=Nepomuk/Strigi Sunucu Yapılandırması +Comment[ug]=Nepomuk/Strigi مۇلازىمېتىر سەپلىمىسى Comment[uk]=Налаштування сервера Nepomuk/Strigi Comment[uz]=Nepomuk/Strigi serverini moslash Comment[uz@cyrillic]=Nepomuk/Strigi серверини мослаш diff --git a/nepomuk/kcm/nepomukconfigwidget.ui b/nepomuk/kcm/nepomukconfigwidget.ui index 3e1d255..83efa4c 100644 --- a/nepomuk/kcm/nepomukconfigwidget.ui +++ b/nepomuk/kcm/nepomukconfigwidget.ui @@ -8,7 +8,7 @@ <number>0</number> </property> <item> - <widget class="KTabWidget" name="tabWidget"> + <widget class="KTabWidget" name="m_mainTabWidget"> <property name="currentIndex"> <number>0</number> </property> @@ -77,6 +77,9 @@ <bold>true</bold> </font> </property> + <property name="text"> + <string notr="true">KSqueezedTextLabel</string> + </property> </widget> </item> </layout> @@ -151,6 +154,9 @@ <bold>true</bold> </font> </property> + <property name="text"> + <string notr="true">KSqueezedTextLabel</string> + </property> </widget> </item> </layout> @@ -207,7 +213,7 @@ </font> </property> <property name="text"> - <string/> + <string notr="true">KSqueezedTextLabel</string> </property> </widget> </item> @@ -221,14 +227,52 @@ </layout> </item> <item> - <widget class="QCheckBox" name="m_checkIndexRemovableMedia"> - <property name="toolTip"> - <string>Index the files on removable media like USB sticks when they are mounted</string> - </property> - <property name="text"> - <string>Index files on removable media</string> - </property> - </widget> + <layout class="QFormLayout" name="formLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Removable media handling:</string> + </property> + <property name="buddy"> + <cstring>m_comboRemovableMediaHandling</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="KComboBox" name="m_comboRemovableMediaHandling"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string comment="@info:tooltip">Indexing of files on removable media</string> + </property> + <property name="whatsThis"> + <string comment="@info:whatsthis"><para>Nepomuk can index files on removable device like USB keys or external hard-disks for fast desktop searches.</para> +<para>By default no files are indexed. Here this behaviour can be changed to one of two options:</para> +<para><list><item><interface>Index files on all removable devices</interface> - Files on removable media are indexed as soon as the medium is mounted. Caution: this does not include media which have been rejected via the second option.</item> +<item><interface>Ask individually when newly mounted</interface> - The user will be asked to decide if files on the newly mounted medium should be indexed or not. Once decided Nepomuk will not ask again.</item></list></para></string> + </property> + <item> + <property name="text"> + <string>Ignore all removable media</string> + </property> + </item> + <item> + <property name="text"> + <string>Index files on all removable devices</string> + </property> + </item> + <item> + <property name="text"> + <string>Ask individually when newly mounted</string> + </property> + </item> + </widget> + </item> + </layout> </item> </layout> </widget> @@ -322,7 +366,7 @@ </font> </property> <property name="text"> - <string/> + <string notr="true">KSqueezedTextLabel</string> </property> </widget> </item> diff --git a/nepomuk/kcm/nepomukserverkcm.cpp b/nepomuk/kcm/nepomukserverkcm.cpp index 97c9421..1a8d456 100644 --- a/nepomuk/kcm/nepomukserverkcm.cpp +++ b/nepomuk/kcm/nepomukserverkcm.cpp @@ -104,7 +104,7 @@ namespace { enum BackupFrequency { DisableAutomaticBackups = 0, DailyBackup = 1, - WeeklyBackup = 2 + WeeklyBackup = 2, }; @@ -170,7 +170,8 @@ Nepomuk::ServerConfigModule::ServerConfigModule( QWidget* parent, const QVariant this, SLOT( changed() ) ); connect( m_sliderMemoryUsage, SIGNAL( valueChanged(int) ), this, SLOT( changed() ) ); - connect( m_checkIndexRemovableMedia, SIGNAL( toggled(bool) ), + connect( m_comboRemovableMediaHandling, SIGNAL( activated(int) + ), this, SLOT( changed() ) ); connect( m_spinMaxResults, SIGNAL( valueChanged( int ) ), this, SLOT( changed() ) ); @@ -216,6 +217,11 @@ Nepomuk::ServerConfigModule::ServerConfigModule( QWidget* parent, const QVariant // update backup status whenever manual backups are created KDirWatch::self()->addDir( KStandardDirs::locateLocal( "data", "nepomuk/backupsync/backups/" ) ); connect( KDirWatch::self(), SIGNAL(dirty(QString)), this, SLOT(updateBackupStatus()) ); + + // args[0] can be the page index allowing to open the config with a specific page + if(args.count() > 0 && args[0].toInt() < m_mainTabWidget->count()) { + m_mainTabWidget->setCurrentIndex(args[0].toInt()); + } } else { QLabel* label = new QLabel( i18n( "The Nepomuk installation is not complete. No Nepomuk settings can be provided." ) ); @@ -250,7 +256,11 @@ void Nepomuk::ServerConfigModule::load() m_indexFolderSelectionDialog->setFolders( strigiConfig.group( "General" ).readPathEntry( "folders", defaultFolders() ), strigiConfig.group( "General" ).readPathEntry( "exclude folders", QStringList() ) ); m_indexFolderSelectionDialog->setExcludeFilters( strigiConfig.group( "General" ).readEntry( "exclude filters", Nepomuk::defaultExcludeFilterList() ) ); - m_checkIndexRemovableMedia->setChecked( strigiConfig.group( "General" ).readEntry( "index newly mounted", false ) ); + + const bool indexNewlyMounted = strigiConfig.group( "RemovableMedia" ).readEntry( "index newly mounted", false ); + const bool askIndividually = strigiConfig.group( "RemovableMedia" ).readEntry( "ask user", false ); + // combobox items: 0 - ignore, 1 - index all, 2 - ask user + m_comboRemovableMediaHandling->setCurrentIndex(int(indexNewlyMounted) + int(askIndividually)); groupBox->setEnabled(m_checkEnableNepomuk->isChecked()); @@ -321,7 +331,10 @@ void Nepomuk::ServerConfigModule::save() strigiConfig.group( "General" ).writePathEntry( "exclude folders", excludeFolders ); strigiConfig.group( "General" ).writeEntry( "exclude filters", m_indexFolderSelectionDialog->excludeFilters() ); strigiConfig.group( "General" ).writeEntry( "index hidden folders", m_indexFolderSelectionDialog->indexHiddenFolders() ); - strigiConfig.group( "General" ).writeEntry( "index newly mounted", m_checkIndexRemovableMedia->isChecked() ); + + // combobox items: 0 - ignore, 1 - index all, 2 - ask user + strigiConfig.group( "RemovableMedia" ).writeEntry( "index newly mounted", m_comboRemovableMediaHandling->currentIndex() > 0 ); + strigiConfig.group( "RemovableMedia" ).writeEntry( "ask user", m_comboRemovableMediaHandling->currentIndex() == 2 ); // 3. update kio_nepomuksearch config diff --git a/nepomuk/kcm/statuswidget.cpp b/nepomuk/kcm/statuswidget.cpp index b198433..4798d7f 100644 --- a/nepomuk/kcm/statuswidget.cpp +++ b/nepomuk/kcm/statuswidget.cpp @@ -41,6 +41,7 @@ Nepomuk::StatusWidget::StatusWidget( QWidget* parent ) m_updatingJobCnt( 0 ), m_updateRequested( false ) { + KGlobal::locale()->insertCatalog("kcm_nepomuk"); setupUi( mainWidget() ); setCaption( m_title->text() ); diff --git a/nepomuk/kioslaves/common/resourcestat.cpp b/nepomuk/kioslaves/common/resourcestat.cpp index bbd2c4a..53c2f1a 100644 --- a/nepomuk/kioslaves/common/resourcestat.cpp +++ b/nepomuk/kioslaves/common/resourcestat.cpp @@ -171,7 +171,7 @@ KUrl Nepomuk::convertRemovableMediaFileUrl( const KUrl& url, bool evenMountIfNec ( storage->isAccessible() || ( evenMountIfNecessary && Nepomuk::mountAndWait( storage ) ) ) ) { kDebug() << "converted:" << KUrl( storage->filePath() + QLatin1String( "/" ) + url.path() ); - return storage->filePath() + QLatin1String( "/" ) + url.path(); + return QString( storage->filePath() + QLatin1String( "/" ) + url.path() ); } else { return KUrl(); diff --git a/nepomuk/kioslaves/search/CMakeLists.txt b/nepomuk/kioslaves/search/CMakeLists.txt index 887c735..dd12acf 100644 --- a/nepomuk/kioslaves/search/CMakeLists.txt +++ b/nepomuk/kioslaves/search/CMakeLists.txt @@ -10,6 +10,7 @@ include_directories( ${KDE4_KIO_INCLUDES} ${SOPRANO_INCLUDE_DIR} ${NEPOMUK_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} ${nepomuk_kio_slaves_SOURCE_DIR}/common ) @@ -21,6 +22,8 @@ set(kio_nepomuksearch_PART_SRCS ../common/resourcestat.cpp ) +soprano_add_ontology(kio_nepomuksearch_PART_SRCS ${nepomuk_ontologies_SOURCE_DIR}/kext.trig "KExt" "Nepomuk::Vocabulary" "trig") + kde4_add_plugin(kio_nepomuksearch ${kio_nepomuksearch_PART_SRCS}) diff --git a/nepomuk/kioslaves/search/kdedmodule/CMakeLists.txt b/nepomuk/kioslaves/search/kdedmodule/CMakeLists.txt index 86c40f5..807dc0c 100644 --- a/nepomuk/kioslaves/search/kdedmodule/CMakeLists.txt +++ b/nepomuk/kioslaves/search/kdedmodule/CMakeLists.txt @@ -14,6 +14,8 @@ set(nepomuksearchmodule_SRCS ../../common/timelinetools.cpp ) +soprano_add_ontology(nepomuksearchmodule_SRCS ${nepomuk_ontologies_SOURCE_DIR}/kext.trig "KExt" "Nepomuk::Vocabulary" "trig") + set_source_files_properties( ${nepomuk_kio_slaves_SOURCE_DIR}/../interfaces/org.kde.nepomuk.Query.xml PROPERTIES INCLUDE "nepomuk/result.h") diff --git a/nepomuk/kioslaves/search/kdedmodule/nepomuksearchmodule.desktop b/nepomuk/kioslaves/search/kdedmodule/nepomuksearchmodule.desktop index bbd71f4..a3fbcc9 100644 --- a/nepomuk/kioslaves/search/kdedmodule/nepomuksearchmodule.desktop +++ b/nepomuk/kioslaves/search/kdedmodule/nepomuksearchmodule.desktop @@ -10,6 +10,7 @@ Name[ar]=وحدة بحث نبومك Name[ast]=Módulu de gueta Nepomuk Name[bg]=Модул за търсене Nepomuk Name[bn]=নেপোমুক সন্ধান মডিউল +Name[bs]=Pretraživački modul Nepomuka Name[ca]=Mòdul de cerques del Nepomuk Name[ca@valencia]=Mòdul de cerques del Nepomuk Name[cs]=Vyhledávací modul Nepomuk @@ -39,7 +40,6 @@ Name[kn]=ನೆಪೋಮುಕ್ ಹುಡುಕು ಘಟಕ Name[ko]=Nepomuk 검색 모듈 Name[lt]=Nepomuk paieškos modulis Name[lv]=Nepomuk meklēšanas modulis -Name[mai]=नेपोमक खोज मोड्यूल Name[mk]=Модул на Непомук за пребарување Name[ml]=നെപ്പോമുക്ക് തെരച്ചില് മൊഡ്യൂള് Name[nb]=Nepomuk søkemodul @@ -63,6 +63,7 @@ Name[sv]=Nepomuk-sökmodul Name[tg]=Хидматҳои ҷустуҷӯии Nepomuk Name[th]=มอดูลค้นหาของบริการ Neomuk Name[tr]=Nepomuk Arama Modülü +Name[ug]=Nepomuk ئىزدەش بۆلىكى Name[uk]=Модуль пошуку Nepomuk Name[wa]=Module di cweraedje Nepomuk Name[x-test]=xxNepomuk Search Modulexx @@ -71,6 +72,8 @@ Name[zh_TW]=Nepomuk 搜尋模組 Comment=Helper module for KIO to ensure automatic updates of nepomuksearch listings. Comment[ar]=الوحدة المساعدة لدخل وخرج كدي لضمان التحديث التلقائي للوائح بحث نِبوموك Comment[ast]=Módulu auxiliar de KIO p'asegurar l'anovamientu automáticu de llistaos de guetes con Nepomuk. +Comment[bg]=Помощен модул за KIO осигуряващ автоматично обновяване на списък с търсени резултати +Comment[bs]=Pomoćni modul za K‑U/I koji obezbeđuje automatsko ažuriranje spiskova Nepomukove pretrage. Comment[ca]=Mòdul ajudant per al KIO que assegura les actualitzacions automàtiques dels llistats del «nepomuksearch». Comment[ca@valencia]=Mòdul ajudant per al KIO que assegura les actualitzacions automàtiques dels llistats del «nepomuksearch». Comment[cs]=Pomocný KIO modul pro zajištění automatických aktualizací seznamů Vyhledávání Nepomuku. @@ -87,7 +90,7 @@ Comment[ga]=Modúl cabhrach le haghaidh KIO a dhéanann cinnte go bhfuil eolair Comment[he]=מודול עזרה עבור KIO המבטיח עידכון אוטומטי של הרשימות של nepomuksearch. Comment[hr]=Pomoćni modul za KIO koji osigurava automatsko ažuriranje izlistavanja nepomuk pretraga. Comment[hu]=KIO segédmodul a Nepomuk keresőszolgáltatás listái automatikus frissítésének ellenőrzéséhez. -Comment[ia]=Modulo de adjuta pro KIO per assecurar actualisationes automatic de listas de nepomuksearch. +Comment[ia]=Modulo de adjuta pro KIO per assecurar actualisationes automatic de listas de nepomuksearch (cerca de Nepomuk). Comment[id]=Modul penolong untuk KIO untuk memastikan pemutakhiran otomatis pengurutan nepomuksearch. Comment[is]=KIO hjálpareining sem tryggir sjálfvirkar uppfærslur á leitarlistum Nepomuk leitareiningar. Comment[it]=Modulo ausiliare per KIO per assicurare gli aggiornamenti automatici delle liste di ricerca di Nepomuk. @@ -96,6 +99,7 @@ Comment[kk]=nepomuksearch тізімдерін автоматты түрде ж Comment[km]=ម៉ឌុលកម្មវិធីជំនួយសម្រាប់ KIO ដើម្បីប្រាកដថា ធ្វើបច្ចុប្បន្នភាពការរាយរបស់ nepomuksearch ដោយស្វ័យប្រវត្តិ ។ Comment[ko]=nepomuksearch 목록을 자동으로 업데이트하는 KIO 모듈입니다. Comment[lt]=Pagalbos modulis, skirtas KIO, užtikrinantis automatinius atnaujinimus nepomuk paieškos sąrašuose. +Comment[lv]=KIO palīgmodulis, lai nodrošinātu Nepomuk meklēšanas sarakstu automātiskos atjauninājumus. Comment[nb]=Hjelpemodul for KIO – for å sikre automatiske oppdateringer av nepomuksearch-resultater. Comment[nds]=Hülpmoduul för de KDE-In-/Utgaav för automaatsch Opfrischen vun Nepomuk-Sööklisten Comment[nl]=Helper-module voor KIO om automatisch bijwerken van nepomukzoeklijsten. @@ -115,6 +119,7 @@ Comment[sr@latin]=Pomoćni modul za K‑U/I koji obezbeđuje automatsko ažurira Comment[sv]=Hjälpmodul för I/O-slavar för att försäkra att automatisk uppdatering görs av Nepomuk söklistningar. Comment[th]=มอดูลช่วยสำหรับ KIO เพื่อให้แน่ใจว่าจะมีการปรับปรุงการทำรายการของ nepomuksearch Comment[tr]=KIO'nun, nepomuksearch listelerinin otomatik güncellenmesini sağlaması için yardımcı modül. +Comment[ug]=KIO نىڭ nepomuk ئىزدەش تىزىمىنى ئۆزلۈكىدىن يېڭىلاشقا ئىشلىتىدىغان ياردەمچى بۆلىكى Comment[uk]=Допоміжний модуль KIO для забезпечення автоматичного оновлення списків nepomuksearch. Comment[wa]=Module aidant KIO po fé les djivêyes di metaedjes a djoû otomatikes di nepomuksearch Comment[x-test]=xxHelper module for KIO to ensure automatic updates of nepomuksearch listings.xx diff --git a/nepomuk/kioslaves/search/kdedmodule/searchurllistener.cpp b/nepomuk/kioslaves/search/kdedmodule/searchurllistener.cpp index 62074e6..2333e38 100644 --- a/nepomuk/kioslaves/search/kdedmodule/searchurllistener.cpp +++ b/nepomuk/kioslaves/search/kdedmodule/searchurllistener.cpp @@ -27,12 +27,15 @@ #include <kdebug.h> #include <nepomuk/result.h> #include <nepomuk/query.h> +#include <nepomuk/resource.h> #include <QtCore/QHash> #include <QtDBus/QDBusConnection> #include <QtDBus/QDBusObjectPath> #include <QtDBus/QDBusReply> +#include <Soprano/BindingSet> + Nepomuk::SearchUrlListener::SearchUrlListener( const KUrl& queryUrl, const KUrl& notifyUrl ) : QObject( 0 ), @@ -92,14 +95,20 @@ void Nepomuk::SearchUrlListener::slotNewEntries( const QList<Nepomuk::Query::Res } -void Nepomuk::SearchUrlListener::slotEntriesRemoved( const QStringList& entries ) +void Nepomuk::SearchUrlListener::slotEntriesRemoved( const QList<Nepomuk::Query::Result>& entries ) { QStringList urls; - foreach( const QString& uri, entries ) { + foreach( const Query::Result& result, entries ) { + // make sure we use the exact same name used in searchfolder.cpp + KUrl url( result.resource().resourceUri() ); + if( result.requestProperties().contains(Nepomuk::Vocabulary::NIE::url()) ) + url = result[Nepomuk::Vocabulary::NIE::url()].uri(); + KUrl resultUrl( m_notifyUrl ); - resultUrl.addPath( Nepomuk::resourceUriToUdsName( uri ) ); + resultUrl.addPath( Nepomuk::resourceUriToUdsName( url ) ); urls << resultUrl.url(); } + kDebug() << urls; org::kde::KDirNotify::emitFilesRemoved( urls ); } @@ -149,8 +158,8 @@ void Nepomuk::SearchUrlListener::createInterface() QDBusConnection::sessionBus() ); connect( m_queryInterface, SIGNAL( newEntries( QList<Nepomuk::Query::Result> ) ), this, SLOT( slotNewEntries( QList<Nepomuk::Query::Result> ) ) ); - connect( m_queryInterface, SIGNAL( entriesRemoved( QStringList ) ), - this, SLOT( slotEntriesRemoved( QStringList ) ) ); + connect( m_queryInterface, SIGNAL( entriesRemoved( QList<Nepomuk::Query::Result> ) ), + this, SLOT( slotEntriesRemoved( QList<Nepomuk::Query::Result> ) ) ); m_queryInterface->listen(); } } diff --git a/nepomuk/kioslaves/search/kdedmodule/searchurllistener.h b/nepomuk/kioslaves/search/kdedmodule/searchurllistener.h index fdf2700..ba2d3bc 100644 --- a/nepomuk/kioslaves/search/kdedmodule/searchurllistener.h +++ b/nepomuk/kioslaves/search/kdedmodule/searchurllistener.h @@ -42,9 +42,12 @@ namespace Nepomuk { int ref(); int unref(); + KUrl queryUrl() const { return m_queryUrl; } + KUrl notificationUrl() const { return m_notifyUrl; } + private Q_SLOTS: void slotNewEntries( const QList<Nepomuk::Query::Result>& entries ); - void slotEntriesRemoved( const QStringList& entries ); + void slotEntriesRemoved( const QList<Nepomuk::Query::Result>& entries ); void slotQueryServiceInitialized( bool success ); private: diff --git a/nepomuk/kioslaves/search/kio_nepomuksearch.cpp b/nepomuk/kioslaves/search/kio_nepomuksearch.cpp index 58edd26..e68b8bc 100644 --- a/nepomuk/kioslaves/search/kio_nepomuksearch.cpp +++ b/nepomuk/kioslaves/search/kio_nepomuksearch.cpp @@ -300,50 +300,9 @@ bool Nepomuk::SearchProtocol::rewriteUrl( const KUrl& url, KUrl& newURL ) void Nepomuk::SearchProtocol::prepareUDSEntry( KIO::UDSEntry& uds, bool listing ) const { - // for performace reasons we do encode the result's resource URI in the UDS_NAME - // Otherwise we would have to re-query for each stat operation - // This is simple for "direct" query results (SearchFolder takes care of that) - // but a bit harder for items in results that are folders. - // In the latter case we get the parent folder's resource URI (which is encoded in - // the UDS_NAME) and append the filename. - // - // Also note that results listed via a SearchFolder will never go through this method - // since they are listed directly and not via a forward. Forwarding will only happen - // for search results that are folders and for non-listing operations. - - kDebug() << requestedUrl() << processedUrl() << uds.stringValue(KIO::UDSEntry::UDS_NAME); - const QString name = uds.stringValue(KIO::UDSEntry::UDS_NAME); - if(name != QLatin1String(".") && name != QLatin1String("..")) { - // let the ForwardingSlaveBase create UDS_LOCAL_PATH and mimetype entries - // This call depends on the original UDS_NAME which we change below. Thus, it - // is important to let ForwardingSlaveBase do its thing before we start ours - ForwardingSlaveBase::prepareUDSEntry( uds, listing ); - - // encode the URL in the UDS_NAME to prevent a re-query in stat and friends - KUrl resourceUrl(processedUrl()); - if(listing) { - resourceUrl.addPath(name); - } - uds.insert(KIO::UDSEntry::UDS_NAME, Nepomuk::resourceUriToUdsName(resourceUrl)); - if ( !uds.contains( KIO::UDSEntry::UDS_DISPLAY_NAME ) ) { - uds.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, name); - } - - // There is a trade-off between using UDS_TARGET_URL or not. The advantage is that we get proper - // file names in opening applications and non-KDE apps can handle the URLs properly. The downside - // is that we lose the context information, i.e. query results cannot be browsed in the opening - // application. We decide pro-filenames and pro-non-kde-apps here. - if( resourceUrl.isLocalFile() ) { - if ( uds.isDir() ) { - Query::FileQuery query; - query.addIncludeFolder( resourceUrl ); - uds.insert( KIO::UDSEntry::UDS_NEPOMUK_QUERY, query.toString() ); - } - else { - uds.insert( KIO::UDSEntry::UDS_TARGET_URL, resourceUrl.url() ); - } - } - } + // do nothing - we do everything in SearchFolder::statResult + Q_UNUSED(uds); + Q_UNUSED(listing); } diff --git a/nepomuk/kioslaves/search/queryutils.h b/nepomuk/kioslaves/search/queryutils.h index 433936e..3b5b707 100644 --- a/nepomuk/kioslaves/search/queryutils.h +++ b/nepomuk/kioslaves/search/queryutils.h @@ -22,11 +22,17 @@ #ifndef QUERYUTILS_H #define QUERYUTILS_H +#include "kext.h" + #include <KUrl> #include <KDebug> #include <Nepomuk/Query/Query> +#include <Nepomuk/Query/OptionalTerm> +#include <Nepomuk/Query/ComparisonTerm> +#include <Nepomuk/Query/AndTerm> #include <Nepomuk/Vocabulary/NIE> +#include <Nepomuk/Vocabulary/NFO> namespace Nepomuk { @@ -40,9 +46,43 @@ namespace Nepomuk { query = Nepomuk::Query::Query::fromQueryUrl( url ); // request properties to easily create UDSEntry instances - QList<Query::Query::RequestProperty> reqProperties; + QList<Query::RequestProperty> reqProperties; // local URL - reqProperties << Query::Query::RequestProperty( Nepomuk::Vocabulary::NIE::url(), !query.isFileQuery() ); + reqProperties << Query::RequestProperty( Nepomuk::Vocabulary::NIE::url(), !query.isFileQuery() ); +#ifdef Q_OS_UNIX + if( query.isFileQuery() ) { + // file size + ComparisonTerm contentSizeTerm( Nepomuk::Vocabulary::NIE::contentSize(), Term() ); + contentSizeTerm.setVariableName( QLatin1String("size") ); + // mimetype + ComparisonTerm mimetypeTerm( Nepomuk::Vocabulary::NIE::mimeType(), Term() ); + mimetypeTerm.setVariableName( QLatin1String("mime") ); + // mtime + ComparisonTerm mtimeTerm( Nepomuk::Vocabulary::NIE::lastModified(), Term() ); + mtimeTerm.setVariableName( QLatin1String("mtime") ); + // mode + ComparisonTerm modeTerm( Nepomuk::Vocabulary::KExt::unixFileMode(), Term() ); + modeTerm.setVariableName( QLatin1String("mode") ); + // user + ComparisonTerm userTerm( Nepomuk::Vocabulary::KExt::unixFileOwner(), Term() ); + userTerm.setVariableName( QLatin1String("user") ); + // group + ComparisonTerm groupTerm( Nepomuk::Vocabulary::KExt::unixFileGroup(), Term() ); + groupTerm.setVariableName( QLatin1String("group") ); + + // instead of separate request properties we use one optional and term. That way + // all or none of the properties will be bound which makes handling the data in + // SearchFolder::statResult much simpler. + AndTerm filePropertiesTerm; + filePropertiesTerm.addSubTerm( contentSizeTerm ); + filePropertiesTerm.addSubTerm( mimetypeTerm ); + filePropertiesTerm.addSubTerm( mtimeTerm ); + filePropertiesTerm.addSubTerm( modeTerm ); + filePropertiesTerm.addSubTerm( userTerm ); + filePropertiesTerm.addSubTerm( groupTerm ); + query = query && OptionalTerm::optionalizeTerm( filePropertiesTerm ); + } +#endif // Q_OS_UNIX query.setRequestProperties( reqProperties ); if ( query.isValid() ) { diff --git a/nepomuk/kioslaves/search/searchfolder.cpp b/nepomuk/kioslaves/search/searchfolder.cpp index 6fc2f85..aeebc3f 100644 --- a/nepomuk/kioslaves/search/searchfolder.cpp +++ b/nepomuk/kioslaves/search/searchfolder.cpp @@ -192,11 +192,6 @@ void Nepomuk::SearchFolder::statResults() namespace { bool statFile( const KUrl& url, const KUrl& fileUrl, KIO::UDSEntry& uds ) { - // the akonadi kio slave is just way too slow and - // in KDE 4.4 akonadi items should have nepomuk:/res/<uuid> URIs anyway - if ( url.scheme() == QLatin1String( "akonadi" ) ) - return false; - if ( !fileUrl.isEmpty() ) { if ( KIO::StatJob* job = KIO::stat( fileUrl, KIO::HideProgressInfo ) ) { // we do not want to wait for the event loop to delete the job @@ -227,74 +222,121 @@ namespace { KIO::UDSEntry Nepomuk::SearchFolder::statResult( const Query::Result& result ) { Resource res( result.resource() ); - KUrl url( res.resourceUri() ); + const KUrl uri( res.resourceUri() ); KUrl nieUrl( result[Nepomuk::Vocabulary::NIE::url()].uri() ); - if ( nieUrl.isEmpty() ) - nieUrl = Nepomuk::nepomukToFileUrl( url ); + // the additional bindings that we only have on unix systems + // Either all are bound or none of them. + // see also parseQueryUrl (queryutils.h) + const Soprano::BindingSet additionalVars = result.additionalBindings(); + + // the UDSEntry that will contain the final result to list KIO::UDSEntry uds; - if ( statFile( url, nieUrl, uds ) ) { - if ( !nieUrl.isEmpty() ) { - if ( nieUrl.isLocalFile() ) { - // There is a trade-off between using UDS_TARGET_URL or not. The advantage is that we get proper - // file names in opening applications and non-KDE apps can handle the URLs properly. The downside - // is that we lose the context information, i.e. query results cannot be browsed in the opening - // application. We decide pro-filenames and pro-non-kde-apps here. - if( !uds.isDir() ) - uds.insert( KIO::UDSEntry::UDS_TARGET_URL, nieUrl.url() ); - uds.insert( KIO::UDSEntry::UDS_LOCAL_PATH, nieUrl.toLocalFile() ); - } +#ifdef Q_OS_UNIX + if( !nieUrl.isEmpty() && + nieUrl.isLocalFile() && + additionalVars[QLatin1String("mtime")].isLiteral() ) { + // make sure we have unique names for everything + uds.insert( KIO::UDSEntry::UDS_NAME, resourceUriToUdsName( nieUrl ) ); + + // set the name the user will see + uds.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, nieUrl.fileName() ); + + // set the basic file information which we got from Nepomuk + uds.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, additionalVars[QLatin1String("mtime")].literal().toDateTime().toTime_t() ); + uds.insert( KIO::UDSEntry::UDS_SIZE, additionalVars[QLatin1String("size")].literal().toInt() ); + uds.insert( KIO::UDSEntry::UDS_FILE_TYPE, additionalVars[QLatin1String("mode")].literal().toInt() & S_IFMT ); + uds.insert( KIO::UDSEntry::UDS_ACCESS, additionalVars[QLatin1String("mode")].literal().toInt() & 07777 ); + uds.insert( KIO::UDSEntry::UDS_USER, additionalVars[QLatin1String("user")].toString() ); + uds.insert( KIO::UDSEntry::UDS_GROUP, additionalVars[QLatin1String("group")].toString() ); + + // since we change the UDS_NAME KFileItem cannot handle mimetype and such anymore + uds.insert( KIO::UDSEntry::UDS_MIME_TYPE, additionalVars[QLatin1String("mime")].toString() ); + if( uds.stringValue(KIO::UDSEntry::UDS_MIME_TYPE).isEmpty()) + uds.insert( KIO::UDSEntry::UDS_MIME_TYPE, KMimeType::findByUrl(nieUrl)->name() ); + } + else +#endif // Q_OS_UNIX + { + // not a simple local file result + + // check if we have a pimo thing relating to a file + if ( nieUrl.isEmpty() ) + nieUrl = Nepomuk::nepomukToFileUrl( uri ); + + // try to stat the file + if ( statFile( uri, nieUrl, uds ) ) { // make sure we have unique names for everything - uds.insert( KIO::UDSEntry::UDS_NAME, resourceUriToUdsName( nieUrl ) ); - } - else { - // make sure we have unique names for everything - uds.insert( KIO::UDSEntry::UDS_NAME, resourceUriToUdsName( url ) ); - } + // We encode the resource URL or URI into the name so subsequent calls to stat or + // other non-listing commands can easily forward to the appropriate slave. + if ( !nieUrl.isEmpty() ) { + uds.insert( KIO::UDSEntry::UDS_NAME, resourceUriToUdsName( nieUrl ) ); + } + else { + uds.insert( KIO::UDSEntry::UDS_NAME, resourceUriToUdsName( uri ) ); + } - // needed since the file:/ KIO slave does not create them and KFileItem::nepomukUri() - // cannot know that it is a local file since it is forwarded - uds.insert( KIO::UDSEntry::UDS_NEPOMUK_URI, url.url() ); - - // make sure we do not use these ugly names for display - if ( !uds.contains( KIO::UDSEntry::UDS_DISPLAY_NAME ) ) { - // by checking nieUrl we avoid loading the resource for local files - if ( nieUrl.isEmpty() && - res.hasType( Nepomuk::Vocabulary::PIMO::Thing() ) ) { - if ( !res.pimoThing().groundingOccurrences().isEmpty() ) { - res = res.pimoThing().groundingOccurrences().first(); + // make sure we do not use these ugly names for display + if ( !uds.contains( KIO::UDSEntry::UDS_DISPLAY_NAME ) ) { + if ( nieUrl.isEmpty() && + res.hasType( Nepomuk::Vocabulary::PIMO::Thing() ) ) { + if ( !res.pimoThing().groundingOccurrences().isEmpty() ) { + res = res.pimoThing().groundingOccurrences().first(); + nieUrl = res.property(Nepomuk::Vocabulary::NIE::url()).toUrl(); + } } - } - if ( !nieUrl.isEmpty() ) { - uds.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, nieUrl.fileName() ); + if ( !nieUrl.isEmpty() ) { + uds.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, nieUrl.fileName() ); - // since we change the UDS_NAME KFileItem cannot handle mimetype and such anymore - QString mimetype = uds.stringValue( KIO::UDSEntry::UDS_MIME_TYPE ); - if ( mimetype.isEmpty() ) { - mimetype = KMimeType::findByUrl(nieUrl)->name(); - uds.insert( KIO::UDSEntry::UDS_MIME_TYPE, mimetype ); + // since we change the UDS_NAME KFileItem cannot handle mimetype and such anymore + QString mimetype = uds.stringValue( KIO::UDSEntry::UDS_MIME_TYPE ); + if ( mimetype.isEmpty() ) { + mimetype = KMimeType::findByUrl(nieUrl)->name(); + uds.insert( KIO::UDSEntry::UDS_MIME_TYPE, mimetype ); + } + } + else { + uds.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, res.genericLabel() ); } - } - else { - uds.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, res.genericLabel() ); } } + else { + kDebug() << "Stating" << result.resource().resourceUri() << "failed"; + return KIO::UDSEntry(); + } + } - QString excerpt = result.excerpt(); - if( !excerpt.isEmpty() ) { - // KFileItemDelegate cannot handle rich text yet. Thus we need to remove the formatting. - QTextDocument doc; - doc.setHtml(excerpt); - excerpt = doc.toPlainText(); - uds.insert( KIO::UDSEntry::UDS_COMMENT, i18n("Search excerpt: %1", excerpt) ); + if( !nieUrl.isEmpty() ) { + // There is a trade-off between using UDS_URL or not. The advantage is that we get proper + // file names in opening applications and non-KDE apps can handle the URLs properly. The downside + // is that we lose the context information, i.e. query results cannot be browsed in the opening + // application. We decide pro-filenames and pro-non-kde-apps here. + if( !uds.isDir() ) { + uds.insert( KIO::UDSEntry::UDS_TARGET_URL, nieUrl.url() ); } - return uds; + // set the local path so that KIO can handle the rest + if( nieUrl.isLocalFile() ) + uds.insert( KIO::UDSEntry::UDS_LOCAL_PATH, nieUrl.toLocalFile() ); } else { - kDebug() << "Stating" << result.resource().resourceUri() << "failed"; - return KIO::UDSEntry(); + uds.insert( KIO::UDSEntry::UDS_TARGET_URL, uri.url() ); } + + // Tell KIO which Nepomuk resource this actually is + uds.insert( KIO::UDSEntry::UDS_NEPOMUK_URI, uri.url() ); + + // add optional full-text search excerpts + QString excerpt = result.excerpt(); + if( !excerpt.isEmpty() ) { + // KFileItemDelegate cannot handle rich text yet. Thus we need to remove the formatting. + QTextDocument doc; + doc.setHtml(excerpt); + excerpt = doc.toPlainText(); + uds.insert( KIO::UDSEntry::UDS_COMMENT, i18n("Search excerpt: %1", excerpt) ); + } + + return uds; } diff --git a/nepomuk/ontologies/CMakeLists.txt b/nepomuk/ontologies/CMakeLists.txt index c5fed3f..7a7a590 100644 --- a/nepomuk/ontologies/CMakeLists.txt +++ b/nepomuk/ontologies/CMakeLists.txt @@ -1,8 +1,14 @@ project(nepomuk_ontologies) -set(ONTO_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/share/ontology/kde) configure_file(kuvo.ontology.in ${CMAKE_CURRENT_BINARY_DIR}/kuvo.ontology) -install(FILES kuvo.trig ${CMAKE_CURRENT_BINARY_DIR}/kuvo.ontology DESTINATION ${ONTO_INSTALL_DIR}) - configure_file(nrio.ontology.in ${CMAKE_CURRENT_BINARY_DIR}/nrio.ontology) -install(FILES nrio.trig ${CMAKE_CURRENT_BINARY_DIR}/nrio.ontology DESTINATION ${ONTO_INSTALL_DIR}) +configure_file(kext.ontology.in ${CMAKE_CURRENT_BINARY_DIR}/kext.ontology) + +install(FILES + kuvo.trig + ${CMAKE_CURRENT_BINARY_DIR}/kuvo.ontology + nrio.trig + ${CMAKE_CURRENT_BINARY_DIR}/nrio.ontology + kext.trig + ${CMAKE_CURRENT_BINARY_DIR}/kext.ontology +DESTINATION ${CMAKE_INSTALL_PREFIX}/share/ontology/kde) diff --git a/nepomuk/ontologies/kext.ontology.in b/nepomuk/ontologies/kext.ontology.in new file mode 100644 index 0000000..a9ada44 --- /dev/null +++ b/nepomuk/ontologies/kext.ontology.in @@ -0,0 +1,8 @@ +[Ontology] +Version=1.0 +Name=KDE Extensions Ontology +Comment=The KDE Extensions Ontology contains extensions to the shared-desktop-ontologies that are not generic enough. +Namespace=http://nepomuk.kde.org/ontologies/2010/11/29/kext# +Path=${CMAKE_INSTALL_PREFIX}/share/ontology/kde/kext.trig +MimeType=application/x-trig +Type=Data diff --git a/nepomuk/ontologies/kext.trig b/nepomuk/ontologies/kext.trig new file mode 100644 index 0000000..be5d041 --- /dev/null +++ b/nepomuk/ontologies/kext.trig @@ -0,0 +1,116 @@ +# +# Copyright (c) 2010-2011 Sebastian Trueg <trueg@kde.org> +# All rights reserved, licensed under either CC-BY or BSD. +# +# You are free: +# * to Share - to copy, distribute and transmit the work +# * to Remix - to adapt the work +# Under the following conditions: +# * Attribution - You must attribute the work in the manner specified by the author +# or licensor (but not in any way that suggests that they endorse you or your use +# of the work). +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# * Neither the names of the authors nor the names of contributors may +# be used to endorse or promote products derived from this ontology without +# specific prior written permission. +# +# THIS ONTOLOGY IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS ONTOLOGY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix nao: <http://www.semanticdesktop.org/ontologies/2007/08/15/nao#> . +@prefix nrl: <http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#> . +@prefix nie: <http://www.semanticdesktop.org/ontologies/2007/01/19/nie#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix kext: <http://nepomuk.kde.org/ontologies/2010/11/29/kext#> . +@prefix nfo: <http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#> . + +kext: { + kext:unixFileMode + a rdf:Property ; + rdfs:label "Unix file mode" ; + rdfs:comment "The file mode value as seen on unix file systems." ; + rdfs:domain nfo:FileDataObject ; + rdfs:range xsd:integer ; + nrl:maxCardinality 1 ; + nao:userVisible false . + + kext:unixFileOwner + a rdf:Property ; + rdfs:label "Unix file owner" ; + rdfs:comment "The owner of the file as seen on unix file systems. This is intended as the low-level counterpart to nfo:owner." ; + rdfs:domain nfo:FileDataObject ; + rdfs:range xsd:string ; + nrl:maxCardinality 1 ; + nao:userVisible false . + + kext:unixFileGroup + a rdf:Property ; + rdfs:label "Unix file group" ; + rdfs:comment "The group of the file as seen on unix file systems." ; + rdfs:domain nfo:FileDataObject ; + rdfs:range xsd:string ; + nrl:maxCardinality 1 ; + nao:userVisible false . + + kext:Activity + a rdfs:Class ; + rdfs:subClassOf rdfs:Resource ; + rdfs:label "activity" ; + rdfs:comment "A Plasma activity." . + + kext:usedActivity + a rdf:Property ; + rdfs:label "used activity" ; + rdfs:comment "The activity that was active when resource was created. This is mostly used for graphs or desktop events." ; + rdfs:domain rdfs:Resource ; + rdfs:range kext:Activity ; + nrl:maxCardinality 1 ; + nao:userVisible false . + + kext:activityIdentifier + a rdf:Property ; + rdfs:subPropertyOf nao:identifier ; + rdfs:label "activity identifier" ; + rdfs:comment "The unique ID of the activity as used outside of Nepomuk. Typically this is a UUID." ; + rdfs:domain kext:Activity ; + rdfs:range xsd:string ; + nrl:cardinality 1 ; + nao:userVisible false . +} + +<http://nepomuk.kde.org/ontologies/2010/11/29/kext/metadata> { + <http://nepomuk.kde.org/ontologies/2010/11/29/kext/metadata> + a nrl:GraphMetadata ; + nrl:coreGraphMetadataFor kext: . + + + kext: + a nrl:Ontology , nrl:DocumentGraph ; + nao:prefLabel "KDE Extensions Ontology" ; + nao:hasDefaultNamespace "http://nepomuk.kde.org/ontologies/2010/11/29/kext#" ; + nao:hasDefaultNamespaceAbbreviation "kext" ; + nao:lastModified "2011-06-15T18:09:43Z" ; + nao:serializationLanguage "TriG" ; + nao:status "Unstable" ; + nrl:updatable "0" ; + nao:version "2" . +} + diff --git a/nepomuk/ontologies/kuvo.trig b/nepomuk/ontologies/kuvo.trig index 38e0735..11ac9f4 100644 --- a/nepomuk/ontologies/kuvo.trig +++ b/nepomuk/ontologies/kuvo.trig @@ -41,6 +41,7 @@ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . @prefix kuvo: <http://nepomuk.kde.org/ontologies/2010/08/18/kuvo#> . @prefix nuao: <http://www.semanticdesktop.org/ontologies/2010/01/25/nuao#> . +@prefix nfo: <http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#> . kuvo: { # Setting a baseline calms down the algorithm that creates all the entries. @@ -50,6 +51,9 @@ kuvo: { nao:userVisible nao:userVisible false . + nao:hasSubResource + nao:userVisible false . + nuao:Event nao:userVisible false . @@ -77,12 +81,13 @@ kuvo: { kuvo: a nrl:Ontology , nrl:DocumentGraph ; + nao:prefLabel "KDE User Visibility Ontology" ; nao:hasDefaultNamespace "http://nepomuk.kde.org/ontologies/2010/08/18/kuvo#" ; nao:hasDefaultNamespaceAbbreviation "kuvo" ; - nao:lastModified "2010-08-18T12:31:43Z" ; + nao:lastModified "2010-11-29T12:31:43Z" ; nao:serializationLanguage "TriG" ; nao:status "Unstable" ; nrl:updatable "0" ; - nao:version "1" . + nao:version "2" . } diff --git a/nepomuk/removed-services/CMakeLists.txt b/nepomuk/removed-services/CMakeLists.txt index fba5e77..3ff8863 100644 --- a/nepomuk/removed-services/CMakeLists.txt +++ b/nepomuk/removed-services/CMakeLists.txt @@ -4,5 +4,6 @@ install( FILES nepomukactivitiesservice.desktop nepomukontologyloader.desktop + nepomukremovablestorageservice.desktop DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/nepomuk/removed-services/nepomukremovablestorageservice.desktop b/nepomuk/removed-services/nepomukremovablestorageservice.desktop new file mode 100644 index 0000000..e1e3e17 --- /dev/null +++ b/nepomuk/removed-services/nepomukremovablestorageservice.desktop @@ -0,0 +1,2 @@ +[Desktop Entry] +Hidden=true diff --git a/nepomuk/server/CMakeLists.txt b/nepomuk/server/CMakeLists.txt index c468c0e..e0a23ad 100644 --- a/nepomuk/server/CMakeLists.txt +++ b/nepomuk/server/CMakeLists.txt @@ -38,13 +38,6 @@ qt4_add_dbus_interface(nepomukserver_SRCS kde4_add_kdeinit_executable(nepomukserver ${nepomukserver_SRCS}) -if (Q_WS_MAC) - set_target_properties(nepomukserver PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/Info.plist.template) - set_target_properties(nepomukserver PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.nepomukserver") - set_target_properties(nepomukserver PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Nepomuk Server") -endif (Q_WS_MAC) - - target_link_libraries(kdeinit_nepomukserver ${KDE4_KDEUI_LIBS} ${SOPRANO_LIBRARIES} diff --git a/nepomuk/server/nepomukserver.desktop b/nepomuk/server/nepomukserver.desktop index 5f8ffc2..4e69092 100644 --- a/nepomuk/server/nepomukserver.desktop +++ b/nepomuk/server/nepomukserver.desktop @@ -12,6 +12,7 @@ Name[be@latin]=Server „Nepomuk” Name[bg]=Сървър Nepomuk Name[bn]=নেপোমুক সার্ভার Name[bn_IN]=Nepomuk সার্ভার +Name[bs]=Server Nepomuka Name[ca]=Servidor Nepomuk Name[ca@valencia]=Servidor Nepomuk Name[cs]=Server Nepomuku @@ -77,6 +78,7 @@ Name[te]=Nepomuk సేవిక Name[tg]=Хидматгоҳи Nepomuk Name[th]=บริการ Nepomuk Name[tr]=Nepomuk Sunucu +Name[ug]=Nepomuk مۇلازىمېتىرى Name[uk]=Сервер Nepomuk Name[uz]=Nepomuk serveri Name[uz@cyrillic]=Nepomuk сервери @@ -89,6 +91,7 @@ Comment[ar]=يوفر خادم نبومك خدمات تخزين ويتحكم بس Comment[ast]=Sirvidor de Nepomuk qu'ufre servicios d'atroxamientu y control strigi Comment[be@latin]=Server „Nepomuk”, jaki absłuhoŭvaje schovišča źviestak i kiruje słužbaj „Strigi” Comment[bg]=Сървърът Nepomuk предлага контрол на strigi и съхранение +Comment[bs]=Server Nepomuka pruža servis za skladištenje i kontrolu Strigija Comment[ca]=El servidor Nepomuk proporciona serveis d'emmagatzematge i de control de l'Strigi Comment[ca@valencia]=El servidor Nepomuk proporciona serveis d'emmagatzematge i de control de l'Strigi Comment[cs]=Server Nepomuk poskytuje služby pro ukládání a pro ovládání Strigi @@ -153,6 +156,7 @@ Comment[te]=Nepomuk సేవిక నిల్వ సేవలను మరి Comment[tg]=Хидматгоҳи Nepomuk хидматҳои захира ва идоракунии маълумот дастрас мекунад Comment[th]=บริการ Nepomuk จะให้บริการจัดเก็บข้อมูลและทำการควบคุม strigi Comment[tr]=Nepomuk Sunucusu Depolama servislerini ve Strigi uygulamasının kontrolünü sağlar +Comment[ug]=Nepomuk مۇلازىمېتىر ساقلاش مۇلازىمىتى ۋە strigi تىزگىنلەشنى تەمىنلىدى Comment[uk]=Сервер Nepomuk надає служби збереження і керування strigi Comment[wa]=Li sierveu Nepomuk ki dene des siervices di stocaedje et des contrôles po strigi Comment[x-test]=xxThe Nepomuk Server providing Storage services and strigi controllingxx diff --git a/nepomuk/server/nepomukservice.desktop b/nepomuk/server/nepomukservice.desktop index 9daad12..9d443e8 100644 --- a/nepomuk/server/nepomukservice.desktop +++ b/nepomuk/server/nepomukservice.desktop @@ -9,6 +9,7 @@ Comment[be@latin]=Słužba „Nepomuk” Comment[bg]=Услуга Nepomuk Comment[bn]=নেপোমুক সার্ভিস Comment[bn_IN]=Nepomuk পরিসেবা +Comment[bs]=Servis Nepomuka Comment[ca]=Servei Nepomuk Comment[ca@valencia]=Servei Nepomuk Comment[cs]=Nepomuk služba @@ -73,6 +74,7 @@ Comment[te]=Nepomuk సేవ Comment[tg]=Хидматҳои Nepomuk Comment[th]=บริการ Neomuk Comment[tr]=Nepomuk Servisi +Comment[ug]=Nepomuk مۇلازىمىتى Comment[uk]=Служба Nepomuk Comment[uz]=Nepomuk xizmati Comment[uz@cyrillic]=Nepomuk хизмати diff --git a/nepomuk/server/servicecontroller.cpp b/nepomuk/server/servicecontroller.cpp index e903782..13d83cc 100644 --- a/nepomuk/server/servicecontroller.cpp +++ b/nepomuk/server/servicecontroller.cpp @@ -305,6 +305,9 @@ void Nepomuk::ServiceController::slotServiceUnregistered( const QString& service // on its restart-on-crash feature and have to do it manually. Afterwards it is back // to normal if( serviceName == dbusServiceName( name() ) ) { + + emit serviceStopped( this ); + if( d->attached && d->started ) { kDebug() << "Attached service" << name() << "went down. Restarting ourselves."; start(); diff --git a/nepomuk/server/servicecontroller.h b/nepomuk/server/servicecontroller.h index c48714b..75a6f73 100644 --- a/nepomuk/server/servicecontroller.h +++ b/nepomuk/server/servicecontroller.h @@ -78,6 +78,12 @@ namespace Nepomuk { */ void serviceInitialized( ServiceController* ); + /** + * Emitted once the service has stopped, i.e. + * once its DBus service is gone. + */ + void serviceStopped( ServiceController* ); + private Q_SLOTS: void slotProcessFinished( bool ); void slotServiceRegistered( const QString& serviceName ); diff --git a/nepomuk/server/servicemanager.cpp b/nepomuk/server/servicemanager.cpp index a5becd7..475c5d6 100644 --- a/nepomuk/server/servicemanager.cpp +++ b/nepomuk/server/servicemanager.cpp @@ -170,6 +170,12 @@ public: */ void _k_serviceInitialized( ServiceController* ); + /** + * Slot connected to all ServiceController::serviceStopped + * signals. + */ + void _k_serviceStopped( ServiceController* ); + private: bool m_initialized; ServiceManager* q; @@ -198,6 +204,8 @@ void Nepomuk::ServiceManager::Private::buildServiceMap() ServiceController* sc = new ServiceController( service, q ); connect( sc, SIGNAL(serviceInitialized(ServiceController*)), q, SLOT(_k_serviceInitialized(ServiceController*)) ); + connect( sc, SIGNAL(serviceStopped(ServiceController*)), + q, SLOT(_k_serviceStopped(ServiceController*)) ); services.insert( sc->name(), sc ); } } @@ -226,7 +234,7 @@ void Nepomuk::ServiceManager::Private::startService( ServiceController* sc ) bool needToQueue = false; foreach( const QString &dependency, dependencyTree[sc->name()] ) { ServiceController* depSc = findService( dependency ); - if ( !depSc->isInitialized() ) { + if ( !needToQueue && !depSc->isInitialized() ) { kDebug() << "Queueing" << sc->name() << "due to dependency" << dependency; pendingServices.insert( sc ); needToQueue = true; @@ -247,16 +255,17 @@ void Nepomuk::ServiceManager::Private::startService( ServiceController* sc ) bool Nepomuk::ServiceManager::Private::stopService( ServiceController* sc ) { - // make sure the service is not scheduled to be started later anymore - pendingServices.remove( sc ); + // shut down any service depending of this one first + foreach( const QString &dep, dependencyTree.servicesDependingOn( sc->name() ) ) { + ServiceController* sc = services[dep]; + if( sc->isRunning() ) { + pendingServices.insert( sc ); + stopService( sc ); + } + } // stop it if already running if( sc->isRunning() ) { - // shut down any service depending of this one first - foreach( const QString &dep, dependencyTree.servicesDependingOn( sc->name() ) ) { - stopService( services[dep] ); - } - sc->stop(); return true; } @@ -268,6 +277,8 @@ bool Nepomuk::ServiceManager::Private::stopService( ServiceController* sc ) void Nepomuk::ServiceManager::Private::startPendingServices( ServiceController* newService ) { + kDebug() << newService->name() << pendingServices; + // check the list of pending services and start as many as possible // (we can start services whose dependencies are initialized) QList<ServiceController*> sl = pendingServices.toList(); @@ -284,13 +295,28 @@ void Nepomuk::ServiceManager::Private::startPendingServices( ServiceController* void Nepomuk::ServiceManager::Private::_k_serviceInitialized( ServiceController* sc ) { kDebug() << "Service initialized:" << sc->name(); - if ( !pendingServices.isEmpty() ) { - startPendingServices( sc ); - } + + startPendingServices( sc ); emit q->serviceInitialized( sc->name() ); } +void Nepomuk::ServiceManager::Private::_k_serviceStopped( ServiceController* sc ) +{ + kDebug() << "Service stopped:" << sc->name(); + + // stop and queue all services depending on the stopped one + // this will re-trigger this method until all reverse-deps are stopped + foreach( const QString &dep, dependencyTree.servicesDependingOn( sc->name() ) ) { + ServiceController* depsc = services[dep]; + if( depsc->isRunning() ) { + kDebug() << "Stopping and queuing rev-dep" << depsc->name(); + depsc->stop(); + pendingServices.insert( depsc ); + } + } +} + Nepomuk::ServiceManager::ServiceManager( QObject* parent ) : QObject(parent), @@ -350,6 +376,9 @@ bool Nepomuk::ServiceManager::startService( const QString& name ) bool Nepomuk::ServiceManager::stopService( const QString& name ) { if( ServiceController* sc = d->findService( name ) ) { + // make sure the service is not scheduled to be started later anymore + d->pendingServices.remove( sc ); + return d->stopService( sc ); } return false; diff --git a/nepomuk/server/servicemanager.h b/nepomuk/server/servicemanager.h index 069b57c..b6f359b 100644 --- a/nepomuk/server/servicemanager.h +++ b/nepomuk/server/servicemanager.h @@ -124,6 +124,7 @@ namespace Nepomuk { Private* const d; Q_PRIVATE_SLOT( d, void _k_serviceInitialized(ServiceController*) ) + Q_PRIVATE_SLOT( d, void _k_serviceStopped(ServiceController*) ) }; } diff --git a/nepomuk/services/CMakeLists.txt b/nepomuk/services/CMakeLists.txt index 57d8754..19bf3f8 100644 --- a/nepomuk/services/CMakeLists.txt +++ b/nepomuk/services/CMakeLists.txt @@ -2,5 +2,4 @@ add_subdirectory(storage) add_subdirectory(filewatch) add_subdirectory(queryservice) add_subdirectory(strigi) -add_subdirectory(removablestorage) add_subdirectory(backupsync) diff --git a/nepomuk/services/backupsync/gui/CMakeLists.txt b/nepomuk/services/backupsync/gui/CMakeLists.txt index d95ab8f..f746df0 100644 --- a/nepomuk/services/backupsync/gui/CMakeLists.txt +++ b/nepomuk/services/backupsync/gui/CMakeLists.txt @@ -8,9 +8,10 @@ include_directories( ${NEPOMUK_INCLUDE_DIR} ${libnepomuksync_SOURCE_DIR} ${CMAKE_CURRENT_BUILD_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../service/ ) -set( NepomukBackup_GUI_SRCS +set( NepomukBackup_GUI_SRCS backupwizardpages.cpp backupwizard.cpp mergeconflictdelegate.cpp @@ -19,15 +20,25 @@ set( NepomukBackup_GUI_SRCS identifiermodeltree.cpp identifierwidget.cpp filesystemtree.cpp - ../service/dbusoperators.cpp + + # Restoration code + identifier.cpp + merger.cpp + changelogmerger.cpp + syncfileidentifier.cpp + resourcelog.cpp + + # Code required by restoration + ../service/tools.cpp + ../service/changelog.cpp + ../service/changelogrecord.cpp + ../service/identificationset.cpp + ../service/syncfile.cpp ) #----- DBus interfaces -------- set_source_files_properties( - ../../../interfaces/org.kde.nepomuk.services.nepomukbackupsync.identifier.xml - PROPERTIES INCLUDE "../service/dbusoperators.h") -set_source_files_properties( ../../../interfaces/org.kde.nepomuk.services.nepomukbackupsync.backupmanager.xml PROPERTIES INCLUDE "QtCore/QList") set_source_files_properties( @@ -35,12 +46,6 @@ set_source_files_properties( PROPERTIES INCLUDE "QtCore/QString") qt4_add_dbus_interface( NepomukBackup_GUI_SRCS - ../../../interfaces/org.kde.nepomuk.services.nepomukbackupsync.identifier.xml - identifierinterface ) -qt4_add_dbus_interface( NepomukBackup_GUI_SRCS - ../../../interfaces/org.kde.nepomuk.services.nepomukbackupsync.merger.xml - mergerinterface ) -qt4_add_dbus_interface( NepomukBackup_GUI_SRCS ../../../interfaces/org.kde.nepomuk.services.nepomukbackupsync.syncmanager.xml syncmanagerinterface ) qt4_add_dbus_interface( NepomukBackup_GUI_SRCS @@ -59,6 +64,13 @@ kde4_add_ui_files( NepomukBackup_GUI_SRCS errorpage.ui ) +#--------- Ontologies -------# +soprano_add_ontology(NepomukBackup_GUI_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/../../../ontologies/nrio.trig + "NRIO" + "Nepomuk::Vocabulary" + "trig") + kde4_add_executable(nepomukbackup ${NepomukBackup_GUI_SRCS}) target_link_libraries(nepomukbackup @@ -66,6 +78,7 @@ target_link_libraries(nepomukbackup ${KDE4_KIO_LIBS} ${NEPOMUK_LIBRARIES} ${SOPRANO_LIBRARIES} + nepomuksync ) install(TARGETS nepomukbackup ${INSTALL_TARGETS_DEFAULT_ARGS} ) diff --git a/nepomuk/services/backupsync/gui/backupwizard.cpp b/nepomuk/services/backupsync/gui/backupwizard.cpp index c5600ed..103495d 100644 --- a/nepomuk/services/backupsync/gui/backupwizard.cpp +++ b/nepomuk/services/backupsync/gui/backupwizard.cpp @@ -45,6 +45,18 @@ Nepomuk::BackupWizard::BackupWizard(QWidget* parent, Qt::WindowFlags flags) setPage( Id_RestoreFinalPage, new RestoreFinalPage( this ) ); setPage( Id_ErrorPage, new ErrorPage( this ) ); setStartId( Id_IntroPage ); + + m_identifier = Identifier::instance(); + m_merger = Merger::instance(); + + // IMPORTANT : We've used "Nepomuk::ChangeLog" in the string cause in the slots, signals, and + // connect statement we're using Nepomuk::ChangeLog, NOT ChangeLog + qRegisterMetaType<Nepomuk::ChangeLog>("Nepomuk::ChangeLog"); + + //registerMetaTypes(); + connect( m_identifier, SIGNAL( processed( Nepomuk::ChangeLog ) ), + m_merger, SLOT( process( Nepomuk::ChangeLog ) ) ); + } void Nepomuk::BackupWizard::startBackup() diff --git a/nepomuk/services/backupsync/gui/backupwizard.h b/nepomuk/services/backupsync/gui/backupwizard.h index ab81526..429dc40 100644 --- a/nepomuk/services/backupsync/gui/backupwizard.h +++ b/nepomuk/services/backupsync/gui/backupwizard.h @@ -26,6 +26,9 @@ #include <QtGui/QWizard> +#include "identifier.h" +#include "merger.h" + namespace Nepomuk { class BackupWizard : public QWizard @@ -45,10 +48,14 @@ namespace Nepomuk { Id_RestoreFinalPage, Id_ErrorPage }; - + void startBackup(); void startRestore(); void showError(const QString& error); + + public: + Identifier* m_identifier; + Merger* m_merger; }; } diff --git a/nepomuk/services/backupsync/gui/backupwizardpages.cpp b/nepomuk/services/backupsync/gui/backupwizardpages.cpp index ad7581d..f94de60 100644 --- a/nepomuk/services/backupsync/gui/backupwizardpages.cpp +++ b/nepomuk/services/backupsync/gui/backupwizardpages.cpp @@ -25,7 +25,6 @@ #include "backupwizard.h" #include "identifierwidget.h" -#include "identifierinterface.h" #include <KDebug> #include <KLineEdit> @@ -51,7 +50,7 @@ int Nepomuk::IntroPage::nextId() const return BackupWizard::Id_BackupSettingsPage; else if( m_restore->isChecked() ) return BackupWizard::Id_RestoreSelectionPage; - + return -1; } @@ -185,9 +184,7 @@ Nepomuk::RestorePage::RestorePage(QWidget* parent) m_backupManager = new BackupManager( QLatin1String("org.kde.nepomuk.services.nepomukbackupsync"), "/backupmanager", QDBusConnection::sessionBus(), this); - m_identifier = new Identifier( QLatin1String("org.kde.nepomuk.services.nepomukbackupsync"), - QLatin1String("/identifier"), - QDBusConnection::sessionBus(), this ); + m_identifier = Identifier::instance(); connect( m_identifier, SIGNAL(identificationDone(int,int)), this, SLOT(slotIdentificationDone(int,int)) ); @@ -198,11 +195,12 @@ void Nepomuk::RestorePage::initializePage() { QString backupUrl = field("backupToRestorePath").toString(); kDebug() << "Restoring : " << backupUrl; - - QDBusPendingReply< int > reply = m_backupManager->restore( backupUrl ); - reply.waitForFinished(); - if( !reply.isError() ) - m_id = reply.value(); + + + if( backupUrl.isEmpty() ) + backupUrl = KStandardDirs::locateLocal( "data", "nepomuk/backupsync/backup" ); + + m_id = Identifier::instance()->process( SyncFile(backupUrl) ); if( m_id == -1 ) { //FIXME: This isn't implemented in the service. It's just there so that we have a @@ -229,12 +227,8 @@ int Nepomuk::RestorePage::nextId() const bool Nepomuk::RestorePage::validatePage() { - if( m_identifier->isValid() ) { - m_identifier->completeIdentification( m_id ); - return true; - } - - return false; + m_identifier->completeIdentification( m_id ); + return true; } void Nepomuk::RestorePage::slotIdentificationDone(int id, int unidentified) @@ -287,13 +281,8 @@ Nepomuk::RestoreFinalPage::RestoreFinalPage(QWidget* parent): QWizardPage(parent { setupUi( this ); setCommitPage( true ); - m_merger = new Merger( QLatin1String("org.kde.nepomuk.services.nepomukbackupsync"), - QLatin1String("/merger"), - QDBusConnection::sessionBus(), this ); - - if( m_merger->isValid() ) { - connect( m_merger, SIGNAL(completed(int)), this, SLOT(slotDone(int)) ); - } + m_merger = Merger::instance(); + connect( m_merger, SIGNAL(completed(int)), this, SLOT(slotDone(int)) ); m_progressBar->setMinimum( 0 ); m_progressBar->setMaximum( 100 ); diff --git a/nepomuk/services/backupsync/gui/backupwizardpages.h b/nepomuk/services/backupsync/gui/backupwizardpages.h index 8f3edbb..ab64145 100644 --- a/nepomuk/services/backupsync/gui/backupwizardpages.h +++ b/nepomuk/services/backupsync/gui/backupwizardpages.h @@ -2,7 +2,7 @@ This file is part of the Nepomuk KDE project. Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either @@ -10,12 +10,12 @@ later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. - + 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 Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library. If not, see <http://www.gnu.org/licenses/>. */ @@ -30,8 +30,8 @@ #include <QtGui/QListWidget> #include "backupmanagerinterface.h" -#include "mergerinterface.h" -#include "identifierinterface.h" +#include "merger.h" +#include "identifier.h" #include "ui_intropage.h" #include "ui_backuppage.h" @@ -43,11 +43,9 @@ namespace Nepomuk { class BackupWizard; - + typedef org::kde::nepomuk::services::nepomukbackupsync::BackupManager BackupManager; - typedef org::kde::nepomuk::services::nepomukbackupsync::Merger Merger; - typedef org::kde::nepomuk::services::nepomukbackupsync::Identifier Identifier; - + class IntroPage : public QWizardPage, public Ui::IntroPage { Q_OBJECT @@ -80,7 +78,7 @@ namespace Nepomuk { bool isComplete() const; int nextId() const; - private: + private: BackupManager* m_backupManager; bool m_backupDone; @@ -104,13 +102,13 @@ namespace Nepomuk { private slots: void slotSelectionChanged(); void slotCustomBackupUrl(); - + private: QString m_backupFilePath; }; - + class IdentifierWidget; - + class RestorePage : public QWizardPage { Q_OBJECT @@ -123,7 +121,7 @@ namespace Nepomuk { private slots: void slotIdentificationDone( int id, int unidentified ); - + private: IdentifierWidget* m_identifierWidget; Identifier * m_identifier; diff --git a/nepomuk/services/backupsync/gui/changelogmerger.cpp b/nepomuk/services/backupsync/gui/changelogmerger.cpp new file mode 100644 index 0000000..f670d70 --- /dev/null +++ b/nepomuk/services/backupsync/gui/changelogmerger.cpp @@ -0,0 +1,345 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + + + +#include "changelogmerger.h" +#include "nrio.h" + +#include <algorithm> + +#include <QtCore/QMultiHash> +#include <QtCore/QHashIterator> +#include <QtCore/QThread> + +#include <Soprano/Vocabulary/NAO> +#include <Soprano/Vocabulary/NRL> +#include <Soprano/Vocabulary/RDF> +#include <Nepomuk/Vocabulary/NIE> +#include <Nepomuk/Vocabulary/NFO> + +#include <Soprano/Node> +#include <Soprano/Statement> +#include <Soprano/Model> +#include <Soprano/QueryResultIterator> +#include <Soprano/StatementIterator> +#include <Soprano/NodeIterator> + +#include <Nepomuk/Resource> +#include <Nepomuk/Variant> +#include <Nepomuk/Types/Property> +#include <Nepomuk/ResourceManager> + +#include <KDebug> + +int Nepomuk::ChangeLogMerger::NextId = 0; + +Nepomuk::ChangeLogMerger::ChangeLogMerger(Nepomuk::ChangeLog log) + : ResourceMerger(), + m_logFile( log ) +{ + m_id = NextId++; +} + +int Nepomuk::ChangeLogMerger::id() +{ + return m_id; +} + +void Nepomuk::ChangeLogMerger::load() +{ + kDebug() << "Loading the ChangeLog..." << m_logFile.size(); + m_hash = ResourceLogMap::fromChangeLog( m_logFile ); + + // The records are stored according to dateTime + Q_ASSERT( m_logFile.toList().isEmpty() == false ); + m_minDateTime = m_logFile.toList().first().dateTime(); +} + +namespace { + + // + // Cache the results. This could have very bad consequences if someone updates the ontology + // when the service is running + // + static QSet<KUrl> nonMergeable; + bool isMergeable( const KUrl & prop, Soprano::Model * model ) { + + if( nonMergeable.contains( prop ) ) + return false; + + QString query = QString::fromLatin1( "ask { %1 %2 \"false\"^^xsd:boolean . }" ) + .arg( Soprano::Node::resourceToN3( prop ) ) + .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NRIO::mergeable() ) ); + + bool isMergeable = !model->executeQuery( query, Soprano::Query::QueryLanguageSparql ).boolValue(); + + if( !isMergeable ) { + nonMergeable.insert( prop ); + return false; + } + return true; + } + + QList<Nepomuk::ChangeLogRecord> getRecords( const Nepomuk::ResourceLogMap & hash, const KUrl resUri, const KUrl & propUri ) { + + Nepomuk::ResourceLogMap::const_iterator it = hash.constFind( resUri ); + if( it == hash.constEnd() ) { + return QList<Nepomuk::ChangeLogRecord>(); + } + + return it->prop.values( propUri ); + } +} + +//TODO: Add completed signal +void Nepomuk::ChangeLogMerger::mergeChangeLog() +{ + m_theGraph = createGraph(); + + kDebug(); + const Types::Property mergeableProperty( Nepomuk::Vocabulary::NRIO::mergeable() ); + + // + // Get own changeLog + // + kDebug() << "minDateTime : " << m_minDateTime; + ChangeLog ownLog;// = LogStorage::instance()->getChangeLog( m_minDateTime ); + kDebug() << "own Log : " << ownLog.size(); + + // Get our and their hash + // ownHash = current local hash from system's ChangeLog + // theirHash = derived from external ChangeLog + ResourceLogMap ownHash = ResourceLogMap::fromChangeLog( ownLog ); + ResourceLogMap & theirHash = m_hash; + + kDebug() << "own Hash : " << ownHash.size(); + kDebug() << "their hash : " << theirHash.size(); + + QHashIterator<KUrl, ResourceLog> it( theirHash ); + while( it.hasNext() ) { + it.next(); + + // Check for resource deletions + if( handleResourceDeletion( it.key() ) ) + continue; + + const KUrl & resUri = it.key(); + const ResourceLog & resLog = it.value(); + + kDebug() << "Resolving " << resUri; + + const QList<KUrl> & properties = resLog.prop.uniqueKeys(); + foreach( const KUrl & propUri, properties ) { + kDebug() << propUri; + + if( !isMergeable( propUri, model() ) ) { + kDebug() << propUri << " is non Mergeable - IGNORING"; + continue; + } + + Nepomuk::Types::Property prop( propUri ); + int cardinality = prop.maxCardinality(); + + QList<ChangeLogRecord> theirRecords = resLog.prop.values( propUri ); + QList<ChangeLogRecord> ownRecords = getRecords( ownHash, resUri, propUri ); + //kDebug() << "own Records : " << ownRecords.size(); + + // This case shouldn't ever happen, but just to be sure + if( theirRecords.empty() ) + continue; + + if( cardinality == 1 ) { + resolveSingleCardinality( theirRecords, ownRecords ); + } + else { + resolveMultipleCardinality( theirRecords, ownRecords ); + } + } + + //if( !rs.propHash.isEmpty() ) + // m_jobs.append( rs ); + } + //theirHash.clear(); + //kDebug() << "Done with merge resolution : " << m_jobs.size(); + + //processJobs(); +} + + +namespace { + + Nepomuk::ChangeLogRecord maxRecord( const QList<Nepomuk::ChangeLogRecord> & records ) { + QList<Nepomuk::ChangeLogRecord>::const_iterator it = std::max_element( records.begin(), records.end() ); + if( it != records.constEnd() ) + return *it; + return Nepomuk::ChangeLogRecord(); + } +} + + +void Nepomuk::ChangeLogMerger::resolveSingleCardinality(const QList< Nepomuk::ChangeLogRecord >& theirRecords, const QList< Nepomuk::ChangeLogRecord >& ownRecords) +{ + kDebug() << "O: " << ownRecords.size() << " " << "T:" << theirRecords.size(); + + //Find max on the basis of time stamp + ChangeLogRecord theirMax = maxRecord( theirRecords ); + ChangeLogRecord ownMax = maxRecord( ownRecords ); + kDebug() << "TheirMax : "<< theirMax.toString(); + kDebug() << "OwnMax " << ownMax.toString(); + + if( theirMax > ownMax ) { + Soprano::Statement statement( theirMax.st().subject(), theirMax.st().predicate(), + Soprano::Node(), Soprano::Node() ); + + if( theirMax.added() ) { + Soprano::Node object = theirMax.st().object(); + kDebug() << "Resolved - Adding " << object; + + if( !model()->containsAnyStatement( statement ) ) { + statement.setObject( object ); + statement.setContext( m_theGraph ); + model()->addStatement( statement ); + } + } + else { + kDebug() << "Resolved - Removing"; + model()->removeAllStatements( statement ); + } + } +} + +namespace { + + struct MergeData { + bool added; + QDateTime dateTime; + + MergeData( bool add, const QDateTime & dt ) + : added( add ), + dateTime( dt ) + {} + }; + + +} + +void Nepomuk::ChangeLogMerger::resolveMultipleCardinality( const QList<Nepomuk::ChangeLogRecord>& theirRecords, const QList<Nepomuk::ChangeLogRecord>& ownRecords) +{ + kDebug() << "MULTIPLE"; + kDebug() << "O: " << ownRecords.size() << " " << "T:" << theirRecords.size(); + + const Soprano::Statement& reference = theirRecords.first().st(); + Soprano::Statement baseStatement( reference.subject(), reference.predicate(), Soprano::Node(), Soprano::Node() ); + + // + // Merge both record lists + // + //TODO: Optimize merging - use merge sort or something equivilant + QList<ChangeLogRecord> records = ownRecords; + records << theirRecords; + qSort( records ); + + QHash<Soprano::Node, MergeData> hash; + foreach( const ChangeLogRecord rec, records ) { + Soprano::Node object = rec.st().object(); + QHash<Soprano::Node, MergeData>::const_iterator it = hash.constFind( object ); + if( it == hash.constEnd() ) { + hash.insert( object, MergeData( rec.added(), rec.dateTime() ) ); + } + else { + // +ve after -ve + if( rec.added() == true && it.value().added == false ) { + hash.remove( object ); + hash.insert( object, MergeData( rec.added(), rec.dateTime() ) ); + } + // -ve after +ve + else if( rec.added() == false && it.value().added == true ) { + hash.remove( object ); + } + // +ve after +ve + // -ve after -ve + // Do nothing + } + } + + // + // Do the actual merging + // + QHashIterator<Soprano::Node, MergeData> it( hash ); + while( it.hasNext() ) { + it.next(); + + Soprano::Statement st( baseStatement ); + st.setObject( it.key() ); + + MergeData data = it.value(); + if( data.added == true ) { + if( !model()->containsAnyStatement( st ) ) { + st.setContext( m_theGraph ); + model()->addStatement( st ); + kDebug() << "adding - " << st; + } + } + else { + kDebug() << "removing " << st; + model()->removeAllStatements( st ); + } + } + + m_multipleMergers.append( Soprano::Statement( baseStatement.subject(), + baseStatement.predicate(), + Soprano::Node() ) ); +} + +QList< Soprano::Statement > Nepomuk::ChangeLogMerger::multipleMergers() const +{ + return m_multipleMergers; +} + +bool Nepomuk::ChangeLogMerger::handleResourceDeletion(const KUrl& resUri) +{ + ResourceLog & log = m_hash[ resUri ]; + const KUrl& rdfTypeProp = Soprano::Vocabulary::RDF::type(); + + QList<ChangeLogRecord> records = log.prop.values( rdfTypeProp ); + if( records.empty() ) + return false; + + // + // Check if rdf:type is being removed + // + bool removed = false; + foreach( const ChangeLogRecord & r, records ) { + if( !r.added() ) { + removed = true; + break; + } + } + if( !removed ) + return false; + + // If removed, remove all records and delete the resource + m_hash.remove( resUri ); + Resource res( resUri ); + res.remove(); + return true; +} diff --git a/nepomuk/services/backupsync/gui/changelogmerger.h b/nepomuk/services/backupsync/gui/changelogmerger.h new file mode 100644 index 0000000..3d1ca42 --- /dev/null +++ b/nepomuk/services/backupsync/gui/changelogmerger.h @@ -0,0 +1,88 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + + + +#ifndef CHANGELOGMERGER_H +#define CHANGELOGMERGER_H + +#include "resourcemerger.h" +#include "syncresource.h" +#include "changelog.h" +#include "resourcelog.h" + +#include <QtCore/QDateTime> + +namespace Nepomuk { + class ChangeLogMerger : public Sync::ResourceMerger + { + public: + ChangeLogMerger( ChangeLog log ); + + int id(); + void mergeChangeLog(); + + /** + * Converts the contents of the LogFile into the MergeHash + * The LogFile can get rather large, so this could be a time consuming + * process. + */ + void load(); + + QList<Soprano::Statement> multipleMergers() const; + + private: + ChangeLog m_logFile; + QDateTime m_minDateTime; + + QList<Sync::SyncResource> m_jobs; + QList<Soprano::Statement> m_multipleMergers; + + /// Contains all the records from the LogFile + ResourceLogMap m_hash; + + static int NextId; + int m_id; + + KUrl m_theGraph; + + /** + * Handles the case when the Resource's metadata has been deleted from + * the LogFile. + * \return true if resUri was deleted, false otherwsie + */ + bool handleResourceDeletion( const KUrl & resUri ); + + /** + * Resolve conflicts for properties with multiple cardinalities. It works by locally + * making all the changes in the SyncResource. + */ + void resolveMultipleCardinality( const QList<ChangeLogRecord >& theirRecords, const QList<ChangeLogRecord >& ownRecords ); + + /** + * Same as multiple Cardinality resolution, but a lot faster. It figures out which statement + * should be present by looking at the max time stamp. + */ + void resolveSingleCardinality( const QList< Nepomuk::ChangeLogRecord >& theirRecords, const QList< Nepomuk::ChangeLogRecord >& ownRecords ); + }; + +} +#endif // CHANGELOGMERGER_H diff --git a/nepomuk/services/backupsync/gui/identifier.cpp b/nepomuk/services/backupsync/gui/identifier.cpp new file mode 100644 index 0000000..b4e9ccf --- /dev/null +++ b/nepomuk/services/backupsync/gui/identifier.cpp @@ -0,0 +1,264 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "changelog.h" +#include "identifier.h" + +#include <QtDBus/QDBusConnection> + +#include <QtCore/QMutexLocker> +#include <QtCore/QHashIterator> +#include <QtCore/QFile> + +#include <Soprano/Model> +#include <Soprano/Graph> +#include <Soprano/QueryResultIterator> +#include <Soprano/Vocabulary/RDF> +#include <Soprano/Serializer> +#include <Soprano/PluginManager> +#include <Soprano/Util/SimpleStatementIterator> + +#include <Nepomuk/ResourceManager> +#include <Nepomuk/Resource> +#include <Nepomuk/Variant> +#include <Nepomuk/Vocabulary/NIE> + +#include <KDebug> + +Nepomuk::Identifier::Identifier(QObject* parent): QThread(parent) +{ + //Register DBus interface + //new IdentifierAdaptor( this ); + //QDBusConnection dbus = QDBusConnection::sessionBus(); + //dbus.registerObject( QLatin1String("/identifier"), this ); + + start(); +} + +Nepomuk::Identifier* Nepomuk::Identifier::instance() +{ + static Identifier ident; + return &ident; +} + +Nepomuk::Identifier::~Identifier() +{ + stop(); + quit(); +} + + +int Nepomuk::Identifier::process(const Nepomuk::SyncFile& sf) +{ + m_queueMutex.lock(); + + SyncFileIdentifier* identifier = new SyncFileIdentifier( sf ); + int id = identifier->id(); + m_queue.enqueue( identifier ); + + m_queueMutex.unlock(); + m_queueWaiter.wakeAll(); + + kDebug() << "Processing ID : " << id; + return id; +} + + +void Nepomuk::Identifier::stop() +{ + m_stopped = true; + m_queueWaiter.wakeAll(); +} + + + +void Nepomuk::Identifier::run() +{ + m_stopped = false; + + while( !m_stopped ) { + + // lock for initial iteration + m_queueMutex.lock(); + + while( !m_queue.isEmpty() ) { + + SyncFileIdentifier* identifier = m_queue.dequeue(); + + // unlock after queue utilization + m_queueMutex.unlock(); + + identifier->load(); + identifyAllWithCompletedSignals( identifier ); + + emit identificationDone( identifier->id(), identifier->unidentified().size() ); + + m_processMutex.lock(); + m_processes[ identifier->id() ] = identifier; + m_processMutex.unlock(); + + // Send the sigals + foreach( const KUrl & uri, identifier->unidentified() ) { + emitNotIdentified( identifier->id(), identifier->statements( uri ).toList() ); + } + + foreach( const KUrl & uri, identifier->mappings().uniqueKeys() ) { + emit identified( identifier->id(), uri.url(), identifier->mappedUri( uri ).url() ); + } + +// if( identifier->allIdentified() ) { +// m_processes.remove( identifier->id() ); +// delete identifier; +// } + + m_queueMutex.lock(); + } + + // wait for more input + kDebug() << "Waiting..."; + m_queueWaiter.wait( &m_queueMutex ); + m_queueMutex.unlock(); + kDebug() << "Woke up."; + } +} + + +bool Nepomuk::Identifier::identify(int id, const QString& oldUriString, const QString& newUriString) +{ + QUrl oldUri( oldUriString ); + QUrl newUri( newUriString ); + + kDebug() << newUri; + // Lock the mutex and all + QMutexLocker lock ( &m_processMutex ); + + QHash<int, SyncFileIdentifier*>::iterator it = m_processes.find( id ); + if( it == m_processes.end() ) + return false; + + SyncFileIdentifier* ip = *it; + + if ( oldUri.scheme() != QLatin1String("nepomuk") ) + return false; + + if( newUri.scheme() == QLatin1String("nepomuk") ) { + ip->forceResource( oldUri, Nepomuk::Resource(newUri) ); + } + else if( newUri.scheme() == "file" ) { + ip->forceResource( oldUri, Nepomuk::Resource(newUri) ); + } + + m_queueMutex.lock(); + m_queue.enqueue( ip ); + m_queueMutex.unlock(); + m_queueWaiter.wakeAll(); + + return true; +} + + +bool Nepomuk::Identifier::ignore(int id, const QString& urlString, bool ignoreSub) +{ + KUrl url( urlString ); + // Lock the mutex and all + QMutexLocker lock ( &m_processMutex ); + + QHash<int, SyncFileIdentifier*>::iterator it = m_processes.find( id ); + if( it == m_processes.end() ) + return false; + + SyncFileIdentifier* identifier = *it; + return identifier->ignore( url, ignoreSub ); +} + +void Nepomuk::Identifier::ignoreAll(int id) +{ + QMutexLocker lock ( &m_processMutex ); + + QHash<int, SyncFileIdentifier*>::iterator it = m_processes.find( id ); + if( it == m_processes.end() ) + return; + + SyncFileIdentifier* identifier = *it; + foreach( const KUrl & url, identifier->unidentified() ) { + identifier->ignore( url, true ); + } +} + +void Nepomuk::Identifier::emitNotIdentified(int id, const QList< Soprano::Statement >& stList) +{ + const Soprano::Serializer* serializer = Soprano::PluginManager::instance()->discoverSerializerForSerialization( Soprano::SerializationNQuads ); + + Soprano::Util::SimpleStatementIterator it( stList ); + QString ser; + QTextStream stream( &ser ); + serializer->serialize( it, stream, Soprano::SerializationNQuads ); + + emit notIdentified( id, ser ); +} + +void Nepomuk::Identifier::test() +{ + kDebug() << "Test!"; +} + +void Nepomuk::Identifier::completeIdentification(int id) +{ + kDebug() << id; + + QMutexLocker lock ( &m_processMutex ); + + QHash<int, SyncFileIdentifier*>::iterator it = m_processes.find( id ); + if( it == m_processes.end() ) + return; + + SyncFileIdentifier* identifier = *it; + m_processes.remove( id ); + + ChangeLog log = identifier->convertedChangeLog(); + kDebug() << "ChangeLog of size " << log.size() << " has been converted"; + if( !log.empty() ) { + kDebug() << "sending ChangeLog of size : " << log.size(); + emit processed( log ); + } + + delete identifier; +} + + +void Nepomuk::Identifier::identifyAllWithCompletedSignals(Nepomuk::SyncFileIdentifier* ident) +{ + int unidentified = ident->unidentified().size(); + float step = 100.0/unidentified; + float progress = 0; + + emit completed( ident->id(), 0 ); + foreach( const KUrl & url, ident->unidentified() ) { + ident->identify( url ); + + progress += step; + emit completed( ident->id(), (int)progress ); + } + emit completed( ident->id(), 100 ); +} + + +#include "identifier.moc" diff --git a/nepomuk/services/backupsync/gui/identifier.h b/nepomuk/services/backupsync/gui/identifier.h new file mode 100644 index 0000000..0cbc0d9 --- /dev/null +++ b/nepomuk/services/backupsync/gui/identifier.h @@ -0,0 +1,98 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef IDENTIFICATIONTHREAD_H +#define IDENTIFICATIONTHREAD_H + +#include <QtCore/QThread> +#include <QtCore/QQueue> +#include <QtCore/QMutex> +#include <QtCore/QWaitCondition> +#include <QtCore/QQueue> +#include <QtCore/QUrl> + +#include "syncfileidentifier.h" + +namespace Soprano { + class Model; +} + +namespace Nepomuk { + + class SyncFile; + class ChangeLog; + + class Identifier : public QThread + { + Q_OBJECT + + Identifier( QObject* parent = 0 ); + virtual ~Identifier(); + + void stop(); + void test(); + + public : + static Identifier* instance(); + + Q_SIGNALS: + void identified( int id, const QString & oldUri, const QString & newUri ); + void notIdentified( int id, const QString & serializedStatements ); + + void identificationDone( int id, int unidentified ); + void processed( const Nepomuk::ChangeLog & logFile ); + + void completed( int id, int progress ); + public Q_SLOTS: + int process( const SyncFile & sf ); + + /** + * Add the (oldUri, newUri) pair to the identifier list + * @param oldUri has to be a nepomuk:/res/ + * @param newUri can be a nepomuk:/res/ or file:/ + */ + bool identify( int id, const QString & oldUri, const QString & newUri ); + + bool ignore(int id, const QString& url, bool ignoreSubDirectories); + + void ignoreAll( int id ); + + void completeIdentification( int id ); + + protected: + virtual void run(); + + private : + QQueue<SyncFileIdentifier *> m_queue; + QMutex m_queueMutex; + QWaitCondition m_queueWaiter; + + bool m_stopped; + + QHash< int, SyncFileIdentifier *> m_processes; + QMutex m_processMutex; + + void emitNotIdentified( int id, const QList<Soprano::Statement> & stList ); + void identifyAllWithCompletedSignals( SyncFileIdentifier * ident ); + }; + +} +#endif // IDENTIFICATIONTHREAD_H diff --git a/nepomuk/services/backupsync/gui/identifierwidget.cpp b/nepomuk/services/backupsync/gui/identifierwidget.cpp index 35ee834..9dbe84b 100644 --- a/nepomuk/services/backupsync/gui/identifierwidget.cpp +++ b/nepomuk/services/backupsync/gui/identifierwidget.cpp @@ -23,8 +23,6 @@ #include "identifierwidget.h" #include "identifiermodel.h" #include "identifiermodeltree.h" -#include "identifierinterface.h" -#include "../service/dbusoperators.h" #include <QtGui/QLabel> #include <QtGui/QVBoxLayout> @@ -39,27 +37,23 @@ Nepomuk::IdentifierWidget::IdentifierWidget(int id, QWidget* parent): QWidget(parent), m_id(id) { - registerMetaTypes(); + //registerMetaTypes(); setupUi(this); - + m_model = new IdentifierModel( this ); MergeConflictDelegate * delegate = new MergeConflictDelegate( m_viewConflicts, this ); m_viewConflicts->setModel( m_model ); m_viewConflicts->setItemDelegate( delegate ); - m_identifier = new Identifier( QLatin1String("org.kde.nepomuk.services.nepomukbackupsync"), - QLatin1String("/identifier"), - QDBusConnection::sessionBus(), this ); - - if( m_identifier->isValid() ) { - connect( m_identifier, SIGNAL(notIdentified(int,QString)), - this, SLOT(notIdentified(int,QString)) ); - connect( m_identifier, SIGNAL(identified(int,QString,QString)), - this, SLOT(identified(int,QString,QString)) ); - connect( m_identifier, SIGNAL(completed(int,int)), - this, SLOT(completed(int,int)) ); - } + m_identifier = Identifier::instance(); + + connect( m_identifier, SIGNAL(notIdentified(int,QString)), + this, SLOT(notIdentified(int,QString)) ); + connect( m_identifier, SIGNAL(identified(int,QString,QString)), + this, SLOT(identified(int,QString,QString)) ); + connect( m_identifier, SIGNAL(completed(int,int)), + this, SLOT(completed(int,int)) ); connect( delegate, SIGNAL(requestResourceResolve(QUrl)), this, SLOT(identify()) ); @@ -73,14 +67,14 @@ Nepomuk::IdentifierWidget::IdentifierWidget(int id, QWidget* parent): QWidget(pa void Nepomuk::IdentifierWidget::ignore(const QUrl& uri) { Q_UNUSED( uri ); - + QModelIndex index = m_viewConflicts->currentIndex(); if( !index.isValid() ) return; IdentifierModelTreeItem * item = static_cast<IdentifierModelTreeItem*>( index.internalPointer() ); item->setDiscarded( true ); - + m_identifier->ignore( m_id, item->resourceUri().toString(), true); } @@ -107,7 +101,7 @@ void Nepomuk::IdentifierWidget::notIdentified(int id, const QString& string) { if( id != m_id ) return; - + kDebug() << string; const Soprano::Parser* parser = Soprano::PluginManager::instance()->discoverParserForSerialization( Soprano::SerializationNQuads ); diff --git a/nepomuk/services/backupsync/gui/identifierwidget.h b/nepomuk/services/backupsync/gui/identifierwidget.h index e737ffc..bcb5b27 100644 --- a/nepomuk/services/backupsync/gui/identifierwidget.h +++ b/nepomuk/services/backupsync/gui/identifierwidget.h @@ -28,10 +28,9 @@ #include <QPushButton> #include "mergeconflictdelegate.h" +#include "identifier.h" #include "ui_mergeconflictwidget.h" -class OrgKdeNepomukServicesNepomukbackupsyncIdentifierInterface; - namespace Nepomuk { class IdentifierModel; @@ -45,18 +44,17 @@ namespace Nepomuk { private slots: void identify( ); void ignore( const QUrl & uri ); - + private slots: void notIdentified( int id, const QString & string ); void identified( int id, const QString & oldUri, const QString & newUri ); void completed( int id, int progress ); void slotDiscardAll(); - + private : IdentifierModel * m_model; int m_id; - typedef OrgKdeNepomukServicesNepomukbackupsyncIdentifierInterface Identifier; Identifier * m_identifier; }; diff --git a/nepomuk/services/backupsync/gui/merger.cpp b/nepomuk/services/backupsync/gui/merger.cpp new file mode 100644 index 0000000..8caea89 --- /dev/null +++ b/nepomuk/services/backupsync/gui/merger.cpp @@ -0,0 +1,135 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "merger.h" + +#include "changelog.h" +#include "syncfile.h" + +#include <QtDBus/QDBusConnection> + +#include <KDebug> + +Nepomuk::Merger::Merger( QObject* parent ) + : QThread( parent ) +{ + //Register DBus interface + //new MergerAdaptor( this ); + //QDBusConnection dbus = QDBusConnection::sessionBus(); + //dbus.registerObject( QLatin1String("/merger"), this ); + + start(); +} + +Nepomuk::Merger* Nepomuk::Merger::instance() +{ + static Merger m; + return &m; +} + + +Nepomuk::Merger::~Merger() +{ + stop(); + wait(); +} + + +void Nepomuk::Merger::stop() +{ + m_stopped = true; + m_queueWaiter.wakeAll(); +} + + +int Nepomuk::Merger::process(const Nepomuk::ChangeLog& changeLog ) +{ + kDebug(); + m_queueMutex.lock(); + + kDebug() << "Received ChangeLog -- " << changeLog.size(); + ChangeLogMerger * request = new ChangeLogMerger( changeLog ); + m_queue.enqueue( request ); + + m_queueMutex.unlock(); + m_queueWaiter.wakeAll(); + + return request->id(); +} + + +void Nepomuk::Merger::run() +{ + m_stopped = false; + + while( !m_stopped ) { + + // lock for initial iteration + m_queueMutex.lock(); + + while( !m_queue.isEmpty() ) { + + ChangeLogMerger* request = m_queue.dequeue(); + //kDebug() << "Processing request #" << request->id() << " with size " << request->size(); + + // unlock after queue utilization + m_queueMutex.unlock(); + + //FIXME: Fake completed signals! + emit completed( 5 ); + request->load(); + emit completed( 55 ); + request->mergeChangeLog(); + emit completed( 100 ); + + /* + if( !request->done() ) { + QMutexLocker lock( &m_processMutex ); + m_processes[ request->id() ] = request; + } + + disconnect( request, SIGNAL(completed(int)), + this, SIGNAL(completed(int)) ); + disconnect( request, SIGNAL(multipleMerge(QString,QString)), + this, SIGNAL(multipleMerge(QString,QString)) ); + + if( request->done() ){*/ + + foreach( const Soprano::Statement & st, request->multipleMergers() ) { + emit multipleMerge( st.subject().uri().toString(), + st.predicate().uri().toString() ); + } + + m_processes.remove( request->id() ); + delete request; + + m_queueMutex.lock(); + } + + kDebug() << "Waiting..."; + m_queueWaiter.wait( &m_queueMutex ); + m_queueMutex.unlock(); + kDebug() << "Woke up."; + } +} + + +#include "merger.moc" diff --git a/nepomuk/services/backupsync/gui/merger.h b/nepomuk/services/backupsync/gui/merger.h new file mode 100644 index 0000000..74efcf7 --- /dev/null +++ b/nepomuk/services/backupsync/gui/merger.h @@ -0,0 +1,76 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MERGETHREAD_H +#define MERGETHREAD_H + +#include <QtCore/QThread> +#include <QtCore/QMutex> +#include <QtCore/QWaitCondition> +#include <QtCore/QUrl> +#include <QtCore/QQueue> + +#include "changelogmerger.h" + +namespace Soprano { + class Model; +} + +namespace Nepomuk { + + class ResourceManager; + class ChangeLog; + + class Merger : public QThread + { + Q_OBJECT + + Merger( QObject* parent = 0 ); + virtual ~Merger(); + + void stop(); + + public : + static Merger* instance(); + + public Q_SLOTS: + int process( const Nepomuk::ChangeLog & logFile ); + + Q_SIGNALS: + void completed( int percent ); + void multipleMerge( const QString & uri, const QString & prop ); + + protected: + virtual void run(); + + private: + QQueue<ChangeLogMerger*> m_queue; + QMutex m_queueMutex; + QWaitCondition m_queueWaiter; + + bool m_stopped; + + QHash<int, ChangeLogMerger*> m_processes; + QMutex m_processMutex; + }; + +} +#endif // MERGETHREAD_H diff --git a/nepomuk/services/backupsync/gui/nepomukbackup.desktop b/nepomuk/services/backupsync/gui/nepomukbackup.desktop index ab1e794..cf54d99 100644 --- a/nepomuk/services/backupsync/gui/nepomukbackup.desktop +++ b/nepomuk/services/backupsync/gui/nepomukbackup.desktop @@ -1,6 +1,7 @@ [Desktop Entry] Name=Nepomuk Backup Name[bg]=Модул за архивиране Nepomuk +Name[bs]=Rezerva Nepomuka Name[ca]=Còpia de seguretat del Nepomuk Name[ca@valencia]=Còpia de seguretat del Nepomuk Name[cs]=Záloha Nepomuku @@ -18,6 +19,7 @@ Name[hi]=नेपोमक बेकप Name[hr]=Nepomukova sigurnosna kopija Name[hu]=Nepomuk mentés Name[ia]=Retrocopia de Nepomuk +Name[is]=Nepomuk öryggisafritun Name[it]=Copia di sicurezza di Nepomuk Name[ja]=Nepomuk バックアップ Name[kk]=Nepomuk сақтық көшірмелеу @@ -25,10 +27,11 @@ Name[km]=ការបម្រុងទុករបស់ Nepomuk Name[kn]=ನೆಪೋಮುಕ್ ಬ್ಯಾಕ್ಅಪ್ Name[ko]=Nepomuk 백업 Name[lt]=Nepomuk atsarginė kopija -Name[mai]=नेपोमक बेकप +Name[lv]=Nepomuk rezerves kopiju veidošana Name[nb]=Nepomuk sikringskopi Name[nds]=Nepomuk-Sekerheitkopie Name[nl]=Nepomuk reservekopie maken +Name[nn]=Reservekopi av Nepomuk Name[pa]=ਨਿਪੋਮੁਕ ਬੈਕਅੱਪ Name[pl]=Kopia zapasowa Nepomuka Name[pt]=Salvaguarda do Nepomuk @@ -44,8 +47,10 @@ Name[sr@latin]=Rezerva Nepomuka Name[sv]=Nepomuk säkerhetskopiering Name[th]=สำรองข้อมูลบริการ Neomuk Name[tr]=Nepomuk Yedekleme +Name[ug]=Nepomuk زاپاسلاش Name[uk]=Резервні копії даних Nepomuk Name[x-test]=xxNepomuk Backupxx +Name[zh_CN]=Nepomuk 备份 Name[zh_TW]=Nepomuk 備份 Exec=nepomukbackup Icon=nepomuk diff --git a/nepomuk/services/backupsync/gui/resourcelog.cpp b/nepomuk/services/backupsync/gui/resourcelog.cpp new file mode 100644 index 0000000..a24d3f0 --- /dev/null +++ b/nepomuk/services/backupsync/gui/resourcelog.cpp @@ -0,0 +1,143 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "resourcelog.h" +#include "changelog.h" +#include "changelogrecord.h" + +#include <QtCore/QMultiHash> + +#include <Nepomuk/Types/Property> +#include <algorithm> + + +Nepomuk::ResourceLogMap Nepomuk::ResourceLogMap::fromChangeLogRecordList(const QList<ChangeLogRecord>& records) +{ + ResourceLogMap hash; + + // + // Organize according to resource uri + // + QMultiHash<KUrl, ChangeLogRecord> multiHash; + foreach( const ChangeLogRecord &r, records ) { + multiHash.insert( r.st().subject().uri(), r ); + } + + // + // Convert to MergeHash + // + const QList<KUrl> & keys = multiHash.uniqueKeys(); + foreach( const KUrl & key, keys ) { + ResourceLog ms; + ms.uri = key; + + const QList<ChangeLogRecord>& records = multiHash.values( key ); + foreach( const ChangeLogRecord & r, records ) { + const KUrl &pred = r.st().predicate().uri(); + ms.prop.insert( pred, r ); + } + hash.insert( ms.uri, ms ); + } + + return hash; +} + + +Nepomuk::ResourceLogMap Nepomuk::ResourceLogMap::fromChangeLog(const Nepomuk::ChangeLog& log) +{ + return fromChangeLogRecordList( log.toList() ); +} + + +namespace { + + Nepomuk::ChangeLogRecord maxRecord( const QList<Nepomuk::ChangeLogRecord> & records ) { + QList<Nepomuk::ChangeLogRecord>::const_iterator it = std::max_element( records.begin(), records.end() ); + if( it != records.constEnd() ) + return *it; + return Nepomuk::ChangeLogRecord(); + } + + typedef QHash<Soprano::Node, Nepomuk::ChangeLogRecord> OptimizeHash; + +} + + +void Nepomuk::ResourceLogMap::optimize() +{ + QMutableHashIterator<KUrl, ResourceLog> it( *this ); + while( it.hasNext() ) { + it.next(); + + ResourceLog & log = it.value(); + + const QList<KUrl> & properties = log.prop.uniqueKeys(); + foreach( const KUrl & propUri, properties ) { + QList<ChangeLogRecord> records = log.prop.values( propUri ); + + Types::Property property( propUri ); + int maxCard = property.maxCardinality(); + + if( maxCard == 1 ) { + ChangeLogRecord max = maxRecord( records ); + records.clear(); + records.append( max ); + } + else { + // The records have to be sorted by timeStamp in order to optimize them + qSort( records ); + + OptimizeHash hash; + foreach( const ChangeLogRecord & record, records ) { + if( record.added() ) { + OptimizeHash::const_iterator iter = hash.constFind( record.st().object() ); + if( iter != hash.constEnd() ){ + if( !iter.value().added() ) { + hash.remove( record.st().object() ); + } + } + else { + hash.insert( record.st().object(), record ); + } + } + else { + OptimizeHash::const_iterator iter = hash.constFind( record.st().object() ); + if( iter != hash.constEnd() ) { + if( iter.value().added() ) { + hash.remove( record.st().object() ); + } + } + else + hash.insert( record.st().object(), record ); + } + } + records = hash.values(); + qSort( records ); + + // Update the log + log.prop.remove( propUri ); + foreach( const ChangeLogRecord & record, records ) + log.prop.insert( propUri, record ); + } + } + + } +} diff --git a/nepomuk/services/backupsync/gui/resourcelog.h b/nepomuk/services/backupsync/gui/resourcelog.h new file mode 100644 index 0000000..fde6dc5 --- /dev/null +++ b/nepomuk/services/backupsync/gui/resourcelog.h @@ -0,0 +1,53 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#ifndef RESOURCELOG_H +#define RESOURCELOG_H + +#include <QtCore/QUrl> +#include <QtCore/QMultiHash> + +#include <KUrl> + +#include "changelog.h" +#include "changelogrecord.h" + +namespace Nepomuk { + + + class ResourceLog + { + public: + KUrl uri; + QMultiHash<KUrl, ChangeLogRecord> prop; + }; + + class ResourceLogMap : public QHash<KUrl, ResourceLog> { + public: + static ResourceLogMap fromChangeLogRecordList( const QList<ChangeLogRecord> & records ); + static ResourceLogMap fromChangeLog( const Nepomuk::ChangeLog& log ); + + void optimize(); + }; + +} +#endif // RESOURCELOG_H diff --git a/nepomuk/services/backupsync/gui/syncfileidentifier.cpp b/nepomuk/services/backupsync/gui/syncfileidentifier.cpp new file mode 100644 index 0000000..1eb1db5 --- /dev/null +++ b/nepomuk/services/backupsync/gui/syncfileidentifier.cpp @@ -0,0 +1,205 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "syncfileidentifier.h" +#include "changelogrecord.h" +#include "syncresource.h" + +#include <QtCore/QFile> +#include <QtCore/QDir> + +#include <KDebug> + +#include <Nepomuk/Resource> +#include <Nepomuk/Variant> +#include <Nepomuk/Vocabulary/NIE> + +int Nepomuk::SyncFileIdentifier::NextId = 0; + +Nepomuk::SyncFileIdentifier::SyncFileIdentifier(const Nepomuk::SyncFile& sf) + : ResourceIdentifier() +{ + m_id = NextId++; + + m_changeLog = sf.changeLog(); + m_identificationSet = sf.identificationSet(); + + //kDebug() << "Identification Set - "; + //kDebug() << m_identificationSet.toList(); +} + +Nepomuk::SyncFileIdentifier::~SyncFileIdentifier() +{ + // What do we do over here? +} + +namespace { + + // + // Removes the old home directory and replaces it with the current one + // TODO: Make it OS independent + // + QUrl translateHomeUri( const QUrl & uri ) { + QString uriString = uri.toString(); + + QRegExp regEx("^file://(/home/[^/]*)(/.*)$"); + if( regEx.exactMatch( uriString ) ) { + QString newUriString = "file://" + QDir::homePath() + regEx.cap(2); + + uriString.replace( regEx, newUriString ); + return QUrl( newUriString ); + } + return uri; + } +} + +void Nepomuk::SyncFileIdentifier::load() +{ + if( unidentified().size() > 0 ) + return; + + // + // Translate all file:// uri so that the home dir is correct. + // + QList<Soprano::Statement> identList = m_identificationSet.toList(); + QMutableListIterator<Soprano::Statement> it( identList ); + while( it.hasNext() ) { + it.next(); + + Soprano::Statement & st = it.value(); + if( st.object().isResource() && st.object().uri().scheme() == QLatin1String("file") ) + st.setObject( Soprano::Node( translateHomeUri( st.object().uri() ) ) ); + } + + //kDebug() << "After translation : "; + //kDebug() << identList; + + addStatements( identList ); +} + + +Nepomuk::ChangeLog Nepomuk::SyncFileIdentifier::convertedChangeLog() +{ + QList<ChangeLogRecord> masterLogRecords = m_changeLog.toList(); + kDebug() << "masterLogRecords : " << masterLogRecords.size(); + + QList<ChangeLogRecord> identifiedRecords; + QMutableListIterator<ChangeLogRecord> it( masterLogRecords ); + + while( it.hasNext() ) { + ChangeLogRecord r = it.next(); + + // Identify Subject + KUrl subUri = r.st().subject().uri(); + if( subUri.scheme() == QLatin1String("nepomuk") ) { + KUrl newUri = mappedUri( subUri ); + if( newUri.isEmpty() ) + continue; + + r.setSubject( newUri ); + } + + // Identify object + if( r.st().object().isResource() ) { + KUrl objUri = r.st().object().uri(); + if( objUri.scheme() == QLatin1String("nepomuk") ) { + KUrl newUri = mappedUri( objUri ); + if( newUri.isEmpty() ) + continue; + + r.setObject( newUri ); + } + } + + identifiedRecords.push_back( r ); + + // Remove the statement from the masterchangerecords + it.remove(); + } + + // Update the master change log + m_changeLog = ChangeLog::fromList( masterLogRecords ); + + return ChangeLog::fromList( identifiedRecords ); +} + +void Nepomuk::SyncFileIdentifier::identifyAll() +{ + Nepomuk::Sync::ResourceIdentifier::identifyAll(); +} + +int Nepomuk::SyncFileIdentifier::id() +{ + return m_id; +} + + +Nepomuk::Resource Nepomuk::SyncFileIdentifier::createNewResource(const Sync::SyncResource & simpleRes) const +{ + kDebug(); + Nepomuk::Resource res; + + if( simpleRes.isFileDataObject() ) { + res = Nepomuk::Resource( simpleRes.nieUrl() ); + if( res.exists() ) { + // If the resource already exists. We should not create it. This is to avoid the bug where + // a different file with the same nie:url exists. If it was the same file, identification + // should have found it. If it hasn't, well tough luck. No other option but to manually + // identify + return Resource(); + } + } + + const QList<KUrl> & keys = simpleRes.uniqueKeys(); + foreach( const KUrl & prop, keys ) { + //kDebug() << "Prop " << prop; + + const QList<Soprano::Node> nodeList = simpleRes.values( prop ); + res.setProperty( prop, Nepomuk::Variant::fromNodeList( nodeList ) ); + } + return res.resourceUri(); +} + +bool Nepomuk::SyncFileIdentifier::runIdentification(const KUrl& uri) +{ + if( Nepomuk::Sync::ResourceIdentifier::runIdentification(uri) ) + return true; + + const Sync::SyncResource res = simpleResource( uri ); + + // Add the resource if ( it is NOT a FileDataObject ) or ( if is a FileDataObject and + // exists in the filesystem at the nie:url ) + bool shouldAdd = !res.isFileDataObject(); + if( res.isFileDataObject() ) { + if( QFile::exists( res.nieUrl().toLocalFile() ) ) + shouldAdd = true; + } + + if( shouldAdd ) { + Nepomuk::Resource newRes = createNewResource( res ); + if( newRes.isValid() ) { + forceResource( uri, newRes ); + return true; + } + } + + return false; +} diff --git a/nepomuk/services/backupsync/gui/syncfileidentifier.h b/nepomuk/services/backupsync/gui/syncfileidentifier.h new file mode 100644 index 0000000..3c1f918 --- /dev/null +++ b/nepomuk/services/backupsync/gui/syncfileidentifier.h @@ -0,0 +1,60 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#ifndef SYNCFILEIDENTIFIER_H +#define SYNCFILEIDENTIFIER_H + +#include "resourceidentifier.h" +#include "changelog.h" +#include "identificationset.h" +#include "syncfile.h" + +namespace Nepomuk { + + class SyncFileIdentifier : public Sync::ResourceIdentifier + { + public: + SyncFileIdentifier( const SyncFile & sf ); + virtual ~SyncFileIdentifier(); + + virtual void identifyAll(); + int id(); + + ChangeLog convertedChangeLog(); + void load(); + + protected: + virtual bool runIdentification(const KUrl& uri); + + private: + ChangeLog m_changeLog; + IdentificationSet m_identificationSet; + + static int NextId; + int m_id; + + Resource createNewResource(const Nepomuk::Sync::SyncResource& simpleRes) const; + + }; +} + +#endif // SYNCFILEIDENTIFIER_H diff --git a/nepomuk/services/backupsync/lib/CMakeLists.txt b/nepomuk/services/backupsync/lib/CMakeLists.txt index 7e1bdf7..2d69956 100644 --- a/nepomuk/services/backupsync/lib/CMakeLists.txt +++ b/nepomuk/services/backupsync/lib/CMakeLists.txt @@ -20,14 +20,13 @@ add_definitions(-DDISABLE_NEPOMUK_LEGACY=1) set(nepomuksync_SRCS resourcemerger.cpp resourceidentifier.cpp - resourceidentifier_p.cpp identificationsetgenerator.cpp - simpleresource.cpp + syncresource.cpp ) soprano_add_ontology(nepomuksync_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../../../ontologies/nrio.trig - "backupsync" + "NRIO" "Nepomuk::Vocabulary" "trig") @@ -45,11 +44,11 @@ target_link_libraries(nepomuksync ) install(TARGETS nepomuksync EXPORT kdelibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) - -#install(FILES -# resourceidentifier.h -# resourcemerger.h -# simpleresource.h -# nepomuksync_export.h -# DESTINATION ${INCLUDE_INSTALL_DIR}/nepomuk COMPONENT Devel -#) +# +# install(FILES +# resourceidentifier.h +# resourcemerger.h +# syncresource.h +# nepomuksync_export.h +# DESTINATION ${INCLUDE_INSTALL_DIR}/nepomuk COMPONENT Devel +# ) diff --git a/nepomuk/services/backupsync/lib/identificationsetgenerator.cpp b/nepomuk/services/backupsync/lib/identificationsetgenerator.cpp index d5dd960..ded7478 100644 --- a/nepomuk/services/backupsync/lib/identificationsetgenerator.cpp +++ b/nepomuk/services/backupsync/lib/identificationsetgenerator.cpp @@ -20,6 +20,7 @@ */ #include "identificationsetgenerator_p.h" +#include "nrio.h" #include <Soprano/Statement> #include <Soprano/Node> @@ -27,7 +28,6 @@ #include <Soprano/QueryResultIterator> #include <Soprano/Vocabulary/RDF> #include <Soprano/Vocabulary/RDFS> -#include "backupsync.h" #include <Nepomuk/ResourceManager> @@ -46,7 +46,7 @@ Soprano::QueryResultIterator Nepomuk::Sync::IdentificationSetGenerator::performQ "UNION { ?p %1 %3. } " "FILTER( ?r in ( %4 ) ) . } ") .arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()), - Soprano::Node::resourceToN3(Nepomuk::Vocabulary::backupsync::identifyingProperty()), + Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NRIO::identifyingProperty()), Soprano::Node::resourceToN3(Soprano::Vocabulary::RDF::type()), uris.join(", ")); diff --git a/nepomuk/services/backupsync/lib/nepomuksync_export.h b/nepomuk/services/backupsync/lib/nepomuksync_export.h index 54e6f4c..efe157c 100644 --- a/nepomuk/services/backupsync/lib/nepomuksync_export.h +++ b/nepomuk/services/backupsync/lib/nepomuksync_export.h @@ -40,4 +40,4 @@ # define NEPOMUKSYNC_EXPORT_DEPRECATED KDE_DEPRECATED NEPOMUKSYNC_EXPORT # endif -#endif \ No newline at end of file +#endif diff --git a/nepomuk/services/backupsync/lib/resourceidentifier.cpp b/nepomuk/services/backupsync/lib/resourceidentifier.cpp index 51b05db..ee02470 100644 --- a/nepomuk/services/backupsync/lib/resourceidentifier.cpp +++ b/nepomuk/services/backupsync/lib/resourceidentifier.cpp @@ -22,8 +22,9 @@ #include "resourceidentifier.h" #include "resourceidentifier_p.h" -#include "simpleresource.h" +#include "syncresource.h" #include "identificationsetgenerator_p.h" +#include "nrio.h" #include <QtCore/QSet> @@ -36,23 +37,30 @@ #include <Soprano/Vocabulary/RDF> #include <Soprano/Vocabulary/RDFS> +#include <Soprano/Vocabulary/NAO> #include <Nepomuk/Vocabulary/NIE> -#include "backupsync.h" #include <Nepomuk/Resource> #include <Nepomuk/ResourceManager> #include <Nepomuk/Variant> -#include <Nepomuk/Types/Property> #include <KDebug> #include <KUrl> -Nepomuk::Sync::ResourceIdentifier::ResourceIdentifier(Nepomuk::ResourceManager* rm) +using namespace Nepomuk::Vocabulary; +using namespace Soprano::Vocabulary; + +Nepomuk::Sync::ResourceIdentifier::ResourceIdentifier(Soprano::Model * model) : d( new Nepomuk::Sync::ResourceIdentifier::Private(this) ) { - d->init( rm ); + d->m_model = model ? model : ResourceManager::instance()->mainModel(); } +Nepomuk::Sync::ResourceIdentifier::Private::Private( ResourceIdentifier * parent ) + : q( parent ), + m_model(0) +{ +} Nepomuk::Sync::ResourceIdentifier::~ResourceIdentifier() { @@ -62,20 +70,21 @@ Nepomuk::Sync::ResourceIdentifier::~ResourceIdentifier() void Nepomuk::Sync::ResourceIdentifier::addStatement(const Soprano::Statement& st) { - QHash<KUrl, SimpleResource>::iterator it = d->m_resourceHash.find( st.subject().uri() ); + SyncResource res; + res.setUri( st.subject() ); + + QHash<KUrl, SyncResource>::iterator it = d->m_resourceHash.find( res.uri() ); if( it != d->m_resourceHash.end() ) { - SimpleResource & res = it.value(); + SyncResource & res = it.value(); res.insert( st.predicate().uri(), st.object() ); return; } - // Doesn't exist. Create it - SimpleResource res; - res.setUri( st.subject().uri() ); + // Doesn't exist - Create it and insert it into the resourceHash res.insert( st.predicate().uri(), st.object() ); - d->m_resourceHash.insert( st.subject().uri(), res ); - d->m_notIdentified.insert( st.subject().uri() ); + d->m_resourceHash.insert( res.uri(), res ); + d->m_notIdentified.insert( res.uri() ); } void Nepomuk::Sync::ResourceIdentifier::addStatements(const Soprano::Graph& graph) @@ -84,7 +93,7 @@ void Nepomuk::Sync::ResourceIdentifier::addStatements(const Soprano::Graph& grap KUrl::List uniqueKeys = resHash.uniqueKeys(); foreach( const KUrl & resUri, uniqueKeys ) { - QHash<KUrl, SimpleResource>::iterator it = d->m_resourceHash.find( resUri ); + QHash<KUrl, SyncResource>::iterator it = d->m_resourceHash.find( resUri ); if( it != d->m_resourceHash.end() ) { it.value() += resHash.value( resUri ); } @@ -103,11 +112,13 @@ void Nepomuk::Sync::ResourceIdentifier::addStatements(const QList< Soprano::Stat } -void Nepomuk::Sync::ResourceIdentifier::addSimpleResource(const Nepomuk::Sync::SimpleResource& res) +void Nepomuk::Sync::ResourceIdentifier::addSyncResource(const Nepomuk::Sync::SyncResource& res) { - QHash<KUrl, SimpleResource>::iterator it = d->m_resourceHash.find( res.uri() ); + Q_ASSERT( !res.uri().isEmpty() ); + QHash<KUrl, SyncResource>::iterator it = d->m_resourceHash.find( res.uri() ); if( it == d->m_resourceHash.end() ) { d->m_resourceHash.insert( res.uri(), res ); + d->m_notIdentified.insert( res.uri() ); } else { it.value().unite( res ); @@ -115,19 +126,6 @@ void Nepomuk::Sync::ResourceIdentifier::addSimpleResource(const Nepomuk::Sync::S } -Nepomuk::ResourceManager* Nepomuk::Sync::ResourceIdentifier::resourceManager() const -{ - return d->m_resourceManger; -} - -void Nepomuk::Sync::ResourceIdentifier::setResourceManager(ResourceManager* rm) -{ - Q_ASSERT( rm ); - d->m_resourceManger = rm; - d->m_model = rm->mainModel(); -} - - // // Identification // @@ -136,34 +134,153 @@ void Nepomuk::Sync::ResourceIdentifier::identifyAll() { int totalSize = d->m_notIdentified.size(); kDebug() << totalSize; - + return identify( d->m_notIdentified.toList() ); } bool Nepomuk::Sync::ResourceIdentifier::identify(const KUrl& uri) { - identify( KUrl::List() << uri ); - return d->m_hash.contains( uri ); + // If already identified + if( d->m_hash.contains( uri ) ) + return true; + + // Avoid recursive calls + if( d->m_beingIdentified.contains( uri ) ) + return false; + + bool result = runIdentification( uri ); + d->m_beingIdentified.remove( uri ); + + if( result ) + d->m_notIdentified.remove( uri ); + + return result; } void Nepomuk::Sync::ResourceIdentifier::identify(const KUrl::List& uriList) { foreach( const KUrl & uri, uriList ) { - // If already identified - if( d->m_hash.contains( uri ) ) { + identify( uri ); + } +} + +bool Nepomuk::Sync::ResourceIdentifier::runIdentification(const KUrl& uri) +{ + const Sync::SyncResource & res = simpleResource( uri ); + + // Make sure that the res has some rdf:type statements + if( !res.contains( RDF::type() ) ) { + kDebug() << "No rdf:type statements - Not identifying"; + return false; + } + + QString query; + + int numIdentifyingProperties = 0; + QStringList identifyingProperties; + + QHash< KUrl, Soprano::Node >::const_iterator it = res.constBegin(); + QHash< KUrl, Soprano::Node >::const_iterator constEnd = res.constEnd(); + for( ; it != constEnd; it++ ) { + const QUrl & prop = it.key(); + + // Special handling for rdf:type + if( prop == RDF::type() ) { + query += QString::fromLatin1(" ?r a %1 . ").arg( it.value().toN3() ); continue; } - - d->m_beingIdentified.clear(); - - if( d->identify( uri ) ) { - d->m_notIdentified.remove( uri ); + + if( !isIdentifyingProperty( prop ) ) { + continue; + } + + identifyingProperties << Soprano::Node::resourceToN3( prop ); + + Soprano::Node object = it.value(); + if( object.isBlank() + || ( object.isResource() && object.uri().scheme() == QLatin1String("nepomuk") ) ) { + + QUrl objectUri = object.isResource() ? object.uri() : QString( "_:" + object.identifier() ); + if( !identify( objectUri ) ) { + //kDebug() << "Identification of object " << objectUri << " failed"; + continue; + } + + object = mappedUri( objectUri ); } + + // FIXME: What about optional properties? + query += QString::fromLatin1(" optional { ?r %1 ?o%3 . } . filter(!bound(?o%3) || ?o%3=%2). ") + .arg( Soprano::Node::resourceToN3( prop ), + object.toN3(), + QString::number( numIdentifyingProperties++ ) ); + } + + if( identifyingProperties.isEmpty() || numIdentifyingProperties == 0 ) { + //kDebug() << "No identification properties found!"; + return false; + } + + // Make sure atleast one of the identification properties has been matched + // by adding filter( bound(?o1) || bound(?o2) ... ) + query += QString::fromLatin1("filter( "); + for( int i=0; i<numIdentifyingProperties-1; i++ ) { + query += QString::fromLatin1(" bound(?o%1) || ").arg( QString::number( i ) ); + } + query += QString::fromLatin1(" bound(?o%1) ) . }").arg( QString::number( numIdentifyingProperties - 1 ) ); + + // Construct the entire query + QString queryBegin = QString::fromLatin1("select distinct ?r count(?p) as ?cnt " + "where { ?r ?p ?o. filter( ?p in (%1) ).") + .arg( identifyingProperties.join(",") ); + + query = queryBegin + query + QString::fromLatin1(" order by desc(?cnt)"); + + kDebug() << query; + + // + // Only store the results which have the maximum score + // + QSet<KUrl> results; + int score = -1; + Soprano::QueryResultIterator qit = d->m_model->executeQuery( query, Soprano::Query::QueryLanguageSparql ); + while( qit.next() ) { + //kDebug() << "RESULT: " << qit["r"] << " " << qit["cnt"]; + + int count = qit["cnt"].literal().toInt(); + if( score == -1 ) { + score = count; + } + else if( count < score ) + break; + + results << qit["r"].uri(); + } + + //kDebug() << "Got " << results.size() << " results"; + if( results.empty() ) + return false; + + KUrl newUri; + if( results.size() == 1 ) + newUri = *results.begin(); + else { + kDebug() << "DUPLICATE RESULTS!"; + newUri = duplicateMatch( res.uri(), results ); + } + + if( !newUri.isEmpty() ) { + kDebug() << uri << " --> " << newUri; + manualIdentification( uri, newUri ); + return true; } + + return false; } + bool Nepomuk::Sync::ResourceIdentifier::allIdentified() const { return d->m_notIdentified.isEmpty(); @@ -173,17 +290,22 @@ bool Nepomuk::Sync::ResourceIdentifier::allIdentified() const // Getting the info // -Nepomuk::Resource Nepomuk::Sync::ResourceIdentifier::mappedResource(const KUrl& resourceUri) const +Soprano::Model* Nepomuk::Sync::ResourceIdentifier::model() { - QHash< KUrl, Resource >::iterator it = d->m_hash.find( resourceUri ); - if( it != d->m_hash.end() ) - return it.value(); - return Resource(); + return d->m_model; +} + +void Nepomuk::Sync::ResourceIdentifier::setModel(Soprano::Model* model) +{ + d->m_model = model ? model : ResourceManager::instance()->mainModel(); } KUrl Nepomuk::Sync::ResourceIdentifier::mappedUri(const KUrl& resourceUri) const { - return mappedResource( resourceUri ).resourceUri(); + QHash< KUrl, KUrl >::iterator it = d->m_hash.find( resourceUri ); + if( it != d->m_hash.end() ) + return it.value(); + return KUrl(); } KUrl::List Nepomuk::Sync::ResourceIdentifier::mappedUris() const @@ -191,19 +313,19 @@ KUrl::List Nepomuk::Sync::ResourceIdentifier::mappedUris() const return d->m_hash.uniqueKeys(); } -QHash< KUrl, Nepomuk::Resource > Nepomuk::Sync::ResourceIdentifier::mappings() const +QHash<KUrl, KUrl> Nepomuk::Sync::ResourceIdentifier::mappings() const { return d->m_hash; } -Nepomuk::Sync::SimpleResource Nepomuk::Sync::ResourceIdentifier::simpleResource(const KUrl& uri) +Nepomuk::Sync::SyncResource Nepomuk::Sync::ResourceIdentifier::simpleResource(const KUrl& uri) { - QHash< KUrl, SimpleResource >::const_iterator it = d->m_resourceHash.constFind( uri ); + QHash< KUrl, SyncResource >::const_iterator it = d->m_resourceHash.constFind( uri ); if( it != d->m_resourceHash.constEnd() ) { return it.value(); } - - return SimpleResource(); + + return SyncResource(); } @@ -223,13 +345,19 @@ QSet< KUrl > Nepomuk::Sync::ResourceIdentifier::unidentified() const return d->m_notIdentified; } +QSet< KUrl > Nepomuk::Sync::ResourceIdentifier::identified() const +{ + return d->m_hash.keys().toSet(); +} + + // // Property settings // -void Nepomuk::Sync::ResourceIdentifier::addOptionalProperty(const Nepomuk::Types::Property& property) +void Nepomuk::Sync::ResourceIdentifier::addOptionalProperty(const QUrl& property) { - d->m_optionalProperties.append( property.uri() ); + d->m_optionalProperties.append( property ); } void Nepomuk::Sync::ResourceIdentifier::clearOptionalProperties() @@ -242,41 +370,11 @@ KUrl::List Nepomuk::Sync::ResourceIdentifier::optionalProperties() const return d->m_optionalProperties; } -void Nepomuk::Sync::ResourceIdentifier::addVitalProperty(const Nepomuk::Types::Property& property) -{ - d->m_vitalProperties.append( property.uri() ); -} - -void Nepomuk::Sync::ResourceIdentifier::clearVitalProperties() -{ - d->m_vitalProperties.clear(); -} - -KUrl::List Nepomuk::Sync::ResourceIdentifier::vitalProperties() const -{ - return d->m_vitalProperties; -} - - -// -// Score -// - -float Nepomuk::Sync::ResourceIdentifier::minScore() const -{ - return d->m_minScore; -} - -void Nepomuk::Sync::ResourceIdentifier::setMinScore(float score) -{ - d->m_minScore = score; -} - namespace { - + QString stripFileName( const QString & url ) { - kDebug() << url; + //kDebug() << url; int lastIndex = url.lastIndexOf('/') + 1; // the +1 is because we want to keep the trailing / return QString(url).remove( lastIndex, url.size() ); } @@ -285,35 +383,35 @@ namespace { void Nepomuk::Sync::ResourceIdentifier::forceResource(const KUrl& oldUri, const Nepomuk::Resource& res) { - d->m_hash[ oldUri ] = res; + d->m_hash[ oldUri ] = res.resourceUri(); d->m_notIdentified.remove( oldUri ); if( res.isFile() ) { const QUrl nieUrlProp = Nepomuk::Vocabulary::NIE::url(); - - Sync::SimpleResource & simRes = d->m_resourceHash[ oldUri ]; + + Sync::SyncResource & simRes = d->m_resourceHash[ oldUri ]; KUrl oldNieUrl = simRes.nieUrl(); KUrl newNieUrl = res.property( nieUrlProp ).toUrl(); - + // // Modify resourceUri's nie:url // simRes.remove( nieUrlProp ); simRes.insert( nieUrlProp, Soprano::Node( newNieUrl ) ); - + // Remove from list. Insert later d->m_notIdentified.remove( oldUri ); - + // // Modify other non identified resources with similar nie:urls // QString oldString; QString newString; - + if( !simRes.isFolder() ) { oldString = stripFileName( oldNieUrl.url( KUrl::RemoveTrailingSlash ) ); newString = stripFileName( newNieUrl.url( KUrl::RemoveTrailingSlash ) ); - + kDebug() << oldString; kDebug() << newString; } @@ -321,18 +419,18 @@ void Nepomuk::Sync::ResourceIdentifier::forceResource(const KUrl& oldUri, const oldString = oldNieUrl.url( KUrl::AddTrailingSlash ); newString = newNieUrl.url( KUrl::AddTrailingSlash ); } - + foreach( const KUrl & uri, d->m_notIdentified ) { // Ignore If already identified if( d->m_hash.contains( uri ) ) continue; - - Sync::SimpleResource& simpleRes = d->m_resourceHash[ uri ]; + + Sync::SyncResource& simpleRes = d->m_resourceHash[ uri ]; // Check if it has a nie:url QString nieUrl = simpleRes.nieUrl().url(); if( nieUrl.isEmpty() ) return; - + // Modify the existing nie:url if( nieUrl.startsWith(oldString) ) { nieUrl.replace( oldString, newString ); @@ -341,7 +439,7 @@ void Nepomuk::Sync::ResourceIdentifier::forceResource(const KUrl& oldUri, const simpleRes.insert( nieUrlProp, Soprano::Node( KUrl(nieUrl) ) ); } } - + d->m_notIdentified.insert( oldUri ); } } @@ -351,23 +449,23 @@ bool Nepomuk::Sync::ResourceIdentifier::ignore(const KUrl& resUri, bool ignoreSu { kDebug() << resUri; kDebug() << "Ignore Sub : " << ignoreSub; - + if( d->m_hash.contains( resUri ) ) { kDebug() << d->m_hash; return false; } // Remove the resource - const Sync::SimpleResource & res = d->m_resourceHash.value( resUri ); + const Sync::SyncResource & res = d->m_resourceHash.value( resUri ); d->m_resourceHash.remove( resUri ); d->m_notIdentified.remove( resUri ); kDebug() << "Removed!"; - + // Remove all the statements that contain the resoruce QList<KUrl> allUris = d->m_resourceHash.uniqueKeys(); foreach( const KUrl & uri, allUris ) { - SimpleResource res = d->m_resourceHash[ uri ]; + SyncResource res = d->m_resourceHash[ uri ]; res.removeObject( resUri ); } @@ -381,16 +479,16 @@ bool Nepomuk::Sync::ResourceIdentifier::ignore(const KUrl& resUri, bool ignoreSu QList<Soprano::Node> nieUrlNodes = res.values( nieUrlProp ); if( nieUrlNodes.size() != 1 ) return false; - + KUrl mainNieUrl = nieUrlNodes.first().uri(); - + foreach( const KUrl & uri, d->m_notIdentified ) { - Sync::SimpleResource res = d->m_resourceHash[ uri ]; - + Sync::SyncResource res = d->m_resourceHash[ uri ]; + // If already identified if( d->m_hash.contains(uri) ) continue; - + // Check if it has a nie:url QList<Soprano::Node> nieUrls = res.values( nieUrlProp ); if( nieUrls.empty() ) @@ -408,12 +506,11 @@ bool Nepomuk::Sync::ResourceIdentifier::ignore(const KUrl& resUri, bool ignoreSu } -KUrl Nepomuk::Sync::ResourceIdentifier::duplicateMatch(const KUrl& uri, const QSet< KUrl >& matchedUris, float score) +KUrl Nepomuk::Sync::ResourceIdentifier::duplicateMatch(const KUrl& uri, const QSet< KUrl >& matchedUris) { Q_UNUSED( uri ); Q_UNUSED( matchedUris ); - Q_UNUSED( score ); - + // By default - Identification fails return KUrl(); } @@ -425,9 +522,29 @@ Soprano::Graph Nepomuk::Sync::ResourceIdentifier::createIdentifyingStatements(co return gen.generate(); } -Nepomuk::Resource Nepomuk::Sync::ResourceIdentifier::additionalIdentification(const KUrl& uri) +void Nepomuk::Sync::ResourceIdentifier::manualIdentification(const KUrl& oldUri, const KUrl& newUri) { - Q_UNUSED( uri ); - // Do nothing - identification fails - return Nepomuk::Resource(); + d->m_hash[ oldUri ] = newUri; + d->m_notIdentified.remove( oldUri ); +} + +bool Nepomuk::Sync::ResourceIdentifier::isIdentifyingProperty(const QUrl& uri) +{ + if( uri == NAO::created() + || uri == NAO::creator() + || uri == NAO::lastModified() + || uri == NAO::userVisible() ) { + return false; + } + + // TODO: Hanlde nxx:FluxProperty and nxx:resourceRangePropWhichCanIdentified + const QString query = QString::fromLatin1("ask { %1 %2 ?range . " + " %1 a %3 . " + "{ FILTER( regex(str(?range), '^http://www.w3.org/2001/XMLSchema#') ) . }" + " UNION { %1 a rdf:Property . } }") // rdf:Property should be nxx:resourceRangePropWhichCanIdentified + .arg( Soprano::Node::resourceToN3( uri ), + Soprano::Node::resourceToN3( RDFS::range() ), + Soprano::Node::resourceToN3( RDF::Property() ) ); + + return model()->executeQuery( query, Soprano::Query::QueryLanguageSparql ).boolValue(); } diff --git a/nepomuk/services/backupsync/lib/resourceidentifier.h b/nepomuk/services/backupsync/lib/resourceidentifier.h index ab43f19..2a787c4 100644 --- a/nepomuk/services/backupsync/lib/resourceidentifier.h +++ b/nepomuk/services/backupsync/lib/resourceidentifier.h @@ -31,20 +31,16 @@ namespace Soprano { class Statement; class Graph; + class Model; } namespace Nepomuk { class Resource; - class ResourceManager; - namespace Types { - class Property; - } - namespace Sync { - class SimpleResource; + class SyncResource; /** * \class ResourceIdentifier resourceidentifier.h @@ -52,30 +48,30 @@ namespace Nepomuk { * This class is used to identify already existing resources from a set of * properties and objects. It identifies the resources on the basis of the * identifying statements provided. - * + * * \author Vishesh Handa <handa.vish@gmail.com> */ class NEPOMUKSYNC_EXPORT ResourceIdentifier { public: - ResourceIdentifier( ResourceManager * rm = 0 ); + ResourceIdentifier( Soprano::Model * model = 0 ); virtual ~ResourceIdentifier(); - ResourceManager * resourceManager() const; - void setResourceManager( ResourceManager * rm ); + Soprano::Model * model(); + void setModel( Soprano::Model * model ); // // Processing // - virtual void identifyAll(); + void identifyAll(); - virtual bool identify( const KUrl & uri ); + bool identify( const KUrl & uri ); /** * Identifies all the resources present in the \p uriList. */ - virtual void identify( const KUrl::List & uriList ); - + void identify( const KUrl::List & uriList ); + /** * This returns true if ALL the external ResourceUris have been identified. * If this is false, you should manually identify some of the resources by @@ -88,31 +84,31 @@ namespace Nepomuk { virtual void addStatement( const Soprano::Statement & st ); virtual void addStatements( const Soprano::Graph& graph ); virtual void addStatements( const QList<Soprano::Statement> & stList ); - virtual void addSimpleResource( const SimpleResource & res ); - + virtual void addSyncResource( const SyncResource & res ); + // // Getting the info // /** * Returns the detected uri for the given resourceUri. - * This method usefull only after identifyAll() method was called + * This method useful only after identifyAll() method was called */ KUrl mappedUri( const KUrl & resourceUri ) const; - Resource mappedResource( const KUrl & resourceUri ) const; - KUrl::List mappedUris() const; - + /** * Returns mappings of the identified uri */ - QHash<KUrl, Resource> mappings() const; + QHash<KUrl, KUrl> mappings() const; /** * Returns urls that were not successfully identified */ QSet<KUrl> unidentified() const; + QSet<KUrl> identified() const; + /** * Returns all the statements that are being used to identify \p uri */ @@ -120,20 +116,7 @@ namespace Nepomuk { QList<Soprano::Statement> identifyingStatements() const; - SimpleResource simpleResource( const KUrl & uri ); - // - // Score - // - /** - * Returns the min % of the number of statements that should match during identification - * in order for a resource to be successfully identified. - * - * Returns a value between [0,1] - */ - float minScore() const; - - void setMinScore( float score ); - + SyncResource simpleResource( const KUrl & uri ); // // Property Settings // @@ -141,23 +124,11 @@ namespace Nepomuk { * The property \p prop will be matched during identification, but it will * not contribute to the actual score if it cannot be matched. */ - void addOptionalProperty( const Types::Property & property ); + void addOptionalProperty( const QUrl & property ); void clearOptionalProperties(); - - KUrl::List optionalProperties() const; - - /** - * If the property \p prop cannot be matched during identification then the - * identification for that resource will fail. - * - * By default - rdf:type is the only vital property - */ - void addVitalProperty( const Types::Property & property ); - void clearVitalProperties(); - - KUrl::List vitalProperties() const; + KUrl::List optionalProperties() const; // // Manual Identification @@ -186,12 +157,13 @@ namespace Nepomuk { * contain \p uri as the object. */ bool ignore( const KUrl& uri, bool ignoreSub = false ); - + // // Identification Statement generator // static Soprano::Graph createIdentifyingStatements( const KUrl::List & uriList ); + virtual bool isIdentifyingProperty( const QUrl & uri ); private: class Private; Private * d; @@ -202,13 +174,20 @@ namespace Nepomuk { * * The default behavior is to return an empty uri, which depicts identification failure */ - virtual KUrl duplicateMatch( const KUrl & uri, const QSet<KUrl> & matchedUris, float score ); + virtual KUrl duplicateMatch( const KUrl & uri, const QSet<KUrl> & matchedUris ); + + /** + * This function returns true if identification was successful, and false if it was not. + * If you need to customize the identification process, you will need to overload this + * function. + */ + virtual bool runIdentification( const KUrl& uri ); /** - * In case identification fails for \p uri this method would be called. Derived classes - * can implement their own identification mechanisms over here. + * Sets oldUri -> newUri in the mappings. + * This is useful when runIdentification has been reimplemented. */ - virtual Nepomuk::Resource additionalIdentification( const KUrl & uri ); + void manualIdentification( const KUrl & oldUri, const KUrl & newUri ); }; } } diff --git a/nepomuk/services/backupsync/lib/resourceidentifier_p.cpp b/nepomuk/services/backupsync/lib/resourceidentifier_p.cpp deleted file mode 100644 index a53fab6..0000000 --- a/nepomuk/services/backupsync/lib/resourceidentifier_p.cpp +++ /dev/null @@ -1,250 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "resourceidentifier.h" -#include "resourceidentifier_p.h" -#include "backupsync.h" - -#include <QtCore/QDir> -#include <QtCore/QSet> - -#include <Soprano/Statement> -#include <Soprano/Vocabulary/RDF> -#include <Soprano/Vocabulary/RDFS> -#include <Soprano/Model> -#include <Soprano/QueryResultIterator> -#include <Soprano/StatementIterator> -#include <Soprano/NodeIterator> -#include <Soprano/Node> - -#include <Nepomuk/ResourceManager> -#include <Nepomuk/Resource> -#include <Nepomuk/Variant> - -#include <Nepomuk/Vocabulary/NIE> -#include <Nepomuk/Vocabulary/NFO> -#include <Soprano/Vocabulary/NAO> - -#include <KDebug> -#include <KUrl> - -Nepomuk::Sync::ResourceIdentifier::Private::Private( ResourceIdentifier * parent ) - : q( parent ), - m_model(0), - m_resourceManger(0), - m_minScore( 0.60 ) -{;} - - -void Nepomuk::Sync::ResourceIdentifier::Private::init(Nepomuk::ResourceManager* rm) -{ - if( !rm ) - m_resourceManger = ResourceManager::instance(); - m_model = m_resourceManger->mainModel(); - - // rdf:type is by default vital - m_vitalProperties.append( Soprano::Vocabulary::RDF::type() ); -} - - -namespace { - - //TODO: Use Nepomuk::Type::Property - bool isIdentifyingProperty( KUrl prop, Soprano::Model * model ) { - QString query = QString::fromLatin1( "ask { %1 %2 %3 }" ) - .arg( Soprano::Node::resourceToN3( prop ) ) - .arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()) ) - .arg( Soprano::Node::resourceToN3(Nepomuk::Vocabulary::backupsync::identifyingProperty()) ); - return model->executeQuery( query, Soprano::Query::QueryLanguageSparql ).boolValue(); - } -} - - - -bool Nepomuk::Sync::ResourceIdentifier::Private::identify( const KUrl& oldUri ) -{ - kDebug() << oldUri; - - if( m_hash.contains( oldUri ) ) - return true; - - const SimpleResource & res = m_resourceHash[ oldUri ]; - KUrl resourceUri = findMatch( res ); - - if( resourceUri.isEmpty() ) { - resourceUri = q->additionalIdentification( oldUri ).resourceUri(); - if( resourceUri.isEmpty() ) - return false; - } - m_hash[ oldUri ] = resourceUri; - - kDebug() << oldUri << " ---> " << resourceUri; - return true; -} - - - -bool Nepomuk::Sync::ResourceIdentifier::Private::queryIdentify(const KUrl& oldUri) -{ - if( m_beingIdentified.contains( oldUri ) ) - return false; - bool result = identify( oldUri ); - - if( result ) - m_notIdentified.remove( oldUri ); - - return result; -} - - -//TODO: Optimize -KUrl Nepomuk::Sync::ResourceIdentifier::Private::findMatch(const Nepomuk::Sync::SimpleResource& simpleRes) -{ - kDebug() << "SimpleResource: " << simpleRes; - // - // Vital Properties - // - int numOfVitalStatements = 0; - QString query = QString::fromLatin1("select distinct ?r where {"); - kDebug() << m_vitalProperties; - foreach( const KUrl & prop, m_vitalProperties ) { - QList<Soprano::Node> objects = simpleRes.property( prop ); - foreach( const Soprano::Node & obj, objects ) { - query += QString::fromLatin1(" ?r %1 %2. ") - .arg( Soprano::Node::resourceToN3( prop ), - obj.toN3() ); - numOfVitalStatements++; - } - } - query += " }"; - - kDebug() << "Number of Vital Statements : " << numOfVitalStatements; - kDebug() << query; - // - // Insert them in resourceCount with count = 0 - // - Soprano::QueryResultIterator it = m_model->executeQuery( query, Soprano::Query::QueryLanguageSparql ); - - // The first int is the score while the second int is the additional - // maxScore ( for optional properties ) - QHash<KUrl, QPair<int, int> > resourceCount; - while( it.next() ) { - resourceCount.insert( it[0].uri(), QPair<int, int>(0,0) ); - } - - // No match - if( resourceCount.isEmpty() ) - return KUrl(); - - - // - // Get all the other properties, and increase resourceCount accordingly. - // Ignore vital properties. Don't increment the maxScore when an optional property is not found. - // - int numStatementsCompared = 0; - QList<KUrl> properties = simpleRes.uniqueKeys(); - foreach( const KUrl & propUri, properties ) { - - if( m_vitalProperties.contains( propUri ) ) - continue; - - bool isOptionalProp = m_optionalProperties.contains( propUri ); - - Soprano::Statement statement( Soprano::Node(), propUri, Soprano::Node(), Soprano::Node() ); - - QList<Soprano::Node> objList = simpleRes.values( propUri ); - foreach( const Soprano::Node& n, objList ) { - if( n.isResource() && n.uri().scheme() == QLatin1String("nepomuk") ) { - if( !queryIdentify( n.uri() ) ) { - continue; - } - } - statement.setObject( n ); - - Soprano::NodeIterator iter = m_model->listStatements( statement ).iterateSubjects(); - while( iter.next() ) { - QHash< KUrl, QPair<int,int> >::iterator it = resourceCount.find( iter.current().uri() ); - - if( it != resourceCount.end() ) { - if( isOptionalProp ) { - // It is an optional property and it has matched - // -> Increase the score ( first ) - // -> and increase the max score ( second ) - // ( optional properties don't contribute to the max score unless matched ) - it.value().first++; - it.value().second++; - } - else { - if( it != resourceCount.end() ) { - it.value().first++; // only increase the score - } - } - } - } - if( !isOptionalProp ) - numStatementsCompared++; - } - } - - // - // Find the resources with the max score - // - QSet<KUrl> maxResources; - float maxScore = -1; - - foreach( const KUrl & key, resourceCount.keys() ) { - QPair<int, int> scorePair = resourceCount.value( key ); - - // The divisor will be the total number of statements it was compared to - // ie optional-properties-matched ( stored in scorePair.second ) + numStatementsCompared - float divisor = scorePair.second + numStatementsCompared; - float score = 0; - if( divisor ) - score = scorePair.first / divisor; - - if( score > maxScore ) { - maxScore = score; - maxResources.clear(); - maxResources.insert( key ); - } - else if( score == maxScore ) { - maxResources.insert( key ); - } - } - - if( maxScore < m_minScore ) { - return KUrl(); - } - - if( maxResources.empty() ) - return KUrl(); - - if( maxResources.size() > 1 ) { - kDebug() << "WE GOT A PROBLEM!!"; - kDebug() << "More than one resource with the exact same score found"; - kDebug() << "NOT IDENTIFYING IT! Do it manually!"; - - return q->duplicateMatch( simpleRes.uri(), maxResources, maxScore ); - } - - return (*maxResources.begin()); -} - diff --git a/nepomuk/services/backupsync/lib/resourceidentifier_p.h b/nepomuk/services/backupsync/lib/resourceidentifier_p.h index a9543ff..7c98603 100644 --- a/nepomuk/services/backupsync/lib/resourceidentifier_p.h +++ b/nepomuk/services/backupsync/lib/resourceidentifier_p.h @@ -31,7 +31,7 @@ #include <KUrl> #include "resourceidentifier.h" -#include "simpleresource.h" +#include "syncresource.h" #include <Nepomuk/ResourceManager> @@ -43,75 +43,43 @@ namespace Soprano { namespace Nepomuk { namespace Sync { - + class ResourceIdentifier::Private { public: /** * Contstructor. * It will initialize all pointers with NULL and all values that - * has a incorrect value with this value. For example -1 will be - * assigned for m_id. + * has a incorrect value with this value. */ - Private( ResourceIdentifier * parent ); - void init( ResourceManager * rm ); - + Private( Nepomuk::Sync::ResourceIdentifier* parent ); + ResourceIdentifier * q; - + Soprano::Model * m_model; - ResourceManager * m_resourceManger; - + /** * The main identification hash which maps external ResourceUris * with the internal ones */ - QHash<KUrl, Nepomuk::Resource> m_hash; - + QHash<KUrl, KUrl> m_hash; + QSet<KUrl> m_notIdentified; - + /// Used to store all the identification statements ResourceHash m_resourceHash; // // Properties // - KUrl::List m_vitalProperties; KUrl::List m_optionalProperties; - + /** * This contains all the urls that are being identified, at any moment. * It is used to avoid infinite recursion while generating the sparql * query. */ QSet<KUrl> m_beingIdentified; - - float m_minScore; - - // - // Identification Procedures - // - - - bool identify( const KUrl & uri ); - - /** - * Checks if the @p oldUri is already in the process of being identified. - * The function returns false if it is being identified otherwise it - * returns the value of identify( const KUrl & ) - * - * \sa identify - */ - bool queryIdentify( const KUrl & oldUri ); - - /** - * Finds the best possible match for \p rs from the internal model. It uses - * constructIdentificationQuery to create the query - * - * \param minScore dictates the min number of properties that should match - * - * \sa queryIdentify - */ - KUrl findMatch( const Nepomuk::Sync::SimpleResource& simpleRes ); }; } } diff --git a/nepomuk/services/backupsync/lib/resourcemerger.cpp b/nepomuk/services/backupsync/lib/resourcemerger.cpp index c84263e..613904f 100644 --- a/nepomuk/services/backupsync/lib/resourcemerger.cpp +++ b/nepomuk/services/backupsync/lib/resourcemerger.cpp @@ -1,6 +1,6 @@ /* This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + Copyright (C) 2010-11 Vishesh Handa <handa.vish@gmail.com> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,36 +22,35 @@ #include "resourcemerger.h" -#define USING_SOPRANO_NRLMODEL_UNSTABLE_API - #include <Soprano/Model> -#include <Soprano/Vocabulary/NRL> -#include <Soprano/NRLModel> #include <Soprano/Graph> -#include <Nepomuk/Resource> +#include <Soprano/Vocabulary/NRL> +#include <Soprano/Vocabulary/RDF> + #include <Nepomuk/ResourceManager> #include <KUrl> #include <KDebug> +using namespace Soprano::Vocabulary; + class Nepomuk::Sync::ResourceMerger::Private { public: Private( ResourceMerger * resMerger ); - + Soprano::Model * m_model; - ResourceManager * m_resourceManager; - Soprano::NRLModel * m_nrlModel; - KUrl m_graphType; + KUrl m_graph; ResourceMerger * q; - - QHash<KUrl, Resource> m_oldMappings; - QHash<KUrl, Resource> m_newMappings; - void push( const Soprano::Statement & st, const KUrl & graphUri ); - KUrl resolve( const KUrl& oldUri ); + QHash<KUrl, KUrl> m_mappings; + + QMultiHash<QUrl, Soprano::Node> m_additionalMetadata; + + bool push( const Soprano::Statement & st ); + KUrl resolve( const Soprano::Node & n ); }; Nepomuk::Sync::ResourceMerger::Private::Private(Nepomuk::Sync::ResourceMerger* resMerger) @@ -60,12 +59,11 @@ Nepomuk::Sync::ResourceMerger::Private::Private(Nepomuk::Sync::ResourceMerger* r } -Nepomuk::Sync::ResourceMerger::ResourceMerger(Nepomuk::ResourceManager* rm) +Nepomuk::Sync::ResourceMerger::ResourceMerger( Soprano::Model * model, const QHash<KUrl, KUrl> & mappings ) : d( new Nepomuk::Sync::ResourceMerger::Private( this ) ) { - d->m_nrlModel = 0; - setResourceManager( rm ); - d->m_graphType = Soprano::Vocabulary::NRL::InstanceBase(); + setModel( model ); + setMappings( mappings ); } Nepomuk::Sync::ResourceMerger::~ResourceMerger() @@ -73,117 +71,196 @@ Nepomuk::Sync::ResourceMerger::~ResourceMerger() delete d; } -void Nepomuk::Sync::ResourceMerger::setResourceManager(Nepomuk::ResourceManager* rm) +void Nepomuk::Sync::ResourceMerger::setModel(Soprano::Model* model) { - if( !rm ) - d->m_resourceManager = ResourceManager::instance(); - d->m_model = d->m_resourceManager->mainModel(); - - delete d->m_nrlModel; - d->m_nrlModel = new Soprano::NRLModel( d->m_model ); + if( model == 0 ) { + d->m_model = ResourceManager::instance()->mainModel(); + return; + } + d->m_model = model; } -Nepomuk::ResourceManager* Nepomuk::Sync::ResourceMerger::resourceManager() const +Soprano::Model* Nepomuk::Sync::ResourceMerger::model() const { - return d->m_resourceManager; + return d->m_model; } +void Nepomuk::Sync::ResourceMerger::setMappings(const QHash< KUrl, KUrl >& mappings) +{ + d->m_mappings = mappings; +} -Soprano::Model* Nepomuk::Sync::ResourceMerger::model() const +QHash< KUrl, KUrl > Nepomuk::Sync::ResourceMerger::mappings() const { - return d->m_resourceManager->mainModel(); + return d->m_mappings; } +void Nepomuk::Sync::ResourceMerger::setAdditionalGraphMetadata(const QMultiHash< QUrl, Soprano::Node >& additionalMetadata) +{ + d->m_additionalMetadata = additionalMetadata; +} -Nepomuk::Types::Class Nepomuk::Sync::ResourceMerger::graphType() const +QMultiHash< QUrl, Soprano::Node > Nepomuk::Sync::ResourceMerger::additionalMetadata() const { - return d->m_graphType; + return d->m_additionalMetadata; } -bool Nepomuk::Sync::ResourceMerger::setGraphType(const Nepomuk::Types::Class& type) +KUrl Nepomuk::Sync::ResourceMerger::resolveUnidentifiedResource(const KUrl& uri) { - if( type.isSubClassOf( Soprano::Vocabulary::NRL::Graph() ) ){ - d->m_graphType = type.uri(); - return true; - } - return false; + //TODO: The resource should also get its metadata -> nao:created, nao:lastModified + KUrl newUri = createResourceUri(); + d->m_mappings.insert( uri, newUri ); + return newUri; } -Nepomuk::Resource Nepomuk::Sync::ResourceMerger::resolveUnidentifiedResource(const KUrl& uri) +bool Nepomuk::Sync::ResourceMerger::merge( const Soprano::Graph& graph ) { - // The default implementation is to create it. - QHash< KUrl, Resource >::const_iterator it = d->m_newMappings.constFind( uri ); - if( it != d->m_newMappings.constEnd() ) - return it.value(); - - KUrl newUri = d->m_resourceManager->generateUniqueUri( QString("res") ); - d->m_newMappings.insert( uri, newUri ); - return Nepomuk::Resource( newUri ); + const QList<Soprano::Statement> statements = graph.toList(); + foreach( Soprano::Statement st, statements ) { + if(!mergeStatement( st )) + return false; + } + return true; } -void Nepomuk::Sync::ResourceMerger::merge(const Soprano::Graph& graph, const QHash< KUrl, Nepomuk::Resource >& mappings) +bool Nepomuk::Sync::ResourceMerger::resolveStatement(Soprano::Statement& st) { - d->m_oldMappings = mappings; + if( !st.isValid() ) { + QString error = QString::fromLatin1("Invalid statement encountered"); + return false; + } - KUrl graphUri = createGraph(); - - QList<Soprano::Statement> statements = graph.toList(); - foreach( Soprano::Statement st, statements ) { - if( !st.isValid() ) - continue; - - st.setSubject( d->resolve( st.subject().uri() ) ); - if( st.object().isResource() ) { - KUrl resolvedObject = d->resolve( st.object().uri() ); - if( resolvedObject.isEmpty() ) { - kDebug() << st.object().uri() << " resolution failed!"; - continue; - } - st.setObject( resolvedObject ); - } + KUrl resolvedSubject = d->resolve( st.subject() ); + if( !resolvedSubject.isValid() ) { + QString error = QString::fromLatin1("Subject - %1 resolution failed") + .arg( st.subject().toN3() ); + kDebug() << error; + setError( error ); + return false; + } - d->push( st, graphUri ); + st.setSubject( resolvedSubject ); + Soprano::Node object = st.object(); + if( (object.isResource() && object.uri().scheme() == QLatin1String("nepomuk") ) + || object.isBlank() ) { + KUrl resolvedObject = d->resolve( object ); + if( resolvedObject.isEmpty() ) { + QString error = QString::fromLatin1("Object - %1 resolution failed") + .arg( object.toN3() ); + kDebug() << error; + setError( error ); + return false; + } + st.setObject( resolvedObject ); } + + return true; +} + + +bool Nepomuk::Sync::ResourceMerger::mergeStatement(const Soprano::Statement& statement) +{ + Soprano::Statement st( statement ); + if( !resolveStatement( st ) ) + return false; + + return d->push( st ); } KUrl Nepomuk::Sync::ResourceMerger::createGraph() { - return d->m_nrlModel->createGraph( d->m_graphType ); + KUrl graphUri = createGraphUri(); + KUrl metadataGraph = createResourceUri(); + + addStatement( metadataGraph, RDF::type(), NRL::GraphMetadata(), metadataGraph ); + addStatement( metadataGraph, NRL::coreGraphMetadataFor(), graphUri, metadataGraph ); + + if( !d->m_additionalMetadata.contains( RDF::type(), NRL::InstanceBase() ) ) + d->m_additionalMetadata.insert( RDF::type(), NRL::InstanceBase() ); + + for(QHash<QUrl, Soprano::Node>::const_iterator it = d->m_additionalMetadata.constBegin(); + it != d->m_additionalMetadata.constEnd(); ++it) { + addStatement(graphUri, it.key(), it.value(), metadataGraph); + } + + return graphUri; +} + + +KUrl Nepomuk::Sync::ResourceMerger::graph() +{ + if( !d->m_graph.isValid() ) { + d->m_graph = createGraph(); + if( !d->m_graph.isValid() ) { + setError( QString::fromLatin1("Graph creation failed. A valid graph was not returned %1") + .arg( d->m_graph.url() ), Soprano::Error::ErrorInvalidArgument ); + } + } + return d->m_graph; } -void Nepomuk::Sync::ResourceMerger::Private::push(const Soprano::Statement& st, const KUrl& graphUri) +bool Nepomuk::Sync::ResourceMerger::Private::push(const Soprano::Statement& st) { + Soprano::Statement statement( st ); if( m_model->containsAnyStatement( st.subject(), st.predicate(), st.object() ) ) { - // Already exists. Ignore - return; + return q->resolveDuplicate( statement ); } - Soprano::Statement statement( st ); - if( statement.context().isEmpty() ) - statement.setContext( graphUri ); - - m_model->addStatement( statement ); + if( !m_graph.isValid() ) { + m_graph = q->createGraph(); + if( !m_graph.isValid() ) + return false; + } + statement.setContext( m_graph ); + //kDebug() << "Pushing - " << statement; + return q->addStatement( statement ) == Soprano::Error::ErrorNone; } -KUrl Nepomuk::Sync::ResourceMerger::Private::resolve(const KUrl& oldUri) +KUrl Nepomuk::Sync::ResourceMerger::Private::resolve(const Soprano::Node& n) { + const QUrl oldUri = n.isResource() ? n.uri() : QUrl( n.toN3() ); + // Find in mappings - QHash< KUrl, Resource >::const_iterator it = m_oldMappings.constFind( oldUri ); - if( it != m_oldMappings.constEnd() ) { - return it.value().resourceUri(); + QHash< KUrl, KUrl >::const_iterator it = m_mappings.constFind( oldUri ); + if( it != m_mappings.constEnd() ) { + return it.value(); } else { - Nepomuk::Resource res = q->resolveUnidentifiedResource( oldUri ); - return res.resourceUri(); + return q->resolveUnidentifiedResource( oldUri ); } } -void Nepomuk::Sync::ResourceMerger::push(const Soprano::Statement& st) +bool Nepomuk::Sync::ResourceMerger::resolveDuplicate(const Soprano::Statement& /*newSt*/) { - if( !st.context().isEmpty() ) - d->push( st, QUrl() ); + return true; } + +bool Nepomuk::Sync::ResourceMerger::push(const Soprano::Statement& st) +{ + return d->push( st ); +} + +QUrl Nepomuk::Sync::ResourceMerger::createResourceUri() +{ + return ResourceManager::instance()->generateUniqueUri("res"); +} + +QUrl Nepomuk::Sync::ResourceMerger::createGraphUri() +{ + return ResourceManager::instance()->generateUniqueUri("ctx"); +} + +Soprano::Error::ErrorCode Nepomuk::Sync::ResourceMerger::addStatement(const Soprano::Statement& st) +{ + return d->m_model->addStatement( st ); +} + +Soprano::Error::ErrorCode Nepomuk::Sync::ResourceMerger::addStatement(const Soprano::Node& subject, const Soprano::Node& property, const Soprano::Node& object, const Soprano::Node& graph) +{ + return d->m_model->addStatement( Soprano::Statement(subject, property, object, graph) ); +} + diff --git a/nepomuk/services/backupsync/lib/resourcemerger.h b/nepomuk/services/backupsync/lib/resourcemerger.h index bbb3d70..03ec014 100644 --- a/nepomuk/services/backupsync/lib/resourcemerger.h +++ b/nepomuk/services/backupsync/lib/resourcemerger.h @@ -1,6 +1,6 @@ /* This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + Copyright (C) 2010-11 Vishesh Handa <handa.vish@gmail.com> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -29,11 +29,13 @@ #include <KUrl> #include "nepomuksync_export.h" +#include <Soprano/Error/ErrorCode> namespace Soprano { class Statement; class Model; class Graph; + class Node; } namespace Nepomuk { @@ -44,7 +46,7 @@ namespace Nepomuk { namespace Types { class Class; } - + namespace Sync { /** @@ -56,59 +58,132 @@ namespace Nepomuk { * * By default, it pushes all the statements with a nrl:InstanceBase graph. If a * statement already exists in the repository then it is NOT overwritten. - * + * * \author Vishesh Handa <handa.vish@gmail.com> */ - class NEPOMUKSYNC_EXPORT ResourceMerger + class NEPOMUKSYNC_EXPORT ResourceMerger : public Soprano::Error::ErrorCache { public: - ResourceMerger( ResourceManager * rm = 0 ); + ResourceMerger( Soprano::Model * model =0, const QHash<KUrl, KUrl> & mappings = (QHash<KUrl, KUrl>()) ); virtual ~ResourceMerger(); - ResourceManager * resourceManager() const; - void setResourceManager( ResourceManager * rm ); - + void setModel( Soprano::Model * model ); Soprano::Model * model() const; + void setMappings( const QHash<KUrl, KUrl> & mappings ); + QHash<KUrl, KUrl> mappings() const; + + /** + * Merges all the statements in \p graph into the model, by calling + * mergeStatement + * + * It stops merging if any of the statements in \p graph fail to merge + * + * \sa mergeStatement + * \return \c true if merging was successful + */ + virtual bool merge( const Soprano::Graph & graph ); + /** - * Pushes all the statements in \p graph into the model. If the statement + * Merges the statement \p st into the model. If the statement * already exists then resolveDuplicate() is called. * * If any of the statements contains a graph, then that graph is used. Otherwise - * a newly generated graph is used. + * createGraph() is called which returns a new graph. + * + * lastError() is set, if merging fails. * - * \sa setGraphType graphType + * \sa createGraph + * \return \c true if the merging was sucessful. + * \c false if merging failed */ - virtual void merge( const Soprano::Graph & graph, const QHash<KUrl, Resource> & mappings ); + virtual bool mergeStatement( const Soprano::Statement & st ); /** - * The graph type by default is nrl:InstanceBase. If \p type is not a subclass of - * nrl:Graph then it is ignored. + * Sets the graph metadata which will be used to create a graph. + * + * \sa createGraph */ - bool setGraphType( const Types::Class & type ); - - Types::Class graphType() const; + void setAdditionalGraphMetadata( const QMultiHash<QUrl, Soprano::Node>& additionalMetadata ); + + QMultiHash<QUrl, Soprano::Node> additionalMetadata() const; protected: - /** * Called when trying to merge a statement which contains a Resource that * has not been identified. - * - * The default implementation of this creates the resource in the main model. + * + * The default implementation of this creates the resource in the model. + * The resourceUri is generated using createResourceUri. + * + * If the resolution is supposed to fail, this function returns KUrl(). + * The reason why resolution failed should also be set with setError() + * + * \sa createResourceUri */ - virtual Resource resolveUnidentifiedResource( const KUrl & uri ); + virtual KUrl resolveUnidentifiedResource( const KUrl & uri ); /** - * Creates a new graph of type graphType() + * Creates a new graph with the additional metadata. + * All graphs that are created should be a subtype of nrl:Graph + * + * \sa additionalMetadata */ virtual KUrl createGraph(); /** - * Push the statement into the Nepomuk repository if it doesn't already exist! + * Push the statement into the Nepomuk repository. + * If a statement with the same subject, predicate and object already + * exists in the model, then resolveDuplicate is called. + * + * \sa resolveDuplicate + * \return \c true if pushing the statement was successful */ - void push( const Soprano::Statement & st ); + bool push( const Soprano::Statement & st ); + /** + * If the statement being pushed already exists this method is called. + * By default it does nothing which means keeping the old statement + * + * \return \c true if resolution was successful + * \c false if resolution failed, and merging and should fail + */ + virtual bool resolveDuplicate( const Soprano::Statement & newSt ); + + /** + * Creates a new resource uri. By default this creates it using the + * ResourceManager::instace()->generateUniqueUri("res") + */ + virtual QUrl createResourceUri(); + + /** + * Creates a new graph uri. By default this creates it using the + * ResourceManager::instace()->generateUniqueUri("ctx") + */ + virtual QUrl createGraphUri(); + + /** + * Returns the graph that is being used to add new statements. + * If this graph does not exist it is created using createGraph + * + * \sa createGraph + */ + KUrl graph(); + + /** + * Add the statement in the model. By default it just calls + * Soprano::Model::addStatement() + * + * \return \c Soprano::Error::ErrorNone if added to model + */ + virtual Soprano::Error::ErrorCode addStatement( const Soprano::Statement & st ); + Soprano::Error::ErrorCode addStatement( const Soprano::Node& subject, const Soprano::Node& property, + const Soprano::Node& object, const Soprano::Node& graph ); + + /** + * Resolves the subject and object and gets the object ready for pushing + */ + bool resolveStatement( Soprano::Statement& st ); private: class Private; Private * d; diff --git a/nepomuk/services/backupsync/lib/simpleresource.cpp b/nepomuk/services/backupsync/lib/simpleresource.cpp deleted file mode 100644 index 17d10c5..0000000 --- a/nepomuk/services/backupsync/lib/simpleresource.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - - -#include "simpleresource.h" - -#include <Soprano/Node> -#include <Soprano/Graph> -#include <Soprano/Statement> -#include <Soprano/StatementIterator> - -#include <Nepomuk/Vocabulary/NIE> -#include <Nepomuk/Vocabulary/NFO> -#include <Soprano/Vocabulary/RDF> - -#include <QtCore/QSharedData> - -class Nepomuk::Sync::SimpleResource::Private : public QSharedData { -public: - KUrl uri; -}; - - -Nepomuk::Sync::SimpleResource::SimpleResource() - : d( new Nepomuk::Sync::SimpleResource::Private ) -{ -} - -Nepomuk::Sync::SimpleResource::SimpleResource(const Nepomuk::Sync::SimpleResource& rhs) - : QMultiHash< KUrl, Soprano::Node >(rhs), - d( rhs.d ) -{ -} - -Nepomuk::Sync::SimpleResource::~SimpleResource() -{ -} - -Nepomuk::Sync::SimpleResource& Nepomuk::Sync::SimpleResource::operator=(const Nepomuk::Sync::SimpleResource& rhs) -{ - d = rhs.d; - return *this; -} - -bool Nepomuk::Sync::SimpleResource::operator==(const Nepomuk::Sync::SimpleResource& res) -{ - return d->uri == res.d->uri && - this->QHash<KUrl, Soprano::Node>::operator==( res ); -} - -QList< Soprano::Statement > Nepomuk::Sync::SimpleResource::toStatementList() const -{ - QList<Soprano::Statement> list; - const QList<KUrl> & keys = uniqueKeys(); - foreach( const KUrl & key, keys ) { - Soprano::Statement st; - st.setSubject( Soprano::Node( d->uri ) ); - st.setPredicate( Soprano::Node( key ) ); - - const QList<Soprano::Node>& objects = values( key ); - foreach( const Soprano::Node & node, objects ) { - st.setObject( node ); - list.append( st ); - } - } - return list; -} - - -bool Nepomuk::Sync::SimpleResource::isFolder() const -{ - return values( Soprano::Vocabulary::RDF::type() ).contains( Soprano::Node( Nepomuk::Vocabulary::NFO::Folder() ) ); -} - - -bool Nepomuk::Sync::SimpleResource::isFileDataObject() const -{ - return values( Soprano::Vocabulary::RDF::type() ).contains( Soprano::Node( Nepomuk::Vocabulary::NFO::FileDataObject() ) ); -} - - -KUrl Nepomuk::Sync::SimpleResource::nieUrl() const -{ - const QHash<KUrl, Soprano::Node>::const_iterator it = constFind( Nepomuk::Vocabulary::NIE::url() ); - if( it == constEnd() ) - return KUrl(); - else - return it.value().uri(); -} - - -void Nepomuk::Sync::SimpleResource::setUri(const KUrl& newUri) -{ - d->uri = newUri; -} - -KUrl Nepomuk::Sync::SimpleResource::uri() const -{ - return d->uri; -} - -QList< Soprano::Node > Nepomuk::Sync::SimpleResource::property(const KUrl& url) const -{ - return values(url); -} - -void Nepomuk::Sync::SimpleResource::removeObject(const KUrl& uri) -{ - QMutableHashIterator<KUrl, Soprano::Node> iter( *this ); - while( iter.hasNext() ) { - iter.next(); - - if( iter.value().isResource() && iter.value().uri() == uri ) - iter.remove(); - } -} - -// static -Nepomuk::Sync::SimpleResource Nepomuk::Sync::SimpleResource::fromStatementList(const QList< Soprano::Statement >& list) -{ - Q_ASSERT( !list.isEmpty() ); - - SimpleResource res; - res.setUri( list.first().subject().uri() ); - - foreach( const Soprano::Statement & st, list ) { - KUrl pred = st.predicate().uri(); - Soprano::Node obj = st.object(); - - if( !res.contains( pred, obj ) ) - res.insert( pred, obj ); - } - - return res; -} - -// -// ResourceHash -// - -// static -Nepomuk::Sync::ResourceHash Nepomuk::Sync::ResourceHash::fromGraph(const Soprano::Graph& graph) -{ - return fromStatementList( graph.listStatements().allStatements() ); -} - -// static -Nepomuk::Sync::ResourceHash Nepomuk::Sync::ResourceHash::fromStatementList(const QList< Soprano::Statement >& allStatements) -{ - // - // Convert into multi hash for easier look up - // - QMultiHash<KUrl, Soprano::Statement> stHash; - stHash.reserve( allStatements.size() ); - foreach( const Soprano::Statement & st, allStatements ) { - KUrl uri = st.subject().uri(); - stHash.insert( uri, st ); - } - - // - // Convert them into a better format --> SimpleResource - // - const QList<KUrl> & uniqueUris = stHash.uniqueKeys(); - - ResourceHash resources; - resources.reserve( uniqueUris.size() ); - - foreach( const KUrl & resUri, uniqueUris ) { - SimpleResource res = SimpleResource::fromStatementList( stHash.values( resUri ) ); - resources.insert( res.uri(), res ); - } - - return resources; -} - - -QList< Soprano::Statement > Nepomuk::Sync::ResourceHash::toStatementList() const -{ - QList<Soprano::Statement> stList; - Q_FOREACH( const KUrl& uri, uniqueKeys() ) { - const SimpleResource & res = value( uri ); - stList += res.toStatementList(); - } - - return stList; -} diff --git a/nepomuk/services/backupsync/lib/simpleresource.h b/nepomuk/services/backupsync/lib/simpleresource.h deleted file mode 100644 index f95b0e1..0000000 --- a/nepomuk/services/backupsync/lib/simpleresource.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - - -#ifndef NEPOMUK_SIMPLERESOURCEH_H -#define NEPOMUK_SIMPLERESOURCEH_H - -#include <KUrl> - -#include <QtCore/QList> -#include <QtCore/QHash> -#include <QtCore/QSharedDataPointer> - -#include "nepomuksync_export.h" - -namespace Soprano { - class Node; - class Statement; - class Graph; -} - -namespace Nepomuk { - namespace Sync { - - /** - * \class SimpleResource simpleresource.h - * - * A SimpleResource is a convenient way of storing a set of properties and objects for - * a common subject. This class does not represent an actual resource present in the - * repository. It's just a collection of in-memory statements. - * - * Interally it uses a multi-hash to store the properties and objects. - * - * \author Vishesh Handa <handa.vish@gmail.com> - */ - class NEPOMUKSYNC_EXPORT SimpleResource : public QMultiHash<KUrl, Soprano::Node> - { - public : - SimpleResource(); - SimpleResource( const SimpleResource & rhs ); - virtual ~SimpleResource(); - - /** - * It uses the the first element's subject as the uri and ignores all further subjects. - * Please make sure all the subjects are the same cause no kind of checks are made. - */ - static SimpleResource fromStatementList(const QList<Soprano::Statement> & list); - - QList<Soprano::Statement> toStatementList() const; - - bool isFileDataObject() const; - bool isFolder() const; - KUrl nieUrl() const; - - KUrl uri() const; - void setUri( const KUrl & newUri ); - - - QList<Soprano::Node> property( const KUrl & url ) const; - - /** - * Removes all the statements whose object is \p uri - */ - void removeObject( const KUrl & uri ); - - SimpleResource& operator=( const SimpleResource & rhs ); - bool operator==( const SimpleResource & res ); - private: - class Private; - QSharedDataPointer<Private> d; - }; - - /** - * \class ResourceHash simpleresource.h - * - * A SimpleResource is a convenient way of representing a list of Soprano::Statements - * or a Soprano::Graph. - * It provides easy lookup of resources. - * - * \author Vishesh Handa <handa.vish@gmail.com> - */ - class NEPOMUKSYNC_EXPORT ResourceHash : public QHash<KUrl, SimpleResource> { - public : - static ResourceHash fromStatementList( const QList<Soprano::Statement> & list ); - static ResourceHash fromGraph( const Soprano::Graph & graph ); - - QList<Soprano::Statement> toStatementList() const; - }; - - } -} -#endif // NEPOMUK_SIMPLERESOURCEH_H diff --git a/nepomuk/services/backupsync/lib/syncresource.cpp b/nepomuk/services/backupsync/lib/syncresource.cpp new file mode 100644 index 0000000..72f4701 --- /dev/null +++ b/nepomuk/services/backupsync/lib/syncresource.cpp @@ -0,0 +1,236 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "syncresource.h" + +#include <Soprano/Node> +#include <Soprano/Graph> +#include <Soprano/Statement> +#include <Soprano/StatementIterator> + +#include <Nepomuk/Vocabulary/NIE> +#include <Nepomuk/Vocabulary/NFO> +#include <Soprano/Vocabulary/RDF> + +#include <QtCore/QSharedData> + +class Nepomuk::Sync::SyncResource::Private : public QSharedData { +public: + KUrl uri; +}; + + +Nepomuk::Sync::SyncResource::SyncResource() + : d( new Nepomuk::Sync::SyncResource::Private ) +{ +} + +Nepomuk::Sync::SyncResource::SyncResource(const KUrl& uri) + : d( new Nepomuk::Sync::SyncResource::Private ) +{ + setUri( uri ); +} + +Nepomuk::Sync::SyncResource::SyncResource(const Nepomuk::Sync::SyncResource& rhs) + : QMultiHash< KUrl, Soprano::Node >(rhs), + d( rhs.d ) +{ +} + +Nepomuk::Sync::SyncResource::~SyncResource() +{ +} + +Nepomuk::Sync::SyncResource& Nepomuk::Sync::SyncResource::operator=(const Nepomuk::Sync::SyncResource& rhs) +{ + d = rhs.d; + return *this; +} + +bool Nepomuk::Sync::SyncResource::operator==(const Nepomuk::Sync::SyncResource& res) const +{ + return d->uri == res.d->uri && + this->QHash<KUrl, Soprano::Node>::operator==( res ); +} + +QList< Soprano::Statement > Nepomuk::Sync::SyncResource::toStatementList() const +{ + QList<Soprano::Statement> list; + const QList<KUrl> & keys = uniqueKeys(); + foreach( const KUrl & key, keys ) { + Soprano::Statement st; + Soprano::Node sub = d->uri.url().startsWith("_:") ? Soprano::Node(d->uri.url().mid(2)) : d->uri; + st.setSubject( sub ); + st.setPredicate( Soprano::Node( key ) ); + + const QList<Soprano::Node>& objects = values( key ); + foreach( const Soprano::Node & node, objects ) { + st.setObject( node ); + list.append( st ); + } + } + return list; +} + + +bool Nepomuk::Sync::SyncResource::isFolder() const +{ + return values( Soprano::Vocabulary::RDF::type() ).contains( Soprano::Node( Nepomuk::Vocabulary::NFO::Folder() ) ); +} + + +bool Nepomuk::Sync::SyncResource::isFileDataObject() const +{ + return values( Soprano::Vocabulary::RDF::type() ).contains( Soprano::Node( Nepomuk::Vocabulary::NFO::FileDataObject() ) ); +} + + +KUrl Nepomuk::Sync::SyncResource::nieUrl() const +{ + const QHash<KUrl, Soprano::Node>::const_iterator it = constFind( Nepomuk::Vocabulary::NIE::url() ); + if( it == constEnd() ) + return KUrl(); + else + return it.value().uri(); +} + + +void Nepomuk::Sync::SyncResource::setUri(const Soprano::Node& node) +{ + if( node.isResource() ) { + d->uri = node.uri(); + } + else if( node.isBlank() ) { + d->uri = KUrl( node.toN3() ); + } +} + +KUrl Nepomuk::Sync::SyncResource::uri() const +{ + return d->uri; +} + +QList< Soprano::Node > Nepomuk::Sync::SyncResource::property(const KUrl& url) const +{ + return values(url); +} + +void Nepomuk::Sync::SyncResource::removeObject(const KUrl& uri) +{ + QMutableHashIterator<KUrl, Soprano::Node> iter( *this ); + while( iter.hasNext() ) { + iter.next(); + + if( iter.value().isResource() && iter.value().uri() == uri ) + iter.remove(); + } +} + +namespace { + // Blank nodes are stored as "_:identifier" in urls + QUrl getUri( const Soprano::Node & n ) { + if( n.isBlank() ) + return QUrl( n.toN3() ); + else + return n.uri(); + } +} +// static +Nepomuk::Sync::SyncResource Nepomuk::Sync::SyncResource::fromStatementList(const QList< Soprano::Statement >& list) +{ + if( list.isEmpty() ) + return SyncResource(); + + SyncResource res; + Soprano::Node subject = list.first().subject(); + res.setUri( getUri(subject) ); + + foreach( const Soprano::Statement & st, list ) { + if( st.subject() != subject ) + continue; + + KUrl pred = st.predicate().uri(); + Soprano::Node obj = st.object(); + + if( !res.contains( pred, obj ) ) + res.insert( pred, obj ); + } + + return res; +} + +// +// ResourceHash +// + +// static +Nepomuk::Sync::ResourceHash Nepomuk::Sync::ResourceHash::fromGraph(const Soprano::Graph& graph) +{ + return fromStatementList( graph.listStatements().allStatements() ); +} + +// static +Nepomuk::Sync::ResourceHash Nepomuk::Sync::ResourceHash::fromStatementList(const QList< Soprano::Statement >& allStatements) +{ + // + // Convert into multi hash for easier look up + // + QMultiHash<KUrl, Soprano::Statement> stHash; + stHash.reserve( allStatements.size() ); + foreach( const Soprano::Statement & st, allStatements ) { + KUrl uri = getUri( st.subject() ); + stHash.insert( uri, st ); + } + + // + // Convert them into a better format --> SyncResource + // + const QList<KUrl> & uniqueUris = stHash.uniqueKeys(); + + ResourceHash resources; + resources.reserve( uniqueUris.size() ); + + foreach( const KUrl & resUri, uniqueUris ) { + SyncResource res = SyncResource::fromStatementList( stHash.values( resUri ) ); + resources.insert( res.uri(), res ); + } + + return resources; +} + + +QList< Soprano::Statement > Nepomuk::Sync::ResourceHash::toStatementList() const +{ + QList<Soprano::Statement> stList; + Q_FOREACH( const KUrl& uri, uniqueKeys() ) { + const SyncResource & res = value( uri ); + stList += res.toStatementList(); + } + + return stList; +} + + +bool Nepomuk::Sync::SyncResource::isValid() const +{ + return !d->uri.isEmpty() && !isEmpty(); +} diff --git a/nepomuk/services/backupsync/lib/syncresource.h b/nepomuk/services/backupsync/lib/syncresource.h new file mode 100644 index 0000000..77f3b90 --- /dev/null +++ b/nepomuk/services/backupsync/lib/syncresource.h @@ -0,0 +1,118 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#ifndef NEPOMUK_SIMPLERESOURCEH_H +#define NEPOMUK_SIMPLERESOURCEH_H + +#include <KUrl> + +#include <QtCore/QList> +#include <QtCore/QHash> +#include <QtCore/QSharedDataPointer> + +#include "nepomuksync_export.h" + +namespace Soprano { + class Node; + class Statement; + class Graph; +} + +namespace Nepomuk { + namespace Sync { + + /** + * \class SyncResource syncresource.h + * + * A SyncResource is a convenient way of storing a set of properties and objects for + * a common subject. This class does not represent an actual resource present in the + * repository. It's just a collection of in-memory statements. + * + * Interally it uses a multi-hash to store the properties and objects. + * + * \author Vishesh Handa <handa.vish@gmail.com> + */ + class NEPOMUKSYNC_EXPORT SyncResource : public QMultiHash<KUrl, Soprano::Node> + { + public : + SyncResource(); + SyncResource( const KUrl & uri ); + SyncResource( const SyncResource & rhs ); + virtual ~SyncResource(); + + /** + * It uses the the first element's subject as the uri and ignores all further subjects. + * Please make sure all the subjects are the same cause no kind of checks are made. + */ + static SyncResource fromStatementList(const QList<Soprano::Statement> & list); + + QList<Soprano::Statement> toStatementList() const; + + bool isFileDataObject() const; + bool isFolder() const; + KUrl nieUrl() const; + + KUrl uri() const; + + /** + * If \p node is resource node the uri is set to the node's uri + * Otherwise if \p node is a blank node then the uri + * is set to its identifier + */ + void setUri( const Soprano::Node & node ); + + QList<Soprano::Node> property( const KUrl & url ) const; + + /** + * Removes all the statements whose object is \p uri + */ + void removeObject( const KUrl & uri ); + + SyncResource& operator=( const SyncResource & rhs ); + bool operator==( const SyncResource & res ) const; + + bool isValid() const; + private: + class Private; + QSharedDataPointer<Private> d; + }; + + /** + * \class ResourceHash syncresource.h + * + * A SyncResource is a convenient way of representing a list of Soprano::Statements + * or a Soprano::Graph. + * It provides easy lookup of resources. + * + * \author Vishesh Handa <handa.vish@gmail.com> + */ + class NEPOMUKSYNC_EXPORT ResourceHash : public QHash<KUrl, SyncResource> { + public : + static ResourceHash fromStatementList( const QList<Soprano::Statement> & list ); + static ResourceHash fromGraph( const Soprano::Graph & graph ); + + QList<Soprano::Statement> toStatementList() const; + }; + + } +} +#endif // NEPOMUK_SIMPLERESOURCEH_H diff --git a/nepomuk/services/backupsync/service/CMakeLists.txt b/nepomuk/services/backupsync/service/CMakeLists.txt index 252c850..41ef5df 100644 --- a/nepomuk/services/backupsync/service/CMakeLists.txt +++ b/nepomuk/services/backupsync/service/CMakeLists.txt @@ -7,7 +7,7 @@ find_package(Nepomuk REQUIRED) include(SopranoAddOntology) add_definitions(-DDISABLE_NEPOMUK_LEGACY=1) -add_definitions(-DKDE_DEFAULT_DEBUG_AREA=300106) +#add_definitions(-DKDE_DEFAULT_DEBUG_AREA=300106) include (KDE4Defaults) @@ -22,32 +22,28 @@ include_directories( ) set( BackupSyncService_SRCS - identifier.cpp - merger.cpp logstorage.cpp backupsyncservice.cpp diffgenerator.cpp syncmanager.cpp backupmanager.cpp dbusoperators.cpp - changelogmerger.cpp - syncfileidentifier.cpp tools.cpp - resourcelog.cpp changelog.cpp changelogrecord.cpp identificationset.cpp syncfile.cpp + backupgenerationjob.cpp ) #--------- Adaptors ---------# -qt4_add_dbus_adaptor( BackupSyncService_SRCS - ../../../interfaces/org.kde.nepomuk.services.nepomukbackupsync.identifier.xml - identifier.h Nepomuk::Identifier ) -qt4_add_dbus_adaptor( BackupSyncService_SRCS - ../../../interfaces/org.kde.nepomuk.services.nepomukbackupsync.merger.xml - merger.h Nepomuk::Merger ) +# qt4_add_dbus_adaptor( BackupSyncService_SRCS +# ../../../interfaces/org.kde.nepomuk.services.nepomukbackupsync.identifier.xml +# identifier.h Nepomuk::Identifier ) +# qt4_add_dbus_adaptor( BackupSyncService_SRCS +# ../../../interfaces/org.kde.nepomuk.services.nepomukbackupsync.merger.xml +# merger.h Nepomuk::Merger ) qt4_add_dbus_adaptor( BackupSyncService_SRCS ../../../interfaces/org.kde.nepomuk.services.nepomukbackupsync.backupmanager.xml backupmanager.h Nepomuk::BackupManager ) @@ -58,7 +54,7 @@ qt4_add_dbus_adaptor( BackupSyncService_SRCS #--------- Ontologies -------# soprano_add_ontology(BackupSyncService_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../../../ontologies/nrio.trig - "backupsync" + "NRIO" "Nepomuk::Vocabulary" "trig") diff --git a/nepomuk/services/backupsync/service/backupgenerationjob.cpp b/nepomuk/services/backupsync/service/backupgenerationjob.cpp new file mode 100644 index 0000000..94514a3 --- /dev/null +++ b/nepomuk/services/backupsync/service/backupgenerationjob.cpp @@ -0,0 +1,45 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "backupgenerationjob.h" +#include "tools.h" + +#include <QtCore/QTimer> + +Nepomuk::BackupGenerationJob::BackupGenerationJob(const QUrl& url, QObject* parent) + : KJob(parent), + m_url( url ) +{ +} + +void Nepomuk::BackupGenerationJob::start() +{ + QTimer::singleShot( 0, this, SLOT(doWork()) ); +} + +void Nepomuk::BackupGenerationJob::doWork() +{ + Nepomuk::saveBackupSyncFile( m_url ); + emitResult(); +} + + + diff --git a/nepomuk/services/backupsync/service/backupgenerationjob.h b/nepomuk/services/backupsync/service/backupgenerationjob.h new file mode 100644 index 0000000..63837c0 --- /dev/null +++ b/nepomuk/services/backupsync/service/backupgenerationjob.h @@ -0,0 +1,44 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef BACKUPGENERATOR_H +#define BACKUPGENERATOR_H + +#include <KJob> +#include <QtCore/QUrl> + +namespace Nepomuk { + + class BackupGenerationJob : public KJob + { + Q_OBJECT + public: + BackupGenerationJob(const QUrl& url, QObject* parent = 0); + virtual void start(); + + private slots: + void doWork(); + + private: + QUrl m_url; + }; +} + +#endif // BACKUPGENERATOR_H diff --git a/nepomuk/services/backupsync/service/backupmanager.cpp b/nepomuk/services/backupsync/service/backupmanager.cpp index 3b39c5b..8cbc3b2 100644 --- a/nepomuk/services/backupsync/service/backupmanager.cpp +++ b/nepomuk/services/backupsync/service/backupmanager.cpp @@ -23,12 +23,12 @@ #include "backupmanager.h" #include "backupmanageradaptor.h" #include "logstorage.h" -#include "identifier.h" #include "tools.h" #include "changelog.h" #include "syncfile.h" #include "identificationset.h" +#include "backupgenerationjob.h" #include <QtDBus/QDBusConnection> #include <QtCore/QListIterator> @@ -45,9 +45,8 @@ #include <KCalendarSystem> -Nepomuk::BackupManager::BackupManager(Nepomuk::Identifier* ident, QObject* parent) +Nepomuk::BackupManager::BackupManager(QObject* parent) : QObject( parent ), - m_identifier( ident ), m_config( "nepomukbackuprc" ) { new BackupManagerAdaptor( this ); @@ -86,19 +85,10 @@ void Nepomuk::BackupManager::backup(const QString& oldUrl) QFile::remove( url ); - saveBackupSyncFile( url ); - emit backupDone(); -} - + KJob * job = new BackupGenerationJob( url, this ); -int Nepomuk::BackupManager::restore(const QString& oldUrl) -{ - //TODO: Some kind of error checking! - QString url = oldUrl; - if( url.isEmpty() ) - url = KStandardDirs::locateLocal( "data", "nepomuk/backupsync/backup" ); - - return m_identifier->process( SyncFile(url) ); + connect( job, SIGNAL(finished(KJob*)), this, SLOT(slotBackupDone(KJob*)) ); + job->start(); } void Nepomuk::BackupManager::automatedBackup() @@ -114,10 +104,10 @@ void Nepomuk::BackupManager::slotConfigDirty() { kDebug(); m_config.reparseConfiguration(); - + QString freq = m_config.group("Backup").readEntry( "backup frequency", QString("disabled") ); kDebug() << "Frequency : " << freq; - + if( freq == QLatin1String("disabled") ) { kDebug() << "Auto Backups Disabled"; m_timer.stop(); @@ -128,7 +118,7 @@ void Nepomuk::BackupManager::slotConfigDirty() m_backupTime = QTime::fromString( timeString, Qt::ISODate ); if( freq == QLatin1String("daily") ) { - m_daysBetweenBackups = 0; + m_daysBetweenBackups = 0; } else if( freq == QLatin1String("weekly") ) { @@ -159,7 +149,7 @@ void Nepomuk::BackupManager::slotConfigDirty() else if( freq == QLatin1String("monthly") ) { //TODO: Implement me! } - + m_maxBackups = m_config.group("Backup").readEntry<int>("max backups", 1); // Remove old timers and start new @@ -181,7 +171,7 @@ void Nepomuk::BackupManager::resetTimer() if( dateTime < current ) { dateTime = dateTime.addDays( 1 ); } - + int msecs = current.msecsTo( dateTime ); m_timer.stop(); @@ -202,6 +192,13 @@ void Nepomuk::BackupManager::removeOldBackups() } } +void Nepomuk::BackupManager::slotBackupDone(KJob* job) +{ + if( !job->error() ) { + emit backupDone(); + } +} + #include "backupmanager.moc" diff --git a/nepomuk/services/backupsync/service/backupmanager.h b/nepomuk/services/backupsync/service/backupmanager.h index bed862b..911990c 100644 --- a/nepomuk/services/backupsync/service/backupmanager.h +++ b/nepomuk/services/backupsync/service/backupmanager.h @@ -28,30 +28,26 @@ #include <QtCore/QTimer> #include <KConfig> +#include <KJob> namespace Nepomuk { - class Identifier; - class Merger; - class BackupManager : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.nepomuk.services.nepomukbackupsync.BackupManager") public: - BackupManager(Nepomuk::Identifier* ident, QObject* parent = 0); + BackupManager(QObject* parent = 0); virtual ~BackupManager(); public slots: void backup( const QString & url = QString() ); - int restore( const QString & url = QString() ); signals: void backupDone(); - + private: - Identifier * m_identifier; QString m_backupLocation; QTime m_backupTime; @@ -63,10 +59,11 @@ namespace Nepomuk { QTimer m_timer; void resetTimer(); void removeOldBackups(); - + private slots: void slotConfigDirty(); void automatedBackup(); + void slotBackupDone(KJob * job); }; } diff --git a/nepomuk/services/backupsync/service/backupsyncservice.cpp b/nepomuk/services/backupsync/service/backupsyncservice.cpp index 7f5df1b..8dc2ae5 100644 --- a/nepomuk/services/backupsync/service/backupsyncservice.cpp +++ b/nepomuk/services/backupsync/service/backupsyncservice.cpp @@ -22,8 +22,6 @@ #include "backupsyncservice.h" #include "diffgenerator.h" -#include "identifier.h" -#include "merger.h" #include "syncmanager.h" #include "backupmanager.h" #include "dbusoperators.h" @@ -44,19 +42,9 @@ Nepomuk::BackupSyncService::BackupSyncService( QObject* parent, const QList< QVa kDebug(); m_diffGenerator = new DiffGenerator( this ); - m_identifier = new Identifier( this ); - m_merger = new Merger( this ); - m_syncManager = new SyncManager( m_identifier, this ); - m_backupManager = new BackupManager( m_identifier, this ); - - // IMPORTANT : We've used "Nepomuk::ChangeLog" in the string cause in the slots, signals, and - // connect statement we're using Nepomuk::ChangeLog, NOT ChangeLog - qRegisterMetaType<Nepomuk::ChangeLog>("Nepomuk::ChangeLog"); - - registerMetaTypes(); - connect( m_identifier, SIGNAL( processed( Nepomuk::ChangeLog ) ), - m_merger, SLOT( process( Nepomuk::ChangeLog ) ) ); + m_syncManager = new SyncManager( this ); + m_backupManager = new BackupManager( this ); } Nepomuk::BackupSyncService::~BackupSyncService() @@ -67,7 +55,6 @@ void Nepomuk::BackupSyncService::test() { //QUrl url("/home/vishesh/syncnew"); - m_identifier->test(); /* LogFile lf; lf.load(QUrl("file:///home/vishesh/ident.trig")); diff --git a/nepomuk/services/backupsync/service/backupsyncservice.h b/nepomuk/services/backupsync/service/backupsyncservice.h index fc38521..aa8face 100644 --- a/nepomuk/services/backupsync/service/backupsyncservice.h +++ b/nepomuk/services/backupsync/service/backupsyncservice.h @@ -28,8 +28,6 @@ namespace Nepomuk { class DiffGenerator; - class Identifier; - class Merger; class SyncManager; class BackupManager; @@ -47,8 +45,6 @@ namespace Nepomuk { private: DiffGenerator * m_diffGenerator; - Identifier * m_identifier; - Merger * m_merger; SyncManager * m_syncManager; BackupManager * m_backupManager; diff --git a/nepomuk/services/backupsync/service/changelogmerger.cpp b/nepomuk/services/backupsync/service/changelogmerger.cpp deleted file mode 100644 index 2c259a4..0000000 --- a/nepomuk/services/backupsync/service/changelogmerger.cpp +++ /dev/null @@ -1,346 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - - - -#include "changelogmerger.h" -#include "backupsync.h" -#include "logstorage.h" - -#include <algorithm> - -#include <QtCore/QMultiHash> -#include <QtCore/QHashIterator> -#include <QtCore/QThread> - -#include <Soprano/Vocabulary/NAO> -#include <Soprano/Vocabulary/NRL> -#include <Soprano/Vocabulary/RDF> -#include <Nepomuk/Vocabulary/NIE> -#include <Nepomuk/Vocabulary/NFO> - -#include <Soprano/Node> -#include <Soprano/Statement> -#include <Soprano/Model> -#include <Soprano/QueryResultIterator> -#include <Soprano/StatementIterator> -#include <Soprano/NodeIterator> - -#include <Nepomuk/Resource> -#include <Nepomuk/Variant> -#include <Nepomuk/Types/Property> -#include <Nepomuk/ResourceManager> - -#include <KDebug> - -int Nepomuk::ChangeLogMerger::NextId = 0; - -Nepomuk::ChangeLogMerger::ChangeLogMerger(Nepomuk::ChangeLog log) - : ResourceMerger(), - m_logFile( log ) -{ - m_id = NextId++; -} - -int Nepomuk::ChangeLogMerger::id() -{ - return m_id; -} - -void Nepomuk::ChangeLogMerger::load() -{ - kDebug() << "Loading the ChangeLog..." << m_logFile.size(); - m_hash = ResourceLogMap::fromChangeLog( m_logFile ); - - // The records are stored according to dateTime - Q_ASSERT( m_logFile.toList().isEmpty() == false ); - m_minDateTime = m_logFile.toList().first().dateTime(); -} - -namespace { - - // - // Cache the results. This could have very bad consequences if someone updates the ontology - // when the service is running - // - static QSet<KUrl> nonMergeable; - bool isMergeable( const KUrl & prop, Soprano::Model * model ) { - - if( nonMergeable.contains( prop ) ) - return false; - - QString query = QString::fromLatin1( "ask { %1 %2 \"false\"^^xsd:boolean . }" ) - .arg( Soprano::Node::resourceToN3( prop ) ) - .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::backupsync::mergeable() ) ); - - bool isMergeable = !model->executeQuery( query, Soprano::Query::QueryLanguageSparql ).boolValue(); - - if( !isMergeable ) { - nonMergeable.insert( prop ); - return false; - } - return true; - } - - QList<Nepomuk::ChangeLogRecord> getRecords( const Nepomuk::ResourceLogMap & hash, const KUrl resUri, const KUrl & propUri ) { - - Nepomuk::ResourceLogMap::const_iterator it = hash.constFind( resUri ); - if( it == hash.constEnd() ) { - return QList<Nepomuk::ChangeLogRecord>(); - } - - return it->prop.values( propUri ); - } -} - -//TODO: Add completed signal -void Nepomuk::ChangeLogMerger::mergeChangeLog() -{ - m_theGraph = createGraph(); - - kDebug(); - const Types::Property mergeableProperty( Nepomuk::Vocabulary::backupsync::mergeable() ); - - // - // Get own changeLog - // - kDebug() << "minDateTime : " << m_minDateTime; - ChangeLog ownLog = LogStorage::instance()->getChangeLog( m_minDateTime ); - kDebug() << "own Log : " << ownLog.size(); - - // Get our and their hash - // ownHash = current local hash from system's ChangeLog - // theirHash = derived from external ChangeLog - ResourceLogMap ownHash = ResourceLogMap::fromChangeLog( ownLog ); - ResourceLogMap & theirHash = m_hash; - - kDebug() << "own Hash : " << ownHash.size(); - kDebug() << "their hash : " << theirHash.size(); - - QHashIterator<KUrl, ResourceLog> it( theirHash ); - while( it.hasNext() ) { - it.next(); - - // Check for resource deletions - if( handleResourceDeletion( it.key() ) ) - continue; - - const KUrl & resUri = it.key(); - const ResourceLog & resLog = it.value(); - - kDebug() << "Resolving " << resUri; - - const QList<KUrl> & properties = resLog.prop.uniqueKeys(); - foreach( const KUrl & propUri, properties ) { - kDebug() << propUri; - - if( !isMergeable( propUri, model() ) ) { - kDebug() << propUri << " is non Mergeable - IGNORING"; - continue; - } - - Nepomuk::Types::Property prop( propUri ); - int cardinality = prop.maxCardinality(); - - QList<ChangeLogRecord> theirRecords = resLog.prop.values( propUri ); - QList<ChangeLogRecord> ownRecords = getRecords( ownHash, resUri, propUri ); - //kDebug() << "own Records : " << ownRecords.size(); - - // This case shouldn't ever happen, but just to be sure - if( theirRecords.empty() ) - continue; - - if( cardinality == 1 ) { - resolveSingleCardinality( theirRecords, ownRecords ); - } - else { - resolveMultipleCardinality( theirRecords, ownRecords ); - } - } - - //if( !rs.propHash.isEmpty() ) - // m_jobs.append( rs ); - } - //theirHash.clear(); - //kDebug() << "Done with merge resolution : " << m_jobs.size(); - - //processJobs(); -} - - -namespace { - - Nepomuk::ChangeLogRecord maxRecord( const QList<Nepomuk::ChangeLogRecord> & records ) { - QList<Nepomuk::ChangeLogRecord>::const_iterator it = std::max_element( records.begin(), records.end() ); - if( it != records.constEnd() ) - return *it; - return Nepomuk::ChangeLogRecord(); - } -} - - -void Nepomuk::ChangeLogMerger::resolveSingleCardinality(const QList< Nepomuk::ChangeLogRecord >& theirRecords, const QList< Nepomuk::ChangeLogRecord >& ownRecords) -{ - kDebug() << "O: " << ownRecords.size() << " " << "T:" << theirRecords.size(); - - //Find max on the basis of time stamp - ChangeLogRecord theirMax = maxRecord( theirRecords ); - ChangeLogRecord ownMax = maxRecord( ownRecords ); - kDebug() << "TheirMax : "<< theirMax.toString(); - kDebug() << "OwnMax " << ownMax.toString(); - - if( theirMax > ownMax ) { - Soprano::Statement statement( theirMax.st().subject(), theirMax.st().predicate(), - Soprano::Node(), Soprano::Node() ); - - if( theirMax.added() ) { - Soprano::Node object = theirMax.st().object(); - kDebug() << "Resolved - Adding " << object; - - if( !model()->containsAnyStatement( statement ) ) { - statement.setObject( object ); - statement.setContext( m_theGraph ); - model()->addStatement( statement ); - } - } - else { - kDebug() << "Resolved - Removing"; - model()->removeAllStatements( statement ); - } - } -} - -namespace { - - struct MergeData { - bool added; - QDateTime dateTime; - - MergeData( bool add, const QDateTime & dt ) - : added( add ), - dateTime( dt ) - {} - }; - - -} - -void Nepomuk::ChangeLogMerger::resolveMultipleCardinality( const QList<Nepomuk::ChangeLogRecord>& theirRecords, const QList<Nepomuk::ChangeLogRecord>& ownRecords) -{ - kDebug() << "MULTIPLE"; - kDebug() << "O: " << ownRecords.size() << " " << "T:" << theirRecords.size(); - - const Soprano::Statement& reference = theirRecords.first().st(); - Soprano::Statement baseStatement( reference.subject(), reference.predicate(), Soprano::Node(), Soprano::Node() ); - - // - // Merge both record lists - // - //TODO: Optimize merging - use merge sort or something equivilant - QList<ChangeLogRecord> records = ownRecords; - records << theirRecords; - qSort( records ); - - QHash<Soprano::Node, MergeData> hash; - foreach( const ChangeLogRecord rec, records ) { - Soprano::Node object = rec.st().object(); - QHash<Soprano::Node, MergeData>::const_iterator it = hash.constFind( object ); - if( it == hash.constEnd() ) { - hash.insert( object, MergeData( rec.added(), rec.dateTime() ) ); - } - else { - // +ve after -ve - if( rec.added() == true && it.value().added == false ) { - hash.remove( object ); - hash.insert( object, MergeData( rec.added(), rec.dateTime() ) ); - } - // -ve after +ve - else if( rec.added() == false && it.value().added == true ) { - hash.remove( object ); - } - // +ve after +ve - // -ve after -ve - // Do nothing - } - } - - // - // Do the actual merging - // - QHashIterator<Soprano::Node, MergeData> it( hash ); - while( it.hasNext() ) { - it.next(); - - Soprano::Statement st( baseStatement ); - st.setObject( it.key() ); - - MergeData data = it.value(); - if( data.added == true ) { - if( !model()->containsAnyStatement( st ) ) { - st.setContext( m_theGraph ); - model()->addStatement( st ); - kDebug() << "adding - " << st; - } - } - else { - kDebug() << "removing " << st; - model()->removeAllStatements( st ); - } - } - - m_multipleMergers.append( Soprano::Statement( baseStatement.subject(), - baseStatement.predicate(), - Soprano::Node() ) ); -} - -QList< Soprano::Statement > Nepomuk::ChangeLogMerger::multipleMergers() const -{ - return m_multipleMergers; -} - -bool Nepomuk::ChangeLogMerger::handleResourceDeletion(const KUrl& resUri) -{ - ResourceLog & log = m_hash[ resUri ]; - const KUrl& rdfTypeProp = Soprano::Vocabulary::RDF::type(); - - QList<ChangeLogRecord> records = log.prop.values( rdfTypeProp ); - if( records.empty() ) - return false; - - // - // Check if rdf:type is being removed - // - bool removed = false; - foreach( const ChangeLogRecord & r, records ) { - if( !r.added() ) { - removed = true; - break; - } - } - if( !removed ) - return false; - - // If removed, remove all records and delete the resource - m_hash.remove( resUri ); - Resource res( resUri ); - res.remove(); - return true; -} \ No newline at end of file diff --git a/nepomuk/services/backupsync/service/changelogmerger.h b/nepomuk/services/backupsync/service/changelogmerger.h deleted file mode 100644 index b968342..0000000 --- a/nepomuk/services/backupsync/service/changelogmerger.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - - - -#ifndef CHANGELOGMERGER_H -#define CHANGELOGMERGER_H - -#include "resourcemerger.h" -#include "simpleresource.h" -#include "changelog.h" -#include "resourcelog.h" - -#include <QtCore/QDateTime> - -namespace Nepomuk { - class ChangeLogMerger : public Sync::ResourceMerger - { - public: - ChangeLogMerger( ChangeLog log ); - - int id(); - void mergeChangeLog(); - - /** - * Converts the contents of the LogFile into the MergeHash - * The LogFile can get rather large, so this could be a time consuming - * process. - */ - void load(); - - QList<Soprano::Statement> multipleMergers() const; - - private: - ChangeLog m_logFile; - QDateTime m_minDateTime; - - QList<Sync::SimpleResource> m_jobs; - QList<Soprano::Statement> m_multipleMergers; - - /// Contains all the records from the LogFile - ResourceLogMap m_hash; - - static int NextId; - int m_id; - - KUrl m_theGraph; - - /** - * Handles the case when the Resource's metadata has been deleted from - * the LogFile. - * \return true if resUri was deleted, false otherwsie - */ - bool handleResourceDeletion( const KUrl & resUri ); - - /** - * Resolve conflicts for properties with multiple cardinalities. It works by locally - * making all the changes in the SimpleResource. - */ - void resolveMultipleCardinality( const QList<ChangeLogRecord >& theirRecords, const QList<ChangeLogRecord >& ownRecords ); - - /** - * Same as multiple Cardinality resolution, but a lot faster. It figures out which statement - * should be present by looking at the max time stamp. - */ - void resolveSingleCardinality( const QList< Nepomuk::ChangeLogRecord >& theirRecords, const QList< Nepomuk::ChangeLogRecord >& ownRecords ); - }; - -} -#endif // CHANGELOGMERGER_H diff --git a/nepomuk/services/backupsync/service/changelogrecord.cpp b/nepomuk/services/backupsync/service/changelogrecord.cpp index ae27e14..6fe7f85 100644 --- a/nepomuk/services/backupsync/service/changelogrecord.cpp +++ b/nepomuk/services/backupsync/service/changelogrecord.cpp @@ -128,7 +128,10 @@ QString Nepomuk::ChangeLogRecord::toString() const QString statement; QTextStream ts( &statement ); - Private::serializer->serialize( it, ts, Soprano::SerializationNQuads); + if( !Private::serializer->serialize( it, ts, Soprano::SerializationNQuads) ) { + kWarning() << "Serialization using NQuads failed for " << d->st; + return QString(); + } return s + statement; } diff --git a/nepomuk/services/backupsync/service/diffgenerator.cpp b/nepomuk/services/backupsync/service/diffgenerator.cpp index fdfbba1..9d432db 100644 --- a/nepomuk/services/backupsync/service/diffgenerator.cpp +++ b/nepomuk/services/backupsync/service/diffgenerator.cpp @@ -21,7 +21,7 @@ #include "diffgenerator.h" -#include "backupsync.h" +#include "nrio.h" #include "logstorage.h" #include "changelog.h" @@ -67,7 +67,7 @@ Nepomuk::DiffGenerator::~DiffGenerator() namespace { - const Nepomuk::Types::Property identifyingProperty( Nepomuk::Vocabulary::backupsync::identifyingProperty() ); + const Nepomuk::Types::Property identifyingProperty( Nepomuk::Vocabulary::NRIO::identifyingProperty() ); } bool Nepomuk::DiffGenerator::backupStatement(const Soprano::Statement& st) diff --git a/nepomuk/services/backupsync/service/identificationset.cpp b/nepomuk/services/backupsync/service/identificationset.cpp index e811553..9a641ba 100644 --- a/nepomuk/services/backupsync/service/identificationset.cpp +++ b/nepomuk/services/backupsync/service/identificationset.cpp @@ -23,7 +23,7 @@ #include "identificationset.h" #include "changelog.h" #include "changelogrecord.h" -#include "backupsync.h" +#include "nrio.h" #include <QtCore/QList> #include <QtCore/QFile> @@ -47,15 +47,19 @@ #include <Nepomuk/ResourceManager> #include <KDebug> +#include <Soprano/Vocabulary/NRL> + +using namespace Soprano::Vocabulary; +using namespace Nepomuk::Vocabulary; namespace { class IdentificationSetGenerator { public : IdentificationSetGenerator( const QSet<QUrl>& uniqueUris, Soprano::Model * m , const QSet<QUrl> & ignoreList = QSet<QUrl>()); - Soprano::Model * model; - QSet<QUrl> done; - QSet<QUrl> notDone; + Soprano::Model * m_model; + QSet<QUrl> m_done; + QSet<QUrl> m_notDone; QList<Soprano::Statement> statements; @@ -63,43 +67,46 @@ namespace { void iterate(); QList<Soprano::Statement> generate(); - static const int maxIterationSize = 50; + static const int maxIterationSize = 500; + + bool done() const { return m_notDone.isEmpty(); } }; IdentificationSetGenerator::IdentificationSetGenerator(const QSet<QUrl>& uniqueUris, Soprano::Model* m, const QSet<QUrl> & ignoreList) { - notDone = uniqueUris - ignoreList; - model = m; - done = ignoreList; - + m_notDone = uniqueUris - ignoreList; + m_model = m; + m_done = ignoreList; } Soprano::QueryResultIterator IdentificationSetGenerator::queryIdentifyingStatements(const QStringList& uris) { + // + // select distinct ?r ?p ?o where { ?r ?p ?o. + // ?p rdfs:subPropertyOf nrio:identifyingProperty . + // FILTER( ?r in ( <res1>, <res2>, ... ) ) . } + // QString query = QString::fromLatin1("select distinct ?r ?p ?o where { ?r ?p ?o. " - "{ ?p %1 %2 .} " - "UNION { ?p %1 %3. } " + "?p %1 %2 . " " FILTER( ?r in ( %4 ) ) . } ") - .arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()), - Soprano::Node::resourceToN3(Nepomuk::Vocabulary::backupsync::identifyingProperty()), - Soprano::Node::resourceToN3(Soprano::Vocabulary::RDF::type()), - uris.join(", ")); + .arg(Soprano::Node::resourceToN3( RDFS::subPropertyOf() ), + Soprano::Node::resourceToN3( NRIO::identifyingProperty() ), + uris.join(", ") ); - - return model->executeQuery(query, Soprano::Query::QueryLanguageSparql); + return m_model->executeQuery(query, Soprano::Query::QueryLanguageSparql); } void IdentificationSetGenerator::iterate() { QStringList uris; - QMutableSetIterator<QUrl> iter( notDone ); + QMutableSetIterator<QUrl> iter( m_notDone ); while( iter.hasNext() ) { const QUrl & uri = iter.next(); - done.insert( uri ); + m_done.insert( uri ); uris.append( Soprano::Node::resourceToN3( uri ) ); - + iter.remove(); if( uris.size() == maxIterationSize ) break; @@ -116,8 +123,8 @@ namespace { // If the object is also a nepomuk uri, it too needs to be identified. const QUrl & objUri = obj.uri(); if( objUri.toString().startsWith("nepomuk:/res/") ) { - if( !done.contains( objUri ) ) { - notDone.insert( objUri ); + if( !m_done.contains( objUri ) ) { + m_notDone.insert( objUri ); } } } @@ -125,9 +132,9 @@ namespace { QList<Soprano::Statement> IdentificationSetGenerator::generate() { - done.clear(); + m_done.clear(); - while( !notDone.isEmpty() ) { + while( !done() ) { iterate(); } return statements; @@ -200,7 +207,7 @@ Nepomuk::IdentificationSet Nepomuk::IdentificationSet::fromTextStream(QTextStrea namespace { // - // Seperate all the unique URIs of scheme "nepomuk" from the subject and object in all the statements. + // Separate all the unique URIs of scheme "nepomuk" from the subject and object in all the statements. // // vHanda: Maybe we should separate the graphs as well. Identification isn't meant for graphs. QSet<QUrl> getUniqueUris( const QList<Nepomuk::ChangeLogRecord> records ) { @@ -259,7 +266,7 @@ namespace { QString query = QString::fromLatin1( "ask { %1 %2 %3 }" ) .arg( Soprano::Node::resourceToN3( prop ) ) .arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subPropertyOf()) ) - .arg( Soprano::Node::resourceToN3(Nepomuk::Vocabulary::backupsync::identifyingProperty()) ); + .arg( Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NRIO::identifyingProperty()) ); return model->executeQuery( query, Soprano::Query::QueryLanguageSparql ).boolValue(); } } @@ -346,3 +353,30 @@ void Nepomuk::IdentificationSet::mergeWith(const IdentificationSet & rhs) return; } +void Nepomuk::IdentificationSet::createIdentificationSet(const QSet<QUrl>& uniqueUris, const QUrl& outputUrl) +{ + QFile file( outputUrl.path() ); + if( !file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) ) { + kWarning() << "File could not be opened : " << outputUrl.path(); + return; + } + + QTextStream out( &file ); + + IdentificationSet set; + Soprano::Model * model = ResourceManager::instance()->mainModel(); + + IdentificationSetGenerator generator( uniqueUris, model ); + while( !generator.done() ) { + generator.statements.clear(); + kDebug() << "iterating"; + generator.iterate(); + kDebug() << "Done : " << generator.m_done.size(); + kDebug() << "Num statements: " << generator.statements.size(); + set.d->m_statements.clear(); + set.d->m_statements = generator.statements; + set.save( out ); + } + kDebug() << "Done creating Identification Set"; +} + diff --git a/nepomuk/services/backupsync/service/identificationset.h b/nepomuk/services/backupsync/service/identificationset.h index 854ab51..8757e2e 100644 --- a/nepomuk/services/backupsync/service/identificationset.h +++ b/nepomuk/services/backupsync/service/identificationset.h @@ -99,6 +99,8 @@ namespace Nepomuk { IdentificationSet & operator=( const IdentificationSet & rhs ); IdentificationSet& operator<<(const IdentificationSet & rhs); + + static void createIdentificationSet( const QSet< QUrl >& uniqueUris, const QUrl& outputUrl ); private : class Private; QSharedDataPointer<Private> d; diff --git a/nepomuk/services/backupsync/service/identifier.cpp b/nepomuk/services/backupsync/service/identifier.cpp deleted file mode 100644 index 5b4c0f3..0000000 --- a/nepomuk/services/backupsync/service/identifier.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "changelog.h" -#include "identifieradaptor.h" - -#include <QtDBus/QDBusConnection> - -#include <QtCore/QMutexLocker> -#include <QtCore/QHashIterator> -#include <QtCore/QFile> - -#include <Soprano/Model> -#include <Soprano/Graph> -#include <Soprano/QueryResultIterator> -#include <Soprano/Vocabulary/RDF> -#include <Soprano/Serializer> -#include <Soprano/PluginManager> -#include <Soprano/Util/SimpleStatementIterator> - -#include <Nepomuk/ResourceManager> -#include <Nepomuk/Resource> -#include <Nepomuk/Variant> -#include <Nepomuk/Vocabulary/NIE> - -#include <KDebug> - -Nepomuk::Identifier::Identifier(QObject* parent): QThread(parent) -{ - //Register DBus interface - new IdentifierAdaptor( this ); - QDBusConnection dbus = QDBusConnection::sessionBus(); - dbus.registerObject( QLatin1String("/identifier"), this ); - - start(); -} - - -Nepomuk::Identifier::~Identifier() -{ - stop(); - quit(); -} - - -int Nepomuk::Identifier::process(const Nepomuk::SyncFile& sf) -{ - m_queueMutex.lock(); - - SyncFileIdentifier* identifier = new SyncFileIdentifier( sf ); - int id = identifier->id(); - m_queue.enqueue( identifier ); - - m_queueMutex.unlock(); - m_queueWaiter.wakeAll(); - - kDebug() << "Processing ID : " << id; - return id; -} - - -void Nepomuk::Identifier::stop() -{ - m_stopped = true; - m_queueWaiter.wakeAll(); -} - - - -void Nepomuk::Identifier::run() -{ - m_stopped = false; - - while( !m_stopped ) { - - // lock for initial iteration - m_queueMutex.lock(); - - while( !m_queue.isEmpty() ) { - - SyncFileIdentifier* identifier = m_queue.dequeue(); - - // unlock after queue utilization - m_queueMutex.unlock(); - - identifier->load(); - identifyAllWithCompletedSignals( identifier ); - - emit identificationDone( identifier->id(), identifier->unidentified().size() ); - - m_processMutex.lock(); - m_processes[ identifier->id() ] = identifier; - m_processMutex.unlock(); - - // Send the sigals - foreach( const KUrl & uri, identifier->unidentified() ) { - emitNotIdentified( identifier->id(), identifier->statements( uri ).toList() ); - } - - foreach( const KUrl & uri, identifier->mappings().uniqueKeys() ) { - emit identified( identifier->id(), uri.url(), identifier->mappedUri( uri ).url() ); - } - -// if( identifier->allIdentified() ) { -// m_processes.remove( identifier->id() ); -// delete identifier; -// } - - m_queueMutex.lock(); - } - - // wait for more input - kDebug() << "Waiting..."; - m_queueWaiter.wait( &m_queueMutex ); - m_queueMutex.unlock(); - kDebug() << "Woke up."; - } -} - - -bool Nepomuk::Identifier::identify(int id, const QString& oldUriString, const QString& newUriString) -{ - QUrl oldUri( oldUriString ); - QUrl newUri( newUriString ); - - kDebug() << newUri; - // Lock the mutex and all - QMutexLocker lock ( &m_processMutex ); - - QHash<int, SyncFileIdentifier*>::iterator it = m_processes.find( id ); - if( it == m_processes.end() ) - return false; - - SyncFileIdentifier* ip = *it; - - if ( oldUri.scheme() != QLatin1String("nepomuk") ) - return false; - - if( newUri.scheme() == QLatin1String("nepomuk") ) { - ip->forceResource( oldUri, Nepomuk::Resource(newUri) ); - } - else if( newUri.scheme() == "file" ) { - ip->forceResource( oldUri, Nepomuk::Resource(newUri) ); - } - - m_queueMutex.lock(); - m_queue.enqueue( ip ); - m_queueMutex.unlock(); - m_queueWaiter.wakeAll(); - - return true; -} - - -bool Nepomuk::Identifier::ignore(int id, const QString& urlString, bool ignoreSub) -{ - KUrl url( urlString ); - // Lock the mutex and all - QMutexLocker lock ( &m_processMutex ); - - QHash<int, SyncFileIdentifier*>::iterator it = m_processes.find( id ); - if( it == m_processes.end() ) - return false; - - SyncFileIdentifier* identifier = *it; - return identifier->ignore( url, ignoreSub ); -} - -void Nepomuk::Identifier::ignoreAll(int id) -{ - QMutexLocker lock ( &m_processMutex ); - - QHash<int, SyncFileIdentifier*>::iterator it = m_processes.find( id ); - if( it == m_processes.end() ) - return; - - SyncFileIdentifier* identifier = *it; - foreach( const KUrl & url, identifier->unidentified() ) { - identifier->ignore( url, true ); - } -} - -void Nepomuk::Identifier::emitNotIdentified(int id, const QList< Soprano::Statement >& stList) -{ - const Soprano::Serializer* serializer = Soprano::PluginManager::instance()->discoverSerializerForSerialization( Soprano::SerializationNQuads ); - - Soprano::Util::SimpleStatementIterator it( stList ); - QString ser; - QTextStream stream( &ser ); - serializer->serialize( it, stream, Soprano::SerializationNQuads ); - - emit notIdentified( id, ser ); -} - -void Nepomuk::Identifier::test() -{ - kDebug() << "Test!"; -} - -void Nepomuk::Identifier::completeIdentification(int id) -{ - kDebug() << id; - - QMutexLocker lock ( &m_processMutex ); - - QHash<int, SyncFileIdentifier*>::iterator it = m_processes.find( id ); - if( it == m_processes.end() ) - return; - - SyncFileIdentifier* identifier = *it; - m_processes.remove( id ); - - ChangeLog log = identifier->convertedChangeLog(); - kDebug() << "ChangeLog of size " << log.size() << " has been converted"; - if( !log.empty() ) { - kDebug() << "sending ChangeLog of size : " << log.size(); - emit processed( log ); - } - - delete identifier; -} - - -void Nepomuk::Identifier::identifyAllWithCompletedSignals(Nepomuk::SyncFileIdentifier* ident) -{ - int unidentified = ident->unidentified().size(); - float step = 100.0/unidentified; - float progress = 0; - - emit completed( ident->id(), 0 ); - foreach( const KUrl & url, ident->unidentified() ) { - ident->identify( url ); - - progress += step; - emit completed( ident->id(), (int)progress ); - } - emit completed( ident->id(), 100 ); -} - - -#include "identifier.moc" diff --git a/nepomuk/services/backupsync/service/identifier.h b/nepomuk/services/backupsync/service/identifier.h deleted file mode 100644 index 8f22f93..0000000 --- a/nepomuk/services/backupsync/service/identifier.h +++ /dev/null @@ -1,96 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef IDENTIFICATIONTHREAD_H -#define IDENTIFICATIONTHREAD_H - -#include <QtCore/QThread> -#include <QtCore/QQueue> -#include <QtCore/QMutex> -#include <QtCore/QWaitCondition> -#include <QtCore/QQueue> -#include <QtCore/QUrl> - -#include "syncfileidentifier.h" - -namespace Soprano { - class Model; -} - -namespace Nepomuk { - - class SyncFile; - class ChangeLog; - - class Identifier : public QThread - { - Q_OBJECT - - public : - Identifier( QObject* parent = 0 ); - virtual ~Identifier(); - - void stop(); - void test(); - - Q_SIGNALS: - void identified( int id, const QString & oldUri, const QString & newUri ); - void notIdentified( int id, const QString & serializedStatements ); - - void identificationDone( int id, int unidentified ); - void processed( const Nepomuk::ChangeLog & logFile ); - - void completed( int id, int progress ); - public Q_SLOTS: - int process( const SyncFile & sf ); - - /** - * Add the (oldUri, newUri) pair to the identifier list - * @param oldUri has to be a nepomuk:/res/ - * @param newUri can be a nepomuk:/res/ or file:/ - */ - bool identify( int id, const QString & oldUri, const QString & newUri ); - - bool ignore(int id, const QString& url, bool ignoreSubDirectories); - - void ignoreAll( int id ); - - void completeIdentification( int id ); - - protected: - virtual void run(); - - private : - QQueue<SyncFileIdentifier *> m_queue; - QMutex m_queueMutex; - QWaitCondition m_queueWaiter; - - bool m_stopped; - - QHash< int, SyncFileIdentifier *> m_processes; - QMutex m_processMutex; - - void emitNotIdentified( int id, const QList<Soprano::Statement> & stList ); - void identifyAllWithCompletedSignals( SyncFileIdentifier * ident ); - }; - -} -#endif // IDENTIFICATIONTHREAD_H diff --git a/nepomuk/services/backupsync/service/logstorage.cpp b/nepomuk/services/backupsync/service/logstorage.cpp index 21ec4cf..249b995 100644 --- a/nepomuk/services/backupsync/service/logstorage.cpp +++ b/nepomuk/services/backupsync/service/logstorage.cpp @@ -83,7 +83,7 @@ Nepomuk::ChangeLog Nepomuk::LogStorage::getChangeLog(const QDateTime& min) continue; //TODO: Optimize : Every record shouldn't be checked. Be smart! - log += ChangeLog::fromUrl( m_dirUrl + fileName, min ); + log += ChangeLog::fromUrl( QString( m_dirUrl + fileName ), min ); } //TODO: Optimize this! @@ -109,7 +109,7 @@ void Nepomuk::LogStorage::addRecord(const Nepomuk::ChangeLogRecord& record) //IMPORTANT: This function doesn't actually remove ALL the records less than min // This has been done purposely, as otherwise one would need to read the entire contents -// of atleast one LogFile. +// of at least one LogFile. void Nepomuk::LogStorage::removeRecords(const QDateTime& min) { QDir dir( m_dirUrl ); diff --git a/nepomuk/services/backupsync/service/merger.cpp b/nepomuk/services/backupsync/service/merger.cpp deleted file mode 100644 index 3fcaa7c..0000000 --- a/nepomuk/services/backupsync/service/merger.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "merger.h" - -#include "changelog.h" -#include "syncfile.h" -#include "mergeradaptor.h" - -#include <QtDBus/QDBusConnection> - -#include <KDebug> - -Nepomuk::Merger::Merger( QObject* parent ) - : QThread( parent ) -{ - //Register DBus interface - new MergerAdaptor( this ); - QDBusConnection dbus = QDBusConnection::sessionBus(); - dbus.registerObject( QLatin1String("/merger"), this ); - - start(); -} - - -Nepomuk::Merger::~Merger() -{ - stop(); - wait(); -} - - -void Nepomuk::Merger::stop() -{ - m_stopped = true; - m_queueWaiter.wakeAll(); -} - - -int Nepomuk::Merger::process(const Nepomuk::ChangeLog& changeLog ) -{ - kDebug(); - m_queueMutex.lock(); - - kDebug() << "Received ChangeLog -- " << changeLog.size(); - ChangeLogMerger * request = new ChangeLogMerger( changeLog ); - m_queue.enqueue( request ); - - m_queueMutex.unlock(); - m_queueWaiter.wakeAll(); - - return request->id(); -} - - -void Nepomuk::Merger::run() -{ - m_stopped = false; - - while( !m_stopped ) { - - // lock for initial iteration - m_queueMutex.lock(); - - while( !m_queue.isEmpty() ) { - - ChangeLogMerger* request = m_queue.dequeue(); - //kDebug() << "Processing request #" << request->id() << " with size " << request->size(); - - // unlock after queue utilization - m_queueMutex.unlock(); - - //FIXME: Fake completed signals! - emit completed( 5 ); - request->load(); - emit completed( 55 ); - request->mergeChangeLog(); - emit completed( 100 ); - - /* - if( !request->done() ) { - QMutexLocker lock( &m_processMutex ); - m_processes[ request->id() ] = request; - } - - disconnect( request, SIGNAL(completed(int)), - this, SIGNAL(completed(int)) ); - disconnect( request, SIGNAL(multipleMerge(QString,QString)), - this, SIGNAL(multipleMerge(QString,QString)) ); - - if( request->done() ){*/ - - foreach( const Soprano::Statement & st, request->multipleMergers() ) { - emit multipleMerge( st.subject().uri().toString(), - st.predicate().uri().toString() ); - } - - m_processes.remove( request->id() ); - delete request; - - m_queueMutex.lock(); - } - - kDebug() << "Waiting..."; - m_queueWaiter.wait( &m_queueMutex ); - m_queueMutex.unlock(); - kDebug() << "Woke up."; - } -} - - -#include "merger.moc" diff --git a/nepomuk/services/backupsync/service/merger.h b/nepomuk/services/backupsync/service/merger.h deleted file mode 100644 index feb20a5..0000000 --- a/nepomuk/services/backupsync/service/merger.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef MERGETHREAD_H -#define MERGETHREAD_H - -#include <QtCore/QThread> -#include <QtCore/QMutex> -#include <QtCore/QWaitCondition> -#include <QtCore/QUrl> -#include <QtCore/QQueue> - -#include "changelogmerger.h" - -namespace Soprano { - class Model; -} - -namespace Nepomuk { - - class ResourceManager; - class ChangeLog; - - class Merger : public QThread - { - Q_OBJECT - - public : - Merger( QObject* parent = 0 ); - virtual ~Merger(); - - void stop(); - - public Q_SLOTS: - int process( const Nepomuk::ChangeLog & logFile ); - - Q_SIGNALS: - void completed( int percent ); - void multipleMerge( const QString & uri, const QString & prop ); - - protected: - virtual void run(); - - private: - QQueue<ChangeLogMerger*> m_queue; - QMutex m_queueMutex; - QWaitCondition m_queueWaiter; - - bool m_stopped; - - QHash<int, ChangeLogMerger*> m_processes; - QMutex m_processMutex; - }; - -} -#endif // MERGETHREAD_H diff --git a/nepomuk/services/backupsync/service/nepomukbackupsync.desktop b/nepomuk/services/backupsync/service/nepomukbackupsync.desktop index 297b893..2562bb3 100644 --- a/nepomuk/services/backupsync/service/nepomukbackupsync.desktop +++ b/nepomuk/services/backupsync/service/nepomukbackupsync.desktop @@ -7,6 +7,7 @@ X-KDE-Nepomuk-start-on-demand=false X-KDE-Nepomuk-dependencies=nepomukstorage Name=Nepomuk Backup and Sync Name[bg]=Модул за архивиране и синхронизиране Nepomuk +Name[bs]=Rezerva i sinhronizacija Nepomuka Name[ca]=Còpia de seguretat i sincronització del Nepomuk Name[ca@valencia]=Còpia de seguretat i sincronització del Nepomuk Name[cs]=Zálohování a synchronizace Nepomuku @@ -23,6 +24,7 @@ Name[he]=Nepomuk גיבויים וסינכרונים Name[hr]=Nepomukova sigurnosna kopija i sinkronizacija Name[hu]=Nepomuk mentés és szinkronizálás Name[ia]=Retrocopia (Backup) e Sync de Nepomuk +Name[is]=Nepomuk öryggisafritun og samstilling Name[it]=Copia di sicurezza e sincronizzazione di Nepomuk Name[ja]=Nepomuk バックアップと同期 Name[kk]=Nepomuk сақтық көшірмелеу және қадамдастыру @@ -30,9 +32,11 @@ Name[km]=ការធ្វើសមកាលកម្ម និងប Name[kn]=ನೆಪೋಮುಕ್ ಬ್ಯಾಕ್ಅಪ್ ಹಾಗು ಸಿಂಕ್ Name[ko]=Nepomuk 백업 및 동기화 Name[lt]=Nepomuk atsarginė kopija ir sinchronizacija +Name[lv]=Nepomuk rezerves kopiju veidošana un sinhronizācija Name[nb]=Nepomuk sikringskopi og synkronisering Name[nds]=Nepomuk-Sekerheitkopie un Synkroniseren Name[nl]=Nepomuk reservekopie-synchronisatie +Name[nn]=Reservekopi og synkronisering av Nepomuk Name[pa]=ਨਿਪੋਮੁਕ ਬੈਕਅੱਪ ਅਤੇ ਸਿੰਕਨਿਪੋਮੁਕ ਬੈਕਅਨਿਪੋਮੁਕ ਬੈਕਅੱਤ Name[pl]=Kopia zapasowa i synchronizacja Nepomuka Name[pt]=Salvaguarda e Sincronização do Nepomuk @@ -48,11 +52,14 @@ Name[sr@latin]=Rezerva i sinhronizacija Nepomuka Name[sv]=Nepomuk säkerhetskopiering och synkronisering Name[th]=การสำรองและปรับเทียบข้อมูลของ Nepomuk Name[tr]=Nepomuk Yedekleme ve Eşzamanlama +Name[ug]=Nepomuk زاپاسلاش ۋە قەدەمداش Name[uk]=Створення резервних копій і синхронізація Nepomuk Name[x-test]=xxNepomuk Backup and Syncxx +Name[zh_CN]=Nepomuk 备份和同步 Name[zh_TW]=Nepomuk 備份與同步 Comment=Nepomuk Service which handles backup and sync. Comment[bg]=Услуга на Nepomuk, която се грижи за архивиране и синхронизиране +Comment[bs]=Servis Nepomuka za pravljenje rezervi i sinhronizaciju. Comment[ca]=Servei del Nepomuk que gestiona còpies de seguretat i sincronització. Comment[ca@valencia]=Servei del Nepomuk que gestiona còpies de seguretat i sincronització. Comment[cs]=Služba Nepomuku pro zálohu a synchronizaci. @@ -69,15 +76,18 @@ Comment[he]=שירות של Nepomuk המטפל בגיבוי וסינכרון. Comment[hr]=Nepomukov servis koji upravlja sigurnosnim kopijama i sinkronizacijom. Comment[hu]=Biztonsági mentéseket és szinkronizálást kezelő Nepomuk szolgáltatás. Comment[ia]=Servicio de Nepomuk que manea retrocopia e sync. +Comment[is]=Nepomuk þjónusta sem sér um öryggisafritun og samstillingu Comment[it]=Servizio di Nepomuk che gestisce le copie di sicurezza e la sincronizzazione. Comment[ja]=バックアップと同期を行う Nepomuk サービス Comment[kk]=Cақтық көшірмелеу және қадамдастыруды басқаратын Nepomuk қызметі. Comment[km]=សេវា Nepomuk ដែលគ្រប់គ្រងការធ្វើសមកាលកម្ម និងបម្រុងទុក Comment[ko]=백업과 동기화를 담당하는 Nepomuk 서비스입니다. Comment[lt]=Nepamok tarnyba, valdanti atsargines kopijas ir sinchronizavimą +Comment[lv]=Nepomuk serviss, kurš vada rezerves kopiju veidošanu un sinhronizāciju. Comment[nb]=Nepomuk-tjeneste som håndterer sikringskopi og synkronisering. Comment[nds]=Nepomuk-Deenst för Sekerheitkopien un Synkroniseren. Comment[nl]=Nepomuk service die reservekopie-synchronisatie afhandelt. +Comment[nn]=Nepomuk-teneste som handterer reservekopiering og synkronisering. Comment[pa]=ਨਿਪੋਮੁਕ ਸਰਵਿਸ, ਜੋ ਕਿ ਬੈਕਅੱਪ ਤੇ ਸਿੰਕ ਨੂੰ ਹੈਂਡਸ ਕਰਦੀ ਹੈ। Comment[pl]=Usługa Nepomuka, która zajmuje się kopiami zapasowymi i synchronizacją. Comment[pt]=O serviço do Nepomuk que lida com a salvaguarda e a sincronização dos dados. @@ -93,6 +103,8 @@ Comment[sr@latin]=Servis Nepomuka za pravljenje rezervi i sinhronizaciju. Comment[sv]=Nepomuk-tjänst som hanterar säkerhetskopiering och synkronisering. Comment[th]=บริการ Nepomuk ที่ทำงานเกี่ยวกับการสำรองและปรับเทียบข้อมูล Comment[tr]=Yedekleme ve eşzamanlama yapan Nepomuk servisi +Comment[ug]=زاپاسلاش ۋە قەدەمداشنى بىر تەرەپ قىلىدىغان Nepomuk مۇلازىمىتى Comment[uk]=Служба Nepomuk, призначена для створення резервних копій і синхронізації. Comment[x-test]=xxNepomuk Service which handles backup and sync.xx +Comment[zh_CN]=Nepomuk 处理备份和同步的服务。 Comment[zh_TW]=處理備份與同步的 Nepomuk 服務。 diff --git a/nepomuk/services/backupsync/service/resourcelog.cpp b/nepomuk/services/backupsync/service/resourcelog.cpp deleted file mode 100644 index a24d3f0..0000000 --- a/nepomuk/services/backupsync/service/resourcelog.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "resourcelog.h" -#include "changelog.h" -#include "changelogrecord.h" - -#include <QtCore/QMultiHash> - -#include <Nepomuk/Types/Property> -#include <algorithm> - - -Nepomuk::ResourceLogMap Nepomuk::ResourceLogMap::fromChangeLogRecordList(const QList<ChangeLogRecord>& records) -{ - ResourceLogMap hash; - - // - // Organize according to resource uri - // - QMultiHash<KUrl, ChangeLogRecord> multiHash; - foreach( const ChangeLogRecord &r, records ) { - multiHash.insert( r.st().subject().uri(), r ); - } - - // - // Convert to MergeHash - // - const QList<KUrl> & keys = multiHash.uniqueKeys(); - foreach( const KUrl & key, keys ) { - ResourceLog ms; - ms.uri = key; - - const QList<ChangeLogRecord>& records = multiHash.values( key ); - foreach( const ChangeLogRecord & r, records ) { - const KUrl &pred = r.st().predicate().uri(); - ms.prop.insert( pred, r ); - } - hash.insert( ms.uri, ms ); - } - - return hash; -} - - -Nepomuk::ResourceLogMap Nepomuk::ResourceLogMap::fromChangeLog(const Nepomuk::ChangeLog& log) -{ - return fromChangeLogRecordList( log.toList() ); -} - - -namespace { - - Nepomuk::ChangeLogRecord maxRecord( const QList<Nepomuk::ChangeLogRecord> & records ) { - QList<Nepomuk::ChangeLogRecord>::const_iterator it = std::max_element( records.begin(), records.end() ); - if( it != records.constEnd() ) - return *it; - return Nepomuk::ChangeLogRecord(); - } - - typedef QHash<Soprano::Node, Nepomuk::ChangeLogRecord> OptimizeHash; - -} - - -void Nepomuk::ResourceLogMap::optimize() -{ - QMutableHashIterator<KUrl, ResourceLog> it( *this ); - while( it.hasNext() ) { - it.next(); - - ResourceLog & log = it.value(); - - const QList<KUrl> & properties = log.prop.uniqueKeys(); - foreach( const KUrl & propUri, properties ) { - QList<ChangeLogRecord> records = log.prop.values( propUri ); - - Types::Property property( propUri ); - int maxCard = property.maxCardinality(); - - if( maxCard == 1 ) { - ChangeLogRecord max = maxRecord( records ); - records.clear(); - records.append( max ); - } - else { - // The records have to be sorted by timeStamp in order to optimize them - qSort( records ); - - OptimizeHash hash; - foreach( const ChangeLogRecord & record, records ) { - if( record.added() ) { - OptimizeHash::const_iterator iter = hash.constFind( record.st().object() ); - if( iter != hash.constEnd() ){ - if( !iter.value().added() ) { - hash.remove( record.st().object() ); - } - } - else { - hash.insert( record.st().object(), record ); - } - } - else { - OptimizeHash::const_iterator iter = hash.constFind( record.st().object() ); - if( iter != hash.constEnd() ) { - if( iter.value().added() ) { - hash.remove( record.st().object() ); - } - } - else - hash.insert( record.st().object(), record ); - } - } - records = hash.values(); - qSort( records ); - - // Update the log - log.prop.remove( propUri ); - foreach( const ChangeLogRecord & record, records ) - log.prop.insert( propUri, record ); - } - } - - } -} diff --git a/nepomuk/services/backupsync/service/resourcelog.h b/nepomuk/services/backupsync/service/resourcelog.h deleted file mode 100644 index fde6dc5..0000000 --- a/nepomuk/services/backupsync/service/resourcelog.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - - -#ifndef RESOURCELOG_H -#define RESOURCELOG_H - -#include <QtCore/QUrl> -#include <QtCore/QMultiHash> - -#include <KUrl> - -#include "changelog.h" -#include "changelogrecord.h" - -namespace Nepomuk { - - - class ResourceLog - { - public: - KUrl uri; - QMultiHash<KUrl, ChangeLogRecord> prop; - }; - - class ResourceLogMap : public QHash<KUrl, ResourceLog> { - public: - static ResourceLogMap fromChangeLogRecordList( const QList<ChangeLogRecord> & records ); - static ResourceLogMap fromChangeLog( const Nepomuk::ChangeLog& log ); - - void optimize(); - }; - -} -#endif // RESOURCELOG_H diff --git a/nepomuk/services/backupsync/service/syncfile.cpp b/nepomuk/services/backupsync/service/syncfile.cpp index c0a08d7..a0d3e66 100644 --- a/nepomuk/services/backupsync/service/syncfile.cpp +++ b/nepomuk/services/backupsync/service/syncfile.cpp @@ -129,6 +129,7 @@ bool Nepomuk::SyncFile::load(const QUrl& syncFile) bool Nepomuk::SyncFile::save( const QUrl& outFile ) { + kDebug() << "Saving at " << outFile; KTempDir tempDir; QUrl logFileUrl( tempDir.name() + "changelog" ); @@ -137,15 +138,21 @@ bool Nepomuk::SyncFile::save( const QUrl& outFile ) QUrl identFileUrl( tempDir.name() + "identificationset" ); d->m_identificationSet.save( identFileUrl ); + return createSyncFile( logFileUrl, identFileUrl, outFile ); +} + +// static +bool Nepomuk::SyncFile::createSyncFile(const QUrl& logfile, const QUrl& identFile, const QUrl & outFile) +{ KTar tarFile( outFile.toString(), QString::fromLatin1("application/x-gzip") ); if( !tarFile.open( QIODevice::WriteOnly ) ) { kWarning() << "File could not be opened : " << outFile.path(); return false; } - - tarFile.addLocalFile( logFileUrl.path(), "changelog" ); - tarFile.addLocalFile( identFileUrl.path(), "identificationset" ); - + + tarFile.addLocalFile( logfile.path(), "changelog" ); + tarFile.addLocalFile( identFile.path(), "identificationset" ); + return true; } diff --git a/nepomuk/services/backupsync/service/syncfile.h b/nepomuk/services/backupsync/service/syncfile.h index 49f58fe..4363c7c 100644 --- a/nepomuk/services/backupsync/service/syncfile.h +++ b/nepomuk/services/backupsync/service/syncfile.h @@ -115,6 +115,7 @@ namespace Nepomuk { const ChangeLog& changeLog() const; const IdentificationSet & identificationSet() const; + static bool createSyncFile( const QUrl& logfile, const QUrl& identFile, const QUrl& outFile ); private: class Private; Private * d; diff --git a/nepomuk/services/backupsync/service/syncfileidentifier.cpp b/nepomuk/services/backupsync/service/syncfileidentifier.cpp deleted file mode 100644 index a8a60f8..0000000 --- a/nepomuk/services/backupsync/service/syncfileidentifier.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "syncfileidentifier.h" -#include "changelogrecord.h" -#include "simpleresource.h" - -#include <QtCore/QFile> -#include <QtCore/QDir> - -#include <KDebug> - -#include <Nepomuk/Resource> -#include <Nepomuk/Variant> -#include <Nepomuk/Vocabulary/NIE> - -int Nepomuk::SyncFileIdentifier::NextId = 0; - -Nepomuk::SyncFileIdentifier::SyncFileIdentifier(const Nepomuk::SyncFile& sf) - : ResourceIdentifier() -{ - m_id = NextId++; - - m_changeLog = sf.changeLog(); - m_identificationSet = sf.identificationSet(); - - //kDebug() << "Identification Set - "; - //kDebug() << m_identificationSet.toList(); -} - -Nepomuk::SyncFileIdentifier::~SyncFileIdentifier() -{ - // What do we do over here? -} - -namespace { - - // - // Removes the old home directory and replaces it with the current one - // TODO: Make it OS independent - // - QUrl translateHomeUri( const QUrl & uri ) { - QString uriString = uri.toString(); - - QRegExp regEx("^file://(/home/[^/]*)(/.*)$"); - if( regEx.exactMatch( uriString ) ) { - QString newUriString = "file://" + QDir::homePath() + regEx.cap(2); - - uriString.replace( regEx, newUriString ); - return QUrl( newUriString ); - } - return uri; - } -} - -void Nepomuk::SyncFileIdentifier::load() -{ - if( unidentified().size() > 0 ) - return; - - // - // Translate all file:// uri so that the home dir is correct. - // - QList<Soprano::Statement> identList = m_identificationSet.toList(); - QMutableListIterator<Soprano::Statement> it( identList ); - while( it.hasNext() ) { - it.next(); - - Soprano::Statement & st = it.value(); - if( st.object().isResource() && st.object().uri().scheme() == QLatin1String("file") ) - st.setObject( Soprano::Node( translateHomeUri( st.object().uri() ) ) ); - } - - //kDebug() << "After translation : "; - //kDebug() << identList; - - addStatements( identList ); -} - - -Nepomuk::ChangeLog Nepomuk::SyncFileIdentifier::convertedChangeLog() -{ - QList<ChangeLogRecord> masterLogRecords = m_changeLog.toList(); - kDebug() << "masterLogRecords : " << masterLogRecords.size(); - - QList<ChangeLogRecord> identifiedRecords; - QMutableListIterator<ChangeLogRecord> it( masterLogRecords ); - - while( it.hasNext() ) { - ChangeLogRecord r = it.next(); - - // Identify Subject - KUrl subUri = r.st().subject().uri(); - if( subUri.scheme() == QLatin1String("nepomuk") ) { - KUrl newUri = mappedUri( subUri ); - if( newUri.isEmpty() ) - continue; - - r.setSubject( newUri ); - } - - // Identify object - if( r.st().object().isResource() ) { - KUrl objUri = r.st().object().uri(); - if( objUri.scheme() == QLatin1String("nepomuk") ) { - KUrl newUri = mappedUri( objUri ); - if( newUri.isEmpty() ) - continue; - - r.setObject( newUri ); - } - } - - identifiedRecords.push_back( r ); - - // Remove the statement from the masterchangerecords - it.remove(); - } - - // Update the master change log - m_changeLog = ChangeLog::fromList( masterLogRecords ); - - return ChangeLog::fromList( identifiedRecords ); -} - -void Nepomuk::SyncFileIdentifier::identifyAll() -{ - Nepomuk::Sync::ResourceIdentifier::identifyAll(); -} - -int Nepomuk::SyncFileIdentifier::id() -{ - return m_id; -} - - -Nepomuk::Resource Nepomuk::SyncFileIdentifier::createNewResource(const Sync::SimpleResource & simpleRes) const -{ - kDebug(); - Nepomuk::Resource res; - - if( simpleRes.isFileDataObject() ) { - res = Nepomuk::Resource( simpleRes.nieUrl() ); - if( res.exists() ) { - // If the resource already exists. We should not create it. This is to avoid the bug where - // a different file with the same nie:url exists. If it was the same file, identification - // should have found it. If it hasn't, well tough luck. No other option but to manually - // identify - return Resource(); - } - } - - const QList<KUrl> & keys = simpleRes.uniqueKeys(); - foreach( const KUrl & prop, keys ) { - //kDebug() << "Prop " << prop; - - const QList<Soprano::Node> nodeList = simpleRes.values( prop ); - res.setProperty( prop, Nepomuk::Variant::fromNodeList( nodeList ) ); - } - return res.resourceUri(); -} - -Nepomuk::Resource Nepomuk::SyncFileIdentifier::additionalIdentification(const KUrl& uri) -{ - Sync::SimpleResource res = simpleResource( uri ); - - // Add the resource if ( it is NOT a FileDataObject ) or ( if is a FileDataObject and - // exists in the filesystem at the nie:url ) - bool shouldAdd = !res.isFileDataObject(); - if( res.isFileDataObject() ) { - if( QFile::exists( res.nieUrl().toLocalFile() ) ) - shouldAdd = true; - } - - if( shouldAdd ) { - Nepomuk::Resource newRes = createNewResource( res ); - if( newRes.isValid() ) - forceResource( uri, newRes ); - else - return Resource(); - } - - return Resource(); -} diff --git a/nepomuk/services/backupsync/service/syncfileidentifier.h b/nepomuk/services/backupsync/service/syncfileidentifier.h deleted file mode 100644 index d913b04..0000000 --- a/nepomuk/services/backupsync/service/syncfileidentifier.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - - -#ifndef SYNCFILEIDENTIFIER_H -#define SYNCFILEIDENTIFIER_H - -#include "resourceidentifier.h" -#include "changelog.h" -#include "identificationset.h" -#include "syncfile.h" - -namespace Nepomuk { - - class SyncFileIdentifier : public Sync::ResourceIdentifier - { - public: - SyncFileIdentifier( const SyncFile & sf ); - virtual ~SyncFileIdentifier(); - - virtual void identifyAll(); - int id(); - - ChangeLog convertedChangeLog(); - void load(); - - protected: - virtual Resource additionalIdentification(const KUrl& uri); - - private: - ChangeLog m_changeLog; - IdentificationSet m_identificationSet; - - static int NextId; - int m_id; - - Resource createNewResource(const Nepomuk::Sync::SimpleResource& simpleRes) const; - - }; -} - -#endif // SYNCFILEIDENTIFIER_H diff --git a/nepomuk/services/backupsync/service/syncmanager.cpp b/nepomuk/services/backupsync/service/syncmanager.cpp index 95865ed..99eae53 100644 --- a/nepomuk/services/backupsync/service/syncmanager.cpp +++ b/nepomuk/services/backupsync/service/syncmanager.cpp @@ -20,7 +20,6 @@ */ #include "syncmanager.h" -#include "identifier.h" #include "syncfile.h" #include "changelog.h" #include "diffgenerator.h" @@ -35,9 +34,8 @@ #include <Nepomuk/Resource> #include <Nepomuk/Variant> -Nepomuk::SyncManager::SyncManager(Nepomuk::Identifier* ident, QObject* parent) - : QObject( parent ), - m_identifier( ident ) +Nepomuk::SyncManager::SyncManager(QObject* parent) + : QObject( parent ) { new SyncManagerAdaptor( this ); // Register DBus Object @@ -50,20 +48,6 @@ Nepomuk::SyncManager::~SyncManager() { } - -int Nepomuk::SyncManager::sync(const QString& url) -{ - return sync( QUrl( url ) ); -} - - -int Nepomuk::SyncManager::sync(const QUrl& url) -{ - SyncFile syncFile( url ); - return m_identifier->process( syncFile ); -} - - void Nepomuk::SyncManager::createSyncFile( const QString& url, const QString& startTime ) { ChangeLog logFile = LogStorage::instance()->getChangeLog( startTime ); @@ -116,6 +100,7 @@ void Nepomuk::SyncManager::createSyncFile(const QUrl& outputUrl, QSet<QUrl> & ne void Nepomuk::SyncManager::createFirstSyncFile(const QUrl& outputUrl) const { + Q_UNUSED(outputUrl); //SyncFile sf = Nepomuk::createFirstSyncFile(); //sf.save( outputUrl ); } diff --git a/nepomuk/services/backupsync/service/syncmanager.h b/nepomuk/services/backupsync/service/syncmanager.h index f6b2cbb..93fb7db 100644 --- a/nepomuk/services/backupsync/service/syncmanager.h +++ b/nepomuk/services/backupsync/service/syncmanager.h @@ -28,29 +28,24 @@ namespace Nepomuk { - class Identifier; class SyncFile; - + class SyncManager : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.nepomuk.services.nepomukbackupsync.SyncManager") - + public : - SyncManager(Identifier * ident, QObject * parent = 0); + SyncManager(QObject * parent = 0); virtual ~SyncManager(); public slots: - int sync( const QString & url ); - int sync( const QUrl & url ); - void createSyncFile( const QString & url, const QString& startTime ); void createSyncFile( const QUrl& outputUrl, const QList<QString> & nieUrls ); void createSyncFile( const QUrl& outputUrl, QSet<QUrl>& nepomukUris, const QDateTime & min ); void createFirstSyncFile( const QUrl& outputUrl ) const; private : - Identifier * m_identifier; }; } diff --git a/nepomuk/services/backupsync/service/tools.cpp b/nepomuk/services/backupsync/service/tools.cpp index 74517ec..317faf7 100644 --- a/nepomuk/services/backupsync/service/tools.cpp +++ b/nepomuk/services/backupsync/service/tools.cpp @@ -33,28 +33,37 @@ #include <KTemporaryFile> #include <KDebug> +#include "identificationset.h" -void Nepomuk::saveBackupChangeLog(const QUrl& url) +int Nepomuk::saveBackupChangeLog(const QUrl& url, QSet<QUrl> & uniqueUris ) { - const int step = 100; - - ChangeLog changeLog; - - const QString query = QString::fromLatin1("select ?r ?p ?o ?g where { graph ?g { ?r ?p ?o. } ?g a nrl:InstanceBase . FILTER(!bif:exists( ( select (1) where { ?g a nrl:DiscardableInstanceBase . } ) )) . }"); - + const int step = 1000; + const QString query = QString::fromLatin1("select ?r ?p ?o ?g where { " + "graph ?g { ?r ?p ?o. } " + "?g a nrl:InstanceBase . " + "FILTER(!bif:exists( ( select (1) where { ?g a nrl:DiscardableInstanceBase . } ) )) ." + "FILTER(regex(str(?r), '^nepomuk:/res/')). " + "}"); + Soprano::Model * model = Nepomuk::ResourceManager::instance()->mainModel(); - Soprano::QueryResultIterator iter= model->executeQuery( query, Soprano::Query::QueryLanguageSparql ); - - int i=0; + + int totalNumRecords = 0; + int i = 0; + ChangeLog changeLog; while( iter.next() ) { Soprano::Statement st( iter["r"], iter["p"], iter["o"], iter["g"] ); - //kDebug() << st; + changeLog += ChangeLogRecord( st ); + totalNumRecords++; + + uniqueUris.insert( st.subject().uri() ); + if( st.object().isResource() && st.object().uri().scheme() == QLatin1String("nepomuk") ) + uniqueUris.insert( st.object().uri() ); if( ++i >= step ) { - //kDebug() << "Saving .. " << changeLog.size(); + kDebug() << "Saving .. " << changeLog.size(); changeLog.save( url ); changeLog.clear(); i = 0; @@ -62,27 +71,23 @@ void Nepomuk::saveBackupChangeLog(const QUrl& url) } changeLog.save( url ); + kDebug() << "Total Records : " << totalNumRecords; + return totalNumRecords; } -//TODO: This doesn't really solve the problem that the backup maybe huge and -// large parts of the memory may get used. The proper solution would be -// to re-implement ChangeLog and IdentSet so that they don't load everything -// in one go. - bool Nepomuk::saveBackupSyncFile(const QUrl& url) { - KTemporaryFile file; - file.open(); + kDebug() << url; + KTemporaryFile logFile; + logFile.open(); - saveBackupChangeLog( file.fileName() ); - ChangeLog log = ChangeLog::fromUrl( file.fileName() ); - kDebug() << "Log size: " << log.size(); + QSet<QUrl> uniqueUris; + saveBackupChangeLog( logFile.fileName(), uniqueUris ); - if( log.empty() ) { - kDebug() << "Nothing to save.."; - return false; - } - - SyncFile syncFile( log ); - return syncFile.save( url ); + KTemporaryFile identificationFile; + identificationFile.open(); + const QUrl identUrl( identificationFile.fileName() ); + + IdentificationSet::createIdentificationSet( uniqueUris, identUrl ); + return SyncFile::createSyncFile( logFile.fileName(), identUrl, url ); } diff --git a/nepomuk/services/backupsync/service/tools.h b/nepomuk/services/backupsync/service/tools.h index 98164ef..c35e002 100644 --- a/nepomuk/services/backupsync/service/tools.h +++ b/nepomuk/services/backupsync/service/tools.h @@ -36,8 +36,12 @@ namespace Nepomuk { /** * Saves a changeLog with the list of all the statements that should be backed up. * It's useful in when doing a first sync or first backup. + * + * \param uniqueUris After execution, it will contain a list of unique uris + * + * Returns the numbers of records in the changelog */ - void saveBackupChangeLog( const QUrl& url ); + int saveBackupChangeLog( const QUrl& url, QSet<QUrl> & uniqueUris ); bool saveBackupSyncFile( const QUrl& url ); } diff --git a/nepomuk/services/filewatch/CMakeLists.txt b/nepomuk/services/filewatch/CMakeLists.txt index 753f621..d480b3c 100644 --- a/nepomuk/services/filewatch/CMakeLists.txt +++ b/nepomuk/services/filewatch/CMakeLists.txt @@ -10,6 +10,7 @@ include_directories( ${SOPRANO_INCLUDE_DIR} ${NEPOMUK_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../strigi + ${CMAKE_CURRENT_SOURCE_DIR}/../storage/lib/ ) set(SRCS @@ -18,10 +19,10 @@ set(SRCS updaterequest.cpp invalidfileresourcecleaner.cpp ../strigi/strigiserviceconfig.cpp + removabledeviceindexnotification.cpp ) qt4_add_dbus_interface(SRCS ../../interfaces/org.kde.nepomuk.Strigi.xml strigiserviceinterface) -qt4_add_dbus_interface(SRCS ../../interfaces/org.kde.nepomuk.RemovableStorage.xml removablestorageserviceinterface) if(CMAKE_SYSTEM_NAME MATCHES "Linux") set(SRCS @@ -35,10 +36,12 @@ kde4_add_plugin(nepomukfilewatch ${SRCS}) target_link_libraries(nepomukfilewatch nepomukcommon + nepomukdatamanagement ${SOPRANO_CLIENT_LIBRARIES} ${SOPRANO_LIBRARIES} ${KDE4_KDEUI_LIBS} ${KDE4_KIO_LIBS} + ${KDE4_SOLID_LIBS} ${NEPOMUK_LIBRARIES} ${NEPOMUK_QUERY_LIBRARIES} ) @@ -49,5 +52,8 @@ install( install( TARGETS nepomukfilewatch DESTINATION ${PLUGIN_INSTALL_DIR}) +install( + FILES nepomukfilewatch.notifyrc + DESTINATION ${DATA_INSTALL_DIR}/nepomukfilewatch) add_subdirectory(test) diff --git a/nepomuk/services/filewatch/invalidfileresourcecleaner.cpp b/nepomuk/services/filewatch/invalidfileresourcecleaner.cpp index f1e0478..a4bb346 100644 --- a/nepomuk/services/filewatch/invalidfileresourcecleaner.cpp +++ b/nepomuk/services/filewatch/invalidfileresourcecleaner.cpp @@ -23,6 +23,7 @@ #include <QtCore/QList> #include <QtCore/QFile> +#include <QtCore/QTimer> #include <Soprano/Node> #include <Soprano/Model> @@ -52,40 +53,31 @@ Nepomuk::InvalidFileResourceCleaner::~InvalidFileResourceCleaner() wait(); } -namespace { - - /* BUG: This is a workaround to "Removable Storage" or "Network Storage" bug, where the metadata of all - * the files in the NAS would be deleted if the NAS was unmounted and Nepomuk was restarted. - * - * FIXME: This is NOT a permanent fix and should be removed in 4.7 - */ - QString deletionBlacklistFilter() { - KConfig config("nepomukserverrc"); - QStringList directories = config.group("Service-nepomukfilewatch").readPathEntry("Deletion Blacklist", QStringList()); - - QString filter; - Q_FOREACH( const QString & dir, directories ) { - filter += QString::fromLatin1( " && !regex(str(?url), '^%1') " ) - .arg( KUrl(dir).url( KUrl::AddTrailingSlash ) ); - } - return filter; - } -} void Nepomuk::InvalidFileResourceCleaner::run() { kDebug() << "Searching for invalid local file entries"; +#ifndef NDEBUG + QTime timer; + timer.start(); +#endif // // Since the removal of the graphs could intefere with the iterator and result - // in jumping of rows (worst case) we cache all graphs to remove + // in jumping of rows (worst case) we cache all resources to remove // QList<Soprano::Node> resourcesToRemove; - QString query = QString::fromLatin1( "select distinct ?r ?url where { " - "?r %1 ?url. " - "FILTER(regex(str(?url), 'file://') %2 ). }" ) - .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), - deletionBlacklistFilter() ); - Soprano::QueryResultIterator it = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( query, Soprano::Query::QueryLanguageSparql ); + QString basePathFilter; + if(!m_basePath.isEmpty()) { + basePathFilter = QString::fromLatin1("FILTER(REGEX(STR(?url), '^%1')) . ") + .arg(KUrl(m_basePath).url(KUrl::AddTrailingSlash)); + } + const QString query + = QString::fromLatin1( "select distinct ?r ?url where { " + "?r %1 ?url . %2}" ) + .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), + basePathFilter ); + Soprano::QueryResultIterator it + = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( query, Soprano::Query::QueryLanguageSparql ); while( it.next() && !m_stopped ) { QUrl url( it["url"].uri() ); @@ -100,10 +92,20 @@ void Nepomuk::InvalidFileResourceCleaner::run() Q_FOREACH( const Soprano::Node& r, resourcesToRemove ) { if ( m_stopped ) break; + // TODO: use DMS here instead of Soprano Nepomuk::ResourceManager::instance()->mainModel()->removeAllStatements( r, Soprano::Node(), Soprano::Node() ); Nepomuk::ResourceManager::instance()->mainModel()->removeAllStatements( Soprano::Node(), Soprano::Node(), r ); } kDebug() << "Done searching for invalid local file entries"; +#ifndef NDEBUG + kDebug() << "Time elapsed: " << timer.elapsed()/1000.0 << "sec"; +#endif +} + +void Nepomuk::InvalidFileResourceCleaner::start(const QString &basePath) +{ + m_basePath = basePath; + start(); } #include "invalidfileresourcecleaner.moc" diff --git a/nepomuk/services/filewatch/invalidfileresourcecleaner.h b/nepomuk/services/filewatch/invalidfileresourcecleaner.h index 61e76e8..b3501bd 100644 --- a/nepomuk/services/filewatch/invalidfileresourcecleaner.h +++ b/nepomuk/services/filewatch/invalidfileresourcecleaner.h @@ -53,10 +53,19 @@ namespace Nepomuk { */ ~InvalidFileResourceCleaner(); + /** + * Start the cleaner thread with a base path, ie. only + * check files under that optional path. + */ + void start(const QString& basePath); + + using QThread::start; + private: void run(); bool m_stopped; + QString m_basePath; }; } diff --git a/nepomuk/services/filewatch/metadatamover.cpp b/nepomuk/services/filewatch/metadatamover.cpp index b1ca616..8db2ecf 100644 --- a/nepomuk/services/filewatch/metadatamover.cpp +++ b/nepomuk/services/filewatch/metadatamover.cpp @@ -18,31 +18,16 @@ #include "metadatamover.h" #include "nepomukfilewatch.h" +#include "datamanagement.h" -#include <QtCore/QDir> -#include <QtCore/QRegExp> -#include <QtCore/QFileInfo> #include <QtCore/QTimer> #include <Soprano/Model> -#include <Soprano/StatementIterator> -#include <Soprano/Statement> #include <Soprano/Node> #include <Soprano/NodeIterator> #include <Soprano/QueryResultIterator> #include <Soprano/LiteralValue> -#include <Soprano/Vocabulary/Xesam> - -#include <Nepomuk/Resource> -#include <Nepomuk/ResourceManager> -#include <Nepomuk/Variant> -#include <Nepomuk/Types/Property> -#include <Nepomuk/Query/Query> -#include <Nepomuk/Query/ComparisonTerm> -#include <Nepomuk/Query/ResourceTerm> -#include <Nepomuk/Query/LiteralTerm> - -#include <Nepomuk/Vocabulary/NFO> + #include <Nepomuk/Vocabulary/NIE> #include <KDebug> @@ -51,8 +36,7 @@ Nepomuk::MetadataMover::MetadataMover( Soprano::Model* model, QObject* parent ) : QThread( parent ), m_stopped(false), - m_model( model ), - m_strigiParentUrlUri( "http://strigi.sf.net/ontologies/0.9#parentUrl" ) + m_model( model ) { QTimer* timer = new QTimer( this ); connect( timer, SIGNAL( timeout() ), this, SLOT( slotClearRecentlyFinishedRequests() ) ); @@ -77,6 +61,8 @@ void Nepomuk::MetadataMover::stop() void Nepomuk::MetadataMover::moveFileMetadata( const KUrl& from, const KUrl& to ) { kDebug() << from << to; + Q_ASSERT( !from.path().isEmpty() && from.path() != "/" ); + Q_ASSERT( !to.path().isEmpty() && to.path() != "/" ); m_queueMutex.lock(); UpdateRequest req( from, to ); if ( !m_updateQueue.contains( req ) && @@ -89,6 +75,7 @@ void Nepomuk::MetadataMover::moveFileMetadata( const KUrl& from, const KUrl& to void Nepomuk::MetadataMover::removeFileMetadata( const KUrl& file ) { + Q_ASSERT( !file.path().isEmpty() && file.path() != "/" ); removeFileMetadata( KUrl::List() << file ); } @@ -127,16 +114,6 @@ void Nepomuk::MetadataMover::run() kDebug() << "========================= handling" << updateRequest.source() << updateRequest.target(); - // - // IMPORTANT: clear the ResourceManager cache since otherwise the following will happen - // (see also ResourceData::invalidateCache): - // We update the nie:url for resource X. Afterwards we get a fileDeleted event for the - // old URL. That one is still in the ResourceManager cache and will bring us directly - // to resource X. As a result we will delete all its metadata. - // This is a bug in ResourceManager for which I (trueg) have not found a good solution yet. - // - ResourceManager::instance()->clearCache(); - // an empty second url means deletion if( updateRequest.target().isEmpty() ) { KUrl url = updateRequest.source(); @@ -177,12 +154,9 @@ void Nepomuk::MetadataMover::removeMetadata( const KUrl& url ) kDebug() << "empty path. Looks like a bug somewhere..."; } else { - bool isFolder = url.url().endsWith('/'); - Resource res( url ); - if ( res.exists() ) { - kDebug() << "removing metadata for file" << url << "with resource URI" << res.resourceUri(); - res.remove(); - } + const bool isFolder = url.url().endsWith('/'); + kDebug() << "removing metadata for file" << url; + Nepomuk::removeResources(QList<QUrl>() << url); if( isFolder ) { // @@ -207,15 +181,17 @@ void Nepomuk::MetadataMover::removeMetadata( const KUrl& url ) // cached items. // while ( 1 ) { - QList<Soprano::Node> urls = m_model->executeQuery( query + QLatin1String( " LIMIT 100" ), - Soprano::Query::QueryLanguageSparql ) - .iterateBindings( 0 ).allNodes(); - if ( urls.isEmpty() ) + QList<QUrl> urls; + Soprano::QueryResultIterator it = m_model->executeQuery( query + QLatin1String( " LIMIT 20" ), + Soprano::Query::QueryLanguageSparql ); + while(it.next()) { + urls << it[0].uri(); + } + if ( !urls.isEmpty() ) { + Nepomuk::removeResources(urls); + } + else { break; - - for ( int i = 0; i < urls.count(); ++i ) { - // the old URL of the resource to update - Resource::fromResourceUri( urls[i].uri() ).remove(); } } } @@ -223,93 +199,14 @@ void Nepomuk::MetadataMover::removeMetadata( const KUrl& url ) } -void Nepomuk::MetadataMover::updateMetadata( const KUrl& from, const KUrl& to, bool includeChildren ) +void Nepomuk::MetadataMover::updateMetadata( const KUrl& from, const KUrl& to ) { kDebug() << from << "->" << to; - // - // Since KDE 4.4 we use nepomuk:/res/<UUID> Uris for all resources including files. Thus, moving a file - // means updating two things: - // 1. the nie:url property - // 2. the nie:isPartOf relation (only necessary if the file and not the whole folder was moved) - // - // However, since we will still have file:/ resource URIs from old data we will also handle that - // and convert them to new nepomuk:/res/<UUID> style URIs. - // - - Resource oldResource( from ); - - if ( oldResource.exists() ) { - const QUrl oldResourceUri = oldResource.resourceUri(); - QUrl newResourceUri( oldResourceUri ); - - // - // Handle legacy file:/ resource URIs - // - if ( from.equals( oldResourceUri, KUrl::CompareWithoutTrailingSlash ) ) { - newResourceUri = updateLegacyMetadata( oldResourceUri ); - } - - // - // Now update the nie:url, nfo:fileName, and nie:isPartOf relations. - // - // We do NOT use Resource::setProperty to avoid the overhead and data clutter of creating - // new metadata graphs for the changed data. - // - // TODO: put this into a nice API which can act as the low-level version of Nepomuk::Resource - // - QString query = QString::fromLatin1( "select distinct ?g ?u ?f ?p where { " - "graph ?g { %1 ?prop ?obj . " - "OPTIONAL { %1 %2 ?u . } . " - "OPTIONAL { %1 %3 ?f . } . " - "OPTIONAL { %1 %4 ?p . } . " - "} . " - "FILTER(BOUND(?u) || BOUND(?f) || BOUND(?p)) . }" ) - .arg( Soprano::Node::resourceToN3( newResourceUri ), - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NFO::fileName() ), - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::isPartOf() ) ); - QList<Soprano::BindingSet> graphs - = m_model->executeQuery( query, Soprano::Query::QueryLanguageSparql ).allBindings(); - - Q_FOREACH( const Soprano::BindingSet& graph, graphs ) { - - if ( graph["u"].isValid() ) { - m_model->removeStatement( newResourceUri, - Nepomuk::Vocabulary::NIE::url(), - graph["u"], - graph["g"] ); - m_model->addStatement( newResourceUri, - Nepomuk::Vocabulary::NIE::url(), - to, - graph["g"] ); - } - - if ( graph["f"].isValid() ) { - m_model->removeStatement( newResourceUri, - Nepomuk::Vocabulary::NFO::fileName(), - graph["f"], - graph["g"] ); - m_model->addStatement( newResourceUri, - Nepomuk::Vocabulary::NFO::fileName(), - Soprano::LiteralValue( to.fileName() ), - graph["g"] ); - } - - if ( graph["p"].isValid() ) { - m_model->removeStatement( newResourceUri, - Nepomuk::Vocabulary::NIE::isPartOf(), - graph["p"], - graph["g"] ); - Resource newParent( to.directory( KUrl::IgnoreTrailingSlash ) ); - if ( newParent.exists() ) { - m_model->addStatement( newResourceUri, - Nepomuk::Vocabulary::NIE::isPartOf(), - newParent.resourceUri(), - graph["g"] ); - } - } - } + if ( m_model->executeQuery(QString::fromLatin1("ask where { { %1 ?p ?o . } UNION { ?r nie:url %1 . } . }") + .arg(Soprano::Node::resourceToN3(from)), + Soprano::Query::QueryLanguageSparql).boolValue() ) { + Nepomuk::setProperty(QList<QUrl>() << from, Nepomuk::Vocabulary::NIE::url(), QVariantList() << to); } else { // @@ -318,107 +215,6 @@ void Nepomuk::MetadataMover::updateMetadata( const KUrl& from, const KUrl& to, b // emit movedWithoutData( to.directory( KUrl::IgnoreTrailingSlash ) ); } - - if ( includeChildren && QFileInfo( to.toLocalFile() ).isDir() ) { - // - // Recursively update children - // We cannot use the nie:isPartOf relation since only children could have metadata. Thus, we do a regex - // match on all files and folders below the URL we got. - // - // CAUTION: The trailing slash on the from URL is essential! Otherwise we might match the newly added - // URLs, too (in case a rename only added chars to the name) - // - const QString query = QString::fromLatin1( "select distinct ?url where { " - "?r %1 ?url . " - "FILTER(REGEX(STR(?url),'^%2')) . " - "}" ) - .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), - from.url(KUrl::AddTrailingSlash) ); - kDebug() << query; - - const QString oldBasePath = from.path( KUrl::AddTrailingSlash ); - const QString newBasePath = to.path( KUrl::AddTrailingSlash ); - - // - // We cannot use one big loop since our updateMetadata calls below can change the iterator - // which could have bad effects like row skipping. Thus, we handle the urls in chunks of - // cached items. - // - while ( 1 ) { - QList<Soprano::Node> urls = m_model->executeQuery( query + QLatin1String( " LIMIT 100" ), - Soprano::Query::QueryLanguageSparql ) - .iterateBindings( 0 ).allNodes(); - if ( urls.isEmpty() ) - break; - - for ( int i = 0; i < urls.count(); ++i ) { - // the old URL of the resource to update - const KUrl url = urls[i].uri(); - - // now construct the new URL - QString oldRelativePath = url.path().mid( oldBasePath.length() ); - KUrl newUrl( newBasePath + oldRelativePath ); - - // finally update the metadata (excluding children since we already handle them all here) - updateMetadata( url, newUrl, false ); - } - } - } -} - - -QUrl Nepomuk::MetadataMover::updateLegacyMetadata( const QUrl& oldResourceUri ) -{ - kDebug() << oldResourceUri; - - // - // Get a new resource URI - // - QUrl newResourceUri = ResourceManager::instance()->generateUniqueUri( QString() ); - - // - // update the resource properties - // - QList<Soprano::Statement> sl = m_model->listStatements( oldResourceUri, - Soprano::Node(), - Soprano::Node() ).allStatements(); - Q_FOREACH( const Soprano::Statement& s, sl ) { - // - // Skip all the stuff we will update later on - // - if ( s.predicate() == Soprano::Vocabulary::Xesam::url() || - s.predicate() == Nepomuk::Vocabulary::NIE::url() || - s.predicate() == m_strigiParentUrlUri || - s.predicate() == Nepomuk::Vocabulary::NIE::isPartOf() ) { - continue; - } - - m_model->addStatement( newResourceUri, - s.predicate(), - s.object(), - s.context() ); - } - - m_model->removeStatements( sl ); - - - // - // update resources relating to it - // - sl = m_model->listStatements( Soprano::Node(), - Soprano::Node(), - oldResourceUri ).allStatements(); - Q_FOREACH( const Soprano::Statement& s, sl ) { - m_model->addStatement( s.subject(), - s.predicate(), - newResourceUri, - s.context() ); - } - m_model->removeStatements( sl ); - - kDebug() << "->" << newResourceUri; - - return newResourceUri; } diff --git a/nepomuk/services/filewatch/metadatamover.h b/nepomuk/services/filewatch/metadatamover.h index c5f5925..30a8ef7 100644 --- a/nepomuk/services/filewatch/metadatamover.h +++ b/nepomuk/services/filewatch/metadatamover.h @@ -74,20 +74,8 @@ namespace Nepomuk { /** * Recursively update the nie:url and nie:isPartOf properties * of the resource describing \p from. - * - * If old pre-KDE 4.4 file:/ resource URIs are used these are - * updated to the new nepomuk:/res/<UUID> scheme */ - void updateMetadata( const KUrl& from, const KUrl& to, bool includeChildren = true ); - - /** - * Convert old pre-KDE 4.4 style file:/ resource URIs to the - * new nepomuk:/res/<UUID> scheme. - * - * \return The new resource URI. This resource does not have - * nie:url or nie:isPartOf properties yet. - */ - QUrl updateLegacyMetadata( const QUrl& oldResourceUri ); + void updateMetadata( const KUrl& from, const KUrl& to ); // if the second url is empty, just delete the metadata QQueue<UpdateRequest> m_updateQueue; @@ -105,8 +93,6 @@ namespace Nepomuk { bool m_stopped; Soprano::Model* m_model; - - const QUrl m_strigiParentUrlUri; }; } diff --git a/nepomuk/services/filewatch/nepomukfilewatch.cpp b/nepomuk/services/filewatch/nepomukfilewatch.cpp index 2a49df5..fad7d0d 100644 --- a/nepomuk/services/filewatch/nepomukfilewatch.cpp +++ b/nepomuk/services/filewatch/nepomukfilewatch.cpp @@ -21,6 +21,8 @@ #include "strigiserviceinterface.h" #include "fileexcludefilters.h" #include "invalidfileresourcecleaner.h" +#include "removabledeviceindexnotification.h" +#include "removablemediacache.h" #include "../strigi/strigiserviceconfig.h" #ifdef BUILD_KINOTIFY @@ -34,6 +36,7 @@ #include <KDebug> #include <KUrl> #include <KPluginFactory> +#include <KConfigGroup> #include <Nepomuk/ResourceManager> #include <Nepomuk/Vocabulary/NIE> @@ -155,6 +158,12 @@ Nepomuk::FileWatch::FileWatch( QObject* parent, const QList<QVariant>& ) connectToKDirWatch(); #endif + // we automatically watch newly mounted media - it is very unlikely that anything non-interesting is mounted + m_removableMediaCache = new RemovableMediaCache(this); + connect(m_removableMediaCache, SIGNAL(deviceMounted(const Nepomuk::RemovableMediaCache::Entry*)), + this, SLOT(slotDeviceMounted(const Nepomuk::RemovableMediaCache::Entry*))); + addWatchesForMountedRemovableMedia(); + (new InvalidFileResourceCleaner(this))->start(); connect( StrigiServiceConfig::self(), SIGNAL( configChanged() ), @@ -225,13 +234,13 @@ void Nepomuk::FileWatch::slotFileDeleted( const QString& urlString, bool isDir ) void Nepomuk::FileWatch::slotFileCreated( const QString& path ) { kDebug() << path; - updateFolderViaStrigi( path ); + updateFileViaStrigi( path ); } void Nepomuk::FileWatch::slotFileModified( const QString& path ) { - updateFolderViaStrigi( path ); + updateFileViaStrigi( path ); } @@ -242,6 +251,18 @@ void Nepomuk::FileWatch::slotMovedWithoutData( const QString& path ) // static +void Nepomuk::FileWatch::updateFileViaStrigi(const QString &path) +{ + if( StrigiServiceConfig::self()->shouldBeIndexed(path) ) { + org::kde::nepomuk::Strigi strigi( "org.kde.nepomuk.services.nepomukstrigiservice", "/nepomukstrigiservice", QDBusConnection::sessionBus() ); + if ( strigi.isValid() ) { + strigi.indexFile( path ); + } + } +} + + +// static void Nepomuk::FileWatch::updateFolderViaStrigi( const QString& path ) { if( StrigiServiceConfig::self()->shouldBeIndexed(path) ) { @@ -299,4 +320,62 @@ void Nepomuk::FileWatch::updateIndexedFoldersWatches() #endif } + +void Nepomuk::FileWatch::addWatchesForMountedRemovableMedia() +{ + Q_FOREACH(const RemovableMediaCache::Entry* entry, m_removableMediaCache->allMedia()) { + if(entry->isMounted()) + slotDeviceMounted(entry); + } +} + +void Nepomuk::FileWatch::slotDeviceMounted(const Nepomuk::RemovableMediaCache::Entry* entry) +{ + // + // now that the device is mounted we can clean up our db - in case we have any + // data for file that have been deleted from the device in the meantime. + // + InvalidFileResourceCleaner* cleaner = new InvalidFileResourceCleaner(this); + cleaner->start(entry->mountPath()); + + // + // tell Strigi to update the newly mounted device + // + KConfig strigiConfig( "nepomukstrigirc" ); + int index = 0; + if(strigiConfig.group("Devices").hasKey(entry->url())) { + index = strigiConfig.group("Devices").readEntry(entry->url(), false) ? 1 : -1; + } + + const bool indexNewlyMounted = strigiConfig.group( "RemovableMedia" ).readEntry( "index newly mounted", false ); + const bool askIndividually = strigiConfig.group( "RemovableMedia" ).readEntry( "ask user", false ); + + if( index == 0 && indexNewlyMounted && !askIndividually ) { + index = 1; + } + + // index automatically + if( index == 1 ) { + kDebug() << "Device configured for automatic indexing. Calling Strigi service."; + org::kde::nepomuk::Strigi strigi( "org.kde.nepomuk.services.nepomukstrigiservice", "/nepomukstrigiservice", QDBusConnection::sessionBus() ); + if ( strigi.isValid() ) { + strigi.indexFolder( entry->mountPath(), true /* recursive */, false /* no forced update */ ); + } + } + + // ask the user if we should index + else if( index == 0 && indexNewlyMounted && askIndividually ) { + kDebug() << "Device unknown. Asking user for action."; + (new RemovableDeviceIndexNotification(entry, this))->sendEvent(); + } + + else { + // TODO: remove all the indexed info + kDebug() << "Device configured to not be indexed."; + } + + kDebug() << "Installing watch for removable storage at mount point" << entry->mountPath(); + watchFolder(entry->mountPath()); +} + #include "nepomukfilewatch.moc" diff --git a/nepomuk/services/filewatch/nepomukfilewatch.desktop b/nepomuk/services/filewatch/nepomukfilewatch.desktop index 8cc7e95..e79c0aa 100644 --- a/nepomuk/services/filewatch/nepomukfilewatch.desktop +++ b/nepomuk/services/filewatch/nepomukfilewatch.desktop @@ -11,6 +11,7 @@ Name[be@latin]=NepomukFileWatch Name[bg]=NepomukFileWatch Name[bn]=NepomukFileWatch Name[bn_IN]=NepomukFileWatch +Name[bs]=Nepomukov nadzor datoteka Name[ca]=NepomukFileWatch Name[ca@valencia]=NepomukFileWatch Name[cs]=Nepomuk - sledování souboru @@ -75,6 +76,7 @@ Name[te]=Nepomukఫైల్ వాచ్ Name[tg]=NepomukFileWatch Name[th]=NepomukFileWatch Name[tr]=NepomukFileWatch +Name[ug]=Nepomuk ھۆججەت نازارەت Name[uk]=NepomukFileWatch Name[wa]=RiwaitaedjeFitchîNepomuk Name[x-test]=xxNepomukFileWatchxx @@ -85,6 +87,7 @@ Comment[ar]=تقوم خدمة نبومك لمراقبة الملفات برصد Comment[ast]=Serviciu de monitor de ficheros de Nepomuk pa monitorizar cambeos de los ficheros Comment[be@latin]=Słužba „Nepomuk”, jakaja naziraje za źmienami ŭ fajłach Comment[bg]=Услугата NepomukFileWatch следи за промени във файловете +Comment[bs]=Nepomukov servis za nadgledanje izmjena nad datotekema Comment[ca]=El servei de control de fitxers del Nepomuk per fer un seguiment dels canvis en fitxers Comment[ca@valencia]=El servei de control de fitxers del Nepomuk per fer un seguiment dels canvis en fitxers Comment[cs]=Služba Nepomuku, která sleduje změny v souborech @@ -96,7 +99,7 @@ Comment[en_GB]=The Nepomuk file watch service for monitoring file changes Comment[eo]=La atend-servo de Nepomuk dosiero por monitora dosiero ŝanĝiĝis. Comment[es]=Servicio de monitor de archivos de Nepomuk para monitorizar cambios de los archivos Comment[et]=Nepomuki failijälgimise teenus failide muutuste jälgimiseks -Comment[eu]=Nepomuk fitxategi zaiintzailea, fitxategien aldaketak monitorizatzeko +Comment[eu]=Nepomuken fitxategiak zaintzeko zerbitzua, fitxategien aldaketak zelatatzeko Comment[fi]=Tiedostojen muutoksia tarkkaileva Nepomuk-tiedostonseurantapalvelu Comment[fr]=Le service de surveillance des fichiers, pour relever les modifications de fichiers Comment[fy]=De Nepomuk triem observaasje tsjinst foar triemferoarings @@ -121,7 +124,7 @@ Comment[ko]=파일 변화를 감시하는 Nepumuk 파일 감시 서비스 Comment[ku]=Nepomuk Servîsa nihêrîna pelê ji bo dîtina guherînan Comment[lt]=Nepomuk failų stebėjimo tarnyba failų pakeitimams sekti Comment[lv]=Nepomuk failu novērošanas serviss, kas novēro izmaiņas failos -Comment[mai]=फाइल परिवर्तनक मानिटरिंगक लेल नेपोमक फाइल निगरानी सेवा +Comment[mai]=फ़ाइल परिवर्तनक मानिटरिंगक लेल नेपोमक फ़ाइल निगरानी सेवा Comment[ml]=ഫയല് മാറ്റങ്ങള് നിരീക്ഷിക്കുവാനുള്ള നെപ്പോമുക്ക് ഫയല് നിരീക്ഷണ സേവനം. Comment[nb]=Nepomuk filovervåkning for å detektere filendringer Comment[nds]=Nepomuk sien Dateibeluurdeenst, de Ännern an Dateien vermeldt @@ -147,6 +150,7 @@ Comment[te]=ఫైల్ మార్పులను మానిటరింగ Comment[tg]=Системаи Nepomuk тағйиротҳоро дар файлҳо муайян мекунад Comment[th]=บริการของ Nepomuk สำหรับคอยตรวจจับความเปลี่ยนแปลงของแฟ้ม Comment[tr]=Dosya değişikliklerini izlemek için Nepomuk dosya izleme servisi +Comment[ug]=ھۆججەت ئۆزگىرىشىنى نازارەت قىلىدىغان Nepomuk ھۆججەت نازارەت مۇلازىمىتى Comment[uk]=Служба Nepomuk для спостереження за змінами в файлах Comment[wa]=Li siervice di rwaitaedje des fitchî di Nepomuk po corwaitî les candjmints e fitchîs Comment[x-test]=xxThe Nepomuk file watch service for monitoring file changesxx diff --git a/nepomuk/services/filewatch/nepomukfilewatch.h b/nepomuk/services/filewatch/nepomukfilewatch.h index cac403e..3e61b91 100644 --- a/nepomuk/services/filewatch/nepomukfilewatch.h +++ b/nepomuk/services/filewatch/nepomukfilewatch.h @@ -25,6 +25,8 @@ #include <QtCore/QVariant> #include <QtCore/QSet> +#include "removablemediacache.h" + namespace Soprano { class Model; namespace Client { @@ -50,6 +52,12 @@ namespace Nepomuk { ~FileWatch(); /** + * Tells strigi to update the file (it can also be a folder but + * then updating will not be recursive) at \p path. + */ + static void updateFileViaStrigi( const QString& path ); + + /** * Tells strigi to update the folder at \p path or the folder * containing \p path in case it is a file. */ @@ -76,8 +84,20 @@ namespace Nepomuk { */ void updateIndexedFoldersWatches(); + /** + * Connected to each removable media. Adds a watch for the mount point, + * cleans up the index with respect to removed files, and optionally + * tells the indexer service to run on the mount path. + */ + void slotDeviceMounted( const Nepomuk::RemovableMediaCache::Entry* ); + private: /** + * Adds watches for all mounted removable media. + */ + void addWatchesForMountedRemovableMedia(); + + /** * Returns true if the path is one that should be always ignored. * This includes such things like temporary files and folders as * they are created for example by build systems. @@ -91,6 +111,7 @@ namespace Nepomuk { #endif RegExpCache* m_pathExcludeRegExpCache; + RemovableMediaCache* m_removableMediaCache; }; } diff --git a/nepomuk/services/filewatch/nepomukfilewatch.notifyrc b/nepomuk/services/filewatch/nepomukfilewatch.notifyrc new file mode 100644 index 0000000..b10e859 --- /dev/null +++ b/nepomuk/services/filewatch/nepomukfilewatch.notifyrc @@ -0,0 +1,148 @@ +[Global] +IconName=nepomuk +Comment=Nepomuk file watch service +Comment[bg]=Услуга на Nepomuk за следене на файлове +Comment[bs]=Servis Nepomuka +Comment[ca]=Servei de control de fitxers del Nepomuk +Comment[ca@valencia]=Servei de control de fitxers del Nepomuk +Comment[cs]=Služba Nepomuku sledující soubory +Comment[da]=Filovervågningstjeneste til Nepomuk +Comment[de]=Nepomuk-Dienst zur Dateiüberwachung +Comment[es]=Servicio de Nepomuk de observación de archivos +Comment[et]=Nepomuki failijälgimise teenus +Comment[eu]=Nepomuken fitxategiak zaintzeko zerbitzua +Comment[fi]=Nepomuk-tiedostonseurantapalvelu +Comment[fr]=Service de surveillance des fichiers de Nepomuk +Comment[he]=משגוח הקבצים של Nepomuk +Comment[hr]=Nepomukova usluga nadgledanja datoteka +Comment[hu]=Nepomuk fájlfigyelő szolgáltatás +Comment[ia]=Servicio de guarda de file de Nepomuk +Comment[is]=Nepomuk skráavöktunarþjónusta +Comment[it]=Servizio monitoraggio file di Nepomuk +Comment[kk]=Nepomuk файл бақылау қызметі +Comment[km]=សេវាកម្មវិធីមើលឯកសាររបស់ Nepomuk +Comment[kn]=ನೆಪೋಮುಕ್ ಕಡತ ಗಮನಿಸುವ ಸೇವೆ +Comment[ko]=Nepomuk 파일 감시 서비스 +Comment[lt]=Nepomuk failų stebėjimo tarnyba +Comment[lv]=Nepomuk failu novērošanas serviss +Comment[nb]=Nepomuk filovervåkningstjeneste +Comment[nds]=Nepomuk-Dateibeluurdeenst +Comment[nl]=Nepomuk bestandenbewakingsdienst +Comment[nn]=Filovervakingsteneste for Nepomuk +Comment[pa]=ਨਿਪੋਮੁਕ ਫਾਇਲ ਵਾਚ ਸਰਵਿਸ +Comment[pl]=Usługa Nepomuk obserwacji pliku +Comment[pt]=Serviço de pesquisa de ficheiros do Nepomuk +Comment[pt_BR]=O serviço de inspeção de arquivos do Nepomuk +Comment[ro]=Serviciu de supraveghere a fișierelor Nepomuk +Comment[ru]=Отслеживание изменений в файлах +Comment[sl]=Nepomukova storitev za opazovanje datotek +Comment[sr]=Непомуков сервис за надгледање фајлова +Comment[sr@ijekavian]=Непомуков сервис за надгледање фајлова +Comment[sr@ijekavianlatin]=Nepomukov servis za nadgledanje fajlova +Comment[sr@latin]=Nepomukov servis za nadgledanje fajlova +Comment[sv]=Nepomuk-tjänst för filövervakning +Comment[th]=บริการสอดส่องการเปลี่ยนแปลงของแฟ้มสำหรับ Neomuk +Comment[tr]=Nepomuk dosya izleme servisi +Comment[ug]=Nepomuk ھۆججەت نازارەت مۇلازىمىتى +Comment[uk]=Служба нагляду за файлами Nepomuk +Comment[x-test]=xxNepomuk file watch servicexx +Comment[zh_CN]=Nepomuk 文件监视服务 +Comment[zh_TW]=Nepomuk 檔案監控服務 + +[Event/nepomuk_new_removable_device] +Name=New Removable Device +Name[bg]=Ново преносимо устройство +Name[bs]=Uklonjivi uređaji +Name[ca]=Dispositiu extraïble nou +Name[ca@valencia]=Dispositiu extraïble nou +Name[cs]=Nové odpojitelné zařízení +Name[da]=Ny flytbar enhed +Name[de]=Neues Wechselmedium +Name[es]=Nuevo dispositivo extraíble +Name[et]=Uus eemaldatav seade +Name[eu]=Gailu aldagarri berria +Name[fi]=Uusi irrotettava laite +Name[fr]=Nouveau périphérique amovible +Name[he]=התקן נשלף חדש +Name[hr]=Novi uklonjivi uređaji +Name[hu]=Új cserélhető eszközök +Name[ia]=Nove dispositivo removibile +Name[is]=Nýtt útskiptanlegt tæki +Name[it]=Nuovo dispositivo rimovibile +Name[kk]=Жаңа ауыстырмалы құрылғы +Name[km]=ឧបករណ៍ចល័តថ្មី +Name[kn]=ಹೊಸ ತೆಗೆದುಹಾಕಬಹುದಾದ ಸಾಧನಗಳು +Name[ko]=새 이동식 장치 +Name[lt]=Naujas pašalinamas įrenginys +Name[lv]=Jauna noņemamā iekārta +Name[nb]=Ny flyttbar enhet +Name[nds]=Niege tuuschbore Reedschappen +Name[nl]=Nieuwe verwijderbare apparaten +Name[nn]=Ny flyttbar eining +Name[pa]=ਨਵਾਂ ਹਟਾਉਣਯੋਗ ਜੰਤਰ +Name[pl]=Nowe urządzenie wymienne +Name[pt]=Novo Dispositivo Removível +Name[pt_BR]=Novo dispositivo removível +Name[ro]=Dispozitiv amovibil nou +Name[ru]=Новое внешнее устройство +Name[sl]=Nova odstranljiva naprava +Name[sr]=Нови уклоњиви уређај +Name[sr@ijekavian]=Нови уклоњиви уређај +Name[sr@ijekavianlatin]=Novi uklonjivi uređaj +Name[sr@latin]=Novi uklonjivi uređaj +Name[sv]=Flyttbara enheter +Name[th]=อุปกรณ์ที่สามารถถอด/เสียบได้ตัวใหม่ +Name[tr]=Yeni Çıkarılabilir Aygıt +Name[ug]=يېڭى يۆتكىلىشچان ئۈسكۈنە +Name[uk]=Новий портативний пристрій +Name[x-test]=xxNew Removable Devicexx +Name[zh_CN]=新移动设备 +Name[zh_TW]=新的可移除裝置 +Comment=A new unknown removable device has been mounted +Comment[bg]=Монтирано е ново непознато преносимо устройство +Comment[bs]=Novi nepoznati uklonjivi uređaj je montiran +Comment[ca]=S'ha muntat un dispositiu extraïble desconegut nou +Comment[ca@valencia]=S'ha muntat un dispositiu extraïble desconegut nou +Comment[cs]=Bylo připojeno nové neznámé odpojitelné zařízeni +Comment[da]=En ny ukendt flytbar enhed er blevet monteret +Comment[de]=Ein neues, unbekanntes Wechselmedium ist eingebunden worden +Comment[es]=Se ha montado un nuevo dispositivo extraíble desconocido +Comment[et]=Ühendati uus tundmatu eemaldatav seade +Comment[eu]=Gailu aldagarri berri ezezagun bat muntatu da +Comment[fi]=Liitettiin uusi tuntematon irrotettava laite +Comment[fr]=Un nouveau périphérique amovible inconnu a été monté +Comment[he]=חובר התקן חדש, לא מוכר +Comment[hr]=Montiran je novi nepoznati uklonjivi uređaj +Comment[hu]=Új, ismeretlen cserélhető eszköz került csatolásra +Comment[ia]=Un nove dispositivo removibile incognite ha essite montate +Comment[is]=Nútt óþekkt útskiptanlegt tæki hefur verið tengt +Comment[it]=È stato montato un nuovo dispositivo rimovibile sconosciuto +Comment[kk]=Жаңа беймәлім ауыстырмалы құрылғы тіркеуден өтті +Comment[km]=ឧបករណ៍ចល័តដែលមិនស្គាល់ថ្មីត្រូវបានម៉ោន +Comment[ko]=새 이동식 장치가 마운트됨 +Comment[lt]=Prijungtas naujas nežinomas pašalinamas įrenginys +Comment[lv]=Tika piemontēta jauna, nezināma noņemamā ierīce +Comment[nb]=En ny ukjent flyttbar enhet er montert +Comment[nds]=En nieg nich begäng tuuschbor Reedschap wöör inhangt +Comment[nl]=Een nieuw verwijderbaar apparaat is aangekoppeld +Comment[nn]=Ei ny ukjend flyttbar eining er no montert +Comment[pa]=ਇੱਕ ਨਵਾਂ ਅਣਜਾਣ ਹਟਾਉਣਯੋਗ ਜੰਤਰ ਮਾਊਂਟ ਕੀਤਾ ਗਿਆ ਹੈ +Comment[pl]=Nowe nieznane urządzenie wymienne zostało zamontowane +Comment[pt]=Foi montado um novo dispositivo removível desconhecido +Comment[pt_BR]=Foi montado um novo dispositivo removível desconhecido +Comment[ro]=Un dispozitiv amovibil necunoscut nou a fost montat +Comment[ru]=Подключено новое, ранее неизвестное внешнее устройство +Comment[sl]=Priklopljena je bila nova neznana odstranljiva baprava +Comment[sr]=Монтиран је непознат нови уклоњиви уређај +Comment[sr@ijekavian]=Монтиран је непознат нови уклоњиви уређај +Comment[sr@ijekavianlatin]=Montiran je nepoznat novi uklonjivi uređaj +Comment[sr@latin]=Montiran je nepoznat novi uklonjivi uređaj +Comment[sv]=En ny obekant flyttbar enhet har monterats +Comment[th]=มีการเมานท์อุปกรณ์ที่สามารถถอด/เสียบได้ตัวใหม่ที่ไม่รู้จักแล้ว +Comment[tr]=Yeni bir bilinmeyen çıkarılabilir aygıt bağlandı +Comment[ug]=يېڭى بىر يوچۇن يۆتكىلىشچان ئۈسكۈنە ئېگەرلەندى +Comment[uk]=Було змонтовано новий невідомий портативний пристрій +Comment[x-test]=xxA new unknown removable device has been mountedxx +Comment[zh_CN]=已经挂载了一个新移动设备 +Comment[zh_TW]=有一個新的可移除裝置已經被掛載了 +Action=Popup diff --git a/nepomuk/services/filewatch/removabledeviceindexnotification.cpp b/nepomuk/services/filewatch/removabledeviceindexnotification.cpp new file mode 100644 index 0000000..fa9ade8 --- /dev/null +++ b/nepomuk/services/filewatch/removabledeviceindexnotification.cpp @@ -0,0 +1,99 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "removabledeviceindexnotification.h" +#include "strigiserviceinterface.h" + +#include <KLocale> +#include <KConfig> +#include <KConfigGroup> +#include <KIcon> +#include <KDebug> +#include <KToolInvocation> + +#include <Solid/StorageAccess> + +RemovableDeviceIndexNotification::RemovableDeviceIndexNotification(const Nepomuk::RemovableMediaCache::Entry* medium, + QObject *parent) + : KNotification(QLatin1String("nepomuk_new_removable_device"), + KNotification::Persistent, + parent), + m_medium(medium) +{ + setTitle(i18nc("@title", "New removable device detected")); + setText(i18nc("@info", "Do you want files on removable device <resource>%1</resource> to be indexed for fast desktop searches?", m_medium->device().description())); + setPixmap(KIcon(QLatin1String("nepomuk")).pixmap(32, 32)); + + setActions(QStringList() + << i18nc("@action", "Index files") + << i18nc("@action", "Ignore device") + << i18nc("@action", "Configure")); + connect(this, SIGNAL(activated(uint)), this, SLOT(slotActionActivated(uint))); + + // as soon as the device is unmounted this notification becomes pointless + if ( const Solid::StorageAccess* storage = m_medium->device().as<Solid::StorageAccess>() ) { + connect(storage, SIGNAL(accessibilityChanged(bool,QString)), SLOT(close())); + } +} + +void RemovableDeviceIndexNotification::slotActionDoIndexActivated() +{ + KConfig strigiConfig( "nepomukstrigirc" ); + strigiConfig.group("Devices").writeEntry(m_medium->url(), true); + + org::kde::nepomuk::Strigi strigi( "org.kde.nepomuk.services.nepomukstrigiservice", "/nepomukstrigiservice", QDBusConnection::sessionBus() ); + strigi.indexFolder( m_medium->mountPath(), true /* recursive */, false /* no forced update */ ); + + close(); +} + +void RemovableDeviceIndexNotification::slotActionDoNotIndexActivated() +{ + KConfig strigiConfig( "nepomukstrigirc" ); + strigiConfig.group("Devices").writeEntry(m_medium->url(), false); + + close(); +} + +void RemovableDeviceIndexNotification::slotActionConfigureActivated() +{ + QStringList args; + args << "kcm_nepomuk" << "--args" << "1"; + KToolInvocation::kdeinitExec("kcmshell4", args); +} + +void RemovableDeviceIndexNotification::slotActionActivated(uint action) +{ + kDebug() << action; + switch(action) { + case 1: + slotActionDoIndexActivated(); + break; + case 2: + slotActionDoNotIndexActivated(); + break; + case 3: + slotActionConfigureActivated(); + break; + } +} + +#include "removabledeviceindexnotification.moc" diff --git a/nepomuk/services/filewatch/removabledeviceindexnotification.h b/nepomuk/services/filewatch/removabledeviceindexnotification.h new file mode 100644 index 0000000..e0b1dae --- /dev/null +++ b/nepomuk/services/filewatch/removabledeviceindexnotification.h @@ -0,0 +1,48 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef REMOVABLEDEVICEINDEXNOTIFICATION_H +#define REMOVABLEDEVICEINDEXNOTIFICATION_H + +#include "removablemediacache.h" + +#include <KNotification> + +#include <Solid/Device> + +class RemovableDeviceIndexNotification : public KNotification +{ + Q_OBJECT + +public: + RemovableDeviceIndexNotification(const Nepomuk::RemovableMediaCache::Entry* medium, QObject *parent = 0); + +private slots: + void slotActionActivated(uint action); + void slotActionDoIndexActivated(); + void slotActionDoNotIndexActivated(); + void slotActionConfigureActivated(); + +private: + const Nepomuk::RemovableMediaCache::Entry* m_medium; +}; + +#endif // REMOVABLEDEVICEINDEXNOTIFICATION_H diff --git a/nepomuk/services/queryservice/folder.cpp b/nepomuk/services/queryservice/folder.cpp index 030fe06..8596498 100644 --- a/nepomuk/services/queryservice/folder.cpp +++ b/nepomuk/services/queryservice/folder.cpp @@ -30,6 +30,7 @@ #include <KDebug> #include <QtCore/QThreadPool> +#include <QtCore/QMutexLocker> Nepomuk::Query::Folder::Folder( const Query& query, QObject* parent ) @@ -37,7 +38,8 @@ Nepomuk::Query::Folder::Folder( const Query& query, QObject* parent ) m_isSparqlQueryFolder( false ), m_query( query ), m_currentSearchRunnable( 0 ), - m_currentCountQueryRunnable( 0 ) + m_currentCountQueryRunnable( 0 ), + m_runnableMutex(QMutex::Recursive) { init(); } @@ -49,7 +51,8 @@ Nepomuk::Query::Folder::Folder( const QString& query, const RequestPropertyMap& m_sparqlQuery( query ), m_requestProperties( requestProps ), m_currentSearchRunnable( 0 ), - m_currentCountQueryRunnable( 0 ) + m_currentCountQueryRunnable( 0 ), + m_runnableMutex(QMutex::Recursive) { init(); } @@ -75,6 +78,7 @@ void Nepomuk::Query::Folder::init() Nepomuk::Query::Folder::~Folder() { + QMutexLocker lock(&m_runnableMutex); if( m_currentSearchRunnable ) m_currentSearchRunnable->cancel(); if( m_currentCountQueryRunnable ) @@ -88,6 +92,7 @@ Nepomuk::Query::Folder::~Folder() void Nepomuk::Query::Folder::update() { + QMutexLocker lock(&m_runnableMutex); if ( !m_currentSearchRunnable ) { m_currentSearchRunnable = new SearchRunnable( this ); QueryService::searchThreadPool()->start( m_currentSearchRunnable, 1 ); @@ -106,12 +111,14 @@ void Nepomuk::Query::Folder::update() QList<Nepomuk::Query::Result> Nepomuk::Query::Folder::entries() const { - return m_results.toList(); + QMutexLocker lock(&m_runnableMutex); + return m_results.values(); } bool Nepomuk::Query::Folder::initialListingDone() const { + QMutexLocker lock(&m_runnableMutex); return m_initialListingDone; } @@ -137,22 +144,23 @@ Nepomuk::Query::RequestPropertyMap Nepomuk::Query::Folder::requestPropertyMap() // called from SearchRunnable in the search thread void Nepomuk::Query::Folder::addResults( const QList<Nepomuk::Query::Result>& results ) { + QMutexLocker lock(&m_runnableMutex); + QSet<Result> newResults; Q_FOREACH( const Result& result, results ) { - if ( !m_results.contains( result ) ) { + if ( !m_results.contains( result.resource().resourceUri() ) ) { newResults.insert( result ); } } - if ( m_initialListingDone ) { - m_newResults += QSet<Result>::fromList(results); - } - else { - m_results += QSet<Result>::fromList(results); + Q_FOREACH(const Result& result, results) { + if ( !m_newResults.contains( result.resource().resourceUri() ) ) { + m_newResults.insert(result.resource().resourceUri(), result); + } } if( !newResults.isEmpty() ) { - emit newEntries( newResults.toList() ); + emit newEntries( newResults.values() ); } } @@ -160,21 +168,31 @@ void Nepomuk::Query::Folder::addResults( const QList<Nepomuk::Query::Result>& re // called from SearchRunnable in the search thread void Nepomuk::Query::Folder::listingFinished() { + QMutexLocker lock(&m_runnableMutex); + m_currentSearchRunnable = 0; - if ( m_initialListingDone ) { - // inform about removed items - foreach( const Result& result, m_results ) { - if ( !m_newResults.contains( result ) ) { - emit entriesRemoved( QList<QUrl>() << result.resource().resourceUri() ); - } + // inform about removed items + QList<Result> removedResults; + + // legacy removed results + foreach( const Result& result, m_results ) { + if ( !m_newResults.contains( result.resource().resourceUri() ) ) { + removedResults << result; + emit entriesRemoved( QList<QUrl>() << KUrl(result.resource().resourceUri()).url() ); } + } - // reset - m_results = m_newResults; - m_newResults.clear(); + // new removed results which include all the details to be used for optimizations + if( !removedResults.isEmpty() ) { + emit entriesRemoved( removedResults ); } - else { + + // reset + m_results = m_newResults; + m_newResults.clear(); + + if ( !m_initialListingDone ) { kDebug() << "Listing done. Total:" << m_results.count(); m_initialListingDone = true; emit finishedListing(); @@ -189,6 +207,7 @@ void Nepomuk::Query::Folder::listingFinished() void Nepomuk::Query::Folder::slotStorageChanged() { + QMutexLocker lock(&m_runnableMutex); if ( !m_updateTimer.isActive() && !m_currentSearchRunnable ) { update(); } @@ -201,6 +220,7 @@ void Nepomuk::Query::Folder::slotStorageChanged() // if there was a change in the nepomuk store we update void Nepomuk::Query::Folder::slotUpdateTimeout() { + QMutexLocker lock(&m_runnableMutex); if ( m_storageChanged && !m_currentSearchRunnable ) { m_storageChanged = false; update(); @@ -211,6 +231,8 @@ void Nepomuk::Query::Folder::slotUpdateTimeout() // called from CountQueryRunnable in the search thread void Nepomuk::Query::Folder::countQueryFinished( int count ) { + QMutexLocker lock(&m_runnableMutex); + m_currentCountQueryRunnable = 0; m_resultCount = count; diff --git a/nepomuk/services/queryservice/folder.h b/nepomuk/services/queryservice/folder.h index b7d2727..7a579b5 100644 --- a/nepomuk/services/queryservice/folder.h +++ b/nepomuk/services/queryservice/folder.h @@ -27,6 +27,7 @@ #include <QtCore/QSet> #include <QtCore/QTimer> #include <QtCore/QPointer> +#include <QtCore/QMutex> #include <KUrl> @@ -105,6 +106,7 @@ namespace Nepomuk { Q_SIGNALS: void newEntries( const QList<Nepomuk::Query::Result>& entries ); void entriesRemoved( const QList<QUrl>& entries ); + void entriesRemoved( const QList<Nepomuk::Query::Result>& entries ); /** * Emitted once the result count is available. @@ -158,14 +160,15 @@ namespace Nepomuk { bool m_initialListingDone; /// the actual current results - QSet<Result> m_results; + QHash<QUrl, Result> m_results; /// the results gathered during an update, needed to find removed items - QSet<Result> m_newResults; + QHash<QUrl, Result> m_newResults; /// the runnable doing work at the moment or 0 if idle SearchRunnable* m_currentSearchRunnable; CountQueryRunnable* m_currentCountQueryRunnable; + mutable QMutex m_runnableMutex; /// did the nepomuk store change after the last update - used for caching of update signals via m_updateTimer bool m_storageChanged; diff --git a/nepomuk/services/queryservice/folderconnection.cpp b/nepomuk/services/queryservice/folderconnection.cpp index a47438e..28bc681 100644 --- a/nepomuk/services/queryservice/folderconnection.cpp +++ b/nepomuk/services/queryservice/folderconnection.cpp @@ -20,6 +20,9 @@ #include "folder.h" #include "queryadaptor.h" +#include <nepomuk/resource.h> +#include <nepomuk/result.h> + #include <QtCore/QStringList> #include <QtDBus/QDBusServiceWatcher> #include <QtDBus/QDBusConnection> @@ -47,8 +50,8 @@ void Nepomuk::Query::FolderConnection::list() m_folder->disconnect( this ); connect( m_folder, SIGNAL( newEntries( QList<Nepomuk::Query::Result> ) ), this, SIGNAL( newEntries( QList<Nepomuk::Query::Result> ) ) ); - connect( m_folder, SIGNAL( entriesRemoved( QList<QUrl> ) ), - this, SLOT( slotEntriesRemoved( QList<QUrl> ) ) ); + connect( m_folder, SIGNAL( entriesRemoved( QList<Nepomuk::Query::Result> ) ), + this, SLOT( slotEntriesRemoved( QList<Nepomuk::Query::Result> ) ) ); // report cached entries if ( !m_folder->entries().isEmpty() ) { @@ -88,8 +91,8 @@ void Nepomuk::Query::FolderConnection::listen() if ( m_folder->initialListingDone() ) { connect( m_folder, SIGNAL( newEntries( QList<Nepomuk::Query::Result> ) ), this, SIGNAL( newEntries( QList<Nepomuk::Query::Result> ) ) ); - connect( m_folder, SIGNAL( entriesRemoved( QList<QUrl> ) ), - this, SLOT( slotEntriesRemoved( QList<QUrl> ) ) ); + connect( m_folder, SIGNAL( entriesRemoved( QList<Nepomuk::Query::Result> ) ), + this, SLOT( slotEntriesRemoved( QList<Nepomuk::Query::Result> ) ) ); connect( m_folder, SIGNAL( resultCount( int ) ), this, SIGNAL( resultCount( int ) ) ); } @@ -102,13 +105,14 @@ void Nepomuk::Query::FolderConnection::listen() } -void Nepomuk::Query::FolderConnection::slotEntriesRemoved( const QList<QUrl>& entries ) +void Nepomuk::Query::FolderConnection::slotEntriesRemoved( const QList<Nepomuk::Query::Result>& entries ) { QStringList uris; for ( int i = 0; i < entries.count(); ++i ) { - uris.append( entries[i].toString() ); + uris.append( entries[i].resource().resourceUri().toString() ); } emit entriesRemoved( uris ); + emit entriesRemoved( entries ); } @@ -118,8 +122,8 @@ void Nepomuk::Query::FolderConnection::slotFinishedListing() // finished we can start listening for changes connect( m_folder, SIGNAL( newEntries( QList<Nepomuk::Query::Result> ) ), this, SIGNAL( newEntries( QList<Nepomuk::Query::Result> ) ) ); - connect( m_folder, SIGNAL( entriesRemoved( QList<QUrl> ) ), - this, SLOT( slotEntriesRemoved( QList<QUrl> ) ) ); + connect( m_folder, SIGNAL( entriesRemoved( QList<Nepomuk::Query::Result> ) ), + this, SLOT( slotEntriesRemoved( QList<Nepomuk::Query::Result> ) ) ); } diff --git a/nepomuk/services/queryservice/folderconnection.h b/nepomuk/services/queryservice/folderconnection.h index df75b2b..835cb9d 100644 --- a/nepomuk/services/queryservice/folderconnection.h +++ b/nepomuk/services/queryservice/folderconnection.h @@ -62,12 +62,15 @@ namespace Nepomuk { Q_SIGNALS: void newEntries( const QList<Nepomuk::Query::Result>& ); void entriesRemoved( const QStringList& ); + void entriesRemoved( const QList<Nepomuk::Query::Result>& entries ); + void resultCount( int count ); void totalResultCount( int count ); void finishedListing(); private Q_SLOTS: - void slotEntriesRemoved( const QList<QUrl>& entries ); + //void slotNewEntries( const QList<Nepomuk::Query::Result>& results ); + void slotEntriesRemoved( const QList<Nepomuk::Query::Result>& entries ); void slotFinishedListing(); private: diff --git a/nepomuk/services/queryservice/nepomukqueryservice.desktop b/nepomuk/services/queryservice/nepomukqueryservice.desktop index 7c1e2df..1fd6311 100644 --- a/nepomuk/services/queryservice/nepomukqueryservice.desktop +++ b/nepomuk/services/queryservice/nepomukqueryservice.desktop @@ -11,6 +11,7 @@ Name[be@latin]=Słužba „NepomukQueryService” Name[bg]=NepomukQueryService Name[bn]=NepomukQueryService Name[bn_IN]=NepomukQueryService +Name[bs]=Servis Nepomukovih upita Name[ca]=NepomukQueryService Name[ca@valencia]=NepomukQueryService Name[cs]=Dotazovací služba Nepomuku @@ -74,6 +75,7 @@ Name[ta]=NepomukQueryService Name[tg]=Хидматҳои Nepomuk Name[th]=NepomukQueryService Name[tr]=Nepomuk Sorgulama Servisi +Name[ug]=Nepomuk سۈرۈشتۈرۈش مۇلازىمىتى Name[uk]=NepomukQueryService Name[wa]=SierviceCweraedjeNepomuk Name[x-test]=xxNepomukQueryServicexx @@ -83,6 +85,8 @@ Comment=The Nepomuk Query Service provides an interface for persistent query fol Comment[ar]=تقدم خدمة استعلام نبومك واجهة لمجلدات الاستعلام الموجودة مسبقا. Comment[ast]=El serviciu de consultes de Nepomuk ufre una interface pa consultes a carpetes persistentes Comment[be@latin]=Słužba zapytaŭ „Nepomuk” absłuhoŭvaje interfejs dla stałych katalohaŭ, jakija biaruć źviestki ad zapytaŭ. +Comment[bg]=Услуга на Nepomuk, която позволява запазване на търсене като директория +Comment[bs]=Servis Nepomukovih upita pruža sučelje za trajne upitne fascikle Comment[ca]=El servei de consultes del Nepomuk proporciona una interfície per a carpetes persistents de consultes Comment[ca@valencia]=El servei de consultes del Nepomuk proporciona una interfície per a carpetes persistents de consultes Comment[cs]=Dotazovací služba Nepomuku poskytuje rozhraní pro složky trvalých dotazů @@ -105,7 +109,7 @@ Comment[hi]=नेपोमक क्वैरी सेवा जो परस Comment[hr]=Nepomukova usluga upita omogućuje sučelje za trajne upitne direktorije Comment[hsb]=Nepomukowa naprašowanska słužba staji intefejs za persistentne naprašowanske zapiski k dispoziciji. Comment[hu]=A Nepomuk lekérdező szolgáltatás keresőmappákhoz nyújt felületet -Comment[ia]=Le Nepomuk Query Service forni un interface pro dossieres persistente de demanda +Comment[ia]=Le Nepomuk Query Service forni un interface pro dossieres de demanda persistente Comment[id]=Layanan Tanya Nepomuk menyediakan antarmuka untuk folder pertanyaan yang kukuh Comment[is]=Nepomuk fyrirspurnamiðlarinn er viðmót fyrir viðvarandi fyrirspurnamöppur (persistent query folders) Comment[it]=Il servizio di interrogazione di Nepomuk fornisce un'interfaccia per cartelle di interrogazione persistenti @@ -142,6 +146,7 @@ Comment[ta]=The Nepomuk Query Service provides an interface for persistent query Comment[te]=నెపోమక్ క్వరీ సేవ అనునది ఎప్పుడూవుండే క్వరీ ఫోల్డర్సుకు ఇంటర్ఫేస్ను అందిస్తుంది Comment[th]=บริการสืบค้น Nepomuk เป็นส่วนติดต่อสำหรับโฟลเดอร์สืบค้นข้อมูลถาวร Comment[tr]=Nepomuk Sorgulama Servisi kalıcı sorgu klasörleri için bir arayüz sağlar +Comment[ug]=Nepomuk سۈرۈشتۈرۈش مۇلازىمىتى بىر خىل ئىزچىل بولغان ھۆججەت سۈرۈشتۈرۈش ئېغىزى بىلەن تەمىنلىدى Comment[uk]=Служба запитів Nepomuk надає інтерфейс для постійних тек запитів Comment[wa]=Li siervice di cweraedje di Nepomuk dene èn eterface po des ridants d' cweraedje wårdés Comment[x-test]=xxThe Nepomuk Query Service provides an interface for persistent query foldersxx diff --git a/nepomuk/services/removablestorage/CMakeLists.txt b/nepomuk/services/removablestorage/CMakeLists.txt deleted file mode 100644 index 07d19b8..0000000 --- a/nepomuk/services/removablestorage/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -project(nepomukremovablestorageservice) - -include_directories( - ${QT_INCLUDES} - ${KDE4_INCLUDES} - ${SOPRANO_INCLUDE_DIR} - ${CMAKE_SOURCE_DIR} - ${NEPOMUK_INCLUDE_DIR} - ) - -set(SRCS - removablestorageservice.cpp - ) - -qt4_add_dbus_interface(SRCS ../../interfaces/org.kde.nepomuk.Strigi.xml strigiserviceinterface) -qt4_add_dbus_interface(SRCS ../../interfaces/org.kde.nepomuk.FileWatch.xml filewatchserviceinterface) - -kde4_add_plugin(nepomukremovablestorageservice ${SRCS}) - -target_link_libraries(nepomukremovablestorageservice - nepomukcommon - ${SOPRANO_CLIENT_LIBRARIES} - ${SOPRANO_LIBRARIES} - ${KDE4_KDEUI_LIBS} - ${KDE4_KIO_LIBS} - ${NEPOMUK_LIBRARIES} - ${KDE4_SOLID_LIBS} - ) - -install( - FILES nepomukremovablestorageservice.desktop - DESTINATION ${SERVICES_INSTALL_DIR}) -install( - TARGETS nepomukremovablestorageservice - DESTINATION ${PLUGIN_INSTALL_DIR}) diff --git a/nepomuk/services/removablestorage/nepomukremovablestorageservice.desktop b/nepomuk/services/removablestorage/nepomukremovablestorageservice.desktop deleted file mode 100644 index e2bba8c..0000000 --- a/nepomuk/services/removablestorage/nepomukremovablestorageservice.desktop +++ /dev/null @@ -1,124 +0,0 @@ -[Desktop Entry] -Type=Service -ServiceTypes=NepomukService -X-KDE-Library=nepomukremovablestorageservice -X-KDE-Nepomuk-autostart=true -X-KDE-Nepomuk-start-on-demand=false -Name=Nepomuk Removable Storage Service -Name[ar]=خدمة نبومك للتخزين القابل للإزالة -Name[ast]=Serviciu d'almacenamientu estrayíble de Nepomuk -Name[ca]=Servei d'emmagatzematge extraïble del Nepomuk -Name[ca@valencia]=Servei d'emmagatzematge extraïble del Nepomuk -Name[cs]=Služba odpojitelných úložišť Nepomuku -Name[csb]=Ùsłëznota Nepomuka dlô przenosnych mediów -Name[da]=Nepomuk-tjeneste til flytbare lagringsenheder -Name[de]=Nepomuk-Dienst für Wechselmedien -Name[el]=Υπηρεσία αφαιρούμενων μέσων αποθήκευσης του Nepomuk -Name[en_GB]=Nepomuk Removable Storage Service -Name[es]=Servicio de almacenamiento extraíble de Nepomuk -Name[et]=Nepomuki eemaldatava salvesti teenus -Name[eu]=Nepomuk biltegiratzeko aldagarrien zerbitzua -Name[fi]=Nepomukin irrotettava tallennuspalvelu -Name[fr]=Service de stockage amovible de Nepomuk -Name[fy]=Nepomuk opslach tsjinst -Name[ga]=Seirbhís Stórála Inbhainte Nepomuk -Name[he]=שירות התקני אחסון נשלפים של Nepomuk -Name[hi]=नेपोमक हटानेयोग्य भंडार सेवा -Name[hr]=Nepomukova usluga za uklonjivo skladište -Name[hu]=Nepomuk cserélhető adathordozó szolgáltatás -Name[ia]=Servicio de Nepomuk de immagazinage (Storage )Removibile -Name[id]=Layanan Penyimpanan Dapat Dilepas Nepomuk -Name[is]=Nepomuk miðlari fyrir aftengjanlegar gagnageymslur -Name[it]=Servizio per supporti rimovibili di Nepomuk -Name[ja]=Nepomuk リムーバブルストレージサービス -Name[kk]=Ауыстырмалы Nepomuk құрылғыда сақтау қызметі -Name[km]=សេវាផ្ទុកចល័ររបស់ Nepomuk -Name[kn]=ನೆಪೋಮುಕ್ ತೆಗೆದು ಹಾಕಬಹುದಾದ ಶೇಖರಣಾ ಸೇವೆ -Name[ko]=Nepomuk 이동식 저장소 서비스 -Name[lt]=Nepomuk atjungiamų laikmenų paslauga -Name[lv]=Nepomuk noņemamo glabāšanas ierīču serviss -Name[mai]=नेपोमक हटानेयोग्य भंडार सेवा -Name[mk]=Сервис на Непомук за подвижни медиуми -Name[ml]=നെപ്പോമുക്ക് സൂക്ഷിപ്പു് സേവനം -Name[nb]=Nepomuk flyttbar lagringstjeneste -Name[nds]=Nepomuk-Tuuschbor-Loopwark-Deenst -Name[nl]=Nepomuk verwijderbare opslagdienst -Name[nn]=Nepomuk-teneste for lagringsmedium -Name[pa]=ਨਿਪੋਮੁਕ ਹਟਾਉਣਯੋਗ ਸਟੋਰੇਜ਼ ਸਰਵਿਸ -Name[pl]=Usługa mediów wymiennych Nepomuka -Name[pt]=Serviço de Armazenamento Removível do Nepomuk -Name[pt_BR]=Serviço de armazenamento removível do Nepomuk -Name[ro]=Serviciu Nepomuk de stocare detașabilă -Name[ru]=Служба хранилища Nepomuk на съёмных носителях -Name[si]=Nepomuk ඉවත්කල හැකි ගබඩා සේවාව -Name[sk]=Služba pre vymeniteľné úložiská Nepomuku -Name[sl]=Storitev odstranljivih nosilcev za Nepomuk -Name[sr]=Непомуков сервис уклоњивих складишта -Name[sr@ijekavian]=Непомуков сервис уклоњивих складишта -Name[sr@ijekavianlatin]=Nepomukov servis uklonjivih skladišta -Name[sr@latin]=Nepomukov servis uklonjivih skladišta -Name[sv]=Nepomuk-tjänst för flyttbara medier -Name[tg]=Хидмати захирагоҳи Nepomuk -Name[th]=บริการเก็บข้อมูล Nepomuk บนสื่อถอด/เสียบได้ -Name[tr]=Nepomuk Çıkarılabilir Depolama Servisi -Name[uk]=Служба зберігання на портативних пристроях Nepomuk -Name[wa]=Li siervice des éndjins di stocaedje bodjåves di Nepomuk -Name[x-test]=xxNepomuk Removable Storage Servicexx -Name[zh_CN]=Nepomuk 移动存储服务 -Name[zh_TW]=Nepomuk 可移除儲存服務 -Comment=The Nepomuk removable storage service, providing access to Nepomuk metadata on removable storage devices. -Comment[ar]=خدمة نِبوموك للتخزين القابل للإزالة, توفر الوصول إلى البيانات العامة لنِبوموك على أجهزة التخزين القابلة للإزالة -Comment[ast]=El serviciu d'atroxamientu estrayíble de Nepomuk, ufre acesu a metadatos de Nepomuk en preseos d'atroxamientu estrayibles. -Comment[ca]=El servei d'emmagatzematge extraïble del Nepomuk, proporciona accés a les metadades del Nepomuk en dispositius d'emmagatzematge extraïbles. -Comment[ca@valencia]=El servei d'emmagatzematge extraïble del Nepomuk, proporciona accés a les metadades del Nepomuk en dispositius d'emmagatzematge extraïbles. -Comment[cs]=Služba odpojitelných úložišť Nepomuku poskytuje přístup pro metadata Nepomuku na odpojitelných zařízeních. -Comment[csb]=Ùsłëznota Nepomuka dlô przenoslnych mediów dôwô przëstãp do pòdôwków meta Nepomùka na wëmienialnych mediach. -Comment[da]=Nepomuk-tjenesten til flytbare lagringsenheder giver adgang til Nepomuk-metadata på flytbare lagringsenheder. -Comment[de]=Der Nepomuk-Dienst für Wechselmedien, ermöglicht Zugriff auf Nepomuk-Metadaten auf Wechselmedien. -Comment[el]=Η υπηρεσία αφαιρούμενων μέσων αποθήκευσης του Nepomuk, παρέχει πρόσβαση στα μεταδεδομένα του Nepomuk σε αφαιρούμενες συσκευές αποθήκευσης. -Comment[en_GB]=The Nepomuk removable storage service, providing access to Nepomuk metadata on removable storage devices. -Comment[es]=El servicio de almacenamiento extraíble de Nepomuk, que proporciona acceso a metadatos de Nepomuk en dispositivos de almacenamiento extraíbles. -Comment[et]=Nepomuki eemaldatava salvesti teenus, mis võimaldab kasutada Nepomuki metaandmeid eemaldataval salvestusseadmel. -Comment[eu]=Nepomuken biltegiratzeko aldagarrien zerbitzua, Nepomuken metadatuei sarbidea ematen die biltegiratzeko gailu aldagarrietan. -Comment[fi]=Nepomuk-irrotettavien tallennuslaitteiden palvelu tarjoaa pääsyn Nepomuk-metadataan irrotettavilla tallennuslaitteilla. -Comment[fr]=Le service de stockage amovible de Nepomuk, fournissant les méta-données de Nepomuk sur les périphériques de stockage amovibles. -Comment[fy]=De Nepomuk útnimber opslach tsjinst, ferskaft troch de Nepomuk metadata op útnimber opslach apparaten. -Comment[he]=שירות התקני אחסון נשלפים של Nepomuk, מספק גישה למידע של Nepomuk עבור התקני אחסון נשלפים. -Comment[hr]=Nepomukova usluga uklonjivih uređaja za pohranu omogućuje pristup Nepomukovim metapodacima na uklonjivim uređajima za pohranu. -Comment[hu]=A Nepomuk cserélhető adathordozó szolgáltatás hozzáférést biztosít a cserélhető adathordozókon található metaadatokhoz. -Comment[ia]=Le servicio de immagazinage removibile de Nepomuk forni accesso a metadata de Nepomuk su dispositivos de immagazinage removibile. -Comment[id]=Layanan penyimpanan dapat dilepas Nepomuk, memberikan akses ke metadata Nepomuk di divais penyimpanan dapat dilepas. -Comment[is]=Nepomuk miðlari fyrir aftengjanlegar gagnageymslur, gerir kleift að nálgast Nepomuk lýsigögn á aftengjanlegum gagnageymslum. -Comment[it]=Il servizio per supporti rimovibili di Nepomuk, che dà accesso ai dati aggiuntivi di Nepomuk sui supporti rimovibili. -Comment[ja]=リムーバブルストレージデバイス上の Nepomuk メタデータへのアクセスを提供する Nepomuk サービス -Comment[kk]=Ауыстырмалы Nepomuk құрылғыда метадеректеріне қатынауды қамтамасыз етіп сақтау қызметі. -Comment[km]=សេវាឧបករណ៍ផ្ទុកចល័តរបស់ Nepomuk ដោយផ្ដល់ការចូលដំណើរការទៅកាន់ទិន្នន័យមេតារបស់ Nepomuk នៅលើឧបករណ៍ផ្ទុកចល័ត ។ -Comment[kn]=ತೆಗೆದು ಹಾಕಬಹುದಾದ ಸಾಧನದಲ್ಲಿರುವ ನೆಪೋಮುಕ್ ಮೆಟಾ ದತ್ತಾಂಶಕ್ಕೆ ನೆಪೋಮುಕ್ ತೆಗೆಧು ಹಾಕಬಹುದಾದ ಶೇಖರಣಾ ಸೇವೆ ಪ್ರವೇಶಾಧಿಕಾರ ನೀಡುತ್ತದೆ. -Comment[ko]=Nepomuk 이동식 저장소 서비스는 이동식 저장소에 있는 Nepomuk 메타데이터를 사용합니다. -Comment[lt]=Nepomuk atjungiamų kaupiklių tarnba, leidžianti pasiekti Nepomuk metaduomenis atjungiamuose kaupikliuose. -Comment[lv]=Nepomuk noņemamo ierīču serviss, nodrošina piekļuvi pie Nepomuk medatatiem failiem uz noņemamajām ierīcēm. -Comment[ml]=നീക്കം ചെയ്യാവുന്ന സംഭരണോപകരണങ്ങള്ക്കു് നെപ്പോമുക്ക് മെറ്റാഡാറ്റ ഉപയോഗിയ്ക്കാന് അനുവദിയ്ക്കുന്ന നെപ്പോമുക്കിന്റെ നീക്കം ചെയ്യാവുന്ന സംഭരണ സേവനം. -Comment[nb]=Nepomuk flyttbar lagringstjeneste som gir tilgang til Nepomuk metadata på flyttbare lagringsenheter. -Comment[nds]=De Nepomuk-Deenst för tuuschbor Loopwarken; stellt Togriep op Nepomuk-Metadaten op tuuschbor Spiekerreedschappen praat. -Comment[nl]=De Nepomuk verwijderbare opslagservice, levert toegang tot Nepomuk metagegevens over verwijderbare opslagapparaten. -Comment[nn]=Nepomuk-tenesta for lagringsmedium gjev tilgang til Nepomuk-metadata på flyttbare einingar. -Comment[pa]=ਨਿਪੋਮੁਕ ਹਟਾਉਣਯੋਗ ਸਟੋਰੇਜ਼ ਸਰਵਿਸ, ਜੋ ਕਿ ਨਿਪੋਮੁਕ ਮੇਟਾਡਾਟਾ ਲਈ ਹਟਾਉਣਯੋਗ ਸਟੋਰੇਜ਼ ਜੰਤਰ ਉੱਤ ਅਸੈੱਸ ਦਿੰਦੀ ਹੈ। -Comment[pl]=Usługa mediów wymiennych Nepomuka, daje dostęp do danych Nepomuka na wymiennych nośnikach. -Comment[pt]=O serviço de armazenamento removíveis do Nepomuk, o qual fornece o acesso aos meta-dados do Nepomuk para os dispositivos removíveis de armazenamento. -Comment[pt_BR]=Serviço de dispositivo de armazenamento removível do Nepomuk, provendo acesso a metadados do Nepomuk nos dispositivos de armazenamento removíveis. -Comment[ro]=Serviciul Nepomuk de stocare detașabilă, furnizînd acces la metadate Nepomuk aflate pe dispozitive de stocare detașabile. -Comment[ru]=Служба хранилища Nepomuk на съёмных носителях предоставляет доступ к метаданным Nepomuk на съёмных устройствах хранения. -Comment[si]=ඉවත්කල හැකි ගබඩා මෙවලම් මත Nepomuk මෙටා දත්ත ප්රවේශය සපයමින් Nepomuk ඉවත්කල හැකි ගබඩා සේවාව. -Comment[sk]=Služba Nepomuku, ktorá poskytuje prístup k metadátam Nepomuku na vymeniteľných úložných zariadeniach. -Comment[sl]=Storitev odstranljivih nosilcev za semantično namizje, ki omogoča dostop do semantičnih metapodatkov na odstranljivih napravah za shranjevanje. -Comment[sr]=Непомуков сервис уклоњивих складишта даје приступ Непомуковим метаподацима на уклоњивим складишним уређајима. -Comment[sr@ijekavian]=Непомуков сервис уклоњивих складишта даје приступ Непомуковим метаподацима на уклоњивим складишним уређајима. -Comment[sr@ijekavianlatin]=Nepomukov servis uklonjivih skladišta daje pristup Nepomukovim metapodacima na uklonjivim skladišnim uređajima. -Comment[sr@latin]=Nepomukov servis uklonjivih skladišta daje pristup Nepomukovim metapodacima na uklonjivim skladišnim uređajima. -Comment[sv]=Nepomuks tjänst för flyttbara medier, vilken ger tillgång till Nepomuk metadata för flyttbara lagringsenheter. -Comment[th]=บริการเก็บข้อมูล Nepomuk บนสื่อถอด/เสียบได้ ให้บริการในการเข้าใช้งานข้อมูลต่าง ๆ ของ Nepomuk บนอุปกรณ์ต่าง ๆ ที่เป็นประเภทถอด/เสียบได้ -Comment[tr]=Nepomuk çıkarılabilir depolama servisi, çıkarılabilir depolama aygıtlarındaki Nepomuk meta verisine erişmeyi sağlar. -Comment[uk]=Служба зберігання на портативних носіях Nepomuk надає вам доступ до метаданих Nepomuk, що зберігаються на портативних носіях. -Comment[x-test]=xxThe Nepomuk removable storage service, providing access to Nepomuk metadata on removable storage devices.xx -Comment[zh_CN]=Nepomuk 移动存储服务,提供了对移动存储设备上的 Nepomuk 元数据访问功能。 -Comment[zh_TW]=Nepomuk 可移除儲存服務,提供存取可移除裝置的 Nepomuk 描述資料。 diff --git a/nepomuk/services/removablestorage/removablestorageservice.cpp b/nepomuk/services/removablestorage/removablestorageservice.cpp deleted file mode 100644 index 039ff6f..0000000 --- a/nepomuk/services/removablestorage/removablestorageservice.cpp +++ /dev/null @@ -1,411 +0,0 @@ -/* This file is part of the KDE Project - Copyright (c) 2009-2010 Sebastian Trueg <trueg@kde.org> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#include "removablestorageservice.h" -#include "strigiserviceinterface.h" -#include "filewatchserviceinterface.h" - -#include <QtDBus/QDBusConnection> -#include <QtCore/QUuid> -#include <QtCore/QTextStream> -#include <QtCore/QFile> -#include <QtCore/QFileInfo> - -#include <KDebug> -#include <KUrl> -#include <KPluginFactory> -#include <KStandardDirs> -#include <KConfig> -#include <KConfigGroup> -#include <kdirnotify.h> - -#include <Solid/DeviceNotifier> -#include <Solid/DeviceInterface> -#include <Solid/Block> -#include <Solid/Device> -#include <Solid/StorageDrive> -#include <Solid/StorageVolume> -#include <Solid/StorageAccess> -#include <Solid/Predicate> - -#include <Soprano/StatementIterator> -#include <Soprano/Statement> -#include <Soprano/NodeIterator> -#include <Soprano/Node> -#include <Soprano/Model> -#include <Soprano/QueryResultIterator> -#include <Soprano/Serializer> -#include <Soprano/Parser> -#include <Soprano/PluginManager> -#include <Soprano/Util/SimpleStatementIterator> -#include <Soprano/Vocabulary/RDF> -#include <Soprano/Vocabulary/NAO> -#include <Soprano/Vocabulary/NRL> -#include <Soprano/Vocabulary/Xesam> - -#include <Nepomuk/Vocabulary/NFO> -#include <Nepomuk/Vocabulary/NIE> -#include <Nepomuk/Resource> -#include <Nepomuk/Variant> - -// -// A few notes on filex: -// Relative URLs: -// without device: filex:///relative/path -// with device: filex://<UID>/relative/path -// [Absolute URLs: -// without device: filex:///absolute/path -// with device: filex:/<UID>//absolute/path] -// - -using namespace Soprano; - -namespace { - bool isUsableVolume( const Solid::Device& dev ) { - if ( dev.is<Solid::StorageVolume>() && - dev.is<Solid::StorageAccess>() && - dev.parent().is<Solid::StorageDrive>() && - ( dev.parent().as<Solid::StorageDrive>()->isRemovable() || - dev.parent().as<Solid::StorageDrive>()->isHotpluggable() ) ) { - const Solid::StorageVolume* volume = dev.as<Solid::StorageVolume>(); - if ( !volume->isIgnored() && volume->usage() == Solid::StorageVolume::FileSystem ) - return true; - } - - return false; - } - - bool isUsableVolume( const QString& udi ) { - Solid::Device dev( udi ); - return isUsableVolume( dev ); - } -} - - -Nepomuk::RemovableStorageService::RemovableStorageService( QObject* parent, const QList<QVariant>& ) - : Service( parent ) -{ - kDebug(); - - initCacheEntries(); - - connect( Solid::DeviceNotifier::instance(), SIGNAL( deviceAdded( const QString& ) ), - this, SLOT( slotSolidDeviceAdded( const QString& ) ) ); - connect( Solid::DeviceNotifier::instance(), SIGNAL( deviceRemoved( const QString& ) ), - this, SLOT( slotSolidDeviceRemoved( const QString& ) ) ); -} - - -Nepomuk::RemovableStorageService::~RemovableStorageService() -{ -} - - -QString Nepomuk::RemovableStorageService::resourceUriFromLocalFileUrl( const QString& urlString ) -{ - KUrl url( urlString ); - KUrl fileXUrl; - QString path = url.path(); - - for ( QHash<QString, Entry>::ConstIterator it = m_metadataCache.constBegin(); - it != m_metadataCache.constEnd(); ++it ) { - const Entry& entry = it.value(); - if ( !entry.m_lastMountPath.isEmpty() && path.startsWith( entry.m_lastMountPath ) ) { - // construct the filex:/ URL and use it below - fileXUrl = entry.constructRelativeUrl( path ); - break; - } - } - - - // - // This is a query similar to the one Resource in libnepomuk uses. Once we found the filex:/ URL above we - // can simply continue with that. If the entry does not exist yet libnepomuk will create a new - // random nepomuk:/ URL. - // - QString query; - if ( fileXUrl.isEmpty() ) - query = QString::fromLatin1("select distinct ?r ?o where { " - "{ ?r %1 %2 . } " - "UNION " - "{ %2 ?p ?o . } " - "} LIMIT 1") - .arg( Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NIE::url()), - Soprano::Node::resourceToN3(url) ); - else - query = QString::fromLatin1("select distinct ?r ?o where { " - "{ ?r %1 %2 . } " - "UNION " - "{ ?r %1 %3 . } " - "UNION " - "{ %2 ?p ?o . } " - "} LIMIT 1") - .arg( Soprano::Node::resourceToN3(Nepomuk::Vocabulary::NIE::url()), - Soprano::Node::resourceToN3(url), - Soprano::Node::resourceToN3(fileXUrl) ); - - Soprano::QueryResultIterator it = mainModel()->executeQuery( query, Soprano::Query::QueryLanguageSparql ); - if( it.next() ) { - KUrl resourceUri = it["r"].uri(); - if( resourceUri.isEmpty() ) - return url.url(); - else - return resourceUri.url(); - } - else { - return QString(); - } -} - - -QStringList Nepomuk::RemovableStorageService::currentlyMountedAndIndexed() -{ - if( KConfig( "nepomukstrigirc" ).group( "General" ).readEntry( "index newly mounted", false ) ) { - QStringList paths; - for ( QHash<QString, Entry>::ConstIterator it = m_metadataCache.constBegin(); - it != m_metadataCache.constEnd(); ++it ) { - const Entry& entry = it.value(); - const Solid::StorageAccess* storage = entry.m_device.as<Solid::StorageAccess>(); - if ( storage && storage->isAccessible() ) { - paths << storage->filePath(); - } - } - return paths; - } - else { - return QStringList(); - } -} - - -void Nepomuk::RemovableStorageService::initCacheEntries() -{ - QList<Solid::Device> devices - = Solid::Device::listFromQuery( QLatin1String( "StorageVolume.usage=='FileSystem'" ) ); - foreach( const Solid::Device& dev, devices ) { - if ( isUsableVolume( dev ) ) { - Entry* entry = createCacheEntry( dev ); - const Solid::StorageAccess* storage = entry->m_device.as<Solid::StorageAccess>(); - if ( storage && storage->isAccessible() ) - slotAccessibilityChanged( true, dev.udi() ); - } - } -} - - -Nepomuk::RemovableStorageService::Entry* Nepomuk::RemovableStorageService::createCacheEntry( const Solid::Device& dev ) -{ - Entry entry( this ); - entry.m_device = dev; - entry.m_description = dev.description(); - entry.m_uuid = entry.m_device.as<Solid::StorageVolume>()->uuid(); - connect( dev.as<Solid::StorageAccess>(), SIGNAL(accessibilityChanged(bool, QString)), - this, SLOT(slotAccessibilityChanged(bool, QString)) ); - - m_metadataCache.insert( dev.udi(), entry ); - - kDebug() << "Found removable storage volume for Nepomuk docking:" << dev.udi() << dev.description(); - - return &m_metadataCache[dev.udi()]; -} - - -Nepomuk::RemovableStorageService::Entry* Nepomuk::RemovableStorageService::findEntryByFilePath( const QString& path ) -{ - for( QHash<QString, Entry>::iterator it = m_metadataCache.begin(); - it != m_metadataCache.end(); ++it ) { - Entry& entry = *it; - if ( entry.m_device.as<Solid::StorageAccess>()->isAccessible() && - path.startsWith( entry.m_device.as<Solid::StorageAccess>()->filePath() ) ) - return &entry; - } - return 0; -} - - -void Nepomuk::RemovableStorageService::slotSolidDeviceAdded( const QString& udi ) -{ - kDebug() << udi; - - if ( isUsableVolume( udi ) ) { - createCacheEntry( Solid::Device( udi ) ); - } -} - - -void Nepomuk::RemovableStorageService::slotSolidDeviceRemoved( const QString& udi ) -{ - kDebug() << udi; - if ( m_metadataCache.contains( udi ) ) { - kDebug() << "Found removable storage volume for Nepomuk undocking:" << udi; - m_metadataCache.remove( udi ); - } -} - - -void Nepomuk::RemovableStorageService::slotAccessibilityChanged( bool accessible, const QString& udi ) -{ - kDebug() << accessible << udi; - - Entry& entry = m_metadataCache[udi]; - if ( accessible ) { - // - // cache new mount path - // - entry.m_lastMountPath = entry.m_device.as<Solid::StorageAccess>()->filePath(); - - if ( entry.hasLastMountPath() ) { - // - // tell the filewatch service that it should monitor the new medium - // - org::kde::nepomuk::FileWatch( "org.kde.nepomuk.services.nepomukfilewatch", - "/nepomukfilewatch", - QDBusConnection::sessionBus() ) - .watchFolder( entry.m_lastMountPath ); - - - // - // tell Strigi to update the newly mounted device - // - if( KConfig( "nepomukstrigirc" ).group( "General" ).readEntry( "index newly mounted", false ) ) { - org::kde::nepomuk::Strigi( "org.kde.nepomuk.services.nepomukstrigiservice", - "/nepomukstrigiservice", - QDBusConnection::sessionBus() ) - .indexFolder( entry.m_lastMountPath, true /* recursive */, false /* no force */ ); - } - } - - // - // Remove all metadata for files that have been removed from the media whilst it was not mounted with us. - // The Strigi service cannot handle this since it does not support filex:/ URLs when updating folders. - // - QString query = QString::fromLatin1( "select ?url ?r where { " - "?r a %1 . " - "?r %2 ?fs . " - "?r %3 ?url . " - "?fs a %4 . " - "?fs %5 %6 . " - "}" ) - .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NFO::FileDataObject() ), - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::isPartOf() ), - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NFO::Filesystem() ), - Soprano::Node::resourceToN3( Soprano::Vocabulary::NAO::identifier() ), - Soprano::Node::literalToN3( entry.m_uuid ) ); - Soprano::QueryResultIterator it = mainModel()->executeQuery( query, Soprano::Query::QueryLanguageSparql ); - while ( it.next() ) { - QString path = entry.constructLocalPath( it["url"].uri() ); - if ( !QFile::exists( path ) ) { - kDebug() << "Removing metadata for no longer existing" << path; - Nepomuk::Resource( it["r"].uri() ).remove(); - } - } - } - else if ( entry.hasLastMountPath() ) { - // - // The first thing we need to do is to inform nepomuk:/ kio slave instances that something has changed - // so any caches will be cleared. Otherwise KDirModel and friends might try to access the old media URLs - // which nepomuk:/ KIO has rewritten without asking again. - // - // FIXME: cannot use "org::kde::KDirNotify::emitFilesRemoved( QStringList() << entry.m_lastMountPath );" - // as that will make the FileWatchService delete all metadata - // - - // - // First we create the filesystem resource. We mostly need this for the user readable label. - // - Nepomuk::Resource fsRes( entry.m_uuid, Nepomuk::Vocabulary::NFO::Filesystem() ); - fsRes.setLabel( entry.m_description ); - - // - // We change all absolute file:/ URLs to relative filex:/ URLs which include the solid id - // - // FIXME: do this asyncroneously - // - QString query = QString::fromLatin1( "select ?r ?url ?g where { " // FIXME: can Virtuoso directly select a substring of the url? Can we maybe even do this in an update query? - "graph ?g { ?r %1 ?url . } . " - "FILTER(REGEX(STR(?url),'^file://%2/')) . }" ) - .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ) ) - .arg( entry.m_lastMountPath ); - kDebug() << query; - QList<Soprano::BindingSet> bindings = mainModel()->executeQuery( query, Soprano::Query::QueryLanguageSparql ).allBindings(); - - foreach( const Soprano::BindingSet& b, bindings ) { - const QUrl resource = b["r"].uri(); - const QString path = b["url"].uri().path(); - const QUrl graph = b["g"].uri(); - - // construct the new filex:/ URL from the solid device UID and the relative path on the device - const QUrl filexUrl = entry.constructRelativeUrl( path ); - - kDebug() << "Converting URL" << b["url"] << "to" << filexUrl; - - // for performance reasons we do not use Nepomuk::Resource but do it the old fashioned way - mainModel()->removeAllStatements( resource, Nepomuk::Vocabulary::NIE::url(), Node() ); - mainModel()->addStatement( resource, Nepomuk::Vocabulary::NIE::url(), filexUrl, graph ); - - // IDEA: What about nie:hasPart nfo:FileSystem for all top-level items instead of the nfo:Folder that is the mount point. - // But then we run into the recursion problem - Resource fileRes( resource ); - fileRes.addProperty( Nepomuk::Vocabulary::NIE::isPartOf(), fsRes ); - } - - // - // the mount point is no longer the parent folder of the top level files on the removable device - // We need to delete those relations seperately - // - QUrl parentUrl = Resource( KUrl( entry.m_lastMountPath ) ).resourceUri(); - if( parentUrl.isValid() ) - mainModel()->removeAllStatements( Soprano::Node(), Nepomuk::Vocabulary::NIE::isPartOf(), parentUrl ); - } - kDebug() << "done"; -} - - -Nepomuk::RemovableStorageService::Entry::Entry( RemovableStorageService* parent ) - : q( parent ) -{ -} - - -KUrl Nepomuk::RemovableStorageService::Entry::constructRelativeUrl( const QString& path ) const -{ - const QString relativePath = path.mid( m_lastMountPath.count() ); - return KUrl( QLatin1String("filex://") + m_uuid + relativePath ); -} - - -QString Nepomuk::RemovableStorageService::Entry::constructLocalPath( const KUrl& filexUrl ) const -{ - QString path( m_lastMountPath ); - if ( path.endsWith( QLatin1String( "/" ) ) ) - path.truncate( path.length()-1 ); - path += filexUrl.path(); - return path; -} - - -bool Nepomuk::RemovableStorageService::Entry::hasLastMountPath() const -{ - return( !m_lastMountPath.isEmpty() && - m_lastMountPath != QLatin1String( "/" ) ); -} - -NEPOMUK_EXPORT_SERVICE( Nepomuk::RemovableStorageService, "nepomukremovablestorageservice") - -#include "removablestorageservice.moc" diff --git a/nepomuk/services/removablestorage/removablestorageservice.h b/nepomuk/services/removablestorage/removablestorageservice.h deleted file mode 100644 index 687acce..0000000 --- a/nepomuk/services/removablestorage/removablestorageservice.h +++ /dev/null @@ -1,112 +0,0 @@ -/* This file is part of the KDE Project - Copyright (c) 2009 Sebastian Trueg <trueg@kde.org> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License version 2 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#ifndef _NEPOMUK_REMOVABLE_STORAGE_SERVICE_H_ -#define _NEPOMUK_REMOVABLE_STORAGE_SERVICE_H_ - -#include <Nepomuk/Service> - -#include <QtCore/QUrl> -#include <QtCore/QVariant> -#include <QtCore/QHash> - -#include <Solid/Device> - -namespace Solid { - class StorageAccess; - class StorageVolume; -} - -class KUrl; - -namespace Nepomuk { - /** - * This service caches metadata stored on removable devices such as usb sticks in the local - * Nepomuk repository. Thus, the metadata can be searched like any other local file metadata. - * - * Once a device is mounted, the data is cached. When unmounted the cached data is removed again. - */ - class RemovableStorageService : public Service - { - Q_OBJECT - Q_CLASSINFO( "D-Bus Interface", "org.kde.nepomuk.RemovableStorage" ) - - public: - RemovableStorageService( QObject* parent, const QVariantList& ); - ~RemovableStorageService(); - - public Q_SLOTS: - /** - * Determines the resource URI for a local file URL. This also handles - * local file URLs that are not stored as such in Nepomuk but using a - * filex:/ scheme URL for a mounted filesystem. - * - * This method is called by libnepomuk's Resource class to handle - * filex:/ URLs transparently. - * - * \return The resource URI for \p url or an empty string if it could - * not be found (this is the case if the filesystem is not mounted or - * if \p url is simply invalid or not a used URL.) - */ - Q_SCRIPTABLE QString resourceUriFromLocalFileUrl( const QString& url ); - - Q_SCRIPTABLE QStringList currentlyMountedAndIndexed(); - - private Q_SLOTS: - void slotSolidDeviceAdded( const QString& udi ); - void slotSolidDeviceRemoved( const QString& udi ); - - /** - * Reacts on Solid::StorageAccess::accessibilityChanged: - * \li If \p accessible is true, the metadata from the removable storage - * is cached in the local Nepomuk store. - * \li Otherwise the corresponding cache is removed. - */ - void slotAccessibilityChanged( bool accessible, const QString& udi ); - - private: - void initCacheEntries(); - - class Entry { - RemovableStorageService* q; - - public: - Entry() {} - Entry( RemovableStorageService* ); - - KUrl constructRelativeUrl( const QString& path ) const; - QString constructLocalPath( const KUrl& filexUrl ) const; - bool hasLastMountPath() const; - - Solid::Device m_device; - QString m_lastMountPath; - - // need to cache the device properties in case - // an USB device is simply ejected without properly - // unmounting before - QString m_description; - QString m_uuid; - }; - QHash<QString, Entry> m_metadataCache; - - Entry* createCacheEntry( const Solid::Device& dev ); - Entry* findEntryByFilePath( const QString& path ); - }; -} - -#endif // _NEPOMUK_REMOVABLE_STORAGE_SERVICE_H_ diff --git a/nepomuk/services/storage/CMakeLists.txt b/nepomuk/services/storage/CMakeLists.txt index ee0781d..f882d34 100644 --- a/nepomuk/services/storage/CMakeLists.txt +++ b/nepomuk/services/storage/CMakeLists.txt @@ -6,6 +6,8 @@ include_directories( ${SOPRANO_INCLUDE_DIR} ${CMAKE_SOURCE_DIR} ${NEPOMUK_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/lib + ${CMAKE_CURRENT_SOURCE_DIR}/../backupsync/lib ) set(storage_SRCS @@ -18,20 +20,28 @@ set(storage_SRCS graphretriever.cpp crappyinferencer.cpp crappyinferencer2.cpp - typevisibilitytree.cpp + removablemediamodel.cpp + datamanagementmodel.cpp + datamanagementadaptor.cpp + datamanagementcommand.cpp + classandpropertytree.cpp + resourcemerger.cpp + resourceidentifier.cpp + resourcewatchermanager.cpp + resourcewatcherconnection.cpp + graphmaintainer.cpp ) -soprano_add_ontology(storage_SRCS - ${CMAKE_CURRENT_SOURCE_DIR}/../../ontologies/kuvo.trig - "KUVO" - "Nepomuk::Vocabulary" - "trig") - qt4_add_dbus_adaptor(storage_SRCS ../../interfaces/org.kde.nepomuk.OntologyManager.xml ontologyloader.h Nepomuk::OntologyLoader) +qt4_add_dbus_adaptor(storage_SRCS + ../../interfaces/org.kde.nepomuk.ResourceWatcherConnection.xml + resourcewatcherconnection.h + Nepomuk::ResourceWatcherConnection) + set(HAVE_SOPRANO_INDEX ${SopranoIndex_FOUND}) kde4_add_plugin(nepomukstorage ${storage_SRCS}) @@ -41,7 +51,11 @@ target_link_libraries(nepomukstorage ${SOPRANO_SERVER_LIBRARIES} ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} + ${KDE4_SOLID_LIBS} ${NEPOMUK_LIBRARIES} + nepomuksync + nepomukdatamanagement + nepomukcommon ) install( @@ -57,4 +71,5 @@ install( DESTINATION ${PLUGIN_INSTALL_DIR}) # ----------------------------- +add_subdirectory(lib) add_subdirectory(test) diff --git a/nepomuk/services/storage/Messages.sh b/nepomuk/services/storage/Messages.sh index 494d8c5..1574b8e 100755 --- a/nepomuk/services/storage/Messages.sh +++ b/nepomuk/services/storage/Messages.sh @@ -1,2 +1,2 @@ #! /usr/bin/env bash -$XGETTEXT `find . -name "*.cpp"` -o $podir/nepomukstorage.pot +$XGETTEXT `find . -name "*.cpp" | grep -v '/test/'` -o $podir/nepomukstorage.pot diff --git a/nepomuk/services/storage/classandpropertytree.cpp b/nepomuk/services/storage/classandpropertytree.cpp new file mode 100644 index 0000000..cb16f07 --- /dev/null +++ b/nepomuk/services/storage/classandpropertytree.cpp @@ -0,0 +1,680 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010-2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "classandpropertytree.h" +#include "simpleresource.h" +#include "simpleresourcegraph.h" + +#include <QtCore/QSet> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QDateTime> +#include <QtCore/QMutexLocker> + +#include <Soprano/Version> +#include <Soprano/Node> +#include <Soprano/LiteralValue> +#include <Soprano/QueryResultIterator> +#include <Soprano/Model> +#include <Soprano/Vocabulary/RDFS> +#include <Soprano/Vocabulary/NAO> +#include <Soprano/Vocabulary/NRL> +#include <Soprano/Vocabulary/XMLSchema> + +#include <KDebug> + +using namespace Soprano; +using namespace Soprano::Vocabulary; + +Nepomuk::ClassAndPropertyTree* Nepomuk::ClassAndPropertyTree::s_self = 0; + +class Nepomuk::ClassAndPropertyTree::ClassOrProperty +{ +public: + ClassOrProperty() + : isProperty(false), + maxCardinality(0), + userVisible(0), + identifying(0) { + } + + /// true if this is a property, for classes this is false + bool isProperty; + + /// the uri of the class or property + QUrl uri; + + /// the direct parents, ie. those for which a rdfs relations exists + QSet<QUrl> directParents; + + /// includes all parents, even grand-parents and further up + QSet<QUrl> allParents; + + /// the max cardinality if this is a property with a max cardinality set, 0 otherwise + int maxCardinality; + + /// 0 - undecided, 1 - visible, -1 - non-visible + int userVisible; + + /// 0 - undecided, 1 - identifying, -1 - non-identifying + int identifying; + + /// only valid for properties + QUrl domain; + QUrl range; +}; + +Nepomuk::ClassAndPropertyTree::ClassAndPropertyTree(QObject *parent) + : QObject(parent), + m_mutex(QMutex::Recursive) +{ + Q_ASSERT(s_self == 0); + s_self = this; +} + +Nepomuk::ClassAndPropertyTree::~ClassAndPropertyTree() +{ + qDeleteAll(m_tree); + s_self = 0; +} + +bool Nepomuk::ClassAndPropertyTree::isKnownClass(const QUrl &uri) const +{ + QMutexLocker lock(&m_mutex); + if(const ClassOrProperty* cop = findClassOrProperty(uri)) + return !cop->isProperty; + else + return false; +} + +QSet<QUrl> Nepomuk::ClassAndPropertyTree::allParents(const QUrl &uri) const +{ + QMutexLocker lock(&m_mutex); + if(const ClassOrProperty* cop = findClassOrProperty(uri)) + return cop->allParents; + else + return QSet<QUrl>(); +} + +bool Nepomuk::ClassAndPropertyTree::isChildOf(const QUrl &type, const QUrl &superClass) const +{ + QMutexLocker lock(&m_mutex); + if(const ClassOrProperty* cop = findClassOrProperty(type)) + return cop->allParents.contains(superClass); + else + return 0; +} + +bool Nepomuk::ClassAndPropertyTree::isChildOf(const QList< QUrl >& types, const QUrl& superClass) const +{ + if(superClass == RDFS::Resource()) { + return true; + } + + foreach( const QUrl & type, types ) { + if( isChildOf( type, superClass ) ) + return true; + } + return false; +} + +int Nepomuk::ClassAndPropertyTree::maxCardinality(const QUrl &type) const +{ + QMutexLocker lock(&m_mutex); + if(const ClassOrProperty* cop = findClassOrProperty(type)) + return cop->maxCardinality; + else + return 0; +} + +bool Nepomuk::ClassAndPropertyTree::isUserVisible(const QUrl &type) const +{ + QMutexLocker lock(&m_mutex); + if(const ClassOrProperty* cop = findClassOrProperty(type)) + return cop->userVisible == 1; + else + return true; +} + +QUrl Nepomuk::ClassAndPropertyTree::propertyDomain(const QUrl &uri) const +{ + QMutexLocker lock(&m_mutex); + if(const ClassOrProperty* cop = findClassOrProperty(uri)) + return cop->domain; + else + return QUrl(); +} + +QUrl Nepomuk::ClassAndPropertyTree::propertyRange(const QUrl &uri) const +{ + QMutexLocker lock(&m_mutex); + if(const ClassOrProperty* cop = findClassOrProperty(uri)) + return cop->range; + else + return QUrl(); +} + +bool Nepomuk::ClassAndPropertyTree::hasLiteralRange(const QUrl &uri) const +{ + // TODO: this is a rather crappy check for literal range + QMutexLocker lock(&m_mutex); + if(const ClassOrProperty* cop = findClassOrProperty(uri)) + return (cop->range.toString().startsWith(XMLSchema::xsdNamespace().toString() ) || + cop->range == RDFS::Literal()); + else + return false; +} + +bool Nepomuk::ClassAndPropertyTree::isIdentifyingProperty(const QUrl &uri) const +{ + QMutexLocker lock(&m_mutex); + if(const ClassOrProperty* cop = findClassOrProperty(uri)) + return cop->identifying == 1; + else + return true; // we default to true for unknown properties to ensure that we never perform invalid merges +} + +Soprano::Node Nepomuk::ClassAndPropertyTree::variantToNode(const QVariant &value, const QUrl &property) const +{ + QSet<Soprano::Node> nodes = variantListToNodeSet(QVariantList() << value, property); + if(nodes.isEmpty()) + return Soprano::Node(); + else + return *nodes.begin(); +} + + +namespace Soprano { +namespace Vocabulary { + namespace XMLSchema { + QUrl xsdDuration() { + return QUrl( Soprano::Vocabulary::XMLSchema::xsdNamespace().toString() + QLatin1String("duration") ); + } + } +} +} + +QSet<Soprano::Node> Nepomuk::ClassAndPropertyTree::variantListToNodeSet(const QVariantList &vl, const QUrl &property) const +{ + clearError(); + + QSet<Soprano::Node> nodes; + QUrl range = propertyRange(property); + + // + // Special case: xsd:duration - Soprano doesn't handle it + // + if(range == XMLSchema::xsdDuration()) { + range = XMLSchema::unsignedInt(); + } + + // + // Special case: rdfs:Literal + // + if(range == RDFS::Literal()) { + Q_FOREACH(const QVariant& value, vl) { + nodes.insert(Soprano::LiteralValue::createPlainLiteral(value.toString())); + } + } + + // + // Special case: abstract properties - we do not allow setting them + // + else if(range.isEmpty()) { + setError(QString::fromLatin1("Cannot set values for abstract property '%1'.").arg(property.toString()), Soprano::Error::ErrorInvalidArgument); + return QSet<Soprano::Node>(); + } + + // + // The standard case + // + else { + const QVariant::Type literalType = Soprano::LiteralValue::typeFromDataTypeUri(range); + if(literalType == QVariant::Invalid) { + Q_FOREACH(const QVariant& value, vl) { + // treat as a resource range for now + if(value.type() == QVariant::Url) { + nodes.insert(value.toUrl()); + } + else if(value.type() == QVariant::String) { + QString s = value.toString(); + if(!s.isEmpty()) { + // for convinience we support local file paths + if(s[0] == QDir::separator() && QFile::exists(s)) { + nodes.insert(QUrl::fromLocalFile(s)); + } + else { + // treat it as a URI + nodes.insert(QUrl(s)); + } + } + else { + // empty string + setError(QString::fromLatin1("Encountered an empty string where a resource URI was expected."), Soprano::Error::ErrorInvalidArgument); + return QSet<Soprano::Node>(); + } + } + else { + // invalid type + setError(QString::fromLatin1("Encountered '%1' where a resource URI was expected.").arg(value.toString()), Soprano::Error::ErrorInvalidArgument); + return QSet<Soprano::Node>(); + } + } + } + else { + Q_FOREACH(const QVariant& value, vl) { + // + // Exiv data often contains floating point values encoded as a fraction + // + if((range == XMLSchema::xsdFloat() || range == XMLSchema::xsdDouble()) + && value.type() == QVariant::String) { + int x = 0; + int y = 0; + if ( sscanf( value.toString().toLatin1().data(), "%d/%d", &x, &y ) == 2 && y != 0 ) { + const double v = double( x )/double( y ); +#if SOPRANO_IS_VERSION(2, 6, 51) + nodes.insert(LiteralValue::fromVariant(v, range)); +#else + nodes.insert(LiteralValue::fromString(QString::number(v), range)); +#endif + continue; + } + } + + // + // ID3 tags sometimes only contain the year of publication. We cover this + // special case here with a very dumb heuristic + // + else if(range == XMLSchema::dateTime() + && value.canConvert(QVariant::UInt)) { + bool ok = false; + const int t = value.toInt(&ok); + if(ok && t > 0 && t <= 9999) { + nodes.insert(LiteralValue(QDateTime(QDate(t, 1, 1), QTime(0, 0), Qt::UTC))); + continue; + } + } + +#if SOPRANO_IS_VERSION(2, 6, 51) + Soprano::LiteralValue v = Soprano::LiteralValue::fromVariant(value, range); + if(v.isValid()) { + nodes.insert(v); + } + else { + // failed literal conversion + setError(QString::fromLatin1("Failed to convert '%1' to literal of type '%2'.").arg(value.toString(), range.toString()), Soprano::Error::ErrorInvalidArgument); + return QSet<Soprano::Node>(); + } +#else + // + // We handle a few special cases here. + // + // Special Case 1: support conversion from time_t to QDateTime + // + if(range == XMLSchema::dateTime() && + value.canConvert(QVariant::UInt)) { + bool ok = false; + int v = value.toUInt(&ok); + if(ok) { + nodes.insert(Soprano::LiteralValue(QDateTime::fromTime_t(v))); + continue; + } + } + + // + // if the types differ we try to convert + // (We need to treat int and friends as a special case since xsd defines more than one + // type mapping to them.) + // + if(value.type() != literalType + || ((value.type() == QVariant::Int || + value.type() == QVariant::UInt || + value.type() == QVariant::Double) + && range != Soprano::LiteralValue::dataTypeUriFromType(value.type()))) { + Soprano::LiteralValue v = Soprano::LiteralValue::fromString(value.toString(), range); + if(v.isValid()) { + nodes.insert(v); + } + else { + // failed literal conversion + setError(QString::fromLatin1("Failed to convert '%1' to literal of type '%2'.").arg(value.toString(), range.toString()), Soprano::Error::ErrorInvalidArgument); + return QSet<Soprano::Node>(); + } + } + else { + // we already have the correct type + nodes.insert(Soprano::LiteralValue(value)); + } +#endif + } + } + } + + return nodes; +} + +void Nepomuk::ClassAndPropertyTree::rebuildTree(Soprano::Model* model) +{ + QMutexLocker lock(&m_mutex); + + qDeleteAll(m_tree); + m_tree.clear(); + + QString query + = QString::fromLatin1("select distinct ?r ?p ?v ?mc ?c ?domain ?range ?ct ?pt " + "where { " + "{ ?r a ?ct . FILTER(?ct=rdfs:Class) . " + "OPTIONAL { ?r rdfs:subClassOf ?p . ?p a rdfs:Class . } . } " + "UNION " + "{ ?r a ?pt . FILTER(?pt=rdf:Property) . " + "OPTIONAL { ?r rdfs:subPropertyOf ?p . ?p a rdf:Property . } . } " + "OPTIONAL { ?r %1 ?mc . } . " + "OPTIONAL { ?r %2 ?c . } . " + "OPTIONAL { ?r %3 ?v . } . " + "OPTIONAL { ?r %4 ?domain . } . " + "OPTIONAL { ?r %5 ?range . } . " + "FILTER(?r!=%6) . " + "}" ) + .arg(Soprano::Node::resourceToN3(NRL::maxCardinality()), + Soprano::Node::resourceToN3(NRL::cardinality()), + Soprano::Node::resourceToN3(NAO::userVisible()), + Soprano::Node::resourceToN3(RDFS::domain()), + Soprano::Node::resourceToN3(RDFS::range()), + Soprano::Node::resourceToN3(RDFS::Resource())); +// kDebug() << query; + Soprano::QueryResultIterator it + = model->executeQuery( query, Soprano::Query::QueryLanguageSparql ); + while( it.next() ) { + const QUrl r = it["r"].uri(); + const Soprano::Node p = it["p"]; + const Soprano::Node v = it["v"]; + int mc = it["mc"].literal().toInt(); + int c = it["c"].literal().toInt(); + const QUrl domain = it["domain"].uri(); + const QUrl range = it["range"].uri(); + + ClassOrProperty* r_cop = 0; + QHash<QUrl, ClassOrProperty*>::iterator copIt = m_tree.find(r); + if(copIt != m_tree.end()) { + r_cop = copIt.value(); + } + else { + r_cop = new ClassOrProperty; + r_cop->uri = r; + m_tree.insert( r, r_cop ); + } + + r_cop->isProperty = it["pt"].isValid(); + + if( v.isLiteral() ) { + r_cop->userVisible = (v.literal().toBool() ? 1 : -1); + } + + if(mc > 0 || c > 0) { + r_cop->maxCardinality = qMax(mc, c); + } + + if(!domain.isEmpty()) { + r_cop->domain = domain; + } + + if(!range.isEmpty()) { + r_cop->range = range; + } + else { + // no range -> resource range + r_cop->identifying = -1; + } + + if ( p.isResource() && + p.uri() != r && + p.uri() != RDFS::Resource() ) { + ClassOrProperty* p_cop = 0; + if ( !m_tree.contains( p.uri() ) ) { + p_cop = new ClassOrProperty; + p_cop->uri = p.uri(); + m_tree.insert( p.uri(), p_cop ); + } + r_cop->directParents.insert(p.uri()); + } + } + + // although nao:identifier is actually an abstract property Nepomuk has been using + // it for very long to store string identifiers (instead of nao:personalIdentifier). + // Thus, we force its range to xsd:string for correct conversion in variantListToNodeSet() + if(m_tree.contains(NAO::identifier())) + m_tree[NAO::identifier()]->range = XMLSchema::string(); + + // make sure rdfs:Resource is visible by default + ClassOrProperty* rdfsResourceNode = 0; + QHash<QUrl, ClassOrProperty*>::iterator rdfsResourceIt = m_tree.find(RDFS::Resource()); + if( rdfsResourceIt == m_tree.end() ) { + rdfsResourceNode = new ClassOrProperty; + rdfsResourceNode->uri = RDFS::Resource(); + m_tree.insert( RDFS::Resource(), rdfsResourceNode ); + } + else { + rdfsResourceNode = rdfsResourceIt.value(); + } + if( rdfsResourceNode->userVisible == 0 ) { + rdfsResourceNode->userVisible = 1; + } + // add rdfs:Resource as parent for all top-level classes + for ( QHash<QUrl, ClassOrProperty*>::iterator it = m_tree.begin(); + it != m_tree.end(); ++it ) { + if( it.value() != rdfsResourceNode && it.value()->directParents.isEmpty() ) { + it.value()->directParents.insert( RDFS::Resource() ); + } + } + + // update all visibility + for ( QHash<QUrl, ClassOrProperty*>::iterator it = m_tree.begin(); + it != m_tree.end(); ++it ) { + QSet<QUrl> visitedNodes; + updateUserVisibility( it.value(), visitedNodes ); + } + + // complete the allParents lists + for ( QHash<QUrl, ClassOrProperty*>::iterator it = m_tree.begin(); + it != m_tree.end(); ++it ) { + QSet<QUrl> visitedNodes; + getAllParents( it.value(), visitedNodes ); + } + + // update all identifying and flux properties + // by default all properties with a literal range are identifying + // and all properties with a resource range are non-idenifying + query = QString::fromLatin1("select ?p ?t where { " + "?p a rdf:Property . " + "?p a ?t . FILTER(?t!=rdf:Property) . }"); + it = model->executeQuery( query, Soprano::Query::QueryLanguageSparql ); + while( it.next() ) { + const QUrl p = it["p"].uri(); + const QUrl t = it["t"].uri(); + + if(t == QUrl(NRL::nrlNamespace().toString() + QLatin1String("IdentifyingProperty"))) { + m_tree[p]->identifying = 1; + } + else if(t == QUrl(NRL::nrlNamespace().toString() + QLatin1String("FluxProperty"))) { + m_tree[p]->identifying = -1; + } + } + for ( QHash<QUrl, ClassOrProperty*>::iterator it = m_tree.begin(); + it != m_tree.end(); ++it ) { + if(it.value()->isProperty) { + QSet<QUrl> visitedNodes; + updateIdentifying( it.value(), visitedNodes ); + } + } +} + +const Nepomuk::ClassAndPropertyTree::ClassOrProperty * Nepomuk::ClassAndPropertyTree::findClassOrProperty(const QUrl &uri) const +{ + QHash<QUrl, ClassOrProperty*>::const_iterator it = m_tree.constFind(uri); + if(it == m_tree.constEnd()) + return 0; + else + return it.value(); +} + +bool Nepomuk::ClassAndPropertyTree::contains(const QUrl& uri) const +{ + return m_tree.contains(uri); +} + + +/** + * Set the value of nao:userVisible. + * A class is visible if it has at least one visible direct parent class. + */ +int Nepomuk::ClassAndPropertyTree::updateUserVisibility( ClassOrProperty* cop, QSet<QUrl>& visitedNodes ) +{ + if ( cop->userVisible != 0 ) { + return cop->userVisible; + } + else { + for ( QSet<QUrl>::iterator it = cop->directParents.begin(); + it != cop->directParents.end(); ++it ) { + // avoid endless loops + if( visitedNodes.contains(*it) ) + continue; + visitedNodes.insert(*it); + if ( updateUserVisibility( m_tree[*it], visitedNodes ) == 1 ) { + cop->userVisible = 1; + break; + } + } + if ( cop->userVisible == 0 ) { + // default to invisible + cop->userVisible = -1; + } + //kDebug() << "Setting nao:userVisible of" << cop->uri.toString() << ( cop->userVisible == 1 ); + return cop->userVisible; + } +} + +/** + * Set the value of identifying. + * An identifying property has at least one identifying direct parent property. + */ +int Nepomuk::ClassAndPropertyTree::updateIdentifying( ClassOrProperty* cop, QSet<QUrl>& identifyingNodes ) +{ + if ( cop->identifying != 0 ) { + return cop->identifying; + } + else { + for ( QSet<QUrl>::iterator it = cop->directParents.begin(); + it != cop->directParents.end(); ++it ) { + // avoid endless loops + if( identifyingNodes.contains(*it) ) + continue; + identifyingNodes.insert(*it); + if ( updateIdentifying( m_tree[*it], identifyingNodes ) == 1 ) { + cop->identifying = 1; + break; + } + } + if ( cop->identifying == 0 ) { + // properties with a literal range default to identifying + cop->identifying = hasLiteralRange(cop->uri) ? 1 : -1; + } + //kDebug() << "Setting identifying of" << cop->uri.toString() << ( cop->identifying == 1 ); + return cop->identifying; + } +} + +QSet<QUrl> Nepomuk::ClassAndPropertyTree::getAllParents(ClassOrProperty* cop, QSet<QUrl>& visitedNodes) +{ + if(cop->allParents.isEmpty()) { + for ( QSet<QUrl>::iterator it = cop->directParents.begin(); + it != cop->directParents.end(); ++it ) { + // avoid endless loops + if( visitedNodes.contains(*it) ) + continue; + visitedNodes.insert( *it ); + cop->allParents += getAllParents(m_tree[*it], visitedNodes); + } + cop->allParents += cop->directParents; + + // some cleanup to fix inheritance loops + cop->allParents << RDFS::Resource(); + cop->allParents.remove(cop->uri); + } + return cop->allParents; +} + + +namespace { + Soprano::Node convertIfBlankNode( const Soprano::Node & n ) { + if( n.isResource() ) { + const QString uriString = n.uri().toString(); + if( uriString.startsWith("_:") ) { + return Soprano::Node( uriString.mid(2) ); // "_:" take 2 characters + } + } + return n; + } +} + +QList<Soprano::Statement> Nepomuk::ClassAndPropertyTree::simpleResourceToStatementList(const Nepomuk::SimpleResource &res) const +{ + const Soprano::Node subject = convertIfBlankNode(res.uri()); + QList<Soprano::Statement> list; + PropertyHash properties = res.properties(); + QHashIterator<QUrl, QVariant> it( properties ); + while( it.hasNext() ) { + it.next(); + const Soprano::Node object = variantToNode(it.value(), it.key()); + list << Soprano::Statement(subject, + it.key(), + convertIfBlankNode(object)); + } + return list; +} + +QList<Soprano::Statement> Nepomuk::ClassAndPropertyTree::simpleResourceGraphToStatementList(const Nepomuk::SimpleResourceGraph &graph) const +{ + QList<Soprano::Statement> list; + foreach(const SimpleResource& res, graph.toList()) { + list += simpleResourceToStatementList(res); + } + return list; +} + +QList<QUrl> Nepomuk::ClassAndPropertyTree::visibleTypes() const +{ + QList<QUrl> types; + QHash<QUrl, ClassOrProperty*>::const_iterator end = m_tree.constEnd(); + for(QHash<QUrl, ClassOrProperty*>::const_iterator it = m_tree.constBegin(); it != end; ++it) { + const ClassOrProperty* cop = *it; + if(!cop->isProperty && cop->userVisible == 1) { + types << cop->uri; + } + } + return types; +} + +Nepomuk::ClassAndPropertyTree * Nepomuk::ClassAndPropertyTree::self() +{ + return s_self; +} + +#include "classandpropertytree.moc" diff --git a/nepomuk/services/storage/classandpropertytree.h b/nepomuk/services/storage/classandpropertytree.h new file mode 100644 index 0000000..af31091 --- /dev/null +++ b/nepomuk/services/storage/classandpropertytree.h @@ -0,0 +1,109 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef CLASSANDPROPERTYTREE_H +#define CLASSANDPROPERTYTREE_H + +#include <QtCore/QObject> +#include <QtCore/QUrl> +#include <QtCore/QVariant> +#include <QtCore/QMutex> +#include <QtCore/QList> + +#include <Soprano/Error/ErrorCache> + +namespace Soprano { +class Model; +class Node; +class Statement; +} + + +namespace Nepomuk { + +class SimpleResource; +class SimpleResourceGraph; + +class ClassAndPropertyTree : public QObject, public Soprano::Error::ErrorCache +{ + Q_OBJECT + +public: + ClassAndPropertyTree(QObject *parent = 0); + ~ClassAndPropertyTree(); + + /// \return \p true if \p uri is a class + bool isKnownClass(const QUrl& uri) const; + + QSet<QUrl> allParents(const QUrl& uri) const; + bool isChildOf(const QUrl& type, const QUrl& superClass) const; + + /// Returns true if the uri is a Class or a Property + bool contains(const QUrl& uri) const; + + /** + * Returns true if any one of the uris in \p types is a child of \p superClass + */ + bool isChildOf(const QList<QUrl> & types, const QUrl& superClass) const; + int maxCardinality(const QUrl& type) const; + bool isUserVisible(const QUrl& type) const; + + QUrl propertyDomain(const QUrl& uri) const; + QUrl propertyRange(const QUrl& uri) const; + + /// \return \p true if \p uri is a property and has a literal range, \p false otherwise. + bool hasLiteralRange(const QUrl& uri) const; + + /// \return \p true if \p uri is an identifying property, \p false otherwise + bool isIdentifyingProperty(const QUrl& uri) const; + + /// \return A list of all known rdf classes that are visible (nao:userVisible) + QList<QUrl> visibleTypes() const; + + /// will try very hard to convert a variant into a node. Supports literal XML types and QUrl + Soprano::Node variantToNode(const QVariant& value, const QUrl& property) const; + QSet<Soprano::Node> variantListToNodeSet(const QVariantList& vl, const QUrl& property) const; + + QList<Soprano::Statement> simpleResourceToStatementList(const Nepomuk::SimpleResource& res) const; + QList<Soprano::Statement> simpleResourceGraphToStatementList(const Nepomuk::SimpleResourceGraph& graph) const; + + static ClassAndPropertyTree* self(); + +public Q_SLOTS: + void rebuildTree(Soprano::Model* model); + +private: + class ClassOrProperty; + + const ClassOrProperty* findClassOrProperty(const QUrl& uri) const; + int updateUserVisibility(ClassOrProperty *cop, QSet<QUrl> &visitedNodes); + int updateIdentifying(ClassOrProperty* cop, QSet<QUrl>& identifyingNodes); + QSet<QUrl> getAllParents(ClassOrProperty *cop, QSet<QUrl> &visitedNodes); + + QHash<QUrl, ClassOrProperty*> m_tree; + + mutable QMutex m_mutex; + + static ClassAndPropertyTree* s_self; +}; +} + +#endif diff --git a/nepomuk/services/storage/crappyinferencer2.cpp b/nepomuk/services/storage/crappyinferencer2.cpp index 95e5feb..1dbdf03 100644 --- a/nepomuk/services/storage/crappyinferencer2.cpp +++ b/nepomuk/services/storage/crappyinferencer2.cpp @@ -20,7 +20,7 @@ */ #include "crappyinferencer2.h" -#include "typevisibilitytree.h" +#include "classandpropertytree.h" #include <Soprano/Vocabulary/NRL> #include <Soprano/Vocabulary/NAO> @@ -61,7 +61,7 @@ public: QSet<QUrl> buildSuperClassesHash( const QUrl& type, QSet<QUrl>& visitedTypes, const QMultiHash<QUrl, QUrl>& rdfsSubClassRelations ); QHash<QUrl, QSet<QUrl> > m_superClasses; - TypeVisibilityTree* m_typeVisibilityTree; + Nepomuk::ClassAndPropertyTree* m_typeVisibilityTree; QMutex m_mutex; UpdateAllResourcesThread* m_updateAllResourcesThread; @@ -102,6 +102,10 @@ public: // resources that do not have visibility set are treated as invisible, thus there is no need // to write their visibility value Q_FOREACH(const QUrl& type, m_model->d->m_typeVisibilityTree->visibleTypes()) { + if(m_canceled) + break; + if(m_model->d->m_typeVisibilityTree->isChildOf(type, Soprano::Vocabulary::NRL::Graph())) + continue; const QString query = QString::fromLatin1("insert into graph %1 { ?r %2 1 . } where { " "graph ?g { ?r a %3 . } . " "FILTER(?g!=%1) . " @@ -117,12 +121,16 @@ public: #endif // update the resource type inference on all resources that were created before KDE 4.6.1 + // we ignore graphs entirely to save space and allow the DMS to use SPARUL to remove graphs Soprano::QueryResultIterator it = m_model->executeQuery(QString::fromLatin1("select distinct ?t where { ?t a %1 . " - "FILTER(bif:exists((select (1) where { graph ?g { ?r a ?t . } . FILTER(?g!=%2) . }))) . }") + "FILTER(bif:exists((select (1) where { graph ?g { ?r a ?t . } . FILTER(?g!=%2) . }))) . " + "FILTER(!bif:exists((select (1) where { ?t %3 %4 . }))) . }") .arg(Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::Class()), - Soprano::Node::resourceToN3(m_model->crappyInferenceContext())), + Soprano::Node::resourceToN3(m_model->crappyInferenceContext()), + Soprano::Node::resourceToN3(Soprano::Vocabulary::RDFS::subClassOf()), + Soprano::Node::resourceToN3(Soprano::Vocabulary::NRL::Graph())), Soprano::Query::QueryLanguageSparql); - while(it.next()) { + while(!m_canceled && it.next()) { const QUrl type = it["t"].uri(); const QSet<QUrl> superClasses = m_model->d->m_superClasses[type]; QString superTypeTerms; @@ -180,7 +188,7 @@ bool CrappyInferencer2::Private::isVisibleFromTypes(const QSet<QUrl> &types) con // if there is one leaf type left that is visible the resource is visible, too Q_FOREACH( const QUrl& type, leafTypes ) { - if( m_typeVisibilityTree->isVisible(type) ) { + if( m_typeVisibilityTree->isUserVisible(type) ) { return true; } } @@ -226,13 +234,13 @@ QSet<QUrl> CrappyInferencer2::Private::buildSuperClassesHash( const QUrl& type, } } -CrappyInferencer2::CrappyInferencer2(Soprano::Model* parent) +CrappyInferencer2::CrappyInferencer2(Nepomuk::ClassAndPropertyTree* tree, Soprano::Model* parent) : Soprano::FilterModel(parent), d(new Private()) { d->q = this; d->m_inferenceContext = QUrl::fromEncoded("urn:crappyinference2:inferredtriples"); - d->m_typeVisibilityTree = new TypeVisibilityTree(this); + d->m_typeVisibilityTree = tree; if ( parent ) updateInferenceIndex(); } @@ -268,11 +276,12 @@ Soprano::Error::ErrorCode CrappyInferencer2::addStatement(const Soprano::Stateme Soprano::Error::ErrorCode r = parentModel()->addStatement( statement ); if( r == Soprano::Error::ErrorNone ) { // - // Handle the inference + // Handle the inference. However, we ignore inference for all graphs to save space and allow the DMS to use SPARUL there. // if ( statement.subject().isResource() && - statement.object().isResource() && - statement.predicate() == Soprano::Vocabulary::RDF::type() ) { + statement.object().isResource() && + statement.predicate() == Soprano::Vocabulary::RDF::type() && + !d->m_typeVisibilityTree->isChildOf(statement.object().uri(), Soprano::Vocabulary::NRL::Graph())) { addInferenceStatements( statement.subject().uri(), statement.object().uri() ); } } @@ -486,16 +495,12 @@ void CrappyInferencer2::updateInferenceIndex() rdfsResIt.value().insert(Soprano::Vocabulary::RDFS::Resource()); } - // Build visibility tree - // ============================================== - d->m_typeVisibilityTree->rebuildTree(); - #ifndef NDEBUG // count for debugging int cnt = 0; for( QHash<QUrl, QSet<QUrl> >::const_iterator it = d->m_superClasses.constBegin(); it != d->m_superClasses.constEnd(); ++it ) { - kDebug() << it.key() << "->" << it.value(); + //kDebug() << it.key() << "->" << it.value(); cnt += it.value().count(); } kDebug() << "Number of classes: " << d->m_superClasses.count(); @@ -525,7 +530,7 @@ void CrappyInferencer2::addInferenceStatements( const QUrl& resource, const QSet QSet<QUrl> superTypes; Q_FOREACH( const QUrl& type, types ) { superTypes += d->superClasses(type); - if( !haveVisibleType && d->m_typeVisibilityTree->isVisible(type) ) + if( !haveVisibleType && d->m_typeVisibilityTree->isUserVisible(type) ) haveVisibleType = true; } QSet<QUrl>::const_iterator end = superTypes.constEnd(); @@ -575,7 +580,7 @@ void CrappyInferencer2::removeInferenceStatements(const QUrl &resource, const QL bool haveVisibleType = false; Q_FOREACH( const QUrl& typeToRemove, typesToRemove ) { - if( !haveVisibleType && d->m_typeVisibilityTree->isVisible(typeToRemove) ) + if( !haveVisibleType && d->m_typeVisibilityTree->isUserVisible(typeToRemove) ) haveVisibleType = true; parentModel()->removeStatement(resource, Soprano::Vocabulary::RDF::type(), diff --git a/nepomuk/services/storage/crappyinferencer2.h b/nepomuk/services/storage/crappyinferencer2.h index b17726d..37c3598 100644 --- a/nepomuk/services/storage/crappyinferencer2.h +++ b/nepomuk/services/storage/crappyinferencer2.h @@ -24,6 +24,10 @@ #include <Soprano/FilterModel> +namespace Nepomuk { +class ClassAndPropertyTree; +} + /** * The second version of the crappy inferencer it even crappier than the first. * It only does rdf:type inferencing, nothing else. In the long run this is not @@ -38,7 +42,7 @@ class CrappyInferencer2 : public Soprano::FilterModel Q_OBJECT public: - CrappyInferencer2(Soprano::Model* parent = 0); + CrappyInferencer2(Nepomuk::ClassAndPropertyTree* tree, Soprano::Model* parent = 0); ~CrappyInferencer2(); /** diff --git a/nepomuk/services/storage/datamanagementadaptor.cpp b/nepomuk/services/storage/datamanagementadaptor.cpp new file mode 100644 index 0000000..4ca8114 --- /dev/null +++ b/nepomuk/services/storage/datamanagementadaptor.cpp @@ -0,0 +1,225 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010-2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. + + The basis for this file was generated by qdbusxml2cpp version 0.7 + Command line was: qdbusxml2cpp -a datamanagementadaptor -c DataManagementAdaptor -m org.kde.nepomuk.DataManagement.xml + + qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + */ + +#include "datamanagementadaptor.h" +#include "datamanagementmodel.h" +#include "datamanagementcommand.h" +#include "simpleresourcegraph.h" +#include "dbustypes.h" + +#include <QtCore/QMetaObject> +#include <QtCore/QByteArray> +#include <QtCore/QList> +#include <QtCore/QMap> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QVariant> +#include <QtCore/QThreadPool> + +#define USING_SOPRANO_NRLMODEL_UNSTABLE_API +#include <Soprano/NRLModel> + + +Nepomuk::DataManagementAdaptor::DataManagementAdaptor(Nepomuk::DataManagementModel *parent) + : QObject(parent), + m_model(parent), + m_namespacePrefixRx(QLatin1String("(\\w+)\\:(\\w+)")) +{ + DBus::registerDBusTypes(); + + m_threadPool = new QThreadPool(this); + + // never let go of our threads - that is just pointless cpu cycles wasted + m_threadPool->setExpiryTimeout(-1); + + // N threads means N connections to Virtuoso + m_threadPool->setMaxThreadCount(10); +} + +Nepomuk::DataManagementAdaptor::~DataManagementAdaptor() +{ +} + +void Nepomuk::DataManagementAdaptor::addProperty(const QStringList &resources, const QString &property, const QVariantList &values, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new AddPropertyCommand(decodeUris(resources), decodeUri(property), Nepomuk::DBus::resolveDBusArguments(values), app, m_model, message())); +} + +void Nepomuk::DataManagementAdaptor::addProperty(const QString &resource, const QString &property, const QDBusVariant &value, const QString &app) +{ + addProperty(QStringList() << resource, property, QVariantList() << Nepomuk::DBus::resolveDBusArguments(value.variant()), app); +} + +QString Nepomuk::DataManagementAdaptor::createResource(const QStringList &types, const QString &label, const QString &description, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new CreateResourceCommand(decodeUris(types), label, description, app, m_model, message())); + // QtDBus will ignore this return value + return QString(); +} + +QString Nepomuk::DataManagementAdaptor::createResource(const QString &type, const QString &label, const QString &description, const QString &app) +{ + return createResource(QStringList() << type, label, description, app); +} + +QList<Nepomuk::SimpleResource> Nepomuk::DataManagementAdaptor::describeResources(const QStringList &resources, bool includeSubResources) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new DescribeResourcesCommand(decodeUris(resources), includeSubResources, m_model, message())); + // QtDBus will ignore this return value + return QList<SimpleResource>(); +} + +void Nepomuk::DataManagementAdaptor::storeResources(const QList<Nepomuk::SimpleResource>& resources, int identificationMode, int flags, const Nepomuk::PropertyHash &additionalMetadata, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new StoreResourcesCommand(resources, app, identificationMode, flags, additionalMetadata, m_model, message())); +} + +void Nepomuk::DataManagementAdaptor::mergeResources(const QString &resource1, const QString &resource2, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new MergeResourcesCommand(decodeUri(resource1), decodeUri(resource2), app, m_model, message())); +} + +void Nepomuk::DataManagementAdaptor::removeDataByApplication(int flags, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new RemoveDataByApplicationCommand(app, flags, m_model, message())); +} + +void Nepomuk::DataManagementAdaptor::removeDataByApplication(const QStringList &resources, int flags, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new RemoveResourcesByApplicationCommand(decodeUris(resources), app, flags, m_model, message())); +} + +void Nepomuk::DataManagementAdaptor::removeProperties(const QStringList &resources, const QStringList &properties, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new RemovePropertiesCommand(decodeUris(resources), decodeUris(properties), app, m_model, message())); +} + +void Nepomuk::DataManagementAdaptor::removeProperties(const QString &resource, const QString &property, const QString &app) +{ + removeProperties(QStringList() << resource, QStringList() << property, app); +} + +void Nepomuk::DataManagementAdaptor::removeProperty(const QStringList &resources, const QString &property, const QVariantList &values, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new RemovePropertyCommand(decodeUris(resources), decodeUri(property), Nepomuk::DBus::resolveDBusArguments(values), app, m_model, message())); +} + +void Nepomuk::DataManagementAdaptor::removeProperty(const QString &resource, const QString &property, const QDBusVariant &value, const QString &app) +{ + removeProperty(QStringList() << resource, property, QVariantList() << Nepomuk::DBus::resolveDBusArguments(value.variant()), app); +} + +void Nepomuk::DataManagementAdaptor::removeResources(const QStringList &resources, int flags, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new RemoveResourcesCommand(decodeUris(resources), app, flags, m_model, message())); +} + +void Nepomuk::DataManagementAdaptor::removeResources(const QString &resource, int flags, const QString &app) +{ + removeResources(QStringList() << resource, flags, app); +} + +void Nepomuk::DataManagementAdaptor::setProperty(const QStringList &resources, const QString &property, const QVariantList &values, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new SetPropertyCommand(decodeUris(resources), decodeUri(property), Nepomuk::DBus::resolveDBusArguments(values), app, m_model, message())); +} + +void Nepomuk::DataManagementAdaptor::setProperty(const QString &resource, const QString &property, const QDBusVariant &value, const QString &app) +{ + setProperty(QStringList() << resource, property, QVariantList() << Nepomuk::DBus::resolveDBusArguments(value.variant()), app); +} + +void Nepomuk::DataManagementAdaptor::enqueueCommand(DataManagementCommand *cmd) +{ + m_threadPool->start(cmd); +} + +QUrl Nepomuk::DataManagementAdaptor::decodeUri(const QString &s, bool namespaceAbbrExpansion) const +{ + if(namespaceAbbrExpansion) { + if(m_namespacePrefixRx.exactMatch(s)) { + const QString ns = m_namespacePrefixRx.cap(1); + const QString name = m_namespacePrefixRx.cap(2); + QHash<QString, QString>::const_iterator it = m_namespaces.constFind(ns); + if(it != m_namespaces.constEnd()) { + return QUrl::fromEncoded(QString(it.value() + name).toAscii()); + } + } + } + + // fallback + return Nepomuk::decodeUrl(s); +} + +QList<QUrl> Nepomuk::DataManagementAdaptor::decodeUris(const QStringList &urlStrings, bool namespaceAbbrExpansions) const +{ + QList<QUrl> urls; + Q_FOREACH(const QString& urlString, urlStrings) { + urls << decodeUri(urlString, namespaceAbbrExpansions); + } + return urls; +} + +void Nepomuk::DataManagementAdaptor::setPrefixes(const QHash<QString, QString>& prefixes) +{ + m_namespaces = prefixes; +} + +void Nepomuk::DataManagementAdaptor::importResources(const QString &url, const QString &serialization, int identificationMode, int flags, const QString &app) +{ + importResources(url, serialization, identificationMode, flags, PropertyHash(), app); +} + +void Nepomuk::DataManagementAdaptor::importResources(const QString &url, const QString &serialization, int identificationMode, int flags, const Nepomuk::PropertyHash &additionalMetadata, const QString &app) +{ + Q_ASSERT(calledFromDBus()); + setDelayedReply(true); + enqueueCommand(new ImportResourcesCommand(decodeUri(url), Soprano::mimeTypeToSerialization(serialization), serialization, identificationMode, flags, additionalMetadata, app, m_model, message())); +} + +#include "datamanagementadaptor.moc" diff --git a/nepomuk/services/storage/datamanagementadaptor.h b/nepomuk/services/storage/datamanagementadaptor.h new file mode 100644 index 0000000..05ef64c --- /dev/null +++ b/nepomuk/services/storage/datamanagementadaptor.h @@ -0,0 +1,105 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. + + The basis for this file was generated by qdbusxml2cpp version 0.7 + Command line was: qdbusxml2cpp -a datamanagementadaptor -c DataManagementAdaptor -m org.kde.nepomuk.DataManagement.xml + + qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). + */ + +#ifndef _DATAMANAGEMENTADAPTOR_H_ +#define _DATAMANAGEMENTADAPTOR_H_ + +#include <QtCore/QObject> +#include <QtCore/QHash> +#include <QtCore/QRegExp> +#include <QtDBus/QDBusContext> +#include <QtDBus/QDBusVariant> + +#include "simpleresource.h" + +class QThreadPool; + +namespace Nepomuk { +class DataManagementModel; +class DataManagementCommand; + +/* + * Adaptor class for interface org.kde.nepomuk.DataManagement + */ +class DataManagementAdaptor: public QObject, protected QDBusContext +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.nepomuk.DataManagement") + +public: + DataManagementAdaptor(Nepomuk::DataManagementModel* parent); + ~DataManagementAdaptor(); + + /** + * Set the prefixes that will be supported for script convenience. The provided hash + * maps prefixes to namespaces. Typical prefixes include "nao" or "rdfs". + */ + void setPrefixes(const QHash<QString, QString>& prefixes); + + /** + * Tries to decode a URI including namespace abbreviation lookup for known ontologies (Example: nao:Tag). + */ + QUrl decodeUri(const QString& s, bool namespaceAbbrExpansion = true) const; + + /** + * Tries to decode a list of URIs including namespace abbreviation lookup for known ontologies (Example: nao:Tag). + */ + QList<QUrl> decodeUris(const QStringList& s, bool namespaceAbbrExpansion = true) const; + +public Q_SLOTS: + Q_SCRIPTABLE void setProperty(const QStringList &resources, const QString &property, const QVariantList &values, const QString &app); + Q_SCRIPTABLE void addProperty(const QStringList &resources, const QString &property, const QVariantList &values, const QString &app); + Q_SCRIPTABLE void removeProperty(const QStringList &resources, const QString &property, const QVariantList &values, const QString &app); + Q_SCRIPTABLE void removeProperties(const QStringList &resources, const QStringList &properties, const QString &app); + Q_SCRIPTABLE QString createResource(const QStringList &types, const QString &label, const QString &description, const QString &app); + Q_SCRIPTABLE void removeResources(const QStringList &resources, int flags, const QString &app); + Q_SCRIPTABLE QList<Nepomuk::SimpleResource> describeResources(const QStringList &resources, bool includeSubResources); + Q_SCRIPTABLE void storeResources(const QList<Nepomuk::SimpleResource>& resources, int identificationMode, int flags, const Nepomuk::PropertyHash &additionalMetadata, const QString &app); + Q_SCRIPTABLE void mergeResources(const QString &resource1, const QString &resource2, const QString &app); + Q_SCRIPTABLE void removeDataByApplication(int flags, const QString &app); + Q_SCRIPTABLE void removeDataByApplication(const QStringList &resources, int flags, const QString &app); + Q_SCRIPTABLE void importResources(const QString& url, const QString& serialization, int identificationMode, int flags, const Nepomuk::PropertyHash &additionalMetadata, const QString& app); + + /// convinience overloads for scripts (no lists) + Q_SCRIPTABLE void setProperty(const QString &resource, const QString &property, const QDBusVariant &value, const QString &app); + Q_SCRIPTABLE void addProperty(const QString &resource, const QString &property, const QDBusVariant &value, const QString &app); + Q_SCRIPTABLE void removeProperty(const QString &resource, const QString &property, const QDBusVariant &value, const QString &app); + Q_SCRIPTABLE void removeProperties(const QString &resource, const QString &property, const QString &app); + Q_SCRIPTABLE QString createResource(const QString &type, const QString &label, const QString &description, const QString &app); + Q_SCRIPTABLE void removeResources(const QString &resource, int flags, const QString &app); + Q_SCRIPTABLE void importResources(const QString& url, const QString& serialization, int identificationMode, int flags, const QString& app); + +private: + void enqueueCommand(Nepomuk::DataManagementCommand* cmd); + + Nepomuk::DataManagementModel* m_model; + QThreadPool* m_threadPool; + QHash<QString, QString> m_namespaces; + QRegExp m_namespacePrefixRx; +}; +} + +#endif diff --git a/nepomuk/services/storage/datamanagementcommand.cpp b/nepomuk/services/storage/datamanagementcommand.cpp new file mode 100644 index 0000000..7cfc323 --- /dev/null +++ b/nepomuk/services/storage/datamanagementcommand.cpp @@ -0,0 +1,115 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "datamanagementcommand.h" +#include "datamanagementmodel.h" + +#include <Soprano/Error/Error> +#include <Soprano/Error/ErrorCode> + +#include <QtDBus/QDBusConnection> + +#include <QtCore/QStringList> +#include <QtCore/QEventLoop> + +#include <KUrl> + + +namespace { +QDBusError::ErrorType convertSopranoErrorCode(int code) +{ + switch(code) { + case Soprano::Error::ErrorInvalidArgument: + return QDBusError::InvalidArgs; + default: + return QDBusError::Failed; + } +} +} + + +Nepomuk::DataManagementCommand::DataManagementCommand(DataManagementModel* model, const QDBusMessage& msg) + : QRunnable(), + m_model(model), + m_msg(msg) +{ +} + +Nepomuk::DataManagementCommand::~DataManagementCommand() +{ +} + +void Nepomuk::DataManagementCommand::run() +{ + QVariant result = runCommand(); + Soprano::Error::Error error = model()->lastError(); + if(error) { + // send error reply + QDBusConnection::sessionBus().send(m_msg.createErrorReply(convertSopranoErrorCode(error.code()), error.message())); + } + else { + // encode result (ie. convert QUrl to QString) + if(result.isValid()) { + if(result.type() == QVariant::Url) { + result = encodeUrl(result.toUrl()); + } + QDBusConnection::sessionBus().send(m_msg.createReply(result)); + } + else { + QDBusConnection::sessionBus().send(m_msg.createReply()); + } + } + + // + // DBus requires event handling for signals to be emitted properly. + // (for example the Soprano statement signals which are emitted a + // lot during command execution.) + // Otherwise memory will fill up with queued DBus message objects. + // Instead of executing an event loop we avoid all the hassle and + // simply handle all events here. + // + QEventLoop loop; + loop.processEvents(); +} + + +// static +QUrl Nepomuk::decodeUrl(const QString& urlsString) +{ + // we use the power of KUrl to automatically convert file paths to file:/ URLs + return KUrl(urlsString); +} + +// static +QList<QUrl> Nepomuk::decodeUrls(const QStringList& urlStrings) +{ + QList<QUrl> urls; + Q_FOREACH(const QString& urlString, urlStrings) { + urls << decodeUrl(urlString); + } + return urls; +} + +// static +QString Nepomuk::encodeUrl(const QUrl& url) +{ + return QString::fromAscii(url.toEncoded()); +} diff --git a/nepomuk/services/storage/datamanagementcommand.h b/nepomuk/services/storage/datamanagementcommand.h new file mode 100644 index 0000000..c970404 --- /dev/null +++ b/nepomuk/services/storage/datamanagementcommand.h @@ -0,0 +1,377 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef DATAMANAGEMENTCOMMAND_H +#define DATAMANAGEMENTCOMMAND_H + +#include <QRunnable> +#include <QtCore/QVariant> +#include <QtDBus/QDBusMessage> + +#include "lib/dbustypes.h" +#include "lib/simpleresource.h" +#include "lib/simpleresourcegraph.h" +#include "lib/datamanagement.h" +#include "datamanagementmodel.h" + + +namespace Nepomuk { + +QUrl decodeUrl(const QString& urlsString); +QList<QUrl> decodeUrls(const QStringList& urlStrings); +QString encodeUrl(const QUrl& url); + +class DataManagementCommand : public QRunnable +{ +public: + DataManagementCommand(DataManagementModel* model, const QDBusMessage& msg); + virtual ~DataManagementCommand(); + + void run(); + + DataManagementModel* model() const { return m_model; } + +protected: + /** + * Reimplement this method. Return the original value from the call. + * It will be properly converted automatically. + * Error handling is done automatically, too. + * Only method calls to model() are supported. + */ + virtual QVariant runCommand() = 0; + +private: + DataManagementModel* m_model; + QDBusMessage m_msg; +}; + +class AddPropertyCommand : public DataManagementCommand +{ +public: + AddPropertyCommand(const QList<QUrl>& res, + const QUrl& property, + const QVariantList& values, + const QString& app, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_resources(res), + m_property(property), + m_values(values), + m_app(app) {} + +private: + QVariant runCommand() { + model()->addProperty(m_resources, m_property, m_values, m_app); + return QVariant(); + } + + QList<QUrl> m_resources; + QUrl m_property; + QVariantList m_values; + QString m_app; +}; + +class SetPropertyCommand : public DataManagementCommand +{ +public: + SetPropertyCommand(const QList<QUrl>& res, + const QUrl& property, + const QVariantList& values, + const QString& app, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_resources(res), + m_property(property), + m_values(values), + m_app(app) {} + +private: + QVariant runCommand() { + model()->setProperty(m_resources, m_property, m_values, m_app); + return QVariant(); + } + + QList<QUrl> m_resources; + QUrl m_property; + QVariantList m_values; + QString m_app; +}; + +class CreateResourceCommand : public DataManagementCommand +{ +public: + CreateResourceCommand(const QList<QUrl>& resources, + const QString& label, + const QString& desc, + const QString& app, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_resources(resources), + m_label(label), + m_desc(desc), + m_app(app) {} + +private: + QVariant runCommand() { + return model()->createResource(m_resources, m_label, m_desc, m_app); + } + + QList<QUrl> m_resources; + QString m_label; + QString m_desc; + QString m_app; +}; + +class StoreResourcesCommand : public DataManagementCommand +{ +public: + StoreResourcesCommand(const SimpleResourceGraph& resources, + const QString& app, + int identificationMode, + int flags, + const QHash<QUrl, QVariant>& additionalMetadata, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_resources(resources), + m_app(app), + m_identificationMode(Nepomuk::StoreIdentificationMode(identificationMode)), + m_flags(flags), + m_additionalMetadata(additionalMetadata) {} + +private: + QVariant runCommand() { + model()->storeResources(m_resources, m_app, m_identificationMode, m_flags, m_additionalMetadata); + return QVariant(); + } + + SimpleResourceGraph m_resources; + QString m_app; + Nepomuk::StoreIdentificationMode m_identificationMode; + Nepomuk::StoreResourcesFlags m_flags; + QHash<QUrl, QVariant> m_additionalMetadata; +}; + +class MergeResourcesCommand : public DataManagementCommand +{ +public: + MergeResourcesCommand(const QUrl& resource1, + const QUrl& resource2, + const QString& app, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_resource1(resource1), + m_resource2(resource2), + m_app(app) {} + +private: + QVariant runCommand() { + model()->mergeResources(m_resource1, m_resource2, m_app); + return QVariant(); + } + + QUrl m_resource1; + QUrl m_resource2; + QString m_app; +}; + +class RemoveResourcesByApplicationCommand : public DataManagementCommand +{ +public: + RemoveResourcesByApplicationCommand(const QList<QUrl>& res, + const QString& app, + int flags, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_resources(res), + m_app(app), + m_flags(flags) {} + +private: + QVariant runCommand() { + model()->removeDataByApplication(m_resources, m_flags, m_app); + return QVariant(); + } + + QList<QUrl> m_resources; + QString m_app; + Nepomuk::RemovalFlags m_flags; +}; + +class RemoveDataByApplicationCommand : public DataManagementCommand +{ +public: + RemoveDataByApplicationCommand(const QString& app, + int flags, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_app(app), + m_flags(flags) {} + +private: + QVariant runCommand() { + model()->removeDataByApplication(m_flags, m_app); + return QVariant(); + } + + QString m_app; + Nepomuk::RemovalFlags m_flags; +}; + +class RemovePropertiesCommand : public DataManagementCommand +{ +public: + RemovePropertiesCommand(const QList<QUrl>& res, + const QList<QUrl>& properties, + const QString& app, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_resources(res), + m_properties(properties), + m_app(app) {} + +private: + QVariant runCommand() { + model()->removeProperties(m_resources, m_properties, m_app); + return QVariant(); + } + + QList<QUrl> m_resources; + QList<QUrl> m_properties; + QString m_app; +}; + +class RemovePropertyCommand : public DataManagementCommand +{ +public: + RemovePropertyCommand(const QList<QUrl>& res, + const QUrl& property, + const QVariantList& values, + const QString& app, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_resources(res), + m_property(property), + m_values(values), + m_app(app) {} + +private: + QVariant runCommand() { + model()->removeProperty(m_resources, m_property, m_values, m_app); + return QVariant(); + } + + QList<QUrl> m_resources; + QUrl m_property; + QVariantList m_values; + QString m_app; +}; + +class RemoveResourcesCommand : public DataManagementCommand +{ +public: + RemoveResourcesCommand(const QList<QUrl>& res, + const QString& app, + int flags, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_resources(res), + m_app(app), + m_flags(flags) {} + +private: + QVariant runCommand() { + model()->removeResources(m_resources, m_flags, m_app); + return QVariant(); + } + + QList<QUrl> m_resources; + QString m_app; + Nepomuk::RemovalFlags m_flags; +}; + +class DescribeResourcesCommand : public DataManagementCommand +{ +public: + DescribeResourcesCommand(const QList<QUrl>& res, + bool includeSubResources, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_resources(res), + m_includeSubResources(includeSubResources) {} + +private: + QVariant runCommand() { + return QVariant::fromValue(model()->describeResources(m_resources, m_includeSubResources).toList()); + } + + QList<QUrl> m_resources; + bool m_includeSubResources; +}; + +class ImportResourcesCommand : public DataManagementCommand +{ +public: + ImportResourcesCommand(const QUrl& url, + Soprano::RdfSerialization serialization, + const QString& userSerialization, + int identificationMode, + int flags, + const PropertyHash& additionalProps, + const QString& app, + Nepomuk::DataManagementModel* model, + const QDBusMessage& msg) + : DataManagementCommand(model, msg), + m_url(url), + m_serialization(serialization), + m_userSerialization(userSerialization), + m_identificationMode(Nepomuk::StoreIdentificationMode(identificationMode)), + m_flags(flags), + m_additionalProperties(additionalProps), + m_app(app) {} + +private: + QVariant runCommand() { + model()->importResources(m_url, m_app, m_serialization, m_userSerialization, m_identificationMode, m_flags, m_additionalProperties); + return QVariant(); + } + + QUrl m_url; + Soprano::RdfSerialization m_serialization; + QString m_userSerialization; + Nepomuk::StoreIdentificationMode m_identificationMode; + Nepomuk::StoreResourcesFlags m_flags; + PropertyHash m_additionalProperties; + QString m_app; +}; +} + +#endif diff --git a/nepomuk/services/storage/datamanagementmodel.cpp b/nepomuk/services/storage/datamanagementmodel.cpp new file mode 100644 index 0000000..af653f9 --- /dev/null +++ b/nepomuk/services/storage/datamanagementmodel.cpp @@ -0,0 +1,2590 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010-2011 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "datamanagementmodel.h" +#include "classandpropertytree.h" +#include "resourcemerger.h" +#include "resourceidentifier.h" +#include "simpleresourcegraph.h" +#include "simpleresource.h" +#include "resourcewatchermanager.h" +#include "syncresource.h" +#include "nepomuktools.h" + +#include <Soprano/Vocabulary/NRL> +#include <Soprano/Vocabulary/NAO> +#include <Soprano/Vocabulary/RDF> +#include <Soprano/Vocabulary/RDFS> + +#include <Soprano/Graph> +#include <Soprano/QueryResultIterator> +#include <Soprano/StatementIterator> +#include <Soprano/NodeIterator> +#include <Soprano/Error/ErrorCode> +#include <Soprano/Parser> +#include <Soprano/PluginManager> + +#include <QtCore/QHash> +#include <QtCore/QUrl> +#include <QtCore/QVariant> +#include <QtCore/QDateTime> +#include <QtCore/QUuid> +#include <QtCore/QSet> +#include <QtCore/QPair> +#include <QtCore/QFileInfo> + +#include <Nepomuk/Vocabulary/NIE> +#include <Nepomuk/Vocabulary/NFO> + +#include <KDebug> +#include <KService> +#include <KServiceTypeTrader> +#include <KProtocolInfo> + +#include <KIO/NetAccess> + +#define STRIGI_INDEX_GRAPH_FOR "http://www.strigi.org/fields#indexGraphFor" + +using namespace Nepomuk::Vocabulary; +using namespace Soprano::Vocabulary; + +//// TODO: do not allow to create properties or classes through the "normal" methods. Instead provide methods for it. +//// IDEAS: +//// 1. Somehow handle nie:hasPart (at least in describeResources - compare text annotations where we only want to annotate part of a text) + +namespace { + /// convert a hash of URL->URI mappings to N3, omitting empty URIs. + /// This is a helper for the return type of DataManagementModel::resolveUrls + QStringList resourceHashToN3(const QHash<QUrl, QUrl>& urls) { + QStringList n3; + QHash<QUrl, QUrl>::const_iterator end = urls.constEnd(); + for(QHash<QUrl, QUrl>::const_iterator it = urls.constBegin(); it != end; ++it) { + if(!it.value().isEmpty()) + n3 << Soprano::Node::resourceToN3(it.value()); + } + return n3; + } + + QStringList nodesToN3(const QSet<Soprano::Node>& nodes) { + QStringList n3; + Q_FOREACH(const Soprano::Node& node, nodes) { + n3 << node.toN3(); + } + return n3; + } + + template<typename T> QString createResourceFilter(const T& resources, const QString& var, bool exclude = true) { + QString filter = QString::fromLatin1("%1 in (%2)").arg(var, Nepomuk::resourcesToN3(resources).join(QLatin1String(","))); + if(exclude) { + filter = QString::fromLatin1("!(%1)").arg(filter); + } + return filter; + } + + /* + * Creates a filter (without the "FILTER" keyword) which either excludes the \p propVar + * as once of the metadata properties or forces it to be one. + */ + QString createResourceMetadataPropertyFilter(const QString& propVar, bool exclude = true) { + return createResourceFilter(QList<QUrl>() + << NAO::created() + << NAO::lastModified() + << NAO::creator() + << NAO::userVisible() + << NIE::url(), + propVar, + exclude); + } + + enum UriState { + /// the URI is a URL which points to an existing local file + ExistingFileUrl, + /// the URI is a file URL which points to a non-existing local file + NonExistingFileUrl, + /// the URI is a URL which uses on of the protocols supported by KIO + SupportedUrl, + /// the URI is a nepomuk:/ URI + NepomukUri, + /// A uri of the form "_:id" + BlankUri, + /// A uri in the ontology + OntologyUri, + /// the URI is some other non-supported URI + OtherUri + }; + + /// Check if a URL points to a local file. This should be the only place where the local file is stat'ed + inline UriState uriState(const QUrl& uri, bool statLocalFiles = true) { + if(uri.scheme() == QLatin1String("nepomuk")) { + return NepomukUri; + } + else if(uri.scheme() == QLatin1String("file")) { + if(!statLocalFiles || + QFile::exists(uri.toLocalFile())) { + return ExistingFileUrl; + } + else { + return NonExistingFileUrl; + } + } + else if(Nepomuk::ClassAndPropertyTree::self()->contains(uri)) { + return OntologyUri; + } + // if supported by kio + else if( KProtocolInfo::isKnownProtocol(uri) ) { + return SupportedUrl; + } + else if(uri.toString().startsWith("_:") ) { + return BlankUri; + } + else { + return OtherUri; + } + } + + inline Soprano::Node convertIfBlankUri(const QUrl &uri) { + if( uri.toString().startsWith(QLatin1String("_:")) ) + return Soprano::Node( uri.toString().mid(2) ); + else + return Soprano::Node( uri ); + } +} + +class Nepomuk::DataManagementModel::Private +{ +public: + ClassAndPropertyTree* m_classAndPropertyTree; + ResourceWatcherManager* m_watchManager; + + /// a set of properties that are maintained by the service and cannot be changed by clients + QSet<QUrl> m_protectedProperties; +}; + +Nepomuk::DataManagementModel::DataManagementModel(Nepomuk::ClassAndPropertyTree* tree, Soprano::Model* model, QObject *parent) + : Soprano::FilterModel(model), + d(new Private()) +{ + d->m_classAndPropertyTree = tree; + d->m_watchManager = new ResourceWatcherManager(this); + + setParent(parent); + + // meta data properties are protected. This means they cannot be removed. But they + // can be set. + d->m_protectedProperties.insert(NAO::created()); + d->m_protectedProperties.insert(NAO::creator()); + d->m_protectedProperties.insert(NAO::lastModified()); + d->m_protectedProperties.insert(NAO::userVisible()); + d->m_protectedProperties.insert(NIE::url()); +} + +Nepomuk::DataManagementModel::~DataManagementModel() +{ + delete d; +} + + +Soprano::Error::ErrorCode Nepomuk::DataManagementModel::updateModificationDate(const QUrl& resource, const QUrl & graph, const QDateTime& date, bool includeCreationDate) +{ + return updateModificationDate(QSet<QUrl>() << resource, graph, date, includeCreationDate); +} + + +Soprano::Error::ErrorCode Nepomuk::DataManagementModel::updateModificationDate(const QSet<QUrl>& resources, const QUrl & graph, const QDateTime& date, bool includeCreationDate) +{ + if(resources.isEmpty()) { + return Soprano::Error::ErrorNone; + } + + QUrl metadataGraph(graph); + if(metadataGraph.isEmpty()) { + metadataGraph = createGraph(); + } + + QSet<QUrl> mtimeGraphs; + Soprano::QueryResultIterator it = executeQuery(QString::fromLatin1("select distinct ?g where { graph ?g { ?r %1 ?d . FILTER(?r in (%2)) . } . }") + .arg(Soprano::Node::resourceToN3(NAO::lastModified()), + resourcesToN3(resources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + while(it.next()) { + mtimeGraphs << it[0].uri(); + } + + foreach(const QUrl& resource, resources) { + Soprano::Error::ErrorCode c = removeAllStatements(resource, NAO::lastModified(), Soprano::Node()); + if (c != Soprano::Error::ErrorNone) + return c; + addStatement(resource, NAO::lastModified(), Soprano::LiteralValue( date ), metadataGraph); + if(includeCreationDate && !containsAnyStatement(resource, NAO::created(), Soprano::Node())) { + addStatement(resource, NAO::created(), Soprano::LiteralValue( date ), metadataGraph); + } + } + + removeTrailingGraphs(mtimeGraphs); + + return Soprano::Error::ErrorNone; +} + +void Nepomuk::DataManagementModel::addProperty(const QList<QUrl> &resources, const QUrl &property, const QVariantList &values, const QString &app) +{ + // + // Check parameters + // + if(app.isEmpty()) { + setError(QLatin1String("addProperty: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(resources.isEmpty()) { + setError(QLatin1String("addProperty: No resource specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + foreach( const QUrl & res, resources ) { + if(res.isEmpty()) { + setError(QLatin1String("addProperty: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument); + return; + } + } + if(property.isEmpty()) { + setError(QLatin1String("addProperty: Property needs to be specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(values.isEmpty()) { + setError(QLatin1String("addProperty: Values needs to be specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(d->m_protectedProperties.contains(property)) { + setError(QString::fromLatin1("addProperty: %1 is a protected property which can only be set.").arg(property.toString()), + Soprano::Error::ErrorInvalidArgument); + return; + } + + + // + // Convert all values to RDF nodes. This includes the property range check and conversion of local file paths to file URLs + // + const QSet<Soprano::Node> nodes = d->m_classAndPropertyTree->variantListToNodeSet(values, property); + if(nodes.isEmpty()) { + setError(d->m_classAndPropertyTree->lastError()); + return; + } + + + // + // We need to ensure that no client removes any ontology constructs or graphs, + // we can check this before resolving file URLs since no protected resource will + // ever have a nie:url + // + if(containsResourceWithProtectedType(QSet<QUrl>::fromList(resources))) { + return; + } + + + clearError(); + + + // + // Hash to keep mapping from provided URL/URI to resource URIs + // + QHash<Soprano::Node, Soprano::Node> resolvedNodes; + + + if(property == NIE::url()) { + if(resources.count() != 1) { + setError(QLatin1String("addProperty: no two resources can have the same nie:url."), Soprano::Error::ErrorInvalidArgument); + return; + } + else if(nodes.count() > 1) { + setError(QLatin1String("addProperty: One resource can only have one nie:url."), Soprano::Error::ErrorInvalidArgument); + return; + } + + if(!nodes.isEmpty()) { + // check if another resource already uses the URL - no two resources can have the same URL at the same time + // CAUTION: There is one theoretical situation in which this breaks (more than this actually): + // A file is moved and before the nie:url is updated data is added to the file in the new location. + // At this point the file is there twice and the data should ideally be merged. But how to decide that + // and how to distiguish between that situation and a file overwrite? + if(containsAnyStatement(Soprano::Node(), NIE::url(), *nodes.constBegin())) { + setError(QLatin1String("addProperty: No two resources can have the same nie:url at the same time."), Soprano::Error::ErrorInvalidArgument); + return; + } + else if(containsAnyStatement(resources.first(), NIE::url(), Soprano::Node())) { + // TODO: this can be removed as soon as nie:url gets a max cardinality of 1 + // vHanda: nie:url as of KDE 4.6 has a max cardinality of 1. This is due to the + // nepomuk resource identification ontology + setError(QLatin1String("addProperty: One resource can only have one nie:url."), Soprano::Error::ErrorInvalidArgument); + return; + } + + // nie:url is the only property for which we do not want to resolve URLs + resolvedNodes.insert(*nodes.constBegin(), *nodes.constBegin()); + } + } + else { + resolvedNodes = resolveNodes(nodes); + if(lastError()) { + return; + } + } + + + // + // Resolve local file URLs (we need to hash the values since we do not want to write anything yet) + // + QHash<QUrl, QUrl> uriHash = resolveUrls(resources); + if(lastError()) { + return; + } + + + // + // Check cardinality conditions + // + const int maxCardinality = d->m_classAndPropertyTree->maxCardinality(property); + if( maxCardinality == 1 ) { + // check if any of the resources already has a value set which differs from the one we want to add + + // an empty hashed value means that the resource for a file URL does not exist yet. Thus, there is + // no need to filter it out. We basically only need to check if any value exists. + QString valueFilter; + if(resolvedNodes.constBegin().value().isValid()) { + valueFilter = QString::fromLatin1("FILTER(?v!=%3) . ") + .arg(resolvedNodes.constBegin().value().toN3()); + } + + QStringList terms; + Q_FOREACH(const QUrl& res, resources) { + if(!uriHash[res].isEmpty()) { + terms << QString::fromLatin1("%1 %2 ?v . %3") + .arg(Soprano::Node::resourceToN3(uriHash[res]), + Soprano::Node::resourceToN3(property), + valueFilter); + } + } + + const QString cardinalityQuery = QString::fromLatin1("ask where { { %1 } }") + .arg(terms.join(QLatin1String("} UNION {"))); + + if(executeQuery(cardinalityQuery, Soprano::Query::QueryLanguageSparql).boolValue()) { + 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.") + .arg(Soprano::Node::resourceToN3(property)), Soprano::Error::ErrorInvalidArgument); + return; + } + } + + + // + // Do the actual work + // + addProperty(uriHash, property, resolvedNodes, app); +} + + +// setting a property can be implemented by way of addProperty. All we have to do before calling addProperty is to remove all +// the values that are not in the setProperty call +void Nepomuk::DataManagementModel::setProperty(const QList<QUrl> &resources, const QUrl &property, const QVariantList &values, const QString &app) +{ + // + // Special case: setting to the empty list + // + if(values.isEmpty()) { + removeProperties(resources, QList<QUrl>() << property, app); + return; + } + + // + // Check parameters + // + if(app.isEmpty()) { + setError(QLatin1String("setProperty: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(resources.isEmpty()) { + setError(QLatin1String("setProperty: No resource specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + foreach( const QUrl & res, resources ) { + if(res.isEmpty()) { + setError(QLatin1String("setProperty: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument); + return; + } + } + if(property.isEmpty()) { + setError(QLatin1String("setProperty: Property needs to be specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + + + // + // Convert all values to RDF nodes. This includes the property range check and conversion of local file paths to file URLs + // + const QSet<Soprano::Node> nodes = d->m_classAndPropertyTree->variantListToNodeSet(values, property); + if(nodes.isEmpty()) { + setError(d->m_classAndPropertyTree->lastError()); + return; + } + + + // + // We need to ensure that no client removes any ontology constructs or graphs, + // we can check this before resolving file URLs since no protected resource will + // ever have a nie:url + // + if(containsResourceWithProtectedType(QSet<QUrl>::fromList(resources))) { + return; + } + + + clearError(); + + + // + // Hash to keep mapping from provided URL/URI to resource URIs + // + QHash<Soprano::Node, Soprano::Node> resolvedNodes; + + // + // Setting nie:url on file resources also means updating nfo:fileName and possibly nie:isPartOf + // + if(property == NIE::url()) { + if(resources.count() != 1) { + setError(QLatin1String("setProperty: no two resources can have the same nie:url."), Soprano::Error::ErrorInvalidArgument); + return; + } + else if(nodes.count() > 1) { + setError(QLatin1String("setProperty: One resource can only have one nie:url."), Soprano::Error::ErrorInvalidArgument); + return; + } + + // check if another resource already uses the URL - no two resources can have the same URL at the same time + // CAUTION: There is one theoretical situation in which this breaks (more than this actually): + // A file is moved and before the nie:url is updated data is added to the file in the new location. + // At this point the file is there twice and the data should ideally be merged. But how to decide that + // and how to distiguish between that situation and a file overwrite? + if(containsAnyStatement(Soprano::Node(), NIE::url(), *nodes.constBegin())) { + setError(QLatin1String("setProperty: No two resources can have the same nie:url at the same time."), Soprano::Error::ErrorInvalidArgument); + return; + } + + if(updateNieUrlOnLocalFile(resources.first(), nodes.constBegin()->uri())) { + return; + } + + // nie:url is the only property for which we do not want to resolve URLs + resolvedNodes.insert(*nodes.constBegin(), *nodes.constBegin()); + } + else { + resolvedNodes = resolveNodes(nodes); + if(lastError()) { + return; + } + } + + // + // Resolve local file URLs + // + QHash<QUrl, QUrl> uriHash = resolveUrls(resources); + if(lastError()) { + return; + } + + // + // Remove values that are not wanted anymore + // + const QStringList uriHashN3 = resourceHashToN3(uriHash); + if(!uriHashN3.isEmpty()) { + const QSet<Soprano::Node> existingValues = QSet<Soprano::Node>::fromList(resolvedNodes.values()); + QSet<QUrl> graphs; + QList<Soprano::BindingSet> existing + = executeQuery(QString::fromLatin1("select ?r ?v ?g where { graph ?g { ?r %1 ?v . FILTER(?r in (%2)) . } . }") + .arg(Soprano::Node::resourceToN3(property), + uriHashN3.join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql).allBindings(); + Q_FOREACH(const Soprano::BindingSet& binding, existing) { + if(!existingValues.contains(binding["v"])) { + removeAllStatements(binding["r"], property, binding["v"]); + graphs.insert(binding["g"].uri()); + d->m_watchManager->removeProperty(binding["r"], property, binding["v"]); + } + } + removeTrailingGraphs(graphs); + } + + + // + // And finally add the rest of the statements (only if there is anything to add) + // + if(!nodes.isEmpty()) { + addProperty(uriHash, property, resolvedNodes, app); + } +} + +void Nepomuk::DataManagementModel::removeProperty(const QList<QUrl> &resources, const QUrl &property, const QVariantList &values, const QString &app) +{ + // 1. remove the triples + // 2. remove trailing graphs + // 3. update resource mtime only if we actually change anything on the resource + + // + // Check arguments + // + if(app.isEmpty()) { + setError(QLatin1String("removeProperty: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(resources.isEmpty()) { + setError(QLatin1String("removeProperty: No resource specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + foreach( const QUrl & res, resources ) { + if(res.isEmpty()) { + setError(QLatin1String("removeProperty: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument); + return; + } + } + if(property.isEmpty()) { + setError(QLatin1String("removeProperty: Property needs to be specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(values.isEmpty()) { + setError(QLatin1String("removeProperty: Values needs to be specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(d->m_protectedProperties.contains(property)) { + setError(QString::fromLatin1("removeProperty: %1 is a protected property which can only be changed by the data management service itself.").arg(property.toString()), + Soprano::Error::ErrorInvalidArgument); + return; + } + + const QSet<Soprano::Node> valueNodes = d->m_classAndPropertyTree->variantListToNodeSet(values, property); + if(valueNodes.isEmpty()) { + setError(d->m_classAndPropertyTree->lastError()); + return; + } + + + clearError(); + + + // + // Resolve file URLs, we can simply ignore the non-existing file resources which are reflected by empty resolved URIs + // + QSet<QUrl> resolvedResources = QSet<QUrl>::fromList(resolveUrls(resources).values()); + resolvedResources.remove(QUrl()); + if(resolvedResources.isEmpty() || lastError()) { + return; + } + + QSet<Soprano::Node> resolvedNodes = QSet<Soprano::Node>::fromList(resolveNodes(valueNodes).values()); + resolvedNodes.remove(Soprano::Node()); + if(resolvedNodes.isEmpty() || lastError()) { + return; + } + + + // + // We need to ensure that no client removes any ontology constructs or graphs + // + if(containsResourceWithProtectedType(resolvedResources)) { + return; + } + + + // + // Actually change data + // + QUrl mtimeGraph; + QSet<QUrl> graphs; + const QString propertyN3 = Soprano::Node::resourceToN3(property); + foreach( const QUrl & res, resolvedResources ) { + const QList<Soprano::BindingSet> valueGraphs + = executeQuery(QString::fromLatin1("select ?g ?v where { graph ?g { %1 %2 ?v . } . FILTER(?v in (%3)) . }") + .arg(Soprano::Node::resourceToN3(res), + propertyN3, + nodesToN3(resolvedNodes).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql).allBindings(); + + foreach(const Soprano::BindingSet& binding, valueGraphs) { + graphs.insert( binding["g"].uri() ); + removeAllStatements( res, property, binding["v"] ); + d->m_watchManager->removeProperty( res, property, binding["v"]); + } + + // we only update the mtime in case we actually remove anything + if(!valueGraphs.isEmpty()) { + // If the resource is now empty we remove it completely + if(!doesResourceExist(res)) { + removeResources(QList<QUrl>() << res, NoRemovalFlags, app); + } + else { + if(mtimeGraph.isEmpty()) { + mtimeGraph = createGraph(app); + } + updateModificationDate(res, mtimeGraph); + } + } + } + + removeTrailingGraphs( graphs ); +} + +void Nepomuk::DataManagementModel::removeProperties(const QList<QUrl> &resources, const QList<QUrl> &properties, const QString &app) +{ + // 1. remove the triples + // 2. remove trailing graphs + // 3. update resource mtime + + // + // Check arguments + // + if(app.isEmpty()) { + setError(QLatin1String("removeProperties: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(resources.isEmpty()) { + setError(QLatin1String("removeProperties: No resource specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + foreach( const QUrl & res, resources ) { + if(res.isEmpty()) { + setError(QLatin1String("removeProperties: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument); + return; + } + } + if(properties.isEmpty()) { + setError(QLatin1String("removeProperties: No properties specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + foreach(const QUrl& property, properties) { + if(property.isEmpty()) { + setError(QLatin1String("removeProperties: Encountered empty property URI."), Soprano::Error::ErrorInvalidArgument); + return; + } + else if(d->m_protectedProperties.contains(property)) { + setError(QString::fromLatin1("removeProperties: %1 is a protected property which can only be changed by the data management service itself.").arg(property.toString()), + Soprano::Error::ErrorInvalidArgument); + return; + } + } + + + clearError(); + + + // + // Resolve file URLs, we can simply ignore the non-existing file resources which are reflected by empty resolved URIs + // + QSet<QUrl> resolvedResources = QSet<QUrl>::fromList(resolveUrls(resources).values()); + resolvedResources.remove(QUrl()); + if(resolvedResources.isEmpty() || lastError()) { + return; + } + + + // + // We need to ensure that no client removes any ontology constructs or graphs + // + if(containsResourceWithProtectedType(resolvedResources)) { + return; + } + + + // + // Actually change data + // + QUrl mtimeGraph; + QSet<QUrl> graphs; + foreach( const QUrl & res, resolvedResources ) { + QSet<Soprano::Node> propertiesToRemove; + QList<QPair<Soprano::Node, Soprano::Node> > propertyValues; + Soprano::QueryResultIterator it + = executeQuery(QString::fromLatin1("select distinct ?g ?p ?v where { graph ?g { %1 ?p ?v . } . FILTER(?p in (%2)) . }") + .arg(Soprano::Node::resourceToN3(res), + resourcesToN3(properties).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + while(it.next()) { + graphs.insert(it["g"].uri()); + propertiesToRemove.insert(it["p"]); + propertyValues.append(qMakePair(it["p"], it["v"])); + } + + // remove the data + foreach(const Soprano::Node& property, propertiesToRemove) { + removeAllStatements( res, property, Soprano::Node() ); + } + + // inform interested parties + for(QList<QPair<Soprano::Node, Soprano::Node> >::const_iterator it = propertyValues.constBegin(); + it != propertyValues.constEnd(); ++it) { + d->m_watchManager->removeProperty(res, it->first.uri(), it->second); + } + + // we only update the mtime in case we actually remove anything + if(!propertiesToRemove.isEmpty()) { + // If the resource is now empty we remove it completely + if(!doesResourceExist(res)) { + removeResources(QList<QUrl>() << res, NoRemovalFlags, app); + } + else { + if(mtimeGraph.isEmpty()) { + mtimeGraph = createGraph(app); + } + updateModificationDate(res, mtimeGraph); + } + } + } + + removeTrailingGraphs( graphs ); +} + + +QUrl Nepomuk::DataManagementModel::createResource(const QList<QUrl> &types, const QString &label, const QString &description, const QString &app) +{ + // 1. create an new graph + // 2. check if the app exists, if not create it in the new graph + // 3. create the new resource in the new graph + // 4. return the resource's URI + + // + // Check parameters + // + if(app.isEmpty()) { + setError(QLatin1String("createResource: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument); + return QUrl(); + } + else if(types.isEmpty()) { + setError(QLatin1String("createResource: No type specified. Cannot create resources without a type."), Soprano::Error::ErrorInvalidArgument); + return QUrl(); + } + else { + foreach(const QUrl& type, types) { + if(type.isEmpty()) { + setError(QLatin1String("createResource: Encountered empty type URI."), Soprano::Error::ErrorInvalidArgument); + return QUrl(); + } + else if(!d->m_classAndPropertyTree->isKnownClass(type)) { + setError(QLatin1String("createResource: Encountered invalid type URI."), Soprano::Error::ErrorInvalidArgument); + return QUrl(); + } + } + } + + clearError(); + + // create new URIs and a new graph + const QUrl graph = createGraph(app); + const QUrl resUri = createUri(ResourceUri); + + // add provided metadata + foreach(const QUrl& type, types) { + addStatement(resUri, RDF::type(), type, graph); + } + if(!label.isEmpty()) { + addStatement(resUri, NAO::prefLabel(), Soprano::LiteralValue::createPlainLiteral(label), graph); + } + if(!description.isEmpty()) { + addStatement(resUri, NAO::description(), Soprano::LiteralValue::createPlainLiteral(description), graph); + } + + // add basic metadata to the new resource + const QDateTime now = QDateTime::currentDateTime(); + addStatement(resUri, NAO::created(), Soprano::LiteralValue(now), graph); + addStatement(resUri, NAO::lastModified(), Soprano::LiteralValue(now), graph); + + // inform interested parties + d->m_watchManager->createResource(resUri, types); + + return resUri; +} + +void Nepomuk::DataManagementModel::removeResources(const QList<QUrl> &resources, RemovalFlags flags, const QString &app) +{ + kDebug() << resources << app << flags; + + Q_UNUSED(app); + // 1. get all sub-resources and check if they are used by some other resource (not in the list of resources to remove) + // for the latter one can use a bif:exists and a !filter(?s in <s>, <s>, ...) - based on the value of force + // 2. remove the resources and the sub-resources + // 3. drop trailing graphs (could be optimized by enumerating the graphs up front) + + // + // Check parameters + // + if(app.isEmpty()) { + setError(QLatin1String("removeResources: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(resources.isEmpty()) { + setError(QLatin1String("removeResources: No resource specified."), Soprano::Error::ErrorInvalidArgument); + return; + } + foreach( const QUrl & res, resources ) { + if(res.isEmpty()) { + setError(QLatin1String("removeResources: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument); + return; + } + } + + + // + // Resolve file URLs, we can simply ignore the non-existing file resources which are reflected by empty resolved URIs + // + QSet<QUrl> resolvedResources = QSet<QUrl>::fromList(resolveUrls(resources).values()); + resolvedResources.remove(QUrl()); + if(resolvedResources.isEmpty() || lastError()) { + return; + } + + + // + // We need to ensure that no client removes any ontology constructs or graphs + // + if(containsResourceWithProtectedType(resolvedResources)) { + return; + } + + + clearError(); + + + // + // Actually remove the data + // + removeAllResources(resolvedResources, flags); +} + +void Nepomuk::DataManagementModel::removeDataByApplication(const QList<QUrl> &resources, RemovalFlags flags, const QString &app) +{ + // + // Check parameters + // + if(app.isEmpty()) { + setError(QLatin1String("removeDataByApplication: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument); + return; + } + foreach( const QUrl & res, resources ) { + if(res.isEmpty()) { + setError(QLatin1String("removeDataByApplication: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument); + return; + } + } + + + clearError(); + + + // we handle one special case below: legacy data from the file indexer + const QUrl appRes = findApplicationResource(app, false); + if(appRes.isEmpty() && app != QLatin1String("nepomukindexer")) { + return; + } + + + // + // Resolve file URLs, we can simply ignore the non-existing file resources which are reflected by empty resolved URIs + // + QSet<QUrl> resolvedResources = QSet<QUrl>::fromList(resolveUrls(resources).values()); + resolvedResources.remove(QUrl()); + if(resolvedResources.isEmpty() || lastError()) { + return; + } + + + // + // Handle the sub-resources: we can delete all sub-resources of the deleted ones that are entirely defined by our app + // and are not related by other resources. + // this has to be done before deleting the resouces in resolvedResources. Otherwise the nao:hasSubResource relationships are already gone! + // + // Explanation of the query: + // The query selects all subresources of the resources in resolvedResources. + // It then filters out the sub-resources that have properties defined by other apps which are not metadata. + // It then filters out the sub-resources that are related from other resources that are not the ones being deleted. + // + if(flags & RemoveSubResoures && !appRes.isEmpty()) { + QSet<QUrl> subResources = resolvedResources; + int resCount = 0; + do { + resCount = resolvedResources.count(); + Soprano::QueryResultIterator it + = executeQuery(QString::fromLatin1("select ?r where { graph ?g { ?r ?p ?o . } . " + "?parent %1 ?r . " + "FILTER(?parent in (%2)) . " + "?g %3 %4 . " + "FILTER(!bif:exists((select (1) where { graph ?g2 { ?r ?p2 ?o2 . } . ?g2 %3 ?a2 . FILTER(?a2!=%4) . FILTER(%6) . }))) . " + "FILTER(!bif:exists((select (1) where { graph ?g2 { ?r2 ?p3 ?r . FILTER(%5) . } . FILTER(!bif:exists((select (1) where { ?x %1 ?r2 . FILTER(?x in (%2)) . }))) . }))) . " + "}") + .arg(Soprano::Node::resourceToN3(NAO::hasSubResource()), + resourcesToN3(subResources).join(QLatin1String(",")), + Soprano::Node::resourceToN3(NAO::maintainedBy()), + Soprano::Node::resourceToN3(appRes), + createResourceFilter(subResources, QLatin1String("?r2")), + createResourceMetadataPropertyFilter(QLatin1String("?p2"))), + Soprano::Query::QueryLanguageSparql); + subResources.clear(); + while(it.next()) { + subResources << it[0].uri(); + } + resolvedResources += subResources; + } while(resCount < resolvedResources.count()); + } + + QList<Soprano::BindingSet> graphRemovalCandidates; + + if(!appRes.isEmpty()) { + // + // Get the graphs we need to check with removeTrailingGraphs later on. + // query all graphs the app maintains which contain any information about one of the resources + // count the number of apps maintaining those graphs (later we will only remove the app as maintainer but keep the graph) + // count the number of metadata properties defined in those graphs (we will keep that information in case the resource is not fully removed) + // + // We combine the results of 2 queries since Virtuoso can optimize FILTER(?r in (...)) but cannot optimize FILTER(?r in (...) || ?o in (...)) + // + graphRemovalCandidates += executeQuery(QString::fromLatin1("select distinct " + "?g " + "(select count(distinct ?app) where { ?g %1 ?app . }) as ?c " + "where { " + "graph ?g { ?r ?p ?o . FILTER(?r in (%3)) . } . " + "?g %1 %2 . }") + .arg(Soprano::Node::resourceToN3(NAO::maintainedBy()), + Soprano::Node::resourceToN3(appRes), + resourcesToN3(resolvedResources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql).allElements(); + graphRemovalCandidates += executeQuery(QString::fromLatin1("select distinct " + "?g " + "(select count(distinct ?app) where { ?g %1 ?app . }) as ?c " + "where { " + "graph ?g { ?r ?p ?o . FILTER(?o in (%3)) . } . " + "?g %1 %2 . }") + .arg(Soprano::Node::resourceToN3(NAO::maintainedBy()), + Soprano::Node::resourceToN3(appRes), + resourcesToN3(resolvedResources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql).allElements(); + } + + + // + // Handle legacy file indexer data which was stored in graphs marked via a special property + // + if(app == QLatin1String("nepomukindexer")) { + graphRemovalCandidates += executeQuery(QString::fromLatin1("select distinct ?g (0) as ?c where { " + "?g <"STRIGI_INDEX_GRAPH_FOR"> ?r . " + "FILTER(?r in (%1)) . }") + .arg(Nepomuk::resourcesToN3(resolvedResources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql).allElements(); + } + + // + // Split the list of graph removal candidates into those we actually remove and those we only stop maintaining + // + QSet<QUrl> graphsToRemove; + QSet<QUrl> graphsToStopMaintaining; + for(QList<Soprano::BindingSet>::const_iterator it = graphRemovalCandidates.constBegin(); it != graphRemovalCandidates.constEnd(); ++it) { + if(it->value("c").literal().toInt() <= 1) { + graphsToRemove << it->value("g").uri(); + } + else { + graphsToStopMaintaining << it->value("g").uri(); + } + } + // free some mem + graphRemovalCandidates.clear(); + + + // the set of resources that we did modify but not remove entirely + QSet<QUrl> modifiedResources; + + + // + // Fetch all resources that are changed, ie. that are related to the deleted resource in some way. + // We need to update the mtime of those resources, too. + // + if(!appRes.isEmpty()) { + Soprano::QueryResultIterator relatedResIt = executeQuery(QString::fromLatin1("select distinct ?r where { " + "graph ?g { ?r ?p ?rr . } . " + "?g %1 %2 . " + "FILTER(?rr in (%3)) . " + "FILTER(!(?r in (%3))) . }") + .arg(Soprano::Node::resourceToN3(NAO::maintainedBy()), + Soprano::Node::resourceToN3(appRes), + resourcesToN3(resolvedResources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + while(relatedResIt.next()) { + modifiedResources.insert(relatedResIt[0].uri()); + } + } + + + // + // remove the resources + // Other apps might be maintainer, too. In that case only remove the app as a maintainer but keep the data + // + + // + // We cannot remove the metadata if the resource is not removed completely. + // Thus, we cache it and deal with it later. But we only need to cache the metadata from graphs + // we will actually delete. + // + // Tests showed that one query per graph is faster than one query for all graphs! + // + Soprano::Graph metadata; + foreach(const QUrl& g, graphsToRemove) { + Soprano::QueryResultIterator mdIt + = executeQuery(QString::fromLatin1("select ?r ?p ?o where { graph %3 { ?r ?p ?o . FILTER(?r in (%1)) . FILTER(%2) . } . }") + .arg(resourcesToN3(resolvedResources).join(QLatin1String(",")), + createResourceMetadataPropertyFilter(QLatin1String("?p"), false), + Soprano::Node::resourceToN3(g)), + Soprano::Query::QueryLanguageSparql); + + while(mdIt.next()) { + metadata.addStatement(mdIt["r"], mdIt["p"], mdIt["o"]); + } + } + + + // + // Gather the resources that we actually change, ie. those which have non-metadata props in the removed graphs + // + Soprano::QueryResultIterator mResIt + = executeQuery(QString::fromLatin1("select ?r where { graph ?g { ?r ?p ?o . FILTER(?r in (%1)) . FILTER(%2) . } . FILTER(?g in (%3)) . }") + .arg(resourcesToN3(resolvedResources).join(QLatin1String(",")), + createResourceMetadataPropertyFilter(QLatin1String("?p"), true), + resourcesToN3(graphsToRemove).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + while(mResIt.next()) { + modifiedResources.insert(mResIt[0].uri()); + } + + + // + // Remove the actual data. This has to be done using removeAllStatements. Otherwise the crappy inferencer cannot follow the changes. + // + foreach(const QUrl& g, graphsToRemove) { + foreach(const QUrl& res, resolvedResources) { + removeAllStatements(res, Soprano::Node(), Soprano::Node(), g); + removeAllStatements(Soprano::Node(), Soprano::Node(), res, g); + } + } + + + // + // Remove the app as mainainer from the rest of the graphs + // + foreach(const QUrl& g, graphsToStopMaintaining) { + const int otherCnt = executeQuery(QString::fromLatin1("select count (distinct ?other) where { " + "graph %1 { ?other ?op ?oo . " + "FILTER(!(?other in (%2)) && !(?oo in (%2))) . " + "} . }") + .arg(Soprano::Node::resourceToN3(g), + resourcesToN3(resolvedResources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql).iterateBindings(0).allElements().first().literal().toInt(); + if(otherCnt > 0) { + // + // if the graph contains anything else besides the data we want to delete + // we need to keep the app as maintainer of that data. That is only possible + // by splitting the graph. + // + const QUrl newGraph = splitGraph(g, QUrl(), QUrl()); + Q_ASSERT(!newGraph.isEmpty()); + + // + // we now have two graphs the the same metadata: g and newGraph. + // we now move the resources we delete into the new graph and only + // remove the app as maintainer from that graph. + // (we can use fancy queries since we do not actually change data. Thus + // the crappy inferencer and other models which act on statement commands + // are not really interested) + // + executeQuery(QString::fromLatin1("insert into %1 { ?r ?p ?o . } where { graph %2 { ?r ?p ?o . FILTER(?r in (%3) || ?o in (%3)) . } . }") + .arg(Soprano::Node::resourceToN3(newGraph), + Soprano::Node::resourceToN3(g), + resourcesToN3(resolvedResources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + executeQuery(QString::fromLatin1("delete from %1 { ?r ?p ?o . } where { ?r ?p ?o . FILTER(?r in (%2) || ?o in (%2)) . }") + .arg(Soprano::Node::resourceToN3(g), + resourcesToN3(resolvedResources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + + // and finally remove the app as maintainer of the new graph + removeAllStatements(newGraph, NAO::maintainedBy(), appRes); + } + else { + // we simply remove the app as maintainer of this graph + removeAllStatements(g, NAO::maintainedBy(), appRes); + } + } + + + // + // Determine the resources we did not remove completely. + // This includes resource that have still other properties than the metadata ones defined + // and those that have relations from other resources created by other applications and have a nie:url. + // The latter is a special case which has two reasons: + // 1. A nie:url identifies the resource even outside of Nepomuk + // 2. The indexers use this method to update indexed data. If the resource has incoming relations + // they should be kept between updates. (The only exception is the legacy index graph relation.) + // + QSet<QUrl> resourcesToRemoveCompletely(resolvedResources); + Soprano::QueryResultIterator resComplIt + = executeQuery(QString::fromLatin1("select ?r where { ?r ?p ?o . FILTER(?r in (%1)) . FILTER(%2) . }") + .arg(resourcesToN3(resolvedResources).join(QLatin1String(",")), + createResourceMetadataPropertyFilter(QLatin1String("?p"))), + Soprano::Query::QueryLanguageSparql); + while(resComplIt.next()) { + resourcesToRemoveCompletely.remove(resComplIt[0].uri()); + } + resComplIt = executeQuery(QString::fromLatin1("select ?r where { ?o ?p ?r . FILTER(?r in (%1)) . FILTER(?p != <"STRIGI_INDEX_GRAPH_FOR">) . }") + .arg(resourcesToN3(resolvedResources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + while(resComplIt.next()) { + const QUrl r = resComplIt[0].uri(); + if(metadata.containsAnyStatement(r, NIE::url(), Soprano::Node())) { + resourcesToRemoveCompletely.remove(resComplIt[0].uri()); + } + } + + // no need to update metadata on resource we remove completely + modifiedResources -= resourcesToRemoveCompletely; + + + // + // Re-add the metadata for the resources that we did not remove entirely + // + // 1. use the same time for all mtime changes + const QDateTime now = QDateTime::currentDateTime(); + // 2. Remove the metadata for the resources we remove completely + foreach(const QUrl& res, resourcesToRemoveCompletely) { + metadata.removeAllStatements(res, Soprano::Node(), Soprano::Node()); + } + // 3. Update the mtime for modifiedResources as those are the only ones we did touch + foreach(const QUrl& res, modifiedResources) { + if(metadata.containsAnyStatement(res, NAO::lastModified(), Soprano::Node())) { + metadata.removeAllStatements(res, NAO::lastModified(), Soprano::Node()); + metadata.addStatement(res, NAO::lastModified(), Soprano::LiteralValue(now)); + modifiedResources.remove(res); + } + } + // 4. add all the updated metadata into a new metadata graph + if(!metadata.isEmpty() || !modifiedResources.isEmpty()) { + const QUrl metadataGraph = createGraph(); + if(lastError()) { + return; + } + + foreach(const Soprano::Statement& s, metadata.toList()) { + addStatement(s.subject(), s.predicate(), s.object(), metadataGraph); + } + + // update mtime of all resources we did touch and not handle above yet + updateModificationDate(modifiedResources, metadataGraph, now); + } + + + // + // Remove the resources that are gone completely + // + if(!resourcesToRemoveCompletely.isEmpty()){ + removeAllResources(resourcesToRemoveCompletely, flags); + } + + // make sure we do not leave trailing graphs behind + removeTrailingGraphs(graphsToRemove); +} + +namespace { +struct GraphRemovalCandidate { + int appCnt; + QHash<QUrl, int> resources; +}; +} + +void Nepomuk::DataManagementModel::removeDataByApplication(RemovalFlags flags, const QString &app) +{ + // + // Check parameters + // + if(app.isEmpty()) { + setError(QLatin1String("removeDataByApplication: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument); + return; + } + + clearError(); + + const QUrl appRes = findApplicationResource(app, false); + if(appRes.isEmpty()) { + return; + } + + + // + // Get the graphs we need to check with removeTrailingGraphs later on. + // query all graphs the app maintains + // count the number of apps maintaining those graphs (later we will only remove the app as maintainer but keep the graph) + // count the number of metadata properties defined in those graphs (we will keep that information in case the resource is not fully removed) + // + QHash<QUrl, GraphRemovalCandidate> graphRemovalCandidates; + Soprano::QueryResultIterator it + = executeQuery(QString::fromLatin1("select distinct " + "?g ?r " + "(select count(distinct ?app) where { ?g %1 ?app . }) as ?c " + "(select count (*) where { graph ?g { ?r ?mp ?mo . FILTER(%3) . } . }) as ?mc " + "where { " + "graph ?g { ?r ?p ?o . } . " + "?g %1 %2 . " + "}") + .arg(Soprano::Node::resourceToN3(NAO::maintainedBy()), + Soprano::Node::resourceToN3(appRes), + createResourceMetadataPropertyFilter(QLatin1String("?mp"), false)), + Soprano::Query::QueryLanguageSparql); + while(it.next()) { + GraphRemovalCandidate& g = graphRemovalCandidates[it["g"].uri()]; + g.appCnt = it["c"].literal().toInt(); + g.resources[it["r"].uri()] = it["mc"].literal().toInt(); + } + + QSet<QUrl> allResources; + QSet<QUrl> graphs; + const QDateTime now = QDateTime::currentDateTime(); + QUrl mtimeGraph; + for(QHash<QUrl, GraphRemovalCandidate>::const_iterator it = graphRemovalCandidates.constBegin(); it != graphRemovalCandidates.constEnd(); ++it) { + const QUrl& g = it.key(); + const int appCnt = it.value().appCnt; + if(appCnt == 1) { + // + // Run through all resources and see what needs to be done + // TODO: this can surely be improved by moving at least one query + // one layer above + // + for(QHash<QUrl, int>::const_iterator resIt = it->resources.constBegin(); resIt != it->resources.constEnd(); ++resIt) { + const QUrl& res = resIt.key(); + const int metadataPropCount = resIt.value(); + if(doesResourceExist(res, g)) { + // + // We cannot remove the metadata if the resource is not removed completely + // Thus, we re-add them later on. + // trueg: as soon as we have real inference and do not rely on the crappy inferencer to update types via the + // Soprano statement manipulation methods we can use powerful queries like this one: + // executeQuery(QString::fromLatin1("delete from %1 { %2 ?p ?o . } where { %2 ?p ?o . FILTER(%3) . }") + // .arg(Soprano::Node::resourceToN3(g), + // Soprano::Node::resourceToN3(res), + // createResourceMetadataPropertyFilter(QLatin1String("?p"))), + // Soprano::Query::QueryLanguageSparql); + // + QList<Soprano::BindingSet> metadataProps; + if(metadataPropCount > 0) { + // remember the metadata props + metadataProps = executeQuery(QString::fromLatin1("select ?p ?o where { graph %1 { %2 ?p ?o . FILTER(%3) . } . }") + .arg(Soprano::Node::resourceToN3(g), + Soprano::Node::resourceToN3(res), + createResourceMetadataPropertyFilter(QLatin1String("?p"), false)), + Soprano::Query::QueryLanguageSparql).allBindings(); + } + + removeAllStatements(res, Soprano::Node(), Soprano::Node(), g); + removeAllStatements(Soprano::Node(), Soprano::Node(), res, g); + + foreach(const Soprano::BindingSet& set, metadataProps) { + addStatement(res, set["p"], set["o"], g); + } + + // update mtime + if(mtimeGraph.isEmpty()) { + mtimeGraph = createGraph(app); + if(lastError()) { + return; + } + } + updateModificationDate(res, mtimeGraph, now); + } + + allResources.insert(res); + } + + + graphs.insert(g); + } + else { + // we simply remove the app as maintainer of this graph + removeAllStatements(g, NAO::maintainedBy(), appRes); + } + } + + + // make sure we do not leave anything empty trailing around and propery update the mtime + QList<QUrl> resourcesToRemoveCompletely; + foreach(const QUrl& res, allResources) { + if(!doesResourceExist(res)) { + resourcesToRemoveCompletely << res; + } + } + if(!resourcesToRemoveCompletely.isEmpty()){ + removeResources(resourcesToRemoveCompletely, flags, app); + } + + + removeTrailingGraphs(graphs); +} + + +//// TODO: do not allow to create properties or classes this way +void Nepomuk::DataManagementModel::storeResources(const Nepomuk::SimpleResourceGraph &resources, + const QString &app, + Nepomuk::StoreIdentificationMode identificationMode, + Nepomuk::StoreResourcesFlags flags, + const QHash<QUrl, QVariant> &additionalMetadata) +{ + if(app.isEmpty()) { + setError(QLatin1String("storeResources: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(resources.isEmpty()) { + clearError(); + return; + } + + /// Holds the mapping of <file://url> to <nepomuk:/res/> uris + QHash<QUrl, QUrl> resolvedNodes; + + // + // Resolve the nie URLs which are present as resource uris + // + QSet<QUrl> allNonFileResources; + SimpleResourceGraph resGraph( resources ); + QList<SimpleResource> resGraphList = resGraph.toList(); + QMutableListIterator<SimpleResource> iter( resGraphList ); + while( iter.hasNext() ) { + SimpleResource & res = iter.next(); + + if( !res.isValid() ) { + setError(QLatin1String("storeResources: One of the resources is Invalid."), Soprano::Error::ErrorInvalidArgument); + return; + } + + const UriState state = uriState(res.uri()); + if(state == NepomukUri || state == BlankUri) { + allNonFileResources << res.uri(); + } + // Handle nie urls + else if(state == NonExistingFileUrl) { + setError(QString::fromLatin1("Cannot store information about non-existing local files. File '%1' does not exist.").arg(res.uri().toLocalFile()), Soprano::Error::ErrorInvalidArgument); + return; + } + else if(state == ExistingFileUrl || state == SupportedUrl) { + const QUrl nieUrl = res.uri(); + QUrl newResUri = resolveUrl( nieUrl ); + if( lastError() ) + return; + + if( newResUri.isEmpty() ) { + // Resolution of one url failed. Assign it a random blank uri + newResUri = SimpleResource().uri(); // HACK: improveme + + res.addProperty( NIE::url(), nieUrl ); + res.addProperty( RDF::type(), NFO::FileDataObject() ); + if( QFileInfo( nieUrl.toLocalFile() ).isDir() ) + res.addProperty( RDF::type(), NFO::Folder() ); + } + resolvedNodes.insert( nieUrl, newResUri ); + + res.setUri( newResUri ); + } + else if( state == OtherUri ) { + // Legacy support - Sucks but we need it + const QUrl legacyUri = resolveUrl( res.uri() ); + if( lastError() ) + return; + + allNonFileResources << legacyUri; + } + else if( state == OntologyUri ) { + setError(QLatin1String("It is not allowed to add classes or properties through this API."), Soprano::Error::ErrorInvalidArgument); + return; + } + } + resGraph = resGraphList; + + + // + // We need to ensure that no client removes any ontology constructs or graphs + // + if(!allNonFileResources.isEmpty() && + containsResourceWithProtectedType(allNonFileResources)) { + return; + } + + + ResourceIdentifier resIdent( identificationMode, this ); + QList<Soprano::Statement> allStatements; + QList<Sync::SyncResource> extraResources; + + + // + // Resolve URLs in property values and prepare the resource identifier + // + foreach( const SimpleResource& res, resGraph.toList() ) { + // Convert to a Sync::SyncResource + // + Sync::SyncResource syncRes( res.uri() ); //vHanda: Will this set the uri properly? + QHashIterator<QUrl, QVariant> hit( res.properties() ); + while( hit.hasNext() ) { + hit.next(); + + Soprano::Node n = d->m_classAndPropertyTree->variantToNode( hit.value(), hit.key() ); + // The ClassAndPropertyTree returns blank nodes as URIs. It does not understand the + // concept of blank nodes, and since only storeResources requires blank nodes, + // it's easier to keep the blank node logic outside. + if( n.isResource() ) + n = convertIfBlankUri( n.uri() ); + + const Soprano::Error::Error error = d->m_classAndPropertyTree->lastError(); + if( error ) { + setError( error.message(), error.code() ); + return; + } + syncRes.insert( hit.key(), n ); + } + + QMutableHashIterator<KUrl, Soprano::Node> it( syncRes ); + while( it.hasNext() ) { + it.next(); + + const Soprano::Node object = it.value(); + if( object.isResource() && it.key() != NIE::url() ) { + const UriState state = uriState(object.uri()); + if(state==NepomukUri || state==BlankUri || state == OntologyUri) { + continue; + } + else if(state == NonExistingFileUrl) { + setError(QString::fromLatin1("Cannot store information about non-existing local files. File '%1' does not exist.").arg(object.uri().toLocalFile()), + Soprano::Error::ErrorInvalidArgument); + return; + } + else if(state == ExistingFileUrl || state==SupportedUrl) { + const QUrl nieUrl = object.uri(); + // Need to resolve it + QHash< QUrl, QUrl >::const_iterator findIter = resolvedNodes.constFind( nieUrl ); + if( findIter != resolvedNodes.constEnd() ) { + it.setValue( convertIfBlankUri(findIter.value()) ); + } + else { + Sync::SyncResource newRes; + + // It doesn't exist, create it + QUrl resolvedUri = resolveUrl( nieUrl ); + if( resolvedUri.isEmpty() ) { + resolvedUri = SimpleResource().uri(); // HACK: improveme + + newRes.insert( RDF::type(), NFO::FileDataObject() ); + newRes.insert( NIE::url(), nieUrl ); + if( QFileInfo( nieUrl.toLocalFile() ).isDir() ) + newRes.insert( RDF::type(), NFO::Folder() ); + } + + newRes.setUri( resolvedUri ); + extraResources.append( newRes ); + + resolvedNodes.insert( nieUrl, resolvedUri ); + it.setValue( convertIfBlankUri(resolvedUri) ); + } + } + else if(state == OtherUri) { + // We use resolveUrl to check if the otherUri exists. If it doesn't exist, + // then resolveUrl which set the last error + const QUrl legacyUri = resolveUrl( object.uri() ); + if( lastError() ) + return; + + // It apparently exists, so we must support it + } + } + } // while( it.hasNext() ) + + // The resource is now ready. + // Push it into the Resource Identifier + QList< Soprano::Statement > stList = syncRes.toStatementList(); + allStatements << stList; + + if(stList.isEmpty()) { + setError(d->m_classAndPropertyTree->lastError()); + return; + } + + if( !syncRes.isValid() ) { + setError(QLatin1String("storeResources: Contains invalid resources."), Soprano::Error::ErrorParsingFailed); + return; + } + resIdent.addSyncResource( syncRes ); + } + + + // + // Check the created statements + // + foreach(const Soprano::Statement& s, allStatements) { + if(!s.isValid()) { + kDebug() << "Invalid statement after resource conversion:" << s; + setError(QLatin1String("storeResources: Encountered invalid statement after resource conversion."), Soprano::Error::ErrorInvalidArgument); + return; + } + } + + // + // For better identification add rdf:type and nie:url to resolvedNodes + // + QHashIterator<QUrl, QUrl> it( resolvedNodes ); + while( it.hasNext() ) { + it.next(); + + const QUrl fileUrl = it.key(); + const QUrl uri = it.value(); + + const Sync::SyncResource existingRes = resIdent.simpleResource( uri ); + + Sync::SyncResource res; + res.setUri( uri ); + + if( !existingRes.contains( RDF::type(), NFO::FileDataObject() ) ) + res.insert( RDF::type(), NFO::FileDataObject() ); + + if( !existingRes.contains( NIE::url(), fileUrl ) ) + res.insert( NIE::url(), fileUrl ); + + if( QFileInfo( fileUrl.toString() ).isDir() && !existingRes.contains( RDF::type(), NFO::Folder() ) ) + res.insert( RDF::type(), NFO::Folder() ); + + resIdent.addSyncResource( res ); + } + + clearError(); + + + // + // Perform the actual identification + // + resIdent.identifyAll(); + + if( resIdent.mappings().empty() ) { + kDebug() << "Nothing was mapped merging everything as it is."; + } + + foreach( const Sync::SyncResource & res, extraResources ) { + allStatements << res.toStatementList(); + } + + ResourceMerger merger( this, app, additionalMetadata, flags ); + merger.setMappings( resIdent.mappings() ); + if( !merger.merge( Soprano::Graph(allStatements) ) ) { + kDebug() << " MERGING FAILED! "; + kDebug() << "Setting error!" << merger.lastError(); + setError( merger.lastError() ); + } +} + +void Nepomuk::DataManagementModel::importResources(const QUrl &url, + const QString &app, + Soprano::RdfSerialization serialization, + const QString &userSerialization, + Nepomuk::StoreIdentificationMode identificationMode, + Nepomuk::StoreResourcesFlags flags, + const QHash<QUrl, QVariant>& additionalMetadata) +{ + // download the file + QString tmpFileName; + if(!KIO::NetAccess::download(url, tmpFileName, 0)) { + setError(QString::fromLatin1("Failed to download '%1'.").arg(url.toString())); + return; + } + + // guess the serialization + if(serialization == Soprano::SerializationUnknown) { + const QString extension = KUrl(url).fileName().section('.', -1).toLower(); + if(extension == QLatin1String("trig")) + serialization = Soprano::SerializationTrig; + else if(extension == QLatin1String("n3")) + serialization = Soprano::SerializationNTriples; + else if(extension == QLatin1String("xml")) + serialization = Soprano::SerializationRdfXml; + } + + const Soprano::Parser* parser = Soprano::PluginManager::instance()->discoverParserForSerialization(serialization, userSerialization); + if(!parser) { + setError(QString::fromLatin1("Failed to create parser for serialization '%1'").arg(Soprano::serializationMimeType(serialization, userSerialization))); + } + else { + SimpleResourceGraph graph; + Soprano::StatementIterator it = parser->parseFile(tmpFileName, QUrl(), serialization, userSerialization); + while(it.next()) { + graph.addStatement(*it); + } + if(parser->lastError()) { + setError(parser->lastError()); + } + else if(it.lastError()) { + setError(it.lastError()); + } + else { + storeResources(graph, app, identificationMode, flags, additionalMetadata); + } + } + + KIO::NetAccess::removeTempFile(tmpFileName); +} + +void Nepomuk::DataManagementModel::mergeResources(const QUrl &res1, const QUrl &res2, const QString &app) +{ + if(res1.isEmpty() || res2.isEmpty()) { + setError(QLatin1String("mergeResources: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument); + return; + } + if(app.isEmpty()) { + setError(QLatin1String("mergeResources: Empty application specified. This is not supported."), Soprano::Error::ErrorInvalidArgument); + return; + } + + // + // We need to ensure that no client removes any ontology constructs or graphs, + // we can check this before resolving file URLs since no protected resource will + // ever have a nie:url + // + if(containsResourceWithProtectedType(QSet<QUrl>() << res1 << res2)) { + return; + } + + clearError(); + + + // TODO: Is it correct that all metadata stays the same? + + // + // Copy all property values of res2 that are not also defined for res1 + // + const QList<Soprano::BindingSet> res2Properties + = executeQuery(QString::fromLatin1("select ?g ?p ?v (select count(distinct ?v2) where { %2 ?p ?v2 . }) as ?c where { " + "graph ?g { %1 ?p ?v . } . " + "FILTER(!bif:exists((select (1) where { %2 ?p ?v . }))) . " + "}") + .arg(Soprano::Node::resourceToN3(res2), + Soprano::Node::resourceToN3(res1)), + Soprano::Query::QueryLanguageSparql).allBindings(); + foreach(const Soprano::BindingSet& binding, res2Properties) { + const QUrl& prop = binding["p"].uri(); + // if the property has no cardinality of 1 or no value is defined yet we can simply convert the value to res1 + if(d->m_classAndPropertyTree->maxCardinality(prop) != 1 || + binding["c"].literal().toInt() == 0) { + addStatement(res1, prop, binding["v"], binding["g"]); + } + } + + + // + // Copy all backlinks from res2 that are not valid for res1 + // + const QList<Soprano::BindingSet> res2Backlinks + = executeQuery(QString::fromLatin1("select ?g ?p ?r where { graph ?g { ?r ?p %1 . } . " + "FILTER(?r!=%2) . " + "FILTER(!bif:exists((select (1) where { ?r ?p %2 . }))) . " + "}") + .arg(Soprano::Node::resourceToN3(res2), + Soprano::Node::resourceToN3(res1)), + Soprano::Query::QueryLanguageSparql).allBindings(); + foreach(const Soprano::BindingSet& binding, res2Backlinks) { + addStatement(binding["r"], binding["p"], res1, binding["g"]); + } + + + // + // Finally delete res2 as it is now merged into res1 + // + removeResources(QList<QUrl>() << res2, NoRemovalFlags, app); +} + + +namespace { +QVariant nodeToVariant(const Soprano::Node& node) { + if(node.isResource()) + return node.uri(); + else if(node.isBlank()) + return QUrl(QLatin1String("_:") + node.identifier()); + else + return node.literal().variant(); +} +} + +Nepomuk::SimpleResourceGraph Nepomuk::DataManagementModel::describeResources(const QList<QUrl> &resources, bool includeSubResources) const +{ + kDebug() << resources << includeSubResources; + + // + // check parameters + // + if(resources.isEmpty()) { + setError(QLatin1String("describeResources: No resource specified."), Soprano::Error::ErrorInvalidArgument); + return SimpleResourceGraph(); + } + foreach( const QUrl & res, resources ) { + if(res.isEmpty()) { + setError(QLatin1String("describeResources: Encountered empty resource URI."), Soprano::Error::ErrorInvalidArgument); + return SimpleResourceGraph(); + } + } + + + clearError(); + + + // + // Split into nie:urls and URIs so we can get all data in one query + // + QSet<QUrl> nieUrls; + QSet<QUrl> resUris; + foreach( const QUrl & res, resources ) { + const UriState state = uriState(res); + if(state == NonExistingFileUrl) { + setError(QString::fromLatin1("Cannot store information about non-existing local files. File '%1' does not exist.").arg(res.toLocalFile()), + Soprano::Error::ErrorInvalidArgument); + return SimpleResourceGraph(); + } + else if(state == ExistingFileUrl || state == SupportedUrl) { + nieUrls.insert(res); + } + else if(state == NepomukUri || state == OtherUri){ + resUris.insert(res); + } + else if(state == BlankUri) { + setError(QString::fromLatin1("Cannot give information about blank uris. '%1' does not exist").arg(res.toString()), + Soprano::Error::ErrorInvalidArgument); + return SimpleResourceGraph(); + } + } + + + // + // Build the query + // + QStringList terms; + if(!nieUrls.isEmpty()) { + terms << QString::fromLatin1("?s ?p ?o . ?s %1 ?u . FILTER(?u in (%2)) . ") + .arg(Soprano::Node::resourceToN3(Vocabulary::NIE::url()), + resourcesToN3(nieUrls).join(QLatin1String(","))); + if(includeSubResources) { + terms << QString::fromLatin1("?s ?p ?o . ?r %1 ?s . ?r %2 ?u . FILTER(?u in (%3)) . ") + .arg(Soprano::Node::resourceToN3(NAO::hasSubResource()), + Soprano::Node::resourceToN3(Vocabulary::NIE::url()), + resourcesToN3(nieUrls).join(QLatin1String(","))); + } + } + if(!resUris.isEmpty()) { + terms << QString::fromLatin1("?s ?p ?o . FILTER(?s in (%1)) . ") + .arg(resourcesToN3(resUris).join(QLatin1String(","))); + if(includeSubResources) { + terms << QString::fromLatin1("?s ?p ?o . ?r %1 ?s . FILTER(?r in (%2)) . ") + .arg(Soprano::Node::resourceToN3(NAO::hasSubResource()), + resourcesToN3(resUris).join(QLatin1String(","))); + } + } + + QString query = QLatin1String("select distinct ?s ?p ?o where { "); + if(terms.count() == 1) { + query += terms.first(); + } + else { + query += QLatin1String("{ ") + terms.join(QLatin1String("} UNION { ")) + QLatin1String("}"); + } + query += QLatin1String(" }"); + + + // + // Build the graph + // + QHash<QUrl, SimpleResource> graph; + Soprano::QueryResultIterator it = executeQuery(query, Soprano::Query::QueryLanguageSparql); + while(it.next()) { + const QUrl r = it["s"].uri(); + graph[r].setUri(r); + graph[r].addProperty(it["p"].uri(), nodeToVariant(it["o"])); + } + if(it.lastError()) { + setError(it.lastError()); + return SimpleResourceGraph(); + } + + return graph.values(); +} + + +void Nepomuk::DataManagementModel::removeTrailingGraphs(const QSet<QUrl>& graphs) +{ + kDebug() << graphs; + +#ifndef NDEBUG + if(graphs.contains(QUrl())) + kDebug() << "Encountered the empty graph. This most likely stems from buggy data which was generated by some older (hopefully) version of Nepomuk. Ignoring."; +#endif + + QSet<QUrl> gl(graphs); + gl.remove(QUrl()); + + if(!gl.isEmpty()) { + // TODO: for some weird reason the filter statement in the query below does not work. Thus, we use a count instead. + QList<Soprano::Node> allMetadataGraphs; +// = executeQuery(QString::fromLatin1("select ?mg where { ?mg %1 ?g . FILTER(?g in (%2)) . FILTER(!bif:exists((select (1) where { graph ?g { ?s ?p ?o . } . }))) . }") +// .arg(Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor()), +// resourcesToN3(gl).join(QLatin1String(","))), +// Soprano::Query::QueryLanguageSparql).iterateBindings(0).allNodes(); + + Soprano::QueryResultIterator it + = executeQuery(QString::fromLatin1("select ?mg (select count(*) where { graph ?g { ?s ?p ?o . } . }) as ?cnt where { ?mg %1 ?g . FILTER(?g in (%2)) . }") + .arg(Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor()), + resourcesToN3(gl).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + while(it.next()) { + if(it[1].literal().toInt() == 0) + allMetadataGraphs << it[0]; + } + + foreach(const Soprano::Node& mg, allMetadataGraphs) { + executeQuery(QString::fromLatin1("clear graph %1").arg(mg.toN3()), + Soprano::Query::QueryLanguageSparql); + } + } +} + + +QUrl Nepomuk::DataManagementModel::createGraph(const QString &app, const QHash<QUrl, QVariant> &additionalMetadata) +{ + QHash<QUrl, Soprano::Node> graphMetaData; + + for(QHash<QUrl, QVariant>::const_iterator it = additionalMetadata.constBegin(); + it != additionalMetadata.constEnd(); ++it) { + Soprano::Node node = d->m_classAndPropertyTree->variantToNode(it.value(), it.key()); + if(node.isValid()) { + graphMetaData.insert(it.key(), node); + } + else { + setError(d->m_classAndPropertyTree->lastError()); + return QUrl(); + } + } + + return createGraph( app, graphMetaData ); +} + +QUrl Nepomuk::DataManagementModel::createGraph(const QString& app, const QMultiHash< QUrl, Soprano::Node >& additionalMetadata) +{ + QHash<QUrl, Soprano::Node> graphMetaData = additionalMetadata; + + // determine the graph type + bool haveGraphType = false; + for(QHash<QUrl, Soprano::Node>::const_iterator it = additionalMetadata.constBegin(); + it != additionalMetadata.constEnd(); ++it) { + const QUrl& property = it.key(); + + if(property == RDF::type()) { + // check if it is a valid type + if(!it.value().isResource()) { + setError(QString::fromLatin1("rdf:type has resource range. '%1' does not have a resource type.").arg(it.value().toN3()), Soprano::Error::ErrorInvalidArgument); + return QUrl(); + } + else { + if(d->m_classAndPropertyTree->isChildOf(it.value().uri(), NRL::Graph())) + haveGraphType = true; + } + } + + else if(property == NAO::created()) { + if(!it.value().literal().isDateTime()) { + setError(QString::fromLatin1("nao:created has xsd:dateTime range. '%1' is not convertable to a dateTime.").arg(it.value().toN3()), Soprano::Error::ErrorInvalidArgument); + return QUrl(); + } + } + + else { + // FIXME: check property, domain, and range + // Reuse code from ResourceMerger::checkGraphMetadata + } + } + + // add missing metadata + if(!haveGraphType) { + graphMetaData.insert(RDF::type(), NRL::InstanceBase()); + } + if(!graphMetaData.contains(NAO::created())) { + graphMetaData.insert(NAO::created(), Soprano::LiteralValue(QDateTime::currentDateTime())); + } + if(!graphMetaData.contains(NAO::maintainedBy()) && !app.isEmpty()) { + graphMetaData.insert(NAO::maintainedBy(), findApplicationResource(app)); + } + + const QUrl graph = createUri(GraphUri); + const QUrl metadatagraph = createUri(GraphUri); + + // add metadata graph itself + addStatement(metadatagraph, NRL::coreGraphMetadataFor(), graph, metadatagraph); + addStatement(metadatagraph, RDF::type(), NRL::GraphMetadata(), metadatagraph); + + for(QHash<QUrl, Soprano::Node>::const_iterator it = graphMetaData.constBegin(); + it != graphMetaData.constEnd(); ++it) { + addStatement(graph, it.key(), it.value(), metadatagraph); + } + + return graph; +} + + +QUrl Nepomuk::DataManagementModel::splitGraph(const QUrl &graph, const QUrl& metadataGraph_, const QUrl &appRes) +{ + const QUrl newGraph = createUri(GraphUri); + const QUrl newMetadataGraph = createUri(GraphUri); + + QUrl metadataGraph(metadataGraph_); + if(metadataGraph.isEmpty()) { + Soprano::QueryResultIterator it = executeQuery(QString::fromLatin1("select ?m where { ?m %1 %2 . } LIMIT 1") + .arg(Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor()), + Soprano::Node::resourceToN3(graph)), + Soprano::Query::QueryLanguageSparql); + if(it.next()) { + metadataGraph = it[0].uri(); + } + else { + kError() << "Failed to get metadata graph for" << graph; + return QUrl(); + } + } + + // add metadata graph + addStatement( newMetadataGraph, NRL::coreGraphMetadataFor(), newGraph, newMetadataGraph ); + addStatement( newMetadataGraph, RDF::type(), NRL::GraphMetadata(), newMetadataGraph ); + + // copy the metadata from the old graph to the new one + executeQuery(QString::fromLatin1("insert into %1 { %2 ?p ?o . } where { graph %3 { %4 ?p ?o . } . }") + .arg(Soprano::Node::resourceToN3(newMetadataGraph), + Soprano::Node::resourceToN3(newGraph), + Soprano::Node::resourceToN3(metadataGraph), + Soprano::Node::resourceToN3(graph)), + Soprano::Query::QueryLanguageSparql); + + // add the new app + if(!appRes.isEmpty()) + addStatement(newGraph, NAO::maintainedBy(), appRes, newMetadataGraph); + + return newGraph; +} + + +QUrl Nepomuk::DataManagementModel::findApplicationResource(const QString &app, bool create) +{ + Soprano::QueryResultIterator it = + executeQuery(QString::fromLatin1("select ?r where { ?r a %1 . ?r %2 %3 . } LIMIT 1") + .arg(Soprano::Node::resourceToN3(NAO::Agent()), + Soprano::Node::resourceToN3(NAO::identifier()), + Soprano::Node::literalToN3(app)), + Soprano::Query::QueryLanguageSparql); + if(it.next()) { + return it[0].uri(); + } + else if(create) { + const QUrl graph = createUri(GraphUri); + const QUrl metadatagraph = createUri(GraphUri); + const QUrl uri = createUri(ResourceUri); + + // graph metadata + addStatement( metadatagraph, NRL::coreGraphMetadataFor(), graph, metadatagraph ); + addStatement( metadatagraph, RDF::type(), NRL::GraphMetadata(), metadatagraph ); + addStatement( graph, RDF::type(), NRL::InstanceBase(), metadatagraph ); + addStatement( graph, NAO::created(), Soprano::LiteralValue(QDateTime::currentDateTime()), metadatagraph ); + + // the app itself + addStatement( uri, RDF::type(), NAO::Agent(), graph ); + addStatement( uri, NAO::identifier(), Soprano::LiteralValue(app), graph ); + + KService::List services = KServiceTypeTrader::self()->query(QLatin1String("Application"), + QString::fromLatin1("DesktopEntryName=='%1'").arg(app)); + if(services.count() == 1) { + addStatement(uri, NAO::prefLabel(), Soprano::LiteralValue(services.first()->name()), graph); + } + + return uri; + } + else { + return QUrl(); + } +} + +QUrl Nepomuk::DataManagementModel::createUri(Nepomuk::DataManagementModel::UriType type) +{ + QString typeToken; + if(type == GraphUri) + typeToken = QLatin1String("ctx"); + else + typeToken = QLatin1String("res"); + + while( 1 ) { + QString uuid = QUuid::createUuid().toString(); + uuid = uuid.mid(1, uuid.length()-2); + const QUrl uri = QUrl( QLatin1String("nepomuk:/") + typeToken + QLatin1String("/") + uuid ); + if ( !FilterModel::executeQuery( QString::fromLatin1("ask where { " + "{ %1 ?p1 ?o1 . } " + "UNION " + "{ ?s2 %1 ?o2 . } " + "UNION " + "{ ?s3 ?p3 %1 . } " + "UNION " + "{ graph %1 { ?s4 ?4 ?o4 . } . } " + "}") + .arg( Soprano::Node::resourceToN3(uri) ), Soprano::Query::QueryLanguageSparql ).boolValue() ) { + return uri; + } + } +} + + +// TODO: emit resource watcher resource creation signals +void Nepomuk::DataManagementModel::addProperty(const QHash<QUrl, QUrl> &resources, const QUrl &property, const QHash<Soprano::Node, Soprano::Node> &nodes, const QString &app) +{ + kDebug() << resources << property << nodes << app; + Q_ASSERT(!resources.isEmpty()); + Q_ASSERT(!nodes.isEmpty()); + Q_ASSERT(!property.isEmpty()); + + // + // Check cardinality conditions + // + const int maxCardinality = d->m_classAndPropertyTree->maxCardinality(property); + if( maxCardinality == 1 ) { + if( nodes.size() != 1 ) { + setError(QString::fromLatin1("%1 has cardinality of 1. Cannot set more then one value.").arg(property.toString()), Soprano::Error::ErrorInvalidArgument); + return; + } + } + + + clearError(); + + + // + // Resolve file URLs + // + QUrl graph; + QSet<Soprano::Node> resolvedNodes; + QHash<Soprano::Node, Soprano::Node>::const_iterator end = nodes.constEnd(); + for(QHash<Soprano::Node, Soprano::Node>::const_iterator it = nodes.constBegin(); + it != end; ++it) { + if(it.value().isEmpty()) { + if(graph.isEmpty()) { + graph = createGraph( app ); + if(!graph.isValid()) { + // error has been set in createGraph + return; + } + } + const QUrl uri = createUri(ResourceUri); + addStatement(uri, Vocabulary::NIE::url(), it.key(), graph); + if(it.key().uri().scheme() == QLatin1String("file")) { + addStatement(uri, RDF::type(), NFO::FileDataObject(), graph); + } + resolvedNodes.insert(uri); + } + else { + resolvedNodes.insert(it.value()); + } + } + + QSet<QPair<QUrl, Soprano::Node> > finalProperties; + QList<QUrl> knownResources; + for(QHash<QUrl, QUrl>::const_iterator it = resources.constBegin(); + it != resources.constEnd(); ++it) { + QUrl uri = it.value(); + if(uri.isEmpty()) { + if(graph.isEmpty()) { + graph = createGraph( app ); + if(!graph.isValid()) { + // error has been set in createGraph + return; + } + } + uri = createUri(ResourceUri); + addStatement(uri, Vocabulary::NIE::url(), it.key(), graph); + if(it.key().scheme() == QLatin1String("file")) { + addStatement(uri, RDF::type(), NFO::FileDataObject(), graph); + } + } + else { + knownResources << uri; + } + Q_FOREACH(const Soprano::Node& node, resolvedNodes) { + finalProperties << qMakePair(uri, node); + } + } + + const QUrl appRes = findApplicationResource(app); + + + // + // Check if values already exist. If so remove the resources from the resourceSet and only add the application + // as maintainedBy in a new graph (except if its the only statement in the graph) + // + if(!knownResources.isEmpty()) { + const QString existingValuesQuery = QString::fromLatin1("select distinct ?r ?v ?g ?m " + "(select count(*) where { graph ?g { ?s ?p ?o . } . FILTER(%5) . }) as ?cnt " + "where { " + "graph ?g { ?r %2 ?v . } . " + "?m %1 ?g . " + "FILTER(?r in (%3)) . " + "FILTER(?v in (%4)) . }") + .arg(Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor()), + Soprano::Node::resourceToN3(property), + resourcesToN3(knownResources).join(QLatin1String(",")), + nodesToN3(resolvedNodes).join(QLatin1String(",")), + createResourceMetadataPropertyFilter(QLatin1String("?p"))); + QList<Soprano::BindingSet> existingValueBindings = executeQuery(existingValuesQuery, Soprano::Query::QueryLanguageSparql).allBindings(); + Q_FOREACH(const Soprano::BindingSet& binding, existingValueBindings) { + kDebug() << "Existing value" << binding; + const QUrl r = binding["r"].uri(); + const Soprano::Node v = binding["v"]; + const QUrl g = binding["g"].uri(); + const QUrl m = binding["m"].uri(); + const int cnt = binding["cnt"].literal().toInt(); + + // we handle this property here - thus, no need to handle it below + finalProperties.remove(qMakePair(r, v)); + + // in case the app is the same there is no need to do anything + if(containsStatement(g, NAO::maintainedBy(), appRes, m)) { + continue; + } + else if(cnt == 1) { + // we can reuse the graph + addStatement(g, NAO::maintainedBy(), appRes, m); + } + else { + // we need to split the graph + // FIXME: do not split the same graph again and again. Check if the graph in question already is the one we created. + const QUrl newGraph = splitGraph(g, m, appRes); + + // and finally move the actual property over to the new graph + removeStatement(r, property, v, g); + addStatement(r, property, v, newGraph); + } + } + } + + + // + // All conditions have been checked - create the actual data + // + if(!finalProperties.isEmpty()) { + if(graph.isEmpty()) { + graph = createGraph( app ); + if(!graph.isValid()) { + // error has been set in createGraph + return; + } + } + + // add all the data + // TODO: check if using one big sparql insert improves performance + QSet<QUrl> finalResources; + for(QSet<QPair<QUrl, Soprano::Node> >::const_iterator it = finalProperties.constBegin(); it != finalProperties.constEnd(); ++it) { + addStatement(it->first, property, it->second, graph); + if(property == NIE::url() && it->second.uri().scheme() == QLatin1String("file")) { + addStatement(it->first, RDF::type(), NFO::FileDataObject(), graph); + } + finalResources.insert(it->first); + d->m_watchManager->addProperty(it->first, property, it->second); + } + + // update modification date + Q_FOREACH(const QUrl& res, finalResources) { + updateModificationDate(res, graph, QDateTime::currentDateTime(), true); + } + } +} + +bool Nepomuk::DataManagementModel::doesResourceExist(const QUrl &res, const QUrl& graph) const +{ + if(graph.isEmpty()) { + return executeQuery(QString::fromLatin1("ask where { %1 ?p ?v . FILTER(%2) . }") + .arg(Soprano::Node::resourceToN3(res), + createResourceMetadataPropertyFilter(QLatin1String("?p"))), + Soprano::Query::QueryLanguageSparql).boolValue(); + } + else { + return executeQuery(QString::fromLatin1("ask where { graph %1 { %2 ?p ?v . FILTER(%3) . } . }") + .arg(Soprano::Node::resourceToN3(graph), + Soprano::Node::resourceToN3(res), + createResourceMetadataPropertyFilter(QLatin1String("?p"))), + Soprano::Query::QueryLanguageSparql).boolValue(); + } +} + +QHash<QUrl, QUrl> Nepomuk::DataManagementModel::resolveUrls(const QList<QUrl> &urls) const +{ + QHash<QUrl, QUrl> uriHash; + Q_FOREACH(const QUrl& url, urls) { + const QUrl resolved = resolveUrl(url, true); + if(url.isEmpty() && lastError()) { + break; + } + uriHash.insert(url, resolved); + } + return uriHash; +} + +QUrl Nepomuk::DataManagementModel::resolveUrl(const QUrl &url, bool statLocalFiles) const +{ + const UriState state = uriState( url, statLocalFiles ); + + if( state == NepomukUri || state == OntologyUri ) { + // nothing to resolve over here + return url; + } + + // + // First check if the URL does exists as resource URI + // + else if( executeQuery(QString::fromLatin1("ask where { %1 ?p ?o . }") + .arg(Soprano::Node::resourceToN3(url)), + Soprano::Query::QueryLanguageSparql).boolValue() ) { + return url; + } + + // + // we resolve all URLs except nepomuk:/ URIs. While the DMS does only use nie:url + // on local files libnepomuk used to use it for everything but nepomuk:/ URIs. + // Thus, we need to handle that legacy data by checking if url does exist as nie:url + // + else { + Soprano::QueryResultIterator it + = executeQuery(QString::fromLatin1("select ?r where { ?r %1 %2 . } limit 1") + .arg(Soprano::Node::resourceToN3(NIE::url()), + Soprano::Node::resourceToN3(url)), + Soprano::Query::QueryLanguageSparql); + + // if the URL is used as a nie:url return the corresponding resource URI + if(it.next()) { + return it[0].uri(); + } + + // non-existing unsupported URL + else if( state == OtherUri ) { + QString error = QString::fromLatin1("Unknown protocol '%1' encountered in %2") + .arg(url.scheme(), url.toString()); + setError(error, Soprano::Error::ErrorInvalidArgument); + return QUrl(); + } + + // if there is no existing URI return an empty match for local files (since we do always want to use nie:url here) + else { + // we only throw an error if the file:/ URL points to a non-existing file AND it does not exist in the database. + if(state == NonExistingFileUrl) { + setError(QString::fromLatin1("Cannot store information about non-existing local files. File '%1' does not exist.").arg(url.toLocalFile()), + Soprano::Error::ErrorInvalidArgument); + } + + return QUrl(); + } + } + + // fallback + return url; +} + +QHash<Soprano::Node, Soprano::Node> Nepomuk::DataManagementModel::resolveNodes(const QSet<Soprano::Node> &nodes) const +{ + QHash<Soprano::Node, Soprano::Node> resolvedNodes; + Q_FOREACH(const Soprano::Node& node, nodes) { + if(node.isResource()) { + resolvedNodes.insert(node, resolveUrl(node.uri(), true)); + } + else { + resolvedNodes.insert(node, node); + } + } + return resolvedNodes; +} + +bool Nepomuk::DataManagementModel::updateNieUrlOnLocalFile(const QUrl &resource, const QUrl &nieUrl) +{ + kDebug() << resource << "->" << nieUrl; + + // + // Since KDE 4.4 we use nepomuk:/res/<UUID> Uris for all resources including files. Thus, moving a file + // means updating two things: + // 1. the nie:url property + // 2. the nie:isPartOf relation (only necessary if the file and not the whole folder was moved) + // + + // + // Now update the nie:url, nfo:fileName, and nie:isPartOf relations. + // + // We do NOT use setProperty to avoid the overhead and data clutter of creating + // new metadata graphs for the changed data. + // + + // remember for url, filename, and parent the graph they are defined in + QUrl resUri, oldNieUrl, oldNieUrlGraph, oldParentResource, oldParentResourceGraph, oldFileNameGraph; + QString oldFileName; + + // we do not use isLocalFileUrl() here since we also handle already moved files + if(resource.scheme() == QLatin1String("file")) { + oldNieUrl = resource; + Soprano::QueryResultIterator it + = executeQuery(QString::fromLatin1("select distinct ?gu ?gf ?gp ?r ?f ?p where { " + "graph ?gu { ?r %2 %1 . } . " + "OPTIONAL { graph ?gf { ?r %3 ?f . } . } . " + "OPTIONAL { graph ?gp { ?r %4 ?p . } . } . " + "} LIMIT 1") + .arg(Soprano::Node::resourceToN3(resource), + Soprano::Node::resourceToN3(NIE::url()), + Soprano::Node::resourceToN3(NFO::fileName()), + Soprano::Node::resourceToN3(NIE::isPartOf())), + Soprano::Query::QueryLanguageSparql); + if(it.next()) { + resUri= it["r"].uri(); + oldNieUrlGraph = it["gu"].uri(); + oldParentResource = it["p"].uri(); + oldParentResourceGraph = it["gp"].uri(); + oldFileName = it["f"].toString(); + oldFileNameGraph = it["gf"].uri(); + } + } + else { + resUri = resource; + Soprano::QueryResultIterator it + = executeQuery(QString::fromLatin1("select distinct ?gu ?gf ?gp ?u ?f ?p where { " + "graph ?gu { %1 %2 ?u . } . " + "OPTIONAL { graph ?gf { %1 %3 ?f . } . } . " + "OPTIONAL { graph ?gp { %1 %4 ?p . } . } . " + "} LIMIT 1") + .arg(Soprano::Node::resourceToN3(resource), + Soprano::Node::resourceToN3(NIE::url()), + Soprano::Node::resourceToN3(NFO::fileName()), + Soprano::Node::resourceToN3(NIE::isPartOf())), + Soprano::Query::QueryLanguageSparql); + if(it.next()) { + oldNieUrl = it["u"].uri(); + oldNieUrlGraph = it["gu"].uri(); + oldParentResource = it["p"].uri(); + oldParentResourceGraph = it["gp"].uri(); + oldFileName = it["f"].toString(); + oldFileNameGraph = it["gf"].uri(); + } + } + + if (!oldNieUrlGraph.isEmpty()) { + removeStatement(resUri, NIE::url(), oldNieUrl, oldNieUrlGraph); + addStatement(resUri, NIE::url(), nieUrl, oldNieUrlGraph); + + if (!oldFileNameGraph.isEmpty()) { + // we only update the filename if it actually changed + if(KUrl(oldNieUrl).fileName() != KUrl(nieUrl).fileName()) { + removeStatement(resUri, NFO::fileName(), Soprano::LiteralValue(oldFileName), oldFileNameGraph); + addStatement(resUri, NFO::fileName(), Soprano::LiteralValue(KUrl(nieUrl).fileName()), oldFileNameGraph); + } + } + + if (!oldParentResourceGraph.isEmpty()) { + // we only update the parent folder if it actually changed + const KUrl nieUrlParent = KUrl(nieUrl).directory(KUrl::IgnoreTrailingSlash); + const KUrl oldUrlParent = KUrl(oldNieUrl).directory(KUrl::IgnoreTrailingSlash); + if(nieUrlParent != oldUrlParent) { + removeStatement(resUri, NIE::isPartOf(), oldParentResource, oldParentResourceGraph); + const QUrl newParentRes = resolveUrl(nieUrlParent); + if (!newParentRes.isEmpty()) { + addStatement(resUri, NIE::isPartOf(), newParentRes, oldParentResourceGraph); + } + } + } + + // + // Update all children + // We only need to update the nie:url properties. Filenames and nie:isPartOf relations cannot change + // due to a renaming of the parent folder. + // + // CAUTION: The trailing slash on the from URL is essential! Otherwise we might match the newly added + // URLs, too (in case a rename only added chars to the name) + // + const QString query = QString::fromLatin1("select distinct ?r ?u ?g where { " + "graph ?g { ?r %1 ?u . } . " + "FILTER(REGEX(STR(?u),'^%2')) . " + "}") + .arg(Soprano::Node::resourceToN3(NIE::url()), + KUrl(oldNieUrl).url(KUrl::AddTrailingSlash)); + + const QString oldBasePath = KUrl(oldNieUrl).path(KUrl::AddTrailingSlash); + const QString newBasePath = KUrl(nieUrl).path(KUrl::AddTrailingSlash); + + // + // We cannot use one big loop since our updateMetadata calls below can change the iterator + // which could have bad effects like row skipping. Thus, we handle the urls in chunks of + // cached items. + // + forever { + const QList<Soprano::BindingSet> urls = executeQuery(query + QLatin1String( " LIMIT 500" ), + Soprano::Query::QueryLanguageSparql) + .allBindings(); + if (urls.isEmpty()) + break; + + for (int i = 0; i < urls.count(); ++i) { + const KUrl u = urls[i]["u"].uri(); + const QUrl r = urls[i]["r"].uri(); + const QUrl g = urls[i]["g"].uri(); + + // now construct the new URL + const QString oldRelativePath = u.path().mid(oldBasePath.length()); + const KUrl newUrl(newBasePath + oldRelativePath); + + removeStatement(r, NIE::url(), u, g); + addStatement(r, NIE::url(), newUrl, g); + } + } + + return true; + } + else { + // no old nie:url found + return false; + } +} + +//void Nepomuk::DataManagementModel::insertStatements(const QSet<QUrl> &resources, const QUrl &property, const QSet<Soprano::Node> &values, const QUrl &graph) +//{ +// const QString propertyString = Soprano::Node::resourceToN3(property); + +// QString query = QString::fromLatin1("insert into %1 { ") +// .arg(Soprano::Node::resourceToN3(graph)); + +// foreach(const QUrl& res, resources) { +// foreach(const Soprano::Node& value, values) { +// query.append(QString::fromLatin1("%1 %2 %3 . ") +// .arg(Soprano::Node::resourceToN3(res), +// propertyString, +// value.toN3())); +// } +// } +// query.append(QLatin1String("}")); + +// executeQuery(query, Soprano::Query::QueryLanguageSparql); +//} + +Nepomuk::ClassAndPropertyTree* Nepomuk::DataManagementModel::classAndPropertyTree() +{ + return d->m_classAndPropertyTree; +} + +bool Nepomuk::DataManagementModel::containsResourceWithProtectedType(const QSet<QUrl> &resources) const +{ + if(executeQuery(QString::fromLatin1("ask where { ?r a ?t . FILTER(?r in (%1)) . FILTER(?t in (%2,%3,%4)) . }") + .arg(resourcesToN3(resources).join(QLatin1String(",")), + Soprano::Node::resourceToN3(RDFS::Class()), + Soprano::Node::resourceToN3(RDF::Property()), + Soprano::Node::resourceToN3(NRL::Graph())), + Soprano::Query::QueryLanguageSparql).boolValue()) { + setError(QLatin1String("It is not allowed to remove classes, properties, or graphs through this API."), Soprano::Error::ErrorInvalidArgument); + return true; + } + else { + return false; + } +} + +Nepomuk::ResourceWatcherManager* Nepomuk::DataManagementModel::resourceWatcherManager() const +{ + return d->m_watchManager; +} + +void Nepomuk::DataManagementModel::removeAllResources(const QSet<QUrl> &resources, RemovalFlags flags) +{ + QSet<QUrl> resolvedResources(resources); + + // + // Handle the sub-resources: + // this has to be done before deleting the resouces in resolvedResources. Otherwise the nao:hasSubResource relationships are already gone! + // + // Explanation of the query: + // The query selects all subresources of the resources in resolvedResources. + // It then filters out the sub-resources that are related from other resources that are not the ones being deleted. + // + if(flags & RemoveSubResoures) { + QSet<QUrl> subResources = resolvedResources; + int resCount = 0; + do { + resCount = resolvedResources.count(); + Soprano::QueryResultIterator it + = executeQuery(QString::fromLatin1("select ?r where { ?r ?p ?o . " + "?parent %1 ?r . " + "FILTER(?parent in (%2)) . " + "FILTER(!bif:exists((select (1) where { ?r2 ?p3 ?r . FILTER(%3) . FILTER(!bif:exists((select (1) where { ?x %1 ?r2 . FILTER(?x in (%2)) . }))) . }))) . " + "}") + .arg(Soprano::Node::resourceToN3(NAO::hasSubResource()), + resourcesToN3(subResources).join(QLatin1String(",")), + createResourceFilter(resolvedResources, QLatin1String("?r2"))), + Soprano::Query::QueryLanguageSparql); + subResources.clear(); + while(it.next()) { + subResources << it[0].uri(); + } + resolvedResources += subResources; + } while(resCount < resolvedResources.count()); + } + + + // get the graphs we need to check with removeTrailingGraphs later on + QSet<QUrl> graphs; + QSet<QUrl> actuallyRemovedResources; + Soprano::QueryResultIterator it + = executeQuery(QString::fromLatin1("select distinct ?g ?r where { graph ?g { ?r ?p ?o . } . FILTER(?r in (%1)) . }") + .arg(resourcesToN3(resolvedResources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + while(it.next()) { + graphs << it[0].uri(); + actuallyRemovedResources << it[1].uri(); + } + + // get the resources that we modify by removing relations to one of the deleted ones + QSet<QUrl> modifiedResources; + it = executeQuery(QString::fromLatin1("select distinct ?g ?o where { graph ?g { ?o ?p ?r . } . FILTER(?r in (%1)) . }") + .arg(resourcesToN3(resolvedResources).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + while(it.next()) { + graphs << it[0].uri(); + modifiedResources << it[1].uri(); + } + modifiedResources -= actuallyRemovedResources; + + + // remove the resources + foreach(const Soprano::Node& res, actuallyRemovedResources) { + removeAllStatements(res, Soprano::Node(), Soprano::Node()); + removeAllStatements(Soprano::Node(), Soprano::Node(), res); + } + updateModificationDate(modifiedResources); + removeTrailingGraphs(graphs); + + // inform interested parties + // TODO: ideally we should also report the types the removed resources had + foreach(const Soprano::Node& res, actuallyRemovedResources) { + d->m_watchManager->removeResource(res.uri(), QList<QUrl>()); + } +} + +#include "datamanagementmodel.moc" diff --git a/nepomuk/services/storage/datamanagementmodel.h b/nepomuk/services/storage/datamanagementmodel.h new file mode 100644 index 0000000..9722157 --- /dev/null +++ b/nepomuk/services/storage/datamanagementmodel.h @@ -0,0 +1,324 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef DATAMANAGEMENTMODEL_H +#define DATAMANAGEMENTMODEL_H + +#include "lib/datamanagement.h" + +#include <Soprano/FilterModel> + +#include <QtCore/QDateTime> + +namespace Nepomuk { + +class ClassAndPropertyTree; +class ResourceMerger; +class SimpleResourceGraph; +class ResourceWatcherManager; + +class DataManagementModel : public Soprano::FilterModel +{ + Q_OBJECT + +public: + DataManagementModel(ClassAndPropertyTree* tree, Soprano::Model* model, QObject *parent = 0); + ~DataManagementModel(); + + /// used by the unit tests + ResourceWatcherManager* resourceWatcherManager() const; + +public Q_SLOTS: + /** + * \name Basic API + */ + //@{ + /** + * Add \p property with \p values to each resource + * from \p resources. Existing values will not be touched. + * If a cardinality is breached an error will be thrown. + */ + void addProperty(const QList<QUrl>& resources, + const QUrl& property, + const QVariantList& values, + const QString& app); + + /** + * Set, ie. overwrite properties. Set \p property with + * \p values for each resource from \p resources. Existing + * values will be replaced. + */ + void setProperty(const QList<QUrl>& resources, + const QUrl& property, + const QVariantList& values, + const QString& app); + + /** + * Remove the property \p property with \p values from each + * resource in \p resources. + */ + void removeProperty(const QList<QUrl>& resources, + const QUrl& property, + const QVariantList& values, + const QString& app); + + /** + * Remove all statements involving any proerty from \p properties from + * all resources in \p resources. + */ + void removeProperties(const QList<QUrl>& resources, + const QList<QUrl>& properties, + const QString& app); + + /** + * Create a new resource with several \p types. + */ + QUrl createResource(const QList<QUrl>& types, + const QString& label, + const QString& description, + const QString& app); + + /** + * Remove resources from the database. + * \param resources The URIs of the resources to be removed. + * \param app The calling application. + * \param force Force deletion of the resource and all sub-resources. + * If false sub-resources will be kept if they are still referenced by + * other resources. + */ + void removeResources(const QList<QUrl>& resources, + Nepomuk::RemovalFlags flags, + const QString& app); + //@} + + /** + * \name Advanced API + */ + //@{ + /** + * Remove all information about resources from the database which + * have been created by a specific application. + * \param resources The URIs of the resources to be removed. + * \param app The application for which data should be removed. + * \param force Force deletion of the resource and all sub-resources. + * If false sub-resources will be kept if they are still referenced by + * other resources. + */ + void removeDataByApplication(const QList<QUrl>& resources, + RemovalFlags flags, + const QString& app); + + /** + * Remove all information from the database which + * has been created by a specific application. + * \param app The application for which data should be removed. + * \param force Force deletion of the resource and all sub-resources. + * If false sub-resources will be kept if they are still referenced by + * resources that have been created by other applications. + */ + void removeDataByApplication(RemovalFlags flags, + const QString& app); + + /** + * \param resources The resources to be merged. Blank nodes will be converted into new + * URIs (unless the corresponding resource already exists). + * \param identificationMode This method can try hard to avoid duplicate resources by looking + * for already existing duplicates based on nrl:IdentifyingProperty. By default it only looks + * for duplicates of resources that do not have a resource URI (SimpleResource::uri()) defined. + * This behaviour can be changed with this parameter. + * \param flags Additional flags to change the behaviour of the method. + * \param additionalMetadata Additional metadata for the added resources. This can include + * such details as the creator of the data or details on the method of data recovery. + * One typical usecase is that the file indexer uses (rdf:type, nrl:DiscardableInstanceBase) + * to state that the provided information can be recreated at any time. Only built-in types + * such as int, string, or url are supported. + * \param app The calling application + */ + void storeResources(const SimpleResourceGraph& resources, + const QString& app, + Nepomuk::StoreIdentificationMode identificationMode = Nepomuk::IdentifyNew, + Nepomuk::StoreResourcesFlags flags = Nepomuk::NoStoreResourcesFlags, + const QHash<QUrl, QVariant>& additionalMetadata = QHash<QUrl, QVariant>() ); + + /** + * Merges two resources into one. Properties from \p resource1 + * take precedence over that from \p resource2 (for properties with cardinality 1). + */ + void mergeResources(const QUrl& resource1, const QUrl& resource2, const QString& app); + + /** + * Import an RDF graph from a URL. + * \param url The url from which the graph should be loaded. This does not have to be local. + * \param serialization The RDF serialization used for the file. If Soprano::SerializationUnknown a crude automatic + * detection based on file extension is used. + * \param userSerialization If \p serialization is Soprano::SerializationUser this value is used. See Soprano::Parser + * for details. + * \param identificationMode This method can try hard to avoid duplicate resources by looking + * for already existing duplicates based on nrl:IdentifyingProperty. By default it only looks + * for duplicates of resources that do not have a resource URI (SimpleResource::uri()) defined. + * This behaviour can be changed with this parameter. + * \param flags Additional flags to change the behaviour of the method. + * \param additionalMetadata Additional metadata for the added resources. This can include + * such details as the creator of the data or details on the method of data recovery. + * One typical usecase is that the file indexer uses (rdf:type, nrl:DiscardableInstanceBase) + * to state that the provided information can be recreated at any time. Only built-in types + * such as int, string, or url are supported. + * \param app The calling application + */ + void importResources(const QUrl& url, const QString& app, + Soprano::RdfSerialization serialization, + const QString& userSerialization = QString(), + Nepomuk::StoreIdentificationMode identificationMode = Nepomuk::IdentifyNew, + Nepomuk::StoreResourcesFlags flags = Nepomuk::NoStoreResourcesFlags, + const QHash<QUrl, QVariant>& additionalMetadata = QHash<QUrl, QVariant>()); + + /** + * Describe a set of resources, i.e. retrieve all their properties. + * \param resources The resource URIs of the resources to describe. + * \param includeSubResources If \p true sub resources will be included. + */ + SimpleResourceGraph describeResources(const QList<QUrl>& resources, + bool includeSubResources) const; + //@} + +private: + QUrl createGraph(const QString& app = QString(), const QHash<QUrl, QVariant>& additionalMetadata = QHash<QUrl, QVariant>()); + QUrl createGraph(const QString& app, const QMultiHash<QUrl, Soprano::Node>& additionalMetadata); + + /** + * Splits \p graph into two. This essentially copies the graph metadata to a new graph and metadata graph pair. + * The newly created graph will set as being maintained by \p appRes. + * + * \param graph The graph that should be split/copied. + * \param metadataGraph The metadata graph of graph. This can be empty. + * \param appRes The application resource which will be added as maintaining the newly created graph. Can be empty. + * + * \return The URI of the newly created graph. + */ + QUrl splitGraph(const QUrl& graph, const QUrl& metadataGraph, const QUrl& appRes); + + QUrl findApplicationResource(const QString& app, bool create = true); + + /** + * Updates the modification date of \p resource to \p date. + * Adds the new statement in \p graph. + */ + Soprano::Error::ErrorCode updateModificationDate( const QUrl& resource, const QUrl& graph = QUrl(), const QDateTime& date = QDateTime::currentDateTime(), bool includeCreationDate = false ); + + /** + * Updates the modification date of \p resources to \p date. + * Adds the new statement in \p graph. + */ + Soprano::Error::ErrorCode updateModificationDate( const QSet<QUrl>& resources, const QUrl& graph = QUrl(), const QDateTime& date = QDateTime::currentDateTime(), bool includeCreationDate = false ); + + /** + * Removes all the graphs from \p graphs which do not contain any statements + */ + void removeTrailingGraphs( const QSet<QUrl>& graphs ); + + /** + * Adds for each resource in \p resources a property for each node in nodes. \p nodes cannot be empty. + * This method is used in the public setProperty and addProperty slots to avoid a lot of code duplication. + * + * \param resources A hash mapping the resources provided by the client to the actual resource URIs. This hash is created via resolveUrls() and can + * contain empty values which means that the resource corresponding to a file URL does not exist yet. + * This hash cannot be empty. + * \param property The property to use. This cannot be empty. + * \param nodes A hash mapping value nodes as created via resolveNodes from the output of ClassAndPropertyTree::variantToNodeSet. Like \p resources + * this hash might contain empty values which refer to non-existing file resources. This cannot be empty. + * \param app The calling application. + */ + void addProperty(const QHash<QUrl, QUrl>& resources, const QUrl& property, const QHash<Soprano::Node, Soprano::Node>& nodes, const QString& app); + + /** + * Removes the given resources without any additional checks. The provided list needs to contain already resolved valid resource URIs. + * + * Used by removeResources() and removeDataByApplication() + */ + void removeAllResources(const QSet<QUrl>& resourceUris, RemovalFlags flags); + + /** + * Checks if resource \p res actually exists. A resource exists if any information other than the standard metadata + * (nao:created, nao:creator, nao:lastModified, nao:userVisible) or the nie:url is defined. + */ + bool doesResourceExist(const QUrl& res, const QUrl& graph = QUrl()) const; + + /** + * Resolves a local file url to its resource URI. Returns \p url if it is not a file URL and + * an empty QUrl in case \p url is a file URL but has no resource URI in the model. + * + * \param statLocalFiles If \p true the method will check if local files exist and set an error + * if not. + */ + QUrl resolveUrl(const QUrl& url, bool statLocalFiles = false) const; + + /** + * Resolves local file URLs through nie:url. + * \return a Hash mapping \p urls to their actual resource URIs or an empty QUrl if the resource does not exist. + * + * This method does check if the local file exists and may set an error. + */ + QHash<QUrl, QUrl> resolveUrls(const QList<QUrl>& urls) const; + + /** + * Resolves local file URLs through nie:url. + * \return a Hash mapping \p nodes to the nodes that should actually be added to the model or an empty node if the resource for a file URL + * does not exist yet. + * + * This method does check if the local file exists and may set an error. + */ + QHash<Soprano::Node, Soprano::Node> resolveNodes(const QSet<Soprano::Node>& nodes) const; + + /** + * Updates the nie:url of a local file resource. + * \return \p true if the url has been updated and nothing else needs to be done, \p false + * if the update was not handled. An error also results in a return value of \p true. + * + * \param resource The resource to update. Both file URLs and resource URIs are supported. Thus, there is no need to resolve the URL + * before calling this method. + * \param nieUrl The new nie:url to assign to the resource. + */ + bool updateNieUrlOnLocalFile(const QUrl& resource, const QUrl& nieUrl); + + ClassAndPropertyTree * classAndPropertyTree(); + + enum UriType { + GraphUri, + ResourceUri + }; + QUrl createUri(UriType type); + + /** + * Checks if any of the provided resources has a protected type (class, property, graph), ie. one + * of the resources should not be changed through the standard API. + * + * If the method returns \p true it has already set an appropriate error. + */ + bool containsResourceWithProtectedType(const QSet<QUrl>& resources) const; + + class Private; + Private* const d; + + friend class ResourceMerger; +}; +} + +#endif diff --git a/nepomuk/services/storage/graphmaintainer.cpp b/nepomuk/services/storage/graphmaintainer.cpp new file mode 100644 index 0000000..0415243 --- /dev/null +++ b/nepomuk/services/storage/graphmaintainer.cpp @@ -0,0 +1,96 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "graphmaintainer.h" + +#include <Soprano/Model> +#include <Soprano/QueryResultIterator> +#include <Soprano/NodeIterator> +#include <Soprano/BindingSet> +#include <Soprano/Node> +#include <Soprano/Statement> +#include <Soprano/Vocabulary/NRL> + +#include <KDebug> + + +using namespace Soprano::Vocabulary; + +Nepomuk::GraphMaintainer::GraphMaintainer(Soprano::Model* model) + : QThread(model), + m_model(model), + m_canceled(false) +{ +} + +Nepomuk::GraphMaintainer::~GraphMaintainer() +{ + m_canceled = true; + wait(); +} + +void Nepomuk::GraphMaintainer::run() +{ + kDebug() << "Running graph maintenance"; + + m_canceled = false; + + // + // query all empty graphs in batches + // + const QString query = QString::fromLatin1("select ?g ?mg where { " + "?g a %1 . " + "OPTIONAL { ?mg %2 ?g . } . " + "FILTER(!bif:exists((select (1) where { graph ?g { ?s ?p ?o . } . }))) . " + "} LIMIT 100") + .arg(Soprano::Node::resourceToN3(NRL::InstanceBase()), + Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor())); + + while(!m_canceled) { + QList<Soprano::BindingSet> graphs = m_model->executeQuery(query, Soprano::Query::QueryLanguageSparql).allBindings(); + if(graphs.isEmpty()) { + break; + } + foreach(const Soprano::BindingSet& graph, graphs) { + const Soprano::Node g = graph["g"]; + const Soprano::Node mg = graph["mg"]; + + if(m_canceled) { + break; + } + + // clear the metadata graph + if(mg.isResource()) { + m_model->removeContext(mg); + } + + // clear out anything that is left in crappy inference graphs + m_model->removeAllStatements(g, Soprano::Node(), Soprano::Node()); + + // wait a bit so we do not hog all CPU. + msleep(200); + } + } + + kDebug() << "Finished graph maintenance"; +} + +#include "graphmaintainer.moc" diff --git a/nepomuk/services/storage/graphmaintainer.h b/nepomuk/services/storage/graphmaintainer.h new file mode 100644 index 0000000..8ca547f --- /dev/null +++ b/nepomuk/services/storage/graphmaintainer.h @@ -0,0 +1,53 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef NEPOMUK_GRAPHMAINTAINER_H +#define NEPOMUK_GRAPHMAINTAINER_H + +#include <QtCore/QThread> + +namespace Soprano { +class Model; +} + +namespace Nepomuk { + +/** + * The graph maintainer takes care of cleaning up danging data like + * empty graphs which have been left by old or buggy code. + */ +class GraphMaintainer : public QThread +{ + Q_OBJECT + +public: + GraphMaintainer(Soprano::Model *model); + ~GraphMaintainer(); + +private: + void run(); + + Soprano::Model* m_model; + bool m_canceled; +}; +} + +#endif // NEPOMUK_GRAPHMAINTAINER_H diff --git a/nepomuk/services/storage/lib/CMakeLists.txt b/nepomuk/services/storage/lib/CMakeLists.txt new file mode 100644 index 0000000..5848c4b --- /dev/null +++ b/nepomuk/services/storage/lib/CMakeLists.txt @@ -0,0 +1,55 @@ +project(libdatamanagement) + +set(datamanagement_SRC + simpleresource.cpp + simpleresourcegraph.cpp + genericdatamanagementjob.cpp + createresourcejob.cpp + describeresourcesjob.cpp + dbustypes.cpp + datamanagement.cpp + resourcewatcher.cpp + abstracttimeoutdbusinterface.cpp + datamanagementinterface.cpp +) + +qt4_add_dbus_interface(datamanagement_SRC + ../../../interfaces/org.kde.nepomuk.ResourceWatcherConnection.xml + resourcewatcherconnectioninterface) + +qt4_add_dbus_interface(datamanagement_SRC + ../../../interfaces/org.kde.nepomuk.ResourceWatcher.xml + resourcewatchermanagerinterface) + +kde4_add_library(nepomukdatamanagement SHARED + ${datamanagement_SRC}) + +target_link_libraries(nepomukdatamanagement + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${SOPRANO_LIBRARIES} + ${NEPOMUK_LIBRARIES} + ${KDE4_KDECORE_LIBS}) + +install(TARGETS nepomukdatamanagement DESTINATION ${LIB_INSTALL_DIR}) +install(FILES + simpleresource.h + simpleresourcegraph.h + datamanagement.h + createresourcejob.h + describeresourcesjob.h + resourcewatcher.h + nepomukdatamanagement_export.h + DESTINATION ${INCLUDE_INSTALL_DIR}/nepomuk) + + +# API docs +find_package(Doxygen) + +if(DOXYGEN_EXECUTABLE) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + add_custom_target( + dms-apidox + COMMAND ${DOXYGEN_EXECUTABLE} Doxyfile) +endif(DOXYGEN_EXECUTABLE) diff --git a/nepomuk/services/storage/lib/Doxyfile.cmake b/nepomuk/services/storage/lib/Doxyfile.cmake new file mode 100644 index 0000000..cf9d13f --- /dev/null +++ b/nepomuk/services/storage/lib/Doxyfile.cmake @@ -0,0 +1,242 @@ +# Doxyfile 1.5.3 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = Nepomuk Data Management +PROJECT_NUMBER = ${KDE_VERSION} +OUTPUT_DIRECTORY = docs +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class " \ + "The $name widget " \ + "The $name file " \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = YES +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = ./ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +QT_AUTOBRIEF = YES +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = YES +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = YES +HIDE_FRIEND_COMPOUNDS = YES +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = YES +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text " +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = ${CMAKE_CURRENT_SOURCE_DIR} +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.h +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = ${CMAKE_CURRENT_SOURCE_DIR}/*_p.h ${CMAKE_CURRENT_SOURCE_DIR}/dbustypes.h +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = NO +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +HTML_DYNAMIC_SECTIONS = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = EIGEN_MAKE_DYNAMICSIZE_TYPEDEFS \ + EIGEN_MAKE_FIXEDSIZE_TYPEDEFS +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = NO +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = YES +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/nepomuk/services/storage/lib/abstracttimeoutdbusinterface.cpp b/nepomuk/services/storage/lib/abstracttimeoutdbusinterface.cpp new file mode 100644 index 0000000..cc060d3 --- /dev/null +++ b/nepomuk/services/storage/lib/abstracttimeoutdbusinterface.cpp @@ -0,0 +1,42 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "abstracttimeoutdbusinterface.h" + +#include <QtDBus/QDBusPendingCall> + +AbstractTimeoutDBusInterface::AbstractTimeoutDBusInterface(const QString& service, const QString& path, const char* interface, const QDBusConnection& connection, QObject* parent) + : QDBusAbstractInterface( service, path, interface, connection, parent ) +{ +} + +AbstractTimeoutDBusInterface::~AbstractTimeoutDBusInterface() +{ +} + +QDBusPendingCall AbstractTimeoutDBusInterface::asyncCallWithArgumentList(const QString &method, const QList<QVariant> &args, int timeout) +{ + QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(), interface(), method); + msg.setArguments(args); + return connection().asyncCall(msg, timeout); +} + +#include "abstracttimeoutdbusinterface.moc" diff --git a/nepomuk/services/storage/lib/abstracttimeoutdbusinterface.h b/nepomuk/services/storage/lib/abstracttimeoutdbusinterface.h new file mode 100644 index 0000000..3cd0f3f --- /dev/null +++ b/nepomuk/services/storage/lib/abstracttimeoutdbusinterface.h @@ -0,0 +1,41 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef ABSTRACTTIMEOUTDBUSINTERFACE_H +#define ABSTRACTTIMEOUTDBUSINTERFACE_H + +#include <QDBusAbstractInterface> + +/** + * An extension of the abstract DBus interface which allows to set the timeout. + */ +class AbstractTimeoutDBusInterface : public QDBusAbstractInterface +{ + Q_OBJECT + +public: + AbstractTimeoutDBusInterface(const QString& service, const QString& path, const char* interface, const QDBusConnection& connection, QObject* parent = 0); + ~AbstractTimeoutDBusInterface(); + + QDBusPendingCall asyncCallWithArgumentList(const QString &method, const QList<QVariant> &args, int timeout); +}; + +#endif // ABSTRACTTIMEOUTDBUSINTERFACE_H diff --git a/nepomuk/services/storage/lib/createresourcejob.cpp b/nepomuk/services/storage/lib/createresourcejob.cpp new file mode 100644 index 0000000..fb7880e --- /dev/null +++ b/nepomuk/services/storage/lib/createresourcejob.cpp @@ -0,0 +1,93 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "createresourcejob.h" +#include "datamanagementinterface.h" +#include "dbustypes.h" +#include "genericdatamanagementjob_p.h" + +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusPendingReply> +#include <QtDBus/QDBusPendingCallWatcher> + +#include <QtCore/QVariant> +#include <QtCore/QUrl> + +#include <KComponentData> +#include <KUrl> + + +class Nepomuk::CreateResourceJob::Private +{ +public: + QUrl m_resourceUri; +}; + +Nepomuk::CreateResourceJob::CreateResourceJob(const QList<QUrl>& types, + const QString& label, + const QString& description, + const KComponentData& component) + : KJob(0), + d(new Private) +{ + org::kde::nepomuk::DataManagement dms(QLatin1String(DMS_DBUS_SERVICE), + QLatin1String("/datamanagement"), + QDBusConnection::sessionBus()); + QDBusPendingCallWatcher* dbusCallWatcher + = new QDBusPendingCallWatcher(dms.createResource(Nepomuk::DBus::convertUriList(types), + label, + description, + component.componentName())); + connect(dbusCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(slotDBusCallFinished(QDBusPendingCallWatcher*))); +} + +Nepomuk::CreateResourceJob::~CreateResourceJob() +{ + delete d; +} + +void Nepomuk::CreateResourceJob::start() +{ + // do nothing, we do everything in the constructor +} + +void Nepomuk::CreateResourceJob::slotDBusCallFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QString> reply = *watcher; + if (reply.isError()) { + QDBusError error = reply.error(); + setError(1); + setErrorText(error.message()); + } + else { + d->m_resourceUri = KUrl(reply.value()); + } + watcher->deleteLater(); + emitResult(); +} + +QUrl Nepomuk::CreateResourceJob::resourceUri() const +{ + return d->m_resourceUri; +} + +#include "createresourcejob.moc" diff --git a/nepomuk/services/storage/lib/createresourcejob.h b/nepomuk/services/storage/lib/createresourcejob.h new file mode 100644 index 0000000..190aba9 --- /dev/null +++ b/nepomuk/services/storage/lib/createresourcejob.h @@ -0,0 +1,85 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef CREATERESOURCEJOB_H +#define CREATERESOURCEJOB_H + +#include <KJob> + +#include <QtCore/QHash> +#include <QtCore/QUrl> + +#include "nepomukdatamanagement_export.h" + +class KComponentData; +class QDBusPendingCallWatcher; + +namespace Nepomuk { +/** + * \class CreateResourceJob createresourcejob.h Nepomuk/CreateResourceJob + * + * \brief Job returned by Nepomuk::createResource(). + * + * Access the result through the resources() method in the slot connected + * to the KJOb::result() signal. + * + * \author Sebastian Trueg <trueg@kde.org> + */ +class NEPOMUK_DATA_MANAGEMENT_EXPORT CreateResourceJob : public KJob +{ + Q_OBJECT + +public: + /** + * Destructor. The job does delete itself as soon + * as it is done. + */ + ~CreateResourceJob(); + + /** + * The returned resource URI. + * + * Access the result in a slot connected to the KJob::result() + * signal. + */ + QUrl resourceUri() const; + +private Q_SLOTS: + void slotDBusCallFinished(QDBusPendingCallWatcher *watcher); + +private: + CreateResourceJob(const QList<QUrl>& types, + const QString& label, + const QString& description, + const KComponentData& component); + void start(); + + class Private; + Private* const d; + + friend Nepomuk::CreateResourceJob* Nepomuk::createResource(const QList<QUrl>&, + const QString&, + const QString&, + const KComponentData&); +}; +} + +#endif diff --git a/nepomuk/services/storage/lib/datamanagement.cpp b/nepomuk/services/storage/lib/datamanagement.cpp new file mode 100644 index 0000000..9c570b0 --- /dev/null +++ b/nepomuk/services/storage/lib/datamanagement.cpp @@ -0,0 +1,169 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "datamanagement.h" +#include "genericdatamanagementjob_p.h" +#include "createresourcejob.h" +#include "describeresourcesjob.h" +#include "dbustypes.h" +#include "simpleresourcegraph.h" + +#include <QtCore/QStringList> +#include <QtCore/QMutableListIterator> + +#include <KUrl> + + +KJob* Nepomuk::addProperty(const QList<QUrl>& resources, + const QUrl& property, + const QVariantList& values, + const KComponentData& component) +{ + return new GenericDataManagementJob("addProperty", + Q_ARG(QStringList, Nepomuk::DBus::convertUriList(resources)), + Q_ARG(QString, DBus::convertUri(property)), + Q_ARG(QVariantList, Nepomuk::DBus::normalizeVariantList(values)), + Q_ARG(QString, component.componentName())); +} + +KJob* Nepomuk::setProperty(const QList<QUrl>& resources, + const QUrl& property, + const QVariantList& values, + const KComponentData& component) +{ + return new GenericDataManagementJob("setProperty", + Q_ARG(QStringList, Nepomuk::DBus::convertUriList(resources)), + Q_ARG(QString, DBus::convertUri(property)), + Q_ARG(QVariantList, Nepomuk::DBus::normalizeVariantList(values)), + Q_ARG(QString, component.componentName())); +} + + +KJob* Nepomuk::removeProperty(const QList<QUrl>& resources, + const QUrl& property, + const QVariantList& values, + const KComponentData& component) +{ + return new GenericDataManagementJob("removeProperty", + Q_ARG(QStringList, Nepomuk::DBus::convertUriList(resources)), + Q_ARG(QString, DBus::convertUri(property)), + Q_ARG(QVariantList, Nepomuk::DBus::normalizeVariantList(values)), + Q_ARG(QString, component.componentName())); +} + + +KJob* Nepomuk::removeProperties(const QList<QUrl>& resources, + const QList<QUrl>& properties, + const KComponentData& component) +{ + return new GenericDataManagementJob("removeProperties", + Q_ARG(QStringList, Nepomuk::DBus::convertUriList(resources)), + Q_ARG(QStringList, Nepomuk::DBus::convertUriList(properties)), + Q_ARG(QString, component.componentName())); +} + + +Nepomuk::CreateResourceJob* Nepomuk::createResource(const QList<QUrl>& types, + const QString& label, + const QString& description, + const KComponentData& component) +{ + return new CreateResourceJob(types, label, description, component); +} + + +KJob* Nepomuk::removeResources(const QList<QUrl>& resources, + RemovalFlags flags, + const KComponentData& component) +{ + return new GenericDataManagementJob("removeResources", + Q_ARG(QStringList, Nepomuk::DBus::convertUriList(resources)), + Q_ARG(int, int(flags)), + Q_ARG(QString, component.componentName())); +} + + +KJob* Nepomuk::removeDataByApplication(const QList<QUrl>& resources, + RemovalFlags flags, + const KComponentData& component) +{ + return new GenericDataManagementJob("removeDataByApplication", + Q_ARG(QStringList, Nepomuk::DBus::convertUriList(resources)), + Q_ARG(int, int(flags)), + Q_ARG(QString, component.componentName())); +} + + +KJob* Nepomuk::removeDataByApplication(RemovalFlags flags, + const KComponentData& component) +{ + return new GenericDataManagementJob("removeDataByApplication", + Q_ARG(int, int(flags)), + Q_ARG(QString, component.componentName())); +} + + +KJob* Nepomuk::mergeResources(const QUrl& resource1, + const QUrl& resource2, + const KComponentData& component) +{ + return new GenericDataManagementJob("mergeResources", + Q_ARG(QString, Nepomuk::DBus::convertUri(resource1)), + Q_ARG(QString, Nepomuk::DBus::convertUri(resource2)), + Q_ARG(QString, component.componentName())); +} + +KJob* Nepomuk::storeResources(const SimpleResourceGraph& resources, + StoreIdentificationMode identificationMode, + StoreResourcesFlags flags, + const QHash<QUrl, QVariant>& additionalMetadata, + const KComponentData& component) +{ + return new GenericDataManagementJob("storeResources", + Q_ARG(QList<Nepomuk::SimpleResource>, resources.toList()), + Q_ARG(int, int(identificationMode)), + Q_ARG(int, int(flags)), + Q_ARG(Nepomuk::PropertyHash, additionalMetadata), + Q_ARG(QString, component.componentName())); +} + +KJob* Nepomuk::importResources(const KUrl& url, + Soprano::RdfSerialization serialization, + const QString& userSerialization, + StoreIdentificationMode identificationMode, + StoreResourcesFlags flags, + const QHash<QUrl, QVariant>& additionalMetadata, + const KComponentData& component) +{ + return new GenericDataManagementJob("importResources", + Q_ARG(QString, Nepomuk::DBus::convertUri(url)), + Q_ARG(QString, Soprano::serializationMimeType(serialization, userSerialization)), + Q_ARG(int, int(identificationMode)), + Q_ARG(int, int(flags)), + Q_ARG(Nepomuk::PropertyHash, additionalMetadata), + Q_ARG(QString, component.componentName())); +} + +Nepomuk::DescribeResourcesJob* Nepomuk::describeResources(const QList<QUrl>& resources, + bool includeSubResources) +{ + return new DescribeResourcesJob(resources, includeSubResources); +} diff --git a/nepomuk/services/storage/lib/datamanagement.h b/nepomuk/services/storage/lib/datamanagement.h new file mode 100644 index 0000000..97f0a9b --- /dev/null +++ b/nepomuk/services/storage/lib/datamanagement.h @@ -0,0 +1,466 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef DATAMANAGEMENT_H +#define DATAMANAGEMENT_H + +#include <QtCore/QObject> + +#include <KComponentData> +#include <KGlobal> + +#include <Soprano/Global> + +#include "nepomukdatamanagement_export.h" + +class KJob; +class KUrl; + + +// FIXME: remove this section in KDE 4.8 +/** + * \mainpage %Nepomuk Data Managment API Documentation + * + * This API is subject to change! + * + * See \ref nepomuk_datamanagement for all the gory details. + */ + + +namespace Nepomuk { + class DescribeResourcesJob; + class CreateResourceJob; + class SimpleResourceGraph; + + /** + * \defgroup nepomuk_datamanagement Nepomuk Data Management + * + * \brief The basic Nepomuk data manipulation interface. + * + * The Data Management API is the basic interface to manipulate data in Nepomuk. It provides methods to add, remove, + * change properties, add and remove resources, merge resources, integrate blobs of information, and retrieve resources + * according to certain criteria. + * + * The data management API can be split into two groups: the basic and the advanced API. The basic API allows to modify + * properties and resources in a very straight-forward manner: add or set a property value, remove property values or + * whole properties, and so on. The advanced API on the other hand deals with more complicated situations mostly based on + * the fact that Nepomuk always remembers which application did what. Each of the methods has a parameter which states + * the calling component. This allows clients to for example only remove information that it actually created, making + * updates very easy. + * + * + * \section nepomuk_dms_resource_uris Resource URIs + * + * Most methods take a single or a list of resource URIs. Normally all resources in Nepomuk have a unique URI of the form + * \c nepomuk:/res/UUID where \a UUID is a randomly generated universal identifer. However, the data management service + * methods can handle a bit more than that. Any local file URL can be used where a resource URI is required. It will + * automatically be converted to the appropriate resource URI. The same is true for any URL with a protocol supported + * by KIO. These URLs will not be used as resource URIs but as values of the \c nie:url property. + * + * Thus, when setting a property on a local file URL like <tt>file:///home/user/file.txt</tt> the property will actually be set + * on the resource corresponding to the local file. This resources has a resource URI of the form detailed above. Its + * \c nie:url property, however, will be set to <tt>file:///home/user/file.txt</tt>. + * + * In addition to the above URL conversion local file paths are also supported. They will be converted to local file URLs + * and then treated the same way as explained above. + * + * In general one should never create resource URIs manually. Instead use createResource() or storeResources() with an + * empty URI in the SimpleResource. The only exception are resources that have counterparts with URLs on the desktop like + * local files or web pages or the like. + * + * + * \section nepomuk_dms_sub_resources Sub-Resources + * + * Nepomuk has the concept of sub-resources. The basic idea of a sub-resource is that while it is a resource in itself, it + * does not make sense without its parent resource. The typical example would be contact details: + * + * \code + * <nepomuk:/res/A> a nco:Contact ; + * nco:fullname "Nepomuk Nasenbär" ; + * nco:hasPostalAddress <nepomuk:/res/B> ; + * nao:hasSubResource <nepomuk:/res/B> . + * + * <nepomuk:/res/B> a nco:PostalAddress ; + * nco:streetAddress "Nepomuk street 1" ; + * [...] . + * \endcode + * + * While in theory a \c nco:PostalAddress resource could live on its own it does not make much sense without the contact. + * Thus, it is marked as being the sub-resource of the \c nco:Contact. + * + * Less obvious examples are contacts that are just created for indexing email senders or for indexing music files. These + * are contacts the user most likely does not have need for without the original data - the email or the music file. Thus, + * these contacts would also be marked as sub-resources of the email or music file. + * + * This allows for nice cleanup when removing resources. The methods removeResources() and removeDataByApplication() allow + * to specify additional flags. One of these flags is RemoveSubResources. Specifying the flag results in the removal of + * sub-resources. + * + * + * \section nepomuk_dms_metadata Resource Metadata + * + * When thinking in Nepomuk terms all the information that is added is only data, not meta-data. (The file indexer which + * reads file meta-data to store it in Nepomuk also creates just data.) However, Nepomuk also maintains its own meta-data. + * For each resource the following properties are kept as meta-data: + * + * - \c nao:created - The creation date of the resource. + * - \c nao:lastModified - The last time the resource was modified. This includes the addition, removal, the change of + * properties, both with the resource as subject and as object. + * - \c nao:userVisible - A boolean property stating whether the resource should be shown to the user or not. This mostly + * applies to graphical user interfaces and is used automatically in the Nepomuk Query API. + * + * This information is updated automatically and cannot be changed through the API (except for special cases used for + * syncing). But it can be queried at any time to be used for whatever purpose. + * + * + * \section nepomuk_dms_advanced Advanced Nepomuk Concepts + * + * This section described advanced concepts in Nepomuk such as the data layout used throughout the database. + * + * \subsection nepomuk_dms_graphs Named Graphs in Nepomuk + * + * Nepomuk makes heavy use of named graphs or contexts when talking in terms of Soprano. Essentially the named graphs allow + * to group triples by adding a fourth node to a <a href="http://soprano.sourceforge.net/apidox/trunk/classSoprano_1_1Statement.html">statement</a> + * which can then be used like any other resource. In Nepomuk this mechanism is used to categorize triples + * and to store meta-data about them. + * + * Nepomuk makes the distincion between four basic types of graphs: + * - \c nrl:Ontology - An ontology graph contains class and property definitions which are read by the ontology loader. Typically + * these ontologies come from the <a href="http://oscaf.sf.net/">Shared-Desktop-Ontologies</a> package installed on the system. + * - \c nrl:InstanceBase - The instance base is the "normal" graph type. All information added to Nepomuk by clients is stored in + * instance base graphs. + * - \c nrl:DiscardableInstanceBase - Being a sub-class of \c nrl:InstanceBase the discardable instance base also contains "normal" + * information. The only difference is that this information can be recreated at any time. Thus, it is seen as \a discardable and + * is, for example not taken into account in backups. Typical discardable information includes file meta-data created by the file indexer. + * - \c nrl:GraphMetadata - The graph meta-data graphs only contains meta-data about graphs. This includes the type of a graph, its + * creation date, and so on. + * + * In addition to its type the following information is stored about each graph, and thus, about each triple in the graph: + * - \c nao:created - The creation date of the graph (and the triples within) + * - \c nao:maintainedBy - The application which triggered the creation of the graph. This is important for methods like removeDataByApplication(). + * + * Typically the information about one resource is scatterned over several graphs over time since every change to a resource leads + * to the creation of a new graph to save the creation date and the creating application. + * + * The following example shows the result of indexing one local file: + * + * \code + * <nepomuk:/ctx/b17ee4b5-ab8b-4fc5-bcc2-4bc25859cfa6> { + * <nepomuk:/res/e02fe67e-7b69-4ea7-847e-ebe2d3623d5c> + * nao:created “2011-05-20T11:23:45Z”^^xsd:dateTime ; + * nao:lastModified “2011-05-20T11:23:45Z”^^xsd:dateTime ; + * + * nie:contentSize "1286"^^xsd:int ; + * nie:isPartOf <nepomuk:/res/80b4187c-9c40-4e98-9322-9ebcc10bd0bd> ; + * nie:lastModified "2010-12-14T14:49:49Z"^^xsd:dateTime ; + * nie:mimeType "text/plain"^^xsd:string ; + * nie:plainTextContent "[...]"^^xsd:string ; + * nie:url <file:///home/nepomuk/helloworld.txt> ; + * nfo:characterCount "1249"^^xsd:int ; + * nfo:fileName "helloworld.txt"^^xsd:string ; + * nfo:lineCount "37"^^xsd:int ; + * nfo:wordCount "126"^^xsd:int ; + * a nfo:PlainTextDocument, nfo:FileDataObject . + * } + * <nepomuk:/ctx/5cf7070e-e4f4-4a0f-8b9a-fe9d94187d82> { + * <nepomuk:/ctx/5cf7070e-e4f4-4a0f-8b9a-fe9d94187d82> + * a nrl:GraphMetadata ; + * nrl:coreGraphMetadataFor <nepomuk:/ctx/b17ee4b5-ab8b-4fc5-bcc2-4bc25859cfa6> . + * + * <nepomuk:/ctx/b17ee4b5-ab8b-4fc5-bcc2-4bc25859cfa6> + * a nrl:DiscardableInstanceBase ; + * nao:created "2011-05-04T09:46:11.724Z"^^xsd:dateTime ; + * nao:maintainedBy <nepomuk:/res/someapp> . + * } + * \endcode + * + * Here one important thing is to be noted: the example contains two different last modification dates: \c nao:lastModified and \c nie:lastModified. + * \c nie:lastModified refers to the file on disk while \c nao:lastModified refers to the Nepomuk resource in the database. + * + * \author Sebastian Trueg <trueg@kde.org>, Vishesh Handa <handa.vish@gmail.com> + */ + //@{ + /** + * \name Basic Data Managment API + */ + //@{ + /** + * \brief Flags to influence the behaviour of the data management methods + * removeResources() and removeDataByApplication(). + */ + enum RemovalFlag { + /// No flags - default behaviour + NoRemovalFlags = 0, + + // trueg: why don't we make the removal of sub-resources the default? + /** + * Remove sub resources of the resources specified in the parameters. + * This will remove sub-resources that are not referenced by any resource + * that will not be deleted. + * See \ref nepomuk_dms_sub_resources for details. + */ + RemoveSubResoures = 1 + }; + Q_DECLARE_FLAGS(RemovalFlags, RemovalFlag) + + /** + * \brief Add one or more property values to one or more resources. + * + * Adds property values in addition to the existing ones. + * + * \param resources The resources to add the new property values to. See \ref nepomuk_dms_resource_uris for details. + * \param property The property to be changed. This needs to be the URI of a known property. If the property has + * cardinality restrictions which would be violated by this operation it will fail. + * \param values The values to add. For each resource and each value a triple will be created. + * \param component The calling component. Typically this is left to the default. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT KJob* addProperty(const QList<QUrl>& resources, + const QUrl& property, + const QVariantList& values, + const KComponentData& component = KGlobal::mainComponent()); + + /** + * \brief Set the values of a property for one or more resources. + * + * Sets property values overwriting/replacing the existing ones. + * + * \param resources The resources to add the new property values to. See \ref nepomuk_dms_resource_uris for details. + * \param property The property to be set. This needs to be the URI of a known property. If the property has + * cardinality restrictions which would be violated by this operation it will fail. + * \param values The values to set. For each resource and each value a triple will be created. Existing values will + * be overwritten. + * \param component The calling component. Typically this is left to the default. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT KJob* setProperty(const QList<QUrl>& resources, + const QUrl& property, + const QVariantList& values, + const KComponentData& component = KGlobal::mainComponent()); + + /** + * \brief Remove values of a property from one or more resources. + * + * Removes the given property values. + * + * \param resources The resources to remove the property values from. See \ref nepomuk_dms_resource_uris for details. + * \param property The property to be changed. This needs to be the URI of a known property. If the property has + * cardinality restrictions which would be violated by this operation it will fail. + * \param values The values to remove. + * \param component The calling component. Typically this is left to the default. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT KJob* removeProperty(const QList<QUrl>& resources, + const QUrl& property, + const QVariantList& values, + const KComponentData& component = KGlobal::mainComponent()); + + /** + * \brief Remove one or more properties from one or more resources. + * + * Removes all values from all given properties from all given resources. + * + * \param resources The resources to remove the properties from. See \ref nepomuk_dms_resource_uris for details. + * \param properties The properties to be changed. These need to be the URIs of known properties. If one pf the + * properties has cardinality restrictions which would be violated by this operation it will fail. + * \param component The calling component. Typically this is left to the default. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT KJob* removeProperties(const QList<QUrl>& resources, + const QList<QUrl>& properties, + const KComponentData& component = KGlobal::mainComponent()); + + /** + * \brief Create a new resource. + * + * Creates a new resource with the given types, label, and description and returns the new resource's URI. + * + * \param types A list of RDF types that the new resource should have. These need to be the URIs of known RDF classes. + * \param label The optional nao:prefLabel to be set. + * \param description The optional nao:description to be set. + * \param component The calling component. Typically this is left to the default. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT CreateResourceJob* createResource(const QList<QUrl>& types, + const QString& label, + const QString& description, + const KComponentData& component = KGlobal::mainComponent()); + + /** + * \brief Completely remove resources from the database. + * + * \param resources The resources to remove. See \ref nepomuk_dms_resource_uris for details. + * \param flags Optional flags to change the detail of what should be removed. + * \param component The calling component. Typically this is left to the default. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT KJob* removeResources(const QList<QUrl>& resources, + Nepomuk::RemovalFlags flags = Nepomuk::NoRemovalFlags, + const KComponentData& component = KGlobal::mainComponent()); + //@} + + /** + * \name Advanced Data Managment API + */ + //@{ + /** + * \brief The identification mode used by storeResources(). + * + * This states which given resources should be merged + * with existing ones that match. + */ + enum StoreIdentificationMode { + /// This is the default mode. Only new resources without a resource URI are identified. All others + /// are just saved with their given URI, provided the URI already exists. + IdentifyNew = 0, + + /// All resources are treated as new ones. The only exception are those with a defined + /// resource URI. + IdentifyNone = 2 + }; + + /** + * \brief Flags to influence the behaviour of storeResources(). + */ + enum StoreResourcesFlag { + /// No flags - default behaviour + NoStoreResourcesFlags = 0, + + /// By default storeResources() will only append data and fail if properties with + /// cardinality 1 already have a value. This flag changes the behaviour to force the + /// new values instead. + OverwriteProperties = 1, + + /// When lazy cardinalities are enabled any value that would violate a cardinality restriction + /// is simply dropped without throwing an error. + LazyCardinalities = 2 + }; + Q_DECLARE_FLAGS(StoreResourcesFlags, StoreResourcesFlag) + + /** + * \brief Remove all information about resources from the database which + * has been created by a specific application. + * + * \param resources The resources to remove the data from. See \ref nepomuk_dms_resource_uris for details. + * \param flags Optional flags to change the detail of what should be removed. When specifying RemoveSubResources + * even sub-resources created by other applications are removed if they are not referenced by other resources + * anymore. + * \param component The calling component. Only data created by this component is removed. Everything else + * is left untouched. Essential properties like \a nie:url are only removed if the entire resource is + * removed. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT KJob* removeDataByApplication(const QList<QUrl>& resources, + Nepomuk::RemovalFlags flags = Nepomuk::NoRemovalFlags, + const KComponentData& component = KGlobal::mainComponent()); + + /** + * \brief Remove all information created by a specific application. + * + * \param flags Optional flags to change the detail of what should be removed. Here RemoveSubResources is a bit + * special as it only applies to resources that are removed completely and spans sub-resources created by + * other applications. + * \param component The calling component. Only data created by this component is removed. Everything else + * is left untouched. Essential properties like \a nie:url are only removed if the entire resource is + * removed. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT KJob* removeDataByApplication(Nepomuk::RemovalFlags flags = Nepomuk::NoRemovalFlags, + const KComponentData& component = KGlobal::mainComponent()); + + /** + * \brief Merge two resources into one. + * + * \param resource1 The first resource to merge. If both resources have conflicting properties like different + * values on a property with a cardinality restriction the values from resource1 take precedence. + * See \ref nepomuk_dms_resource_uris for details. + * \param resource2 The resource to be merged into the first. See \ref nepomuk_dms_resource_uris for details. + * \param component The calling component. Typically this is left to the default. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT KJob* mergeResources(const QUrl& resource1, + const QUrl& resource2, + const KComponentData& component = KGlobal::mainComponent()); + + /** + * \brief Store many resources at once. + * + * This is the most powerful method of them all. It allows to store a whole set of resources in one + * go including creating new resources. + * + * \param resources The resources to be merged. Blank nodes (URIs of the form \a _:xyz) will be converted into new + * URIs (unless the identificationMode allows to merge with an existing resource). See \ref nepomuk_dms_resource_uris for details. + * \param identificationMode This method can try hard to avoid duplicate resources by looking + * for already existing duplicates based on nrl:IdentifyingProperty. By default it only looks + * for duplicates of resources that do not have a resource URI (SimpleResource::uri()) defined. + * This behaviour can be changed with this parameter. + * \param flags Additional flags to change the behaviour of the method. + * \param additionalMetadata Additional metadata for the added resources. This can include + * such details as the creator of the data or details on the method of data recovery. + * One typical usecase is that the file indexer uses (rdf:type, nrl:DiscardableInstanceBase) + * to state that the provided information can be recreated at any time. Only built-in types + * such as int, string, or url are supported. + * \param component The calling component. Typically this is left to the default. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT KJob* storeResources(const Nepomuk::SimpleResourceGraph& resources, + Nepomuk::StoreIdentificationMode identificationMode = Nepomuk::IdentifyNew, + Nepomuk::StoreResourcesFlags flags = Nepomuk::NoStoreResourcesFlags, + const QHash<QUrl, QVariant>& additionalMetadata = QHash<QUrl, QVariant>(), + const KComponentData& component = KGlobal::mainComponent()); + + /** + * \brief Import an RDF graph from a URL. + * + * This is essentially the same method as storeResources() except that it uses a different method + * encoding the resources. + * + * \param url The url from which the graph should be loaded. This does not have to be local. + * \param serialization The RDF serialization used for the file. If Soprano::SerializationUnknown a crude automatic + * detection based on file extension is used. + * \param userSerialization If \p serialization is Soprano::SerializationUser this value is used. See Soprano::Parser + * for details. + * \param identificationMode This method can try hard to avoid duplicate resources by looking + * for already existing duplicates based on nrl:IdentifyingProperty. By default it only looks + * for duplicates of resources that do not have a resource URI (SimpleResource::uri()) defined. + * This behaviour can be changed with this parameter. + * \param flags Additional flags to change the behaviour of the method. + * \param additionalMetadata Additional metadata for the added resources. This can include + * such details as the creator of the data or details on the method of data recovery. + * One typical usecase is that the file indexer uses (rdf:type, nrl:DiscardableInstanceBase) + * to state that the provided information can be recreated at any time. Only built-in types + * such as int, string, or url are supported. + * \param component The calling component. Typically this is left to the default. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT KJob* importResources(const KUrl& url, + Soprano::RdfSerialization serialization, + const QString& userSerialization = QString(), + StoreIdentificationMode identificationMode = IdentifyNew, + StoreResourcesFlags flags = NoStoreResourcesFlags, + const QHash<QUrl, QVariant>& additionalMetadata = QHash<QUrl, QVariant>(), + const KComponentData& component = KGlobal::mainComponent()); + + /** + * \brief Retrieve all information about a set of resources. + * + * \param resources The resources to describe. See \ref nepomuk_dms_resource_uris for details. + * \param includeSubResources If \p true sub resources will be included. See \ref nepomuk_dms_sub_resources for details. + */ + NEPOMUK_DATA_MANAGEMENT_EXPORT DescribeResourcesJob* describeResources(const QList<QUrl>& resources, + bool includeSubResources); + //@} + //@} +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(Nepomuk::RemovalFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(Nepomuk::StoreResourcesFlags) + +#endif diff --git a/nepomuk/services/storage/lib/datamanagementinterface.cpp b/nepomuk/services/storage/lib/datamanagementinterface.cpp new file mode 100644 index 0000000..5019353 --- /dev/null +++ b/nepomuk/services/storage/lib/datamanagementinterface.cpp @@ -0,0 +1,28 @@ +/* + * This file was generated by qdbusxml2cpp version 0.7 + * Command line was: qdbusxml2cpp -m -i dbustypes.h -p datamanagementinterface /home/trueg/kde/dev/kde/src/kde-runtime/nepomuk/interfaces/org.kde.nepomuk.DataManagement.xml + * + * qdbusxml2cpp is Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + * + * This is an auto-generated file. + * This file may have been hand-edited. Look for HAND-EDIT comments + * before re-generating it. + */ + +#include "datamanagementinterface.h" + +/* + * Implementation of interface class OrgKdeNepomukDataManagementInterface + */ + +OrgKdeNepomukDataManagementInterface::OrgKdeNepomukDataManagementInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) + : AbstractTimeoutDBusInterface(service, path, staticInterfaceName(), connection, parent) +{ +} + +OrgKdeNepomukDataManagementInterface::~OrgKdeNepomukDataManagementInterface() +{ +} + + +#include "datamanagementinterface.moc" diff --git a/nepomuk/services/storage/lib/datamanagementinterface.h b/nepomuk/services/storage/lib/datamanagementinterface.h new file mode 100644 index 0000000..bf51780 --- /dev/null +++ b/nepomuk/services/storage/lib/datamanagementinterface.h @@ -0,0 +1,190 @@ +/* + * This file was generated by qdbusxml2cpp version 0.7 + * Command line was: qdbusxml2cpp -m -i dbustypes.h -p datamanagementinterface /home/trueg/kde/dev/kde/src/kde-runtime/nepomuk/interfaces/org.kde.nepomuk.DataManagement.xml + * + * qdbusxml2cpp is Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + * + * This is an auto-generated file. + * + * It contains one small change: it is derived from AbstractTimeoutDbusInterface instead of QDBusAbstractInterface and uses a big timeout. + */ + +#ifndef DATAMANAGEMENTINTERFACE_H_1308595912 +#define DATAMANAGEMENTINTERFACE_H_1308595912 + +#include <QtCore/QObject> +#include <QtCore/QByteArray> +#include <QtCore/QList> +#include <QtCore/QMap> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QVariant> +#include <QtDBus/QtDBus> +#include "dbustypes.h" + +#include "abstracttimeoutdbusinterface.h" + +/* + * Proxy class for interface org.kde.nepomuk.DataManagement + */ +class OrgKdeNepomukDataManagementInterface: public AbstractTimeoutDBusInterface +{ + Q_OBJECT + + /// we use a big timeout (10 min) since commands are queued in the DMS + static const int s_defaultTimeout = 600000; + +public: + static inline const char *staticInterfaceName() + { return "org.kde.nepomuk.DataManagement"; } + +public: + OrgKdeNepomukDataManagementInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0); + + ~OrgKdeNepomukDataManagementInterface(); + +public Q_SLOTS: // METHODS + inline QDBusPendingReply<> addProperty(const QString &resource, const QString &property, const QDBusVariant &value, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resource) << qVariantFromValue(property) << qVariantFromValue(value) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("addProperty"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> addProperty(const QStringList &resources, const QString &property, const QVariantList &values, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resources) << qVariantFromValue(property) << qVariantFromValue(values) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("addProperty"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<QString> createResource(const QString &type, const QString &label, const QString &description, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(type) << qVariantFromValue(label) << qVariantFromValue(description) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("createResource"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<QString> createResource(const QStringList &types, const QString &label, const QString &description, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(types) << qVariantFromValue(label) << qVariantFromValue(description) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("createResource"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<QList<Nepomuk::SimpleResource> > describeResources(const QStringList &resources, bool includeSubResources) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resources) << qVariantFromValue(includeSubResources); + return asyncCallWithArgumentList(QLatin1String("describeResources"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> importResources(const QString &url, const QString &serialization, int identificationMode, int flags, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(url) << qVariantFromValue(serialization) << qVariantFromValue(identificationMode) << qVariantFromValue(flags) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("importResources"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> importResources(const QString &url, const QString &serialization, int identificationMode, int flags, Nepomuk::PropertyHash additionalMetadata, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(url) << qVariantFromValue(serialization) << qVariantFromValue(identificationMode) << qVariantFromValue(flags) << qVariantFromValue(additionalMetadata) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("importResources"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> mergeResources(const QString &resource1, const QString &resource2, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resource1) << qVariantFromValue(resource2) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("mergeResources"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> removeDataByApplication(int flags, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(flags) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("removeDataByApplication"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> removeDataByApplication(const QStringList &resources, int flags, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resources) << qVariantFromValue(flags) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("removeDataByApplication"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> removeProperties(const QString &resource, const QString &property, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resource) << qVariantFromValue(property) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("removeProperties"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> removeProperties(const QStringList &resources, const QStringList &properties, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resources) << qVariantFromValue(properties) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("removeProperties"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> removeProperty(const QString &resource, const QString &property, const QDBusVariant &value, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resource) << qVariantFromValue(property) << qVariantFromValue(value) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("removeProperty"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> removeProperty(const QStringList &resources, const QString &property, const QVariantList &values, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resources) << qVariantFromValue(property) << qVariantFromValue(values) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("removeProperty"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> removeResources(const QString &resource, int flags, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resource) << qVariantFromValue(flags) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("removeResources"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> removeResources(const QStringList &resources, int flags, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resources) << qVariantFromValue(flags) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("removeResources"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> setProperty(const QString &resource, const QString &property, const QDBusVariant &value, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resource) << qVariantFromValue(property) << qVariantFromValue(value) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("setProperty"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> setProperty(const QStringList &resources, const QString &property, const QVariantList &values, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resources) << qVariantFromValue(property) << qVariantFromValue(values) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("setProperty"), argumentList, s_defaultTimeout); + } + + inline QDBusPendingReply<> storeResources(const QList<Nepomuk::SimpleResource> &resources, int identificationMode, int flags, Nepomuk::PropertyHash additionalMetadata, const QString &app) + { + QList<QVariant> argumentList; + argumentList << qVariantFromValue(resources) << qVariantFromValue(identificationMode) << qVariantFromValue(flags) << qVariantFromValue(additionalMetadata) << qVariantFromValue(app); + return asyncCallWithArgumentList(QLatin1String("storeResources"), argumentList, s_defaultTimeout); + } + +Q_SIGNALS: // SIGNALS +}; + +namespace org { + namespace kde { + namespace nepomuk { + typedef ::OrgKdeNepomukDataManagementInterface DataManagement; + } + } +} +#endif diff --git a/nepomuk/services/storage/lib/dbustypes.cpp b/nepomuk/services/storage/lib/dbustypes.cpp new file mode 100644 index 0000000..27fd0f4 --- /dev/null +++ b/nepomuk/services/storage/lib/dbustypes.cpp @@ -0,0 +1,208 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dbustypes.h" + +#include <QtCore/QStringList> +#include <QtCore/QDate> +#include <QtCore/QTime> +#include <QtCore/QDateTime> +#include <QtDBus/QDBusMetaType> + +#include <KUrl> +#include <KDebug> + +QString Nepomuk::DBus::convertUri(const QUrl& uri) +{ + return KUrl(uri).url(); +} + +QStringList Nepomuk::DBus::convertUriList(const QList<QUrl>& uris) +{ + QStringList uriStrings; + foreach(const QUrl& uri, uris) + uriStrings << convertUri(uri); + return uriStrings; +} + +QVariantList Nepomuk::DBus::normalizeVariantList(const QVariantList& l) +{ + QVariantList newL; + QListIterator<QVariant> it(l); + while(it.hasNext()) { + QVariant v = it.next(); + if(v.userType() == qMetaTypeId<KUrl>()) { + newL.append(QVariant(QUrl(v.value<KUrl>()))); + } + else { + newL.append(v); + } + } + return newL; +} + +QVariant Nepomuk::DBus::resolveDBusArguments(const QVariant& v) +{ + // + // trueg: QDBus does not automatically convert non-basic types but gives us a QDBusArgument in a QVariant. + // Thus, we need to handle QUrl, QTime, QDate, and QDateTime as a special cases here. They is the only complex types we support. + // + if(v.userType() == qMetaTypeId<QDBusArgument>()) { + const QDBusArgument arg = v.value<QDBusArgument>(); + + QVariant v; + if(arg.currentSignature() == QLatin1String("(s)")) { + QUrl url; + arg >> url; + return url; + } + else if(arg.currentSignature() == QLatin1String("(iii)")) { + QDate date; + arg >> date; + return date; + } + else if(arg.currentSignature() == QLatin1String("(iiii)")) { + QTime time; + arg >> time; + return time; + } + else if(arg.currentSignature() == QLatin1String("((iii)(iiii)i)")) { + QDateTime dt; + arg >> dt; + return dt; + } + else { + kDebug() << "Unknown type signature in property hash value:" << arg.currentSignature(); + return QVariant(); + } + } + else { + return v; + } +} + +QVariantList Nepomuk::DBus::resolveDBusArguments(const QVariantList& l) +{ + QVariantList newL; + QListIterator<QVariant> it(l); + while(it.hasNext()) { + newL.append(resolveDBusArguments(it.next())); + } + return newL; +} + +void Nepomuk::DBus::registerDBusTypes() +{ + // we need QUrl to be able to pass it in a QVariant + qDBusRegisterMetaType<QUrl>(); + + // the central struct for storeResources and describeResources + qDBusRegisterMetaType<Nepomuk::SimpleResource>(); + + // we use a list instead of a struct for SimpleResourceGraph + qDBusRegisterMetaType<QList<Nepomuk::SimpleResource> >(); + + // required for the additional metadata in storeResources + qDBusRegisterMetaType<Nepomuk::PropertyHash>(); +} + +// We need the QUrl serialization to be able to pass URIs in variants +QDBusArgument& operator<<( QDBusArgument& arg, const QUrl& url ) +{ + arg.beginStructure(); + arg << QString::fromAscii(url.toEncoded()); + arg.endStructure(); + return arg; +} + +// We need the QUrl serialization to be able to pass URIs in variants +const QDBusArgument& operator>>( const QDBusArgument& arg, QUrl& url ) +{ + arg.beginStructure(); + QString uriString; + arg >> uriString; + url = QUrl::fromEncoded(uriString.toAscii()); + arg.endStructure(); + return arg; +} + +QDBusArgument& operator<<( QDBusArgument& arg, const Nepomuk::PropertyHash& ph ) +{ + arg.beginMap( QVariant::String, qMetaTypeId<QDBusVariant>()); + for(Nepomuk::PropertyHash::const_iterator it = ph.constBegin(); + it != ph.constEnd(); ++it) { + arg.beginMapEntry(); + arg << QString::fromAscii(it.key().toEncoded()); + + // a small hack to allow usage of KUrl + if(it.value().userType() == qMetaTypeId<KUrl>()) + arg << QDBusVariant(QUrl(it.value().value<KUrl>())); + else + arg << QDBusVariant(it.value()); + + arg.endMapEntry(); + } + arg.endMap(); + return arg; +} + +const QDBusArgument& operator>>( const QDBusArgument& arg, Nepomuk::PropertyHash& ph ) +{ + ph.clear(); + arg.beginMap(); + while(!arg.atEnd()) { + QString key; + QDBusVariant value; + arg.beginMapEntry(); + arg >> key >> value; + + const QUrl p = QUrl::fromEncoded(key.toAscii()); + const QVariant v = Nepomuk::DBus::resolveDBusArguments(value.variant()); + + ph.insertMulti(p, v); + + arg.endMapEntry(); + } + arg.endMap(); + return arg; +} + +QDBusArgument& operator<<( QDBusArgument& arg, const Nepomuk::SimpleResource& res ) +{ + arg.beginStructure(); + arg << QString::fromAscii(res.uri().toEncoded()); + arg << res.properties(); + arg.endStructure(); + return arg; +} + +const QDBusArgument& operator>>( const QDBusArgument& arg, Nepomuk::SimpleResource& res ) +{ + arg.beginStructure(); + QString uriS; + Nepomuk::PropertyHash props; + arg >> uriS; + res.setUri( QUrl::fromEncoded(uriS.toAscii()) ); + arg >> props; + res.setProperties(props); + arg.endStructure(); + return arg; +} diff --git a/nepomuk/services/storage/lib/dbustypes.h b/nepomuk/services/storage/lib/dbustypes.h new file mode 100644 index 0000000..900e219 --- /dev/null +++ b/nepomuk/services/storage/lib/dbustypes.h @@ -0,0 +1,62 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef DBUSTYPES_H +#define DBUSTYPES_H + +#include <QtCore/QMetaType> +#include <QtCore/QHash> +#include <QtCore/QString> +#include <QtCore/QUrl> +#include <QtDBus/QDBusVariant> +#include <QtDBus/QDBusArgument> + +#include "simpleresource.h" +#include "nepomukdatamanagement_export.h" + +Q_DECLARE_METATYPE(Nepomuk::PropertyHash) +Q_DECLARE_METATYPE(Nepomuk::SimpleResource) +Q_DECLARE_METATYPE(QList<Nepomuk::SimpleResource>) + +namespace Nepomuk { + namespace DBus { + QString convertUri(const QUrl& uri); + QStringList convertUriList(const QList<QUrl>& uris); + + /// Convert QDBusArguments variants into QUrl, QDate, QTime, and QDateTime variants + NEPOMUK_DATA_MANAGEMENT_EXPORT QVariant resolveDBusArguments(const QVariant& v); + NEPOMUK_DATA_MANAGEMENT_EXPORT QVariantList resolveDBusArguments(const QVariantList& l); + + /// Replaces KUrl with QUrl for DBus marshalling. + QVariantList normalizeVariantList(const QVariantList& l); + + NEPOMUK_DATA_MANAGEMENT_EXPORT void registerDBusTypes(); + } +} + +QDBusArgument& operator<<( QDBusArgument& arg, const QUrl& url ); +const QDBusArgument& operator>>( const QDBusArgument& arg, QUrl& url ); +QDBusArgument& operator<<( QDBusArgument& arg, const Nepomuk::PropertyHash& ph ); +const QDBusArgument& operator>>( const QDBusArgument& arg, Nepomuk::PropertyHash& ph ); +QDBusArgument& operator<<( QDBusArgument& arg, const Nepomuk::SimpleResource& res ); +const QDBusArgument& operator>>( const QDBusArgument& arg, Nepomuk::SimpleResource& res ); + +#endif // DBUSTYPES_H diff --git a/nepomuk/services/storage/lib/describeresourcesjob.cpp b/nepomuk/services/storage/lib/describeresourcesjob.cpp new file mode 100644 index 0000000..b955ea1 --- /dev/null +++ b/nepomuk/services/storage/lib/describeresourcesjob.cpp @@ -0,0 +1,92 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "describeresourcesjob.h" +#include "datamanagementinterface.h" +#include "simpleresourcegraph.h" +#include "dbustypes.h" +#include "genericdatamanagementjob_p.h" + +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusPendingReply> +#include <QtDBus/QDBusPendingCallWatcher> + +#include <QtCore/QVariant> +#include <QtCore/QUrl> + +#include <KComponentData> +#include <KDebug> + + +class Nepomuk::DescribeResourcesJob::Private +{ +public: + SimpleResourceGraph m_resources; +}; + +Nepomuk::DescribeResourcesJob::DescribeResourcesJob(const QList<QUrl>& resources, + bool includeSubResources) + : KJob(0), + d(new Private) +{ + DBus::registerDBusTypes(); + + org::kde::nepomuk::DataManagement dms(QLatin1String(DMS_DBUS_SERVICE), + QLatin1String("/datamanagement"), + QDBusConnection::sessionBus()); + QDBusPendingCallWatcher* dbusCallWatcher + = new QDBusPendingCallWatcher(dms.describeResources(Nepomuk::DBus::convertUriList(resources), + includeSubResources)); + connect(dbusCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(slotDBusCallFinished(QDBusPendingCallWatcher*))); +} + +Nepomuk::DescribeResourcesJob::~DescribeResourcesJob() +{ + delete d; +} + +void Nepomuk::DescribeResourcesJob::start() +{ + // do nothing, we do everything in the constructor +} + +void Nepomuk::DescribeResourcesJob::slotDBusCallFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QList<Nepomuk::SimpleResource> > reply = *watcher; + if (reply.isError()) { + QDBusError error = reply.error(); + setError(1); + setErrorText(error.message()); + } + else { + d->m_resources = reply.value(); + } + watcher->deleteLater(); + emitResult(); +} + +Nepomuk::SimpleResourceGraph Nepomuk::DescribeResourcesJob::resources() const +{ + return d->m_resources; +} + +#include "describeresourcesjob.moc" diff --git a/nepomuk/services/storage/lib/describeresourcesjob.h b/nepomuk/services/storage/lib/describeresourcesjob.h new file mode 100644 index 0000000..b63cd2b --- /dev/null +++ b/nepomuk/services/storage/lib/describeresourcesjob.h @@ -0,0 +1,82 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef DESCRIBERESOURCESJOB_H +#define DESCRIBERESOURCESJOB_H + +#include <KJob> + +#include <QtCore/QList> +#include <QtCore/QUrl> + +#include "nepomukdatamanagement_export.h" + +class QDBusPendingCallWatcher; + +namespace Nepomuk { +class SimpleResourceGraph; + +/** + * \class DescribeResourcesJob describeresourcesjob.h Nepomuk/DescribeResourcesJob + * + * \brief Job returned by Nepomuk::describeResources(). + * + * Access the result through the resources() method in the slot connected + * to the KJOb::result() signal. + * + * \author Sebastian Trueg <trueg@kde.org> + */ +class NEPOMUK_DATA_MANAGEMENT_EXPORT DescribeResourcesJob : public KJob +{ + Q_OBJECT + +public: + /** + * Destructor. The job does delete itself as soon + * as it is done. + */ + ~DescribeResourcesJob(); + + /** + * The returned resources. + * + * Access the result in a slot connected to the KJob::result() + * signal. + */ + SimpleResourceGraph resources() const; + +private Q_SLOTS: + void slotDBusCallFinished(QDBusPendingCallWatcher *watcher); + +private: + DescribeResourcesJob(const QList<QUrl>& resources, + bool includeSubResources); + void start(); + + class Private; + Private* const d; + + friend Nepomuk::DescribeResourcesJob* Nepomuk::describeResources(const QList<QUrl>&, + bool); +}; +} + +#endif diff --git a/nepomuk/services/storage/lib/genericdatamanagementjob.cpp b/nepomuk/services/storage/lib/genericdatamanagementjob.cpp new file mode 100644 index 0000000..949dfc0 --- /dev/null +++ b/nepomuk/services/storage/lib/genericdatamanagementjob.cpp @@ -0,0 +1,88 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "genericdatamanagementjob_p.h" +#include "datamanagementinterface.h" +#include "dbustypes.h" + +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusPendingReply> +#include <QtDBus/QDBusPendingCallWatcher> + +#include <QtCore/QVariant> +#include <QtCore/QHash> + +#include <KDebug> + +Nepomuk::GenericDataManagementJob::GenericDataManagementJob(const char *methodName, + QGenericArgument val0, + QGenericArgument val1, + QGenericArgument val2, + QGenericArgument val3, + QGenericArgument val4, + QGenericArgument val5) + : KJob(0) +{ + // DBus types necessary for storeResources + DBus::registerDBusTypes(); + + org::kde::nepomuk::DataManagement dms(QLatin1String(DMS_DBUS_SERVICE), + QLatin1String("/datamanagement"), + QDBusConnection::sessionBus()); + QDBusPendingReply<> reply; + QMetaObject::invokeMethod(&dms, + methodName, + Qt::DirectConnection, + Q_RETURN_ARG(QDBusPendingReply<> , reply), + val0, + val1, + val2, + val3, + val4, + val5); + QDBusPendingCallWatcher* dbusCallWatcher = new QDBusPendingCallWatcher(reply); + connect(dbusCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(slotDBusCallFinished(QDBusPendingCallWatcher*))); +} + +Nepomuk::GenericDataManagementJob::~GenericDataManagementJob() +{ +} + +void Nepomuk::GenericDataManagementJob::start() +{ + // do nothing +} + +void Nepomuk::GenericDataManagementJob::slotDBusCallFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<> reply = *watcher; + if (reply.isError()) { + QDBusError error = reply.error(); + kDebug() << error; + setError(int(error.type())); + setErrorText(error.message()); + } + delete watcher; + emitResult(); +} + +#include "genericdatamanagementjob_p.moc" diff --git a/nepomuk/services/storage/lib/genericdatamanagementjob_p.h b/nepomuk/services/storage/lib/genericdatamanagementjob_p.h new file mode 100644 index 0000000..4774ba0 --- /dev/null +++ b/nepomuk/services/storage/lib/genericdatamanagementjob_p.h @@ -0,0 +1,64 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef GENERICDATAMANAGEMENTJOB_H +#define GENERICDATAMANAGEMENTJOB_H + +#include <QtGlobal> + +#ifndef NDEBUG +#define DMS_DBUS_SERVICE (qgetenv("NEPOMUK_FAKE_DMS_DBUS_SERVICE").isEmpty() ? "org.kde.nepomuk.DataManagement" : qgetenv("NEPOMUK_FAKE_DMS_DBUS_SERVICE").constData()) +#else +#define DMS_DBUS_SERVICE "org.kde.nepomuk.DataManagement" +#endif + +#include <KJob> + +class QDBusPendingCallWatcher; + +namespace Nepomuk { +class GenericDataManagementJob : public KJob +{ + Q_OBJECT + +public: + /** + * Start any Data Management Service method with a void + * return type. + */ + GenericDataManagementJob(const char* methodName, + QGenericArgument val0, + QGenericArgument val1 = QGenericArgument(), + QGenericArgument val2 = QGenericArgument(), + QGenericArgument val3 = QGenericArgument(), + QGenericArgument val4 = QGenericArgument(), + QGenericArgument val5 = QGenericArgument()); + ~GenericDataManagementJob(); + + /// does nothing, we do all in the constructor - it simply needs to be implemented + void start(); + +private Q_SLOTS: + void slotDBusCallFinished(QDBusPendingCallWatcher*); +}; +} + +#endif diff --git a/nepomuk/services/storage/lib/nepomukdatamanagement_export.h b/nepomuk/services/storage/lib/nepomukdatamanagement_export.h new file mode 100644 index 0000000..929a737 --- /dev/null +++ b/nepomuk/services/storage/lib/nepomukdatamanagement_export.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE project + Copyright (C) 2007 David Faure <faure@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef NEPOMUKDATAMANAGEMENT_EXPORT_H +#define NEPOMUKDATAMANAGEMENT_EXPORT_H + +/* needed for KDE_EXPORT and KDE_IMPORT macros */ +#include <kdemacros.h> + +#ifndef NEPOMUK_DATA_MANAGEMENT_EXPORT +# if defined(MAKE_NEPOMUKDATAMANAGEMENT_LIB) + /* We are building this library */ +# define NEPOMUK_DATA_MANAGEMENT_EXPORT KDE_EXPORT +# else + /* We are using this library */ +# define NEPOMUK_DATA_MANAGEMENT_EXPORT KDE_IMPORT +# endif +#endif + +# ifndef NEPOMUK_DATA_MANAGEMENT_EXPORT_DEPRECATED +# define NEPOMUK_DATA_MANAGEMENT_EXPORT_DEPRECATED KDE_DEPRECATED NEPOMUK_DATA_MANAGEMENT_EXPORT +# endif + +#endif diff --git a/nepomuk/services/storage/lib/resourcewatcher.cpp b/nepomuk/services/storage/lib/resourcewatcher.cpp new file mode 100644 index 0000000..f6fc93c --- /dev/null +++ b/nepomuk/services/storage/lib/resourcewatcher.cpp @@ -0,0 +1,203 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "resourcewatcher.h" +#include "resourcewatcherconnectioninterface.h" +#include "resourcewatchermanagerinterface.h" + +#include <QtDBus/QDBusObjectPath> + +#include <Nepomuk/Resource> + +#include <KUrl> + +namespace { +QList<QUrl> convertUris(const QStringList& uris) { + QList<QUrl> us; + foreach(const QString& uri, uris) { + us << KUrl(uri); + } + return us; +} +} + +class Nepomuk::ResourceWatcher::Private { +public: + QList<Types::Class> m_types; + QList<Nepomuk::Resource> m_resources; + QList<Types::Property> m_properties; + + org::kde::nepomuk::ResourceWatcherConnection * m_connectionInterface; + org::kde::nepomuk::ResourceWatcher * m_watchManagerInterface; +}; + +Nepomuk::ResourceWatcher::ResourceWatcher(QObject* parent) + : QObject(parent), + d(new Private) +{ + d->m_watchManagerInterface + = new org::kde::nepomuk::ResourceWatcher( "org.kde.nepomuk.DataManagement", + "/resourcewatcher", + QDBusConnection::sessionBus() ); + d->m_connectionInterface = 0; +} + +Nepomuk::ResourceWatcher::~ResourceWatcher() +{ + stop(); + delete d; +} + +bool Nepomuk::ResourceWatcher::start() +{ + // + // Convert to list of strings + // + QList<QString> uris; + foreach( const Nepomuk::Resource & res, d->m_resources ) { + uris << KUrl(res.resourceUri()).url(); + } + + QList<QString> props; + foreach( const Types::Property & prop, d->m_properties ) { + props << KUrl(prop.uri()).url(); + } + + QList<QString> types_; + foreach( const Types::Class & cl, d->m_types ) { + types_ << KUrl(cl.uri()).url(); + } + + // + // Create the dbus object to watch + // + QDBusPendingReply<QDBusObjectPath> reply = d->m_watchManagerInterface->watch( uris, props, types_ ); + QDBusObjectPath path = reply.value(); + + if(!path.path().isEmpty()) { + d->m_connectionInterface = new org::kde::nepomuk::ResourceWatcherConnection( "org.kde.nepomuk.DataManagement", + path.path(), + QDBusConnection::sessionBus() ); + connect( d->m_connectionInterface, SIGNAL(propertyAdded(QString,QString,QDBusVariant)), + this, SLOT(slotPropertyAdded(QString,QString,QDBusVariant)) ); + connect( d->m_connectionInterface, SIGNAL(propertyRemoved(QString,QString,QDBusVariant)), + this, SLOT(slotPropertyRemoved(QString,QString,QDBusVariant)) ); + connect( d->m_connectionInterface, SIGNAL(resourceCreated(QString,QStringList)), + this, SLOT(slotResourceCreated(QString,QStringList)) ); + connect( d->m_connectionInterface, SIGNAL(resourceRemoved(QString,QStringList)), + this, SLOT(slotResourceRemoved(QString,QStringList)) ); + connect( d->m_connectionInterface, SIGNAL(resourceTypeAdded(QString,QString)), + this, SLOT(slotResourceTypeAdded(QString,QString)) ); + connect( d->m_connectionInterface, SIGNAL(resourceTypeRemoved(QString,QString)), + this, SLOT(slotResourceTypeRemoved(QString,QString)) ); + return true; + } + else { + return false; + } +} + +void Nepomuk::ResourceWatcher::stop() +{ + if (d->m_connectionInterface) { + d->m_connectionInterface->close(); + delete d->m_connectionInterface; + d->m_connectionInterface = 0; + } +} + +void Nepomuk::ResourceWatcher::addProperty(const Nepomuk::Types::Property& property) +{ + d->m_properties << property; +} + +void Nepomuk::ResourceWatcher::addResource(const Nepomuk::Resource& res) +{ + d->m_resources << res; +} + +void Nepomuk::ResourceWatcher::addType(const Nepomuk::Types::Class& type) +{ + d->m_types << type; +} + +QList< Nepomuk::Types::Property > Nepomuk::ResourceWatcher::properties() const +{ + return d->m_properties; +} + +QList<Nepomuk::Resource> Nepomuk::ResourceWatcher::resources() const +{ + return d->m_resources; +} + +QList< Nepomuk::Types::Class > Nepomuk::ResourceWatcher::types() const +{ + return d->m_types; +} + +void Nepomuk::ResourceWatcher::setProperties(const QList< Nepomuk::Types::Property >& properties_) +{ + d->m_properties = properties_; +} + +void Nepomuk::ResourceWatcher::setResources(const QList< Nepomuk::Resource >& resources_) +{ + d->m_resources = resources_; +} + +void Nepomuk::ResourceWatcher::setTypes(const QList< Nepomuk::Types::Class >& types_) +{ + d->m_types = types_; +} + +void Nepomuk::ResourceWatcher::slotResourceCreated(const QString &res, const QStringList &types) +{ + emit resourceCreated(Nepomuk::Resource::fromResourceUri(KUrl(res)), convertUris(types)); +} + +void Nepomuk::ResourceWatcher::slotResourceRemoved(const QString &res, const QStringList &types) +{ + emit resourceRemoved(KUrl(res), convertUris(types)); +} + +void Nepomuk::ResourceWatcher::slotResourceTypeAdded(const QString &res, const QString &type) +{ + emit resourceTypeAdded(KUrl(res), KUrl(type)); +} + +void Nepomuk::ResourceWatcher::slotResourceTypeRemoved(const QString &res, const QString &type) +{ + emit resourceTypeRemoved(KUrl(res), KUrl(type)); +} + +void Nepomuk::ResourceWatcher::slotPropertyAdded(const QString& res, const QString& prop, const QDBusVariant& object) +{ + emit propertyAdded( Resource::fromResourceUri(KUrl(res)), Types::Property( KUrl(prop) ), object.variant() ); +} + +void Nepomuk::ResourceWatcher::slotPropertyRemoved(const QString& res, const QString& prop, const QDBusVariant& object) +{ + emit propertyRemoved( Resource::fromResourceUri(KUrl(res)), Types::Property( KUrl(prop) ), object.variant() ); +} + +#include "resourcewatcher.moc" + diff --git a/nepomuk/services/storage/lib/resourcewatcher.h b/nepomuk/services/storage/lib/resourcewatcher.h new file mode 100644 index 0000000..ac4425d --- /dev/null +++ b/nepomuk/services/storage/lib/resourcewatcher.h @@ -0,0 +1,266 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef RESOURCEWATCHER_H +#define RESOURCEWATCHER_H + +#include <Nepomuk/Types/Class> +#include <Nepomuk/Types/Property> +#include <Nepomuk/Resource> + +#include <QtDBus/QDBusVariant> + +#include "nepomukdatamanagement_export.h" + +namespace Nepomuk { + + /** + * \class ResourceWatcher resourcewatcher.h + * + * \brief Selectively monitor the nepomuk repository for changes. + * + * Resources may be monitored on the basis of types, properties, and uris. + * + * Changes may be monitored in one of the following ways: + * -# By resources - + * Specify the exact resources that should be watched. Any changes made to the specified resources + * (Excluding \ref nepomuk_dms_metadata) will be notified through the propertyAdded() and propertyRemoved() + * signals. Notifications will also be sent if any of the watched resources is deleted. + * -# By resources and properties - + * Specify the exact resources and their properties. Any changes made to the specified resources + * which touch one of the specified properties will be notified through the propertyAdded() and propertyRemoved() + * signals. + * -# By types - + * Specific types may be specified via add/setType. If types are set, then notifications will be + * sent for all new resources of that type. This includes property changes and resource creation and removal. + * TODO: add flags that allow to only watch for resource creation and removal. + * -# By types and properties - + * Both the types and properties may be specified. Notifications will be sent for property changes + * in resource with the specified types. + * + * \section nepomuk_rw_examples Resource Watcher Usage Example + * + * The following code creates a new ResourceWatcher, configures it to listen to changes on the \c nmm:performer + * property on one specific resource \c res. + * + * \code + * Nepomuk::ResourceWatcher* watcher = new Nepomuk::ResourceWatcher(this); + * watcher->addResource(res); + * watcher->addProperty(NMM:performer()); + * connect(watcher, SIGNAL(propertyAdded(Nepomuk::Resource, Nepomuk::Types::Property, QVariant)), + * this, SLOT(slotPropertyChanged())); + * connect(watcher, SIGNAL(propertyRemoved(Nepomuk::Resource, Nepomuk::Types::Property, QVariant)), + * this, SLOT(slotPropertyChanged())); + * rwatcher->start(); + * \endcode + * + * \author Vishesh Handa <handa.vish@gmail.com>, Sebastian Trueg <trueg@kde.org> + * + * \ingroup nepomuk_datamanagement + */ + class NEPOMUK_DATA_MANAGEMENT_EXPORT ResourceWatcher : public QObject + { + Q_OBJECT + + public: + /** + * \brief Create a new %ResourceWatcher instance. + * + * This instance will not emit any signals before it has been configured + * and started. + */ + ResourceWatcher( QObject* parent = 0 ); + + /** + * \brief Destructor. + */ + virtual ~ResourceWatcher(); + + /** + * \brief Add a type to be watched. + * + * Every resource of this type will be watched for changes. + * + * \sa setTypes() + */ + void addType( const Types::Class & type ); + + /** + * \brief Add a resource to be watched. + * + * Every change to this resource will be + * signalled, depending on the configured properties(). + * + * \sa setResources() + */ + void addResource( const Nepomuk::Resource & res ); + + /** + * \brief Add a property to be watched. + * + * Every change to a value of this property + * will be signalled, depending on the configured resources() or types(). + * + * \sa setProperties() + */ + void addProperty( const Types::Property & property ); + + /** + * \brief Set the types to be watched. + * + * Every resource having one of these types will be watched for changes. + * + * \sa addType() + */ + void setTypes( const QList<Types::Class> & types_ ); + + /** + * \brief Set the resources to be watched. + * + * Every change to one of these resources will be + * signalled, depending on the configured properties(). + * + * \sa addResource() + */ + void setResources( const QList<Nepomuk::Resource> & resources_ ); + + /** + * \brief Set the properties to be watched. + * + * Every change to a value of any of these properties + * will be signalled, depending on the configured resources() or types(). + * + * \sa addProperty() + */ + void setProperties( const QList<Types::Property> & properties_ ); + + /** + * \brief The types that have been configured via addType() and setTypes(). + * + * Every resource having one of these types will be watched + * for changes. + */ + QList<Types::Class> types() const; + + /** + * \brief The resources that have been configured via addResource() and setResources(). + * + * Every change to one of these resources will be + * signalled, depending on the configured properties(). + */ + QList<Nepomuk::Resource> resources() const; + + /** + * \brief The properties that have been configured via addProperty() and setProperties(). + * + * Every change to a value of any of these properties + * will be signalled, depending on the configured resources() or types(). + */ + QList<Types::Property> properties() const; + + public Q_SLOTS: + /** + * \brief Start the signalling of changes. + * + * Before calling this method no signal will be emitted. In + * combination with stop() this allows to suspend the watching. + * Calling start() multiple times has no effect. + */ + bool start(); + + /** + * \brief Stop the signalling of changes. + * + * Allows to stop the watcher which has been started + * via start(). Calling stop() multiple times has no effect. + */ + void stop(); + + Q_SIGNALS: + /** + * \brief This signal is emitted when a new resource is created. + * \param resource The newly created resource. + * \param types The types the new resource has. If types() have been configured this list will always + * contain one of the configured types. + */ + void resourceCreated( const Nepomuk::Resource & resource, const QList<QUrl>& types ); //FIXME: Use either Resource or uri, not a mix + + /** + * \brief This signal is emitted when a resource is deleted. + * \param uri The resource URI of the removed resource. + * \param types The types the removed resource had. If types() have been configured this list will always + * contain one of the configured types. + */ + void resourceRemoved( const QUrl & uri, const QList<QUrl>& types ); + + /** + * \brief This signal is emitted when a type has been added to a resource. This does not include creation which + * is signalled via resourceCreated(). It only applies to changes in a resource's types. + * \param res The changed resource. + * \param type The newly added type. If types() have been configured it will be one of them. + */ + void resourceTypeAdded( const Nepomuk::Resource & res, const Types::Class & type ); + + /** + * \brief This signal is emitted when a type has been removed from a resource. + * + * This does not include removal of entire resources which is signalled via resourceRemoved(). + * It only applies to changes in a resource's types. + * \param res The changed resource. + * \param type The removed type. If types() have been configured it will be one of them. + */ + void resourceTypeRemoved( const Nepomuk::Resource & res, const Types::Class & type ); + + /** + * \brief This signal is emitted when a property value is added. + * \param resource The changed resource. + * \param property The property which has a new value. + * \param value The newly added property value. + */ + void propertyAdded( const Nepomuk::Resource & resource, + const Nepomuk::Types::Property & property, + const QVariant & value ); + + /** + * \brief This signal is emitted when a property value is removed. + * \param resource The changed resource. + * \param property The property which was changed. + * \param value The removed property value. + */ + void propertyRemoved( const Nepomuk::Resource & resource, + const Nepomuk::Types::Property & property, + const QVariant & value ); + + private Q_SLOTS: + void slotResourceCreated(const QString& res, const QStringList& types); + void slotResourceRemoved(const QString& res, const QStringList& types); + void slotResourceTypeAdded(const QString& res, const QString& type); + void slotResourceTypeRemoved(const QString& res, const QString& type); + void slotPropertyAdded(const QString& res, const QString& prop, const QDBusVariant& object); + void slotPropertyRemoved(const QString& res, const QString& prop, const QDBusVariant& object); + + private: + class Private; + Private * d; + }; +} + +#endif // RESOURCEWATCHER_H diff --git a/nepomuk/services/storage/lib/simpleresource.cpp b/nepomuk/services/storage/lib/simpleresource.cpp new file mode 100644 index 0000000..3d529bb --- /dev/null +++ b/nepomuk/services/storage/lib/simpleresource.cpp @@ -0,0 +1,278 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "simpleresource.h" + +#include <QtCore/QHashIterator> +#include <QtCore/QSharedData> +#include <QtCore/QVariant> +#include <QtCore/QDebug> + +#include <Soprano/Node> +#include <Soprano/LiteralValue> +#include <Soprano/Vocabulary/RDF> + + +namespace { +QAtomicInt s_idCnt; + +QUrl createBlankUri() +{ + // convert int to string (a...z,aa...az,ba....bz,...) + int idCnt = s_idCnt.fetchAndAddRelaxed(1); + QByteArray id; + do { + const int rest = idCnt%26; + id.append('a' + rest); + idCnt -= rest; + idCnt /= 26; + } while(idCnt > 0); + + const QUrl uri = QString(QLatin1String("_:") + id); + return uri; +} +} + +class Nepomuk::SimpleResource::Private : public QSharedData +{ +public: + QUrl m_uri; + PropertyHash m_properties; +}; + +Nepomuk::SimpleResource::SimpleResource(const QUrl& uri) +{ + d = new Private(); + setUri(uri); +} + +Nepomuk::SimpleResource::SimpleResource(const PropertyHash& properties) +{ + d = new Private(); + setUri(QUrl()); + setProperties(properties); +} + +Nepomuk::SimpleResource::SimpleResource(const SimpleResource& other) + : d(other.d) +{ +} + +Nepomuk::SimpleResource::~SimpleResource() +{ +} + +Nepomuk::SimpleResource & Nepomuk::SimpleResource::operator =(const Nepomuk::SimpleResource &other) +{ + d = other.d; + return *this; +} + +QUrl Nepomuk::SimpleResource::uri() const +{ + return d->m_uri; +} + +void Nepomuk::SimpleResource::setUri(const QUrl& uri) +{ + if(uri.isEmpty()) + d->m_uri = createBlankUri(); + else + d->m_uri = uri; +} + +namespace { + Soprano::Node convertIfBlankNode( const Soprano::Node & n ) { + if( n.isResource() && n.uri().toString().startsWith("_:") ) { + return Soprano::Node( n.uri().toString().mid(2) ); // "_:" take 2 characters + } + return n; + } +} + +QList< Soprano::Statement > Nepomuk::SimpleResource::toStatementList() const +{ + QList<Soprano::Statement> list; + QHashIterator<QUrl, QVariant> it( d->m_properties ); + while( it.hasNext() ) { + it.next(); + + Soprano::Node object; + if( it.value().type() == QVariant::Url ) + object = it.value().toUrl(); + else + object = Soprano::LiteralValue( it.value() ); + + list << Soprano::Statement( convertIfBlankNode( d->m_uri ), + it.key(), + convertIfBlankNode( object ) ); + } + return list; +} + +bool Nepomuk::SimpleResource::isValid() const +{ + // We do not check if m_uri.isValid() as a blank uri of the form "_:daf" would be invalid + if(d->m_uri.isEmpty() || d->m_properties.isEmpty()) { + return false; + } + + // properties cannot have empty values + PropertyHash::const_iterator end = d->m_properties.constEnd(); + for(PropertyHash::const_iterator it = d->m_properties.constBegin(); it != end; ++it) { + if(!it.value().isValid()) { + return false; + } + } + + return true; +} + +bool Nepomuk::SimpleResource::operator ==(const Nepomuk::SimpleResource &other) const +{ + return d->m_uri == other.d->m_uri && d->m_properties == other.d->m_properties; +} + +Nepomuk::PropertyHash Nepomuk::SimpleResource::properties() const +{ + return d->m_properties; +} + +bool Nepomuk::SimpleResource::contains(const QUrl &property) const +{ + return d->m_properties.contains(property); +} + +bool Nepomuk::SimpleResource::contains(const QUrl &property, const QVariant &value) const +{ + return d->m_properties.contains(property, value); +} + +bool Nepomuk::SimpleResource::containsNode(const QUrl &property, const Soprano::Node &node) const +{ + if(node.isLiteral()) + return contains(property, node.literal().variant()); + else if(node.isResource()) + return contains(property, node.uri()); + else + return false; +} + +void Nepomuk::SimpleResource::setPropertyNode(const QUrl &property, const Soprano::Node &value) +{ + d->m_properties.remove(property); + addPropertyNode(property, value); +} + +void Nepomuk::SimpleResource::setProperty(const QUrl &property, const QVariant &value) +{ + d->m_properties.remove(property); + addProperty(property, value); +} + +void Nepomuk::SimpleResource::setProperty(const QUrl& property, const Nepomuk::SimpleResource& res) +{ + setProperty(property, res.uri()); +} + + +void Nepomuk::SimpleResource::setProperty(const QUrl &property, const QVariantList &values) +{ + d->m_properties.remove(property); + foreach(const QVariant& v, values) { + addProperty(property, v); + } +} + +void Nepomuk::SimpleResource::addProperty(const QUrl &property, const QVariant &value) +{ + // QMultiHash even stores the same key/value pair multiple times! + if(!d->m_properties.contains(property, value)) + d->m_properties.insertMulti(property, value); +} + +void Nepomuk::SimpleResource::addProperty(const QUrl& property, const Nepomuk::SimpleResource& res) +{ + addProperty(property, res.uri()); +} + +void Nepomuk::SimpleResource::addPropertyNode(const QUrl &property, const Soprano::Node &node) +{ + if(node.isResource()) + addProperty(property, QVariant(node.uri())); + else if(node.isLiteral()) + addProperty(property, node.literal().variant()); + // else do nothing +} + +void Nepomuk::SimpleResource::removeProperty(const QUrl &property, const QVariant &value) +{ + d->m_properties.remove(property, value); +} + +void Nepomuk::SimpleResource::removeProperty(const QUrl &property) +{ + d->m_properties.remove(property); +} + +void Nepomuk::SimpleResource::addType(const QUrl &type) +{ + addProperty(Soprano::Vocabulary::RDF::type(), type); +} + +void Nepomuk::SimpleResource::setTypes(const QList<QUrl> &types) +{ + QVariantList values; + foreach(const QUrl& type, types) { + values << type; + } + setProperty(Soprano::Vocabulary::RDF::type(), values); +} + +void Nepomuk::SimpleResource::setProperties(const Nepomuk::PropertyHash &properties) +{ + d->m_properties = properties; +} + +void Nepomuk::SimpleResource::clear() +{ + d->m_properties.clear(); +} + +void Nepomuk::SimpleResource::addProperties(const Nepomuk::PropertyHash &properties) +{ + d->m_properties += properties; +} + +QVariantList Nepomuk::SimpleResource::property(const QUrl &property) const +{ + return d->m_properties.values(property); +} + +uint Nepomuk::qHash(const SimpleResource& res) +{ + return qHash(res.uri()); +} + +QDebug Nepomuk::operator<<(QDebug dbg, const Nepomuk::SimpleResource& res) +{ + return dbg << res.uri() << res.properties(); +} diff --git a/nepomuk/services/storage/lib/simpleresource.h b/nepomuk/services/storage/lib/simpleresource.h new file mode 100644 index 0000000..c6c3b04 --- /dev/null +++ b/nepomuk/services/storage/lib/simpleresource.h @@ -0,0 +1,181 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010-2011 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2010-2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef SIMPLERESOURCE_H +#define SIMPLERESOURCE_H + +#include <QtCore/QUrl> +#include <QtCore/QMultiHash> +#include <QtCore/QList> +#include <QtCore/QSharedDataPointer> + +#include <Soprano/Statement> + +#include "nepomukdatamanagement_export.h" + +namespace Nepomuk { + +typedef QMultiHash<QUrl, QVariant> PropertyHash; + +/** + * \class SimpleResource simpleresource.h Nepomuk/SimpleResource + * + * \brief Represents a snapshot of one %Nepomuk resource. + * + * \author Vishesh Handa <handa.vish@gmail.com>, Sebastian Trueg <trueg@kde.org> + */ +class NEPOMUK_DATA_MANAGEMENT_EXPORT SimpleResource +{ +public: + explicit SimpleResource(const QUrl& uri = QUrl()); + SimpleResource(const PropertyHash& properties); + SimpleResource(const SimpleResource& other); + virtual ~SimpleResource(); + + SimpleResource& operator=(const SimpleResource& other); + + bool operator==(const SimpleResource& other) const; + + QUrl uri() const; + + /** + * Setting an invalid/empty uri will create a new random ID. + * This allows to reuse SimpleResource instances. + */ + void setUri( const QUrl & uri ); + + bool contains(const QUrl& property) const; + bool contains(const QUrl& property, const QVariant& value) const; + bool containsNode(const QUrl& property, const Soprano::Node& value) const; + + /** + * Clear the resource, remove all properties. + */ + void clear(); + + /** + * Set the properties, replacing the existing properties. + */ + void setProperties(const PropertyHash& properties); + + /** + * Add a set of properties to the existing ones. + */ + void addProperties(const PropertyHash& properties); + + /** + * Set a property overwriting existing values. + * \param property The property to set + * \param value The value of the property. + */ + void setProperty(const QUrl& property, const QVariant& value); + + /** + * Set a property overwriting existing values. + * \param property The property to set + * \param values The values of the property. + */ + void setProperty(const QUrl& property, const QVariantList& values); + + /** + * Set a property overwriting existing values. + * \param property The property to set + * \param set The values of the property. + */ + void setProperty(const QUrl& property, const SimpleResource& res); + + /** + * Set a property overwriting existing values. + * \param property The property to set + * \param value The value of the property. Will be converted to a QVariant. + */ + void setPropertyNode(const QUrl& property, const Soprano::Node& value); + + /** + * Add a property. This allows to add more than one value for a property. + * \param property The property to set + * \param value The value of the property. + */ + void addProperty(const QUrl& property, const QVariant& value); + + /** + * Add a property. This allows to add more than one value for a property. + * \param property The property to set + * \param res The value of the property. + */ + void addProperty(const QUrl& property, const SimpleResource& res); + + /** + * Add a property. + * \param property The property to set + * \param value The value of the property. Will be converted to a QVariant. + */ + void addPropertyNode(const QUrl& property, const Soprano::Node& value); + + void removeProperty(const QUrl& property, const QVariant& value); + void removeProperty(const QUrl& property); + + /** + * A convenience method which adds a property of type rdf:type. + * \param type The type to add to the resource. Must be the URI of an RDF class. + */ + void addType(const QUrl& type); + + /** + * A convenience method which sets the property of type rdf:type. + * \param types The types to set to the resource. Must be URIs of RDF classes. + */ + void setTypes(const QList<QUrl>& types); + + /** + * Get all values for \p property. + */ + QVariantList property(const QUrl& property) const; + + /** + * \return All properties. + */ + PropertyHash properties() const; + + /** + * Converts the resource into a list of statements. None of the statements will + * have a valid context set. + */ + QList<Soprano::Statement> toStatementList() const; + + /** + * \return \p true if the resource is valid, ie. if it has a valid uri() and + * a non-empty list of properties. + */ + bool isValid() const; + +private: + class Private; + QSharedDataPointer<Private> d; +}; + +NEPOMUK_DATA_MANAGEMENT_EXPORT QDebug operator<<(QDebug dbg, const Nepomuk::SimpleResource& res); + +NEPOMUK_DATA_MANAGEMENT_EXPORT uint qHash(const SimpleResource& res); +} + +#endif diff --git a/nepomuk/services/storage/lib/simpleresourcegraph.cpp b/nepomuk/services/storage/lib/simpleresourcegraph.cpp new file mode 100644 index 0000000..419a949 --- /dev/null +++ b/nepomuk/services/storage/lib/simpleresourcegraph.cpp @@ -0,0 +1,220 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "simpleresourcegraph.h" +#include "simpleresource.h" +#include "datamanagement.h" + +#include <QtCore/QSharedData> +#include <QtCore/QHash> +#include <QtCore/QUrl> +#include <QtCore/QDebug> + +#include <KRandom> + +class Nepomuk::SimpleResourceGraph::Private : public QSharedData +{ +public: + QHash<QUrl, SimpleResource> resources; +}; + + +Nepomuk::SimpleResourceGraph::SimpleResourceGraph() + : d(new Private) +{ +} + +Nepomuk::SimpleResourceGraph::SimpleResourceGraph(const SimpleResource& resource) + : d(new Private) +{ + insert(resource); +} + +Nepomuk::SimpleResourceGraph::SimpleResourceGraph(const QList<SimpleResource>& resources) + : d(new Private) +{ + Q_FOREACH(const SimpleResource& res, resources) { + insert(res); + } +} + +Nepomuk::SimpleResourceGraph::SimpleResourceGraph(const QSet<SimpleResource>& resources) + : d(new Private) +{ + Q_FOREACH(const SimpleResource& res, resources) { + insert(res); + } +} + +Nepomuk::SimpleResourceGraph::SimpleResourceGraph(const SimpleResourceGraph& other) + : d(other.d) +{ +} + +Nepomuk::SimpleResourceGraph::~SimpleResourceGraph() +{ +} + +Nepomuk::SimpleResourceGraph & Nepomuk::SimpleResourceGraph::operator=(const Nepomuk::SimpleResourceGraph &other) +{ + d = other.d; + return *this; +} + +void Nepomuk::SimpleResourceGraph::insert(const SimpleResource &res) +{ + d->resources.insert(res.uri(), res); +} + +Nepomuk::SimpleResourceGraph& Nepomuk::SimpleResourceGraph::operator<<(const SimpleResource &res) +{ + insert(res); + return *this; +} + +void Nepomuk::SimpleResourceGraph::remove(const QUrl &uri) +{ + d->resources.remove(uri); +} + +void Nepomuk::SimpleResourceGraph::remove(const SimpleResource &res) +{ + if( contains( res ) ) + remove( res.uri() ); +} + +void Nepomuk::SimpleResourceGraph::add(const QUrl &uri, const QUrl &property, const QVariant &value) +{ + if(!uri.isEmpty()) { + d->resources[uri].setUri(uri); + d->resources[uri].addProperty(property, value); + } +} + +void Nepomuk::SimpleResourceGraph::set(const QUrl &uri, const QUrl &property, const QVariant &value) +{ + removeAll(uri, property); + add(uri, property, value); +} + +void Nepomuk::SimpleResourceGraph::remove(const QUrl &uri, const QUrl &property, const QVariant &value) +{ + QHash< QUrl, SimpleResource >::iterator it = d->resources.find( uri ); + if( it != d->resources.end() ) { + it.value().removeProperty(property, value); + } +} + +void Nepomuk::SimpleResourceGraph::removeAll(const QUrl &uri, const QUrl &property) +{ + QHash< QUrl, SimpleResource >::iterator it = d->resources.find( uri ); + if( it != d->resources.end() ) { + it.value().removeProperty(property); + } +} + +bool Nepomuk::SimpleResourceGraph::contains(const QUrl &uri) const +{ + return d->resources.contains(uri); +} + +bool Nepomuk::SimpleResourceGraph::containsAny(const QUrl &res, const QUrl &property) const +{ + QHash< QUrl, SimpleResource >::const_iterator it = d->resources.constFind( res ); + if( it == d->resources.constEnd() ) + return false; + + return it.value().contains(property); +} + +bool Nepomuk::SimpleResourceGraph::contains(const SimpleResource &res) const +{ + QHash< QUrl, SimpleResource >::const_iterator it = d->resources.find( res.uri() ); + if( it == d->resources.constEnd() ) + return false; + + return res == it.value(); +} + +Nepomuk::SimpleResource Nepomuk::SimpleResourceGraph::operator[](const QUrl &uri) const +{ + return d->resources[uri]; +} + +QSet<Nepomuk::SimpleResource> Nepomuk::SimpleResourceGraph::toSet() const +{ + return QSet<SimpleResource>::fromList(toList()); +} + +QList<Nepomuk::SimpleResource> Nepomuk::SimpleResourceGraph::toList() const +{ + return d->resources.values(); +} + +void Nepomuk::SimpleResourceGraph::clear() +{ + d->resources.clear(); +} + +bool Nepomuk::SimpleResourceGraph::isEmpty() const +{ + return d->resources.isEmpty(); +} + +int Nepomuk::SimpleResourceGraph::count() const +{ + return d->resources.count(); +} + +namespace { +QVariant nodeToVariant(const Soprano::Node& node) { + if(node.isResource()) + return node.uri(); + else if(node.isBlank()) + return QUrl(QLatin1String("_:") + node.identifier()); + else + return node.literal().variant(); +} +} + +void Nepomuk::SimpleResourceGraph::addStatement(const Soprano::Statement &s) +{ + const QUrl uri = nodeToVariant(s.subject()).toUrl(); + const QVariant value = nodeToVariant(s.object()); + d->resources[uri].setUri(uri); + d->resources[uri].addProperty(s.predicate().uri(), value); +} + +void Nepomuk::SimpleResourceGraph::addStatement(const Soprano::Node& subject, const Soprano::Node& predicate, const Soprano::Node& object) +{ + addStatement( Soprano::Statement( subject, predicate, object ) ); +} + + +KJob* Nepomuk::SimpleResourceGraph::save(const KComponentData& component) const +{ + return Nepomuk::storeResources(*this, Nepomuk::IdentifyNew, Nepomuk::NoStoreResourcesFlags, QHash<QUrl, QVariant>(), component); +} + +QDebug Nepomuk::operator<<(QDebug dbg, const Nepomuk::SimpleResourceGraph& graph) +{ + return dbg << graph.toList(); +} diff --git a/nepomuk/services/storage/lib/simpleresourcegraph.h b/nepomuk/services/storage/lib/simpleresourcegraph.h new file mode 100644 index 0000000..6a3a0ee --- /dev/null +++ b/nepomuk/services/storage/lib/simpleresourcegraph.h @@ -0,0 +1,109 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef SIMPLERESOURCEGRAPH_H +#define SIMPLERESOURCEGRAPH_H + +#include <QtCore/QSharedDataPointer> +#include <QtCore/QList> +#include <QtCore/QSet> +#include <QtCore/QUrl> +#include <QtCore/QMetaType> + +#include <KGlobal> + +#include "nepomukdatamanagement_export.h" + +class KJob; +namespace Soprano { +class Statement; +class Node; +} +namespace Nepomuk { +class SimpleResource; + +class NEPOMUK_DATA_MANAGEMENT_EXPORT SimpleResourceGraph +{ +public: + SimpleResourceGraph(); + SimpleResourceGraph(const SimpleResource& resource); + SimpleResourceGraph(const QList<SimpleResource>& resources); + SimpleResourceGraph(const QSet<SimpleResource>& resources); + SimpleResourceGraph(const SimpleResourceGraph& other); + ~SimpleResourceGraph(); + + SimpleResourceGraph& operator=(const SimpleResourceGraph& other); + + /** + * Adds a resource to the graph. An invalid resource will get a + * new blank node as resource URI. + */ + void insert(const SimpleResource& res); + SimpleResourceGraph& operator<<(const SimpleResource& res); + + void remove(const QUrl& uri); + void remove(const SimpleResource& res); + + void add(const QUrl& uri, const QUrl& property, const QVariant& value); + void set(const QUrl& uri, const QUrl& property, const QVariant& value); + + void remove(const QUrl& uri, const QUrl& property, const QVariant& value); + void removeAll(const QUrl& uri, const QUrl& property); + + void clear(); + + int count() const; + bool isEmpty() const; + + bool contains(const SimpleResource& res) const; + bool contains(const QUrl& res) const; + bool containsAny(const QUrl& res, const QUrl& property) const; + + SimpleResource operator[](const QUrl& uri) const; + + QSet<SimpleResource> toSet() const; + QList<SimpleResource> toList() const; + + void addStatement(const Soprano::Statement& statement); + void addStatement( const Soprano::Node & subject, const Soprano::Node & predicate, + const Soprano::Node & object ); + /** + * Save the graph to the Nepomuk database. + * \return A job that will perform the saving + * and emit the result() signal once done. + * Use the typical KJob error handling methods. + * + * \param component The component which should be given to + * Nepomuk for it to relate the newly created data to it. + * + * \sa Nepomuk::storeResources() + */ + KJob* save(const KComponentData& component = KGlobal::mainComponent()) const; + +private: + class Private; + QSharedDataPointer<Private> d; +}; + +NEPOMUK_DATA_MANAGEMENT_EXPORT QDebug operator<<(QDebug dbg, const Nepomuk::SimpleResourceGraph& graph); +} + +#endif diff --git a/nepomuk/services/storage/nepomukcore.cpp b/nepomuk/services/storage/nepomukcore.cpp index f9ed06c..452c98b 100644 --- a/nepomuk/services/storage/nepomukcore.cpp +++ b/nepomuk/services/storage/nepomukcore.cpp @@ -83,10 +83,6 @@ void Nepomuk::Core::slotRepositoryOpened( Repository* repo, bool success ) void Nepomuk::Core::slotOntologiesLoaded() { - // once ontologies are updated we should update the query prefixes - m_repository->setEnableQueryPrefixExpansion(false); - m_repository->setEnableQueryPrefixExpansion(true); - // the first time this is a very long procedure. Thus, we do it while Nepomuk is active although then some queries might return invalid results m_repository->updateInference(); diff --git a/nepomuk/services/storage/nepomukstorage.desktop b/nepomuk/services/storage/nepomukstorage.desktop index 4fdaa3f..30d5ddc 100644 --- a/nepomuk/services/storage/nepomukstorage.desktop +++ b/nepomuk/services/storage/nepomukstorage.desktop @@ -10,6 +10,7 @@ Name[ast]=Atroxamientu de datos Nepomuk Name[be@latin]=Schovišča dla źviestak „Nepomuk” Name[bg]=Хранилище Nepomuk Name[bn_IN]=Nepomuk ডাটা সংরক্ষণব্যবস্থা +Name[bs]=Nepomukovo skladište Name[ca]=Emmagatzematge de dades del Nepomuk Name[ca@valencia]=Emmagatzematge de dades del Nepomuk Name[cs]=Datové úložiště Nepomuku @@ -74,6 +75,7 @@ Name[te]=Nepomuk డాటా నిల్వ Name[tg]=Захирагоҳи Nepomuk Name[th]=ที่จัดเก็บข้อมูลของ Nepomuk Name[tr]=Nepomuk Veri Depolama +Name[ug]=Nepomuk سانلىق مەلۇمات ساقلاش Name[uk]=Збереження даних Nepomuk Name[wa]=Sitocaedje di dnêyes Nepomuk Name[x-test]=xxNepomuk Data Storagexx @@ -84,6 +86,7 @@ Comment[ar]=خدمة تخزين بيانات نبومك الأساسية Comment[ast]=Serviciu principal d'atroxamientu de datos Nepomuk Comment[be@latin]=Hałoŭnaja słužba „Nepomuk” dla zachoŭvańnia źviestak Comment[bg]=Основното място, където се съхраняват данните на Nepomuk +Comment[bs]=Jezgarni servis Nepomuka za skladištenje podataka Comment[ca]=El servei d'emmagatzematge de dades del nucli del Nepomuk Comment[ca@valencia]=El servei d'emmagatzematge de dades del nucli del Nepomuk Comment[cs]=Jádro služby Datové úložiště Nepomuku @@ -147,6 +150,7 @@ Comment[te]=ఆధార Nepomuk డాటా నిల్వ సేవ Comment[tg]=Хидмати асосии захирагоҳи Nepomuk Comment[th]=บริการจัดเก็บข้อมูลระดับแกนของ Nepomik Comment[tr]=Nepomuk Ana veri depolama servisi +Comment[ug]=Nepomuk يادرولۇق سانلىق مەلۇمات ساقلاش مۇلازىمىتى Comment[uk]=Ядро служби збереження даних Nepomuk Comment[wa]=Li siervice cour di stocaedje di dnêyes Nepomuk Comment[x-test]=xxThe Core Nepomuk data storage servicexx diff --git a/nepomuk/services/storage/nepomukstorage.notifyrc b/nepomuk/services/storage/nepomukstorage.notifyrc index 485c082..47e3012 100644 --- a/nepomuk/services/storage/nepomukstorage.notifyrc +++ b/nepomuk/services/storage/nepomukstorage.notifyrc @@ -4,6 +4,7 @@ Name=Semantic Data Storage Name[ar]=مخزن البيانات الدلالية Name[ast]=Atroxamientu de datos semánticos Name[bg]=Хранилище за семантични данни +Name[bs]=Semantičko skladištenje podataka Name[ca]=Emmagatzematge de dades semàntiques Name[ca@valencia]=Emmagatzematge de dades semàntiques Name[cs]=Úložistě sémantických dat @@ -33,7 +34,6 @@ Name[kn]=ಸೆಮಾಂಟಿಕ್ ದತ್ತ ಸಂಗ್ರಹ Name[ko]=시맨틱 데이터 저장소 Name[lt]=Semantinių duomenų saugykla Name[lv]=Semantisko datu glabātuve -Name[mai]=सेमांटिक डाटा भंडार Name[ml]=സെമാന്റിക്ക് ഡേറ്റാ സംഭരണം Name[nb]=Semantisk datalagring Name[nds]=Semantsch Datenaflaag @@ -56,6 +56,7 @@ Name[sv]=Semantisk datalagring Name[tg]=Захирагоҳи маъноии маълумот Name[th]=ที่จัดเก็บข้อมูลของระบบค้นหา Name[tr]=Anlamsal Veri Depolama +Name[ug]=Semantic سانلىق مەلۇمات ساقلاش Name[uk]=Сховище семантичних даних Name[x-test]=xxSemantic Data Storagexx Name[zh_CN]=语义学数据存储 @@ -64,6 +65,7 @@ Comment=Semantic Desktop Comment[ar]=سطح المكتب الدلالي Comment[ast]=Escritoriu semánticu Comment[bg]=Семантичен работен плот +Comment[bs]=Semantička površ Comment[ca]=Escriptori semàntic Comment[ca@valencia]=Escriptori semàntic Comment[cs]=Sémantický desktop @@ -93,7 +95,6 @@ Comment[kn]=ಸೆಮಾಂಟಿಕ್ ಗಣಕತೆರೆ Comment[ko]=시맨틱 데스크톱 Comment[lt]=Semantinis darbastalis Comment[lv]=Semantiskā darbvirsma -Comment[mai]=सेमांटिक डेस्कटॉप Comment[ml]=സെമാന്റിക്ക് പണിയിടം Comment[nb]=Semantisk skrivebord Comment[nds]=Schriefdischbeslöteln @@ -104,6 +105,7 @@ Comment[pl]=Pulpit semantyczny Comment[pt]=Ambiente de Trabalho Semântico Comment[pt_BR]=Área de trabalho semântica Comment[ro]=Birou semantic +Comment[ru]=Служба семантических связей Nepomuk Comment[sk]=Sémantický desktop Comment[sl]=Semantično namizje Comment[sr]=Семантичка површ @@ -114,6 +116,7 @@ Comment[sv]=Semantiskt skrivbord Comment[tg]=Мизи кории маъноӣ Comment[th]=ระบบค้นหาผ่านพื้นที่ทำงาน Comment[tr]=Anlamsal Masaüstü +Comment[ug]=Semantic ئۈستەلئۈستى Comment[uk]=Семантична стільниця Comment[x-test]=xxSemantic Desktopxx Comment[zh_CN]=语义学桌面 @@ -124,6 +127,7 @@ Name=Failed to start Nepomuk Name[ar]=فشل تشغيل نِيبوموك Name[ast]=Fallu al entamar Nepomuk Name[bg]=Грешка при зареждане на Nepomuk +Name[bs]=Neuspelo pokretanje Nepomuka Name[ca]=Ha fallat en iniciar el Nepomuk Name[ca@valencia]=Ha fallat en iniciar el Nepomuk Name[cs]=Nepovedlo se spustit Nepomuk @@ -156,7 +160,6 @@ Name[kn]=ನೆಪೋಮುಕ್ ಅನ್ನು ಆರಂಭಿಸುವಲ್ Name[ko]=Nepomuk을 시작할 수 없음 Name[lt]=Nepavyko paleisti Nepomuk Name[lv]=Neizdevās palaist Nepomuk -Name[mai]=Nepomuk चालू नही कर पाए Name[mk]=Не успеа стартувањето на Непомук Name[ml]=നെപ്പോമുക്ക് ആരംഭിയ്ക്കുന്നതില് പരാജയപ്പെട്ടു Name[nb]=Klarte ikke å starte Nepomuk @@ -180,6 +183,7 @@ Name[sv]=Misslyckades starta Nepomuk Name[tg]=Оғози Nepomuk қатъ шуд Name[th]=ล้มเหลวในการเริ่มบริการ Nepomuk Name[tr]=Nepomuk başlatılamadı +Name[ug]=Nepomuk نى قوزغىتالمىدى Name[uk]=Не вдалося запустити Nepomuk Name[x-test]=xxFailed to start Nepomukxx Name[zh_CN]=启动 Nepomuk 失败 @@ -187,6 +191,8 @@ Name[zh_TW]=無法啟動 Nepomuk Comment=The Nepomuk Semantic Desktop system could not be started Comment[ar]=لم يمكن بِدؤ نظام نِبوموك لسطح المكتب الدلالي Comment[ast]=Nun pudo entamase'l sistema d'Escritoriu Semánticu Nepomuk +Comment[bg]=Системата за семантичен работен плот Nepomuk не може да се зареди +Comment[bs]=Sistem semantičke površi Nepomuk ne može da se pokrene Comment[ca]=El sistema de l'escriptori semàntic del Nepomuk no s'ha pogut engegar Comment[ca@valencia]=El sistema de l'escriptori semàntic del Nepomuk no s'ha pogut engegar Comment[cs]=Systém pro sémantický desktop Nepomuk nemohl být spuštěn @@ -238,6 +244,7 @@ Comment[sr@latin]=Sistem semantičke površi Nepomuk ne može da se pokrene Comment[sv]=Det semantiska skrivbordssystemet Nepomuk kunde inte startas Comment[th]=ไม่สามารถเริ่มการทำงานของ Nepomuk - ระบบค้นหาของพื้นที่ทำงาน ได้ Comment[tr]=Nepomuk Semantik Masaüstü sistemi başlatılamadı +Comment[ug]=Nepomuk Semantic ئۈستەلئۈستى سىستېمىسىنى قوزغىتالمىدى Comment[uk]=Не вдалося запустити систему семантичної стільниці Nepomuk Comment[x-test]=xxThe Nepomuk Semantic Desktop system could not be startedxx Comment[zh_CN]=无法启动 Nepomuk 语义桌面系统 @@ -250,6 +257,7 @@ Name[ar]=تحويل بيانات نبومك Name[ast]=Convirtiendo datos de Nepomuk Name[be@latin]=Pieraŭtvareńnie źviestak „Nepomuk” Name[bg]=Преобразуване данните на Nepomuk +Name[bs]=Pretvaram Nepomukove podatke Name[ca]=S'estan convertint les dades del Nepomuk Name[ca@valencia]=S'estan convertint les dades del Nepomuk Name[cs]=Probíhá převod dat Nepomuku @@ -313,6 +321,7 @@ Name[te]=నెపోమక్ దత్తాంశమును మార్ప Name[tg]=Служба Nepomuk Data Storage Name[th]=ทำการแปลงข้อมูลของ Nepomuk Name[tr]=Nepomuk verileri dönüştürülüyor +Name[ug]=Nepomuk سانلىق-مەلۇماتىنى ئايلاندۇرۇۋاتىدۇ Name[uk]=Перетворення даних Nepomuk Name[wa]=Dji coviersêye les dinêyes Nepomuk Name[x-test]=xxConverting Nepomuk dataxx @@ -322,6 +331,8 @@ Comment=All Nepomuk data is converted to a new storage backend Comment[ar]=تم تحويل كامل بيانات نبومك إلى منتهى خلفي جديد للتخزين Comment[ast]=Tolos datos de Nepomuk tan convertíos al nuevu motor d'atroxamientu Comment[be@latin]=Usie źviestki „Nepomuk” pieraŭtvoranyja dla novaha schovišča. +Comment[bg]=Всички данни от Nepomuk са преобразувани към новото ядро за съхранение +Comment[bs]=Svi Nepomukovi podaci su pretvoreni za novu skladišnu pozadinu Comment[ca]=S'estan convertint totes les dades del Nepomuk a un nou dorsal d'emmagatzematge Comment[ca@valencia]=S'estan convertint totes les dades del Nepomuk a un nou dorsal d'emmagatzematge Comment[cs]=Všechna data Nepomuku převedena do nového úložného systému @@ -384,6 +395,7 @@ Comment[ta]=All Nepomuk data is converted to a new storage backend Comment[te]=నెపోమక్ యొక్క మొత్తం డాటా కొత్త నిల్వ బ్యాకెండ్కు మార్పడి చేయబడింది Comment[th]=ข้อมูลทั้งหมดของ Nepomuk ได้ถูกแปลงไปยังแบ็คเอนด์ตัวใหม่แล้ว Comment[tr]=Tüm Nepomuk verileri yeni depolama arka ucuna dönüştürüldü +Comment[ug]=ھەممە Nepomuk سانلىق مەلۇمات يېڭى ساقلاش ئارقا ئۇچىغا ئايلاندۇرۇلدى Comment[uk]=Всі дані Nepomuk перетворено для нового сервера зберігання Comment[wa]=Totes les dnêyes di Nepomuk sont coviersêyes dins on novea programe fondmint di stocaedje Comment[x-test]=xxAll Nepomuk data is converted to a new storage backendxx @@ -397,6 +409,7 @@ Name[ar]=فشلت علمية تحويل بيانات نبومك Name[ast]=Conversión de datos Nepomuk fallida Name[be@latin]=Nie ŭdałosia pieraŭtvaryć źviestki „Nepomuk”. Name[bg]=Грешка при преобразуване данните на Nepomuk +Name[bs]=Propalo pretvaranje Nepomukovih podataka Name[ca]=La conversió de dades del Nepomuk ha fallat Name[ca@valencia]=La conversió de dades del Nepomuk ha fallat Name[cs]=Převod dat Nepomuku selhal @@ -460,6 +473,7 @@ Name[te]=నెపోమక్ దత్తాంశమును మార్ప Name[tg]=Nepomuk Data Migration Level 1 Name[th]=การแปลงข้อมูลของ Nepomuk ล้มเหลว Name[tr]=Nepomuk verileri dönüştürülemedi +Name[ug]=Nepomuk سانلىق-مەلۇماتىنى ئايلاندۇرۇش مەغلۇپ بولدى Name[uk]=Спроба перетворення даних Nepomuk завершилася невдало Name[wa]=Li coviersaedje des dinêyes Nepomuk a fwait berwete Name[x-test]=xxConverting Nepomuk data failedxx @@ -469,6 +483,8 @@ Comment=Converting Nepomuk data to a new backend failed Comment[ar]=فشلت علمية تحويل بيانات نبومك إلى منتهى خلفي جديد Comment[ast]=Falló la conversión de datos Nepomuk al nuevu motor Comment[be@latin]=Nie ŭdałosia pieraŭtvaryć źviestki „Nepomuk” dla novaha schovišča. +Comment[bg]=Грешка при преобразуване на данните от Nepomuk към ново ядро за съхранение +Comment[bs]=Pretvaranje Nepomukovih podataka za novu pozadinu nije uspelo Comment[ca]=La conversió de dades del Nepomuk a un dorsal nou ha fallat Comment[ca@valencia]=La conversió de dades del Nepomuk a un dorsal nou ha fallat Comment[cs]=Převod dat Nepomuku do nového úložného systému selhal @@ -531,6 +547,7 @@ Comment[ta]=Converting Nepomuk data to a new backend failed Comment[te]=నెపోమక్ దత్తాంశ క్షేత్రమును కొత్త బ్యాకెండ్కు మార్పిడి చేయుటలో వైఫల్యం Comment[th]=การแปลงข้อมูลของ Nepomuk ไปยังแบ็คเอนด์ตัวใหม่ล้มเหลว Comment[tr]=Nepomuk verileri yeni arka uca dönüştürülemedi +Comment[ug]=Nepomuk سانلىق-مەلۇماتىنى يېڭى ئارقا ئۇچقا ئايلاندۇرۇش مەغلۇپ بولدى Comment[uk]=Спроба перетворення даних Nepomuk для нового сервера зазнала невдачі Comment[wa]=Li coviersaedje des dinêyes Nepomuk viè on novea programe fondmint a fwait berwete Comment[x-test]=xxConverting Nepomuk data to a new backend failedxx @@ -544,6 +561,7 @@ Name[ar]=انتهت عملية تحويل بيانات نبومك Name[ast]=Fecha la conversión de datos Nepomuk Name[be@latin]=Pieraŭtvareńnie źviestak „Nepomuk” skončanaje. Name[bg]=Преобразуването данните на Nepomuk приключи +Name[bs]=Gotovo pretvaranje Nepomukovih podataka Name[ca]=S'ha fet la conversió de dades del Nepomuk Name[ca@valencia]=S'ha fet la conversió de dades del Nepomuk Name[cs]=Data Nepomuku byla převedena @@ -607,6 +625,7 @@ Name[te]=నెపోమక్ దత్తాంశం మార్పిడి Name[tg]=Nepomuk Data Migration Level 1 Name[th]=การแปลงข้อมูลของ Nepomuk เสร็จสิ้นแล้ว Name[tr]=Nepomuk verileri dönüştürüldü +Name[ug]=Nepomuk سانلىق-مەلۇماتىنى ئايلاندۇرۇش تامام Name[uk]=Перетворення даних Nepomuk завершено Name[wa]=Li coviersaedje des dinêyes Nepomukest fwait Name[x-test]=xxConverting Nepomuk data donexx @@ -616,6 +635,8 @@ Comment=Successfully converted Nepomuk data to new backend Comment[ar]=حولت بيانات نبومك بنجاح إلى منتهى خلفي جديد Comment[ast]=Convirtiéronse correutamente los datos Nepomuk al nuevu motor Comment[be@latin]=Pieraŭtvareńnie źviestak „Nepomuk” dla novaj versii paśpiachova skončanaje. +Comment[bg]=Данните от Nepomuk са успешно преобразувани +Comment[bs]=Nepomukovi podaci su uspješno pretvoreni za novu pozadinu Comment[ca]=Les dades del Nepomuk s'han convertit correctament a un dorsal nou Comment[ca@valencia]=Les dades del Nepomuk s'han convertit correctament a un dorsal nou Comment[cs]=Data Nepomuku úspěšně převedena do nového úložného systému @@ -677,6 +698,7 @@ Comment[ta]=Successfully converted Nepomuk data to new backend Comment[te]=నెపోమక్ దత్తాంశం కొత్త బ్యాకెండ్కు సమర్ధవంతంగా మార్పిడి జరిగింది Comment[th]=การแปลงข้อมูลของ Nepomuk ไปยังแบ็คเอนด์ตัวใหม่เสร็จสิ้นแล้ว Comment[tr]=Nepomuk verileri yeni arka uca başarılı bir şekilde dönüştürüldü +Comment[ug]=Nepomuk سانلىق مەلۇماتى مۇۋەپپەقىيەتلىك ھالدا يېڭى ئارقا ئۇچقا ئايلاندۇرۇلدى Comment[uk]=Дані Nepomuk успішно перетворено для нового сервера Comment[wa]=Dj' a coviersé les dinêyes Nepomuk viè on novea programe fondmint comifåt Comment[x-test]=xxSuccessfully converted Nepomuk data to new backendxx diff --git a/nepomuk/services/storage/ontologyloader.cpp b/nepomuk/services/storage/ontologyloader.cpp index 729a031..c06a297 100644 --- a/nepomuk/services/storage/ontologyloader.cpp +++ b/nepomuk/services/storage/ontologyloader.cpp @@ -20,7 +20,6 @@ #include "ontologymanagermodel.h" #include "ontologymanageradaptor.h" #include "graphretriever.h" -#include "kuvo.h" #include <Soprano/Global> #include <Soprano/Node> @@ -75,6 +74,24 @@ void Nepomuk::OntologyLoader::Private::updateOntology( const QString& filename ) // only update if the modification date of the ontology file changed (not the desktop file). // ------------------------------------ QFileInfo ontoFileInf( df.readEntry( QLatin1String("Path") ) ); + +#ifdef Q_OS_WIN + if ( ! ontoFileInf.exists() ) { + // On Windows the Path setting is always the install directory which is not + // something like /usr/share/ontologies but something like + // r:\kderoot\build\shared-desktop-ontologies-mingw-i686\image\shared\ontologies + // so if you did not compile shared-desktop-ontologies yourself the Path + // is useless. + // We expect a default ontologies installation where the .ontology files are placed + // in the same directory as the .trig files. + QString alt_filename = filename; + QFileInfo ontoAlternative( alt_filename.replace( QLatin1String(".ontology"), + QLatin1String(".trig") ) ); + ontoFileInf = ontoAlternative; + kDebug() << "Ontology path: " << filename << " does not exist. Using " + << alt_filename << " instead"; + } +#endif QString ontoNamespace = df.readEntry( QLatin1String("Namespace") ); QDateTime ontoLastModified = model->ontoModificationDate( ontoNamespace ); bool update = false; @@ -83,9 +100,9 @@ void Nepomuk::OntologyLoader::Private::updateOntology( const QString& filename ) kDebug() << "Ontology" << ontoNamespace << "needs updating."; update = true; } - else { - kDebug() << "Ontology" << ontoNamespace << "up to date."; - } + //else { + // kDebug() << "Ontology" << ontoNamespace << "up to date."; + //} if( !update && forceOntologyUpdate ) { kDebug() << "Ontology update forced."; diff --git a/nepomuk/services/storage/ontologyloader.h b/nepomuk/services/storage/ontologyloader.h index 901ad43..4e0ce26 100644 --- a/nepomuk/services/storage/ontologyloader.h +++ b/nepomuk/services/storage/ontologyloader.h @@ -68,7 +68,7 @@ namespace Nepomuk { Q_SIGNALS: /** * Emitted once the update of the ontologies is done. - * This signal is emited whenever the ontologies change + * This signal is emitted whenever the ontologies change * and needed updating. */ void ontologyLoadingFinished( Nepomuk::OntologyLoader* ); diff --git a/nepomuk/services/storage/ontologymanagermodel.cpp b/nepomuk/services/storage/ontologymanagermodel.cpp index 63d020b..3ee1cd0 100644 --- a/nepomuk/services/storage/ontologymanagermodel.cpp +++ b/nepomuk/services/storage/ontologymanagermodel.cpp @@ -387,6 +387,8 @@ bool Nepomuk::OntologyManagerModel::removeOntology( const QUrl& ns ) // now removing the ontology is simple removeContext( dataGraphUri ); removeContext( metadataGraphUri ); + // be sure we remove any junk from buggy versions + removeAllStatements( dataGraphUri, Soprano::Node(), Soprano::Node() ); return true; } else { @@ -404,14 +406,14 @@ QDateTime Nepomuk::OntologyManagerModel::ontoModificationDate( const QUrl& uri ) "?onto <%1> ?ns . " "?onto <%3> ?date . " "FILTER(STR(?ns) = \"%2\") . " - "FILTER(DATATYPE(?date) = <%4>) . }" ) + "FILTER(DATATYPE(?date) = <%4>) . } LIMIT 1" ) .arg( Soprano::Vocabulary::NAO::hasDefaultNamespace().toString() ) .arg( uri.toString() ) .arg( Soprano::Vocabulary::NAO::lastModified().toString() ) .arg( Soprano::Vocabulary::XMLSchema::dateTime().toString() ); QueryResultIterator it = executeQuery( query, Soprano::Query::QueryLanguageSparql ); if ( it.next() ) { - kDebug() << "Found modification date for" << uri << it.binding( "date" ).literal().toDateTime(); + //kDebug() << "Found modification date for" << uri << it.binding( "date" ).literal().toDateTime(); return it.binding( "date" ).literal().toDateTime(); } else { diff --git a/nepomuk/services/storage/removablemediamodel.cpp b/nepomuk/services/storage/removablemediamodel.cpp new file mode 100644 index 0000000..ebd83f1 --- /dev/null +++ b/nepomuk/services/storage/removablemediamodel.cpp @@ -0,0 +1,412 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "removablemediamodel.h" +#include "removablemediacache.h" + +#include <Soprano/Statement> +#include <Soprano/StatementIterator> +#include <Soprano/IteratorBackend> +#include <Soprano/QueryResultIterator> +#include <Soprano/QueryResultIteratorBackend> +#include <Soprano/Vocabulary/NAO> + +#include <Nepomuk/Vocabulary/NIE> +#include <Nepomuk/Vocabulary/NFO> + +#include <KUrl> +#include <KDebug> + +#include <QtCore/QFile> + + +using namespace Nepomuk::Vocabulary; + +// TODO: how do we handle this scenario: the indexer indexes files on a removable medium. This includes +// a nie:isPartOf relation to the parent folder of the mount point. This is technically not correct +// as it should be part of the removable file system instead. Right? + + +/** + * A simple wrapper iterator which converts all filex:/ URLs to local file:/ URLs (if possible). + */ +class Nepomuk::RemovableMediaModel::StatementIteratorBackend : public Soprano::IteratorBackend<Soprano::Statement> +{ +public: + StatementIteratorBackend(const RemovableMediaModel* model, + const Soprano::StatementIterator& it) + : m_it(it), + m_model(model) { + } + + bool next() { + return m_it.next(); + } + + Soprano::Statement current() const { + return m_model->convertFilexUrls(m_it.current()); + } + + void close() { + m_it.close(); + } + +private: + Soprano::StatementIterator m_it; + const RemovableMediaModel* m_model; +}; + + +/** + * A simple wrapper iterator which converts all filex:/ URLs to local file:/ URLs (if possible). + */ +class Nepomuk::RemovableMediaModel::QueryResultIteratorBackend : public Soprano::QueryResultIteratorBackend +{ +public: + QueryResultIteratorBackend(const Nepomuk::RemovableMediaModel* model, const Soprano::QueryResultIterator& it) + : m_it(it), + m_model(model) { + } + + bool next() { + return m_it.next(); + } + + void close() { + m_it.close(); + } + + Soprano::Statement currentStatement() const { + return m_model->convertFilexUrls(m_it.currentStatement()); + } + + Soprano::Node binding( const QString &name ) const { + return m_model->convertFilexUrl(m_it.binding(name)); + } + + Soprano::Node binding( int offset ) const { + return m_model->convertFilexUrl(m_it.binding(offset)); + } + + int bindingCount() const { + return m_it.bindingCount(); + } + + QStringList bindingNames() const { + return m_it.bindingNames(); + } + + bool isGraph() const { + return m_it.isGraph(); + } + + bool isBinding() const { + return m_it.isBinding(); + } + + bool isBool() const { + return m_it.isBool(); + } + + bool boolValue() const { + return m_it.boolValue(); + } + +private: + Soprano::QueryResultIterator m_it; + const RemovableMediaModel* m_model; +}; + + +Nepomuk::RemovableMediaModel::RemovableMediaModel(Soprano::Model* parentModel, QObject* parent) + : Soprano::FilterModel(parentModel) +{ + setParent(parent); + m_removableMediaCache = new RemovableMediaCache(this); +} + +Nepomuk::RemovableMediaModel::~RemovableMediaModel() +{ +} + +Soprano::Error::ErrorCode Nepomuk::RemovableMediaModel::addStatement(const Soprano::Statement &statement) +{ + // + // First we create the filesystem resource. We mostly need this for the user readable label. + // +// Nepomuk::Resource fsRes( entry.m_uuid, Nepomuk::Vocabulary::NFO::Filesystem() ); +// fsRes.setLabel( entry.m_description ); + + // IDEA: What about nie:hasPart nfo:FileSystem for all top-level items instead of the nfo:Folder that is the mount point. + // But then we run into the recursion problem +// Resource fileRes( resource ); +// fileRes.addProperty( Nepomuk::Vocabulary::NIE::isPartOf(), fsRes ); + + return FilterModel::addStatement(convertFileUrls(statement)); +} + +Soprano::Error::ErrorCode Nepomuk::RemovableMediaModel::removeStatement(const Soprano::Statement &statement) +{ + return FilterModel::removeStatement(convertFileUrls(statement)); +} + +Soprano::Error::ErrorCode Nepomuk::RemovableMediaModel::removeAllStatements(const Soprano::Statement &statement) +{ + return FilterModel::removeAllStatements(convertFileUrls(statement)); +} + +bool Nepomuk::RemovableMediaModel::containsStatement(const Soprano::Statement &statement) const +{ + return FilterModel::containsStatement(convertFileUrls(statement)); +} + +bool Nepomuk::RemovableMediaModel::containsAnyStatement(const Soprano::Statement &statement) const +{ + return FilterModel::containsAnyStatement(convertFileUrls(statement)); +} + +Soprano::StatementIterator Nepomuk::RemovableMediaModel::listStatements(const Soprano::Statement &partial) const +{ + return new StatementIteratorBackend(this, FilterModel::listStatements(partial)); +} + +Soprano::QueryResultIterator Nepomuk::RemovableMediaModel::executeQuery(const QString &query, Soprano::Query::QueryLanguage language, const QString &userQueryLanguage) const +{ + return new QueryResultIteratorBackend(this, FilterModel::executeQuery(convertFileUrls(query), language, userQueryLanguage)); +} + +Soprano::Node Nepomuk::RemovableMediaModel::convertFileUrl(const Soprano::Node &node) const +{ + if(node.isResource()) { + const QUrl url = node.uri(); + if(url.scheme() == QLatin1String("file")) { + const QString localFilePath = url.toLocalFile(); + if(const RemovableMediaCache::Entry* entry = m_removableMediaCache->findEntryByFilePath(localFilePath)) { + if(entry->isMounted()) { + return entry->constructRelativeUrl(localFilePath); + } + } + } + } + + return node; +} + + +Soprano::Statement Nepomuk::RemovableMediaModel::convertFileUrls(const Soprano::Statement &statement) const +{ + if(statement.predicate().uri() == NIE::url() || + (statement.predicate().isEmpty() && statement.object().isResource())) { + Soprano::Statement newStatement(statement); + newStatement.setObject(convertFileUrl(statement.object())); + return newStatement; + } + + return statement; +} + + +Soprano::Statement Nepomuk::RemovableMediaModel::convertFilexUrls(const Soprano::Statement &s) const +{ + if(s.predicate().uri() == NIE::url()) { + Soprano::Statement newStatement(s); + newStatement.setObject(convertFilexUrl(s.object())); + return newStatement; + } + else { + return s; + } +} + +Soprano::Node Nepomuk::RemovableMediaModel::convertFilexUrl(const Soprano::Node &node) const +{ + if(node.isResource()) { + const QUrl url = node.uri(); + if(m_removableMediaCache->hasRemovableSchema(url)) { + if(const RemovableMediaCache::Entry* entry = m_removableMediaCache->findEntryByUrl(url)) { + if(entry->isMounted()) { + return QUrl::fromLocalFile(entry->constructLocalPath(url)); + } + } + } + } + // fallback + return node; +} + +QString Nepomuk::RemovableMediaModel::convertFileUrls(const QString &query) const +{ + // + // There are at least two cases to handle: + // 1. Simple file:/ URLs used as resources (Example: "<file:///home/foobar>") + // 2. REGEX filters which contain partial file:/ URLs (Example: "FILTER(REGEX(STR(?u),'^file:///home'))") + // + // In theory there are more candidates like matching part of a URL but these have no relevance in Nepomuk. + // + // We cannot simply match via a regular expression since in theory file URLs could appear in literals + // as well. We do not want to change literals. + // + // Literals are either enclosed in a set of single or double quotes or in two sets of 3 of the same. + // Examples: 'Hello', "Hello", '''Hello''', """Hello""" + // + + // is 0, 1, or 3 - nothing else + int quoteCnt = 0; + bool inRegEx = false; + bool inRes = false; + QChar quote; + QString newQuery; + for(int i = 0; i < query.length(); ++i) { + const QChar c = query[i]; + + // first we update the quoteCnt + if(c == '\'' || c == '"') { + if(quoteCnt == 0) { + // opening a literal + quote = c; + ++quoteCnt; + newQuery.append(c); + // peek forward to see if there are three + if(i+2 < query.length() && + query[i+1] == c && + query[i+2] == c) { + quoteCnt = 3; + i += 2; + newQuery.append(c); + newQuery.append(c); + } + continue; + } + else if(c == quote) { + // possibly closing a literal + if(quoteCnt == 1) { + quoteCnt = 0; + newQuery.append(c); + continue; + } + else { // quoteCnt == 3 + // peek forward to see if we have three closing ones + if(i+2 < query.length() && + query[i+1] == c && + query[i+2] == c) { + quoteCnt = 0; + i += 2; + newQuery.append(c); + newQuery.append(c); + newQuery.append(c); + continue; + } + } + } + } + + // + // If we are not in a quote we can look for a resource + // + if(!quoteCnt && c == '<') { + // peek forward to see if its a file:/ URL + if(i+6 < query.length() && + query[i+1] == 'f' && + query[i+2] == 'i' && + query[i+3] == 'l' && + query[i+4] == 'e' && + query[i+5] == ':' && + query[i+6] == '/') { + // look for the end of the file URL + int pos = query.indexOf('>', i+6); + if(pos > i+6) { + // convert the file URL into a filex URL (if necessary) + const KUrl fileUrl = query.mid(i+1, pos-i-1); + newQuery += convertFileUrl(fileUrl).toN3(); + i = pos; + continue; + } + } + inRes = true; + } + + else if(inRes && c == '>') { + inRes = false; + } + + // + // Or we check for a regex filter + // + else if(!inRes && !inRegEx && !quoteCnt && c.toLower() == 'r') { + // peek forward to see if we have a REGEX filter + if(i+4 < query.length() && + query[i+1].toLower() == 'e' && + query[i+2].toLower() == 'g' && + query[i+3].toLower() == 'e' && + query[i+4].toLower() == 'x') { + inRegEx = true; + } + } + + // + // Find the end of a regex. + // FIXME: this is a bit tricky. There might be additional brackets in a regex. not sure. + // + else if(inRegEx && !quoteCnt && c == ')') { + inRegEx = false; + } + + // + // Check for a file URL in a regex. This means we need to be in quotes + // and in a regex + // This is not perfect as in theory we could have a regex which checks + // some random literal which happens to mention a file URL. However, + // that case seems unlikely enough for us to go this way. + // FIXME: it would be best to check if the filter is done on nie:url. + // + else if(inRegEx && quoteCnt && c == 'f') { + // peek forward to see if its a file URL + if(i+5 < query.length() && + query[i+1] == 'i' && + query[i+2] == 'l' && + query[i+3] == 'e' && + query[i+4] == ':' && + query[i+5] == '/') { + // find end of regex + QString quoteEnd = quote; + if(quoteCnt == 3) { + quoteEnd += quote; + quoteEnd += quote; + } + int pos = query.indexOf(quoteEnd, i+6); + if(pos > 0) { + // convert the file URL into a filex URL (if necessary) + const KUrl fileUrl = query.mid(i, pos-i); + newQuery += KUrl(convertFileUrl(fileUrl).uri()).url(); + // set i to last char we handled and let the loop continue with the end of the quote + i = pos-1; + continue; + } + } + } + + newQuery += c; + } + + return newQuery; +} + +#include "removablemediamodel.moc" diff --git a/nepomuk/services/storage/removablemediamodel.h b/nepomuk/services/storage/removablemediamodel.h new file mode 100644 index 0000000..52e4eda --- /dev/null +++ b/nepomuk/services/storage/removablemediamodel.h @@ -0,0 +1,114 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef REMOVABLEMEDIAMODEL_H +#define REMOVABLEMEDIAMODEL_H + +#include <Soprano/FilterModel> + +#include <QtCore/QVariant> +#include <QtCore/QHash> + +namespace Solid { + class StorageAccess; + class StorageVolume; +} + +class KUrl; + +namespace Nepomuk { +class RemovableMediaCache; + +/** + * Filter model that performs automatic conversion of file URLs + * from and to the filex:/ protocol. + * + * The basic idea is that all resources representing files on removable media + * do have a nie:url with schema filex:/<UUID>/<relative-path> where <UUID> + * is the UUID of the removable media and <relative-path> is the path of the + * file on the medium. This is necessary since different media might be mounted + * at the same mount point which could lead to clashing URLs. + * + * In theory all file URLs could be stored this way. However, to improve performance + * this conversion is only done for files on removable media. + * + * The following conversions are performed to provide a handling + * of files on removable media that is as transparent as possible: + * + * \li file:/ URLs used in all statement commands and queries are converted to + * the corresponding filex:/ URL if necessary. + * \li filex:/ URLs referring to existing files (on a medium currently mounted) + * are converted to file:/ URLs in query and listStatement results. + * \li filex:/ URLs referring to non-existing files (on a medium currently not + * mounted) will not be converted (since it is not possible). + * + * Thus, whenever clients see a filex:/ URL they can be sure that it refers to + * a file that is not accessible at that time and act accordingly (show a message + * box, hide the query results, etc.). + * + * \author Sebastian Trueg <trueg@kde.org> + */ +class RemovableMediaModel : public Soprano::FilterModel +{ + Q_OBJECT + +public: + RemovableMediaModel(Soprano::Model *parentModel = 0, QObject* parent = 0); + ~RemovableMediaModel(); + + // overloaded methods that provide file:/filex: transparent conversion + Soprano::Error::ErrorCode addStatement(const Soprano::Statement &statement); + Soprano::Error::ErrorCode removeStatement(const Soprano::Statement &statement); + Soprano::Error::ErrorCode removeAllStatements(const Soprano::Statement &statement); + bool containsStatement(const Soprano::Statement &statement) const; + bool containsAnyStatement(const Soprano::Statement &statement) const; + Soprano::StatementIterator listStatements(const Soprano::Statement &partial) const; + Soprano::QueryResultIterator executeQuery(const QString &query, Soprano::Query::QueryLanguage language, const QString &userQueryLanguage = QString()) const; + + using FilterModel::addStatement; + using FilterModel::listStatements; + +private: + /** + * Converts file:/ URLs into their filex:/ counterpart if necessary. + * Used in all statement handling methods. + */ + Soprano::Statement convertFileUrls(const Soprano::Statement& s) const; + + Soprano::Node convertFileUrl(const Soprano::Node& node) const; + + /** + * Converts file:/ URLs into their filex:/ counterpart if necessary. + * This includes a simple handling of REGEX filters. + */ + QString convertFileUrls(const QString& query) const; + + Soprano::Statement convertFilexUrls(const Soprano::Statement& s) const; + Soprano::Node convertFilexUrl(const Soprano::Node& node) const; + + RemovableMediaCache* m_removableMediaCache; + + class StatementIteratorBackend; + class QueryResultIteratorBackend; +}; +} + +#endif diff --git a/nepomuk/services/storage/repository.cpp b/nepomuk/services/storage/repository.cpp index 93698e5..5578c0c 100644 --- a/nepomuk/services/storage/repository.cpp +++ b/nepomuk/services/storage/repository.cpp @@ -15,6 +15,11 @@ #include "repository.h" #include "modelcopyjob.h" #include "crappyinferencer2.h" +#include "removablemediamodel.h" +#include "datamanagementmodel.h" +#include "datamanagementadaptor.h" +#include "classandpropertytree.h" +#include "graphmaintainer.h" #include <Soprano/Backend> #include <Soprano/PluginManager> @@ -24,6 +29,8 @@ #include <Soprano/Error/Error> #include <Soprano/Vocabulary/RDF> #include <Soprano/Util/SignalCacheModel> +#define USING_SOPRANO_NRLMODEL_UNSTABLE_API +#include <Soprano/NRLModel> #include <KStandardDirs> #include <KDebug> @@ -38,6 +45,7 @@ #include <QtCore/QFile> #include <QtCore/QThread> #include <QtCore/QCoreApplication> +#include <QtDBus/QDBusConnection> namespace { @@ -53,6 +61,7 @@ Nepomuk::Repository::Repository( const QString& name ) m_state( CLOSED ), m_model( 0 ), m_inferencer( 0 ), + m_removableStorageModel( 0 ), m_backend( 0 ), m_modelCopyJob( 0 ), m_oldStorageBackend( 0 ) @@ -74,6 +83,9 @@ void Nepomuk::Repository::close() delete m_inferencer; m_inferencer = 0; + delete m_removableStorageModel; + m_removableStorageModel = 0; + delete m_modelCopyJob; m_modelCopyJob = 0; @@ -150,7 +162,7 @@ void Nepomuk::Repository::open() // remove old pre 4.4 clucene index // ================================= if ( QFile::exists( m_basePath + QLatin1String( "index" ) ) ) { - KIO::del( m_basePath + QLatin1String( "index" ) ); + KIO::del( QString( m_basePath + QLatin1String( "index" ) ) ); } // open storage @@ -166,16 +178,40 @@ void Nepomuk::Repository::open() kDebug() << "Successfully created new model for repository" << name(); + // Fire up the graph maintainer on the pure data model. + // ================================= + GraphMaintainer* graphMaintainer = new GraphMaintainer(m_model); + connect(graphMaintainer, SIGNAL(finished()), graphMaintainer, SLOT(deleteLater())); + graphMaintainer->start(); + + // create the one class and property tree to be used in the crappy inferencer 2 and in DMS + // ================================= + m_classAndPropertyTree = new Nepomuk::ClassAndPropertyTree(this); + + // create the crappy inference model which handles rdfs:subClassOf only -> we only use this to improve performance of ResourceTypeTerms + // ================================= + m_inferencer = new CrappyInferencer2( m_classAndPropertyTree, m_model ); + + // create the RemovableMediaModel which does the transparent handling of removable mounts + // ================================= + m_removableStorageModel = new Nepomuk::RemovableMediaModel(m_inferencer); + // create a SignalCacheModel to make sure no client slows us down by listening to the stupid signals // ================================= - Soprano::Util::SignalCacheModel* scm = new Soprano::Util::SignalCacheModel( m_model ); + Soprano::Util::SignalCacheModel* scm = new Soprano::Util::SignalCacheModel( m_removableStorageModel ); scm->setParent(this); // memory management - setParentModel( scm ); - // create the crappy inference model which handles rdfs:subClassOf only -> we only use this to improve performance of ResourceTypeTerms + // Create the NRLModel which is required by the DMM below // ================================= - m_inferencer = new CrappyInferencer2( scm ); - setParentModel(m_inferencer); + m_nrlModel = new Soprano::NRLModel(scm); + m_nrlModel->setParent(this); // memory management + + // create the DataManagementModel on top of everything + // ================================= + m_dataManagementModel = new DataManagementModel(m_classAndPropertyTree, m_nrlModel, this); + m_dataManagementAdaptor = new Nepomuk::DataManagementAdaptor(m_dataManagementModel); + QDBusConnection::sessionBus().registerObject(QLatin1String("/datamanagement"), m_dataManagementAdaptor, QDBusConnection::ExportScriptableContents); + setParentModel(m_dataManagementModel); // check if we have to convert // ================================= @@ -356,6 +392,21 @@ Soprano::BackendSettings Nepomuk::Repository::readVirtuosoSettings() const void Nepomuk::Repository::updateInference() { + // the funny way to update the query prefix cache + m_nrlModel->setEnableQueryPrefixExpansion(false); + m_nrlModel->setEnableQueryPrefixExpansion(true); + + // update the prefixes in the DMS adaptor for script convenience + QHash<QString, QString> prefixes; + const QHash<QString, QUrl> namespaces = m_nrlModel->queryPrefixes(); + for(QHash<QString, QUrl>::const_iterator it = namespaces.constBegin(); + it != namespaces.constEnd(); ++it) { + prefixes.insert(it.key(), QString::fromAscii(it.value().toEncoded())); + } + m_dataManagementAdaptor->setPrefixes(prefixes); + + // update the rest + m_classAndPropertyTree->rebuildTree(this); m_inferencer->updateInferenceIndex(); m_inferencer->updateAllResources(); } diff --git a/nepomuk/services/storage/repository.h b/nepomuk/services/storage/repository.h index 2610bd8..e9fb7e5 100644 --- a/nepomuk/services/storage/repository.h +++ b/nepomuk/services/storage/repository.h @@ -19,30 +19,43 @@ #include <QtCore/QMap> #include <Soprano/BackendSettings> -#define USING_SOPRANO_NRLMODEL_UNSTABLE_API -#include <Soprano/NRLModel> +#include <Soprano/FilterModel> namespace Soprano { class Model; class Backend; + class NRLModel; } class KJob; class CrappyInferencer2; namespace Nepomuk { - + class RemovableMediaModel; + class ResourceWatcherModel; class ModelCopyJob; + class DataManagementModel; + class DataManagementAdaptor; + class ClassAndPropertyTree; /** * Represents the main Nepomuk model. While it looks as if there could be more than * one instance of Repository there is only the one. * - * Repository is based on NRLModel only for the query prefix expansion feature. It - * uses a Soprano::Utils::SignalCacheModel to compact the several statementsAdded() - * and statementsRemoved() signals, and uses CrappyInferencer2 to keep rdfs:subClassOf - * and nao:userVisible inference up-to-date. + * Repository uses a whole stack of Soprano models to provide its functionality. The + * following list shows the layering from top to bottom: + * + * \li The DataManagementModel provides the actual data modification interface. For this + * purpose it is exported via DBus. + * \li The Soprano::NRLModel provides query prefix expansion and graph cleanup features + * that are required by the DMM. + * \li The Soprano::Utils::SignalCacheModel is used to compact the several statementsAdded() + * and statementsRemoved() signals. + * \li RemovableMediaModel is used to automatically convert the URLs of files + * on USB keys, network shares, and so on from and into mount-point independant URLs + * like nfs://<HOST>/<HOST-PATH>/local/path.ext. + * \li CrappyInferencer2 keeps rdfs:subClassOf and nao:userVisible inference up-to-date. * * On construction it checks for and optionally performs conversion from an old repository * type (pre-Virtuoso times) and runs CrappyInferencer2::updateAllResources() which is @@ -50,7 +63,7 @@ namespace Nepomuk { * * \author Sebastian Trueg <trueg@kde.org> */ - class Repository : public Soprano::NRLModel + class Repository : public Soprano::FilterModel { Q_OBJECT @@ -92,7 +105,12 @@ namespace Nepomuk { State m_state; Soprano::Model* m_model; + Nepomuk::ClassAndPropertyTree* m_classAndPropertyTree; CrappyInferencer2* m_inferencer; + RemovableMediaModel* m_removableStorageModel; + DataManagementModel* m_dataManagementModel; + Nepomuk::DataManagementAdaptor* m_dataManagementAdaptor; + Soprano::NRLModel* m_nrlModel; const Soprano::Backend* m_backend; // only used during opening diff --git a/nepomuk/services/storage/resourceidentifier.cpp b/nepomuk/services/storage/resourceidentifier.cpp new file mode 100644 index 0000000..3a988e6 --- /dev/null +++ b/nepomuk/services/storage/resourceidentifier.cpp @@ -0,0 +1,154 @@ +/* + <one line to give the library's name and an idea of what it does.> + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "resourceidentifier.h" +#include "syncresource.h" +#include "classandpropertytree.h" + +#include <QtCore/QDateTime> +#include <QtCore/QSet> + +#include <Soprano/Node> +#include <Soprano/Model> +#include <Soprano/QueryResultIterator> +#include <Soprano/Vocabulary/NAO> +#include <Soprano/StatementIterator> +#include <Soprano/NodeIterator> +#include <Soprano/Vocabulary/RDFS> +#include <Soprano/Vocabulary/RDF> +#include <Nepomuk/Vocabulary/NIE> + +#include <KDebug> + +using namespace Soprano::Vocabulary; +using namespace Nepomuk::Vocabulary; + +namespace { + /// used to handle sets and lists of QUrls + template<typename T> QStringList resourcesToN3(const T& urls) { + QStringList n3; + Q_FOREACH(const QUrl& url, urls) { + n3 << Soprano::Node::resourceToN3(url); + } + return n3; + } +} + +Nepomuk::ResourceIdentifier::ResourceIdentifier( Nepomuk::StoreIdentificationMode mode, + Soprano::Model *model) + : Nepomuk::Sync::ResourceIdentifier( model ), + m_mode( mode ) +{ + // Resource Metadata + addOptionalProperty( NAO::created() ); + addOptionalProperty( NAO::lastModified() ); + addOptionalProperty( NAO::creator() ); + addOptionalProperty( NAO::userVisible() ); +} + + +bool Nepomuk::ResourceIdentifier::exists(const KUrl& uri) +{ + QString query = QString::fromLatin1("ask { %1 ?p ?o . } ").arg( Soprano::Node::resourceToN3(uri) ); + return model()->executeQuery( query, Soprano::Query::QueryLanguageSparql ).boolValue(); +} + +KUrl Nepomuk::ResourceIdentifier::duplicateMatch(const KUrl& origUri, + const QSet<KUrl>& matchedUris ) +{ + Q_UNUSED( origUri ); + // + // We return the uri that has the oldest nao:created + // For backwards compatibility we keep in mind that three are resources which do not have nao:created defined. + // + Soprano::QueryResultIterator it + = model()->executeQuery(QString::fromLatin1("select ?r where { ?r %1 ?date . FILTER(?r in (%2)) . } ORDER BY ASC(?date) LIMIT 1") + .arg(Soprano::Node::resourceToN3(NAO::created()), + resourcesToN3(matchedUris).join(QLatin1String(","))), + Soprano::Query::QueryLanguageSparql); + if(it.next()) { + return it[0].uri(); + } + else { + // FIXME: fallback to what? a random one from the set? + return KUrl(); + } +} + +bool Nepomuk::ResourceIdentifier::isIdentifyingProperty(const QUrl& uri) +{ + if( uri == NAO::created() + || uri == NAO::creator() + || uri == NAO::lastModified() + || uri == NAO::userVisible() ) { + return false; + } + else { + return ClassAndPropertyTree::self()->isIdentifyingProperty(uri); + } +} + + +bool Nepomuk::ResourceIdentifier::runIdentification(const KUrl& uri) +{ + if( m_mode == IdentifyNone ) + return false; + + if( m_mode == IdentifyNew ) { + if( exists( uri ) ) { + manualIdentification( uri, uri ); + return true; + } + } + + //kDebug() << "Identifying : " << uri; + // + // Check if a uri with the same name exists + // + if( exists( uri ) ) { + manualIdentification( uri, uri ); + return true; + } + + const Sync::SyncResource & res = simpleResource( uri ); + //kDebug() << res; + + // + // Check if a uri with the same nie:url exists + // + QUrl nieUrl = res.nieUrl(); + if( !nieUrl.isEmpty() ) { + QString query = QString::fromLatin1("select ?r where { ?r %1 %2 . }") + .arg( Soprano::Node::resourceToN3( NIE::url() ), + Soprano::Node::resourceToN3( nieUrl ) ); + Soprano::QueryResultIterator it = model()->executeQuery( query, Soprano::Query::QueryLanguageSparql ); + if( it.next() ) { + const QUrl newUri = it["r"].uri(); + kDebug() << uri << " --> " << newUri; + manualIdentification( uri, newUri ); + return true; + } + + return false; + } + + // Run the normal identification procedure + return Sync::ResourceIdentifier::runIdentification( uri ); +} diff --git a/nepomuk/services/storage/resourceidentifier.h b/nepomuk/services/storage/resourceidentifier.h new file mode 100644 index 0000000..14d073c --- /dev/null +++ b/nepomuk/services/storage/resourceidentifier.h @@ -0,0 +1,51 @@ +/* + <one line to give the library's name and an idea of what it does.> + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef RESOURCEIDENTIFIER_H +#define RESOURCEIDENTIFIER_H + +#include "../backupsync/lib/resourceidentifier.h" +#include "datamanagement.h" + +#include <KUrl> + +namespace Nepomuk { + +class ResourceIdentifier : public Sync::ResourceIdentifier +{ +public: + ResourceIdentifier( Nepomuk::StoreIdentificationMode mode, Soprano::Model *model); + +protected: + virtual KUrl duplicateMatch(const KUrl& uri, const QSet< KUrl >& matchedUris ); + virtual bool runIdentification(const KUrl& uri); + +private: + bool isIdentifyingProperty( const QUrl& uri ); + + /// Returns true if a resource with uri \p uri exists + bool exists( const KUrl& uri ); + + Nepomuk::StoreIdentificationMode m_mode; +}; + +} + +#endif // RESOURCEIDENTIFIER_H diff --git a/nepomuk/services/storage/resourcemerger.cpp b/nepomuk/services/storage/resourcemerger.cpp new file mode 100644 index 0000000..6d287e3 --- /dev/null +++ b/nepomuk/services/storage/resourcemerger.cpp @@ -0,0 +1,962 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "resourcemerger.h" +#include "datamanagementmodel.h" +#include "classandpropertytree.h" +#include "nepomuktools.h" + +#include <Soprano/Vocabulary/NRL> +#include <Soprano/Vocabulary/RDF> +#include <Soprano/Vocabulary/RDFS> +#include <Soprano/Vocabulary/NAO> +#include <Soprano/Vocabulary/XMLSchema> + +#include <Soprano/StatementIterator> +#include <Soprano/QueryResultIterator> +#include <Soprano/FilterModel> +#include <Soprano/NodeIterator> +#include <Soprano/LiteralValue> +#include <Soprano/Node> + +#include <KDebug> +#include <Soprano/Graph> +#include "resourcewatchermanager.h" + +using namespace Soprano::Vocabulary; + + +Nepomuk::ResourceMerger::ResourceMerger(Nepomuk::DataManagementModel* model, const QString& app, + const QHash< QUrl, QVariant >& additionalMetadata, + const StoreResourcesFlags& flags ) +{ + m_app = app; + m_additionalMetadata = additionalMetadata; + m_model = model; + m_flags = flags; + m_rvm = model->resourceWatcherManager(); + + //setModel( m_model ); + + // Resource Metadata + metadataProperties.reserve( 4 ); + metadataProperties.insert( NAO::lastModified() ); + metadataProperties.insert( NAO::userVisible() ); + metadataProperties.insert( NAO::created() ); + metadataProperties.insert( NAO::creator() ); +} + + +Nepomuk::ResourceMerger::~ResourceMerger() +{ +} + +void Nepomuk::ResourceMerger::setMappings(const QHash< KUrl, KUrl >& mappings) +{ + m_mappings = mappings; +} + +QHash< KUrl, KUrl > Nepomuk::ResourceMerger::mappings() const +{ + return m_mappings; +} + +void Nepomuk::ResourceMerger::setAdditionalGraphMetadata(const QHash<QUrl, QVariant>& additionalMetadata) +{ + m_additionalMetadata = additionalMetadata; +} + +QHash< QUrl, QVariant > Nepomuk::ResourceMerger::additionalMetadata() const +{ + return m_additionalMetadata; +} + +Soprano::Statement Nepomuk::ResourceMerger::resolveStatement(const Soprano::Statement& st) +{ + if( !st.isValid() ) { + QString error = QString::fromLatin1("Invalid statement encountered"); + setError( error, Soprano::Error::ErrorInvalidStatement ); + return Soprano::Statement(); + } + + Soprano::Node resolvedSubject = resolveMappedNode( st.subject() ); + if( lastError() ) + return Soprano::Statement(); + + Soprano::Statement newSt( st ); + newSt.setSubject( resolvedSubject ); + + Soprano::Node object = st.object(); + if( ( object.isResource() && object.uri().scheme() == QLatin1String("nepomuk") ) || object.isBlank() ) { + Soprano::Node resolvedObject = resolveMappedNode( object ); + if( lastError() ) + return Soprano::Statement(); + newSt.setObject( resolvedObject ); + } + + return newSt; +} + + +bool Nepomuk::ResourceMerger::push(const Soprano::Statement& st) +{ + ClassAndPropertyTree *tree = ClassAndPropertyTree::self(); + if( tree->maxCardinality( st.predicate().uri() ) == 1 ) { + const bool lazy = ( m_flags & LazyCardinalities ); + const bool overwrite = (m_flags & OverwriteProperties) && + tree->maxCardinality( st.predicate().uri() ) == 1; + + if( lazy || overwrite ) { + // FIXME: This may create some empty graphs + // Store them somewhere and remove them if they are now empty + m_model->removeAllStatements( st.subject(), st.predicate(), Soprano::Node() ); + } + } + + Soprano::Statement statement( st ); + if( statement.context().isEmpty() ) + statement.setContext( m_graph ); + + + return m_model->addStatement( statement ); +} + + +QUrl Nepomuk::ResourceMerger::createGraph() +{ + return m_model->createGraph( m_app, m_additionalMetadata ); +} + +QMultiHash< QUrl, Soprano::Node > Nepomuk::ResourceMerger::getPropertyHashForGraph(const QUrl& graph) const +{ + // trueg: this is more a hack than anything else: exclude the inference types + // a real solution would either ignore supertypes of nrl:Graph in checkGraphMetadata() + // or only check the new metadata for consistency + Soprano::QueryResultIterator it + = m_model->executeQuery(QString::fromLatin1("select ?p ?o where { graph ?g { %1 ?p ?o . } . FILTER(?g!=<urn:crappyinference2:inferredtriples>) . }") + .arg(Soprano::Node::resourceToN3(graph)), + Soprano::Query::QueryLanguageSparql); + //Convert to prop hash + QMultiHash<QUrl, Soprano::Node> propHash; + while(it.next()) { + propHash.insert( it["p"].uri(), it["o"] ); + } + return propHash; +} + + +bool Nepomuk::ResourceMerger::areEqual(const QMultiHash<QUrl, Soprano::Node>& oldPropHash, + const QMultiHash<QUrl, Soprano::Node>& newPropHash) +{ + // + // When checking if two graphs are equal, certain stuff needs to be considered + // + // 1. The nao:created might not be the same + // 2. One graph may contain more rdf:types than the other, but still be the same + // 3. The newPropHash does not contain the nao:maintainedBy statement + + QSet<QUrl> oldTypes; + QSet<QUrl> newTypes; + + QHash< QUrl, Soprano::Node >::const_iterator it = oldPropHash.constBegin(); + for( ; it != oldPropHash.constEnd(); it++ ) { + const QUrl & propUri = it.key(); + if( propUri == NAO::maintainedBy() || propUri == NAO::created() ) + continue; + + if( propUri == RDF::type() ) { + oldTypes << it.value().uri(); + continue; + } + + //kDebug() << " --> " << it.key() << " " << it.value(); + if( !newPropHash.contains( it.key(), it.value() ) ) { + //kDebug() << "False value : " << newPropHash.value( it.key() ); + return false; + } + } + + it = newPropHash.constBegin(); + for( ; it != newPropHash.constEnd(); it++ ) { + const QUrl & propUri = it.key(); + if( propUri == NAO::maintainedBy() || propUri == NAO::created() ) + continue; + + if( propUri == RDF::type() ) { + newTypes << it.value().uri(); + continue; + } + + //kDebug() << " --> " << it.key() << " " << it.value(); + if( !oldPropHash.contains( it.key(), it.value() ) ) { + //kDebug() << "False value : " << oldPropHash.value( it.key() ); + return false; + } + } + + // + // Check the types + // + newTypes << NRL::InstanceBase(); + if( !containsAllTypes( oldTypes, newTypes ) || !containsAllTypes( newTypes, oldTypes ) ) + return false; + + // Check nao:maintainedBy + it = oldPropHash.find( NAO::maintainedBy() ); + if( it == oldPropHash.constEnd() ) + return false; + + if( it.value().uri() != m_model->findApplicationResource(m_app, false) ) + return false; + + return true; +} + +bool Nepomuk::ResourceMerger::containsAllTypes(const QSet< QUrl >& types, const QSet< QUrl >& masterTypes) +{ + ClassAndPropertyTree* tree = m_model->classAndPropertyTree(); + foreach( const QUrl & type, types ) { + if( !masterTypes.contains( type) ) { + QSet<QUrl> superTypes = tree->allParents( type ); + superTypes.intersect(masterTypes); + if(superTypes.isEmpty()) { + return false; + } + } + } + + return true; +} + + +// Graph Merge rules +// 1. If old graph is of type discardable and new is non-discardable +// -> Then update the graph +// 2. Otherwsie +// -> Keep the old graph + +QUrl Nepomuk::ResourceMerger::mergeGraphs(const QUrl& oldGraph) +{ + // + // Check if mergeGraphs has already been called for oldGraph + // + QHash< QUrl, QUrl >::const_iterator fit = m_graphHash.constFind( oldGraph ); + if( fit != m_graphHash.constEnd() ) { + //kDebug() << "Already merged once, just returning"; + return fit.value(); + } + + QMultiHash<QUrl, Soprano::Node> oldPropHash = getPropertyHashForGraph( oldGraph ); + QMultiHash<QUrl, Soprano::Node> newPropHash = m_additionalMetadataHash; + + // Compare the old and new property hash + // If both have the same properties then there is no point in creating a new graph. + // vHanda: This check is very expensive. Is it worth it? + if( areEqual( oldPropHash, newPropHash ) ) { + //kDebug() << "SAME!!"; + // They are the same - Don't do anything + m_graphHash.insert( oldGraph, QUrl() ); + return QUrl(); + } + + QMultiHash<QUrl, Soprano::Node> finalPropHash; + // + // Graph type nrl:DiscardableInstanceBase is a special case. + // Only If both the old and new graph contain nrl:DiscardableInstanceBase then + // will the new graph also be discardable. + // + if( oldPropHash.contains( RDF::type(), NRL::DiscardableInstanceBase() ) && + newPropHash.contains( RDF::type(), NRL::DiscardableInstanceBase() ) ) + finalPropHash.insert( RDF::type(), NRL::DiscardableInstanceBase() ); + + oldPropHash.remove( RDF::type(), NRL::DiscardableInstanceBase() ); + newPropHash.remove( RDF::type(), NRL::DiscardableInstanceBase() ); + + finalPropHash.unite( oldPropHash ); + finalPropHash.unite( newPropHash ); + + // Add app uri + if( m_appUri.isEmpty() ) + m_appUri = m_model->findApplicationResource( m_app ); + if( !finalPropHash.contains( NAO::maintainedBy(), m_appUri ) ) + finalPropHash.insert( NAO::maintainedBy(), m_appUri ); + + //kDebug() << "Creating : " << finalPropHash; + QUrl graph = m_model->createGraph( m_app, finalPropHash ); + + m_graphHash.insert( oldGraph, graph ); + return graph; +} + +QMultiHash< QUrl, Soprano::Node > Nepomuk::ResourceMerger::toNodeHash(const QHash< QUrl, QVariant >& hash) +{ + QMultiHash<QUrl, Soprano::Node> propHash; + ClassAndPropertyTree *tree = ClassAndPropertyTree::self(); + + QHash< QUrl, QVariant >::const_iterator it = hash.constBegin(); + QHash< QUrl, QVariant >::const_iterator constEnd = hash.constEnd(); + for( ; it != constEnd; ++it ) { + Soprano::Node n = tree->variantToNode( it.value(), it.key() ); + if( tree->lastError() ) { + setError( tree->lastError().message() ,tree->lastError().code() ); + return QMultiHash< QUrl, Soprano::Node >(); + } + + propHash.insert( it.key(), n ); + } + + return propHash; +} + +bool Nepomuk::ResourceMerger::checkGraphMetadata(const QMultiHash< QUrl, Soprano::Node >& hash) +{ + ClassAndPropertyTree* tree = m_model->classAndPropertyTree(); + + QList<QUrl> types; + QHash<QUrl, int> propCardinality; + + QHash< QUrl, Soprano::Node >::const_iterator it = hash.constBegin(); + for( ; it != hash.constEnd(); it++ ) { + const QUrl& propUri = it.key(); + if( propUri == RDF::type() ) { + Soprano::Node object = it.value(); + if( !object.isResource() ) { + setError(QString::fromLatin1("rdf:type has resource range. '%1' does not have a resource type.").arg(object.toN3()), Soprano::Error::ErrorInvalidArgument); + return false; + } + + // All the types should be a sub-type of nrl:Graph + // FIXME: there could be multiple types in the old graph from inferencing. all superclasses of nrl:Graph. However, it would still be valid. + if( !tree->isChildOf( object.uri(), NRL::Graph() ) ) { + setError( QString::fromLatin1("Any rdf:type specified in the additional metadata should be a subclass of nrl:Graph. '%1' is not.").arg(object.uri().toString()), + Soprano::Error::ErrorInvalidArgument ); + return false; + } + types << object.uri(); + } + + // Save the cardinality of each property + QHash< QUrl, int >::iterator propIter = propCardinality.find( propUri ); + if( propIter == propCardinality.end() ) { + propCardinality.insert( propUri, 1 ); + } + else { + propIter.value()++; + } + } + + it = hash.constBegin(); + for( ; it != hash.constEnd(); it++ ) { + const QUrl & propUri = it.key(); + // Check the cardinality + int maxCardinality = tree->maxCardinality( propUri ); + int curCardinality = propCardinality.value( propUri ); + + if( maxCardinality != 0 ) { + if( curCardinality > maxCardinality ) { + setError( QString::fromLatin1("%1 has a max cardinality of %2").arg(propUri.toString()).arg(maxCardinality), Soprano::Error::ErrorInvalidArgument ); + return false; + } + } + + // + // Check the domain and range + const QUrl domain = tree->propertyDomain( propUri ); + const QUrl range = tree->propertyRange( propUri ); + + // domain + if( !domain.isEmpty() && !tree->isChildOf( types, domain ) ) { + setError( QString::fromLatin1("%1 has a rdfs:domain of %2").arg( propUri.toString(), domain.toString() ), Soprano::Error::ErrorInvalidArgument); + return false; + } + + // range + if( !range.isEmpty() ) { + const Soprano::Node& object = it.value(); + if( object.isResource() ) { + if( !isOfType( object.uri(), range ) ) { + setError( QString::fromLatin1("%1 has a rdfs:range of %2").arg( propUri.toString(), range.toString() ), Soprano::Error::ErrorInvalidArgument); + return false; + } + } + else if( object.isLiteral() ) { + const Soprano::LiteralValue lv = object.literal(); + if( lv.dataTypeUri() != range ) { + setError( QString::fromLatin1("%1 has a rdfs:range of %2").arg( propUri.toString(), range.toString() ), Soprano::Error::ErrorInvalidArgument); + return false; + } + } + } // range + } + + //kDebug() << hash; + return true; +} + +QUrl Nepomuk::ResourceMerger::createResourceUri() +{ + return m_model->createUri( DataManagementModel::ResourceUri ); +} + +QUrl Nepomuk::ResourceMerger::createGraphUri() +{ + return m_model->createUri( DataManagementModel::GraphUri ); +} + +QList< QUrl > Nepomuk::ResourceMerger::existingTypes(const QUrl& uri) const +{ + QList<QUrl> types; + QList<Soprano::Node> existingTypes = m_model->listStatements( uri, RDF::type(), Soprano::Node() ) + .iterateObjects().allNodes(); + foreach( const Soprano::Node & n, existingTypes ) { + types << n.uri(); + } + // all resources have rdfs:Resource type by default + types << RDFS::Resource(); + + return types; +} + +bool Nepomuk::ResourceMerger::isOfType(const Soprano::Node & node, const QUrl& type, const QList<QUrl> & newTypes) const +{ + //kDebug() << "Checking " << node << " for type " << type; + ClassAndPropertyTree * tree = m_model->classAndPropertyTree(); + + QList<QUrl> types( newTypes ); + if( !node.isBlank() ) { + types << existingTypes( node.uri() ); + } + types += newTypes; + + if( types.isEmpty() ) { + kDebug() << node << " does not have a type!!"; + return false; + } + + foreach( const QUrl & uri, types ) { + if( uri == type || tree->isChildOf( uri, type ) ) { + return true; + } + } + + return false; +} + +Soprano::Node Nepomuk::ResourceMerger::resolveMappedNode(const Soprano::Node& node) +{ + // Find in mappings + const QUrl uri = node.isBlank() ? node.toN3() : node.uri(); + QHash< KUrl, KUrl >::const_iterator it = m_mappings.constFind( uri ); + if( it != m_mappings.constEnd() ) { + return it.value(); + } + + // Do not resolve the blank nodes which need to be created + if( node.isBlank() ) + return node; + + if( uri.scheme() == QLatin1String("nepomuk") ) { + QString error = QString::fromLatin1("Could not resolve %1. " + "You cannot create nepomuk uris using this method") + .arg( Soprano::Node::resourceToN3( uri ) ); + setError( error, Soprano::Error::ErrorInvalidArgument ); + return Soprano::Node(); + } + + return node; +} + +Soprano::Node Nepomuk::ResourceMerger::resolveUnmappedNode(const Soprano::Node& node) +{ + if( !node.isBlank() ) + return node; + + QHash< KUrl, KUrl >::const_iterator it = m_mappings.constFind( QUrl(node.toN3()) ); + if( it != m_mappings.constEnd() ) { + return it.value(); + } + + QUrl newUri = createResourceUri(); + m_mappings.insert( QUrl(node.toN3()), newUri ); + + Soprano::Node dateTime( Soprano::LiteralValue( QDateTime::currentDateTime() ) ); + m_model->addStatement( newUri, NAO::created(), dateTime, m_graph ); + m_model->addStatement( newUri, NAO::lastModified(), dateTime, m_graph ); + + return newUri; +} + +void Nepomuk::ResourceMerger::resolveBlankNodesInList(QList<Soprano::Statement> *stList) +{ + QMutableListIterator<Soprano::Statement> iter( *stList ); + while( iter.hasNext() ) { + Soprano::Statement &st = iter.next(); + + st.setSubject( resolveUnmappedNode(st.subject()) ); + st.setObject( resolveUnmappedNode(st.object()) ); + } +} + +void Nepomuk::ResourceMerger::removeDuplicatesInList(QList<Soprano::Statement> *stList) +{ + QMutableListIterator<Soprano::Statement> it( *stList ); + while( it.hasNext() ) { + const Soprano::Statement &st = it.next(); + if( st.subject().isBlank() || st.object().isBlank() ) + continue; + + const QString query = QString::fromLatin1("select ?g where { graph ?g { %1 %2 %3 . } . } LIMIT 1") + .arg(st.subject().toN3(), + st.predicate().toN3(), + st.object().toN3()); + + Soprano::QueryResultIterator qit = m_model->executeQuery( query, Soprano::Query::QueryLanguageSparql); + if(qit.next()) { + const QUrl oldGraph = qit[0].uri(); + qit.close(); + + m_duplicateStatements.insert( oldGraph, st ); + it.remove(); + } + } +} + +namespace { + QUrl getBlankOrResourceUri( const Soprano::Node & n ) { + if( n.isResource() ) { + return n.uri(); + } + else if( n.isBlank() ) { + return QString( QLatin1String("_:") + n.identifier() ); + } + return QUrl(); + } + + QUrl xsdDuration() { + return QUrl( Soprano::Vocabulary::XMLSchema::xsdNamespace().toString() + QLatin1String("duration") ); + } + + QStringList nodesToN3( const QList<Soprano::Node> &nodes ) { + QStringList list; + foreach( const Soprano::Node& node, nodes ) { + list << node.toN3(); + } + return list; + } +} + +/* + Rough algorithm - + + - - Validity checks -- + 1. Check the graph's additional metadata for validity + 2. Resolve all the identified uris - Do not create new nodes + 3. Check statement validity - + a. Max cardinality checks + - Take OverwriteProperties and LazyCardinalities into account + b. Domain/Range checks + + -- Graph Handling -- + 4. Get all the statements which already exist in the model, but in a different graph. + 5. Create new graphs which are the result of the merging the present graph and the new one + a.) In case the old and new graph are the same then forget about those statements + 6. Create the main graph + 7. Iterate through all the remaining statements and make a list of the resources that + we will be modifying + + -- Actual Statement pushing -- + 8. Create new resources for all the unidentified blank uris + - Notify the RWM + 9. Push all the statements + a.) Push <nao:lastModified, currentDateTime()> for all the resources that will be modified + b.) Push all the type statements + - Inform the RWM about these new types + c.) Push other statements + - Inform the RWM + - Remove existing statements if OverwriteProperties or LazyCardinalities + d.) Update nao:lastModified for all modified resources + e.) Push extra metadata statements, so that the specified nao:lastModified, nao:created + are taken into considerations + 10. You're done! + + */ +bool Nepomuk::ResourceMerger::merge( const Soprano::Graph& stGraph ) +{ + // + // Check if the additional metadata is valid + // + if( !additionalMetadata().isEmpty() ) { + QMultiHash<QUrl, Soprano::Node> additionalMetadata = toNodeHash(m_additionalMetadata); + if( lastError() ) + return false; + + if( !checkGraphMetadata( additionalMetadata ) ) { + return false; + } + } + + // + // Resolve all the mapped statements + // + // FIXME: Use toSet() once 4.7 has released. toSet() is faster, but it requires a newer version + // of Soprano + QList<Soprano::Statement> statements = stGraph.toList(); + QMutableListIterator<Soprano::Statement> sit( statements ); + while( sit.hasNext() ) { + Soprano::Statement &st = sit.next(); + st = resolveStatement( st ); + if( lastError() ) + return false; + } + + // + // Check the statement metadata + // + + /// Maps a resource to all its types + QMultiHash<QUrl, QUrl> types; + + /// Maps <sub,pred> pair to all its values + QMultiHash<QPair<QUrl,QUrl>, Soprano::Node> cardinality; + + ClassAndPropertyTree * tree = m_model->classAndPropertyTree(); + // + // First separate all the statements predicate rdf:type. + // and collect info required to check the types and cardinality + // + QList<Soprano::Statement> remainingStatements; + QList<Soprano::Statement> typeStatements; + QList<Soprano::Statement> metadataStatements; + + foreach( const Soprano::Statement & st, statements ) { + const QUrl subUri = getBlankOrResourceUri( st.subject() ); + const QUrl objUri = getBlankOrResourceUri( st.object() ); + + const QUrl prop = st.predicate().uri(); + if( prop == RDF::type() ) { + typeStatements << st; + types.insert( subUri, objUri ); + continue; + } + // we ignore the metadata properties as they will get special + // treatment duing the merging + else if( metadataProperties.contains( prop ) ) { + metadataStatements << st; + continue; + } + else { + remainingStatements << st; + } + + // Get the cardinality + if( tree->maxCardinality( prop ) > 0 ) { + QPair<QUrl,QUrl> subPredPair( subUri, st.predicate().uri() ); + cardinality.insert( subPredPair, st.object() ); + } + } + + + // + // Check the cardinality + // + QMultiHash<QPair<QUrl,QUrl>, Soprano::Node>::const_iterator cIter = cardinality.constBegin(); + QMultiHash<QPair<QUrl,QUrl>, Soprano::Node>::const_iterator cIterEnd = cardinality.constEnd(); + for( ; cIter != cIterEnd; ) { + const QPair<QUrl,QUrl> subPredPair = cIter.key(); + QList<Soprano::Node> objectValues; + for( ; cIter != cIterEnd && cIter.key() == subPredPair ; cIter++ ) { + objectValues << cIter.value(); + } + + const QUrl subUri = subPredPair.first; + const QUrl propUri = subPredPair.second; + + int maxCardinality = tree->maxCardinality( propUri ); + + if( maxCardinality > 0 ) { + + QStringList filterStringList; + QStringList objectN3 = nodesToN3( objectValues ); + foreach( const QString &n3, objectN3 ) + filterStringList << QString::fromLatin1("?v!=%1").arg( n3 ); + + const QString query = QString::fromLatin1("select count(distinct ?v) where {" + " %1 %2 ?v ." + "FILTER( %3 ) . }") + .arg( Soprano::Node::resourceToN3( subUri ), + Soprano::Node::resourceToN3( propUri ), + filterStringList.join( QLatin1String(" && ") ) ); + + int existingCardinality = m_model->executeQuery( query, + Soprano::Query::QueryLanguageSparql ) + .iterateBindings(0) + .allNodes().first().literal().toInt(); + + const int newCardinality = objectValues.size() + existingCardinality; + + // TODO: This can be made faster by not calculating all these values when flags are set + if( newCardinality > maxCardinality ) { + // Special handling for max Cardinality == 1 + if( maxCardinality == 1 ) { + // If the difference is 1, then that is okay, as the OverwriteProperties flag + // has been set + if( (m_flags & OverwriteProperties) && (newCardinality-maxCardinality) == 1 ) { + continue; + } + } + + // The LazyCardinalities flag has been set, we don't care about cardinalities any more + if( (m_flags & LazyCardinalities) ) { + continue; + } + + // TODO: Maybe list the existing values as well? + QString error = QString::fromLatin1("%1 has a max cardinality of %2. Provided " + "%3 values - %4") + .arg( propUri.toString(), + QString::number(maxCardinality), + QString::number(objectN3.size()), + objectN3.join(QLatin1String(", ")) ); + setError( error, Soprano::Error::ErrorInvalidStatement ); + return false; + } + } + } + + foreach( const Soprano::Statement & st, remainingStatements ) { + const QUrl subUri = getBlankOrResourceUri( st.subject() ); + const QUrl &propUri = st.predicate().uri(); + + // + // Check for rdfs:domain and rdfs:range + // + + QUrl domain = tree->propertyDomain( propUri ); + QUrl range = tree->propertyRange( propUri ); + +// kDebug() << "Domain : " << domain; +// kDebug() << "Range : " << range; + + QList<QUrl> subjectNewTypes = types.values( subUri ); + + // domain + if( !domain.isEmpty() && !isOfType( subUri, domain, subjectNewTypes ) ) { + // Error + QList<QUrl> allTypes = ( subjectNewTypes + existingTypes(subUri) ); + + QString error = QString::fromLatin1("%1 has a rdfs:domain of %2. " + "%3 only has the following types %4" ) + .arg( Soprano::Node::resourceToN3( propUri ), + Soprano::Node::resourceToN3( domain ), + Soprano::Node::resourceToN3( subUri ), + Nepomuk::resourcesToN3( allTypes ).join(", ") ); + setError( error, Soprano::Error::ErrorInvalidArgument); + return false; + } + + // range + if( !range.isEmpty() ) { + if( st.object().isResource() || st.object().isBlank() ) { + const QUrl objUri = getBlankOrResourceUri( st.object() ); + QList<QUrl> objectNewTypes= types.values( objUri ); + + if( !isOfType( objUri, range, objectNewTypes ) ) { + // Error + QList<QUrl> allTypes = ( objectNewTypes + existingTypes(objUri) ); + + QString error = QString::fromLatin1("%1 has a rdfs:range of %2. " + "%3 only has the following types %4" ) + .arg( Soprano::Node::resourceToN3( propUri ), + Soprano::Node::resourceToN3( range ), + Soprano::Node::resourceToN3( objUri ), + resourcesToN3( allTypes ).join(", ") ); + setError( error, Soprano::Error::ErrorInvalidArgument ); + return false; + } + } + else if( st.object().isLiteral() ) { + const Soprano::LiteralValue lv = st.object().literal(); + // Special handling for xsd:duration + if( range == xsdDuration() && lv.isUnsignedInt() ) { + continue; + } + if( (!lv.isPlain() && lv.dataTypeUri() != range) || + (lv.isPlain() && range != RDFS::Literal()) ) { + // Error + QString error = QString::fromLatin1("%1 has a rdfs:range of %2. " + "Provided %3") + .arg( Soprano::Node::resourceToN3( propUri ), + Soprano::Node::resourceToN3( range ), + Soprano::Node::literalToN3(lv) ); + setError( error, Soprano::Error::ErrorInvalidArgument); + return false; + } + } + } // range + + } // foreach + + // The graph is error free. + + //Merge its statements except for the resource metadata statements + //QList<Soprano::Statement> mergeStatements = remainingStatements + typeStatements; + + // + // Graph Handling + // + removeDuplicatesInList( &remainingStatements ); + + // If the resource exists then all the type statements provided must match + // Therefore after this typeStatements, will only contain the types for the new types + removeDuplicatesInList( &typeStatements ); + + + // + // Create all the graphs + // + QMutableHashIterator<QUrl, Soprano::Statement> hit( m_duplicateStatements ); + while( hit.hasNext() ) { + hit.next(); + const QUrl& oldGraph = hit.key(); + + const QUrl newGraph = mergeGraphs( oldGraph ); + + // The newGraph is invalid when the oldGraph and the newGraph are the same + // In that case those statements can just be ignored. + if( !newGraph.isValid() ) { + hit.remove(); + } + } + + // Create the main graph, if they are any statements to merge + if( !remainingStatements.isEmpty() || !typeStatements.isEmpty() ) { + m_graph = createGraph(); + } + + // Count all the modified resources + QSet<QUrl> modifiedResources; + foreach( const Soprano::Statement & st, remainingStatements ) { + if( !st.subject().isBlank() ) + modifiedResources.insert( st.subject().uri() ); + //FIXME: Inform RWM about typeAdded() + } + + // + // Actual statement pushing + // + + // Count all the blank nodes + /// Maps a blank node with all its types + QMultiHash<QUrl, QUrl> typeHash; + foreach( const Soprano::Statement& st, typeStatements ) { + if( st.subject().isBlank() ) + typeHash.insert( st.subject().toN3(), st.object().uri() ); + } + + // Create all the blank nodes + resolveBlankNodesInList( &typeStatements ); + resolveBlankNodesInList( &remainingStatements ); + resolveBlankNodesInList( &metadataStatements ); + + // Push all these statements and get the list of all the modified resource + foreach( Soprano::Statement st, typeStatements ) { + st.setContext( m_graph ); + m_model->addStatement( st ); + } + + foreach( const Soprano::Statement &st, remainingStatements ) { + push( st ); + m_rvm->addStatement( st ); + } + + // Inform the ResourceWatcherManager of these new types + QHash<QUrl, QUrl>::const_iterator typeIt = typeHash.constBegin(); + QHash<QUrl, QUrl>::const_iterator typeItEnd = typeHash.constEnd(); + for( ; typeIt != typeItEnd; ) { + const QUrl blankUri = typeIt.key(); + QList<QUrl> types; + for( ; typeIt != typeItEnd && typeIt.key() == blankUri ; typeIt++) + types << typeIt.value(); + + // Get its resource uri + const QUrl resUri = m_mappings.value( blankUri ); + m_rvm->createResource( resUri, types ); + } + + // Push all the duplicateStatements + QHashIterator<QUrl, Soprano::Statement> hashIter( m_duplicateStatements ); + while( hashIter.hasNext() ) { + hashIter.next(); + Soprano::Statement st = hashIter.value(); + + m_model->removeAllStatements( st.subject(), st.predicate(), st.object(), hashIter.key() ); + const QUrl newGraph( m_graphHash[hashIter.key()] ); + st.setContext( newGraph ); + + // No need to inform the RVM, we're just changing the graph. + m_model->addStatement( st ); + } + + // + // Handle Resource metadata + // + + // TODO: Maybe inform the RVM about these metadata changes? + // First update the mtime of all the modified resources + Soprano::Node currentDateTime = Soprano::LiteralValue( QDateTime::currentDateTime() ); + foreach( const QUrl & resUri, modifiedResources ) { + m_model->removeAllStatements( resUri, NAO::lastModified(), Soprano::Node() ); + m_model->addStatement( resUri, NAO::lastModified(), currentDateTime, m_graph ); + } + + // then push the individual metadata statements + foreach( Soprano::Statement st, metadataStatements ) { + addResMetadataStatement( st ); + } + + return true; +} + + +Soprano::Error::ErrorCode Nepomuk::ResourceMerger::addResMetadataStatement(const Soprano::Statement& st) +{ + const QUrl & predicate = st.predicate().uri(); + + // Special handling for nao:lastModified and nao:userVisible: only the latest value is correct + if( predicate == NAO::lastModified() || + predicate == NAO::userVisible() ) { + m_model->removeAllStatements( st.subject(), st.predicate(), Soprano::Node() ); + } + + // Special handling for nao:created: only the first value is correct + else if( predicate == NAO::created() ) { + // If nao:created already exists, then do nothing + // FIXME: only write nao:created if we actually create the resource or if it was provided by the client, otherwise drop it. + if( m_model->containsAnyStatement( st.subject(), NAO::created(), Soprano::Node() ) ) + return Soprano::Error::ErrorNone; + } + + // Special handling for nao:creator + else if( predicate == NAO::creator() ) { + // FIXME: handle nao:creator somehow + } + + return m_model->addStatement( st ); +} diff --git a/nepomuk/services/storage/resourcemerger.h b/nepomuk/services/storage/resourcemerger.h new file mode 100644 index 0000000..be369b3 --- /dev/null +++ b/nepomuk/services/storage/resourcemerger.h @@ -0,0 +1,140 @@ +/* + <one line to give the library's name and an idea of what it does.> + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef NEPOMUK_DATAMANAGEMENT_RESOURCEMERGER_H +#define NEPOMUK_DATAMANAGEMENT_RESOURCEMERGER_H + +#include <QtCore/QHash> +#include <QtCore/QVariant> +#include <QtCore/QUrl> +#include <QtCore/QSet> + +#include <KUrl> +#include <Soprano/Error/ErrorCache> + +#include "datamanagement.h" + +namespace Soprano { + class Node; + class Statement; + class Graph; +} + +namespace Nepomuk { + class DataManagementModel; + class ResourceWatcherManager; + + class ResourceMerger : public Soprano::Error::ErrorCache + { + public: + ResourceMerger( Nepomuk::DataManagementModel * model, const QString & app, + const QHash<QUrl, QVariant>& additionalMetadata, + const StoreResourcesFlags& flags ); + virtual ~ResourceMerger(); + + void setMappings( const QHash<KUrl, KUrl> & mappings ); + QHash<KUrl, KUrl> mappings() const; + + bool merge(const Soprano::Graph& graph); + + void setAdditionalGraphMetadata( const QHash<QUrl, QVariant>& additionalMetadata ); + QHash<QUrl, QVariant> additionalMetadata() const; + + private: + virtual QUrl createGraph(); + virtual QUrl createResourceUri(); + virtual QUrl createGraphUri(); + + virtual Soprano::Error::ErrorCode addResMetadataStatement( const Soprano::Statement & st ); + + bool push( const Soprano::Statement & st ); + + // + // Resolution + // + Soprano::Statement resolveStatement( const Soprano::Statement& st ); + Soprano::Node resolveMappedNode( const Soprano::Node& node ); + Soprano::Node resolveUnmappedNode( const Soprano::Node& node ); + + /// This modifies the list + void resolveBlankNodesInList( QList<Soprano::Statement> *stList ); + + /** + * Removes all the statements that already exist in the model + * and adds them to m_duplicateStatements + */ + void removeDuplicatesInList( QList<Soprano::Statement> *stList ); + QMultiHash<QUrl, Soprano::Statement> m_duplicateStatements; + + QHash<KUrl, KUrl> m_mappings; + + /// Can set the error + QMultiHash<QUrl, Soprano::Node> toNodeHash( const QHash<QUrl, QVariant> &hash ); + + /** + * Each statement that is being merged and already exists, belongs to a graph. This hash + * maps that oldGraph -> newGraph. + * The newGraph is generated by mergeGraphs, and contains the metdata from the oldGraph + * and the additionalMetadata provided. + * + * \sa mergeGraphs + */ + QHash<QUrl, QUrl> m_graphHash; + QHash<QUrl, Soprano::Node> m_additionalMetadataHash; + QHash<QUrl, QVariant> m_additionalMetadata; + + QString m_app; + QUrl m_appUri; + QUrl m_graph; + + StoreResourcesFlags m_flags; + Nepomuk::DataManagementModel * m_model; + + QUrl mergeGraphs( const QUrl& oldGraph ); + + QList<QUrl> existingTypes( const QUrl& uri ) const; + + /** + * Checks if \p node is of rdf:type \p type. + * + * \param newTypes contains additional types that should be considered as belonging to \p node + */ + bool isOfType( const Soprano::Node& node, const QUrl& type, const QList<QUrl>& newTypes = QList<QUrl>() ) const; + + QMultiHash<QUrl, Soprano::Node> getPropertyHashForGraph( const QUrl & graph ) const; + + bool checkGraphMetadata( const QMultiHash<QUrl, Soprano::Node> & hash ); + bool areEqual( const QMultiHash<QUrl, Soprano::Node>& oldPropHash, + const QMultiHash<QUrl, Soprano::Node>& newPropHash ); + + /** + * Returns true if all the types in \p types are present in \p masterTypes + */ + bool containsAllTypes( const QSet<QUrl>& types, const QSet<QUrl>& masterTypes ); + + /// Refers to the properties which are considered as resource metadata + QSet<QUrl> metadataProperties; + + ResourceWatcherManager *m_rvm; + }; + +} + +#endif // NEPOMUK_DATAMANAGEMENT_RESOURCEMERGER_H diff --git a/nepomuk/services/storage/resourcewatcherconnection.cpp b/nepomuk/services/storage/resourcewatcherconnection.cpp new file mode 100644 index 0000000..4378c96 --- /dev/null +++ b/nepomuk/services/storage/resourcewatcherconnection.cpp @@ -0,0 +1,70 @@ +/* + Copyright (C) 2010-11 Vishesh handa <handa.vish@gmail.com> + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This program 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 program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include "resourcewatcherconnection.h" +#include "resourcewatcherconnectionadaptor.h" +#include "resourcewatchermanager.h" + +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusObjectPath> +#include <QtDBus/QDBusServiceWatcher> + +Nepomuk::ResourceWatcherConnection::ResourceWatcherConnection( ResourceWatcherManager* parent, bool hasProperties ) + : QObject( parent ), + m_hasProperties( hasProperties ), + m_manager(parent) +{ +} + +Nepomuk::ResourceWatcherConnection::~ResourceWatcherConnection() +{ + m_manager->removeConnection(this); +} + +bool Nepomuk::ResourceWatcherConnection::hasProperties() const +{ + return m_hasProperties; +} + +QDBusObjectPath Nepomuk::ResourceWatcherConnection::registerDBusObject( const QString& dbusClient, int id ) +{ + // build the dbus object path from the id and register the connection as a Query dbus object + new ResourceWatcherConnectionAdaptor( this ); + const QString dbusObjectPath = QString::fromLatin1( "/resourcewatcher/watch%1" ).arg( id ); + QDBusConnection::sessionBus().registerObject( dbusObjectPath, this ); + + // watch the dbus client for unregistration for auto-cleanup + m_serviceWatcher = new QDBusServiceWatcher( dbusClient, + QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForUnregistration, + this ); + connect( m_serviceWatcher, SIGNAL(serviceUnregistered(QString)), + this, SLOT(close()) ); + + // finally return the dbus object path this connection can be found on + return QDBusObjectPath( dbusObjectPath ); +} + +void Nepomuk::ResourceWatcherConnection::close() +{ + deleteLater(); +} + +#include "resourcewatcherconnection.moc" diff --git a/nepomuk/services/storage/resourcewatcherconnection.h b/nepomuk/services/storage/resourcewatcherconnection.h new file mode 100644 index 0000000..6100fb4 --- /dev/null +++ b/nepomuk/services/storage/resourcewatcherconnection.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This program 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 program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#ifndef RESOURCEWATCHERCONNECTION_H +#define RESOURCEWATCHERCONNECTION_H + +#include <QtCore/QObject> +#include <QtCore/QUrl> +#include <QtDBus/QDBusObjectPath> + +class QDBusServiceWatcher; + +namespace Nepomuk { + + class ResourceWatcherManager; + + class ResourceWatcherConnection : public QObject + { + Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.nepomuk.ResourceWatcherConnection" ) + + public: + ResourceWatcherConnection( ResourceWatcherManager* parent, bool hasProperties ); + ~ResourceWatcherConnection(); + + signals: + Q_SCRIPTABLE void resourceCreated( const QString & uri, const QStringList& types ); + Q_SCRIPTABLE void resourceRemoved( const QString & uri, const QStringList& types ); + Q_SCRIPTABLE void resourceTypeAdded( const QString & resUri, const QString & type ); + Q_SCRIPTABLE void resourceTypeRemoved( const QString & resUri, const QString & type ); + Q_SCRIPTABLE void propertyAdded( const QString & resource, + const QString & property, + const QDBusVariant & value ); + Q_SCRIPTABLE void propertyRemoved( const QString & resource, + const QString & property, + const QDBusVariant & value ); + + public Q_SLOTS: + Q_SCRIPTABLE void close(); + + public: + bool hasProperties() const; + + QDBusObjectPath registerDBusObject(const QString &dbusClient, int id); + + private: + QString m_objectPath; + bool m_hasProperties; + + ResourceWatcherManager* m_manager; + QDBusServiceWatcher* m_serviceWatcher; + + friend class ResourceWatcherManager; + }; + +} + +#endif // RESOURCEWATCHERCONNECTION_H diff --git a/nepomuk/services/storage/resourcewatchermanager.cpp b/nepomuk/services/storage/resourcewatchermanager.cpp new file mode 100644 index 0000000..6276254 --- /dev/null +++ b/nepomuk/services/storage/resourcewatchermanager.cpp @@ -0,0 +1,254 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This program 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 program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include "resourcewatchermanager.h" +#include "resourcewatcherconnection.h" + +#include <Soprano/Statement> +#include <Soprano/Vocabulary/RDF> + +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusMessage> + +#include <KUrl> +#include <KDebug> + +#include <QtCore/QStringList> +#include <QtCore/QSet> + + +using namespace Soprano::Vocabulary; + +namespace { +QDBusVariant nodeToVariant(const Soprano::Node& node) { + if(node.isResource()) { + return QDBusVariant(node.uri()); + } + else { + return QDBusVariant(node.literal().variant()); + } +} + +QStringList convertUris(const QList<QUrl>& uris) { + QStringList sl; + foreach(const QUrl& uri, uris) + sl << KUrl(uri).url(); + return sl; +} + +QList<QUrl> convertUris(const QStringList& uris) { + QList<QUrl> sl; + foreach(const QString& uri, uris) + sl << KUrl(uri); + return sl; +} +} + +Nepomuk::ResourceWatcherManager::ResourceWatcherManager(QObject* parent) + : QObject(parent), + m_connectionCount(0) +{ + QDBusConnection::sessionBus().registerObject("/resourcewatcher", this, QDBusConnection::ExportScriptableSlots); +} + +Nepomuk::ResourceWatcherManager::~ResourceWatcherManager() +{ + // the connections call removeConnection() from their descrutors. Thus, + // we need to clean them up before we are deleted ourselves + QSet<ResourceWatcherConnection*> allConnections + = QSet<ResourceWatcherConnection*>::fromList(m_resHash.values()) + + QSet<ResourceWatcherConnection*>::fromList(m_propHash.values()) + + QSet<ResourceWatcherConnection*>::fromList(m_typeHash.values()); + qDeleteAll(allConnections); +} + + +void Nepomuk::ResourceWatcherManager::addStatement(const Soprano::Statement& st) +{ + addProperty( st.subject(), st.predicate().uri(), st.object() ); +} + +void Nepomuk::ResourceWatcherManager::addProperty(const Soprano::Node res, const QUrl& property, const Soprano::Node& value) +{ + typedef ResourceWatcherConnection RWC; + + // FIXME: take care of duplicate signals! + + // + // Emit signals for all the connections that are only watching specific resources + // + QSet<RWC*> resConnections; + QList<RWC*> connections = m_resHash.values( res.uri() ); + foreach( RWC* con, connections ) { + if( !con->hasProperties() ) { + emit con->propertyAdded( KUrl(res.uri()).url(), + property.toString(), + nodeToVariant(value) ); + } + else { + resConnections << con; + } + } + + // + // Emit signals for the connections that are watching specific resources and properties + // + QList<RWC*> propConnections = m_propHash.values( property ); + foreach( RWC* con, propConnections ) { + QSet<RWC*>::const_iterator it = resConnections.constFind( con ); + if( it != resConnections.constEnd() ) { + emit con->propertyAdded( KUrl(res.uri()).url(), + property.toString(), + nodeToVariant(value) ); + } + } + + // + // Emit type + property signals + // + //TODO: Implement me! ( How? ) +} + +void Nepomuk::ResourceWatcherManager::removeProperty(const Soprano::Node res, const QUrl& property, const Soprano::Node& value) +{ + typedef ResourceWatcherConnection RWC; + + // + // Emit signals for all the connections that are only watching specific resources + // + QSet<RWC*> resConnections; + QList<RWC*> connections = m_resHash.values( res.uri() ); + foreach( RWC* con, connections ) { + if( !con->hasProperties() ) { + emit con->propertyRemoved( KUrl(res.uri()).url(), + property.toString(), + nodeToVariant(value) ); + } + else { + resConnections << con; + } + } + + // + // Emit signals for the conn2ections that are watching specific resources and properties + // + QList<RWC*> propConnections = m_propHash.values( property ); + foreach( RWC* con, propConnections ) { + QSet<RWC*>::const_iterator it = resConnections.constFind( con ); + if( it != resConnections.constEnd() ) { + emit con->propertyRemoved( KUrl(res.uri()).url(), + property.toString(), + nodeToVariant(value) ); + } + } +} + +void Nepomuk::ResourceWatcherManager::createResource(const QUrl &uri, const QList<QUrl> &types) +{ + QSet<ResourceWatcherConnection*> connections; + foreach(const QUrl& type, types) { + foreach(ResourceWatcherConnection* con, m_typeHash.values( type )) { + connections += con; + } + } + + foreach(ResourceWatcherConnection* con, connections) { + emit con->resourceCreated(KUrl(uri).url(), convertUris(types)); + } +} + +void Nepomuk::ResourceWatcherManager::removeResource(const QUrl &res, const QList<QUrl>& types) +{ + QSet<ResourceWatcherConnection*> connections; + foreach(const QUrl& type, types) { + foreach(ResourceWatcherConnection* con, m_typeHash.values( type )) { + connections += con; + } + } + foreach(ResourceWatcherConnection* con, m_resHash.values( res )) { + connections += con; + } + + foreach(ResourceWatcherConnection* con, connections) { + emit con->resourceRemoved(KUrl(res).url(), convertUris(types)); + } +} + +Nepomuk::ResourceWatcherConnection* Nepomuk::ResourceWatcherManager::createConnection(const QList<QUrl> &resources, + const QList<QUrl> &properties, + const QList<QUrl> &types) +{ + kDebug() << resources << properties << types; + + if( resources.isEmpty() && properties.isEmpty() && types.isEmpty() ) { + return 0; + } + + ResourceWatcherConnection* con = new ResourceWatcherConnection( this, !properties.isEmpty() ); + foreach( const QUrl& res, resources ) { + m_resHash.insert(res, con); + } + + foreach( const QUrl& prop, properties ) { + m_propHash.insert(prop, con); + } + + foreach( const QUrl& type, types ) { + m_typeHash.insert(type, con); + } + + return con; +} + +QDBusObjectPath Nepomuk::ResourceWatcherManager::watch(const QStringList& resources, + const QStringList& properties, + const QStringList& types) +{ + kDebug() << resources << properties << types; + + if(ResourceWatcherConnection* con = createConnection(convertUris(resources), convertUris(properties), convertUris(types))) { + return con->registerDBusObject(message().service(), ++m_connectionCount); + } + else { + return QDBusObjectPath(); + } +} + +namespace { + void removeConnectionFromHash( QMultiHash<QUrl, Nepomuk::ResourceWatcherConnection*> & hash, + const Nepomuk::ResourceWatcherConnection * con ) + { + QMutableHashIterator<QUrl, Nepomuk::ResourceWatcherConnection*> it( hash ); + while( it.hasNext() ) { + if( it.next().value() == con ) + it.remove(); + } + } +} + +void Nepomuk::ResourceWatcherManager::removeConnection(Nepomuk::ResourceWatcherConnection *con) +{ + removeConnectionFromHash( m_resHash, con ); + removeConnectionFromHash( m_propHash, con ); + removeConnectionFromHash( m_typeHash, con ); +} + +#include "resourcewatchermanager.moc" diff --git a/nepomuk/services/storage/resourcewatchermanager.h b/nepomuk/services/storage/resourcewatchermanager.h new file mode 100644 index 0000000..707ddab --- /dev/null +++ b/nepomuk/services/storage/resourcewatchermanager.h @@ -0,0 +1,82 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This program 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 program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#ifndef RESOURCEWATCHMANAGER_H +#define RESOURCEWATCHMANAGER_H + +#include <QtCore/QMultiHash> +#include <QtCore/QSet> + +#include <Soprano/FilterModel> +#include <QtDBus/QDBusObjectPath> +#include <QtDBus/QDBusContext> + +namespace Nepomuk { + + class ResourceWatcherConnection; + + class ResourceWatcherManager : public QObject, protected QDBusContext + { + Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.nepomuk.ResourceWatcher" ) + + public: + ResourceWatcherManager( QObject* parent = 0 ); + ~ResourceWatcherManager(); + + void addStatement(const Soprano::Statement &st); + void addProperty(const Soprano::Node res, const QUrl& property, const Soprano::Node& value); + void removeProperty(const Soprano::Node res, const QUrl& property, const Soprano::Node& value); + void createResource(const QUrl& uri, const QList<QUrl>& types); + void removeResource(const QUrl& uri, const QList<QUrl>& types); + + public slots: + /** + * Used internally by watch() and by the unit tests to create watcher connections. + */ + ResourceWatcherConnection* createConnection(const QList<QUrl>& resources, + const QList<QUrl>& properties, + const QList<QUrl>& types ); + + /** + * The main DBus methods exposed by the ResourceWatcher + */ + Q_SCRIPTABLE QDBusObjectPath watch( const QStringList& resources, + const QStringList& properties, + const QStringList& types ); + + private: + /// called by ResourceWatcherConnection destructor + void removeConnection(ResourceWatcherConnection*); + + QMultiHash<QUrl, ResourceWatcherConnection*> m_resHash; + QMultiHash<QUrl, ResourceWatcherConnection*> m_propHash; + QMultiHash<QUrl, ResourceWatcherConnection*> m_typeHash; + + // only used to generate unique dbus paths + int m_connectionCount; + + friend class ResourceWatcherConnection; + }; + +} + +#endif // RESOURCEWATCHMANAGER_H diff --git a/nepomuk/services/storage/storage.cpp b/nepomuk/services/storage/storage.cpp index af29526..9bb295e 100644 --- a/nepomuk/services/storage/storage.cpp +++ b/nepomuk/services/storage/storage.cpp @@ -38,8 +38,12 @@ NEPOMUK_EXPORT_SERVICE( Nepomuk::Storage, "nepomukstorage" ) Nepomuk::Storage::Storage( QObject* parent, const QList<QVariant>& ) : Service( parent, true /* delayed initialization */ ) { + // register the fancier name for this important service QDBusConnection::sessionBus().registerService( "org.kde.NepomukStorage" ); + // TODO: remove this one + QDBusConnection::sessionBus().registerService(QLatin1String("org.kde.nepomuk.DataManagement")); + m_core = new Core( this ); connect( m_core, SIGNAL( initializationDone(bool) ), this, SLOT( slotNepomukCoreInitialized(bool) ) ); diff --git a/nepomuk/services/storage/test/CMakeLists.txt b/nepomuk/services/storage/test/CMakeLists.txt index bc65f1c..e8490ed 100644 --- a/nepomuk/services/storage/test/CMakeLists.txt +++ b/nepomuk/services/storage/test/CMakeLists.txt @@ -1,18 +1,156 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( + ${SOPRANO_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR} + ${NEPOMUK_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ) + kde4_add_unit_test(crappyinferencer2test crappyinferencer2test.cpp ../crappyinferencer2.cpp - ../typevisibilitytree.cpp) + ../classandpropertytree.cpp) target_link_libraries(crappyinferencer2test ${QT_QTTEST_LIBRARY} ${SOPRANO_LIBRARIES} - ${KDE4_KDECORE_LIBS}) + ${KDE4_KDECORE_LIBS} + nepomukdatamanagement) + +kde4_add_unit_test(removablemediamodeltest + removablemediamodeltest.cpp + ../removablemediamodel.cpp +) +add_definitions(-DFAKE_COMPUTER_XML="\\"${CMAKE_CURRENT_SOURCE_DIR}/solid/fakecomputer.xml\\"") +target_link_libraries(removablemediamodeltest + ${QT_QTTEST_LIBRARY} + ${SOPRANO_LIBRARIES} + ${KDE4_KDECORE_LIBS} + ${KDE4_SOLID_LIBS} + ${NEPOMUK_LIBRARIES} + nepomukcommon +) + +set( DMTEST + ../classandpropertytree.cpp + ../datamanagementmodel.cpp + ../resourcewatchermanager.cpp + ../resourcewatcherconnection.cpp + ../datamanagementadaptor.cpp + ../datamanagementcommand.cpp + ../resourcemerger.cpp + ../resourceidentifier.cpp + qtest_dms.cpp +) + +qt4_add_dbus_adaptor(DMTEST + ../../../interfaces/org.kde.nepomuk.ResourceWatcherConnection.xml + resourcewatcherconnection.h + Nepomuk::ResourceWatcherConnection) + + +kde4_add_library( datamanagementtestlib STATIC ${DMTEST} ) + +target_link_libraries( datamanagementtestlib + ${SOPRANO_LIBRARIES} + ${KDE4_KIO_LIBS} + ${KDE4_SOLID_LIBS} + ${NEPOMUK_LIBRARIES} + nepomukdatamanagement + nepomuksync +) + +kde4_add_unit_test(classandpropertytreetest + classandpropertytreetest.cpp +) +target_link_libraries(classandpropertytreetest + ${QT_QTTEST_LIBRARY} + ${SOPRANO_LIBRARIES} + ${KDE4_KDECORE_LIBS} + nepomukdatamanagement + datamanagementtestlib +) + +kde4_add_unit_test(datamanagementmodeltest + datamanagementmodeltest.cpp +) + +target_link_libraries(datamanagementmodeltest + ${QT_QTTEST_LIBRARY} + ${SOPRANO_LIBRARIES} + ${KDE4_KIO_LIBS} + ${KDE4_SOLID_LIBS} + ${NEPOMUK_LIBRARIES} + nepomuksync + nepomukdatamanagement + datamanagementtestlib +) + + +kde4_add_unit_test(datamanagementadaptortest + datamanagementadaptortest.cpp +) + +target_link_libraries(datamanagementadaptortest + ${QT_QTTEST_LIBRARY} + ${SOPRANO_LIBRARIES} + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${KDE4_SOLID_LIBS} + ${NEPOMUK_LIBRARIES} + nepomuksync + nepomukdatamanagement + datamanagementtestlib +) + + +configure_file(nepomuk_dms_test_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/nepomuk_dms_test_config.h) + +kde4_add_executable(fakedms + fakedatamanagementservice.cpp +) + +target_link_libraries(fakedms + ${QT_QTTEST_LIBRARY} + ${SOPRANO_LIBRARIES} + ${SOPRANO_SERVER_LIBRARIES} + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${KDE4_SOLID_LIBS} + ${NEPOMUK_LIBRARIES} + nepomuksync + nepomukdatamanagement + datamanagementtestlib +) + + +kde4_add_unit_test(asyncclientapitest + asyncclientapitest.cpp +) + +target_link_libraries(asyncclientapitest + ${QT_QTTEST_LIBRARY} + ${SOPRANO_LIBRARIES} + ${SOPRANO_CLIENT_LIBRARIES} + ${KDE4_KDECORE_LIBS} + ${NEPOMUK_LIBRARIES} + nepomukdatamanagement +) + +kde4_add_executable(resourcewatchertest + resourcewatchertest.cpp +) -kde4_add_unit_test(typevisibilitytreetest - typevisibilitytreetest.cpp - ../typevisibilitytree.cpp) -target_link_libraries(typevisibilitytreetest +target_link_libraries(resourcewatchertest ${QT_QTTEST_LIBRARY} ${SOPRANO_LIBRARIES} - ${KDE4_KDECORE_LIBS}) + ${SOPRANO_SERVER_LIBRARIES} + ${KDE4_KDECORE_LIBS} + ${KDE4_KIO_LIBS} + ${KDE4_SOLID_LIBS} + ${NEPOMUK_LIBRARIES} + nepomuksync + nepomukdatamanagement + datamanagementtestlib +) diff --git a/nepomuk/services/storage/test/asyncclientapitest.cpp b/nepomuk/services/storage/test/asyncclientapitest.cpp new file mode 100644 index 0000000..c430f06 --- /dev/null +++ b/nepomuk/services/storage/test/asyncclientapitest.cpp @@ -0,0 +1,471 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "asyncclientapitest.h" +#include "../datamanagementmodel.h" +#include "../datamanagementadaptor.h" +#include "../classandpropertytree.h" +#include "simpleresource.h" +#include "simpleresourcegraph.h" +#include "datamanagement.h" +#include "createresourcejob.h" +#include "describeresourcesjob.h" +#include "nepomuk_dms_test_config.h" + +#include <QtTest> +#include "qtest_kde.h" + +#include <QtDBus> +#include <QProcess> +#include <Soprano/Soprano> +#include <Soprano/Client/DBusModel> + +#include <Soprano/Graph> +#define USING_SOPRANO_NRLMODEL_UNSTABLE_API +#include <Soprano/NRLModel> + +#include <ktempdir.h> +#include <KDebug> +#include <KJob> + +#include <Nepomuk/Vocabulary/NFO> +#include <Nepomuk/Vocabulary/NMM> +#include <Nepomuk/Vocabulary/NCO> +#include <Nepomuk/Vocabulary/NIE> + +using namespace Soprano; +using namespace Soprano::Vocabulary; +using namespace Nepomuk; +using namespace Nepomuk::Vocabulary; + + +void AsyncClientApiTest::initTestCase() +{ + kDebug() << "Starting fake DMS:" << FAKEDMS_BIN; + + // setup the service watcher so we know when the fake DMS is up + QDBusServiceWatcher watcher(QLatin1String("org.kde.nepomuk.FakeDataManagement"), + QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForRegistration); + + // start the fake DMS + m_fakeDms = new QProcess(); + m_fakeDms->setProcessChannelMode(QProcess::ForwardedChannels); + m_fakeDms->start(QLatin1String(FAKEDMS_BIN)); + + // wait for it to come up + QTest::kWaitForSignal(&watcher, SIGNAL(serviceRegistered(QString))); + + // get us access to the fake DMS's model + m_model = new Soprano::Client::DBusModel(QLatin1String("org.kde.nepomuk.FakeDataManagement"), QLatin1String("/model")); + + qputenv("NEPOMUK_FAKE_DMS_DBUS_SERVICE", "org.kde.nepomuk.FakeDataManagement"); +} + +void AsyncClientApiTest::cleanupTestCase() +{ + kDebug() << "Shutting down fake DMS..."; + QDBusInterface(QLatin1String("org.kde.nepomuk.FakeDataManagement"), + QLatin1String("/MainApplication"), + QLatin1String("org.kde.KApplication"), + QDBusConnection::sessionBus()).call(QLatin1String("quit")); + m_fakeDms->waitForFinished(); + delete m_fakeDms; + delete m_model; +} + +void AsyncClientApiTest::resetModel() +{ + // remove all the junk from previous tests + m_model->removeAllStatements(); + + // add some classes and properties + QUrl graph("graph:/onto"); + m_model->addStatement( graph, RDF::type(), NRL::Ontology(), graph ); + // removeResources depends on type inference + m_model->addStatement( graph, RDF::type(), NRL::Graph(), graph ); + + m_model->addStatement( QUrl("prop:/int"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/int"), RDFS::range(), XMLSchema::xsdInt(), graph ); + + m_model->addStatement( QUrl("prop:/int2"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/int2"), RDFS::range(), XMLSchema::xsdInt(), graph ); + + m_model->addStatement( QUrl("prop:/int3"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/int3"), RDFS::range(), XMLSchema::xsdInt(), graph ); + + m_model->addStatement( QUrl("prop:/int_c1"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/int_c1"), RDFS::range(), XMLSchema::xsdInt(), graph ); + m_model->addStatement( QUrl("prop:/int_c1"), NRL::maxCardinality(), LiteralValue(1), graph ); + + m_model->addStatement( QUrl("prop:/string"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/string"), RDFS::range(), XMLSchema::string(), graph ); + + m_model->addStatement( QUrl("prop:/res"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/res"), RDFS::range(), RDFS::Resource(), graph ); + + m_model->addStatement( QUrl("prop:/res_c1"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/res_c1"), RDFS::range(), RDFS::Resource(), graph ); + m_model->addStatement( QUrl("prop:/res_c1"), NRL::maxCardinality(), LiteralValue(1), graph ); + + m_model->addStatement( QUrl("prop:/date"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/date"), RDFS::range(), XMLSchema::date(), graph ); + + m_model->addStatement( QUrl("prop:/time"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/time"), RDFS::range(), XMLSchema::time(), graph ); + + m_model->addStatement( QUrl("prop:/dateTime"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/dateTime"), RDFS::range(), XMLSchema::dateTime(), graph ); + + m_model->addStatement( QUrl("class:/A"), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( QUrl("class:/B"), RDF::type(), RDFS::Class(), graph ); + + // properties used all the time + m_model->addStatement( NAO::identifier(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( RDF::type(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( RDF::type(), RDFS::range(), RDFS::Class(), graph ); + m_model->addStatement( NIE::url(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NIE::url(), RDFS::range(), RDFS::Resource(), graph ); + + // some ontology things the ResourceMerger depends on + m_model->addStatement( RDFS::Class(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( RDFS::Class(), RDFS::subClassOf(), RDFS::Resource(), graph ); + m_model->addStatement( NRL::Graph(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NRL::InstanceBase(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NRL::InstanceBase(), RDFS::subClassOf(), NRL::Graph(), graph ); + m_model->addStatement( NAO::prefLabel(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NAO::prefLabel(), RDFS::range(), RDFS::Literal(), graph ); + m_model->addStatement( NFO::fileName(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NFO::fileName(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NCO::fullname(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NCO::fullname(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NIE::title(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NIE::title(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NAO::created(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NAO::created(), RDFS::range(), XMLSchema::dateTime(), graph ); + m_model->addStatement( NAO::created(), NRL::maxCardinality(), LiteralValue(1), graph ); + m_model->addStatement( NAO::lastModified(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NAO::lastModified(), RDFS::range(), XMLSchema::dateTime(), graph ); + m_model->addStatement( NAO::lastModified(), NRL::maxCardinality(), LiteralValue(1), graph ); + + // rebuild the internals of the data management model + QDBusInterface(QLatin1String("org.kde.nepomuk.FakeDataManagement"), + QLatin1String("/fakedms"), + QLatin1String("org.kde.nepomuk.FakeDataManagement"), + QDBusConnection::sessionBus()).call(QLatin1String("updateClassAndPropertyTree")); +} + +void AsyncClientApiTest::init() +{ + resetModel(); +} + + +void AsyncClientApiTest::testAddProperty() +{ + KJob* job = Nepomuk::addProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/int"), QVariantList() << 42); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42))); +} + +void AsyncClientApiTest::testSetProperty() +{ + KJob* job = Nepomuk::setProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/int"), QVariantList() << 42); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42))); +} + +void AsyncClientApiTest::testRemoveProperties() +{ + Soprano::NRLModel nrlModel(m_model); + const QUrl g1 = nrlModel.createGraph(NRL::InstanceBase()); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(12), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(2), g1); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(12), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(2), g1); + + KJob* job = Nepomuk::removeProperties(QList<QUrl>() << QUrl("res:/A"), QList<QUrl>() << QUrl("prop:/int") << QUrl("prop:/int2")); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int"), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int2"), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); +} + +void AsyncClientApiTest::testCreateResource() +{ + CreateResourceJob* job = Nepomuk::createResource(QList<QUrl>() << QUrl("class:/A") << QUrl("class:/B"), QLatin1String("label"), QLatin1String("desc")); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + const QUrl uri = job->resourceUri(); + QVERIFY(!uri.isEmpty()); + + QVERIFY(m_model->containsAnyStatement(uri, RDF::type(), QUrl("class:/A"))); + QVERIFY(m_model->containsAnyStatement(uri, RDF::type(), QUrl("class:/B"))); + QVERIFY(m_model->containsAnyStatement(uri, NAO::prefLabel(), LiteralValue(QLatin1String("label")))); + QVERIFY(m_model->containsAnyStatement(uri, NAO::description(), LiteralValue(QLatin1String("desc")))); +} + +void AsyncClientApiTest::testRemoveProperty() +{ + Soprano::NRLModel nrlModel(m_model); + const QUrl g1 = nrlModel.createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/A"), NAO::lastModified(), LiteralValue(QDateTime::currentDateTime()), g1); + + KJob* job = Nepomuk::removeProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/string"), QVariantList() << QLatin1String("hello world")); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + // test that the data has been removed + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")))); +} + +void AsyncClientApiTest::testRemoveResources() +{ + Soprano::NRLModel nrlModel(m_model); + const QUrl g1 = nrlModel.createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("res:/A"), RDF::type(), NAO::Tag(), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + + KJob* job = Nepomuk::removeResources(QList<QUrl>() << QUrl("res:/A"), NoRemovalFlags); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + // verify that the resource is gone + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); +} + +void AsyncClientApiTest::testRemoveDataByApplication() +{ + Soprano::NRLModel nrlModel(m_model); + + // create our apps (we need to use the component name for the first one as that will be reused in the call below) + QUrl appG = nrlModel.createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(KGlobal::mainComponent().componentName()), appG); + appG = nrlModel.createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + // create the resource to delete + QUrl mg1; + const QUrl g1 = nrlModel.createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + QUrl mg2; + const QUrl g2 = nrlModel.createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + + // delete the resource + KJob* job = Nepomuk::removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), NoRemovalFlags); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + // verify that graph1 is gone completely + QVERIFY(!m_model->containsAnyStatement(Node(), Node(), Node(), g1)); + + // only two statements left: the one in the second graph and the last modification date + QCOMPARE(m_model->listStatements(QUrl("res:/A"), Node(), Node()).allStatements().count(), 2); + QVERIFY(m_model->containsStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2)); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), NAO::lastModified(), Node())); + + // four graphs: g2, the 2 app graphs, and the mtime graph + QCOMPARE(m_model->listStatements(Node(), RDF::type(), NRL::InstanceBase()).allStatements().count(), 4); +} + +void AsyncClientApiTest::testStoreResources() +{ + // store a resource just to check if the method is called properly + // and all types are property handled + SimpleResource res; + res.setUri(QUrl("_:A")); + res.addProperty(RDF::type(), NAO::Tag()); + res.addProperty(QUrl("prop:/string"), QLatin1String("Foobar")); + res.addProperty(QUrl("prop:/int"), 42); + res.addProperty(QUrl("prop:/date"), QDate::currentDate()); + res.addProperty(QUrl("prop:/time"), QTime::currentTime()); + res.addProperty(QUrl("prop:/dateTime"), QDateTime::currentDateTime()); + res.addProperty(QUrl("prop:/res"), QUrl("res:/A")); + + KJob* job = Nepomuk::storeResources(SimpleResourceGraph() << res); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + // check if the resource exists + QVERIFY(m_model->containsAnyStatement(Soprano::Node(), RDF::type(), NAO::Tag())); + QVERIFY(m_model->containsAnyStatement(Soprano::Node(), QUrl("prop:/string"), Soprano::LiteralValue(QLatin1String("Foobar")))); + QVERIFY(m_model->containsAnyStatement(Soprano::Node(), QUrl("prop:/int"), Soprano::LiteralValue(42))); + QVERIFY(m_model->containsAnyStatement(Soprano::Node(), QUrl("prop:/date"), Soprano::LiteralValue(res.property(QUrl("prop:/date")).first().toDate()))); + QVERIFY(m_model->containsAnyStatement(Soprano::Node(), QUrl("prop:/time"), Soprano::LiteralValue(res.property(QUrl("prop:/time")).first().toTime()))); + QVERIFY(m_model->containsAnyStatement(Soprano::Node(), QUrl("prop:/dateTime"), Soprano::LiteralValue(res.property(QUrl("prop:/dateTime")).first().toDateTime()))); +} + +void AsyncClientApiTest::testMergeResources() +{ + // create some resources + Soprano::NRLModel nrlModel(m_model); + const QUrl g1 = nrlModel.createGraph(NRL::InstanceBase()); + + // the resource in which we want to merge + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int_c1"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + // the resource that is going to be merged + // one duplicate property and one that differs, one backlink to ignore, + // one property with cardinality 1 to ignore + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/int_c1"), LiteralValue(12), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/B"), g1); + + KJob* job = Nepomuk::mergeResources(QUrl("res:/A"), QUrl("res:/B")); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + // make sure B is gone + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(Node(), Node(), QUrl("res:/B"))); + + // make sure A has all the required properties + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int_c1"), LiteralValue(42))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello")))); + + // make sure A has no superfluous properties + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int_c1"), LiteralValue(12))); + QCOMPARE(m_model->listStatements(QUrl("res:/A"), QUrl("prop:/int"), Node()).allElements().count(), 1); +} + +void AsyncClientApiTest::testDescribeResources() +{ + // create some resources + Soprano::NRLModel nrlModel(m_model); + const QUrl g1 = nrlModel.createGraph(NRL::InstanceBase()); + + m_model->addStatement(QUrl("res:/A"), RDF::type(), NAO::Tag(), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/B"), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/C"), NAO::hasSubResource(), QUrl("res:/D"), g1); + + m_model->addStatement(QUrl("res:/D"), QUrl("prop:/string"), LiteralValue(QLatin1String("Hello")), g1); + + + // we only use one of the test cases from the dms test: get two resources with subresoruces + DescribeResourcesJob* job = Nepomuk::describeResources(QList<QUrl>() << QUrl("res:/A") << QUrl("res:/C"), true); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + QList<SimpleResource> g = job->resources().toList(); + + // only one resource in the result + QCOMPARE(g.count(), 4); + + // the results are res:/A, res:/B, res:/C and res:/D + QList<SimpleResource>::const_iterator it = g.constBegin(); + SimpleResource r1 = *it; + ++it; + SimpleResource r2 = *it; + ++it; + SimpleResource r3 = *it; + ++it; + SimpleResource r4 = *it; + QVERIFY(r1.uri() == QUrl("res:/A") || r2.uri() == QUrl("res:/A") || r3.uri() == QUrl("res:/A") || r4.uri() == QUrl("res:/A")); + QVERIFY(r1.uri() == QUrl("res:/B") || r2.uri() == QUrl("res:/B") || r3.uri() == QUrl("res:/B") || r4.uri() == QUrl("res:/B")); + QVERIFY(r1.uri() == QUrl("res:/C") || r2.uri() == QUrl("res:/C") || r3.uri() == QUrl("res:/C") || r4.uri() == QUrl("res:/C")); + QVERIFY(r1.uri() == QUrl("res:/D") || r2.uri() == QUrl("res:/D") || r3.uri() == QUrl("res:/D") || r4.uri() == QUrl("res:/D")); +} + +void AsyncClientApiTest::testImportResources() +{ + // create the test data + QTemporaryFile fileA; + fileA.open(); + + Soprano::Graph graph; + graph.addStatement(Node(QString::fromLatin1("res1")), QUrl("prop:/int"), LiteralValue(42)); + graph.addStatement(Node(QString::fromLatin1("res1")), RDF::type(), QUrl("class:/typeA")); + graph.addStatement(Node(QString::fromLatin1("res1")), QUrl("prop:/res"), Node(QString::fromLatin1("res2"))); + graph.addStatement(Node(QString::fromLatin1("res2")), RDF::type(), QUrl("class:/typeB")); + graph.addStatement(QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/int"), LiteralValue(12)); + graph.addStatement(QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar"))); + + // write the test file + QTemporaryFile tmp; + tmp.open(); + QTextStream str(&tmp); + Q_FOREACH(const Statement& s, graph.toList()) { + str << s.subject().toN3() << " " << s.predicate().toN3() << " " << s.object().toN3() << " ." << endl; + } + tmp.close(); + + + // import the file + KJob* job = Nepomuk::importResources(QUrl::fromLocalFile(tmp.fileName()), Soprano::SerializationNTriples); + QTest::kWaitForSignal(job, SIGNAL(result(KJob*)), 5000); + QVERIFY(!job->error()); + + + // make sure the data has been imported properly + QVERIFY(m_model->containsAnyStatement(Node(), QUrl("prop:/int"), LiteralValue(42))); + const QUrl res1Uri = m_model->listStatements(Node(), QUrl("prop:/int"), LiteralValue(42)).allStatements().first().subject().uri(); + QVERIFY(m_model->containsAnyStatement(res1Uri, RDF::type(), QUrl("class:/typeA"))); + QVERIFY(m_model->containsAnyStatement(res1Uri, QUrl("prop:/res"), Node())); + const QUrl res2Uri = m_model->listStatements(res1Uri, QUrl("prop:/res"), Node()).allStatements().first().object().uri(); + QVERIFY(m_model->containsAnyStatement(res2Uri, RDF::type(), QUrl("class:/typeB"))); + QVERIFY(m_model->containsAnyStatement(Node(), NIE::url(), QUrl::fromLocalFile(fileA.fileName()))); + const QUrl res3Uri = m_model->listStatements(Node(), NIE::url(), QUrl::fromLocalFile(fileA.fileName())).allStatements().first().subject().uri(); + QVERIFY(m_model->containsAnyStatement(res3Uri, QUrl("prop:/int"), LiteralValue(12))); + QVERIFY(m_model->containsAnyStatement(res3Uri, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + + // make sure the metadata is there + QVERIFY(m_model->containsAnyStatement(res1Uri, NAO::lastModified(), Node())); + QVERIFY(m_model->containsAnyStatement(res1Uri, NAO::created(), Node())); + QVERIFY(m_model->containsAnyStatement(res2Uri, NAO::lastModified(), Node())); + QVERIFY(m_model->containsAnyStatement(res2Uri, NAO::created(), Node())); + QVERIFY(m_model->containsAnyStatement(res3Uri, NAO::lastModified(), Node())); + QVERIFY(m_model->containsAnyStatement(res3Uri, NAO::created(), Node())); +} + +QTEST_KDEMAIN_CORE(AsyncClientApiTest) + +#include "asyncclientapitest.moc" diff --git a/nepomuk/services/storage/test/asyncclientapitest.h b/nepomuk/services/storage/test/asyncclientapitest.h new file mode 100644 index 0000000..3cb6bec --- /dev/null +++ b/nepomuk/services/storage/test/asyncclientapitest.h @@ -0,0 +1,59 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef ASYNCCLIENTAPITEST_H +#define ASYNCCLIENTAPITEST_H + +#include <QObject> + +class QProcess; +namespace Soprano { +class Model; +} +class AsyncClientApiTest : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + + void testAddProperty(); + void testSetProperty(); + void testCreateResource(); + void testRemoveProperty(); + void testRemoveProperties(); + void testRemoveResources(); + void testRemoveDataByApplication(); + void testStoreResources(); + void testMergeResources(); + void testDescribeResources(); + void testImportResources(); + +private: + void resetModel(); + + QProcess* m_fakeDms; + Soprano::Model* m_model; +}; + +#endif diff --git a/nepomuk/services/storage/test/classandpropertytreetest.cpp b/nepomuk/services/storage/test/classandpropertytreetest.cpp new file mode 100644 index 0000000..6d6ee75 --- /dev/null +++ b/nepomuk/services/storage/test/classandpropertytreetest.cpp @@ -0,0 +1,290 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010-2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "classandpropertytreetest.h" +#include "../classandpropertytree.h" + +#include <QtTest> +#include "qtest_kde.h" +#include "qtest_dms.h" + +#include <Soprano/Soprano> + +#include <ktempdir.h> +#include <kdebug.h> + +using namespace Soprano; +using namespace Soprano::Vocabulary; + +Q_DECLARE_METATYPE(Soprano::Node) + +void ClassAndPropertyTreeTest::initTestCase() +{ + // we need to use a Virtuoso model as tmp model since redland misses important SPARQL features + // that are used by libnepomuk below + const Soprano::Backend* backend = Soprano::PluginManager::instance()->discoverBackendByName( "virtuosobackend" ); + QVERIFY( backend ); + m_storageDir = new KTempDir(); + m_model = backend->createModel( Soprano::BackendSettings() << Soprano::BackendSetting(Soprano::BackendOptionStorageDir, m_storageDir->name()) ); + QVERIFY( m_model ); + + m_typeTree = new Nepomuk::ClassAndPropertyTree( this ); +} + +void ClassAndPropertyTreeTest::cleanupTestCase() +{ + delete m_model; + delete m_typeTree; + delete m_storageDir; +} + + +void ClassAndPropertyTreeTest::init() +{ + m_model->removeAllStatements(); + + // we create one fake ontology + // + // situations we need to test: + // * class that is marked visible should stay visible + // * class that is marked invisible should stay invisible + // * non-marked subclass of visible should be visible, too + // * non-marked subclass of invisible should be invisible, too + // * marked subclass should keep its own visiblity and not inherit from parent + // * whole branch should inherit from parent + // * if one parent is visible the class is visible, too, even if N other parents are not + // * if all parents are invisible, the class is invisible, even if higher up in the branch a class is visible + // * properly handle loops (as in: do not run into an endless loop) + // + // A + // |- B - invisible + // |- C + // |- D - visible + // |- E + // |- F + // |- G + // + // AA - invisible + // | - F + // | - G + // + // X + // |- Y - invisible + // |- Z + // |- X + + QUrl graph("graph:/onto"); + m_model->addStatement( graph, Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::NRL::Ontology(), graph ); + + m_model->addStatement( QUrl("onto:/A"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + m_model->addStatement( QUrl("onto:/B"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + m_model->addStatement( QUrl("onto:/C"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + m_model->addStatement( QUrl("onto:/D"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + m_model->addStatement( QUrl("onto:/E"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + m_model->addStatement( QUrl("onto:/F"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + m_model->addStatement( QUrl("onto:/G"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + m_model->addStatement( QUrl("onto:/AA"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + m_model->addStatement( QUrl("onto:/X"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + m_model->addStatement( QUrl("onto:/Y"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + m_model->addStatement( QUrl("onto:/Z"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); + + m_model->addStatement( QUrl("onto:/B"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/A"), graph ); + m_model->addStatement( QUrl("onto:/C"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/B"), graph ); + m_model->addStatement( QUrl("onto:/D"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/C"), graph ); + m_model->addStatement( QUrl("onto:/E"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/D"), graph ); + m_model->addStatement( QUrl("onto:/F"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/E"), graph ); + m_model->addStatement( QUrl("onto:/G"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/B"), graph ); + m_model->addStatement( QUrl("onto:/F"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/AA"), graph ); + m_model->addStatement( QUrl("onto:/G"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/AA"), graph ); + m_model->addStatement( QUrl("onto:/Y"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/X"), graph ); + m_model->addStatement( QUrl("onto:/Z"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/Y"), graph ); + m_model->addStatement( QUrl("onto:/X"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/Z"), graph ); + + m_model->addStatement( QUrl("onto:/B"), Soprano::Vocabulary::NAO::userVisible(), LiteralValue(false), graph ); + m_model->addStatement( QUrl("onto:/D"), Soprano::Vocabulary::NAO::userVisible(), LiteralValue(true), graph ); + m_model->addStatement( QUrl("onto:/AA"), Soprano::Vocabulary::NAO::userVisible(), LiteralValue(false), graph ); + m_model->addStatement( QUrl("onto:/Y"), Soprano::Vocabulary::NAO::userVisible(), LiteralValue(false), graph ); + + // a few properties for node conversion testing and range checking + m_model->addStatement( QUrl("prop:/A"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/B"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/B1"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/B2"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/C"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/D"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/E"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDF::Property(), graph ); + + m_model->addStatement( QUrl("prop:/A"), Soprano::Vocabulary::RDFS::range(), Soprano::Vocabulary::XMLSchema::string(), graph ); + m_model->addStatement( QUrl("prop:/B"), Soprano::Vocabulary::RDFS::range(), Soprano::Vocabulary::XMLSchema::xsdInt(), graph ); + m_model->addStatement( QUrl("prop:/B1"), Soprano::Vocabulary::RDFS::range(), Soprano::Vocabulary::XMLSchema::integer(), graph ); + m_model->addStatement( QUrl("prop:/B2"), Soprano::Vocabulary::RDFS::range(), Soprano::Vocabulary::XMLSchema::unsignedInt(), graph ); + m_model->addStatement( QUrl("prop:/C"), Soprano::Vocabulary::RDFS::range(), Soprano::Vocabulary::XMLSchema::xsdDouble(), graph ); + m_model->addStatement( QUrl("prop:/D"), Soprano::Vocabulary::RDFS::range(), Soprano::Vocabulary::XMLSchema::dateTime(), graph ); + m_model->addStatement( QUrl("prop:/E"), Soprano::Vocabulary::RDFS::range(), QUrl("onto:/A"), graph ); + + m_model->addStatement( QUrl("prop:/C"), Soprano::Vocabulary::NRL::maxCardinality(), LiteralValue(1), graph ); + m_model->addStatement( QUrl("prop:/D"), Soprano::Vocabulary::NRL::cardinality(), LiteralValue(1), graph ); + + m_typeTree->rebuildTree(m_model); +} + +void ClassAndPropertyTreeTest::testVisibility() +{ + QVERIFY(m_typeTree->isUserVisible(QUrl("onto:/A"))); + QVERIFY(!m_typeTree->isUserVisible(QUrl("onto:/B"))); + QVERIFY(!m_typeTree->isUserVisible(QUrl("onto:/C"))); + QVERIFY(m_typeTree->isUserVisible(QUrl("onto:/D"))); + QVERIFY(m_typeTree->isUserVisible(QUrl("onto:/E"))); + QVERIFY(m_typeTree->isUserVisible(QUrl("onto:/F"))); + QVERIFY(!m_typeTree->isUserVisible(QUrl("onto:/G"))); + QVERIFY(!m_typeTree->isUserVisible(QUrl("onto:/AA"))); + QVERIFY(!m_typeTree->isUserVisible(QUrl("onto:/X"))); // because only top-level classes inherit from rdfs:Resource + QVERIFY(!m_typeTree->isUserVisible(QUrl("onto:/Y"))); + QVERIFY(!m_typeTree->isUserVisible(QUrl("onto:/Z"))); +} + +void ClassAndPropertyTreeTest::testParents() +{ + QCOMPARE(m_typeTree->allParents(QUrl("onto:/A")).count(), 1); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/A"), Soprano::Vocabulary::RDFS::Resource())); + + QCOMPARE(m_typeTree->allParents(QUrl("onto:/B")).count(), 2); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/B"), Soprano::Vocabulary::RDFS::Resource())); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/B"), QUrl("onto:/A"))); + + QCOMPARE(m_typeTree->allParents(QUrl("onto:/C")).count(), 3); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/C"), Soprano::Vocabulary::RDFS::Resource())); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/C"), QUrl("onto:/A"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/C"), QUrl("onto:/B"))); + + QCOMPARE(m_typeTree->allParents(QUrl("onto:/D")).count(), 4); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/D"), Soprano::Vocabulary::RDFS::Resource())); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/D"), QUrl("onto:/A"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/D"), QUrl("onto:/B"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/D"), QUrl("onto:/C"))); + + QCOMPARE(m_typeTree->allParents(QUrl("onto:/E")).count(), 5); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/E"), Soprano::Vocabulary::RDFS::Resource())); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/E"), QUrl("onto:/A"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/E"), QUrl("onto:/B"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/E"), QUrl("onto:/C"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/E"), QUrl("onto:/D"))); + + QCOMPARE(m_typeTree->allParents(QUrl("onto:/F")).count(), 7); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/F"), Soprano::Vocabulary::RDFS::Resource())); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/F"), QUrl("onto:/A"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/F"), QUrl("onto:/B"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/F"), QUrl("onto:/C"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/F"), QUrl("onto:/D"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/F"), QUrl("onto:/E"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/F"), QUrl("onto:/AA"))); + + QCOMPARE(m_typeTree->allParents(QUrl("onto:/G")).count(), 4); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/G"), Soprano::Vocabulary::RDFS::Resource())); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/G"), QUrl("onto:/AA"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/G"), QUrl("onto:/A"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/G"), QUrl("onto:/B"))); + + QCOMPARE(m_typeTree->allParents(QUrl("onto:/X")).count(), 3); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/X"), Soprano::Vocabulary::RDFS::Resource())); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/X"), QUrl("onto:/Y"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/X"), QUrl("onto:/Z"))); + + QCOMPARE(m_typeTree->allParents(QUrl("onto:/Y")).count(), 3); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/Y"), Soprano::Vocabulary::RDFS::Resource())); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/Y"), QUrl("onto:/X"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/Y"), QUrl("onto:/Z"))); + + QCOMPARE(m_typeTree->allParents(QUrl("onto:/Z")).count(), 3); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/Z"), Soprano::Vocabulary::RDFS::Resource())); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/Z"), QUrl("onto:/X"))); + QVERIFY(m_typeTree->isChildOf(QUrl("onto:/Z"), QUrl("onto:/Y"))); +} + +void ClassAndPropertyTreeTest::testVariantToNode_data() +{ + QTest::addColumn<QVariant>( "value" ); + QTest::addColumn<QUrl>( "property" ); + QTest::addColumn<Soprano::Node>( "node" ); + + // simple literal values + QTest::newRow("string-simple") << QVariant("foobar") << QUrl("prop:/A") << Soprano::Node(LiteralValue(QLatin1String("foobar"))); + QTest::newRow("int-simple") << QVariant(42) << QUrl("prop:/B") << Soprano::Node(LiteralValue(42)); + QTest::newRow("double-simple") << QVariant(42.1) << QUrl("prop:/C") << Soprano::Node(LiteralValue(42.1)); + const QDateTime now = QDateTime::currentDateTime(); + QTest::newRow("datatime-simple") << QVariant(now) << QUrl("prop:/D") << Soprano::Node(LiteralValue(now)); + + // literal values that can be converted to strings + QTest::newRow("string-int") << QVariant(42) << QUrl("prop:/A") << Soprano::Node(LiteralValue(QLatin1String("42"))); + QTest::newRow("string-double") << QVariant(42.2) << QUrl("prop:/A") << Soprano::Node(LiteralValue(QLatin1String("42.2"))); + QTest::newRow("string-datetime") << QVariant(now) << QUrl("prop:/A") << Soprano::Node(LiteralValue(QVariant(now).toString())); + + // literal values that can be converted from strings + QTest::newRow("int-string") << QVariant("42") << QUrl("prop:/B") << Soprano::Node(LiteralValue(42)); + QTest::newRow("double-string") << QVariant("42.2") << QUrl("prop:/C") << Soprano::Node(LiteralValue(42.2)); + QTest::newRow("datetime-string") << QVariant(LiteralValue(now).toString()) << QUrl("prop:/D") << Soprano::Node(LiteralValue(now)); + + // different types of int + QTest::newRow("int-unsigned") << QVariant(42) << QUrl("prop:/B2") << Soprano::Node(LiteralValue(uint(42))); + QTest::newRow("int-integer") << QVariant(42) << QUrl("prop:/B1") << Soprano::Node(LiteralValue::fromString(QLatin1String("42"), XMLSchema::integer())); + + // literal values that cannot be converted + QTest::newRow("int-invalid") << QVariant("43g") << QUrl("prop:/B") << Soprano::Node(); + QTest::newRow("double-invalid") << QVariant("43g") << QUrl("prop:/C") << Soprano::Node(); + + // resource URI + QTest::newRow("res-uri") << QVariant(QUrl("res:/A")) << QUrl("prop:/E") << Soprano::Node(QUrl("res:/A")); + QTest::newRow("res-string") << QVariant(QLatin1String("res:/A")) << QUrl("prop:/E") << Soprano::Node(QUrl("res:/A")); + + // local file + QTest::newRow("file-path") << QVariant(QLatin1String("/tmp")) << QUrl("prop:/E") << Soprano::Node(QUrl("file:///tmp")); +} + +void ClassAndPropertyTreeTest::testVariantToNode() +{ + QFETCH(QVariant, value); + QFETCH(QUrl, property); + QFETCH(Soprano::Node, node); + + QCOMPARE(m_typeTree->variantToNode(value, property), node); +} + +void ClassAndPropertyTreeTest::testProperties() +{ + QCOMPARE(m_typeTree->maxCardinality(QUrl("prop:/C")), 1); + QCOMPARE(m_typeTree->maxCardinality(QUrl("prop:/D")), 1); +} + +void ClassAndPropertyTreeTest::testVisibleType() +{ + const QList<QUrl> types = m_typeTree->visibleTypes(); + kDebug() << types; + QCOMPARE(types.count(), 5); + QVERIFY(types.contains(RDFS::Resource())); + QVERIFY(types.contains(QUrl("onto:/A"))); + QVERIFY(types.contains(QUrl("onto:/D"))); + QVERIFY(types.contains(QUrl("onto:/E"))); + QVERIFY(types.contains(QUrl("onto:/F"))); +} + +QTEST_KDEMAIN_CORE(ClassAndPropertyTreeTest) + +#include "classandpropertytreetest.moc" diff --git a/nepomuk/services/storage/test/classandpropertytreetest.h b/nepomuk/services/storage/test/classandpropertytreetest.h new file mode 100644 index 0000000..29d961b --- /dev/null +++ b/nepomuk/services/storage/test/classandpropertytreetest.h @@ -0,0 +1,56 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010-2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef CLASSANDPROPERTYTREETEST_H +#define CLASSANDPROPERTYTREETEST_H + +#include <QObject> + +class KTempDir; +namespace Soprano { +class Model; +} +namespace Nepomuk { +class ClassAndPropertyTree; +} + +class ClassAndPropertyTreeTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void testVisibility(); + void testParents(); + void testVariantToNode_data(); + void testVariantToNode(); + void testProperties(); + void testVisibleType(); + +private: + KTempDir* m_storageDir; + Soprano::Model* m_model; + Nepomuk::ClassAndPropertyTree* m_typeTree; +}; + +#endif diff --git a/nepomuk/services/storage/test/crappyinferencer2test.cpp b/nepomuk/services/storage/test/crappyinferencer2test.cpp index 0350058..f70b767 100644 --- a/nepomuk/services/storage/test/crappyinferencer2test.cpp +++ b/nepomuk/services/storage/test/crappyinferencer2test.cpp @@ -21,6 +21,7 @@ #include "crappyinferencer2test.h" #include "../crappyinferencer2.h" +#include "../classandpropertytree.h" #include <QtTest> #include "qtest_kde.h" @@ -45,7 +46,8 @@ void CrappyInferencer2Test::initTestCase() m_baseModel = backend->createModel( Soprano::BackendSettings() << Soprano::BackendSetting(Soprano::BackendOptionStorageDir, m_storageDir->name()) ); QVERIFY( m_baseModel ); - m_model = new CrappyInferencer2( m_baseModel ); + m_typeTree = new Nepomuk::ClassAndPropertyTree( this ); + m_model = new CrappyInferencer2( m_typeTree, m_baseModel ); } void CrappyInferencer2Test::cleanupTestCase() @@ -101,6 +103,7 @@ void CrappyInferencer2Test::init() m_baseModel->addStatement( QUrl("onto:/L2"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); m_baseModel->addStatement( QUrl("onto:/L2"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/L1"), graph ); + m_typeTree->rebuildTree(m_model); m_model->updateInferenceIndex(); } diff --git a/nepomuk/services/storage/test/crappyinferencer2test.h b/nepomuk/services/storage/test/crappyinferencer2test.h index cafe073..1ad41dd 100644 --- a/nepomuk/services/storage/test/crappyinferencer2test.h +++ b/nepomuk/services/storage/test/crappyinferencer2test.h @@ -29,6 +29,9 @@ class CrappyInferencer2; namespace Soprano { class Model; } +namespace Nepomuk { +class ClassAndPropertyTree; +} class CrappyInferencer2Test : public QObject { @@ -51,6 +54,7 @@ private Q_SLOTS: private: KTempDir* m_storageDir; Soprano::Model* m_baseModel; + Nepomuk::ClassAndPropertyTree* m_typeTree; CrappyInferencer2* m_model; }; diff --git a/nepomuk/services/storage/test/datamanagementadaptortest.cpp b/nepomuk/services/storage/test/datamanagementadaptortest.cpp new file mode 100644 index 0000000..78d47d4 --- /dev/null +++ b/nepomuk/services/storage/test/datamanagementadaptortest.cpp @@ -0,0 +1,131 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "datamanagementadaptortest.h" +#include "../datamanagementmodel.h" +#include "../datamanagementadaptor.h" +#include "../classandpropertytree.h" +#include "simpleresource.h" + +#include <QtTest> +#include "qtest_kde.h" + +#include <Soprano/Soprano> +#include <Soprano/Graph> +#define USING_SOPRANO_NRLMODEL_UNSTABLE_API +#include <Soprano/NRLModel> + +#include <ktempdir.h> +#include <KDebug> + +#include <Nepomuk/Vocabulary/NFO> +#include <Nepomuk/Vocabulary/NMM> +#include <Nepomuk/Vocabulary/NCO> +#include <Nepomuk/Vocabulary/NIE> + +using namespace Soprano; +using namespace Soprano::Vocabulary; +using namespace Nepomuk; +using namespace Nepomuk::Vocabulary; + + +void DataManagementAdaptorTest::resetModel() +{ + // remove all the junk from previous tests + m_model->removeAllStatements(); + + // add some classes and properties + QUrl graph1("graph:/onto1/"); + m_model->addStatement( graph1, RDF::type(), NRL::Ontology(), graph1 ); + m_model->addStatement( graph1, NAO::hasDefaultNamespace(), QUrl("graph:/onto1/"), graph1 ); + m_model->addStatement( graph1, NAO::hasDefaultNamespaceAbbreviation(), LiteralValue(QLatin1String("itda")), graph1 ); + + QUrl graph2("graph:/onto2#"); + m_model->addStatement( graph2, RDF::type(), NRL::Ontology(), graph2 ); + m_model->addStatement( graph2, NAO::hasDefaultNamespace(), QUrl("graph:/onto2#"), graph1 ); + m_model->addStatement( graph2, NAO::hasDefaultNamespaceAbbreviation(), LiteralValue(QLatin1String("wbzo")), graph1 ); + + + m_model->addStatement( QUrl("graph:/onto1/P1"), RDF::type(), RDF::Property(), graph1 ); + m_model->addStatement( QUrl("graph:/onto1/P2"), RDF::type(), RDF::Property(), graph1 ); + m_model->addStatement( QUrl("graph:/onto1/T1"), RDFS::Class(), RDF::Property(), graph1 ); + + m_model->addStatement( QUrl("graph:/onto2#P1"), RDF::type(), RDF::Property(), graph2 ); + m_model->addStatement( QUrl("graph:/onto2#P2"), RDF::type(), RDF::Property(), graph2 ); + m_model->addStatement( QUrl("graph:/onto2#T1"), RDFS::Class(), RDF::Property(), graph2 ); + + + m_classAndPropertyTree->rebuildTree(m_dmModel); + m_nrlModel->setEnableQueryPrefixExpansion(false); + m_nrlModel->setEnableQueryPrefixExpansion(true); + QHash<QString, QString> prefixes; + const QHash<QString, QUrl> namespaces = m_nrlModel->queryPrefixes(); + for(QHash<QString, QUrl>::const_iterator it = namespaces.constBegin(); + it != namespaces.constEnd(); ++it) { + prefixes.insert(it.key(), QString::fromAscii(it.value().toEncoded())); + } + m_dmAdaptor->setPrefixes(prefixes); +} + +void DataManagementAdaptorTest::initTestCase() +{ + const Soprano::Backend* backend = Soprano::PluginManager::instance()->discoverBackendByName( "virtuosobackend" ); + QVERIFY( backend ); + m_storageDir = new KTempDir(); + m_model = backend->createModel( Soprano::BackendSettings() << Soprano::BackendSetting(Soprano::BackendOptionStorageDir, m_storageDir->name()) ); + QVERIFY( m_model ); + + // DataManagementModel relies on the ussage of a NRLModel in the storage service + m_nrlModel = new Soprano::NRLModel(m_model); + + m_classAndPropertyTree = new Nepomuk::ClassAndPropertyTree(this); + + m_dmModel = new Nepomuk::DataManagementModel(m_classAndPropertyTree, m_nrlModel); + + m_dmAdaptor = new Nepomuk::DataManagementAdaptor(m_dmModel); +} + +void DataManagementAdaptorTest::cleanupTestCase() +{ + delete m_dmAdaptor; + delete m_dmModel; + delete m_nrlModel; + delete m_model; + delete m_storageDir; +} + +void DataManagementAdaptorTest::init() +{ + resetModel(); +} + +void DataManagementAdaptorTest::testNamespaceExpansion() +{ + QCOMPARE(m_dmAdaptor->decodeUri(QLatin1String("itda:P1"), true), QUrl("graph:/onto1/P1")); + QCOMPARE(m_dmAdaptor->decodeUri(QLatin1String("itda:T1"), true), QUrl("graph:/onto1/T1")); + QCOMPARE(m_dmAdaptor->decodeUri(QLatin1String("wbzo:P1"), true), QUrl("graph:/onto2#P1")); + QCOMPARE(m_dmAdaptor->decodeUri(QLatin1String("wbzo:T1"), true), QUrl("graph:/onto2#T1")); +} + + +QTEST_KDEMAIN_CORE(DataManagementAdaptorTest) + +#include "datamanagementadaptortest.moc" diff --git a/nepomuk/services/storage/test/datamanagementadaptortest.h b/nepomuk/services/storage/test/datamanagementadaptortest.h new file mode 100644 index 0000000..a7c4652 --- /dev/null +++ b/nepomuk/services/storage/test/datamanagementadaptortest.h @@ -0,0 +1,60 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef DATAMANAGEMENTADAPTORTEST_H +#define DATAMANAGEMENTADAPTORTEST_H + +#include <QObject> + +namespace Soprano { +class Model; +class NRLModel; +} +namespace Nepomuk { +class DataManagementModel; +class DataManagementAdaptor; +class ClassAndPropertyTree; +} +class KTempDir; + +class DataManagementAdaptorTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + + void testNamespaceExpansion(); + +private: + void resetModel(); + + KTempDir* m_storageDir; + Soprano::Model* m_model; + Soprano::NRLModel* m_nrlModel; + Nepomuk::ClassAndPropertyTree* m_classAndPropertyTree; + Nepomuk::DataManagementModel* m_dmModel; + Nepomuk::DataManagementAdaptor* m_dmAdaptor; +}; + +#endif diff --git a/nepomuk/services/storage/test/datamanagementmodeltest.cpp b/nepomuk/services/storage/test/datamanagementmodeltest.cpp new file mode 100644 index 0000000..89914e6 --- /dev/null +++ b/nepomuk/services/storage/test/datamanagementmodeltest.cpp @@ -0,0 +1,5032 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "datamanagementmodeltest.h" +#include "../datamanagementmodel.h" +#include "../classandpropertytree.h" +#include "simpleresource.h" +#include "simpleresourcegraph.h" + +#include <QtTest> +#include "qtest_kde.h" +#include "qtest_dms.h" + +#include <Soprano/Soprano> +#include <Soprano/Graph> +#define USING_SOPRANO_NRLMODEL_UNSTABLE_API +#include <Soprano/NRLModel> + +#include <KTemporaryFile> +#include <KTempDir> +#include <KProtocolInfo> +#include <KDebug> + +#include <Nepomuk/Vocabulary/NFO> +#include <Nepomuk/Vocabulary/NMM> +#include <Nepomuk/Vocabulary/NCO> +#include <Nepomuk/Vocabulary/NIE> +#include <Nepomuk/ResourceManager> + +using namespace Soprano; +using namespace Soprano::Vocabulary; +using namespace Nepomuk; +using namespace Nepomuk::Vocabulary; + + +// TODO: test nao:created and nao:lastModified, these should always be correct for existing resources. This is especially important in the removeDataByApplication methods. + +void DataManagementModelTest::resetModel() +{ + // remove all the junk from previous tests + m_model->removeAllStatements(); + + // add some classes and properties + QUrl graph("graph:/onto"); + Nepomuk::insertOntologies( m_model, graph ); + + // rebuild the internals of the data management model + m_classAndPropertyTree->rebuildTree(m_dmModel); +} + + +void DataManagementModelTest::initTestCase() +{ + const Soprano::Backend* backend = Soprano::PluginManager::instance()->discoverBackendByName( "virtuosobackend" ); + QVERIFY( backend ); + m_storageDir = new KTempDir(); + m_model = backend->createModel( Soprano::BackendSettings() << Soprano::BackendSetting(Soprano::BackendOptionStorageDir, m_storageDir->name()) ); + QVERIFY( m_model ); + + // DataManagementModel relies on the ussage of a NRLModel in the storage service + m_nrlModel = new Soprano::NRLModel(m_model); + m_classAndPropertyTree = new Nepomuk::ClassAndPropertyTree(this); + m_dmModel = new Nepomuk::DataManagementModel(m_classAndPropertyTree, m_nrlModel); +} + +void DataManagementModelTest::cleanupTestCase() +{ + delete m_dmModel; + delete m_nrlModel; + delete m_model; + delete m_storageDir; + delete m_classAndPropertyTree; +} + +void DataManagementModelTest::init() +{ + resetModel(); +} + + +void DataManagementModelTest::testAddProperty() +{ + // we start by simply adding a property + m_dmModel->addProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/string"), QVariantList() << QVariant(QLatin1String("foobar")), QLatin1String("Testapp")); + + QVERIFY(!m_dmModel->lastError()); + + // check that the actual data is there + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + + // check that the app resource has been created with its corresponding graphs + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { " + "graph ?g { ?r a %1 . ?r %2 %3 . } . " + "graph ?mg { ?g a %4 . ?mg a %5 . ?mg %6 ?g . } . }") + .arg(Soprano::Node::resourceToN3(NAO::Agent()), + Soprano::Node::resourceToN3(NAO::identifier()), + Soprano::Node::literalToN3(QLatin1String("Testapp")), + Soprano::Node::resourceToN3(NRL::InstanceBase()), + Soprano::Node::resourceToN3(NRL::GraphMetadata()), + Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor())), + Soprano::Query::QueryLanguageSparql).boolValue()); + + // check that we have an InstanceBase with a GraphMetadata graph + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { " + "graph ?g { <nepomuk:/res/A> <prop:/string> %1 . } . " + "graph ?mg { ?g a %2 . ?mg a %3 . ?mg %4 ?g . } . " + "}") + .arg(Soprano::Node::literalToN3(QLatin1String("foobar")), + Soprano::Node::resourceToN3(NRL::InstanceBase()), + Soprano::Node::resourceToN3(NRL::GraphMetadata()), + Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor())), + Soprano::Query::QueryLanguageSparql).boolValue()); + + // check the number of graphs (two for the app, two for the actual data, and one for the ontology) + QCOMPARE(m_model->listContexts().allElements().count(), 5); + + + // + // add another property value on top of the existing one + // + m_dmModel->addProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/string"), QVariantList() << QVariant(QLatin1String("hello world")), QLatin1String("Testapp")); + + // verify the values + QCOMPARE(m_model->listStatements(QUrl("nepomuk:/res/A"), QUrl("prop:/string"), Node()).allStatements().count(), 2); + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")))); + + // check that we only have one agent instance + QCOMPARE(m_model->listStatements(Node(), RDF::type(), NAO::Agent()).allStatements().count(), 1); + + // + // rewrite the same property with the same app + // + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + m_dmModel->addProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/string"), QVariantList() << QVariant(QLatin1String("hello world")), QLatin1String("Testapp")); + + // nothing should have changed + QCOMPARE(existingStatements, Soprano::Graph(m_model->listStatements().allStatements())); + + + // + // rewrite the same property with another app + // + m_dmModel->addProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/string"), QVariantList() << QVariant(QLatin1String("hello world")), QLatin1String("Otherapp")); + + // there should only be the new app, nothing else + // thus, all previous statements need to be there + foreach(const Statement& s, existingStatements.toList()) { + QVERIFY(m_model->containsStatement(s)); + } + + + // plus the new app + existingStatements.addStatements( + m_model->executeQuery(QString::fromLatin1("select ?g ?s ?p ?o where { graph ?g { ?s ?p ?o . } . filter(bif:exists((select (1) where { graph ?g { ?a a %1 . ?a %2 %3 . } . })))}") + .arg(Soprano::Node::resourceToN3(NAO::Agent()), + Soprano::Node::resourceToN3(NAO::identifier()), + Soprano::Node::literalToN3(QLatin1String("Otherapp"))), + Soprano::Query::QueryLanguageSparql) + .iterateStatementsFromBindings(QLatin1String("s"), QLatin1String("p"), QLatin1String("o"), QLatin1String("g")) + .allStatements() + + m_model->executeQuery(QString::fromLatin1("select ?g ?s ?p ?o where { graph ?g { ?s ?p ?o . } . filter(bif:exists((select (1) where { graph ?gg { ?a a %1 . ?a %2 %3 . } . ?g %4 ?gg . })))}") + .arg(Soprano::Node::resourceToN3(NAO::Agent()), + Soprano::Node::resourceToN3(NAO::identifier()), + Soprano::Node::literalToN3(QLatin1String("Otherapp")), + Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor())), + Soprano::Query::QueryLanguageSparql) + .iterateStatementsFromBindings(QLatin1String("s"), QLatin1String("p"), QLatin1String("o"), QLatin1String("g")) + .allStatements() + + m_model->listStatements(Node(), NAO::maintainedBy(), Node(), Node()).allStatements() + ); + + QCOMPARE(existingStatements, Soprano::Graph(m_model->listStatements().allStatements())); + + QVERIFY(!haveTrailingGraphs()); +} + +// test that creating a resource by adding a property on its URI properly sets metadata +void DataManagementModelTest::testAddProperty_createRes() +{ + // we create a new res by simply adding a property to it + m_dmModel->addProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("Testapp")); + + // now the newly created resource should have all the metadata a resource needs to have + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), NAO::created(), Node())); + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), NAO::lastModified(), Node())); + + // and both created and last modification date should be similar + QCOMPARE(m_model->listStatements(QUrl("nepomuk:/res/A"), NAO::created(), Node()).iterateObjects().allNodes().first(), + m_model->listStatements(QUrl("nepomuk:/res/A"), NAO::lastModified(), Node()).iterateObjects().allNodes().first()); + + QVERIFY(!haveTrailingGraphs()); +} + + +void DataManagementModelTest::testAddProperty_cardinality() +{ + // adding the same value twice in one call should result in one insert. This also includes the cardinality check + m_dmModel->addProperty(QList<QUrl>() << QUrl("nepomuk:/res/AA"), QUrl("prop:/res_c1"), QVariantList() << QVariant(QUrl("nepomuk:/res/B")) << QVariant(QUrl("nepomuk:/res/B")), QLatin1String("Testapp")); + QVERIFY(!m_dmModel->lastError()); + QCOMPARE(m_model->listStatements(QUrl("nepomuk:/res/AA"), QUrl("prop:/res_c1"), QUrl("nepomuk:/res/B")).allStatements().count(), 1); + + // we now add two values for a property with cardinality 1 + m_dmModel->addProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/res_c1"), QVariantList() << QVariant(QUrl("nepomuk:/res/B")) << QVariant(QUrl("nepomuk:/res/C")), QLatin1String("Testapp")); + QVERIFY(m_dmModel->lastError()); + + m_dmModel->addProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/res_c1"), QVariantList() << QVariant(QUrl("nepomuk:/res/B")), QLatin1String("Testapp")); + m_dmModel->addProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/res_c1"), QVariantList() << QVariant(QUrl("nepomuk:/res/C")), QLatin1String("Testapp")); + + // the second call needs to fail + QVERIFY(m_dmModel->lastError()); + + QVERIFY(!haveTrailingGraphs()); +} + + +void DataManagementModelTest::testAddProperty_file() +{ + QTemporaryFile fileA; + fileA.open(); + QTemporaryFile fileB; + fileB.open(); + QTemporaryFile fileC; + fileC.open(); + + m_dmModel->addProperty(QList<QUrl>() << QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/string"), QVariantList() << QVariant(QLatin1String("foobar")), QLatin1String("Testapp")); + + // make sure the nie:url relation has been created + QVERIFY(m_model->containsAnyStatement(Node(), NIE::url(), QUrl::fromLocalFile(fileA.fileName()))); + QVERIFY(!m_model->containsAnyStatement(QUrl::fromLocalFile(fileA.fileName()), Node(), Node())); + + // get the resource uri + const QUrl fileAResUri = m_model->listStatements(Node(), NIE::url(), QUrl::fromLocalFile(fileA.fileName())).allStatements().first().subject().uri(); + + // make sure the resource is a file + QVERIFY(m_model->containsAnyStatement(fileAResUri, RDF::type(), NFO::FileDataObject())); + + // make sure the actual value is there + QVERIFY(m_model->containsAnyStatement(fileAResUri, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + + + // add relation from file to file + m_dmModel->addProperty(QList<QUrl>() << QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/res"), QVariantList() << QVariant(QUrl::fromLocalFile(fileB.fileName())), QLatin1String("Testapp")); + + // make sure the nie:url relation has been created + QVERIFY(m_model->containsAnyStatement(Node(), NIE::url(), QUrl::fromLocalFile(fileB.fileName()))); + QVERIFY(!m_model->containsAnyStatement(QUrl::fromLocalFile(fileB.fileName()), Node(), Node())); + + // get the resource uri + const QUrl fileBResUri = m_model->listStatements(Node(), NIE::url(), QUrl::fromLocalFile(fileB.fileName())).allStatements().first().subject().uri(); + + // make sure the resource is a file + QVERIFY(m_model->containsAnyStatement(fileBResUri, RDF::type(), NFO::FileDataObject())); + + // make sure the actual value is there + QVERIFY(m_model->containsAnyStatement(fileAResUri, QUrl("prop:/res"), fileBResUri)); + + + // add the same relation but with another app + m_dmModel->addProperty(QList<QUrl>() << QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/res"), QVariantList() << QVariant(QUrl::fromLocalFile(fileB.fileName())), QLatin1String("Otherapp")); + + // there is only one prop:/res relation defined + QCOMPARE(m_model->listStatements(Node(), QUrl("prop:/res"), Node()).allStatements().count(), 1); + + // we now add two values for a property with cardinality 1 + m_dmModel->addProperty(QList<QUrl>() << QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/res_c1"), QVariantList() << QVariant(QUrl::fromLocalFile(fileB.fileName())), QLatin1String("Testapp")); + m_dmModel->addProperty(QList<QUrl>() << QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/res_c1"), QVariantList() << QVariant(QUrl::fromLocalFile(fileC.fileName())), QLatin1String("Testapp")); + + // the second call needs to fail + QVERIFY(m_dmModel->lastError()); + + + // test adding a property to both the file and the resource URI. The result should be the exact same as doing it with only one of them + m_dmModel->addProperty(QList<QUrl>() << fileAResUri << QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/string"), QVariantList() << QVariant(QLatin1String("Whatever")), QLatin1String("Testapp")); + + QCOMPARE(m_model->listStatements(fileAResUri, QUrl("prop:/string"), LiteralValue(QLatin1String("Whatever"))).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(Node(), NIE::url(), QUrl::fromLocalFile(fileA.fileName())).allStatements().count(), 1); + + // test the same with the file as object + m_dmModel->addProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/res"), QVariantList() << QVariant(KUrl(fileA.fileName())) << QVariant(fileAResUri), QLatin1String("Testapp")); + + QCOMPARE(m_model->listStatements(QUrl("nepomuk:/res/A"), QUrl("prop:/res"), fileAResUri).allStatements().count(), 1); + QVERIFY(!m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), QUrl("prop:/res"), QUrl::fromLocalFile(fileA.fileName()))); + QCOMPARE(m_model->listStatements(Node(), NIE::url(), QUrl::fromLocalFile(fileA.fileName())).allStatements().count(), 1); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testAddProperty_invalidFile() +{ + KTemporaryFile f1; + QVERIFY( f1.open() ); + QUrl f1Url( f1.fileName() ); + //f1Url.setScheme("file"); + + m_dmModel->addProperty( QList<QUrl>() << f1Url, RDF::type(), QVariantList() << NAO::Tag(), QLatin1String("testapp") ); + + // There should be some error that '' protocol doesn't exist + QVERIFY(m_dmModel->lastError()); + + // The support for plain file paths is in the DBus adaptor through the usage of KUrl. If + // local path support is neccesary on the level of the model, simply use KUrl which + // will automatically add the file:/ protocol to local paths. + QVERIFY( !m_model->containsAnyStatement( Node(), NIE::url(), f1Url ) ); + + m_dmModel->addProperty( QList<QUrl>() << QUrl("file:///Blah"), NIE::comment(), + QVariantList() << "Comment", QLatin1String("testapp") ); + + // There should be some error as '/Blah' does not exist + QVERIFY(m_dmModel->lastError()); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testAddProperty_invalid_args() +{ + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // empty resource list + m_dmModel->addProperty(QList<QUrl>(), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty property uri + m_dmModel->addProperty(QList<QUrl>() << QUrl("res:/A"), QUrl(), QVariantList() << 42, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty value list + m_dmModel->addProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/int"), QVariantList(), QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty app + m_dmModel->addProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/int"), QVariantList() << 42, QString()); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // invalid range + m_dmModel->addProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/int"), QVariantList() << QLatin1String("foobar"), QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // protected properties 1 + m_dmModel->addProperty(QList<QUrl>() << QUrl("res:/A"), NAO::created(), QVariantList() << QDateTime::currentDateTime(), QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // protected properties 2 + m_dmModel->addProperty(QList<QUrl>() << QUrl("res:/A"), NAO::lastModified(), QVariantList() << QDateTime::currentDateTime(), QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // make sure we cannot add anything to non-existing files + const QUrl nonExistingFileUrl("file:///a/file/that/is/very/unlikely/to/exist"); + m_dmModel->addProperty(QList<QUrl>() << nonExistingFileUrl, QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // non-existing file as object + m_dmModel->addProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/res"), QVariantList() << nonExistingFileUrl, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // TODO: try setting protected properties like nie:url, nfo:fileName, nie:isPartOf (only applies to files) +} + +void DataManagementModelTest::testAddProperty_protectedTypes() +{ + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // property + m_dmModel->addProperty(QList<QUrl>() << QUrl("prop:/res"), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // class + m_dmModel->addProperty(QList<QUrl>() << NRL::Graph(), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // graph + m_dmModel->addProperty(QList<QUrl>() << QUrl("graph:/onto"), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +void DataManagementModelTest::testAddProperty_akonadi() +{ + // create our app + const QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // create the graph + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + QUrl resA("nepomuk:/res/A"); + QUrl akonadiUrl("akonadi:id=5"); + m_model->addStatement( resA, RDF::type(), NIE::DataObject(), g1 ); + m_model->addStatement( resA, NIE::url(), akonadiUrl, g1 ); + + // add a property using the akonadi URL + // the tricky thing here is that nao:identifier does not have a range! + m_dmModel->addProperty( QList<QUrl>() << akonadiUrl, + NAO::identifier(), + QVariantList() << QString("akon"), + QLatin1String("AppA") ); + + QVERIFY(!m_dmModel->lastError()); + + // check that the akonadi URL has been resolved to the resource URI + QVERIFY(m_model->containsAnyStatement( resA, NAO::identifier(), Soprano::Node() )); + + // check that the property has the desired value + QVERIFY(m_model->containsAnyStatement( resA, NAO::identifier(), LiteralValue("akon") )); +} + +void DataManagementModelTest::testSetProperty() +{ + // adding the most basic property + m_dmModel->setProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/string"), QVariantList() << QVariant(QLatin1String("foobar")), QLatin1String("Testapp")); + + QVERIFY(!m_dmModel->lastError()); + + // check that the actual data is there + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + + // check that the app resource has been created with its corresponding graphs + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { " + "graph ?g { ?r a %1 . ?r %2 %3 . } . " + "graph ?mg { ?g a %4 . ?mg a %5 . ?mg %6 ?g . } . }") + .arg(Soprano::Node::resourceToN3(NAO::Agent()), + Soprano::Node::resourceToN3(NAO::identifier()), + Soprano::Node::literalToN3(QLatin1String("Testapp")), + Soprano::Node::resourceToN3(NRL::InstanceBase()), + Soprano::Node::resourceToN3(NRL::GraphMetadata()), + Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor())), + Soprano::Query::QueryLanguageSparql).boolValue()); + + // check that we have an InstanceBase with a GraphMetadata graph + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { " + "graph ?g { <nepomuk:/res/A> <prop:/string> %1 . } . " + "graph ?mg { ?g a %2 . ?mg a %3 . ?mg %4 ?g . } . " + "}") + .arg(Soprano::Node::literalToN3(QLatin1String("foobar")), + Soprano::Node::resourceToN3(NRL::InstanceBase()), + Soprano::Node::resourceToN3(NRL::GraphMetadata()), + Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor())), + Soprano::Query::QueryLanguageSparql).boolValue()); + + // check the number of graphs (two for the app, two for the actual data, and one for the ontology) + QCOMPARE(m_model->listContexts().allElements().count(), 5); + + QVERIFY(!haveTrailingGraphs()); +} + +// test that creating a resource by setting a property on its URI properly sets metadata +void DataManagementModelTest::testSetProperty_createRes() +{ + // we create a new res by simply adding a property to it + m_dmModel->setProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("Testapp")); + + // now the newly created resource should have all the metadata a resource needs to have + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), NAO::created(), Node())); + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), NAO::lastModified(), Node())); + + // and both created and last modification date should be similar + QCOMPARE(m_model->listStatements(QUrl("nepomuk:/res/A"), NAO::created(), Node()).iterateObjects().allNodes().first(), + m_model->listStatements(QUrl("nepomuk:/res/A"), NAO::lastModified(), Node()).iterateObjects().allNodes().first()); + + QVERIFY(!haveTrailingGraphs()); +} + + +void DataManagementModelTest::testSetProperty_overwrite() +{ + // create an app graph + const QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // add a resource with 2 properties + QUrl mg; + const QUrl g = m_nrlModel->createGraph(NRL::InstanceBase(), &mg); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + + m_model->addStatement(g, NAO::maintainedBy(), QUrl("app:/A"), mg); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/A"), mg2); + + m_model->addStatement(QUrl("res:/A"), RDF::type(), NAO::Tag(), g); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42), g); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(42), g); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int3"), LiteralValue(42), g2); + + QVERIFY(!haveTrailingGraphs()); + + + // + // now overwrite the one property + // + m_dmModel->setProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/int"), QVariantList() << 12, QLatin1String("testapp")); + + // now the model should have replaced the old value and added the new value in a new graph + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(12))); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42))); + + // a new graph + QCOMPARE(m_model->listStatements(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(12)).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(42)).allStatements().count(), 1); + QVERIFY(m_model->listStatements(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(12)).allStatements().first().context().uri() != g); + QVERIFY(m_model->listStatements(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(42)).allStatements().first().context().uri() == g); + + // the testapp Agent as maintainer of the new graph + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { " + "graph ?g { <res:/A> <prop:/int> %1 . } . " + "graph ?mg { ?g a %2 . ?mg a %3 . ?mg %4 ?g . } . " + "?g %5 ?a . ?a %6 %7 . " + "}") + .arg(Soprano::Node::literalToN3(12), + Soprano::Node::resourceToN3(NRL::InstanceBase()), + Soprano::Node::resourceToN3(NRL::GraphMetadata()), + Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor()), + Soprano::Node::resourceToN3(NAO::maintainedBy()), + Soprano::Node::resourceToN3(NAO::identifier()), + Soprano::Node::literalToN3(QLatin1String("testapp"))), + Soprano::Query::QueryLanguageSparql).boolValue()); + + QVERIFY(!haveTrailingGraphs()); + + + // + // Rewrite the same value + // + m_dmModel->setProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/int2"), QVariantList() << 42, QLatin1String("testapp")); + + // the value should only be there once + QCOMPARE(m_model->listStatements(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(42)).allStatements().count(), 1); + + // in a new graph since the old one still contains the type + QVERIFY(m_model->listStatements(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(42)).allStatements().first().context().uri() != g); + + // there should be one graph now which contains the value and which is marked as being maintained by both apps + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { " + "graph ?g { <res:/A> <prop:/int2> %1 . } . " + "graph ?mg { ?g a %2 . ?mg a %3 . ?mg %4 ?g . } . " + "?g %5 ?a1 . ?a1 %6 %7 . " + "?g %5 ?a2 . ?a2 %6 %8 . " + "}") + .arg(Soprano::Node::literalToN3(42), + Soprano::Node::resourceToN3(NRL::InstanceBase()), + Soprano::Node::resourceToN3(NRL::GraphMetadata()), + Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor()), + Soprano::Node::resourceToN3(NAO::maintainedBy()), + Soprano::Node::resourceToN3(NAO::identifier()), + Soprano::Node::literalToN3(QLatin1String("testapp")), + Soprano::Node::literalToN3(QLatin1String("A"))), + Soprano::Query::QueryLanguageSparql).boolValue()); + + // + // Now we rewrite the type which should result in reusing the old graph but with the new app as maintainer + // + m_dmModel->setProperty(QList<QUrl>() << QUrl("res:/A"), RDF::type(), QVariantList() << NAO::Tag(), QLatin1String("testapp")); + + // the type should only be define once + QCOMPARE(m_model->listStatements(QUrl("res:/A"), RDF::type(), NAO::Tag()).allStatements().count(), 1); + + // the graph should be the same + QVERIFY(m_model->listStatements(QUrl("res:/A"), RDF::type(), NAO::Tag()).allStatements().first().context().uri() == g); + + // the new app should be listed as maintainer as should be the old one + QCOMPARE(m_model->listStatements(g, NAO::maintainedBy(), Node(), mg).allStatements().count(), 2); + + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { " + "graph ?g { <res:/A> a %1 . } . " + "graph ?mg { ?g a %2 . ?mg a %3 . ?mg %4 ?g . } . " + "?g %5 ?a1 . ?a1 %6 %7 . " + "?g %5 ?a2 . ?a2 %6 %8 . " + "}") + .arg(Soprano::Node::resourceToN3(NAO::Tag()), + Soprano::Node::resourceToN3(NRL::InstanceBase()), + Soprano::Node::resourceToN3(NRL::GraphMetadata()), + Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor()), + Soprano::Node::resourceToN3(NAO::maintainedBy()), + Soprano::Node::resourceToN3(NAO::identifier()), + Soprano::Node::literalToN3(QLatin1String("testapp")), + Soprano::Node::literalToN3(QLatin1String("A"))), + Soprano::Query::QueryLanguageSparql).boolValue()); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testSetProperty_invalid_args() +{ + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // empty resource list + m_dmModel->setProperty(QList<QUrl>(), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty property uri + m_dmModel->setProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl(), QVariantList() << 42, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty value list + m_dmModel->setProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/int"), QVariantList(), QLatin1String("testapp")); + + // the call should NOT have failed + QVERIFY(!m_dmModel->lastError()); + + // but nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty app + m_dmModel->setProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/int"), QVariantList() << 42, QString()); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // invalid range + m_dmModel->setProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/int"), QVariantList() << QLatin1String("foobar"), QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // make sure we cannot add anything to non-existing files + const QUrl nonExistingFileUrl("file:///a/file/that/is/very/unlikely/to/exist"); + m_dmModel->setProperty(QList<QUrl>() << nonExistingFileUrl, QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // non-existing file as object + m_dmModel->setProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), QUrl("prop:/res"), QVariantList() << nonExistingFileUrl, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +void DataManagementModelTest::testSetProperty_nieUrl1() +{ + // setting nie:url if it is not there yet should result in a normal setProperty including graph creation + m_dmModel->setProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), NIE::url(), QVariantList() << QUrl("file:///tmp/A"), QLatin1String("testapp")); + + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), NIE::url(), QUrl("file:///tmp/A"))); + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), RDF::type(), NFO::FileDataObject())); + + // remember the graph since it should not change later on + const QUrl nieUrlGraph = m_model->listStatements(QUrl("nepomuk:/res/A"), NIE::url(), QUrl("file:///tmp/A")).allStatements().first().context().uri(); + + QVERIFY(!haveTrailingGraphs()); + + + // we reset the URL + m_dmModel->setProperty(QList<QUrl>() << QUrl("nepomuk:/res/A"), NIE::url(), QVariantList() << QUrl("file:///tmp/B"), QLatin1String("testapp")); + + // the url should have changed + QVERIFY(!m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), NIE::url(), QUrl("file:///tmp/A"))); + QVERIFY(m_model->containsAnyStatement(QUrl("nepomuk:/res/A"), NIE::url(), QUrl("file:///tmp/B"))); + + // the graph should have been kept + QCOMPARE(m_model->listStatements(QUrl("nepomuk:/res/A"), NIE::url(), Node()).allStatements().first().context().uri(), nieUrlGraph); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testSetProperty_nieUrl2() +{ + KTempDir* dir = createNieUrlTestData(); + + // change the nie:url of one of the top level dirs + const QUrl newDir1Url = QUrl(QLatin1String("file://") + dir->name() + QLatin1String("dir1-new")); + + // we first need to move the file, otherwise the file check in the dms kicks in + QVERIFY(QFile::rename(dir->name() + QLatin1String("dir1"), newDir1Url.toLocalFile())); + + // now update the database + m_dmModel->setProperty(QList<QUrl>() << QUrl("res:/dir1"), NIE::url(), QVariantList() << newDir1Url, QLatin1String("testapp")); + + // this should have updated the nie:urls of all children, too + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir1"), NIE::url(), newDir1Url)); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir1"), NFO::fileName(), LiteralValue(QLatin1String("dir1-new")))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir11"), NIE::url(), QUrl(newDir1Url.toString() + QLatin1String("/dir11")))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir12"), NIE::url(), QUrl(newDir1Url.toString() + QLatin1String("/dir12")))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir13"), NIE::url(), QUrl(newDir1Url.toString() + QLatin1String("/dir13")))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/file11"), NIE::url(), QUrl(newDir1Url.toString() + QLatin1String("/file11")))); + + delete dir; + + QVERIFY(!haveTrailingGraphs()); +} + +// the same test as above only using the file URL +void DataManagementModelTest::testSetProperty_nieUrl3() +{ + KTempDir* dir = createNieUrlTestData(); + + // change the nie:url of one of the top level dirs + const QUrl oldDir1Url = QUrl(QLatin1String("file://") + dir->name() + QLatin1String("dir1")); + const QUrl newDir1Url = QUrl(QLatin1String("file://") + dir->name() + QLatin1String("dir1-new")); + + // we first need to move the file, otherwise the file check in the dms kicks in + QVERIFY(QFile::rename(oldDir1Url.toLocalFile(), newDir1Url.toLocalFile())); + + // now update the database + m_dmModel->setProperty(QList<QUrl>() << oldDir1Url, NIE::url(), QVariantList() << newDir1Url, QLatin1String("testapp")); + + // this should have updated the nie:urls of all children, too + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir1"), NIE::url(), newDir1Url)); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir1"), NFO::fileName(), LiteralValue(QLatin1String("dir1-new")))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir11"), NIE::url(), QUrl(newDir1Url.toString() + QLatin1String("/dir11")))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir12"), NIE::url(), QUrl(newDir1Url.toString() + QLatin1String("/dir12")))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir13"), NIE::url(), QUrl(newDir1Url.toString() + QLatin1String("/dir13")))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/file11"), NIE::url(), QUrl(newDir1Url.toString() + QLatin1String("/file11")))); + + delete dir; + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testSetProperty_nieUrl4() +{ + KTempDir* dir = createNieUrlTestData(); + + // move one of the dirs to a new parent + const QUrl oldDir121Url = QUrl(QLatin1String("file://") + dir->name() + QLatin1String("dir1/dir12/dir121")); + const QUrl newDir121Url = QUrl(QLatin1String("file://") + dir->name() + QLatin1String("dir1/dir12/dir121-new")); + + // we first need to move the file, otherwise the file check in the dms kicks in + QVERIFY(QFile::rename(oldDir121Url.toLocalFile(), newDir121Url.toLocalFile())); + + // now update the database + m_dmModel->setProperty(QList<QUrl>() << QUrl("res:/dir121"), NIE::url(), QVariantList() << newDir121Url, QLatin1String("testapp")); + + // the url + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir121"), NIE::url(), newDir121Url)); + + // the child file + QVERIFY(m_model->containsAnyStatement(QUrl("res:/file1211"), NIE::url(), QUrl(newDir121Url.toString() + QLatin1String("/file1211")))); + + // the nie:isPartOf relationship should have been updated, too + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir121"), NIE::isPartOf(), QUrl("res:/dir12"))); + + QVERIFY(!haveTrailingGraphs()); +} + +// the same test as above only using the file URL +void DataManagementModelTest::testSetProperty_nieUrl5() +{ + KTempDir* dir = createNieUrlTestData(); + + // move one of the dirs to a new parent + const QUrl oldDir121Url = QUrl(QLatin1String("file://") + dir->name() + QLatin1String("dir1/dir12/dir121")); + const QUrl newDir121Url = QUrl(QLatin1String("file://") + dir->name() + QLatin1String("dir2/dir121")); + + // we first need to move the file, otherwise the file check in the dms kicks in + QVERIFY(QFile::rename(oldDir121Url.toLocalFile(), newDir121Url.toLocalFile())); + + // now update the database + m_dmModel->setProperty(QList<QUrl>() << oldDir121Url, NIE::url(), QVariantList() << newDir121Url, QLatin1String("testapp")); + + // the url + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir121"), NIE::url(), newDir121Url)); + + // the child file + QVERIFY(m_model->containsAnyStatement(QUrl("res:/file1211"), NIE::url(), QUrl(newDir121Url.toString() + QLatin1String("/file1211")))); + + // the nie:isPartOf relationship should have been updated, too + QVERIFY(m_model->containsAnyStatement(QUrl("res:/dir121"), NIE::isPartOf(), QUrl("res:/dir2"))); + + QVERIFY(!haveTrailingGraphs()); +} + +// test support for any other URL scheme which already exists as nie:url (This is what libnepomuk does support) +void DataManagementModelTest::testSetProperty_nieUrl6() +{ + // create a resource that has a URL + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase()); + const QUrl url("http://nepomuk.kde.org/"); + + m_model->addStatement(QUrl("res:/A"), NIE::url(), url, g1); + + + // use the url to set a property + m_dmModel->setProperty(QList<QUrl>() << url, QUrl("prop:/int"), QVariantList() << 42, QLatin1String("A")); + + // check that the property has been added to the resource + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42))); + + // check that no new resource has been created + QCOMPARE(m_model->listStatements(Node(), QUrl("prop:/int"), LiteralValue(42)).allElements().count(), 1); +} + +void DataManagementModelTest::testSetProperty_protectedTypes() +{ + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // property + m_dmModel->setProperty(QList<QUrl>() << QUrl("prop:/res"), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // class + m_dmModel->setProperty(QList<QUrl>() << NRL::Graph(), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // graph + m_dmModel->setProperty(QList<QUrl>() << QUrl("graph:/onto"), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +// make sure we reuse legacy resource URIs +void DataManagementModelTest::testSetProperty_legacyData() +{ + // create some legacy data + QTemporaryFile file; + file.open(); + const KUrl url(file.fileName()); + + const QUrl g = m_nrlModel->createGraph(NRL::InstanceBase()); + + m_model->addStatement(url, QUrl("prop:/int"), LiteralValue(42), g); + + // set some data with the url + m_dmModel->setProperty(QList<QUrl>() << url, QUrl("prop:/int"), QVariantList() << 2, QLatin1String("A")); + + // make sure the resource has changed + QCOMPARE(m_model->listStatements(url, QUrl("prop:/int"), Node()).allElements().count(), 1); + QCOMPARE(m_model->listStatements(url, QUrl("prop:/int"), Node()).allElements().first().object().literal(), LiteralValue(2)); +} + +void DataManagementModelTest::testRemoveProperty() +{ + const int cleanCount = m_model->statementCount(); + + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/A"), NAO::lastModified(), LiteralValue(QDateTime::currentDateTime()), g1); + + m_dmModel->removeProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/string"), QVariantList() << QLatin1String("hello world"), QLatin1String("Testapp")); + + QVERIFY(!m_dmModel->lastError()); + + // test that the data has been removed + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")))); + + // test that the mtime has been updated (and is thus in another graph) + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), NAO::lastModified(), Soprano::Node(), g1)); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), NAO::lastModified(), Soprano::Node())); + + // test that the other property value is still valid + QVERIFY(m_model->containsStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1)); + + QVERIFY(!haveTrailingGraphs()); + + + // step 2: remove the second value + m_dmModel->removeProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/string"), QVariantList() << QLatin1String("foobar"), QLatin1String("Testapp")); + + QVERIFY(!m_dmModel->lastError()); + + // the property should be gone entirely + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/string"), Soprano::Node())); + + // even the resource should be gone since the NAO mtime does not count as a "real" property + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Soprano::Node(), Soprano::Node())); + + // nothing except the ontology and the Testapp Agent should be left + QCOMPARE(m_model->statementCount(), cleanCount+6); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testRemoveProperty_file() +{ + QTemporaryFile fileA; + fileA.open(); + QTemporaryFile fileB; + fileB.open(); + + // prepare some test data + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + + m_model->addStatement(QUrl("res:/A"), NIE::url(), QUrl::fromLocalFile(fileA.fileName()), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("whatever")), g1); + + m_model->addStatement(QUrl("res:/B"), NIE::url(), QUrl::fromLocalFile(fileB.fileName()), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/C"), g1); + + + + // now we remove one value via the file URL + m_dmModel->removeProperty(QList<QUrl>() << QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/string"), QVariantList() << QLatin1String("hello world"), QLatin1String("Testapp")); + + QCOMPARE(m_model->listStatements(QUrl("res:/A"), QUrl("prop:/string"), Node()).allStatements().count(), 2); + QCOMPARE(m_model->listStatements(QUrl("res:/A"), NIE::url(), Node()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(QUrl("res:/A"), NIE::url(), QUrl::fromLocalFile(fileA.fileName())).allStatements().count(), 1); + + + // test the same with a file URL value + m_dmModel->removeProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/res"), QVariantList() << QUrl::fromLocalFile(fileB.fileName()), QLatin1String("Testapp")); + + QCOMPARE(m_model->listStatements(QUrl("res:/A"), QUrl("prop:/res"), Node()).allStatements().count(), 1); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testRemoveProperty_invalid_args() +{ + // prepare some test data + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/A"), NAO::lastModified(), LiteralValue(QDateTime::currentDateTime()), g1); + + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // empty resource list + m_dmModel->removeProperty(QList<QUrl>(), QUrl("prop:/string"), QVariantList() << QLatin1String("foobar"), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // resource list with empty URL + m_dmModel->removeProperty(QList<QUrl>() << QUrl() << QUrl("res:/A"), QUrl("prop:/string"), QVariantList() << QLatin1String("foobar"), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty property + m_dmModel->removeProperty(QList<QUrl>() << QUrl("res:/A"), QUrl(), QVariantList() << QLatin1String("foobar"), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty values + m_dmModel->removeProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/string"), QVariantList(), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty app + m_dmModel->removeProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/string"), QVariantList() << QLatin1String("foobar"), QString()); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // invalid value type + m_dmModel->removeProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/int"), QVariantList() << QLatin1String("foobar"), QString("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // protected property 1 + m_dmModel->removeProperty(QList<QUrl>() << QUrl("res:/A"), NAO::created(), QVariantList() << QDateTime::currentDateTime(), QString("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // protected property 2 + m_dmModel->removeProperty(QList<QUrl>() << QUrl("res:/A"), NAO::lastModified(), QVariantList() << QDateTime::currentDateTime(), QString("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // make sure we cannot add anything to non-existing files + const QUrl nonExistingFileUrl("file:///a/file/that/is/very/unlikely/to/exist"); + m_dmModel->removeProperty(QList<QUrl>() << nonExistingFileUrl, QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // non-existing file as object + m_dmModel->removeProperty(QList<QUrl>() << QUrl("res:/A"), QUrl("prop:/res"), QVariantList() << nonExistingFileUrl, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +// it is not allowed to change properties, classes or graphs through this API +void DataManagementModelTest::testRemoveProperty_protectedTypes() +{ + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // property + m_dmModel->removeProperty(QList<QUrl>() << QUrl("prop:/res"), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // class + m_dmModel->removeProperty(QList<QUrl>() << NRL::Graph(), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // graph + m_dmModel->removeProperty(QList<QUrl>() << QUrl("graph:/onto"), QUrl("prop:/int"), QVariantList() << 42, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +void DataManagementModelTest::testRemoveProperties() +{ + QTemporaryFile fileA; + fileA.open(); + QTemporaryFile fileB; + fileB.open(); + + // prepare some test data + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + + m_model->addStatement(QUrl("res:/A"), NIE::url(), QUrl::fromLocalFile(fileA.fileName()), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("whatever")), g1); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(12), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(2), g1); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(12), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(2), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("whatever")), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/int"), LiteralValue(6), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/int"), LiteralValue(12), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/int"), LiteralValue(2), g1); + + m_model->addStatement(QUrl("res:/B"), NIE::url(), QUrl::fromLocalFile(fileB.fileName()), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/C"), g1); + + + // test removing one property from one resource + m_dmModel->removeProperties(QList<QUrl>() << QUrl("res:/A"), QList<QUrl>() << QUrl("prop:/string"), QLatin1String("testapp")); + + // check that all values are gone + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/string"), Node())); + + // check that all other values from that prop are still there + QCOMPARE(m_model->listStatements(Node(), QUrl("prop:/string"), Node()).allStatements().count(), 3); + QCOMPARE(m_model->listStatements(QUrl("res:/B"), QUrl("prop:/string"), Node()).allStatements().count(), 3); + + + // test removing a property from more than one resource + m_dmModel->removeProperties(QList<QUrl>() << QUrl("res:/A") << QUrl("res:/B"), QList<QUrl>() << QUrl("prop:/int"), QLatin1String("testapp")); + + // check that all values are gone + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int"), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), QUrl("prop:/int"), Node())); + + // check that other properties from res:/B are still there + QCOMPARE(m_model->listStatements(QUrl("res:/B"), QUrl("prop:/string"), Node()).allStatements().count(), 3); + + + // test file URLs in the resources + m_dmModel->removeProperties(QList<QUrl>() << QUrl::fromLocalFile(fileA.fileName()), QList<QUrl>() << QUrl("prop:/int2"), QLatin1String("testapp")); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/int2"), Node())); + + // TODO: verify graphs + + QVERIFY(!haveTrailingGraphs()); +} + + +void DataManagementModelTest::testRemoveProperties_invalid_args() +{ + QTemporaryFile fileA; + fileA.open(); + QTemporaryFile fileB; + fileB.open(); + + // prepare some test data + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + + m_model->addStatement(QUrl("res:/A"), NIE::url(), QUrl::fromLocalFile(fileA.fileName()), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("whatever")), g1); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(12), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int"), LiteralValue(2), g1); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(12), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/int2"), LiteralValue(2), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("whatever")), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/int"), LiteralValue(6), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/int"), LiteralValue(12), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/int"), LiteralValue(2), g1); + + m_model->addStatement(QUrl("res:/B"), NIE::url(), QUrl::fromLocalFile(fileB.fileName()), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/C"), g1); + + + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // empty resource list + m_dmModel->removeProperties(QList<QUrl>(), QList<QUrl>() << QUrl("prop:/string"), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // resource list with empty url + m_dmModel->removeProperties(QList<QUrl>() << QUrl() << QUrl("res:/A"), QList<QUrl>() << QUrl("prop:/string"), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty property list + m_dmModel->removeProperties(QList<QUrl>() << QUrl("res:/A"), QList<QUrl>(), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // property list with empty url + m_dmModel->removeProperties(QList<QUrl>() << QUrl("res:/A"), QList<QUrl>() << QUrl("prop:/string") << QUrl(), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty app + m_dmModel->removeProperties(QList<QUrl>() << QUrl("res:/A"), QList<QUrl>() << QUrl("prop:/string"), QString()); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // protected property 1 + m_dmModel->removeProperties(QList<QUrl>() << QUrl("res:/A"), QList<QUrl>() << NAO::created(), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // protected property 2 + m_dmModel->removeProperties(QList<QUrl>() << QUrl("res:/A"), QList<QUrl>() << NAO::lastModified(), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // make sure we cannot add anything to non-existing files + const QUrl nonExistingFileUrl("file:///a/file/that/is/very/unlikely/to/exist"); + m_dmModel->removeProperties(QList<QUrl>() << nonExistingFileUrl, QList<QUrl>() << QUrl("prop:/int"), QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + + +void DataManagementModelTest::testRemoveProperties_protectedTypes() +{ + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // property + m_dmModel->removeProperties(QList<QUrl>() << QUrl("prop:/res"), QList<QUrl>() << QUrl("prop:/int"), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // class + m_dmModel->removeProperties(QList<QUrl>() << NRL::Graph(), QList<QUrl>() << QUrl("prop:/int"), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // graph + m_dmModel->removeProperties(QList<QUrl>() << QUrl("graph:/onto"), QList<QUrl>() << QUrl("prop:/int"), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements);} + +void DataManagementModelTest::testRemoveResources() +{ + QTemporaryFile fileA; + fileA.open(); + QTemporaryFile fileB; + fileB.open(); + + // prepare some test data + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + + m_model->addStatement(QUrl("res:/A"), NIE::url(), QUrl::fromLocalFile(fileA.fileName()), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("whatever")), g1); + + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/int"), LiteralValue(6), g2); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/int"), LiteralValue(12), g2); + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/int"), LiteralValue(2), g2); + m_model->addStatement(QUrl("res:/B"), NIE::url(), QUrl::fromLocalFile(fileB.fileName()), g2); + + + m_dmModel->removeResources(QList<QUrl>() << QUrl("res:/A"), Nepomuk::NoRemovalFlags, QLatin1String("testapp")); + + // verify that the resource is gone + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + + // verify that other resources were not touched + QCOMPARE(m_model->listStatements(QUrl("res:/B"), Node(), Node()).allStatements().count(), 4); + QCOMPARE(m_model->listStatements(QUrl("res:/C"), Node(), Node()).allStatements().count(), 2); + + // verify that removing resources by file URL works + m_dmModel->removeResources(QList<QUrl>() << QUrl::fromLocalFile(fileB.fileName()), Nepomuk::NoRemovalFlags, QLatin1String("testapp")); + + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), Node(), Node())); + + // verify that other resources were not touched + QCOMPARE(m_model->listStatements(QUrl("res:/C"), Node(), Node()).allStatements().count(), 2); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testRemoveResources_subresources() +{ + // create our apps (we use more than one since we also test that it is ignored) + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + // create the graphs + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + QUrl mg3; + const QUrl g3 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg3); + m_model->addStatement(g3, NAO::maintainedBy(), QUrl("app:/A"), mg3); + + // create the resource to delete + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + + // sub-resource 1: can be deleted + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + // sub-resource 2: can be deleted (is defined in another graph by the same app) + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/AA"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/AA"), g1); + m_model->addStatement(QUrl("res:/AA"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g3); + + // sub-resource 3: can be deleted although another res refs it (we also delete the other res) + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/C"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/C"), g1); + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/res"), QUrl("res:/C"), g1); + + // sub-resource 4: cannot be deleted since another res refs it + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/D"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/D"), g1); + m_model->addStatement(QUrl("res:/D"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/E"), QUrl("prop:/res"), QUrl("res:/D"), g1); + m_model->addStatement(QUrl("res:/E"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + // sub-resource 5: can be deleted although another app added properties + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/F"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/F"), g1); + m_model->addStatement(QUrl("res:/F"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/F"), QUrl("prop:/int"), LiteralValue(42), g2); + + // delete the resource + m_dmModel->removeResources(QList<QUrl>() << QUrl("res:/A"), Nepomuk::RemoveSubResoures, QLatin1String("A")); + + // this should have removed A, B and C + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/C"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/F"), Node(), Node())); + + // E and F need to be preserved + QCOMPARE(m_model->listStatements(QUrl("res:/D"), Node(), Node()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(QUrl("res:/E"), Node(), Node()).allStatements().count(), 2); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testRemoveResources_invalid_args() +{ + QTemporaryFile fileA; + fileA.open(); + + // prepare some test data + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + + m_model->addStatement(QUrl("res:/A"), NIE::url(), QUrl::fromLocalFile(fileA.fileName()), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("whatever")), g1); + + + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // empty resource list + m_dmModel->removeResources(QList<QUrl>(), Nepomuk::NoRemovalFlags, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // resource list with empty URL + m_dmModel->removeResources(QList<QUrl>() << QUrl("res:/A") << QUrl(), Nepomuk::NoRemovalFlags, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty app + m_dmModel->removeResources(QList<QUrl>() << QUrl("res:/A"), Nepomuk::NoRemovalFlags, QString()); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // non-existing file + const QUrl nonExistingFileUrl("file:///a/file/that/is/very/unlikely/to/exist"); + m_dmModel->removeResources(QList<QUrl>() << nonExistingFileUrl, Nepomuk::NoRemovalFlags, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +// make sure we do not allow to remove classes, properties, and graphs +void DataManagementModelTest::testRemoveResources_protectedTypes() +{ + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // property + m_dmModel->removeResources(QList<QUrl>() << QUrl("prop:/res"), Nepomuk::NoRemovalFlags, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // class + m_dmModel->removeResources(QList<QUrl>() << NRL::Graph(), Nepomuk::NoRemovalFlags, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // graph + m_dmModel->removeResources(QList<QUrl>() << QUrl("graph:/onto"), Nepomuk::NoRemovalFlags, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +// make sure the mtime of related resources is updated properly +void DataManagementModelTest::testRemoveResources_mtimeRelated() +{ + // first we create our apps and graphs (just to have some pseudo real data) + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + + const QDateTime date = QDateTime::currentDateTime(); + + + // now we create different resources + // A is the resource to be deleted + // B is related to A and its mtime needs update + // C is unrelated and no mtime change should occur + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/A"), NAO::lastModified(), LiteralValue(date), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + m_model->addStatement(QUrl("res:/B"), NAO::created(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/B"), NAO::lastModified(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/res"), QUrl("res:/A"), g2); + + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + m_model->addStatement(QUrl("res:/C"), NAO::created(), LiteralValue(date), g2); + m_model->addStatement(QUrl("res:/C"), NAO::lastModified(), LiteralValue(date), g2); + + + // now we remove res:/A + m_dmModel->removeResources(QList<QUrl>() << QUrl("res:/A"), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + + // now only the mtime of B should have changed + QCOMPARE(m_model->listStatements(QUrl("res:/B"), NAO::lastModified(), Node()).allElements().count(), 1); + QVERIFY(m_model->listStatements(QUrl("res:/B"), NAO::lastModified(), Node()).allElements().first().object().literal().toDateTime() > date); + + QCOMPARE(m_model->listStatements(QUrl("res:/C"), NAO::lastModified(), Node()).allElements().count(), 1); + QCOMPARE(m_model->listStatements(QUrl("res:/C"), NAO::lastModified(), Node()).allElements().first().object().literal().toDateTime(), date); +} + +// make sure we can remove data from non-existing files +void DataManagementModelTest::testRemoveResources_deletedFile() +{ + QTemporaryFile fileA; + fileA.open(); + + const KUrl fileUrl(fileA.fileName()); + + // create our app + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // create the data graph + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + // create the resource + m_model->addStatement(QUrl("res:/A"), NIE::url(), fileUrl, g1); + m_model->addStatement(QUrl("res:/A"), RDF::type(), NFO::FileDataObject(), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + // now remove the file + fileA.close(); + QFile::remove(fileUrl.toLocalFile()); + + // now try removing the data + m_dmModel->removeResources(QList<QUrl>() << fileUrl, NoRemovalFlags, QLatin1String("A")); + + // the call should succeed + QVERIFY(!m_dmModel->lastError()); + + // the resource should be gone + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(Node(), Node(), fileUrl)); +} + +void DataManagementModelTest::testCreateResource() +{ + // the simple test: we just create a resource using all params + const QUrl resUri = m_dmModel->createResource(QList<QUrl>() << QUrl("class:/typeA") << QUrl("class:/typeB"), QLatin1String("the label"), QLatin1String("the desc"), QLatin1String("A")); + + // this call should succeed + QVERIFY(!m_dmModel->lastError()); + + // check if the returned uri is valid + QVERIFY(!resUri.isEmpty()); + QCOMPARE(resUri.scheme(), QString(QLatin1String("nepomuk"))); + + // check if the resource was created properly + QVERIFY(m_model->containsAnyStatement(resUri, RDF::type(), QUrl("class:/typeA"))); + QVERIFY(m_model->containsAnyStatement(resUri, RDF::type(), QUrl("class:/typeB"))); + QVERIFY(m_model->containsAnyStatement(resUri, NAO::prefLabel(), LiteralValue::createPlainLiteral(QLatin1String("the label")))); + QVERIFY(m_model->containsAnyStatement(resUri, NAO::description(), LiteralValue::createPlainLiteral(QLatin1String("the desc")))); +} + +void DataManagementModelTest::testCreateResource_invalid_args() +{ + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // try to create a resource without any types + m_dmModel->createResource(QList<QUrl>(), QString(), QString(), QLatin1String("A")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // use an invalid type + m_dmModel->createResource(QList<QUrl>() << QUrl("class:/non-existing-type"), QString(), QString(), QLatin1String("A")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // use a property as type + m_dmModel->createResource(QList<QUrl>() << NAO::prefLabel(), QString(), QString(), QLatin1String("A")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +// the isolated test: create one graph with one resource, delete that resource +void DataManagementModelTest::testRemoveDataByApplication1() +{ + // create our app + const QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // create the resource to delete + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + + // delete the resource + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // verify that nothing is left, not even the graph + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(Node(), NAO::maintainedBy(), QUrl("app:/A"))); + QCOMPARE(m_model->listStatements(Node(), RDF::type(), NRL::InstanceBase()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(Node(), RDF::type(), NRL::GraphMetadata()).allStatements().count(), 1); + + QVERIFY(!haveTrailingGraphs()); +} + +// scatter resource over two graphs, only one of which is supposed to be removed +void DataManagementModelTest::testRemoveDataByApplication2() +{ + // create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + // create the resource to delete + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + + // delete the resource + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // verify that graph1 is gone completely + QVERIFY(!m_model->containsAnyStatement(Node(), Node(), Node(), g1)); + + // only two statements left: the one in the second graph and the last modification date + QCOMPARE(m_model->listStatements(QUrl("res:/A"), Node(), Node()).allStatements().count(), 2); + QVERIFY(m_model->containsStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2)); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), NAO::lastModified(), Node())); + + // four graphs: g2, the 2 app graphs, and the mtime graph + QCOMPARE(m_model->listStatements(Node(), RDF::type(), NRL::InstanceBase()).allStatements().count(), 4); + + QVERIFY(!haveTrailingGraphs()); +} + +// two apps that maintain a graph should keep the data when one removes it +void DataManagementModelTest::testRemoveDataByApplication3() +{ + // create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + // create the resource to delete + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/B"), mg1); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + + // delete the resource + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // the resource should still be there, without any changes, not even a changed mtime + QCOMPARE(m_model->listStatements(QUrl("res:/A"), Node(), Node()).allStatements().count(), 2); + + QVERIFY(!m_model->containsAnyStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1)); + QCOMPARE(m_model->listStatements(g1, NAO::maintainedBy(), Node()).allStatements().count(), 1); + + QVERIFY(!haveTrailingGraphs()); +} + +// test file URLs + not removing nie:url +void DataManagementModelTest::testRemoveDataByApplication4() +{ + QTemporaryFile fileA; + fileA.open(); + + // create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + // create the resource to delete + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + + m_model->addStatement(QUrl("res:/A"), NIE::url(), QUrl::fromLocalFile(fileA.fileName()), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + + // delete the resource + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl::fromLocalFile(fileA.fileName()), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // now the nie:url should still be there even though A created it + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), NIE::url(), QUrl::fromLocalFile(fileA.fileName()))); + + // creation time should have been created + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), NAO::lastModified(), Node())); + + // the foobar value should be gone + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + + // the "hello world" should still be there + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")))); + + QVERIFY(!haveTrailingGraphs()); +} + +// test sub-resource handling the easy kind +void DataManagementModelTest::testRemoveDataByApplication5() +{ + // create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // create the resource to delete + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + // delete the resource + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), Nepomuk::RemoveSubResoures, QLatin1String("A")); + + // this should have removed both A and B + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), Node(), Node())); + + QVERIFY(!haveTrailingGraphs()); +} + +// test sub-resource handling +void DataManagementModelTest::testRemoveDataByApplication6() +{ + // create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + // create the graphs + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + QUrl mg3; + const QUrl g3 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg3); + m_model->addStatement(g3, NAO::maintainedBy(), QUrl("app:/A"), mg3); + + // create the resource to delete + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + + // sub-resource 1: can be deleted + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + // sub-resource 2: can be deleted (is defined in another graph by the same app) + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/AA"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/AA"), g1); + m_model->addStatement(QUrl("res:/AA"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g3); + + // sub-resource 3: can be deleted although another res refs it (we also delete the other res) + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/C"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/C"), g1); + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/res"), QUrl("res:/C"), g1); + + // sub-resource 4: cannot be deleted since another res refs it + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/D"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/D"), g1); + m_model->addStatement(QUrl("res:/D"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/E"), QUrl("prop:/res"), QUrl("res:/D"), g1); + m_model->addStatement(QUrl("res:/E"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + // sub-resource 5: cannot be deleted since another app added properties + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/F"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/F"), g1); + m_model->addStatement(QUrl("res:/F"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/F"), QUrl("prop:/int"), LiteralValue(42), g2); + + // sub-resource 6: can be deleted since the prop another app added is only metadata + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/G"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/G"), g1); + m_model->addStatement(QUrl("res:/G"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/G"), NAO::lastModified(), LiteralValue(QDateTime::currentDateTime()), g2); + + // delete the resource + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), Nepomuk::RemoveSubResoures, QLatin1String("A")); + + // this should have removed A, B and C + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/C"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/G"), Node(), Node())); + + // E and F need to be preserved + QCOMPARE(m_model->listStatements(QUrl("res:/D"), Node(), Node()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(QUrl("res:/E"), Node(), Node()).allStatements().count(), 2); + QCOMPARE(m_model->listStatements(QUrl("res:/F"), Node(), Node()).allStatements().count(), 2); + + QVERIFY(!haveTrailingGraphs()); +} + +// make sure that we do not remove metadata from resources that were also touched by other apps +void DataManagementModelTest::testRemoveDataByApplication7() +{ + // create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + // create the resource to delete + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + + // delete the resource + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // verify that the creation date is still there + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), NAO::created(), Soprano::Node())); + + QVERIFY(!haveTrailingGraphs()); +} + +// make sure everything is removed even if splitted in more than one graph +void DataManagementModelTest::testRemoveDataByApplication8() +{ + // create our app + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // create the resource to delete + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/A"), mg2); + + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + + // delete the resource + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // verify that all has gone + // verify that nothing is left, not even the graph + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(Node(), NAO::maintainedBy(), QUrl("app:/A"))); + QCOMPARE(m_model->listStatements(Node(), RDF::type(), NRL::InstanceBase()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(Node(), RDF::type(), NRL::GraphMetadata()).allStatements().count(), 1); + + QVERIFY(!haveTrailingGraphs()); +} + +// make sure that we still maintain other resources in the same graph after deleting one resource +void DataManagementModelTest::testRemoveDataByApplication9() +{ + // create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + // create the graph + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/B"), mg1); + + // create the resources + const QDateTime dt = QDateTime::currentDateTime(); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(dt), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/B"), NAO::created(), LiteralValue(dt), g1); + + // now remove res:/A by app A + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // now there should be 2 graphs - once for res:/A which is only maintained by B, and one for res:/B which is still + // maintained by A and B + // 1. check that B still maintains res:/A (all of it in one graph) + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { graph ?g { <res:/A> <prop:/string> %1 . <res:/A> %2 %3 . } . ?g %4 <app:/B> . }") + .arg(Soprano::Node::literalToN3(LiteralValue(QLatin1String("foobar"))), + Soprano::Node::resourceToN3(NAO::created()), + Soprano::Node::literalToN3(LiteralValue(dt)), + Soprano::Node::resourceToN3(NAO::maintainedBy())), + Soprano::Query::QueryLanguageSparql).boolValue()); + + // 2. check that A does not maintain res:/A anymore + QVERIFY(!m_model->executeQuery(QString::fromLatin1("ask where { graph ?g { <res:/A> ?p ?o } . ?g %1 <app:/A> . }") + .arg(Soprano::Node::resourceToN3(NAO::maintainedBy())), + Soprano::Query::QueryLanguageSparql).boolValue()); + + // 3. check that both A and B do still maintain res:/B + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { graph ?g { <res:/B> <prop:/string> %1 . <res:/B> %2 %3 . } . ?g %4 <app:/A> . ?g %4 <app:/B> . }") + .arg(Soprano::Node::literalToN3(LiteralValue(QLatin1String("hello world"))), + Soprano::Node::resourceToN3(NAO::created()), + Soprano::Node::literalToN3(LiteralValue(dt)), + Soprano::Node::resourceToN3(NAO::maintainedBy())), + Soprano::Query::QueryLanguageSparql).boolValue()); +} + +// This test simply creates a lot of resources using storeResources and then +// removes all of them using removeDataByApplication. +// This is exactly what the strigi service does. +void DataManagementModelTest::testRemoveDataByApplication10() +{ + QLatin1String app("AppA"); + QList<QUrl> uris; + + for( int i=0; i<10; i++ ) { + QTemporaryFile fileA; + fileA.open(); + + SimpleResource res; + res.addProperty( RDF::type(), NFO::FileDataObject() ); + res.addProperty( NIE::url(), QUrl(fileA.fileName()) ); + + m_dmModel->storeResources( SimpleResourceGraph() << res, app ); + QVERIFY( !m_dmModel->lastError() ); + + QString query = QString::fromLatin1("select ?r where { ?r %1 %2 . }") + .arg( Node::resourceToN3( NIE::url() ), + Node::resourceToN3( QUrl(fileA.fileName()) ) ); + + QList<Node> list = m_dmModel->executeQuery( query, Soprano::Query::QueryLanguageSparql ).iterateBindings(0).allNodes(); + QCOMPARE( list.size(), 1 ); + + uris << list.first().uri(); + } + + // + // Remove the data + // + + m_dmModel->removeDataByApplication( uris, Nepomuk::RemoveSubResoures, + QLatin1String("AppA") ); + QVERIFY( !m_dmModel->lastError() ); + + QString query = QString::fromLatin1("ask where { graph ?g { ?r ?p ?o . } ?g %1 ?app . ?app %2 %3 . }") + .arg( Node::resourceToN3( NAO::maintainedBy() ), + Node::resourceToN3( NAO::identifier() ), + Node::literalToN3( app ) ); + + QVERIFY( !m_dmModel->executeQuery( query, Soprano::Query::QueryLanguageSparql ).boolValue() ); + + foreach( const QUrl resUri, uris ) { + + // The Resource should no longer have any statements + QList<Soprano::Statement> l = m_dmModel->listStatements( resUri, Node(), Node() ).allStatements(); + QVERIFY( l.isEmpty() ); + } + + QVERIFY(!haveTrailingGraphs()); +} + +// make sure that graphs which do not have a maintaining app are handled properly, too +void DataManagementModelTest::testRemoveDataByApplication11() +{ + QTemporaryFile fileA; + fileA.open(); + + // create our app + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // create the resource to delete + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + // graph 2 does not have a maintaining app! + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase()); + + m_model->addStatement(QUrl("res:/A"), NIE::url(), QUrl::fromLocalFile(fileA.fileName()), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + + // delete the resource + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl::fromLocalFile(fileA.fileName()), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // now the nie:url should still be there even though A created it + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), NIE::url(), QUrl::fromLocalFile(fileA.fileName()))); + + // creation time should have been created + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), NAO::lastModified(), Node())); + + // the foobar value should be gone + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + + // the "hello world" should still be there + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")))); + + QVERIFY(!haveTrailingGraphs()); +} + +// make sure that weird cross sub-resource'ing is handled properly. This is very unlikely to ever happen, but still... +void DataManagementModelTest::testRemoveDataByApplication_subResourcesOfSubResources() +{ + // create our app + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // create the graph + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + // create the resource to delete + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + + // sub-resource 1 + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + // sub-resource 2 + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/C"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/C"), g1); + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + // sub-resource 3 (also sub-resource to res:/C) + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/D"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/D"), g1); + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/res"), QUrl("res:/D"), g1); + m_model->addStatement(QUrl("res:/C"), NAO::hasSubResource(), QUrl("res:/D"), g1); + m_model->addStatement(QUrl("res:/D"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + + // delete the resource + QBENCHMARK_ONCE + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), Nepomuk::RemoveSubResoures, QLatin1String("A")); + + + // all resources should have been removed + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/C"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/D"), Node(), Node())); +} + +// This is some real data that I have in my nepomuk repo +void DataManagementModelTest::testRemoveDataByApplication_realLife() +{ + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/c07bb72a-6aec-450a-9622-fe8f05f83d79>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/c07bb72a-6aec-450a-9622-fe8f05f83d79>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/c07bb72a-6aec-450a-9622-fe8f05f83d79>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#userVisible>"),Soprano::Node::fromN3("\"1\"^^<http://www.w3.org/2001/XMLSchema#int>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/c07bb72a-6aec-450a-9622-fe8f05f83d79>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#lastModified>"),Soprano::Node::fromN3("\"2011-05-24T10:35:39.414Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"),Soprano::Node::fromN3("<nepomuk:/ctx/1b99f767-3652-4bb5-97a5-dcb469ffa186>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/c07bb72a-6aec-450a-9622-fe8f05f83d79>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#hasTag>"),Soprano::Node::fromN3("<nepomuk:/res/8e251223-a066-4c90-863f-2f49237c870f>"),Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/c07bb72a-6aec-450a-9622-fe8f05f83d79>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#created>"),Soprano::Node::fromN3("\"2011-04-26T19:25:21.435Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"),Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/c07bb72a-6aec-450a-9622-fe8f05f83d79>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/01/19/nie#url>"),Soprano::Node::fromN3("<file:///home/vishesh/Videos/The%20Big%20Bang%20Theory/Season%201/7_Dumpling%20Paradox.avi>"),Soprano::Node::fromN3("<nepomuk:/ctx/33966b99-de71-4d77-8cbc-a3e3c104d681>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/8e251223-a066-4c90-863f-2f49237c870f>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/8e251223-a066-4c90-863f-2f49237c870f>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#Tag>"),Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/8e251223-a066-4c90-863f-2f49237c870f>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#userVisible>"),Soprano::Node::fromN3("\"1\"^^<http://www.w3.org/2001/XMLSchema#int>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/8e251223-a066-4c90-863f-2f49237c870f>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#identifier>"),Soprano::Node::fromN3("\"Big%20Bang\"^^<http://www.w3.org/2001/XMLSchema#string>"),Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/8e251223-a066-4c90-863f-2f49237c870f>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#created>"),Soprano::Node::fromN3("\"2011-04-26T19:25:21.475Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"),Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Data>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Graph>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#InstanceBase>"),Soprano::Node::fromN3("<nepomuk:/ctx/a33dd431-cbf7-4aef-8217-a0dedfa7b56d>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#userVisible>"),Soprano::Node::fromN3("\"1\"^^<http://www.w3.org/2001/XMLSchema#int>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#created>"),Soprano::Node::fromN3("\"2011-04-26T19:25:21.443Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"),Soprano::Node::fromN3("<nepomuk:/ctx/a33dd431-cbf7-4aef-8217-a0dedfa7b56d>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/1b99f767-3652-4bb5-97a5-dcb469ffa186>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/1b99f767-3652-4bb5-97a5-dcb469ffa186>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Data>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/1b99f767-3652-4bb5-97a5-dcb469ffa186>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Graph>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/1b99f767-3652-4bb5-97a5-dcb469ffa186>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#InstanceBase>"),Soprano::Node::fromN3("<nepomuk:/ctx/81107abc-443a-4d75-9cb3-3a93d18af6c2>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/1b99f767-3652-4bb5-97a5-dcb469ffa186>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#userVisible>"),Soprano::Node::fromN3("\"1\"^^<http://www.w3.org/2001/XMLSchema#int>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/1b99f767-3652-4bb5-97a5-dcb469ffa186>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#created>"),Soprano::Node::fromN3("\"2011-05-24T10:35:39.52Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"),Soprano::Node::fromN3("<nepomuk:/ctx/81107abc-443a-4d75-9cb3-3a93d18af6c2>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/1b99f767-3652-4bb5-97a5-dcb469ffa186>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#maintainedBy>"),Soprano::Node::fromN3("<nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0>"),Soprano::Node::fromN3("<nepomuk:/ctx/81107abc-443a-4d75-9cb3-3a93d18af6c2>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#Agent>"),Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#userVisible>"),Soprano::Node::fromN3("\"1\"^^<http://www.w3.org/2001/XMLSchema#int>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#identifier>"),Soprano::Node::fromN3("\"nepomukindexer\"^^<http://www.w3.org/2001/XMLSchema#string>"),Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Data>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Graph>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#InstanceBase>"),Soprano::Node::fromN3("<nepomuk:/ctx/2e4f3918-7da3-4736-b998-2d1ab7cbd6e4>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#userVisible>"),Soprano::Node::fromN3("\"1\"^^<http://www.w3.org/2001/XMLSchema#int>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#created>"),Soprano::Node::fromN3("\"2011-05-16T20:42:12.551Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"),Soprano::Node::fromN3("<nepomuk:/ctx/2e4f3918-7da3-4736-b998-2d1ab7cbd6e4>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Data>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Graph>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#InstanceBase>"),Soprano::Node::fromN3("<nepomuk:/ctx/a33dd431-cbf7-4aef-8217-a0dedfa7b56d>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#userVisible>"),Soprano::Node::fromN3("\"1\"^^<http://www.w3.org/2001/XMLSchema#int>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/4da68d35-c6a2-4029-8fd9-a84ef7c2c60f>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#created>"),Soprano::Node::fromN3("\"2011-04-26T19:25:21.443Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"),Soprano::Node::fromN3("<nepomuk:/ctx/a33dd431-cbf7-4aef-8217-a0dedfa7b56d>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/33966b99-de71-4d77-8cbc-a3e3c104d681>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/33966b99-de71-4d77-8cbc-a3e3c104d681>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Data>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/33966b99-de71-4d77-8cbc-a3e3c104d681>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Graph>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/33966b99-de71-4d77-8cbc-a3e3c104d681>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#InstanceBase>"),Soprano::Node::fromN3("<nepomuk:/ctx/873b6b73-5dd5-44d3-b138-4b280065f5af>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/33966b99-de71-4d77-8cbc-a3e3c104d681>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#userVisible>"),Soprano::Node::fromN3("\"1\"^^<http://www.w3.org/2001/XMLSchema#int>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/33966b99-de71-4d77-8cbc-a3e3c104d681>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#created>"),Soprano::Node::fromN3("\"2011-04-26T19:25:21.443Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"),Soprano::Node::fromN3("<nepomuk:/ctx/873b6b73-5dd5-44d3-b138-4b280065f5af>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/33966b99-de71-4d77-8cbc-a3e3c104d681>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#maintainedBy>"),Soprano::Node::fromN3("<nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0>"),Soprano::Node::fromN3("<nepomuk:/ctx/873b6b73-5dd5-44d3-b138-4b280065f5af>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#Agent>"),Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#userVisible>"),Soprano::Node::fromN3("\"1\"^^<http://www.w3.org/2001/XMLSchema#int>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#identifier>"),Soprano::Node::fromN3("\"nepomukindexer\"^^<http://www.w3.org/2001/XMLSchema#string>"),Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.w3.org/2000/01/rdf-schema#Resource>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Data>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#Graph>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#InstanceBase>"),Soprano::Node::fromN3("<nepomuk:/ctx/2e4f3918-7da3-4736-b998-2d1ab7cbd6e4>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#userVisible>"),Soprano::Node::fromN3("\"1\"^^<http://www.w3.org/2001/XMLSchema#int>"),Soprano::Node::fromN3("<urn:crappyinference2:inferredtriples>") ); + m_model->addStatement( Soprano::Node::fromN3("<nepomuk:/ctx/90105034-0d05-444a-8f7f-5a957dae9f14>"),Soprano::Node::fromN3("<http://www.semanticdesktop.org/ontologies/2007/08/15/nao#created>"),Soprano::Node::fromN3("\"2011-05-16T20:42:12.551Z\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"),Soprano::Node::fromN3("<nepomuk:/ctx/2e4f3918-7da3-4736-b998-2d1ab7cbd6e4>") ); + + + QUrl resUri("nepomuk:/res/c07bb72a-6aec-450a-9622-fe8f05f83d79"); + QUrl nieUrl("file:///home/vishesh/Videos/The%20Big%20Bang%20Theory/Season%201/7_Dumpling%20Paradox.avi"); + + QString query = QString::fromLatin1("select ?app where { graph ?g { %1 %2 %3 .} " + "?g %4 ?app . ?app %5 %6 . }" ) + .arg( Node::resourceToN3( resUri ), + Node::resourceToN3( NIE::url() ), + Node::resourceToN3( nieUrl ), + Node::resourceToN3( NAO::maintainedBy() ), + Node::resourceToN3( NAO::identifier() ), + Node::literalToN3( LiteralValue("nepomukindexer") ) ); + + QueryResultIterator it = m_model->executeQuery( query, Soprano::Query::QueryLanguageSparql ); + QUrl appUri; + if( it.next() ) { + appUri = it[0].uri(); + } + QCOMPARE( appUri, QUrl("nepomuk:/res/e2eb2efb-14ee-4038-ac24-698f916289b0") ); + + m_dmModel->removeDataByApplication( QList<QUrl>() << resUri, + Nepomuk::RemoveSubResoures, + QLatin1String("nepomukindexer") ); + + QString query2 = QString::fromLatin1("ask where { graph ?g { ?r %1 %2 .} " + "?g %3 ?app . ?app %4 %5 . }" ) + .arg( Node::resourceToN3( NIE::url() ), + Node::resourceToN3( nieUrl ), + Node::resourceToN3( NAO::maintainedBy() ), + Node::resourceToN3( NAO::identifier() ), + Node::literalToN3( LiteralValue("nepomukindexer") ) ); + + QVERIFY( !m_model->executeQuery( query2, Soprano::Query::QueryLanguageSparql ).boolValue() ); +} + +void DataManagementModelTest::testRemoveDataByApplication_nieUrl() +{ + KTemporaryFile file; + file.open(); + const QUrl fileUrl( file.fileName() ); + + const QUrl res1("nepomuk:/res/1"); + + // The file is tagged via Dolphin + const QUrl g1 = m_nrlModel->createGraph( NRL::InstanceBase() ); + m_model->addStatement( res1, RDF::type(), NFO::FileDataObject() ); + m_model->addStatement( res1, NIE::url(), fileUrl, g1 ); + QDateTime now = QDateTime::currentDateTime(); + m_model->addStatement( res1, NAO::created(), LiteralValue(QVariant(now)), g1 ); + m_model->addStatement( res1, NAO::lastModified(), LiteralValue(QVariant(now)), g1 ); + + const QUrl tag("nepomuk:/res/tag"); + m_model->addStatement( tag, RDF::type(), NAO::Tag(), g1 ); + m_model->addStatement( tag, NAO::identifier(), LiteralValue("tag"), g1 ); + m_model->addStatement( res1, NAO::hasTag(), tag, g1 ); + + // Indexed via strigi + SimpleResource simpleRes( res1 ); + simpleRes.addProperty( RDF::type(), NFO::FileDataObject() ); + simpleRes.addProperty( RDF::type(), NMM::MusicPiece() ); + simpleRes.addProperty( NIE::url(), fileUrl ); + + m_dmModel->storeResources( SimpleResourceGraph() << simpleRes, QLatin1String("nepomukindexer")); + QVERIFY( !m_dmModel->lastError() ); + + // Remove strigi indexed content + m_dmModel->removeDataByApplication( QList<QUrl>() << res1, Nepomuk::RemoveSubResoures, + QLatin1String("nepomukindexer") ); + + // The tag should still be there + QVERIFY( m_model->containsStatement( tag, RDF::type(), NAO::Tag(), g1 ) ); + QVERIFY( m_model->containsStatement( tag, NAO::identifier(), LiteralValue("tag"), g1 ) ); + QVERIFY( m_model->containsStatement( res1, NAO::hasTag(), tag, g1 ) ); + + // The resource should not have any data maintained by "nepomukindexer" + QString query = QString::fromLatin1("select * where { graph ?g { %1 ?p ?o. } ?g %2 ?a . ?a %3 %4. }") + .arg( Node::resourceToN3( res1 ), + Node::resourceToN3( NAO::maintainedBy() ), + Node::resourceToN3( NAO::identifier() ), + Node::literalToN3( LiteralValue("nepomukindexer") ) ); + + QList< BindingSet > bs = m_model->executeQuery( query, Soprano::Query::QueryLanguageSparql ).allBindings(); + kDebug() << bs; + QVERIFY( bs.isEmpty() ); + + // The nie:url should still exist + QVERIFY( m_model->containsAnyStatement( res1, NIE::url(), fileUrl ) ); +} + +// make sure we keep the nie:url in case a relation from another resource exists which is not removed +void DataManagementModelTest::testRemoveDataByApplication_nieUrlRelated() +{ + // create the file we will be removing data for + KTemporaryFile file; + file.open(); + const KUrl fileUrl( file.fileName() ); + + + // Indexed the file with some data + SimpleResource simpleRes( fileUrl ); + simpleRes.addProperty( RDF::type(), NFO::FileDataObject() ); + simpleRes.addProperty( RDF::type(), NMM::MusicPiece() ); + m_dmModel->storeResources( SimpleResourceGraph() << simpleRes, QLatin1String("A")); + QVERIFY( !m_dmModel->lastError() ); + QVERIFY(m_model->containsAnyStatement(Soprano::Node(), NIE::url(), fileUrl)); + + + // now add a relation to some other resource + const QUrl res = m_dmModel->createResource(QList<QUrl>() << QUrl("class:/typeA"), QLatin1String("foobar"), QString(), QLatin1String("B")); + QVERIFY( !m_dmModel->lastError() ); + m_dmModel->addProperty(QList<QUrl>() << res, QUrl("prop:/res"), QVariantList() << QUrl(fileUrl), QLatin1String("B")); + QVERIFY( !m_dmModel->lastError() ); + + + // remove all the indexed data + m_dmModel->removeDataByApplication(QList<QUrl>() << fileUrl, NoRemovalFlags, QLatin1String("A")); + QVERIFY( !m_dmModel->lastError() ); + + + // now the basic data from the indexed file should still be there + QVERIFY(m_model->containsAnyStatement(Soprano::Node(), NIE::url(), fileUrl)); + + // and the relation created by the other application should still be there, too + const QUrl fileResUri = m_model->listStatements(Soprano::Node(), NIE::url(), fileUrl).allElements().first().subject().uri(); + QVERIFY(m_model->containsAnyStatement(res, QUrl("prop:/res"), fileResUri)); + + QVERIFY(!haveTrailingGraphs()); +} + +// make sure the mtime is updated properly in different situations +void DataManagementModelTest::testRemoveDataByApplication_mtime() +{ + // first we create our apps and graphs + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + + const QDateTime date = QDateTime::currentDateTime(); + + + // now we create different resources + // A has actual data maintained by app:/A + // B has only metadata maintained by app:/A + // C has nothing maintained by app:/A + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello")), g2); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/A"), NAO::lastModified(), LiteralValue(date), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + m_model->addStatement(QUrl("res:/B"), NAO::created(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/B"), NAO::lastModified(), LiteralValue(date), g1); + + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + m_model->addStatement(QUrl("res:/C"), NAO::created(), LiteralValue(date), g2); + m_model->addStatement(QUrl("res:/C"), NAO::lastModified(), LiteralValue(date), g2); + + + // we delete all three + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A") << QUrl("res:/B") << QUrl("res:/C"), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + + // now only the mtime of A should have changed + QCOMPARE(m_model->listStatements(QUrl("res:/A"), NAO::lastModified(), Node()).allElements().count(), 1); + QVERIFY(m_model->listStatements(QUrl("res:/A"), NAO::lastModified(), Node()).allElements().first().object().literal().toDateTime() > date); + + QCOMPARE(m_model->listStatements(QUrl("res:/B"), NAO::lastModified(), Node()).allElements().count(), 1); + QCOMPARE(m_model->listStatements(QUrl("res:/B"), NAO::lastModified(), Node()).allElements().first().object().literal().toDateTime(), date); + + QCOMPARE(m_model->listStatements(QUrl("res:/C"), NAO::lastModified(), Node()).allElements().count(), 1); + QCOMPARE(m_model->listStatements(QUrl("res:/C"), NAO::lastModified(), Node()).allElements().first().object().literal().toDateTime(), date); +} + +// make sure the mtime of resources that are related to deleted ones is updated +void DataManagementModelTest::testRemoveDataByApplication_mtimeRelated() +{ + // first we create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + + const QDateTime date = QDateTime::currentDateTime(); + + // we three two resources - one to delete, and one which is related to the one to be deleted, + // one which is also related but will not be changed + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello")), g2); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/res"), QUrl("res:/A"), g1); + m_model->addStatement(QUrl("res:/B"), NAO::created(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/B"), NAO::lastModified(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/res"), QUrl("res:/A"), g2); + m_model->addStatement(QUrl("res:/C"), NAO::created(), LiteralValue(date), g2); + m_model->addStatement(QUrl("res:/C"), NAO::lastModified(), LiteralValue(date), g2); + + // now we remove res:/A + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A"), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // now the mtime of res:/B should have been changed + QCOMPARE(m_model->listStatements(QUrl("res:/B"), NAO::lastModified(), Node()).allElements().count(), 1); + QVERIFY(m_model->listStatements(QUrl("res:/B"), NAO::lastModified(), Node()).allElements().first().object().literal().toDateTime() > date); + + // the mtime of res:/C should NOT have changed + QCOMPARE(m_model->listStatements(QUrl("res:/C"), NAO::lastModified(), Node()).allElements().count(), 1); + QCOMPARE(m_model->listStatements(QUrl("res:/C"), NAO::lastModified(), Node()).allElements().first().object().literal().toDateTime(), date); +} + +// make sure relations to removed resources are handled properly +void DataManagementModelTest::testRemoveDataByApplication_related() +{ + // first we create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + // we create 3 graphs, maintained by different apps + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + QUrl mg3; + const QUrl g3 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg3); + m_model->addStatement(g3, NAO::maintainedBy(), QUrl("app:/A"), mg3); + + const QDateTime date = QDateTime::currentDateTime(); + + // create two resources: + // A is split across both graphs + // B is only in one graph + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("Hello World")), g2); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/A"), NAO::lastModified(), LiteralValue(date), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/B"), NAO::created(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/B"), NAO::lastModified(), LiteralValue(date), g1); + + // three relations B -> A, one in each graph + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/res"), QUrl("res:/A"), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/res2"), QUrl("res:/A"), g2); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/res3"), QUrl("res:/A"), g3); + + // a third resource which is deleted entirely but has one relation in another graph + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/C"), NAO::created(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/C"), NAO::lastModified(), LiteralValue(date), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/res"), QUrl("res:/C"), g2); + + + // now remove A + m_dmModel->removeDataByApplication(QList<QUrl>() << QUrl("res:/A") << QUrl("res:/C"), Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // now only the relation in the first graph should have been removed + QVERIFY(!m_model->containsStatement(QUrl("res:/B"), QUrl("prop:/res"), QUrl("res:/A"), g1)); + QVERIFY(!m_model->containsStatement(QUrl("res:/B"), QUrl("prop:/res3"), QUrl("res:/A"), g3)); + QVERIFY(m_model->containsStatement(QUrl("res:/B"), QUrl("prop:/res2"), QUrl("res:/A"), g2)); + QVERIFY(!m_model->containsStatement(QUrl("res:/B"), QUrl("prop:/res3"), QUrl("res:/C"), g2)); + + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), QUrl("prop:/res"), QUrl("res:/A"))); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), QUrl("prop:/res3"), QUrl("res:/A"))); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/B"), QUrl("prop:/res2"), QUrl("res:/A"))); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), QUrl("prop:/res3"), QUrl("res:/C"))); +} + +// make sure legacy indexer data (the graphs marked with indexGraphFor) is removed properly +void DataManagementModelTest::testRemoveDataByApplication_legacyIndexerData() +{ + // create our file + QTemporaryFile fileA; + fileA.open(); + const QUrl fileAUrl = QUrl::fromLocalFile(fileA.fileName()); + const QUrl fileARes("res:/A"); + + // create the graph containing the legacy data + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::DiscardableInstanceBase(), &mg1); + + // mark the graph as being the legacy index graph + m_model->addStatement(g1, QUrl("http://www.strigi.org/fields#indexGraphFor"), fileARes, mg1); + + // create the index data + m_model->addStatement(fileARes, NIE::url(), fileAUrl, g1); + m_model->addStatement(fileARes, RDF::type(), NFO::FileDataObject(), g1); + m_model->addStatement(fileARes, RDF::type(), NIE::InformationElement(), g1); + m_model->addStatement(fileARes, NIE::title(), LiteralValue(QLatin1String("foobar")), g1); + + + // remove the information claiming to be the indexer + QBENCHMARK_ONCE + m_dmModel->removeDataByApplication(QList<QUrl>() << fileAUrl, NoRemovalFlags, QLatin1String("nepomukindexer")); + + // the call should succeed + QVERIFY(!m_dmModel->lastError()); + + // now make sure that everything is gone + QVERIFY(!m_model->containsAnyStatement(fileARes, Node(), Node(), Node())); + + QVERIFY(!m_model->containsAnyStatement(Node(), Node(), Node(), g1)); + QVERIFY(!m_model->containsAnyStatement(Node(), Node(), Node(), mg1)); +} + +// make sure we can remove data from non-existing files +void DataManagementModelTest::testRemoveDataByApplication_deletedFile() +{ + QTemporaryFile* fileA = new QTemporaryFile(); + fileA->open(); + const KUrl fileUrl(fileA->fileName()); + delete fileA; + QVERIFY(!QFile::exists(fileUrl.toLocalFile())); + + // create our app + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // create the data graph + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + // create the resource + m_model->addStatement(QUrl("res:/A"), NIE::url(), fileUrl, g1); + m_model->addStatement(QUrl("res:/A"), RDF::type(), NFO::FileDataObject(), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + + // now try removing the data + m_dmModel->removeDataByApplication(QList<QUrl>() << fileUrl, NoRemovalFlags, QLatin1String("A")); + + // the call should succeed + QVERIFY(!m_dmModel->lastError()); + + // the resource should be gone + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(Node(), Node(), fileUrl)); +} + +// test that all is removed, ie. storage is clear afterwards +void DataManagementModelTest::testRemoveAllDataByApplication1() +{ + // create our app + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/A"), mg2); + + // create two resources to remove + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar 2")), g2); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world 2")), g2); + m_model->addStatement(QUrl("res:/B"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + + m_dmModel->removeDataByApplication(Nepomuk::NoRemovalFlags, QLatin1String("A")); + + // make sure nothing is there anymore + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/B"), Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(Node(), NAO::maintainedBy(), QUrl("app:/A"))); + + QVERIFY(!haveTrailingGraphs()); + + // everything should be as before + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +// test that other resources are not removed - the easy way +void DataManagementModelTest::testRemoveAllDataByApplication2() +{ + // create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + + // create two resources to remove + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar 2")), g2); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world 2")), g2); + m_model->addStatement(QUrl("res:/B"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g2); + + m_dmModel->removeDataByApplication(Nepomuk::NoRemovalFlags, QLatin1String("A")); + + QVERIFY(!m_model->containsAnyStatement(QUrl("res:/A"), Node(), Node())); + QCOMPARE(m_model->listStatements(QUrl("res:/B"), Node(), Node()).allStatements().count(), 3); + QVERIFY(!m_model->containsAnyStatement(Node(), NAO::maintainedBy(), QUrl("app:/A"))); + + QVERIFY(!haveTrailingGraphs()); +} + +// test that an app is simply removed as maintainer of a graph +void DataManagementModelTest::testRemoveAllDataByApplication3() +{ + // create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/B"), mg1); + + // create two resources to remove + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar 2")), g1); + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world 2")), g1); + m_model->addStatement(QUrl("res:/B"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + + m_dmModel->removeDataByApplication(Nepomuk::NoRemovalFlags, QLatin1String("A")); + + QCOMPARE(m_model->listStatements(QUrl("res:/A"), Node(), Node()).allStatements().count(), 3); + QCOMPARE(m_model->listStatements(QUrl("res:/B"), Node(), Node()).allStatements().count(), 3); + QVERIFY(!m_model->containsAnyStatement(Node(), NAO::maintainedBy(), QUrl("app:/A"))); + QCOMPARE(m_model->listStatements(Node(), NAO::maintainedBy(), QUrl("app:/B")).allStatements().count(), 1); + + QVERIFY(!haveTrailingGraphs()); +} + +// test that metadata is not removed if the resource still exists even if its in a deleted graph +void DataManagementModelTest::testRemoveAllDataByApplication4() +{ + // create our apps + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/B"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/B"), NAO::identifier(), LiteralValue(QLatin1String("B")), appG); + + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + QUrl mg2; + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg2); + m_model->addStatement(g2, NAO::maintainedBy(), QUrl("app:/B"), mg2); + + // create two resources to remove + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g2); + m_model->addStatement(QUrl("res:/A"), NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + + m_dmModel->removeDataByApplication(Nepomuk::NoRemovalFlags, QLatin1String("A")); + + QCOMPARE(m_model->listStatements(QUrl("res:/A"), Node(), Node()).allStatements().count(), 3); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), NAO::created(), Soprano::Node())); + QVERIFY(m_model->containsAnyStatement(QUrl("res:/A"), NAO::lastModified(), Soprano::Node())); + QVERIFY(m_model->containsAnyStatement(Node(), NAO::maintainedBy(), QUrl("app:/A"))); + + QVERIFY(!haveTrailingGraphs()); +} + + +namespace { + int push( Soprano::Model * model, Nepomuk::SimpleResource res, QUrl graph ) { + QHashIterator<QUrl, QVariant> it( res.properties() ); + if( !res.uri().isValid() ) + return 0; + + int numPushed = 0; + Soprano::Statement st( res.uri(), Soprano::Node(), Soprano::Node(), graph ); + while( it.hasNext() ) { + it.next(); + st.setPredicate( it.key() ); + if(it.value().type() == QVariant::Url) + st.setObject(it.value().toUrl()); + else + st.setObject( Soprano::LiteralValue(it.value()) ); + if( model->addStatement( st ) == Soprano::Error::ErrorNone ) + numPushed++; + } + return numPushed; + } +} + + +void DataManagementModelTest::testStoreResources_strigiCase() +{ + // + // This is for testing exactly how Strigi will use storeResources ie + // have some blank nodes ( some of which may already exists ) and a + // main resources which does not exist + // + + kDebug() << "Starting Strigi merge test"; + + QUrl graphUri = m_nrlModel->createGraph(NRL::InstanceBase()); + + Nepomuk::SimpleResource coldplay; + coldplay.addProperty( RDF::type(), NCO::Contact() ); + coldplay.addProperty( NCO::fullname(), "Coldplay" ); + + // Push it into the model with a proper uri + coldplay.setUri( QUrl("nepomuk:/res/coldplay") ); + QVERIFY( push( m_model, coldplay, graphUri ) == coldplay.properties().size() ); + + // Now keep it as a blank node + coldplay.setUri( QUrl("_:coldplay") ); + + Nepomuk::SimpleResource album; + album.setUri( QUrl("_:XandY") ); + album.addProperty( RDF::type(), NMM::MusicAlbum() ); + album.addProperty( NIE::title(), "X&Y" ); + + Nepomuk::SimpleResource res1; + res1.addProperty( RDF::type(), NFO::FileDataObject() ); + res1.addProperty( RDF::type(), NMM::MusicPiece() ); + res1.addProperty( NFO::fileName(), "Yellow.mp3" ); + res1.addProperty( NMM::performer(), QUrl("_:coldplay") ); + res1.addProperty( NMM::musicAlbum(), QUrl("_:XandY") ); + Nepomuk::SimpleResourceGraph resGraph; + resGraph << res1 << coldplay << album; + + // + // Do the actual merging + // + kDebug() << "Perform the merge"; + m_dmModel->storeResources( resGraph, "TestApp" ); + QVERIFY( !m_dmModel->lastError() ); + + QList<Soprano::Statement> stList = m_model->listStatements( Node(), RDF::type(), NMM::MusicPiece() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + const QUrl res1Uri = stList.first().subject().uri(); + + QVERIFY( m_model->containsAnyStatement( res1Uri, Soprano::Node(), + Soprano::Node() ) ); + QVERIFY( m_model->containsAnyStatement( res1Uri, NFO::fileName(), + Soprano::LiteralValue("Yellow.mp3") ) ); + // Make sure we have the nao:created and nao:lastModified + QVERIFY( m_model->containsAnyStatement( res1Uri, NAO::lastModified(), + Soprano::Node() ) ); + QVERIFY( m_model->containsAnyStatement( res1Uri, NAO::created(), + Soprano::Node() ) ); + kDebug() << m_model->listStatements( res1Uri, Soprano::Node(), Soprano::Node() ).allStatements(); + // The +2 is because nao:created and nao:lastModified would have also been added + QCOMPARE( m_model->listStatements( res1Uri, Soprano::Node(), Soprano::Node() ).allStatements().size(), + res1.properties().size() + 2 ); + + QList< Node > objects = m_model->listStatements( res1Uri, NMM::performer(), Soprano::Node() ).iterateObjects().allNodes(); + + QVERIFY( objects.size() == 1 ); + QVERIFY( objects.first().isResource() ); + + QUrl coldplayUri = objects.first().uri(); + QCOMPARE( coldplayUri, QUrl("nepomuk:/res/coldplay") ); + stList = coldplay.toStatementList(); + foreach( Soprano::Statement st, stList ) { + st.setSubject( coldplayUri ); + QVERIFY( m_model->containsAnyStatement( st ) ); + } + + objects = m_model->listStatements( res1Uri, NMM::musicAlbum(), Soprano::Node() ).iterateObjects().allNodes(); + + QVERIFY( objects.size() == 1 ); + QVERIFY( objects.first().isResource() ); + + QUrl albumUri = objects.first().uri(); + stList = album.toStatementList(); + foreach( Soprano::Statement st, stList ) { + st.setSubject( albumUri ); + QVERIFY( m_model->containsAnyStatement( st ) ); + } + + QVERIFY(!haveTrailingGraphs()); +} + + +void DataManagementModelTest::testStoreResources_graphRules() +{ + // + // Test the graph rules. If a resource exists in a nrl:DiscardableInstanceBase + // and it is merged with a non-discardable graph. Then the graph should be replaced + // In the opposite case - nothing should be done + { + Nepomuk::SimpleResource res; + res.addProperty( RDF::type(), NCO::Contact() ); + res.addProperty( NCO::fullname(), "Lion" ); + + QUrl graphUri = m_nrlModel->createGraph( NRL::DiscardableInstanceBase() ); + + res.setUri(QUrl("nepomuk:/res/Lion")); + QVERIFY( push( m_model, res, graphUri ) == res.properties().size() ); + res.setUri(QUrl("_:lion")); + + Nepomuk::SimpleResourceGraph resGraph; + resGraph << res; + + QHash<QUrl, QVariant> additionalMetadata; + additionalMetadata.insert( RDF::type(), NRL::InstanceBase() ); + m_dmModel->storeResources( resGraph, "TestApp", Nepomuk::IdentifyNew, Nepomuk::NoStoreResourcesFlags, additionalMetadata ); + QVERIFY( !m_dmModel->lastError() ); + + QList<Soprano::Statement> stList = m_model->listStatements( Soprano::Node(), NCO::fullname(), + Soprano::LiteralValue("Lion") ).allStatements(); + kDebug() << stList; + QVERIFY( stList.size() == 1 ); + Soprano::Node lionNode = stList.first().subject(); + QVERIFY( lionNode.isResource() ); + Soprano::Node graphNode = stList.first().context(); + QVERIFY( graphNode.isResource() ); + + QVERIFY( !m_model->containsAnyStatement( graphNode, RDF::type(), NRL::DiscardableInstanceBase() ) ); + + QVERIFY(!haveTrailingGraphs()); + } + { + Nepomuk::SimpleResource res; + res.addProperty( RDF::type(), NCO::Contact() ); + res.addProperty( NCO::fullname(), "Tiger" ); + + QUrl graphUri = m_nrlModel->createGraph( NRL::DiscardableInstanceBase() ); + + res.setUri(QUrl("nepomuk:/res/Tiger")); + QVERIFY( push( m_model, res, graphUri ) == res.properties().size() ); + res.setUri(QUrl("_:tiger")); + + Nepomuk::SimpleResourceGraph resGraph; + resGraph << res; + + QHash<QUrl, QVariant> additionalMetadata; + additionalMetadata.insert( RDF::type(), NRL::InstanceBase() ); + m_dmModel->storeResources( resGraph, "TestApp", Nepomuk::IdentifyNew, Nepomuk::NoStoreResourcesFlags, additionalMetadata ); + QVERIFY( !m_dmModel->lastError() ); + + QList<Soprano::Statement> stList = m_model->listStatements( Soprano::Node(), NCO::fullname(), + Soprano::LiteralValue("Tiger") ).allStatements(); + QVERIFY( stList.size() == 1 ); + Soprano::Node TigerNode = stList.first().subject(); + QVERIFY( TigerNode.isResource() ); + Soprano::Node graphNode = stList.first().context(); + QVERIFY( graphNode.isResource() ); + + QVERIFY( !m_model->containsAnyStatement( graphNode, RDF::type(), NRL::DiscardableInstanceBase() ) ); + + QVERIFY(!haveTrailingGraphs()); + } +} + + +void DataManagementModelTest::testStoreResources_createResource() +{ + // + // Simple case: create a resource by merging it + // + SimpleResource res; + res.setUri(QUrl("_:A")); + res.addProperty(RDF::type(), NAO::Tag()); + res.addProperty(NAO::prefLabel(), QLatin1String("Foobar")); + + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("testapp")); + QVERIFY( !m_dmModel->lastError() ); + + // check if the resource exists + QVERIFY(m_model->containsAnyStatement(Soprano::Node(), RDF::type(), NAO::Tag())); + QVERIFY(m_model->containsAnyStatement(Soprano::Node(), NAO::prefLabel(), Soprano::LiteralValue::createPlainLiteral(QLatin1String("Foobar")))); + + // make sure only one tag resource was created + QCOMPARE(m_model->listStatements(Node(), RDF::type(), NAO::Tag()).allElements().count(), 1); + + // get the new resources URI + const QUrl resUri = m_model->listStatements(Node(), RDF::type(), NAO::Tag()).iterateSubjects().allNodes().first().uri(); + + // check that it has the default metadata + QVERIFY(m_model->containsAnyStatement(resUri, NAO::created(), Node())); + QVERIFY(m_model->containsAnyStatement(resUri, NAO::lastModified(), Node())); + + // and both created and last modification date should be similar + QCOMPARE(m_model->listStatements(resUri, NAO::created(), Node()).iterateObjects().allNodes().first(), + m_model->listStatements(resUri, NAO::lastModified(), Node()).iterateObjects().allNodes().first()); + + // check if all the correct metadata graphs exist + // ask where { + // graph ?g { ?r a nao:Tag . ?r nao:prefLabel "Foobar" . } . + // graph ?mg { ?g a nrl:InstanceBase . ?mg a nrl:GraphMetadata . ?mg nrl:coreGraphMetadataFor ?g . } . + // ?g nao:maintainedBy ?a . ?a nao:identifier "testapp" + // } + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { " + "graph ?g { ?r a %1 . ?r %2 %3 . } . " + "graph ?mg { ?g a %4 . ?mg a %5 . ?mg %6 ?g . } . " + "?g %7 ?a . ?a %8 %9 . " + "}") + .arg(Soprano::Node::resourceToN3(NAO::Tag()), + Soprano::Node::resourceToN3(NAO::prefLabel()), + Soprano::Node::literalToN3(LiteralValue::createPlainLiteral(QLatin1String("Foobar"))), + Soprano::Node::resourceToN3(NRL::InstanceBase()), + Soprano::Node::resourceToN3(NRL::GraphMetadata()), + Soprano::Node::resourceToN3(NRL::coreGraphMetadataFor()), + Soprano::Node::resourceToN3(NAO::maintainedBy()), + Soprano::Node::resourceToN3(NAO::identifier()), + Soprano::Node::literalToN3(QLatin1String("testapp"))), + Soprano::Query::QueryLanguageSparql).boolValue()); + + // + // Now create the same resource again + // + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("testapp")); + QVERIFY( !m_dmModel->lastError() ); + + // nothing should have happened + QCOMPARE(existingStatements, Soprano::Graph(m_model->listStatements().allStatements())); + + // + // Now create the same resource with a different app + // + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("testapp2")); + QVERIFY( !m_dmModel->lastError() ); + + // only one thing should have been added: the new app Agent and its role as maintainer for the existing graph + // vHanda: Shouldn't there be a new graph, with the resources statements which hash both + // testapp and testapp2 as maintainers? + + //Q_FOREACH(const Soprano::Statement& s, existingStatements.toList()) { + // kDebug() << s; + // QVERIFY(m_model->containsStatement(s)); + //} + + // ask where { + // graph ?g { ?r a nao:Tag. ?r nao:prefLabel "Foobar" . } . + // ?g nao:maintainedBy ?a1 . ?a1 nao:identifier "testapp" . + // ?g nao:maintainedBy ?a2 . ?a2 nao:identifier "testapp2" . + // } + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { " + "graph ?g { ?r a %1 . ?r %2 %3 . } . " + "?g %4 ?a1 . ?a1 %5 %6 . " + "?g %4 ?a2 . ?a2 %5 %7 . " + "}") + .arg(Soprano::Node::resourceToN3(NAO::Tag()), + Soprano::Node::resourceToN3(NAO::prefLabel()), + Soprano::Node::literalToN3(LiteralValue::createPlainLiteral(QLatin1String("Foobar"))), + Soprano::Node::resourceToN3(NAO::maintainedBy()), + Soprano::Node::resourceToN3(NAO::identifier()), + Soprano::Node::literalToN3(QLatin1String("testapp")), + Soprano::Node::literalToN3(QLatin1String("testapp2"))), + Soprano::Query::QueryLanguageSparql).boolValue()); + + + // create a resource by specifying the URI +// SimpleResource res2; +// res2.setUri(QUrl("nepomuk:/res/A")); +// res2.addProperty(QUrl("prop:/string"), QVariant(QLatin1String("foobar"))); +// m_dmModel->storeResources(SimpleResourceGraph() << res2, QLatin1String("testapp")); +// QVERIFY( !m_dmModel->lastError() ); +// +// QVERIFY(m_model->containsAnyStatement( res2.uri(), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testStoreResources_invalid_args() +{ + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // empty resources -> no error but no change either + m_dmModel->storeResources(SimpleResourceGraph(), QLatin1String("testapp")); + + // no error + QVERIFY(!m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty app + m_dmModel->storeResources(SimpleResourceGraph(), QString()); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // invalid resource in graph + m_dmModel->storeResources(SimpleResourceGraph() << SimpleResource(), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // invalid range used in one resource + SimpleResource res; + res.setUri(QUrl("nepomuk:/res/A")); + res.addProperty(QUrl("prop:/int"), QVariant(QLatin1String("foobar"))); + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // non-existing file + const QUrl nonExistingFileUrl("file:///a/file/that/is/very/unlikely/to/exist"); + SimpleResource nonExistingFileRes; + nonExistingFileRes.setUri(nonExistingFileUrl); + nonExistingFileRes.addProperty(QUrl("prop:/int"), QVariant(42)); + m_dmModel->storeResources(SimpleResourceGraph() << nonExistingFileRes, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // non-existing file as object + nonExistingFileRes.setUri(QUrl("nepomuk:/res/A")); + nonExistingFileRes.addProperty(QUrl("prop:/res"), nonExistingFileUrl); + m_dmModel->storeResources(SimpleResourceGraph() << nonExistingFileRes, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // invalid graph metadata 1 + SimpleResource res2; + res.setUri(QUrl("nepomuk:/res/A")); + res.addProperty(QUrl("prop:/int"), QVariant(42)); + QHash<QUrl, QVariant> invalidMetadata; + invalidMetadata.insert(QUrl("prop:/int"), QLatin1String("foobar")); + m_dmModel->storeResources(SimpleResourceGraph() << res2, QLatin1String("testapp"), Nepomuk::IdentifyNew, Nepomuk::NoStoreResourcesFlags, invalidMetadata); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // invalid graph metadata 2 + invalidMetadata.clear(); + invalidMetadata.insert(NAO::maintainedBy(), QLatin1String("foobar")); + m_dmModel->storeResources(SimpleResourceGraph() << res2, QLatin1String("testapp"), Nepomuk::IdentifyNew, Nepomuk::NoStoreResourcesFlags, invalidMetadata); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +void DataManagementModelTest::testStoreResources_invalid_args_with_existing() +{ + // create a test resource + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + // create the resource to delete + QUrl resA("nepomuk:/res/A"); + QUrl resB("nepomuk:/res/B"); + m_model->addStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(resA, QUrl("prop:/res"), resB, g1); + const QDateTime now = QDateTime::currentDateTime(); + m_model->addStatement(resA, NAO::created(), LiteralValue(now), g1); + m_model->addStatement(resA, NAO::lastModified(), LiteralValue(now), g1); + + + // create a resource to merge + SimpleResource a(resA); + a.addProperty(QUrl("prop:/int"), 42); + + + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // empty resources -> no error but no change either + m_dmModel->storeResources(SimpleResourceGraph(), QLatin1String("testapp")); + + // no error + QVERIFY(!m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // empty app + m_dmModel->storeResources(SimpleResourceGraph() << a, QString()); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // invalid resource in graph + m_dmModel->storeResources(SimpleResourceGraph() << a << SimpleResource(), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // invalid range used in one resource + SimpleResource res(a); + res.addProperty(QUrl("prop:/int"), QVariant(QLatin1String("foobar"))); + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // non-existing file as object + const QUrl nonExistingFileUrl("file:///a/file/that/is/very/unlikely/to/exist"); + SimpleResource nonExistingFileRes(a); + nonExistingFileRes.addProperty(QUrl("prop:/res"), nonExistingFileUrl); + m_dmModel->storeResources(SimpleResourceGraph() << nonExistingFileRes, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // invalid cardinality in resource + SimpleResource invalidCRes(a); + invalidCRes.addProperty(QUrl("prop:/int_c1"), 42); + invalidCRes.addProperty(QUrl("prop:/int_c1"), 2); + m_dmModel->storeResources(SimpleResourceGraph() << invalidCRes, QLatin1String("testapp")); + + // the call should have failed + QVERIFY(m_dmModel->lastError()); + + // nothing should have changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +void DataManagementModelTest::testStoreResources_file1() +{ + QTemporaryFile fileA; + fileA.open(); + + // merge a file URL + SimpleResource r1; + r1.setUri(QUrl::fromLocalFile(fileA.fileName())); + r1.addProperty(RDF::type(), NAO::Tag()); + r1.addProperty(QUrl("prop:/string"), QLatin1String("Foobar")); + + m_dmModel->storeResources(SimpleResourceGraph() << r1, QLatin1String("testapp")); + QVERIFY( !m_dmModel->lastError() ); + + // a nie:url relation should have been created + QVERIFY(m_model->containsAnyStatement(Node(), NIE::url(), QUrl::fromLocalFile(fileA.fileName()))); + + // the file URL should never be used as subject + QVERIFY(!m_model->containsAnyStatement(QUrl::fromLocalFile(fileA.fileName()), Node(), Node())); + + // make sure file URL and res URI are properly related including the properties + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { ?r %1 %4 . " + "?r a %2 . " + "?r <prop:/string> %3 . }") + .arg(Node::resourceToN3(NIE::url()), + Node::resourceToN3(NAO::Tag()), + Node::literalToN3(LiteralValue(QLatin1String("Foobar"))), + Node::resourceToN3(QUrl::fromLocalFile(fileA.fileName()))), + Query::QueryLanguageSparql).boolValue()); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testStoreResources_file2() +{ + // merge a property with non-existing file value + QTemporaryFile fileA; + fileA.open(); + const QUrl fileUrl = QUrl::fromLocalFile(fileA.fileName()); + + SimpleResource r1; + r1.addProperty(QUrl("prop:/res"), fileUrl); + + m_dmModel->storeResources(SimpleResourceGraph() << r1, QLatin1String("testapp")); + QVERIFY( !m_dmModel->lastError() ); + + QList<Soprano::Statement> stList = m_model->listStatements( Node(), QUrl("prop:/res"), Node() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + const QUrl r1Uri = stList.first().subject().uri(); + + // the property should have been created + QVERIFY(m_model->containsAnyStatement(r1Uri, QUrl("prop:/res"), Node())); + + // but it should not be related to the file URL + QVERIFY(!m_model->containsAnyStatement(r1Uri, QUrl("prop:/res"), fileUrl)); + + // there should be a nie:url for the file URL + QVERIFY(m_model->containsAnyStatement(Node(), NIE::url(), fileUrl)); + + // make sure file URL and res URI are properly related including the properties + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { %3 <prop:/res> ?r . " + "?r %1 %2 . }") + .arg(Node::resourceToN3(NIE::url()), + Node::resourceToN3(fileUrl), + Node::resourceToN3(r1Uri)), + Query::QueryLanguageSparql).boolValue()); + + QVERIFY(!haveTrailingGraphs()); +} + + +void DataManagementModelTest::testStoreResources_file3() +{ + QTemporaryFile fileA; + fileA.open(); + const QUrl fileUrl = QUrl::fromLocalFile(fileA.fileName()); + + SimpleResource r1; + r1.setUri( fileUrl ); + r1.addProperty( RDF::type(), QUrl("class:/typeA") ); + + m_dmModel->storeResources( SimpleResourceGraph() << r1, QLatin1String("origApp") ); + QVERIFY( !m_dmModel->lastError() ); + + // Make sure all the data has been added and it belongs to origApp + QString query = QString::fromLatin1("ask { graph ?g { ?r a %6 . " + " ?r %7 %1 . " + " ?r a %2 . } ?g %3 ?app . ?app %4 %5 . }") + .arg( Soprano::Node::resourceToN3( fileUrl ), + Soprano::Node::resourceToN3( QUrl("class:/typeA") ), + Soprano::Node::resourceToN3( NAO::maintainedBy() ), + Soprano::Node::resourceToN3( NAO::identifier() ), + Soprano::Node(Soprano::LiteralValue("origApp")).toN3(), + Soprano::Node::resourceToN3( NFO::FileDataObject()), + Soprano::Node::resourceToN3( NIE::url() ) + ); + + QVERIFY( m_model->executeQuery( query, Soprano::Query::QueryLanguageSparql ).boolValue() ); + + QList< Statement > stList = m_model->listStatements( Soprano::Node(), NIE::url(), fileUrl ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + QUrl resUri = stList.first().subject().uri(); + QVERIFY( resUri.scheme() == QLatin1String("nepomuk") ); + + SimpleResource r2; + r2.setUri( fileUrl ); + r2.addProperty( QUrl("prop:/res"), NFO::FileDataObject() ); + + m_dmModel->storeResources( SimpleResourceGraph() << r2, QLatin1String("newApp") ); + QVERIFY( !m_dmModel->lastError() ); + + // Make sure it was identified properly + stList = m_model->listStatements( Soprano::Node(), NIE::url(), fileUrl ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + QUrl resUri2 = stList.first().subject().uri(); + QVERIFY( resUri2.scheme() == QLatin1String("nepomuk") ); + QCOMPARE( resUri, resUri2 ); + + // The only statement that should have acquired "newApp" as the maintainer should be + // r2 <prop:/res> <object:/custom> + + query = QString::fromLatin1("select ?name where { graph ?g { %1 %2 %3 . %1 a %4. }" + " ?g %5 ?app . ?app %6 ?name . }") + .arg( Soprano::Node::resourceToN3( resUri ), + Soprano::Node::resourceToN3( NIE::url() ), + Soprano::Node::resourceToN3( fileUrl ), + Soprano::Node::resourceToN3( NFO::FileDataObject() ), + Soprano::Node::resourceToN3( NAO::maintainedBy() ), + Soprano::Node::resourceToN3( NAO::identifier() ) ); + + QueryResultIterator it = m_model->executeQuery( query, Soprano::Query::QueryLanguageSparql ); + QStringList appList; + while( it.next() ) + appList << it["name"].literal().toString(); + + QCOMPARE( appList.size(), 1 ); + + //query = QString::fromLatin1("ask { ) + +} + + +void DataManagementModelTest::testStoreResources_file4() +{ + QTemporaryFile fileA; + fileA.open(); + const QUrl fileUrl = QUrl::fromLocalFile(fileA.fileName()); + + SimpleResource res; + res.addProperty( RDF::type(), fileUrl ); + res.addProperty( QUrl("prop:/res"), fileUrl ); + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("app1") ); + QVERIFY( !m_dmModel->lastError() ); + + // Make sure the fileUrl got added + QList< Statement > stList = m_model->listStatements( Node(), NIE::url(), fileUrl ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + QUrl fileResUri = stList.first().subject().uri(); + + // Make sure the SimpleResource was stored + stList = m_model->listStatements( Node(), RDF::type(), fileResUri ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + QUrl resUri = stList.first().subject().uri(); + + // Check for the other statement + stList = m_model->listStatements( resUri, QUrl("prop:/res"), Node() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + QUrl fileResUri2 = stList.first().object().uri(); + QCOMPARE( fileResUri, fileResUri2 ); +} + + +void DataManagementModelTest::testStoreResources_folder() +{ + QTemporaryFile file; + QVERIFY( file.open() ); + KUrl fileUrl = KUrl::fromLocalFile( file.fileName() ); + QUrl folderUrl = QUrl::fromLocalFile( fileUrl.directory() ); + + SimpleResource res( fileUrl ); + res.addProperty( RDF::type(), NMM::MusicPiece() ); + res.addProperty( NAO::prefLabel(), QLatin1String("Label") ); + res.addProperty( NIE::isPartOf(), fileUrl.directory() ); + + SimpleResourceGraph graph; + graph << res; + + m_dmModel->storeResources( graph, QLatin1String("testApp") ); + QVERIFY( !m_dmModel->lastError() ); + + QList<Statement> stList = m_model->listStatements( Node(), NIE::isPartOf(), Node() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + const QUrl folderResUri = stList.first().object().uri(); + QVERIFY( m_dmModel->containsAnyStatement( folderResUri, RDF::type(), NFO::FileDataObject() ) ); + QVERIFY( m_dmModel->containsAnyStatement( folderResUri, RDF::type(), NFO::Folder() ) ); + QVERIFY( m_dmModel->containsAnyStatement( folderResUri, NIE::url(), folderUrl ) ); +} + + +void DataManagementModelTest::testStoreResources_fileExists() +{ + SimpleResource res(QUrl("file:///a/b/v/c/c")); + res.addType( NMM::MusicPiece() ); + res.addProperty( NAO::numericRating(), 10 ); + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("app") ); + + // Should give an error - The file does not exist ( probably ) + QVERIFY( m_dmModel->lastError() ); +} + + +void DataManagementModelTest::testStoreResources_sameNieUrl() +{ + QTemporaryFile fileA; + fileA.open(); + const QUrl fileUrl = QUrl::fromLocalFile(fileA.fileName()); + + SimpleResource res; + res.setUri( fileUrl ); + res.addProperty( RDF::type(), NFO::FileDataObject() ); + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("app1") ); + QVERIFY( !m_dmModel->lastError() ); + + // Make sure the fileUrl got added + QList< Statement > stList = m_model->listStatements( Node(), NIE::url(), fileUrl ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + QUrl fileResUri = stList.first().subject().uri(); + + // Make sure there is only one rdf:type nfo:FileDataObject + stList = m_model->listStatements( Node(), RDF::type(), NFO::FileDataObject() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + QCOMPARE( stList.first().subject().uri(), fileResUri ); + + SimpleResource res2; + res2.addProperty( NIE::url(), fileUrl ); + res2.addProperty( NAO::numericRating(), QVariant(10) ); + + m_dmModel->storeResources( SimpleResourceGraph() << res2, QLatin1String("app1") ); + QVERIFY( !m_dmModel->lastError() ); + + // Make sure it got mapped + stList = m_model->listStatements( Node(), NAO::numericRating(), LiteralValue(10) ).allStatements(); + QCOMPARE( stList.size(), 1 ); + QCOMPARE( stList.first().subject().uri(), fileResUri ); +} + +// metadata should be ignored when merging one resource into another +void DataManagementModelTest::testStoreResources_metadata() +{ + // create our app + const QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // create a resource + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + const QDateTime now = QDateTime::currentDateTime(); + QUrl resA("nepomuk:/res/A"); + m_model->addStatement(resA, RDF::type(), NAO::Tag(), g1); + m_model->addStatement(resA, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("Foobar")), g1); + m_model->addStatement(resA, QUrl("prop:/string2"), LiteralValue(QLatin1String("Foobar2")), g1); + m_model->addStatement(resA, NAO::created(), LiteralValue(now), g1); + m_model->addStatement(resA, NAO::lastModified(), LiteralValue(now), g1); + + + // now we merge the same resource (with differing metadata) + SimpleResource a; + a.addProperty(RDF::type(), NAO::Tag()); + a.addProperty(QUrl("prop:/int"), QVariant(42)); + a.addProperty(QUrl("prop:/string"), QVariant(QLatin1String("Foobar"))); + QDateTime creationDateTime(QDate(2010, 12, 24), QTime::currentTime()); + a.addProperty(NAO::created(), QVariant(creationDateTime)); + + // merge the resource + m_dmModel->storeResources(SimpleResourceGraph() << a, QLatin1String("B")); + QVERIFY(!m_dmModel->lastError()); + + // make sure no new resource has been created + QCOMPARE(m_model->listStatements(Node(), RDF::type(), NAO::Tag()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(Node(), QUrl("prop:/int"), Node()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(Node(), QUrl("prop:/string"), Node()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(resA, NAO::created(), Node()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(resA, NAO::lastModified(), Node()).allStatements().count(), 1); + + // make sure the new app has been created + QueryResultIterator it = m_model->executeQuery(QString::fromLatin1("select ?a where { ?a a %1 . ?a %2 %3 . }") + .arg(Node::resourceToN3(NAO::Agent()), + Node::resourceToN3(NAO::identifier()), + Node::literalToN3(QLatin1String("B"))), + Soprano::Query::QueryLanguageSparql); + QVERIFY(it.next()); + const QUrl appBRes = it[0].uri(); + + // make sure the data is now maintained by both apps + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { graph ?g { %1 <prop:/int> %2 . } . ?g %3 %4 . ?g %3 <app:/A> . }") + .arg(Node::resourceToN3(resA), + Node::literalToN3(42), + Node::resourceToN3(NAO::maintainedBy()), + Node::resourceToN3(appBRes)), + Soprano::Query::QueryLanguageSparql).boolValue()); + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { graph ?g { %1 <prop:/string> %2 . } . ?g %3 %4 . ?g %3 <app:/A> . }") + .arg(Node::resourceToN3(resA), + Node::literalToN3(QLatin1String("Foobar")), + Node::resourceToN3(NAO::maintainedBy()), + Node::resourceToN3(appBRes)), + Soprano::Query::QueryLanguageSparql).boolValue()); + QVERIFY(m_model->executeQuery(QString::fromLatin1("ask where { graph ?g { %1 a %2 . } . ?g %3 %4 . ?g %3 <app:/A> . }") + .arg(Node::resourceToN3(resA), + Node::resourceToN3(NAO::Tag()), + Node::resourceToN3(NAO::maintainedBy()), + Node::resourceToN3(appBRes)), + Soprano::Query::QueryLanguageSparql).boolValue()); + QVERIFY(!m_model->executeQuery(QString::fromLatin1("ask where { graph ?g { %1 <prop:/string2> %2 . } . ?g %3 %4 . ?g %3 <app:/A> . }") + .arg(Node::resourceToN3(resA), + Node::literalToN3(QLatin1String("Foobar2")), + Node::resourceToN3(NAO::maintainedBy()), + Node::resourceToN3(appBRes)), + Soprano::Query::QueryLanguageSparql).boolValue()); + + // Make sure that the nao:lastModified and nao:created have not changed + QDateTime mod = m_model->listStatements( resA, NAO::lastModified(), Soprano::Node() ).iterateObjects().allNodes().first().literal().toDateTime(); + QDateTime creation = m_model->listStatements( resA, NAO::created(), Soprano::Node() ).iterateObjects().allNodes().first().literal().toDateTime(); + + QCOMPARE( mod, now ); + // The creation date for now will always stay the same. + // FIXME: Should we allow changes? + QVERIFY( creation != creationDateTime ); + + QVERIFY(!haveTrailingGraphs()); + + + // now merge the same resource with some new data - just to make sure the metadata is updated properly + SimpleResource sResA(resA); + sResA.addProperty(RDF::type(), NAO::Tag()); + sResA.addProperty(QUrl("prop:/int2"), 42); + + // merge the resource + m_dmModel->storeResources(SimpleResourceGraph() << sResA, QLatin1String("B")); + QVERIFY(!m_dmModel->lastError()); + + // make sure the new data is there + QVERIFY(m_model->containsAnyStatement(resA, QUrl("prop:/int2"), LiteralValue(42))); + + // make sure creation date did not change + QCOMPARE(m_model->listStatements(resA, NAO::created(), Node()).allStatements().count(), 1); + QVERIFY(m_model->containsAnyStatement(resA, NAO::created(), LiteralValue(now))); + + // make sure mtime has changed - the resource has changed + QCOMPARE(m_model->listStatements(resA, NAO::lastModified(), Node()).allStatements().count(), 1); + QVERIFY(m_model->listStatements(resA, NAO::lastModified(), Node()).iterateObjects().allNodes().first().literal().toDateTime() != now); +} + +void DataManagementModelTest::testStoreResources_protectedTypes() +{ + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // property + SimpleResource propertyRes(QUrl("prop:/res")); + propertyRes.addProperty(QUrl("prop:/int"), 42); + + // store the resource + m_dmModel->storeResources(SimpleResourceGraph() << propertyRes, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // class + SimpleResource classRes(NRL::Graph()); + classRes.addProperty(QUrl("prop:/int"), 42); + + // store the resource + m_dmModel->storeResources(SimpleResourceGraph() << classRes, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // graph + SimpleResource graphRes(QUrl("graph:/onto")); + propertyRes.addProperty(QUrl("prop:/int"), 42); + + // store the resource + m_dmModel->storeResources(SimpleResourceGraph() << graphRes, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +// make sure storeResources ignores supertypes +void DataManagementModelTest::testStoreResources_superTypes() +{ + // 1. create a resource to merge + QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + QUrl resA("nepomuk:/res/A"); + m_model->addStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("hello world")), g1); + m_model->addStatement(resA, RDF::type(), QUrl("class:/typeA"), g1); + m_model->addStatement(resA, RDF::type(), QUrl("class:/typeB"), g1); + const QDateTime now = QDateTime::currentDateTime(); + m_model->addStatement(resA, NAO::created(), LiteralValue(now), g1); + m_model->addStatement(resA, NAO::lastModified(), LiteralValue(now), g1); + + + // now merge the same resource (excluding the super-type A) + SimpleResource a; + a.addProperty(RDF::type(), QUrl("class:/typeB")); + a.addProperty(QUrl("prop:/string"), QLatin1String("hello world")); + + m_dmModel->storeResources(SimpleResourceGraph() << a, QLatin1String("A")); + QVERIFY( !m_dmModel->lastError() ); + + + // make sure the existing resource was reused + QCOMPARE(m_model->listStatements(Node(), QUrl("prop:/string"), LiteralValue(QLatin1String("hello world"))).allElements().count(), 1); +} + +// make sure merging even works with missing metadata in store +void DataManagementModelTest::testStoreResources_missingMetadata() +{ + // create our app + const QUrl appG = m_nrlModel->createGraph(NRL::InstanceBase()); + m_model->addStatement(QUrl("app:/A"), RDF::type(), NAO::Agent(), appG); + m_model->addStatement(QUrl("app:/A"), NAO::identifier(), LiteralValue(QLatin1String("A")), appG); + + // create a resource (without creation date) + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + m_model->addStatement(g1, NAO::maintainedBy(), QUrl("app:/A"), mg1); + + const QDateTime now = QDateTime::currentDateTime(); + QUrl resA("nepomuk:/res/A"); + m_model->addStatement(resA, RDF::type(), NAO::Tag(), g1); + m_model->addStatement(resA, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("Foobar")), g1); + m_model->addStatement(resA, NAO::lastModified(), LiteralValue(now), g1); + + + // now we merge the same resource + SimpleResource a; + a.addProperty(RDF::type(), NAO::Tag()); + a.addProperty(QUrl("prop:/int"), QVariant(42)); + a.addProperty(QUrl("prop:/string"), QVariant(QLatin1String("Foobar"))); + + // merge the resource + m_dmModel->storeResources(SimpleResourceGraph() << a, QLatin1String("B")); + QVERIFY( !m_dmModel->lastError() ); + + // make sure no new resource has been created + QCOMPARE(m_model->listStatements(Node(), RDF::type(), NAO::Tag()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(Node(), QUrl("prop:/int"), Node()).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(Node(), QUrl("prop:/string"), Node()).allStatements().count(), 1); + + QVERIFY(!haveTrailingGraphs()); + + + // now merge the same resource with some new data - just to make sure the metadata is updated properly + SimpleResource simpleResA(resA); + simpleResA.addProperty(RDF::type(), NAO::Tag()); + simpleResA.addProperty(QUrl("prop:/int2"), 42); + + // merge the resource + m_dmModel->storeResources(SimpleResourceGraph() << simpleResA, QLatin1String("B")); + QVERIFY( !m_dmModel->lastError() ); + + // make sure the new data is there + QVERIFY(m_model->containsAnyStatement(resA, QUrl("prop:/int2"), LiteralValue(42))); + + // make sure creation date did not change, ie. it was not created as that would be wrong + QVERIFY(!m_model->containsAnyStatement(resA, NAO::created(), Node())); + + // make sure the last mtime has been updated + QCOMPARE(m_model->listStatements(resA, NAO::lastModified(), Node()).allStatements().count(), 1); + QDateTime newDt = m_model->listStatements(resA, NAO::lastModified(), Node()).iterateObjects().allNodes().first().literal().toDateTime(); + QVERIFY( newDt > now); + + // + // Merge the resource again, but this time make sure it is identified as well + // + SimpleResource resB; + resB.addProperty(RDF::type(), NAO::Tag()); + resB.addProperty(QUrl("prop:/int"), QVariant(42)); + resB.addProperty(QUrl("prop:/string"), QVariant(QLatin1String("Foobar"))); + resB.addProperty(QUrl("prop:/int2"), 42); + resB.addProperty(QUrl("prop:/int3"), 50); + + // merge the resource + m_dmModel->storeResources(SimpleResourceGraph() << resB, QLatin1String("B")); + QVERIFY( !m_dmModel->lastError() ); + + // make sure the new data is there + QVERIFY(m_model->containsAnyStatement(resA, QUrl("prop:/int3"), LiteralValue(50))); + + // make sure creation date did not change, ie. it was not created as that would be wrong + QVERIFY(!m_model->containsAnyStatement(resA, NAO::created(), Node())); + + // make sure the last mtime has been updated + QCOMPARE(m_model->listStatements(resA, NAO::lastModified(), Node()).allStatements().count(), 1); + QVERIFY(m_model->listStatements(resA, NAO::lastModified(), Node()).iterateObjects().allNodes().first().literal().toDateTime() > newDt); +} + +// test merging when there is more than one candidate resource to merge with +void DataManagementModelTest::testStoreResources_multiMerge() +{ + // create two resource which could be matches for the one we will store + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase()); + + // the resource in which we want to merge + QUrl resA("nepomuk:/res/A"); + m_model->addStatement(resA, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resA, RDF::type(), NAO::Tag(), g1); + m_model->addStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(resA, NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + m_model->addStatement(resA, NAO::lastModified(), LiteralValue(QDateTime::currentDateTime()), g1); + + QUrl resB("nepomuk:/res/B"); + m_model->addStatement(resB, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resB, RDF::type(), NAO::Tag(), g1); + m_model->addStatement(resB, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(resB, NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + m_model->addStatement(resB, NAO::lastModified(), LiteralValue(QDateTime::currentDateTime()), g1); + + + // now store the exact same resource + SimpleResource res; + res.addProperty(RDF::type(), NAO::Tag()); + res.addProperty(QUrl("prop:/int"), 42); + res.addProperty(QUrl("prop:/string"), QLatin1String("foobar")); + + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("A")); + QVERIFY(!m_dmModel->lastError()); + + // make sure no new resource was created + QCOMPARE(m_model->listStatements(Node(), QUrl("prop:/int"), LiteralValue(42)).allElements().count(), 2); + QCOMPARE(m_model->listStatements(Node(), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar"))).allElements().count(), 2); + + // make sure both resources still exist + QVERIFY(m_model->containsAnyStatement(resA, Node(), Node())); + QVERIFY(m_model->containsAnyStatement(resB, Node(), Node())); +} + +// an example from real-life which made an early version of DMS fail +void DataManagementModelTest::testStoreResources_realLife() +{ + // we deal with one file + QTemporaryFile theFile; + theFile.open(); + + // the full data - slightly cleanup up (excluding additional video track resources) + // this data is a combination from the file indexing service and DMS storeResources calls + + // the resource URIs used + const QUrl fileResUri("nepomuk:/res/3ff603a5-4023-4c2f-bd89-372002a0ffd2"); + const QUrl tvSeriesUri("nepomuk:/res/e6fbe22d-bb5c-416c-a935-407a34b58c76"); + const QUrl appRes("nepomuk:/res/275907b0-c120-4581-83d5-ea9ec034dbcd"); + + // the graph URIs + // two strigi graphs due to the nie:url preservation + const QUrl strigiG1("nepomuk:/ctx/5ca62cd0-ccff-4484-99e3-1fd9f782a3a4"); + const QUrl strigiG2("nepomuk:/ctx/9f0bac21-f8e4-4e82-b51d-e0a7585f1c8d"); + const QUrl strigiMG1("nepomuk:/ctx/0117104c-d501-48ce-badd-6f363bfde3e2"); + const QUrl strigiMG2("nepomuk:/ctx/e52c8b8a-2e32-4a27-8633-03f27fec441b"); + + const QUrl dmsG1("nepomuk:/ctx/8bea556f-cacf-4f31-be73-7f7c0f14024b"); + const QUrl dmsG2("nepomuk:/ctx/374d3968-0d20-4807-8a87-d2d6b87e7de3"); + const QUrl dmsMG1("nepomuk:/ctx/9902847f-dfe8-489a-881b-4abf1707fee7"); + const QUrl dmsMG2("nepomuk:/ctx/72ef2cdf-26e7-42b6-9093-0b7b0a7c25fc"); + + const QUrl appG1("nepomuk:/ctx/7dc9f013-4e45-42bf-8595-a12e78adde81"); + const QUrl appMG1("nepomuk:/ctx/1ffcb2bb-525d-4173-b211-ebdf28c0897b"); + + // strings we reuse + const QString seriesTitle("Nepomuk The Series"); + const QString episodeTitle("Who are you?"); + const QString seriesOverview("Nepomuk is a series about information and the people needing this information for informational purposes."); + const QString episodeOverview("The series pilot focusses on this and that, nothing in particular and it not really that interesting at all."); + + // the file resource + m_model->addStatement(fileResUri, NIE::isPartOf(), QUrl("nepomuk:/res/e9f85f29-150d-49b6-9ffb-264ae7ec3864"), strigiG1); + m_model->addStatement(fileResUri, NIE::contentSize(), Soprano::LiteralValue::fromString("369532928", XMLSchema::xsdInt()), strigiG1); + m_model->addStatement(fileResUri, NIE::mimeType(), Soprano::LiteralValue::fromString("audio/x-riff", XMLSchema::string()), strigiG1); + m_model->addStatement(fileResUri, NIE::mimeType(), Soprano::LiteralValue::fromString("video/x-msvideo", XMLSchema::string()), strigiG1); + m_model->addStatement(fileResUri, NFO::fileName(), Soprano::LiteralValue(KUrl(theFile.fileName()).fileName()), strigiG1); + m_model->addStatement(fileResUri, NIE::lastModified(), Soprano::LiteralValue::fromString("2010-06-29T15:44:44Z", XMLSchema::dateTime()), strigiG1); + m_model->addStatement(fileResUri, NFO::codec(), Soprano::LiteralValue::fromString("MP3", XMLSchema::string()), strigiG1); + m_model->addStatement(fileResUri, NFO::codec(), Soprano::LiteralValue::fromString("xvid", XMLSchema::string()), strigiG1); + m_model->addStatement(fileResUri, NFO::averageBitrate(), Soprano::LiteralValue::fromString("1132074", XMLSchema::xsdInt()), strigiG1); + m_model->addStatement(fileResUri, NFO::duration(), Soprano::LiteralValue::fromString("2567", XMLSchema::xsdInt()), strigiG1); + m_model->addStatement(fileResUri, NFO::duration(), Soprano::LiteralValue::fromString("2611", XMLSchema::xsdInt()), strigiG1); + m_model->addStatement(fileResUri, NFO::frameRate(), Soprano::LiteralValue::fromString("23", XMLSchema::xsdInt()), strigiG1); + m_model->addStatement(fileResUri, NIE::hasPart(), QUrl("nepomuk:/res/b805e3bb-db13-4561-b457-8da8d13ce34d"), strigiG1); + m_model->addStatement(fileResUri, NIE::hasPart(), QUrl("nepomuk:/res/c438df3c-1446-4931-9d9e-3665567025b9"), strigiG1); + m_model->addStatement(fileResUri, NFO::horizontalResolution(), Soprano::LiteralValue::fromString("624", XMLSchema::xsdInt()), strigiG1); + m_model->addStatement(fileResUri, NFO::verticalResolution(), Soprano::LiteralValue::fromString("352", XMLSchema::xsdInt()), strigiG1); + + m_model->addStatement(fileResUri, RDF::type(), NFO::FileDataObject(), strigiG2); + m_model->addStatement(fileResUri, NIE::url(), QUrl::fromLocalFile(theFile.fileName()), strigiG2); + + m_model->addStatement(fileResUri, RDF::type(), NMM::TVShow(), dmsG1); + m_model->addStatement(fileResUri, NAO::created(), Soprano::LiteralValue::fromString("2011-03-14T10:06:38.317Z", XMLSchema::dateTime()), dmsG1); + m_model->addStatement(fileResUri, NIE::title(), Soprano::LiteralValue(episodeTitle), dmsG1); + m_model->addStatement(fileResUri, NAO::lastModified(), Soprano::LiteralValue::fromString("2011-03-14T10:06:38.317Z", XMLSchema::dateTime()), dmsG1); + m_model->addStatement(fileResUri, NMM::synopsis(), Soprano::LiteralValue(episodeOverview), dmsG1); + m_model->addStatement(fileResUri, NMM::series(), tvSeriesUri, dmsG1); + m_model->addStatement(fileResUri, NMM::season(), Soprano::LiteralValue::fromString("1", XMLSchema::xsdInt()), dmsG1); + m_model->addStatement(fileResUri, NMM::episodeNumber(), Soprano::LiteralValue::fromString("1", XMLSchema::xsdInt()), dmsG1); + m_model->addStatement(fileResUri, RDF::type(), NIE::InformationElement(), dmsG2); + m_model->addStatement(fileResUri, RDF::type(), NFO::Video(), dmsG2); + + // the TV Series resource + m_model->addStatement(tvSeriesUri, RDF::type(), NMM::TVSeries(), dmsG1); + m_model->addStatement(tvSeriesUri, NAO::created(), Soprano::LiteralValue::fromString("2011-03-14T10:06:38.317Z", XMLSchema::dateTime()), dmsG1); + m_model->addStatement(tvSeriesUri, NIE::title(), Soprano::LiteralValue(seriesTitle), dmsG1); + m_model->addStatement(tvSeriesUri, NAO::lastModified(), Soprano::LiteralValue::fromString("2011-03-14T10:06:38.317Z", XMLSchema::dateTime()), dmsG1); + m_model->addStatement(tvSeriesUri, NIE::description(), Soprano::LiteralValue(seriesOverview), dmsG1); + m_model->addStatement(tvSeriesUri, NMM::hasEpisode(), fileResUri, dmsG1); + m_model->addStatement(tvSeriesUri, RDF::type(), NIE::InformationElement(), dmsG2); + + // the app that called storeResources + m_model->addStatement(appRes, RDF::type(), NAO::Agent(), appG1); + m_model->addStatement(appRes, NAO::prefLabel(), Soprano::LiteralValue::fromString("Nepomuk TVNamer", XMLSchema::string()), appG1); + m_model->addStatement(appRes, NAO::identifier(), Soprano::LiteralValue::fromString("nepomuktvnamer", XMLSchema::string()), appG1); + + // all the graph metadata + m_model->addStatement(strigiG1, RDF::type(), NRL::DiscardableInstanceBase(), strigiMG1); + m_model->addStatement(strigiG1, NAO::created(), Soprano::LiteralValue::fromString("2010-10-22T14:13:42.204Z", XMLSchema::dateTime()), strigiMG1); + m_model->addStatement(strigiG1, QUrl("http://www.strigi.org/fields#indexGraphFor"), fileResUri, strigiMG1); + m_model->addStatement(strigiMG1, RDF::type(), NRL::GraphMetadata(), strigiMG1); + m_model->addStatement(strigiMG1, NRL::coreGraphMetadataFor(), strigiG1, strigiMG1); + + m_model->addStatement(strigiG2, RDF::type(), NRL::InstanceBase(), strigiMG2); + m_model->addStatement(strigiG2, NAO::created(), Soprano::LiteralValue::fromString("2010-10-22T14:13:42.204Z", XMLSchema::dateTime()), strigiMG2); + m_model->addStatement(strigiG2, QUrl("http://www.strigi.org/fields#indexGraphFor"), fileResUri, strigiMG2); + m_model->addStatement(strigiG2, NAO::maintainedBy(), appRes, strigiMG2); + m_model->addStatement(strigiMG2, RDF::type(), NRL::GraphMetadata(), strigiMG2); + m_model->addStatement(strigiMG2, NRL::coreGraphMetadataFor(), strigiG2, strigiMG2); + + m_model->addStatement(dmsG1, RDF::type(), NRL::InstanceBase(), dmsMG1); + m_model->addStatement(dmsG1, NAO::created(), Soprano::LiteralValue::fromString("2011-03-14T10:06:38.343Z", XMLSchema::dateTime()), dmsMG1); + m_model->addStatement(dmsG1, NAO::maintainedBy(), appRes, dmsMG1); + m_model->addStatement(dmsMG1, RDF::type(), NRL::GraphMetadata(), dmsMG1); + m_model->addStatement(dmsMG1, NRL::coreGraphMetadataFor(), dmsG1, dmsMG1); + + m_model->addStatement(dmsG2, RDF::type(), NRL::InstanceBase(), dmsMG2); + m_model->addStatement(dmsG2, NAO::created(), Soprano::LiteralValue::fromString("2011-03-14T10:06:38.621Z", XMLSchema::dateTime()), dmsMG2); + m_model->addStatement(dmsG2, NAO::maintainedBy(), appRes, dmsMG2); + m_model->addStatement(dmsMG2, RDF::type(), NRL::GraphMetadata(), dmsMG2); + m_model->addStatement(dmsMG2, NRL::coreGraphMetadataFor(), dmsG2, dmsMG2); + + m_model->addStatement(appG1, RDF::type(), NRL::InstanceBase(), appMG1); + m_model->addStatement(appG1, NAO::created(), Soprano::LiteralValue::fromString("2011-03-12T17:48:44.307Z", XMLSchema::dateTime()), appMG1); + m_model->addStatement(appMG1, RDF::type(), NRL::GraphMetadata(), appMG1); + m_model->addStatement(appMG1, NRL::coreGraphMetadataFor(), appG1, appMG1); + + + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // now the TV show information is stored again + SimpleResourceGraph graph; + SimpleResource tvShowRes(QUrl::fromLocalFile(theFile.fileName())); + tvShowRes.addProperty(RDF::type(), NMM::TVShow()); + tvShowRes.addProperty(NMM::episodeNumber(), 1); + tvShowRes.addProperty(NMM::season(), 1); + tvShowRes.addProperty(NIE::title(), episodeTitle); + tvShowRes.addProperty(NMM::synopsis(), episodeOverview); + + SimpleResource tvSeriesRes; + tvSeriesRes.addProperty(RDF::type(), NMM::TVSeries()); + tvSeriesRes.addProperty(NIE::title(), seriesTitle); + tvSeriesRes.addProperty(NIE::description(), seriesOverview); + tvSeriesRes.addProperty(NMM::hasEpisode(), tvShowRes.uri()); + + tvShowRes.addProperty(NMM::series(), tvSeriesRes.uri()); + + graph << tvShowRes << tvSeriesRes; + + m_dmModel->storeResources(graph, QLatin1String("nepomuktvnamer")); + QVERIFY(!m_dmModel->lastError()); + + // now test the data - nothing should have changed at all + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +void DataManagementModelTest::testStoreResources_trivialMerge() +{ + // we create a resource with some properties + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase()); + + QUrl resA("nepomuk:/res/A"); + m_model->addStatement(resA, RDF::type(), QUrl("class:/typeA"), g1); + m_model->addStatement(resA, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(resA, NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + m_model->addStatement(resA, NAO::lastModified(), LiteralValue(QDateTime::currentDateTime()), g1); + + + // now we store a trivial resource + SimpleResource res; + res.addProperty(RDF::type(), QUrl("class:/typeA")); + + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("A")); + QVERIFY( !m_dmModel->lastError() ); + + // the two resources should NOT have been merged + QCOMPARE(m_model->listStatements(Node(), RDF::type(), QUrl("class:/typeA")).allElements().count(), 2); +} + +// make sure that two resources are not merged if they have no matching type even if the rest of the idenfifying props match. +// the merged resource does not have any type +void DataManagementModelTest::testStoreResources_noTypeMatch1() +{ + // we create a resource with some properties + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase()); + + QUrl resA("nepomuk:/res/A"); + m_model->addStatement(resA, RDF::type(), QUrl("class:/typeA"), g1); + m_model->addStatement(resA, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(resA, NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + m_model->addStatement(resA, NAO::lastModified(), LiteralValue(QDateTime::currentDateTime()), g1); + + // now we store the resource without a type + SimpleResource res; + res.addProperty(QUrl("prop:/int"), 42); + res.addProperty(QUrl("prop:/string"), QLatin1String("foobar")); + + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("A")); + QVERIFY( !m_dmModel->lastError() ); + + // the two resources should NOT have been merged - we should have a new resource + QCOMPARE(m_model->listStatements(Soprano::Node(), QUrl("prop:/int"), Soprano::LiteralValue(42)).allStatements().count(), 2); + QCOMPARE(m_model->listStatements(Soprano::Node(), QUrl("prop:/string"), Soprano::LiteralValue(QLatin1String("foobar"))).allStatements().count(), 2); + + // two different subjects + QCOMPARE(m_model->listStatements(Soprano::Node(), QUrl("prop:/int"), Soprano::LiteralValue(42)).iterateSubjects().allNodes().toSet().count(), 2); +} + +// make sure that two resources are not merged if they have no matching type even if the rest of the idenfifying props match. +// the merged resource has a different type than the one in store +void DataManagementModelTest::testStoreResources_noTypeMatch2() +{ + // we create a resource with some properties + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase()); + + QUrl resA("nepomuk:/res/A"); + m_model->addStatement(resA, RDF::type(), QUrl("class:/typeA"), g1); + m_model->addStatement(resA, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(resA, NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + m_model->addStatement(resA, NAO::lastModified(), LiteralValue(QDateTime::currentDateTime()), g1); + + // now we store the resource with a different type + SimpleResource res; + res.addType(QUrl("class:/typeB")); + res.addProperty(QUrl("prop:/int"), 42); + res.addProperty(QUrl("prop:/string"), QLatin1String("foobar")); + + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("A")); + QVERIFY( !m_dmModel->lastError() ); + + // the two resources should NOT have been merged - we should have a new resource + QCOMPARE(m_model->listStatements(Soprano::Node(), QUrl("prop:/int"), Soprano::LiteralValue(42)).allStatements().count(), 2); + QCOMPARE(m_model->listStatements(Soprano::Node(), QUrl("prop:/string"), Soprano::LiteralValue(QLatin1String("foobar"))).allStatements().count(), 2); + + // two different subjects + QCOMPARE(m_model->listStatements(Soprano::Node(), QUrl("prop:/int"), Soprano::LiteralValue(42)).iterateSubjects().allNodes().toSet().count(), 2); +} + +void DataManagementModelTest::testStoreResources_faultyMetadata() +{ + KTemporaryFile file; + file.open(); + const QUrl fileUrl( file.fileName() ); + + SimpleResource res; + res.addProperty( RDF::type(), NFO::FileDataObject() ); + res.addProperty( NIE::url(), fileUrl ); + res.addProperty( NAO::lastModified(), QVariant( 5 ) ); + res.addProperty( NAO::created(), QVariant(QLatin1String("oh no") ) ); + + QList<Soprano::Statement> list = m_model->listStatements().allStatements(); + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("testApp") ); + + // The should be an error + QVERIFY(m_dmModel->lastError()); + + // And the statements should not exist + QVERIFY(!m_model->containsAnyStatement( Node(), RDF::type(), NFO::FileDataObject() )); + QVERIFY(!m_model->containsAnyStatement( Node(), NIE::url(), fileUrl )); + QVERIFY(!m_model->containsAnyStatement( Node(), NAO::lastModified(), Node() )); + QVERIFY(!m_model->containsAnyStatement( Node(), NAO::created(), Node() )); + + QList<Soprano::Statement> list2 = m_model->listStatements().allStatements(); + QCOMPARE( list, list2 ); +} + +void DataManagementModelTest::testStoreResources_additionalMetadataApp() +{ + KTemporaryFile file; + file.open(); + const QUrl fileUrl( file.fileName() ); + + SimpleResource res; + res.addProperty( RDF::type(), NFO::FileDataObject() ); + res.addProperty( NIE::url(), fileUrl ); + + SimpleResource app; + app.addProperty( RDF::type(), NAO::Agent() ); + app.addProperty( NAO::identifier(), "appB" ); + + SimpleResourceGraph g; + g << res << app; + + QHash<QUrl, QVariant> additionalMetadata; + additionalMetadata.insert( NAO::maintainedBy(), app.uri() ); + + m_dmModel->storeResources( g, QLatin1String("appA"), Nepomuk::IdentifyNew, Nepomuk::NoStoreResourcesFlags, additionalMetadata ); + + //FIXME: for now this should fail as nao:maintainedBy is protected, + // but what if we want to add some additionalMetadata which references + // a node in the SimpleResourceGraph. There needs to be a test for that. + QVERIFY(m_dmModel->lastError()); +} + +void DataManagementModelTest::testStoreResources_itemUris() +{ + SimpleResourceGraph g; + + for (int i = 0; i < 10; i++) { + QUrl uri( "testuri:?item="+QString::number(i) ); + SimpleResource r(uri); + r.addType( NIE::DataObject() ); + r.addType( NIE::InformationElement() ); + + QString label = QLatin1String("label") + QString::number(i); + r.setProperty( NAO::prefLabel(), label ); + g.insert(r); + } + + m_dmModel->storeResources( g, "app" ); + + // Should give an error 'testuri' is an unknown protocol + QVERIFY(m_dmModel->lastError()); +} + +void DataManagementModelTest::testStoreResources_kioProtocols() +{ + QStringList protocolList = KProtocolInfo::protocols(); + protocolList.removeAll( QLatin1String("nepomuk") ); + protocolList.removeAll( QLatin1String("file") ); + + kDebug() << "List: " << protocolList; + foreach( const QString& protocol, protocolList ) { + SimpleResource res( QUrl(protocol + ":/item") ); + res.addType( NFO::FileDataObject() ); + res.addType( NMM::MusicPiece() ); + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("app") ); + QVERIFY(!m_dmModel->lastError()); + + QVERIFY( m_model->containsAnyStatement( Node(), NIE::url(), res.uri() ) ); + + const QUrl resUri = m_model->listStatements( Node(), NIE::url(), res.uri() ).allStatements().first().subject().uri(); + + QVERIFY( m_model->containsAnyStatement( resUri, RDF::type(), NFO::FileDataObject() ) ); + QVERIFY( m_model->containsAnyStatement( resUri, RDF::type(), NMM::MusicPiece() ) ); + } +} + + +void DataManagementModelTest::testStoreResources_duplicates() +{ + KTemporaryFile file; + file.open(); + const QUrl fileUrl( file.fileName() ); + + SimpleResource res; + res.addType( NFO::FileDataObject() ); + res.addProperty( NIE::url(), fileUrl ); + + SimpleResource hash1; + hash1.addType( NFO::FileHash() ); + hash1.addProperty( NFO::hashAlgorithm(), QLatin1String("SHA1") ); + hash1.addProperty( NFO::hashValue(), QLatin1String("ddaa6b339428b75ee1545f80f1f35fb89c166bf9") ); + + SimpleResource hash2; + hash2.addType( NFO::FileHash() ); + hash2.addProperty( NFO::hashAlgorithm(), QLatin1String("SHA1") ); + hash2.addProperty( NFO::hashValue(), QLatin1String("ddaa6b339428b75ee1545f80f1f35fb89c166bf9") ); + + res.addProperty( NFO::hasHash(), hash1.uri() ); + res.addProperty( NFO::hasHash(), hash2.uri() ); + + SimpleResourceGraph graph; + graph << res << hash1 << hash2; + + m_dmModel->storeResources( graph, "appA" ); + QVERIFY(!m_dmModel->lastError()); + + // hash1 and hash2 are the same, they should have been merged together + int hashCount = m_model->listStatements( Node(), RDF::type(), NFO::FileHash() ).allStatements().size(); + QCOMPARE( hashCount, 1 ); + + // res should have only have one hash1 + QCOMPARE( m_model->listStatements( Node(), NFO::hasHash(), Node() ).allStatements().size(), 1 ); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testStoreResources_overwriteProperties() +{ + SimpleResource contact; + contact.addType( NCO::Contact() ); + contact.addProperty( NCO::fullname(), QLatin1String("Spiderman") ); + + m_dmModel->storeResources( SimpleResourceGraph() << contact, QLatin1String("app") ); + QVERIFY( !m_dmModel->lastError() ); + + QList< Statement > stList = m_model->listStatements( Node(), RDF::type(), NCO::Contact() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + const QUrl resUri = stList.first().subject().uri(); + + SimpleResource contact2( resUri ); + contact2.addType( NCO::Contact() ); + contact2.addProperty( NCO::fullname(), QLatin1String("Peter Parker") ); + + //m_dmModel->storeResources( SimpleResourceGraph() << contact2, QLatin1String("app") ); + //QVERIFY( m_dmModel->lastError() ); // should fail without the merge flags + + // Now everyone will know who Spiderman really is + m_dmModel->storeResources( SimpleResourceGraph() << contact2, QLatin1String("app"), IdentifyNew, OverwriteProperties ); + QVERIFY( !m_dmModel->lastError() ); + + stList = m_model->listStatements( resUri, NCO::fullname(), Node() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + QString newName = stList.first().object().literal().toString(); + QCOMPARE( newName, QLatin1String("Peter Parker") ); +} + +// make sure that already existing resource types are taken into account for domain checks +void DataManagementModelTest::testStoreResources_correctDomainInStore() +{ + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase()); + + // create the resource + const QUrl resA("nepomuk:/res/A"); + m_model->addStatement(resA, RDF::type(), NMM::MusicPiece(), g1); + m_model->addStatement(resA, NAO::lastModified(), Soprano::LiteralValue(QDateTime::currentDateTime()), g1); + + // now store a music piece with a performer. + // the performer does not have a type in the simple res but only in store + SimpleResource piece(resA); + piece.addProperty(NIE::title(), QLatin1String("Hello World")); + SimpleResource artist; + artist.addType(NCO::Contact()); + artist.addProperty(NCO::fullname(), QLatin1String("foobar")); + piece.addProperty(NMM::performer(), artist); + + m_dmModel->storeResources(SimpleResourceGraph() << piece << artist, QLatin1String("testapp")); + + QVERIFY(!m_dmModel->lastError()); +} + +void DataManagementModelTest::testStoreResources_correctDomainInStore2() +{ + SimpleResource res; + res.addType( NMM::MusicPiece() ); + res.addType( NFO::FileDataObject() ); + res.addProperty( NIE::title(), QLatin1String("Music") ); + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("testApp") ); + QVERIFY( !m_dmModel->lastError() ); + + QList<Soprano::Statement> stList = m_model->listStatements( Node(), RDF::type(), NFO::FileDataObject() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + const QUrl resUri = stList.first().subject().uri(); + + SimpleResource musicPiece; + musicPiece.addType( NFO::FileDataObject() ); + // We're not giving it a nmm:MusicPiece type + musicPiece.addProperty( NIE::title(), QLatin1String("Music") ); + + SimpleResource artist; + artist.addType( NCO::Contact() ); + artist.addProperty( NCO::fullname(), QLatin1String("Snow Patrol") ); + + // nmm:performer has a domain of nmm:MusicPiece which is already present in the store + musicPiece.addProperty( NMM::performer(), artist ); + + m_dmModel->storeResources( SimpleResourceGraph() << musicPiece << artist, + QLatin1String("testApp") ); + QVERIFY( !m_dmModel->lastError() ); + + // musicPiece should have gotten identified as res + stList = m_model->listStatements( Node(), RDF::type(), NFO::FileDataObject() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + const QUrl musicPieceUri = stList.first().subject().uri(); + QCOMPARE( musicPieceUri, resUri ); + + // It should have the artist + QVERIFY( m_model->containsAnyStatement( musicPieceUri, NMM::performer(), Node() ) ); +} + +// make sure that already existing resource types are taken into account for range checks +void DataManagementModelTest::testStoreResources_correctRangeInStore() +{ + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase()); + + // create the resource + const QUrl resA("nepomuk:/res/A"); + m_model->addStatement(resA, RDF::type(), NCO::Contact(), g1); + m_model->addStatement(resA, NAO::lastModified(), Soprano::LiteralValue(QDateTime::currentDateTime()), g1); + + // now store a music piece with a performer. + // the performer does not have a type in the simple res but only in store + SimpleResource piece; + piece.addType(NMM::MusicPiece()); + piece.addProperty(NIE::title(), QLatin1String("Hello World")); + SimpleResource artist(resA); + artist.addProperty(NCO::fullname(), QLatin1String("foobar")); + piece.addProperty(NMM::performer(), artist); + + m_dmModel->storeResources(SimpleResourceGraph() << piece << artist, QLatin1String("testapp")); + + QVERIFY(!m_dmModel->lastError()); +} + + +void DataManagementModelTest::testStoreResources_correctRangeInStore2() +{ + SimpleResource res; + res.addType( NCO::Contact() ); + res.addType( NFO::FileDataObject() ); + res.addProperty( NCO::fullname(), QLatin1String("Jack Black") ); + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("testApp") ); + QVERIFY( !m_dmModel->lastError() ); + + QList<Soprano::Statement> stList = m_model->listStatements( Node(), RDF::type(), NFO::FileDataObject() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + const QUrl resUri = stList.first().subject().uri(); + + SimpleResource musicPiece; + musicPiece.addType( NFO::FileDataObject() ); + musicPiece.addType( NMM::MusicPiece() ); + musicPiece.addProperty( NIE::title(), QLatin1String("Music") ); + + SimpleResource artist; + artist.addType( NFO::FileDataObject() ); + // We're not giving it the type NCO::Contact - should be inferred from the store + artist.addProperty( NCO::fullname(), QLatin1String("Jack Black") ); + + // nmm:performer has a range of nco:Contact which is already present in the store + musicPiece.addProperty( NMM::performer(), artist ); + + m_dmModel->storeResources( SimpleResourceGraph() << musicPiece << artist, + QLatin1String("testApp") ); + QVERIFY( !m_dmModel->lastError() ); + + // artist should have gotten identified as res + stList = m_model->listStatements( Node(), RDF::type(), NCO::Contact() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + const QUrl artistUri = stList.first().subject().uri(); + QCOMPARE( artistUri, resUri ); + + // It should have the artist + QVERIFY( m_model->containsAnyStatement( Node(), NMM::performer(), artistUri ) ); +} + +// make sure that the same values are simply merged even if encoded differently +void DataManagementModelTest::testStoreResources_duplicateValuesAsString() +{ + SimpleResource res; + + // add the same type twice + res.addType(QUrl("class:/typeA")); + res.addProperty(RDF::type(), QLatin1String("class:/typeA")); + + // add the same value twice + res.addProperty(QUrl("prop:/int"), 42); + res.addProperty(QUrl("prop:/int"), QLatin1String("42")); + + // now add the resource + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("testapp")); + + // this should succeed + QVERIFY(!m_dmModel->lastError()); + + // make sure all is well + QCOMPARE(m_model->listStatements(Soprano::Node(), RDF::type(), QUrl("class:/typeA")).allStatements().count(), 1); + QCOMPARE(m_model->listStatements(Soprano::Node(), QUrl("prop:/int"), LiteralValue(42)).allStatements().count(), 1); +} + +void DataManagementModelTest::testStoreResources_ontology() +{ + SimpleResource res( NFO::FileDataObject() ); + res.addType( NCO::Contact() ); + + m_dmModel->storeResources(SimpleResourceGraph() << res, QLatin1String("testapp")); + + // There should be some error, we're trying to set an ontology + QVERIFY( m_dmModel->lastError() ); +} + +void DataManagementModelTest::testStoreResources_legacyUris() +{ + const QUrl uri("res:/A"); + + const QUrl graphUri = m_nrlModel->createGraph( NRL::InstanceBase() ); + m_model->addStatement( uri, RDF::type(), NFO::FileDataObject(), graphUri ); + m_model->addStatement( uri, RDF::type(), NFO::Folder(), graphUri ); + m_model->addStatement( uri, NAO::numericRating(), LiteralValue(5), graphUri ); + + SimpleResource res( uri ); + res.addType( NFO::Folder() ); + res.addType( NFO::FileDataObject() ); + res.addProperty( NAO::numericRating(), QLatin1String("5") ); + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("app"), IdentifyNew, OverwriteProperties ); + QVERIFY( !m_dmModel->lastError() ); + + QVERIFY( m_model->containsAnyStatement( uri, NAO::numericRating(), LiteralValue(5) ) ); + + SimpleResource res2; + res2.addType( NFO::FileDataObject() ); + res2.addProperty( NIE::isPartOf(), uri ); + + m_dmModel->storeResources( SimpleResourceGraph() << res2, QLatin1String("app") ); + QVERIFY( !m_dmModel->lastError() ); + + QVERIFY( m_model->containsAnyStatement( Node(), NIE::isPartOf(), uri ) ); +} + +void DataManagementModelTest::testStoreResources_lazyCardinalities() +{ + SimpleResource res; + res.addType( NCO::Contact() ); + res.addProperty( NCO::fullname(), QLatin1String("Superman") ); + res.addProperty( NCO::fullname(), QLatin1String("Clark Kent") ); // Don't tell Lex! + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("testApp"), + Nepomuk::IdentifyNew, Nepomuk::LazyCardinalities ); + + // There shouldn't be any error, even though nco:fullname has maxCardinality = 1 + QVERIFY( !m_dmModel->lastError() ); + + QList< Statement > stList = m_model->listStatements( Node(), NCO::fullname(), Node() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + QString name = stList.first().object().literal().toString(); + bool isClark = ( name == QLatin1String("Clark Kent") ); + bool isSuperMan = ( name == QLatin1String("Superman") ); + + QVERIFY( isClark || isSuperMan ); +} + +void DataManagementModelTest::testStoreResources_graphMetadataFail() +{ + QList<Soprano::Statement> stList = m_model->listStatements().allStatements(); + + QHash<QUrl, QVariant> additionalMetadata; + additionalMetadata.insert( NCO::fullname(), QLatin1String("graphs can't have names") ); + + SimpleResource res; + res.addType( NCO::Contact() ); + res.addProperty( NCO::fullname(), QLatin1String("Harry Potter") ); + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("testApp"), + IdentifyNew, NoStoreResourcesFlags, additionalMetadata ); + + // There should be an error as graphs cannot have NFO::FileDataObject + QVERIFY( m_dmModel->lastError() ); + + // Nothing should have changed + QList<Soprano::Statement> newStList = m_model->listStatements().allStatements(); + QCOMPARE( stList, newStList ); +} + +void DataManagementModelTest::testStoreResources_randomNepomukUri() +{ + SimpleResource res(QUrl("nepomuk:/res/random-uri")); + res.addType( NCO::Contact() ); + res.addProperty( NCO::fullname(), QLatin1String("Mickey Mouse") ); + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("testApp") ); + + // There should be an error - We do not allow creation of arbitrary uris + // All uris must be created by the DataManagementModel + QVERIFY( m_dmModel->lastError() ); +} + + +void DataManagementModelTest::testMergeResources() +{ + // first we need to create the two resources we want to merge as well as one that should not be touched + // for this simple test we put everything into one graph + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + + // the resource in which we want to merge + QUrl resA("nepomuk:/res/A"); + m_model->addStatement(resA, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resA, QUrl("prop:/int_c1"), LiteralValue(42), g1); + m_model->addStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + // the resource that is going to be merged + // one duplicate property and one that differs, one backlink to ignore, + // one property with cardinality 1 to ignore + QUrl resB("nepomuk:/res/B"); + m_model->addStatement(resB, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resB, QUrl("prop:/int_c1"), LiteralValue(12), g1); + m_model->addStatement(resB, QUrl("prop:/string"), LiteralValue(QLatin1String("hello")), g1); + m_model->addStatement(resA, QUrl("prop:/res"), resB, g1); + + // resource C to ignore (except the backlink which needs to be updated) + QUrl resC("nepomuk:/res/C"); + m_model->addStatement(resC, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resC, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + m_model->addStatement(resC, QUrl("prop:/res"), resB, g1); + + + // now merge the resources + m_dmModel->mergeResources(resA, resB, QLatin1String("A")); + + // make sure B is gone + QVERIFY(!m_model->containsAnyStatement(resB, Node(), Node())); + QVERIFY(!m_model->containsAnyStatement(Node(), Node(), resB)); + + // make sure A has all the required properties + QVERIFY(m_model->containsAnyStatement(resA, QUrl("prop:/int"), LiteralValue(42))); + QVERIFY(m_model->containsAnyStatement(resA, QUrl("prop:/int_c1"), LiteralValue(42))); + QVERIFY(m_model->containsAnyStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + QVERIFY(m_model->containsAnyStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("hello")))); + + // make sure A has no superfluous properties + QVERIFY(!m_model->containsAnyStatement(resA, QUrl("prop:/int_c1"), LiteralValue(12))); + QCOMPARE(m_model->listStatements(resA, QUrl("prop:/int"), Node()).allElements().count(), 1); + + // make sure the backlink was updated + QVERIFY(m_model->containsAnyStatement(resC, QUrl("prop:/res"), resA)); + + // make sure C was not touched apart from the backlink + QVERIFY(m_model->containsStatement(resC, QUrl("prop:/int"), LiteralValue(42), g1)); + QVERIFY(m_model->containsStatement(resC, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1)); + + QVERIFY(!haveTrailingGraphs()); +} + +void DataManagementModelTest::testMergeResources_protectedTypes() +{ + // create one resource to be merged with something else + QUrl mg1; + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase(), &mg1); + + QUrl resA("res:/A"); + m_model->addStatement(resA, RDF::type(), NAO::Tag(), g1); + m_model->addStatement(resA, QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(resA, QUrl("prop:/string"), LiteralValue(QLatin1String("Foobar")), g1); + m_model->addStatement(resA, NAO::created(), LiteralValue(QDateTime::currentDateTime()), g1); + + + // remember current state to compare later on + Soprano::Graph existingStatements = m_model->listStatements().allStatements(); + + + // property 1 + m_dmModel->mergeResources(resA, QUrl("prop:/int"), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // property 2 + m_dmModel->mergeResources(QUrl("prop:/int"), resA, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // class 1 + m_dmModel->mergeResources(resA, NRL::Graph(), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // property 2 + m_dmModel->mergeResources(NRL::Graph(), resA, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // graph 1 + m_dmModel->mergeResources(resA, QUrl("graph:/onto"), QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); + + + // graph 2 + m_dmModel->mergeResources(QUrl("graph:/onto"), resA, QLatin1String("testapp")); + + // this call should fail + QVERIFY(m_dmModel->lastError()); + + // no data should have been changed + QCOMPARE(Graph(m_model->listStatements().allStatements()), existingStatements); +} + +void DataManagementModelTest::testDescribeResources() +{ + QTemporaryFile fileC; + fileC.open(); + + // create some resources + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase()); + + m_model->addStatement(QUrl("res:/A"), RDF::type(), NAO::Tag(), g1); + m_model->addStatement(QUrl("res:/A"), QUrl("prop:/res"), QUrl("res:/B"), g1); + m_model->addStatement(QUrl("res:/A"), NAO::hasSubResource(), QUrl("res:/B"), g1); + + m_model->addStatement(QUrl("res:/B"), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")), g1); + + m_model->addStatement(QUrl("res:/C"), NIE::url(), QUrl::fromLocalFile(fileC.fileName()), g1); + m_model->addStatement(QUrl("res:/C"), QUrl("prop:/int"), LiteralValue(42), g1); + m_model->addStatement(QUrl("res:/C"), NAO::hasSubResource(), QUrl("res:/D"), g1); + + m_model->addStatement(QUrl("res:/D"), QUrl("prop:/string"), LiteralValue(QLatin1String("Hello")), g1); + + + // get one resource without sub-res + QList<SimpleResource> g = m_dmModel->describeResources(QList<QUrl>() << QUrl("res:/A"), false).toList(); + + // no error + QVERIFY(!m_dmModel->lastError()); + + // only one resource in the result + QCOMPARE(g.count(), 1); + + // the one result is res:/A + QCOMPARE(g.first().uri(), QUrl("res:/A")); + + // res:/A has 3 properties + QCOMPARE(g.first().properties().count(), 3); + + + // get one resource by file-url without sub-res + g = m_dmModel->describeResources(QList<QUrl>() << QUrl::fromLocalFile(fileC.fileName()), false).toList(); + + // no error + QVERIFY(!m_dmModel->lastError()); + + // only one resource in the result + QCOMPARE(g.count(), 1); + + // the one result is res:/C + QCOMPARE(g.first().uri(), QUrl("res:/C")); + + // res:/C has 3 properties + QCOMPARE(g.first().properties().count(), 3); + + + // get one resource with sub-res + g = m_dmModel->describeResources(QList<QUrl>() << QUrl("res:/A"), true).toList(); + + // no error + QVERIFY(!m_dmModel->lastError()); + + // only one resource in the result + QCOMPARE(g.count(), 2); + + // the results are res:/A and res:/B + SimpleResource r1 = g.first(); + SimpleResource r2 = g.back(); + QVERIFY(r1.uri() == QUrl("res:/A") || r2.uri() == QUrl("res:/A")); + QVERIFY(r1.uri() == QUrl("res:/B") || r2.uri() == QUrl("res:/B")); + + // res:/A has 3 properties + if(r1.uri() == QUrl("res:/A")) { + QCOMPARE(r1.properties().count(), 3); + QCOMPARE(r2.properties().count(), 1); + } + else { + QCOMPARE(r1.properties().count(), 1); + QCOMPARE(r2.properties().count(), 3); + } + + + // get one resource via file URL with sub-res + g = m_dmModel->describeResources(QList<QUrl>() << QUrl::fromLocalFile(fileC.fileName()), true).toList(); + + // no error + QVERIFY(!m_dmModel->lastError()); + + // only one resource in the result + QCOMPARE(g.count(), 2); + + // the results are res:/C and res:/D + r1 = g.first(); + r2 = g.back(); + QVERIFY(r1.uri() == QUrl("res:/C") || r2.uri() == QUrl("res:/C")); + QVERIFY(r1.uri() == QUrl("res:/D") || r2.uri() == QUrl("res:/D")); + + // res:/A has 3 properties + if(r1.uri() == QUrl("res:/C")) { + QCOMPARE(r1.properties().count(), 3); + QCOMPARE(r2.properties().count(), 1); + } + else { + QCOMPARE(r1.properties().count(), 1); + QCOMPARE(r2.properties().count(), 3); + } + + + // get two resources without sub-res + g = m_dmModel->describeResources(QList<QUrl>() << QUrl("res:/A") << QUrl("res:/C"), false).toList(); + + // no error + QVERIFY(!m_dmModel->lastError()); + + // only one resource in the result + QCOMPARE(g.count(), 2); + + // the results are res:/A and res:/C + r1 = g.first(); + r2 = g.back(); + QVERIFY(r1.uri() == QUrl("res:/A") || r2.uri() == QUrl("res:/A")); + QVERIFY(r1.uri() == QUrl("res:/C") || r2.uri() == QUrl("res:/C")); + + // res:/A has 3 properties + QCOMPARE(r1.properties().count(), 3); + QCOMPARE(r2.properties().count(), 3); + + + // get two resources with sub-res + g = m_dmModel->describeResources(QList<QUrl>() << QUrl("res:/A") << QUrl("res:/C"), true).toList(); + + // no error + QVERIFY(!m_dmModel->lastError()); + + // only one resource in the result + QCOMPARE(g.count(), 4); + + // the results are res:/A, res:/B, res:/C and res:/D + QList<SimpleResource>::const_iterator it = g.constBegin(); + r1 = *it; + ++it; + r2 = *it; + ++it; + SimpleResource r3 = *it; + ++it; + SimpleResource r4 = *it; + QVERIFY(r1.uri() == QUrl("res:/A") || r2.uri() == QUrl("res:/A") || r3.uri() == QUrl("res:/A") || r4.uri() == QUrl("res:/A")); + QVERIFY(r1.uri() == QUrl("res:/B") || r2.uri() == QUrl("res:/B") || r3.uri() == QUrl("res:/B") || r4.uri() == QUrl("res:/B")); + QVERIFY(r1.uri() == QUrl("res:/C") || r2.uri() == QUrl("res:/C") || r3.uri() == QUrl("res:/C") || r4.uri() == QUrl("res:/C")); + QVERIFY(r1.uri() == QUrl("res:/D") || r2.uri() == QUrl("res:/D") || r3.uri() == QUrl("res:/D") || r4.uri() == QUrl("res:/D")); + + + // get two resources with sub-res and mixed URL/URI + g = m_dmModel->describeResources(QList<QUrl>() << QUrl("res:/A") << QUrl::fromLocalFile(fileC.fileName()), true).toList(); + + // no error + QVERIFY(!m_dmModel->lastError()); + + // only one resource in the result + QCOMPARE(g.count(), 4); + + // the results are res:/A, res:/B, res:/C and res:/D + it = g.constBegin(); + r1 = *it; + ++it; + r2 = *it; + ++it; + r3 = *it; + ++it; + r4 = *it; + QVERIFY(r1.uri() == QUrl("res:/A") || r2.uri() == QUrl("res:/A") || r3.uri() == QUrl("res:/A") || r4.uri() == QUrl("res:/A")); + QVERIFY(r1.uri() == QUrl("res:/B") || r2.uri() == QUrl("res:/B") || r3.uri() == QUrl("res:/B") || r4.uri() == QUrl("res:/B")); + QVERIFY(r1.uri() == QUrl("res:/C") || r2.uri() == QUrl("res:/C") || r3.uri() == QUrl("res:/C") || r4.uri() == QUrl("res:/C")); + QVERIFY(r1.uri() == QUrl("res:/D") || r2.uri() == QUrl("res:/D") || r3.uri() == QUrl("res:/D") || r4.uri() == QUrl("res:/D")); +} + +KTempDir * DataManagementModelTest::createNieUrlTestData() +{ + // now we create a real example with some real files: + // mainDir + // |- dir1 + // |- dir11 + // |- file111 + // |- dir12 + // |- dir121 + // |- file1211 + // |- file11 + // |- dir13 + // |- dir2 + KTempDir* mainDir = new KTempDir(); + QDir dir(mainDir->name()); + dir.mkdir(QLatin1String("dir1")); + dir.mkdir(QLatin1String("dir2")); + dir.cd(QLatin1String("dir1")); + dir.mkdir(QLatin1String("dir11")); + dir.mkdir(QLatin1String("dir12")); + dir.mkdir(QLatin1String("dir13")); + QFile file(dir.filePath(QLatin1String("file11"))); + file.open(QIODevice::WriteOnly); + file.close(); + dir.cd(QLatin1String("dir12")); + dir.mkdir(QLatin1String("dir121")); + dir.cd(QLatin1String("dir121")); + file.setFileName(dir.filePath(QLatin1String("file1211"))); + file.open(QIODevice::WriteOnly); + file.close(); + dir.cdUp(); + dir.cdUp(); + dir.cd(QLatin1String("dir11")); + file.setFileName(dir.filePath(QLatin1String("file111"))); + file.open(QIODevice::WriteOnly); + file.close(); + + // We now create the situation in the model + // for that we use 2 graphs + const QUrl g1 = m_nrlModel->createGraph(NRL::InstanceBase()); + const QUrl g2 = m_nrlModel->createGraph(NRL::InstanceBase()); + const QString basePath = mainDir->name(); + + // nie:url properties for all of them (spread over both graphs) + m_model->addStatement(QUrl("res:/dir1"), NIE::url(), QUrl(QLatin1String("file://") + basePath + QLatin1String("dir1")), g1); + m_model->addStatement(QUrl("res:/dir2"), NIE::url(), QUrl(QLatin1String("file://") + basePath + QLatin1String("dir2")), g2); + m_model->addStatement(QUrl("res:/dir11"), NIE::url(), QUrl(QLatin1String("file://") + basePath + QLatin1String("dir1/dir11")), g1); + m_model->addStatement(QUrl("res:/dir12"), NIE::url(), QUrl(QLatin1String("file://") + basePath + QLatin1String("dir1/dir12")), g2); + m_model->addStatement(QUrl("res:/dir13"), NIE::url(), QUrl(QLatin1String("file://") + basePath + QLatin1String("dir1/dir13")), g1); + m_model->addStatement(QUrl("res:/file11"), NIE::url(), QUrl(QLatin1String("file://") + basePath + QLatin1String("dir1/file11")), g2); + m_model->addStatement(QUrl("res:/file111"), NIE::url(), QUrl(QLatin1String("file://") + basePath + QLatin1String("dir1/dir11/file111")), g1); + m_model->addStatement(QUrl("res:/dir121"), NIE::url(), QUrl(QLatin1String("file://") + basePath + QLatin1String("dir2/dir121")), g2); + m_model->addStatement(QUrl("res:/file1211"), NIE::url(), QUrl(QLatin1String("file://") + basePath + QLatin1String("dir2/dir121/file1211")), g1); + + // we define filename and parent folder only for some to test if the optional clause in the used query works properly + m_model->addStatement(QUrl("res:/dir1"), NFO::fileName(), LiteralValue(QLatin1String("dir1")), g1); + m_model->addStatement(QUrl("res:/dir2"), NFO::fileName(), LiteralValue(QLatin1String("dir2")), g1); + m_model->addStatement(QUrl("res:/dir11"), NFO::fileName(), LiteralValue(QLatin1String("dir11")), g2); + m_model->addStatement(QUrl("res:/dir12"), NFO::fileName(), LiteralValue(QLatin1String("dir12")), g2); + m_model->addStatement(QUrl("res:/file11"), NFO::fileName(), LiteralValue(QLatin1String("file11")), g1); + m_model->addStatement(QUrl("res:/file111"), NFO::fileName(), LiteralValue(QLatin1String("file111")), g2); + m_model->addStatement(QUrl("res:/dir121"), NFO::fileName(), LiteralValue(QLatin1String("dir121")), g2); + + m_model->addStatement(QUrl("res:/dir11"), NIE::isPartOf(), QUrl("res:/dir1"), g1); + m_model->addStatement(QUrl("res:/dir12"), NIE::isPartOf(), QUrl(QLatin1String("res:/dir1")), g2); + m_model->addStatement(QUrl("res:/dir13"), NIE::isPartOf(), QUrl(QLatin1String("res:/dir1")), g1); + m_model->addStatement(QUrl("res:/file111"), NIE::isPartOf(), QUrl(QLatin1String("res:/dir11")), g1); + m_model->addStatement(QUrl("res:/dir121"), NIE::isPartOf(), QUrl(QLatin1String("res:/dir2")), g2); + m_model->addStatement(QUrl("res:/file1211"), NIE::isPartOf(), QUrl(QLatin1String("res:/dir121")), g1); + + return mainDir; +} + +bool DataManagementModelTest::haveTrailingGraphs() const +{ + return m_model->executeQuery(QString::fromLatin1("ask where { " + "?g a ?t . " + "FILTER(!bif:exists( (select (1) where { graph ?g { ?s ?p ?o . } . }))) . " + "FILTER(?t in (%1,%2,%3)) . " + "}") + .arg(Node::resourceToN3(NRL::InstanceBase()), + Node::resourceToN3(NRL::DiscardableInstanceBase()), + Node::resourceToN3(NRL::GraphMetadata())), + Soprano::Query::QueryLanguageSparql).boolValue(); +} + +void DataManagementModelTest::testImportResources() +{ + // create the test data + QTemporaryFile fileA; + fileA.open(); + + Soprano::Graph graph; + graph.addStatement(Node(QString::fromLatin1("res1")), QUrl("prop:/int"), LiteralValue(42)); + graph.addStatement(Node(QString::fromLatin1("res1")), RDF::type(), QUrl("class:/typeA")); + graph.addStatement(Node(QString::fromLatin1("res1")), QUrl("prop:/res"), Node(QString::fromLatin1("res2"))); + graph.addStatement(Node(QString::fromLatin1("res2")), RDF::type(), QUrl("class:/typeB")); + graph.addStatement(QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/int"), LiteralValue(12)); + graph.addStatement(QUrl::fromLocalFile(fileA.fileName()), QUrl("prop:/string"), LiteralValue(QLatin1String("foobar"))); + + // write the test file + QTemporaryFile tmp; + tmp.open(); + QTextStream str(&tmp); + Q_FOREACH(const Statement& s, graph.toList()) { + str << s.subject().toN3() << " " << s.predicate().toN3() << " " << s.object().toN3() << " ." << endl; + } + tmp.close(); + + + // import the file + m_dmModel->importResources(QUrl::fromLocalFile(tmp.fileName()), QLatin1String("A"), Soprano::SerializationNTriples); + + + // make sure the data has been imported properly + QVERIFY(m_model->containsAnyStatement(Node(), QUrl("prop:/int"), LiteralValue(42))); + const QUrl res1Uri = m_model->listStatements(Node(), QUrl("prop:/int"), LiteralValue(42)).allStatements().first().subject().uri(); + QVERIFY(m_model->containsAnyStatement(res1Uri, RDF::type(), QUrl("class:/typeA"))); + QVERIFY(m_model->containsAnyStatement(res1Uri, QUrl("prop:/res"), Node())); + const QUrl res2Uri = m_model->listStatements(res1Uri, QUrl("prop:/res"), Node()).allStatements().first().object().uri(); + QVERIFY(m_model->containsAnyStatement(res2Uri, RDF::type(), QUrl("class:/typeB"))); + QVERIFY(m_model->containsAnyStatement(Node(), NIE::url(), QUrl::fromLocalFile(fileA.fileName()))); + const QUrl res3Uri = m_model->listStatements(Node(), NIE::url(), QUrl::fromLocalFile(fileA.fileName())).allStatements().first().subject().uri(); + QVERIFY(m_model->containsAnyStatement(res3Uri, QUrl("prop:/int"), LiteralValue(12))); + QVERIFY(m_model->containsAnyStatement(res3Uri, QUrl("prop:/string"), LiteralValue(QLatin1String("foobar")))); + + // make sure the metadata is there + QVERIFY(m_model->containsAnyStatement(res1Uri, NAO::lastModified(), Node())); + QVERIFY(m_model->containsAnyStatement(res1Uri, NAO::created(), Node())); + QVERIFY(m_model->containsAnyStatement(res2Uri, NAO::lastModified(), Node())); + QVERIFY(m_model->containsAnyStatement(res2Uri, NAO::created(), Node())); + QVERIFY(m_model->containsAnyStatement(res3Uri, NAO::lastModified(), Node())); + QVERIFY(m_model->containsAnyStatement(res3Uri, NAO::created(), Node())); +} + +QTEST_KDEMAIN_CORE(DataManagementModelTest) + +#include "datamanagementmodeltest.moc" diff --git a/nepomuk/services/storage/test/datamanagementmodeltest.h b/nepomuk/services/storage/test/datamanagementmodeltest.h new file mode 100644 index 0000000..14b70ea --- /dev/null +++ b/nepomuk/services/storage/test/datamanagementmodeltest.h @@ -0,0 +1,171 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef DATAMANAGEMENTMODELTEST_H +#define DATAMANAGEMENTMODELTEST_H + +#include <QObject> + +namespace Soprano { +class Model; +class NRLModel; +} +namespace Nepomuk { +class DataManagementModel; +class ClassAndPropertyTree; +} +class KTempDir; + +class DataManagementModelTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + + void testAddProperty(); + void testAddProperty_createRes(); + void testAddProperty_cardinality(); + void testAddProperty_file(); + void testAddProperty_invalidFile(); + void testAddProperty_invalid_args(); + void testAddProperty_protectedTypes(); + void testAddProperty_akonadi(); + + void testSetProperty(); + void testSetProperty_createRes(); + void testSetProperty_overwrite(); + void testSetProperty_invalid_args(); + void testSetProperty_nieUrl1(); + void testSetProperty_nieUrl2(); + void testSetProperty_nieUrl3(); + void testSetProperty_nieUrl4(); + void testSetProperty_nieUrl5(); + void testSetProperty_nieUrl6(); + void testSetProperty_protectedTypes(); + void testSetProperty_legacyData(); + + void testRemoveProperty(); + void testRemoveProperty_file(); + void testRemoveProperty_invalid_args(); + void testRemoveProperty_protectedTypes(); + + void testRemoveProperties(); + void testRemoveProperties_invalid_args(); + void testRemoveProperties_protectedTypes(); + + void testRemoveResources(); + void testRemoveResources_subresources(); + void testRemoveResources_invalid_args(); + void testRemoveResources_protectedTypes(); + void testRemoveResources_mtimeRelated(); + void testRemoveResources_deletedFile(); + + void testCreateResource(); + void testCreateResource_invalid_args(); + + void testRemoveDataByApplication1(); + void testRemoveDataByApplication2(); + void testRemoveDataByApplication3(); + void testRemoveDataByApplication4(); + void testRemoveDataByApplication5(); + void testRemoveDataByApplication6(); + void testRemoveDataByApplication7(); + void testRemoveDataByApplication8(); + void testRemoveDataByApplication9(); + void testRemoveDataByApplication10(); + void testRemoveDataByApplication11(); + void testRemoveDataByApplication_subResourcesOfSubResources(); + void testRemoveDataByApplication_realLife(); + void testRemoveDataByApplication_nieUrl(); + void testRemoveDataByApplication_nieUrlRelated(); + void testRemoveDataByApplication_mtime(); + void testRemoveDataByApplication_mtimeRelated(); + void testRemoveDataByApplication_related(); + void testRemoveDataByApplication_legacyIndexerData(); + void testRemoveDataByApplication_deletedFile(); + + void testRemoveAllDataByApplication1(); + void testRemoveAllDataByApplication2(); + void testRemoveAllDataByApplication3(); + void testRemoveAllDataByApplication4(); + + void testStoreResources_strigiCase(); + void testStoreResources_graphRules(); + void testStoreResources_createResource(); + void testStoreResources_invalid_args(); + void testStoreResources_invalid_args_with_existing(); + void testStoreResources_file1(); + void testStoreResources_file2(); + void testStoreResources_file3(); + void testStoreResources_file4(); + void testStoreResources_folder(); + void testStoreResources_fileExists(); + void testStoreResources_sameNieUrl(); + void testStoreResources_metadata(); + void testStoreResources_protectedTypes(); + void testStoreResources_superTypes(); + void testStoreResources_missingMetadata(); + void testStoreResources_multiMerge(); + void testStoreResources_realLife(); + void testStoreResources_trivialMerge(); + void testStoreResources_noTypeMatch1(); + void testStoreResources_noTypeMatch2(); + void testStoreResources_faultyMetadata(); + void testStoreResources_additionalMetadataApp(); + void testStoreResources_itemUris(); + void testStoreResources_kioProtocols(); + void testStoreResources_duplicates(); + void testStoreResources_overwriteProperties(); + void testStoreResources_correctDomainInStore(); + void testStoreResources_correctDomainInStore2(); + void testStoreResources_correctRangeInStore(); + void testStoreResources_correctRangeInStore2(); + void testStoreResources_duplicateValuesAsString(); + void testStoreResources_ontology(); + void testStoreResources_legacyUris(); + void testStoreResources_lazyCardinalities(); + void testStoreResources_graphMetadataFail(); + void testStoreResources_randomNepomukUri(); + + void testMergeResources(); + void testMergeResources_protectedTypes(); + + void testDescribeResources(); + + void testImportResources(); + +private: + KTempDir* createNieUrlTestData(); + + void resetModel(); + bool haveTrailingGraphs() const; + + KTempDir* m_storageDir; + Soprano::Model* m_model; + Soprano::NRLModel* m_nrlModel; + Nepomuk::ClassAndPropertyTree* m_classAndPropertyTree; + Nepomuk::DataManagementModel* m_dmModel; +}; + +#endif diff --git a/nepomuk/services/storage/test/fakedatamanagementservice.cpp b/nepomuk/services/storage/test/fakedatamanagementservice.cpp new file mode 100644 index 0000000..40140be --- /dev/null +++ b/nepomuk/services/storage/test/fakedatamanagementservice.cpp @@ -0,0 +1,147 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "fakedatamanagementservice.h" +#include "../datamanagementmodel.h" +#include "../datamanagementadaptor.h" +#include "../classandpropertytree.h" + +#include <Soprano/Soprano> +#include <Soprano/Server/DBusExportModel> +#include <Soprano/Graph> +#define USING_SOPRANO_NRLMODEL_UNSTABLE_API +#include <Soprano/NRLModel> + +#include <ktempdir.h> +#include <KDebug> + +#include <KApplication> +#include <KAboutData> +#include <KCmdLineArgs> +#include <KCmdLineOptions> + +#include <QtDBus> + +#include <signal.h> +#include <stdio.h> + +using namespace Soprano; +using namespace Nepomuk; + +namespace { +#ifndef Q_OS_WIN + void signalHandler( int signal ) + { + switch( signal ) { + case SIGHUP: + case SIGQUIT: + case SIGINT: + QCoreApplication::exit( 0 ); + } + } +#endif + + void installSignalHandler() { +#ifndef Q_OS_WIN + struct sigaction sa; + ::memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = signalHandler; + sigaction( SIGHUP, &sa, 0 ); + sigaction( SIGINT, &sa, 0 ); + sigaction( SIGQUIT, &sa, 0 ); +#endif + } +} + +FakeDataManagementService::FakeDataManagementService(QObject *parent) + : QObject(parent) +{ + // create our fake storage + const Soprano::Backend* backend = Soprano::PluginManager::instance()->discoverBackendByName( "virtuosobackend" ); + Q_ASSERT(backend); + m_storageDir = new KTempDir(); + m_model = backend->createModel( Soprano::BackendSettings() << Soprano::BackendSetting(Soprano::BackendOptionStorageDir, m_storageDir->name()) ); + Q_ASSERT(m_model); + + // create the data management service stack connected to the fake storage + m_nrlModel = new Soprano::NRLModel(m_model); + m_classAndPropertyTree = new Nepomuk::ClassAndPropertyTree(this); + m_dmModel = new Nepomuk::DataManagementModel(m_classAndPropertyTree, m_nrlModel); + m_dmAdaptor = new Nepomuk::DataManagementAdaptor(m_dmModel); + + // register the adaptor + QDBusConnection::sessionBus().registerObject(QLatin1String("/datamanagement"), m_dmAdaptor, QDBusConnection::ExportScriptableContents); + + // register the dm model itself - simply to let the test case have access to the updateTypeCachesAndSoOn() method + QDBusConnection::sessionBus().registerObject(QLatin1String("/fakedms"), this, QDBusConnection::ExportAllSlots); + + // register the service itself + QDBusConnection::sessionBus().registerService(QLatin1String("org.kde.nepomuk.FakeDataManagement")); + + // register our base model via dbus so the test case can access it + Soprano::Server::DBusExportModel* dbusModel = new Soprano::Server::DBusExportModel(m_model); + dbusModel->setParent(this); + dbusModel->registerModel(QLatin1String("/model")); +} + +FakeDataManagementService::~FakeDataManagementService() +{ + delete m_dmAdaptor; + delete m_dmModel; + delete m_nrlModel; + delete m_model; + delete m_storageDir; +} + + +void FakeDataManagementService::updateClassAndPropertyTree() +{ + m_classAndPropertyTree->rebuildTree(m_model); +} + + +int main( int argc, char** argv ) +{ + KAboutData aboutData( "fakedms", "fakedms", + ki18n("Fake Data Management Service"), + "0.1", + ki18n("Fake Data Management Service"), + KAboutData::License_GPL, + ki18n("(c) 2011, Sebastian Trüg"), + KLocalizedString(), + "http://nepomuk.kde.org" ); + aboutData.setProgramIconName( "nepomuk" ); + aboutData.addAuthor(ki18n("Sebastian Trüg"),ki18n("Maintainer"), "trueg@kde.org"); + + KCmdLineOptions options; + KCmdLineArgs::addCmdLineOptions( options ); + KCmdLineArgs::init( argc, argv, &aboutData ); + + KApplication app( false ); + app.disableSessionManagement(); + installSignalHandler(); + QApplication::setQuitOnLastWindowClosed( false ); + + FakeDataManagementService fs; + return app.exec(); +} + +#include "fakedatamanagementservice.moc" diff --git a/nepomuk/services/storage/test/fakedatamanagementservice.h b/nepomuk/services/storage/test/fakedatamanagementservice.h new file mode 100644 index 0000000..b462f0a --- /dev/null +++ b/nepomuk/services/storage/test/fakedatamanagementservice.h @@ -0,0 +1,59 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef FAKEDATAMANAGEMENTSERVICE_H +#define FAKEDATAMANAGEMENTSERVICE_H + +#include <QObject> + +namespace Soprano { +class Model; +class NRLModel; +} +namespace Nepomuk { +class DataManagementModel; +class DataManagementAdaptor; +class ClassAndPropertyTree; +} +class KTempDir; + +class FakeDataManagementService : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.nepomuk.FakeDataManagement") + +public: + FakeDataManagementService(QObject *parent = 0); + ~FakeDataManagementService(); + +public Q_SLOTS: + void updateClassAndPropertyTree(); + +private: + KTempDir* m_storageDir; + Soprano::Model* m_model; + Soprano::NRLModel* m_nrlModel; + Nepomuk::ClassAndPropertyTree* m_classAndPropertyTree; + Nepomuk::DataManagementModel* m_dmModel; + Nepomuk::DataManagementAdaptor* m_dmAdaptor; +}; + +#endif diff --git a/nepomuk/services/storage/test/nepomuk_dms_test_config.h.cmake b/nepomuk/services/storage/test/nepomuk_dms_test_config.h.cmake new file mode 100644 index 0000000..e0e7b8d --- /dev/null +++ b/nepomuk/services/storage/test/nepomuk_dms_test_config.h.cmake @@ -0,0 +1,27 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef NEPOMUK_DMS_TEST_CONFIG_H_CMAKE +#define NEPOMUK_DMS_TEST_CONFIG_H_CMAKE + +#define FAKEDMS_BIN "${EXECUTABLE_OUTPUT_PATH}/fakedms" + +#endif // NEPOMUK_DMS_TEST_CONFIG_H_CMAKE diff --git a/nepomuk/services/storage/test/qtest_dms.cpp b/nepomuk/services/storage/test/qtest_dms.cpp new file mode 100644 index 0000000..57ec301 --- /dev/null +++ b/nepomuk/services/storage/test/qtest_dms.cpp @@ -0,0 +1,176 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "qtest_dms.h" + +#include <Soprano/Vocabulary/RDF> +#include <Soprano/Vocabulary/RDFS> +#include <Soprano/Vocabulary/NAO> +#include <Soprano/Vocabulary/NRL> +#include <Soprano/Vocabulary/XMLSchema> +#include <Nepomuk/Vocabulary/NFO> +#include <Nepomuk/Vocabulary/NMM> +#include <Nepomuk/Vocabulary/NCO> +#include <Nepomuk/Vocabulary/NIE> + +#include <Soprano/LiteralValue> + +using namespace Soprano::Vocabulary; +using namespace Soprano; +using namespace Nepomuk::Vocabulary; +using namespace Nepomuk; + +void Nepomuk::insertOntologies(Soprano::Model* m_model, const QUrl& graph) +{ + m_model->addStatement( graph, RDF::type(), NRL::Ontology(), graph ); + // removeResources depends on type inference + m_model->addStatement( graph, RDF::type(), NRL::Graph(), graph ); + + m_model->addStatement( QUrl("prop:/int"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/int"), RDFS::range(), XMLSchema::xsdInt(), graph ); + + m_model->addStatement( QUrl("prop:/int2"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/int2"), RDFS::range(), XMLSchema::xsdInt(), graph ); + + m_model->addStatement( QUrl("prop:/int3"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/int3"), RDFS::range(), XMLSchema::xsdInt(), graph ); + + m_model->addStatement( QUrl("prop:/int_c1"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/int_c1"), RDFS::range(), XMLSchema::xsdInt(), graph ); + m_model->addStatement( QUrl("prop:/int_c1"), NRL::maxCardinality(), LiteralValue(1), graph ); + + m_model->addStatement( QUrl("prop:/string"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/string"), RDFS::range(), XMLSchema::string(), graph ); + + m_model->addStatement( QUrl("prop:/res"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/res"), RDFS::range(), RDFS::Resource(), graph ); + + m_model->addStatement( QUrl("prop:/res2"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/res2"), RDFS::range(), RDFS::Resource(), graph ); + + m_model->addStatement( QUrl("prop:/res3"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/res3"), RDFS::range(), RDFS::Resource(), graph ); + + m_model->addStatement( QUrl("prop:/res_c1"), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( QUrl("prop:/res_c1"), RDFS::range(), RDFS::Resource(), graph ); + m_model->addStatement( QUrl("prop:/res_c1"), NRL::maxCardinality(), LiteralValue(1), graph ); + + m_model->addStatement( QUrl("class:/typeA"), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( QUrl("class:/typeB"), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( QUrl("class:/typeB"), RDFS::subClassOf(), QUrl("class:/typeA"), graph ); + + // properties used all the time + m_model->addStatement( NAO::identifier(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( RDF::type(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( RDF::type(), RDFS::range(), RDFS::Class(), graph ); + m_model->addStatement( NIE::url(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NIE::url(), RDFS::range(), RDFS::Resource(), graph ); + + + // some ontology things the ResourceMerger depends on + m_model->addStatement( RDFS::Class(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( RDFS::Class(), RDFS::subClassOf(), RDFS::Resource(), graph ); + m_model->addStatement( NRL::Graph(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NRL::InstanceBase(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NRL::InstanceBase(), RDFS::subClassOf(), NRL::Graph(), graph ); + m_model->addStatement( NAO::prefLabel(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NAO::prefLabel(), RDFS::range(), RDFS::Literal(), graph ); + m_model->addStatement( NFO::fileName(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NFO::fileName(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NCO::fullname(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NCO::fullname(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NIE::title(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NIE::title(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NAO::created(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NAO::created(), RDFS::range(), XMLSchema::dateTime(), graph ); + m_model->addStatement( NAO::created(), NRL::maxCardinality(), LiteralValue(1), graph ); + m_model->addStatement( NAO::lastModified(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NAO::lastModified(), RDFS::range(), XMLSchema::dateTime(), graph ); + m_model->addStatement( NAO::lastModified(), NRL::maxCardinality(), LiteralValue(1), graph ); + + // used in testStoreResources_sameNieUrl + m_model->addStatement( NAO::numericRating(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NAO::numericRating(), RDFS::range(), XMLSchema::xsdInt(), graph ); + m_model->addStatement( NAO::numericRating(), NRL::maxCardinality(), LiteralValue(1), graph ); + + // some ontology things we need in testStoreResources_realLife + m_model->addStatement( NMM::season(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NMM::season(), RDFS::range(), XMLSchema::xsdInt(), graph ); + m_model->addStatement( NMM::episodeNumber(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NMM::episodeNumber(), RDFS::range(), XMLSchema::xsdInt(), graph ); + m_model->addStatement( NMM::hasEpisode(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NMM::hasEpisode(), RDFS::range(), NMM::TVShow(), graph ); + m_model->addStatement( NIE::description(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NIE::description(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NMM::synopsis(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NMM::synopsis(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NMM::series(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NMM::series(), RDFS::range(), NMM::TVSeries(), graph ); + m_model->addStatement( NIE::title(), RDFS::subClassOf(), QUrl("http://www.semanticdesktop.org/ontologies/2007/08/15/nao#identifyingProperty"), graph ); + + // some ontology things we need in testStoreResources_strigiCase + m_model->addStatement( NMM::performer(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NMM::performer(), RDFS::domain(), NMM::MusicPiece(), graph ); + m_model->addStatement( NMM::performer(), RDFS::range(), NCO::Contact(), graph ); + m_model->addStatement( NMM::musicAlbum(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NMM::musicAlbum(), RDFS::range(), NMM::MusicAlbum(), graph ); + m_model->addStatement( NMM::MusicAlbum(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NMM::TVShow(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NMM::TVSeries(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NMM::MusicPiece(), RDF::type(), RDFS::Class(), graph ); + + // used by testStoreResources_duplicates + m_model->addStatement( NFO::hashAlgorithm(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NFO::hashAlgorithm(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NFO::hashValue(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NFO::hashValue(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NFO::hashValue(), NRL::maxCardinality(), LiteralValue(1), graph ); + m_model->addStatement( NFO::hasHash(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NFO::hasHash(), RDFS::range(), NFO::FileHash(), graph ); + m_model->addStatement( NFO::hasHash(), RDFS::domain(), NFO::FileDataObject(), graph ); + m_model->addStatement( NFO::FileHash(), RDF::type(), RDFS::Resource(), graph ); + m_model->addStatement( NFO::FileHash(), RDF::type(), RDFS::Class(), graph ); + + + m_model->addStatement( NIE::isPartOf(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NIE::isPartOf(), RDFS::range(), NFO::FileDataObject(), graph ); + m_model->addStatement( NIE::lastModified(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NIE::lastModified(), RDFS::range(), XMLSchema::dateTime(), graph ); + + m_model->addStatement( NCO::fullname(), RDF::type(), RDF::Property(), graph ); + m_model->addStatement( NCO::fullname(), RDFS::range(), XMLSchema::string(), graph ); + m_model->addStatement( NCO::fullname(), RDFS::domain(), NCO::Contact(), graph ); + m_model->addStatement( NCO::fullname(), NRL::maxCardinality(), LiteralValue(1), graph ); + m_model->addStatement( NCO::Contact(), RDF::type(), RDFS::Resource(), graph ); + m_model->addStatement( NCO::Contact(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NCO::Contact(), RDFS::subClassOf(), NCO::Role(), graph ); + m_model->addStatement( NCO::Contact(), RDFS::subClassOf(), NAO::Party(), graph ); + + m_model->addStatement( NAO::Tag(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NFO::FileDataObject(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NFO::Folder(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NFO::Video(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( NIE::InformationElement(), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( QUrl("class:/typeA"), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( QUrl("class:/typeB"), RDF::type(), RDFS::Class(), graph ); + m_model->addStatement( QUrl("class:/typeC"), RDF::type(), RDFS::Class(), graph ); +} diff --git a/nepomuk/services/storage/test/qtest_dms.h b/nepomuk/services/storage/test/qtest_dms.h new file mode 100644 index 0000000..68c3a8f --- /dev/null +++ b/nepomuk/services/storage/test/qtest_dms.h @@ -0,0 +1,49 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef QTEST_DMS_H +#define QTEST_DMS_H + +#include <QtTest> +#include <Soprano/Statement> +#include <Soprano/Node> +#include <Soprano/Model> + +namespace QTest { +template<> +inline char* toString(const Soprano::Node& node) { + return qstrdup( node.toN3().toLatin1().data() ); +} + +template<> +inline char* toString(const Soprano::Statement& s) { + return qstrdup( (s.subject().toN3() + QLatin1String(" ") + + s.predicate().toN3() + QLatin1String(" ") + + s.object().toN3() + QLatin1String(" . ")).toLatin1().data() ); +} + +} + +namespace Nepomuk { + void insertOntologies( Soprano::Model *model, const QUrl &ontologyGraph ); +} + +#endif // QTEST_DMS_H diff --git a/nepomuk/services/storage/test/removablemediamodeltest.cpp b/nepomuk/services/storage/test/removablemediamodeltest.cpp new file mode 100644 index 0000000..8bf3ab1 --- /dev/null +++ b/nepomuk/services/storage/test/removablemediamodeltest.cpp @@ -0,0 +1,288 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "removablemediamodeltest.h" + +#define private public +#include "../removablemediamodel.h" +#undef private + +#include <QtTest> +#include "qtest_kde.h" +#include "qtest_dms.h" + +#include <QtDBus> +#include <Soprano/Soprano> +#include <KDebug> + +#include <Solid/DeviceNotifier> +#include <Solid/DeviceInterface> +#include <Solid/Block> +#include <Solid/Device> +#include <Solid/StorageDrive> +#include <Solid/StorageVolume> +#include <Solid/StorageAccess> +#include <Solid/Predicate> + +#include <Nepomuk/Vocabulary/NIE> + +#ifndef FAKE_COMPUTER_XML + #error "FAKE_COMPUTER_XML not set. An XML file describing a computer is required for this test" +#endif + +#define SOLID_FAKEHW_SERVICE QDBusConnection::sessionBus().baseService() +#define SOLID_FAKEHW_PATH "/org/kde/solid/fakehw" +#define SOLID_FAKEHW_INTERFACE "local.qttest.Solid.Backends.Fake.FakeManager" + +// TODO: also test mounting a different device to the same mount path + +using namespace Nepomuk; +using namespace Nepomuk::Vocabulary; +using namespace Soprano; + +Q_DECLARE_METATYPE(Soprano::Node) +Q_DECLARE_METATYPE(Soprano::Statement) + + +namespace { +/// Plug a device in the fake Solid hw manager. +void plugDevice(const QString& udi) { + QDBusInterface(SOLID_FAKEHW_SERVICE, SOLID_FAKEHW_PATH, SOLID_FAKEHW_INTERFACE, QDBusConnection::sessionBus()) + .call(QLatin1String("plug"), udi); +} + +/// Unplug a device in the fake Solid hw manager. +void unplugDevice(const QString& udi) { + QDBusInterface(SOLID_FAKEHW_SERVICE, SOLID_FAKEHW_PATH, SOLID_FAKEHW_INTERFACE, QDBusConnection::sessionBus()) + .call(QLatin1String("unplug"), udi); +} + +const char* s_udiXyz123 = "/org/kde/solid/fakehw/volume_part1_size_993284096"; +} + +void RemovableMediaModelTest::initTestCase() +{ + // make sure Solid uses the fake manager + setenv("SOLID_FAKEHW", FAKE_COMPUTER_XML, 1); + + // we simply need some memory model for now - nothing fancy + m_model = Soprano::createModel(); + m_model->setParent(this); + m_rmModel = new RemovableMediaModel(m_model, this); +} + + +void RemovableMediaModelTest::testConvertFileUrlsInStatement_data() +{ + QTest::addColumn<Statement>( "original" ); + QTest::addColumn<Statement>( "converted" ); + + const Statement randomStatement(QUrl("nepomuk:/res/xyz"), QUrl("onto:someProp"), LiteralValue("foobar")); + QTest::newRow("noFileUrls") << randomStatement << randomStatement; + + const Statement randomFileSubject(QUrl("file:///tmp/test"), QUrl("onto:someProp"), LiteralValue("foobar")); + QTest::newRow("randomFileUrlInSubject") << randomFileSubject << randomFileSubject; + + const Statement convertableFileSubject(QUrl("file:///media/XO-Y4/test.txt"), QUrl("onto:someProp"), LiteralValue("foobar")); + QTest::newRow("convertableFileUrlInSubject") << convertableFileSubject << convertableFileSubject; + + const Statement convertableFileObjectWithoutNieUrl(QUrl("nepomuk:/res/xyz"), QUrl("onto:someProp"), QUrl("file:///media/XO-Y4/test.txt")); + QTest::newRow("convertableFileUrlInObjectWithoutNieUrl") << convertableFileObjectWithoutNieUrl << convertableFileObjectWithoutNieUrl; + + const Statement convertableFileObjectWithNieUrl1_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/XO-Y4/test.txt")); + const Statement convertableFileObjectWithNieUrl1_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("filex://xyz-123/test.txt")); + QTest::newRow("convertableFileUrlInObjectWithNieUrl1") << convertableFileObjectWithNieUrl1_original << convertableFileObjectWithNieUrl1_converted; + + const Statement convertableFileObjectWithNieUrl2_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/XO-Y4")); + const Statement convertableFileObjectWithNieUrl2_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("filex://xyz-123")); + QTest::newRow("convertableFileUrlInObjectWithNieUrl2") << convertableFileObjectWithNieUrl2_original << convertableFileObjectWithNieUrl2_converted; + + const Statement convertableFileObjectWithNieUrl3_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/nfs/test.txt")); + const Statement convertableFileObjectWithNieUrl3_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("nfs://thehost/solid-path/test.txt")); + QTest::newRow("convertableFileUrlInObjectWithNieUrl3") << convertableFileObjectWithNieUrl3_original << convertableFileObjectWithNieUrl3_converted; + + const Statement convertableFileObjectWithNieUrl4_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/nfs")); + const Statement convertableFileObjectWithNieUrl4_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("nfs://thehost/solid-path")); + QTest::newRow("convertableFileUrlInObjectWithNieUrl4") << convertableFileObjectWithNieUrl4_original << convertableFileObjectWithNieUrl4_converted; +} + + +void RemovableMediaModelTest::testConvertFileUrlsInStatement() +{ + QFETCH(Statement, original); + QFETCH(Statement, converted); + + QCOMPARE(m_rmModel->convertFileUrls(original), converted); +} + +void RemovableMediaModelTest::testConvertFileUrlsInQuery_data() +{ + QTest::addColumn<QString>( "original" ); + QTest::addColumn<QString>( "converted" ); + + QString query = QString::fromLatin1("select ?r where { ?r ?p <file:///media/foobar/test.txt> . }"); + QTest::newRow("queryWithNonConvertableFileUrl") << query << query; + + QTest::newRow("queryWithConvertableFileUrl1") + << QString::fromLatin1("select ?r where { ?r ?p <file:///media/XO-Y4/test.txt> . }") + << QString::fromLatin1("select ?r where { ?r ?p <filex://xyz-123/test.txt> . }"); + + QTest::newRow("queryWithConvertableFileUrl2") + << QString::fromLatin1("select ?r where { ?r ?p <file:///media/nfs/test.txt> . }") + << QString::fromLatin1("select ?r where { ?r ?p <nfs://thehost/solid-path/test.txt> . }"); + + QTest::newRow("queryWithConvertableRegex1") + << QString::fromLatin1("select ?r where { ?r nie:url ?u . FILTER(REGEX(?u, '^file:///media/XO-Y4/test')) . }") + << QString::fromLatin1("select ?r where { ?r nie:url ?u . FILTER(REGEX(?u, '^filex://xyz-123/test')) . }"); + + QTest::newRow("queryWithConvertableRegex2") + << QString::fromLatin1("select ?r where { ?r nie:url ?u . FILTER(REGEX(?u, '^file:///media/nfs/')) . }") + << QString::fromLatin1("select ?r where { ?r nie:url ?u . FILTER(REGEX(?u, '^nfs://thehost/solid-path/')) . }"); +} + +void RemovableMediaModelTest::testConvertFileUrlsInQuery() +{ + QFETCH(QString, original); + QFETCH(QString, converted); + + QCOMPARE(m_rmModel->convertFileUrls(original), converted); +} + +void RemovableMediaModelTest::testConvertFilxUrl_data() +{ + QTest::addColumn<Node>( "original" ); + QTest::addColumn<Node>( "converted" ); + + const Node nothingToConvertFilex(QUrl("filex://abc-789/hello/world")); + QTest::newRow("nothingToConvertFilex") << nothingToConvertFilex << nothingToConvertFilex; + + const Node convertFilex1(QUrl("filex://xyz-123/hello/world")); + QTest::newRow("convertFilex1") << convertFilex1 << Node(QUrl("file:///media/XO-Y4/hello/world")); + + const Node convertFilex2(QUrl("filex://xyz-123")); + QTest::newRow("convertFilex2") << convertFilex2 << Node(QUrl("file:///media/XO-Y4")); + + const Node convertnfs(QUrl("nfs://thehost/solid-path")); + QTest::newRow("convertnfs") << convertnfs << Node(QUrl("file:///media/nfs")); +} + +void RemovableMediaModelTest::testConvertFilxUrl() +{ + QFETCH(Node, original); + QFETCH(Node, converted); + + QCOMPARE(m_rmModel->convertFilexUrl(original), converted); +} + +void RemovableMediaModelTest::testConvertFilxUrls_data() +{ + QTest::addColumn<Statement>( "original" ); + QTest::addColumn<Statement>( "converted" ); + + const Statement randomStatement(QUrl("nepomuk:/res/xyz"), QUrl("onto:someProp"), LiteralValue("foobar")); + QTest::newRow("noFileUrls") << randomStatement << randomStatement; + + const Statement randomFilexSubject(QUrl("filex://123-123/tmp/test"), QUrl("onto:someProp"), LiteralValue("foobar")); + QTest::newRow("randomFilexUrlInSubject") << randomFilexSubject << randomFilexSubject; + + const Statement convertableFilexSubject(QUrl("filex://xyz-123/test.txt"), QUrl("onto:someProp"), LiteralValue("foobar")); + QTest::newRow("convertableFilexUrlInSubject") << convertableFilexSubject << convertableFilexSubject; + + const Statement convertableFilexObjectWithoutNieUrl(QUrl("nepomuk:/res/xyz"), QUrl("onto:someProp"), QUrl("filex://xyz-123/test.txt")); + QTest::newRow("convertableFilexUrlInObjectWithoutNieUrl") << convertableFilexObjectWithoutNieUrl << convertableFilexObjectWithoutNieUrl; + + const Statement convertableFilexObjectWithNieUrl1_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("filex://xyz-123/test.txt")); + const Statement convertableFilexObjectWithNieUrl1_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/XO-Y4/test.txt")); + QTest::newRow("convertableFilexUrlInObjectWithNieUrl1") << convertableFilexObjectWithNieUrl1_original << convertableFilexObjectWithNieUrl1_converted; + + const Statement convertableFilexObjectWithNieUrl2_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("filex://xyz-123")); + const Statement convertableFilexObjectWithNieUrl2_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/XO-Y4")); + QTest::newRow("convertableFilexUrlInObjectWithNieUrl2") << convertableFilexObjectWithNieUrl2_original << convertableFilexObjectWithNieUrl2_converted; + + const Statement convertableFilexObjectWithNieUrl3_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("nfs://thehost/solid-path/test.txt")); + const Statement convertableFilexObjectWithNieUrl3_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/nfs/test.txt")); + QTest::newRow("convertableFileUrlInObjectWithNieUrl3") << convertableFilexObjectWithNieUrl3_original << convertableFilexObjectWithNieUrl3_converted; + + const Statement convertableFilexObjectWithNieUrl4_original(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("nfs://thehost/solid-path")); + const Statement convertableFilexObjectWithNieUrl4_converted(QUrl("nepomuk:/res/xyz"), NIE::url(), QUrl("file:///media/nfs")); + QTest::newRow("convertableFileUrlInObjectWithNieUrl4") << convertableFilexObjectWithNieUrl4_original << convertableFilexObjectWithNieUrl4_converted; +} + +void RemovableMediaModelTest::testConvertFilxUrls() +{ + QFETCH(Statement, original); + QFETCH(Statement, converted); + + QCOMPARE(m_rmModel->convertFilexUrls(original), converted); +} + +void RemovableMediaModelTest::testConversionWithUnmount() +{ + KUrl fileXUrl("filex://xyz-123/hello/world"); + KUrl fileUrl("file:///media/XO-Y4/hello/world"); + + // device mounted + plugDevice(QLatin1String(s_udiXyz123)); + + // conversion should work + QCOMPARE(m_rmModel->convertFileUrl(fileUrl), Soprano::Node(fileXUrl)); + QCOMPARE(m_rmModel->convertFilexUrl(fileXUrl), Soprano::Node(fileUrl)); + + + // add some data to query + m_rmModel->addStatement(QUrl("nepomuk:/res/foobar"), NIE::url(), fileUrl); + + // make sure it is converted when queried + QCOMPARE(m_rmModel->listStatements(QUrl("nepomuk:/res/foobar"), NIE::url(), Soprano::Node()).allElements().count(), 1); + QCOMPARE(m_rmModel->listStatements(QUrl("nepomuk:/res/foobar"), NIE::url(), Soprano::Node()).allElements().first().object(), + Soprano::Node(fileUrl)); + QCOMPARE(m_rmModel->executeQuery(QString::fromLatin1("select ?u where { <nepomuk:/res/foobar> ?p ?u . }"), + Soprano::Query::QueryLanguageSparql).iterateBindings(0).allElements().count(), 1); + QCOMPARE(m_rmModel->executeQuery(QString::fromLatin1("select ?u where { <nepomuk:/res/foobar> ?p ?u . }"), + Soprano::Query::QueryLanguageSparql).iterateBindings(0).allElements().first(), + Soprano::Node(fileUrl)); + + + // unmount device + unplugDevice(s_udiXyz123); + + // now conversion should do noting + QCOMPARE(m_rmModel->convertFileUrl(fileUrl), Soprano::Node(fileUrl)); + QCOMPARE(m_rmModel->convertFilexUrl(fileXUrl), Soprano::Node(fileXUrl)); + + // make sure nothing is converted anymore + QCOMPARE(m_rmModel->listStatements(QUrl("nepomuk:/res/foobar"), NIE::url(), Soprano::Node()).allElements().count(), 1); + QCOMPARE(m_rmModel->listStatements(QUrl("nepomuk:/res/foobar"), NIE::url(), Soprano::Node()).allElements().first().object(), + Soprano::Node(fileXUrl)); + QCOMPARE(m_rmModel->executeQuery(QString::fromLatin1("select ?u where { <nepomuk:/res/foobar> ?p ?u . }"), + Soprano::Query::QueryLanguageSparql).iterateBindings(0).allElements().count(), 1); + QCOMPARE(m_rmModel->executeQuery(QString::fromLatin1("select ?u where { <nepomuk:/res/foobar> ?p ?u . }"), + Soprano::Query::QueryLanguageSparql).iterateBindings(0).allElements().first(), + Soprano::Node(fileXUrl)); + + + // re-plug device for other tests + plugDevice(QLatin1String(s_udiXyz123)); +} + +QTEST_KDEMAIN_CORE(RemovableMediaModelTest) + +#include "removablemediamodeltest.moc" diff --git a/nepomuk/services/storage/test/removablemediamodeltest.h b/nepomuk/services/storage/test/removablemediamodeltest.h new file mode 100644 index 0000000..e7723ce --- /dev/null +++ b/nepomuk/services/storage/test/removablemediamodeltest.h @@ -0,0 +1,56 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef REMOVABLEMEDIAMODELTEST_H +#define REMOVABLEMEDIAMODELTEST_H + +#include <QObject> +#include <QStringList> + +namespace Nepomuk { +class RemovableMediaModel; +} +namespace Soprano { +class Model; +} + +class RemovableMediaModelTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void testConvertFileUrlsInStatement_data(); + void testConvertFileUrlsInStatement(); + void testConvertFileUrlsInQuery_data(); + void testConvertFileUrlsInQuery(); + void testConvertFilxUrl_data(); + void testConvertFilxUrl(); + void testConvertFilxUrls_data(); + void testConvertFilxUrls(); + void testConversionWithUnmount(); + +private: + Soprano::Model* m_model; + Nepomuk::RemovableMediaModel* m_rmModel; +}; + +#endif diff --git a/nepomuk/services/storage/test/resourcewatchertest.cpp b/nepomuk/services/storage/test/resourcewatchertest.cpp new file mode 100644 index 0000000..dd3034a --- /dev/null +++ b/nepomuk/services/storage/test/resourcewatchertest.cpp @@ -0,0 +1,216 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "resourcewatchertest.h" +#include "../datamanagementmodel.h" +#include "../classandpropertytree.h" +#include "../resourcewatcherconnection.h" +#include "../resourcewatchermanager.h" + +#include "simpleresource.h" +#include "simpleresourcegraph.h" + +#include <QtTest> +#include "qtest_kde.h" +#include "qtest_dms.h" +#include <QStringList> +#include <Soprano/Soprano> +#include <Soprano/Graph> +#define USING_SOPRANO_NRLMODEL_UNSTABLE_API +#include <Soprano/NRLModel> + +#include <KTemporaryFile> +#include <KTempDir> +#include <KTempDir> +#include <KDebug> + +#include <Nepomuk/Vocabulary/NFO> +#include <Nepomuk/Vocabulary/NMM> +#include <Nepomuk/Vocabulary/NCO> +#include <Nepomuk/Vocabulary/NIE> +#include <Nepomuk/ResourceManager> + +using namespace Soprano; +using namespace Soprano::Vocabulary; +using namespace Nepomuk; +using namespace Nepomuk::Vocabulary; + + +void ResourceWatcherTest::resetModel() +{ + // remove all the junk from previous tests + m_model->removeAllStatements(); + + // add some classes and properties + QUrl graph("graph:/onto"); + Nepomuk::insertOntologies( m_model, graph ); + + // rebuild the internals of the data management model + m_classAndPropertyTree->rebuildTree(m_dmModel); +} + + +void ResourceWatcherTest::initTestCase() +{ + const Soprano::Backend* backend = Soprano::PluginManager::instance()->discoverBackendByName( "virtuosobackend" ); + QVERIFY( backend ); + m_storageDir = new KTempDir(); + m_model = backend->createModel( Soprano::BackendSettings() << Soprano::BackendSetting(Soprano::BackendOptionStorageDir, m_storageDir->name()) ); + QVERIFY( m_model ); + + // DataManagementModel relies on the ussage of a NRLModel in the storage service + m_nrlModel = new Soprano::NRLModel(m_model); + m_classAndPropertyTree = new Nepomuk::ClassAndPropertyTree(this); + m_dmModel = new Nepomuk::DataManagementModel(m_classAndPropertyTree, m_nrlModel); +} + +void ResourceWatcherTest::cleanupTestCase() +{ + delete m_dmModel; + delete m_nrlModel; + delete m_model; + delete m_storageDir; + delete m_classAndPropertyTree; +} + +void ResourceWatcherTest::init() +{ + resetModel(); +} + +void ResourceWatcherTest::testPropertyAddedSignal() +{ + // create a dummy resource which we will use + const QUrl resA = m_dmModel->createResource(QList<QUrl>() << QUrl("class:/typeA"), QString(), QString(), QLatin1String("A")); + + // no error should be generated after the above method is executed. + QVERIFY(!m_dmModel->lastError()); + + // create a connection which listens to changes in res:/A + Nepomuk::ResourceWatcherConnection* con = m_dmModel->resourceWatcherManager()->createConnection(QList<QUrl>() << resA, QList<QUrl>(), QList<QUrl>()); + QVERIFY(!m_dmModel->lastError()); + + // spy for the propertyAdded signal + QSignalSpy spy(con, SIGNAL(propertyAdded(QString, QString, QVariant))); + + // change the resource + m_dmModel->setProperty(QList<QUrl>() << resA, NAO::prefLabel(), QVariantList() << QLatin1String("foobar"), QLatin1String("A")); + QVERIFY(!m_dmModel->lastError()); + + // check that we actually got one signal + QCOMPARE( spy.count(), 1 ); + + // check that we got the correct values + QList<QVariant> args = spy.takeFirst(); + + // 1 param: the resource + QCOMPARE(args[0].toString(), resA.toString()); + + // 2 param: the property + QCOMPARE(args[1].toString(), NAO::prefLabel().toString()); + + // 3 param: the value + QCOMPARE(args[2].value<QVariant>(), QVariant(QString(QLatin1String("foobar")))); + + // cleanup + con->deleteLater(); +} + +void ResourceWatcherTest::testPropertyRemovedSignal() +{ + const QUrl resA = m_dmModel->createResource(QList<QUrl>() << QUrl("class:/typeA"), QLatin1String("foobar"), QString(), QLatin1String("A")); + QVERIFY(!m_dmModel->lastError()); + + Nepomuk::ResourceWatcherConnection* con = m_dmModel->resourceWatcherManager()->createConnection(QList<QUrl>() << resA, QList<QUrl>(), QList<QUrl>()); + + QSignalSpy spy(con, SIGNAL(propertyRemoved(QString, QString, QVariant))); + + m_dmModel->removeProperty(QList<QUrl>() << resA, NAO::prefLabel(), QVariantList() << QLatin1String("foobar"), QLatin1String("A")); + QVERIFY(!m_dmModel->lastError()); + + QCOMPARE( spy.count(), 1 ); + + QList<QVariant> args = spy.takeFirst(); + + QCOMPARE(args[0].toString(), resA.toString()); + QCOMPARE(args[1].toString(), NAO::prefLabel().toString()); + QCOMPARE(args[2].value<QVariant>(), QVariant(QString(QLatin1String("foobar")))); + + con->deleteLater(); +} + +void ResourceWatcherTest::testResourceRemovedSignal() +{ + const QUrl resA = m_dmModel->createResource(QList<QUrl>() << QUrl("class:/typeA"), QString(), QString(), QLatin1String("A")); + QVERIFY(!m_dmModel->lastError()); + + Nepomuk::ResourceWatcherConnection* con = m_dmModel->resourceWatcherManager()->createConnection(QList<QUrl>() << resA, QList<QUrl>(), QList<QUrl>()); + QVERIFY(!m_dmModel->lastError()); + + QSignalSpy spy(con, SIGNAL(resourceRemoved(QString, QStringList))); + + m_dmModel->removeResources(QList<QUrl>() << resA, Nepomuk::RemovalFlags() , QLatin1String("A")); + QVERIFY(!m_dmModel->lastError()); + + QCOMPARE( spy.count(), 1 ); + + QList<QVariant> args = spy.takeFirst(); + + QCOMPARE(args[0].toString(), resA.toString()); + + con->deleteLater(); +} + +void ResourceWatcherTest::testStoreResources_createResources() +{ + ResourceWatcherManager *rvm = m_dmModel->resourceWatcherManager(); + ResourceWatcherConnection* con = rvm->createConnection( QList<QUrl>(), QList<QUrl>(), + QList<QUrl>() << NCO::Contact() ); + QVERIFY(!m_dmModel->lastError()); + + SimpleResource res; + res.addType( NCO::Contact() ); + res.addProperty( NCO::fullname(), QLatin1String("Haruki Murakami") ); + + QSignalSpy spy(con, SIGNAL(resourceCreated(QString, QStringList))); + + m_dmModel->storeResources( SimpleResourceGraph() << res, QLatin1String("testApp") ); + QVERIFY(!m_dmModel->lastError()); + + QCOMPARE( spy.count(), 1 ); + + QList<QVariant> args = spy.takeFirst(); + + QList< Statement > stList = m_model->listStatements( Node(), RDF::type(), NCO::Contact() ).allStatements(); + QCOMPARE( stList.size(), 1 ); + + const QUrl resUri = stList.first().subject().uri(); + QCOMPARE(args[0].toString(), resUri.toString()); + QCOMPARE(args[1].toStringList(), QStringList() << NCO::Contact().toString() ); + + con->deleteLater(); +} + + +QTEST_KDEMAIN_CORE(ResourceWatcherTest) + +#include "resourcewatchertest.moc" diff --git a/nepomuk/services/storage/test/resourcewatchertest.h b/nepomuk/services/storage/test/resourcewatchertest.h new file mode 100644 index 0000000..b9ef725 --- /dev/null +++ b/nepomuk/services/storage/test/resourcewatchertest.h @@ -0,0 +1,65 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef RESOURCEWATCHERSIGNALTEST_H +#define RESOURCEWATCHERSIGNALTEST_H + +#include <QObject> + +namespace Soprano { +class Model; +class NRLModel; +} +namespace Nepomuk { +class DataManagementModel; +class ClassAndPropertyTree; +} +class KTempDir; + +class ResourceWatcherTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + + void testPropertyAddedSignal(); + void testPropertyRemovedSignal(); + void testResourceRemovedSignal(); + + void testStoreResources_createResources(); +private: + KTempDir* createNieUrlTestData(); + + void resetModel(); + bool haveTrailingGraphs() const; + + KTempDir* m_storageDir; + Soprano::Model* m_model; + Soprano::NRLModel* m_nrlModel; + Nepomuk::ClassAndPropertyTree* m_classAndPropertyTree; + Nepomuk::DataManagementModel* m_dmModel; +}; + +#endif diff --git a/nepomuk/services/storage/test/solid/fakecomputer.xml b/nepomuk/services/storage/test/solid/fakecomputer.xml new file mode 100644 index 0000000..e3fcfb8 --- /dev/null +++ b/nepomuk/services/storage/test/solid/fakecomputer.xml @@ -0,0 +1,550 @@ +<!-- Please note that in this file we indent more than necessary so that the + device tree is visible --> + +<machine> + <!-- This is a computer --> + <device udi="/org/kde/solid/fakehw/computer"> + <property key="name">Computer</property> + <property key="vendor">Solid</property> + </device> + + + <!-- A system with its own AC adapter and a battery (like a laptop) --> + <device udi="/org/kde/solid/fakehw/acpi_AC"> + <property key="name">AC Adapter</property> + <property key="interfaces">AcAdapter</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + <property key="isPlugged">false</property> + </device> + <device udi="/org/kde/solid/fakehw/acpi_BAT0"> + <property key="name">Battery Bay</property> + <property key="vendor">Acme Corporation</property> + <property key="interfaces">Battery</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + <property key="isPlugged">true</property> + <property key="batteryType">primary</property> + <property key="chargeLevelUnit">mWh</property> + <property key="maxLevel">43200000</property> + <property key="lastFullLevel">42165000</property> + <property key="currentLevel">42100000</property> + <property key="warningLevel">140550000</property> + <property key="lowLevel">7027500</property> + <property key="voltageUnit">mV</property> + <property key="voltage">11999</property> + <property key="isRechargeable">true</property> + <property key="chargeState">discharging</property> + </device> + + + + <!-- So that it looks like a laptop, + provide this computer a few buttons: + - power button + - sleep button + - lid switch --> + <device udi="/org/kde/solid/fakehw/acpi_PWB"> + <property key="name">Power Button</property> + <property key="interfaces">Button</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + + <property key="type">PowerButton</property> + <property key="hasState">false</property> + </device> + <device udi="/org/kde/solid/fakehw/acpi_SLPB"> + <property key="name">Sleep Button</property> + <property key="interfaces">Button</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + + <property key="type">SleepButton</property> + <property key="hasState">false</property> + </device> + <device udi="/org/kde/solid/fakehw/acpi_LID0"> + <property key="name">Lid Switch</property> + <property key="interfaces">Button</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + + <property key="type">LidButton</property> + <property key="hasState">true</property> + <property key="stateValue">false</property> + </device> + + + + <!-- Two CPUs --> + <device udi="/org/kde/solid/fakehw/acpi_CPU0"> + <property key="name">Solid Processor #0</property> + <property key="interfaces">Processor</property> + <property key="vendor">Acme Corporation</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + <property key="number">0</property> + <property key="maxSpeed">3200</property> + <property key="canChangeFrequency">true</property> + <property key="instructionSets">mmx,sse</property> + </device> + <device udi="/org/kde/solid/fakehw/acpi_CPU1"> + <property key="name">Solid Processor #1</property> + <property key="interfaces">Processor</property> + <property key="vendor">Acme Corporation</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + <property key="number">1</property> + <property key="maxSpeed">3200</property> + <property key="canChangeFrequency">true</property> + </device> + + + + <!-- Platform Device for a floppy drive --> + <device udi="/org/kde/solid/fakehw/platform_floppy_0"> + <property key="name">Platform Device (floppy)</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + </device> + <!-- The actual floppy device --> + <device udi="/org/kde/solid/fakehw/platform_floppy_0_storage"> + <property key="name">PC Floppy Drive</property> + <property key="interfaces">StorageDrive,Block</property> + <property key="parent">/org/kde/solid/fakehw/platform_floppy_0</property> + + <property key="minor">0</property> + <property key="major">2</property> + <property key="device">/dev/fd0</property> + + <property key="bus">platform</property> + <property key="driveType">floppy</property> + <property key="isRemovable">true</property> + <property key="isEjectRequired">false</property> + <property key="isHotpluggable">false</property> + <property key="isMediaCheckEnabled">false</property> + </device> + <!-- A (generally) virtual volume tracking the floppy drive state --> + <device udi="/org/kde/solid/fakehw/platform_floppy_0_storage_virt_volume"> + <property key="name">Floppy Disk</property> + <property key="interfaces">Block,StorageVolume,StorageAccess</property> + <property key="parent">/org/kde/solid/fakehw/platform_floppy_0_storage</property> + + <property key="minor">0</property> + <property key="major">2</property> + <property key="device">/dev/fd0</property> + + <property key="isIgnored">false</property> + <property key="isMounted">true</property> + <property key="mountPoint">/media/floppy0</property> + <property key="usage">filesystem</property> + </device> + + + + <!-- Primary IDE controller --> + <device udi="/org/kde/solid/fakehw/pci_001"> + <property key="name">99021 IDE Controller #1</property> + <property key="vendor">Acme Corporation</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + </device> + + <!-- Master device... --> + <device udi="/org/kde/solid/fakehw/pci_001_ide_0_0"> + <property key="name">IDE device (master)</property> + <property key="parent">/org/kde/solid/fakehw/pci_001</property> + </device> + <!-- ... is a 250GB disk... --> + <device udi="/org/kde/solid/fakehw/storage_serial_HD56890I"> + <property key="name">HD250GB</property> + <property key="vendor">Acme Corporation</property> + <property key="interfaces">StorageDrive,Block</property> + <property key="parent">/org/kde/solid/fakehw/pci_001_ide_0_0</property> + + <property key="minor">0</property> + <property key="major">3</property> + <property key="device">/dev/hda</property> + + <property key="bus">scsi</property> + <property key="driveType">disk</property> + <property key="isRemovable">false</property> + <property key="isEjectRequired">false</property> + <property key="isHotpluggable">false</property> + <property key="isMediaCheckEnabled">false</property> + <property key="product">HD250GBSATA</property> + </device> + <!-- ... with five partitions: + - one physical partition (the root /, ext3, 20GB) + - one extended containing three logical volumes: + - a swap volume (2GB) + - /home volume (xfs, 208GB) + - /foreign volume (ntfs, 20GB) + --> + <device udi="/org/kde/solid/fakehw/volume_uuid_feedface"> + <property key="name">/</property> + <property key="interfaces">Block,StorageVolume,StorageAccess</property> + <property key="parent">/org/kde/solid/fakehw/storage_serial_HD56890I</property> + + <property key="minor">1</property> + <property key="major">3</property> + <property key="device">/dev/hda1</property> + + <property key="isIgnored">true</property> + <property key="isMounted">true</property> + <property key="mountPoint">/</property> + <property key="usage">filesystem</property> + <property key="fsType">ext3</property> + <property key="label">Root</property> + <property key="uuid">feedface</property> + <property key="size">21474836480</property> + </device> + <device udi="/org/kde/solid/fakehw/volume_uuid_c0ffee"> + <property key="name">/home</property> + <property key="interfaces">Block,StorageVolume,StorageAccess</property> + <property key="parent">/org/kde/solid/fakehw/storage_serial_HD56890I</property> + + <property key="minor">6</property> + <property key="major">3</property> + <property key="device">/dev/hda6</property> + + <property key="isIgnored">true</property> + <property key="isMounted">true</property> + <property key="mountPoint">/home</property> + <property key="usage">filesystem</property> + <property key="fsType">xfs</property> + <property key="label">Home</property> + <property key="uuid">c0ffee</property> + <property key="size">223338299392</property> + </device> + <device udi="/org/kde/solid/fakehw/volume_uuid_f00ba7"> + <property key="name">/foreign</property> + <property key="interfaces">Block,StorageVolume,StorageAccess</property> + <property key="parent">/org/kde/solid/fakehw/storage_serial_HD56890I</property> + + <property key="minor">7</property> + <property key="major">3</property> + <property key="device">/dev/hda7</property> + + <property key="isIgnored">false</property> + <property key="isMounted">true</property> + <property key="mountPoint">/foreign</property> + <property key="usage">filesystem</property> + <property key="fsType">ntfs</property> + <property key="label">Foreign</property> + <property key="uuid">f00ba7</property> + <property key="size">21474836480</property> + </device> + <device udi="/org/kde/solid/fakehw/volume_part2_size_1024"> + <property key="name">StorageVolume</property> + <property key="interfaces">Block,StorageVolume,StorageAccess</property> + <property key="parent">/org/kde/solid/fakehw/storage_serial_HD56890I</property> + + <property key="minor">2</property> + <property key="major">3</property> + <property key="device">/dev/hda2</property> + + <property key="isIgnored">true</property> + <property key="isMounted">false</property> + <property key="usage">other</property> + <property key="size">1024</property> + </device> + <device udi="/org/kde/solid/fakehw/volume_part5_size_1048576"> + <property key="name">StorageVolume (swap)</property> + <property key="interfaces">Block,StorageVolume,StorageAccess</property> + <property key="parent">/org/kde/solid/fakehw/storage_serial_HD56890I</property> + + <property key="minor">5</property> + <property key="major">3</property> + <property key="device">/dev/hda5</property> + + <property key="isIgnored">true</property> + <property key="isMounted">false</property> + <property key="usage">other</property> + <property key="fsType">swap</property> + <property key="size">2147483648</property> + </device> + + + <!-- Secondary IDE controller --> + <device udi="/org/kde/solid/fakehw/pci_002"> + <property key="name">99021 IDE Controller #2</property> + <property key="vendor">Acme Corporation</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + </device> + + <!-- Master device... --> + <device udi="/org/kde/solid/fakehw/pci_002_ide_1_0"> + <property key="name">IDE device (master)</property> + <property key="parent">/org/kde/solid/fakehw/pci_002</property> + </device> + <!-- ... is a DVD writer... --> + <device udi="/org/kde/solid/fakehw/storage_model_solid_writer"> + <property key="name">Solid IDE DVD Writer</property> + <property key="vendor">Acme Corporation</property> + <property key="interfaces">Block,StorageDrive,OpticalDrive</property> + <property key="parent">/org/kde/solid/fakehw/pci_002_ide_1_0</property> + + <property key="minor">0</property> + <property key="major">22</property> + <property key="device">/dev/hdc</property> + + <property key="bus">ide</property> + <property key="driveType">cdrom</property> + <property key="isRemovable">true</property> + <property key="isEjectRequired">true</property> + <property key="isHotpluggable">false</property> + <property key="isMediaCheckEnabled">true</property> + <property key="product">Solid DVD Writer</property> + + <property key="supportedMedia">cdr,cdrw,dvd,dvdr,dvdrw</property> + <property key="readSpeed">4234</property> + <property key="writeSpeed">4234</property> + <property key="writeSpeeds">4234,2822,2117,1411,706</property> + </device> + <!-- ... with a cd-r in it --> + <device udi="/org/kde/solid/fakehw/volume_uuid_5011"> + <property key="name">FooDistro i386</property> + <property key="interfaces">Block,StorageVolume,OpticalDisc,StorageAccess</property> + <property key="parent">/org/kde/solid/fakehw/storage_model_solid_writer</property> + + <property key="discType">cd_rw</property> + <property key="isAppendable">false</property> + <property key="isRewritable">true</property> + <property key="isBlank">false</property> + <property key="availableContent">data</property> + <property key="mountPoint">/media/cdrom</property> + + <property key="uuid">5011</property> + <property key="size">731047936</property> + <property key="label">FooDistro i386</property> + </device> + + <!-- Slave device... --> + <device udi="/org/kde/solid/fakehw/pci_002_ide_1_1"> + <property key="name">IDE device (slave)</property> + <property key="parent">/org/kde/solid/fakehw/pci_002</property> + </device> + <!-- ... is a DVD reader... --> + <device udi="/org/kde/solid/fakehw/storage_model_solid_reader"> + <property key="name">Solid IDE DVD Reader</property> + <property key="vendor">Acme Corporation</property> + <property key="interfaces">Block,StorageDrive,OpticalDrive</property> + <property key="parent">/org/kde/solid/fakehw/pci_002_ide_1_1</property> + + <property key="minor">0</property> + <property key="major">22</property> + <property key="device">/dev/hdc</property> + + <property key="bus">ide</property> + <property key="driveType">cdrom</property> + <property key="isRemovable">true</property> + <property key="isEjectRequired">true</property> + <property key="isHotpluggable">false</property> + <property key="isMediaCheckEnabled">true</property> + <property key="product">Solid DVD Reader</property> + + <property key="supportedMedia">cdr,cdrw,dvd,dvdr,dvdrw,dvdram,dvdplusr,dvdplusrw</property> + <property key="readSpeed">4234</property> + </device> + <!-- ... with a DVD Video in it --> + <device udi="/org/kde/solid/fakehw/volume_label_SOLIDMAN_BEGINS"> + <property key="name">SolidMan Begins</property> + <property key="interfaces">Block,StorageVolume,OpticalDisc,StorageAccess</property> + <property key="parent">/org/kde/solid/fakehw/storage_model_solid_reader</property> + + <property key="discType">dvd_rom</property> + <property key="isAppendable">false</property> + <property key="isRewritable">false</property> + <property key="isBlank">false</property> + <property key="availableContent">dvdvideo</property> + + <property key="uuid">5012</property> + <property key="size">8033075200</property> + <property key="label">SolidMan Begins</property> + <property key="isMounted">true</property> + <property key="mountPoint">/media/dvd</property> + <property key="usage">filesystem</property> + <property key="fsType">udf</property> + <property key="label">SOLIDMAN_BEGINS</property> + <property key="size">4235423524</property> + </device> + + + + <!-- First USB Controller --> + <device udi="/org/kde/solid/fakehw/pci_8086_265c"> + <property key="name">99021 USB2 EHCI Controller #1</property> + <property key="vendor">Acme Corporation</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + </device> + <!-- Host Controller --> + <device udi="/org/kde/solid/fakehw/usb_device_0_0_1d_7"> + <property key="name">EHCI Host Controller</property> + <property key="vendor">Kernel ehci_hcd</property> + <property key="parent">/org/kde/solid/fakehw/pci_8086_265c</property> + </device> + <!-- USB Device --> + <device udi="/org/kde/solid/fakehw/usb_device_4e8_5041"> + <property key="name">Acme XO-Y4</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_0_0_1d_7</property> + </device> + <!-- Mass Storage Interface --> + <device udi="/org/kde/solid/fakehw/usb_device_4e8_5041_if0"> + <property key="name">USB Mass Storage Inferface</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_4e8_5041</property> + </device> + <!-- SCSI Adapter --> + <device udi="/org/kde/solid/fakehw/usb_device_4e8_5041_if0_scsi_host"> + <property key="name">SCSI Host Adapter</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_4e8_5041_if0</property> + </device> + <!-- SCSI Device --> + <device udi="/org/kde/solid/fakehw/usb_device_4e8_5041_if0_scsi_host_scsi_device_lun0"> + <property key="name">SCSI Device</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_4e8_5041_if0_scsi_host</property> + </device> + <!-- We finally find the storage device, which is a portable media player... --> + <device udi="/org/kde/solid/fakehw/storage_serial_XOY4_5206"> + <property key="name">XO-Y4</property> + <property key="vendor">Acme Electronics</property> + <property key="interfaces">StorageDrive,Block,PortableMediaPlayer</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_4e8_5041_if0_scsi_host_scsi_device_lun0</property> + + <property key="minor">0</property> + <property key="major">8</property> + <property key="device">/dev/sda</property> + + <property key="bus">usb</property> + <property key="driveType">disk</property> + <property key="isRemovable">true</property> + <property key="isEjectRequired">true</property> + <property key="isHotpluggable">true</property> + <property key="isMediaCheckEnabled">true</property> + <property key="product">XO-Y4</property> + + <property key="accessMethod">MassStorage</property> + <property key="outputFormats">audio/x-mp3</property> + <property key="inputFormats">audio/x-wav,audio/x-mp3,audio/vorbis</property> + <property key="playlistFormats">audio/x-mpegurl</property> + </device> + <!-- ... with a partition since it's a USB Mass Storage device --> + <device udi="/org/kde/solid/fakehw/volume_part1_size_993284096"> + <property key="name">StorageVolume (vfat)</property> + <property key="interfaces">Block,StorageVolume,StorageAccess</property> + <property key="parent">/org/kde/solid/fakehw/storage_serial_XOY4_5206</property> + <property key="uuid">XYZ-123</property> + + <property key="minor">1</property> + <property key="major">8</property> + <property key="device">/dev/sda1</property> + + <property key="isIgnored">false</property> + <property key="isMounted">true</property> + <property key="mountPoint">/media/XO-Y4</property> + <property key="usage">filesystem</property> + <property key="fsType">vfat</property> + <property key="size">993284096</property> + </device> + + + + <!-- Second USB Controller --> + <device udi="/org/kde/solid/fakehw/pci_8086_265d"> + <property key="name">99021 USB UHCI #1</property> + <property key="vendor">Acme Corporation</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + </device> + <!-- Host Controller --> + <device udi="/org/kde/solid/fakehw/usb_device_0_0_1d_2"> + <property key="name">UHCI Host Controller</property> + <property key="vendor">Kernel uhci_hcd</property> + <property key="parent">/org/kde/solid/fakehw/pci_8086_265d</property> + </device> + + <!-- USB Device #1 --> + <device udi="/org/kde/solid/fakehw/usb_device_4a9_30b9_noserial"> + <property key="name">PowerBullet A35</property> + <property key="vendor">Firearm Inc.</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_0_0_1d_2</property> + </device> + <!-- We finally find the camera interface --> + <device udi="/org/kde/solid/fakehw/usb_device_4a9_30b9_noserial_if0"> + <property key="name">USB Imaging Interface</property> + <property key="interfaces">Camera</property> + <property key="parent">/org/kde/solid/fakehw/usb_device_4a9_30b9_noserial</property> + <property key="accessMethod">ptp</property> + <property key="gphotoSupport">true</property> + </device> + + <!-- PCI Bridge --> + <device udi="/org/kde/solid/fakehw/pci_8086_2448"> + <property key="name">99021 PCI Bridge</property> + <property key="vendor">Acme Corporation</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + </device> + + <!-- PCI device #1 --> + <device udi="/org/kde/solid/fakehw/pci_3452_7890"> + <property key="name">Wireless 1144AH</property> + <property key="vendor">Acme Corporation</property> + <property key="parent">/org/kde/solid/fakehw/pci_8086_2448</property> + </device> + <!-- Wireless network interface --> + <device udi="/org/kde/solid/fakehw/net_00_1a_eb_c9_64_16"> + <property key="name">WLAN Interface</property> + <property key="interfaces">NetworkInterface</property> + <property key="parent">/org/kde/solid/fakehw/pci_3452_7890</property> + <property key="ifaceName">wlan0</property> + <property key="wireless">true</property> + <property key="hwAddress">00:1A:EB:C9:64:16</property> + <property key="macAddress">0x001AEBC96416</property> + </device> + + <!-- PCI device #2 --> + <device udi="/org/kde/solid/fakehw/pci_8904_5e21"> + <property key="name">RC-1893</property> + <property key="vendor">ReallyChip Corporation</property> + <property key="parent">/org/kde/solid/fakehw/pci_8086_2448</property> + </device> + <!-- Ethernet network interface --> + <device udi="/org/kde/solid/fakehw/net_00_1a_eb_c9_64_15"> + <property key="name">Networking Interface</property> + <property key="interfaces">NetworkInterface</property> + <property key="parent">/org/kde/solid/fakehw/pci_8904_5e21</property> + <property key="ifaceName">eth0</property> + <property key="wireless">false</property> + <property key="hwAddress">00:1A:EB:C9:64:15</property> + <property key="macAddress">0x001AEBC96415</property> + </device> + + <!-- PCI device #3 --> + <device udi="/org/kde/solid/fakehw/pci_8843_6a11"> + <property key="name">VisioTNT</property> + <property key="vendor">DigitalVid Ltd</property> + <property key="parent">/org/kde/solid/fakehw/pci_8086_2448</property> + </device> + <!-- Digital video broadcasting interface --> + <device udi="/org/kde/solid/fakehw/pci_8843_6a11_dvb1"> + <property key="name">DVB Interface</property> + <property key="interfaces">DvbInterface</property> + <property key="parent">/org/kde/solid/fakehw/pci_8843_6a11</property> + <property key="device">/dev/broadcast0/demux</property> + <property key="deviceAdapter">2</property> + <property key="deviceType">demux</property> + <property key="deviceIndex">1</property> + </device> + + <device udi="/org/kde/solid/fakehw/fstab"> + <property key="name">Network Shares</property> + <property key="vendor">KDE</property> + <property key="product">Network Shares</property> + <property key="parent">/org/kde/solid/fakehw/computer</property> + </device> + + <device udi="/org/kde/solid/fakehw/fstab/thehost/solidpath"> + <property key="parent">/org/kde/fstab</property> + <property key="interfaces">StorageAccess,NetworkShare</property> + + <property key="vendor">/solidpath</property> + <property key="product">thehost</property> + + <property key="type">nfs</property> + <property key="url">nfs://thehost/solid-path</property> + + <property key="filePath">/media/nfs</property> + <property key="isIgnored">false</property> + <property key="isMounted">true</property> + <property key="mountPoint">/media/nfs</property> + </device> +</machine> diff --git a/nepomuk/services/storage/test/typevisibilitytreetest.cpp b/nepomuk/services/storage/test/typevisibilitytreetest.cpp deleted file mode 100644 index 0a79c05..0000000 --- a/nepomuk/services/storage/test/typevisibilitytreetest.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "typevisibilitytreetest.h" -#include "../typevisibilitytree.h" - -#include <QtTest> -#include "qtest_kde.h" - -#include <Soprano/Soprano> - -#include <ktempdir.h> - -using namespace Soprano; - -void TypeVisibilityTreeTest::initTestCase() -{ - // we need to use a Virtuoso model as tmp model since redland misses important SPARQL features - // that are used by libnepomuk below - const Soprano::Backend* backend = Soprano::PluginManager::instance()->discoverBackendByName( "virtuosobackend" ); - QVERIFY( backend ); - m_storageDir = new KTempDir(); - m_model = backend->createModel( Soprano::BackendSettings() << Soprano::BackendSetting(Soprano::BackendOptionStorageDir, m_storageDir->name()) ); - QVERIFY( m_model ); - - m_typeTree = new TypeVisibilityTree( m_model ); -} - -void TypeVisibilityTreeTest::cleanupTestCase() -{ - delete m_model; - delete m_typeTree; - delete m_storageDir; -} - - -void TypeVisibilityTreeTest::init() -{ - m_model->removeAllStatements(); - - // we create one fake ontology - // - // situations we need to test: - // * class that is marked visible should stay visible - // * class that is marked invisible should stay invisible - // * non-marked subclass of visible should be visible, too - // * non-marked subclass of invisible should be invisible, too - // * marked subclass should keep its own visiblity and not inherit from parent - // * whole branch should inherit from parent - // * if one parent is visible the class is visible, too, even if N other parents are not - // * if all parents are invisible, the class is invisible, even if higher up in the branch a class is visible - // * properly handle loops (as in: do not run into an endless loop) - // - // A - // |- B - invisible - // |- C - // |- D - visible - // |- E - // |- F - // |- G - // - // AA - invisible - // | - F - // | - G - // - // X - // |- Y - invisible - // |- Z - // |- X - - QUrl graph("graph:/onto"); - m_model->addStatement( graph, Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::NRL::Ontology(), graph ); - - m_model->addStatement( QUrl("onto:/A"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - m_model->addStatement( QUrl("onto:/B"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - m_model->addStatement( QUrl("onto:/C"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - m_model->addStatement( QUrl("onto:/D"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - m_model->addStatement( QUrl("onto:/E"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - m_model->addStatement( QUrl("onto:/F"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - m_model->addStatement( QUrl("onto:/G"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - m_model->addStatement( QUrl("onto:/AA"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - m_model->addStatement( QUrl("onto:/X"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - m_model->addStatement( QUrl("onto:/Y"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - m_model->addStatement( QUrl("onto:/Z"), Soprano::Vocabulary::RDF::type(), Soprano::Vocabulary::RDFS::Class(), graph ); - - m_model->addStatement( QUrl("onto:/B"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/A"), graph ); - m_model->addStatement( QUrl("onto:/C"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/B"), graph ); - m_model->addStatement( QUrl("onto:/D"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/C"), graph ); - m_model->addStatement( QUrl("onto:/E"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/D"), graph ); - m_model->addStatement( QUrl("onto:/F"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/E"), graph ); - m_model->addStatement( QUrl("onto:/G"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/B"), graph ); - m_model->addStatement( QUrl("onto:/F"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/AA"), graph ); - m_model->addStatement( QUrl("onto:/G"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/AA"), graph ); - m_model->addStatement( QUrl("onto:/Y"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/X"), graph ); - m_model->addStatement( QUrl("onto:/Z"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/Y"), graph ); - m_model->addStatement( QUrl("onto:/X"), Soprano::Vocabulary::RDFS::subClassOf(), QUrl("onto:/Z"), graph ); - - m_model->addStatement( QUrl("onto:/B"), Soprano::Vocabulary::NAO::userVisible(), LiteralValue(false), graph ); - m_model->addStatement( QUrl("onto:/D"), Soprano::Vocabulary::NAO::userVisible(), LiteralValue(true), graph ); - m_model->addStatement( QUrl("onto:/AA"), Soprano::Vocabulary::NAO::userVisible(), LiteralValue(false), graph ); - m_model->addStatement( QUrl("onto:/Y"), Soprano::Vocabulary::NAO::userVisible(), LiteralValue(false), graph ); - - m_typeTree->rebuildTree(); -} - -void TypeVisibilityTreeTest::testTypeVisibilityTree() -{ - QVERIFY(m_typeTree->isVisible(QUrl("onto:/A"))); - QVERIFY(!m_typeTree->isVisible(QUrl("onto:/B"))); - QVERIFY(!m_typeTree->isVisible(QUrl("onto:/C"))); - QVERIFY(m_typeTree->isVisible(QUrl("onto:/D"))); - QVERIFY(m_typeTree->isVisible(QUrl("onto:/E"))); - QVERIFY(m_typeTree->isVisible(QUrl("onto:/F"))); - QVERIFY(!m_typeTree->isVisible(QUrl("onto:/G"))); - QVERIFY(!m_typeTree->isVisible(QUrl("onto:/AA"))); - QVERIFY(!m_typeTree->isVisible(QUrl("onto:/X"))); // because only top-level classes inherit from rdfs:Resource - QVERIFY(!m_typeTree->isVisible(QUrl("onto:/Y"))); - QVERIFY(!m_typeTree->isVisible(QUrl("onto:/Z"))); -} - -QTEST_KDEMAIN_CORE(TypeVisibilityTreeTest) - -#include "typevisibilitytreetest.moc" diff --git a/nepomuk/services/storage/test/typevisibilitytreetest.h b/nepomuk/services/storage/test/typevisibilitytreetest.h deleted file mode 100644 index e2b23f6..0000000 --- a/nepomuk/services/storage/test/typevisibilitytreetest.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef TYPEVISIBILITYTREETEST_H -#define TYPEVISIBILITYTREETEST_H - -#include <QObject> - -class KTempDir; -class TypeVisibilityTree; -namespace Soprano { -class Model; -} - -class TypeVisibilityTreeTest : public QObject -{ - Q_OBJECT - -private Q_SLOTS: - void initTestCase(); - void cleanupTestCase(); - void init(); - void testTypeVisibilityTree(); - -private: - KTempDir* m_storageDir; - Soprano::Model* m_model; - TypeVisibilityTree* m_typeTree; -}; - -#endif // TYPEVISIBILITYTREETEST_H diff --git a/nepomuk/services/storage/typevisibilitytree.cpp b/nepomuk/services/storage/typevisibilitytree.cpp deleted file mode 100644 index 5c6fefe..0000000 --- a/nepomuk/services/storage/typevisibilitytree.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "typevisibilitytree.h" - -#include <Soprano/Model> -#include <Soprano/QueryResultIterator> -#include <Soprano/Node> -#include <Soprano/LiteralValue> -#include <Soprano/Vocabulary/NAO> -#include <Soprano/Vocabulary/RDFS> - -#include <QtCore/QMutexLocker> - -#include <KDebug> - - -class TypeVisibilityTree::TypeVisibilityNode -{ -public: - TypeVisibilityNode( const QUrl& r ) - : uri( r ), - userVisible( 0 ) { - } - - QUrl uri; - int userVisible; - QSet<TypeVisibilityNode*> parents; - - /** - * Set the value of nao:userVisible. - * A class is visible if it has at least one visible direct parent class. - */ - int updateUserVisibility( QSet<TypeVisibilityNode*>& visitedNodes ) { - if ( userVisible != 0 ) { - return userVisible; - } - else { - if ( !parents.isEmpty() ) { - for ( QSet<TypeVisibilityNode*>::iterator it = parents.begin(); - it != parents.end(); ++it ) { - // avoid endless loops - if( visitedNodes.contains(*it) ) - continue; - visitedNodes.insert( *it ); - if ( (*it)->updateUserVisibility( visitedNodes ) == 1 ) { - userVisible = 1; - break; - } - } - } - if ( userVisible == 0 ) { - // default to invisible - userVisible = -1; - } - kDebug() << "Setting nao:userVisible of" << uri.toString() << ( userVisible == 1 ); - return userVisible; - } - } -}; - -TypeVisibilityTree::TypeVisibilityTree( Soprano::Model* model ) - : m_model(model) -{ -} - -TypeVisibilityTree::~TypeVisibilityTree() -{ - QMutexLocker lock( &m_mutex ); -} - -void TypeVisibilityTree::rebuildTree() -{ - QMutexLocker lock( &m_mutex ); - - // cleanup - m_visibilityHash.clear(); - - // we build a temporary helper tree - QHash<QUrl, TypeVisibilityNode*> tree; - - const QString query - = QString::fromLatin1( "select distinct ?r ?p ?v where { " - "?r a rdfs:Class . " - "OPTIONAL { ?r rdfs:subClassOf ?p . ?p a rdfs:Class . } . " - "OPTIONAL { ?r %1 ?v . } . " - "FILTER(?r!=rdfs:Resource) . " - "}" ) - .arg( Soprano::Node::resourceToN3( Soprano::Vocabulary::NAO::userVisible() ) ); - kDebug() << query; - Soprano::QueryResultIterator it - = m_model->executeQuery( query, Soprano::Query::QueryLanguageSparql ); - while( it.next() ) { - const QUrl r = it["r"].uri(); - const Soprano::Node p = it["p"]; - const Soprano::Node v = it["v"]; - - if ( !tree.contains( r ) ) { - TypeVisibilityNode* r_uvn = new TypeVisibilityNode( r ); - tree.insert( r, r_uvn ); - } - if( v.isLiteral() ) - tree[r]->userVisible = (v.literal().toBool() ? 1 : -1); - if ( p.isResource() && - p.uri() != r && - p.uri() != Soprano::Vocabulary::RDFS::Resource() ) { - if ( !tree.contains( p.uri() ) ) { - TypeVisibilityNode* p_uvn = new TypeVisibilityNode( p.uri() ); - tree.insert( p.uri(), p_uvn ); - tree[r]->parents.insert( p_uvn ); - } - else { - tree[r]->parents.insert( tree[p.uri()] ); - } - } - } - - // make sure rdfs:Resource is visible by default - TypeVisibilityNode* rdfsResourceNode = 0; - QHash<QUrl, TypeVisibilityNode*>::iterator rdfsResourceIt = tree.find(Soprano::Vocabulary::RDFS::Resource()); - if( rdfsResourceIt == tree.end() ) { - rdfsResourceNode = new TypeVisibilityNode(Soprano::Vocabulary::RDFS::Resource()); - tree.insert( Soprano::Vocabulary::RDFS::Resource(), rdfsResourceNode ); - } - else { - rdfsResourceNode = rdfsResourceIt.value(); - } - if( rdfsResourceNode->userVisible == 0 ) { - rdfsResourceNode->userVisible = 1; - } - // add rdfs:Resource as parent for all top-level classes - for ( QHash<QUrl, TypeVisibilityNode*>::iterator it = tree.begin(); - it != tree.end(); ++it ) { - if( it.value() != rdfsResourceNode && it.value()->parents.isEmpty() ) { - it.value()->parents.insert( rdfsResourceNode ); - } - } - - // finally determine visibility of all nodes - for ( QHash<QUrl, TypeVisibilityNode*>::iterator it = tree.begin(); - it != tree.end(); ++it ) { - QSet<TypeVisibilityNode*> visitedNodes; - m_visibilityHash.insert(it.key(), it.value()->updateUserVisibility( visitedNodes ) == 1 ); - } - - // get rid of the temp tree - qDeleteAll(tree); -} - -bool TypeVisibilityTree::isVisible(const QUrl &type) const -{ - QMutexLocker lock( &m_mutex ); - - QHash<QUrl, bool>::const_iterator it = m_visibilityHash.constFind(type); - if( it != m_visibilityHash.constEnd() ) { - return *it; - } - else { - kDebug() << "Could not find type" << type << "in tree. Defaulting to visible."; - return true; - } -} - -QList<QUrl> TypeVisibilityTree::visibleTypes() const -{ - QList<QUrl> types; - for( QHash<QUrl, bool>::const_iterator it = m_visibilityHash.constBegin(); - it != m_visibilityHash.constEnd(); ++it ) { - if( it.value() ) - types << it.key(); - } - return types; -} diff --git a/nepomuk/services/storage/typevisibilitytree.h b/nepomuk/services/storage/typevisibilitytree.h deleted file mode 100644 index 41e4a2b..0000000 --- a/nepomuk/services/storage/typevisibilitytree.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef TYPEVISIBILITYTREE_H -#define TYPEVISIBILITYTREE_H - -#include <QtCore/QUrl> -#include <QtCore/QHash> -#include <QtCore/QMutex> - -namespace Soprano { -class Model; -} - -/** - * Builds a type tree that allows to check the nao:userVisible - * value for each existing type. - */ -class TypeVisibilityTree -{ -public: - TypeVisibilityTree( Soprano::Model* model ); - ~TypeVisibilityTree(); - - void rebuildTree(); - - /** - * Check if the specified type is visible. - */ - bool isVisible( const QUrl& type ) const; - - QList<QUrl> visibleTypes() const; - -private: - Soprano::Model* m_model; - class TypeVisibilityNode; - QHash<QUrl, bool> m_visibilityHash; - mutable QMutex m_mutex; -}; - -#endif // TYPEVISIBILITYTREE_H diff --git a/nepomuk/services/strigi/CMakeLists.txt b/nepomuk/services/strigi/CMakeLists.txt index ee550e2..ce8c3d1 100644 --- a/nepomuk/services/strigi/CMakeLists.txt +++ b/nepomuk/services/strigi/CMakeLists.txt @@ -8,44 +8,28 @@ include_directories( ${SOPRANO_INCLUDE_DIR} ${CMAKE_SOURCE_DIR} ${NEPOMUK_INCLUDE_DIR} - ${STRIGI_INCLUDE_DIR} - ${nepomukstrigiservice_BUILD_DIR} + ../storage/lib/ ) -# Check if Strigi has the FileInputStream::open method which was introduced between versions 0.7.2 and 0.7.3 -include(CheckCXXSourceCompiles) -set(CMAKE_REQUIRED_INCLUDES ${STRIGI_INCLUDE_DIR}) -set(CMAKE_REQUIRED_LIBRARIES ${STRIGI_STREAMS_LIBRARY}) -CHECK_CXX_SOURCE_COMPILES( - "#include <strigi/fileinputstream.h>\nint main(int ac,char* av[]) { Strigi::FileInputStream::open(\"dummy\"); }" - STRIGI_HAS_FILEINPUTSTREAM_OPEN -) -if(${STRIGI_HAS_FILEINPUTSTREAM_OPEN}) - add_definitions(-DSTRIGI_HAS_FILEINPUTSTREAM_OPEN) -endif(${STRIGI_HAS_FILEINPUTSTREAM_OPEN}) - set(strigiservice_SRCS strigiservice.cpp indexscheduler.cpp + indexcleaner.cpp strigiserviceconfig.cpp eventmonitor.cpp - nepomukindexwriter.cpp - nepomukindexfeeder.cpp util.cpp nepomukindexer.cpp ) qt4_add_dbus_adaptor(strigiservice_SRCS ../../interfaces/org.kde.nepomuk.Strigi.xml strigiservice.h Nepomuk::StrigiService ) -qt4_add_dbus_interface(strigiservice_SRCS ../../interfaces/org.kde.nepomuk.RemovableStorage.xml removablestorageserviceinterface) qt4_add_dbus_interface(strigiservice_SRCS ../../interfaces/org.kde.nepomuk.FileWatch.xml filewatchserviceinterface) kde4_add_plugin(nepomukstrigiservice ${strigiservice_SRCS}) target_link_libraries(nepomukstrigiservice nepomukcommon - ${STRIGI_STREAMANALYZER_LIBRARY} - ${STRIGI_STREAMS_LIBRARY} + nepomukdatamanagement ${KDE4_KDEUI_LIBS} ${KDE4_KIO_LIBS} ${KDE4_SOLID_LIBS} @@ -66,4 +50,6 @@ install( install( TARGETS nepomukstrigiservice DESTINATION ${PLUGIN_INSTALL_DIR}) + +add_subdirectory(indexer) # ----------------------------- diff --git a/nepomuk/services/strigi/eventmonitor.cpp b/nepomuk/services/strigi/eventmonitor.cpp index 51ae599..621cb25 100644 --- a/nepomuk/services/strigi/eventmonitor.cpp +++ b/nepomuk/services/strigi/eventmonitor.cpp @@ -75,7 +75,7 @@ Nepomuk::EventMonitor::EventMonitor( IndexScheduler* scheduler, QObject* parent connect( m_indexScheduler, SIGNAL( indexingStopped() ), this, SLOT( slotIndexingStopped() ), Qt::QueuedConnection ); - + connect( m_indexScheduler, SIGNAL( indexingSuspended(bool) ), this, SLOT( slotIndexingSuspended(bool) ) ); } @@ -98,7 +98,6 @@ void Nepomuk::EventMonitor::slotPowerManagementStatusChanged( bool conserveResou sendEvent( "indexingResumed", i18n("Resuming indexing of files for fast searching."), "battery-charging" ); } else if ( conserveResources && - m_indexScheduler->isRunning() && !m_indexScheduler->isSuspended() ) { kDebug() << "Pausing indexer due to power management"; m_wasIndexingWhenPaused = m_indexScheduler->isIndexing(); @@ -114,8 +113,7 @@ void Nepomuk::EventMonitor::slotCheckAvailableSpace() KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo( KStandardDirs::locateLocal( "data", "nepomuk/repository/", false ) ); if ( info.isValid() ) { if ( info.available() <= StrigiServiceConfig::self()->minDiskSpace() ) { - if ( m_indexScheduler->isRunning() && - !m_indexScheduler->isSuspended() ) { + if ( !m_indexScheduler->isSuspended() ) { pauseIndexing( PausedDueToAvailSpace ); sendEvent( "indexingSuspended", i18n("Disk space is running low (%1 left). Suspending indexing of files.", @@ -142,7 +140,7 @@ void Nepomuk::EventMonitor::slotIndexingStopped() if ( !m_indexScheduler->isSuspended() ) { m_totalIndexingSeconds += m_indexingStartTime.secsTo( QDateTime::currentDateTime() ); const int elapsed = m_totalIndexingSeconds * 1000; - + kDebug() << "initial indexing took" << elapsed; sendEvent( "initialIndexingFinished", i18nc( "@info %1 is a duration formatted using KLocale::prettyFormatDuration", diff --git a/nepomuk/services/strigi/indexcleaner.cpp b/nepomuk/services/strigi/indexcleaner.cpp new file mode 100644 index 0000000..a8bdeff --- /dev/null +++ b/nepomuk/services/strigi/indexcleaner.cpp @@ -0,0 +1,331 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010-2011 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2010-2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "indexcleaner.h" +#include "strigiserviceconfig.h" +#include "util.h" + +#include <QtCore/QTimer> +#include <QtCore/QMutexLocker> +#include <KDebug> + +#include <Nepomuk/Resource> +#include <Nepomuk/ResourceManager> + +#include <Soprano/Model> +#include <Soprano/QueryResultIterator> +#include <Soprano/NodeIterator> +#include <Soprano/Node> + +#include <Soprano/Vocabulary/RDF> +#include <Soprano/Vocabulary/Xesam> +#include <Soprano/Vocabulary/NAO> +#include <Nepomuk/Vocabulary/NFO> +#include <Nepomuk/Vocabulary/NIE> + +using namespace Nepomuk::Vocabulary; +using namespace Soprano::Vocabulary; + + +Nepomuk::IndexCleaner::IndexCleaner(QObject* parent) + : KJob(parent), + m_delay(0) +{ + setCapabilities( Suspendable ); +} + +namespace { + /** + * Creates one SPARQL filter expression that excludes the include folders. + * This is necessary since constructFolderSubFilter will append a slash to + * each folder to make sure it does not match something like + * '/home/foobar' with '/home/foo'. + */ + QString constructExcludeIncludeFoldersFilter() + { + QStringList filters; + foreach( const QString& folder, Nepomuk::StrigiServiceConfig::self()->includeFolders() ) { + filters << QString::fromLatin1( "(?url!=%1)" ).arg( Soprano::Node::resourceToN3( KUrl( folder ) ) ); + } + return filters.join( QLatin1String( " && " ) ); + } + + QString constructFolderSubFilter( const QList<QPair<QString, bool> > folders, int& index ) + { + QString path = folders[index].first; + if ( !path.endsWith( '/' ) ) + path += '/'; + const bool include = folders[index].second; + + ++index; + + QStringList subFilters; + while ( index < folders.count() && + folders[index].first.startsWith( path ) ) { + subFilters << constructFolderSubFilter( folders, index ); + } + + QString thisFilter = QString::fromLatin1( "REGEX(STR(?url),'^%1')" ).arg( QString::fromAscii( KUrl( path ).toEncoded() ) ); + + // we want all folders that should NOT be indexed + if ( include ) { + thisFilter.prepend( '!' ); + } + subFilters.prepend( thisFilter ); + + if ( subFilters.count() > 1 ) { + return '(' + subFilters.join( include ? QLatin1String( " || " ) : QLatin1String( " && " ) ) + ')'; + } + else { + return subFilters.first(); + } + } + + /** + * Creates one SPARQL filter which matches all files and folders that should NOT be indexed. + */ + QString constructFolderFilter() + { + QStringList subFilters( constructExcludeIncludeFoldersFilter() ); + + // now add the actual filters + QList<QPair<QString, bool> > folders = Nepomuk::StrigiServiceConfig::self()->folders(); + int index = 0; + while ( index < folders.count() ) { + subFilters << constructFolderSubFilter( folders, index ); + } + QString filters = subFilters.join(" && "); + if( !filters.isEmpty() ) + return QString::fromLatin1("FILTER(%1) .").arg(filters); + + return QString(); + } +} + +void Nepomuk::IndexCleaner::start() +{ + const QString folderFilter = constructFolderFilter(); + + const int limit = 20; + + // + // Create all queries that return indexed data which should not be there anymore. + // + + // + // Query the nepomukindexer app resource in order to speed up the queries. + // + QUrl appRes; + Soprano::QueryResultIterator appIt + = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery(QString::fromLatin1("select ?app where { ?app %1 %2 . } LIMIT 1") + .arg(Soprano::Node::resourceToN3( NAO::identifier() ), + Soprano::Node::literalToN3(QLatin1String("nepomukindexer"))), + Soprano::Query::QueryLanguageSparql); + if(appIt.next()) { + appRes = appIt[0].uri(); + } + + // + // 1. Data that has been created in KDE >= 4.7 using the DMS + // + if(!appRes.isEmpty()) { + m_removalQueries << QString::fromLatin1( "select distinct ?r where { " + "graph ?g { ?r %1 ?url . } . " + "?g %2 %3 . " + " %4 } LIMIT %5" ) + .arg( Soprano::Node::resourceToN3( NIE::url() ), + Soprano::Node::resourceToN3( NAO::maintainedBy() ), + Soprano::Node::resourceToN3( appRes ), + folderFilter, + QString::number( limit ) ); + } + + + // + // 2. (legacy data) We query all files that should not be in the store. + // This for example excludes all filex:/ URLs. + // + m_removalQueries << QString::fromLatin1( "select distinct ?r where { " + "?r %1 ?url . " + "?g <http://www.strigi.org/fields#indexGraphFor> ?r . " + "FILTER(REGEX(STR(?url),'^file:/')) . " + "%2 } LIMIT %3" ) + .arg( Soprano::Node::resourceToN3( NIE::url() ), + folderFilter ) + .arg(limit); + + + // + // 3. Build filter query for all exclude filters + // + QStringList fileFilters; + foreach( const QString& filter, Nepomuk::StrigiServiceConfig::self()->excludeFilters() ) { + QString filterRxStr = QRegExp::escape( filter ); + filterRxStr.replace( "\\*", QLatin1String( ".*" ) ); + filterRxStr.replace( "\\?", QLatin1String( "." ) ); + filterRxStr.replace( '\\',"\\\\" ); + fileFilters << QString::fromLatin1( "REGEX(STR(?fn),\"^%1$\")" ).arg( filterRxStr ); + } + const QString includeExcludeFilters = constructExcludeIncludeFoldersFilter(); + + if( !includeExcludeFilters.isEmpty() && !fileFilters.isEmpty() ) { + const QString filters = QString::fromLatin1("FILTER((%1) && (%2)) .").arg( includeExcludeFilters, fileFilters.join(" || ") ); + + // 3.1. Data for files which are excluded through filters + if(!appRes.isEmpty()) { + m_removalQueries << QString::fromLatin1( "select distinct ?r ?fn where { " + "graph ?g { ?r %1 ?url . } . " + "?r %2 ?fn . " + "?g %3 %4 . " + "FILTER(REGEX(STR(?url),\"^file:/\")) . " + "%6 } LIMIT %7" ) + .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), + Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NFO::fileName() ), + Soprano::Node::resourceToN3( NAO::maintainedBy() ), + Soprano::Node::resourceToN3( appRes ), + filters ) + .arg(limit); + m_excludeFilterRemovalQueries << m_removalQueries.last(); + } + + // 3.2. (legacy data) Data for files which are excluded through filters + m_removalQueries << QString::fromLatin1( "select distinct ?r ?fn where { " + "?r %1 ?url . " + "?r %2 ?fn . " + "?g <http://www.strigi.org/fields#indexGraphFor> ?r . " + "FILTER(REGEX(STR(?url),\"^file:/\")) . " + "%3 } LIMIT %4" ) + .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), + Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NFO::fileName() ), + filters ) + .arg(limit); + m_excludeFilterRemovalQueries << m_removalQueries.last(); + } + + + // + // 4. (legacy data) Remove all old data from Xesam-times. While we leave out the data created by libnepomuk + // there is no problem since libnepomuk still uses backwards compatible queries and we use + // libnepomuk to determine URIs in the strigi backend. + // + m_removalQueries << QString::fromLatin1( "select distinct ?r where { " + "?r <http://strigi.sf.net/ontologies/0.9#parentUrl> ?p1 . } LIMIT %1" ) + .arg(limit); + m_removalQueries << QString::fromLatin1( "select distinct ?r where { " + "?r %1 ?u2 . } LIMIT %2" ) + .arg( Soprano::Node::resourceToN3( Soprano::Vocabulary::Xesam::url() ) ) + .arg(limit); + + + // + // 5. (legacy data) Remove data which is useless but still around from before. This could happen due to some buggy version of + // the indexer or the filewatch service or even some application messing up the data. + // We look for indexed files that do not have a nie:url defined and thus, will never be caught by any of the + // other queries. In addition we check for an isPartOf relation since strigi produces EmbeddedFileDataObjects + // for video and audio streams. + // + m_removalQueries << QString::fromLatin1("select ?r where { " + "graph ?g { ?r ?pp ?oo . } . " + "?g <http://www.strigi.org/fields#indexGraphFor> ?r . " + "FILTER(!bif:exists((select (1) where { ?r %1 ?u . }))) . " + "FILTER(!bif:exists((select (1) where { ?r %2 ?p . }))) . " + "} LIMIT %3") + .arg(Soprano::Node::resourceToN3(NIE::url()), + Soprano::Node::resourceToN3(NIE::isPartOf())) + .arg(limit); + + // + // Start the removal + // + m_query = m_removalQueries.dequeue(); + clearNextBatch(); +} + +void Nepomuk::IndexCleaner::slotRemoveResourcesDone(KJob* job) +{ + if( job->error() ) { + kDebug() << job->errorString(); + } + + QMutexLocker lock(&m_stateMutex); + if( !m_suspended ) { + QTimer::singleShot(m_delay, this, SLOT(clearNextBatch())); + } +} + +void Nepomuk::IndexCleaner::clearNextBatch() +{ + QList<QUrl> resources; + Soprano::QueryResultIterator it + = ResourceManager::instance()->mainModel()->executeQuery( m_query, Soprano::Query::QueryLanguageSparql ); + while( it.next() ) { + // + // Workaround for a bug in Virtuoso 6.1.3 where file names with umlauts and + // accents always match + // + if(m_excludeFilterRemovalQueries.contains(m_query)) { + const QString fileName = it["fn"].toString(); + if(StrigiServiceConfig::self()->shouldFileBeIndexed(fileName)) { + continue; + } + } + resources << it[0].uri(); + } + + if( !resources.isEmpty() ) { + KJob* job = Nepomuk::clearIndexedData(resources); + connect( job, SIGNAL(finished(KJob*)), this, SLOT(slotRemoveResourcesDone(KJob*)) ); + } + + else if( !m_removalQueries.isEmpty() ) { + m_query = m_removalQueries.dequeue(); + clearNextBatch(); + } + + else { + emitResult(); + } +} + +bool Nepomuk::IndexCleaner::doSuspend() +{ + QMutexLocker locker(&m_stateMutex); + m_suspended = true; + return true; +} + +bool Nepomuk::IndexCleaner::doResume() +{ + QMutexLocker locker(&m_stateMutex); + if(m_suspended) { + m_suspended = false; + QTimer::singleShot( 0, this, SLOT(clearNextBatch()) ); + } + return true; +} + +void Nepomuk::IndexCleaner::setDelay(int msecs) +{ + m_delay = msecs; +} + +#include "indexcleaner.moc" diff --git a/nepomuk/services/strigi/indexcleaner.h b/nepomuk/services/strigi/indexcleaner.h new file mode 100644 index 0000000..459748a --- /dev/null +++ b/nepomuk/services/strigi/indexcleaner.h @@ -0,0 +1,72 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + Copyright (C) 2011 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INDEXCLEANER_H +#define INDEXCLEANER_H + +#include <QtCore/QStringList> +#include <QtCore/QQueue> +#include <QtCore/QMutex> + +#include <KJob> + +namespace Nepomuk { + class IndexCleaner : public KJob + { + Q_OBJECT + + public: + IndexCleaner(QObject* parent=0); + + virtual void start(); + virtual bool doSuspend(); + virtual bool doResume(); + + public slots: + /** + * Set the delay between the cleanup queries. + * Used for throtteling the cleaner to not grab too + * many resources. Default is 0. + * + * \sa IndexScheduler::setIndexingSpeed() + */ + void setDelay(int msecs); + + private slots: + void clearNextBatch(); + void slotRemoveResourcesDone(KJob* job); + + private: + QQueue<QString> m_removalQueries; + + QString m_query; + + QMutex m_stateMutex; + bool m_suspended; + int m_delay; + + /// the queries from m_removalQueries that handle exclude filters + QStringList m_excludeFilterRemovalQueries; + }; +} + +#endif // INDEXCLEANER_H diff --git a/nepomuk/services/strigi/indexer/CMakeLists.txt b/nepomuk/services/strigi/indexer/CMakeLists.txt new file mode 100644 index 0000000..5f94d8d --- /dev/null +++ b/nepomuk/services/strigi/indexer/CMakeLists.txt @@ -0,0 +1,57 @@ +project(nepomukindexer) + +find_package(KDE4 REQUIRED) +find_package(Nepomuk REQUIRED) + +include(KDE4Defaults) +include(SopranoAddOntology) + +# Check if Strigi has the FileInputStream::open method which was introduced between versions 0.7.2 and 0.7.3 +include(CheckCXXSourceCompiles) +set(CMAKE_REQUIRED_INCLUDES ${STRIGI_INCLUDE_DIR}) +set(CMAKE_REQUIRED_LIBRARIES ${STRIGI_STREAMS_LIBRARY}) +CHECK_CXX_SOURCE_COMPILES( + "#include <strigi/fileinputstream.h>\nint main(int ac,char* av[]) { Strigi::FileInputStream::open(\"dummy\"); }" + STRIGI_HAS_FILEINPUTSTREAM_OPEN +) +if(${STRIGI_HAS_FILEINPUTSTREAM_OPEN}) + add_definitions(-DSTRIGI_HAS_FILEINPUTSTREAM_OPEN) +endif(${STRIGI_HAS_FILEINPUTSTREAM_OPEN}) + +include_directories( + ${QT_INCLUDES} + ${KDE4_INCLUDES} + ${SOPRANO_INCLUDE_DIR} + ${NEPOMUK_INCLUDE_DIR} + ${LIBSTREAMS_INCLUDE_DIRS} + ${LIBSTREAMANALYZER_INCLUDE_DIRS} + ../../storage/lib/ + ) + +set(indexer_SRCS + main.cpp + indexer.cpp + nepomukindexwriter.cpp + ../util.cpp + ../../../servicestub/priority.cpp + ) + +soprano_add_ontology(indexer_SRCS ${nepomuk_ontologies_SOURCE_DIR}/kext.trig "KExt" "Nepomuk::Vocabulary" "trig") + +kde4_add_executable( nepomukindexer ${indexer_SRCS} ) + +target_link_libraries( nepomukindexer + nepomukcommon + ${STRIGI_STREAMANALYZER_LIBRARY} + ${STRIGI_STREAMS_LIBRARY} + ${KDE4_KIO_LIBS} + ${NEPOMUK_QUERY_LIBRARIES} + ${NEPOMUK_LIBRARIES} + ${SOPRANO_LIBRARIES} + nepomukdatamanagement + ) + +install( + TARGETS nepomukindexer + DESTINATION ${BIN_INSTALL_DIR} ) +# ----------------------------- diff --git a/nepomuk/services/strigi/indexer/indexer.cpp b/nepomuk/services/strigi/indexer/indexer.cpp new file mode 100644 index 0000000..54dc8b4 --- /dev/null +++ b/nepomuk/services/strigi/indexer/indexer.cpp @@ -0,0 +1,158 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010-2011 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "indexer.h" +#include "nepomukindexwriter.h" + +#include <Nepomuk/Vocabulary/NIE> + +#include <KDebug> + +#include <QtCore/QDataStream> +#include <QtCore/QDateTime> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> + +#include <strigi/strigiconfig.h> +#include <strigi/indexwriter.h> +#include <strigi/analysisresult.h> +#include <strigi/fileinputstream.h> +#include <strigi/analyzerconfiguration.h> + +#include <iostream> + + +namespace { + class StoppableConfiguration : public Strigi::AnalyzerConfiguration + { + public: + StoppableConfiguration() + : m_stop(false) { +#if defined(STRIGI_IS_VERSION) +#if STRIGI_IS_VERSION( 0, 6, 1 ) + setIndexArchiveContents( false ); +#endif +#endif + } + + bool indexMore() const { + return !m_stop; + } + + bool addMoreText() const { + return !m_stop; + } + + void setStop( bool s ) { + m_stop = s; + } + + private: + bool m_stop; + }; +} + + +class Nepomuk::Indexer::Private +{ +public: + StoppableConfiguration m_analyzerConfig; + StrigiIndexWriter* m_indexWriter; + Strigi::StreamAnalyzer* m_streamAnalyzer; +}; + + +Nepomuk::Indexer::Indexer( QObject* parent ) + : QObject( parent ), + d( new Private() ) +{ + d->m_indexWriter = new StrigiIndexWriter(); + d->m_streamAnalyzer = new Strigi::StreamAnalyzer( d->m_analyzerConfig ); + d->m_streamAnalyzer->setIndexWriter( *d->m_indexWriter ); +} + + +Nepomuk::Indexer::~Indexer() +{ + delete d->m_streamAnalyzer; + delete d->m_indexWriter; + delete d; +} + + +void Nepomuk::Indexer::indexFile( const KUrl& url, const KUrl resUri, uint mtime ) +{ + indexFile( QFileInfo( url.toLocalFile() ), resUri, mtime ); +} + + +void Nepomuk::Indexer::indexFile( const QFileInfo& info, const KUrl resUri, uint mtime ) +{ + if( !info.exists() ) { + kDebug() << info.filePath() << " does not exist"; + return; + } + + d->m_analyzerConfig.setStop( false ); + d->m_indexWriter->forceUri( resUri ); + + // strigi asserts if the file path has a trailing slash + const KUrl url( info.filePath() ); + const QString filePath = url.toLocalFile( KUrl::RemoveTrailingSlash ); + const QString dir = url.directory( KUrl::IgnoreTrailingSlash ); + + kDebug() << "Starting to analyze" << info.filePath(); + + Strigi::AnalysisResult analysisresult( QFile::encodeName( filePath ).data(), + mtime ? mtime : info.lastModified().toTime_t(), + *d->m_indexWriter, + *d->m_streamAnalyzer, + QFile::encodeName( dir ).data() ); + if ( info.isFile() && !info.isSymLink() ) { +#ifdef STRIGI_HAS_FILEINPUTSTREAM_OPEN + Strigi::InputStream* stream = Strigi::FileInputStream::open( QFile::encodeName( info.filePath() ) ); + analysisresult.index( stream ); + delete stream; +#else + Strigi::FileInputStream stream( QFile::encodeName( info.filePath() ) ); + analysisresult.index( &stream ); +#endif + } + else { + analysisresult.index(0); + } +} + +void Nepomuk::Indexer::indexStdin(const KUrl resUri, uint mtime) +{ + d->m_analyzerConfig.setStop( false ); + d->m_indexWriter->forceUri( resUri ); + + Strigi::AnalysisResult analysisresult( QFile::encodeName(resUri.fileName()).data(), + mtime ? mtime : QDateTime::currentDateTime().toTime_t(), + *d->m_indexWriter, + *d->m_streamAnalyzer ); + Strigi::FileInputStream stream( stdin, QFile::encodeName(resUri.toLocalFile()).data() ); + analysisresult.index( &stream ); +} + +#include "indexer.moc" diff --git a/nepomuk/services/strigi/indexer/indexer.h b/nepomuk/services/strigi/indexer/indexer.h new file mode 100644 index 0000000..4a604bd --- /dev/null +++ b/nepomuk/services/strigi/indexer/indexer.h @@ -0,0 +1,77 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _NEPOMUK_STRIG_INDEXER_H_ +#define _NEPOMUK_STRIG_INDEXER_H_ + +#include <QtCore/QObject> +#include <KUrl> + +class QDateTime; +class QDataStream; +class QFileInfo; + +namespace Nepomuk { + + class Resource; + + class Indexer : public QObject + { + Q_OBJECT + + public: + /** + * Create a new indexer. + */ + Indexer( QObject* parent = 0 ); + + /** + * Destructor + */ + ~Indexer(); + + /** + * Index a single local file or folder (files in a folder will + * not be indexed recursively). + */ + void indexFile( const KUrl& url, const KUrl resUri, uint mtime = 0 ); + + /** + * Index a single local file or folder (files in a folder will + * not be indexed recursively). This method does the exact same + * as the above except that it saves an addditional stat of the + * file. + */ + void indexFile( const QFileInfo& info, const KUrl resUri, uint mtime=0 ); + + /** + * Index a file whose contents are provided via standard input. + */ + void indexStdin( const KUrl resUri, uint mtime=0 ); + + private: + class Private; + Private* const d; + }; +} + +#endif diff --git a/nepomuk/services/strigi/indexer/main.cpp b/nepomuk/services/strigi/indexer/main.cpp new file mode 100644 index 0000000..72d373a --- /dev/null +++ b/nepomuk/services/strigi/indexer/main.cpp @@ -0,0 +1,86 @@ +/* + This file is part of the Nepomuk KDE project. + Copyright (C) 2010-11 Vishesh Handa <handa.vish@gmail.com> + Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "indexer.h" +#include "../util.h" +#include "../../../servicestub/priority.h" + +#include <KAboutData> +#include <KCmdLineArgs> +#include <KLocale> +#include <KComponentData> + +#include <QtCore/QCoreApplication> +#include <QtCore/QDir> + +#include <KDebug> +#include <KUrl> + +int main(int argc, char *argv[]) +{ + lowerIOPriority(); + lowerSchedulingPriority(); + lowerPriority(); + + KAboutData aboutData("nepomukindexer", 0, ki18n("NepomukIndexer"), + "1.0", + ki18n("NepomukIndexer indexes the contents of a file and saves the results in Nepomuk"), + KAboutData::License_LGPL_V2, + ki18n("(C) 2011, Vishesh Handa")); + aboutData.addAuthor(ki18n("Vishesh Handa"), ki18n("Current maintainer"), "handa.vish@gmail.com"); + aboutData.addCredit(ki18n("Sebastian Trüg"), ki18n("Developer"), "trueg@kde.org"); + + KCmdLineArgs::init(argc, argv, &aboutData); + + KCmdLineOptions options; + options.add("uri <uri>", ki18n("The URI provided will be forced on the resource")); + options.add("mtime <time>", ki18n("The modification time of the resource in time_t format")); + options.add("+[url]", ki18n("The URL of the file to be indexed")); + options.add("clear", ki18n("Remove all indexed data of the URL provided")); + + KCmdLineArgs::addCmdLineOptions(options); + const KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + // Application + QCoreApplication app( argc, argv ); + KComponentData data( aboutData, KComponentData::RegisterAsMainComponent ); + + const KUrl uri = args->getOption("uri"); + const uint mtime = args->getOption("mtime").toUInt(); + + if( args->count() == 0 ) { + Nepomuk::Indexer indexer; + indexer.indexStdin( uri, mtime ); + return 0; + } + else if( args->isSet("clear") ) { + Nepomuk::clearIndexedData( args->url(0) ); + kDebug() << "Removed indexed data for" << args->url(0); + return 0; + } + else { + Nepomuk::Indexer indexer; + indexer.indexFile( args->url(0), uri, mtime ); + kDebug() << "Indexed data for" << args->url(0); + return 0; + } +} diff --git a/nepomuk/services/strigi/indexer/nepomukindexwriter.cpp b/nepomuk/services/strigi/indexer/nepomukindexwriter.cpp new file mode 100644 index 0000000..ce3cad7 --- /dev/null +++ b/nepomuk/services/strigi/indexer/nepomukindexwriter.cpp @@ -0,0 +1,566 @@ +/* + Copyright (C) 2007-2011 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> + + 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 "nepomukindexwriter.h" +#include "../util.h" +#include "kext.h" +#include "datamanagement.h" +#include "simpleresource.h" +#include "simpleresourcegraph.h" + +#include <Soprano/Vocabulary/RDF> +#include <Soprano/Vocabulary/NAO> +#include <Soprano/Vocabulary/NRL> +#include <Soprano/LiteralValue> +#include <Soprano/Node> +#include <Soprano/QueryResultIterator> + +#include <QtCore/QList> +#include <QtCore/QHash> +#include <QtCore/QVariant> +#include <QtCore/QFileInfo> +#include <QtCore/QFile> +#include <QtCore/QUrl> +#include <QtCore/QDateTime> +#include <QtCore/QByteArray> +#include <QtCore/QStack> +#include <QtCore/QMutex> + +#include <KUrl> +#include <KDebug> +#include <KMimeType> +#include <kde_file.h> +#include <KJob> + +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> + +#include <map> +#include <sstream> +#include <algorithm> + +#include <Nepomuk/Vocabulary/NFO> +#include <Nepomuk/Vocabulary/NIE> + + + +// IMPORTANT: strings in Strigi are apparently UTF8! Except for file names. Those are in local encoding. + +using namespace Soprano; +using namespace Strigi; +using namespace Soprano::Vocabulary; +using namespace Nepomuk::Vocabulary; + + +uint qHash( const std::string& s ) +{ + return qHash( s.c_str() ); +} + +namespace { + QString findArchivePath( const QString& path ) { + QString p( path ); + int i = 0; + while ( ( i = p.lastIndexOf( '/' ) ) > 0 ) { + p.truncate( i ); + if ( QFileInfo( p ).isFile() ) { + return p; + } + } + return QString(); + } + + QUrl createFileUrl( const Strigi::AnalysisResult* idx ) { + // HACK: Strigi includes analysers that recurse into tar or zip archives and index + // the files therein. In KDE these files could perfectly be handled through kio slaves + // such as tar:/ or zip:/ + // Here we try to use KDE-compatible URIs for these indexed files the best we can + // everything else defaults to file:/ + QUrl uri; + QString path = QFile::decodeName( idx->path().c_str() ); + if ( KUrl::isRelativeUrl( path ) ) + uri = QUrl::fromLocalFile( QFileInfo( path ).absoluteFilePath() ); + else + uri = KUrl( path ); // try to support http and other URLs + + if ( idx->depth() > 0 ) { + QString archivePath = findArchivePath( path ); + if ( QFile::exists( archivePath ) ) { + if ( archivePath.endsWith( QLatin1String( ".tar" ) ) || + archivePath.endsWith( QLatin1String( ".tar.gz" ) ) || + archivePath.endsWith( QLatin1String( ".tar.bz2" ) ) || + archivePath.endsWith( QLatin1String( ".tar.lzma" ) ) || + archivePath.endsWith( QLatin1String( ".tar.xz" ) ) ) { + uri.setScheme( "tar" ); + } + else if ( archivePath.endsWith( QLatin1String( ".zip" ) ) ) { + uri.setScheme( "zip" ); + } + } + } + + // fallback for all + if ( uri.scheme().isEmpty() ) { + uri.setScheme( "file" ); + } + + return uri; + } + + + /** + * A simple cache for properties that are used in Strigi. + * This avoids matching them again and again. + * + * Also the class provides easy conversion methods for + * values provided by Strigi to values that Nepomuk understands. + */ + class RegisteredFieldData + { + public: + RegisteredFieldData( const QUrl& prop ) + : m_property( prop ) { + } + + QUrl property() const { return m_property; } + + private: + /// The actual property URI + QUrl m_property; + }; + + + /** + * Data objects that are used to store information relative to one + * indexing run. + */ + class FileMetaData + { + public: + FileMetaData( const Strigi::AnalysisResult* idx, const QUrl & resUri ); + + /// convert the Strigi ids found in object values into the corresponding sub-resource URIs + Nepomuk::SimpleResource convertSubResourceIds(const Nepomuk::SimpleResource& res) const; + + /// The resource URI + QUrl resourceUri; + + /// The file URL (nie:url) + KUrl fileUrl; + + /// The file info - saved to prevent multiple stats + QFileInfo fileInfo; + + /// a buffer for all plain-text content generated by strigi + std::string content; + + /// The main file's metadata + Nepomuk::SimpleResource data; + + /// The sub-resources, mapped by the identifier libstreamanalyzer provided + QHash<QString, Nepomuk::SimpleResource> subResources; + + private: + /// The Strigi result + const Strigi::AnalysisResult* m_analysisResult; + }; + + + FileMetaData::FileMetaData( const Strigi::AnalysisResult* idx, const QUrl & resUri ) + : m_analysisResult( idx ) + { + fileUrl = createFileUrl( idx ); + fileInfo = QFileInfo( fileUrl.toLocalFile() ); + resourceUri = resUri; + if(resourceUri.isEmpty()) { + data.setUri(fileUrl); + } + else { + data.setUri(resourceUri); + } + } + + Nepomuk::SimpleResource FileMetaData::convertSubResourceIds(const Nepomuk::SimpleResource& res) const + { + Nepomuk::PropertyHash props = res.properties(); + QMutableHashIterator<QUrl, QVariant> it(props); + while(it.hasNext()) { + it.next(); + if(it.value().type() == QVariant::String) { + QHash<QString, Nepomuk::SimpleResource>::const_iterator subResIt = subResources.constFind(it.value().toString()); + if(subResIt != subResources.constEnd()) { + it.setValue(subResIt.value().uri()); + } + } + } + Nepomuk::SimpleResource newRes(res); + newRes.setProperties(props); + return newRes; + } + + FileMetaData* fileDataForResult( const Strigi::AnalysisResult* idx ) + { + return static_cast<FileMetaData*>( idx->writerData() ); + } +} + + +class Nepomuk::StrigiIndexWriter::Private +{ +public: + // + // The Strigi API does not provide context information in addTriplet, i.e. the AnalysisResult. + // However, we only use one thread, only one AnalysisResult at the time. + // Thus, we can just remember that and use it in addTriplet. + // + QMutex resultStackMutex; + QStack<const Strigi::AnalysisResult*> currentResultStack; + + // Some services may need to force a specific resource uri + QUrl resourceUri; +}; + + +Nepomuk::StrigiIndexWriter::StrigiIndexWriter() + : Strigi::IndexWriter(), + d( new Private() ) +{ +} + + +Nepomuk::StrigiIndexWriter::~StrigiIndexWriter() +{ + kDebug(); + delete d; +} + + +// unused +void Nepomuk::StrigiIndexWriter::commit() +{ + // do nothing + Q_ASSERT(false); +} + + +// unused +void Nepomuk::StrigiIndexWriter::deleteEntries( const std::vector<std::string>& entries ) +{ + // do nothing + Q_UNUSED(entries); + Q_ASSERT(false); +} + + +// unused +void Nepomuk::StrigiIndexWriter::deleteAllEntries() +{ + // do nothing + Q_ASSERT(false); +} + + +// called for each indexed file +void Nepomuk::StrigiIndexWriter::startAnalysis( const AnalysisResult* idx ) +{ + // we need to remember the AnalysisResult since addTriplet does not provide it + d->currentResultStack.push(idx); + + // for now we ignore embedded files -> too many false positives and useless query results + if ( idx->depth() > 0 ) { + return; + } + + // create the file data used during the analysis + FileMetaData* data = new FileMetaData( idx, d->resourceUri ); + + // remove previously indexed data + // but only for files. We cannot remove folders since that would also remove the + // nie:isPartOf links from the files. + if(!data->fileInfo.isDir()) { + if( !data->resourceUri.isEmpty() ) { + Nepomuk::clearIndexedData(data->resourceUri)->exec(); + } + else { + Nepomuk::clearIndexedData(data->fileUrl)->exec(); + } + } + + // remember the file data + idx->setWriterData( data ); +} + + +void Nepomuk::StrigiIndexWriter::addText( const AnalysisResult* idx, const char* text, int32_t length ) +{ + if ( idx->depth() > 0 ) { + return; + } + + FileMetaData* md = fileDataForResult( idx ); + + // make sure text fragments are separated + if(md->content.size() > 0) + md->content.append( " " ); + + // append the new fragment + md->content.append( text, length ); +} + + +void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult* idx, + const RegisteredField* field, + const std::string& value ) +{ + if ( idx->depth() > 0 ) { + return; + } + + if ( value.length() > 0 ) { + FileMetaData* md = fileDataForResult( idx ); + RegisteredFieldData* rfd = reinterpret_cast<RegisteredFieldData*>( field->writerData() ); + + // + // ignore the path as we handle that ourselves in startAnalysis + // ignore the mimetype since we handle that ourselves in finishAnalysis + // and KDE's mimetype is much more reliable than Strigi's. + // TODO: remember the mimetype and use it only if KMimeType does not find one. + // + if ( field->key() != FieldRegister::pathFieldName && + field->key() != FieldRegister::mimetypeFieldName ) { + md->data.addProperty(rfd->property(), QString::fromUtf8(value.c_str())); + } + } +} + + +// the main addValue method +void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult* idx, + const RegisteredField* field, + const unsigned char* data, + uint32_t size ) +{ + addValue( idx, field, std::string( ( const char* )data, size ) ); +} + + +void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult*, const RegisteredField*, + const std::string&, const std::string& ) +{ + // we do not support map types +} + + +void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult* idx, + const RegisteredField* field, + uint32_t value ) +{ + if ( idx->depth() > 0 ) { + return; + } + + FileMetaData* md = fileDataForResult( idx ); + RegisteredFieldData* rfd = reinterpret_cast<RegisteredFieldData*>( field->writerData() ); + + md->data.addProperty(rfd->property(), value); +} + + +void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult* idx, + const RegisteredField* field, + int32_t value ) +{ + if ( idx->depth() > 0 ) { + return; + } + + FileMetaData* md = fileDataForResult( idx ); + RegisteredFieldData* rfd = reinterpret_cast<RegisteredFieldData*>( field->writerData() ); + + md->data.addProperty(rfd->property(), value); +} + + +void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult* idx, + const RegisteredField* field, + double value ) +{ + if ( idx->depth() > 0 ) { + return; + } + + FileMetaData* md = fileDataForResult( idx ); + RegisteredFieldData* rfd = reinterpret_cast<RegisteredFieldData*>( field->writerData() ); + + md->data.addProperty(rfd->property(), value); +} + + +void Nepomuk::StrigiIndexWriter::addTriplet( const std::string& s, + const std::string& p, + const std::string& o ) +{ + if ( d->currentResultStack.top()->depth() > 0 ) { + kDebug() << "Depth > 0 -" << s.c_str() << p.c_str() << o.c_str(); + return; + } + FileMetaData* md = fileDataForResult( d->currentResultStack.top() ); + + // convert the std strings to Qt values + const QString subResId(QString::fromUtf8(s.c_str())); + const QUrl property(QString::fromUtf8(p.c_str())); + const QString value(QString::fromUtf8(o.c_str())); + + // the subject might be the indexed file itself + if(KUrl(subResId) == md->fileUrl) { + md->data.addProperty(property, value); + } + else { + // Find the corresponding sub-resource + QHash<QString, Nepomuk::SimpleResource>::iterator it = md->subResources.find(subResId); + if(it == md->subResources.end()) { + Nepomuk::SimpleResource subRes; + subRes.addProperty(property, value); + md->subResources.insert(subResId, subRes); + } + else { + it->addProperty(property, value); + } + } +} + + +// called after each indexed file +void Nepomuk::StrigiIndexWriter::finishAnalysis( const AnalysisResult* idx ) +{ + d->currentResultStack.pop(); + + if ( idx->depth() > 0 ) { + return; + } + + FileMetaData* md = fileDataForResult( idx ); + + // store the full text of the file + if ( md->content.length() > 0 ) { + md->data.addProperty(Nepomuk::Vocabulary::NIE::plainTextContent(), + QString::fromUtf8( md->content.c_str() )); + } + + // store the nie:url in any case + md->data.addProperty(Nepomuk::Vocabulary::NIE::url(), md->fileUrl); + + if( md->fileInfo.exists() ) { + // + // Strigi only indexes files and many extractors are still not perfect with types. + // Each file is a nie:DataObject and a nie:InformationElement. Many Strigi plugins + // forget at least one of the two. + // Thus, here we go the easy way and mark each indexed file as a nfo:FileDataObject + // and a nie:InformationElement, thus, at least providing the basic types to Nepomuk's + // domain and range checking. + // + md->data.addType(Nepomuk::Vocabulary::NFO::FileDataObject()); + md->data.addType(Nepomuk::Vocabulary::NIE::InformationElement()); + if ( md->fileInfo.isDir() ) { + md->data.addType(Nepomuk::Vocabulary::NFO::Folder()); + } + + // write the mimetype for local files + md->data.addProperty( Nepomuk::Vocabulary::NIE::mimeType(), + KMimeType::findByUrl(md->fileUrl)->name() ); +#ifdef Q_OS_UNIX + KDE_struct_stat statBuf; + if( KDE_stat( QFile::encodeName(md->fileInfo.absoluteFilePath()).data(), &statBuf ) == 0 ) { + md->data.addProperty( Nepomuk::Vocabulary::KExt::unixFileMode(), + int(statBuf.st_mode) ); + } + if( !md->fileInfo.owner().isEmpty() ) + md->data.addProperty( Nepomuk::Vocabulary::KExt::unixFileOwner(), + md->fileInfo.owner() ); + if( !md->fileInfo.group().isEmpty() ) + md->data.addProperty( Nepomuk::Vocabulary::KExt::unixFileGroup(), + md->fileInfo.group() ); +#endif // Q_OS_UNIX + } + + + // + // Finally push all the information to Nepomuk + // + Nepomuk::SimpleResourceGraph graph; + + // Add all the sub-resources as sub-resources of the main resource + for(QHash<QString, Nepomuk::SimpleResource>::const_iterator it = md->subResources.constBegin(); + it != md->subResources.constEnd(); ++it) { + md->data.addProperty(NAO::hasSubResource(), it.value()); + graph << md->convertSubResourceIds(it.value()); + } + + // finally add the file resource itself to the graph + graph << md->convertSubResourceIds(md->data); + + // Add additional metadata - mark the information as discardable + QHash<QUrl, QVariant> additionalMetadata; + additionalMetadata.insert( RDF::type(), NRL::DiscardableInstanceBase() ); + + // we do not have an event loop - thus, we need to delete the job ourselves + QScopedPointer<KJob> job( Nepomuk::storeResources( graph, Nepomuk::IdentifyNew, Nepomuk::LazyCardinalities|Nepomuk::OverwriteProperties, additionalMetadata ) ); + job->setAutoDelete(false); + job->exec(); + if( job->error() ) { + kDebug() << job->errorString(); + } + + // cleanup + delete md; + idx->setWriterData( 0 ); +} + + +void Nepomuk::StrigiIndexWriter::initWriterData( const Strigi::FieldRegister& f ) +{ + // cache type conversion for all strigi fields + std::map<std::string, RegisteredField*>::const_iterator i; + std::map<std::string, RegisteredField*>::const_iterator end = f.fields().end(); + for (i = f.fields().begin(); i != end; ++i) { + const QUrl prop = QUrl::fromEncoded( i->second->key().c_str() ); + i->second->setWriterData( new RegisteredFieldData( prop ) ); + } +} + + +void Nepomuk::StrigiIndexWriter::releaseWriterData( const Strigi::FieldRegister& f ) +{ + std::map<std::string, RegisteredField*>::const_iterator i; + std::map<std::string, RegisteredField*>::const_iterator end = f.fields().end(); + for (i = f.fields().begin(); i != end; ++i) { + delete static_cast<RegisteredFieldData*>( i->second->writerData() ); + i->second->setWriterData( 0 ); + } +} + + +void Nepomuk::StrigiIndexWriter::forceUri(const QUrl& uri) +{ + d->resourceUri = uri; +} diff --git a/nepomuk/services/strigi/indexer/nepomukindexwriter.h b/nepomuk/services/strigi/indexer/nepomukindexwriter.h new file mode 100644 index 0000000..75cadbb --- /dev/null +++ b/nepomuk/services/strigi/indexer/nepomukindexwriter.h @@ -0,0 +1,85 @@ +/* + Copyright (C) 2007-2010 Sebastian Trueg <trueg@kde.org> + + 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 _STRIGI_NEPOMUK_INDEX_WRITER_H_ +#define _STRIGI_NEPOMUK_INDEX_WRITER_H_ + +#include <strigi/indexwriter.h> +#include <strigi/analysisresult.h> +#include <strigi/analyzerconfiguration.h> + +class KUrl; +class QUrl; + +namespace Nepomuk { + + class Resource; + + class StrigiIndexWriter : public Strigi::IndexWriter + { + public: + StrigiIndexWriter(); + ~StrigiIndexWriter(); + + void commit(); + + /** + * Delete the entries with the given paths from the index. + * + * @param entries the paths of the files that should be deleted + **/ + void deleteEntries( const std::vector<std::string>& entries ); + + /** + * Delete all indexed documents from the index. + **/ + void deleteAllEntries(); + + void initWriterData( const Strigi::FieldRegister& ); + void releaseWriterData( const Strigi::FieldRegister& ); + + void startAnalysis( const Strigi::AnalysisResult* ); + void addText( const Strigi::AnalysisResult*, const char* text, int32_t length ); + void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, + const std::string& value ); + void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, + const unsigned char* data, uint32_t size ); + void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, + int32_t value ); + void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, + uint32_t value ); + void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, + double value ); + void addTriplet( const std::string& subject, + const std::string& predicate, const std::string& object ); + void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, + const std::string& name, const std::string& value ); + void finishAnalysis( const Strigi::AnalysisResult* ); + + void forceUri( const QUrl & uri ); + + private: + class Private; + Private* d; + }; +} + +uint qHash( const std::string& s ); + +#endif diff --git a/nepomuk/services/strigi/indexscheduler.cpp b/nepomuk/services/strigi/indexscheduler.cpp index 22b599d..8a051e3 100644 --- a/nepomuk/services/strigi/indexscheduler.cpp +++ b/nepomuk/services/strigi/indexscheduler.cpp @@ -1,6 +1,6 @@ /* This file is part of the KDE Project Copyright (c) 2008-2010 Sebastian Trueg <trueg@kde.org> - Copyright (c) 2010 Vishesh Handa <handa.vish@gmail.com> + Copyright (c) 2010-11 Vishesh Handa <handa.vish@gmail.com> Parts of this file are based on code from Strigi Copyright (C) 2006-2007 Jos van den Oever <jos@vandenoever.info> @@ -24,6 +24,7 @@ #include "strigiserviceconfig.h" #include "nepomukindexer.h" #include "util.h" +#include "datamanagement.h" #include <QtCore/QMutexLocker> #include <QtCore/QList> @@ -41,8 +42,6 @@ #include <Nepomuk/Resource> #include <Nepomuk/ResourceManager> #include <Nepomuk/Variant> -#include <Nepomuk/Query/Query> -#include <Nepomuk/Query/ComparisonTerm> #include <Nepomuk/Query/ResourceTerm> #include <Soprano/Model> @@ -51,52 +50,31 @@ #include <Soprano/Node> #include <Soprano/Vocabulary/RDF> -#include <Soprano/Vocabulary/Xesam> -#include <Nepomuk/Vocabulary/NFO> #include <Nepomuk/Vocabulary/NIE> #include <map> #include <vector> +#include "indexcleaner.h" +using namespace Soprano::Vocabulary; +using namespace Nepomuk::Vocabulary; namespace { const int s_reducedSpeedDelay = 500; // ms const int s_snailPaceDelay = 3000; // ms - - bool isResourcePresent( const QString & dir ) { - QString query = QString::fromLatin1(" ask { ?r %1 %2. } ") - .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), - Soprano::Node::resourceToN3( KUrl( dir ) ) ); - return Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( query, Soprano::Query::QueryLanguageSparql ).boolValue(); - } - QHash<QString, QDateTime> getChildren( const QString& dir ) { QHash<QString, QDateTime> children; - QString query; - - if( !isResourcePresent( dir ) ) { - query = QString::fromLatin1( "select distinct ?url ?mtime where { " - "?r %1 ?url . " - "FILTER( regex(str(?url), '^file://%2/([^/]*)$') ) . " - "?r %3 ?mtime ." - "}" ) - .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), - dir, - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::lastModified() ) ); - } - else { - query = QString::fromLatin1( "select distinct ?url ?mtime where { " - "?r %1 ?parent . ?parent %2 %3 . " - "?r %4 ?mtime . " - "?r %2 ?url . " - "}" ) - .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::isPartOf() ), - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), - Soprano::Node::resourceToN3( KUrl( dir ) ), - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::lastModified() ) ); - } + QString query = QString::fromLatin1( "select distinct ?url ?mtime where { " + "?r %1 ?parent . ?parent %2 %3 . " + "?r %4 ?mtime . " + "?r %2 ?url . " + "}" ) + .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::isPartOf() ), + Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), + Soprano::Node::resourceToN3( KUrl( dir ) ), + Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::lastModified() ) ); //kDebug() << "running getChildren query:" << query; Soprano::QueryResultIterator result = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( query, Soprano::Query::QueryLanguageSparql ); @@ -107,6 +85,31 @@ namespace { return children; } + + QDateTime indexedMTimeForUrl(const KUrl& url) + { + Soprano::QueryResultIterator it + = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery(QString::fromLatin1("select ?mt where { ?r %1 %2 . ?r %3 ?mt . } LIMIT 1") + .arg(Soprano::Node::resourceToN3(NIE::url()), + Soprano::Node::resourceToN3(url), + Soprano::Node::resourceToN3(NIE::lastModified())), + Soprano::Query::QueryLanguageSparql); + if(it.next()) { + return it[0].literal().toDateTime(); + } + else { + return QDateTime(); + } + } + + bool compareIndexedMTime(const KUrl& url, const QDateTime& mtime) + { + const QDateTime indexedMTime = indexedMTimeForUrl(url); + if(indexedMTime.isNull()) + return false; + else + return indexedMTime == mtime; + } } @@ -158,15 +161,21 @@ void Nepomuk::IndexScheduler::UpdateDirQueue::clearByFlags( UpdateDirFlags mask Nepomuk::IndexScheduler::IndexScheduler( QObject* parent ) - : QThread( parent ), + : QObject( parent ), m_suspended( false ), - m_stopped( false ), m_indexing( false ), - m_speed( FullSpeed ) + m_indexingDelay( 0 ) { - m_indexer = new Nepomuk::Indexer( this ); + m_cleaner = new IndexCleaner(this); + connect( m_cleaner, SIGNAL(finished(KJob*)), this, SLOT(slotCleaningDone()) ); + m_cleaner->start(); + connect( StrigiServiceConfig::self(), SIGNAL( configChanged() ), this, SLOT( slotConfigChanged() ) ); + + // start the initial indexing + queueAllFoldersForUpdate(); + callDoIndexing(); } @@ -177,9 +186,12 @@ Nepomuk::IndexScheduler::~IndexScheduler() void Nepomuk::IndexScheduler::suspend() { - if ( isRunning() && !m_suspended ) { - QMutexLocker locker( &m_resumeStopMutex ); + QMutexLocker locker( &m_suspendMutex ); + if ( !m_suspended ) { m_suspended = true; + if( m_cleaner ) { + m_cleaner->suspend(); + } emit indexingSuspended( true ); } } @@ -187,10 +199,16 @@ void Nepomuk::IndexScheduler::suspend() void Nepomuk::IndexScheduler::resume() { - if ( isRunning() && m_suspended ) { - QMutexLocker locker( &m_resumeStopMutex ); + QMutexLocker locker( &m_suspendMutex ); + if ( m_suspended ) { m_suspended = false; - m_resumeStopWc.wakeAll(); + + if( m_cleaner ) { + m_cleaner->resume(); + } + + callDoIndexing(); + emit indexingSuspended( false ); } } @@ -205,31 +223,16 @@ void Nepomuk::IndexScheduler::setSuspended( bool suspended ) } -void Nepomuk::IndexScheduler::stop() -{ - if ( isRunning() ) { - QMutexLocker locker( &m_resumeStopMutex ); - m_stopped = true; - m_suspended = false; - m_indexer->stop(); - m_dirsToUpdateWc.wakeAll(); - m_resumeStopWc.wakeAll(); - } -} - - -void Nepomuk::IndexScheduler::restart() -{ - stop(); - wait(); - start(); -} - - void Nepomuk::IndexScheduler::setIndexingSpeed( IndexingSpeed speed ) { kDebug() << speed; - m_speed = speed; + m_indexingDelay = 0; + if ( speed != FullSpeed ) { + m_indexingDelay = (speed == ReducedSpeed) ? s_reducedSpeedDelay : s_snailPaceDelay; + } + if( m_cleaner ) { + m_cleaner->setDelay(m_indexingDelay); + } } @@ -244,30 +247,36 @@ void Nepomuk::IndexScheduler::setReducedIndexingSpeed( bool reduced ) bool Nepomuk::IndexScheduler::isSuspended() const { - return isRunning() && m_suspended; + QMutexLocker locker( &m_suspendMutex ); + return m_suspended; } bool Nepomuk::IndexScheduler::isIndexing() const { + QMutexLocker locker( &m_indexingMutex ); return m_indexing; } QString Nepomuk::IndexScheduler::currentFolder() const { - return m_currentFolder; + QMutexLocker locker( &m_currentMutex ); + return m_currentUrl.directory(); } QString Nepomuk::IndexScheduler::currentFile() const { + QMutexLocker locker( &m_currentMutex ); return m_currentUrl.toLocalFile(); } void Nepomuk::IndexScheduler::setIndexingStarted( bool started ) { + QMutexLocker locker( &m_indexingMutex ); + if ( started != m_indexing ) { m_indexing = started; emit indexingStateChanged( m_indexing ); @@ -279,78 +288,70 @@ void Nepomuk::IndexScheduler::setIndexingStarted( bool started ) } -void Nepomuk::IndexScheduler::run() +void Nepomuk::IndexScheduler::slotCleaningDone() { - // set lowest priority for this thread - setPriority( QThread::IdlePriority ); - m_indexer->start(); - - setIndexingStarted( true ); - - // initialization - queueAllFoldersForUpdate(); - -#ifndef NDEBUG - QTime timer; - timer.start(); -#endif + m_cleaner = 0; +} - removeOldAndUnwantedEntries(); +void Nepomuk::IndexScheduler::doIndexing() +{ + setIndexingStarted( true ); -#ifndef NDEBUG - kDebug() << "Removed old entries: " << timer.elapsed()/1000.0 << "secs"; - timer.restart(); -#endif + // lock file and dir queues as we check their status + QMutexLocker fileLock( &m_filesToUpdateMutex ); + QMutexLocker dirLock( &m_dirsToUpdateMutex ); - while ( waitForContinue(true) ) { - // wait for more dirs to analyze in case the initial - // indexing is done - m_dirsToUpdateMutex.lock(); - if ( m_dirsToUpdate.isEmpty() ) { - setIndexingStarted( false ); + // get the next file + if( !m_filesToUpdate.isEmpty() ) { + kDebug() << "File"; -#ifndef NDEBUG - kDebug() << "All folders updated: " << timer.elapsed()/1000.0 << "sec"; -#endif + QFileInfo file = m_filesToUpdate.dequeue(); - m_dirsToUpdateWc.wait( &m_dirsToUpdateMutex ); + m_currentMutex.lock(); + m_currentUrl = file.filePath(); + m_currentMutex.unlock(); + emit indexingFile( currentFile() ); -#ifndef NDEBUG - timer.restart(); -#endif + KJob * indexer = new Indexer( file ); + connect( indexer, SIGNAL(finished(KJob*)), this, SLOT(slotIndexingDone(KJob*)) ); + indexer->start(); + } - if ( !m_stopped ) - setIndexingStarted( true ); - } - m_dirsToUpdateMutex.unlock(); + // get the next folder + else if( !m_dirsToUpdate.isEmpty() ) { + kDebug() << "Directory"; - // wait for resume or stop (or simply continue) - if ( !waitForContinue() ) { - break; - } - - // get the next folder - m_dirsToUpdateMutex.lock(); QPair<QString, UpdateDirFlags> dir = m_dirsToUpdate.dequeue(); - m_dirsToUpdateMutex.unlock(); + dirLock.unlock(); + fileLock.unlock(); - // update until stopped - if ( !analyzeDir( dir.first, dir.second ) ) { - break; - } - m_currentFolder.clear(); + analyzeDir( dir.first, dir.second ); + callDoIndexing(); } - setIndexingStarted( false ); + else { + // reset status + m_currentMutex.lock(); + m_currentUrl.clear(); + m_currentMutex.unlock(); - // reset state - m_suspended = false; - m_stopped = false; - m_currentFolder.clear(); + setIndexingStarted( false ); + } } +void Nepomuk::IndexScheduler::slotIndexingDone(KJob* job) +{ + kDebug() << job; + Q_UNUSED( job ); + + m_currentMutex.lock(); + m_currentUrl.clear(); + m_currentMutex.unlock(); -bool Nepomuk::IndexScheduler::analyzeDir( const QString& dir_, UpdateDirFlags flags ) + callDoIndexing(); +} + +void Nepomuk::IndexScheduler::analyzeDir( const QString& dir_, Nepomuk::IndexScheduler::UpdateDirFlags flags ) { // kDebug() << dir << analyzer << recursive; @@ -362,18 +363,20 @@ bool Nepomuk::IndexScheduler::analyzeDir( const QString& dir_, UpdateDirFlags fl // inform interested clients emit indexingFolder( dir ); + m_currentMutex.lock(); + m_currentUrl = KUrl( dir ); + m_currentMutex.unlock(); - m_currentFolder = dir; const bool recursive = flags&UpdateRecursive; const bool forceUpdate = flags&ForceUpdate; // we start by updating the folder itself QFileInfo dirInfo( dir ); KUrl dirUrl( dir ); - Nepomuk::Resource dirRes( dirUrl ); - if ( !dirRes.exists() || - dirRes.property( Nepomuk::Vocabulary::NIE::lastModified() ).toDateTime() != dirInfo.lastModified() ) { - m_indexer->indexFile( dirInfo ); + if ( !compareIndexedMTime(dirUrl, dirInfo.lastModified()) ) { + KJob * indexer = new Indexer( dirInfo ); + connect( indexer, SIGNAL(finished(KJob*)), this, SLOT(slotIndexingDone(KJob*)) ); + indexer->start(); } // get a map of all indexed files from the dir including their stored mtime @@ -440,49 +443,43 @@ bool Nepomuk::IndexScheduler::analyzeDir( const QString& dir_, UpdateDirFlags fl deleteEntries( filesToDelete ); // analyze all files that are new or need updating - foreach( const QFileInfo& file, filesToIndex ) { - - // wait if we are suspended or return if we are stopped - if ( !waitForContinue() ) - return false; + m_filesToUpdateMutex.lock(); + m_filesToUpdate.append( filesToIndex ); + m_filesToUpdateMutex.unlock(); - m_currentUrl = file.filePath(); - m_indexer->indexFile( file ); - m_currentUrl = KUrl(); - } - - return true; + // reset status + m_currentMutex.lock(); + m_currentUrl.clear(); + m_currentMutex.unlock(); } -bool Nepomuk::IndexScheduler::waitForContinue( bool disableDelay ) +void Nepomuk::IndexScheduler::callDoIndexing(bool noDelay) { - QMutexLocker locker( &m_resumeStopMutex ); - if ( m_suspended ) { - setIndexingStarted( false ); - m_resumeStopWc.wait( &m_resumeStopMutex ); - setIndexingStarted( true ); - } - else if ( !disableDelay && m_speed != FullSpeed ) { - msleep( m_speed == ReducedSpeed ? s_reducedSpeedDelay : s_snailPaceDelay ); + if( !m_suspended ) { + QTimer::singleShot( noDelay ? 0 : m_indexingDelay, this, SLOT(doIndexing()) ); } - - return !m_stopped; } void Nepomuk::IndexScheduler::updateDir( const QString& path, UpdateDirFlags flags ) { - QMutexLocker lock( &m_dirsToUpdateMutex ); + QMutexLocker dirLock( &m_dirsToUpdateMutex ); m_dirsToUpdate.prependDir( path, flags & ~AutoUpdateFolder ); - m_dirsToUpdateWc.wakeAll(); + + QMutexLocker statusLock( &m_indexingMutex ); + if( !m_indexing ) + callDoIndexing(); } void Nepomuk::IndexScheduler::updateAll( bool forceUpdate ) { queueAllFoldersForUpdate( forceUpdate ); - m_dirsToUpdateWc.wakeAll(); + + QMutexLocker locker( &m_indexingMutex ); + if( !m_indexing ) + callDoIndexing(); } @@ -506,23 +503,45 @@ void Nepomuk::IndexScheduler::queueAllFoldersForUpdate( bool forceUpdate ) void Nepomuk::IndexScheduler::slotConfigChanged() { - // restart to make sure we update all folders and removeOldAndUnwantedEntries - if ( isRunning() ) - restart(); -} + // TODO: only update folders that were added in the config + updateAll(); + if( m_cleaner ) { + m_cleaner->kill(); + delete m_cleaner; + } -void Nepomuk::IndexScheduler::analyzeResource( const QUrl& uri, const QDateTime& modificationTime, QDataStream& data ) -{ - Indexer indexer; - indexer.indexResource( uri, modificationTime, data ); + // TODO: only clean the folders that were removed from the config + m_cleaner = new IndexCleaner( this ); + connect( m_cleaner, SIGNAL(finished(KJob*)), this, SLOT(slotCleaningDone()) ); + m_cleaner->start(); } void Nepomuk::IndexScheduler::analyzeFile( const QString& path ) { - Indexer indexer; - indexer.indexFile( QFileInfo( path ) ); + kDebug() << path; + QMutexLocker fileLock(&m_filesToUpdateMutex); + + // we prepend the file to give preference to newly created and changed files over + // the initial indexing. Sadly operator== cannot be relied on for QFileInfo. Thus + // we need to do a dumb search + QMutableListIterator<QFileInfo> it(m_filesToUpdate); + while(it.hasNext()) { + if(it.next().filePath() == path) { + kDebug() << "Already queued:" << path << "Moving to front of queue."; + it.remove(); + break; + } + } + kDebug() << "Queuing" << path; + m_filesToUpdate.prepend(path); + + // continue indexing without any delay. We want changes reflected as soon as possible + QMutexLocker statusLock(&m_indexingMutex); + if( !m_indexing ) { + callDoIndexing(true); + } } @@ -530,207 +549,17 @@ void Nepomuk::IndexScheduler::deleteEntries( const QStringList& entries ) { // recurse into subdirs // TODO: use a less mem intensive method + // FIXME: There are 2 lists, one for KUrl and the other for QUrl. They should be automatically + // converted. Fix this in 4.8 + QList<QUrl> urls; + KUrl::List kurls; for ( int i = 0; i < entries.count(); ++i ) { + KUrl url( entries[i] ); + urls << url; + kurls << url; deleteEntries( getChildren( entries[i] ).keys() ); - m_indexer->clearIndexedData( KUrl( entries[i] ) ); - } -} - - -namespace { - /** - * Creates one SPARQL filter expression that excludes the include folders. - * This is necessary since constructFolderSubFilter will append a slash to - * each folder to make sure it does not match something like - * '/home/foobar' with '/home/foo'. - */ - QString constructExcludeIncludeFoldersFilter() - { - QStringList filters; - foreach( const QString& folder, Nepomuk::StrigiServiceConfig::self()->includeFolders() ) { - filters << QString::fromLatin1( "(?url!=%1)" ).arg( Soprano::Node::resourceToN3( KUrl( folder ) ) ); - } - return filters.join( QLatin1String( " && " ) ); - } - - QString constructFolderSubFilter( const QList<QPair<QString, bool> > folders, int& index ) - { - QString path = folders[index].first; - if ( !path.endsWith( '/' ) ) - path += '/'; - const bool include = folders[index].second; - - ++index; - - QStringList subFilters; - while ( index < folders.count() && - folders[index].first.startsWith( path ) ) { - subFilters << constructFolderSubFilter( folders, index ); - } - - QString thisFilter = QString::fromLatin1( "REGEX(STR(?url),'^%1')" ).arg( QString::fromAscii( KUrl( path ).toEncoded() ) ); - - // we want all folders that should NOT be indexed - if ( include ) { - thisFilter.prepend( '!' ); - } - subFilters.prepend( thisFilter ); - - if ( subFilters.count() > 1 ) { - return '(' + subFilters.join( include ? QLatin1String( " || " ) : QLatin1String( " && " ) ) + ')'; - } - else { - return subFilters.first(); - } } - - /** - * Creates one SPARQL filter which matches all files and folders that should NOT be indexed. - */ - QString constructFolderFilter() - { - QStringList subFilters( constructExcludeIncludeFoldersFilter() ); - - // now add the actual filters - QList<QPair<QString, bool> > folders = Nepomuk::StrigiServiceConfig::self()->folders(); - int index = 0; - while ( index < folders.count() ) { - subFilters << constructFolderSubFilter( folders, index ); - } - return subFilters.join(" && "); - } -} - -void Nepomuk::IndexScheduler::removeOldAndUnwantedEntries() -{ - // - // We now query all indexed files that are in folders that should not - // be indexed at once. - // - QString folderFilter = constructFolderFilter(); - if( !folderFilter.isEmpty() ) - folderFilter = QString::fromLatin1("FILTER(%1) .").arg(folderFilter); - - // - // We query all files that should not be in the store - // This for example excludes all filex:/ URLs. - // - QString query = QString::fromLatin1( "select distinct ?g where { " - "?r %1 ?url . " - "?g <http://www.strigi.org/fields#indexGraphFor> ?r . " - "FILTER(REGEX(STR(?url),'^file:/')) . " - "%2 }" ) - .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), - folderFilter ); - kDebug() << query; - if ( !removeAllGraphsFromQuery( query ) ) - return; - - - // - // Build filter query for all exclude filters - // - QStringList fileFilters; - foreach( const QString& filter, Nepomuk::StrigiServiceConfig::self()->excludeFilters() ) { - QString filterRxStr = QRegExp::escape( filter ); - filterRxStr.replace( "\\*", QLatin1String( ".*" ) ); - filterRxStr.replace( "\\?", QLatin1String( "." ) ); - filterRxStr.replace( '\\',"\\\\" ); - fileFilters << QString::fromLatin1( "REGEX(STR(?fn),\"^%1$\")" ).arg( filterRxStr ); - } - QString includeExcludeFilters = constructExcludeIncludeFoldersFilter(); - - QString filters; - if( !includeExcludeFilters.isEmpty() && !fileFilters.isEmpty() ) - filters = QString::fromLatin1("FILTER((%1) && (%2)) .").arg( includeExcludeFilters, fileFilters.join(" || ") ); - else if( !fileFilters.isEmpty() ) - filters = QString::fromLatin1("FILTER(%1) .").arg( fileFilters.join(" || ") ); - else if( !includeExcludeFilters.isEmpty() ) - filters = QString::fromLatin1("FILTER(%1) .").arg( includeExcludeFilters ); - - query = QString::fromLatin1( "select distinct ?g where { " - "?r %1 ?url . " - "?r %2 ?fn . " - "?g <http://www.strigi.org/fields#indexGraphFor> ?r . " - "FILTER(REGEX(STR(?url),\"^file:/\")) . " - "%3 }" ) - .arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NFO::fileName() ), - filters ); - kDebug() << query; - if ( !removeAllGraphsFromQuery( query ) ) - return; - - - // - // Remove all old data from Xesam-times. While we leave out the data created by libnepomuk - // there is no problem since libnepomuk still uses backwards compatible queries and we use - // libnepomuk to determine URIs in the strigi backend. - // - query = QString::fromLatin1( "select distinct ?g where { " - "?g <http://www.strigi.org/fields#indexGraphFor> ?x . " - "{ graph ?g { ?r1 <http://strigi.sf.net/ontologies/0.9#parentUrl> ?p1 . } } " - "UNION " - "{ graph ?g { ?r2 %1 ?u2 . } } " - "}" ) - .arg( Soprano::Node::resourceToN3( Soprano::Vocabulary::Xesam::url() ) ); - kDebug() << query; - if ( !removeAllGraphsFromQuery( query ) ) - return; - - - // - // Remove data which is useless but still around from before. This could happen due to some buggy version of - // the indexer or the filewatch service or even some application messing up the data. - // We look for indexed files that do not have a nie:url defined and thus, will never be catched by any of the - // other queries. In addition we check for an isPartOf relation since strigi produces EmbeddedFileDataObjects - // for video and audio streams. - // - Query::Query q( - Strigi::Ontology::indexGraphFor() == ( Soprano::Vocabulary::RDF::type() == Query::ResourceTerm( Nepomuk::Vocabulary::NFO::FileDataObject() ) && - !( Nepomuk::Vocabulary::NIE::url() == Query::Term() ) && - !( Nepomuk::Vocabulary::NIE::isPartOf() == Query::Term() ) ) - ); - q.setQueryFlags(Query::Query::NoResultRestrictions); - query = q.toSparqlQuery(); - kDebug() << query; - removeAllGraphsFromQuery( query ); -} - - - -/** - * Runs the query using a limit until all graphs have been deleted. This is not done - * in one big loop to avoid the problems with messed up iterators when one of the iterated - * item is deleted. - */ -bool Nepomuk::IndexScheduler::removeAllGraphsFromQuery( const QString& query ) -{ - while ( 1 ) { - // get the next batch of graphs - QList<Soprano::Node> graphs - = ResourceManager::instance()->mainModel()->executeQuery( query + QLatin1String( " LIMIT 200" ), - Soprano::Query::QueryLanguageSparql ).iterateBindings( 0 ).allNodes(); - - // remove all graphs in the batch - Q_FOREACH( const Soprano::Node& graph, graphs ) { - - // wait for resume or stop (or simply continue) - if ( !waitForContinue() ) { - return false; - } - - ResourceManager::instance()->mainModel()->removeContext( graph ); - } - - // we are done when the last graphs are queried - if ( graphs.count() < 200 ) { - return true; - } - } - - // make gcc shut up - return true; + Nepomuk::clearIndexedData(urls); } diff --git a/nepomuk/services/strigi/indexscheduler.h b/nepomuk/services/strigi/indexscheduler.h index 6a03aa4..01d325e 100644 --- a/nepomuk/services/strigi/indexscheduler.h +++ b/nepomuk/services/strigi/indexscheduler.h @@ -24,19 +24,20 @@ #include <QtCore/QWaitCondition> #include <QtCore/QQueue> #include <QtCore/QDateTime> +#include <QtCore/QTimer> #include <vector> #include <string> #include <KUrl> +class KJob; class QFileInfo; class QByteArray; namespace Nepomuk { - class Indexer; - + class IndexCleaner; /** * The IndexScheduler performs the normal indexing, * ie. the initial indexing and the timed updates @@ -44,12 +45,12 @@ namespace Nepomuk { * * Events are not handled. */ - class IndexScheduler : public QThread + class IndexScheduler : public QObject { Q_OBJECT public: - IndexScheduler( QObject* parent ); + IndexScheduler( QObject* parent=0 ); ~IndexScheduler(); bool isSuspended() const; @@ -120,18 +121,14 @@ namespace Nepomuk { SnailPace }; - IndexingSpeed currentSpeed() const { return m_speed; } - public Q_SLOTS: void suspend(); void resume(); - void stop(); - void restart(); void setIndexingSpeed( IndexingSpeed speed ); /** - * A convinience slot which calls setIndexingSpeed + * A convenience slot which calls setIndexingSpeed * with either FullSpeed or ReducedSpeed, based on the * value of \p reduced. */ @@ -162,38 +159,30 @@ namespace Nepomuk { */ void analyzeFile( const QString& path ); - /** - * Analyze a resource that is not read from the local harddisk. - * - * \param uri The resource URI to identify the resource. - * \param modificationTime The modification date of the resource. Used to determine if - * an actual update is necessary. - * \data The data to analyze, ie. the contents of the resource. - */ - void analyzeResource( const QUrl& uri, const QDateTime& modificationTime, QDataStream& data ); - Q_SIGNALS: void indexingStarted(); void indexingStopped(); + /// a combination of the two signals above void indexingStateChanged( bool indexing ); void indexingFolder( const QString& ); + void indexingFile( const QString & ); void indexingSuspended( bool suspended ); private Q_SLOTS: void slotConfigChanged(); + void slotCleaningDone(); + void slotIndexingDone( KJob* job ); + void doIndexing(); private: - void run(); - - bool waitForContinue( bool disableDelay = false ); - /** - * It first indexes \p dir. Then it indexes all the files in \p dir, and - * finally recursivly analyzes all the subfolders in \p dir IF \p flags - * contains the 'UpdateRecursive' flag. It even sets m_currentFolder + * It first indexes \p dir. Then it checks all the files in \p dir + * against the configuration and the data in Nepomuk to fill + * m_filesToUpdate. No actual indexing is done besides \p dir + * itself. */ - bool analyzeDir( const QString& dir, UpdateDirFlags flags ); + void analyzeDir( const QString& dir, UpdateDirFlags flags ); void queueAllFoldersForUpdate( bool forceUpdate = false ); @@ -208,20 +197,16 @@ namespace Nepomuk { void setIndexingStarted( bool started ); /** - * Removes all previously indexed entries that are not in the list of folders - * to index anymore. + * Continue indexing async after waiting for the configured delay. + * \param noDelay If true indexing will be started immediately without any delay. */ - void removeOldAndUnwantedEntries(); - bool removeAllGraphsFromQuery( const QString& query_ ); + void callDoIndexing( bool noDelay = false ); bool m_suspended; - bool m_stopped; bool m_indexing; - QMutex m_resumeStopMutex; - QWaitCondition m_resumeStopWc; - - Indexer* m_indexer; + mutable QMutex m_suspendMutex; + mutable QMutex m_indexingMutex; // A specialized queue that gives priority to dirs that do not use the AutoUpdateFolder flag. class UpdateDirQueue : public QQueue<QPair<QString, UpdateDirFlags> > @@ -234,13 +219,17 @@ namespace Nepomuk { // queue of folders to update (+flags defined in the source file) - changed by updateDir UpdateDirQueue m_dirsToUpdate; + // queue of files to update. This is filled from the dirs queue and manual methods like analyzeFile + QQueue<QFileInfo> m_filesToUpdate; + QMutex m_dirsToUpdateMutex; - QWaitCondition m_dirsToUpdateWc; + QMutex m_filesToUpdateMutex; - QString m_currentFolder; + mutable QMutex m_currentMutex; KUrl m_currentUrl; - IndexingSpeed m_speed; + int m_indexingDelay; + IndexCleaner* m_cleaner; }; QDebug operator<<( QDebug dbg, IndexScheduler::IndexingSpeed speed ); @@ -249,3 +238,4 @@ namespace Nepomuk { Q_DECLARE_OPERATORS_FOR_FLAGS(Nepomuk::IndexScheduler::UpdateDirFlags) #endif + diff --git a/nepomuk/services/strigi/nepomukindexer.cpp b/nepomuk/services/strigi/nepomukindexer.cpp index 0681a5e..d796983 100644 --- a/nepomuk/services/strigi/nepomukindexer.cpp +++ b/nepomuk/services/strigi/nepomukindexer.cpp @@ -1,6 +1,6 @@ /* This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2010-2011 Sebastian Trueg <trueg@kde.org> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -20,211 +20,51 @@ */ #include "nepomukindexer.h" -#include "nepomukindexwriter.h" -#include "nepomukindexfeeder.h" +#include "util.h" -#include <Nepomuk/ResourceManager> #include <Nepomuk/Resource> -#include <Nepomuk/Variant> -#include <Nepomuk/Vocabulary/NIE> #include <KUrl> #include <KDebug> +#include <KProcess> +#include <KStandardDirs> -#include <QtCore/QDataStream> -#include <QtCore/QDateTime> -#include <QtCore/QFile> #include <QtCore/QFileInfo> -#include <strigi/strigiconfig.h> -#include <strigi/indexwriter.h> -#include <strigi/analysisresult.h> -#include <strigi/fileinputstream.h> -#include <strigi/analyzerconfiguration.h> - - -namespace { - class StoppableConfiguration : public Strigi::AnalyzerConfiguration - { - public: - StoppableConfiguration() - : m_stop(false) { -#if defined(STRIGI_IS_VERSION) -#if STRIGI_IS_VERSION( 0, 6, 1 ) - setIndexArchiveContents( false ); -#endif -#endif - } - - bool indexMore() const { - return !m_stop; - } - - bool addMoreText() const { - return !m_stop; - } - - void setStop( bool s ) { - m_stop = s; - } - - private: - bool m_stop; - }; -} - - -class Nepomuk::Indexer::Private -{ -public: - StoppableConfiguration m_analyzerConfig; - IndexFeeder* m_indexFeeder; - StrigiIndexWriter* m_indexWriter; - Strigi::StreamAnalyzer* m_streamAnalyzer; -}; - -Nepomuk::Indexer::Indexer( QObject* parent ) - : QObject( parent ), - d( new Private() ) +Nepomuk::Indexer::Indexer(const KUrl& localUrl, QObject* parent) + : KJob(parent), + m_url( localUrl ), + m_exitCode( -1 ) { - d->m_indexFeeder = new IndexFeeder( this ); - d->m_indexWriter = new StrigiIndexWriter( d->m_indexFeeder ); - d->m_streamAnalyzer = new Strigi::StreamAnalyzer( d->m_analyzerConfig ); - d->m_streamAnalyzer->setIndexWriter( *d->m_indexWriter ); } - -Nepomuk::Indexer::~Indexer() +Nepomuk::Indexer::Indexer(const QFileInfo& info, QObject* parent) + : KJob(parent), + m_url( info.absoluteFilePath() ), + m_exitCode( -1 ) { - delete d->m_streamAnalyzer; - delete d->m_indexWriter; - delete d->m_indexFeeder; - delete d; -} - - -void Nepomuk::Indexer::indexFile( const KUrl& url ) -{ - indexFile( QFileInfo( url.toLocalFile() ) ); -} - - -void Nepomuk::Indexer::indexFile( const QFileInfo& info ) -{ - d->m_analyzerConfig.setStop( false ); - - KUrl url( info.filePath() ); - - // strigi asserts if the file path has a trailing slash - QString filePath = url.toLocalFile( KUrl::RemoveTrailingSlash ); - QString dir = url.directory( KUrl::IgnoreTrailingSlash ); - - Strigi::AnalysisResult analysisresult( QFile::encodeName( filePath ).data(), - info.lastModified().toTime_t(), - *d->m_indexWriter, - *d->m_streamAnalyzer, - QFile::encodeName( dir ).data() ); - if ( info.isFile() && !info.isSymLink() ) { -#ifdef STRIGI_HAS_FILEINPUTSTREAM_OPEN - Strigi::InputStream* stream = Strigi::FileInputStream::open( QFile::encodeName( info.filePath() ) ); - analysisresult.index( stream ); - delete stream; -#else - Strigi::FileInputStream stream( QFile::encodeName( info.filePath() ) ); - analysisresult.index( &stream ); -#endif - } - else { - analysisresult.index(0); - } -} - - -namespace { - class QDataStreamStrigiBufferedStream : public Strigi::BufferedStream<char> - { - public: - QDataStreamStrigiBufferedStream( QDataStream& stream ) - : m_stream( stream ) { - } - - int32_t fillBuffer( char* start, int32_t space ) { - int r = m_stream.readRawData( start, space ); - if ( r == 0 ) { - // Strigi's API is so weird! - return -1; - } - else if ( r < 0 ) { - // Again: weird API. m_status is a protected member of StreamBaseBase (yes, 2x Base) - m_status = Strigi::Error; - return -1; - } - else { - return r; - } - } - - private: - QDataStream& m_stream; - }; } - -void Nepomuk::Indexer::indexResource( const KUrl& uri, const QDateTime& modificationTime, QDataStream& data ) -{ - d->m_analyzerConfig.setStop( false ); - - Resource dirRes( uri ); - if ( !dirRes.exists() || - dirRes.property( Nepomuk::Vocabulary::NIE::lastModified() ).toDateTime() != modificationTime ) { - Strigi::AnalysisResult analysisresult( uri.toEncoded().data(), - modificationTime.toTime_t(), - *d->m_indexWriter, - *d->m_streamAnalyzer ); - QDataStreamStrigiBufferedStream stream( data ); - analysisresult.index( &stream ); - } - else { - kDebug() << uri << "up to date"; - } -} - - -void Nepomuk::Indexer::clearIndexedData( const KUrl& url ) -{ - Nepomuk::IndexFeeder::clearIndexedDataForUrl( url ); -} - - -void Nepomuk::Indexer::clearIndexedData( const QFileInfo& info ) +void Nepomuk::Indexer::start() { - Nepomuk::IndexFeeder::clearIndexedDataForUrl( KUrl(info.filePath()) ); -} + const QString exe = KStandardDirs::findExe(QLatin1String("nepomukindexer")); + m_process = new KProcess( this ); + m_process->setProgram( exe, QStringList() << m_url.toLocalFile() ); + kDebug() << "Running" << exe << m_url.toLocalFile(); -void Nepomuk::Indexer::clearIndexedData( const Nepomuk::Resource& res ) -{ - Nepomuk::IndexFeeder::clearIndexedDataForResourceUri( res.resourceUri() ); + connect( m_process, SIGNAL(finished(int)), this, SLOT(slotIndexedFile(int)) ); + m_process->start(); } -void Nepomuk::Indexer::stop() +void Nepomuk::Indexer::slotIndexedFile(int exitCode) { - kDebug(); - d->m_analyzerConfig.setStop( true ); - d->m_indexFeeder->stop(); - d->m_indexFeeder->wait(); - kDebug() << "done"; -} - + kDebug() << "Indexing of " << m_url.toLocalFile() << "finished with exit code" << exitCode; + m_exitCode = exitCode; -void Nepomuk::Indexer::start() -{ - kDebug(); - d->m_analyzerConfig.setStop( false ); - d->m_indexFeeder->start(); - kDebug() << "Started"; + emitResult(); } #include "nepomukindexer.moc" diff --git a/nepomuk/services/strigi/nepomukindexer.h b/nepomuk/services/strigi/nepomukindexer.h index 58fbd8b..08abb2a 100644 --- a/nepomuk/services/strigi/nepomukindexer.h +++ b/nepomuk/services/strigi/nepomukindexer.h @@ -22,11 +22,10 @@ #ifndef _NEPOMUK_INDEXER_H_ #define _NEPOMUK_INDEXER_H_ -#include <QtCore/QObject> +#include <KJob> +#include <KUrl> -class KUrl; -class QDateTime; -class QDataStream; +class KProcess; class QFileInfo; namespace Nepomuk { @@ -48,71 +47,16 @@ namespace Nepomuk { * * \author Sebastian Trueg <trueg@kde.org> */ - class Indexer : public QObject + class Indexer : public KJob { Q_OBJECT public: - /** - * Create a new indexer. - */ - Indexer( QObject* parent = 0 ); - - /** - * Destructor - */ - ~Indexer(); + Indexer( const QFileInfo& info, QObject* parent = 0 ); + Indexer( const KUrl& localUrl, QObject* parent = 0 ); - /** - * Index a single local file or folder (files in a folder will - * not be indexed recursively). - */ - void indexFile( const KUrl& localUrl ); + virtual void start(); - /** - * Index a single local file or folder (files in a folder will - * not be indexed recursively). This method does the exact same - * as the above except that it saves an addditional stat of the - * file. - */ - void indexFile( const QFileInfo& info ); - - /** - * Index a random resource. - * - * \param url The URL of the resource. - * \param modificationTime the last modification date of the resource. - * \param data The data to be analyzed - */ - void indexResource( const KUrl& url, const QDateTime& modificationTime, QDataStream& data ); - - /** - * Remove all indexed data for the file/resource identified by \p res. - */ - void clearIndexedData( const Nepomuk::Resource& res ); - - /** - * \overload - * - * Remove all indexed data for the file at local url \p url. - */ - void clearIndexedData( const KUrl& url ); - - /** - * \overload - * - * Remove all indexed data for the file identifies by \p info. - */ - void clearIndexedData( const QFileInfo& info ); - - /** - * This method stops the indexer, i.e. cancels potentially - * long indexing operations and any queued data is thrown away. - */ - void stop(); - - void start(); - Q_SIGNALS: /** * Emitted once the indexing for a file or resource has finished. @@ -126,9 +70,13 @@ namespace Nepomuk { // TODO: better only have one method for success and failure. // TODO: actually emit the indexingDone signal + private slots: + void slotIndexedFile(int exitCode); + private: - class Private; - Private* const d; + KUrl m_url; + KProcess* m_process; + int m_exitCode; }; } diff --git a/nepomuk/services/strigi/nepomukindexfeeder.cpp b/nepomuk/services/strigi/nepomukindexfeeder.cpp deleted file mode 100644 index a5fb626..0000000 --- a/nepomuk/services/strigi/nepomukindexfeeder.cpp +++ /dev/null @@ -1,331 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - - -#include "nepomukindexfeeder.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 <Soprano/Vocabulary/NRL> - -#include <Nepomuk/Vocabulary/NIE> -#include <Nepomuk/ResourceManager> -#include <Nepomuk/Resource> - -#include <KDebug> - - -Nepomuk::IndexFeeder::IndexFeeder( QObject* parent ) - : QThread( parent ) -{ - m_stopped = false; - start(); -} - - -Nepomuk::IndexFeeder::~IndexFeeder() -{ - stop(); - wait(); -} - - -void Nepomuk::IndexFeeder::begin( const QUrl & url ) -{ - //kDebug() << "BEGINING"; - Request req; - req.uri = url; - - m_stack.push( req ); -} - - -void Nepomuk::IndexFeeder::addStatement(const Soprano::Statement& st) -{ - Q_ASSERT( !m_stack.isEmpty() ); - Request & req = m_stack.top(); - - const Soprano::Node & n = st.subject(); - - QUrl uriOrId; - if( n.isResource() ) - uriOrId = n.uri(); - else if( n.isBlank() ) - uriOrId = n.identifier(); - - if( !req.hash.contains( uriOrId ) ) { - ResourceStruct rs; - if( n.isResource() ) - rs.uri = n.uri(); - - req.hash.insert( uriOrId, rs ); - } - - ResourceStruct & rs = req.hash[ uriOrId ]; - rs.propHash.insert( st.predicate().uri(), st.object() ); -} - - -void Nepomuk::IndexFeeder::addStatement(const Soprano::Node& subject, const Soprano::Node& predicate, const Soprano::Node& object) -{ - addStatement( Soprano::Statement( subject, predicate, object, Soprano::Node() ) ); -} - - -void Nepomuk::IndexFeeder::end( bool forceCommit ) -{ - if( m_stack.isEmpty() ) - return; - //kDebug() << "ENDING"; - - Request req = m_stack.pop(); - - if ( forceCommit ) { - handleRequest( req ); - } - else { - QMutexLocker lock( &m_queueMutex ); - m_updateQueue.enqueue( req ); - m_queueWaiter.wakeAll(); - } -} - - -void Nepomuk::IndexFeeder::stop() -{ - QMutexLocker lock( &m_queueMutex ); - m_stopped = true; - m_queueWaiter.wakeAll(); -} - - -QString Nepomuk::IndexFeeder::buildResourceQuery(const Nepomuk::IndexFeeder::ResourceStruct& rs) const -{ - 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; -} - - -void Nepomuk::IndexFeeder::addToModel(const Nepomuk::IndexFeeder::ResourceStruct& rs) const -{ - QUrl context = generateGraph( rs.uri ); - 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; - ResourceManager::instance()->mainModel()->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::IndexFeeder::run() -{ - m_stopped = false; - while( !m_stopped ) { - - // lock for initial iteration - m_queueMutex.lock(); - - // work the queue - while( !m_updateQueue.isEmpty() ) { - Request request = m_updateQueue.dequeue(); - - // unlock after queue utilization - m_queueMutex.unlock(); - - handleRequest( request ); - - // lock for next iteration - m_queueMutex.lock(); - } - - // FIXME: this is not very secure. In theory m_stopped could be changed - // just after the if but before the m_queueWaiter has been entered. - // Then we would just hang there forever! - if ( !m_stopped ) { - // wait for more input - kDebug() << "Waiting..."; - m_queueWaiter.wait( &m_queueMutex ); - m_queueMutex.unlock(); - kDebug() << "Woke up."; - } - - } -} - - -void Nepomuk::IndexFeeder::handleRequest( Request& request ) const -{ - // Search for the resources or create them - //kDebug() << " Searching for duplicates or creating them ... "; - QMutableHashIterator<QUrl, ResourceStruct> it( request.hash ); - while( it.hasNext() ) { - it.next(); - - // If it already exists - ResourceStruct & rs = it.value(); - if( !rs.uri.isEmpty() ) - continue; - - QString query = buildResourceQuery( rs ); - //kDebug() << query; - Soprano::QueryResultIterator it = ResourceManager::instance()->mainModel()->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 - addToModel( rs ); - } - } - - // Fix links for main - ResourceStruct & rs = request.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( !request.hash.contains( id ) ) - continue; - QUrl newUri = request.hash.value( id ).uri; - //kDebug() << id << " ---> " << newUri; - iter.value() = Soprano::Node( newUri ); - } - } - - // Add main file to the repository - addToModel( rs ); -} - - -QUrl Nepomuk::IndexFeeder::generateGraph( const QUrl& resourceUri ) const -{ - Soprano::Model* model = ResourceManager::instance()->mainModel(); - 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" ); - model->addStatement( context, - Soprano::Vocabulary::RDF::type(), - Soprano::Vocabulary::NRL::DiscardableInstanceBase(), - metaDataContext ); - model->addStatement( context, - Soprano::Vocabulary::NAO::created(), - Soprano::LiteralValue( QDateTime::currentDateTime() ), - metaDataContext ); - model->addStatement( context, - Strigi::Ontology::indexGraphFor(), - resourceUri, - metaDataContext ); - model->addStatement( metaDataContext, - Soprano::Vocabulary::RDF::type(), - Soprano::Vocabulary::NRL::GraphMetadata(), - metaDataContext ); - model->addStatement( metaDataContext, - Soprano::Vocabulary::NRL::coreGraphMetadataFor(), - context, - metaDataContext ); - - return context; -} - - -// static -bool Nepomuk::IndexFeeder::clearIndexedDataForUrl( const KUrl& url ) -{ - if ( url.isEmpty() ) - return false; - - QString query = QString::fromLatin1( "select ?g where { " - "{ " - "?r %2 %1 . " - "?g %3 ?r . } " - "UNION " - "{ ?g %3 %1 . }" - "}") - .arg( Soprano::Node::resourceToN3( url ), - Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ), - Soprano::Node::resourceToN3( Strigi::Ontology::indexGraphFor() ) ); - - Soprano::QueryResultIterator result = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( query, Soprano::Query::QueryLanguageSparql ); - while ( result.next() ) { - // delete the indexed data (The Soprano::NRLModel in the storage service will take care of - // the metadata graph) - Nepomuk::ResourceManager::instance()->mainModel()->removeContext( result.binding( "g" ) ); - } - - return true; -} - - -// static -bool Nepomuk::IndexFeeder::clearIndexedDataForResourceUri( const KUrl& res ) -{ - if ( res.isEmpty() ) - return false; - - QString query = QString::fromLatin1( "select ?g where { ?g %1 %2 . }" ) - .arg( Soprano::Node::resourceToN3( Strigi::Ontology::indexGraphFor() ), - Soprano::Node::resourceToN3( res ) ); - - Soprano::QueryResultIterator result = Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( query, Soprano::Query::QueryLanguageSparql ); - while ( result.next() ) { - // delete the indexed data (The Soprano::NRLModel in the storage service will take care of - // the metadata graph) - Nepomuk::ResourceManager::instance()->mainModel()->removeContext( result.binding( "g" ) ); - } - - return true; -} diff --git a/nepomuk/services/strigi/nepomukindexfeeder.h b/nepomuk/services/strigi/nepomukindexfeeder.h deleted file mode 100644 index c4d4ed3..0000000 --- a/nepomuk/services/strigi/nepomukindexfeeder.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - This file is part of the Nepomuk KDE project. - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - Copyright (C) 2010 Sebastian Trueg <trueg@kde.org> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) version 3, or any - later version accepted by the membership of KDE e.V. (or its - successor approved by the membership of KDE e.V.), which shall - act as a proxy defined in Section 6 of version 3 of the license. - - 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library. If not, see <http://www.gnu.org/licenses/>. -*/ - - -#ifndef NEPOMUKINDEXFEEDER_H -#define NEPOMUKINDEXFEEDER_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 Statement; - class Node; -} -class KUrl; - -namespace Nepomuk { - class IndexFeeder : public QThread - { - Q_OBJECT - public: - IndexFeeder( QObject* parent = 0 ); - virtual ~IndexFeeder(); - - void stop(); - - public Q_SLOTS: - /** - * Starts the feeding process for a file with resourceUri \p uri. - * This function should be called before adding any statements. - * The function may be called repeatedly. - * - * Should be preceeded by an end() - * - * \sa end - */ - 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 ); - - /** - * Finishes the feeding process, and starts with the resolution and merging - * of resources based on the statements provided. - * - * addStatement should not be called after this function, unless begin has already - * been called. - * - * \param forceCommit If true the request will be handled syncroneously. This is used - * to commit folder resources to Nepomuk right away since they might be needed later on. - * - * \sa begin - */ - void end( bool forceCommit = false ); - - static bool clearIndexedDataForUrl( const KUrl& url ); - static bool clearIndexedDataForResourceUri( const KUrl& res ); - - private: - - struct ResourceStruct { - QUrl uri; - QMultiHash<QUrl, Soprano::Node> propHash; - }; - - // Maps the uri to the ResourceStuct - typedef QHash<QUrl, ResourceStruct> ResourceHash; - - struct Request { - QUrl uri; - ResourceHash hash; - }; - - /// The thread uses this queue to check if it has any requests that need processing - QQueue<Request> m_updateQueue; - - /** - * The stack is used to store the internal state of the Feeder, a new item is pushed into - * the stack everytime begin() is called, and the top most item is poped and sent into the - * update queue when end() is called. - */ - QStack<Request> m_stack; - - QMutex m_queueMutex; - QWaitCondition m_queueWaiter; - bool m_stopped; - - void run(); - - /** - * Handle a single request, i.e. store all its data to Nepomuk. - */ - void handleRequest( Request& request ) const; - - /// Generates a discardable graph for \p resourceUri - QUrl generateGraph( const QUrl& resourceUri ) const; - - /** - * Creates a sparql query which returns 1 resource which matches all the properties, - * and objects present in the propHash of the ResourceStruct - */ - QString buildResourceQuery( const ResourceStruct & rs ) const; - - /** - * Adds all the statements present in the ResourceStruct to the internal model. - * The contex is created via generateGraph - * - * \sa generateGraph - */ - void addToModel( const ResourceStruct &rs ) const; - }; - -} - -#endif // NEPOMUKINDEXFEEDER_H diff --git a/nepomuk/services/strigi/nepomukindexwriter.cpp b/nepomuk/services/strigi/nepomukindexwriter.cpp deleted file mode 100644 index afccb57..0000000 --- a/nepomuk/services/strigi/nepomukindexwriter.cpp +++ /dev/null @@ -1,665 +0,0 @@ -/* - Copyright (C) 2007-2010 Sebastian Trueg <trueg@kde.org> - Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com> - - 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 "nepomukindexwriter.h" -#include "nepomukindexfeeder.h" -#include "util.h" - -#include <Soprano/Vocabulary/RDF> -#include <Soprano/LiteralValue> -#include <Soprano/Node> -#include <Soprano/QueryResultIterator> - -#include <QtCore/QList> -#include <QtCore/QHash> -#include <QtCore/QVariant> -#include <QtCore/QFileInfo> -#include <QtCore/QFile> -#include <QtCore/QUrl> -#include <QtCore/QDateTime> -#include <QtCore/QByteArray> -#include <QtCore/QStack> - -#include <KUrl> -#include <KDebug> - -#include <sys/stat.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <stdio.h> - -#include <map> -#include <sstream> -#include <algorithm> - -#include <Nepomuk/Types/Property> -#include <Nepomuk/Types/Class> -#include <Nepomuk/Types/Literal> -#include <Nepomuk/ResourceManager> -#include <Nepomuk/Resource> -#include <Nepomuk/Vocabulary/NFO> -#include <Nepomuk/Vocabulary/NIE> - - - -// IMPORTANT: strings in Strigi are apparently UTF8! Except for file names. Those are in local encoding. - -using namespace Soprano; -using namespace Strigi; - - -uint qHash( const std::string& s ) -{ - return qHash( s.c_str() ); -} - -namespace { - QString findArchivePath( const QString& path ) { - QString p( path ); - int i = 0; - while ( ( i = p.lastIndexOf( '/' ) ) > 0 ) { - p.truncate( i ); - if ( QFileInfo( p ).isFile() ) { - return p; - } - } - return QString(); - } - - QUrl createFileUrl( const Strigi::AnalysisResult* idx ) { - // HACK: Strigi includes analysers that recurse into tar or zip archives and index - // the files therein. In KDE these files could perfectly be handled through kio slaves - // such as tar:/ or zip:/ - // Here we try to use KDE-compatible URIs for these indexed files the best we can - // everything else defaults to file:/ - QUrl uri; - QString path = QFile::decodeName( idx->path().c_str() ); - if ( KUrl::isRelativeUrl( path ) ) - uri = QUrl::fromLocalFile( QFileInfo( path ).absoluteFilePath() ); - else - uri = KUrl( path ); // try to support http and other URLs - - if ( idx->depth() > 0 ) { - QString archivePath = findArchivePath( path ); - if ( QFile::exists( archivePath ) ) { - if ( archivePath.endsWith( QLatin1String( ".tar" ) ) || - archivePath.endsWith( QLatin1String( ".tar.gz" ) ) || - archivePath.endsWith( QLatin1String( ".tar.bz2" ) ) || - archivePath.endsWith( QLatin1String( ".tar.lzma" ) ) || - archivePath.endsWith( QLatin1String( ".tar.xz" ) ) ) { - uri.setScheme( "tar" ); - } - else if ( archivePath.endsWith( QLatin1String( ".zip" ) ) ) { - uri.setScheme( "zip" ); - } - } - } - - // fallback for all - if ( uri.scheme().isEmpty() ) { - uri.setScheme( "file" ); - } - - return uri; - } - - - /** - * Creates a Blank or Resource Node based on the contents of the string provided. - * If the string is of the form ':identifier', a Blank node is created. - * Otherwise a Resource Node is returned. - */ - Soprano::Node createBlankOrResourceNode( 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) ); - } - - - /** - * A simple cache for properties that are used in Strigi. - * This avoids matching them again and again. - * - * Also the class provides easy conversion methods for - * values provided by Strigi to values that Nepomuk understands. - */ - class RegisteredFieldData - { - public: - RegisteredFieldData( const Nepomuk::Types::Property& prop, QVariant::Type t ) - : m_property( prop ), - m_dataType( t ), - m_isRdfType( prop == Soprano::Vocabulary::RDF::type() ) { - } - - const Nepomuk::Types::Property& property() const { return m_property; } - bool isRdfType() const { return m_isRdfType; } - - Soprano::Node createObject( const std::string& value ); - - private: - Soprano::LiteralValue createLiteralValue( const std::string& value ); - - /// The actual property URI - Nepomuk::Types::Property m_property; - - /// the literal range of the property (if applicable) - QVariant::Type m_dataType; - - /// caching QUrl comparison - bool m_isRdfType; - }; - - - /** - * Data objects that are used to store information relative to one - * indexing run. - */ - class FileMetaData - { - public: - FileMetaData( const Strigi::AnalysisResult* idx ); - - /// stores basic data including the nie:url and the nrl:GraphMetadata in \p model - void storeBasicData( Nepomuk::IndexFeeder* feeder ); - - /// map a blank node to a resource - QUrl mapNode( const std::string& s ); - - /// The resource URI - QUrl resourceUri; - - /// The file URL (nie:url) - KUrl fileUrl; - - /// The file info - saved to prevent multiple stats - QFileInfo fileInfo; - - /// a buffer for all plain-text content generated by strigi - std::string content; - - private: - /// The Strigi result - const Strigi::AnalysisResult* m_analysisResult; - }; - - - Soprano::Node RegisteredFieldData::createObject( const std::string& value ) - { - // - // Strigi uses anonymeous nodes prefixed with ':'. However, it is possible that literals - // start with a ':'. Thus, we also check the range of the property - // - if ( value[0] == ':' && - m_property.range().isValid() ) { - return createBlankOrResourceNode( value ); - } - - // - // We handle only one special case here: relations to other files - // - else if( m_property.range().isValid() && - QFile::exists(QFile::decodeName(value.c_str())) ) { - Nepomuk::Resource fileRes(KUrl::fromLocalFile(QFile::decodeName(value.c_str()))); - if( fileRes.exists() ) { - return Soprano::Node( fileRes.resourceUri() ); - } - else { - kDebug() << "Cannot resolve local file path" << value.c_str() << "to a file resource!"; - return Soprano::Node(); - } - } - - // - // fallback to literals - // - else { - return createLiteralValue( value ); - } - } - - - Soprano::LiteralValue RegisteredFieldData::createLiteralValue( const std::string& value ) - { - QString s = QString::fromUtf8( ( const char* )value.c_str(), value.length() ).trimmed(); - if( s.isEmpty() ) - return Soprano::LiteralValue(); - - // This is a workaround for a Strigi bug which sometimes stores datatime values as strings - // but the format is not supported by Soprano::LiteralValue - if ( m_dataType == QVariant::DateTime ) { - // dateTime is stored as integer (time_t) in strigi - bool ok = false; - uint t = s.toUInt( &ok ); - if ( ok ) { - // workaround for id3 tags which might only have a year encoded - if ( t >= 1900 && t <= 9999 ) - return LiteralValue( QDateTime( QDate(t, 1, 1), QTime(0, 0), Qt::UTC ) ); - else - return LiteralValue( QDateTime::fromTime_t( t ) ); - } - - // workaround for at least nie:contentCreated which is encoded like this: "2005:06:03 17:13:33" - QDateTime dt = QDateTime::fromString( s, QLatin1String( "yyyy:MM:dd hh:mm:ss" ) ); - if ( dt.isValid() ) - return LiteralValue( dt ); - } - - // this is a workaround for EXIF values stored as "1/10" and the like which need to - // be converted to double values. - else if ( m_dataType == QVariant::Double ) { - bool ok = false; - double d = s.toDouble( &ok ); - if ( ok ) - return LiteralValue( d ); - - int x = 0; - int y = 0; - if ( sscanf( s.toLatin1().data(), "%d/%d", &x, &y ) == 2 ) { - return LiteralValue( double( x )/double( y ) ); - } - } - - if ( m_dataType != QVariant::Invalid ) { - return LiteralValue::fromString( s, m_dataType ); - } - else { - // we default to string - return LiteralValue( s ); - } - } - - - FileMetaData::FileMetaData( const Strigi::AnalysisResult* idx ) - : m_analysisResult( idx ) - { - fileUrl = createFileUrl( idx ); - fileInfo = QFileInfo( fileUrl.toLocalFile() ); - - // determine the resource URI by using Nepomuk::Resource's power - // this will automatically find previous uses of the file in question - // with backwards compatibility - resourceUri = Nepomuk::Resource( fileUrl ).resourceUri(); - } - - void FileMetaData::storeBasicData( Nepomuk::IndexFeeder * feeder ) - { - feeder->addStatement( resourceUri, Nepomuk::Vocabulary::NIE::url(), fileUrl ); - - if ( fileInfo.exists() ) { - // 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. - feeder->addStatement( resourceUri, - Vocabulary::RDF::type(), - Nepomuk::Vocabulary::NFO::FileDataObject() ); - if ( fileInfo.isDir() ) { - feeder->addStatement( resourceUri, - Vocabulary::RDF::type(), - Nepomuk::Vocabulary::NFO::Folder() ); - } - } - } - - FileMetaData* fileDataForResult( const Strigi::AnalysisResult* idx ) - { - return static_cast<FileMetaData*>( idx->writerData() ); - } -} - - -class Nepomuk::StrigiIndexWriter::Private -{ -public: - // - // The Strigi API does not provide context information in addTriplet, i.e. the AnalysisResult. - // However, we only use one thread, only one AnalysisResult at the time. - // Thus, we can just remember that and use it in addTriplet. - // - QMutex resultStackMutex; - QStack<const Strigi::AnalysisResult*> currentResultStack; - - Nepomuk::IndexFeeder* feeder; -}; - - -Nepomuk::StrigiIndexWriter::StrigiIndexWriter( IndexFeeder* feeder ) - : Strigi::IndexWriter(), - d( new Private() ) -{ - d->feeder = feeder; -} - - -Nepomuk::StrigiIndexWriter::~StrigiIndexWriter() -{ - kDebug(); - delete d; -} - - -// unused -void Nepomuk::StrigiIndexWriter::commit() -{ - // do nothing -} - - -// delete all indexed data for the files listed in entries -void Nepomuk::StrigiIndexWriter::deleteEntries( const std::vector<std::string>& entries ) -{ - for ( unsigned int i = 0; i < entries.size(); ++i ) { - QString path = QString::fromUtf8( entries[i].c_str() ); - IndexFeeder::clearIndexedDataForUrl( KUrl( path ) ); - } -} - - -// unused -void Nepomuk::StrigiIndexWriter::deleteAllEntries() -{ - // do nothing -} - - -// called for each indexed file -void Nepomuk::StrigiIndexWriter::startAnalysis( const AnalysisResult* idx ) -{ - // we need to remember the AnalysisResult since addTriplet does not provide it - d->currentResultStack.push(idx); - - // for now we ignore embedded files -> too many false positives and useless query results - if ( idx->depth() > 0 ) { - return; - } - - // create the file data used during the analysis - FileMetaData* data = new FileMetaData( idx ); - - // remove previously indexed data - IndexFeeder::clearIndexedDataForResourceUri( data->resourceUri ); - - // It is important to keep the resource URI between updates (especially for sharing of files) - // However, when updating data from pre-KDE 4.4 times we want to get rid of old file:/ resource - // URIs. However, we can only do that if we were the only ones to write info about that file - // Thus, we need to use Nepomuk::Resource again - if ( data->resourceUri.scheme() == QLatin1String( "file" ) ) { - // we need to clear the ResourceManager cache. Otherwise the bug/shortcoming in libnepomuk will - // always get us back to the cached file:/ URI - Nepomuk::ResourceManager::instance()->clearCache(); - data->resourceUri = Nepomuk::Resource( data->fileUrl ).resourceUri(); - } - - // create a new resource URI for non-existing file resources - if ( data->resourceUri.isEmpty() ) - data->resourceUri = Nepomuk::ResourceManager::instance()->generateUniqueUri( QString() ); - - // Initialize the feeder to accept statements - d->feeder->begin( data->resourceUri ); - - // store initial data to make sure newly created URIs are reused directly by libnepomuk - data->storeBasicData( d->feeder ); - - // remember the file data - idx->setWriterData( data ); -} - - -void Nepomuk::StrigiIndexWriter::addText( const AnalysisResult* idx, const char* text, int32_t length ) -{ - if ( idx->depth() > 0 ) { - return; - } - - FileMetaData* md = fileDataForResult( idx ); - md->content.append( text, length ); -} - - -void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult* idx, - const RegisteredField* field, - const std::string& value ) -{ - if ( idx->depth() > 0 ) { - return; - } - - if ( value.length() > 0 ) { - FileMetaData* md = fileDataForResult( idx ); - 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().uri(), Soprano::Node() ); - - // - // Strigi uses rdf:type improperly since it stores the value as a string. We have to - // make sure it is a resource. - // only we handle the basic File/Folder typing ourselves - // - if ( rfd->isRdfType() ) { - const QUrl type = QUrl::fromEncoded( value.c_str(), QUrl::StrictMode ); - if ( statement.object().uri() != Nepomuk::Vocabulary::NFO::FileDataObject() ) { - statement.setObject( type ); - } - } - - // - // parent location is a special case as we need the URI of the corresponding resource instead of the path - // - else if ( field->key() == FieldRegister::parentLocationFieldName ) { - QUrl folderUri = determineFolderResourceUri( QUrl::fromLocalFile( QFile::decodeName( QByteArray::fromRawData( value.c_str(), value.length() ) ) ) ); - if ( !folderUri.isEmpty() ) - statement.setObject( folderUri ); - } - - // - // ignore the path as we handle that ourselves in startAnalysis - // - else if ( field->key() != FieldRegister::pathFieldName ) { - statement.setObject( rfd->createObject( value ) ); - } - - if ( !statement.object().isEmpty() ) { - d->feeder->addStatement( statement ); - } - } -} - - -// the main addValue method -void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult* idx, - const RegisteredField* field, - const unsigned char* data, - uint32_t size ) -{ - addValue( idx, field, std::string( ( const char* )data, size ) ); -} - - -void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult*, const RegisteredField*, - const std::string&, const std::string& ) -{ - // we do not support map types -} - - -void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult* idx, - const RegisteredField* field, - uint32_t value ) -{ - if ( idx->depth() > 0 ) { - return; - } - - FileMetaData* md = fileDataForResult( idx ); - RegisteredFieldData* rfd = reinterpret_cast<RegisteredFieldData*>( field->writerData() ); - - LiteralValue val( value ); - if ( field->type() == FieldRegister::datetimeType ) { - val = QDateTime::fromTime_t( value ); - } - - d->feeder->addStatement( md->resourceUri, rfd->property().uri(), val); -} - - -void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult* idx, - const RegisteredField* field, - int32_t value ) -{ - if ( idx->depth() > 0 ) { - return; - } - - FileMetaData* md = fileDataForResult( idx ); - RegisteredFieldData* rfd = reinterpret_cast<RegisteredFieldData*>( field->writerData() ); - - d->feeder->addStatement( md->resourceUri, rfd->property().uri(), LiteralValue( value ) ); -} - - -void Nepomuk::StrigiIndexWriter::addValue( const AnalysisResult* idx, - const RegisteredField* field, - double value ) -{ - if ( idx->depth() > 0 ) { - return; - } - - FileMetaData* md = fileDataForResult( idx ); - RegisteredFieldData* rfd = reinterpret_cast<RegisteredFieldData*>( field->writerData() ); - - d->feeder->addStatement( md->resourceUri, rfd->property().uri(), LiteralValue( value ) ); -} - - -void Nepomuk::StrigiIndexWriter::addTriplet( const std::string& s, - const std::string& p, - const std::string& o ) -{ - if ( d->currentResultStack.top()->depth() > 0 ) { - return; - } - //FileMetaData* md = fileDataForResult( d->currentResultStack.top() ); - - Soprano::Node subject( createBlankOrResourceNode( s ) ); - Nepomuk::Types::Property property( QUrl( QString::fromUtf8(p.c_str()) ) ); // Was mapped earlier - Soprano::Node object; - if ( property.range().isValid() ) - object = Soprano::Node( createBlankOrResourceNode( o ) ); - else - object = Soprano::LiteralValue::fromString( QString::fromUtf8( o.c_str() ), property.literalRangeType().dataTypeUri() ); - - if( object.isValid() ) - d->feeder->addStatement( subject, property.uri(), object ); - else { - kDebug() << QString::fromUtf8( o.c_str() ) << " could not be parsed as a " << property.literalRangeType().dataTypeUri(); - } - -} - - -// called after each indexed file -void Nepomuk::StrigiIndexWriter::finishAnalysis( const AnalysisResult* idx ) -{ - d->currentResultStack.pop(); - - if ( idx->depth() > 0 ) { - return; - } - - FileMetaData* md = fileDataForResult( idx ); - - // store the full text of the file - if ( md->content.length() > 0 ) { - d->feeder->addStatement( md->resourceUri, - Nepomuk::Vocabulary::NIE::plainTextContent(), - LiteralValue( QString::fromUtf8( md->content.c_str() ) ) ); - } - - d->feeder->end( md->fileInfo.isDir() ); - - // cleanup - delete md; - idx->setWriterData( 0 ); -} - - -void Nepomuk::StrigiIndexWriter::initWriterData( const Strigi::FieldRegister& f ) -{ - // build a temp hash for built-in strigi types - QHash<std::string, QVariant::Type> literalTypes; - 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 ) - - // cache type conversion for all strigi fields - std::map<std::string, RegisteredField*>::const_iterator i; - std::map<std::string, RegisteredField*>::const_iterator end = f.fields().end(); - for (i = f.fields().begin(); i != end; ++i) { - Nepomuk::Types::Property prop = Strigi::Util::fieldUri( i->second->key() ); - - QVariant::Type type( QVariant::Invalid ); - - QHash<std::string, QVariant::Type>::const_iterator it = literalTypes.constFind( i->second->properties().typeUri() ); - if ( it != literalTypes.constEnd() ) { - type = *it; - } - else if ( prop.literalRangeType().isValid() ) { - type = LiteralValue::typeFromDataTypeUri( prop.literalRangeType().dataTypeUri() ); - } - - //kDebug() << prop << type; - - i->second->setWriterData( new RegisteredFieldData( prop, type ) ); - } -} - - -void Nepomuk::StrigiIndexWriter::releaseWriterData( const Strigi::FieldRegister& f ) -{ - std::map<std::string, RegisteredField*>::const_iterator i; - std::map<std::string, RegisteredField*>::const_iterator end = f.fields().end(); - for (i = f.fields().begin(); i != end; ++i) { - delete static_cast<RegisteredFieldData*>( i->second->writerData() ); - i->second->setWriterData( 0 ); - } -} - - -QUrl Nepomuk::StrigiIndexWriter::determineFolderResourceUri( const KUrl& fileUrl ) -{ - Nepomuk::Resource res( fileUrl ); - if ( res.exists() ) { - return res.resourceUri(); - } - else { - kDebug() << "Could not find resource URI for folder (this is not an error)" << fileUrl; - return QUrl(); - } -} diff --git a/nepomuk/services/strigi/nepomukindexwriter.h b/nepomuk/services/strigi/nepomukindexwriter.h deleted file mode 100644 index cdda8c9..0000000 --- a/nepomuk/services/strigi/nepomukindexwriter.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - Copyright (C) 2007-2010 Sebastian Trueg <trueg@kde.org> - - 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 _STRIGI_NEPOMUK_INDEX_WRITER_H_ -#define _STRIGI_NEPOMUK_INDEX_WRITER_H_ - -#include <strigi/indexwriter.h> -#include <strigi/analysisresult.h> -#include <strigi/analyzerconfiguration.h> - -class KUrl; -class QUrl; - -namespace Nepomuk { - - class Resource; - class IndexFeeder; - - class StrigiIndexWriter : public Strigi::IndexWriter - { - public: - StrigiIndexWriter( IndexFeeder* ); - ~StrigiIndexWriter(); - - void commit(); - - /** - * Delete the entries with the given paths from the index. - * - * @param entries the paths of the files that should be deleted - **/ - void deleteEntries( const std::vector<std::string>& entries ); - - /** - * Delete all indexed documents from the index. - **/ - void deleteAllEntries(); - - void initWriterData( const Strigi::FieldRegister& ); - void releaseWriterData( const Strigi::FieldRegister& ); - - void startAnalysis( const Strigi::AnalysisResult* ); - void addText( const Strigi::AnalysisResult*, const char* text, int32_t length ); - void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, - const std::string& value ); - void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, - const unsigned char* data, uint32_t size ); - void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, - int32_t value ); - void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, - uint32_t value ); - void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, - double value ); - void addTriplet( const std::string& subject, - const std::string& predicate, const std::string& object ); - void addValue( const Strigi::AnalysisResult*, const Strigi::RegisteredField* field, - const std::string& name, const std::string& value ); - void finishAnalysis( const Strigi::AnalysisResult* ); - - private: - QUrl determineFolderResourceUri( const KUrl& fileUrl ); - - class Private; - Private* d; - }; -} - -uint qHash( const std::string& s ); - -#endif diff --git a/nepomuk/services/strigi/nepomukstrigiservice.desktop b/nepomuk/services/strigi/nepomukstrigiservice.desktop index 34cd944..961e787 100644 --- a/nepomuk/services/strigi/nepomukstrigiservice.desktop +++ b/nepomuk/services/strigi/nepomukstrigiservice.desktop @@ -12,6 +12,7 @@ Name[be@latin]=Słužba „Nepomuk”/„Strigi” Name[bg]=Услуга Nepomuk Strigi Name[bn]=নেপোমুক স্ট্রিগি সার্ভিস Name[bn_IN]=Nepomuk Strigi পরিসেবা +Name[bs]=Nepomukov servis Strigija Name[ca]=Servei Strigi del Nepomuk Name[ca@valencia]=Servei Strigi del Nepomuk Name[cs]=Nepomuk Strigi služba @@ -75,6 +76,7 @@ Name[te]=Nepomuk Strigi సేవ Name[tg]=Хидматҳои Nepomuk Strigi Name[th]=บริการ Nepomuk Strigi Name[tr]=Nepomuk Strigi Servisi +Name[ug]=Nepomuk Strigi مۇلازىمىتى Name[uk]=Служба Strigi Nepomuk Name[uz]=Nepomuk Strigi xizmati Name[uz@cyrillic]=Nepomuk Strigi хизмати @@ -87,6 +89,7 @@ Comment[ar]=خدمة نبومك المتحكمة بمراقب ستريجي ال Comment[ast]=Serviciu Nepomuk que remana'l strigidaemon, ex. Indexa ficheros del escritoriu Comment[be@latin]=Słužba „Nepomuk”, jakaja kiruje prahramaj „strigidaemon”, indeksujučy fajły ŭ kamputary Comment[bg]=Услуга, която контролира демона на Strigi, напр. файлове с индекси +Comment[bs]=Servis Nepomuka za upravljanje demonom Strigija, tj. indeksiranje datoteka na površi Comment[ca]=Servei del Nepomuk que controla el strigidaemon, p.ex. indexa els fitxers de l'escriptori Comment[ca@valencia]=Servei del Nepomuk que controla el strigidaemon, p.ex. indexa els fitxers de l'escriptori Comment[cs]=Služba Nepomuku ovládající strigidaemon, který indexuje data v počítači @@ -146,6 +149,7 @@ Comment[ta]=Nepomuk Service which controls the strigidaemon, i.e. indexes files Comment[te]=Nepomuk సేవ strigidaemon ను నియత్రిస్తుంది, అంటే. డెస్క్టాప్ పైన దస్త్రములను క్రమపరుస్తుంది Comment[th]=บริการของ Nepomuk สำหรับสำหรับควบคุมดีมอน strigidaemon ที่ทำหน้าที่ เช่น สร้างดัชนีแฟ้มต่าง ๆ บนพื้นที่ทำงาน เป็นต้น Comment[tr]=Strigidaemon uygulamasını yöneten Nepomuk servisi, masaüstünüzdeki dosyaları indeksleyen bir uygulama +Comment[ug]=Nepomuk مۇلازىمىتى strigidaemon تىزگىنلەيدۇ، سىستېمىدىكى ھۆججەتنى ئىندېكىسلايدۇ Comment[uk]=Служба Nepomuk, яка контролює фонову службу strigi, тобто, індексує файли на стільниці Comment[wa]=Siervice Nepomuk ki controle li démon strigi, ki fwait ls indecses des fitchîs sol sicribanne Comment[x-test]=xxNepomuk Service which controls the strigidaemon, i.e. indexes files on the desktopxx diff --git a/nepomuk/services/strigi/nepomukstrigiservice.notifyrc b/nepomuk/services/strigi/nepomukstrigiservice.notifyrc index 5d952d0..baab4e6 100644 --- a/nepomuk/services/strigi/nepomukstrigiservice.notifyrc +++ b/nepomuk/services/strigi/nepomukstrigiservice.notifyrc @@ -5,6 +5,7 @@ Comment[ar]=بحث سطح المكتب Comment[ast]=Guetador d'escritoriu Comment[bg]=Настолно търсене Comment[bn]=ডেস্কটপ সন্ধান +Comment[bs]=Pretraga površi Comment[ca]=Cerca a l'escriptori Comment[ca@valencia]=Cerca a l'escriptori Comment[cs]=Vyhledávací služby @@ -34,7 +35,6 @@ Comment[kn]=ಗಣಕತೆರೆ ಹುಡುಕಾಟ Comment[ko]=데스크톱 검색 Comment[lt]=Darbastalio paieška Comment[lv]=Darbvirsmas meklēšana -Comment[mai]=डेस्कटॉप खोज Comment[ml]=പണിയിട തിരയല് Comment[nb]=Skrivebordssøk Comment[nds]=Schriefdischsöök @@ -57,6 +57,7 @@ Comment[sv]=Skrivbordssökning Comment[tg]=Ҷустуҷӯи мизи корӣ Comment[th]=ค้นหาผ่านพื้นที่ทำงาน Comment[tr]=Masaüstü Araması +Comment[ug]=ئۈستەلئۈستىدە ئىزدەش Comment[uk]=Стільничний пошук Comment[wa]=Cweraedje sol sicribanne Comment[x-test]=xxDesktop Searchxx @@ -70,6 +71,7 @@ Name[ast]=Aniciada la indexación inicial Name[be@latin]=Pačałosia pačatkovaje indeksavańnie. Name[bg]=Започнато е индексиране Name[bn]=প্রাথমিক সূচি তৈরী করা শুরু +Name[bs]=Započeto početno indeksiranje Name[ca]=S'ha engegat la indexació inicial Name[ca@valencia]=S'ha engegat la indexació inicial Name[cs]=Úvodní indexování zahájeno @@ -134,6 +136,7 @@ Name[te]=ప్రాధమిక విషయవర్గీకరణ ప్ర Name[tg]=знак пунктуации, открывающая кавычка Name[th]=เริ่มเตรียมการทำดัชนี Name[tr]=Temel İndeksleme başladı +Name[ug]=ئىندېكىسلاشنى دەسلەپلەشتۈرۈش باشلاندى Name[uk]=Запущено початкове індексування Name[wa]=Li prumî indecsaedje a cmincî Name[x-test]=xxInitial Indexing startedxx @@ -142,6 +145,8 @@ Name[zh_TW]=初始化索引已開始 Comment=Indexing of local files for fast searches has started. Comment[ar]=بدات سراتجي في الفهرسة الأولية للملفات المحلية من أجل البحث السريع Comment[ast]=Anicióse indexación de ficheros llocales pa guetes rápides. +Comment[bg]=Индексиране на локални файлове за по-бързо търсене +Comment[bs]=Započeto je indeksiranje lokalnih datoteka radi brze pretrage. Comment[ca]=S'ha engegat la indexació dels fitxers locals per a les cerques ràpides. Comment[ca@valencia]=S'ha engegat la indexació dels fitxers locals per a les cerques ràpides. Comment[cs]=Bylo zahájeno indexování místních souborů pro rychlé vyhledávání. @@ -158,7 +163,7 @@ Comment[fi]=Paikallisten tiedostojen indeksointi pikahakua varten on käynnistyn Comment[fr]=L'indexation des fichiers locaux pour des recherches rapides a commencé. Comment[fy]=Yndeksearring fan lokale triemmen foar flugge buroblêd-sykopdrachten is úteinsetten. Comment[ga]=Tosaíodh innéacsú de chomhaid logánta a cheadaíonn cuardaigh níos tapúla. -Comment[gl]=Comezouse a indexación dos ficheiros locais para facer procuras rápidas. +Comment[gl]=Comezouse a indexación dos ficheiros locais para facer buscas rápidas. Comment[he]=החל מפתוח של קבצים מקומיים לצורך חיפוש מהיר. Comment[hr]=Započelo je indeksiranje lokalnih datoteka za brzu pretragu. Comment[hu]=A helyi fájlok indexelése a keresések gyorsításához elkezdődött. @@ -192,8 +197,9 @@ Comment[sr@ijekavian]=Започето је индексирање локалн Comment[sr@ijekavianlatin]=Započeto je indeksiranje lokalnih fajlova radi brze pretrage. Comment[sr@latin]=Započeto je indeksiranje lokalnih fajlova radi brze pretrage. Comment[sv]=Indexering av lokala filer för snabb sökning har startat. -Comment[th]=การทำดัชนีของแฟ้มต่าง ๆ ภายในระบบเพื่อการค้นหาที่รวดเร็วได้เริ่มแล้ว +Comment[th]=การทำดัชนีของแฟ้มต่างๆ ภายในระบบเพื่อการค้นหาที่รวดเร็วได้เริ่มแล้ว Comment[tr]=Hızlı arama için yerel dosyaların indekslenme işlemi başlatıldı. +Comment[ug]=يەرلىك ھۆججەتنى تېز ئىزدەش ئۈچۈن ئىندېكسلاش باشلاندى. Comment[uk]=Розпочато індексування локальних файлів для пришвидшення пошуку. Comment[x-test]=xxIndexing of local files for fast searches has started.xx Comment[zh_CN]=本地文件索引已经开始。 @@ -205,8 +211,9 @@ Name=Initial Indexing finished Name[ar]=الفهرسة الأولية انتهت Name[ast]=Finó indexación inicial Name[be@latin]=Skončyłasia pačatkovaje indeksavańnie. -Name[bg]=Индексирането завърши +Name[bg]=Началното индексиране завърши Name[bn]=প্রাথমিক সূচি তৈরী করা সম্পন্ন +Name[bs]=Završeno početno indeksiranje Name[ca]=Ha acabat la indexació inicial Name[ca@valencia]=Ha acabat la indexació inicial Name[cs]=Úvodní indexování dokončeno @@ -271,6 +278,7 @@ Name[te]=ప్రాధమిక విషయవర్గీకరణ పూర Name[tg]=Эҷоди индекс ба итмом расид Name[th]=เตรียมการการทำดัชนีเสร็จแล้ว Name[tr]=Temel İndeksleme tamamlandı +Name[ug]=ئىندېكىسلاشنى دەسلەپلەشتۈرۈش تاماملاندى Name[uk]=Початкове індексування закінчено Name[x-test]=xxInitial Indexing finishedxx Name[zh_CN]=索引初始化完成 @@ -278,6 +286,8 @@ Name[zh_TW]=初始化索引已完成 Comment=The initial indexing of local files for fast desktop searches has completed. Comment[ar]=انتهت سراتجي من الفهرسة الأولية للملفات المحلية من أجل البحث السريع Comment[ast]=Completóse indexación inicial de ficheros llocales pa guetes rápides d'escritoriu. +Comment[bg]=Приключи началното индексиране на локални файлове за по-бързо търсене +Comment[bs]=Završeno je početno indeksiranje lokalnih datoteka radi brze pretrage površi. Comment[ca]=Ha finalitzat la indexació inicial dels fitxers locals per a les cerques ràpides d'escriptori. Comment[ca@valencia]=Ha finalitzat la indexació inicial dels fitxers locals per a les cerques ràpides d'escriptori. Comment[cs]=Úvodní indexování místních souborů pro rychlé hledání bylo dokončeno. @@ -294,7 +304,7 @@ Comment[fi]=Paikallisten tiedostojen alustava indeksointi työpöydän pikahakua Comment[fr]=L'indexation initiale des fichiers locaux pour des recherches rapides est terminée. Comment[fy]=De earste yndeksearring fan lokale triemmen foar flugge buroblêd-sykopdrachten is foltôge. Comment[ga]=Tá an chéad innéacsú de chomhaid logánta críochnaithe. -Comment[gl]=Completouse a indexación inicial dos ficheiros locais para facer procuras rápidas no escritorio. +Comment[gl]=Completouse a indexación inicial dos ficheiros locais para facer buscas rápidas no escritorio. Comment[he]=הסתיים המפתוח הראשוני של קבצים מקומיים לצורך חיפוש מהיר בשולחן העבודה. Comment[hr]=Završilo je prvotno indeksiranje lokalnih datoteka za brzu pretragu računala. Comment[hu]=A helyi fájlok kezdeti indexe a gyorsabb asztali kereséshez elkészült. @@ -331,6 +341,7 @@ Comment[sr@latin]=Završeno je početno indeksiranje lokalnih fajlova radi brze Comment[sv]=Den inledande indexeringen av lokala filer för snabb skrivbordssökning är klar. Comment[th]=การทำดัชนีของแฟ้มภายในระบบเพื่อการค้นหาที่รวดเร็วเสร็จสมบูรณ์แล้ว Comment[tr]=Hızlı masaüstü araması için yerel dosyaların temel indeksleme işlemi tamamlandı. +Comment[ug]=يەرلىك ھۆججەتنى ئۈستەلئۈستىدە تېز ئىزدەش ئۈچۈن ئىندېكىسلاشنى دەسلەپلەشتۈرۈش تاماملاندى. Comment[uk]=Початкове індексування локальних файлів для пришвидшення стільничного пошуку завершено. Comment[x-test]=xxThe initial indexing of local files for fast desktop searches has completed.xx Comment[zh_CN]=Strigi 对本地文件的索引初始化工作已完成。 @@ -344,6 +355,7 @@ Name[ast]=Indexación encaboxada Name[be@latin]=Indeksavańnie spynienaje Name[bg]=Индексирането е прекъснато Name[bn]=সূচি তৈরী করা স্থগিত +Name[bs]=Indeksiranje suspendovano Name[ca]=La indexació s'ha suspès Name[ca@valencia]=La indexació s'ha suspès Name[cs]=Indexování pozastaveno @@ -408,6 +420,7 @@ Name[te]=విషయవర్దీకరణ సంస్పెండ్ చే Name[tg]=Indexing suspended Name[th]=การทำดัชนีถูกหยุดพักไว้ชั่วคราว Name[tr]=İndeksleme beklemeye alındı +Name[ug]=ئىندېكسلاش ۋاقتىنچە توختىتىلدى Name[uk]=Пауза індексування Name[wa]=Indecsaedje djoké Name[x-test]=xxIndexing suspendedxx @@ -416,6 +429,8 @@ Name[zh_TW]=索引暫停 Comment=File indexing has been suspended by the search service. Comment[ar]=خدمة البحث علّقت فهرسة الملفات Comment[ast]=El serviciu de gueta suspendió la indexación de ficheros. +Comment[bg]=Индексирането на файлове е прекъснато от услугата за търсене. +Comment[bs]=Servis pretrage je suspendovao indeksiranje datoteka. Comment[ca]=El servei de cerca ha suspès la indexació de fitxers. Comment[ca@valencia]=El servei de cerca ha suspès la indexació de fitxers. Comment[cs]=Indexování souborů vyhledávací službou bylo pozastaveno. @@ -432,7 +447,7 @@ Comment[fi]=Hakupalvelu on keskeyttänyt tiedostoindeksoinnin. Comment[fr]=L'indexation de fichiers a été suspendue par le service de recherche. Comment[fy]=Triem yndeksearring is ûnderbrútsen troch de syk tsjinst Comment[ga]=Chuir an tseirbhís chuardaigh innéacsú comhad ar fionraí. -Comment[gl]=O servizo de procuras suspendeu a indexación de ficheiros. +Comment[gl]=O servizo de buscas suspendeu a indexación de ficheiros. Comment[he]=מפתוח הקבצים הושהה על־ידי שירות־החיפוש. Comment[hr]=Usluga pretrage je pauzirala indeksiranje datoteka. Comment[hu]=A keresőszolgáltatás felfüggesztette a fájlindexelést. @@ -447,7 +462,6 @@ Comment[kn]=ಹುಡುಕು ಸೇವೆಯಿಂದ ಕಡತಗಳ ಸೂ Comment[ko]=검색 서비스에서 파일 인덱싱을 중지하였습니다. Comment[lt]=Paieškos tarnyba sustabdė failų indeksavimą. Comment[lv]=Failu indeksēšanu apturēja meklēšanas serviss. -Comment[mai]=स्ट्रिगी फाइल सूची बनैनाइ निलंबित कएल गेल अछि Comment[mk]=Индексирањето на датотеки е привремено прекинато од сервисот за пребарување. Comment[ml]=തെരച്ചില് സേവനം ഫയല് സ്വാംശീകരണം താല്കാലികമായി നിര്ത്തിയിരിയ്ക്കുന്നു. Comment[nb]=Søketjenesten har stoppet filindeksering midlertidig. @@ -469,6 +483,7 @@ Comment[sr@latin]=Servis pretrage je suspendovao indeksiranje fajlova. Comment[sv]=Filindexering har tillfälligt stoppats av söktjänsten. Comment[th]=การทำดัชนีถูกหยุดพักชั่วคราวจากบริการค้นหา Comment[tr]=Dosya indeksleyici arama servisi tarafından durduruldu. +Comment[ug]=ھۆججەت ئىندېكسلاش ئىزدەش مۇلازىمىتى تەرىپىدىن ۋاقتىنچە توختىتىلدى. Comment[uk]=Індексування файлів було призупинено службою пошуку. Comment[x-test]=xxFile indexing has been suspended by the search service.xx Comment[zh_CN]=Strigi 文件索引操作已经被搜索服务推迟。 @@ -482,6 +497,7 @@ Name[ast]=Indexación reanudada Name[be@latin]=Indeksavańnie praciahnutaje Name[bg]=Индексирането е продължено Name[bn]=সূচি তৈরী করা পুনরায় চালু +Name[bs]=Indeksiranje nastavljeno Name[ca]=La indexació s'ha reprès Name[ca@valencia]=La indexació s'ha représ Name[cs]=Indexování obnoveno @@ -546,6 +562,7 @@ Name[te]=విషయవర్గీకరణ తిరిగికొనసా Name[tg]=Indexing resumed Name[th]=การทำดัชนีกลับมาทำงานต่อแล้ว Name[tr]=İndeksleme çalışmaya devam ettirildi +Name[ug]=ئىندېكسلاش داۋاملاشتۇرۇلدى Name[uk]=Продовження індексування Name[x-test]=xxIndexing resumedxx Name[zh_CN]=索引已恢复 @@ -553,6 +570,8 @@ Name[zh_TW]=索引回復 Comment=File indexing has been resumed by the search service. Comment[ar]=استأنفت علمية الفهرسة Comment[ast]=El serviciu de gueta reanudó la indexación de ficheros. +Comment[bg]=Индексирането на файлове е продължено от услугата за търсене +Comment[bs]=Servis pretrage je nastavio indeksiranje datoteka. Comment[ca]=El servei de cerca ha reprès la indexació de fitxers. Comment[ca@valencia]=El servei de cerca ha représ la indexació de fitxers. Comment[cs]=Indexování souborů vyhledávací službou bylo obnoveno. @@ -569,7 +588,7 @@ Comment[fi]=Hakupalvelu on käynnistänyt tiedostoindeksoinnin uudelleen Comment[fr]=L'indexation des fichiers a été reprise par le service de recherche. Comment[fy]=Triem yndeksearring is ferfette troch de syk tsjinst Comment[ga]=D'atosaigh an tseirbhís chuardaigh innéacsú comhad. -Comment[gl]=O servizo de procuras continuou a indexación de ficheiros. +Comment[gl]=O servizo de buscas continuou a indexación de ficheiros. Comment[he]=מפתוח הקבצים חזר לפעול על־ידי שירות־החיפוש. Comment[hr]=Usluga pretrage je nastavila indeksiranje datoteka. Comment[hu]=A keresőszolgáltatás újraindította a fájlindexelést. @@ -584,7 +603,6 @@ Comment[kn]=ಹುಡುಕು ಸೇವೆಯಿಂದ ಕಡತಗಳ ಸೂ Comment[ko]=검색 서비스에서 파일 인덱싱을 다시 시작하였습니다. Comment[lt]=Paieškos tarnyba atnaujino failų indeksavimą. Comment[lv]=Failu indeksēšanu atsāka meklēšanas serviss. -Comment[mai]=स्ट्रिगी फाइल सूची बनैनाइ पुनरारंभ Comment[mk]=Индексирањето на датотеки е продолжено од сервисот за пребарување. Comment[ml]=തെരച്ചില് സേവനം ഫയല് സ്വാംശീകരണം തുടരുന്നു. Comment[nb]=Søketjenesten har gjenopptatt filindeksering. @@ -606,6 +624,7 @@ Comment[sr@latin]=Servis pretrage je nastavio indeksiranje fajlova. Comment[sv]=Filindexering har återupptagits av söktjänsten. Comment[th]=การทำดัชนีแฟ้มได้ดำเนินการต่อแล้วโดยบริการค้นหา Comment[tr]=Dosya indeksleyici arama servisi tarafından devam ettirildi. +Comment[ug]=ھۆججەت ئىندېكسلاش ئىزدەش مۇلازىمىتى تەرىپىدىن داۋاملاشتۇرۇلدى. Comment[uk]=Індексування файлів було поновлено службою пошуку. Comment[x-test]=xxFile indexing has been resumed by the search service.xx Comment[zh_CN]=Strigi 文件索引操作已经被搜索服务恢复。 diff --git a/nepomuk/services/strigi/strigiservice.cpp b/nepomuk/services/strigi/strigiservice.cpp index 9db5d56..0189679 100644 --- a/nepomuk/services/strigi/strigiservice.cpp +++ b/nepomuk/services/strigi/strigiservice.cpp @@ -38,14 +38,13 @@ Nepomuk::StrigiService::StrigiService( QObject* parent, const QList<QVariant>& ) : Service( parent ) { - // store the little bits of ontology we need in Strigi - // (TODO: this needs to be deprecated in favor of something NIE) - // ============================================================== - Strigi::Util::storeStrigiMiniOntology( mainModel() ); - // setup the actual index scheduler including strigi stuff // ============================================================== - m_indexScheduler = new IndexScheduler( this ); + m_schedulingThread = new QThread( this ); + m_schedulingThread->start( QThread::IdlePriority ); + + m_indexScheduler = new IndexScheduler(); // must not have a parent + m_indexScheduler->moveToThread( m_schedulingThread ); // monitor all kinds of events ( void )new EventMonitor( m_indexScheduler, this ); @@ -64,6 +63,8 @@ Nepomuk::StrigiService::StrigiService( QObject* parent, const QList<QVariant>& ) this, SIGNAL( statusStringChanged() ) ); connect( m_indexScheduler, SIGNAL( indexingFolder(QString) ), this, SIGNAL( statusStringChanged() ) ); + connect( m_indexScheduler, SIGNAL( indexingFile(QString) ), + this, SIGNAL( statusStringChanged() ) ); connect( m_indexScheduler, SIGNAL( indexingSuspended(bool) ), this, SIGNAL( statusStringChanged() ) ); @@ -75,9 +76,6 @@ Nepomuk::StrigiService::StrigiService( QObject* parent, const QList<QVariant>& ) // FIXME: do not use a random delay value but wait for KDE to be started completely (using the session manager) QTimer::singleShot( 2*60*1000, this, SLOT( finishInitialization() ) ); - // start the actual indexing - m_indexScheduler->start(); - // Connect some signals used in the DBus interface connect( this, SIGNAL( statusStringChanged() ), this, SIGNAL( statusChanged() ) ); @@ -92,8 +90,10 @@ Nepomuk::StrigiService::StrigiService( QObject* parent, const QList<QVariant>& ) Nepomuk::StrigiService::~StrigiService() { - m_indexScheduler->stop(); - m_indexScheduler->wait(); + m_schedulingThread->quit(); + m_schedulingThread->wait(); + + delete m_indexScheduler; } @@ -153,16 +153,22 @@ QString Nepomuk::StrigiService::userStatusString( bool simple ) const { bool indexing = m_indexScheduler->isIndexing(); bool suspended = m_indexScheduler->isSuspended(); - QString folder = m_indexScheduler->currentFolder(); if ( suspended ) { return i18nc( "@info:status", "File indexer is suspended" ); } else if ( indexing ) { + QString folder = m_indexScheduler->currentFolder(); + if ( folder.isEmpty() || simple ) return i18nc( "@info:status", "Strigi is currently indexing files" ); - else - return i18nc( "@info:status", "Strigi is currently indexing files in folder %1", folder ); + else { + QString file = KUrl( m_indexScheduler->currentFile() ).fileName(); + if( file.isEmpty() ) + return i18nc( "@info:status", "Strigi is currently indexing files in folder %1", folder ); + else + return i18nc( "@info:status", "Strigi is currently indexing files in folder %1 (%2)", folder, file ); + } } else { return i18nc( "@info:status", "File indexer is idle" ); @@ -270,28 +276,6 @@ void Nepomuk::StrigiService::indexFolder(const QString& path, bool recursive, bo } -void Nepomuk::StrigiService::analyzeResource(const QString& uri, uint mTime, const QByteArray& data) -{ - QDataStream stream( data ); - m_indexScheduler->analyzeResource( QUrl::fromEncoded( uri.toAscii() ), QDateTime::fromTime_t( mTime ), stream ); -} - - -void Nepomuk::StrigiService::analyzeResourceFromTempFileAndDeleteTempFile(const QString& uri, uint mTime, const QString& tmpFile) -{ - QFile file( tmpFile ); - if ( file.open( QIODevice::ReadOnly ) ) { - QDataStream stream( &file ); - m_indexScheduler->analyzeResource( QUrl::fromEncoded( uri.toAscii() ), QDateTime::fromTime_t( mTime ), stream ); - file.remove(); - } - else { - kDebug() << "Failed to open" << tmpFile; - } -} - - - #include <kpluginfactory.h> #include <kpluginloader.h> diff --git a/nepomuk/services/strigi/strigiservice.h b/nepomuk/services/strigi/strigiservice.h index ecff7a2..2eb7eac 100644 --- a/nepomuk/services/strigi/strigiservice.h +++ b/nepomuk/services/strigi/strigiservice.h @@ -22,7 +22,7 @@ #include <Nepomuk/Service> #include <QtCore/QTimer> - +#include <QtCore/QThread> namespace Strigi { class IndexManager; @@ -44,9 +44,6 @@ namespace Nepomuk { StrigiService( QObject* parent = 0, const QList<QVariant>& args = QList<QVariant>() ); ~StrigiService(); - //vHanda: Is this really required? I've removed all the code that uses it. - IndexScheduler* indexScheduler() const { return m_indexScheduler; } - Q_SIGNALS: void statusStringChanged(); void statusChanged(); //vHanda: Can't we just use statusStringChanged? or should that be renamed @@ -86,7 +83,7 @@ namespace Nepomuk { void updateAllFolders( bool forced ); /** - * Index a folder independant of its configuration status. + * Index a folder independent of its configuration status. */ void indexFolder( const QString& path, bool recursive, bool forced ); @@ -95,9 +92,6 @@ namespace Nepomuk { */ void indexFile( const QString& path ); - void analyzeResource( const QString& uri, uint mTime, const QByteArray& data ); - void analyzeResourceFromTempFileAndDeleteTempFile( const QString& uri, uint mTime, const QString& tmpFile ); - private Q_SLOTS: void finishInitialization(); void updateWatches(); @@ -109,6 +103,7 @@ namespace Nepomuk { QString userStatusString( bool simple ) const; IndexScheduler* m_indexScheduler; + QThread* m_schedulingThread; }; } diff --git a/nepomuk/services/strigi/strigiserviceconfig.cpp b/nepomuk/services/strigi/strigiserviceconfig.cpp index f1e7b72..4a71cf9 100644 --- a/nepomuk/services/strigi/strigiserviceconfig.cpp +++ b/nepomuk/services/strigi/strigiserviceconfig.cpp @@ -17,7 +17,6 @@ */ #include "strigiserviceconfig.h" -#include "removablestorageserviceinterface.h" #include "fileexcludefilters.h" #include <QtCore/QStringList> @@ -174,13 +173,16 @@ bool Nepomuk::StrigiServiceConfig::shouldFolderBeIndexed( const QString& path ) bool Nepomuk::StrigiServiceConfig::shouldFileBeIndexed( const QString& fileName ) const { // check the filters + QMutexLocker lock( &m_folderCacheMutex ); return !m_excludeFilterRegExpCache.exactMatch( fileName ); } bool Nepomuk::StrigiServiceConfig::folderInFolderList( const QString& path, bool& exact ) const { - QString p = KUrl( path ).path( KUrl::RemoveTrailingSlash ); + QMutexLocker lock( &m_folderCacheMutex ); + + const QString p = KUrl( path ).path( KUrl::RemoveTrailingSlash ); // we traverse the list backwards to catch all exclude folders int i = m_folderCache.count(); @@ -249,13 +251,10 @@ namespace { void Nepomuk::StrigiServiceConfig::buildFolderCache() { + QMutexLocker lock( &m_folderCacheMutex ); + QStringList includeFoldersPlain = m_config.group( "General" ).readPathEntry( "folders", QStringList() << QDir::homePath() ); - org::kde::nepomuk::RemovableStorage removableStorageService( "org.kde.nepomuk.services.removablestorageservice", - "/removablestorageservice", - QDBusConnection::sessionBus() ); - if ( removableStorageService.isValid() ) - includeFoldersPlain << removableStorageService.currentlyMountedAndIndexed(); - QStringList excludeFoldersPlain = m_config.group( "General" ).readPathEntry( "exclude folders", QStringList() );; + QStringList excludeFoldersPlain = m_config.group( "General" ).readPathEntry( "exclude folders", QStringList() ); m_folderCache.clear(); insertSortFolders( includeFoldersPlain, true, m_folderCache ); @@ -266,6 +265,7 @@ void Nepomuk::StrigiServiceConfig::buildFolderCache() void Nepomuk::StrigiServiceConfig::buildExcludeFilterRegExpCache() { + QMutexLocker lock( &m_folderCacheMutex ); m_excludeFilterRegExpCache.rebuildCacheFromFilterList( excludeFilters() ); } diff --git a/nepomuk/services/strigi/strigiserviceconfig.h b/nepomuk/services/strigi/strigiserviceconfig.h index 02bfc5f..03d827f 100644 --- a/nepomuk/services/strigi/strigiserviceconfig.h +++ b/nepomuk/services/strigi/strigiserviceconfig.h @@ -22,6 +22,7 @@ #include <QtCore/QObject> #include <QtCore/QList> #include <QtCore/QRegExp> +#include <QtCore/QMutex> #include <kconfig.h> #include <kio/global.h> @@ -137,6 +138,8 @@ namespace Nepomuk { /// cache of regexp objects for all exclude filters /// to prevent regexp parsing over and over RegExpCache m_excludeFilterRegExpCache; + + mutable QMutex m_folderCacheMutex; }; } diff --git a/nepomuk/services/strigi/util.cpp b/nepomuk/services/strigi/util.cpp index 14cc4e6..3736c50 100644 --- a/nepomuk/services/strigi/util.cpp +++ b/nepomuk/services/strigi/util.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2009 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2007-2011 Sebastian Trueg <trueg@kde.org> This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -18,125 +18,42 @@ */ #include "util.h" - -#include <strigi/variant.h> -#include <strigi/fieldtypes.h> +#include "datamanagement.h" +#include "nepomuktools.h" #include <QtCore/QUrl> #include <QtCore/QFile> #include <QtCore/QFileInfo> #include <QtCore/QUuid> +#include <QtCore/QScopedPointer> #include <QtCore/QDebug> -#include <Soprano/Model> -#include <Soprano/Statement> -#include <Soprano/Vocabulary/RDF> -#include <Soprano/Vocabulary/RDFS> -#include <Soprano/Vocabulary/NRL> -#include <Soprano/Vocabulary/XMLSchema> - +#include <KJob> +#include <KDebug> +#include <KGlobal> +#include <KComponentData> -#define STRIGI_NS "http://www.strigi.org/data#" -QUrl Strigi::Util::fieldUri( const std::string& s ) +KJob* Nepomuk::clearIndexedData( const QUrl& url ) { - QString qKey = QString::fromUtf8( s.c_str() ); - QUrl url; - - // very naive test for proper URI - if ( qKey.contains( ":/" ) ) { - url = qKey; - } - else { - url = STRIGI_NS + qKey; - } - - // just to be sure - if ( url.isRelative() ) { - url.setScheme( "http" ); - } - - return url; + return clearIndexedData(QList<QUrl>() << url); } - -QUrl Strigi::Util::fileUrl( const std::string& filename ) +KJob* Nepomuk::clearIndexedData( const QList<QUrl>& urls ) { - QUrl url = QUrl::fromLocalFile( QFileInfo( QString::fromUtf8( filename.c_str() ) ).absoluteFilePath() ); - url.setScheme( "file" ); - return url; -} + if ( urls.isEmpty() ) + return 0; + kDebug() << urls; -std::string Strigi::Util::fieldName( const QUrl& uri ) -{ - QString s = uri.toString(); - if ( s.startsWith( STRIGI_NS ) ) { - s = s.mid( strlen( STRIGI_NS ) ); + // + // New way of storing Strigi Data + // The Datamanagement API will automatically find the resource corresponding to that url + // + KComponentData component = KGlobal::mainComponent(); + if( component.componentName() != QLatin1String("nepomukindexer") ) { + component = KComponentData( QByteArray("nepomukindexer"), + QByteArray(), KComponentData::SkipMainComponentRegistration ); } - return s.toUtf8().data(); -} - - -QUrl Strigi::Util::uniqueUri( const QString& ns, Soprano::Model* model ) -{ - QUrl uri; - do { - QString uid = QUuid::createUuid().toString(); - uri = ( ns + uid.mid( 1, uid.length()-2 ) ); - } while ( model->containsAnyStatement( Soprano::Statement( uri, Soprano::Node(), Soprano::Node() ) ) ); - return uri; -} - - -Strigi::Variant Strigi::Util::nodeToVariant( const Soprano::Node& node ) -{ - if ( node.isLiteral() ) { - switch( node.literal().type() ) { - case QVariant::Int: - case QVariant::UInt: - case QVariant::LongLong: // FIXME: no perfect conversion :( - case QVariant::ULongLong: - return Strigi::Variant( node.literal().toInt() ); - - case QVariant::Bool: - return Strigi::Variant( node.literal().toBool() ); - - default: - return Strigi::Variant( node.literal().toString().toUtf8().data() ); - } - } - else { - qWarning() << "(Soprano::Util::nodeToVariant) cannot convert non-literal node to variant."; - return Strigi::Variant(); - } -} - - -void Strigi::Util::storeStrigiMiniOntology( Soprano::Model* model ) -{ - // we use some nice URI here although we still have the STRIGI_NS for backwards comp - - QUrl graph( "http://nepomuk.kde.org/ontologies/2008/07/24/strigi/metadata" ); - Soprano::Statement depthProp( fieldUri( FieldRegister::embeddepthFieldName ), - Soprano::Vocabulary::RDF::type(), - Soprano::Vocabulary::RDF::Property(), - graph ); - Soprano::Statement metaDataType( graph, - Soprano::Vocabulary::RDF::type(), - Soprano::Vocabulary::NRL::Ontology(), - graph ); - - if ( !model->containsStatement( depthProp ) ) { - model->addStatement( depthProp ); - } - if ( !model->containsStatement( metaDataType ) ) { - model->addStatement( metaDataType ); - } -} - - -QUrl Strigi::Ontology::indexGraphFor() -{ - return QUrl::fromEncoded( "http://www.strigi.org/fields#indexGraphFor", QUrl::StrictMode ); + return Nepomuk::removeDataByApplication( urls, RemoveSubResoures, component ); } diff --git a/nepomuk/services/strigi/util.h b/nepomuk/services/strigi/util.h index 9a1c8f4..2f5d268 100644 --- a/nepomuk/services/strigi/util.h +++ b/nepomuk/services/strigi/util.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2007-2009 Sebastian Trueg <trueg@kde.org> + Copyright (C) 2007-2011 Sebastian Trueg <trueg@kde.org> This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -20,37 +20,13 @@ #ifndef _STRIGI_NEPOMUK_UTIL_H_ #define _STRIGI_NEPOMUK_UTIL_H_ -#include <string> +#include <KUrl> -class QUrl; -class QString; +class KJob; -namespace Soprano { - class Model; - class Node; +namespace Nepomuk { + /// remove all indexed data for \p url the datamanagement way + KJob* clearIndexedData( const QUrl& url ); + KJob* clearIndexedData( const QList<QUrl>& urls ); } - -namespace Strigi { - - class Variant; - - namespace Util { - QUrl fieldUri( const std::string& s ); - QUrl fileUrl( const std::string& filename ); - std::string fieldName( const QUrl& uri ); - QUrl uniqueUri( const QString& ns, ::Soprano::Model* model ); - Strigi::Variant nodeToVariant( const ::Soprano::Node& node ); - - /** - * For now only stores the parentUrl property so it can be - * searched. - */ - void storeStrigiMiniOntology( ::Soprano::Model* model ); - } - - namespace Ontology { - QUrl indexGraphFor(); - } -} - #endif diff --git a/nepomuk/servicestub/CMakeLists.txt b/nepomuk/servicestub/CMakeLists.txt index 0eab0ef..fb10963 100644 --- a/nepomuk/servicestub/CMakeLists.txt +++ b/nepomuk/servicestub/CMakeLists.txt @@ -30,6 +30,7 @@ if (Q_WS_MAC) set_target_properties(nepomukservicestub PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "KDE Nepomuk Service Stub") endif (Q_WS_MAC) + target_link_libraries(nepomukservicestub ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} diff --git a/nepomuk/servicestub/priority.cpp b/nepomuk/servicestub/priority.cpp index 1c4e5687..4b140d8 100644 --- a/nepomuk/servicestub/priority.cpp +++ b/nepomuk/servicestub/priority.cpp @@ -91,14 +91,13 @@ bool lowerPriority() } -// FIXME: is this really useful? Should we better use SCHED_IDLE? bool lowerSchedulingPriority() { -#ifdef SCHED_BATCH +#ifdef SCHED_IDLE struct sched_param param; memset( ¶m, 0, sizeof(param) ); param.sched_priority = 0; - return !sched_setscheduler( 0, SCHED_BATCH, ¶m ); + return !sched_setscheduler( 0, SCHED_IDLE, ¶m ); #else return false; #endif