1/*
2 This file is part of the kcalcore library.
3
4 Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6 Copyright (C) 2012 Christian Mollekopf <mollekopf@kolabsys.com>
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22*/
23/**
24 @file
25 This file is part of the API for handling calendar data and defines
26 classes for managing compatibility between different calendar formats.
27
28 @brief
29 Classes that provide compatibility to older or "broken" calendar formats.
30
31 @author Cornelius Schumacher \<schumacher@kde.org\>
32 @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
33*/
34
35#include "compat.h"
36#include "incidence.h"
37
38#include <KDebug>
39
40#include <QtCore/QRegExp>
41#include <QtCore/QString>
42#include <QtCore/QDate>
43
44using namespace KCalCore;
45
46Compat *CompatFactory::createCompat(const QString &productId,
47 const QString &implementationVersion)
48{
49 Compat *compat = 0;
50
51 int korg = productId.indexOf(QLatin1String("KOrganizer"));
52 int outl9 = productId.indexOf(QLatin1String("Outlook 9.0"));
53
54 if (korg >= 0) {
55 int versionStart = productId.indexOf(QLatin1String(" "), korg);
56 if (versionStart >= 0) {
57 int versionStop = productId.indexOf(QRegExp(QLatin1String("[ /]")), versionStart + 1);
58 if (versionStop >= 0) {
59 QString version = productId.mid(versionStart + 1,
60 versionStop - versionStart - 1);
61
62 int versionNum = version.section(QLatin1Char('.'), 0, 0).toInt() * 10000 +
63 version.section(QLatin1Char('.'), 1, 1).toInt() * 100 +
64 version.section(QLatin1Char('.'), 2, 2).toInt();
65 int releaseStop = productId.indexOf(QLatin1String("/"), versionStop);
66 QString release;
67 if (releaseStop > versionStop) {
68 release = productId.mid(versionStop+1, releaseStop-versionStop-1);
69 }
70 if (versionNum < 30100) {
71 compat = new CompatPre31;
72 } else if (versionNum < 30200) {
73 compat = new CompatPre32;
74 } else if (versionNum == 30200 && release == QLatin1String("pre")) {
75 kDebug() << "Generating compat for KOrganizer 3.2 pre";
76 compat = new Compat32PrereleaseVersions;
77 } else if (versionNum < 30400) {
78 compat = new CompatPre34;
79 } else if (versionNum < 30500) {
80 compat = new CompatPre35;
81 }
82 }
83 }
84 } else if (outl9 >= 0) {
85 kDebug() << "Generating compat for Outlook < 2000 (Outlook 9.0)";
86 compat = new CompatOutlook9;
87 }
88 if (!compat) {
89 compat = new Compat;
90 }
91 // Older implementations lacked the implementation version,
92 // so apply this fix if it is a file from kontact and the version is missing.
93 if (implementationVersion.isEmpty() &&
94 (productId.contains(QLatin1String("libkcal")) ||
95 productId.contains(QLatin1String("KOrganizer")) ||
96 productId.contains(QLatin1String("KAlarm")))) {
97 compat = new CompatPre410(compat);
98 }
99
100 return compat;
101}
102
103Compat::Compat()
104 : d( 0 )
105{
106}
107
108Compat::~Compat()
109{
110}
111
112void Compat::fixEmptySummary(const Incidence::Ptr &incidence)
113{
114 // some stupid vCal exporters ignore the standard and use Description
115 // instead of Summary for the default field. Correct for this: Copy the
116 // first line of the description to the summary (if summary is just one
117 // line, move it)
118 if (incidence->summary().isEmpty() && !(incidence->description().isEmpty())) {
119 QString oldDescription = incidence->description().trimmed();
120 QString newSummary(oldDescription);
121 newSummary.remove(QRegExp(QLatin1String("\n.*")));
122 incidence->setSummary(newSummary);
123 if (oldDescription == newSummary) {
124 incidence->setDescription(QLatin1String(""));
125 }
126 }
127}
128
129void Compat::fixAlarms(const Incidence::Ptr &incidence)
130{
131 Q_UNUSED(incidence);
132}
133
134void Compat::fixFloatingEnd(QDate &date)
135{
136 Q_UNUSED(date);
137}
138
139void Compat::fixRecurrence(const Incidence::Ptr &incidence)
140{
141 Q_UNUSED(incidence);
142 // Prevent use of compatibility mode during subsequent changes by the application
143 // incidence->recurrence()->setCompatVersion();
144}
145
146int Compat::fixPriority(int priority)
147{
148 return priority;
149}
150
151bool Compat::useTimeZoneShift()
152{
153 return true;
154}
155
156void Compat::setCreatedToDtStamp(const Incidence::Ptr &incidence, const KDateTime &dtstamp)
157{
158 Q_UNUSED(incidence);
159 Q_UNUSED(dtstamp);
160}
161
162class CompatDecorator::Private {
163public:
164 Compat *compat;
165};
166
167CompatDecorator::CompatDecorator(Compat *compat)
168 : d(new CompatDecorator::Private)
169{
170 d->compat = compat;
171}
172
173CompatDecorator::~CompatDecorator()
174{
175 delete d->compat;
176 delete d;
177}
178
179void CompatDecorator::fixEmptySummary(const Incidence::Ptr &incidence)
180{
181 d->compat->fixEmptySummary(incidence);
182}
183
184void CompatDecorator::fixAlarms(const Incidence::Ptr &incidence)
185{
186 d->compat->fixAlarms(incidence);
187}
188
189void CompatDecorator::fixFloatingEnd(QDate &date)
190{
191 d->compat->fixFloatingEnd(date);
192}
193
194void CompatDecorator::fixRecurrence(const Incidence::Ptr &incidence)
195{
196 d->compat->fixRecurrence(incidence);
197}
198
199int CompatDecorator::fixPriority(int priority)
200{
201 return d->compat->fixPriority(priority);
202}
203
204bool CompatDecorator::useTimeZoneShift()
205{
206 return d->compat->useTimeZoneShift();
207}
208
209void CompatDecorator::setCreatedToDtStamp(const Incidence::Ptr &incidence,
210 const KDateTime &dtstamp)
211{
212 d->compat->setCreatedToDtStamp(incidence, dtstamp);
213}
214
215void CompatPre35::fixRecurrence(const Incidence::Ptr &incidence)
216{
217 Recurrence *recurrence = incidence->recurrence();
218 if (recurrence) {
219 KDateTime start(incidence->dtStart());
220 // kde < 3.5 only had one rrule, so no need to loop over all RRULEs.
221 RecurrenceRule *r = recurrence->defaultRRule();
222 if (r && !r->dateMatchesRules(start)) {
223 recurrence->addExDateTime(start);
224 }
225 }
226
227 // Call base class method now that everything else is done
228 Compat::fixRecurrence(incidence);
229}
230
231int CompatPre34::fixPriority(int priority)
232{
233 if (0 < priority && priority < 6) {
234 // adjust 1->1, 2->3, 3->5, 4->7, 5->9
235 return 2 * priority - 1;
236 } else {
237 return priority;
238 }
239}
240
241void CompatPre32::fixRecurrence(const Incidence::Ptr &incidence)
242{
243 Recurrence *recurrence = incidence->recurrence();
244 if (recurrence->recurs() && recurrence->duration() > 0) {
245 recurrence->setDuration(recurrence->duration() + incidence->recurrence()->exDates().count());
246 }
247 // Call base class method now that everything else is done
248 CompatPre35::fixRecurrence(incidence);
249}
250
251void CompatPre31::fixFloatingEnd(QDate &endDate)
252{
253 endDate = endDate.addDays(1);
254}
255
256void CompatPre31::fixRecurrence(const Incidence::Ptr &incidence)
257{
258 CompatPre32::fixRecurrence(incidence);
259
260 Recurrence *recur = incidence->recurrence();
261 RecurrenceRule *r = 0;
262 if (recur) {
263 r = recur->defaultRRule();
264 }
265 if (recur && r) {
266 int duration = r->duration();
267 if (duration > 0) {
268 // Backwards compatibility for KDE < 3.1.
269 // rDuration was set to the number of time periods to recur,
270 // with week start always on a Monday.
271 // Convert this to the number of occurrences.
272 r->setDuration(-1);
273 QDate end(r->startDt().date());
274 bool doNothing = false;
275 // # of periods:
276 int tmp = (duration - 1) * r->frequency();
277 switch (r->recurrenceType()) {
278 case RecurrenceRule::rWeekly:
279 {
280 end = end.addDays(tmp * 7 + 7 - end.dayOfWeek());
281 break;
282 }
283 case RecurrenceRule::rMonthly:
284 {
285 int month = end.month() - 1 + tmp;
286 end.setYMD(end.year() + month / 12, month % 12 + 1, 31);
287 break;
288 }
289 case RecurrenceRule::rYearly:
290 {
291 end.setYMD(end.year() + tmp, 12, 31);
292 break;
293 }
294 default:
295 doNothing = true;
296 break;
297 }
298 if (!doNothing) {
299 duration = r->durationTo(
300 KDateTime(end, QTime(0, 0, 0), incidence->dtStart().timeSpec()));
301 r->setDuration(duration);
302 }
303 }
304
305 /* addYearlyNum */
306 // Dates were stored as day numbers, with a fiddle to take account of
307 // leap years. Convert the day number to a month.
308 QList<int> days = r->byYearDays();
309 if (!days.isEmpty()) {
310 QList<int> months = r->byMonths();
311 for (int i = 0; i < months.size(); ++i) {
312 int newmonth =
313 QDate(r->startDt().date().year(), 1, 1).addDays(months.at(i) - 1).month();
314 if (!months.contains(newmonth)) {
315 months.append(newmonth);
316 }
317 }
318
319 r->setByMonths(months);
320 days.clear();
321 r->setByYearDays(days);
322 }
323 }
324}
325
326void CompatOutlook9::fixAlarms(const Incidence::Ptr &incidence)
327{
328 if (!incidence) {
329 return;
330 }
331 Alarm::List alarms = incidence->alarms();
332 Alarm::List::Iterator it;
333 for (it = alarms.begin(); it != alarms.end(); ++it) {
334 Alarm::Ptr al = *it;
335 if (al && al->hasStartOffset()) {
336 Duration offsetDuration = al->startOffset();
337 int offs = offsetDuration.asSeconds();
338 if (offs > 0) {
339 offsetDuration = Duration(-offs);
340 }
341 al->setStartOffset(offsetDuration);
342 }
343 }
344}
345
346bool Compat32PrereleaseVersions::useTimeZoneShift()
347{
348 return false;
349}
350
351CompatPre410::CompatPre410(Compat *decoratedCompat)
352 : CompatDecorator(decoratedCompat)
353{
354}
355
356void CompatPre410::setCreatedToDtStamp(const Incidence::Ptr &incidence, const KDateTime &dtstamp)
357{
358 if (dtstamp.isValid()) {
359 incidence->setCreated(dtstamp);
360 }
361}
362