File indexing completed on 2024-11-10 04:40:40

0001 /*
0002     SPDX-FileCopyrightText: 2006 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "item.h"
0008 #include "akonadicore_debug.h"
0009 #include "item_p.h"
0010 #include "itemserializer_p.h"
0011 #include "private/protocol_p.h"
0012 
0013 #include <QUrl>
0014 #include <QUrlQuery>
0015 
0016 #include <QReadWriteLock>
0017 #include <QScopedValueRollback>
0018 #include <QStringList>
0019 
0020 #include <algorithm>
0021 #include <map>
0022 #include <utility>
0023 
0024 using namespace Akonadi;
0025 
0026 Q_GLOBAL_STATIC(Akonadi::Collection, s_defaultParentCollection) // NOLINT(readability-redundant-member-init)
0027 
0028 size_t Akonadi::qHash(const Akonadi::Item &item, size_t seed) noexcept
0029 {
0030     return ::qHash(item.id(), seed);
0031 }
0032 
0033 // Change to something != RFC822 as soon as the server supports it
0034 const char Item::FullPayload[] = "RFC822";
0035 
0036 Item::Item()
0037     : d_ptr(new ItemPrivate)
0038 {
0039 }
0040 
0041 Item::Item(Id id)
0042     : d_ptr(new ItemPrivate(id))
0043 {
0044 }
0045 
0046 Item::Item(const QString &mimeType)
0047     : d_ptr(new ItemPrivate)
0048 {
0049     d_ptr->mMimeType = mimeType;
0050 }
0051 
0052 Item::Item(const Item &other) = default;
0053 
0054 Item::Item(Item &&other) noexcept = default;
0055 
0056 Item::~Item() = default;
0057 
0058 void Item::setId(Item::Id identifier)
0059 {
0060     d_ptr->mId = identifier;
0061 }
0062 
0063 Item::Id Item::id() const
0064 {
0065     return d_ptr->mId;
0066 }
0067 
0068 void Item::setRemoteId(const QString &id)
0069 {
0070     d_ptr->mRemoteId = id;
0071 }
0072 
0073 QString Item::remoteId() const
0074 {
0075     return d_ptr->mRemoteId;
0076 }
0077 
0078 void Item::setRemoteRevision(const QString &revision)
0079 {
0080     d_ptr->mRemoteRevision = revision;
0081 }
0082 
0083 QString Item::remoteRevision() const
0084 {
0085     return d_ptr->mRemoteRevision;
0086 }
0087 
0088 bool Item::isValid() const
0089 {
0090     return (d_ptr->mId >= 0);
0091 }
0092 
0093 bool Item::operator==(const Item &other) const
0094 {
0095     // Invalid collections are the same, no matter what their internal ID is
0096     return (!isValid() && !other.isValid()) || (d_ptr->mId == other.d_ptr->mId);
0097 }
0098 
0099 bool Akonadi::Item::operator!=(const Item &other) const
0100 {
0101     return (isValid() || other.isValid()) && (d_ptr->mId != other.d_ptr->mId);
0102 }
0103 
0104 Item &Item ::operator=(const Item &other)
0105 {
0106     if (this != &other) {
0107         d_ptr = other.d_ptr;
0108     }
0109 
0110     return *this;
0111 }
0112 
0113 bool Akonadi::Item::operator<(const Item &other) const
0114 {
0115     return d_ptr->mId < other.d_ptr->mId;
0116 }
0117 
0118 void Item::addAttribute(Attribute *attr)
0119 {
0120     ItemChangeLog::instance()->attributeStorage(d_ptr).addAttribute(attr);
0121 }
0122 
0123 void Item::removeAttribute(const QByteArray &type)
0124 {
0125     ItemChangeLog::instance()->attributeStorage(d_ptr).removeAttribute(type);
0126 }
0127 
0128 bool Item::hasAttribute(const QByteArray &type) const
0129 {
0130     return ItemChangeLog::instance()->attributeStorage(d_ptr).hasAttribute(type);
0131 }
0132 
0133 Attribute::List Item::attributes() const
0134 {
0135     return ItemChangeLog::instance()->attributeStorage(d_ptr).attributes();
0136 }
0137 
0138 void Akonadi::Item::clearAttributes()
0139 {
0140     ItemChangeLog::instance()->attributeStorage(d_ptr).clearAttributes();
0141 }
0142 
0143 Attribute *Item::attribute(const QByteArray &type)
0144 {
0145     return ItemChangeLog::instance()->attributeStorage(d_ptr).attribute(type);
0146 }
0147 
0148 const Attribute *Item::attribute(const QByteArray &type) const
0149 {
0150     return ItemChangeLog::instance()->attributeStorage(d_ptr).attribute(type);
0151 }
0152 
0153 Collection &Item::parentCollection()
0154 {
0155     if (!d_ptr->mParent) {
0156         d_ptr->mParent.reset(new Collection());
0157     }
0158     return *(d_ptr->mParent);
0159 }
0160 
0161 Collection Item::parentCollection() const
0162 {
0163     if (!d_ptr->mParent) {
0164         return *(s_defaultParentCollection);
0165     } else {
0166         return *(d_ptr->mParent);
0167     }
0168 }
0169 
0170 void Item::setParentCollection(const Collection &parent)
0171 {
0172     d_ptr->mParent.reset(new Collection(parent));
0173 }
0174 
0175 Item::Flags Item::flags() const
0176 {
0177     return d_ptr->mFlags;
0178 }
0179 
0180 void Item::setFlag(const QByteArray &name)
0181 {
0182     d_ptr->mFlags.insert(name);
0183     if (!d_ptr->mFlagsOverwritten) {
0184         Item::Flags &deletedFlags = ItemChangeLog::instance()->deletedFlags(d_ptr);
0185         auto iter = deletedFlags.find(name);
0186         if (iter != deletedFlags.end()) {
0187             deletedFlags.erase(iter);
0188         } else {
0189             ItemChangeLog::instance()->addedFlags(d_ptr).insert(name);
0190         }
0191     }
0192 }
0193 
0194 void Item::clearFlag(const QByteArray &name)
0195 {
0196     d_ptr->mFlags.remove(name);
0197     if (!d_ptr->mFlagsOverwritten) {
0198         Item::Flags &addedFlags = ItemChangeLog::instance()->addedFlags(d_ptr);
0199         auto iter = addedFlags.find(name);
0200         if (iter != addedFlags.end()) {
0201             addedFlags.erase(iter);
0202         } else {
0203             ItemChangeLog::instance()->deletedFlags(d_ptr).insert(name);
0204         }
0205     }
0206 }
0207 
0208 void Item::setFlags(const Flags &flags)
0209 {
0210     d_ptr->mFlags = flags;
0211     d_ptr->mFlagsOverwritten = true;
0212 }
0213 
0214 void Item::clearFlags()
0215 {
0216     d_ptr->mFlags.clear();
0217     d_ptr->mFlagsOverwritten = true;
0218 }
0219 
0220 QDateTime Item::modificationTime() const
0221 {
0222     return d_ptr->mModificationTime;
0223 }
0224 
0225 void Item::setModificationTime(const QDateTime &datetime)
0226 {
0227     d_ptr->mModificationTime = datetime;
0228 }
0229 
0230 bool Item::hasFlag(const QByteArray &name) const
0231 {
0232     return d_ptr->mFlags.contains(name);
0233 }
0234 
0235 void Item::setTags(const Tag::List &list)
0236 {
0237     d_ptr->mTags = list;
0238     d_ptr->mTagsOverwritten = true;
0239 }
0240 
0241 void Item::setTag(const Tag &tag)
0242 {
0243     d_ptr->mTags << tag;
0244     if (!d_ptr->mTagsOverwritten) {
0245         Tag::List &deletedTags = ItemChangeLog::instance()->deletedTags(d_ptr);
0246         if (deletedTags.contains(tag)) {
0247             deletedTags.removeOne(tag);
0248         } else {
0249             ItemChangeLog::instance()->addedTags(d_ptr).push_back(tag);
0250         }
0251     }
0252 }
0253 
0254 void Item::clearTags()
0255 {
0256     d_ptr->mTags.clear();
0257     d_ptr->mTagsOverwritten = true;
0258 }
0259 
0260 void Item::clearTag(const Tag &tag)
0261 {
0262     d_ptr->mTags.removeOne(tag);
0263     if (!d_ptr->mTagsOverwritten) {
0264         Tag::List &addedTags = ItemChangeLog::instance()->addedTags(d_ptr);
0265         if (addedTags.contains(tag)) {
0266             addedTags.removeOne(tag);
0267         } else {
0268             ItemChangeLog::instance()->deletedTags(d_ptr).push_back(tag);
0269         }
0270     }
0271 }
0272 
0273 bool Item::hasTag(const Tag &tag) const
0274 {
0275     return d_ptr->mTags.contains(tag);
0276 }
0277 
0278 Tag::List Item::tags() const
0279 {
0280     return d_ptr->mTags;
0281 }
0282 
0283 Relation::List Item::relations() const
0284 {
0285     return d_ptr->mRelations;
0286 }
0287 
0288 QSet<QByteArray> Item::loadedPayloadParts() const
0289 {
0290     return ItemSerializer::parts(*this);
0291 }
0292 
0293 QByteArray Item::payloadData() const
0294 {
0295     int version = 0;
0296     QByteArray data;
0297     ItemSerializer::serialize(*this, FullPayload, data, version);
0298     return data;
0299 }
0300 
0301 void Item::setPayloadFromData(const QByteArray &data)
0302 {
0303     ItemSerializer::deserialize(*this, FullPayload, data, 0, ItemSerializer::Internal);
0304 }
0305 
0306 void Item::clearPayload()
0307 {
0308     d_ptr->mClearPayload = true;
0309 }
0310 
0311 int Item::revision() const
0312 {
0313     return d_ptr->mRevision;
0314 }
0315 
0316 void Item::setRevision(int rev)
0317 {
0318     d_ptr->mRevision = rev;
0319 }
0320 
0321 Collection::Id Item::storageCollectionId() const
0322 {
0323     return d_ptr->mCollectionId;
0324 }
0325 
0326 void Item::setStorageCollectionId(Collection::Id collectionId)
0327 {
0328     d_ptr->mCollectionId = collectionId;
0329 }
0330 
0331 QString Item::mimeType() const
0332 {
0333     return d_ptr->mMimeType;
0334 }
0335 
0336 void Item::setSize(qint64 size)
0337 {
0338     d_ptr->mSize = size;
0339     d_ptr->mSizeChanged = true;
0340 }
0341 
0342 qint64 Item::size() const
0343 {
0344     return d_ptr->mSize;
0345 }
0346 
0347 void Item::setMimeType(const QString &mimeType)
0348 {
0349     d_ptr->mMimeType = mimeType;
0350 }
0351 
0352 void Item::setGid(const QString &id)
0353 {
0354     d_ptr->mGid = id;
0355 }
0356 
0357 QString Item::gid() const
0358 {
0359     return d_ptr->mGid;
0360 }
0361 
0362 void Item::setVirtualReferences(const Collection::List &collections)
0363 {
0364     d_ptr->mVirtualReferences = collections;
0365 }
0366 
0367 Collection::List Item::virtualReferences() const
0368 {
0369     return d_ptr->mVirtualReferences;
0370 }
0371 
0372 bool Item::hasPayload() const
0373 {
0374     return d_ptr->hasMetaTypeId(-1);
0375 }
0376 
0377 QUrl Item::url(UrlType type) const
0378 {
0379     QUrlQuery query;
0380     query.addQueryItem(QStringLiteral("item"), QString::number(id()));
0381     if (type == UrlWithMimeType) {
0382         query.addQueryItem(QStringLiteral("type"), mimeType());
0383     }
0384 
0385     QUrl url;
0386     url.setScheme(QStringLiteral("akonadi"));
0387     url.setQuery(query);
0388     return url;
0389 }
0390 
0391 Item Item::fromUrl(const QUrl &url)
0392 {
0393     if (url.scheme() != QLatin1StringView("akonadi")) {
0394         return Item();
0395     }
0396 
0397     const QString itemStr = QUrlQuery(url).queryItemValue(QStringLiteral("item"));
0398     bool ok = false;
0399     Item::Id itemId = itemStr.toLongLong(&ok);
0400     if (!ok) {
0401         return Item();
0402     }
0403 
0404     return Item(itemId);
0405 }
0406 
0407 Internal::PayloadBase *Item::payloadBaseV2(int spid, int mtid) const
0408 {
0409     return d_ptr->payloadBaseImpl(spid, mtid);
0410 }
0411 
0412 bool Item::ensureMetaTypeId(int mtid) const
0413 {
0414     // 0. Nothing there - nothing to convert from, either
0415     if (d_ptr->mPayloads.empty()) {
0416         return false;
0417     }
0418 
0419     // 1. Look whether we already have one:
0420     if (d_ptr->hasMetaTypeId(mtid)) {
0421         return true;
0422     }
0423 
0424     // recursion detection (shouldn't trigger, but does if the
0425     // serialiser plugins are acting funky):
0426     if (d_ptr->mConversionInProgress) {
0427         return false;
0428     }
0429 
0430     // 2. Try to create one by conversion from a different representation:
0431     try {
0432         const QScopedValueRollback guard(d_ptr->mConversionInProgress, true);
0433         Item converted = ItemSerializer::convert(*this, mtid);
0434         return d_ptr->movePayloadFrom(converted.d_ptr, mtid);
0435     } catch (const std::exception &e) {
0436         qCWarning(AKONADICORE_LOG) << "Item payload conversion threw:" << e.what();
0437         return false;
0438     } catch (...) {
0439         qCCritical(AKONADICORE_LOG, "conversion threw something not derived from std::exception: fix the program!");
0440         return false;
0441     }
0442 }
0443 
0444 static QString format_type(int spid, int mtid)
0445 {
0446     return QStringLiteral("sp(%1)<%2>").arg(spid).arg(QLatin1StringView(QMetaType(mtid).name()));
0447 }
0448 
0449 static QString format_types(const PayloadContainer &container)
0450 {
0451     QStringList result;
0452     result.reserve(container.size());
0453     for (auto it = container.begin(), end = container.end(); it != end; ++it) {
0454         result.push_back(format_type(it->sharedPointerId, it->metaTypeId));
0455     }
0456     return result.join(QLatin1StringView(", "));
0457 }
0458 
0459 static QString format_reason(bool valid, Item::Id id)
0460 {
0461     if (valid) {
0462         return QStringLiteral("itemId: %1").arg(id);
0463     } else {
0464         return QStringLiteral("Item is not valid");
0465     }
0466 }
0467 
0468 void Item::throwPayloadException(int spid, int mtid) const
0469 {
0470     const auto reason = format_reason(isValid(), id());
0471 
0472     if (d_ptr->mPayloads.empty()) {
0473         qCDebug(AKONADICORE_LOG) << "Throwing PayloadException for Item" << id() << ": No payload set";
0474         throw PayloadException(QStringLiteral("No Item payload set (%1)").arg(reason));
0475     } else {
0476         const auto requestedType = format_type(spid, mtid);
0477         const auto presentType = format_types(d_ptr->mPayloads);
0478         qCDebug(AKONADICORE_LOG) << "Throwing PayloadException for Item" << id() << ": Wrong payload type (requested:" << requestedType
0479                                  << "; present: " << presentType << "), item mime type is" << mimeType();
0480         throw PayloadException(QStringLiteral("Wrong Item payload type (requested: %1; present: %2, %3)").arg(requestedType, presentType, reason));
0481     }
0482 }
0483 
0484 void Item::setPayloadBaseV2(int spid, int mtid, std::unique_ptr<Internal::PayloadBase> &p)
0485 {
0486     d_ptr->setPayloadBaseImpl(spid, mtid, p, false);
0487 }
0488 
0489 void Item::addPayloadBaseVariant(int spid, int mtid, std::unique_ptr<Internal::PayloadBase> &p) const
0490 {
0491     d_ptr->setPayloadBaseImpl(spid, mtid, p, true);
0492 }
0493 
0494 QSet<QByteArray> Item::cachedPayloadParts() const
0495 {
0496     return d_ptr->mCachedPayloadParts;
0497 }
0498 
0499 void Item::setCachedPayloadParts(const QSet<QByteArray> &cachedParts)
0500 {
0501     d_ptr->mCachedPayloadParts = cachedParts;
0502 }
0503 
0504 QSet<QByteArray> Item::availablePayloadParts() const
0505 {
0506     return ItemSerializer::availableParts(*this);
0507 }
0508 
0509 QList<int> Item::availablePayloadMetaTypeIds() const
0510 {
0511     QList<int> result;
0512     result.reserve(d_ptr->mPayloads.size());
0513     // Stable Insertion Sort - N is typically _very_ low (1 or 2).
0514     for (auto it = d_ptr->mPayloads.begin(), end = d_ptr->mPayloads.end(); it != end; ++it) {
0515         result.insert(std::upper_bound(result.begin(), result.end(), it->metaTypeId), it->metaTypeId);
0516     }
0517     return result;
0518 }
0519 
0520 void Item::setPayloadPath(const QString &filePath)
0521 {
0522     // Load payload from the external file, so that it's accessible via
0523     // Item::payload(). It internally calls setPayload(), which will clear
0524     // mPayloadPath, so we call it afterwards
0525     ItemSerializer::deserialize(*this, "RFC822", filePath.toUtf8(), 0, ItemSerializer::Foreign);
0526     d_ptr->mPayloadPath = filePath;
0527 }
0528 
0529 QString Item::payloadPath() const
0530 {
0531     return d_ptr->mPayloadPath;
0532 }
0533 
0534 void Item::apply(const Item &other)
0535 {
0536     if (mimeType() != other.mimeType() || id() != other.id()) {
0537         qCDebug(AKONADICORE_LOG) << "mimeType() = " << mimeType() << "; other.mimeType() = " << other.mimeType();
0538         qCDebug(AKONADICORE_LOG) << "id() = " << id() << "; other.id() = " << other.id();
0539         Q_ASSERT_X(false, "Item::apply", "mimetype or id mismatch");
0540     }
0541 
0542     setRemoteId(other.remoteId());
0543     setRevision(other.revision());
0544     setRemoteRevision(other.remoteRevision());
0545     setFlags(other.flags());
0546     setTags(other.tags());
0547     setModificationTime(other.modificationTime());
0548     setSize(other.size());
0549     setParentCollection(other.parentCollection());
0550     setStorageCollectionId(other.storageCollectionId());
0551 
0552     ItemChangeLog *changelog = ItemChangeLog::instance();
0553     changelog->attributeStorage(d_ptr) = changelog->attributeStorage(other.d_ptr);
0554 
0555     ItemSerializer::apply(*this, other);
0556     d_ptr->resetChangeLog();
0557 
0558     // Must happen after payload update
0559     d_ptr->mPayloadPath = other.payloadPath();
0560 }