File indexing completed on 2024-04-21 03:52:45
0001 /* 0002 This file is part of the kcalcore library. 0003 0004 SPDX-FileCopyrightText: 2002 Cornelius Schumacher <schumacher@kde.org> 0005 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> 0006 SPDX-FileCopyrightText: 2012 Christian Mollekopf <mollekopf@kolabsys.com> 0007 0008 SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 /** 0011 @file 0012 This file is part of the API for handling calendar data and defines 0013 classes for managing compatibility between different calendar formats. 0014 0015 @brief 0016 Classes that provide compatibility to older or "broken" calendar formats. 0017 0018 @author Cornelius Schumacher \<schumacher@kde.org\> 0019 @author Reinhold Kainhofer \<reinhold@kainhofer.com\> 0020 */ 0021 0022 #include "compat_p.h" 0023 #include "incidence.h" 0024 0025 #include "kcalendarcore_debug.h" 0026 0027 #include <QDate> 0028 #include <QRegularExpression> 0029 #include <QString> 0030 0031 using namespace KCalendarCore; 0032 0033 Compat *CompatFactory::createCompat(const QString &productId, const QString &implementationVersion) 0034 { 0035 Compat *compat = nullptr; 0036 0037 int korg = productId.indexOf(QLatin1String("KOrganizer")); 0038 int outl9 = productId.indexOf(QLatin1String("Outlook 9.0")); 0039 0040 if (korg >= 0) { 0041 int versionStart = productId.indexOf(QLatin1Char(' '), korg); 0042 if (versionStart >= 0) { 0043 int versionStop = productId.indexOf(QRegularExpression(QStringLiteral("[ /]")), versionStart + 1); 0044 if (versionStop >= 0) { 0045 QString version = productId.mid(versionStart + 1, versionStop - versionStart - 1); 0046 0047 int versionNum = version.section(QLatin1Char('.'), 0, 0).toInt() * 10000 + version.section(QLatin1Char('.'), 1, 1).toInt() * 100 0048 + version.section(QLatin1Char('.'), 2, 2).toInt(); 0049 int releaseStop = productId.indexOf(QLatin1Char('/'), versionStop); 0050 QString release; 0051 if (releaseStop > versionStop) { 0052 release = productId.mid(versionStop + 1, releaseStop - versionStop - 1); 0053 } 0054 if (versionNum < 30100) { 0055 compat = new CompatPre31; 0056 } else if (versionNum < 30200) { 0057 compat = new CompatPre32; 0058 } else if (versionNum == 30200 && release == QLatin1String("pre")) { 0059 qCDebug(KCALCORE_LOG) << "Generating compat for KOrganizer 3.2 pre"; 0060 compat = new Compat32PrereleaseVersions; 0061 } else if (versionNum < 30400) { 0062 compat = new CompatPre34; 0063 } else if (versionNum < 30500) { 0064 compat = new CompatPre35; 0065 } 0066 } 0067 } 0068 } else if (outl9 >= 0) { 0069 qCDebug(KCALCORE_LOG) << "Generating compat for Outlook < 2000 (Outlook 9.0)"; 0070 compat = new CompatOutlook9; 0071 } 0072 if (!compat) { 0073 compat = new Compat; 0074 } 0075 // Older implementations lacked the implementation version, 0076 // so apply this fix if it is a file from kontact and the version is missing. 0077 if (implementationVersion.isEmpty() 0078 && (productId.contains(QLatin1String("libkcal")) || productId.contains(QLatin1String("KOrganizer")) || productId.contains(QLatin1String("KAlarm")))) { 0079 compat = new CompatPre410(compat); 0080 } 0081 0082 return compat; 0083 } 0084 0085 Compat::~Compat() = default; 0086 0087 void Compat::fixEmptySummary(const Incidence::Ptr &incidence) 0088 { 0089 // some stupid vCal exporters ignore the standard and use Description 0090 // instead of Summary for the default field. Correct for this: Copy the 0091 // first line of the description to the summary (if summary is just one 0092 // line, move it) 0093 if (incidence->summary().isEmpty() && !(incidence->description().isEmpty())) { 0094 QString oldDescription = incidence->description().trimmed(); 0095 QString newSummary(oldDescription); 0096 newSummary.remove(QRegularExpression(QStringLiteral("\n.*"))); 0097 incidence->setSummary(newSummary); 0098 if (oldDescription == newSummary) { 0099 incidence->setDescription(QLatin1String("")); 0100 } 0101 } 0102 } 0103 0104 void Compat::fixAlarms(const Incidence::Ptr &incidence) 0105 { 0106 Q_UNUSED(incidence); 0107 } 0108 0109 void Compat::fixFloatingEnd(QDate &date) 0110 { 0111 Q_UNUSED(date); 0112 } 0113 0114 void Compat::fixRecurrence(const Incidence::Ptr &incidence) 0115 { 0116 Q_UNUSED(incidence); 0117 // Prevent use of compatibility mode during subsequent changes by the application 0118 // incidence->recurrence()->setCompatVersion(); 0119 } 0120 0121 int Compat::fixPriority(int priority) 0122 { 0123 return priority; 0124 } 0125 0126 bool Compat::useTimeZoneShift() const 0127 { 0128 return true; 0129 } 0130 0131 void Compat::setCreatedToDtStamp(const Incidence::Ptr &incidence, const QDateTime &dtstamp) 0132 { 0133 Q_UNUSED(incidence); 0134 Q_UNUSED(dtstamp); 0135 } 0136 0137 CompatDecorator::CompatDecorator(Compat *compat) 0138 : m_compat(compat) 0139 { 0140 } 0141 0142 CompatDecorator::~CompatDecorator() = default; 0143 0144 void CompatDecorator::fixEmptySummary(const Incidence::Ptr &incidence) 0145 { 0146 m_compat->fixEmptySummary(incidence); 0147 } 0148 0149 void CompatDecorator::fixAlarms(const Incidence::Ptr &incidence) 0150 { 0151 m_compat->fixAlarms(incidence); 0152 } 0153 0154 void CompatDecorator::fixFloatingEnd(QDate &date) 0155 { 0156 m_compat->fixFloatingEnd(date); 0157 } 0158 0159 void CompatDecorator::fixRecurrence(const Incidence::Ptr &incidence) 0160 { 0161 m_compat->fixRecurrence(incidence); 0162 } 0163 0164 int CompatDecorator::fixPriority(int priority) 0165 { 0166 return m_compat->fixPriority(priority); 0167 } 0168 0169 bool CompatDecorator::useTimeZoneShift() const 0170 { 0171 return m_compat->useTimeZoneShift(); 0172 } 0173 0174 void CompatDecorator::setCreatedToDtStamp(const Incidence::Ptr &incidence, const QDateTime &dtstamp) 0175 { 0176 m_compat->setCreatedToDtStamp(incidence, dtstamp); 0177 } 0178 0179 void CompatPre35::fixRecurrence(const Incidence::Ptr &incidence) 0180 { 0181 Recurrence *recurrence = incidence->recurrence(); 0182 if (recurrence) { 0183 QDateTime start(incidence->dtStart()); 0184 // kde < 3.5 only had one rrule, so no need to loop over all RRULEs. 0185 RecurrenceRule *r = recurrence->defaultRRule(); 0186 if (r && !r->dateMatchesRules(start)) { 0187 recurrence->addExDateTime(start); 0188 } 0189 } 0190 0191 // Call base class method now that everything else is done 0192 Compat::fixRecurrence(incidence); 0193 } 0194 0195 int CompatPre34::fixPriority(int priority) 0196 { 0197 if (0 < priority && priority < 6) { 0198 // adjust 1->1, 2->3, 3->5, 4->7, 5->9 0199 return 2 * priority - 1; 0200 } else { 0201 return priority; 0202 } 0203 } 0204 0205 void CompatPre32::fixRecurrence(const Incidence::Ptr &incidence) 0206 { 0207 Recurrence *recurrence = incidence->recurrence(); 0208 if (recurrence->recurs() && recurrence->duration() > 0) { 0209 recurrence->setDuration(recurrence->duration() + incidence->recurrence()->exDates().count()); 0210 } 0211 // Call base class method now that everything else is done 0212 CompatPre35::fixRecurrence(incidence); 0213 } 0214 0215 void CompatPre31::fixFloatingEnd(QDate &endDate) 0216 { 0217 endDate = endDate.addDays(1); 0218 } 0219 0220 void CompatPre31::fixRecurrence(const Incidence::Ptr &incidence) 0221 { 0222 CompatPre32::fixRecurrence(incidence); 0223 0224 Recurrence *recur = incidence->recurrence(); 0225 RecurrenceRule *r = nullptr; 0226 if (recur) { 0227 r = recur->defaultRRule(); 0228 } 0229 if (recur && r) { 0230 int duration = r->duration(); 0231 if (duration > 0) { 0232 // Backwards compatibility for KDE < 3.1. 0233 // rDuration was set to the number of time periods to recur, 0234 // with week start always on a Monday. 0235 // Convert this to the number of occurrences. 0236 r->setDuration(-1); 0237 QDate end(r->startDt().date()); 0238 bool doNothing = false; 0239 // # of periods: 0240 int tmp = (duration - 1) * r->frequency(); 0241 switch (r->recurrenceType()) { 0242 case RecurrenceRule::rWeekly: { 0243 end = end.addDays(tmp * 7 + 7 - end.dayOfWeek()); 0244 break; 0245 } 0246 case RecurrenceRule::rMonthly: { 0247 int month = end.month() - 1 + tmp; 0248 end.setDate(end.year() + month / 12, month % 12 + 1, 31); 0249 break; 0250 } 0251 case RecurrenceRule::rYearly: { 0252 end.setDate(end.year() + tmp, 12, 31); 0253 break; 0254 } 0255 default: 0256 doNothing = true; 0257 break; 0258 } 0259 if (!doNothing) { 0260 duration = r->durationTo(QDateTime(end, QTime(0, 0, 0), incidence->dtStart().timeZone())); 0261 r->setDuration(duration); 0262 } 0263 } 0264 0265 /* addYearlyNum */ 0266 // Dates were stored as day numbers, with a fiddle to take account of 0267 // leap years. Convert the day number to a month. 0268 QList<int> days = r->byYearDays(); 0269 if (!days.isEmpty()) { 0270 QList<int> months = r->byMonths(); 0271 for (int i = 0; i < months.size(); ++i) { 0272 int newmonth = QDate(r->startDt().date().year(), 1, 1).addDays(months.at(i) - 1).month(); 0273 if (!months.contains(newmonth)) { 0274 months.append(newmonth); 0275 } 0276 } 0277 0278 r->setByMonths(months); 0279 days.clear(); 0280 r->setByYearDays(days); 0281 } 0282 } 0283 } 0284 0285 void CompatOutlook9::fixAlarms(const Incidence::Ptr &incidence) 0286 { 0287 if (!incidence) { 0288 return; 0289 } 0290 0291 const Alarm::List alarms = incidence->alarms(); 0292 for (const Alarm::Ptr &al : alarms) { 0293 if (al && al->hasStartOffset()) { 0294 Duration offsetDuration = al->startOffset(); 0295 int offs = offsetDuration.asSeconds(); 0296 if (offs > 0) { 0297 offsetDuration = Duration(-offs); 0298 } 0299 al->setStartOffset(offsetDuration); 0300 } 0301 } 0302 } 0303 0304 bool Compat32PrereleaseVersions::useTimeZoneShift() const 0305 { 0306 return false; 0307 } 0308 0309 CompatPre410::CompatPre410(Compat *decoratedCompat) 0310 : CompatDecorator(decoratedCompat) 0311 { 0312 } 0313 0314 void CompatPre410::setCreatedToDtStamp(const Incidence::Ptr &incidence, const QDateTime &dtstamp) 0315 { 0316 if (dtstamp.isValid()) { 0317 incidence->setCreated(dtstamp); 0318 } 0319 }