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 }