1 | /* |
2 | SPDX-FileCopyrightText: 2012-2015 Vishesh Handa <me@vhanda.in> |
3 | |
4 | SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL |
5 | */ |
6 | |
7 | #include <QCoreApplication> |
8 | #include <QCommandLineParser> |
9 | #include <QFile> |
10 | |
11 | #include <KAboutData> |
12 | #include <KLocalizedString> |
13 | #include <KFormat> |
14 | #include <QProcess> |
15 | #include <QTextStream> |
16 | #include <QFileInfo> |
17 | #include <QLocale> |
18 | |
19 | #include <QDBusConnection> |
20 | #include <QDBusConnectionInterface> |
21 | |
22 | #include "global.h" |
23 | #include "database.h" |
24 | #include "transaction.h" |
25 | #include "databasesize.h" |
26 | #include "config.h" |
27 | |
28 | #include "indexer.h" |
29 | #include "indexerconfig.h" |
30 | #include "idutils.h" |
31 | #include "fileindexerconfig.h" |
32 | #include "monitorcommand.h" |
33 | #include "schedulerinterface.h" |
34 | #include "maininterface.h" |
35 | #include "indexerstate.h" |
36 | #include "configcommand.h" |
37 | #include "statuscommand.h" |
38 | |
39 | using namespace Baloo; |
40 | |
41 | void start() |
42 | { |
43 | const QString exe = QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF "/baloo_file" ); |
44 | QProcess::startDetached(program: exe, arguments: QStringList()); |
45 | } |
46 | |
47 | int main(int argc, char* argv[]) |
48 | { |
49 | QCoreApplication app(argc, argv); |
50 | |
51 | KAboutData aboutData(QStringLiteral("baloo" ), i18n("balooctl" ), QStringLiteral(PROJECT_VERSION)); |
52 | aboutData.addAuthor(i18n("Vishesh Handa" ), task: QString(), QStringLiteral("vhanda@kde.org" )); |
53 | |
54 | KAboutData::setApplicationData(aboutData); |
55 | |
56 | QCommandLineParser parser; |
57 | parser.addPositionalArgument(QStringLiteral("command" ), i18n("The command to execute" )); |
58 | |
59 | parser.addPositionalArgument(QStringLiteral("status" ), i18n("Print the status of the indexer" )); |
60 | parser.addPositionalArgument(QStringLiteral("enable" ), i18n("Enable the file indexer" )); |
61 | parser.addPositionalArgument(QStringLiteral("disable" ), i18n("Disable the file indexer" )); |
62 | parser.addPositionalArgument(QStringLiteral("purge" ), i18n("Remove the index database" )); |
63 | parser.addPositionalArgument(QStringLiteral("suspend" ), i18n("Suspend the file indexer" )); |
64 | parser.addPositionalArgument(QStringLiteral("resume" ), i18n("Resume the file indexer" )); |
65 | parser.addPositionalArgument(QStringLiteral("check" ), i18n("Check for any unindexed files and index them" )); |
66 | parser.addPositionalArgument(QStringLiteral("index" ), i18n("Index the specified files" )); |
67 | parser.addPositionalArgument(QStringLiteral("clear" ), i18n("Forget the specified files" )); |
68 | parser.addPositionalArgument(QStringLiteral("config" ), i18n("Modify the Baloo configuration" )); |
69 | parser.addPositionalArgument(QStringLiteral("monitor" ), i18n("Monitor the file indexer" )); |
70 | parser.addPositionalArgument(QStringLiteral("indexSize" ), i18n("Display the disk space used by index" )); |
71 | parser.addPositionalArgument(QStringLiteral("failed" ), i18n("Display files which could not be indexed" )); |
72 | |
73 | QString statusFormatDescription = i18nc("Format to use for status command, %1|%2|%3 are option values, %4 is a CLI command" , |
74 | "Output format <%1|%2|%3>.\nThe default format is \"%1\".\nOnly applies to \"%4\"" , |
75 | QStringLiteral("multiline" ), |
76 | QStringLiteral("json" ), |
77 | QStringLiteral("simple" ), |
78 | QStringLiteral("balooctl status <file>" )); |
79 | parser.addOption(commandLineOption: {{QStringLiteral("f" ), QStringLiteral("format" )}, |
80 | statusFormatDescription, i18n("format" ), QStringLiteral("multiline" )}); |
81 | |
82 | parser.addVersionOption(); |
83 | parser.addHelpOption(); |
84 | |
85 | parser.process(app); |
86 | if (parser.positionalArguments().isEmpty()) { |
87 | parser.showHelp(exitCode: 1); |
88 | } |
89 | |
90 | QTextStream out(stdout); |
91 | |
92 | QString command = parser.positionalArguments().first(); |
93 | |
94 | org::kde::baloo::main mainInterface(QStringLiteral("org.kde.baloo" ), |
95 | QStringLiteral("/" ), |
96 | QDBusConnection::sessionBus()); |
97 | |
98 | org::kde::baloo::scheduler schedulerinterface(QStringLiteral("org.kde.baloo" ), |
99 | QStringLiteral("/scheduler" ), |
100 | QDBusConnection::sessionBus()); |
101 | |
102 | if (command == QLatin1String("config" )) { |
103 | ConfigCommand command; |
104 | return command.exec(parser); |
105 | } |
106 | |
107 | if (command == QLatin1String("status" )) { |
108 | StatusCommand commandStatus; |
109 | return commandStatus.exec(parser); |
110 | } |
111 | |
112 | if (command == QLatin1String("enable" ) || command == QLatin1String("disable" )) { |
113 | bool isEnabled = false; |
114 | if (command == QLatin1String("enable" )) { |
115 | isEnabled = true; |
116 | } |
117 | else if (command == QLatin1String("disable" )) { |
118 | isEnabled = false; |
119 | } |
120 | |
121 | IndexerConfig cfg; |
122 | cfg.setFileIndexingEnabled(isEnabled); |
123 | |
124 | if (isEnabled) { |
125 | bool running = mainInterface.isValid(); |
126 | if (running) { |
127 | out << "File Indexer already running\n" ; |
128 | } else { |
129 | out << "Enabling and starting the File Indexer\n" ; |
130 | start(); |
131 | } |
132 | } else { |
133 | out << "Disabling and stopping the File Indexer\n" ; |
134 | |
135 | mainInterface.quit(); |
136 | } |
137 | |
138 | return 0; |
139 | } |
140 | |
141 | if (command == QLatin1String("purge" )) { |
142 | bool running = mainInterface.isValid(); |
143 | |
144 | if (running) { |
145 | mainInterface.quit(); |
146 | out << "Stopping the File Indexer ..." ; |
147 | for (int i = 5 * 60; i; --i) { |
148 | QCoreApplication::processEvents(); |
149 | if (!mainInterface.isValid()) { |
150 | break; |
151 | } |
152 | out << "." << Qt::flush; |
153 | QThread::msleep(200); |
154 | } |
155 | if (!mainInterface.isValid()) { |
156 | out << " - done\n" ; |
157 | } else { |
158 | out << " - failed to stop!\n" ; |
159 | return 1; |
160 | } |
161 | } |
162 | |
163 | const QString path = fileIndexDbPath() + QStringLiteral("/index" ); |
164 | QFile(path).remove(); |
165 | out << "Deleted the index database\n" ; |
166 | |
167 | if (running) { |
168 | start(); |
169 | out << "Restarting the File Indexer\n" ; |
170 | } |
171 | |
172 | return 0; |
173 | } |
174 | |
175 | if (command == QLatin1String("suspend" )) { |
176 | schedulerinterface.suspend(); |
177 | out << "File Indexer suspended\n" ; |
178 | return 0; |
179 | } |
180 | |
181 | if (command == QLatin1String("resume" )) { |
182 | schedulerinterface.resume(); |
183 | out << "File Indexer resumed\n" ; |
184 | return 0; |
185 | } |
186 | |
187 | if (command == QLatin1String("check" )) { |
188 | schedulerinterface.checkUnindexedFiles(); |
189 | out << "Started search for unindexed files\n" ; |
190 | return 0; |
191 | } |
192 | |
193 | if (command == QLatin1String("index" )) { |
194 | if (parser.positionalArguments().size() < 2) { |
195 | out << "Please enter a filename to index\n" ; |
196 | return 1; |
197 | } |
198 | |
199 | Database *db = globalDatabaseInstance(); |
200 | if (!db->open(mode: Database::ReadWriteDatabase)) { |
201 | out << "Baloo Index could not be opened\n" ; |
202 | return 1; |
203 | } |
204 | |
205 | Transaction tr(db, Transaction::ReadWrite); |
206 | |
207 | for (int i = 1; i < parser.positionalArguments().size(); ++i) { |
208 | const QString url = QFileInfo(parser.positionalArguments().at(i)).absoluteFilePath(); |
209 | quint64 id = filePathToId(filePath: QFile::encodeName(fileName: url)); |
210 | if (id == 0) { |
211 | out << "Could not stat file: " << url << '\n'; |
212 | continue; |
213 | } |
214 | if (tr.inPhaseOne(id)) { |
215 | out << "Skipping: " << url << " Reason: Already scheduled for indexing\n" ; |
216 | continue; |
217 | } |
218 | if (!tr.documentData(id).isEmpty()) { |
219 | out << "Skipping: " << url << " Reason: Already indexed\n" ; |
220 | continue; |
221 | } |
222 | Indexer indexer(url, &tr); |
223 | out << "Indexing " << url << '\n'; |
224 | indexer.index(); |
225 | } |
226 | tr.commit(); |
227 | out << "File(s) indexed\n" ; |
228 | |
229 | return 0; |
230 | } |
231 | |
232 | if (command == QLatin1String("clear" )) { |
233 | if (parser.positionalArguments().size() < 2) { |
234 | out << "Please enter a filename to index\n" ; |
235 | return 1; |
236 | } |
237 | |
238 | Database *db = globalDatabaseInstance(); |
239 | if (!db->open(mode: Database::ReadWriteDatabase)) { |
240 | out << "Baloo Index could not be opened\n" ; |
241 | return 1; |
242 | } |
243 | |
244 | Transaction tr(db, Transaction::ReadWrite); |
245 | |
246 | for (int i = 1; i < parser.positionalArguments().size(); ++i) { |
247 | const QString url = QFileInfo(parser.positionalArguments().at(i)).absoluteFilePath(); |
248 | quint64 id = filePathToId(filePath: QFile::encodeName(fileName: url)); |
249 | if (id == 0) { |
250 | id = tr.documentId(path: QFile::encodeName(fileName: url)); |
251 | if (id == 0) { |
252 | out << "File not found on filesystem or in DB: " << url << '\n'; |
253 | continue; |
254 | } else { |
255 | out << "File has been deleted, clearing from DB: " << url << '\n'; |
256 | } |
257 | } else { |
258 | out << "Clearing " << url << '\n'; |
259 | } |
260 | |
261 | tr.removeDocument(id); |
262 | } |
263 | tr.commit(); |
264 | out << "File(s) cleared\n" ; |
265 | |
266 | return 0; |
267 | } |
268 | |
269 | if (command == QLatin1String("failed" )) { |
270 | Database *db = globalDatabaseInstance(); |
271 | if (!db->open(mode: Database::ReadOnlyDatabase)) { |
272 | out << "Baloo Index could not be opened\n" ; |
273 | return 1; |
274 | } |
275 | |
276 | Transaction tr(db, Transaction::ReadOnly); |
277 | |
278 | const quint64 limit = 128; |
279 | const QVector<quint64> failedIds = tr.failedIds(limit); |
280 | if (failedIds.isEmpty()) { |
281 | out << "All Files were indexed successfully\n" ; |
282 | return 0; |
283 | } |
284 | |
285 | out << "The following files could not be indexed:\n" ; |
286 | for (auto id : failedIds) { |
287 | out << tr.documentUrl(id) << '\n'; |
288 | } |
289 | if (failedIds.size() == limit) { |
290 | out << "... list truncated\n" ; |
291 | } |
292 | return 0; |
293 | } |
294 | |
295 | if (command == QLatin1String("indexSize" )) { |
296 | Database *db = globalDatabaseInstance(); |
297 | if (!db->open(mode: Database::ReadOnlyDatabase)) { |
298 | out << "Baloo Index could not be opened\n" ; |
299 | return 1; |
300 | } |
301 | |
302 | DatabaseSize size; |
303 | { |
304 | Transaction tr(db, Transaction::ReadOnly); |
305 | size = tr.dbSize(); |
306 | } |
307 | uint totalDataSize = size.expectedSize; |
308 | |
309 | KFormat format(QLocale::system()); |
310 | auto prFunc = [&](const QString& name, uint size) { |
311 | out.setFieldWidth(20); |
312 | out << name; |
313 | out.setFieldWidth(0); |
314 | out << ":" ; |
315 | out.setFieldWidth(15); |
316 | out << format.formatByteSize(size, precision: 2); |
317 | out.setFieldWidth(10); |
318 | out << QString::number((100.0 * size / totalDataSize), format: 'f', precision: 3); |
319 | out.setFieldWidth(0); |
320 | out << " %\n" ; |
321 | }; |
322 | |
323 | out << "File Size: " << format.formatByteSize(size: size.actualSize, precision: 2) << "\n" ; |
324 | out << "Used: " << format.formatByteSize(size: totalDataSize, precision: 2) << "\n\n" ; |
325 | prFunc(QStringLiteral("PostingDB" ), size.postingDb); |
326 | prFunc(QStringLiteral("PositionDB" ), size.positionDb); |
327 | prFunc(QStringLiteral("DocTerms" ), size.docTerms); |
328 | prFunc(QStringLiteral("DocFilenameTerms" ), size.docFilenameTerms); |
329 | prFunc(QStringLiteral("DocXattrTerms" ), size.docXattrTerms); |
330 | prFunc(QStringLiteral("IdTree" ), size.idTree); |
331 | prFunc(QStringLiteral("IdFileName" ), size.idFilename); |
332 | prFunc(QStringLiteral("DocTime" ), size.docTime); |
333 | prFunc(QStringLiteral("DocData" ), size.docData); |
334 | prFunc(QStringLiteral("ContentIndexingDB" ), size.contentIndexingIds); |
335 | prFunc(QStringLiteral("FailedIdsDB" ), size.failedIds); |
336 | prFunc(QStringLiteral("MTimeDB" ), size.mtimeDb); |
337 | |
338 | return 0; |
339 | } |
340 | |
341 | if (command == QLatin1String("monitor" )) { |
342 | MonitorCommand mon; |
343 | return mon.exec(parser); |
344 | } |
345 | |
346 | /* |
347 | TODO: Make separate executable |
348 | if (command == QLatin1String("checkDb")) { |
349 | Database *db = globalDatabaseInstance(); |
350 | if (!db->open(Database::ReadOnlyDatabase)) { |
351 | out << "Baloo Index could not be opened\n"; |
352 | return 1; |
353 | } |
354 | |
355 | Transaction tr(db, Transaction::ReadOnly); |
356 | tr.checkPostingDbinTermsDb(); |
357 | tr.checkTermsDbinPostingDb(); |
358 | out << "Checking file paths .. "<< '\n'; |
359 | tr.checkFsTree(); |
360 | return 0; |
361 | } |
362 | */ |
363 | |
364 | parser.showHelp(exitCode: 1); |
365 | return 0; |
366 | } |
367 | |