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 | |
26 | QString StatusCommand::command() |
27 | { |
28 | return QStringLiteral("status" ); |
29 | } |
30 | |
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 | |
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(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 | |
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, 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 | |
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, 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 | |
196 | void 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 | |
253 | int 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 | |