diff --git a/autotests/unit/file/CMakeLists.txt b/autotests/unit/file/CMakeLists.txt --- a/autotests/unit/file/CMakeLists.txt +++ b/autotests/unit/file/CMakeLists.txt @@ -5,6 +5,16 @@ ) endif() +set (metadatamovertest_SOURCES + metadatamovertest.cpp +) + +qt5_add_dbus_adaptor(metadatamovertest_SOURCES + ${CMAKE_SOURCE_DIR}/src/file/org.kde.BalooWatcherApplication.xml + metadatamovertest.h MetadataMoverTestDBusSpy) + +ecm_add_test(${metadatamovertest_SOURCES} TEST_NAME metadatamovertest LINK_LIBRARIES Qt5::Test baloofilecommon KF5::Baloo) + MACRO(BALOO_FILE_AUTO_TESTS) FOREACH(_testname ${ARGN}) ecm_add_test(${_testname}.cpp TEST_NAME ${_testname} LINK_LIBRARIES Qt5::Test baloofilecommon KF5::Baloo) @@ -18,7 +28,6 @@ regularexpcachebenchmark filtereddiriteratortest unindexedfileiteratortest - metadatamovertest fileinfotest ) diff --git a/autotests/unit/file/filewatchtest.cpp b/autotests/unit/file/filewatchtest.cpp --- a/autotests/unit/file/filewatchtest.cpp +++ b/autotests/unit/file/filewatchtest.cpp @@ -23,6 +23,7 @@ #include "database.h" #include "fileindexerconfig.h" #include "pendingfilequeue.h" +#include "mainhub.h" #include "../lib/xattrdetector.h" #include "../lib/baloo_xattr_p.h" @@ -36,7 +37,38 @@ { Q_OBJECT private Q_SLOTS: + + void init() + { + m_tempDir = new QTemporaryDir(); + m_db = new Database(m_tempDir->path()); + m_db->open(Database::CreateDatabase); + m_dbusInterface = new MainHub(m_db, &m_config); + } + void testFileCreation(); + + void cleanupTestCase() + { + delete m_dbusInterface; + m_dbusInterface = nullptr; + + delete m_db; + m_db = nullptr; + + delete m_tempDir; + m_tempDir = nullptr; + } + +private: + + FileIndexerConfig m_config; + + Database* m_db; + + QTemporaryDir* m_tempDir; + + Baloo::MainHub *m_dbusInterface; }; } @@ -76,7 +108,7 @@ FileIndexerConfig config; - FileWatch fileWatch(&db, &config); + FileWatch fileWatch(&db, &config, m_dbusInterface); fileWatch.m_pendingFileQueue->setMaximumTimeout(0); fileWatch.m_pendingFileQueue->setMinimumTimeout(0); fileWatch.m_pendingFileQueue->setTrackingTime(0); diff --git a/autotests/unit/file/metadatamovertest.h b/autotests/unit/file/metadatamovertest.h new file mode 100644 --- /dev/null +++ b/autotests/unit/file/metadatamovertest.h @@ -0,0 +1,29 @@ +#if !defined METADATAMOVERTEST_H_ +#define METADATAMOVERTEST_H_ + +#include +#include +#include + +class MetadataMoverTestDBusSpy : public QObject +{ + Q_OBJECT +public: + + MetadataMoverTestDBusSpy(QObject* parent = nullptr); + +Q_SIGNALS: + + void fileMetaDataChanged(QStringList fileList); + + void renamedFilesSignal(const QString &from, const QString &to, const QStringList &listFiles); + +public Q_SLOTS: + + void slotFileMetaDataChanged(QStringList fileList); + + void renamedFiles(const QString &from, const QString &to, const QStringList &listFiles); + +}; + +#endif diff --git a/autotests/unit/file/metadatamovertest.cpp b/autotests/unit/file/metadatamovertest.cpp --- a/autotests/unit/file/metadatamovertest.cpp +++ b/autotests/unit/file/metadatamovertest.cpp @@ -18,19 +18,23 @@ * */ +#include "metadatamovertest.h" #include "metadatamover.h" +#include "baloowatcherapplicationadaptor.h" #include "database.h" #include "transaction.h" #include "document.h" #include "basicindexingjob.h" +#include "mainhub.h" #include #include #include #include #include #include +#include using namespace Baloo; @@ -53,7 +57,10 @@ private: quint64 insertUrl(const QString& url); + FileIndexerConfig m_config; + Database* m_db; + QTemporaryDir* m_tempDir; }; @@ -93,6 +100,10 @@ void MetadataMoverTest::testRemoveFile() { + MetadataMoverTestDBusSpy dbusSignalSpy; + QSignalSpy renamedFilesSignalSpy(&dbusSignalSpy, &MetadataMoverTestDBusSpy::renamedFilesSignal); + QSignalSpy fileChangedSpy(&dbusSignalSpy, &MetadataMoverTestDBusSpy::fileMetaDataChanged); + QTemporaryFile file; file.open(); QString url = file.fileName(); @@ -103,10 +114,25 @@ QVERIFY(tr.hasDocument(fid)); } + renamedFilesSignalSpy.wait(100); + fileChangedSpy.wait(100); + + QCOMPARE(renamedFilesSignalSpy.count(), 0); + QCOMPARE(fileChangedSpy.count(), 0); + MetadataMover mover(m_db, this); + + mover.registerBalooWatcher(QStringLiteral("org.kde.baloo.metadatamovertest/org/kde/BalooWatcherApplication")); + file.remove(); mover.removeFileMetadata(url); + renamedFilesSignalSpy.wait(100); + fileChangedSpy.wait(100); + + QCOMPARE(renamedFilesSignalSpy.count(), 0); + QCOMPARE(fileChangedSpy.count(), 0); + { Transaction tr(m_db, Transaction::ReadOnly); QVERIFY(!tr.hasDocument(fid)); @@ -128,6 +154,10 @@ void MetadataMoverTest::testRenameFile() { + MetadataMoverTestDBusSpy dbusSignalSpy; + QSignalSpy renamedFilesSignalSpy(&dbusSignalSpy, &MetadataMoverTestDBusSpy::renamedFilesSignal); + QSignalSpy fileChangedSpy(&dbusSignalSpy, &MetadataMoverTestDBusSpy::fileMetaDataChanged); + QTemporaryDir dir; QString url = dir.path() + "/file"; @@ -139,11 +169,26 @@ QVERIFY(tr.hasDocument(fid)); } + renamedFilesSignalSpy.wait(100); + fileChangedSpy.wait(100); + + QCOMPARE(renamedFilesSignalSpy.count(), 0); + QCOMPARE(fileChangedSpy.count(), 0); + MetadataMover mover(m_db, this); + + mover.registerBalooWatcher(QStringLiteral("org.kde.baloo.metadatamovertest/org/kde/BalooWatcherApplication")); + QString url2 = dir.path() + "/file2"; QFile::rename(url, url2); mover.moveFileMetadata(QFile::encodeName(url), QFile::encodeName(url2)); + renamedFilesSignalSpy.wait(100); + fileChangedSpy.wait(100); + + QCOMPARE(renamedFilesSignalSpy.count(), 1); + QCOMPARE(fileChangedSpy.count(), 0); + { Transaction tr(m_db, Transaction::ReadOnly); QVERIFY(tr.hasDocument(fid)); @@ -153,6 +198,10 @@ void MetadataMoverTest::testMoveFile() { + MetadataMoverTestDBusSpy dbusSignalSpy; + QSignalSpy renamedFilesSignalSpy(&dbusSignalSpy, &MetadataMoverTestDBusSpy::renamedFilesSignal); + QSignalSpy fileChangedSpy(&dbusSignalSpy, &MetadataMoverTestDBusSpy::fileMetaDataChanged); + QTemporaryDir dir; QDir().mkpath(dir.path() + "/a/b/c"); @@ -165,11 +214,26 @@ QVERIFY(tr.hasDocument(fid)); } + renamedFilesSignalSpy.wait(100); + fileChangedSpy.wait(100); + + QCOMPARE(renamedFilesSignalSpy.count(), 0); + QCOMPARE(fileChangedSpy.count(), 0); + MetadataMover mover(m_db, this); + + mover.registerBalooWatcher(QStringLiteral("org.kde.baloo.metadatamovertest/org/kde/BalooWatcherApplication")); + QString url2 = dir.path() + "/file2"; QFile::rename(url, url2); mover.moveFileMetadata(QFile::encodeName(url), QFile::encodeName(url2)); + renamedFilesSignalSpy.wait(100); + fileChangedSpy.wait(100); + + QCOMPARE(renamedFilesSignalSpy.count(), 1); + QCOMPARE(fileChangedSpy.count(), 0); + { Transaction tr(m_db, Transaction::ReadOnly); QVERIFY(tr.hasDocument(fid)); @@ -179,6 +243,10 @@ void MetadataMoverTest::testMoveFolder() { + MetadataMoverTestDBusSpy dbusSignalSpy; + QSignalSpy renamedFilesSignalSpy(&dbusSignalSpy, &MetadataMoverTestDBusSpy::renamedFilesSignal); + QSignalSpy fileChangedSpy(&dbusSignalSpy, &MetadataMoverTestDBusSpy::fileMetaDataChanged); + QTemporaryDir dir; QString folder = dir.path() + "/folder"; @@ -195,11 +263,26 @@ QVERIFY(tr.hasDocument(fid)); } + renamedFilesSignalSpy.wait(100); + fileChangedSpy.wait(100); + + QCOMPARE(renamedFilesSignalSpy.count(), 0); + QCOMPARE(fileChangedSpy.count(), 0); + QString newFolderUrl = dir.path() + "/dir"; QFile::rename(folder, newFolderUrl); MetadataMover mover(m_db, this); + + mover.registerBalooWatcher(QStringLiteral("org.kde.baloo.metadatamovertest/org/kde/BalooWatcherApplication")); + mover.moveFileMetadata(QFile::encodeName(folder), QFile::encodeName(newFolderUrl)); + renamedFilesSignalSpy.wait(100); + fileChangedSpy.wait(100); + + QCOMPARE(renamedFilesSignalSpy.count(), 1); + QCOMPARE(fileChangedSpy.count(), 0); + { Transaction tr(m_db, Transaction::ReadOnly); QVERIFY(tr.hasDocument(did)); @@ -209,6 +292,32 @@ } } + + +MetadataMoverTestDBusSpy::MetadataMoverTestDBusSpy(QObject *parent) : QObject(parent) +{ + QDBusConnection con = QDBusConnection::sessionBus(); + + con.connect(QString(), QStringLiteral("/files"), QStringLiteral("org.kde"), + QStringLiteral("changed"), this, SLOT(slotFileMetaDataChanged(QStringList))); + + + auto mDbusAdaptor = new BalooWatcherApplicationAdaptor(this); + + QCOMPARE(con.registerService(QStringLiteral("org.kde.baloo.metadatamovertest")), true); + QCOMPARE(con.registerObject(QStringLiteral("/org/kde/BalooWatcherApplication"), mDbusAdaptor, QDBusConnection::ExportAllContents), true); +} + +void MetadataMoverTestDBusSpy::slotFileMetaDataChanged(QStringList fileList) +{ + Q_EMIT fileMetaDataChanged(fileList); +} + +void MetadataMoverTestDBusSpy::renamedFiles(const QString &from, const QString &to, const QStringList &listFiles) +{ + Q_EMIT renamedFilesSignal(from, to, listFiles); +} + QTEST_GUILESS_MAIN(MetadataMoverTest) #include "metadatamovertest.moc" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,3 +10,4 @@ endif() add_subdirectory(dbus) + diff --git a/src/engine/transaction.h b/src/engine/transaction.h --- a/src/engine/transaction.h +++ b/src/engine/transaction.h @@ -63,6 +63,7 @@ * \p path into an id. */ quint64 documentId(const QByteArray& path) const; + QVector childrenDocumentId(quint64 parentId) const; QByteArray documentData(quint64 id) const; DocumentTimeDB::TimeInfo documentTimeInfo(quint64 id) const; diff --git a/src/engine/transaction.cpp b/src/engine/transaction.cpp --- a/src/engine/transaction.cpp +++ b/src/engine/transaction.cpp @@ -127,6 +127,13 @@ return parentId; } +QVector Transaction::childrenDocumentId(quint64 parentId) const +{ + DocumentUrlDB docUrlDB(m_dbis.idTreeDbi, m_dbis.idFilenameDbi, m_txn); + + return docUrlDB.getChildren(parentId); +} + DocumentTimeDB::TimeInfo Transaction::documentTimeInfo(quint64 id) const { Q_ASSERT(m_txn); diff --git a/src/file/CMakeLists.txt b/src/file/CMakeLists.txt --- a/src/file/CMakeLists.txt +++ b/src/file/CMakeLists.txt @@ -38,12 +38,15 @@ filewatch.cpp pendingfilequeue.cpp metadatamover.cpp + org.kde.BalooWatcherApplication.xml pendingfile.cpp kinotify.cpp ) ecm_qt_declare_logging_category(file_static_lib_SRCS HEADER baloodebug.h IDENTIFIER BALOO CATEGORY_NAME org.kde.baloo) +qt5_add_dbus_interface(file_static_lib_SRCS org.kde.BalooWatcherApplication.xml baloowatcherapplication_interface) + add_library(baloofilecommon STATIC ${file_static_lib_SRCS}) target_link_libraries(baloofilecommon Qt5::DBus @@ -72,3 +75,7 @@ install(FILES baloo_file.desktop DESTINATION ${AUTOSTART_INSTALL_DIR}) add_subdirectory(extractor) + +install(FILES + org.kde.BalooWatcherApplication.xml + DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) diff --git a/src/file/filewatch.h b/src/file/filewatch.h --- a/src/file/filewatch.h +++ b/src/file/filewatch.h @@ -31,13 +31,14 @@ class FileIndexerConfig; class PendingFileQueue; class FileWatchTest; +class MainHub; class FileWatch : public QObject { Q_OBJECT public: - FileWatch(Database* db, FileIndexerConfig* config, QObject* parent = nullptr); + FileWatch(Database* db, FileIndexerConfig* config, Baloo::MainHub *dbusInterface, QObject* parent = nullptr); ~FileWatch(); public Q_SLOTS: @@ -50,6 +51,8 @@ void watchIndexedFolders(); + void registerBalooWatcher(const QString &service); + Q_SIGNALS: void indexNewFile(const QString& string); void indexModifiedFile(const QString& string); diff --git a/src/file/filewatch.cpp b/src/file/filewatch.cpp --- a/src/file/filewatch.cpp +++ b/src/file/filewatch.cpp @@ -36,7 +36,7 @@ using namespace Baloo; -FileWatch::FileWatch(Database* db, FileIndexerConfig* config, QObject* parent) +FileWatch::FileWatch(Database* db, FileIndexerConfig* config, Baloo::MainHub *dbusInterface, QObject* parent) : QObject(parent) , m_db(db) , m_config(config) @@ -82,6 +82,15 @@ } } +void FileWatch::registerBalooWatcher(const QString &service) +{ + if (!m_metadataMover) { + return; + } + + m_metadataMover->registerBalooWatcher(service); +} + // FIXME: listen to Create for folders! void FileWatch::watchFolder(const QString& path) { diff --git a/src/file/mainhub.h b/src/file/mainhub.h --- a/src/file/mainhub.h +++ b/src/file/mainhub.h @@ -25,6 +25,8 @@ #include "filewatch.h" #include "fileindexscheduler.h" +#include + namespace Baloo { class Database; @@ -40,13 +42,15 @@ public Q_SLOTS: Q_SCRIPTABLE void quit() const; Q_SCRIPTABLE void updateConfig(); + Q_SCRIPTABLE void registerBalooWatcher(const QString &service); private: Database* m_db; FileIndexerConfig* m_config; FileWatch m_fileWatcher; FileIndexScheduler m_fileIndexScheduler; + }; } diff --git a/src/file/mainhub.cpp b/src/file/mainhub.cpp --- a/src/file/mainhub.cpp +++ b/src/file/mainhub.cpp @@ -24,6 +24,7 @@ #include #include #include +#include using namespace Baloo; @@ -65,3 +66,8 @@ //m_fileIndexer.updateConfig(); m_fileWatcher.updateIndexedFoldersWatches(); } + +void MainHub::registerBalooWatcher(const QString &service) +{ + m_fileWatcher.registerBalooWatcher(service); +} diff --git a/src/file/metadatamover.h b/src/file/metadatamover.h --- a/src/file/metadatamover.h +++ b/src/file/metadatamover.h @@ -21,6 +21,22 @@ #define BALOO_METADATA_MOVER_H_ #include +#include +#include + +class OrgKdeBalooWatcherApplicationInterface; + +namespace org +{ + +namespace kde +{ + +typedef ::OrgKdeBalooWatcherApplicationInterface BalooWatcherApplication; + +} + +} namespace Baloo { @@ -36,10 +52,14 @@ MetadataMover(Database* db, QObject* parent = nullptr); ~MetadataMover(); + bool hasWatcher() const; + public Q_SLOTS: void moveFileMetadata(const QString& from, const QString& to); void removeFileMetadata(const QString& file); + void registerBalooWatcher(const QString &service); + Q_SIGNALS: /** * Emitted for files (and folders) that have been moved but @@ -52,6 +72,10 @@ void fileRemoved(const QString& path); +private Q_SLOTS: + + void watcherServiceUnregistered(const QString &serviceName); + private: /** * Remove the metadata for file \p url @@ -64,6 +88,12 @@ */ void updateMetadata(Transaction* tr, const QString& from, const QString& to); + void notifyWatchers(const QString &from, const QString &to, const QList &filesList); + + QMap m_watcherApplications; + + QDBusServiceWatcher m_serviceWatcher; + Database* m_db; }; } diff --git a/src/file/metadatamover.cpp b/src/file/metadatamover.cpp --- a/src/file/metadatamover.cpp +++ b/src/file/metadatamover.cpp @@ -22,23 +22,49 @@ #include "transaction.h" #include "basicindexingjob.h" #include "idutils.h" +#include "mainhub.h" #include "baloodebug.h" +#include "baloowatcherapplication_interface.h" + #include +#include +#include using namespace Baloo; MetadataMover::MetadataMover(Database* db, QObject* parent) : QObject(parent) , m_db(db) { + m_serviceWatcher.setConnection(QDBusConnection::sessionBus()); + m_serviceWatcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration); + + connect(&m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, + this, &MetadataMover::watcherServiceUnregistered); } MetadataMover::~MetadataMover() { } +bool MetadataMover::hasWatcher() const +{ + return !m_watcherApplications.isEmpty(); +} + + +static void buildRecursiveList(quint64 parentId, QList &fileList, Transaction &tr) +{ + fileList.push_back(QFile::decodeName(tr.documentUrl(parentId))); + + const auto childrenIds = tr.childrenDocumentId(parentId); + + for (const auto oneChildren : childrenIds) { + buildRecursiveList(oneChildren, fileList, tr); + } +} void MetadataMover::moveFileMetadata(const QString& from, const QString& to) { @@ -48,14 +74,27 @@ Transaction tr(m_db, Transaction::ReadWrite); + quint64 id = tr.documentId(QFile::encodeName(from)); + QList filesList; + qCDebug(BALOO) << "MetadataMover::moveFileMetadata" << (hasWatcher() ? "has watcher" : "has no watcher"); + qCDebug(BALOO) << "MetadataMover::moveFileMetadata" << "id" << id; + if (id && hasWatcher()) { + buildRecursiveList(id, filesList, tr); + } + // We do NOT get deleted messages for overwritten files! Thus, we // have to remove all metadata for overwritten files first. removeMetadata(&tr, to); // and finally update the old statements updateMetadata(&tr, from, to); tr.commit(); + + if (hasWatcher()) { + qCDebug(BALOO) << "MetadataMover::moveFileMetadata" << "notifyWatchers" << filesList; + notifyWatchers(from, to, filesList); + } } void MetadataMover::removeFileMetadata(const QString& file) @@ -67,6 +106,23 @@ tr.commit(); } +void MetadataMover::registerBalooWatcher(const QString &service) +{ + int firstSlash = service.indexOf('/'); + if (firstSlash == -1) { + return; + } + + QString dbusServiceName = service.left(firstSlash); + QString dbusPath = service.mid(firstSlash); + + m_serviceWatcher.addWatchedService(dbusServiceName); + + m_watcherApplications.insert(dbusServiceName, new org::kde::BalooWatcherApplication(dbusServiceName, dbusPath, QDBusConnection::sessionBus(), this)); + + qCDebug(BALOO) << "MetadataMover::registerBalooWatcher" << service << dbusServiceName << dbusPath; +} + void MetadataMover::removeMetadata(Transaction* tr, const QString& url) { Q_ASSERT(!url.isEmpty()); @@ -119,3 +175,24 @@ // 1. file moves to the same device - id is preserved // 2. file moves to a different device - id is not preserved } + +void MetadataMover::notifyWatchers(const QString &from, const QString &to, const QList &filesList) +{ + Q_FOREACH(org::kde::BalooWatcherApplication *watcherApplication, m_watcherApplications) { + qCDebug(BALOO) << "MetadataMover::notifyWatchers" << watcherApplication->service() << watcherApplication->objectName() << watcherApplication->path(); + watcherApplication->renamedFiles(from, to, filesList); + } +} + +void MetadataMover::watcherServiceUnregistered(const QString &serviceName) +{ + auto itService = m_watcherApplications.find(serviceName); + if (itService == m_watcherApplications.end()) { + return; + } + + qDebug() << "MetadataMover::watcherServiceUnregistered" << itService.key(); + + delete itService.value(); + m_watcherApplications.erase(itService); +} diff --git a/src/file/org.kde.BalooWatcherApplication.xml b/src/file/org.kde.BalooWatcherApplication.xml new file mode 100644 --- /dev/null +++ b/src/file/org.kde.BalooWatcherApplication.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + +