1/*
2 This file is part of the KDE Baloo project.
3 SPDX-FileCopyrightText: 2015 Vishesh Handa <vhanda@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-or-later
6*/
7
8#include "statuscommand.h"
9#include "indexerconfig.h"
10
11#include "global.h"
12#include "database.h"
13#include "transaction.h"
14#include "idutils.h"
15
16#include "fileindexerinterface.h"
17#include "schedulerinterface.h"
18#include "maininterface.h"
19#include "indexerstate.h"
20
21#include <KLocalizedString>
22#include <KFormat>
23
24using namespace Baloo;
25
26QString StatusCommand::command()
27{
28 return QStringLiteral("status");
29}
30
31QString StatusCommand::description()
32{
33 return i18n("Print the status of the Indexer");
34}
35
36class FileIndexStatus
37{
38public:
39 enum class FileStatus : uint8_t {
40 NonExisting,
41 Directory,
42 RegularFile,
43 SymLink,
44 Other,
45 };
46 enum class IndexStateReason : uint8_t {
47 NoFileOrDirectory,
48 ExcludedByPath, // FIXME - be more specific, requires changes to shouldBeIndexed(path)
49 WaitingForIndexingBoth,
50 WaitingForBasicIndexing,
51 BasicIndexingDone,
52 WaitingForContentIndexing,
53 FailedToIndex,
54 Done,
55 };
56 const QString m_filePath;
57 FileStatus m_fileStatus;
58 IndexStateReason m_indexState;
59 uint32_t m_dataSize;
60};
61
62FileIndexStatus collectFileStatus(Transaction& tr, IndexerConfig& cfg, const QString& file)
63{
64 using FileStatus = FileIndexStatus::FileStatus;
65 using IndexStateReason = FileIndexStatus::IndexStateReason;
66
67 bool onlyBasicIndexing = cfg.onlyBasicIndexing();
68
69 const QFileInfo fileInfo = QFileInfo(file);
70 const QString filePath = fileInfo.absoluteFilePath();
71 quint64 id = filePathToId(filePath: QFile::encodeName(fileName: filePath));
72 if (id == 0) {
73 return FileIndexStatus{.m_filePath: filePath, .m_fileStatus: FileStatus::NonExisting, .m_indexState: IndexStateReason::NoFileOrDirectory, .m_dataSize: 0};
74 }
75
76 FileStatus fileStatus = fileInfo.isSymLink() ? FileStatus::SymLink :
77 fileInfo.isFile() ? FileStatus::RegularFile :
78 fileInfo.isDir() ? FileStatus::Directory : FileStatus::Other;
79
80 if (fileStatus == FileStatus::Other || fileStatus == FileStatus::SymLink) {
81 return FileIndexStatus{.m_filePath: filePath, .m_fileStatus: fileStatus, .m_indexState: IndexStateReason::NoFileOrDirectory, .m_dataSize: 0};
82 }
83
84 if (!cfg.shouldBeIndexed(path: filePath)) {
85 return FileIndexStatus{.m_filePath: filePath, .m_fileStatus: fileStatus, .m_indexState: IndexStateReason::ExcludedByPath, .m_dataSize: 0};
86 }
87
88 if (onlyBasicIndexing || fileStatus == FileStatus::Directory) {
89 if (!tr.hasDocument(id)) {
90 return FileIndexStatus{.m_filePath: filePath, .m_fileStatus: fileStatus, .m_indexState: IndexStateReason::WaitingForBasicIndexing, .m_dataSize: 0};
91 } else {
92 return FileIndexStatus{.m_filePath: filePath, .m_fileStatus: fileStatus, .m_indexState: IndexStateReason::BasicIndexingDone, .m_dataSize: 0};
93 }
94 }
95
96 // File && shouldBeIndexed && contentIndexing
97 if (!tr.hasDocument(id)) {
98 return FileIndexStatus{.m_filePath: filePath, .m_fileStatus: fileStatus, .m_indexState: IndexStateReason::WaitingForIndexingBoth, .m_dataSize: 0};
99 } else if (tr.inPhaseOne(id)) {
100 return FileIndexStatus{.m_filePath: filePath, .m_fileStatus: fileStatus, .m_indexState: IndexStateReason::WaitingForContentIndexing, .m_dataSize: 0};
101 } else if (tr.hasFailed(id)) {
102 return FileIndexStatus{.m_filePath: filePath, .m_fileStatus: fileStatus, .m_indexState: IndexStateReason::FailedToIndex, .m_dataSize: 0};
103 } else {
104 uint32_t size = tr.documentData(id).size();
105 return FileIndexStatus{.m_filePath: filePath, .m_fileStatus: fileStatus, .m_indexState: IndexStateReason::Done, .m_dataSize: size};
106 }
107}
108
109void printMultiLine(Transaction& tr, IndexerConfig& cfg, const QStringList& args) {
110 using FileStatus = FileIndexStatus::FileStatus;
111 using IndexStateReason = FileIndexStatus::IndexStateReason;
112
113 QTextStream out(stdout);
114 QTextStream err(stderr);
115
116 const QMap<IndexStateReason, QString> basicIndexStateValue = {
117 { IndexStateReason::NoFileOrDirectory, i18n("File ignored") },
118 { IndexStateReason::ExcludedByPath, i18n("Basic Indexing: Disabled") },
119 { IndexStateReason::WaitingForIndexingBoth, i18n("Basic Indexing: Scheduled") },
120 { IndexStateReason::WaitingForBasicIndexing, i18n("Basic Indexing: Scheduled") },
121 { IndexStateReason::BasicIndexingDone, i18n("Basic Indexing: Done") },
122 { IndexStateReason::WaitingForContentIndexing, i18n("Basic Indexing: Done") },
123 { IndexStateReason::FailedToIndex, i18n("Basic Indexing: Done") },
124 { IndexStateReason::Done, i18n("Basic Indexing: Done") },
125 };
126
127 const QMap<IndexStateReason, QString> contentIndexStateValue = {
128 { IndexStateReason::NoFileOrDirectory, QString() },
129 { IndexStateReason::ExcludedByPath, QString() },
130 { IndexStateReason::WaitingForIndexingBoth, i18n("Content Indexing: Scheduled") },
131 { IndexStateReason::WaitingForBasicIndexing, QString() },
132 { IndexStateReason::BasicIndexingDone, QString() },
133 { IndexStateReason::WaitingForContentIndexing, i18n("Content Indexing: Scheduled") },
134 { IndexStateReason::FailedToIndex, i18n("Content Indexing: Failed") },
135 { IndexStateReason::Done, i18n("Content Indexing: Done") },
136 };
137
138 for (const auto& fileName : args) {
139 const auto file = collectFileStatus(tr, cfg, file: fileName);
140
141 if (file.m_fileStatus == FileStatus::NonExisting) {
142 err << i18n("Ignoring non-existent file %1", file.m_filePath) << '\n';
143 continue;
144 }
145
146 if (file.m_fileStatus == FileStatus::SymLink || file.m_fileStatus == FileStatus::Other) {
147 err << i18n("Ignoring symlink/special file %1", file.m_filePath) << '\n';
148 continue;
149 }
150
151 out << i18n("File: %1", file.m_filePath) << '\n';
152 out << basicIndexStateValue[file.m_indexState] << '\n';
153 const QString contentState = contentIndexStateValue[file.m_indexState];
154 if (!contentState.isEmpty()) {
155 out << contentState << '\n';
156 }
157 }
158}
159
160void printSimpleFormat(Transaction& tr, IndexerConfig& cfg, const QStringList& args) {
161 using FileStatus = FileIndexStatus::FileStatus;
162 using IndexStateReason = FileIndexStatus::IndexStateReason;
163
164 QTextStream out(stdout);
165 QTextStream err(stderr);
166
167 const QMap<IndexStateReason, QString> simpleIndexStateValue = {
168 { IndexStateReason::NoFileOrDirectory, QStringLiteral("No regular file or directory") },
169 { IndexStateReason::ExcludedByPath, QStringLiteral("Indexing disabled") },
170 { IndexStateReason::WaitingForIndexingBoth, QStringLiteral("Basic and Content indexing scheduled") },
171 { IndexStateReason::WaitingForBasicIndexing, QStringLiteral("Basic indexing scheduled") },
172 { IndexStateReason::BasicIndexingDone, QStringLiteral("Basic indexing done") },
173 { IndexStateReason::WaitingForContentIndexing, QStringLiteral("Content indexing scheduled") },
174 { IndexStateReason::FailedToIndex, QStringLiteral("Content indexing failed") },
175 { IndexStateReason::Done, QStringLiteral("Content indexing done") },
176 };
177
178 for (const auto& fileName : args) {
179 const auto file = collectFileStatus(tr, cfg, file: fileName);
180
181 if (file.m_fileStatus == FileStatus::NonExisting) {
182 err << i18n("Ignoring non-existent file %1", file.m_filePath) << '\n';
183 continue;
184 }
185
186 if (file.m_fileStatus == FileStatus::SymLink || file.m_fileStatus == FileStatus::Other) {
187 err << i18n("Ignoring symlink/special file %1", file.m_filePath) << '\n';
188 continue;
189 }
190
191 out << simpleIndexStateValue[file.m_indexState];
192 out << ": " << file.m_filePath << '\n';
193 }
194}
195
196void printJSON(Transaction& tr, IndexerConfig& cfg, const QStringList& args)
197{
198 using FileStatus = FileIndexStatus::FileStatus;
199 using IndexStateReason = FileIndexStatus::IndexStateReason;
200
201 QJsonArray filesInfo;
202 QTextStream err(stderr);
203
204 const QMap<IndexStateReason, QString> jsonIndexStateValue = {
205 { IndexStateReason::NoFileOrDirectory, QStringLiteral("nofile") },
206 { IndexStateReason::ExcludedByPath, QStringLiteral("disabled") },
207 { IndexStateReason::WaitingForIndexingBoth, QStringLiteral("scheduled") },
208 { IndexStateReason::WaitingForBasicIndexing, QStringLiteral("scheduled") },
209 { IndexStateReason::BasicIndexingDone, QStringLiteral("done") },
210 { IndexStateReason::WaitingForContentIndexing, QStringLiteral("scheduled") },
211 { IndexStateReason::FailedToIndex, QStringLiteral("failed") },
212 { IndexStateReason::Done, QStringLiteral("done") },
213 };
214
215 const QMap<IndexStateReason, QString> jsonIndexLevelValue = {
216 { IndexStateReason::NoFileOrDirectory, QStringLiteral("nofile") },
217 { IndexStateReason::ExcludedByPath, QStringLiteral("none") },
218 { IndexStateReason::WaitingForIndexingBoth, QStringLiteral("content") },
219 { IndexStateReason::WaitingForBasicIndexing, QStringLiteral("basic") },
220 { IndexStateReason::BasicIndexingDone, QStringLiteral("basic") },
221 { IndexStateReason::WaitingForContentIndexing, QStringLiteral("content") },
222 { IndexStateReason::FailedToIndex, QStringLiteral("content") },
223 { IndexStateReason::Done, QStringLiteral("content") },
224 };
225
226 for (const auto& fileName : args) {
227 const auto file = collectFileStatus(tr, cfg, file: fileName);
228
229 if (file.m_fileStatus == FileStatus::NonExisting) {
230 err << i18n("Ignoring non-existent file %1", file.m_filePath) << '\n';
231 continue;
232 }
233
234 if (file.m_fileStatus == FileStatus::SymLink || file.m_fileStatus == FileStatus::Other) {
235 err << i18n("Ignoring symlink/special file %1", file.m_filePath) << '\n';
236 continue;
237 }
238
239 QJsonObject fileInfo;
240 fileInfo[QStringLiteral("file")] = file.m_filePath;
241 fileInfo[QStringLiteral("indexing")] = jsonIndexLevelValue[file.m_indexState];
242 fileInfo[QStringLiteral("status")] = jsonIndexStateValue[file.m_indexState];
243
244 filesInfo.append(value: fileInfo);
245 }
246
247 QJsonDocument json;
248 json.setArray(filesInfo);
249 QTextStream out(stdout);
250 out << json.toJson(format: QJsonDocument::Indented);
251}
252
253int StatusCommand::exec(const QCommandLineParser& parser)
254{
255 QTextStream out(stdout);
256 QTextStream err(stderr);
257
258 const QStringList allowedFormats({QStringLiteral("simple"), QStringLiteral("json"), QStringLiteral("multiline")});
259 const QString format = parser.value(QStringLiteral("format"));
260
261 if (!allowedFormats.contains(str: format)) {
262 err << i18n("Output format \"%1\" is invalid, use one of:\n", format);
263 for (const auto& format : allowedFormats) {
264 err << i18nc("bullet list item with output format", "- %1\n", format);
265 }
266 return 1;
267 }
268
269 IndexerConfig cfg;
270 if (!cfg.fileIndexingEnabled()) {
271 err << i18n("Baloo is currently disabled. To enable, please run %1\n", QStringLiteral("balooctl enable"));
272 return 1;
273 }
274
275 Database *db = globalDatabaseInstance();
276 if (!db->open(mode: Database::ReadOnlyDatabase)) {
277 err << i18n("Baloo Index could not be opened\n");
278 return 1;
279 }
280
281 Transaction tr(db, Transaction::ReadOnly);
282
283 QStringList args = parser.positionalArguments();
284 args.pop_front();
285
286 if (args.isEmpty()) {
287 org::kde::baloo::main mainInterface(QStringLiteral("org.kde.baloo"),
288 QStringLiteral("/"),
289 QDBusConnection::sessionBus());
290
291 org::kde::baloo::scheduler schedulerinterface(QStringLiteral("org.kde.baloo"),
292 QStringLiteral("/scheduler"),
293 QDBusConnection::sessionBus());
294
295 bool running = mainInterface.isValid();
296
297 if (running) {
298 org::kde::baloo::fileindexer indexerInterface(QStringLiteral("org.kde.baloo"),
299 QStringLiteral("/fileindexer"),
300 QDBusConnection::sessionBus());
301
302 const QString currentFile = indexerInterface.currentFile();
303
304 out << i18n("Baloo File Indexer is running\n");
305 if (!currentFile.isEmpty()) {
306 out << i18n("Indexer state: %1", stateString(IndexerState::ContentIndexing)) << '\n';
307 out << i18nc("currently indexed file", "Indexing: %1", currentFile) << '\n';
308 } else {
309 out << i18n("Indexer state: %1", stateString(schedulerinterface.state())) << '\n';
310 }
311 }
312 else {
313 out << i18n("Baloo File Indexer is not running\n");
314 }
315
316 uint phaseOne = tr.phaseOneSize();
317 uint total = tr.size();
318 uint failed = tr.failedIds(limit: 100).size();
319
320 out << i18n("Total files indexed: %1", total) << '\n';
321 out << i18n("Files waiting for content indexing: %1", phaseOne) << '\n';
322 out << i18n("Files failed to index: %1", failed) << '\n';
323
324 const QString path = fileIndexDbPath();
325
326 const QFileInfo indexInfo(path + QLatin1String("/index"));
327 const auto size = indexInfo.size();
328 KFormat format(QLocale::system());
329 if (size) {
330 out << i18n("Current size of index is %1", format.formatByteSize(size, 2)) << '\n';
331 } else {
332 out << i18n("Index does not exist yet\n");
333 }
334 } else if (format == allowedFormats[0]){
335 printSimpleFormat(tr, cfg, args);
336 } else if (format == allowedFormats[1]){
337 printJSON(tr, cfg, args);
338 } else {
339 printMultiLine(tr, cfg, args);
340 }
341
342 return 0;
343}
344

source code of baloo/src/tools/balooctl/statuscommand.cpp