1/*
2 This file is part of the kcalcore library.
3
4 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22/**
23 @file
24 This file is part of the API for handling calendar data and
25 defines the FreeBusy class.
26
27 @brief
28 Provides information about the free/busy time of a calendar user.
29
30 @author Cornelius Schumacher \<schumacher@kde.org\>
31 @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
32*/
33#include "freebusy.h"
34#include "visitor.h"
35
36#include "icalformat.h"
37
38#include <KDebug>
39#include <QTime>
40
41using namespace KCalCore;
42
43//@cond PRIVATE
44class KCalCore::FreeBusy::Private
45{
46private:
47 FreeBusy *q;
48public:
49 Private(FreeBusy *qq) : q(qq)
50 {}
51
52 Private(const KCalCore::FreeBusy::Private &other, FreeBusy *qq) : q(qq)
53 {
54 init(other);
55 }
56
57 Private(const FreeBusyPeriod::List &busyPeriods, FreeBusy *qq)
58 : q(qq), mBusyPeriods(busyPeriods)
59 {}
60
61 void init(const KCalCore::FreeBusy::Private &other);
62 void init(const Event::List &events, const KDateTime &start, const KDateTime &end);
63
64 KDateTime mDtEnd; // end datetime
65 FreeBusyPeriod::List mBusyPeriods; // list of periods
66
67 // This is used for creating a freebusy object for the current user
68 bool addLocalPeriod(FreeBusy *fb, const KDateTime &start, const KDateTime &end);
69};
70
71void KCalCore::FreeBusy::Private::init(const KCalCore::FreeBusy::Private &other)
72{
73 mDtEnd = other.mDtEnd;
74 mBusyPeriods = other.mBusyPeriods;
75}
76//@endcond
77
78FreeBusy::FreeBusy()
79 : d(new KCalCore::FreeBusy::Private(this))
80{
81}
82
83FreeBusy::FreeBusy(const FreeBusy &other)
84 : IncidenceBase(other),
85 d(new KCalCore::FreeBusy::Private(*other.d, this))
86{
87}
88
89FreeBusy::FreeBusy(const KDateTime &start, const KDateTime &end)
90 : d(new KCalCore::FreeBusy::Private(this))
91{
92 setDtStart(start);
93 setDtEnd(end);
94}
95
96FreeBusy::FreeBusy(const Event::List &events, const KDateTime &start, const KDateTime &end)
97 : d(new KCalCore::FreeBusy::Private(this))
98{
99 setDtStart(start);
100 setDtEnd(end);
101
102 d->init(events, start, end);
103}
104
105//@cond PRIVATE
106void FreeBusy::Private::init(const Event::List &eventList,
107 const KDateTime &start, const KDateTime &end)
108{
109 int extraDays, i, x, duration;
110 duration = start.daysTo(end);
111 QDate day;
112 KDateTime tmpStart;
113 KDateTime tmpEnd;
114
115 // Loops through every event in the calendar
116 Event::List::ConstIterator it;
117 for (it = eventList.constBegin(); it != eventList.constEnd(); ++it) {
118 Event::Ptr event = *it;
119
120 // If this event is transparent it shouldn't be in the freebusy list.
121 if (event->transparency() == Event::Transparent) {
122 continue;
123 }
124
125 // The code below can not handle all-day events. Fixing this resulted
126 // in a lot of duplicated code. Instead, make a copy of the event and
127 // set the period to the full day(s). This trick works for recurring,
128 // multiday, and single day all-day events.
129 Event::Ptr allDayEvent;
130 if (event->allDay()) {
131 // addDay event. Do the hack
132 kDebug() << "All-day event";
133 allDayEvent = Event::Ptr(new Event(*event));
134
135 // Set the start and end times to be on midnight
136 KDateTime st = allDayEvent->dtStart();
137 st.setTime(QTime(0, 0));
138 KDateTime nd = allDayEvent->dtEnd();
139 nd.setTime(QTime(23, 59, 59, 999));
140 allDayEvent->setAllDay(false);
141 allDayEvent->setDtStart(st);
142 allDayEvent->setDtEnd(nd);
143
144 kDebug() << "Use:" << st.toString() << "to" << nd.toString();
145 // Finally, use this event for the setting below
146 event = allDayEvent;
147 }
148
149 // This whole for loop is for recurring events, it loops through
150 // each of the days of the freebusy request
151
152 for (i = 0; i <= duration; ++i) {
153 day = start.addDays(i).date();
154 tmpStart.setDate(day);
155 tmpEnd.setDate(day);
156
157 if (event->recurs()) {
158 if (event->isMultiDay()) {
159 // FIXME: This doesn't work for sub-daily recurrences or recurrences with
160 // a different time than the original event.
161 extraDays = event->dtStart().daysTo(event->dtEnd());
162 for (x = 0; x <= extraDays; ++x) {
163 if (event->recursOn(day.addDays(-x), start.timeSpec())) {
164 tmpStart.setDate(day.addDays(-x));
165 tmpStart.setTime(event->dtStart().time());
166 tmpEnd = event->duration().end(tmpStart);
167
168 addLocalPeriod(q, tmpStart, tmpEnd);
169 break;
170 }
171 }
172 } else {
173 if (event->recursOn(day, start.timeSpec())) {
174 tmpStart.setTime(event->dtStart().time());
175 tmpEnd.setTime(event->dtEnd().time());
176
177 addLocalPeriod(q, tmpStart, tmpEnd);
178 }
179 }
180 }
181
182 }
183 // Non-recurring events
184 addLocalPeriod(q, event->dtStart(), event->dtEnd());
185 }
186
187 q->sortList();
188}
189//@endcond
190
191FreeBusy::FreeBusy(const Period::List &busyPeriods)
192 : d(new KCalCore::FreeBusy::Private(this))
193{
194 addPeriods(busyPeriods);
195}
196
197FreeBusy::FreeBusy(const FreeBusyPeriod::List &busyPeriods)
198 : d(new KCalCore::FreeBusy::Private(busyPeriods, this))
199{
200}
201
202FreeBusy::~FreeBusy()
203{
204 delete d;
205}
206
207IncidenceBase::IncidenceType FreeBusy::type() const
208{
209 return TypeFreeBusy;
210}
211
212QByteArray FreeBusy::typeStr() const
213{
214 return "FreeBusy";
215}
216
217void FreeBusy::setDtStart(const KDateTime &start)
218{
219 IncidenceBase::setDtStart(start.toUtc());
220 updated();
221}
222
223void FreeBusy::setDtEnd(const KDateTime &end)
224{
225 d->mDtEnd = end;
226}
227
228KDateTime FreeBusy::dtEnd() const
229{
230 return d->mDtEnd;
231}
232
233Period::List FreeBusy::busyPeriods() const
234{
235 Period::List res;
236
237 foreach(const FreeBusyPeriod &p, d->mBusyPeriods) {
238 res << p;
239 }
240
241 return res;
242}
243
244FreeBusyPeriod::List FreeBusy::fullBusyPeriods() const
245{
246 return d->mBusyPeriods;
247}
248
249void FreeBusy::sortList()
250{
251 qSort(d->mBusyPeriods);
252 return;
253}
254
255void FreeBusy::addPeriods(const Period::List &list)
256{
257 foreach(const Period &p, list) {
258 d->mBusyPeriods << FreeBusyPeriod(p);
259 }
260 sortList();
261}
262
263void FreeBusy::addPeriods(const FreeBusyPeriod::List &list)
264{
265 d->mBusyPeriods += list;
266 sortList();
267}
268
269void FreeBusy::addPeriod(const KDateTime &start, const KDateTime &end)
270{
271 d->mBusyPeriods.append(FreeBusyPeriod(start, end));
272 sortList();
273}
274
275void FreeBusy::addPeriod(const KDateTime &start, const Duration &duration)
276{
277 d->mBusyPeriods.append(FreeBusyPeriod(start, duration));
278 sortList();
279}
280
281void FreeBusy::merge(FreeBusy::Ptr freeBusy)
282{
283 if (freeBusy->dtStart() < dtStart()) {
284 setDtStart(freeBusy->dtStart());
285 }
286
287 if (freeBusy->dtEnd() > dtEnd()) {
288 setDtEnd(freeBusy->dtEnd());
289 }
290
291 Period::List periods = freeBusy->busyPeriods();
292 Period::List::ConstIterator it;
293 for (it = periods.constBegin(); it != periods.constEnd(); ++it) {
294 d->mBusyPeriods.append(FreeBusyPeriod((*it).start(), (*it).end()));
295 }
296 sortList();
297}
298
299void FreeBusy::shiftTimes(const KDateTime::Spec &oldSpec,
300 const KDateTime::Spec &newSpec)
301{
302 if (oldSpec.isValid() && newSpec.isValid() && oldSpec != newSpec) {
303 IncidenceBase::shiftTimes(oldSpec, newSpec);
304 d->mDtEnd = d->mDtEnd.toTimeSpec(oldSpec);
305 d->mDtEnd.setTimeSpec(newSpec);
306 foreach(FreeBusyPeriod p, d->mBusyPeriods) { //krazy:exclude=foreach
307 p.shiftTimes(oldSpec, newSpec);
308 }
309 }
310}
311
312IncidenceBase &FreeBusy::assign(const IncidenceBase &other)
313{
314 if (&other != this) {
315 IncidenceBase::assign(other);
316 const FreeBusy *f = static_cast<const FreeBusy*>(&other);
317 d->init(*(f->d));
318 }
319 return *this;
320}
321
322bool FreeBusy::equals(const IncidenceBase &freeBusy) const
323{
324 if (!IncidenceBase::equals(freeBusy)) {
325 return false;
326 } else {
327 // If they weren't the same type IncidenceBase::equals would had returned false already
328 const FreeBusy *fb = static_cast<const FreeBusy*>(&freeBusy);
329 return
330 dtEnd() == fb->dtEnd() &&
331 d->mBusyPeriods == fb->d->mBusyPeriods;
332 }
333}
334
335bool FreeBusy::accept(Visitor &v, IncidenceBase::Ptr incidence)
336{
337 return v.visit(incidence.staticCast<FreeBusy>());
338}
339
340KDateTime FreeBusy::dateTime(DateTimeRole role) const
341{
342 Q_UNUSED(role);
343 // No roles affecting freeBusy yet
344 return KDateTime();
345}
346
347void FreeBusy::setDateTime(const KDateTime &dateTime, DateTimeRole role)
348{
349 Q_UNUSED(dateTime);
350 Q_UNUSED(role);
351}
352
353void FreeBusy::virtual_hook(int id, void *data)
354{
355 Q_UNUSED(id);
356 Q_UNUSED(data);
357 Q_ASSERT(false);
358}
359
360//@cond PRIVATE
361bool FreeBusy::Private::addLocalPeriod(FreeBusy *fb,
362 const KDateTime &eventStart,
363 const KDateTime &eventEnd)
364{
365 KDateTime tmpStart;
366 KDateTime tmpEnd;
367
368 //Check to see if the start *or* end of the event is
369 //between the start and end of the freebusy dates.
370 KDateTime start = fb->dtStart();
371 if (!(((start.secsTo(eventStart) >= 0) &&
372 (eventStart.secsTo(mDtEnd) >= 0)) ||
373 ((start.secsTo(eventEnd) >= 0) &&
374 (eventEnd.secsTo(mDtEnd) >= 0)))) {
375 return false;
376 }
377
378 if (eventStart.secsTo(start) >= 0) {
379 tmpStart = start;
380 } else {
381 tmpStart = eventStart;
382 }
383
384 if (eventEnd.secsTo(mDtEnd) <= 0) {
385 tmpEnd = mDtEnd;
386 } else {
387 tmpEnd = eventEnd;
388 }
389
390 FreeBusyPeriod p(tmpStart, tmpEnd);
391 mBusyPeriods.append(p);
392
393 return true;
394}
395//@endcond
396
397QLatin1String FreeBusy::mimeType() const
398{
399 return FreeBusy::freeBusyMimeType();
400}
401
402QLatin1String KCalCore::FreeBusy::freeBusyMimeType()
403{
404 return QLatin1String("application/x-vnd.akonadi.calendar.freebusy");
405}
406
407QDataStream &KCalCore::operator<<(QDataStream &stream, const KCalCore::FreeBusy::Ptr &freebusy)
408{
409 KCalCore::ICalFormat format;
410 QString data = format.createScheduleMessage(freebusy, iTIPPublish);
411 return stream << data;
412}
413
414QDataStream &KCalCore::operator>>(QDataStream &stream, KCalCore::FreeBusy::Ptr &freebusy)
415{
416 QString freeBusyVCal;
417 stream >> freeBusyVCal;
418
419 KCalCore::ICalFormat format;
420 freebusy = format.parseFreeBusy(freeBusyVCal);
421
422 if (!freebusy) {
423 kDebug() << "Error parsing free/busy";
424 kDebug() << freeBusyVCal;
425 }
426
427 return stream;
428}
429
430