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/Event>
16 #include <KCalendarCore/Alarm>
17 #include <KCalendarCore/MemoryCalendar>
18 
19 #include <klocalizedstring.h>
20 #include "kalarmcal_debug.h"
21 
22 #include <QMap>
23 #include <QFile>
24 #include <QFileInfo>
25 #include <QTextStream>
26 
27 using namespace KCalendarCore;
28 
29 namespace KAlarmCal
30 {
31 
32 const QLatin1String MIME_BASE("application/x-vnd.kde.alarm");
33 const QLatin1String MIME_ACTIVE("application/x-vnd.kde.alarm.active");
34 const QLatin1String MIME_ARCHIVED("application/x-vnd.kde.alarm.archived");
35 const QLatin1String MIME_TEMPLATE("application/x-vnd.kde.alarm.template");
36 
37 static const QByteArray VERSION_PROPERTY("VERSION"); // X-KDE-KALARM-VERSION VCALENDAR property
38 
39 static bool isUTC(const QString &localFile);
40 
41 class Private
42 {
43 public:
44  static int readKAlarmVersion(const FileStorage::Ptr &, QString &subVersion, QString &versionString);
45 
46  static QByteArray mIcalProductId;
47 };
48 
49 QByteArray Private::mIcalProductId;
50 
51 //=============================================================================
52 
53 namespace KACalendar
54 {
55 
56 const QByteArray APPNAME("KALARM");
57 
58 void setProductId(const QByteArray &progName, const QByteArray &progVersion)
59 {
60  Private::mIcalProductId = QByteArray("-//K Desktop Environment//NONSGML " + progName + " " + progVersion + "//EN");
61 }
62 
64 {
65  return Private::mIcalProductId.isEmpty() ? QByteArray("-//K Desktop Environment//NONSGML //EN") : Private::mIcalProductId;
66 }
67 
68 /******************************************************************************
69 * Set the X-KDE-KALARM-VERSION property in a calendar.
70 */
71 void setKAlarmVersion(const Calendar::Ptr &calendar)
72 {
73  calendar->setCustomProperty(APPNAME, VERSION_PROPERTY, QString::fromLatin1(KAEvent::currentCalendarVersionString()));
74 }
75 
76 /******************************************************************************
77 * Check the version of KAlarm which wrote a calendar file, and convert it in
78 * memory to the current KAlarm format if possible. The storage file is not
79 * updated. The compatibility of the calendar format is indicated by the return
80 * value.
81 */
82 int updateVersion(const FileStorage::Ptr &fileStorage, QString &versionString)
83 {
84  QString subVersion;
85  const int version = Private::readKAlarmVersion(fileStorage, subVersion, versionString);
86  if (version == CurrentFormat) {
87  return CurrentFormat; // calendar is in the current KAlarm format
88  }
89  if (version == IncompatibleFormat || version > KAEvent::currentCalendarVersion()) {
90  return IncompatibleFormat; // calendar was created by another program, or an unknown version of KAlarm
91  }
92 
93  // Calendar was created by an earlier version of KAlarm.
94  // Convert it to the current format.
95  const QString localFile = fileStorage->fileName();
96  int ver = version;
97  if (version == KAlarmCal::Version(0, 5, 7) && !localFile.isEmpty()) {
98  // KAlarm version 0.5.7 - check whether times are stored in UTC, in which
99  // case it is the KDE 3.0.0 version, which needs adjustment of summer times.
100  if (isUTC(localFile)) {
101  ver = -version;
102  }
103  qCDebug(KALARMCAL_LOG) << "KAlarm version 0.5.7 (" << (ver < 0 ? "" : "non-") << "UTC)";
104  } else {
105  qCDebug(KALARMCAL_LOG) << "KAlarm version" << version;
106  }
107 
108  // Convert events to current KAlarm format for when/if the calendar is saved.
109  KAEvent::convertKCalEvents(fileStorage->calendar(), ver);
110  // Set the new calendar version.
111  setKAlarmVersion(fileStorage->calendar());
112  return version;
113 }
114 
115 } // namespace KACalendar
116 
117 /******************************************************************************
118 * Return the KAlarm version which wrote the calendar which has been loaded.
119 * The format is, for example, 000507 for 0.5.7.
120 * Reply = CurrentFormat if the calendar was created by the current version of KAlarm
121 * = IncompatibleFormat if it was created by KAlarm pre-0.3.5, or another program
122 * = version number if created by another KAlarm version.
123 */
124 int Private::readKAlarmVersion(const FileStorage::Ptr &fileStorage, QString &subVersion, QString &versionString)
125 {
126  subVersion.clear();
127  Calendar::Ptr calendar = fileStorage->calendar();
128  versionString = calendar->customProperty(KACalendar::APPNAME, VERSION_PROPERTY);
129  qCDebug(KALARMCAL_LOG) << "File=" << fileStorage->fileName() << ", version=" << versionString;
130 
131  if (versionString.isEmpty()) {
132  // Pre-KAlarm 1.4 defined the KAlarm version number in the PRODID field.
133  // If another application has written to the file, this may not be present.
134  const QString prodid = calendar->productId();
135  if (prodid.isEmpty()) {
136  // Check whether the calendar file is empty, in which case
137  // it can be written to freely.
138  const QFileInfo fi(fileStorage->fileName());
139  if (!fi.size()) {
140  return KACalendar::CurrentFormat;
141  }
142  }
143 
144  // Find the KAlarm identifier
145  QString progname = QStringLiteral(" KAlarm ");
146  int i = prodid.indexOf(progname, 0, Qt::CaseInsensitive);
147  if (i < 0) {
148  // Older versions used KAlarm's translated name in the product ID, which
149  // could have created problems using a calendar in different locales.
150  progname = QLatin1String(" ") + i18n("KAlarm") + QLatin1Char(' ');
151  i = prodid.indexOf(progname, 0, Qt::CaseInsensitive);
152  if (i < 0) {
153  return KACalendar::IncompatibleFormat; // calendar wasn't created by KAlarm
154  }
155  }
156 
157  // Extract the KAlarm version string
158  versionString = prodid.mid(i + progname.length()).trimmed();
159  i = versionString.indexOf(QLatin1Char('/'));
160  const int j = versionString.indexOf(QLatin1Char(' '));
161  if (j >= 0 && j < i) {
162  i = j;
163  }
164  if (i <= 0) {
165  return KACalendar::IncompatibleFormat; // missing version string
166  }
167  versionString.truncate(i); // 'versionString' now contains the KAlarm version string
168  }
169  if (versionString == QLatin1String(KAEvent::currentCalendarVersionString())) {
170  return KACalendar::CurrentFormat; // the calendar is in the current KAlarm format
171  }
172  const int ver = KAlarmCal::getVersionNumber(versionString, &subVersion);
173  if (ver == KAEvent::currentCalendarVersion()) {
174  return KACalendar::CurrentFormat; // the calendar is in the current KAlarm format
175  }
176  return KAlarmCal::getVersionNumber(versionString, &subVersion);
177 }
178 
179 /******************************************************************************
180 * Check whether the calendar file has its times stored as UTC times,
181 * indicating that it was written by the KDE 3.0.0 version of KAlarm 0.5.7.
182 * Reply = true if times are stored in UTC
183 * = false if the calendar is a vCalendar, times are not UTC, or any error occurred.
184 */
185 bool isUTC(const QString &localFile)
186 {
187  // Read the calendar file into a string
188  QFile file(localFile);
189  if (!file.open(QIODevice::ReadOnly)) {
190  return false;
191  }
192  QTextStream ts(&file);
193  ts.setCodec("ISO 8859-1");
194  const QByteArray text = ts.readAll().toLocal8Bit();
195  file.close();
196 
197  // Extract the CREATED property for the first VEVENT from the calendar
198  const QByteArray BEGIN_VCALENDAR("BEGIN:VCALENDAR");
199  const QByteArray BEGIN_VEVENT("BEGIN:VEVENT");
200  const QByteArray CREATED("CREATED:");
201  const QList<QByteArray> lines = text.split('\n');
202  for (int i = 0, end = lines.count(); i < end; ++i) {
203  if (lines[i].startsWith(BEGIN_VCALENDAR)) {
204  while (++i < end) {
205  if (lines[i].startsWith(BEGIN_VEVENT)) {
206  while (++i < end) {
207  if (lines[i].startsWith(CREATED)) {
208  return lines[i].endsWith('Z');
209  }
210  }
211  }
212  }
213  break;
214  }
215  }
216  return false;
217 }
218 
219 //=============================================================================
220 
221 namespace CalEvent
222 {
223 
224 // Struct to contain static strings, to allow use of Q_GLOBAL_STATIC
225 // to delete them on program termination.
226 struct StaticStrings {
227  StaticStrings()
228  : STATUS_PROPERTY("TYPE"),
229  ACTIVE_STATUS(QStringLiteral("ACTIVE")),
230  TEMPLATE_STATUS(QStringLiteral("TEMPLATE")),
231  ARCHIVED_STATUS(QStringLiteral("ARCHIVED")),
232  DISPLAYING_STATUS(QStringLiteral("DISPLAYING")),
233  ARCHIVED_UID(QStringLiteral("exp-")),
234  DISPLAYING_UID(QStringLiteral("disp-")),
235  OLD_ARCHIVED_UID(QStringLiteral("-exp-")),
236  OLD_TEMPLATE_UID(QStringLiteral("-tmpl-"))
237  {}
238  // Event custom properties.
239  // Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file.
240  const QByteArray STATUS_PROPERTY; // X-KDE-KALARM-TYPE property
241  const QString ACTIVE_STATUS;
242  const QString TEMPLATE_STATUS;
243  const QString ARCHIVED_STATUS;
244  const QString DISPLAYING_STATUS;
245 
246  // Event ID identifiers
247  const QString ARCHIVED_UID;
248  const QString DISPLAYING_UID;
249 
250  // Old KAlarm format identifiers
251  const QString OLD_ARCHIVED_UID;
252  const QString OLD_TEMPLATE_UID;
253 };
254 Q_GLOBAL_STATIC(StaticStrings, staticStrings)
255 
256 /******************************************************************************
257 * Convert a unique ID to indicate that the event is in a specified calendar file.
258 * This is done by prefixing archived or displaying alarms with "exp-" or "disp-",
259 * while active alarms have no prefix.
260 * Note that previously, "-exp-" was inserted in the middle of the UID.
261 */
262 QString uid(const QString &id, Type status)
263 {
264  QString result = id;
265  Type oldType;
266  int i, 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:117
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:71
bool isEmpty() const const
bool endsWith(const T &value) const const
not written by KAlarm, or a newer KAlarm version
Definition: kacalendar.h:68
QString & remove(int position, int n)
void clear()
QByteArray icalProductId()
Return the product ID string for use in calendars.
Definition: kacalendar.cpp:63
Type
The category of an event, indicated by the middle part of its UID.
Definition: kacalendar.h:113
const QByteArray APPNAME("KALARM")
The application name ("KALARM") used in calendar properties.
Definition: kacalendar.h:96
int count(const T &value) const const
the event is currently being displayed
Definition: kacalendar.h:118
the event is currently active
Definition: kacalendar.h:115
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:82
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:114
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:66
the event is archived
Definition: kacalendar.h:116
QString readAll()
void setProductId(const QByteArray &progName, const QByteArray &progVersion)
Set the program name and version for use in calendars.
Definition: kacalendar.cpp:58
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Aug 13 2020 23:09:39 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.