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(fd: fileno(stdout));
33 if(isTty) {
34 QString colorStart = QStringLiteral("\033[0;%1m").arg(a: 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(key: static_cast<KFileMetaData::Property::Property>(p), value: 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"),
62 KAboutLicense::GPL,
63 i18n("(c) 2012, Vishesh Handa"));
64
65 KAboutData::setApplicationData(aboutData);
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(commandLineOption: QCommandLineOption(QStringList() << QStringLiteral("x"),
70 i18n("Print internal info")));
71 parser.addOption(commandLineOption: QCommandLineOption(QStringList() << QStringLiteral("i"),
72 i18n("Arguments are interpreted as inode numbers (requires -d)")));
73 parser.addOption(commandLineOption: 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(exitCode: 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: &ok, base: 10);
91 if (!ok) {
92 devId = parser.value(QStringLiteral("d")).toULong(ok: &ok, base: 16);
93 }
94 }
95
96 if (useInodes && devId == 0) {
97 stream << i18n("Error: -i requires specifying a device (-d <deviceId>)") << '\n';
98 parser.showHelp(exitCode: 1);
99 }
100
101 Baloo::Database *db = Baloo::globalDatabaseInstance();
102 if (!db->open(mode: 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(fileName: url)) {
115 quint64 fsFid = Baloo::filePathToId(filePath: QFile::encodeName(fileName: url));
116 fid = tr.documentId(path: QFile::encodeName(fileName: url));
117 internalUrl = QFile::decodeName(localFileName: tr.documentUrl(id: 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(id: fid);
122 auto fsInode = Baloo::idToInode(id: fsFid);
123 auto dbDevId = Baloo::idToDeviceId(id: fid);
124 auto fsDevId = Baloo::idToDeviceId(id: 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: &ok, base: 10);
135 if (!ok) {
136 fid = url.toULongLong(ok: &ok, base: 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(localFileName: tr.documentUrl(id: fid));
143 internalUrl = url;
144 }
145
146 } else {
147 bool ok;
148 quint32 inode = url.toULong(ok: &ok, base: 10);
149 if (!ok) {
150 inode = url.toULong(ok: &ok, base: 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(localFileName: tr.documentUrl(id: fid));
159 internalUrl = url;
160 }
161
162 if (fid) {
163 stream << colorString(input: QString::number(fid, base: 16), color: 31) << ' ';
164 stream << colorString(input: QString::number(Baloo::idToDeviceId(id: fid)), color: 28) << ' ';
165 stream << colorString(input: QString::number(Baloo::idToInode(id: fid)), color: 28) << ' ';
166 }
167 if (fid && tr.hasDocument(id: fid)) {
168 stream << colorString(input: url, color: 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(id: fid);
182 stream << QStringLiteral("\tMtime: %1 ").arg(a: time.mTime)
183 << QDateTime::fromSecsSinceEpoch(secs: time.mTime).toString(format: Qt::ISODate)
184 << QStringLiteral("\n\tCtime: %1 ").arg(a: time.cTime)
185 << QDateTime::fromSecsSinceEpoch(secs: time.cTime).toString(format: Qt::ISODate)
186 << '\n';
187
188 const QJsonDocument jdoc = QJsonDocument::fromJson(json: tr.documentData(id: 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) {
197 QStringList list;
198 const auto vars = it.value().toList();
199 for (const QVariant& var : vars) {
200 list << var.toString();
201 }
202 str = list.join(sep: 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(docId: fid);
213 QVector<QByteArray> fileNameTerms = tr.documentFileNameTerms(docId: fid);
214 QVector<QByteArray> xAttrTerms = tr.documentXattrTerms(docId: fid);
215
216 auto join = [](const QVector<QByteArray>& v) {
217 QByteArray ba;
218 for (const QByteArray& arr : v) {
219 ba.append(a: arr);
220 ba.append(c: ' ');
221 }
222 return QString::fromUtf8(ba);
223 };
224
225 auto propertiesBegin = std::stable_partition(first: terms.begin(), last: terms.end(),
226 pred: [](const auto & t) { return t.isEmpty() || t[0] < 'A' || t[0] > 'Z'; });
227 const QVector<QByteArray> propertyTerms{propertiesBegin, terms.end()};
228 terms.erase(abegin: propertiesBegin, aend: 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(ba: arr.toPercentEncoding());
242 };
243
244 if (arr.length() < 1) {
245 auto error = QStringLiteral("malformed term (short): '%1'\n").arg(a: arrAsPrintable());
246 stream << errorPrefix.subs(a: error).toString();
247 continue;
248 }
249
250 const QString word = QString::fromUtf8(ba: 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(args: word, args: arrAsPrintable());
256 stream << errorPrefix.subs(a: error).toString();
257 continue;
258 }
259 const int posOfNonNumeric = word.indexOf(c: QLatin1Char('-'), from: 2);
260 if ((posOfNonNumeric < 0) || ((posOfNonNumeric + 1) == word.length())) {
261 auto error = QStringLiteral("malformed property term (no data): '%1' in '%2'\n").arg(args: word, args: arrAsPrintable());
262 stream << errorPrefix.subs(a: error).toString();
263 continue;
264 }
265
266 bool ok;
267 const QStringView prop = QStringView(word).mid(pos: 1, n: posOfNonNumeric - 1);
268 int propNum = prop.toInt(ok: &ok);
269 if (!ok) {
270 auto error = QStringLiteral("malformed property term (bad index): '%1' in '%2'\n").arg(args: prop, args: arrAsPrintable());
271 stream << errorPrefix.subs(a: error).toString();
272 continue;
273 }
274
275 const QString value = word.mid(position: posOfNonNumeric + 1);
276 propertyWords[propNum].append(t: value);
277 }
278 }
279
280 for (auto it = propertyWords.constBegin(); it != propertyWords.constEnd(); it++) {
281 auto prop = static_cast<KFileMetaData::Property::Property>(it.key());
282 KFileMetaData::PropertyInfo pi(prop);
283
284 stream << pi.name() << ": " << it.value().join(sep: QLatin1Char(' ')) << '\n';
285 }
286 }
287 }
288 stream.flush();
289 return 0;
290}
291

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