KAlarmCal

kacalendar.cpp
1 /*
2  * kacalendar.cpp - KAlarm kcal library calendar and event functions
3  * This file is part of kalarmcal library, which provides access to KAlarm
4  * calendar data.
5  * SPDX-FileCopyrightText: 2001-2020 David Jarvie <[email protected]>
6  *
7  * SPDX-License-Identifier: LGPL-2.0-or-later
8  */
9 
10 #include "kacalendar.h"
11 
12 #include "kaevent.h"
13 #include "version.h"
14 
15 #include <KCalendarCore/Alarm>
16 #include <KCalendarCore/MemoryCalendar>
17 
18 #include <klocalizedstring.h>
19 #include "kalarmcal_debug.h"
20 
21 #include <QMap>
22 #include <QFile>
23 #include <QFileInfo>
24 #include <QTextStream>
25 
26 using namespace KCalendarCore;
27 
28 namespace KAlarmCal
29 {
30 
31 const QLatin1String MIME_BASE("application/x-vnd.kde.alarm");
32 const QLatin1String MIME_ACTIVE("application/x-vnd.kde.alarm.active");
33 const QLatin1String MIME_ARCHIVED("application/x-vnd.kde.alarm.archived");
34 const QLatin1String MIME_TEMPLATE("application/x-vnd.kde.alarm.template");
35 
36 static const QByteArray VERSION_PROPERTY("VERSION"); // X-KDE-KALARM-VERSION VCALENDAR property
37 
38 static bool isUTC(const QString &localFile);
39 
40 class Private
41 {
42 public:
43  static int readKAlarmVersion(const FileStorage::Ptr &, QString &subVersion, QString &versionString);
44 
45  static QByteArray mIcalProductId;
46 };
47 
48 QByteArray Private::mIcalProductId;
49 
50 //=============================================================================
51 
52 namespace KACalendar
53 {
54 
55 const QByteArray APPNAME("KALARM");
56 
57 void setProductId(const QByteArray &progName, const QByteArray &progVersion)
58 {
59  Private::mIcalProductId = QByteArray("-//K Desktop Environment//NONSGML " + progName + " " + progVersion + "//EN");
60 }
61 
63 {
64  return Private::mIcalProductId.isEmpty() ? QByteArray("-//K Desktop Environment//NONSGML //EN") : Private::mIcalProductId;
65 }
66 
67 /******************************************************************************
68 * Set the X-KDE-KALARM-VERSION property in a calendar.
69 */
70 void setKAlarmVersion(const Calendar::Ptr &calendar)
71 {
72  calendar->setCustomProperty(APPNAME, VERSION_PROPERTY, QString::fromLatin1(KAEvent::currentCalendarVersionString()));
73 }
74 
75 /******************************************************************************
76 * Check the version of KAlarm which wrote a calendar file, and convert it in
77 * memory to the current KAlarm format if possible. The storage file is not
78 * updated. The compatibility of the calendar format is indicated by the return
79 * value.
80 */
81 int updateVersion(const FileStorage::Ptr &fileStorage, QString &versionString)
82 {
83  QString subVersion;
84  const int version = Private::readKAlarmVersion(fileStorage, subVersion, versionString);
85  if (version == CurrentFormat) {
86  return CurrentFormat; // calendar is in the current KAlarm format
87  }
88  if (version == IncompatibleFormat || version > KAEvent::currentCalendarVersion()) {
89  return IncompatibleFormat; // calendar was created by another program, or an unknown version of KAlarm
90  }
91 
92  // Calendar was created by an earlier version of KAlarm.
93  // Convert it to the current format.
94  const QString localFile = fileStorage->fileName();
95  int ver = version;
96  if (version == KAlarmCal::Version(0, 5, 7) && !localFile.isEmpty()) {
97  // KAlarm version 0.5.7 - check whether times are stored in UTC, in which
98  // case it is the KDE 3.0.0 version, which needs adjustment of summer times.
99  if (isUTC(localFile)) {
100  ver = -version;
101  }
102  qCDebug(KALARMCAL_LOG) << "KAlarm version 0.5.7 (" << (ver < 0 ? "" : "non-") << "UTC)";
103  } else {
104  qCDebug(KALARMCAL_LOG) << "KAlarm version" << version;
105  }
106 
107  // Convert events to current KAlarm format for when/if the calendar is saved.
108  KAEvent::convertKCalEvents(fileStorage->calendar(), ver);
109  // Set the new calendar version.
110  setKAlarmVersion(fileStorage->calendar());
111  return version;
112 }
113 
114 } // namespace KACalendar
115 
116 /******************************************************************************
117 * Return the KAlarm version which wrote the calendar which has been loaded.
118 * The format is, for example, 000507 for 0.5.7.
119 * Reply = CurrentFormat if the calendar was created by the current version of KAlarm
120 * = IncompatibleFormat if it was created by KAlarm pre-0.3.5, or another program
121 * = version number if created by another KAlarm version.
122 */
123 int Private::readKAlarmVersion(const FileStorage::Ptr &fileStorage, QString &subVersion, QString &versionString)
124 {
125  subVersion.clear();
126  Calendar::Ptr calendar = fileStorage->calendar();
127  versionString = calendar->customProperty(KACalendar::APPNAME, VERSION_PROPERTY);
128  qCDebug(KALARMCAL_LOG) << "File=" << fileStorage->fileName() << ", version=" << versionString;
129 
130  if (versionString.isEmpty()) {
131  // Pre-KAlarm 1.4 defined the KAlarm version number in the PRODID field.
132  // If another application has written to the file, this may not be present.
133  const QString prodid = calendar->productId();
134  if (prodid.isEmpty()) {
135  // Check whether the calendar file is empty, in which case
136  // it can be written to freely.
137  const QFileInfo fi(fileStorage->fileName());
138  if (!fi.size()) {
139  return KACalendar::CurrentFormat;
140  }
141  }
142 
143  // Find the KAlarm identifier
144  QString progname = QStringLiteral(" KAlarm ");
145  int i = prodid.indexOf(progname, 0, Qt::CaseInsensitive);
146  if (i < 0) {
147  // Older versions used KAlarm's translated name in the product ID, which
148  // could have created problems using a calendar in different locales.
149  progname = QLatin1String(" ") + i18n("KAlarm") + QLatin1Char(' ');
150  i = prodid.indexOf(progname, 0, Qt::CaseInsensitive);
151  if (i < 0) {
152  return KACalendar::IncompatibleFormat; // calendar wasn't created by KAlarm
153  }
154  }
155 
156  // Extract the KAlarm version string
157  versionString = prodid.mid(i + progname.length()).trimmed();
158  i = versionString.indexOf(QLatin1Char('/'));
159  const int j = versionString.indexOf(QLatin1Char(' '));
160  if (j >= 0 && j < i) {
161  i = j;
162  }
163  if (i <= 0) {
164  return KACalendar::IncompatibleFormat; // missing version string
165  }
166  versionString.truncate(i); // 'versionString' now contains the KAlarm version string
167  }
168  if (versionString == QLatin1String(KAEvent::currentCalendarVersionString())) {
169  return KACalendar::CurrentFormat; // the calendar is in the current KAlarm format
170  }
171  const int ver = KAlarmCal::getVersionNumber(versionString, &subVersion);
172  if (ver == KAEvent::currentCalendarVersion()) {
173  return KACalendar::CurrentFormat; // the calendar is in the current KAlarm format
174  }
175  return KAlarmCal::getVersionNumber(versionString, &subVersion);
176 }
177 
178 /******************************************************************************
179 * Check whether the calendar file has its times stored as UTC times,
180 * indicating that it was written by the KDE 3.0.0 version of KAlarm 0.5.7.
181 * Reply = true if times are stored in UTC
182 * = false if the calendar is a vCalendar, times are not UTC, or any error occurred.
183 */
184 bool isUTC(const QString &localFile)
185 {
186  // Read the calendar file into a string
187  QFile file(localFile);
188  if (!file.open(QIODevice::ReadOnly)) {
189  return false;
190  }
191  QTextStream ts(&file);
192  ts.setCodec("ISO 8859-1");
193  const QByteArray text = ts.readAll().toLocal8Bit();
194  file.close();
195 
196  // Extract the CREATED property for the first VEVENT from the calendar
197  const QByteArray BEGIN_VCALENDAR("BEGIN:VCALENDAR");
198  const QByteArray BEGIN_VEVENT("BEGIN:VEVENT");
199  const QByteArray CREATED("CREATED:");
200  const QList<QByteArray> lines = text.split('\n');
201  for (int i = 0, end = lines.count(); i < end; ++i) {
202  if (lines[i].startsWith(BEGIN_VCALENDAR)) {
203  while (++i < end) {
204  if (lines[i].startsWith(BEGIN_VEVENT)) {
205  while (++i < end) {
206  if (lines[i].startsWith(CREATED)) {
207  return lines[i].endsWith('Z');
208  }
209  }
210  }
211  }
212  break;
213  }
214  }
215  return false;
216 }
217 
218 //=============================================================================
219 
220 namespace CalEvent
221 {
222 
223 // Struct to contain static strings, to allow use of Q_GLOBAL_STATIC
224 // to delete them on program termination.
225 struct StaticStrings {
226  StaticStrings()
227  : STATUS_PROPERTY("TYPE"),
228  ACTIVE_STATUS(QStringLiteral("ACTIVE")),
229  TEMPLATE_STATUS(QStringLiteral("TEMPLATE")),
230  ARCHIVED_STATUS(QStringLiteral("ARCHIVED")),
231  DISPLAYING_STATUS(QStringLiteral("DISPLAYING")),
232  ARCHIVED_UID(QStringLiteral("exp-")),
233  DISPLAYING_UID(QStringLiteral("disp-")),
234  OLD_ARCHIVED_UID(QStringLiteral("-exp-")),
235  OLD_TEMPLATE_UID(QStringLiteral("-tmpl-"))
236  {}
237  // Event custom properties.
238  // Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file.
239  const QByteArray STATUS_PROPERTY; // X-KDE-KALARM-TYPE property
240  const QString ACTIVE_STATUS;
241  const QString TEMPLATE_STATUS;
242  const QString ARCHIVED_STATUS;
243  const QString DISPLAYING_STATUS;
244 
245  // Event ID identifiers
246  const QString ARCHIVED_UID;
247  const QString DISPLAYING_UID;
248 
249  // Old KAlarm format identifiers
250  const QString OLD_ARCHIVED_UID;
251  const QString OLD_TEMPLATE_UID;
252 };
253 Q_GLOBAL_STATIC(StaticStrings, staticStrings)
254 
255 /******************************************************************************
256 * Convert a unique ID to indicate that the event is in a specified calendar file.
257 * This is done by prefixing archived or displaying alarms with "exp-" or "disp-",
258 * while active alarms have no prefix.
259 * Note that previously, "-exp-" was inserted in the middle of the UID.
260 */
261 QString uid(const QString &id, Type status)
262 {
263  QString result = id;
264  Type oldType;
265  int i;
266  int len;
267  if (result.startsWith(staticStrings->ARCHIVED_UID)) {
268  oldType = ARCHIVED;
269  len = staticStrings->ARCHIVED_UID.length();
270  } else if (result.startsWith(staticStrings->DISPLAYING_UID)) {
271  oldType = DISPLAYING;
272  len = staticStrings->DISPLAYING_UID.length();
273  } else {
274  if ((i = result.indexOf(staticStrings->OLD_ARCHIVED_UID)) > 0) {
275  result.remove(i, staticStrings->OLD_ARCHIVED_UID.length());
276  }
277  oldType = ACTIVE;
278  len = 0;
279  }
280  if (status != oldType) {
281  QString part;
282  switch (status) {
283  case ARCHIVED: part = staticStrings->ARCHIVED_UID; break;
284  case DISPLAYING: part = staticStrings->DISPLAYING_UID; break;
285  case ACTIVE:
286  break;
287  case TEMPLATE:
288  case EMPTY:
289  default:
290  return result;
291  }
292  result.replace(0, len, part);
293  }
294  return result;
295 }
296 
297 /******************************************************************************
298 * Check an event to determine its type - active, archived, template or empty.
299 * The default type is active if it contains alarms and there is nothing to
300 * indicate otherwise.
301 * Note that the mere fact that all an event's alarms have passed does not make
302 * an event archived, since it may be that they have not yet been able to be
303 * triggered. They will be archived once KAlarm tries to handle them.
304 * Do not call this function for the displaying alarm calendar.
305 */
306 Type status(const Event::Ptr &event, QString *param)
307 {
308  // Set up a static quick lookup for type strings
309  typedef QMap<QString, Type> PropertyMap;
310  static PropertyMap properties;
311  if (properties.isEmpty()) {
312  properties[staticStrings->ACTIVE_STATUS] = ACTIVE;
313  properties[staticStrings->TEMPLATE_STATUS] = TEMPLATE;
314  properties[staticStrings->ARCHIVED_STATUS] = ARCHIVED;
315  properties[staticStrings->DISPLAYING_STATUS] = DISPLAYING;
316  }
317 
318  if (param) {
319  param->clear();
320  }
321  if (!event) {
322  return EMPTY;
323  }
324  const Alarm::List alarms = event->alarms();
325  if (alarms.isEmpty()) {
326  return EMPTY;
327  }
328 
329  const QString property = event->customProperty(KACalendar::APPNAME, staticStrings->STATUS_PROPERTY);
330  if (!property.isEmpty()) {
331  // There's a X-KDE-KALARM-TYPE property.
332  // It consists of the event type, plus an optional parameter.
333  PropertyMap::ConstIterator it = properties.constFind(property);
334  if (it != properties.constEnd()) {
335  return it.value();
336  }
337  const int i = property.indexOf(QLatin1Char(';'));
338  if (i < 0) {
339  return EMPTY;
340  }
341  it = properties.constFind(property.left(i));
342  if (it == properties.constEnd()) {
343  return EMPTY;
344  }
345  if (param) {
346  *param = property.mid(i + 1);
347  }
348  return it.value();
349  }
350 
351  // The event either wasn't written by KAlarm, or was written by a pre-2.0 version.
352  // Check first for an old KAlarm format, which indicated the event type in the
353  // middle of its UID.
354  const QString uid = event->uid();
355  if (uid.indexOf(staticStrings->OLD_ARCHIVED_UID) > 0) {
356  return ARCHIVED;
357  }
358  if (uid.indexOf(staticStrings->OLD_TEMPLATE_UID) > 0) {
359  return TEMPLATE;
360  }
361 
362  // Otherwise, assume it's an active alarm
363  return ACTIVE;
364 }
365 
366 /******************************************************************************
367 * Set the event's type - active, archived, template, etc.
368 * If a parameter is supplied, it will be appended as a second parameter to the
369 * custom property.
370 */
371 void setStatus(const Event::Ptr &event, Type status, const QString &param)
372 {
373  if (!event) {
374  return;
375  }
376  QString text;
377  switch (status) {
378  case ACTIVE: text = staticStrings->ACTIVE_STATUS; break;
379  case TEMPLATE: text = staticStrings->TEMPLATE_STATUS; break;
380  case ARCHIVED: text = staticStrings->ARCHIVED_STATUS; break;
381  case DISPLAYING: text = staticStrings->DISPLAYING_STATUS; break;
382  default:
383  event->removeCustomProperty(KACalendar::APPNAME, staticStrings->STATUS_PROPERTY);
384  return;
385  }
386  if (!param.isEmpty()) {
387  text += QLatin1Char(';') + param;
388  }
389  event->setCustomProperty(KACalendar::APPNAME, staticStrings->STATUS_PROPERTY, text);
390 }
391 
392 Type type(const QString &mimeType)
393 {
394  if (mimeType == MIME_ACTIVE) {
395  return ACTIVE;
396  }
397  if (mimeType == MIME_ARCHIVED) {
398  return ARCHIVED;
399  }
400  if (mimeType == MIME_TEMPLATE) {
401  return TEMPLATE;
402  }
403  return EMPTY;
404 }
405 
406 Types types(const QStringList &mimeTypes)
407 {
408  Types types = {};
409  for (const QString &type : mimeTypes) {
410  if (type == MIME_ACTIVE) {
411  types |= ACTIVE;
412  } else if (type == MIME_ARCHIVED) {
413  types |= ARCHIVED;
414  } else if (type == MIME_TEMPLATE) {
415  types |= TEMPLATE;
416  }
417  }
418  return types;
419 }
420 
421 QString mimeType(Type type)
422 {
423  switch (type) {
424  case ACTIVE: return MIME_ACTIVE;
425  case ARCHIVED: return MIME_ARCHIVED;
426  case TEMPLATE: return MIME_TEMPLATE;
427  default: return QString();
428  }
429 }
430 
431 QStringList mimeTypes(Types types)
432 {
433  QStringList mimes;
434  for (int i = 1; types; i <<= 1) {
435  if (types & i) {
436  mimes += mimeType(Type(i));
437  types &= ~i;
438  }
439  }
440  return mimes;
441 }
442 
443 } // namespace CalEvent
444 
445 } // namespace KAlarmCal
446 
448 {
449  const char* str;
450  switch (type) {
451  case KAlarmCal::CalEvent::ACTIVE: str = "Active alarms"; break;
452  case KAlarmCal::CalEvent::ARCHIVED: str = "Archived alarms"; break;
453  case KAlarmCal::CalEvent::TEMPLATE: str = "Alarm templates"; break;
454  default: return debug;
455  }
456  debug << str;
457  return debug;
458 }
459 
460 // vim: et sw=4:
void setCodec(QTextCodec *codec)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
void truncate(int position)
the event is an alarm template
Definition: kacalendar.h:116
QList< QByteArray > split(char sep) const const
void setKAlarmVersion(const Calendar::Ptr &calendar)
Set the KAlarm version custom property for a calendar.
Definition: kacalendar.cpp:70
bool isEmpty() const const
bool endsWith(const T &value) const const
not written by KAlarm, or a newer KAlarm version
Definition: kacalendar.h:67
QString & remove(int position, int n)
void clear()
QByteArray icalProductId()
Return the product ID string for use in calendars.
Definition: kacalendar.cpp:62
Type
The category of an event, indicated by the middle part of its UID.
Definition: kacalendar.h:112
const QByteArray APPNAME("KALARM")
The application name ("KALARM") used in calendar properties.
Definition: kacalendar.h:95
int count(const T &value) const const
the event is currently being displayed
Definition: kacalendar.h:117
the event is currently active
Definition: kacalendar.h:114
CaseInsensitive
int updateVersion(const FileStorage::Ptr &fileStorage, QString &versionString)
Check the version of KAlarm which wrote a calendar file, and convert it in memory to the current KAla...
Definition: kacalendar.cpp:81
bool isEmpty() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
virtual bool open(QIODevice::OpenMode mode) override
QByteArray toLocal8Bit() const const
QString i18n(const char *text, const TYPE &arg...)
QString & replace(int position, int n, QChar after)
QDataStream & operator<<(QDataStream &out, const KDateTime::Spec &spec)
QString mid(int position, int n) const const
the event has no alarms
Definition: kacalendar.h:113
bool isEmpty() const const
virtual void close() override
int length() const const
QString fromLatin1(const char *str, int size)
current KAlarm format
Definition: kacalendar.h:65
the event is archived
Definition: kacalendar.h:115
QString readAll()
void setProductId(const QByteArray &progName, const QByteArray &progVersion)
Set the program name and version for use in calendars.
Definition: kacalendar.cpp:57
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Oct 24 2021 23:11:10 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.