Baloo

tools/balooshow/main.cpp
1 /*
2  SPDX-FileCopyrightText: 2012-2013 Vishesh Handa <[email protected]>
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 
30 QString 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 
43 inline 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 
54 int 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().type() == QVariant::List) {
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 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
268  const QStringView prop = QStringView(word).mid(1, posOfNonNumeric - 1);
269 #else
270  const QStringRef prop = word.midRef(1, posOfNonNumeric - 1);
271 #endif
272  int propNum = prop.toInt(&ok);
273  if (!ok) {
274  auto error = QStringLiteral("malformed property term (bad index): '%1' in '%2'\n").arg(prop, arrAsPrintable());
275  stream << errorPrefix.subs(error).toString();
276  continue;
277  }
278 
279  const QString value = word.mid(posOfNonNumeric + 1);
280  propertyWords[propNum].append(value);
281  }
282  }
283 
284  for (auto it = propertyWords.constBegin(); it != propertyWords.constEnd(); it++) {
285  auto prop = static_cast<KFileMetaData::Property::Property>(it.key());
287 
288  stream << pi.name() << ": " << it.value().join(QLatin1Char(' ')) << '\n';
289  }
290  }
291  }
292  stream.flush();
293  return 0;
294 }
int toInt(bool *ok, int base) const const
QJsonObject object() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QString number(int n, int base)
QString fromUtf8(const char *str, int size)
KLocalizedString KI18N_EXPORT ki18nc(const char *context, const char *text)
QByteArray & append(char ch)
QVector::iterator begin()
QStringRef midRef(int position, int n) const const
static void setApplicationData(const KAboutData &aboutData)
QByteArray encodeName(const QString &fileName)
QVariantMap toVariantMap() const const
QStringView mid(qsizetype start) const const
void addPositionalArgument(const QString &name, const QString &description, const QString &syntax)
QStringList positionalArguments() const const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
QString toString() const
bool exists() const const
ulong toULong(bool *ok, int base) const const
void showHelp(int exitCode)
QString i18n(const char *text, const TYPE &arg...)
void process(const QStringList &arguments)
QHash::const_iterator constBegin() const const
QHash::const_iterator constEnd() const const
bool isEmpty() const const
int length() const const
QString value(const QString &optionName) const const
bool isEmpty() const const
KLocalizedString subs(const KLocalizedString &a, int fieldWidth=0, QChar fillChar=QLatin1Char(' ')) const
QString join(const QString &separator) const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isSet(const QString &name) const const
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QVector::iterator end()
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromLatin1(const char *str, int size)
QDateTime fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offsetSeconds)
QVector::iterator erase(QVector::iterator begin, QVector::iterator end)
bool addOption(const QCommandLineOption &option)
QString toString(Qt::DateFormat format) const const
QString mid(int position, int n) const const
QCommandLineOption addHelpOption()
T value(int i) const const
QString decodeName(const QByteArray &localFileName)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Nov 29 2023 03:56:26 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.