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"