1/*
2 This file is part of the kcalcore library.
3
4 Copyright (C) 2013 Christian Mollekopf <mollekopf@kolabsys.com>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20*/
21/**
22 @file
23 This file is part of the API for handling calendar data and
24 defines the OccurrenceIterator class.
25
26 @brief
27 This class provides an iterator to iterate over all occurrences of incidences.
28
29 @author Christian Mollekopf \<mollekopf@kolabsys.com\>
30 */
31
32#include "occurrenceiterator.h"
33#include "calendar.h"
34#include "calfilter.h"
35
36#include <KDebug>
37#include <QDate>
38
39using namespace KCalCore;
40
41/**
42 Private class that helps to provide binary compatibility between releases.
43 @internal
44*/
45//@cond PRIVATE
46class KCalCore::OccurrenceIterator::Private
47{
48public:
49 Private(OccurrenceIterator *qq)
50 : q(qq),
51 occurrenceIt(occurrenceList)
52 {
53 }
54
55 OccurrenceIterator *q;
56 KDateTime start;
57 KDateTime end;
58
59 struct Occurrence
60 {
61 Occurrence()
62 {
63 }
64
65 Occurrence(const Incidence::Ptr &i, const KDateTime &d)
66 : incidence(i), date(d)
67 {
68 }
69
70 Incidence::Ptr incidence;
71 KDateTime date;
72 };
73 QList<Occurrence> occurrenceList;
74 QListIterator<Occurrence> occurrenceIt;
75 Occurrence current;
76
77 /*
78 * KCalCore::CalFilter can't handle individual occurrences.
79 * When filtering completed to-dos, the CalFilter doesn't hide
80 * them if it's a recurring to-do.
81 */
82 bool occurrenceIsHidden(const Calendar &calendar,
83 const Incidence::Ptr &inc,
84 const KDateTime &occurrenceDate)
85 {
86 if ((inc->type() == Incidence::TypeTodo) &&
87 calendar.filter() &&
88 (calendar.filter()->criteria() & KCalCore::CalFilter::HideCompletedTodos)) {
89 if (inc->recurs()) {
90 const Todo::Ptr todo = inc.staticCast<Todo>();
91 if (todo && (occurrenceDate < todo->dtDue())) {
92 return true;
93 }
94 } else if (inc->hasRecurrenceId()) {
95 const Todo::Ptr mainTodo = calendar.todo(inc->uid());
96 if (mainTodo && mainTodo->isCompleted()) {
97 return true;
98 }
99 }
100 }
101 return false;
102 }
103
104 void setupIterator(const Calendar &calendar, const Incidence::List &incidences)
105 {
106 foreach(const Incidence::Ptr &inc, incidences) {
107 if (inc->hasRecurrenceId()) {
108 continue;
109 }
110 if (inc->recurs()) {
111 QHash<KDateTime, Incidence::Ptr> recurrenceIds;
112 KDateTime incidenceRecStart = inc->dateTime(Incidence::RoleRecurrenceStart);
113 foreach(const Incidence::Ptr &exception, calendar.instances(inc)) {
114 if (incidenceRecStart.isValid())
115 recurrenceIds.insert(exception->recurrenceId().toTimeSpec(incidenceRecStart.timeSpec()), exception);
116 }
117 const bool isAllDay = inc->allDay();
118 const DateTimeList occurrences = inc->recurrence()->timesInInterval(start, end);
119 Incidence::Ptr incidence(inc);
120 qint64 offset(0);
121 foreach(KDateTime occurrenceDate, occurrences) { //krazy:exclude=foreach
122 //timesInInterval generates always date-times,
123 //which is not what we want for all-day events
124 occurrenceDate.setDateOnly(isAllDay);
125
126 bool resetIncidence = false;
127 if (recurrenceIds.contains(occurrenceDate)) {
128 // TODO: exclude exceptions where the start/end is not within
129 // (so the occurrence of the recurrence is omitted, but no exception is added)
130 if (recurrenceIds.value(occurrenceDate)->status() == Incidence::StatusCanceled)
131 continue;
132
133 incidence = recurrenceIds.value(occurrenceDate);
134 occurrenceDate = incidence->dtStart();
135 resetIncidence = !incidence->thisAndFuture();
136 offset = incidence->recurrenceId().secsTo_long(incidence->dtStart());
137 } else if (inc != incidence) { //thisAndFuture exception is active
138 occurrenceDate = occurrenceDate.addSecs(offset);
139 }
140 if (!occurrenceIsHidden(calendar, incidence, occurrenceDate)) {
141 occurrenceList << Private::Occurrence(incidence, occurrenceDate);
142 }
143 if (resetIncidence) {
144 incidence = inc;
145 offset = 0;
146 }
147 }
148 } else {
149 occurrenceList << Private::Occurrence(inc, inc->dtStart());
150 }
151 }
152 occurrenceIt = QListIterator<Private::Occurrence>(occurrenceList);
153 }
154};
155//@endcond
156
157static uint qHash(const KDateTime &dt)
158{
159 return qHash(dt.toString());
160}
161
162/**
163 * Right now there is little point in the iterator, but:
164 * With an iterator it should be possible to solve this more memory efficiently
165 * and with immediate results at the beginning of the selected timeframe.
166 * Either all events are iterated simoulatneously, resulting in occurrences
167 * of all events in parallel in the correct time-order, or incidence after
168 * incidence, which would be even more efficient.
169 *
170 * By making this class a friend of calendar, we could also use the internally
171 * available data structures.
172 */
173OccurrenceIterator::OccurrenceIterator(const Calendar &calendar,
174 const KDateTime &start,
175 const KDateTime &end)
176 : d(new KCalCore::OccurrenceIterator::Private(this))
177{
178 d->start = start;
179 d->end = end;
180
181 Event::List events = calendar.rawEvents(start.date(), end.date(), start.timeSpec());
182 if (calendar.filter()) {
183 calendar.filter()->apply(&events);
184 }
185
186 Todo::List todos = calendar.rawTodos(start.date(), end.date(), start.timeSpec());
187 if (calendar.filter()) {
188 calendar.filter()->apply(&todos);
189 }
190
191 Journal::List journals;
192 const Journal::List allJournals = calendar.rawJournals();
193 foreach(const KCalCore::Journal::Ptr &journal, allJournals) {
194 const QDate journalStart = journal->dtStart().toTimeSpec(start.timeSpec()).date();
195 if (journal->dtStart().isValid() &&
196 journalStart >= start.date() &&
197 journalStart <= end.date())
198 journals << journal;
199 }
200
201 if (calendar.filter()) {
202 calendar.filter()->apply(&journals);
203 }
204
205 const Incidence::List incidences =
206 KCalCore::Calendar::mergeIncidenceList(events, todos, journals);
207 d->setupIterator(calendar, incidences);
208}
209
210OccurrenceIterator::OccurrenceIterator(const Calendar &calendar,
211 const Incidence::Ptr &incidence,
212 const KDateTime &start,
213 const KDateTime &end)
214 : d(new KCalCore::OccurrenceIterator::Private(this))
215{
216 Q_ASSERT(incidence);
217 d->start = start;
218 d->end = end;
219 d->setupIterator(calendar, Incidence::List() << incidence);
220}
221
222OccurrenceIterator::~OccurrenceIterator()
223{
224}
225
226bool OccurrenceIterator::hasNext() const
227{
228 return d->occurrenceIt.hasNext();
229}
230
231void OccurrenceIterator::next()
232{
233 d->current = d->occurrenceIt.next();
234}
235
236Incidence::Ptr OccurrenceIterator::incidence() const
237{
238 return d->current.incidence;
239}
240
241KDateTime OccurrenceIterator::occurrenceStartDate() const
242{
243 return d->current.date;
244}
245