1/*
2 Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
3 Copyright (c) 2007 Robert Zwerus <arzie@dds.nl>
4 Copyright (c) 2014 Daniel Vrátil <dvratil@redhat.com>
5
6 This library is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Library General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at your
9 option) any later version.
10
11 This library is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14 License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301, USA.
20*/
21
22#include "itemcreatejob.h"
23
24#include "collection.h"
25#include "imapparser_p.h"
26#include "item.h"
27#include "item_p.h"
28#include "itemserializer_p.h"
29#include "job_p.h"
30#include "protocolhelper_p.h"
31#include "gid/gidextractor_p.h"
32
33#include <QtCore/QDateTime>
34#include <QtCore/QFile>
35
36#include <kdebug.h>
37
38using namespace Akonadi;
39
40class Akonadi::ItemCreateJobPrivate : public JobPrivate
41{
42public:
43 ItemCreateJobPrivate(ItemCreateJob *parent)
44 : JobPrivate(parent)
45 , mMergeOptions(ItemCreateJob::NoMerge)
46 , mItemReceived(false)
47 {
48 }
49
50 QByteArray nextPartHeader();
51
52 Collection mCollection;
53 Item mItem;
54 QSet<QByteArray> mParts;
55 Item::Id mUid;
56 QDateTime mDatetime;
57 QByteArray mPendingData;
58 ItemCreateJob::MergeOptions mMergeOptions;
59 bool mItemReceived;
60};
61
62QByteArray ItemCreateJobPrivate::nextPartHeader()
63{
64 QByteArray command;
65 if (!mParts.isEmpty()) {
66 QSetIterator<QByteArray> it(mParts);
67 const QByteArray label = it.next();
68 mParts.remove(label);
69
70 mPendingData.clear();
71 int version = 0;
72 ItemSerializer::serialize(mItem, label, mPendingData, version);
73 command += ' ' + ProtocolHelper::encodePartIdentifier(ProtocolHelper::PartPayload, label, version);
74 if (mPendingData.size() > 0) {
75 command += " {" + QByteArray::number(mPendingData.size()) + "}\n";
76 } else {
77 if (mPendingData.isNull()) {
78 command += " NIL";
79 } else {
80 command += " \"\"";
81 }
82 command += nextPartHeader();
83 }
84 } else {
85 command += ")\n";
86 }
87 return command;
88}
89
90ItemCreateJob::ItemCreateJob(const Item &item, const Collection &collection, QObject *parent)
91 : Job(new ItemCreateJobPrivate(this), parent)
92{
93 Q_D(ItemCreateJob);
94
95 Q_ASSERT(!item.mimeType().isEmpty());
96 d->mItem = item;
97 d->mParts = d->mItem.loadedPayloadParts();
98 d->mCollection = collection;
99}
100
101ItemCreateJob::~ItemCreateJob()
102{
103}
104
105void ItemCreateJob::doStart()
106{
107 Q_D(ItemCreateJob);
108
109 QByteArray remoteId;
110
111 QList<QByteArray> flags;
112 flags.append("\\MimeType[" + d->mItem.mimeType().toLatin1() + ']');
113 const QString gid = GidExtractor::getGid(d->mItem);
114 if (!gid.isNull()) {
115 flags.append(ImapParser::quote("\\Gid[" + gid.toUtf8() + ']'));
116 }
117 if (!d->mItem.remoteId().isEmpty()) {
118 flags.append(ImapParser::quote("\\RemoteId[" + d->mItem.remoteId().toUtf8() + ']'));
119 }
120 if (!d->mItem.remoteRevision().isEmpty()) {
121 flags.append(ImapParser::quote("\\RemoteRevision[" + d->mItem.remoteRevision().toUtf8() + ']'));
122 }
123 const bool mergeByGid = (d->mMergeOptions & GID) && !d->mItem.gid().isEmpty();
124 const bool mergeByRid = (d->mMergeOptions & RID) && !d->mItem.remoteId().isEmpty();
125 const bool mergeSilent = (d->mMergeOptions & Silent);
126 const bool merge = mergeByGid || mergeByRid;
127 if (d->mItem.d_func()->mFlagsOverwritten || !merge) {
128 flags += d->mItem.flags().toList();
129 } else {
130 Q_FOREACH(const QByteArray &flag, d->mItem.d_func()->mAddedFlags.toList()) {
131 flags += "+" + flag;
132 }
133 Q_FOREACH(const QByteArray &flag, d->mItem.d_func()->mDeletedFlags.toList()) {
134 flags += "-" + flag;
135 }
136 }
137 if (d->mItem.d_func()->mTagsOverwritten || !merge) {
138 Q_FOREACH(const Akonadi::Tag &tag, d->mItem.d_func()->mAddedTags) {
139 if (tag.gid().isEmpty() && !tag.remoteId().isEmpty()) {
140 flags += "\\RTag[" + tag.remoteId() + ']';
141 } else if (!tag.gid().isEmpty()) {
142 flags += "\\Tag[" + tag.gid() + ']';
143 }
144 }
145 } else {
146 Q_FOREACH(const Akonadi::Tag &tag, d->mItem.d_func()->mAddedTags) {
147 if (tag.gid().isEmpty() && !tag.remoteId().isEmpty()) {
148 flags += "+\\RTag[" + tag.remoteId() + ']';
149 } else if (!tag.gid().isEmpty()) {
150 flags += "+\\Tag[" + tag.gid() + ']';
151 }
152 }
153 Q_FOREACH(const Akonadi::Tag &tag, d->mItem.d_func()->mDeletedTags) {
154 if (tag.gid().isEmpty() && !tag.remoteId().isEmpty()) {
155 flags += "-\\RTag[" + tag.remoteId() + ']';
156 } else if (!tag.gid().isEmpty()) {
157 flags += "-\\Tag[" + tag.gid() + ']';
158 }
159 }
160 }
161
162 QByteArray command = d->newTag();
163 if (merge) {
164 QList<QByteArray> mergeArgs;
165 if (mergeByGid) {
166 mergeArgs << "GID";
167 }
168 if (mergeByRid) {
169 mergeArgs << "REMOTEID";
170 }
171 if (mergeSilent) {
172 mergeArgs << "SILENT";
173 }
174 command += " MERGE (" + ImapParser::join(mergeArgs, " ") + ") ";
175 } else {
176 command += " X-AKAPPEND ";
177 }
178
179 command += QByteArray::number(d->mCollection.id())
180 + ' ' + QByteArray::number(d->mItem.size())
181 + " (" + ImapParser::join(flags, " ") + ")"
182 + " ("; // list of parts
183 const QByteArray attrs = ProtocolHelper::attributesToByteArray(d->mItem, true);
184 if (!attrs.isEmpty()) {
185 command += attrs;
186 }
187
188 command += d->nextPartHeader();
189
190 d->writeData(command);
191}
192
193void ItemCreateJob::doHandleResponse(const QByteArray &tag, const QByteArray &data)
194{
195 Q_D(ItemCreateJob);
196
197 if (tag == "+") { // ready for literal data
198 if (data.startsWith("STREAM")) {
199 QByteArray error;
200 if (!ProtocolHelper::streamPayloadToFile(data, d->mPendingData, error)) {
201 d->writeData("* NO " + error);
202 return;
203 }
204 } else {
205 d->writeData(d->mPendingData);
206 }
207 d->writeData(d->nextPartHeader());
208 return;
209 }
210 if (tag == "*") {
211 int begin = data.indexOf("FETCH");
212 if (begin >= 0) {
213 QList<QByteArray> fetchResponse;
214 ImapParser::parseParenthesizedList(data, fetchResponse, begin + 6);
215
216 Item item;
217 ProtocolHelper::parseItemFetchResult(fetchResponse, item);
218 if (!item.isValid()) {
219 // Error, maybe?
220 return;
221 }
222 d->mItemReceived = true;
223 d->mItem = item;
224 }
225 return;
226 }
227 if (tag == d->tag()) {
228 int uidNextPos = data.indexOf("UIDNEXT");
229 if (uidNextPos != -1) {
230 bool ok = false;
231 ImapParser::parseNumber(data, d->mUid, &ok, uidNextPos + 7);
232 if (!ok) {
233 kDebug() << "Invalid UIDNEXT response to APPEND command: "
234 << tag << data;
235 }
236 }
237 int dateTimePos = data.indexOf("DATETIME");
238 if (dateTimePos != -1) {
239 int resultPos = ImapParser::parseDateTime(data, d->mDatetime, dateTimePos + 8);
240 if (resultPos == (dateTimePos + 8)) {
241 kDebug() << "Invalid DATETIME response to APPEND command: "
242 << tag << data;
243 }
244 }
245 }
246}
247
248void ItemCreateJob::setMerge(ItemCreateJob::MergeOptions options)
249{
250 Q_D(ItemCreateJob);
251
252 d->mMergeOptions = options;
253}
254
255Item ItemCreateJob::item() const
256{
257 Q_D(const ItemCreateJob);
258
259 if (d->mItemReceived) {
260 return d->mItem;
261 }
262
263 if (d->mUid == 0) {
264 return Item();
265 }
266
267 Item item(d->mItem);
268 item.setId(d->mUid);
269 item.setRevision(0);
270 item.setModificationTime(d->mDatetime);
271 item.setParentCollection(d->mCollection);
272 item.setStorageCollectionId(d->mCollection.id());
273
274 return item;
275}
276