File indexing completed on 2024-05-12 05:13:14
0001 /* 0002 SPDX-FileCopyrightText: 2010 Klarlvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 0003 SPDX-FileContributor: Allen Winter <allen.winter@kdab.com> 0004 0005 SPDX-FileCopyrightText: 2014 Sergio Martins <iamsergio@gmail.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 /** 0011 @file 0012 This file is part of the API for handling calendar data and provides 0013 static functions for dealing with calendar incidence attachments. 0014 0015 @brief 0016 vCalendar/iCalendar attachment handling. 0017 0018 @author Allen Winter \<winter@kde.org\> 0019 */ 0020 #include "attachmenthandler.h" 0021 #include "calendarsupport_debug.h" 0022 0023 #include <Akonadi/CalendarUtils> 0024 #include <Akonadi/ItemFetchJob> 0025 0026 #include <KIO/FileCopyJob> 0027 #include <KIO/JobUiDelegate> 0028 #include <KIO/OpenUrlJob> 0029 #include <KIO/StatJob> 0030 #include <KJob> 0031 #include <KJobWidgets> 0032 #include <KLocalizedString> 0033 #include <KMessageBox> 0034 0035 #include <QDesktopServices> 0036 #include <QFile> 0037 #include <QFileDialog> 0038 #include <QMimeDatabase> 0039 #include <QPointer> 0040 #include <QTemporaryFile> 0041 0042 using namespace KCalendarCore; 0043 using namespace Akonadi; 0044 0045 namespace CalendarSupport 0046 { 0047 struct ReceivedInfo { 0048 QString uid; 0049 QString attachmentName; 0050 }; 0051 0052 class AttachmentHandlerPrivate 0053 { 0054 public: 0055 explicit AttachmentHandlerPrivate(QWidget *parent) 0056 : mParent(parent) 0057 { 0058 } 0059 0060 QMap<KJob *, ReceivedInfo> mJobToReceivedInfo; 0061 QPointer<QWidget> const mParent; 0062 }; 0063 0064 AttachmentHandler::AttachmentHandler(QWidget *parent) 0065 : QObject(parent) 0066 , d(new AttachmentHandlerPrivate(parent)) 0067 { 0068 } 0069 0070 AttachmentHandler::~AttachmentHandler() = default; 0071 0072 Attachment AttachmentHandler::find(const QString &attachmentName, const Incidence::Ptr &incidence) 0073 { 0074 if (!incidence) { 0075 return Attachment(); 0076 } 0077 0078 // get the attachment by name from the incidence 0079 const Attachment::List as = incidence->attachments(); 0080 Attachment a; 0081 if (!as.isEmpty()) { 0082 Attachment::List::ConstIterator it; 0083 Attachment::List::ConstIterator end(as.constEnd()); 0084 0085 for (it = as.constBegin(); it != end; ++it) { 0086 if ((*it).label() == attachmentName) { 0087 a = *it; 0088 break; 0089 } 0090 } 0091 } 0092 0093 if (a.isEmpty()) { 0094 KMessageBox::error(d->mParent, i18n("No attachment named \"%1\" found in the incidence.", attachmentName)); 0095 return Attachment(); 0096 } 0097 0098 if (a.isUri()) { 0099 auto job = KIO::stat(QUrl(a.uri()), KIO::StatJob::SourceSide, KIO::StatBasic); 0100 0101 KJobWidgets::setWindow(job, d->mParent); 0102 if (!job->exec()) { 0103 KMessageBox::error( 0104 d->mParent, 0105 i18n("The attachment \"%1\" is a web link that is inaccessible from this computer. ", QUrl::fromPercentEncoding(a.uri().toLatin1()))); 0106 return Attachment(); 0107 } 0108 } 0109 return a; 0110 } 0111 0112 Attachment AttachmentHandler::find(const QString &attachmentName, const ScheduleMessage::Ptr &message) 0113 { 0114 if (!message) { 0115 return Attachment(); 0116 } 0117 0118 Incidence::Ptr incidence = message->event().dynamicCast<Incidence>(); 0119 if (!incidence) { 0120 KMessageBox::error(d->mParent, 0121 i18n("The calendar invitation stored in this email message is broken in some way. " 0122 "Unable to continue.")); 0123 return Attachment(); 0124 } 0125 0126 return find(attachmentName, incidence); 0127 } 0128 0129 static QTemporaryFile *s_tempFile = nullptr; 0130 0131 static QUrl tempFileForAttachment(const Attachment &attachment) 0132 { 0133 QUrl url; 0134 0135 QMimeDatabase db; 0136 QStringList patterns = db.mimeTypeForName(attachment.mimeType()).globPatterns(); 0137 if (!patterns.empty()) { 0138 s_tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1StringView("/attachementview_XXXXXX") + patterns.first().remove(QLatin1Char('*'))); 0139 } else { 0140 s_tempFile = new QTemporaryFile(); 0141 } 0142 s_tempFile->setAutoRemove(false); 0143 s_tempFile->open(); 0144 s_tempFile->setPermissions(QFile::ReadUser); 0145 s_tempFile->write(QByteArray::fromBase64(attachment.data())); 0146 s_tempFile->close(); 0147 QFile tf(s_tempFile->fileName()); 0148 if (tf.size() != attachment.size()) { 0149 // whoops. failed to write the entire attachment. return an invalid URL. 0150 delete s_tempFile; 0151 s_tempFile = nullptr; 0152 return url; 0153 } 0154 0155 url.setPath(s_tempFile->fileName()); 0156 return url; 0157 } 0158 0159 bool AttachmentHandler::view(const Attachment &attachment) 0160 { 0161 if (attachment.isEmpty()) { 0162 return false; 0163 } 0164 0165 bool stat = true; 0166 if (attachment.isUri()) { 0167 QDesktopServices::openUrl(QUrl(attachment.uri())); 0168 } else { 0169 // put the attachment in a temporary file and launch it 0170 QUrl tempUrl = tempFileForAttachment(attachment); 0171 if (tempUrl.isValid()) { 0172 auto job = new KIO::OpenUrlJob(tempUrl, attachment.mimeType()); 0173 job->setDeleteTemporaryFile(true); 0174 job->setRunExecutables(true); 0175 job->start(); 0176 } else { 0177 stat = false; 0178 KMessageBox::error(d->mParent, i18n("Unable to create a temporary file for the attachment.")); 0179 } 0180 delete s_tempFile; 0181 s_tempFile = nullptr; 0182 } 0183 return stat; 0184 } 0185 0186 bool AttachmentHandler::view(const QString &attachmentName, const Incidence::Ptr &incidence) 0187 { 0188 return view(find(attachmentName, incidence)); 0189 } 0190 0191 void AttachmentHandler::view(const QString &attachmentName, const QString &uid) 0192 { 0193 Item item; 0194 item.setGid(uid); 0195 auto job = new ItemFetchJob(item); 0196 connect(job, &ItemFetchJob::result, this, &AttachmentHandler::slotFinishView); 0197 ReceivedInfo info; 0198 info.attachmentName = attachmentName; 0199 info.uid = uid; 0200 d->mJobToReceivedInfo[job] = info; 0201 } 0202 0203 bool AttachmentHandler::view(const QString &attachmentName, const ScheduleMessage::Ptr &message) 0204 { 0205 return view(find(attachmentName, message)); 0206 } 0207 0208 bool AttachmentHandler::saveAs(const Attachment &attachment) 0209 { 0210 // get the saveas file name 0211 const QString saveAsFile = QFileDialog::getSaveFileName(d->mParent, i18n("Save Attachment"), attachment.label()); 0212 if (saveAsFile.isEmpty()) { 0213 return false; 0214 } 0215 0216 bool stat = false; 0217 if (attachment.isUri()) { 0218 // save the attachment url 0219 auto job = KIO::file_copy(QUrl(attachment.uri()), QUrl::fromLocalFile(saveAsFile)); 0220 stat = job->exec(); 0221 } else { 0222 // put the attachment in a temporary file and save it 0223 QUrl tempUrl = tempFileForAttachment(attachment); 0224 if (tempUrl.isValid()) { 0225 auto job = KIO::file_copy(tempUrl, QUrl::fromLocalFile(saveAsFile)); 0226 stat = job->exec(); 0227 if (!stat && job->error()) { 0228 KMessageBox::error(d->mParent, job->errorString()); 0229 } 0230 } else { 0231 stat = false; 0232 KMessageBox::error(d->mParent, i18n("Unable to create a temporary file for the attachment.")); 0233 } 0234 delete s_tempFile; 0235 s_tempFile = nullptr; 0236 } 0237 return stat; 0238 } 0239 0240 bool AttachmentHandler::saveAs(const QString &attachmentName, const Incidence::Ptr &incidence) 0241 { 0242 return saveAs(find(attachmentName, incidence)); 0243 } 0244 0245 void AttachmentHandler::saveAs(const QString &attachmentName, const QString &uid) 0246 { 0247 Item item; 0248 item.setGid(uid); 0249 auto job = new ItemFetchJob(item); 0250 connect(job, &ItemFetchJob::result, this, &AttachmentHandler::slotFinishView); 0251 0252 ReceivedInfo info; 0253 info.attachmentName = attachmentName; 0254 info.uid = uid; 0255 d->mJobToReceivedInfo[job] = info; 0256 } 0257 0258 bool AttachmentHandler::saveAs(const QString &attachmentName, const ScheduleMessage::Ptr &message) 0259 { 0260 return saveAs(find(attachmentName, message)); 0261 } 0262 0263 void AttachmentHandler::slotFinishSaveAs(KJob *job) 0264 { 0265 ReceivedInfo info = d->mJobToReceivedInfo[job]; 0266 bool success = false; 0267 0268 if (job->error() != 0) { 0269 auto fetchJob = qobject_cast<ItemFetchJob *>(job); 0270 const Item::List items = fetchJob->items(); 0271 if (!items.isEmpty()) { 0272 Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(items.first()); 0273 success = incidence && saveAs(info.attachmentName, incidence); 0274 } else { 0275 qCWarning(CALENDARSUPPORT_LOG) << Q_FUNC_INFO << "No item found"; 0276 } 0277 } else { 0278 qCWarning(CALENDARSUPPORT_LOG) << Q_FUNC_INFO << "Job error:" << job->errorString(); 0279 } 0280 0281 Q_EMIT saveAsFinished(info.uid, info.attachmentName, success); 0282 d->mJobToReceivedInfo.remove(job); 0283 } 0284 0285 void AttachmentHandler::slotFinishView(KJob *job) 0286 { 0287 ReceivedInfo info = d->mJobToReceivedInfo[job]; 0288 bool success = false; 0289 0290 if (job->error()) { 0291 auto fetchJob = qobject_cast<ItemFetchJob *>(job); 0292 const Item::List items = fetchJob->items(); 0293 if (!items.isEmpty()) { 0294 Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(items.first()); 0295 success = incidence && view(info.attachmentName, incidence); 0296 } else { 0297 qCWarning(CALENDARSUPPORT_LOG) << Q_FUNC_INFO << "No item found"; 0298 } 0299 } else { 0300 qCWarning(CALENDARSUPPORT_LOG) << Q_FUNC_INFO << "Job error:" << job->errorString(); 0301 } 0302 0303 Q_EMIT viewFinished(info.uid, info.attachmentName, success); 0304 d->mJobToReceivedInfo.remove(job); 0305 } 0306 } // namespace CalendarSupport 0307 0308 #include "moc_attachmenthandler.cpp"