diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-06-14 14:59:43 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-06-14 15:51:56 +0200 |
commit | 5941fdf6d65ecf29fc46dab000987041f5f4a8d8 (patch) | |
tree | 07a04edf9079955aa725ea24014106dcb6616911 /framework/src | |
parent | 8809ffea6390e593b1d83f69cc50dfe8d7dadad0 (diff) | |
download | kube-5941fdf6d65ecf29fc46dab000987041f5f4a8d8.tar.gz kube-5941fdf6d65ecf29fc46dab000987041f5f4a8d8.zip |
Attachment support in the composer
Diffstat (limited to 'framework/src')
-rw-r--r-- | framework/src/domain/composercontroller.cpp | 151 | ||||
-rw-r--r-- | framework/src/domain/composercontroller.h | 17 |
2 files changed, 165 insertions, 3 deletions
diff --git a/framework/src/domain/composercontroller.cpp b/framework/src/domain/composercontroller.cpp index cbc43afe..7c495543 100644 --- a/framework/src/domain/composercontroller.cpp +++ b/framework/src/domain/composercontroller.cpp | |||
@@ -27,6 +27,10 @@ | |||
27 | #include <QSortFilterProxyModel> | 27 | #include <QSortFilterProxyModel> |
28 | #include <QList> | 28 | #include <QList> |
29 | #include <QDebug> | 29 | #include <QDebug> |
30 | #include <QMimeDatabase> | ||
31 | #include <QUrlQuery> | ||
32 | #include <QFileInfo> | ||
33 | #include <QFile> | ||
30 | #include <sink/store.h> | 34 | #include <sink/store.h> |
31 | #include <sink/log.h> | 35 | #include <sink/log.h> |
32 | 36 | ||
@@ -84,8 +88,16 @@ ComposerController::ComposerController() | |||
84 | mIdentitySelector{new IdentitySelector{*this}}, | 88 | mIdentitySelector{new IdentitySelector{*this}}, |
85 | mToModel{new QStringListModel}, | 89 | mToModel{new QStringListModel}, |
86 | mCcModel{new QStringListModel}, | 90 | mCcModel{new QStringListModel}, |
87 | mBccModel{new QStringListModel} | 91 | mBccModel{new QStringListModel}, |
92 | mAttachmentModel{new QStandardItemModel} | ||
88 | { | 93 | { |
94 | mAttachmentModel->setItemRoleNames({{NameRole, "name"}, | ||
95 | {FilenameRole, "filename"}, | ||
96 | {ContentRole, "content"}, | ||
97 | {MimeTypeRole, "mimetype"}, | ||
98 | {DescriptionRole, "description"}, | ||
99 | {IconNameRole, "iconName"}, | ||
100 | {InlineRole, "inline"}}); | ||
89 | updateSaveAsDraftAction(); | 101 | updateSaveAsDraftAction(); |
90 | // mSendAction->monitorProperty<To>(); | 102 | // mSendAction->monitorProperty<To>(); |
91 | // mSendAction->monitorProperty<Send>([] (const QString &) -> bool{ | 103 | // mSendAction->monitorProperty<Send>([] (const QString &) -> bool{ |
@@ -175,6 +187,46 @@ void ComposerController::removeBcc(const QString &s) | |||
175 | updateSendAction(); | 187 | updateSendAction(); |
176 | } | 188 | } |
177 | 189 | ||
190 | QAbstractItemModel *ComposerController::attachmentModel() const | ||
191 | { | ||
192 | return mAttachmentModel.data(); | ||
193 | } | ||
194 | |||
195 | void ComposerController::addAttachment(const QUrl &url) | ||
196 | { | ||
197 | QMimeDatabase db; | ||
198 | auto mimeType = db.mimeTypeForUrl(url); | ||
199 | if (mimeType.name() == QLatin1String("inode/directory")) { | ||
200 | qWarning() << "Can't deal with directories yet."; | ||
201 | } else { | ||
202 | if (!url.isLocalFile()) { | ||
203 | qWarning() << "Cannot attach remote file: " << url; | ||
204 | return; | ||
205 | } | ||
206 | |||
207 | QFileInfo fileInfo(url.toLocalFile()); | ||
208 | if (!fileInfo.exists()) { | ||
209 | qWarning() << "The file doesn't exist: " << url; | ||
210 | } | ||
211 | |||
212 | QFile file{fileInfo.filePath()}; | ||
213 | file.open(QIODevice::ReadOnly); | ||
214 | const auto data = file.readAll(); | ||
215 | auto item = new QStandardItem; | ||
216 | item->setData(fileInfo.fileName(), NameRole); | ||
217 | item->setData(mimeType.name().toLatin1(), MimeTypeRole); | ||
218 | item->setData(fileInfo.fileName(), FilenameRole); | ||
219 | item->setData(false, InlineRole); | ||
220 | item->setData(mimeType.iconName(), IconNameRole); | ||
221 | item->setData(data, ContentRole); | ||
222 | mAttachmentModel->appendRow(item); | ||
223 | } | ||
224 | } | ||
225 | |||
226 | void ComposerController::removeAttachment(const QString &s) | ||
227 | { | ||
228 | } | ||
229 | |||
178 | Completer *ComposerController::recipientCompleter() const | 230 | Completer *ComposerController::recipientCompleter() const |
179 | { | 231 | { |
180 | return mRecipientCompleter.data(); | 232 | return mRecipientCompleter.data(); |
@@ -215,6 +267,56 @@ static QStringList getStringListFromAddresses(const QString &s) | |||
215 | return list; | 267 | return list; |
216 | } | 268 | } |
217 | 269 | ||
270 | void ComposerController::addAttachmentPart(KMime::Content *partToAttach) | ||
271 | { | ||
272 | auto item = new QStandardItem; | ||
273 | |||
274 | if (partToAttach->contentType()->mimeType() == "multipart/digest" || | ||
275 | partToAttach->contentType()->mimeType() == "message/rfc822") { | ||
276 | // if it is a digest or a full message, use the encodedContent() of the attachment, | ||
277 | // which already has the proper headers | ||
278 | item->setData(partToAttach->encodedContent(), ContentRole); | ||
279 | } else { | ||
280 | item->setData(partToAttach->decodedContent(), ContentRole); | ||
281 | } | ||
282 | item->setData(partToAttach->contentType()->mimeType(), MimeTypeRole); | ||
283 | |||
284 | QMimeDatabase db; | ||
285 | auto mimeType = db.mimeTypeForName(partToAttach->contentType()->mimeType()); | ||
286 | item->setData(mimeType.iconName(), IconNameRole); | ||
287 | |||
288 | if (partToAttach->contentDescription(false)) { | ||
289 | item->setData(partToAttach->contentDescription()->asUnicodeString(), DescriptionRole); | ||
290 | } | ||
291 | QString name; | ||
292 | QString filename; | ||
293 | if (partToAttach->contentType(false)) { | ||
294 | if (partToAttach->contentType()->hasParameter(QStringLiteral("name"))) { | ||
295 | name = partToAttach->contentType()->parameter(QStringLiteral("name")); | ||
296 | } | ||
297 | } | ||
298 | if (partToAttach->contentDisposition(false)) { | ||
299 | filename = partToAttach->contentDisposition()->filename(); | ||
300 | item->setData(partToAttach->contentDisposition()->disposition() == KMime::Headers::CDinline, InlineRole); | ||
301 | } | ||
302 | |||
303 | if (name.isEmpty() && !filename.isEmpty()) { | ||
304 | name = filename; | ||
305 | } | ||
306 | if (filename.isEmpty() && !name.isEmpty()) { | ||
307 | filename = name; | ||
308 | } | ||
309 | |||
310 | if (!filename.isEmpty()) { | ||
311 | item->setData(filename, FilenameRole); | ||
312 | } | ||
313 | if (!name.isEmpty()) { | ||
314 | item->setData(name, NameRole); | ||
315 | } | ||
316 | |||
317 | mAttachmentModel->appendRow(item); | ||
318 | } | ||
319 | |||
218 | void ComposerController::setMessage(const KMime::Message::Ptr &msg) | 320 | void ComposerController::setMessage(const KMime::Message::Ptr &msg) |
219 | { | 321 | { |
220 | mToModel->setStringList(getStringListFromAddresses(msg->to(true)->asUnicodeString())); | 322 | mToModel->setStringList(getStringListFromAddresses(msg->to(true)->asUnicodeString())); |
@@ -223,6 +325,12 @@ void ComposerController::setMessage(const KMime::Message::Ptr &msg) | |||
223 | 325 | ||
224 | setSubject(msg->subject(true)->asUnicodeString()); | 326 | setSubject(msg->subject(true)->asUnicodeString()); |
225 | setBody(msg->body()); | 327 | setBody(msg->body()); |
328 | |||
329 | //TODO use ObjecTreeParser to get encrypted attachments as well | ||
330 | foreach (const auto &att, msg->attachments()) { | ||
331 | addAttachmentPart(att); | ||
332 | } | ||
333 | |||
226 | setExistingMessage(msg); | 334 | setExistingMessage(msg); |
227 | } | 335 | } |
228 | 336 | ||
@@ -236,7 +344,6 @@ void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft) | |||
236 | Store::fetchOne<Mail>(query).then([this, loadAsDraft](const Mail &mail) { | 344 | Store::fetchOne<Mail>(query).then([this, loadAsDraft](const Mail &mail) { |
237 | setExistingMail(mail); | 345 | setExistingMail(mail); |
238 | 346 | ||
239 | //TODO this should probably happen as reaction to the property being set. | ||
240 | const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage()); | 347 | const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage()); |
241 | if (!mailData.isEmpty()) { | 348 | if (!mailData.isEmpty()) { |
242 | KMime::Message::Ptr mail(new KMime::Message); | 349 | KMime::Message::Ptr mail(new KMime::Message); |
@@ -285,7 +392,6 @@ KMime::Message::Ptr ComposerController::assembleMessage() | |||
285 | mail->from(true)->addAddress(getIdentity()); | 392 | mail->from(true)->addAddress(getIdentity()); |
286 | 393 | ||
287 | mail->subject(true)->fromUnicodeString(getSubject(), "utf-8"); | 394 | mail->subject(true)->fromUnicodeString(getSubject(), "utf-8"); |
288 | mail->setBody(getBody().toUtf8()); | ||
289 | if (!mail->messageID()) { | 395 | if (!mail->messageID()) { |
290 | mail->messageID(true)->generate("org.kde.kube"); | 396 | mail->messageID(true)->generate("org.kde.kube"); |
291 | } | 397 | } |
@@ -293,6 +399,45 @@ KMime::Message::Ptr ComposerController::assembleMessage() | |||
293 | mail->date(true)->setDateTime(QDateTime::currentDateTimeUtc()); | 399 | mail->date(true)->setDateTime(QDateTime::currentDateTimeUtc()); |
294 | } | 400 | } |
295 | 401 | ||
402 | auto root = mAttachmentModel->invisibleRootItem(); | ||
403 | if (root->hasChildren()) { | ||
404 | mail->contentType(true)->setMimeType("multipart/mixed"); | ||
405 | mail->contentType()->setBoundary(KMime::multiPartBoundary()); | ||
406 | mail->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); | ||
407 | mail->setPreamble("This is a multi-part message in MIME format.\n"); | ||
408 | for (int row = 0; row < root->rowCount(); row++) { | ||
409 | auto item = root->child(row, 0); | ||
410 | const auto name = item->data(NameRole).toString(); | ||
411 | const auto filename = item->data(FilenameRole).toString(); | ||
412 | const auto mimeType = item->data(MimeTypeRole).toByteArray(); | ||
413 | const auto isInline = item->data(InlineRole).toBool(); | ||
414 | const auto content = item->data(ContentRole).toByteArray(); | ||
415 | |||
416 | KMime::Content *part = new KMime::Content; | ||
417 | part->contentDisposition(true)->setFilename(filename); | ||
418 | if (isInline) { | ||
419 | part->contentDisposition(true)->setDisposition(KMime::Headers::CDinline); | ||
420 | } else { | ||
421 | part->contentDisposition(true)->setDisposition(KMime::Headers::CDattachment); | ||
422 | } | ||
423 | part->contentType(true)->setMimeType(mimeType); | ||
424 | part->contentType(true)->setName(name, "utf-8"); | ||
425 | //Just always encode attachments base64 so it's safe for binary data | ||
426 | part->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEbase64); | ||
427 | part->setBody(content); | ||
428 | |||
429 | mail->addContent(part); | ||
430 | |||
431 | auto mainMessage = new KMime::Content; | ||
432 | mainMessage->setBody(getBody().toUtf8()); | ||
433 | mainMessage->contentType(true)->setMimeType("text/plain"); | ||
434 | mail->addContent(mainMessage); | ||
435 | } | ||
436 | } else { | ||
437 | //FIXME same implementation as above for attachments | ||
438 | mail->setBody(getBody().toUtf8()); | ||
439 | } | ||
440 | |||
296 | mail->assemble(); | 441 | mail->assemble(); |
297 | return mail; | 442 | return mail; |
298 | } | 443 | } |
diff --git a/framework/src/domain/composercontroller.h b/framework/src/domain/composercontroller.h index 87349d0c..de154b66 100644 --- a/framework/src/domain/composercontroller.h +++ b/framework/src/domain/composercontroller.h | |||
@@ -24,6 +24,7 @@ | |||
24 | #include <QString> | 24 | #include <QString> |
25 | #include <QStringList> | 25 | #include <QStringList> |
26 | #include <QStringListModel> | 26 | #include <QStringListModel> |
27 | #include <QStandardItemModel> | ||
27 | #include <QVariant> | 28 | #include <QVariant> |
28 | #include <sink/applicationdomaintype.h> | 29 | #include <sink/applicationdomaintype.h> |
29 | #include <KMime/Message> | 30 | #include <KMime/Message> |
@@ -66,6 +67,7 @@ class ComposerController : public Kube::Controller | |||
66 | Q_PROPERTY (QAbstractItemModel* toModel READ toModel CONSTANT) | 67 | Q_PROPERTY (QAbstractItemModel* toModel READ toModel CONSTANT) |
67 | Q_PROPERTY (QAbstractItemModel* ccModel READ ccModel CONSTANT) | 68 | Q_PROPERTY (QAbstractItemModel* ccModel READ ccModel CONSTANT) |
68 | Q_PROPERTY (QAbstractItemModel* bccModel READ bccModel CONSTANT) | 69 | Q_PROPERTY (QAbstractItemModel* bccModel READ bccModel CONSTANT) |
70 | Q_PROPERTY (QAbstractItemModel* attachmentModel READ attachmentModel CONSTANT) | ||
69 | 71 | ||
70 | KUBE_CONTROLLER_ACTION(send) | 72 | KUBE_CONTROLLER_ACTION(send) |
71 | KUBE_CONTROLLER_ACTION(saveAsDraft) | 73 | KUBE_CONTROLLER_ACTION(saveAsDraft) |
@@ -81,6 +83,7 @@ public: | |||
81 | QAbstractItemModel *toModel() const; | 83 | QAbstractItemModel *toModel() const; |
82 | QAbstractItemModel *ccModel() const; | 84 | QAbstractItemModel *ccModel() const; |
83 | QAbstractItemModel *bccModel() const; | 85 | QAbstractItemModel *bccModel() const; |
86 | QAbstractItemModel *attachmentModel() const; | ||
84 | 87 | ||
85 | Q_INVOKABLE void addTo(const QString &); | 88 | Q_INVOKABLE void addTo(const QString &); |
86 | Q_INVOKABLE void removeTo(const QString &); | 89 | Q_INVOKABLE void removeTo(const QString &); |
@@ -88,6 +91,8 @@ public: | |||
88 | Q_INVOKABLE void removeCc(const QString &); | 91 | Q_INVOKABLE void removeCc(const QString &); |
89 | Q_INVOKABLE void addBcc(const QString &); | 92 | Q_INVOKABLE void addBcc(const QString &); |
90 | Q_INVOKABLE void removeBcc(const QString &); | 93 | Q_INVOKABLE void removeBcc(const QString &); |
94 | Q_INVOKABLE void addAttachment(const QUrl &); | ||
95 | Q_INVOKABLE void removeAttachment(const QString &); | ||
91 | 96 | ||
92 | public slots: | 97 | public slots: |
93 | virtual void clear() Q_DECL_OVERRIDE; | 98 | virtual void clear() Q_DECL_OVERRIDE; |
@@ -97,8 +102,19 @@ private slots: | |||
97 | void updateSaveAsDraftAction(); | 102 | void updateSaveAsDraftAction(); |
98 | 103 | ||
99 | private: | 104 | private: |
105 | enum AttachmentRoles { | ||
106 | NameRole = Qt::UserRole + 1, | ||
107 | FilenameRole, | ||
108 | ContentRole, | ||
109 | MimeTypeRole, | ||
110 | DescriptionRole, | ||
111 | InlineRole, | ||
112 | IconNameRole | ||
113 | }; | ||
114 | |||
100 | void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName); | 115 | void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName); |
101 | void setMessage(const QSharedPointer<KMime::Message> &msg); | 116 | void setMessage(const QSharedPointer<KMime::Message> &msg); |
117 | void addAttachmentPart(KMime::Content *partToAttach); | ||
102 | KMime::Message::Ptr assembleMessage(); | 118 | KMime::Message::Ptr assembleMessage(); |
103 | 119 | ||
104 | QScopedPointer<Completer> mRecipientCompleter; | 120 | QScopedPointer<Completer> mRecipientCompleter; |
@@ -106,4 +122,5 @@ private: | |||
106 | QScopedPointer<QStringListModel> mToModel; | 122 | QScopedPointer<QStringListModel> mToModel; |
107 | QScopedPointer<QStringListModel> mCcModel; | 123 | QScopedPointer<QStringListModel> mCcModel; |
108 | QScopedPointer<QStringListModel> mBccModel; | 124 | QScopedPointer<QStringListModel> mBccModel; |
125 | QScopedPointer<QStandardItemModel> mAttachmentModel; | ||
109 | }; | 126 | }; |