1/*
2 Copyright (c) 2006 Volker Krause <vkrause@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "item.h"
21#include "item_p.h"
22#include "itemserializer_p.h"
23#include "protocol_p.h"
24
25#include <kurl.h>
26#include <kdebug.h>
27
28#include <QtCore/QStringList>
29#include <QtCore/QReadWriteLock>
30
31#include <algorithm>
32#include <map>
33#include <utility>
34
35using namespace Akonadi;
36using namespace boost;
37
38namespace {
39
40struct nodelete {
41 template <typename T>
42 void operator()(T *)
43 {
44 }
45};
46
47struct ByTypeId {
48 typedef bool result_type;
49 bool operator()(const boost::shared_ptr<PayloadBase> &lhs, const boost::shared_ptr<PayloadBase> &rhs) const
50 {
51 return strcmp(lhs->typeName(), rhs->typeName()) < 0 ;
52 }
53};
54
55} // anon namespace
56
57typedef QHash< QString, std::map< boost::shared_ptr<PayloadBase>, std::pair<int, int>, ByTypeId > > LegacyMap;
58Q_GLOBAL_STATIC(LegacyMap, typeInfoToMetaTypeIdMap)
59Q_GLOBAL_STATIC_WITH_ARGS(QReadWriteLock, legacyMapLock, (QReadWriteLock::Recursive))
60
61void Item::addToLegacyMappingImpl(const QString &mimeType, int spid, int mtid, std::auto_ptr<PayloadBase> p)
62{
63 if (!p.get()) {
64 return;
65 }
66 const boost::shared_ptr<PayloadBase> sp(p);
67 const QWriteLocker locker(legacyMapLock());
68 std::pair<int, int> &item = (*typeInfoToMetaTypeIdMap())[mimeType][sp];
69 item.first = spid;
70 item.second = mtid;
71}
72
73namespace {
74class MyReadLocker {
75public:
76 explicit MyReadLocker(QReadWriteLock *rwl)
77 : rwl(rwl)
78 , locked(false)
79 {
80 if (rwl) {
81 rwl->lockForRead();
82 }
83 locked = true;
84 }
85
86 ~MyReadLocker()
87 {
88 if (rwl && locked) {
89 rwl->unlock();
90 }
91 }
92
93 template <typename T>
94 shared_ptr<T> makeUnlockingPointer(T *t)
95 {
96 if (t) {
97 // the bind() doesn't throw, so if shared_ptr
98 // construction line below, or anything else after it,
99 // throws, we're unlocked. Mark us as such:
100 locked = false;
101 const shared_ptr<T> result(t, bind(&QReadWriteLock::unlock, rwl));
102 // from now on, the shared_ptr is responsible for unlocking
103 return result;
104 } else {
105 return shared_ptr<T>();
106 }
107 }
108private:
109 QReadWriteLock *const rwl;
110 bool locked;
111};
112}
113
114static shared_ptr<const std::pair<int, int> > lookupLegacyMapping(const QString &mimeType, PayloadBase *p)
115{
116 MyReadLocker locker(legacyMapLock());
117 const LegacyMap::const_iterator hit = typeInfoToMetaTypeIdMap()->constFind(mimeType);
118 if (hit == typeInfoToMetaTypeIdMap()->constEnd()) {
119 return shared_ptr<const std::pair<int, int> >();
120 }
121 const boost::shared_ptr<PayloadBase> sp(p, nodelete());
122 const LegacyMap::mapped_type::const_iterator it = hit->find(sp);
123 if (it == hit->end()) {
124 return shared_ptr<const std::pair<int, int> >();
125 }
126
127 return locker.makeUnlockingPointer(&it->second);
128}
129
130// Change to something != RFC822 as soon as the server supports it
131const char *Item::FullPayload = "RFC822";
132
133Item::Item()
134 : Entity(new ItemPrivate)
135{
136}
137
138Item::Item(Id id)
139 : Entity(new ItemPrivate(id))
140{
141}
142
143Item::Item(const QString &mimeType)
144 : Entity(new ItemPrivate)
145{
146 d_func()->mMimeType = mimeType;
147}
148
149Item::Item(const Item &other)
150 : Entity(other)
151{
152}
153
154Item::~Item()
155{
156}
157
158Item::Flags Item::flags() const
159{
160 return d_func()->mFlags;
161}
162
163void Item::setFlag(const QByteArray &name)
164{
165 Q_D(Item);
166 d->mFlags.insert(name);
167 if (!d->mFlagsOverwritten) {
168 if (d->mDeletedFlags.contains(name)) {
169 d->mDeletedFlags.remove(name);
170 } else {
171 d->mAddedFlags.insert(name);
172 }
173 }
174}
175
176void Item::clearFlag(const QByteArray &name)
177{
178 Q_D(Item);
179 d->mFlags.remove(name);
180 if (!d->mFlagsOverwritten) {
181 if (d->mAddedFlags.contains(name)) {
182 d->mAddedFlags.remove(name);
183 } else {
184 d->mDeletedFlags.insert(name);
185 }
186 }
187}
188
189void Item::setFlags(const Flags &flags)
190{
191 Q_D(Item);
192 d->mFlags = flags;
193 d->mFlagsOverwritten = true;
194}
195
196void Item::clearFlags()
197{
198 Q_D(Item);
199 d->mFlags.clear();
200 d->mFlagsOverwritten = true;
201}
202
203QDateTime Item::modificationTime() const
204{
205 return d_func()->mModificationTime;
206}
207
208void Item::setModificationTime(const QDateTime &datetime)
209{
210 d_func()->mModificationTime = datetime;
211}
212
213bool Item::hasFlag(const QByteArray &name) const
214{
215 return d_func()->mFlags.contains(name);
216}
217
218void Item::setTags(const Tag::List &list)
219{
220 Q_D(Item);
221 d->mTags = list;
222 d->mTagsOverwritten = true;
223}
224
225void Item::setTag(const Tag &tag)
226{
227 Q_D(Item);
228 d->mTags << tag;
229 if (!d->mTagsOverwritten) {
230 if (d->mDeletedTags.contains(tag)) {
231 d->mDeletedTags.removeOne(tag);
232 } else {
233 d->mAddedTags << tag ;
234 }
235 }
236}
237
238void Item::clearTags()
239{
240 Q_D(Item);
241 d->mTags.clear();
242 d->mTagsOverwritten = true;
243}
244
245void Item::clearTag(const Tag &tag)
246{
247 Q_D(Item);
248 d->mTags.removeOne(tag);
249 if (!d->mTagsOverwritten) {
250 if (d->mAddedTags.contains(tag)) {
251 d->mAddedTags.removeOne(tag);
252 } else {
253 d->mDeletedTags << tag;
254 }
255 }
256}
257
258bool Item::hasTag(const Tag &tag) const
259{
260 Q_D(const Item);
261 return d->mTags.contains(tag);
262}
263
264Tag::List Item::tags() const
265{
266 Q_D(const Item);
267 return d->mTags;
268}
269
270QSet<QByteArray> Item::loadedPayloadParts() const
271{
272 return ItemSerializer::parts(*this);
273}
274
275QByteArray Item::payloadData() const
276{
277 int version = 0;
278 QByteArray data;
279 ItemSerializer::serialize(*this, FullPayload, data, version);
280 return data;
281}
282
283void Item::setPayloadFromData(const QByteArray &data)
284{
285 ItemSerializer::deserialize(*this, FullPayload, data, 0, false);
286}
287
288void Item::clearPayload()
289{
290 d_func()->mClearPayload = true;
291}
292
293int Item::revision() const
294{
295 return d_func()->mRevision;
296}
297
298void Item::setRevision(int rev)
299{
300 d_func()->mRevision = rev;
301}
302
303Entity::Id Item::storageCollectionId() const
304{
305 return d_func()->mCollectionId;
306}
307
308void Item::setStorageCollectionId(Entity::Id collectionId)
309{
310 d_func()->mCollectionId = collectionId;
311}
312
313QString Item::mimeType() const
314{
315 return d_func()->mMimeType;
316}
317
318void Item::setSize(qint64 size)
319{
320 Q_D(Item);
321 d->mSize = size;
322 d->mSizeChanged = true;
323}
324
325qint64 Item::size() const
326{
327 return d_func()->mSize;
328}
329
330void Item::setMimeType(const QString &mimeType)
331{
332 d_func()->mMimeType = mimeType;
333}
334
335void Item::setGid(const QString &id)
336{
337 d_func()->mGid = id;
338}
339
340QString Item::gid() const
341{
342 return d_func()->mGid;
343}
344
345void Item::setVirtualReferences(const Collection::List &collections)
346{
347 d_func()->mVirtualReferences = collections;
348}
349
350Collection::List Item::virtualReferences() const
351{
352 return d_func()->mVirtualReferences;
353}
354
355bool Item::hasPayload() const
356{
357 return d_func()->hasMetaTypeId(-1);
358}
359
360KUrl Item::url(UrlType type) const
361{
362 KUrl url;
363 url.setProtocol(QString::fromLatin1("akonadi"));
364 url.addQueryItem(QLatin1String("item"), QString::number(id()));
365
366 if (type == UrlWithMimeType) {
367 url.addQueryItem(QLatin1String("type"), mimeType());
368 }
369
370 return url;
371}
372
373Item Item::fromUrl(const KUrl &url)
374{
375 if (url.protocol() != QLatin1String("akonadi")) {
376 return Item();
377 }
378
379 const QString itemStr = url.queryItem(QLatin1String("item"));
380 bool ok = false;
381 Item::Id itemId = itemStr.toLongLong(&ok);
382 if (!ok) {
383 return Item();
384 }
385
386 return Item(itemId);
387}
388
389namespace {
390class Dummy
391{
392};
393}
394
395Q_GLOBAL_STATIC(Payload<Dummy>, dummyPayload)
396
397PayloadBase *Item::payloadBase() const
398{
399 Q_D(const Item);
400 d->tryEnsureLegacyPayload();
401 if (d->mLegacyPayload) {
402 return d->mLegacyPayload.get();
403 } else {
404 return dummyPayload();
405 }
406}
407
408void ItemPrivate::tryEnsureLegacyPayload() const
409{
410 if (!mLegacyPayload) {
411 for (PayloadContainer::const_iterator it = mPayloads.begin(), end = mPayloads.end() ; it != end ; ++it) {
412 if (lookupLegacyMapping(mMimeType, it->payload.get())) {
413 mLegacyPayload = it->payload; // clones
414 }
415 }
416 }
417}
418
419PayloadBase *Item::payloadBaseV2(int spid, int mtid) const
420{
421 return d_func()->payloadBaseImpl(spid, mtid);
422}
423
424namespace {
425class ConversionGuard
426{
427 const bool old;
428 bool &b;
429public:
430 explicit ConversionGuard(bool &b)
431 : old(b)
432 , b(b)
433 {
434 b = true;
435 }
436 ~ConversionGuard() {
437 b = old;
438 }
439};
440}
441
442bool Item::ensureMetaTypeId(int mtid) const
443{
444 Q_D(const Item);
445 // 0. Nothing there - nothing to convert from, either
446 if (d->mPayloads.empty()) {
447 return false;
448 }
449
450 // 1. Look whether we already have one:
451 if (d->hasMetaTypeId(mtid)) {
452 return true;
453 }
454
455 // recursion detection (shouldn't trigger, but does if the
456 // serialiser plugins are acting funky):
457 if (d->mConversionInProgress) {
458 return false;
459 }
460
461 // 2. Try to create one by conversion from a different representation:
462 try {
463 const ConversionGuard guard(d->mConversionInProgress);
464 Item converted = ItemSerializer::convert(*this, mtid);
465 return d->movePayloadFrom(converted.d_func(), mtid);
466 } catch (const std::exception &e) {
467 kDebug() << "conversion threw:" << e.what();
468 return false;
469 } catch (...) {
470 kDebug() << "conversion threw something not derived from std::exception: fix the program!";
471 return false;
472 }
473}
474
475static QString format_type(int spid, int mtid)
476{
477 return QString::fromLatin1("sp(%1)<%2>")
478 .arg(spid).arg(QLatin1String(QMetaType::typeName(mtid)));
479}
480
481static QString format_types(const PayloadContainer &c)
482{
483 QStringList result;
484 for (PayloadContainer::const_iterator it = c.begin(), end = c.end() ; it != end ; ++it) {
485 result.push_back(format_type(it->sharedPointerId, it->metaTypeId));
486 }
487 return result.join(QLatin1String(", "));
488}
489
490#if 0
491QString Item::payloadExceptionText(int spid, int mtid) const
492{
493 Q_D(const Item);
494 if (d->mPayloads.empty()) {
495 return QLatin1String("No payload set");
496 } else {
497 return QString::fromLatin1("Wrong payload type (requested: %1; present: %2")
498 .arg(format_type(spid, mtid), format_types(d->mPayloads));
499 }
500}
501#else
502void Item::throwPayloadException(int spid, int mtid) const
503{
504 Q_D(const Item);
505 if (d->mPayloads.empty()) {
506 throw PayloadException("No payload set");
507 } else {
508 throw PayloadException(QString::fromLatin1("Wrong payload type (requested: %1; present: %2")
509 .arg(format_type(spid, mtid), format_types(d->mPayloads)));
510 }
511}
512#endif
513
514void Item::setPayloadBase(PayloadBase *p)
515{
516 d_func()->setLegacyPayloadBaseImpl(std::auto_ptr<PayloadBase>(p));
517}
518
519void ItemPrivate::setLegacyPayloadBaseImpl(std::auto_ptr<PayloadBase> p)
520{
521 if (const shared_ptr<const std::pair<int, int> > pair = lookupLegacyMapping(mMimeType, p.get())) {
522 std::auto_ptr<PayloadBase> clone;
523 if (p.get()) {
524 clone.reset(p->clone());
525 }
526 setPayloadBaseImpl(pair->first, pair->second, p, false);
527 mLegacyPayload.reset(clone.release());
528 } else {
529 mPayloads.clear();
530 mLegacyPayload.reset(p.release());
531 }
532}
533
534void Item::setPayloadBaseV2(int spid, int mtid, std::auto_ptr<PayloadBase> p)
535{
536 d_func()->setPayloadBaseImpl(spid, mtid, p, false);
537}
538
539void Item::addPayloadBaseVariant(int spid, int mtid, std::auto_ptr<PayloadBase> p) const
540{
541 d_func()->setPayloadBaseImpl(spid, mtid, p, true);
542}
543
544QSet<QByteArray> Item::cachedPayloadParts() const
545{
546 Q_D(const Item);
547 return d->mCachedPayloadParts;
548}
549
550void Item::setCachedPayloadParts(const QSet< QByteArray > &cachedParts)
551{
552 Q_D(Item);
553 d->mCachedPayloadParts = cachedParts;
554}
555
556QSet<QByteArray> Item::availablePayloadParts() const
557{
558 return ItemSerializer::availableParts(*this);
559}
560
561QVector<int> Item::availablePayloadMetaTypeIds() const
562{
563 QVector<int> result;
564 Q_D(const Item);
565 result.reserve(d->mPayloads.size());
566 // Stable Insertion Sort - N is typically _very_ low (1 or 2).
567 for (PayloadContainer::const_iterator it = d->mPayloads.begin(), end = d->mPayloads.end() ; it != end ; ++it) {
568 result.insert(std::upper_bound(result.begin(), result.end(), it->metaTypeId), it->metaTypeId);
569 }
570 return result;
571}
572
573void Item::apply(const Item &other)
574{
575 if (mimeType() != other.mimeType() || id() != other.id()) {
576 kDebug() << "mimeType() = " << mimeType() << "; other.mimeType() = " << other.mimeType();
577 kDebug() << "id() = " << id() << "; other.id() = " << other.id();
578 Q_ASSERT_X(false, "Item::apply", "mimetype or id missmatch");
579 }
580
581 setRemoteId(other.remoteId());
582 setRevision(other.revision());
583 setRemoteRevision(other.remoteRevision());
584 setFlags(other.flags());
585 setTags(other.tags());
586 setModificationTime(other.modificationTime());
587 setSize(other.size());
588 setParentCollection(other.parentCollection());
589 setStorageCollectionId(other.storageCollectionId());
590
591 QList<QByteArray> attrs;
592 foreach (Attribute *attribute, other.attributes()) {
593 addAttribute(attribute->clone());
594 attrs.append(attribute->type());
595 }
596
597 QMutableHashIterator<QByteArray, Attribute *> it(d_ptr->mAttributes);
598 while (it.hasNext()) {
599 it.next();
600 if (!attrs.contains(it.key())) {
601 delete it.value();
602 it.remove();
603 }
604 }
605
606 ItemSerializer::apply(*this, other);
607 d_func()->resetChangeLog();
608}
609
610AKONADI_DEFINE_PRIVATE(Item)
611