File indexing completed on 2024-05-05 12:10:45

0001 /*
0002   This file is part of the kcalcore library.
0003 
0004   SPDX-FileCopyrightText: 2001,2004 Cornelius Schumacher <schumacher@kde.org>
0005   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0006   SPDX-FileCopyrightText: 2009 Nokia Corporation and/or its subsidiary(-ies). All rights reserved.
0007   SPDX-FileContributor: Alvaro Manera <alvaro.manera@nokia.com>
0008 
0009   SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 /**
0012   @file
0013   This file is part of the API for handling calendar data and
0014   defines the IncidenceBase class.
0015 
0016   @brief
0017   An abstract base class that provides a common base for all calendar incidence
0018   classes.
0019 
0020   @author Cornelius Schumacher \<schumacher@kde.org\>
0021   @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
0022 */
0023 
0024 #include "incidencebase.h"
0025 #include "incidencebase_p.h"
0026 #include "calformat.h"
0027 #include "utils_p.h"
0028 #include "visitor.h"
0029 
0030 #include "kcalendarcore_debug.h"
0031 #include <QTime>
0032 
0033 #include <QStringList>
0034 
0035 #define KCALCORE_MAGIC_NUMBER 0xCA1C012E
0036 #define KCALCORE_SERIALIZATION_VERSION 1
0037 
0038 using namespace KCalendarCore;
0039 
0040 //@cond PRIVATE
0041 void IncidenceBasePrivate::init(const IncidenceBasePrivate &other)
0042 {
0043     mLastModified = other.mLastModified;
0044     mDtStart = other.mDtStart;
0045     mOrganizer = other.mOrganizer;
0046     mUid = other.mUid;
0047     mDuration = other.mDuration;
0048     mAllDay = other.mAllDay;
0049     mHasDuration = other.mHasDuration;
0050 
0051     mComments = other.mComments;
0052     mContacts = other.mContacts;
0053 
0054     mAttendees = other.mAttendees;
0055     mAttendees.reserve(other.mAttendees.count());
0056     mUrl = other.mUrl;
0057 }
0058 
0059 //@endcond
0060 
0061 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 91)
0062 IncidenceBase::IncidenceBase()
0063     : d_ptr(new KCalendarCore::IncidenceBasePrivate)
0064 {
0065     mReadOnly = false;
0066     setUid(CalFormat::createUniqueId());
0067 }
0068 
0069 IncidenceBase::IncidenceBase(const IncidenceBase &i)
0070     : CustomProperties(i)
0071     , d_ptr(new KCalendarCore::IncidenceBasePrivate(*i.d_ptr))
0072 {
0073     mReadOnly = i.mReadOnly;
0074 }
0075 #endif
0076 
0077 IncidenceBase::IncidenceBase(IncidenceBasePrivate  *p)
0078     : d_ptr(p)
0079 {
0080     mReadOnly = false;
0081     setUid(CalFormat::createUniqueId());
0082 }
0083 
0084 IncidenceBase::IncidenceBase(const IncidenceBase &i, IncidenceBasePrivate  *p)
0085     : CustomProperties(i)
0086     , d_ptr(p)
0087 {
0088     mReadOnly = i.mReadOnly;
0089 }
0090 
0091 IncidenceBase::~IncidenceBase()
0092 {
0093     delete d_ptr;
0094 }
0095 
0096 IncidenceBase &IncidenceBase::operator=(const IncidenceBase &other)
0097 {
0098     Q_ASSERT(type() == other.type());
0099 
0100     startUpdates();
0101 
0102     // assign is virtual, will call the derived class's
0103     IncidenceBase &ret = assign(other);
0104     endUpdates();
0105     return ret;
0106 }
0107 
0108 IncidenceBase &IncidenceBase::assign(const IncidenceBase &other)
0109 {
0110     CustomProperties::operator=(other);
0111     d_ptr->init(*other.d_ptr);
0112     mReadOnly = other.mReadOnly;
0113     d_ptr->mDirtyFields.clear();
0114     d_ptr->mDirtyFields.insert(FieldUnknown);
0115     return *this;
0116 }
0117 
0118 bool IncidenceBase::operator==(const IncidenceBase &i2) const
0119 {
0120     if (i2.type() != type()) {
0121         return false;
0122     } else {
0123         // equals is virtual, so here we're calling the derived class method
0124         return equals(i2);
0125     }
0126 }
0127 
0128 bool IncidenceBase::operator!=(const IncidenceBase &i2) const
0129 {
0130     return !operator==(i2);
0131 }
0132 
0133 bool IncidenceBase::equals(const IncidenceBase &other) const
0134 {
0135     if (attendees().count() != other.attendees().count()) {
0136         // qCDebug(KCALCORE_LOG) << "Attendee count is different";
0137         return false;
0138     }
0139 
0140     // TODO Does the order of attendees in the list really matter?
0141     // Please delete this comment if you know it's ok, kthx
0142     const Attendee::List list = attendees();
0143     const Attendee::List otherList = other.attendees();
0144 
0145     if (list.size() != otherList.size()) {
0146         return false;
0147     }
0148 
0149     auto [it1, it2] = std::mismatch(list.cbegin(), list.cend(), otherList.cbegin(), otherList.cend());
0150 
0151     // Checking the iterator from one list only, since we've already checked
0152     // they are the same size
0153     if (it1 != list.cend()) {
0154         // qCDebug(KCALCORE_LOG) << "Attendees are different";
0155         return false;
0156     }
0157 
0158     if (!CustomProperties::operator==(other)) {
0159         // qCDebug(KCALCORE_LOG) << "Properties are different";
0160         return false;
0161     }
0162 
0163     // Don't compare lastModified, otherwise the operator is not
0164     // of much use. We are not comparing for identity, after all.
0165     // no need to compare mObserver
0166 
0167     bool a = identical(dtStart(), other.dtStart());
0168     bool b = organizer() == other.organizer();
0169     bool c = uid() == other.uid();
0170     bool d = allDay() == other.allDay();
0171     bool e = duration() == other.duration();
0172     bool f = hasDuration() == other.hasDuration();
0173     bool g = url() == other.url();
0174 
0175     // qCDebug(KCALCORE_LOG) << a << b << c << d << e << f << g;
0176     return a && b && c && d && e && f && g;
0177 }
0178 
0179 bool IncidenceBase::accept(Visitor &v, const IncidenceBase::Ptr &incidence)
0180 {
0181     Q_UNUSED(v);
0182     Q_UNUSED(incidence);
0183     return false;
0184 }
0185 
0186 void IncidenceBase::setUid(const QString &uid)
0187 {
0188     if (d_ptr->mUid != uid) {
0189         update();
0190         d_ptr->mUid = uid;
0191         d_ptr->mDirtyFields.insert(FieldUid);
0192         updated();
0193     }
0194 }
0195 
0196 QString IncidenceBase::uid() const
0197 {
0198     return d_ptr->mUid;
0199 }
0200 
0201 void IncidenceBase::setLastModified(const QDateTime &lm)
0202 {
0203     // DON'T! updated() because we call this from
0204     // Calendar::updateEvent().
0205 
0206     d_ptr->mDirtyFields.insert(FieldLastModified);
0207 
0208     // Convert to UTC and remove milliseconds part.
0209     QDateTime current = lm.toUTC();
0210     QTime t = current.time();
0211     t.setHMS(t.hour(), t.minute(), t.second(), 0);
0212     current.setTime(t);
0213 
0214     d_ptr->mLastModified = current;
0215 }
0216 
0217 QDateTime IncidenceBase::lastModified() const
0218 {
0219     return d_ptr->mLastModified;
0220 }
0221 
0222 void IncidenceBase::setOrganizer(const Person &organizer)
0223 {
0224     update();
0225     // we don't check for readonly here, because it is
0226     // possible that by setting the organizer we are changing
0227     // the event's readonly status...
0228     d_ptr->mOrganizer = organizer;
0229 
0230     d_ptr->mDirtyFields.insert(FieldOrganizer);
0231 
0232     updated();
0233 }
0234 
0235 void IncidenceBase::setOrganizer(const QString &o)
0236 {
0237     QString mail(o);
0238     if (mail.startsWith(QLatin1String("MAILTO:"), Qt::CaseInsensitive)) {
0239         mail.remove(0, 7);
0240     }
0241 
0242     // split the string into full name plus email.
0243     const Person organizer = Person::fromFullName(mail);
0244     setOrganizer(organizer);
0245 }
0246 
0247 Person IncidenceBase::organizer() const
0248 {
0249     return d_ptr->mOrganizer;
0250 }
0251 
0252 void IncidenceBase::setReadOnly(bool readOnly)
0253 {
0254     mReadOnly = readOnly;
0255 }
0256 
0257 bool IncidenceBase::isReadOnly() const
0258 {
0259     return mReadOnly;
0260 }
0261 
0262 void IncidenceBase::setDtStart(const QDateTime &dtStart)
0263 {
0264     //  if ( mReadOnly ) return;
0265 
0266     if (!dtStart.isValid() && type() != IncidenceBase::TypeTodo) {
0267         qCWarning(KCALCORE_LOG) << "Invalid dtStart";
0268     }
0269 
0270     if (!identical(d_ptr->mDtStart, dtStart)) {
0271         update();
0272         d_ptr->mDtStart = dtStart;
0273         d_ptr->mDirtyFields.insert(FieldDtStart);
0274         updated();
0275     }
0276 }
0277 
0278 QDateTime IncidenceBase::dtStart() const
0279 {
0280     return d_ptr->mDtStart;
0281 }
0282 
0283 bool IncidenceBase::allDay() const
0284 {
0285     return d_ptr->mAllDay;
0286 }
0287 
0288 void IncidenceBase::setAllDay(bool f)
0289 {
0290     if (mReadOnly || f == d_ptr->mAllDay) {
0291         return;
0292     }
0293     update();
0294     d_ptr->mAllDay = f;
0295     if (d_ptr->mDtStart.isValid()) {
0296         d_ptr->mDirtyFields.insert(FieldDtStart);
0297     }
0298     updated();
0299 }
0300 
0301 void IncidenceBase::shiftTimes(const QTimeZone &oldZone, const QTimeZone &newZone)
0302 {
0303     update();
0304     d_ptr->mDtStart = d_ptr->mDtStart.toTimeZone(oldZone);
0305     d_ptr->mDtStart.setTimeZone(newZone);
0306     d_ptr->mDirtyFields.insert(FieldDtStart);
0307     updated();
0308 }
0309 
0310 void IncidenceBase::addComment(const QString &comment)
0311 {
0312     update();
0313     d_ptr->mComments += comment;
0314     d_ptr->mDirtyFields.insert(FieldComment);
0315     updated();
0316 }
0317 
0318 bool IncidenceBase::removeComment(const QString &comment)
0319 {
0320     auto it = std::find(d_ptr->mComments.begin(), d_ptr->mComments.end(), comment);
0321     bool found = it != d_ptr->mComments.end();
0322     if (found) {
0323         update();
0324         d_ptr->mComments.erase(it);
0325         d_ptr->mDirtyFields.insert(FieldComment);
0326         updated();
0327     }
0328     return found;
0329 }
0330 
0331 void IncidenceBase::clearComments()
0332 {
0333     update();
0334     d_ptr->mDirtyFields.insert(FieldComment);
0335     d_ptr->mComments.clear();
0336     updated();
0337 }
0338 
0339 QStringList IncidenceBase::comments() const
0340 {
0341     return d_ptr->mComments;
0342 }
0343 
0344 void IncidenceBase::addContact(const QString &contact)
0345 {
0346     if (!contact.isEmpty()) {
0347         update();
0348         d_ptr->mContacts += contact;
0349         d_ptr->mDirtyFields.insert(FieldContact);
0350         updated();
0351     }
0352 }
0353 
0354 bool IncidenceBase::removeContact(const QString &contact)
0355 {
0356     auto it = std::find(d_ptr->mContacts.begin(), d_ptr->mContacts.end(), contact);
0357     bool found = it != d_ptr->mContacts.end();
0358     if (found) {
0359         update();
0360         d_ptr->mContacts.erase(it);
0361         d_ptr->mDirtyFields.insert(FieldContact);
0362         updated();
0363     }
0364     return found;
0365 }
0366 
0367 void IncidenceBase::clearContacts()
0368 {
0369     update();
0370     d_ptr->mDirtyFields.insert(FieldContact);
0371     d_ptr->mContacts.clear();
0372     updated();
0373 }
0374 
0375 QStringList IncidenceBase::contacts() const
0376 {
0377     return d_ptr->mContacts;
0378 }
0379 
0380 void IncidenceBase::addAttendee(const Attendee &a, bool doupdate)
0381 {
0382     if (a.isNull() || mReadOnly) {
0383         return;
0384     }
0385     Q_ASSERT(!a.uid().isEmpty());
0386 
0387     if (doupdate) {
0388         update();
0389     }
0390 
0391     d_ptr->mAttendees.append(a);
0392     if (doupdate) {
0393         d_ptr->mDirtyFields.insert(FieldAttendees);
0394         updated();
0395     }
0396 }
0397 
0398 Attendee::List IncidenceBase::attendees() const
0399 {
0400     return d_ptr->mAttendees;
0401 }
0402 
0403 int IncidenceBase::attendeeCount() const
0404 {
0405     return d_ptr->mAttendees.count();
0406 }
0407 
0408 void IncidenceBase::setAttendees(const Attendee::List &attendees, bool doUpdate)
0409 {
0410     if (mReadOnly) {
0411         return;
0412     }
0413 
0414     // don't simply assign, we need the logic in addAttendee here too
0415     clearAttendees();
0416 
0417     if (doUpdate) {
0418         update();
0419     }
0420 
0421     d_ptr->mAttendees.reserve(attendees.size());
0422     for (const auto &a : attendees) {
0423         addAttendee(a, false);
0424     }
0425 
0426     if (doUpdate) {
0427         d_ptr->mDirtyFields.insert(FieldAttendees);
0428         updated();
0429     }
0430 }
0431 
0432 void IncidenceBase::clearAttendees()
0433 {
0434     if (mReadOnly) {
0435         return;
0436     }
0437     update();
0438     d_ptr->mDirtyFields.insert(FieldAttendees);
0439     d_ptr->mAttendees.clear();
0440     updated();
0441 }
0442 
0443 Attendee IncidenceBase::attendeeByMail(const QString &email) const
0444 {
0445     auto it = std::find_if(d_ptr->mAttendees.cbegin(), d_ptr->mAttendees.cend(), [&email](const Attendee &att) {
0446         return att.email() == email;
0447     });
0448 
0449     return it != d_ptr->mAttendees.cend() ? *it : Attendee{};
0450 }
0451 
0452 Attendee IncidenceBase::attendeeByMails(const QStringList &emails, const QString &email) const
0453 {
0454     QStringList mails = emails;
0455     if (!email.isEmpty()) {
0456         mails.append(email);
0457     }
0458 
0459     auto it = std::find_if(d_ptr->mAttendees.cbegin(), d_ptr->mAttendees.cend(), [&mails](const Attendee &a) {
0460         return mails.contains(a.email());
0461     });
0462 
0463     return it != d_ptr->mAttendees.cend() ? *it : Attendee{};
0464 }
0465 
0466 Attendee IncidenceBase::attendeeByUid(const QString &uid) const
0467 {
0468     auto it = std::find_if(d_ptr->mAttendees.cbegin(), d_ptr->mAttendees.cend(), [&uid](const Attendee &a) {
0469         return a.uid() == uid;
0470     });
0471     return it != d_ptr->mAttendees.cend() ? *it : Attendee{};
0472 }
0473 
0474 void IncidenceBase::setDuration(const Duration &duration)
0475 {
0476     update();
0477     d_ptr->mDuration = duration;
0478     setHasDuration(true);
0479     d_ptr->mDirtyFields.insert(FieldDuration);
0480     updated();
0481 }
0482 
0483 Duration IncidenceBase::duration() const
0484 {
0485     return d_ptr->mDuration;
0486 }
0487 
0488 void IncidenceBase::setHasDuration(bool hasDuration)
0489 {
0490     d_ptr->mHasDuration = hasDuration;
0491 }
0492 
0493 bool IncidenceBase::hasDuration() const
0494 {
0495     return d_ptr->mHasDuration;
0496 }
0497 
0498 void IncidenceBase::setUrl(const QUrl &url)
0499 {
0500     update();
0501     d_ptr->mDirtyFields.insert(FieldUrl);
0502     d_ptr->mUrl = url;
0503     updated();
0504 }
0505 
0506 QUrl IncidenceBase::url() const
0507 {
0508     return d_ptr->mUrl;
0509 }
0510 
0511 void IncidenceBase::registerObserver(IncidenceBase::IncidenceObserver *observer)
0512 {
0513     if (observer && !d_ptr->mObservers.contains(observer)) {
0514         d_ptr->mObservers.append(observer);
0515     }
0516 }
0517 
0518 void IncidenceBase::unRegisterObserver(IncidenceBase::IncidenceObserver *observer)
0519 {
0520     d_ptr->mObservers.removeAll(observer);
0521 }
0522 
0523 void IncidenceBase::update()
0524 {
0525     if (!d_ptr->mUpdateGroupLevel) {
0526         d_ptr->mUpdatedPending = true;
0527         const auto rid = recurrenceId();
0528         for (IncidenceObserver *o : std::as_const(d_ptr->mObservers)) {
0529             o->incidenceUpdate(uid(), rid);
0530         }
0531     }
0532 }
0533 
0534 void IncidenceBase::updated()
0535 {
0536     if (d_ptr->mUpdateGroupLevel) {
0537         d_ptr->mUpdatedPending = true;
0538     } else {
0539         const auto rid = recurrenceId();
0540         for (IncidenceObserver *o : std::as_const(d_ptr->mObservers)) {
0541             o->incidenceUpdated(uid(), rid);
0542         }
0543     }
0544 }
0545 
0546 void IncidenceBase::startUpdates()
0547 {
0548     update();
0549     ++d_ptr->mUpdateGroupLevel;
0550 }
0551 
0552 void IncidenceBase::endUpdates()
0553 {
0554     if (d_ptr->mUpdateGroupLevel > 0) {
0555         if (--d_ptr->mUpdateGroupLevel == 0 && d_ptr->mUpdatedPending) {
0556             d_ptr->mUpdatedPending = false;
0557             updated();
0558         }
0559     }
0560 }
0561 
0562 void IncidenceBase::customPropertyUpdate()
0563 {
0564     update();
0565 }
0566 
0567 void IncidenceBase::customPropertyUpdated()
0568 {
0569     updated();
0570 }
0571 
0572 QDateTime IncidenceBase::recurrenceId() const
0573 {
0574     return QDateTime();
0575 }
0576 
0577 void IncidenceBase::resetDirtyFields()
0578 {
0579     d_ptr->mDirtyFields.clear();
0580 }
0581 
0582 QSet<IncidenceBase::Field> IncidenceBase::dirtyFields() const
0583 {
0584     return d_ptr->mDirtyFields;
0585 }
0586 
0587 void IncidenceBase::setFieldDirty(IncidenceBase::Field field)
0588 {
0589     d_ptr->mDirtyFields.insert(field);
0590 }
0591 
0592 QUrl IncidenceBase::uri() const
0593 {
0594     return QUrl(QStringLiteral("urn:x-ical:") + uid());
0595 }
0596 
0597 void IncidenceBase::setDirtyFields(const QSet<IncidenceBase::Field> &dirtyFields)
0598 {
0599     d_ptr->mDirtyFields = dirtyFields;
0600 }
0601 
0602 void IncidenceBase::serialize(QDataStream &out) const
0603 {
0604     Q_UNUSED(out);
0605 }
0606 
0607 void IncidenceBase::deserialize(QDataStream &in)
0608 {
0609     Q_UNUSED(in);
0610 }
0611 
0612 /** static */
0613 quint32 IncidenceBase::magicSerializationIdentifier()
0614 {
0615     return KCALCORE_MAGIC_NUMBER;
0616 }
0617 
0618 bool KCalendarCore::identical(QDateTime dt1, QDateTime dt2)
0619 {
0620     return dt1 == dt2 && dt1.timeSpec() == dt2.timeSpec() && dt1.timeZone() == dt2.timeZone();
0621 }
0622 
0623 QDataStream &KCalendarCore::operator<<(QDataStream &out, const KCalendarCore::IncidenceBase::Ptr &i)
0624 {
0625     if (!i) {
0626         return out;
0627     }
0628 
0629     out << static_cast<quint32>(KCALCORE_MAGIC_NUMBER); // Magic number to identify KCalendarCore data
0630     out << static_cast<quint32>(KCALCORE_SERIALIZATION_VERSION);
0631     out << static_cast<qint32>(i->type());
0632 
0633     out << *(static_cast<CustomProperties *>(i.data()));
0634     serializeQDateTimeAsKDateTime(out, i->d_ptr->mLastModified);
0635     serializeQDateTimeAsKDateTime(out, i->d_ptr->mDtStart);
0636     out << i->organizer() << i->d_ptr->mUid << i->d_ptr->mDuration << i->d_ptr->mAllDay << i->d_ptr->mHasDuration << i->d_ptr->mComments << i->d_ptr->mContacts
0637         << (qint32)i->d_ptr->mAttendees.count() << i->d_ptr->mUrl;
0638 
0639     for (const Attendee &attendee : std::as_const(i->d_ptr->mAttendees)) {
0640         out << attendee;
0641     }
0642 
0643     // Serialize the sub-class data.
0644     i->serialize(out);
0645 
0646     return out;
0647 }
0648 
0649 QDataStream &KCalendarCore::operator>>(QDataStream &in, KCalendarCore::IncidenceBase::Ptr &i)
0650 {
0651     if (!i) {
0652         return in;
0653     }
0654 
0655     qint32 attendeeCount;
0656     qint32 type;
0657     quint32 magic;
0658     quint32 version;
0659 
0660     in >> magic;
0661 
0662     if (magic != KCALCORE_MAGIC_NUMBER) {
0663         qCWarning(KCALCORE_LOG) << "Invalid magic on serialized data";
0664         return in;
0665     }
0666 
0667     in >> version;
0668 
0669     if (version > KCALCORE_MAGIC_NUMBER) {
0670         qCWarning(KCALCORE_LOG) << "Invalid version on serialized data";
0671         return in;
0672     }
0673 
0674     in >> type;
0675 
0676     in >> *(static_cast<CustomProperties *>(i.data()));
0677     deserializeKDateTimeAsQDateTime(in, i->d_ptr->mLastModified);
0678     deserializeKDateTimeAsQDateTime(in, i->d_ptr->mDtStart);
0679     in >> i->d_ptr->mOrganizer >> i->d_ptr->mUid >> i->d_ptr->mDuration >> i->d_ptr->mAllDay >> i->d_ptr->mHasDuration >> i->d_ptr->mComments >> i->d_ptr->mContacts >> attendeeCount
0680         >> i->d_ptr->mUrl;
0681 
0682     i->d_ptr->mAttendees.clear();
0683     i->d_ptr->mAttendees.reserve(attendeeCount);
0684     for (int it = 0; it < attendeeCount; it++) {
0685         Attendee attendee;
0686         in >> attendee;
0687         i->d_ptr->mAttendees.append(attendee);
0688     }
0689 
0690     // Deserialize the sub-class data.
0691     i->deserialize(in);
0692 
0693     return in;
0694 }
0695 
0696 IncidenceBase::IncidenceObserver::~IncidenceObserver()
0697 {
0698 }
0699 
0700 QVariantList IncidenceBase::attendeesVariant() const
0701 {
0702     QVariantList l;
0703     l.reserve(d_ptr->mAttendees.size());
0704     std::transform(d_ptr->mAttendees.begin(), d_ptr->mAttendees.end(), std::back_inserter(l), [](const Attendee &a) {
0705         return QVariant::fromValue(a);
0706     });
0707     return l;
0708 }
0709 
0710 #include "moc_incidencebase.cpp"