1 /*
2   This file is part of the kcalcore library.
3 
4   SPDX-FileCopyrightText: 2001,2004 Cornelius Schumacher <schumacher@kde.org>
5   SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6   SPDX-FileCopyrightText: 2009 Nokia Corporation and/or its subsidiary(-ies). All rights reserved.
7   SPDX-FileContributor: Alvaro Manera <alvaro.manera@nokia.com>
8 
9   SPDX-License-Identifier: LGPL-2.0-or-later
10 */
11 /**
12   @file
13   This file is part of the API for handling calendar data and
14   defines the IncidenceBase class.
15 
16   @brief
17   An abstract base class that provides a common base for all calendar incidence
18   classes.
19 
20   @author Cornelius Schumacher \<schumacher@kde.org\>
21   @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
22 */
23 
24 #include "incidencebase.h"
25 #include "incidencebase_p.h"
26 #include "calformat.h"
27 #include "utils_p.h"
28 #include "visitor.h"
29 
30 #include "kcalendarcore_debug.h"
31 #include <QTime>
32 
33 #include <QStringList>
34 
35 #define KCALCORE_MAGIC_NUMBER 0xCA1C012E
36 #define KCALCORE_SERIALIZATION_VERSION 1
37 
38 using namespace KCalendarCore;
39 
40 //@cond PRIVATE
init(const IncidenceBasePrivate & other)41 void IncidenceBasePrivate::init(const IncidenceBasePrivate &other)
42 {
43     mLastModified = other.mLastModified;
44     mDtStart = other.mDtStart;
45     mOrganizer = other.mOrganizer;
46     mUid = other.mUid;
47     mDuration = other.mDuration;
48     mAllDay = other.mAllDay;
49     mHasDuration = other.mHasDuration;
50 
51     mComments = other.mComments;
52     mContacts = other.mContacts;
53 
54     mAttendees = other.mAttendees;
55     mAttendees.reserve(other.mAttendees.count());
56     mUrl = other.mUrl;
57 }
58 
59 //@endcond
60 
IncidenceBase()61 IncidenceBase::IncidenceBase()
62     : d(new KCalendarCore::IncidenceBasePrivate)
63 {
64     mReadOnly = false;
65     setUid(CalFormat::createUniqueId());
66 }
67 
IncidenceBase(const IncidenceBase & i)68 IncidenceBase::IncidenceBase(const IncidenceBase &i)
69     : CustomProperties(i)
70     , d(new KCalendarCore::IncidenceBasePrivate(*i.d))
71 {
72     mReadOnly = i.mReadOnly;
73 }
74 
~IncidenceBase()75 IncidenceBase::~IncidenceBase()
76 {
77     delete d;
78 }
79 
operator =(const IncidenceBase & other)80 IncidenceBase &IncidenceBase::operator=(const IncidenceBase &other)
81 {
82     Q_ASSERT(type() == other.type());
83 
84     startUpdates();
85 
86     // assign is virtual, will call the derived class's
87     IncidenceBase &ret = assign(other);
88     endUpdates();
89     return ret;
90 }
91 
assign(const IncidenceBase & other)92 IncidenceBase &IncidenceBase::assign(const IncidenceBase &other)
93 {
94     CustomProperties::operator=(other);
95     d->init(*other.d);
96     mReadOnly = other.mReadOnly;
97     d->mDirtyFields.clear();
98     d->mDirtyFields.insert(FieldUnknown);
99     return *this;
100 }
101 
operator ==(const IncidenceBase & i2) const102 bool IncidenceBase::operator==(const IncidenceBase &i2) const
103 {
104     if (i2.type() != type()) {
105         return false;
106     } else {
107         // equals is virtual, so here we're calling the derived class method
108         return equals(i2);
109     }
110 }
111 
operator !=(const IncidenceBase & i2) const112 bool IncidenceBase::operator!=(const IncidenceBase &i2) const
113 {
114     return !operator==(i2);
115 }
116 
equals(const IncidenceBase & other) const117 bool IncidenceBase::equals(const IncidenceBase &other) const
118 {
119     if (attendees().count() != other.attendees().count()) {
120         // qCDebug(KCALCORE_LOG) << "Attendee count is different";
121         return false;
122     }
123 
124     // TODO Does the order of attendees in the list really matter?
125     // Please delete this comment if you know it's ok, kthx
126     const Attendee::List list = attendees();
127     const Attendee::List otherList = other.attendees();
128 
129     if (list.size() != otherList.size()) {
130         return false;
131     }
132 
133     auto [it1, it2] = std::mismatch(list.cbegin(), list.cend(), otherList.cbegin(), otherList.cend());
134 
135     // Checking the iterator from one list only, since we've already checked
136     // they are the same size
137     if (it1 != list.cend()) {
138         // qCDebug(KCALCORE_LOG) << "Attendees are different";
139         return false;
140     }
141 
142     if (!CustomProperties::operator==(other)) {
143         // qCDebug(KCALCORE_LOG) << "Properties are different";
144         return false;
145     }
146 
147     // Don't compare lastModified, otherwise the operator is not
148     // of much use. We are not comparing for identity, after all.
149     // no need to compare mObserver
150 
151     bool a = ((dtStart() == other.dtStart()) || (!dtStart().isValid() && !other.dtStart().isValid()));
152     bool b = organizer() == other.organizer();
153     bool c = uid() == other.uid();
154     bool d = allDay() == other.allDay();
155     bool e = duration() == other.duration();
156     bool f = hasDuration() == other.hasDuration();
157     bool g = url() == other.url();
158 
159     // qCDebug(KCALCORE_LOG) << a << b << c << d << e << f << g;
160     return a && b && c && d && e && f && g;
161 }
162 
accept(Visitor & v,const IncidenceBase::Ptr & incidence)163 bool IncidenceBase::accept(Visitor &v, const IncidenceBase::Ptr &incidence)
164 {
165     Q_UNUSED(v);
166     Q_UNUSED(incidence);
167     return false;
168 }
169 
setUid(const QString & uid)170 void IncidenceBase::setUid(const QString &uid)
171 {
172     if (d->mUid != uid) {
173         update();
174         d->mUid = uid;
175         d->mDirtyFields.insert(FieldUid);
176         updated();
177     }
178 }
179 
uid() const180 QString IncidenceBase::uid() const
181 {
182     return d->mUid;
183 }
184 
setLastModified(const QDateTime & lm)185 void IncidenceBase::setLastModified(const QDateTime &lm)
186 {
187     // DON'T! updated() because we call this from
188     // Calendar::updateEvent().
189 
190     d->mDirtyFields.insert(FieldLastModified);
191 
192     // Convert to UTC and remove milliseconds part.
193     QDateTime current = lm.toUTC();
194     QTime t = current.time();
195     t.setHMS(t.hour(), t.minute(), t.second(), 0);
196     current.setTime(t);
197 
198     d->mLastModified = current;
199 }
200 
lastModified() const201 QDateTime IncidenceBase::lastModified() const
202 {
203     return d->mLastModified;
204 }
205 
setOrganizer(const Person & organizer)206 void IncidenceBase::setOrganizer(const Person &organizer)
207 {
208     update();
209     // we don't check for readonly here, because it is
210     // possible that by setting the organizer we are changing
211     // the event's readonly status...
212     d->mOrganizer = organizer;
213 
214     d->mDirtyFields.insert(FieldOrganizer);
215 
216     updated();
217 }
218 
setOrganizer(const QString & o)219 void IncidenceBase::setOrganizer(const QString &o)
220 {
221     QString mail(o);
222     if (mail.startsWith(QLatin1String("MAILTO:"), Qt::CaseInsensitive)) {
223         mail.remove(0, 7);
224     }
225 
226     // split the string into full name plus email.
227     const Person organizer = Person::fromFullName(mail);
228     setOrganizer(organizer);
229 }
230 
organizer() const231 Person IncidenceBase::organizer() const
232 {
233     return d->mOrganizer;
234 }
235 
setReadOnly(bool readOnly)236 void IncidenceBase::setReadOnly(bool readOnly)
237 {
238     mReadOnly = readOnly;
239 }
240 
isReadOnly() const241 bool IncidenceBase::isReadOnly() const
242 {
243     return mReadOnly;
244 }
245 
setDtStart(const QDateTime & dtStart)246 void IncidenceBase::setDtStart(const QDateTime &dtStart)
247 {
248     //  if ( mReadOnly ) return;
249 
250     if (!dtStart.isValid() && type() != IncidenceBase::TypeTodo) {
251         qCWarning(KCALCORE_LOG) << "Invalid dtStart";
252     }
253 
254     if (d->mDtStart != dtStart) {
255         update();
256         d->mDtStart = dtStart;
257         d->mDirtyFields.insert(FieldDtStart);
258         updated();
259     }
260 }
261 
dtStart() const262 QDateTime IncidenceBase::dtStart() const
263 {
264     return d->mDtStart;
265 }
266 
allDay() const267 bool IncidenceBase::allDay() const
268 {
269     return d->mAllDay;
270 }
271 
setAllDay(bool f)272 void IncidenceBase::setAllDay(bool f)
273 {
274     if (mReadOnly || f == d->mAllDay) {
275         return;
276     }
277     update();
278     d->mAllDay = f;
279     if (d->mDtStart.isValid()) {
280         d->mDirtyFields.insert(FieldDtStart);
281     }
282     updated();
283 }
284 
shiftTimes(const QTimeZone & oldZone,const QTimeZone & newZone)285 void IncidenceBase::shiftTimes(const QTimeZone &oldZone, const QTimeZone &newZone)
286 {
287     update();
288     d->mDtStart = d->mDtStart.toTimeZone(oldZone);
289     d->mDtStart.setTimeZone(newZone);
290     d->mDirtyFields.insert(FieldDtStart);
291     updated();
292 }
293 
addComment(const QString & comment)294 void IncidenceBase::addComment(const QString &comment)
295 {
296     update();
297     d->mComments += comment;
298     d->mDirtyFields.insert(FieldComment);
299     updated();
300 }
301 
removeComment(const QString & comment)302 bool IncidenceBase::removeComment(const QString &comment)
303 {
304     auto it = std::find(d->mComments.begin(), d->mComments.end(), comment);
305     bool found = it != d->mComments.end();
306     if (found) {
307         update();
308         d->mComments.erase(it);
309         d->mDirtyFields.insert(FieldComment);
310         updated();
311     }
312     return found;
313 }
314 
clearComments()315 void IncidenceBase::clearComments()
316 {
317     update();
318     d->mDirtyFields.insert(FieldComment);
319     d->mComments.clear();
320     updated();
321 }
322 
comments() const323 QStringList IncidenceBase::comments() const
324 {
325     return d->mComments;
326 }
327 
addContact(const QString & contact)328 void IncidenceBase::addContact(const QString &contact)
329 {
330     if (!contact.isEmpty()) {
331         update();
332         d->mContacts += contact;
333         d->mDirtyFields.insert(FieldContact);
334         updated();
335     }
336 }
337 
removeContact(const QString & contact)338 bool IncidenceBase::removeContact(const QString &contact)
339 {
340     auto it = std::find(d->mContacts.begin(), d->mContacts.end(), contact);
341     bool found = it != d->mContacts.end();
342     if (found) {
343         update();
344         d->mContacts.erase(it);
345         d->mDirtyFields.insert(FieldContact);
346         updated();
347     }
348     return found;
349 }
350 
clearContacts()351 void IncidenceBase::clearContacts()
352 {
353     update();
354     d->mDirtyFields.insert(FieldContact);
355     d->mContacts.clear();
356     updated();
357 }
358 
contacts() const359 QStringList IncidenceBase::contacts() const
360 {
361     return d->mContacts;
362 }
363 
addAttendee(const Attendee & a,bool doupdate)364 void IncidenceBase::addAttendee(const Attendee &a, bool doupdate)
365 {
366     if (a.isNull() || mReadOnly) {
367         return;
368     }
369     Q_ASSERT(!a.uid().isEmpty());
370 
371     if (doupdate) {
372         update();
373     }
374 
375     d->mAttendees.append(a);
376     if (doupdate) {
377         d->mDirtyFields.insert(FieldAttendees);
378         updated();
379     }
380 }
381 
attendees() const382 Attendee::List IncidenceBase::attendees() const
383 {
384     return d->mAttendees;
385 }
386 
attendeeCount() const387 int IncidenceBase::attendeeCount() const
388 {
389     return d->mAttendees.count();
390 }
391 
setAttendees(const Attendee::List & attendees,bool doUpdate)392 void IncidenceBase::setAttendees(const Attendee::List &attendees, bool doUpdate)
393 {
394     if (mReadOnly) {
395         return;
396     }
397 
398     if (doUpdate) {
399         update();
400     }
401 
402     // don't simply assign, we need the logic in addAttendee here too
403     clearAttendees();
404     d->mAttendees.reserve(attendees.size());
405     for (const auto &a : attendees) {
406         addAttendee(a, false);
407     }
408 
409     if (doUpdate) {
410         d->mDirtyFields.insert(FieldAttendees);
411         updated();
412     }
413 }
414 
clearAttendees()415 void IncidenceBase::clearAttendees()
416 {
417     if (mReadOnly) {
418         return;
419     }
420     update();
421     d->mDirtyFields.insert(FieldAttendees);
422     d->mAttendees.clear();
423     updated();
424 }
425 
attendeeByMail(const QString & email) const426 Attendee IncidenceBase::attendeeByMail(const QString &email) const
427 {
428     auto it = std::find_if(d->mAttendees.cbegin(), d->mAttendees.cend(), [&email](const Attendee &att) {
429         return att.email() == email;
430     });
431 
432     return it != d->mAttendees.cend() ? *it : Attendee{};
433 }
434 
attendeeByMails(const QStringList & emails,const QString & email) const435 Attendee IncidenceBase::attendeeByMails(const QStringList &emails, const QString &email) const
436 {
437     QStringList mails = emails;
438     if (!email.isEmpty()) {
439         mails.append(email);
440     }
441 
442     auto it = std::find_if(d->mAttendees.cbegin(), d->mAttendees.cend(), [&mails](const Attendee &a) {
443         return mails.contains(a.email());
444     });
445 
446     return it != d->mAttendees.cend() ? *it : Attendee{};
447 }
448 
attendeeByUid(const QString & uid) const449 Attendee IncidenceBase::attendeeByUid(const QString &uid) const
450 {
451     auto it = std::find_if(d->mAttendees.cbegin(), d->mAttendees.cend(), [&uid](const Attendee &a) {
452         return a.uid() == uid;
453     });
454     return it != d->mAttendees.cend() ? *it : Attendee{};
455 }
456 
setDuration(const Duration & duration)457 void IncidenceBase::setDuration(const Duration &duration)
458 {
459     update();
460     d->mDuration = duration;
461     setHasDuration(true);
462     d->mDirtyFields.insert(FieldDuration);
463     updated();
464 }
465 
duration() const466 Duration IncidenceBase::duration() const
467 {
468     return d->mDuration;
469 }
470 
setHasDuration(bool hasDuration)471 void IncidenceBase::setHasDuration(bool hasDuration)
472 {
473     d->mHasDuration = hasDuration;
474 }
475 
hasDuration() const476 bool IncidenceBase::hasDuration() const
477 {
478     return d->mHasDuration;
479 }
480 
setUrl(const QUrl & url)481 void IncidenceBase::setUrl(const QUrl &url)
482 {
483     update();
484     d->mDirtyFields.insert(FieldUrl);
485     d->mUrl = url;
486     updated();
487 }
488 
url() const489 QUrl IncidenceBase::url() const
490 {
491     return d->mUrl;
492 }
493 
registerObserver(IncidenceBase::IncidenceObserver * observer)494 void IncidenceBase::registerObserver(IncidenceBase::IncidenceObserver *observer)
495 {
496     if (observer && !d->mObservers.contains(observer)) {
497         d->mObservers.append(observer);
498     }
499 }
500 
unRegisterObserver(IncidenceBase::IncidenceObserver * observer)501 void IncidenceBase::unRegisterObserver(IncidenceBase::IncidenceObserver *observer)
502 {
503     d->mObservers.removeAll(observer);
504 }
505 
update()506 void IncidenceBase::update()
507 {
508     if (!d->mUpdateGroupLevel) {
509         d->mUpdatedPending = true;
510         const auto rid = recurrenceId();
511         for (IncidenceObserver *o : qAsConst(d->mObservers)) {
512             o->incidenceUpdate(uid(), rid);
513         }
514     }
515 }
516 
updated()517 void IncidenceBase::updated()
518 {
519     if (d->mUpdateGroupLevel) {
520         d->mUpdatedPending = true;
521     } else {
522         const auto rid = recurrenceId();
523         for (IncidenceObserver *o : qAsConst(d->mObservers)) {
524             o->incidenceUpdated(uid(), rid);
525         }
526     }
527 }
528 
startUpdates()529 void IncidenceBase::startUpdates()
530 {
531     update();
532     ++d->mUpdateGroupLevel;
533 }
534 
endUpdates()535 void IncidenceBase::endUpdates()
536 {
537     if (d->mUpdateGroupLevel > 0) {
538         if (--d->mUpdateGroupLevel == 0 && d->mUpdatedPending) {
539             d->mUpdatedPending = false;
540             updated();
541         }
542     }
543 }
544 
customPropertyUpdate()545 void IncidenceBase::customPropertyUpdate()
546 {
547     update();
548 }
549 
customPropertyUpdated()550 void IncidenceBase::customPropertyUpdated()
551 {
552     updated();
553 }
554 
recurrenceId() const555 QDateTime IncidenceBase::recurrenceId() const
556 {
557     return QDateTime();
558 }
559 
resetDirtyFields()560 void IncidenceBase::resetDirtyFields()
561 {
562     d->mDirtyFields.clear();
563 }
564 
dirtyFields() const565 QSet<IncidenceBase::Field> IncidenceBase::dirtyFields() const
566 {
567     return d->mDirtyFields;
568 }
569 
setFieldDirty(IncidenceBase::Field field)570 void IncidenceBase::setFieldDirty(IncidenceBase::Field field)
571 {
572     d->mDirtyFields.insert(field);
573 }
574 
uri() const575 QUrl IncidenceBase::uri() const
576 {
577     return QUrl(QStringLiteral("urn:x-ical:") + uid());
578 }
579 
setDirtyFields(const QSet<IncidenceBase::Field> & dirtyFields)580 void IncidenceBase::setDirtyFields(const QSet<IncidenceBase::Field> &dirtyFields)
581 {
582     d->mDirtyFields = dirtyFields;
583 }
584 
serialize(QDataStream & out) const585 void IncidenceBase::serialize(QDataStream &out) const
586 {
587     Q_UNUSED(out);
588 }
589 
deserialize(QDataStream & in)590 void IncidenceBase::deserialize(QDataStream &in)
591 {
592     Q_UNUSED(in);
593 }
594 
595 /** static */
magicSerializationIdentifier()596 quint32 IncidenceBase::magicSerializationIdentifier()
597 {
598     return KCALCORE_MAGIC_NUMBER;
599 }
600 
operator <<(QDataStream & out,const KCalendarCore::IncidenceBase::Ptr & i)601 QDataStream &KCalendarCore::operator<<(QDataStream &out, const KCalendarCore::IncidenceBase::Ptr &i)
602 {
603     if (!i) {
604         return out;
605     }
606 
607     out << static_cast<quint32>(KCALCORE_MAGIC_NUMBER); // Magic number to identify KCalendarCore data
608     out << static_cast<quint32>(KCALCORE_SERIALIZATION_VERSION);
609     out << static_cast<qint32>(i->type());
610 
611     out << *(static_cast<CustomProperties *>(i.data()));
612     serializeQDateTimeAsKDateTime(out, i->d->mLastModified);
613     serializeQDateTimeAsKDateTime(out, i->d->mDtStart);
614     out << i->organizer() << i->d->mUid << i->d->mDuration << i->d->mAllDay << i->d->mHasDuration << i->d->mComments << i->d->mContacts
615         << i->d->mAttendees.count() << i->d->mUrl;
616 
617     for (const Attendee &attendee : qAsConst(i->d->mAttendees)) {
618         out << attendee;
619     }
620 
621     // Serialize the sub-class data.
622     i->serialize(out);
623 
624     return out;
625 }
626 
operator >>(QDataStream & in,KCalendarCore::IncidenceBase::Ptr & i)627 QDataStream &KCalendarCore::operator>>(QDataStream &in, KCalendarCore::IncidenceBase::Ptr &i)
628 {
629     if (!i) {
630         return in;
631     }
632 
633     qint32 attendeeCount;
634     qint32 type;
635     quint32 magic;
636     quint32 version;
637 
638     in >> magic;
639 
640     if (magic != KCALCORE_MAGIC_NUMBER) {
641         qCWarning(KCALCORE_LOG) << "Invalid magic on serialized data";
642         return in;
643     }
644 
645     in >> version;
646 
647     if (version > KCALCORE_MAGIC_NUMBER) {
648         qCWarning(KCALCORE_LOG) << "Invalid version on serialized data";
649         return in;
650     }
651 
652     in >> type;
653 
654     in >> *(static_cast<CustomProperties *>(i.data()));
655     deserializeKDateTimeAsQDateTime(in, i->d->mLastModified);
656     deserializeKDateTimeAsQDateTime(in, i->d->mDtStart);
657     in >> i->d->mOrganizer >> i->d->mUid >> i->d->mDuration >> i->d->mAllDay >> i->d->mHasDuration >> i->d->mComments >> i->d->mContacts >> attendeeCount
658         >> i->d->mUrl;
659 
660     i->d->mAttendees.clear();
661     i->d->mAttendees.reserve(attendeeCount);
662     for (int it = 0; it < attendeeCount; it++) {
663         Attendee attendee;
664         in >> attendee;
665         i->d->mAttendees.append(attendee);
666     }
667 
668     // Deserialize the sub-class data.
669     i->deserialize(in);
670 
671     return in;
672 }
673 
~IncidenceObserver()674 IncidenceBase::IncidenceObserver::~IncidenceObserver()
675 {
676 }
677 
attendeesVariant() const678 QVariantList IncidenceBase::attendeesVariant() const
679 {
680     QVariantList l;
681     l.reserve(d->mAttendees.size());
682     std::transform(d->mAttendees.begin(), d->mAttendees.end(), std::back_inserter(l), [](const Attendee &a) {
683         return QVariant::fromValue(a);
684     });
685     return l;
686 }
687