File indexing completed on 2024-04-14 03:50:42
0001 /* 0002 This file is part of the kcalcore library. 0003 0004 SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org> 0005 SPDX-FileCopyrightText: 2004 Reinhold Kainhofer <reinhold@kainhofer.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 /** 0010 @file 0011 This file is part of the API for handling calendar data and 0012 defines the FreeBusy class. 0013 0014 @brief 0015 Provides information about the free/busy time of a calendar user. 0016 0017 @author Cornelius Schumacher \<schumacher@kde.org\> 0018 @author Reinhold Kainhofer \<reinhold@kainhofer.com\> 0019 */ 0020 #include "freebusy.h" 0021 #include "utils_p.h" 0022 #include "visitor.h" 0023 0024 #include "icalformat.h" 0025 0026 #include "incidencebase_p.h" 0027 #include "kcalendarcore_debug.h" 0028 #include <QTime> 0029 0030 using namespace KCalendarCore; 0031 0032 //@cond PRIVATE 0033 class KCalendarCore::FreeBusyPrivate : public IncidenceBasePrivate 0034 { 0035 public: 0036 FreeBusyPrivate() = default; 0037 FreeBusyPrivate(const FreeBusyPrivate &other) = default; 0038 0039 FreeBusyPrivate(const FreeBusyPeriod::List &busyPeriods) 0040 : IncidenceBasePrivate() 0041 , mBusyPeriods(busyPeriods) 0042 { 0043 } 0044 0045 void init(const FreeBusyPrivate &other); 0046 void init(const Event::List &events, const QDateTime &start, const QDateTime &end); 0047 0048 QDateTime mDtEnd; // end datetime 0049 FreeBusyPeriod::List mBusyPeriods; // list of periods 0050 0051 // This is used for creating a freebusy object for the current user 0052 bool addLocalPeriod(const QDateTime &eventStart, const QDateTime &eventEnd); 0053 0054 void sortBusyPeriods() 0055 { 0056 std::sort(mBusyPeriods.begin(), mBusyPeriods.end()); 0057 } 0058 }; 0059 0060 void FreeBusyPrivate::init(const FreeBusyPrivate &other) 0061 { 0062 mDtEnd = other.mDtEnd; 0063 mBusyPeriods = other.mBusyPeriods; 0064 } 0065 //@endcond 0066 0067 FreeBusy::FreeBusy() 0068 : IncidenceBase(new FreeBusyPrivate()) 0069 { 0070 } 0071 0072 FreeBusy::FreeBusy(const FreeBusy &other) 0073 : IncidenceBase(other, new FreeBusyPrivate(*other.d_func())) 0074 { 0075 } 0076 0077 FreeBusy::FreeBusy(const QDateTime &start, const QDateTime &end) 0078 : FreeBusy() 0079 { 0080 setDtStart(start); // NOLINT false clang-analyzer-optin.cplusplus.VirtualCall 0081 setDtEnd(end); // NOLINT false clang-analyzer-optin.cplusplus.VirtualCall 0082 } 0083 0084 FreeBusy::FreeBusy(const Event::List &events, const QDateTime &start, const QDateTime &end) 0085 : FreeBusy() 0086 { 0087 setDtStart(start); // NOLINT false clang-analyzer-optin.cplusplus.VirtualCall 0088 setDtEnd(end); // NOLINT false clang-analyzer-optin.cplusplus.VirtualCall 0089 0090 Q_D(FreeBusy); 0091 d->init(events, start, end); 0092 } 0093 0094 //@cond PRIVATE 0095 void FreeBusyPrivate::init(const Event::List &eventList, const QDateTime &start, const QDateTime &end) 0096 { 0097 const qint64 duration = start.daysTo(end); 0098 QDate day; 0099 QDateTime tmpStart; 0100 QDateTime tmpEnd; 0101 0102 // Loops through every event in the calendar 0103 for (auto event : eventList) { 0104 // If this event is transparent it shouldn't be in the freebusy list. 0105 if (event->transparency() == Event::Transparent) { 0106 continue; 0107 } 0108 0109 // The code below can not handle all-day events. Fixing this resulted 0110 // in a lot of duplicated code. Instead, make a copy of the event and 0111 // set the period to the full day(s). This trick works for recurring, 0112 // multiday, and single day all-day events. 0113 Event::Ptr allDayEvent; 0114 if (event->allDay()) { 0115 // addDay event. Do the hack 0116 qCDebug(KCALCORE_LOG) << "All-day event"; 0117 allDayEvent = Event::Ptr(new Event(*event)); 0118 0119 // Set the start and end times to be on midnight 0120 QDateTime st = allDayEvent->dtStart(); 0121 st.setTime(QTime(0, 0)); 0122 QDateTime nd = allDayEvent->dtEnd(); 0123 nd.setTime(QTime(23, 59, 59, 999)); 0124 allDayEvent->setAllDay(false); 0125 allDayEvent->setDtStart(st); 0126 allDayEvent->setDtEnd(nd); 0127 0128 qCDebug(KCALCORE_LOG) << "Use:" << st.toString() << "to" << nd.toString(); 0129 // Finally, use this event for the setting below 0130 event = allDayEvent; 0131 } 0132 0133 // This whole for loop is for recurring events, it loops through 0134 // each of the days of the freebusy request 0135 0136 for (qint64 i = 0; i <= duration; ++i) { 0137 day = start.addDays(i).date(); 0138 tmpStart.setDate(day); 0139 tmpEnd.setDate(day); 0140 0141 if (event->recurs()) { 0142 if (event->isMultiDay()) { 0143 // FIXME: This doesn't work for sub-daily recurrences or recurrences with 0144 // a different time than the original event. 0145 const qint64 extraDays = event->dtStart().daysTo(event->dtEnd()); 0146 for (qint64 x = 0; x <= extraDays; ++x) { 0147 if (event->recursOn(day.addDays(-x), start.timeZone())) { 0148 tmpStart.setDate(day.addDays(-x)); 0149 tmpStart.setTime(event->dtStart().time()); 0150 tmpEnd = event->duration().end(tmpStart); 0151 0152 addLocalPeriod(tmpStart, tmpEnd); 0153 break; 0154 } 0155 } 0156 } else { 0157 if (event->recursOn(day, start.timeZone())) { 0158 tmpStart.setTime(event->dtStart().time()); 0159 tmpEnd.setTime(event->dtEnd().time()); 0160 0161 addLocalPeriod(tmpStart, tmpEnd); 0162 } 0163 } 0164 } 0165 } 0166 0167 // Non-recurring events 0168 addLocalPeriod(event->dtStart(), event->dtEnd()); 0169 } 0170 0171 sortBusyPeriods(); 0172 } 0173 //@endcond 0174 0175 FreeBusy::FreeBusy(const Period::List &busyPeriods) 0176 : IncidenceBase(new FreeBusyPrivate()) 0177 { 0178 addPeriods(busyPeriods); 0179 } 0180 0181 FreeBusy::FreeBusy(const FreeBusyPeriod::List &busyPeriods) 0182 : IncidenceBase(new FreeBusyPrivate(busyPeriods)) 0183 { 0184 } 0185 0186 FreeBusy::~FreeBusy() = default; 0187 0188 IncidenceBase::IncidenceType FreeBusy::type() const 0189 { 0190 return TypeFreeBusy; 0191 } 0192 0193 QByteArray FreeBusy::typeStr() const 0194 { 0195 return QByteArrayLiteral("FreeBusy"); 0196 } 0197 0198 void FreeBusy::setDtStart(const QDateTime &start) 0199 { 0200 IncidenceBase::setDtStart(start.toUTC()); 0201 } 0202 0203 void FreeBusy::setDtEnd(const QDateTime &end) 0204 { 0205 Q_D(FreeBusy); 0206 update(); 0207 d->mDtEnd = end; 0208 setFieldDirty(FieldDtEnd); 0209 updated(); 0210 } 0211 0212 QDateTime FreeBusy::dtEnd() const 0213 { 0214 Q_D(const FreeBusy); 0215 return d->mDtEnd; 0216 } 0217 0218 Period::List FreeBusy::busyPeriods() const 0219 { 0220 Period::List res; 0221 0222 Q_D(const FreeBusy); 0223 res.reserve(d->mBusyPeriods.count()); 0224 for (const FreeBusyPeriod &p : std::as_const(d->mBusyPeriods)) { 0225 res << p; 0226 } 0227 0228 return res; 0229 } 0230 0231 FreeBusyPeriod::List FreeBusy::fullBusyPeriods() const 0232 { 0233 Q_D(const FreeBusy); 0234 return d->mBusyPeriods; 0235 } 0236 0237 void FreeBusy::sortList() 0238 { 0239 Q_D(FreeBusy); 0240 d->sortBusyPeriods(); 0241 } 0242 0243 void FreeBusy::addPeriods(const Period::List &list) 0244 { 0245 Q_D(FreeBusy); 0246 d->mBusyPeriods.reserve(d->mBusyPeriods.count() + list.count()); 0247 for (const Period &p : std::as_const(list)) { 0248 d->mBusyPeriods << FreeBusyPeriod(p); 0249 } 0250 sortList(); 0251 } 0252 0253 void FreeBusy::addPeriods(const FreeBusyPeriod::List &list) 0254 { 0255 Q_D(FreeBusy); 0256 d->mBusyPeriods += list; 0257 sortList(); 0258 } 0259 0260 void FreeBusy::addPeriod(const QDateTime &start, const QDateTime &end) 0261 { 0262 Q_D(FreeBusy); 0263 d->mBusyPeriods.append(FreeBusyPeriod(start, end)); 0264 sortList(); 0265 } 0266 0267 void FreeBusy::addPeriod(const QDateTime &start, const Duration &duration) 0268 { 0269 Q_D(FreeBusy); 0270 d->mBusyPeriods.append(FreeBusyPeriod(start, duration)); 0271 sortList(); 0272 } 0273 0274 void FreeBusy::merge(const FreeBusy::Ptr &freeBusy) 0275 { 0276 if (freeBusy->dtStart() < dtStart()) { 0277 setDtStart(freeBusy->dtStart()); 0278 } 0279 0280 if (freeBusy->dtEnd() > dtEnd()) { 0281 setDtEnd(freeBusy->dtEnd()); 0282 } 0283 0284 Q_D(FreeBusy); 0285 const Period::List periods = freeBusy->busyPeriods(); 0286 d->mBusyPeriods.reserve(d->mBusyPeriods.count() + periods.count()); 0287 for (const auto &p : periods) { 0288 d->mBusyPeriods.append(FreeBusyPeriod(p.start(), p.end())); 0289 } 0290 sortList(); 0291 } 0292 0293 void FreeBusy::shiftTimes(const QTimeZone &oldZone, const QTimeZone &newZone) 0294 { 0295 Q_D(FreeBusy); 0296 if (oldZone.isValid() && newZone.isValid() && oldZone != newZone) { 0297 IncidenceBase::shiftTimes(oldZone, newZone); 0298 update(); 0299 d->mDtEnd = d->mDtEnd.toTimeZone(oldZone); 0300 d->mDtEnd.setTimeZone(newZone); 0301 for (FreeBusyPeriod p : std::as_const(d->mBusyPeriods)) { 0302 p.shiftTimes(oldZone, newZone); 0303 } 0304 setFieldDirty(FieldDtEnd); 0305 updated(); 0306 } 0307 } 0308 0309 IncidenceBase &FreeBusy::assign(const IncidenceBase &other) 0310 { 0311 Q_D(FreeBusy); 0312 if (&other != this) { 0313 IncidenceBase::assign(other); 0314 const FreeBusy *f = static_cast<const FreeBusy *>(&other); 0315 d->init(*(f->d_func())); 0316 } 0317 return *this; 0318 } 0319 0320 bool FreeBusy::equals(const IncidenceBase &freeBusy) const 0321 { 0322 if (!IncidenceBase::equals(freeBusy)) { 0323 return false; 0324 } else { 0325 Q_D(const FreeBusy); 0326 // If they weren't the same type IncidenceBase::equals would had returned false already 0327 const FreeBusy *fb = static_cast<const FreeBusy *>(&freeBusy); 0328 return identical(dtEnd(), fb->dtEnd()) && d->mBusyPeriods == fb->d_func()->mBusyPeriods; 0329 } 0330 } 0331 0332 bool FreeBusy::accept(Visitor &v, const IncidenceBase::Ptr &incidence) 0333 { 0334 return v.visit(incidence.staticCast<FreeBusy>()); 0335 } 0336 0337 QDateTime FreeBusy::dateTime(DateTimeRole role) const 0338 { 0339 Q_UNUSED(role); 0340 // No roles affecting freeBusy yet 0341 return QDateTime(); 0342 } 0343 0344 void FreeBusy::setDateTime(const QDateTime &dateTime, DateTimeRole role) 0345 { 0346 Q_UNUSED(dateTime); 0347 Q_UNUSED(role); 0348 } 0349 0350 void FreeBusy::virtual_hook(VirtualHook id, void *data) 0351 { 0352 Q_UNUSED(id); 0353 Q_UNUSED(data); 0354 Q_ASSERT(false); 0355 } 0356 0357 //@cond PRIVATE 0358 bool FreeBusyPrivate::addLocalPeriod(const QDateTime &eventStart, const QDateTime &eventEnd) 0359 { 0360 QDateTime tmpStart; 0361 QDateTime tmpEnd; 0362 0363 // Check to see if the start *or* end of the event is 0364 // between the start and end of the freebusy dates. 0365 QDateTime start = mDtStart; 0366 if (!(((start.secsTo(eventStart) >= 0) && (eventStart.secsTo(mDtEnd) >= 0)) || ((start.secsTo(eventEnd) >= 0) && (eventEnd.secsTo(mDtEnd) >= 0)))) { 0367 return false; 0368 } 0369 0370 if (eventStart.secsTo(start) >= 0) { 0371 tmpStart = start; 0372 } else { 0373 tmpStart = eventStart; 0374 } 0375 0376 if (eventEnd.secsTo(mDtEnd) <= 0) { 0377 tmpEnd = mDtEnd; 0378 } else { 0379 tmpEnd = eventEnd; 0380 } 0381 0382 FreeBusyPeriod p(tmpStart, tmpEnd); 0383 mBusyPeriods.append(p); 0384 0385 return true; 0386 } 0387 //@endcond 0388 0389 QLatin1String FreeBusy::mimeType() const 0390 { 0391 return FreeBusy::freeBusyMimeType(); 0392 } 0393 0394 QLatin1String KCalendarCore::FreeBusy::freeBusyMimeType() 0395 { 0396 return QLatin1String("application/x-vnd.akonadi.calendar.freebusy"); 0397 } 0398 0399 QDataStream &KCalendarCore::operator<<(QDataStream &stream, const KCalendarCore::FreeBusy::Ptr &freebusy) 0400 { 0401 KCalendarCore::ICalFormat format; 0402 QString data = format.createScheduleMessage(freebusy, iTIPPublish); 0403 return stream << data; 0404 } 0405 0406 QDataStream &KCalendarCore::operator>>(QDataStream &stream, KCalendarCore::FreeBusy::Ptr &freebusy) 0407 { 0408 QString freeBusyVCal; 0409 stream >> freeBusyVCal; 0410 0411 KCalendarCore::ICalFormat format; 0412 freebusy = format.parseFreeBusy(freeBusyVCal); 0413 0414 if (!freebusy) { 0415 qCDebug(KCALCORE_LOG) << "Error parsing free/busy"; 0416 qCDebug(KCALCORE_LOG) << freeBusyVCal; 0417 } 0418 0419 return stream; 0420 }