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
24 using namespace Baloo;
25
command()26 QString StatusCommand::command()
27 {
28 return QStringLiteral("status");
29 }
30
description()31 QString StatusCommand::description()
32 {
33 return i18n("Print the status of the Indexer");
34 }
35
36 class FileIndexStatus
37 {
38 public:
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
collectFileStatus(Transaction & tr,IndexerConfig & cfg,const QString & file)62 FileIndexStatus 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(QFile::encodeName(filePath));
72 if (id == 0) {
73 return FileIndexStatus{filePath, FileStatus::NonExisting, IndexStateReason::NoFileOrDirectory, 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{filePath, fileStatus, IndexStateReason::NoFileOrDirectory, 0};
82 }
83
84 if (!cfg.shouldBeIndexed(filePath)) {
85 return FileIndexStatus{filePath, fileStatus, IndexStateReason::ExcludedByPath, 0};
86 }
87
88 if (onlyBasicIndexing || fileStatus == FileStatus::Directory) {
89 if (!tr.hasDocument(id)) {
90 return FileIndexStatus{filePath, fileStatus, IndexStateReason::WaitingForBasicIndexing, 0};
91 } else {
92 return FileIndexStatus{filePath, fileStatus, IndexStateReason::BasicIndexingDone, 0};
93 }
94 }
95
96 // File && shouldBeIndexed && contentIndexing
97 if (!tr.hasDocument(id)) {
98 return FileIndexStatus{filePath, fileStatus, IndexStateReason::WaitingForIndexingBoth, 0};
99 } else if (tr.inPhaseOne(id)) {
100 return FileIndexStatus{filePath, fileStatus, IndexStateReason::WaitingForContentIndexing, 0};
101 } else if (tr.hasFailed(id)) {
102 return FileIndexStatus{filePath, fileStatus, IndexStateReason::FailedToIndex, 0};
103 } else {
104 uint32_t size = tr.documentData(id).size();
105 return FileIndexStatus{filePath, fileStatus, IndexStateReason::Done, size};
106 }
107 }
108
printMultiLine(Transaction & tr,IndexerConfig & cfg,const QStringList & args)109 void 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, 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
printSimpleFormat(Transaction & tr,IndexerConfig & cfg,const QStringList & args)160 void 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, 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
printJSON(Transaction & tr,IndexerConfig & cfg,const QStringList & args)196 void printJSON(Transaction& tr, IndexerConfig& cfg, const QStringList& args)
197 {
198
199 using FileStatus = FileIndexStatus::FileStatus;
200 using IndexStateReason = FileIndexStatus::IndexStateReason;
201
202 QJsonArray filesInfo;
203 QTextStream err(stderr);
204
205 const QMap<IndexStateReason, QString> jsonIndexStateValue = {
206 { IndexStateReason::NoFileOrDirectory, QStringLiteral("nofile") },
207 { IndexStateReason::ExcludedByPath, QStringLiteral("disabled") },
208 { IndexStateReason::WaitingForIndexingBoth, QStringLiteral("scheduled") },
209 { IndexStateReason::WaitingForBasicIndexing, QStringLiteral("scheduled") },
210 { IndexStateReason::BasicIndexingDone, QStringLiteral("done") },
211 { IndexStateReason::WaitingForContentIndexing, QStringLiteral("scheduled") },
212 { IndexStateReason::FailedToIndex, QStringLiteral("failed") },
213 { IndexStateReason::Done, QStringLiteral("done") },
214 };
215
216 const QMap<IndexStateReason, QString> jsonIndexLevelValue = {
217 { IndexStateReason::NoFileOrDirectory, QStringLiteral("nofile") },
218 { IndexStateReason::ExcludedByPath, QStringLiteral("none") },
219 { IndexStateReason::WaitingForIndexingBoth, QStringLiteral("content") },
220 { IndexStateReason::WaitingForBasicIndexing, QStringLiteral("basic") },
221 { IndexStateReason::BasicIndexingDone, QStringLiteral("basic") },
222 { IndexStateReason::WaitingForContentIndexing, QStringLiteral("content") },
223 { IndexStateReason::FailedToIndex, QStringLiteral("content") },
224 { IndexStateReason::Done, QStringLiteral("content") },
225 };
226
227 for (const auto& fileName : args) {
228 const auto file = collectFileStatus(tr, cfg, fileName);
229
230 if (file.m_fileStatus == FileStatus::NonExisting) {
231 err << i18n("Ignoring non-existent file %1", file.m_filePath) << '\n';
232 continue;
233 }
234
235 if (file.m_fileStatus == FileStatus::SymLink || file.m_fileStatus == FileStatus::Other) {
236 err << i18n("Ignoring symlink/special file %1", file.m_filePath) << '\n';
237 continue;
238 }
239
240 QJsonObject fileInfo;
241 fileInfo[QStringLiteral("file")] = file.m_filePath;
242 fileInfo[QStringLiteral("indexing")] = jsonIndexLevelValue[file.m_indexState];
243 fileInfo[QStringLiteral("status")] = jsonIndexStateValue[file.m_indexState];
244
245 filesInfo.append(fileInfo);
246 }
247
248 QJsonDocument json;
249 json.setArray(filesInfo);
250 QTextStream out(stdout);
251 out << json.toJson(QJsonDocument::Indented);
252 }
253
exec(const QCommandLineParser & parser)254 int StatusCommand::exec(const QCommandLineParser& parser)
255 {
256 QTextStream out(stdout);
257 QTextStream err(stderr);
258
259 const QStringList allowedFormats({QStringLiteral("simple"), QStringLiteral("json"), QStringLiteral("multiline")});
260 const QString format = parser.value(QStringLiteral("format"));
261
262 if (!allowedFormats.contains(format)) {
263 err << i18n("Output format \"%1\" is invalid, use one of:\n", format);
264 for (const auto& format : allowedFormats) {
265 err << i18nc("bullet list item with output format", "- %1\n", format);
266 }
267 return 1;
268 }
269
270 IndexerConfig cfg;
271 if (!cfg.fileIndexingEnabled()) {
272 err << i18n("Baloo is currently disabled. To enable, please run %1\n", QStringLiteral("balooctl enable"));
273 return 1;
274 }
275
276 Database *db = globalDatabaseInstance();
277 if (!db->open(Database::ReadOnlyDatabase)) {
278 err << i18n("Baloo Index could not be opened\n");
279 return 1;
280 }
281
282 Transaction tr(db, Transaction::ReadOnly);
283
284 QStringList args = parser.positionalArguments();
285 args.pop_front();
286
287 if (args.isEmpty()) {
288 org::kde::baloo::main mainInterface(QStringLiteral("org.kde.baloo"),
289 QStringLiteral("/"),
290 QDBusConnection::sessionBus());
291
292 org::kde::baloo::scheduler schedulerinterface(QStringLiteral("org.kde.baloo"),
293 QStringLiteral("/scheduler"),
294 QDBusConnection::sessionBus());
295
296 bool running = mainInterface.isValid();
297
298 if (running) {
299 org::kde::baloo::fileindexer indexerInterface(QStringLiteral("org.kde.baloo"),
300 QStringLiteral("/fileindexer"),
301 QDBusConnection::sessionBus());
302
303 const QString currentFile = indexerInterface.currentFile();
304
305 out << i18n("Baloo File Indexer is running\n");
306 if (!currentFile.isEmpty()) {
307 out << i18n("Indexer state: %1", stateString(IndexerState::ContentIndexing)) << '\n';
308 out << i18nc("currently indexed file", "Indexing: %1", currentFile) << '\n';
309 } else {
310 out << i18n("Indexer state: %1", stateString(schedulerinterface.state())) << '\n';
311 }
312 }
313 else {
314 out << i18n("Baloo File Indexer is not running\n");
315 }
316
317 uint phaseOne = tr.phaseOneSize();
318 uint total = tr.size();
319 uint failed = tr.failedIds(100).size();
320
321 out << i18n("Total files indexed: %1", total) << '\n';
322 out << i18n("Files waiting for content indexing: %1", phaseOne) << '\n';
323 out << i18n("Files failed to index: %1", failed) << '\n';
324
325 const QString path = fileIndexDbPath();
326
327 const QFileInfo indexInfo(path + QLatin1String("/index"));
328 const auto size = indexInfo.size();
329 KFormat format(QLocale::system());
330 if (size) {
331 out << i18n("Current size of index is %1", format.formatByteSize(size, 2)) << '\n';
332 } else {
333 out << i18n("Index does not exist yet\n");
334 }
335 } else if (format == allowedFormats[0]){
336 printSimpleFormat(tr, cfg, args);
337 } else if (format == allowedFormats[1]){
338 printJSON(tr, cfg, args);
339 } else {
340 printMultiLine(tr, cfg, args);
341 }
342
343 return 0;
344 }
345