diff --git a/src/engine/documenturldb.h b/src/engine/documenturldb.h --- a/src/engine/documenturldb.h +++ b/src/engine/documenturldb.h @@ -112,7 +112,8 @@ quint64 id = path.parentId; while (id) { auto path = idFilenameDb.get(id); - Q_ASSERT(!path.name.isEmpty()); + // FIXME: Prevents database cleaning + // Q_ASSERT(!path.name.isEmpty()); QVector subDocs = idTreeDb.get(path.parentId); if (subDocs.size() == 1 && shouldDeleteFolder(id)) { diff --git a/src/engine/experimental/databasesanitizer.h b/src/engine/experimental/databasesanitizer.h --- a/src/engine/experimental/databasesanitizer.h +++ b/src/engine/experimental/databasesanitizer.h @@ -73,6 +73,12 @@ */ void printDevices(const QVector& deviceIds, const ItemAccessFilters accessFilter = IgnoreNone); + void removeStaleEntries(const QVector& deviceIds, + const DatabaseSanitizer::ItemAccessFilters accessFilter = DatabaseSanitizer::IgnoreNone, + const bool dryRun = false, + const QSharedPointer& urlFilter = nullptr + ); + private: DatabaseSanitizer(const DatabaseSanitizer& rhs) = delete; DatabaseSanitizerImpl* m_pimpl; diff --git a/src/engine/experimental/databasesanitizer.cpp b/src/engine/experimental/databasesanitizer.cpp --- a/src/engine/experimental/databasesanitizer.cpp +++ b/src/engine/experimental/databasesanitizer.cpp @@ -41,14 +41,17 @@ } public: + /** * \brief Basic info about database items */ struct FileInfo { quint32 deviceId = 0; quint32 inode = 0; - QString url = QString(); + quint64 id = 0; + bool isSymLink = false; bool accessible = true; + QString url; }; void printProgress(QTextStream& out, uint& cur, const uint max, const uint step) const @@ -127,14 +130,18 @@ info.deviceId = deviceId; info.inode = idToInode(id); info.url = QFile::decodeName(it.value()); - info.accessible = !info.url.isEmpty() && QFileInfo::exists(info.url); + info.id = id; + QFileInfo fileInfo(info.url); + info.accessible = !info.url.isEmpty() && fileInfo.exists(); if (info.accessible && (accessFilter & DatabaseSanitizer::IgnoreAvailable)) { continue; } else if (!info.accessible && (accessFilter & DatabaseSanitizer::IgnoreUnavailable)) { continue; } + info.isSymLink = fileInfo.isSymLink(); + result.append(info); summary.ignored--; if (info.accessible) { @@ -162,8 +169,41 @@ return info; } -private: + + QMap deviceFilters(QVector& infos, const DatabaseSanitizer::ItemAccessFilters accessFilter) + { + QMap result; + for (const auto& info : infos) { + result[info.deviceId] = false; + } + + for (auto it = result.begin(), end = result.end(); it != end; it++) { + const auto storageInfo = getStorageInfo(it.key()); + it.value() = isIgnored(storageInfo, accessFilter); + } + return result; + } + + bool isIgnored(const QStorageInfo& storageInfo, const DatabaseSanitizer::ItemAccessFilters accessFilter) + { + if (storageInfo.isValid() && (accessFilter & DatabaseSanitizer::IgnoreMounted)) { + return true; + } else if (!storageInfo.isValid() && (accessFilter & DatabaseSanitizer::IgnoreUnmounted)) { + return true; + } else if (storageInfo.fileSystemType() == QLatin1String("tmpfs")) { + // Due to the volatility of device ids, an id known by baloo may + // appear as mounted, but is not what baloo expects. + // For example at indexing time 43 was the id of a smb share, but + // at runtime 43 is the id of /run/media/ when other users are + // logged in. The latter have a type of 'tmpfs' and should be ignored. + return true; + } else { + return false; + } + } + Transaction* m_transaction; + }; } @@ -279,3 +319,46 @@ err << i18n("Found %1 matching in %2 devices", matchCount, useCount.size()) << endl; } + +void DatabaseSanitizer::removeStaleEntries(const QVector& deviceIds, + const DatabaseSanitizer::ItemAccessFilters accessFilter, + const bool dryRun, + const QSharedPointer& urlFilter) +{ + auto listResult = m_pimpl->createList(deviceIds, IgnoreAvailable, urlFilter); + + const auto ignoredDevices = m_pimpl->deviceFilters(listResult.first, accessFilter); + + const auto sep = QLatin1Char(' '); + auto& summary = listResult.second; + QTextStream out(stdout); + QTextStream err(stderr); + for (const auto& info: listResult.first) { + if (ignoredDevices[info.deviceId] == true) { + summary.ignored++; + } else { + if (info.isSymLink) { + out << i18n("IgnoredSymbolicLink:"); + summary.ignored++; + } else { + m_pimpl->m_transaction->removeDocument(info.id); + out << i18n("Removing:"); + } + out << sep << QStringLiteral("device: %1").arg(info.deviceId) + << sep << QStringLiteral("inode: %1").arg(info.inode) + << sep << QStringLiteral("url: %1").arg(info.url) + << endl; + } + } + if (dryRun) { + m_pimpl->m_transaction->abort(); + } else { + m_pimpl->m_transaction->commit(); + } + Q_ASSERT(summary.accessible == 0); + err << i18nc("numbers", "Removed: %1, Total: %2, Ignored: %3", + summary.total - summary.ignored, + summary.total, + summary.ignored) + << endl; +} diff --git a/src/tools/experimental/baloodb/main.cpp b/src/tools/experimental/baloodb/main.cpp --- a/src/tools/experimental/baloodb/main.cpp +++ b/src/tools/experimental/baloodb/main.cpp @@ -53,18 +53,18 @@ i18n("integer"), 0 }, - QCommandLineOption{ - QStringList{QStringLiteral("D"), QStringLiteral("dry-run")}, - i18n("Print results of a prune operation, but do not change anything." - "\nOnly applies to \"%1\" command", QStringLiteral("prune")) - }, QCommandLineOption{ QStringList{QStringLiteral("m"), QStringLiteral("missing-only")}, i18n("List only inaccessible entries.\nOnly applies to \"%1\"", QStringLiteral("list")) }, QCommandLineOption{ QStringList{QStringLiteral("u"), QStringLiteral("mounted-only")}, i18n("Act only on item on mounted devices") + }, + QCommandLineOption{ + QStringList{QStringLiteral("D"), QStringLiteral("dry-run")}, + i18n("Print results of a prune operation, but do not change anything." + "\nOnly applies to \"%1\" command", QStringLiteral("prune")) } }; @@ -80,6 +80,12 @@ QStringLiteral("device-id") } }, + Command{ + QStringLiteral("devices"), + i18n("List devices"), + QStringList{}, + QStringList{QStringLiteral("missing-only")} + }, /*TODO: Command{ QStringLiteral("check"), @@ -89,24 +95,17 @@ QStringList{} }, */ - /*TODO: Command{ - QStringLiteral("prune"), + QStringLiteral("clean"), i18n("Remove stale database entries"), QStringList{ QStringLiteral("pattern") }, QStringList{ QStringLiteral("dry-run"), - QStringLiteral("device-id") + QStringLiteral("device-id"), + QStringLiteral("mounted-only") } - }, - */ - Command{ - QStringLiteral("devices"), - i18n("List devices"), - QStringList{}, - QStringList{QStringLiteral("missing-only"), QStringLiteral("mounted-only")} } }; @@ -147,7 +146,7 @@ .arg(argumentStr); const QString str = QStringLiteral("%1 %2") - .arg(commandStr, -48) + .arg(commandStr, -58) .arg(c.description); allowedcommands.append(str); @@ -248,8 +247,14 @@ san.printDevices(deviceIds, accessFilter); } else if (command == QStringLiteral("clean")) { - /* TODO: add prune command */ - parser.showHelp(1); + auto dbMode = Database::ReadWriteDatabase; + if (!db->open(dbMode)) { + err << i18n("Baloo Index could not be opened") << endl; + return 1; + } + DatabaseSanitizer san(db, Transaction::ReadWrite); + err << i18n("Removing stale database contents...") << endl; + san.removeStaleEntries(deviceIds, accessFilter, parser.isSet(QStringLiteral("dry-run")), urlFilter); } else if (command == QStringLiteral("check")) { parser.showHelp(1);