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"