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 | |
38 | using namespace Akonadi; |
39 | |
40 | class Akonadi::ItemCreateJobPrivate : public JobPrivate |
41 | { |
42 | public: |
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 | |
62 | QByteArray ItemCreateJobPrivate::() |
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 | |
90 | ItemCreateJob::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 | |
101 | ItemCreateJob::~ItemCreateJob() |
102 | { |
103 | } |
104 | |
105 | void 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 | |
193 | void 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 | |
248 | void ItemCreateJob::setMerge(ItemCreateJob::MergeOptions options) |
249 | { |
250 | Q_D(ItemCreateJob); |
251 | |
252 | d->mMergeOptions = options; |
253 | } |
254 | |
255 | Item 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 | |