1 /*
2     This file is part of the kolab resource - the implementation of the
3     Kolab storage format. See www.kolab.org for documentation on this.
4 
5     SPDX-FileCopyrightText: 2004 Bo Thorsen <bo@sonofthor.dk>
6 
7     SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "kolabbase.h"
11 #include "pimkolab_debug.h"
12 
13 #include <KCalendarCore/Journal>
14 #include <KContacts/Addressee>
15 #include <KContacts/ContactGroup>
16 
17 using namespace KolabV2;
18 
KolabBase(const QString & tz)19 KolabBase::KolabBase(const QString &tz)
20     : mCreationDate(QDateTime::currentDateTime())
21     , mLastModified(QDateTime::currentDateTimeUtc())
22     , mSensitivity(Public)
23     , mHasPilotSyncId(false)
24     , mHasPilotSyncStatus(false)
25 {
26     // unlike the previously used KTimeZone here, QTimeZone defaults to local time zone if tz.isEmpty()
27     // we therefore force it to invalid, which however is unsafe to use (unlike KTimeZone)
28     // therefore all usage of mTimeZone must be preceded by a validity check
29     mTimeZone = tz.isEmpty() ? QTimeZone() : QTimeZone(tz.toUtf8());
30 }
31 
~KolabBase()32 KolabBase::~KolabBase()
33 {
34 }
35 
setFields(const KCalendarCore::Incidence::Ptr & incidence)36 void KolabBase::setFields(const KCalendarCore::Incidence::Ptr &incidence)
37 {
38     // So far unhandled KCalendarCore::IncidenceBase fields:
39     // mPilotID, mSyncStatus, mFloats
40 
41     setUid(incidence->uid());
42     setBody(incidence->description());
43     setCategories(incidence->categoriesStr());
44     setCreationDate(localToUTC(incidence->created()));
45     setLastModified(incidence->lastModified());
46     setSensitivity(static_cast<Sensitivity>(incidence->secrecy()));
47     // TODO: Attachments
48 }
49 
saveTo(const KCalendarCore::Incidence::Ptr & incidence) const50 void KolabBase::saveTo(const KCalendarCore::Incidence::Ptr &incidence) const
51 {
52     incidence->setUid(uid());
53     incidence->setDescription(body());
54     incidence->setCategories(categories());
55     incidence->setCreated(utcToLocal(creationDate()));
56     incidence->setLastModified(lastModified());
57     switch (sensitivity()) {
58     case 1:
59         incidence->setSecrecy(KCalendarCore::Incidence::SecrecyPrivate);
60         break;
61     case 2:
62         incidence->setSecrecy(KCalendarCore::Incidence::SecrecyConfidential);
63         break;
64     default:
65         incidence->setSecrecy(KCalendarCore::Incidence::SecrecyPublic);
66         break;
67     }
68 
69     // TODO: Attachments
70 }
71 
setFields(const KContacts::Addressee * addressee)72 void KolabBase::setFields(const KContacts::Addressee *addressee)
73 {
74     // An addressee does not have a creation date, so somehow we should
75     // make one, if this is a new entry
76 
77     setUid(addressee->uid());
78     setBody(addressee->note());
79     setCategories(addressee->categories().join(QLatin1Char(',')));
80 
81     // Set creation-time and last-modification-time
82     const QString creationString = addressee->custom(QStringLiteral("KOLAB"), QStringLiteral("CreationDate"));
83     qCDebug(PIMKOLAB_LOG) << "Creation time string:" << creationString;
84     QDateTime creationDate;
85     if (creationString.isEmpty() && mTimeZone.isValid()) {
86         creationDate = QDateTime::currentDateTime().toTimeZone(mTimeZone);
87         qCDebug(PIMKOLAB_LOG) << "Creation date set to current time" << mTimeZone;
88     } else {
89         creationDate = stringToDateTime(creationString);
90         qCDebug(PIMKOLAB_LOG) << "Creation date loaded";
91     }
92     QDateTime modified;
93     if (mTimeZone.isValid()) {
94         modified = addressee->revision().toTimeZone(mTimeZone);
95     }
96     if (!modified.isValid()) {
97         modified = QDateTime::currentDateTimeUtc();
98     }
99     setLastModified(modified);
100     if (modified < creationDate) {
101         // It's not possible that the modification date is earlier than creation
102         creationDate = modified;
103         qCDebug(PIMKOLAB_LOG) << "Creation date set to modification date";
104     }
105     setCreationDate(creationDate);
106     const QString newCreationDate = dateTimeToString(creationDate);
107     if (creationString != newCreationDate) {
108         // We modified the creation date, so store it for future reference
109         const_cast<KContacts::Addressee *>(addressee)->insertCustom(QStringLiteral("KOLAB"), QStringLiteral("CreationDate"), newCreationDate);
110         qCDebug(PIMKOLAB_LOG) << "Creation date modified. New one:" << newCreationDate;
111     }
112 
113     switch (addressee->secrecy().type()) {
114     case KContacts::Secrecy::Private:
115         setSensitivity(Private);
116         break;
117     case KContacts::Secrecy::Confidential:
118         setSensitivity(Confidential);
119         break;
120     default:
121         setSensitivity(Public);
122     }
123 
124     // TODO: Attachments
125 }
126 
saveTo(KContacts::Addressee * addressee) const127 void KolabBase::saveTo(KContacts::Addressee *addressee) const
128 {
129     addressee->setUid(uid());
130     addressee->setNote(body());
131     addressee->setCategories(categories().split(QLatin1Char(','), Qt::SkipEmptyParts));
132     if (mTimeZone.isValid()) {
133         addressee->setRevision(lastModified().toTimeZone(mTimeZone));
134     }
135     addressee->insertCustom(QStringLiteral("KOLAB"), QStringLiteral("CreationDate"), dateTimeToString(creationDate()));
136 
137     switch (sensitivity()) {
138     case Private:
139         addressee->setSecrecy(KContacts::Secrecy(KContacts::Secrecy::Private));
140         break;
141     case Confidential:
142         addressee->setSecrecy(KContacts::Secrecy(KContacts::Secrecy::Confidential));
143         break;
144     default:
145         addressee->setSecrecy(KContacts::Secrecy(KContacts::Secrecy::Public));
146         break;
147     }
148     // TODO: Attachments
149 }
150 
setFields(const KContacts::ContactGroup * contactGroup)151 void KolabBase::setFields(const KContacts::ContactGroup *contactGroup)
152 {
153     // A contactgroup does not have a creation date, so somehow we should
154     // make one, if this is a new entry
155 
156     setUid(contactGroup->id());
157 
158     // Set creation-time and last-modification-time
159     QDateTime creationDate;
160     if (mTimeZone.isValid()) {
161         creationDate = QDateTime::currentDateTime().toTimeZone(mTimeZone);
162     }
163     qCDebug(PIMKOLAB_LOG) << "Creation date set to current time";
164 
165     QDateTime modified = QDateTime::currentDateTimeUtc();
166     setLastModified(modified);
167     if (modified < creationDate) {
168         // It's not possible that the modification date is earlier than creation
169         creationDate = modified;
170         qCDebug(PIMKOLAB_LOG) << "Creation date set to modification date";
171     }
172     setCreationDate(creationDate);
173 }
174 
saveTo(KContacts::ContactGroup * contactGroup) const175 void KolabBase::saveTo(KContacts::ContactGroup *contactGroup) const
176 {
177     contactGroup->setId(uid());
178 }
179 
setUid(const QString & uid)180 void KolabBase::setUid(const QString &uid)
181 {
182     mUid = uid;
183 }
184 
uid() const185 QString KolabBase::uid() const
186 {
187     return mUid;
188 }
189 
setBody(const QString & body)190 void KolabBase::setBody(const QString &body)
191 {
192     mBody = body;
193 }
194 
body() const195 QString KolabBase::body() const
196 {
197     return mBody;
198 }
199 
setCategories(const QString & categories)200 void KolabBase::setCategories(const QString &categories)
201 {
202     mCategories = categories;
203 }
204 
categories() const205 QString KolabBase::categories() const
206 {
207     return mCategories;
208 }
209 
setCreationDate(const QDateTime & date)210 void KolabBase::setCreationDate(const QDateTime &date)
211 {
212     mCreationDate = date;
213 }
214 
creationDate() const215 QDateTime KolabBase::creationDate() const
216 {
217     return mCreationDate;
218 }
219 
setLastModified(const QDateTime & date)220 void KolabBase::setLastModified(const QDateTime &date)
221 {
222     mLastModified = date;
223 }
224 
lastModified() const225 QDateTime KolabBase::lastModified() const
226 {
227     return mLastModified;
228 }
229 
setSensitivity(Sensitivity sensitivity)230 void KolabBase::setSensitivity(Sensitivity sensitivity)
231 {
232     mSensitivity = sensitivity;
233 }
234 
sensitivity() const235 KolabBase::Sensitivity KolabBase::sensitivity() const
236 {
237     return mSensitivity;
238 }
239 
setPilotSyncId(unsigned long id)240 void KolabBase::setPilotSyncId(unsigned long id)
241 {
242     mHasPilotSyncId = true;
243     mPilotSyncId = id;
244 }
245 
hasPilotSyncId() const246 bool KolabBase::hasPilotSyncId() const
247 {
248     return mHasPilotSyncId;
249 }
250 
pilotSyncId() const251 unsigned long KolabBase::pilotSyncId() const
252 {
253     return mPilotSyncId;
254 }
255 
setPilotSyncStatus(int status)256 void KolabBase::setPilotSyncStatus(int status)
257 {
258     mHasPilotSyncStatus = true;
259     mPilotSyncStatus = status;
260 }
261 
hasPilotSyncStatus() const262 bool KolabBase::hasPilotSyncStatus() const
263 {
264     return mHasPilotSyncStatus;
265 }
266 
pilotSyncStatus() const267 int KolabBase::pilotSyncStatus() const
268 {
269     return mPilotSyncStatus;
270 }
271 
loadEmailAttribute(QDomElement & element,Email & email)272 bool KolabBase::loadEmailAttribute(QDomElement &element, Email &email)
273 {
274     for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
275         if (n.isComment()) {
276             continue;
277         }
278         if (n.isElement()) {
279             QDomElement e = n.toElement();
280             const QString tagName = e.tagName();
281 
282             if (tagName == QLatin1String("display-name")) {
283                 email.displayName = e.text();
284             } else if (tagName == QLatin1String("smtp-address")) {
285                 email.smtpAddress = e.text();
286             } else {
287                 // TODO: Unhandled tag - save for later storage
288                 qCDebug(PIMKOLAB_LOG) << "Warning: Unhandled tag" << e.tagName();
289             }
290         } else {
291             qCDebug(PIMKOLAB_LOG) << "Node is not a comment or an element???";
292         }
293     }
294 
295     return true;
296 }
297 
saveEmailAttribute(QDomElement & element,const Email & email,const QString & tagName) const298 void KolabBase::saveEmailAttribute(QDomElement &element, const Email &email, const QString &tagName) const
299 {
300     QDomElement e = element.ownerDocument().createElement(tagName);
301     element.appendChild(e);
302     writeString(e, QStringLiteral("display-name"), email.displayName);
303     writeString(e, QStringLiteral("smtp-address"), email.smtpAddress);
304 }
305 
loadAttribute(QDomElement & element)306 bool KolabBase::loadAttribute(QDomElement &element)
307 {
308     const QString tagName = element.tagName();
309     switch (tagName[0].toLatin1()) {
310     case 'u':
311         if (tagName == QLatin1String("uid")) {
312             setUid(element.text());
313             return true;
314         }
315         break;
316     case 'b':
317         if (tagName == QLatin1String("body")) {
318             setBody(element.text());
319             return true;
320         }
321         break;
322     case 'c':
323         if (tagName == QLatin1String("categories")) {
324             setCategories(element.text());
325             return true;
326         }
327         if (tagName == QLatin1String("creation-date")) {
328             setCreationDate(stringToDateTime(element.text()));
329             return true;
330         }
331         break;
332     case 'l':
333         if (tagName == QLatin1String("last-modification-date")) {
334             setLastModified(stringToDateTime(element.text()));
335             return true;
336         }
337         break;
338     case 's':
339         if (tagName == QLatin1String("sensitivity")) {
340             setSensitivity(stringToSensitivity(element.text()));
341             return true;
342         }
343         break;
344     case 'p':
345         if (tagName == QLatin1String("product-id")) {
346             return true; // ignore this field
347         }
348         if (tagName == QLatin1String("pilot-sync-id")) {
349             setPilotSyncId(element.text().toULong());
350             return true;
351         }
352         if (tagName == QLatin1String("pilot-sync-status")) {
353             setPilotSyncStatus(element.text().toInt());
354             return true;
355         }
356         break;
357     default:
358         break;
359     }
360     return false;
361 }
362 
saveAttributes(QDomElement & element) const363 bool KolabBase::saveAttributes(QDomElement &element) const
364 {
365     writeString(element, QStringLiteral("product-id"), productID());
366     writeString(element, QStringLiteral("uid"), uid());
367     writeString(element, QStringLiteral("body"), body());
368     writeString(element, QStringLiteral("categories"), categories());
369     writeString(element, QStringLiteral("creation-date"), dateTimeToString(creationDate().toUTC()));
370     writeString(element, QStringLiteral("last-modification-date"), dateTimeToString(lastModified().toUTC()));
371     writeString(element, QStringLiteral("sensitivity"), sensitivityToString(sensitivity()));
372     if (hasPilotSyncId()) {
373         writeString(element, QStringLiteral("pilot-sync-id"), QString::number(pilotSyncId()));
374     }
375     if (hasPilotSyncStatus()) {
376         writeString(element, QStringLiteral("pilot-sync-status"), QString::number(pilotSyncStatus()));
377     }
378     return true;
379 }
380 
load(const QString & xml)381 bool KolabBase::load(const QString &xml)
382 {
383     const QDomDocument document = loadDocument(xml);
384     if (document.isNull()) {
385         return false;
386     }
387     // XML file loaded into tree. Now parse it
388     return loadXML(document);
389 }
390 
loadDocument(const QString & xmlData)391 QDomDocument KolabBase::loadDocument(const QString &xmlData)
392 {
393     QString errorMsg;
394     int errorLine, errorColumn;
395     QDomDocument document;
396     bool ok = document.setContent(xmlData, &errorMsg, &errorLine, &errorColumn);
397 
398     if (!ok) {
399         qWarning("Error loading document: %s, line %d, column %d", qPrintable(errorMsg), errorLine, errorColumn);
400         return QDomDocument();
401     }
402 
403     return document;
404 }
405 
domTree()406 QDomDocument KolabBase::domTree()
407 {
408     QDomDocument document;
409 
410     const QString p = QStringLiteral("version=\"1.0\" encoding=\"UTF-8\"");
411     document.appendChild(document.createProcessingInstruction(QStringLiteral("xml"), p));
412 
413     return document;
414 }
415 
dateTimeToString(const QDateTime & time)416 QString KolabBase::dateTimeToString(const QDateTime &time)
417 {
418     return time.toString(Qt::ISODate);
419 }
420 
dateToString(QDate date)421 QString KolabBase::dateToString(QDate date)
422 {
423     return date.toString(Qt::ISODate);
424 }
425 
stringToDateTime(const QString & time)426 QDateTime KolabBase::stringToDateTime(const QString &time)
427 {
428     return QDateTime::fromString(time, Qt::ISODate);
429 }
430 
stringToDate(const QString & date)431 QDate KolabBase::stringToDate(const QString &date)
432 {
433     return QDate::fromString(date, Qt::ISODate);
434 }
435 
sensitivityToString(Sensitivity s)436 QString KolabBase::sensitivityToString(Sensitivity s)
437 {
438     switch (s) {
439     case Private:
440         return QStringLiteral("private");
441     case Confidential:
442         return QStringLiteral("confidential");
443     case Public:
444         return QStringLiteral("public");
445     }
446 
447     return QStringLiteral("What what what???");
448 }
449 
stringToSensitivity(const QString & s)450 KolabBase::Sensitivity KolabBase::stringToSensitivity(const QString &s)
451 {
452     if (s == QLatin1String("private")) {
453         return Private;
454     }
455     if (s == QLatin1String("confidential")) {
456         return Confidential;
457     }
458     return Public;
459 }
460 
colorToString(const QColor & color)461 QString KolabBase::colorToString(const QColor &color)
462 {
463     // Color is in the format "#RRGGBB"
464     return color.name();
465 }
466 
stringToColor(const QString & s)467 QColor KolabBase::stringToColor(const QString &s)
468 {
469     return QColor(s);
470 }
471 
writeString(QDomElement & element,const QString & tag,const QString & tagString)472 void KolabBase::writeString(QDomElement &element, const QString &tag, const QString &tagString)
473 {
474     if (!tagString.isEmpty()) {
475         QDomElement e = element.ownerDocument().createElement(tag);
476         QDomText t = element.ownerDocument().createTextNode(tagString);
477         e.appendChild(t);
478         element.appendChild(e);
479     }
480 }
481 
localToUTC(const QDateTime & time) const482 QDateTime KolabBase::localToUTC(const QDateTime &time) const
483 {
484     return time.toUTC();
485 }
486 
utcToLocal(const QDateTime & time) const487 QDateTime KolabBase::utcToLocal(const QDateTime &time) const
488 {
489     QDateTime dt = time;
490     dt.setTimeSpec(Qt::UTC);
491     return dt;
492 }
493