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