File indexing completed on 2024-05-12 05:10:45
0001 /** 0002 This file is part of the akonadi-calendar library. 0003 0004 SPDX-FileCopyrightText: 2013 Sérgio Martins <iamsergio@gmail.com> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "icalimporter.h" 0010 #include "icalimporter_p.h" 0011 #include "utils_p.h" 0012 0013 #include <Akonadi/AgentInstanceCreateJob> 0014 #include <Akonadi/AgentManager> 0015 #include <Akonadi/ServerManager> 0016 0017 #include <KCalendarCore/FileStorage> 0018 0019 #include <KIO/Job> 0020 #include <KIO/StoredTransferJob> 0021 0022 #include <QDBusInterface> 0023 #include <QFileInfo> 0024 #include <QTemporaryFile> 0025 #include <QTimeZone> 0026 0027 using namespace KCalendarCore; 0028 using namespace Akonadi; 0029 0030 ICalImporterPrivate::ICalImporterPrivate(IncidenceChanger *changer, ICalImporter *qq) 0031 : QObject() 0032 , q(qq) 0033 , m_changer(changer) 0034 { 0035 if (!changer) { 0036 m_changer = new IncidenceChanger(q); 0037 } 0038 connect(m_changer, &IncidenceChanger::createFinished, this, &ICalImporterPrivate::onIncidenceCreated); 0039 } 0040 0041 ICalImporterPrivate::~ICalImporterPrivate() 0042 { 0043 delete m_temporaryFile; 0044 } 0045 0046 void ICalImporterPrivate::onIncidenceCreated(int changeId, 0047 const Akonadi::Item &item, 0048 Akonadi::IncidenceChanger::ResultCode resultCode, 0049 const QString &errorString) 0050 { 0051 Q_UNUSED(item) 0052 0053 if (!m_pendingRequests.contains(changeId)) { 0054 return; // Not ours 0055 } 0056 0057 m_pendingRequests.removeAll(changeId); 0058 0059 if (resultCode != IncidenceChanger::ResultCodeSuccess) { 0060 m_working = false; 0061 setErrorMessage(errorString); 0062 m_pendingRequests.clear(); 0063 Q_EMIT q->importIntoExistingFinished(false, m_numIncidences); 0064 } else if (m_pendingRequests.isEmpty()) { 0065 m_working = false; 0066 Q_EMIT q->importIntoExistingFinished(true, m_numIncidences); 0067 } 0068 } 0069 0070 void ICalImporterPrivate::setErrorMessage(const QString &message) 0071 { 0072 m_lastErrorMessage = message; 0073 qCritical() << message; 0074 } 0075 0076 void ICalImporterPrivate::resourceCreated(KJob *job) 0077 { 0078 auto createjob = qobject_cast<Akonadi::AgentInstanceCreateJob *>(job); 0079 0080 Q_ASSERT(createjob); 0081 m_working = false; 0082 if (createjob->error()) { 0083 setErrorMessage(i18n("Error creating ical resource: %1", createjob->errorString())); 0084 Q_EMIT q->importIntoNewFinished(false); 0085 return; 0086 } 0087 0088 Akonadi::AgentInstance instance = createjob->instance(); 0089 const QString service = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, instance.identifier()); 0090 0091 QDBusInterface iface(service, QStringLiteral("/Settings")); 0092 if (!iface.isValid()) { 0093 setErrorMessage(i18n("Failed to obtain D-Bus interface for remote configuration.")); 0094 Q_EMIT q->importIntoNewFinished(false); 0095 return; 0096 } 0097 0098 const QString path = createjob->property("path").toString(); 0099 Q_ASSERT(!path.isEmpty()); 0100 0101 iface.call(QStringLiteral("setPath"), path); 0102 iface.call(QStringLiteral("save")); 0103 instance.reconfigure(); 0104 0105 Q_EMIT q->importIntoNewFinished(true); 0106 } 0107 0108 void ICalImporterPrivate::remoteDownloadFinished(KIO::Job *job, const QByteArray &data) 0109 { 0110 const bool success = job->error() == 0; 0111 m_working = false; 0112 if (success) { 0113 delete m_temporaryFile; 0114 m_temporaryFile = new QTemporaryFile(); 0115 m_temporaryFile->write(data.constData(), data.size()); 0116 q->importIntoExistingResource(QUrl(m_temporaryFile->fileName()), m_collection); 0117 } else { 0118 setErrorMessage(i18n("Could not download remote file.")); 0119 Q_EMIT q->importIntoExistingFinished(false, 0); 0120 } 0121 } 0122 0123 ICalImporter::ICalImporter(Akonadi::IncidenceChanger *changer, QObject *parent) 0124 : QObject(parent) 0125 , d(new ICalImporterPrivate(changer, this)) 0126 { 0127 } 0128 0129 ICalImporter::~ICalImporter() = default; 0130 0131 QString ICalImporter::errorMessage() const 0132 { 0133 return d->m_lastErrorMessage; 0134 } 0135 0136 bool ICalImporter::importIntoNewResource(const QString &filename) 0137 { 0138 d->m_lastErrorMessage.clear(); 0139 0140 if (d->m_working) { 0141 d->setErrorMessage(i18n("An import task is already in progress.")); 0142 return false; 0143 } 0144 0145 d->m_working = true; 0146 0147 Akonadi::AgentType type = Akonadi::AgentManager::self()->type(QStringLiteral("akonadi_ical_resource")); 0148 0149 auto job = new Akonadi::AgentInstanceCreateJob(type, this); 0150 job->setProperty("path", filename); 0151 connect(job, &KJob::result, d.get(), &ICalImporterPrivate::resourceCreated); 0152 job->start(); 0153 0154 return true; 0155 } 0156 0157 bool ICalImporter::importIntoExistingResource(const QUrl &url, Collection collection) 0158 { 0159 d->m_lastErrorMessage.clear(); 0160 0161 if (d->m_working) { 0162 d->setErrorMessage(i18n("An import task is already in progress.")); 0163 return false; 0164 } 0165 0166 if (url.isEmpty()) { 0167 d->setErrorMessage(i18n("Empty filename. Will not import ical file.")); 0168 return false; 0169 } 0170 0171 if (!url.isValid()) { 0172 d->setErrorMessage(i18n("Url to import is malformed.")); 0173 return false; 0174 } 0175 0176 if (url.isLocalFile()) { 0177 QFileInfo f{url.path()}; 0178 if (!f.exists() || !f.isFile() || !f.isReadable()) { 0179 d->setErrorMessage(i18n("The selected file is not a readable file.")); 0180 return false; 0181 } 0182 MemoryCalendar::Ptr temporaryCalendar(new MemoryCalendar(QTimeZone::systemTimeZone())); 0183 FileStorage storage(temporaryCalendar); 0184 storage.setFileName(url.path()); 0185 bool success = storage.load(); 0186 if (!success) { 0187 d->setErrorMessage(i18n("The selected file is not properly formatted, or in a format not supported by this software.")); 0188 return false; 0189 } 0190 0191 d->m_pendingRequests.clear(); 0192 const Incidence::List incidences = temporaryCalendar->incidences(); 0193 0194 if (incidences.isEmpty()) { 0195 d->setErrorMessage(i18n("The selected file is empty.")); 0196 return false; 0197 } 0198 0199 if (!collection.isValid()) { 0200 int dialogCode; 0201 const QStringList mimeTypes = QStringList() 0202 << KCalendarCore::Event::eventMimeType() << KCalendarCore::Todo::todoMimeType() << KCalendarCore::Journal::journalMimeType(); 0203 collection = CalendarUtils::selectCollection(nullptr, dialogCode /*by-ref*/, mimeTypes); 0204 } 0205 0206 if (!collection.isValid()) { 0207 // user canceled 0208 d->setErrorMessage(QString()); 0209 return false; 0210 } 0211 0212 const IncidenceChanger::DestinationPolicy policySaved = d->m_changer->destinationPolicy(); 0213 d->m_changer->startAtomicOperation(i18n("Merge ical file into existing calendar.")); 0214 d->m_changer->setDestinationPolicy(IncidenceChanger::DestinationPolicyNeverAsk); 0215 for (const Incidence::Ptr &incidence : std::as_const(incidences)) { 0216 Q_ASSERT(incidence); 0217 if (!incidence) { 0218 continue; 0219 } 0220 const int requestId = d->m_changer->createIncidence(incidence, collection); 0221 Q_ASSERT(requestId != -1); // -1 only happens with invalid incidences 0222 if (requestId != -1) { 0223 d->m_pendingRequests << requestId; 0224 } 0225 } 0226 d->m_changer->endAtomicOperation(); 0227 0228 d->m_changer->setDestinationPolicy(policySaved); // restore 0229 d->m_numIncidences = incidences.count(); 0230 } else { 0231 d->m_collection = collection; 0232 KIO::StoredTransferJob *job = KIO::storedGet(url); 0233 connect(job, qOverload<KIO::Job *, const QByteArray &>(&KIO::TransferJob::data), d.get(), [this](KIO::Job *job, const QByteArray &ba) { 0234 d->remoteDownloadFinished(job, ba); 0235 }); 0236 } 0237 0238 d->m_working = true; 0239 return true; 0240 } 0241 0242 #include "moc_icalimporter_p.cpp" 0243 0244 #include "moc_icalimporter.cpp"