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 }