diff options
Diffstat (limited to 'framework/src/domain/composercontroller.cpp')
-rw-r--r-- | framework/src/domain/composercontroller.cpp | 307 |
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 | |||
38 | SINK_DEBUG_AREA("composercontroller"); | ||
39 | |||
40 | class IdentitySelector : public Selector { | ||
41 | public: | ||
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 | } | ||
65 | private: | ||
66 | ComposerController &mController; | ||
67 | }; | ||
68 | |||
69 | class RecipientCompleter : public Completer { | ||
70 | public: | ||
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 | |||
82 | ComposerController::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 | |||
107 | void ComposerController::clear() | ||
108 | { | ||
109 | Controller::clear(); | ||
110 | //Reapply account and identity from selection | ||
111 | mIdentitySelector->reapplyCurrentIndex(); | ||
112 | } | ||
113 | |||
114 | Completer *ComposerController::recipientCompleter() const | ||
115 | { | ||
116 | return mRecipientCompleter.data(); | ||
117 | } | ||
118 | |||
119 | Selector *ComposerController::identitySelector() const | ||
120 | { | ||
121 | return mIdentitySelector.data(); | ||
122 | } | ||
123 | |||
124 | void 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 | |||
133 | void 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 | |||
162 | void 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 | |||
169 | void 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 | |||
180 | KMime::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 | |||
214 | void ComposerController::updateSendAction() | ||
215 | { | ||
216 | auto enabled = !getTo().isEmpty() && !getSubject().isEmpty() && !getAccountId().isEmpty(); | ||
217 | sendAction()->setEnabled(enabled); | ||
218 | } | ||
219 | |||
220 | void 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 | |||
258 | void ComposerController::updateSaveAsDraftAction() | ||
259 | { | ||
260 | bool enabled = !getAccountId().isEmpty(); | ||
261 | sendAction()->setEnabled(enabled); | ||
262 | } | ||
263 | |||
264 | void 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 | } | ||