File indexing completed on 2024-12-01 04:47:51

0001 /*
0002     This file is part of the kolab resource - the implementation of the
0003     Kolab storage format. See www.kolab.org for documentation on this.
0004 
0005     SPDX-FileCopyrightText: 2004 Bo Thorsen <bo@sonofthor.dk>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "kolabbase.h"
0011 #include "pimkolab_debug.h"
0012 
0013 #include <KContacts/Addressee>
0014 #include <KContacts/ContactGroup>
0015 
0016 using namespace KolabV2;
0017 
0018 KolabBase::KolabBase(const QString &tz)
0019     : mCreationDate(QDateTime::currentDateTime())
0020     , mLastModified(QDateTime::currentDateTimeUtc())
0021     , mSensitivity(Public)
0022 {
0023     // unlike the previously used KTimeZone here, QTimeZone defaults to local time zone if tz.isEmpty()
0024     // we therefore force it to invalid, which however is unsafe to use (unlike KTimeZone)
0025     // therefore all usage of mTimeZone must be preceded by a validity check
0026     mTimeZone = tz.isEmpty() ? QTimeZone() : QTimeZone(tz.toUtf8());
0027 }
0028 
0029 KolabBase::~KolabBase() = default;
0030 
0031 void KolabBase::setFields(const KCalendarCore::Incidence::Ptr &incidence)
0032 {
0033     // So far unhandled KCalendarCore::IncidenceBase fields:
0034     // mPilotID, mSyncStatus, mFloats
0035 
0036     setUid(incidence->uid());
0037     setBody(incidence->description());
0038     setCategories(incidence->categoriesStr());
0039     setCreationDate(localToUTC(incidence->created()));
0040     setLastModified(incidence->lastModified());
0041     setSensitivity(static_cast<Sensitivity>(incidence->secrecy()));
0042     // TODO: Attachments
0043 }
0044 
0045 void KolabBase::saveTo(const KCalendarCore::Incidence::Ptr &incidence) const
0046 {
0047     incidence->setUid(uid());
0048     incidence->setDescription(body());
0049     incidence->setCategories(categories());
0050     incidence->setCreated(utcToLocal(creationDate()));
0051     incidence->setLastModified(lastModified());
0052     switch (sensitivity()) {
0053     case 1:
0054         incidence->setSecrecy(KCalendarCore::Incidence::SecrecyPrivate);
0055         break;
0056     case 2:
0057         incidence->setSecrecy(KCalendarCore::Incidence::SecrecyConfidential);
0058         break;
0059     default:
0060         incidence->setSecrecy(KCalendarCore::Incidence::SecrecyPublic);
0061         break;
0062     }
0063 
0064     // TODO: Attachments
0065 }
0066 
0067 void KolabBase::setFields(const KContacts::Addressee *addressee)
0068 {
0069     // An addressee does not have a creation date, so somehow we should
0070     // make one, if this is a new entry
0071 
0072     setUid(addressee->uid());
0073     setBody(addressee->note());
0074     setCategories(addressee->categories().join(QLatin1Char(',')));
0075 
0076     // Set creation-time and last-modification-time
0077     const QString creationString = addressee->custom(QStringLiteral("KOLAB"), QStringLiteral("CreationDate"));
0078     qCDebug(PIMKOLAB_LOG) << "Creation time string:" << creationString;
0079     QDateTime creationDate;
0080     if (creationString.isEmpty() && mTimeZone.isValid()) {
0081         creationDate = QDateTime::currentDateTime().toTimeZone(mTimeZone);
0082         qCDebug(PIMKOLAB_LOG) << "Creation date set to current time" << mTimeZone;
0083     } else {
0084         creationDate = stringToDateTime(creationString);
0085         qCDebug(PIMKOLAB_LOG) << "Creation date loaded";
0086     }
0087     QDateTime modified;
0088     if (mTimeZone.isValid()) {
0089         modified = addressee->revision().toTimeZone(mTimeZone);
0090     }
0091     if (!modified.isValid()) {
0092         modified = QDateTime::currentDateTimeUtc();
0093     }
0094     setLastModified(modified);
0095     if (modified < creationDate) {
0096         // It's not possible that the modification date is earlier than creation
0097         creationDate = modified;
0098         qCDebug(PIMKOLAB_LOG) << "Creation date set to modification date";
0099     }
0100     setCreationDate(creationDate);
0101     const QString newCreationDate = dateTimeToString(creationDate);
0102     if (creationString != newCreationDate) {
0103         // We modified the creation date, so store it for future reference
0104         const_cast<KContacts::Addressee *>(addressee)->insertCustom(QStringLiteral("KOLAB"), QStringLiteral("CreationDate"), newCreationDate);
0105         qCDebug(PIMKOLAB_LOG) << "Creation date modified. New one:" << newCreationDate;
0106     }
0107 
0108     switch (addressee->secrecy().type()) {
0109     case KContacts::Secrecy::Private:
0110         setSensitivity(Private);
0111         break;
0112     case KContacts::Secrecy::Confidential:
0113         setSensitivity(Confidential);
0114         break;
0115     default:
0116         setSensitivity(Public);
0117     }
0118 
0119     // TODO: Attachments
0120 }
0121 
0122 void KolabBase::saveTo(KContacts::Addressee *addressee) const
0123 {
0124     addressee->setUid(uid());
0125     addressee->setNote(body());
0126     addressee->setCategories(categories().split(QLatin1Char(','), Qt::SkipEmptyParts));
0127     if (mTimeZone.isValid()) {
0128         addressee->setRevision(lastModified().toTimeZone(mTimeZone));
0129     }
0130     addressee->insertCustom(QStringLiteral("KOLAB"), QStringLiteral("CreationDate"), dateTimeToString(creationDate()));
0131 
0132     switch (sensitivity()) {
0133     case Private:
0134         addressee->setSecrecy(KContacts::Secrecy(KContacts::Secrecy::Private));
0135         break;
0136     case Confidential:
0137         addressee->setSecrecy(KContacts::Secrecy(KContacts::Secrecy::Confidential));
0138         break;
0139     default:
0140         addressee->setSecrecy(KContacts::Secrecy(KContacts::Secrecy::Public));
0141         break;
0142     }
0143     // TODO: Attachments
0144 }
0145 
0146 void KolabBase::setFields(const KContacts::ContactGroup *contactGroup)
0147 {
0148     // A contactgroup does not have a creation date, so somehow we should
0149     // make one, if this is a new entry
0150 
0151     setUid(contactGroup->id());
0152 
0153     // Set creation-time and last-modification-time
0154     QDateTime creationDate;
0155     if (mTimeZone.isValid()) {
0156         creationDate = QDateTime::currentDateTime().toTimeZone(mTimeZone);
0157     }
0158     qCDebug(PIMKOLAB_LOG) << "Creation date set to current time";
0159 
0160     QDateTime modified = QDateTime::currentDateTimeUtc();
0161     setLastModified(modified);
0162     if (modified < creationDate) {
0163         // It's not possible that the modification date is earlier than creation
0164         creationDate = modified;
0165         qCDebug(PIMKOLAB_LOG) << "Creation date set to modification date";
0166     }
0167     setCreationDate(creationDate);
0168 }
0169 
0170 void KolabBase::saveTo(KContacts::ContactGroup *contactGroup) const
0171 {
0172     contactGroup->setId(uid());
0173 }
0174 
0175 void KolabBase::setUid(const QString &uid)
0176 {
0177     mUid = uid;
0178 }
0179 
0180 QString KolabBase::uid() const
0181 {
0182     return mUid;
0183 }
0184 
0185 void KolabBase::setBody(const QString &body)
0186 {
0187     mBody = body;
0188 }
0189 
0190 QString KolabBase::body() const
0191 {
0192     return mBody;
0193 }
0194 
0195 void KolabBase::setCategories(const QString &categories)
0196 {
0197     mCategories = categories;
0198 }
0199 
0200 QString KolabBase::categories() const
0201 {
0202     return mCategories;
0203 }
0204 
0205 void KolabBase::setCreationDate(const QDateTime &date)
0206 {
0207     mCreationDate = date;
0208 }
0209 
0210 QDateTime KolabBase::creationDate() const
0211 {
0212     return mCreationDate;
0213 }
0214 
0215 void KolabBase::setLastModified(const QDateTime &date)
0216 {
0217     mLastModified = date;
0218 }
0219 
0220 QDateTime KolabBase::lastModified() const
0221 {
0222     return mLastModified;
0223 }
0224 
0225 void KolabBase::setSensitivity(Sensitivity sensitivity)
0226 {
0227     mSensitivity = sensitivity;
0228 }
0229 
0230 KolabBase::Sensitivity KolabBase::sensitivity() const
0231 {
0232     return mSensitivity;
0233 }
0234 
0235 void KolabBase::setPilotSyncId(unsigned long id)
0236 {
0237     mHasPilotSyncId = true;
0238     mPilotSyncId = id;
0239 }
0240 
0241 bool KolabBase::hasPilotSyncId() const
0242 {
0243     return mHasPilotSyncId;
0244 }
0245 
0246 unsigned long KolabBase::pilotSyncId() const
0247 {
0248     return mPilotSyncId;
0249 }
0250 
0251 void KolabBase::setPilotSyncStatus(int status)
0252 {
0253     mHasPilotSyncStatus = true;
0254     mPilotSyncStatus = status;
0255 }
0256 
0257 bool KolabBase::hasPilotSyncStatus() const
0258 {
0259     return mHasPilotSyncStatus;
0260 }
0261 
0262 int KolabBase::pilotSyncStatus() const
0263 {
0264     return mPilotSyncStatus;
0265 }
0266 
0267 bool KolabBase::loadEmailAttribute(QDomElement &element, Email &email)
0268 {
0269     for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
0270         if (n.isComment()) {
0271             continue;
0272         }
0273         if (n.isElement()) {
0274             QDomElement e = n.toElement();
0275             const QString tagName = e.tagName();
0276 
0277             if (tagName == QLatin1StringView("display-name")) {
0278                 email.displayName = e.text();
0279             } else if (tagName == QLatin1StringView("smtp-address")) {
0280                 email.smtpAddress = e.text();
0281             } else {
0282                 // TODO: Unhandled tag - save for later storage
0283                 qCDebug(PIMKOLAB_LOG) << "Warning: Unhandled tag" << e.tagName();
0284             }
0285         } else {
0286             qCDebug(PIMKOLAB_LOG) << "Node is not a comment or an element???";
0287         }
0288     }
0289 
0290     return true;
0291 }
0292 
0293 void KolabBase::saveEmailAttribute(QDomElement &element, const Email &email, const QString &tagName) const
0294 {
0295     QDomElement e = element.ownerDocument().createElement(tagName);
0296     element.appendChild(e);
0297     writeString(e, QStringLiteral("display-name"), email.displayName);
0298     writeString(e, QStringLiteral("smtp-address"), email.smtpAddress);
0299 }
0300 
0301 bool KolabBase::loadAttribute(QDomElement &element)
0302 {
0303     const QString tagName = element.tagName();
0304     switch (tagName[0].toLatin1()) {
0305     case 'u':
0306         if (tagName == QLatin1StringView("uid")) {
0307             setUid(element.text());
0308             return true;
0309         }
0310         break;
0311     case 'b':
0312         if (tagName == QLatin1StringView("body")) {
0313             setBody(element.text());
0314             return true;
0315         }
0316         break;
0317     case 'c':
0318         if (tagName == QLatin1StringView("categories")) {
0319             setCategories(element.text());
0320             return true;
0321         }
0322         if (tagName == QLatin1StringView("creation-date")) {
0323             setCreationDate(stringToDateTime(element.text()));
0324             return true;
0325         }
0326         break;
0327     case 'l':
0328         if (tagName == QLatin1StringView("last-modification-date")) {
0329             setLastModified(stringToDateTime(element.text()));
0330             return true;
0331         }
0332         break;
0333     case 's':
0334         if (tagName == QLatin1StringView("sensitivity")) {
0335             setSensitivity(stringToSensitivity(element.text()));
0336             return true;
0337         }
0338         break;
0339     case 'p':
0340         if (tagName == QLatin1StringView("product-id")) {
0341             return true; // ignore this field
0342         }
0343         if (tagName == QLatin1StringView("pilot-sync-id")) {
0344             setPilotSyncId(element.text().toULong());
0345             return true;
0346         }
0347         if (tagName == QLatin1StringView("pilot-sync-status")) {
0348             setPilotSyncStatus(element.text().toInt());
0349             return true;
0350         }
0351         break;
0352     default:
0353         break;
0354     }
0355     return false;
0356 }
0357 
0358 bool KolabBase::saveAttributes(QDomElement &element) const
0359 {
0360     writeString(element, QStringLiteral("product-id"), productID());
0361     writeString(element, QStringLiteral("uid"), uid());
0362     writeString(element, QStringLiteral("body"), body());
0363     writeString(element, QStringLiteral("categories"), categories());
0364     writeString(element, QStringLiteral("creation-date"), dateTimeToString(creationDate().toUTC()));
0365     writeString(element, QStringLiteral("last-modification-date"), dateTimeToString(lastModified().toUTC()));
0366     writeString(element, QStringLiteral("sensitivity"), sensitivityToString(sensitivity()));
0367     if (hasPilotSyncId()) {
0368         writeString(element, QStringLiteral("pilot-sync-id"), QString::number(pilotSyncId()));
0369     }
0370     if (hasPilotSyncStatus()) {
0371         writeString(element, QStringLiteral("pilot-sync-status"), QString::number(pilotSyncStatus()));
0372     }
0373     return true;
0374 }
0375 
0376 bool KolabBase::load(const QString &xml)
0377 {
0378     const QDomDocument document = loadDocument(xml);
0379     if (document.isNull()) {
0380         return false;
0381     }
0382     // XML file loaded into tree. Now parse it
0383     return loadXML(document);
0384 }
0385 
0386 QDomDocument KolabBase::loadDocument(const QString &xmlData)
0387 {
0388     QString errorMsg;
0389     int errorLine, errorColumn;
0390     QDomDocument document;
0391     bool ok = document.setContent(xmlData, &errorMsg, &errorLine, &errorColumn);
0392 
0393     if (!ok) {
0394         qWarning("Error loading document: %s, line %d, column %d", qPrintable(errorMsg), errorLine, errorColumn);
0395         return {};
0396     }
0397 
0398     return document;
0399 }
0400 
0401 QDomDocument KolabBase::domTree()
0402 {
0403     QDomDocument document;
0404 
0405     const QString p = QStringLiteral("version=\"1.0\" encoding=\"UTF-8\"");
0406     document.appendChild(document.createProcessingInstruction(QStringLiteral("xml"), p));
0407 
0408     return document;
0409 }
0410 
0411 QString KolabBase::dateTimeToString(const QDateTime &time)
0412 {
0413     return time.toString(Qt::ISODate);
0414 }
0415 
0416 QString KolabBase::dateToString(QDate date)
0417 {
0418     return date.toString(Qt::ISODate);
0419 }
0420 
0421 QDateTime KolabBase::stringToDateTime(const QString &time)
0422 {
0423     return QDateTime::fromString(time, Qt::ISODate);
0424 }
0425 
0426 QDate KolabBase::stringToDate(const QString &date)
0427 {
0428     return QDate::fromString(date, Qt::ISODate);
0429 }
0430 
0431 QString KolabBase::sensitivityToString(Sensitivity s)
0432 {
0433     switch (s) {
0434     case Private:
0435         return QStringLiteral("private");
0436     case Confidential:
0437         return QStringLiteral("confidential");
0438     case Public:
0439         return QStringLiteral("public");
0440     }
0441 
0442     return QStringLiteral("What what what???");
0443 }
0444 
0445 KolabBase::Sensitivity KolabBase::stringToSensitivity(const QString &s)
0446 {
0447     if (s == QLatin1StringView("private")) {
0448         return Private;
0449     }
0450     if (s == QLatin1StringView("confidential")) {
0451         return Confidential;
0452     }
0453     return Public;
0454 }
0455 
0456 QString KolabBase::colorToString(const QColor &color)
0457 {
0458     // Color is in the format "#RRGGBB"
0459     return color.name();
0460 }
0461 
0462 QColor KolabBase::stringToColor(const QString &s)
0463 {
0464     return QColor(s);
0465 }
0466 
0467 void KolabBase::writeString(QDomElement &element, const QString &tag, const QString &tagString)
0468 {
0469     if (!tagString.isEmpty()) {
0470         QDomElement e = element.ownerDocument().createElement(tag);
0471         QDomText t = element.ownerDocument().createTextNode(tagString);
0472         e.appendChild(t);
0473         element.appendChild(e);
0474     }
0475 }
0476 
0477 QDateTime KolabBase::localToUTC(const QDateTime &time) const
0478 {
0479     return time.toUTC();
0480 }
0481 
0482 QDateTime KolabBase::utcToLocal(const QDateTime &time) const
0483 {
0484     QDateTime dt = time;
0485     dt.setTimeZone(QTimeZone::utc());
0486     return dt;
0487 }