File indexing completed on 2024-04-28 03:51:53

0001 /*
0002     This file is part of the KDE Baloo project.
0003     SPDX-FileCopyrightText: 2015 Vishesh Handa <vhanda@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "statuscommand.h"
0009 #include "indexerconfig.h"
0010 
0011 #include "global.h"
0012 #include "database.h"
0013 #include "transaction.h"
0014 #include "idutils.h"
0015 
0016 #include "fileindexerinterface.h"
0017 #include "schedulerinterface.h"
0018 #include "maininterface.h"
0019 #include "indexerstate.h"
0020 
0021 #include <KLocalizedString>
0022 #include <KFormat>
0023 
0024 using namespace Baloo;
0025 
0026 QString StatusCommand::command()
0027 {
0028     return QStringLiteral("status");
0029 }
0030 
0031 QString StatusCommand::description()
0032 {
0033     return i18n("Print the status of the Indexer");
0034 }
0035 
0036 class FileIndexStatus
0037 {
0038 public:
0039     enum class FileStatus : uint8_t {
0040         NonExisting,
0041         Directory,
0042         RegularFile,
0043         SymLink,
0044         Other,
0045     };
0046     enum class IndexStateReason : uint8_t {
0047         NoFileOrDirectory,
0048         ExcludedByPath, // FIXME - be more specific, requires changes to shouldBeIndexed(path)
0049         WaitingForIndexingBoth,
0050         WaitingForBasicIndexing,
0051         BasicIndexingDone,
0052         WaitingForContentIndexing,
0053         FailedToIndex,
0054         Done,
0055     };
0056     const QString m_filePath;
0057     FileStatus m_fileStatus;
0058     IndexStateReason m_indexState;
0059     uint32_t m_dataSize;
0060 };
0061 
0062 FileIndexStatus collectFileStatus(Transaction& tr, IndexerConfig&  cfg, const QString& file)
0063 {
0064     using FileStatus = FileIndexStatus::FileStatus;
0065     using IndexStateReason = FileIndexStatus::IndexStateReason;
0066 
0067     bool onlyBasicIndexing = cfg.onlyBasicIndexing();
0068 
0069     const QFileInfo fileInfo = QFileInfo(file);
0070     const QString filePath = fileInfo.absoluteFilePath();
0071     quint64 id = filePathToId(QFile::encodeName(filePath));
0072     if (id == 0) {
0073         return FileIndexStatus{filePath, FileStatus::NonExisting, IndexStateReason::NoFileOrDirectory, 0};
0074     }
0075 
0076     FileStatus fileStatus = fileInfo.isSymLink() ? FileStatus::SymLink :
0077                             fileInfo.isFile() ? FileStatus::RegularFile :
0078                             fileInfo.isDir() ? FileStatus::Directory : FileStatus::Other;
0079 
0080     if (fileStatus == FileStatus::Other || fileStatus == FileStatus::SymLink) {
0081         return FileIndexStatus{filePath, fileStatus, IndexStateReason::NoFileOrDirectory, 0};
0082     }
0083 
0084     if (!cfg.shouldBeIndexed(filePath)) {
0085         return FileIndexStatus{filePath, fileStatus, IndexStateReason::ExcludedByPath, 0};
0086     }
0087 
0088     if (onlyBasicIndexing || fileStatus == FileStatus::Directory) {
0089         if (!tr.hasDocument(id)) {
0090             return FileIndexStatus{filePath, fileStatus, IndexStateReason::WaitingForBasicIndexing, 0};
0091         } else {
0092             return FileIndexStatus{filePath, fileStatus, IndexStateReason::BasicIndexingDone, 0};
0093         }
0094     }
0095 
0096     // File && shouldBeIndexed && contentIndexing
0097     if (!tr.hasDocument(id)) {
0098         return FileIndexStatus{filePath, fileStatus, IndexStateReason::WaitingForIndexingBoth, 0};
0099     } else if (tr.inPhaseOne(id)) {
0100         return FileIndexStatus{filePath, fileStatus, IndexStateReason::WaitingForContentIndexing, 0};
0101     } else if (tr.hasFailed(id)) {
0102         return FileIndexStatus{filePath, fileStatus, IndexStateReason::FailedToIndex, 0};
0103     } else {
0104         uint32_t size = tr.documentData(id).size();
0105         return FileIndexStatus{filePath, fileStatus, IndexStateReason::Done, size};
0106     }
0107 }
0108 
0109 void printMultiLine(Transaction& tr, IndexerConfig&  cfg, const QStringList& args) {
0110     using FileStatus = FileIndexStatus::FileStatus;
0111     using IndexStateReason = FileIndexStatus::IndexStateReason;
0112 
0113     QTextStream out(stdout);
0114     QTextStream err(stderr);
0115 
0116     const QMap<IndexStateReason, QString> basicIndexStateValue = {
0117         { IndexStateReason::NoFileOrDirectory,         i18n("File ignored") },
0118         { IndexStateReason::ExcludedByPath,            i18n("Basic Indexing: Disabled") },
0119         { IndexStateReason::WaitingForIndexingBoth,    i18n("Basic Indexing: Scheduled") },
0120         { IndexStateReason::WaitingForBasicIndexing,   i18n("Basic Indexing: Scheduled") },
0121         { IndexStateReason::BasicIndexingDone,         i18n("Basic Indexing: Done") },
0122         { IndexStateReason::WaitingForContentIndexing, i18n("Basic Indexing: Done") },
0123         { IndexStateReason::FailedToIndex,             i18n("Basic Indexing: Done") },
0124         { IndexStateReason::Done,                      i18n("Basic Indexing: Done") },
0125     };
0126 
0127     const QMap<IndexStateReason, QString> contentIndexStateValue = {
0128         { IndexStateReason::NoFileOrDirectory,         QString() },
0129         { IndexStateReason::ExcludedByPath,            QString() },
0130         { IndexStateReason::WaitingForIndexingBoth,    i18n("Content Indexing: Scheduled") },
0131         { IndexStateReason::WaitingForBasicIndexing,   QString() },
0132         { IndexStateReason::BasicIndexingDone,         QString() },
0133         { IndexStateReason::WaitingForContentIndexing, i18n("Content Indexing: Scheduled") },
0134         { IndexStateReason::FailedToIndex,             i18n("Content Indexing: Failed") },
0135         { IndexStateReason::Done,                      i18n("Content Indexing: Done") },
0136     };
0137 
0138     for (const auto& fileName : args) {
0139         const auto file = collectFileStatus(tr, cfg, fileName);
0140 
0141         if (file.m_fileStatus == FileStatus::NonExisting) {
0142             err << i18n("Ignoring non-existent file %1", file.m_filePath) << '\n';
0143             continue;
0144         }
0145 
0146         if (file.m_fileStatus == FileStatus::SymLink || file.m_fileStatus == FileStatus::Other) {
0147             err << i18n("Ignoring symlink/special file %1", file.m_filePath) << '\n';
0148             continue;
0149         }
0150 
0151         out << i18n("File: %1", file.m_filePath) << '\n';
0152         out << basicIndexStateValue[file.m_indexState] << '\n';
0153         const QString contentState = contentIndexStateValue[file.m_indexState];
0154         if (!contentState.isEmpty()) {
0155             out << contentState << '\n';
0156         }
0157     }
0158 }
0159 
0160 void printSimpleFormat(Transaction& tr, IndexerConfig&  cfg, const QStringList& args) {
0161     using FileStatus = FileIndexStatus::FileStatus;
0162     using IndexStateReason = FileIndexStatus::IndexStateReason;
0163 
0164     QTextStream out(stdout);
0165     QTextStream err(stderr);
0166 
0167     const QMap<IndexStateReason, QString> simpleIndexStateValue = {
0168         { IndexStateReason::NoFileOrDirectory,         QStringLiteral("No regular file or directory") },
0169         { IndexStateReason::ExcludedByPath,            QStringLiteral("Indexing disabled") },
0170         { IndexStateReason::WaitingForIndexingBoth,    QStringLiteral("Basic and Content indexing scheduled") },
0171         { IndexStateReason::WaitingForBasicIndexing,   QStringLiteral("Basic indexing scheduled") },
0172         { IndexStateReason::BasicIndexingDone,         QStringLiteral("Basic indexing done") },
0173         { IndexStateReason::WaitingForContentIndexing, QStringLiteral("Content indexing scheduled") },
0174         { IndexStateReason::FailedToIndex,             QStringLiteral("Content indexing failed") },
0175         { IndexStateReason::Done,                      QStringLiteral("Content indexing done") },
0176     };
0177 
0178     for (const auto& fileName : args) {
0179         const auto file = collectFileStatus(tr, cfg, fileName);
0180 
0181         if (file.m_fileStatus == FileStatus::NonExisting) {
0182             err << i18n("Ignoring non-existent file %1", file.m_filePath) << '\n';
0183             continue;
0184         }
0185 
0186         if (file.m_fileStatus == FileStatus::SymLink || file.m_fileStatus == FileStatus::Other) {
0187             err << i18n("Ignoring symlink/special file %1", file.m_filePath) << '\n';
0188             continue;
0189         }
0190 
0191         out << simpleIndexStateValue[file.m_indexState];
0192         out << ": " << file.m_filePath << '\n';
0193     }
0194 }
0195 
0196 void printJSON(Transaction& tr, IndexerConfig&  cfg, const QStringList& args)
0197 {
0198     using FileStatus = FileIndexStatus::FileStatus;
0199     using IndexStateReason = FileIndexStatus::IndexStateReason;
0200 
0201     QJsonArray filesInfo;
0202     QTextStream err(stderr);
0203 
0204     const QMap<IndexStateReason, QString> jsonIndexStateValue = {
0205         { IndexStateReason::NoFileOrDirectory,         QStringLiteral("nofile") },
0206         { IndexStateReason::ExcludedByPath,            QStringLiteral("disabled") },
0207         { IndexStateReason::WaitingForIndexingBoth,    QStringLiteral("scheduled") },
0208         { IndexStateReason::WaitingForBasicIndexing,   QStringLiteral("scheduled") },
0209         { IndexStateReason::BasicIndexingDone,         QStringLiteral("done") },
0210         { IndexStateReason::WaitingForContentIndexing, QStringLiteral("scheduled") },
0211         { IndexStateReason::FailedToIndex,             QStringLiteral("failed") },
0212         { IndexStateReason::Done,                      QStringLiteral("done") },
0213     };
0214 
0215     const QMap<IndexStateReason, QString> jsonIndexLevelValue = {
0216         { IndexStateReason::NoFileOrDirectory,         QStringLiteral("nofile") },
0217         { IndexStateReason::ExcludedByPath,            QStringLiteral("none") },
0218         { IndexStateReason::WaitingForIndexingBoth,    QStringLiteral("content") },
0219         { IndexStateReason::WaitingForBasicIndexing,   QStringLiteral("basic") },
0220         { IndexStateReason::BasicIndexingDone,         QStringLiteral("basic") },
0221         { IndexStateReason::WaitingForContentIndexing, QStringLiteral("content") },
0222         { IndexStateReason::FailedToIndex,             QStringLiteral("content") },
0223         { IndexStateReason::Done,                      QStringLiteral("content") },
0224     };
0225 
0226     for (const auto& fileName : args) {
0227         const auto file = collectFileStatus(tr, cfg, fileName);
0228 
0229         if (file.m_fileStatus == FileStatus::NonExisting) {
0230             err << i18n("Ignoring non-existent file %1", file.m_filePath) << '\n';
0231             continue;
0232         }
0233 
0234         if (file.m_fileStatus == FileStatus::SymLink || file.m_fileStatus == FileStatus::Other) {
0235             err << i18n("Ignoring symlink/special file %1", file.m_filePath) << '\n';
0236             continue;
0237         }
0238 
0239         QJsonObject fileInfo;
0240         fileInfo[QStringLiteral("file")] = file.m_filePath;
0241         fileInfo[QStringLiteral("indexing")] = jsonIndexLevelValue[file.m_indexState];
0242         fileInfo[QStringLiteral("status")] = jsonIndexStateValue[file.m_indexState];
0243 
0244         filesInfo.append(fileInfo);
0245     }
0246 
0247     QJsonDocument json;
0248     json.setArray(filesInfo);
0249     QTextStream out(stdout);
0250     out << json.toJson(QJsonDocument::Indented);
0251 }
0252 
0253 int StatusCommand::exec(const QCommandLineParser& parser)
0254 {
0255     QTextStream out(stdout);
0256     QTextStream err(stderr);
0257 
0258     const QStringList allowedFormats({QStringLiteral("simple"), QStringLiteral("json"), QStringLiteral("multiline")});
0259     const QString format = parser.value(QStringLiteral("format"));
0260 
0261     if (!allowedFormats.contains(format)) {
0262         err << i18n("Output format \"%1\" is invalid, use one of:\n", format);
0263         for (const auto& format : allowedFormats) {
0264             err << i18nc("bullet list item with output format", "- %1\n", format);
0265         }
0266         return 1;
0267     }
0268 
0269     IndexerConfig cfg;
0270     if (!cfg.fileIndexingEnabled()) {
0271         err << i18n("Baloo is currently disabled. To enable, please run %1\n", QStringLiteral("balooctl enable"));
0272         return 1;
0273     }
0274 
0275     Database *db = globalDatabaseInstance();
0276     if (!db->open(Database::ReadOnlyDatabase)) {
0277         err << i18n("Baloo Index could not be opened\n");
0278         return 1;
0279     }
0280 
0281     Transaction tr(db, Transaction::ReadOnly);
0282 
0283     QStringList args = parser.positionalArguments();
0284     args.pop_front();
0285 
0286     if (args.isEmpty()) {
0287         org::kde::baloo::main mainInterface(QStringLiteral("org.kde.baloo"),
0288                                                     QStringLiteral("/"),
0289                                                     QDBusConnection::sessionBus());
0290 
0291         org::kde::baloo::scheduler schedulerinterface(QStringLiteral("org.kde.baloo"),
0292                                             QStringLiteral("/scheduler"),
0293                                             QDBusConnection::sessionBus());
0294 
0295         bool running = mainInterface.isValid();
0296 
0297         if (running) {
0298             org::kde::baloo::fileindexer indexerInterface(QStringLiteral("org.kde.baloo"),
0299             QStringLiteral("/fileindexer"),
0300             QDBusConnection::sessionBus());
0301 
0302             const QString currentFile = indexerInterface.currentFile();
0303 
0304             out << i18n("Baloo File Indexer is running\n");
0305             if (!currentFile.isEmpty()) {
0306                 out << i18n("Indexer state: %1", stateString(IndexerState::ContentIndexing)) << '\n';
0307                 out << i18nc("currently indexed file", "Indexing: %1", currentFile) << '\n';
0308             } else {
0309                 out << i18n("Indexer state: %1", stateString(schedulerinterface.state())) << '\n';
0310             }
0311         }
0312         else {
0313             out << i18n("Baloo File Indexer is not running\n");
0314         }
0315 
0316         uint phaseOne = tr.phaseOneSize();
0317         uint total = tr.size();
0318         uint failed = tr.failedIds(100).size();
0319 
0320         out << i18n("Total files indexed: %1", total) << '\n';
0321         out << i18n("Files waiting for content indexing: %1", phaseOne) << '\n';
0322         out << i18n("Files failed to index: %1", failed) << '\n';
0323 
0324         const QString path = fileIndexDbPath();
0325 
0326         const QFileInfo indexInfo(path + QLatin1String("/index"));
0327         const auto size = indexInfo.size();
0328         KFormat format(QLocale::system());
0329         if (size) {
0330             out << i18n("Current size of index is %1", format.formatByteSize(size, 2)) << '\n';
0331         } else {
0332             out << i18n("Index does not exist yet\n");
0333         }
0334     } else if (format == allowedFormats[0]){
0335         printSimpleFormat(tr, cfg, args);
0336     } else if (format == allowedFormats[1]){
0337         printJSON(tr, cfg, args);
0338     } else {
0339         printMultiLine(tr, cfg, args);
0340     }
0341 
0342     return 0;
0343 }