File indexing completed on 2024-04-21 03:52:50

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