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 | |
41 | using namespace KCalCore; |
42 | |
43 | //@cond PRIVATE |
44 | class KCalCore::FreeBusy::Private |
45 | { |
46 | private: |
47 | FreeBusy *q; |
48 | public: |
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 | |
71 | void KCalCore::FreeBusy::Private::init(const KCalCore::FreeBusy::Private &other) |
72 | { |
73 | mDtEnd = other.mDtEnd; |
74 | mBusyPeriods = other.mBusyPeriods; |
75 | } |
76 | //@endcond |
77 | |
78 | FreeBusy::FreeBusy() |
79 | : d(new KCalCore::FreeBusy::Private(this)) |
80 | { |
81 | } |
82 | |
83 | FreeBusy::FreeBusy(const FreeBusy &other) |
84 | : IncidenceBase(other), |
85 | d(new KCalCore::FreeBusy::Private(*other.d, this)) |
86 | { |
87 | } |
88 | |
89 | FreeBusy::FreeBusy(const KDateTime &start, const KDateTime &end) |
90 | : d(new KCalCore::FreeBusy::Private(this)) |
91 | { |
92 | setDtStart(start); |
93 | setDtEnd(end); |
94 | } |
95 | |
96 | FreeBusy::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 |
106 | void FreeBusy::Private::init(const Event::List &eventList, |
107 | const KDateTime &start, const KDateTime &end) |
108 | { |
109 | int , 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 | |
191 | FreeBusy::FreeBusy(const Period::List &busyPeriods) |
192 | : d(new KCalCore::FreeBusy::Private(this)) |
193 | { |
194 | addPeriods(busyPeriods); |
195 | } |
196 | |
197 | FreeBusy::FreeBusy(const FreeBusyPeriod::List &busyPeriods) |
198 | : d(new KCalCore::FreeBusy::Private(busyPeriods, this)) |
199 | { |
200 | } |
201 | |
202 | FreeBusy::~FreeBusy() |
203 | { |
204 | delete d; |
205 | } |
206 | |
207 | IncidenceBase::IncidenceType FreeBusy::type() const |
208 | { |
209 | return TypeFreeBusy; |
210 | } |
211 | |
212 | QByteArray FreeBusy::typeStr() const |
213 | { |
214 | return "FreeBusy" ; |
215 | } |
216 | |
217 | void FreeBusy::setDtStart(const KDateTime &start) |
218 | { |
219 | IncidenceBase::setDtStart(start.toUtc()); |
220 | updated(); |
221 | } |
222 | |
223 | void FreeBusy::setDtEnd(const KDateTime &end) |
224 | { |
225 | d->mDtEnd = end; |
226 | } |
227 | |
228 | KDateTime FreeBusy::dtEnd() const |
229 | { |
230 | return d->mDtEnd; |
231 | } |
232 | |
233 | Period::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 | |
244 | FreeBusyPeriod::List FreeBusy::fullBusyPeriods() const |
245 | { |
246 | return d->mBusyPeriods; |
247 | } |
248 | |
249 | void FreeBusy::sortList() |
250 | { |
251 | qSort(d->mBusyPeriods); |
252 | return; |
253 | } |
254 | |
255 | void FreeBusy::addPeriods(const Period::List &list) |
256 | { |
257 | foreach(const Period &p, list) { |
258 | d->mBusyPeriods << FreeBusyPeriod(p); |
259 | } |
260 | sortList(); |
261 | } |
262 | |
263 | void FreeBusy::addPeriods(const FreeBusyPeriod::List &list) |
264 | { |
265 | d->mBusyPeriods += list; |
266 | sortList(); |
267 | } |
268 | |
269 | void FreeBusy::addPeriod(const KDateTime &start, const KDateTime &end) |
270 | { |
271 | d->mBusyPeriods.append(FreeBusyPeriod(start, end)); |
272 | sortList(); |
273 | } |
274 | |
275 | void FreeBusy::addPeriod(const KDateTime &start, const Duration &duration) |
276 | { |
277 | d->mBusyPeriods.append(FreeBusyPeriod(start, duration)); |
278 | sortList(); |
279 | } |
280 | |
281 | void 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 | |
299 | void 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 | |
312 | IncidenceBase &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 | |
322 | bool 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 | |
335 | bool FreeBusy::accept(Visitor &v, IncidenceBase::Ptr incidence) |
336 | { |
337 | return v.visit(incidence.staticCast<FreeBusy>()); |
338 | } |
339 | |
340 | KDateTime FreeBusy::dateTime(DateTimeRole role) const |
341 | { |
342 | Q_UNUSED(role); |
343 | // No roles affecting freeBusy yet |
344 | return KDateTime(); |
345 | } |
346 | |
347 | void FreeBusy::setDateTime(const KDateTime &dateTime, DateTimeRole role) |
348 | { |
349 | Q_UNUSED(dateTime); |
350 | Q_UNUSED(role); |
351 | } |
352 | |
353 | void 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 |
361 | bool 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 | |
397 | QLatin1String FreeBusy::mimeType() const |
398 | { |
399 | return FreeBusy::freeBusyMimeType(); |
400 | } |
401 | |
402 | QLatin1String KCalCore::FreeBusy::freeBusyMimeType() |
403 | { |
404 | return QLatin1String("application/x-vnd.akonadi.calendar.freebusy" ); |
405 | } |
406 | |
407 | QDataStream &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 | |
414 | QDataStream &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 | |