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 }