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
39using namespace Baloo;
40
41void start()
42{
43 const QString exe = QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF "/baloo_file");
44 QProcess::startDetached(program: exe, arguments: QStringList());
45}
46
47int 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

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