summaryrefslogtreecommitdiffstats
path: root/framework/src/domain/composercontroller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/domain/composercontroller.cpp')
-rw-r--r--framework/src/domain/composercontroller.cpp307
1 files changed, 307 insertions, 0 deletions
diff --git a/framework/src/domain/composercontroller.cpp b/framework/src/domain/composercontroller.cpp
new file mode 100644
index 00000000..3328d9eb
--- /dev/null
+++ b/framework/src/domain/composercontroller.cpp
@@ -0,0 +1,307 @@
1/*
2 Copyright (c) 2016 Michael Bohlender <michael.bohlender@kdemail.net>
3 Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
4
5 This library is free software; you can redistribute it and/or modify it
6 under the terms of the GNU Library General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or (at your
8 option) any later version.
9
10 This library is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13 License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
19*/
20
21
22#include "composercontroller.h"
23#include <settings/settings.h>
24#include <KMime/Message>
25#include <KCodecs/KEmailAddress>
26#include <QVariant>
27#include <QSortFilterProxyModel>
28#include <QList>
29#include <QDebug>
30#include <QQmlEngine>
31#include <sink/store.h>
32#include <sink/log.h>
33
34#include "identitiesmodel.h"
35#include "recepientautocompletionmodel.h"
36#include "mailtemplates.h"
37
38SINK_DEBUG_AREA("composercontroller");
39
40class IdentitySelector : public Selector {
41public:
42 IdentitySelector(ComposerController &controller) : Selector(new IdentitiesModel), mController(controller)
43 {
44 }
45
46 void setCurrent(const QModelIndex &index) Q_DECL_OVERRIDE
47 {
48 if (index.isValid()) {
49 auto currentAccountId = index.data(IdentitiesModel::AccountId).toByteArray();
50
51 KMime::Types::Mailbox mb;
52 mb.setName(index.data(IdentitiesModel::Username).toString());
53 mb.setAddress(index.data(IdentitiesModel::Address).toString().toUtf8());
54 SinkLog() << "Setting current identity: " << mb.prettyAddress() << "Account: " << currentAccountId;
55
56 mController.setIdentity(mb);
57 mController.setAccountId(currentAccountId);
58 } else {
59 SinkWarning() << "No valid identity for index: " << index;
60 mController.clearIdentity();
61 mController.clearAccountId();
62 }
63
64 }
65private:
66 ComposerController &mController;
67};
68
69class RecipientCompleter : public Completer {
70public:
71 RecipientCompleter() : Completer(new RecipientAutocompletionModel)
72 {
73 }
74
75 void setSearchString(const QString &s) {
76 static_cast<RecipientAutocompletionModel*>(model())->setFilter(s);
77 Completer::setSearchString(s);
78 }
79};
80
81
82ComposerController::ComposerController()
83 : Kube::Controller(),
84 action_send{new Kube::ControllerAction{this, &ComposerController::send}},
85 action_saveAsDraft{new Kube::ControllerAction{this, &ComposerController::saveAsDraft}},
86 mRecipientCompleter{new RecipientCompleter},
87 mIdentitySelector{new IdentitySelector{*this}}
88{
89 updateSaveAsDraftAction();
90 // mSendAction->monitorProperty<To>();
91 // mSendAction->monitorProperty<Send>([] (const QString &) -> bool{
92 // //validate
93 // });
94 // registerAction<ControllerAction>(&ComposerController::send);
95 // actionDepends<ControllerAction, To, Subject>();
96 // TODO do in constructor
97
98 QObject::connect(this, &ComposerController::toChanged, &ComposerController::updateSendAction);
99 QObject::connect(this, &ComposerController::subjectChanged, &ComposerController::updateSendAction);
100 QObject::connect(this, &ComposerController::accountIdChanged, &ComposerController::updateSendAction);
101 QObject::connect(this, &ComposerController::toChanged, &ComposerController::updateSaveAsDraftAction);
102 QObject::connect(this, &ComposerController::subjectChanged, &ComposerController::updateSaveAsDraftAction);
103 QObject::connect(this, &ComposerController::accountIdChanged, &ComposerController::updateSaveAsDraftAction);
104 updateSendAction();
105}
106
107void ComposerController::clear()
108{
109 Controller::clear();
110 //Reapply account and identity from selection
111 mIdentitySelector->reapplyCurrentIndex();
112}
113
114Completer *ComposerController::recipientCompleter() const
115{
116 return mRecipientCompleter.data();
117}
118
119Selector *ComposerController::identitySelector() const
120{
121 return mIdentitySelector.data();
122}
123
124void ComposerController::setMessage(const KMime::Message::Ptr &msg)
125{
126 setTo(msg->to(true)->asUnicodeString());
127 setCc(msg->cc(true)->asUnicodeString());
128 setSubject(msg->subject(true)->asUnicodeString());
129 setBody(msg->body());
130 setExistingMessage(msg);
131}
132
133void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft)
134{
135 using namespace Sink;
136 using namespace Sink::ApplicationDomain;
137
138 Query query(*message.value<Mail::Ptr>());
139 query.request<Mail::MimeMessage>();
140 Store::fetchOne<Mail>(query).then([this, loadAsDraft](const Mail &mail) {
141 setExistingMail(mail);
142
143 //TODO this should probably happen as reaction to the property being set.
144 const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage());
145 if (!mailData.isEmpty()) {
146 KMime::Message::Ptr mail(new KMime::Message);
147 mail->setContent(mailData);
148 mail->parse();
149 if (loadAsDraft) {
150 setMessage(mail);
151 } else {
152 auto reply = MailTemplates::reply(mail);
153 //We assume reply
154 setMessage(reply);
155 }
156 } else {
157 qWarning() << "Retrieved empty message";
158 }
159 }).exec();
160}
161
162void ComposerController::recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName)
163{
164 if (auto model = static_cast<RecipientAutocompletionModel*>(recipientCompleter()->model())) {
165 model->addEntry(addrSpec, displayName);
166 }
167}
168
169void applyAddresses(const QString &list, std::function<void(const QByteArray &, const QByteArray &)> callback)
170{
171 for (const auto &to : KEmailAddress::splitAddressList(list)) {
172 QByteArray displayName;
173 QByteArray addrSpec;
174 QByteArray comment;
175 KEmailAddress::splitAddress(to.toUtf8(), displayName, addrSpec, comment);
176 callback(addrSpec, displayName);
177 }
178}
179
180KMime::Message::Ptr ComposerController::assembleMessage()
181{
182 auto mail = mExistingMessage;
183 if (!mail) {
184 mail = KMime::Message::Ptr::create();
185 }
186 applyAddresses(getTo(), [&](const QByteArray &addrSpec, const QByteArray &displayName) {
187 mail->to(true)->addAddress(addrSpec, displayName);
188 recordForAutocompletion(addrSpec, displayName);
189 });
190 applyAddresses(getCc(), [&](const QByteArray &addrSpec, const QByteArray &displayName) {
191 mail->cc(true)->addAddress(addrSpec, displayName);
192 recordForAutocompletion(addrSpec, displayName);
193 });
194 applyAddresses(getBcc(), [&](const QByteArray &addrSpec, const QByteArray &displayName) {
195 mail->bcc(true)->addAddress(addrSpec, displayName);
196 recordForAutocompletion(addrSpec, displayName);
197 });
198
199 mail->from(true)->addAddress(getIdentity());
200
201 mail->subject(true)->fromUnicodeString(getSubject(), "utf-8");
202 mail->setBody(getBody().toUtf8());
203 if (!mail->messageID()) {
204 mail->messageID(true)->generate("org.kde.kube");
205 }
206 if (!mail->date(true)->dateTime().isValid()) {
207 mail->date(true)->setDateTime(QDateTime::currentDateTimeUtc());
208 }
209
210 mail->assemble();
211 return mail;
212}
213
214void ComposerController::updateSendAction()
215{
216 auto enabled = !getTo().isEmpty() && !getSubject().isEmpty() && !getAccountId().isEmpty();
217 sendAction()->setEnabled(enabled);
218}
219
220void ComposerController::send()
221{
222 // verify<To, Subject>()
223 // && verify<Subject>();
224 auto message = assembleMessage();
225
226 auto accountId = getAccountId();
227 //SinkLog() << "Sending a mail: " << *this;
228 using namespace Sink;
229 using namespace Sink::ApplicationDomain;
230
231 Q_ASSERT(!accountId.isEmpty());
232 Query query;
233 query.containsFilter<SinkResource::Capabilities>(ResourceCapabilities::Mail::transport);
234 query.filter<SinkResource::Account>(accountId);
235 auto job = Store::fetchAll<SinkResource>(query)
236 .then([=](const QList<SinkResource::Ptr> &resources) {
237 if (!resources.isEmpty()) {
238 auto resourceId = resources[0]->identifier();
239 SinkLog() << "Sending message via resource: " << resourceId;
240 Mail mail(resourceId);
241 mail.setMimeMessage(message->encodedContent());
242 return Store::create(mail)
243 .then<void>([=] {
244 //Trigger a sync, but don't wait for it.
245 Store::synchronize(Sink::SyncScope{}.resourceFilter(resourceId)).exec();
246 });
247 }
248 SinkWarning() << "Failed to find a mailtransport resource";
249 return KAsync::error<void>(0, "Failed to find a MailTransport resource.");
250 })
251 .then([&] (const KAsync::Error &error) {
252 SinkLog() << "Message was sent: ";
253 emit done();
254 });
255 run(job);
256}
257
258void ComposerController::updateSaveAsDraftAction()
259{
260 bool enabled = !getAccountId().isEmpty();
261 sendAction()->setEnabled(enabled);
262}
263
264void ComposerController::saveAsDraft()
265{
266 SinkLog() << "Save as draft";
267 const auto accountId = getAccountId();
268 auto existingMail = getExistingMail();
269
270 auto message = assembleMessage();
271 //FIXME this is something for the validation
272 if (!message) {
273 SinkWarning() << "Failed to get the mail: ";
274 return;
275 }
276
277 using namespace Sink;
278 using namespace Sink::ApplicationDomain;
279
280 auto job = [&] {
281 if (existingMail.identifier().isEmpty()) {
282 SinkLog() << "Creating a new draft" << existingMail.identifier();
283 Query query;
284 query.containsFilter<SinkResource::Capabilities>(ResourceCapabilities::Mail::drafts);
285 query.filter<SinkResource::Account>(accountId);
286 return Store::fetchOne<SinkResource>(query)
287 .then([=](const SinkResource &resource) {
288 Mail mail(resource.identifier());
289 mail.setDraft(true);
290 mail.setMimeMessage(message->encodedContent());
291 return Store::create(mail);
292 })
293 .onError([] (const KAsync::Error &error) {
294 SinkWarning() << "Error while creating draft: " << error.errorMessage;
295 });
296 } else {
297 SinkLog() << "Modifying an existing mail" << existingMail.identifier();
298 existingMail.setDraft(true);
299 existingMail.setMimeMessage(message->encodedContent());
300 return Store::modify(existingMail);
301 }
302 }();
303 job = job.then([&] (const KAsync::Error &) {
304 emit done();
305 });
306 run(job);
307}