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 | |
35 | using namespace Akonadi; |
36 | using namespace boost; |
37 | |
38 | namespace { |
39 | |
40 | struct nodelete { |
41 | template <typename T> |
42 | void operator()(T *) |
43 | { |
44 | } |
45 | }; |
46 | |
47 | struct 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 | |
57 | typedef QHash< QString, std::map< boost::shared_ptr<PayloadBase>, std::pair<int, int>, ByTypeId > > LegacyMap; |
58 | Q_GLOBAL_STATIC(LegacyMap, typeInfoToMetaTypeIdMap) |
59 | Q_GLOBAL_STATIC_WITH_ARGS(QReadWriteLock, legacyMapLock, (QReadWriteLock::Recursive)) |
60 | |
61 | void 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 | |
73 | namespace { |
74 | class MyReadLocker { |
75 | public: |
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 | } |
108 | private: |
109 | QReadWriteLock *const rwl; |
110 | bool locked; |
111 | }; |
112 | } |
113 | |
114 | static 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 |
131 | const char *Item::FullPayload = "RFC822" ; |
132 | |
133 | Item::Item() |
134 | : Entity(new ItemPrivate) |
135 | { |
136 | } |
137 | |
138 | Item::Item(Id id) |
139 | : Entity(new ItemPrivate(id)) |
140 | { |
141 | } |
142 | |
143 | Item::Item(const QString &mimeType) |
144 | : Entity(new ItemPrivate) |
145 | { |
146 | d_func()->mMimeType = mimeType; |
147 | } |
148 | |
149 | Item::Item(const Item &other) |
150 | : Entity(other) |
151 | { |
152 | } |
153 | |
154 | Item::~Item() |
155 | { |
156 | } |
157 | |
158 | Item::Flags Item::flags() const |
159 | { |
160 | return d_func()->mFlags; |
161 | } |
162 | |
163 | void 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 | |
176 | void 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 | |
189 | void Item::setFlags(const Flags &flags) |
190 | { |
191 | Q_D(Item); |
192 | d->mFlags = flags; |
193 | d->mFlagsOverwritten = true; |
194 | } |
195 | |
196 | void Item::clearFlags() |
197 | { |
198 | Q_D(Item); |
199 | d->mFlags.clear(); |
200 | d->mFlagsOverwritten = true; |
201 | } |
202 | |
203 | QDateTime Item::modificationTime() const |
204 | { |
205 | return d_func()->mModificationTime; |
206 | } |
207 | |
208 | void Item::setModificationTime(const QDateTime &datetime) |
209 | { |
210 | d_func()->mModificationTime = datetime; |
211 | } |
212 | |
213 | bool Item::hasFlag(const QByteArray &name) const |
214 | { |
215 | return d_func()->mFlags.contains(name); |
216 | } |
217 | |
218 | void Item::setTags(const Tag::List &list) |
219 | { |
220 | Q_D(Item); |
221 | d->mTags = list; |
222 | d->mTagsOverwritten = true; |
223 | } |
224 | |
225 | void 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 | |
238 | void Item::clearTags() |
239 | { |
240 | Q_D(Item); |
241 | d->mTags.clear(); |
242 | d->mTagsOverwritten = true; |
243 | } |
244 | |
245 | void 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 | |
258 | bool Item::hasTag(const Tag &tag) const |
259 | { |
260 | Q_D(const Item); |
261 | return d->mTags.contains(tag); |
262 | } |
263 | |
264 | Tag::List Item::tags() const |
265 | { |
266 | Q_D(const Item); |
267 | return d->mTags; |
268 | } |
269 | |
270 | QSet<QByteArray> Item::loadedPayloadParts() const |
271 | { |
272 | return ItemSerializer::parts(*this); |
273 | } |
274 | |
275 | QByteArray Item::payloadData() const |
276 | { |
277 | int version = 0; |
278 | QByteArray data; |
279 | ItemSerializer::serialize(*this, FullPayload, data, version); |
280 | return data; |
281 | } |
282 | |
283 | void Item::setPayloadFromData(const QByteArray &data) |
284 | { |
285 | ItemSerializer::deserialize(*this, FullPayload, data, 0, false); |
286 | } |
287 | |
288 | void Item::clearPayload() |
289 | { |
290 | d_func()->mClearPayload = true; |
291 | } |
292 | |
293 | int Item::revision() const |
294 | { |
295 | return d_func()->mRevision; |
296 | } |
297 | |
298 | void Item::setRevision(int rev) |
299 | { |
300 | d_func()->mRevision = rev; |
301 | } |
302 | |
303 | Entity::Id Item::storageCollectionId() const |
304 | { |
305 | return d_func()->mCollectionId; |
306 | } |
307 | |
308 | void Item::setStorageCollectionId(Entity::Id collectionId) |
309 | { |
310 | d_func()->mCollectionId = collectionId; |
311 | } |
312 | |
313 | QString Item::mimeType() const |
314 | { |
315 | return d_func()->mMimeType; |
316 | } |
317 | |
318 | void Item::setSize(qint64 size) |
319 | { |
320 | Q_D(Item); |
321 | d->mSize = size; |
322 | d->mSizeChanged = true; |
323 | } |
324 | |
325 | qint64 Item::size() const |
326 | { |
327 | return d_func()->mSize; |
328 | } |
329 | |
330 | void Item::setMimeType(const QString &mimeType) |
331 | { |
332 | d_func()->mMimeType = mimeType; |
333 | } |
334 | |
335 | void Item::setGid(const QString &id) |
336 | { |
337 | d_func()->mGid = id; |
338 | } |
339 | |
340 | QString Item::gid() const |
341 | { |
342 | return d_func()->mGid; |
343 | } |
344 | |
345 | void Item::setVirtualReferences(const Collection::List &collections) |
346 | { |
347 | d_func()->mVirtualReferences = collections; |
348 | } |
349 | |
350 | Collection::List Item::virtualReferences() const |
351 | { |
352 | return d_func()->mVirtualReferences; |
353 | } |
354 | |
355 | bool Item::hasPayload() const |
356 | { |
357 | return d_func()->hasMetaTypeId(-1); |
358 | } |
359 | |
360 | KUrl 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 | |
373 | Item 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 | |
389 | namespace { |
390 | class Dummy |
391 | { |
392 | }; |
393 | } |
394 | |
395 | Q_GLOBAL_STATIC(Payload<Dummy>, dummyPayload) |
396 | |
397 | PayloadBase *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 | |
408 | void 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 | |
419 | PayloadBase *Item::payloadBaseV2(int spid, int mtid) const |
420 | { |
421 | return d_func()->payloadBaseImpl(spid, mtid); |
422 | } |
423 | |
424 | namespace { |
425 | class ConversionGuard |
426 | { |
427 | const bool old; |
428 | bool &b; |
429 | public: |
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 | |
442 | bool 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 | |
475 | static 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 | |
481 | static 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 |
491 | QString 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 |
502 | void 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 | |
514 | void Item::setPayloadBase(PayloadBase *p) |
515 | { |
516 | d_func()->setLegacyPayloadBaseImpl(std::auto_ptr<PayloadBase>(p)); |
517 | } |
518 | |
519 | void 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 | |
534 | void Item::setPayloadBaseV2(int spid, int mtid, std::auto_ptr<PayloadBase> p) |
535 | { |
536 | d_func()->setPayloadBaseImpl(spid, mtid, p, false); |
537 | } |
538 | |
539 | void Item::addPayloadBaseVariant(int spid, int mtid, std::auto_ptr<PayloadBase> p) const |
540 | { |
541 | d_func()->setPayloadBaseImpl(spid, mtid, p, true); |
542 | } |
543 | |
544 | QSet<QByteArray> Item::cachedPayloadParts() const |
545 | { |
546 | Q_D(const Item); |
547 | return d->mCachedPayloadParts; |
548 | } |
549 | |
550 | void Item::setCachedPayloadParts(const QSet< QByteArray > &cachedParts) |
551 | { |
552 | Q_D(Item); |
553 | d->mCachedPayloadParts = cachedParts; |
554 | } |
555 | |
556 | QSet<QByteArray> Item::availablePayloadParts() const |
557 | { |
558 | return ItemSerializer::availableParts(*this); |
559 | } |
560 | |
561 | QVector<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 | |
573 | void 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 | |
610 | AKONADI_DEFINE_PRIVATE(Item) |
611 | |