Baloo

tools/balooshow/main.cpp
1/*
2 SPDX-FileCopyrightText: 2012-2013 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 <algorithm>
8
9#include <QCoreApplication>
10#include <QCommandLineParser>
11#include <QCommandLineOption>
12#include <QDateTime>
13#include <QFile>
14#include <QTextStream>
15
16#include <KAboutData>
17#include <KLocalizedString>
18
19#include <QJsonDocument>
20#include <QJsonObject>
21
22#include "global.h"
23#include "idutils.h"
24#include "database.h"
25#include "transaction.h"
26
27#include <unistd.h>
28#include <KFileMetaData/PropertyInfo>
29
30QString colorString(const QString& input, int color)
31{
32 static bool isTty = isatty(fileno(stdout));
33 if(isTty) {
34 QString colorStart = QStringLiteral("\033[0;%1m").arg(color);
35 QLatin1String colorEnd("\033[0;0m");
36
37 return colorStart + input + colorEnd;
38 } else {
39 return input;
40 }
41}
42
43inline KFileMetaData::PropertyMultiMap variantToPropertyMultiMap(const QVariantMap &varMap)
44{
45 KFileMetaData::PropertyMultiMap propMap;
46 QVariantMap::const_iterator it = varMap.constBegin();
47 for (; it != varMap.constEnd(); ++it) {
48 int p = it.key().toInt();
49 propMap.insert(static_cast<KFileMetaData::Property::Property>(p), it.value());
50 }
51 return propMap;
52}
53
54int main(int argc, char* argv[])
55{
56 QCoreApplication app(argc, argv);
57
58 KAboutData aboutData(QStringLiteral("balooshow"),
59 i18n("Baloo Show"),
60 QStringLiteral(PROJECT_VERSION),
61 i18n("The Baloo data Viewer - A debugging tool"),
63 i18n("(c) 2012, Vishesh Handa"));
64
66
67 QCommandLineParser parser;
68 parser.addPositionalArgument(QStringLiteral("files"), i18n("Urls, document ids or inodes of the files"), QStringLiteral("[file|id|inode...]"));
69 parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("x"),
70 i18n("Print internal info")));
71 parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("i"),
72 i18n("Arguments are interpreted as inode numbers (requires -d)")));
73 parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("d"),
74 i18n("Device id for the files"), QStringLiteral("deviceId"), QString()));
75 parser.addHelpOption();
76 parser.process(app);
77
78 const QStringList args = parser.positionalArguments();
79
80 if (args.isEmpty()) {
81 parser.showHelp(1);
82 }
83
84 QTextStream stream(stdout);
85
86 bool useInodes = parser.isSet(QStringLiteral("i"));
87 quint32 devId;
88 if (useInodes) {
89 bool ok;
90 devId = parser.value(QStringLiteral("d")).toULong(&ok, 10);
91 if (!ok) {
92 devId = parser.value(QStringLiteral("d")).toULong(&ok, 16);
93 }
94 }
95
96 if (useInodes && devId == 0) {
97 stream << i18n("Error: -i requires specifying a device (-d <deviceId>)") << '\n';
98 parser.showHelp(1);
99 }
100
101 Baloo::Database *db = Baloo::globalDatabaseInstance();
102 if (!db->open(Baloo::Database::ReadOnlyDatabase)) {
103 stream << i18n("The Baloo index could not be opened. Please run \"%1\" to see if Baloo is enabled and working.",
104 QStringLiteral("balooctl status")) << '\n';
105 return 1;
106 }
107
108 Baloo::Transaction tr(db, Baloo::Transaction::ReadOnly);
109
110 for (QString url : args) {
111 quint64 fid = 0;
112 QString internalUrl;
113 if (!useInodes) {
114 if (QFile::exists(url)) {
115 quint64 fsFid = Baloo::filePathToId(QFile::encodeName(url));
116 fid = tr.documentId(QFile::encodeName(url));
117 internalUrl = QFile::decodeName(tr.documentUrl(fsFid));
118
119 if (fid && fid != fsFid) {
120 stream << i18n("The document IDs of the Baloo DB and the filesystem are different:") << '\n';
121 auto dbInode = Baloo::idToInode(fid);
122 auto fsInode = Baloo::idToInode(fsFid);
123 auto dbDevId = Baloo::idToDeviceId(fid);
124 auto fsDevId = Baloo::idToDeviceId(fsFid);
125
126 stream << "Url: " << url << "\n";
127 stream << "ID: " << fid << " (DB) <-> " << fsFid << " (FS)\n";
128 stream << "Inode: " << dbInode << " (DB) " << (dbInode == fsInode ? "== " : "<-> ") << fsInode << " (FS)\n";
129 stream << "DeviceID: " << dbDevId << " (DB) " << (dbDevId == fsDevId ? "== " : "<-> ") << fsDevId << " (FS)\n";
130 }
131 fid = fsFid;
132 } else {
133 bool ok;
134 fid = url.toULongLong(&ok, 10);
135 if (!ok) {
136 fid = url.toULongLong(&ok, 16);
137 }
138 if (!ok) {
139 stream << i18n("%1: Not a valid url or document id", url) << '\n';
140 continue;
141 }
142 url = QFile::decodeName(tr.documentUrl(fid));
143 internalUrl = url;
144 }
145
146 } else {
147 bool ok;
148 quint32 inode = url.toULong(&ok, 10);
149 if (!ok) {
150 inode = url.toULong(&ok, 16);
151 }
152 if (!ok) {
153 stream << i18n("%1: Failed to parse inode number", url) << '\n';
154 continue;
155 }
156
157 fid = Baloo::devIdAndInodeToId(devId, inode);
158 url = QFile::decodeName(tr.documentUrl(fid));
159 internalUrl = url;
160 }
161
162 if (fid) {
163 stream << colorString(QString::number(fid, 16), 31) << ' ';
164 stream << colorString(QString::number(Baloo::idToDeviceId(fid)), 28) << ' ';
165 stream << colorString(QString::number(Baloo::idToInode(fid)), 28) << ' ';
166 }
167 if (fid && tr.hasDocument(fid)) {
168 stream << colorString(url, 32);
169 if (!internalUrl.isEmpty() && internalUrl != url) {
170 // The document is know by a different name inside the DB,
171 // e.g. a hardlink, or untracked rename
172 stream << QLatin1String(" [") << internalUrl << QLatin1Char(']');
173 }
174 stream << '\n';
175 }
176 else {
177 stream << i18n("%1: No index information found", url) << '\n';
178 continue;
179 }
180
181 Baloo::DocumentTimeDB::TimeInfo time = tr.documentTimeInfo(fid);
182 stream << QStringLiteral("\tMtime: %1 ").arg(time.mTime)
184 << QStringLiteral("\n\tCtime: %1 ").arg(time.cTime)
186 << '\n';
187
188 const QJsonDocument jdoc = QJsonDocument::fromJson(tr.documentData(fid));
189 const QVariantMap varMap = jdoc.object().toVariantMap();
190 KFileMetaData::PropertyMultiMap propMap = variantToPropertyMultiMap(varMap);
191 if (!propMap.isEmpty()) {
192 stream << "\tCached properties:" << '\n';
193 }
194 for (auto it = propMap.constBegin(); it != propMap.constEnd(); ++it) {
195 QString str;
196 if (it.value().typeId() == QMetaType::QVariantList) {
198 const auto vars = it.value().toList();
199 for (const QVariant& var : vars) {
200 list << var.toString();
201 }
202 str = list.join(QLatin1String(", "));
203 } else {
204 str = it.value().toString();
205 }
206
207 KFileMetaData::PropertyInfo pi(it.key());
208 stream << "\t\t" << pi.displayName() << ": " << str << '\n';
209 }
210
211 if (parser.isSet(QStringLiteral("x"))) {
212 QVector<QByteArray> terms = tr.documentTerms(fid);
213 QVector<QByteArray> fileNameTerms = tr.documentFileNameTerms(fid);
214 QVector<QByteArray> xAttrTerms = tr.documentXattrTerms(fid);
215
216 auto join = [](const QVector<QByteArray>& v) {
217 QByteArray ba;
218 for (const QByteArray& arr : v) {
219 ba.append(arr);
220 ba.append(' ');
221 }
222 return QString::fromUtf8(ba);
223 };
224
225 auto propertiesBegin = std::stable_partition(terms.begin(), terms.end(),
226 [](const auto & t) { return t.isEmpty() || t[0] < 'A' || t[0] > 'Z'; });
227 const QVector<QByteArray> propertyTerms{propertiesBegin, terms.end()};
228 terms.erase(propertiesBegin, terms.end());
229
230 stream << "\n" << i18n("Internal Info") << "\n";
231 stream << i18n("File Name Terms: %1", join(fileNameTerms)) << "\n";
232 stream << i18n("%1 Terms: %2", QStringLiteral("XAttr"), join(xAttrTerms)) << '\n';
233 stream << i18n("Plain Text Terms: %1", join(terms)) << "\n";
234 stream << i18n("Property Terms: %1", join(propertyTerms)) << "\n";
235
236 QHash<int, QStringList> propertyWords;
237 KLocalizedString errorPrefix = ki18nc("Prefix string for internal errors", "Internal Error - %1");
238
239 for (const QByteArray& arr : propertyTerms) {
240 auto arrAsPrintable = [arr]() {
241 return QString::fromLatin1(arr.toPercentEncoding());
242 };
243
244 if (arr.length() < 1) {
245 auto error = QStringLiteral("malformed term (short): '%1'\n").arg(arrAsPrintable());
246 stream << errorPrefix.subs(error).toString();
247 continue;
248 }
249
250 const QString word = QString::fromUtf8(arr);
251
252 if (word[0] == QLatin1Char('X')) {
253 if (word.length() < 4) {
254 // 'X<num>-<value>
255 auto error = QStringLiteral("malformed property term (short): '%1' in '%2'\n").arg(word, arrAsPrintable());
256 stream << errorPrefix.subs(error).toString();
257 continue;
258 }
259 const int posOfNonNumeric = word.indexOf(QLatin1Char('-'), 2);
260 if ((posOfNonNumeric < 0) || ((posOfNonNumeric + 1) == word.length())) {
261 auto error = QStringLiteral("malformed property term (no data): '%1' in '%2'\n").arg(word, arrAsPrintable());
262 stream << errorPrefix.subs(error).toString();
263 continue;
264 }
265
266 bool ok;
267 const QStringView prop = QStringView(word).mid(1, posOfNonNumeric - 1);
268 int propNum = prop.toInt(&ok);
269 if (!ok) {
270 auto error = QStringLiteral("malformed property term (bad index): '%1' in '%2'\n").arg(prop, arrAsPrintable());
271 stream << errorPrefix.subs(error).toString();
272 continue;
273 }
274
275 const QString value = word.mid(posOfNonNumeric + 1);
276 propertyWords[propNum].append(value);
277 }
278 }
279
280 for (auto it = propertyWords.constBegin(); it != propertyWords.constEnd(); it++) {
281 auto prop = static_cast<KFileMetaData::Property::Property>(it.key());
283
284 stream << pi.name() << ": " << it.value().join(QLatin1Char(' ')) << '\n';
285 }
286 }
287 }
288 stream.flush();
289 return 0;
290}
static void setApplicationData(const KAboutData &aboutData)
QString toString() const
KLocalizedString subs(const KLocalizedString &a, int fieldWidth=0, QChar fillChar=QLatin1Char(' ')) const
KLocalizedString KI18N_EXPORT ki18nc(const char *context, const char *text)
QString i18n(const char *text, const TYPE &arg...)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QByteArray & append(QByteArrayView data)
QCommandLineOption addHelpOption()
bool addOption(const QCommandLineOption &option)
void addPositionalArgument(const QString &name, const QString &description, const QString &syntax)
bool isSet(const QCommandLineOption &option) const const
QStringList positionalArguments() const const
void process(const QCoreApplication &app)
void showHelp(int exitCode)
QString value(const QCommandLineOption &option) const const
QDateTime fromSecsSinceEpoch(qint64 secs)
QString toString(QStringView format, QCalendar cal) const const
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
bool exists() const const
const_iterator constBegin() const const
const_iterator constEnd() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QVariantMap toVariantMap() const const
iterator begin()
iterator end()
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
T value(qsizetype i) const const
QString arg(Args &&... args) const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
ulong toULong(bool *ok, int base) const const
QString join(QChar separator) const const
QStringView mid(qsizetype start, qsizetype length) const const
int toInt(bool *ok, int base) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:52:28 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.