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 | |
22 | using namespace KIO; |
23 | |
24 | // BEGIN UDSEntryPrivate |
25 | |
26 | class KIO::UDSEntryPrivate : public QSharedData |
27 | { |
28 | public: |
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 | |
49 | private: |
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 | |
72 | void UDSEntryPrivate::reserve(int size) |
73 | { |
74 | storage.reserve(n: size); |
75 | } |
76 | |
77 | void 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 | |
89 | void 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 | |
102 | void 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 | |
114 | void 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 | |
127 | int UDSEntryPrivate::count() const |
128 | { |
129 | return storage.size(); |
130 | } |
131 | |
132 | QString 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 | |
143 | long 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 | |
154 | QList<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 | |
164 | bool 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 | |
172 | void UDSEntryPrivate::clear() |
173 | { |
174 | storage.clear(); |
175 | } |
176 | |
177 | void 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 | |
195 | void 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 | |
237 | QString 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 | |
309 | void 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 | |
330 | UDSEntry::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) |
336 | UDSEntry::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 | |
358 | UDSEntry::UDSEntry(const UDSEntry &) = default; |
359 | UDSEntry::~UDSEntry() = default; |
360 | UDSEntry::UDSEntry(UDSEntry &&) = default; |
361 | UDSEntry &UDSEntry::operator=(const UDSEntry &) = default; |
362 | UDSEntry &UDSEntry::operator=(UDSEntry &&) = default; |
363 | |
364 | QString UDSEntry::stringValue(uint field) const |
365 | { |
366 | return d->stringValue(udsField: field); |
367 | } |
368 | |
369 | long long UDSEntry::numberValue(uint field, long long defaultValue) const |
370 | { |
371 | return d->numberValue(udsField: field, defaultValue); |
372 | } |
373 | |
374 | bool UDSEntry::isDir() const |
375 | { |
376 | return Utils::isDirMask(mode: numberValue(field: UDS_FILE_TYPE)); |
377 | } |
378 | |
379 | bool UDSEntry::isLink() const |
380 | { |
381 | return !stringValue(field: UDS_LINK_DEST).isEmpty(); |
382 | } |
383 | |
384 | void UDSEntry::reserve(int size) |
385 | { |
386 | d->reserve(size); |
387 | } |
388 | |
389 | void UDSEntry::fastInsert(uint field, const QString &value) |
390 | { |
391 | d->insert(udsField: field, value); |
392 | } |
393 | |
394 | void UDSEntry::fastInsert(uint field, long long value) |
395 | { |
396 | d->insert(udsField: field, value); |
397 | } |
398 | |
399 | void UDSEntry::replace(uint field, const QString &value) |
400 | { |
401 | d->replace(udsField: field, value); |
402 | } |
403 | |
404 | void UDSEntry::replace(uint field, long long value) |
405 | { |
406 | d->replace(udsField: field, value); |
407 | } |
408 | |
409 | QList<uint> UDSEntry::fields() const |
410 | { |
411 | return d->fields(); |
412 | } |
413 | |
414 | int UDSEntry::count() const |
415 | { |
416 | return d->count(); |
417 | } |
418 | |
419 | bool UDSEntry::contains(uint field) const |
420 | { |
421 | return d->contains(udsField: field); |
422 | } |
423 | |
424 | void UDSEntry::clear() |
425 | { |
426 | d->clear(); |
427 | } |
428 | // END UDSEntry |
429 | |
430 | KIOCORE_EXPORT QDebug operator<<(QDebug stream, const KIO::UDSEntry &entry) |
431 | { |
432 | entry.d->debugUDSEntry(stream); |
433 | return stream; |
434 | } |
435 | |
436 | KIOCORE_EXPORT QDataStream &operator<<(QDataStream &s, const KIO::UDSEntry &a) |
437 | { |
438 | a.d->save(s); |
439 | return s; |
440 | } |
441 | |
442 | KIOCORE_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 |
450 | KIOCORE_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 |
457 | KIOCORE_EXPORT bool operator!=(const KIO::UDSEntry &entry, const KIO::UDSEntry &other) |
458 | { |
459 | return KIO::operator!=(entry, other); |
460 | } |
461 | |
462 | bool 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 | |
488 | bool KIO::operator!=(const KIO::UDSEntry &entry, const KIO::UDSEntry &other) |
489 | { |
490 | return !KIO::operator==(entry, other); |
491 | } |
492 | |