| 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 | |