File indexing completed on 2025-02-16 04:50:26

0001 /*
0002  * SPDX-FileCopyrightText: 2012 Christian Mollekopf <mollekopf@kolabsys.com>
0003  *
0004  * SPDX-License-Identifier: LGPL-3.0-or-later
0005  */
0006 
0007 #include "freebusy.h"
0008 #include "conversion/commonconversion.h"
0009 #include "conversion/kcalconversion.h"
0010 #include "libkolab-version.h"
0011 #include "pimkolab_debug.h"
0012 #include <KCalendarCore/FreeBusy>
0013 #include <KCalendarCore/ICalFormat>
0014 #include <QTime>
0015 #include <QUuid>
0016 
0017 // namespace KCalendarCore {
0018 //     struct KCalFreebusy
0019 // {
0020 //
0021 // void init( const Event::List &eventList, const KDateTime &start, const KDateTime &end )
0022 // {
0023 //     mDtStart = start.toUtc();
0024 //     mDtEnd = end.toUtc();
0025 //
0026 //   // Loops through every event in the calendar
0027 //   Event::List::ConstIterator it;
0028 //   for ( it = eventList.constBegin(); it != eventList.constEnd(); ++it ) {
0029 //     Event::Ptr event = *it;
0030 //
0031 //     // If this event is transparent it shouldn't be in the freebusy list.
0032 //     if ( event->transparency() == Event::Transparent ) {
0033 //       continue;
0034 //     }
0035 //
0036 //     if ( event->hasRecurrenceId() ) {
0037 //       continue; //TODO apply special period exception (duration could be different)
0038 //     }
0039 //
0040 //     const KDateTime eventStart = event->dtStart().toUtc();
0041 //     const KDateTime eventEnd = event->dtEnd().toUtc();
0042 //
0043 //     if ( event->recurs() ) {
0044 //         const KCalendarCore::Duration duration( eventStart, eventEnd );
0045 //         const KCalendarCore::DateTimeList list = event->recurrence()->timesInInterval(start, end);
0046 //         for (const KDateTime &dt : list) {
0047 //             const KDateTime utc = dt.toUtc();
0048 //             addLocalPeriod(utc, duration.end(utc) );
0049 //         }
0050 //     } else {
0051 //         addLocalPeriod( eventStart, eventEnd );
0052 //     }
0053 //   }
0054 //
0055 // //   q->sortList();
0056 // }
0057 //
0058 // bool addLocalPeriod(
0059 //                                         const KDateTime &eventStart,
0060 //                                         const KDateTime &eventEnd )
0061 // {
0062 //   KDateTime tmpStart;
0063 //   KDateTime tmpEnd;
0064 //
0065 //   //Check to see if the start *or* end of the event is
0066 //   //between the start and end of the freebusy dates.
0067 //   if ( !( ( ( mDtStart.secsTo( eventStart ) >= 0 ) &&
0068 //             ( eventStart.secsTo( mDtEnd ) >= 0 ) ) ||
0069 //           ( ( mDtStart.secsTo( eventEnd ) >= 0 ) &&
0070 //             ( eventEnd.secsTo( mDtEnd ) >= 0 ) ) ) ) {
0071 //       qCDebug(PIMKOLAB_LOG) << "out of scope";
0072 //     return false;
0073 //   }
0074 //
0075 // //   qCDebug(PIMKOLAB_LOG) << eventStart.date().toString() << eventStart.time().toString() << mDtStart.toString();
0076 //   if ( eventStart < mDtStart ) { //eventStart is before start
0077 // //       qCDebug(PIMKOLAB_LOG) << "use start";
0078 //     tmpStart = mDtStart;
0079 //   } else {
0080 //     tmpStart = eventStart;
0081 //   }
0082 //
0083 //   qCDebug(PIMKOLAB_LOG) << eventEnd.date().toString() << eventEnd.time().toString() << mDtEnd.toString();
0084 //   if ( eventEnd > mDtEnd ) { //event end is after dtEnd
0085 // //     qCDebug(PIMKOLAB_LOG) << "use end";
0086 //     tmpEnd = mDtEnd;
0087 //   } else {
0088 //     tmpEnd = eventEnd;
0089 //   }
0090 //
0091 // //   qCDebug(PIMKOLAB_LOG) << "########## " << tmpStart.isValid();
0092 //   Q_ASSERT(tmpStart.isValid());
0093 //   Q_ASSERT(tmpEnd.isValid());
0094 // //   qCDebug(PIMKOLAB_LOG) << tmpStart.date().toString() << tmpStart.time().toString() << tmpStart.toString();
0095 //
0096 //   FreeBusyPeriod p( tmpStart, tmpEnd );
0097 //   mBusyPeriods.append( p );
0098 //
0099 //   return true;
0100 // }
0101 //
0102 //     KDateTime mDtStart;
0103 //     KDateTime mDtEnd;                  // end datetime
0104 //     FreeBusyPeriod::List mBusyPeriods; // list of periods
0105 //
0106 // };
0107 //
0108 // } // Namespace
0109 
0110 namespace Kolab
0111 {
0112 namespace FreebusyUtils
0113 {
0114 static QString createUuid()
0115 {
0116     const QString uuid = QUuid::createUuid().toString();
0117     return uuid.mid(1, uuid.size() - 2);
0118 }
0119 
0120 Kolab::Period addLocalPeriod(const QDateTime &eventStart, const QDateTime &eventEnd, const QDateTime &mDtStart, const QDateTime &mDtEnd, bool allDay)
0121 {
0122     QDateTime tmpStart;
0123     QDateTime tmpEnd;
0124 
0125     // Check to see if the start *or* end of the event is
0126     // between the start and end of the freebusy dates.
0127     if (!(((mDtStart <= eventStart) && (eventStart <= mDtEnd)) || ((mDtStart <= eventEnd) && (eventEnd <= mDtEnd)))) {
0128         qCDebug(PIMKOLAB_LOG) << "event is not within the fb range, skipping";
0129         return {};
0130     }
0131 
0132     if (eventStart < mDtStart) { // eventStart is before start
0133         tmpStart = mDtStart;
0134     } else {
0135         tmpStart = eventStart;
0136     }
0137 
0138     //   qCDebug(PIMKOLAB_LOG) << eventEnd.date().toString() << eventEnd.time().toString() << mDtEnd.toString();
0139     if (eventEnd > mDtEnd) { // event end is after dtEnd
0140         tmpEnd = mDtEnd;
0141     } else {
0142         tmpEnd = eventEnd;
0143     }
0144     Q_ASSERT(tmpStart.isValid());
0145     Q_ASSERT(tmpEnd.isValid());
0146     if (allDay) {
0147         tmpStart.setTime(QTime(0, 0, 0, 0));
0148         tmpEnd.setTime(QTime(23, 59, 59, 999)); // The window is inclusive
0149     }
0150     return Kolab::Period(Kolab::Conversion::fromDate(tmpStart, allDay), Kolab::Conversion::fromDate(tmpEnd, allDay));
0151 }
0152 
0153 Freebusy generateFreeBusy(const std::vector<Event> &events, const cDateTime &startDate, const cDateTime &endDate)
0154 {
0155     QList<KCalendarCore::Event::Ptr> list;
0156     list.reserve(events.size());
0157     for (const Kolab::Event &e : events) {
0158         list.append(Kolab::Conversion::toKCalendarCore(e));
0159     }
0160     KCalendarCore::Person person(QStringLiteral("dummyname"), QStringLiteral("dummyemail"));
0161     return generateFreeBusy(list, Kolab::Conversion::toDate(startDate), Kolab::Conversion::toDate(endDate), person, startDate.isDateOnly());
0162 }
0163 
0164 Freebusy generateFreeBusy(const QList<KCalendarCore::Event::Ptr> &events,
0165                           const QDateTime &startDate,
0166                           const QDateTime &endDate,
0167                           const KCalendarCore::Person &organizer,
0168                           bool allDay)
0169 {
0170     /*
0171      * TODO the conversion of date-only values to date-time is only necessary because xCal doesn't allow date only. iCalendar doesn't seem to make this
0172      * restriction so it looks like a bug.
0173      */
0174     QDateTime start = startDate.toUTC();
0175     if (allDay) {
0176         start.setTime(QTime(0, 0, 0, 0));
0177     }
0178     QDateTime end = endDate.toUTC();
0179     if (allDay) {
0180         end = end.addDays(1);
0181         end.setTime(QTime(0, 0, 0, 0)); // The window is inclusive
0182     }
0183 
0184     // TODO try to merge that with KCalendarCore::Freebusy
0185     std::vector<Kolab::FreebusyPeriod> freebusyPeriods;
0186     for (const KCalendarCore::Event::Ptr &event : events) {
0187         // If this event is transparent it shouldn't be in the freebusy list.
0188         if (event->transparency() == KCalendarCore::Event::Transparent) {
0189             continue;
0190         }
0191 
0192         if (event->hasRecurrenceId()) {
0193             continue; // TODO apply special period exception (duration could be different)
0194         }
0195 
0196         const QDateTime eventStart = event->dtStart().toUTC();
0197         const QDateTime eventEnd = event->dtEnd().toUTC();
0198 
0199         std::vector<Kolab::Period> periods;
0200         if (event->recurs()) {
0201             const KCalendarCore::Duration duration(eventStart, eventEnd);
0202             const auto list = event->recurrence()->timesInInterval(start, end);
0203             for (const auto &dt : list) {
0204                 const auto utc = dt.toUTC();
0205                 const Kolab::Period &period = addLocalPeriod(utc, duration.end(utc), start, end, allDay);
0206                 if (period.isValid()) {
0207                     periods.push_back(period);
0208                 }
0209             }
0210         } else {
0211             const Kolab::Period &period = addLocalPeriod(eventStart, eventEnd, start, end, allDay);
0212             if (period.isValid()) {
0213                 periods.push_back(period);
0214             }
0215         }
0216         if (!periods.empty()) {
0217             Kolab::FreebusyPeriod period;
0218             period.setPeriods(periods);
0219             // TODO get busy type from event (out-of-office, tentative)
0220             period.setType(Kolab::FreebusyPeriod::Busy);
0221             period.setEvent(Kolab::Conversion::toStdString(event->uid()),
0222                             Kolab::Conversion::toStdString(event->summary()),
0223                             Kolab::Conversion::toStdString(event->location()));
0224             freebusyPeriods.push_back(period);
0225         }
0226     }
0227 
0228     Kolab::Freebusy freebusy;
0229 
0230     freebusy.setStart(Kolab::Conversion::fromDate(start, allDay));
0231     freebusy.setEnd(Kolab::Conversion::fromDate(end, allDay));
0232     freebusy.setPeriods(freebusyPeriods);
0233     freebusy.setUid(createUuid().toStdString());
0234     freebusy.setTimestamp(Kolab::Conversion::fromDate(QDateTime::currentDateTimeUtc(), false));
0235     if (!organizer.isEmpty()) {
0236         freebusy.setOrganizer(ContactReference(Kolab::ContactReference::EmailReference,
0237                                                Kolab::Conversion::toStdString(organizer.email()),
0238                                                Kolab::Conversion::toStdString(organizer.name())));
0239     }
0240 
0241     return freebusy;
0242 }
0243 
0244 Freebusy aggregateFreeBusy(const std::vector<Freebusy> &fbList, const std::string &organizerEmail, const std::string &organizerName, bool simple)
0245 {
0246     std::vector<Kolab::FreebusyPeriod> periods;
0247 
0248     QDateTime start;
0249     QDateTime end;
0250     bool allDay = false;
0251     for (const Freebusy &fb : fbList) {
0252         const QDateTime &tmpStart = Kolab::Conversion::toDate(fb.start());
0253         if (!start.isValid() || tmpStart < start) {
0254             start = tmpStart;
0255             allDay |= fb.start().isDateOnly();
0256         }
0257         const QDateTime &tmpEnd = Kolab::Conversion::toDate(fb.end());
0258         if (!end.isValid() || tmpEnd > end) {
0259             end = tmpEnd;
0260             allDay |= fb.start().isDateOnly();
0261         }
0262 
0263         const auto fbPeriods{fb.periods()};
0264         for (const Kolab::FreebusyPeriod &period : fbPeriods) {
0265             Kolab::FreebusyPeriod simplifiedPeriod;
0266             simplifiedPeriod.setPeriods(period.periods());
0267             simplifiedPeriod.setType(period.type());
0268             if (!simple) { // Don't copy and reset to avoid unintentional information leaking into simple lists
0269                 simplifiedPeriod.setEvent(period.eventSummary(), period.eventUid(), period.eventLocation());
0270             }
0271             periods.push_back(simplifiedPeriod);
0272         }
0273     }
0274 
0275     Freebusy aggregateFB;
0276 
0277     aggregateFB.setStart(Kolab::Conversion::fromDate(start, allDay));
0278     aggregateFB.setEnd(Kolab::Conversion::fromDate(end, allDay));
0279     aggregateFB.setPeriods(periods);
0280     aggregateFB.setUid(createUuid().toStdString());
0281     aggregateFB.setTimestamp(Kolab::Conversion::fromDate(QDateTime::currentDateTimeUtc(), false));
0282     aggregateFB.setOrganizer(ContactReference(Kolab::ContactReference::EmailReference, organizerEmail, organizerName));
0283     return aggregateFB;
0284 }
0285 
0286 std::string toIFB(const Kolab::Freebusy &freebusy)
0287 {
0288     KCalendarCore::FreeBusy::Ptr fb(new KCalendarCore::FreeBusy(Kolab::Conversion::toDate(freebusy.start()), Kolab::Conversion::toDate(freebusy.end())));
0289     KCalendarCore::FreeBusyPeriod::List list;
0290     const auto freePeriods{freebusy.periods()};
0291     for (const Kolab::FreebusyPeriod &fbPeriod : freePeriods) {
0292         const auto fbPeriodPeriods{fbPeriod.periods()};
0293         for (const Kolab::Period &p : fbPeriodPeriods) {
0294             KCalendarCore::FreeBusyPeriod period(Kolab::Conversion::toDate(p.start), Kolab::Conversion::toDate(p.end));
0295             //             period.setSummary("summary"); Doesn't even work. X-SUMMARY is read though (just not written out)s
0296             // TODO
0297             list.append(period);
0298         }
0299     }
0300     fb->addPeriods(list);
0301 
0302     fb->setUid(QString::fromStdString(freebusy.uid()));
0303     fb->setOrganizer(KCalendarCore::Person(Conversion::fromStdString(freebusy.organizer().name()), Conversion::fromStdString(freebusy.organizer().email())));
0304     fb->setLastModified(Kolab::Conversion::toDate(freebusy.timestamp()));
0305 
0306     KCalendarCore::ICalFormat format;
0307     format.setApplication(QStringLiteral("libkolab"), QStringLiteral(LIBKOLAB_LIB_VERSION_STRING));
0308     QString data = format.createScheduleMessage(fb, KCalendarCore::iTIPPublish);
0309     return Conversion::toStdString(data);
0310 }
0311 }
0312 }