File indexing completed on 2024-11-17 04:45:02

0001 /*
0002     SPDX-FileCopyrightText: 2008 Tobias Koenig <tokoe@kde.org>
0003     SPDX-FileCopyrightText: 2008 Bertjan Broeksema <broeksema@kde.org>
0004     SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "icaldirresource.h"
0010 
0011 #include "settingsadaptor.h"
0012 
0013 #include <Akonadi/ChangeRecorder>
0014 #include <Akonadi/EntityDisplayAttribute>
0015 #include <Akonadi/ItemFetchScope>
0016 
0017 #include <KCalendarCore/FileStorage>
0018 #include <KCalendarCore/ICalFormat>
0019 #include <KCalendarCore/MemoryCalendar>
0020 #include <KLocalizedString>
0021 #include <QDebug>
0022 
0023 #include <QDir>
0024 #include <QDirIterator>
0025 #include <QFile>
0026 #include <QTimeZone>
0027 
0028 using namespace Akonadi;
0029 using namespace KCalendarCore;
0030 
0031 static Incidence::Ptr readFromFile(const QString &fileName, const QString &expectedIdentifier)
0032 {
0033     MemoryCalendar::Ptr calendar = MemoryCalendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
0034     FileStorage::Ptr fileStorage = FileStorage::Ptr(new FileStorage(calendar, fileName, new ICalFormat()));
0035 
0036     Incidence::Ptr incidence;
0037     if (fileStorage->load()) {
0038         Incidence::List incidences = calendar->incidences();
0039         if (incidences.count() == 1 && incidences.first()->instanceIdentifier() == expectedIdentifier) {
0040             incidence = incidences.first();
0041         }
0042     } else {
0043         qCritical() << "Error loading file " << fileName;
0044     }
0045 
0046     return incidence;
0047 }
0048 
0049 static bool writeToFile(const QString &fileName, Incidence::Ptr &incidence)
0050 {
0051     if (!incidence) {
0052         qCritical() << "incidence is 0!";
0053         return false;
0054     }
0055 
0056     MemoryCalendar::Ptr calendar = MemoryCalendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
0057     FileStorage::Ptr fileStorage = FileStorage::Ptr(new FileStorage(calendar, fileName, new ICalFormat()));
0058     calendar->addIncidence(incidence);
0059     Q_ASSERT(calendar->incidences().count() == 1);
0060 
0061     const bool success = fileStorage->save();
0062     if (!success) {
0063         qCritical() << QStringLiteral("Failed to save calendar to file ") + fileName;
0064     }
0065 
0066     return success;
0067 }
0068 
0069 ICalDirResource::ICalDirResource(const QString &id)
0070     : ResourceBase(id)
0071 {
0072     IcalDirResourceSettings::instance(KSharedConfig::openConfig());
0073     // setup the resource
0074     new SettingsAdaptor(IcalDirResourceSettings::self());
0075     QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), IcalDirResourceSettings::self(), QDBusConnection::ExportAdaptors);
0076 
0077     changeRecorder()->itemFetchScope().fetchFullPayload();
0078     connect(this, &ICalDirResource::reloadConfiguration, this, &ICalDirResource::slotReloadConfig);
0079 }
0080 
0081 ICalDirResource::~ICalDirResource() = default;
0082 
0083 void ICalDirResource::slotReloadConfig()
0084 {
0085     IcalDirResourceSettings::self()->load();
0086 
0087     initializeICalDirectory();
0088     loadIncidences();
0089 
0090     synchronize();
0091 }
0092 
0093 void ICalDirResource::aboutToQuit()
0094 {
0095     IcalDirResourceSettings::self()->save();
0096 }
0097 
0098 bool ICalDirResource::loadIncidences()
0099 {
0100     mIncidences.clear();
0101 
0102     QDirIterator it(iCalDirectoryName());
0103     while (it.hasNext()) {
0104         it.next();
0105         if (it.fileName() != QLatin1StringView(".") && it.fileName() != QLatin1StringView("..") && it.fileName() != QLatin1StringView("WARNING_README.txt")) {
0106             const KCalendarCore::Incidence::Ptr incidence = readFromFile(it.filePath(), it.fileName());
0107             if (incidence) {
0108                 mIncidences.insert(incidence->instanceIdentifier(), incidence);
0109             }
0110         }
0111     }
0112 
0113     Q_EMIT status(Idle);
0114     return true;
0115 }
0116 
0117 bool ICalDirResource::retrieveItem(const Akonadi::Item &item, const QSet<QByteArray> &)
0118 {
0119     const QString remoteId = item.remoteId();
0120     if (!mIncidences.contains(remoteId)) {
0121         Q_EMIT error(i18n("Incidence with uid '%1' not found.", remoteId));
0122         return false;
0123     }
0124 
0125     Item newItem(item);
0126     newItem.setPayload<KCalendarCore::Incidence::Ptr>(mIncidences.value(remoteId));
0127     itemRetrieved(newItem);
0128 
0129     return true;
0130 }
0131 
0132 void ICalDirResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &)
0133 {
0134     if (IcalDirResourceSettings::self()->readOnly()) {
0135         Q_EMIT error(i18n("Trying to write to a read-only directory: '%1'", iCalDirectoryName()));
0136         cancelTask();
0137         return;
0138     }
0139 
0140     KCalendarCore::Incidence::Ptr incidence;
0141     if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
0142         incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0143     }
0144 
0145     if (incidence) {
0146         // add it to the cache...
0147         mIncidences.insert(incidence->instanceIdentifier(), incidence);
0148 
0149         // ... and write it through to the file system
0150         const bool success = writeToFile(iCalDirectoryFileName(incidence->instanceIdentifier()), incidence);
0151 
0152         if (success) {
0153             // report everything ok
0154             Item newItem(item);
0155             newItem.setRemoteId(incidence->instanceIdentifier());
0156             changeCommitted(newItem);
0157         } else {
0158             cancelTask();
0159         }
0160     } else {
0161         changeProcessed();
0162     }
0163 }
0164 
0165 void ICalDirResource::itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &)
0166 {
0167     if (IcalDirResourceSettings::self()->readOnly()) {
0168         Q_EMIT error(i18n("Trying to write to a read-only directory: '%1'", iCalDirectoryName()));
0169         cancelTask();
0170         return;
0171     }
0172 
0173     KCalendarCore::Incidence::Ptr incidence;
0174     if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
0175         incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0176     }
0177 
0178     if (incidence) {
0179         // change it in the cache...
0180         mIncidences.insert(incidence->instanceIdentifier(), incidence);
0181 
0182         // ... and write it through to the file system
0183         const bool success = writeToFile(iCalDirectoryFileName(incidence->instanceIdentifier()), incidence);
0184 
0185         if (success) {
0186             Item newItem(item);
0187             newItem.setRemoteId(incidence->instanceIdentifier());
0188             changeCommitted(newItem);
0189         } else {
0190             cancelTask();
0191         }
0192     } else {
0193         changeProcessed();
0194     }
0195 }
0196 
0197 void ICalDirResource::itemRemoved(const Akonadi::Item &item)
0198 {
0199     if (IcalDirResourceSettings::self()->readOnly()) {
0200         Q_EMIT error(i18n("Trying to write to a read-only directory: '%1'", iCalDirectoryName()));
0201         cancelTask();
0202         return;
0203     }
0204 
0205     // remove it from the cache...
0206     mIncidences.remove(item.remoteId());
0207 
0208     // ... and remove it from the file system
0209     QFile::remove(iCalDirectoryFileName(item.remoteId()));
0210 
0211     changeProcessed();
0212 }
0213 
0214 void ICalDirResource::collectionChanged(const Collection &collection)
0215 {
0216     if (collection.hasAttribute<EntityDisplayAttribute>()) {
0217         auto attr = collection.attribute<EntityDisplayAttribute>();
0218         if (attr->displayName() != name()) {
0219             setName(attr->displayName());
0220         }
0221     }
0222 
0223     changeProcessed();
0224 }
0225 
0226 void ICalDirResource::retrieveCollections()
0227 {
0228     Collection c;
0229     c.setParentCollection(Collection::root());
0230     c.setRemoteId(iCalDirectoryName());
0231     c.setName(name());
0232 
0233     QStringList mimetypes;
0234     mimetypes << KCalendarCore::Event::eventMimeType() << KCalendarCore::Todo::todoMimeType() << KCalendarCore::Journal::journalMimeType()
0235               << QStringLiteral("text/calendar");
0236     c.setContentMimeTypes(mimetypes);
0237 
0238     if (IcalDirResourceSettings::self()->readOnly()) {
0239         c.setRights(Collection::CanChangeCollection);
0240     } else {
0241         Collection::Rights rights = Collection::ReadOnly;
0242         rights |= Collection::CanChangeItem;
0243         rights |= Collection::CanCreateItem;
0244         rights |= Collection::CanDeleteItem;
0245         rights |= Collection::CanChangeCollection;
0246         c.setRights(rights);
0247     }
0248 
0249     auto attr = c.attribute<EntityDisplayAttribute>(Collection::AddIfMissing);
0250     attr->setDisplayName(name() == identifier() ? i18n("Calendar Folder") : name());
0251     attr->setIconName(QStringLiteral("office-calendar"));
0252 
0253     const Collection::List list{c};
0254     collectionsRetrieved(list);
0255 }
0256 
0257 void ICalDirResource::retrieveItems(const Akonadi::Collection &)
0258 {
0259     loadIncidences();
0260     Item::List items;
0261     items.reserve(mIncidences.count());
0262 
0263     for (const KCalendarCore::Incidence::Ptr &incidence : std::as_const(mIncidences)) {
0264         Item item;
0265         item.setRemoteId(incidence->instanceIdentifier());
0266         item.setMimeType(incidence->mimeType());
0267         items.append(item);
0268     }
0269 
0270     itemsRetrieved(items);
0271 }
0272 
0273 QString ICalDirResource::iCalDirectoryName() const
0274 {
0275     return IcalDirResourceSettings::self()->path();
0276 }
0277 
0278 QString ICalDirResource::iCalDirectoryFileName(const QString &file) const
0279 {
0280     return IcalDirResourceSettings::self()->path() + QLatin1Char('/') + file;
0281 }
0282 
0283 void ICalDirResource::initializeICalDirectory() const
0284 {
0285     QDir dir(iCalDirectoryName());
0286 
0287     // if folder does not exists, create it
0288     if (!dir.exists()) {
0289         if (!QDir::root().mkpath(dir.absolutePath())) {
0290             qCritical() << "Failed to create ical directory" << dir.absolutePath();
0291         } else {
0292             qDebug() << "iCal directory " << dir.absolutePath() << "successfuly created";
0293         }
0294     }
0295 
0296     // check whether warning file is in place...
0297     QFile file(dir.absolutePath() + QStringLiteral("/WARNING_README.txt"));
0298     if (!file.exists()) {
0299         // ... if not, create it
0300         file.open(QIODevice::WriteOnly);
0301         file.write(
0302             "Important Warning!!!\n\n"
0303             "Don't create or copy files inside this folder manually, they are managed by the Akonadi framework!\n");
0304         file.close();
0305     }
0306 }
0307 
0308 AKONADI_RESOURCE_MAIN(ICalDirResource)
0309 
0310 #include "moc_icaldirresource.cpp"