Warning, file /pim/kalarm/src/kalarmcalendar/kacalendar.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  kacalendar.cpp  -  KAlarm kcal library calendar and event functions
0003  *  This file is part of kalarmcalendar library, which provides access to KAlarm
0004  *  calendar data.
0005  *  Program:  kalarm
0006  *  SPDX-FileCopyrightText: 2001-2022 David Jarvie <djarvie@kde.org>
0007  *
0008  *  SPDX-License-Identifier: LGPL-2.0-or-later
0009  */
0010 
0011 #include "kacalendar.h"
0012 
0013 #include "kaevent.h"
0014 #include "version.h"
0015 #include "kalarmcal_debug.h"
0016 
0017 #include <KCalendarCore/Alarm>
0018 #include <KCalendarCore/CalFormat>
0019 
0020 #include <KLocalizedString>
0021 
0022 #include <QFile>
0023 #include <QFileInfo>
0024 #include <QMap>
0025 
0026 using namespace KCalendarCore;
0027 
0028 namespace KAlarmCal
0029 {
0030 
0031 const QLatin1String MIME_BASE("application/x-vnd.kde.alarm");
0032 const QLatin1String MIME_ACTIVE("application/x-vnd.kde.alarm.active");
0033 const QLatin1String MIME_ARCHIVED("application/x-vnd.kde.alarm.archived");
0034 const QLatin1String MIME_TEMPLATE("application/x-vnd.kde.alarm.template");
0035 
0036 static const QByteArray VERSION_PROPERTY("VERSION");     // X-KDE-KALARM-VERSION VCALENDAR property
0037 
0038 class Private
0039 {
0040 public:
0041     static int readKAlarmVersion(const FileStorage::Ptr&, QString& subVersion, QString& versionString);
0042 
0043     static QByteArray mIcalProductId;
0044 };
0045 
0046 QByteArray Private::mIcalProductId;
0047 
0048 //=============================================================================
0049 
0050 namespace KACalendar
0051 {
0052 
0053 const QByteArray APPNAME("KALARM");
0054 
0055 void setProductId(const QByteArray& progName, const QByteArray& progVersion)
0056 {
0057     Private::mIcalProductId = QByteArray("-//K Desktop Environment//NONSGML " + progName + " " + progVersion + "//EN");
0058     KCalendarCore::CalFormat::setApplication(QString::fromLatin1(progName), QString::fromLatin1(Private::mIcalProductId));
0059 }
0060 
0061 QByteArray icalProductId()
0062 {
0063     return Private::mIcalProductId.isEmpty() ? QByteArray("-//K Desktop Environment//NONSGML  //EN") : Private::mIcalProductId;
0064 }
0065 
0066 /******************************************************************************
0067 * Set the X-KDE-KALARM-VERSION property in a calendar.
0068 */
0069 void setKAlarmVersion(const Calendar::Ptr& calendar)
0070 {
0071     calendar->setCustomProperty(APPNAME, VERSION_PROPERTY, QString::fromLatin1(KAEvent::currentCalendarVersionString()));
0072 }
0073 
0074 /******************************************************************************
0075 * Check the version of KAlarm which wrote a calendar file, and convert it in
0076 * memory to the current KAlarm format if possible. The storage file is not
0077 * updated. The compatibility of the calendar format is indicated by the return
0078 * value.
0079 */
0080 int updateVersion(const FileStorage::Ptr& fileStorage, QString& versionString)
0081 {
0082     QString subVersion;
0083     const int version = Private::readKAlarmVersion(fileStorage, subVersion, versionString);
0084     if (version == CurrentFormat)
0085         return CurrentFormat;    // calendar is in the current KAlarm format
0086     if (version == IncompatibleFormat  ||  version > KAEvent::currentCalendarVersion())
0087         return IncompatibleFormat;    // calendar was created by another program, or an unknown version of KAlarm
0088 
0089     // Calendar was created by an earlier version of KAlarm.
0090     // Convert events to current KAlarm format for when/if the calendar is saved.
0091     qCDebug(KALARMCAL_LOG) << "KAlarm version" << version;
0092     KAEvent::convertKCalEvents(fileStorage->calendar(), version);
0093     // Set the new calendar version.
0094     setKAlarmVersion(fileStorage->calendar());
0095     return version;
0096 }
0097 
0098 } // namespace KACalendar
0099 
0100 /******************************************************************************
0101 * Return the KAlarm version which wrote the calendar which has been loaded.
0102 * The format is, for example, 000507 for 0.5.7.
0103 * Reply = CurrentFormat if the calendar was created by the current version of KAlarm
0104 *       = IncompatibleFormat if it was created by KAlarm pre-0.3.5, or another program
0105 *       = version number if created by another KAlarm version.
0106 */
0107 int Private::readKAlarmVersion(const FileStorage::Ptr& fileStorage, QString& subVersion, QString& versionString)
0108 {
0109     subVersion.clear();
0110     Calendar::Ptr calendar = fileStorage->calendar();
0111     versionString = calendar->customProperty(KACalendar::APPNAME, VERSION_PROPERTY);
0112     qCDebug(KALARMCAL_LOG) << "File=" << fileStorage->fileName() << ", version=" << versionString;
0113 
0114     if (versionString.isEmpty())
0115     {
0116         // Pre-KAlarm 1.4 defined the KAlarm version number in the PRODID field.
0117         // If another application has written to the file, this may not be present.
0118         const QString prodid = calendar->productId();
0119         if (prodid.isEmpty())
0120         {
0121             // Check whether the calendar file is empty, in which case
0122             // it can be written to freely.
0123             const QFileInfo fi(fileStorage->fileName());
0124             if (!fi.size())
0125                 return KACalendar::CurrentFormat;
0126         }
0127 
0128         // Find the KAlarm identifier
0129         QString progname = QStringLiteral(" KAlarm ");
0130         int i = prodid.indexOf(progname, 0, Qt::CaseInsensitive);
0131         if (i < 0)
0132         {
0133             // Older versions used KAlarm's translated name in the product ID, which
0134             // could have created problems using a calendar in different locales.
0135             progname = QLatin1String(" ") + i18n("KAlarm") + QLatin1Char(' ');
0136             i = prodid.indexOf(progname, 0, Qt::CaseInsensitive);
0137             if (i < 0)
0138                 return KACalendar::IncompatibleFormat;    // calendar wasn't created by KAlarm
0139         }
0140 
0141         // Extract the KAlarm version string
0142         versionString = prodid.mid(i + progname.length()).trimmed();
0143         i = versionString.indexOf(QLatin1Char('/'));
0144         const int j = versionString.indexOf(QLatin1Char(' '));
0145         if (j >= 0  &&  j < i)
0146             i = j;
0147         if (i <= 0)
0148             return KACalendar::IncompatibleFormat;    // missing version string
0149         versionString.truncate(i);   // 'versionString' now contains the KAlarm version string
0150     }
0151     if (versionString == QLatin1String(KAEvent::currentCalendarVersionString()))
0152         return KACalendar::CurrentFormat;    // the calendar is in the current KAlarm format
0153     const int ver = KAlarmCal::getVersionNumber(versionString, &subVersion);
0154     if (ver == KAEvent::currentCalendarVersion())
0155         return KACalendar::CurrentFormat;    // the calendar is in the current KAlarm format
0156     return KAlarmCal::getVersionNumber(versionString, &subVersion);
0157 }
0158 
0159 //=============================================================================
0160 
0161 namespace CalEvent
0162 {
0163 
0164 // Struct to contain static strings, to allow use of Q_GLOBAL_STATIC
0165 // to delete them on program termination.
0166 struct StaticStrings
0167 {
0168     StaticStrings()
0169         : STATUS_PROPERTY("TYPE")
0170         , ACTIVE_STATUS(QStringLiteral("ACTIVE"))
0171         , TEMPLATE_STATUS(QStringLiteral("TEMPLATE"))
0172         , ARCHIVED_STATUS(QStringLiteral("ARCHIVED"))
0173         , DISPLAYING_STATUS(QStringLiteral("DISPLAYING"))
0174         , ARCHIVED_UID(QStringLiteral("exp-"))
0175         , DISPLAYING_UID(QStringLiteral("disp-"))
0176         , OLD_ARCHIVED_UID(QStringLiteral("-exp-"))
0177         , OLD_TEMPLATE_UID(QStringLiteral("-tmpl-"))
0178     {}
0179     // Event custom properties.
0180     // Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file.
0181     const QByteArray STATUS_PROPERTY;    // X-KDE-KALARM-TYPE property
0182     const QString ACTIVE_STATUS;
0183     const QString TEMPLATE_STATUS;
0184     const QString ARCHIVED_STATUS;
0185     const QString DISPLAYING_STATUS;
0186 
0187     // Event ID identifiers
0188     const QString ARCHIVED_UID;
0189     const QString DISPLAYING_UID;
0190 
0191     // Old KAlarm format identifiers
0192     const QString OLD_ARCHIVED_UID;
0193     const QString OLD_TEMPLATE_UID;
0194 };
0195 Q_GLOBAL_STATIC(StaticStrings, staticStrings)
0196 
0197 /******************************************************************************
0198 * Convert a unique ID to indicate that the event is in a specified calendar file.
0199 * This is done by prefixing archived or displaying alarms with "exp-" or "disp-",
0200 * while active alarms have no prefix.
0201 * Note that previously, "-exp-" was inserted in the middle of the UID.
0202 */
0203 QString uid(const QString& id, Type status)
0204 {
0205     QString result = id;
0206     Type oldType;
0207     int i;
0208     int len;
0209     if (result.startsWith(staticStrings->ARCHIVED_UID))
0210     {
0211         oldType = ARCHIVED;
0212         len = staticStrings->ARCHIVED_UID.length();
0213     }
0214     else if (result.startsWith(staticStrings->DISPLAYING_UID))
0215     {
0216         oldType = DISPLAYING;
0217         len = staticStrings->DISPLAYING_UID.length();
0218     }
0219     else
0220     {
0221         if ((i = result.indexOf(staticStrings->OLD_ARCHIVED_UID)) > 0)
0222             result.remove(i, staticStrings->OLD_ARCHIVED_UID.length());
0223         oldType = ACTIVE;
0224         len = 0;
0225     }
0226     if (status != oldType)
0227     {
0228         QString part;
0229         switch (status)
0230         {
0231             case ARCHIVED:    part = staticStrings->ARCHIVED_UID;  break;
0232             case DISPLAYING:  part = staticStrings->DISPLAYING_UID;  break;
0233             case ACTIVE:
0234                 break;
0235             case TEMPLATE:
0236             case EMPTY:
0237             default:
0238                 return result;
0239         }
0240         result.replace(0, len, part);
0241     }
0242     return result;
0243 }
0244 
0245 /******************************************************************************
0246 * Check an event to determine its type - active, archived, template or empty.
0247 * The default type is active if it contains alarms and there is nothing to
0248 * indicate otherwise.
0249 * Note that the mere fact that all an event's alarms have passed does not make
0250 * an event archived, since it may be that they have not yet been able to be
0251 * triggered. They will be archived once KAlarm tries to handle them.
0252 * Do not call this function for the displaying alarm calendar.
0253 */
0254 Type status(const Event::Ptr& event, QString* param)
0255 {
0256     // Set up a static quick lookup for type strings
0257     using PropertyMap = QMap<QString, Type>;
0258     static PropertyMap properties;
0259     if (properties.isEmpty())
0260     {
0261         properties[staticStrings->ACTIVE_STATUS]     = ACTIVE;
0262         properties[staticStrings->TEMPLATE_STATUS]   = TEMPLATE;
0263         properties[staticStrings->ARCHIVED_STATUS]   = ARCHIVED;
0264         properties[staticStrings->DISPLAYING_STATUS] = DISPLAYING;
0265     }
0266 
0267     if (param)
0268         param->clear();
0269     if (!event)
0270         return EMPTY;
0271     const Alarm::List alarms = event->alarms();
0272     if (alarms.isEmpty())
0273         return EMPTY;
0274 
0275     const QString property = event->customProperty(KACalendar::APPNAME, staticStrings->STATUS_PROPERTY);
0276     if (!property.isEmpty())
0277     {
0278         // There's a X-KDE-KALARM-TYPE property.
0279         // It consists of the event type, plus an optional parameter.
0280         PropertyMap::ConstIterator it = properties.constFind(property);
0281         if (it != properties.constEnd())
0282             return it.value();
0283         const int i = property.indexOf(QLatin1Char(';'));
0284         if (i < 0)
0285             return EMPTY;
0286         it = properties.constFind(property.left(i));
0287         if (it == properties.constEnd())
0288             return EMPTY;
0289         if (param)
0290             *param = property.mid(i + 1);
0291         return it.value();
0292     }
0293 
0294     // The event either wasn't written by KAlarm, or was written by a pre-2.0 version.
0295     // Check first for an old KAlarm format, which indicated the event type in the
0296     // middle of its UID.
0297     const QString euid = event->uid();
0298     if (euid.indexOf(staticStrings->OLD_ARCHIVED_UID) > 0)
0299         return ARCHIVED;
0300     if (euid.indexOf(staticStrings->OLD_TEMPLATE_UID) > 0)
0301         return TEMPLATE;
0302 
0303     // Otherwise, assume it's an active alarm
0304     return ACTIVE;
0305 }
0306 
0307 /******************************************************************************
0308 * Set the event's type - active, archived, template, etc.
0309 * If a parameter is supplied, it will be appended as a second parameter to the
0310 * custom property.
0311 */
0312 void setStatus(const Event::Ptr& event, Type status, const QString& param)
0313 {
0314     if (!event)
0315         return;
0316     QString text;
0317     switch (status)
0318     {
0319         case ACTIVE:      text = staticStrings->ACTIVE_STATUS;  break;
0320         case TEMPLATE:    text = staticStrings->TEMPLATE_STATUS;  break;
0321         case ARCHIVED:    text = staticStrings->ARCHIVED_STATUS;  break;
0322         case DISPLAYING:  text = staticStrings->DISPLAYING_STATUS;  break;
0323         default:
0324             event->removeCustomProperty(KACalendar::APPNAME, staticStrings->STATUS_PROPERTY);
0325             return;
0326     }
0327     if (!param.isEmpty())
0328         text += QLatin1Char(';') + param;
0329     event->setCustomProperty(KACalendar::APPNAME, staticStrings->STATUS_PROPERTY, text);
0330 }
0331 
0332 Type type(const QString& mimeType)
0333 {
0334     if (mimeType == MIME_ACTIVE)
0335         return ACTIVE;
0336     if (mimeType == MIME_ARCHIVED)
0337         return ARCHIVED;
0338     if (mimeType == MIME_TEMPLATE)
0339         return TEMPLATE;
0340     return EMPTY;
0341 }
0342 
0343 Types types(const QStringList& mimeTypes)
0344 {
0345     Types types = {};
0346     for (const QString& mtype : mimeTypes)
0347     {
0348         if (mtype == MIME_ACTIVE)
0349             types |= ACTIVE;
0350         else if (mtype == MIME_ARCHIVED)
0351             types |= ARCHIVED;
0352         else if (mtype == MIME_TEMPLATE)
0353             types |= TEMPLATE;
0354     }
0355     return types;
0356 }
0357 
0358 QString mimeType(Type mtype)
0359 {
0360     switch (mtype)
0361     {
0362         case ACTIVE:    return MIME_ACTIVE;
0363         case ARCHIVED:  return MIME_ARCHIVED;
0364         case TEMPLATE:  return MIME_TEMPLATE;
0365         default:
0366             return {};
0367     }
0368 }
0369 
0370 QStringList mimeTypes(Types types)
0371 {
0372     QStringList mimes;
0373     for (int i = 1;  types;  i <<= 1)
0374     {
0375         if (types & i)
0376         {
0377             mimes += mimeType(Type(i));
0378             types &= ~i;
0379         }
0380     }
0381     return mimes;
0382 }
0383 
0384 } // namespace CalEvent
0385 
0386 } // namespace KAlarmCal
0387 
0388 QDebug operator<<(QDebug debug, KAlarmCal::CalEvent::Type type)
0389 {
0390     const char* str;
0391     switch (type)
0392     {
0393         case KAlarmCal::CalEvent::ACTIVE:    str = "Active alarms";    break;
0394         case KAlarmCal::CalEvent::ARCHIVED:  str = "Archived alarms";  break;
0395         case KAlarmCal::CalEvent::TEMPLATE:  str = "Alarm templates";  break;
0396         default:        return debug;
0397     }
0398     debug << str;
0399     return debug;
0400 }
0401 
0402 // vim: et sw=4: