1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2007 Norbert Frese <nf2@scheinwelt.at>
5 SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org>
6 SPDX-FileCopyrightText: 2013-2014 Frank Reininghaus <frank78ac@googlemail.com>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10
11#include "udsentry.h"
12
13#include "../utils_p.h"
14
15#include <QDataStream>
16#include <QDebug>
17#include <QList>
18#include <QString>
19
20#include <KUser>
21
22using namespace KIO;
23
24// BEGIN UDSEntryPrivate
25
26class KIO::UDSEntryPrivate : public QSharedData
27{
28public:
29 void reserve(int size);
30 void insert(uint udsField, const QString &value);
31 void replace(uint udsField, const QString &value);
32 void insert(uint udsField, long long value);
33 void replace(uint udsField, long long value);
34 int count() const;
35 QString stringValue(uint udsField) const;
36 long long numberValue(uint udsField, long long defaultValue = -1) const;
37 QList<uint> fields() const;
38 bool contains(uint udsField) const;
39 void clear();
40 void save(QDataStream &s) const;
41 void load(QDataStream &s);
42 void debugUDSEntry(QDebug &stream) const;
43 /*
44 * \a field numeric UDS field id
45 * Returns the name of the field
46 */
47 static QString nameOfUdsField(uint field);
48
49private:
50 struct Field {
51 inline Field()
52 {
53 }
54 inline Field(const uint index, const QString &value)
55 : m_str(value)
56 , m_index(index)
57 {
58 }
59 inline Field(const uint index, long long value = 0)
60 : m_long(value)
61 , m_index(index)
62 {
63 }
64
65 QString m_str;
66 long long m_long = LLONG_MIN;
67 uint m_index = 0;
68 };
69 std::vector<Field> storage;
70};
71
72void UDSEntryPrivate::reserve(int size)
73{
74 storage.reserve(n: size);
75}
76
77void UDSEntryPrivate::insert(uint udsField, const QString &value)
78{
79 Q_ASSERT(udsField & KIO::UDSEntry::UDS_STRING);
80 Q_ASSERT(std::find_if(storage.cbegin(),
81 storage.cend(),
82 [udsField](const Field &entry) {
83 return entry.m_index == udsField;
84 })
85 == storage.cend());
86 storage.emplace_back(args&: udsField, args: value);
87}
88
89void UDSEntryPrivate::replace(uint udsField, const QString &value)
90{
91 Q_ASSERT(udsField & KIO::UDSEntry::UDS_STRING);
92 auto it = std::find_if(first: storage.begin(), last: storage.end(), pred: [udsField](const Field &entry) {
93 return entry.m_index == udsField;
94 });
95 if (it != storage.end()) {
96 it->m_str = value;
97 return;
98 }
99 storage.emplace_back(args&: udsField, args: value);
100}
101
102void UDSEntryPrivate::insert(uint udsField, long long value)
103{
104 Q_ASSERT(udsField & KIO::UDSEntry::UDS_NUMBER);
105 Q_ASSERT(std::find_if(storage.cbegin(),
106 storage.cend(),
107 [udsField](const Field &entry) {
108 return entry.m_index == udsField;
109 })
110 == storage.cend());
111 storage.emplace_back(args&: udsField, args&: value);
112}
113
114void UDSEntryPrivate::replace(uint udsField, long long value)
115{
116 Q_ASSERT(udsField & KIO::UDSEntry::UDS_NUMBER);
117 auto it = std::find_if(first: storage.begin(), last: storage.end(), pred: [udsField](const Field &entry) {
118 return entry.m_index == udsField;
119 });
120 if (it != storage.end()) {
121 it->m_long = value;
122 return;
123 }
124 storage.emplace_back(args&: udsField, args&: value);
125}
126
127int UDSEntryPrivate::count() const
128{
129 return storage.size();
130}
131
132QString UDSEntryPrivate::stringValue(uint udsField) const
133{
134 auto it = std::find_if(first: storage.cbegin(), last: storage.cend(), pred: [udsField](const Field &entry) {
135 return entry.m_index == udsField;
136 });
137 if (it != storage.cend()) {
138 return it->m_str;
139 }
140 return QString();
141}
142
143long long UDSEntryPrivate::numberValue(uint udsField, long long defaultValue) const
144{
145 auto it = std::find_if(first: storage.cbegin(), last: storage.cend(), pred: [udsField](const Field &entry) {
146 return entry.m_index == udsField;
147 });
148 if (it != storage.cend()) {
149 return it->m_long;
150 }
151 return defaultValue;
152}
153
154QList<uint> UDSEntryPrivate::fields() const
155{
156 QList<uint> res;
157 res.reserve(asize: storage.size());
158 for (const Field &field : storage) {
159 res.append(t: field.m_index);
160 }
161 return res;
162}
163
164bool UDSEntryPrivate::contains(uint udsField) const
165{
166 auto it = std::find_if(first: storage.cbegin(), last: storage.cend(), pred: [udsField](const Field &entry) {
167 return entry.m_index == udsField;
168 });
169 return (it != storage.cend());
170}
171
172void UDSEntryPrivate::clear()
173{
174 storage.clear();
175}
176
177void UDSEntryPrivate::save(QDataStream &s) const
178{
179 s << static_cast<quint32>(storage.size());
180
181 for (const Field &field : storage) {
182 uint uds = field.m_index;
183 s << uds;
184
185 if (uds & KIO::UDSEntry::UDS_STRING) {
186 s << field.m_str;
187 } else if (uds & KIO::UDSEntry::UDS_NUMBER) {
188 s << field.m_long;
189 } else {
190 Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type");
191 }
192 }
193}
194
195void UDSEntryPrivate::load(QDataStream &s)
196{
197 clear();
198
199 quint32 size;
200 s >> size;
201 reserve(size);
202
203 // We cache the loaded strings. Some of them, like, e.g., the user,
204 // will often be the same for many entries in a row. Caching them
205 // permits to use implicit sharing to save memory.
206 thread_local QList<QString> cachedStrings;
207 if (quint32(cachedStrings.size()) < size) {
208 cachedStrings.resize(size);
209 }
210
211 for (quint32 i = 0; i < size; ++i) {
212 quint32 uds;
213 s >> uds;
214
215 if (uds & KIO::UDSEntry::UDS_STRING) {
216 // If the QString is the same like the one we read for the
217 // previous UDSEntry at the i-th position, use an implicitly
218 // shared copy of the same QString to save memory.
219 QString buffer;
220 s >> buffer;
221
222 if (buffer != cachedStrings.at(i)) {
223 cachedStrings[i] = buffer;
224 }
225
226 insert(udsField: uds, value: cachedStrings.at(i));
227 } else if (uds & KIO::UDSEntry::UDS_NUMBER) {
228 long long value;
229 s >> value;
230 insert(udsField: uds, value);
231 } else {
232 Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type");
233 }
234 }
235}
236
237QString UDSEntryPrivate::nameOfUdsField(uint field)
238{
239 switch (field) {
240 case UDSEntry::UDS_SIZE:
241 return QStringLiteral("UDS_SIZE");
242 case UDSEntry::UDS_SIZE_LARGE:
243 return QStringLiteral("UDS_SIZE_LARGE");
244 case UDSEntry::UDS_USER:
245 return QStringLiteral("UDS_USER");
246 case UDSEntry::UDS_ICON_NAME:
247 return QStringLiteral("UDS_ICON_NAME");
248 case UDSEntry::UDS_GROUP:
249 return QStringLiteral("UDS_GROUP");
250 case UDSEntry::UDS_NAME:
251 return QStringLiteral("UDS_NAME");
252 case UDSEntry::UDS_LOCAL_GROUP_ID:
253 return QStringLiteral("UDS_LOCAL_GROUP_ID");
254 case UDSEntry::UDS_LOCAL_USER_ID:
255 return QStringLiteral("UDS_LOCAL_USER_ID");
256 case UDSEntry::UDS_LOCAL_PATH:
257 return QStringLiteral("UDS_LOCAL_PATH");
258 case UDSEntry::UDS_HIDDEN:
259 return QStringLiteral("UDS_HIDDEN");
260 case UDSEntry::UDS_ACCESS:
261 return QStringLiteral("UDS_ACCESS");
262 case UDSEntry::UDS_MODIFICATION_TIME:
263 return QStringLiteral("UDS_MODIFICATION_TIME");
264 case UDSEntry::UDS_ACCESS_TIME:
265 return QStringLiteral("UDS_ACCESS_TIME");
266 case UDSEntry::UDS_CREATION_TIME:
267 return QStringLiteral("UDS_CREATION_TIME");
268 case UDSEntry::UDS_FILE_TYPE:
269 return QStringLiteral("UDS_FILE_TYPE");
270 case UDSEntry::UDS_LINK_DEST:
271 return QStringLiteral("UDS_LINK_DEST");
272 case UDSEntry::UDS_URL:
273 return QStringLiteral("UDS_URL");
274 case UDSEntry::UDS_MIME_TYPE:
275 return QStringLiteral("UDS_MIME_TYPE");
276 case UDSEntry::UDS_GUESSED_MIME_TYPE:
277 return QStringLiteral("UDS_GUESSED_MIME_TYPE");
278 case UDSEntry::UDS_XML_PROPERTIES:
279 return QStringLiteral("UDS_XML_PROPERTIES");
280 case UDSEntry::UDS_EXTENDED_ACL:
281 return QStringLiteral("UDS_EXTENDED_ACL");
282 case UDSEntry::UDS_ACL_STRING:
283 return QStringLiteral("UDS_ACL_STRING");
284 case UDSEntry::UDS_DEFAULT_ACL_STRING:
285 return QStringLiteral("UDS_DEFAULT_ACL_STRING");
286 case UDSEntry::UDS_DISPLAY_NAME:
287 return QStringLiteral("UDS_DISPLAY_NAME");
288 case UDSEntry::UDS_TARGET_URL:
289 return QStringLiteral("UDS_TARGET_URL");
290 case UDSEntry::UDS_DISPLAY_TYPE:
291 return QStringLiteral("UDS_DISPLAY_TYPE");
292 case UDSEntry::UDS_ICON_OVERLAY_NAMES:
293 return QStringLiteral("UDS_ICON_OVERLAY_NAMES");
294 case UDSEntry::UDS_COMMENT:
295 return QStringLiteral("UDS_COMMENT");
296 case UDSEntry::UDS_DEVICE_ID:
297 return QStringLiteral("UDS_DEVICE_ID");
298 case UDSEntry::UDS_INODE:
299 return QStringLiteral("UDS_INODE");
300 case UDSEntry::UDS_EXTRA:
301 return QStringLiteral("UDS_EXTRA");
302 case UDSEntry::UDS_EXTRA_END:
303 return QStringLiteral("UDS_EXTRA_END");
304 default:
305 return QStringLiteral("Unknown uds field %1").arg(a: field);
306 }
307}
308
309void UDSEntryPrivate::debugUDSEntry(QDebug &stream) const
310{
311 QDebugStateSaver saver(stream);
312 stream.nospace() << "[";
313 for (const Field &field : storage) {
314 stream << " " << nameOfUdsField(field: field.m_index) << "=";
315 if (field.m_index & KIO::UDSEntry::UDS_STRING) {
316 stream << field.m_str;
317 } else if (field.m_index & KIO::UDSEntry::UDS_NUMBER) {
318 stream << field.m_long;
319 } else {
320 Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type");
321 }
322 }
323 stream << " ]";
324}
325// END UDSEntryPrivate
326
327// BEGIN UDSEntry
328/* ---------- UDSEntry ------------ */
329
330UDSEntry::UDSEntry()
331 : d(new UDSEntryPrivate())
332{
333}
334
335// BUG: this API doesn't allow to handle symlinks correctly (we need buff from QT_LSTAT for most things, but buff from QT_STAT for st_mode and st_size)
336UDSEntry::UDSEntry(const QT_STATBUF &buff, const QString &name)
337 : d(new UDSEntryPrivate())
338{
339#ifndef Q_OS_WIN
340 d->reserve(size: 10);
341#else
342 d->reserve(8);
343#endif
344 d->insert(udsField: UDS_NAME, value: name);
345 d->insert(udsField: UDS_SIZE, value: buff.st_size);
346 d->insert(udsField: UDS_DEVICE_ID, value: buff.st_dev);
347 d->insert(udsField: UDS_INODE, value: buff.st_ino);
348 d->insert(udsField: UDS_FILE_TYPE, value: buff.st_mode & QT_STAT_MASK); // extract file type
349 d->insert(udsField: UDS_ACCESS, value: buff.st_mode & 07777); // extract permissions
350 d->insert(udsField: UDS_MODIFICATION_TIME, value: buff.st_mtime);
351 d->insert(udsField: UDS_ACCESS_TIME, value: buff.st_atime);
352#ifndef Q_OS_WIN
353 d->insert(udsField: UDS_LOCAL_USER_ID, value: buff.st_uid);
354 d->insert(udsField: UDS_LOCAL_GROUP_ID, value: buff.st_gid);
355#endif
356}
357
358UDSEntry::UDSEntry(const UDSEntry &) = default;
359UDSEntry::~UDSEntry() = default;
360UDSEntry::UDSEntry(UDSEntry &&) = default;
361UDSEntry &UDSEntry::operator=(const UDSEntry &) = default;
362UDSEntry &UDSEntry::operator=(UDSEntry &&) = default;
363
364QString UDSEntry::stringValue(uint field) const
365{
366 return d->stringValue(udsField: field);
367}
368
369long long UDSEntry::numberValue(uint field, long long defaultValue) const
370{
371 return d->numberValue(udsField: field, defaultValue);
372}
373
374bool UDSEntry::isDir() const
375{
376 return Utils::isDirMask(mode: numberValue(field: UDS_FILE_TYPE));
377}
378
379bool UDSEntry::isLink() const
380{
381 return !stringValue(field: UDS_LINK_DEST).isEmpty();
382}
383
384void UDSEntry::reserve(int size)
385{
386 d->reserve(size);
387}
388
389void UDSEntry::fastInsert(uint field, const QString &value)
390{
391 d->insert(udsField: field, value);
392}
393
394void UDSEntry::fastInsert(uint field, long long value)
395{
396 d->insert(udsField: field, value);
397}
398
399void UDSEntry::replace(uint field, const QString &value)
400{
401 d->replace(udsField: field, value);
402}
403
404void UDSEntry::replace(uint field, long long value)
405{
406 d->replace(udsField: field, value);
407}
408
409QList<uint> UDSEntry::fields() const
410{
411 return d->fields();
412}
413
414int UDSEntry::count() const
415{
416 return d->count();
417}
418
419bool UDSEntry::contains(uint field) const
420{
421 return d->contains(udsField: field);
422}
423
424void UDSEntry::clear()
425{
426 d->clear();
427}
428// END UDSEntry
429
430KIOCORE_EXPORT QDebug operator<<(QDebug stream, const KIO::UDSEntry &entry)
431{
432 entry.d->debugUDSEntry(stream);
433 return stream;
434}
435
436KIOCORE_EXPORT QDataStream &operator<<(QDataStream &s, const KIO::UDSEntry &a)
437{
438 a.d->save(s);
439 return s;
440}
441
442KIOCORE_EXPORT QDataStream &operator>>(QDataStream &s, KIO::UDSEntry &a)
443{
444 a.d->load(s);
445 return s;
446}
447
448// TODO KF7 remove
449// legacy operator in global namespace for binary compatibility
450KIOCORE_EXPORT bool operator==(const KIO::UDSEntry &entry, const KIO::UDSEntry &other)
451{
452 return KIO::operator==(entry, other);
453}
454
455// TODO KF7 remove
456// legacy operator in global namespace for binary compatibility
457KIOCORE_EXPORT bool operator!=(const KIO::UDSEntry &entry, const KIO::UDSEntry &other)
458{
459 return KIO::operator!=(entry, other);
460}
461
462bool KIO::operator==(const KIO::UDSEntry &entry, const KIO::UDSEntry &other)
463{
464 if (entry.count() != other.count()) {
465 return false;
466 }
467
468 const QList<uint> fields = entry.fields();
469 for (uint field : fields) {
470 if (!other.contains(field)) {
471 return false;
472 }
473
474 if (field & UDSEntry::UDS_STRING) {
475 if (entry.stringValue(field) != other.stringValue(field)) {
476 return false;
477 }
478 } else {
479 if (entry.numberValue(field) != other.numberValue(field)) {
480 return false;
481 }
482 }
483 }
484
485 return true;
486}
487
488bool KIO::operator!=(const KIO::UDSEntry &entry, const KIO::UDSEntry &other)
489{
490 return !KIO::operator==(entry, other);
491}
492

source code of kio/src/core/udsentry.cpp