1 | /* |
2 | Copyright (c) 2008 Volker Krause <vkrause@kde.org> |
3 | |
4 | This library is free software; you can redistribute it and/or modify it |
5 | under the terms of the GNU Library General Public License as published by |
6 | the Free Software Foundation; either version 2 of the License, or (at your |
7 | option) any later version. |
8 | |
9 | This library is distributed in the hope that it will be useful, but WITHOUT |
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public |
12 | License for more details. |
13 | |
14 | You should have received a copy of the GNU Library General Public License |
15 | along with this library; see the file COPYING.LIB. If not, write to the |
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
17 | 02110-1301, USA. |
18 | */ |
19 | |
20 | #include "protocolhelper_p.h" |
21 | |
22 | #include "attributefactory.h" |
23 | #include "collectionstatistics.h" |
24 | #include "entity_p.h" |
25 | #include "exception.h" |
26 | #include "itemserializer_p.h" |
27 | #include "itemserializerplugin.h" |
28 | #include "servermanager.h" |
29 | #include <akonadi/private/xdgbasedirs_p.h> |
30 | |
31 | #include <QtCore/QDateTime> |
32 | #include <QtCore/QFile> |
33 | #include <QtCore/QVarLengthArray> |
34 | #include <QtCore/QFileInfo> |
35 | #include <QtCore/QDir> |
36 | |
37 | #include <kdebug.h> |
38 | #include <klocalizedstring.h> |
39 | |
40 | using namespace Akonadi; |
41 | |
42 | int ProtocolHelper::parseCachePolicy(const QByteArray & data, CachePolicy & policy, int start) |
43 | { |
44 | QVarLengthArray<QByteArray,16> params; |
45 | int end = Akonadi::ImapParser::parseParenthesizedList( data, params, start ); |
46 | for ( int i = 0; i < params.count() - 1; i += 2 ) { |
47 | const QByteArray key = params[i]; |
48 | const QByteArray value = params[i + 1]; |
49 | |
50 | if ( key == "INHERIT" ) |
51 | policy.setInheritFromParent( value == "true" ); |
52 | else if ( key == "INTERVAL" ) |
53 | policy.setIntervalCheckTime( value.toInt() ); |
54 | else if ( key == "CACHETIMEOUT" ) |
55 | policy.setCacheTimeout( value.toInt() ); |
56 | else if ( key == "SYNCONDEMAND" ) |
57 | policy.setSyncOnDemand( value == "true" ); |
58 | else if ( key == "LOCALPARTS" ) { |
59 | QVarLengthArray<QByteArray,16> tmp; |
60 | QStringList parts; |
61 | Akonadi::ImapParser::parseParenthesizedList( value, tmp ); |
62 | for ( int j=0; j<tmp.size(); j++ ) |
63 | parts << QString::fromLatin1( tmp[j] ); |
64 | policy.setLocalParts( parts ); |
65 | } |
66 | } |
67 | return end; |
68 | } |
69 | |
70 | QByteArray ProtocolHelper::cachePolicyToByteArray(const CachePolicy & policy) |
71 | { |
72 | QByteArray rv = "CACHEPOLICY (" ; |
73 | if ( policy.inheritFromParent() ) { |
74 | rv += "INHERIT true" ; |
75 | } else { |
76 | rv += "INHERIT false" ; |
77 | rv += " INTERVAL " + QByteArray::number( policy.intervalCheckTime() ); |
78 | rv += " CACHETIMEOUT " + QByteArray::number( policy.cacheTimeout() ); |
79 | rv += " SYNCONDEMAND " + ( policy.syncOnDemand() ? QByteArray("true" ) : QByteArray("false" ) ); |
80 | rv += " LOCALPARTS (" + policy.localParts().join( QLatin1String(" " ) ).toLatin1() + ')'; |
81 | } |
82 | rv += ')'; |
83 | return rv; |
84 | } |
85 | |
86 | void ProtocolHelper::parseAncestorsCached( const QByteArray &data, Entity *entity, Collection::Id parentCollection, |
87 | ProtocolHelperValuePool *pool, int start ) |
88 | { |
89 | if ( !pool || parentCollection == -1 ) { |
90 | // if no pool or parent collection id is provided we can't cache anything, so continue as usual |
91 | parseAncestors( data, entity, start ); |
92 | return; |
93 | } |
94 | |
95 | if ( pool->ancestorCollections.contains( parentCollection ) ) { |
96 | // ancestor chain is cached already, so use the cached value |
97 | entity->setParentCollection( pool->ancestorCollections.value( parentCollection ) ); |
98 | } else { |
99 | // not cached yet, parse the chain |
100 | parseAncestors( data, entity, start ); |
101 | pool->ancestorCollections.insert( parentCollection, entity->parentCollection() ); |
102 | } |
103 | } |
104 | |
105 | void ProtocolHelper::parseAncestors( const QByteArray &data, Entity *entity, int start ) |
106 | { |
107 | Q_UNUSED( start ); |
108 | |
109 | static const Collection::Id rootCollectionId = Collection::root().id(); |
110 | QVarLengthArray<QByteArray, 16> ancestors; |
111 | QVarLengthArray<QByteArray, 16> parentIds; |
112 | |
113 | ImapParser::parseParenthesizedList( data, ancestors ); |
114 | Entity* current = entity; |
115 | for ( int i = 0; i < ancestors.count(); ++i ) { |
116 | parentIds.clear(); |
117 | ImapParser::parseParenthesizedList( ancestors[ i ], parentIds ); |
118 | if ( parentIds.size() != 2 ) |
119 | break; |
120 | |
121 | const Collection::Id uid = parentIds[ 0 ].toLongLong(); |
122 | if ( uid == rootCollectionId ) { |
123 | current->setParentCollection( Collection::root() ); |
124 | break; |
125 | } |
126 | |
127 | current->parentCollection().setId( uid ); |
128 | current->parentCollection().setRemoteId( QString::fromUtf8( parentIds[ 1 ] ) ); |
129 | current = ¤t->parentCollection(); |
130 | } |
131 | } |
132 | |
133 | static Collection::ListPreference parsePreference( const QByteArray &value ) |
134 | { |
135 | if ( value == "TRUE" ) { |
136 | return Collection::ListEnabled; |
137 | } |
138 | if ( value == "FALSE" ) { |
139 | return Collection::ListDisabled; |
140 | } |
141 | return Collection::ListDefault; |
142 | } |
143 | |
144 | int ProtocolHelper::parseCollection(const QByteArray & data, Collection & collection, int start) |
145 | { |
146 | int pos = start; |
147 | |
148 | // collection and parent id |
149 | Collection::Id colId = -1; |
150 | bool ok = false; |
151 | pos = ImapParser::parseNumber( data, colId, &ok, pos ); |
152 | if ( !ok || colId <= 0 ) { |
153 | kDebug() << "Could not parse collection id from response:" << data; |
154 | return start; |
155 | } |
156 | |
157 | Collection::Id parentId = -1; |
158 | pos = ImapParser::parseNumber( data, parentId, &ok, pos ); |
159 | if ( !ok || parentId < 0 ) { |
160 | kDebug() << "Could not parse parent id from response:" << data; |
161 | return start; |
162 | } |
163 | |
164 | collection = Collection( colId ); |
165 | collection.setParentCollection( Collection( parentId ) ); |
166 | |
167 | // attributes |
168 | QVarLengthArray<QByteArray,16> attributes; |
169 | pos = ImapParser::parseParenthesizedList( data, attributes, pos ); |
170 | |
171 | for ( int i = 0; i < attributes.count() - 1; i += 2 ) { |
172 | const QByteArray key = attributes[i]; |
173 | const QByteArray value = attributes[i + 1]; |
174 | |
175 | if ( key == "NAME" ) { |
176 | collection.setName( QString::fromUtf8( value ) ); |
177 | } else if ( key == "REMOTEID" ) { |
178 | collection.setRemoteId( QString::fromUtf8( value ) ); |
179 | } else if ( key == "REMOTEREVISION" ) { |
180 | collection.setRemoteRevision( QString::fromUtf8( value ) ); |
181 | } else if ( key == "RESOURCE" ) { |
182 | collection.setResource( QString::fromUtf8( value ) ); |
183 | } else if ( key == "MIMETYPE" ) { |
184 | QVarLengthArray<QByteArray,16> ct; |
185 | ImapParser::parseParenthesizedList( value, ct ); |
186 | QStringList ct2; |
187 | for ( int j = 0; j < ct.size(); j++ ) |
188 | ct2 << QString::fromLatin1( ct[j] ); |
189 | collection.setContentMimeTypes( ct2 ); |
190 | } else if ( key == "VIRTUAL" ) { |
191 | collection.setVirtual( value.toUInt() != 0 ); |
192 | } else if ( key == "MESSAGES" ) { |
193 | CollectionStatistics s = collection.statistics(); |
194 | s.setCount( value.toLongLong() ); |
195 | collection.setStatistics( s ); |
196 | } else if ( key == "UNSEEN" ) { |
197 | CollectionStatistics s = collection.statistics(); |
198 | s.setUnreadCount( value.toLongLong() ); |
199 | collection.setStatistics( s ); |
200 | } else if ( key == "SIZE" ) { |
201 | CollectionStatistics s = collection.statistics(); |
202 | s.setSize( value.toLongLong() ); |
203 | collection.setStatistics( s ); |
204 | } else if ( key == "CACHEPOLICY" ) { |
205 | CachePolicy policy; |
206 | ProtocolHelper::parseCachePolicy( value, policy ); |
207 | collection.setCachePolicy( policy ); |
208 | } else if ( key == "ANCESTORS" ) { |
209 | parseAncestors( value, &collection ); |
210 | } else if ( key == "ENABLED" ) { |
211 | collection.setEnabled( value == "TRUE" ); |
212 | } else if ( key == "DISPLAY" ) { |
213 | collection.setLocalListPreference( Collection::ListDisplay, parsePreference( value ) ); |
214 | } else if ( key == "SYNC" ) { |
215 | collection.setLocalListPreference( Collection::ListSync, parsePreference( value ) ); |
216 | } else if ( key == "INDEX" ) { |
217 | collection.setLocalListPreference( Collection::ListIndex, parsePreference( value ) ); |
218 | } else if ( key == "REFERENCED" ) { |
219 | collection.setReferenced( value == "TRUE" ); |
220 | } else { |
221 | Attribute* attr = AttributeFactory::createAttribute( key ); |
222 | Q_ASSERT( attr ); |
223 | attr->deserialize( value ); |
224 | collection.addAttribute( attr ); |
225 | } |
226 | } |
227 | |
228 | return pos; |
229 | } |
230 | |
231 | QByteArray ProtocolHelper::attributesToByteArray(const Entity & entity, bool ns ) |
232 | { |
233 | QList<QByteArray> l; |
234 | foreach ( const Attribute *attr, entity.attributes() ) { |
235 | l << encodePartIdentifier( ns ? PartAttribute : PartGlobal, attr->type() ); |
236 | l << ImapParser::quote( attr->serialized() ); |
237 | } |
238 | return ImapParser::join( l, " " ); |
239 | } |
240 | |
241 | QByteArray ProtocolHelper::attributesToByteArray(const AttributeEntity & entity, bool ns ) |
242 | { |
243 | QList<QByteArray> l; |
244 | foreach ( const Attribute *attr, entity.attributes() ) { |
245 | l << encodePartIdentifier( ns ? PartAttribute : PartGlobal, attr->type() ); |
246 | l << ImapParser::quote( attr->serialized() ); |
247 | } |
248 | return ImapParser::join( l, " " ); |
249 | } |
250 | |
251 | QByteArray ProtocolHelper::encodePartIdentifier(PartNamespace ns, const QByteArray & label, int version ) |
252 | { |
253 | const QByteArray versionString( version != 0 ? QByteArray(QByteArray("[" ) + QByteArray::number( version ) + QByteArray("]" )) : "" ); |
254 | switch ( ns ) { |
255 | case PartGlobal: |
256 | return label + versionString; |
257 | case PartPayload: |
258 | return "PLD:" + label + versionString; |
259 | case PartAttribute: |
260 | return "ATR:" + label + versionString; |
261 | default: |
262 | Q_ASSERT( false ); |
263 | } |
264 | return QByteArray(); |
265 | } |
266 | |
267 | QByteArray ProtocolHelper::decodePartIdentifier( const QByteArray &data, PartNamespace & ns ) |
268 | { |
269 | if ( data.startsWith( "PLD:" ) ) { //krazy:exclude=strings |
270 | ns = PartPayload; |
271 | return data.mid( 4 ); |
272 | } else if ( data.startsWith( "ATR:" ) ) { //krazy:exclude=strings |
273 | ns = PartAttribute; |
274 | return data.mid( 4 ); |
275 | } else { |
276 | ns = PartGlobal; |
277 | return data; |
278 | } |
279 | } |
280 | |
281 | QByteArray ProtocolHelper::entitySetToByteArray( const QList<Item> &_objects, const QByteArray &command ) |
282 | { |
283 | if ( _objects.isEmpty() ) |
284 | throw Exception( "No objects specified" ); |
285 | |
286 | Item::List objects( _objects ); |
287 | std::sort( objects.begin(), objects.end(), boost::bind( &Item::id, _1 ) < boost::bind( &Item::id, _2 ) ); |
288 | if ( objects.first().isValid() ) { |
289 | // all items have a uid set |
290 | return entitySetToByteArray<Item>(objects, command); |
291 | } |
292 | // check if all items have a gid |
293 | if ( std::find_if( objects.constBegin(), objects.constEnd(), |
294 | boost::bind( &QString::isEmpty, boost::bind( &Item::gid, _1 ) ) ) |
295 | == objects.constEnd() ) |
296 | { |
297 | QList<QByteArray> gids; |
298 | foreach ( const Item &object, objects ) { |
299 | gids << ImapParser::quote( object.gid().toUtf8() ); |
300 | } |
301 | |
302 | QByteArray rv; |
303 | //rv += " " AKONADI_CMD_GID " "; |
304 | rv += " " "GID" " " ; |
305 | if ( !command.isEmpty() ) { |
306 | rv += command; |
307 | rv += ' '; |
308 | } |
309 | rv += '('; |
310 | rv += ImapParser::join( gids, " " ); |
311 | rv += ')'; |
312 | return rv; |
313 | } |
314 | return entitySetToByteArray<Item>(objects, command); |
315 | } |
316 | |
317 | QByteArray ProtocolHelper::tagSetToImapSequenceSet( const Akonadi::Tag::List &_objects ) |
318 | { |
319 | if ( _objects.isEmpty() ) |
320 | throw Exception( "No objects specified" ); |
321 | |
322 | Tag::List objects( _objects ); |
323 | |
324 | std::sort( objects.begin(), objects.end(), boost::bind( &Tag::id, _1 ) < boost::bind( &Tag::id, _2 ) ); |
325 | if ( !objects.first().isValid() ) { |
326 | throw Exception( "Not all tags have a uid" ); |
327 | } |
328 | // all items have a uid set |
329 | QVector<Tag::Id> uids; |
330 | foreach ( const Tag &object, objects ) |
331 | uids << object.id(); |
332 | ImapSet set; |
333 | set.add( uids ); |
334 | return set.toImapSequenceSet(); |
335 | } |
336 | |
337 | QByteArray ProtocolHelper::tagSetToByteArray( const Tag::List &_objects, const QByteArray &command ) |
338 | { |
339 | if ( _objects.isEmpty() ) |
340 | throw Exception( "No objects specified" ); |
341 | |
342 | Tag::List objects( _objects ); |
343 | |
344 | QByteArray rv; |
345 | std::sort( objects.begin(), objects.end(), boost::bind( &Tag::id, _1 ) < boost::bind( &Tag::id, _2 ) ); |
346 | if ( objects.first().isValid() ) { |
347 | // all items have a uid set |
348 | rv += " " AKONADI_CMD_UID " " ; |
349 | if ( !command.isEmpty() ) { |
350 | rv += command; |
351 | rv += ' '; |
352 | } |
353 | QVector<Tag::Id> uids; |
354 | foreach ( const Tag &object, objects ) |
355 | uids << object.id(); |
356 | ImapSet set; |
357 | set.add( uids ); |
358 | rv += set.toImapSequenceSet(); |
359 | return rv; |
360 | } |
361 | throw Exception( "Not all tags have a uid" ); |
362 | } |
363 | |
364 | QByteArray ProtocolHelper::commandContextToByteArray(const Akonadi::Collection &collection, const Akonadi::Tag &tag, |
365 | const Item::List &requestedItems, const QByteArray &command) |
366 | { |
367 | QByteArray r = " " ; |
368 | if (requestedItems.isEmpty()) { |
369 | r += command + " 1:*" ; |
370 | } else { |
371 | r += ProtocolHelper::entitySetToByteArray(requestedItems, command); |
372 | } |
373 | |
374 | if (tag.isValid()) { |
375 | r += " " AKONADI_PARAM_TAGID " " + QByteArray::number(tag.id()) + " " ; |
376 | } |
377 | |
378 | if (collection == Collection::root()) { |
379 | if (requestedItems.isEmpty()) { // collection content listing |
380 | throw Exception("Cannot perform item operations on root collection." ); |
381 | } |
382 | } else { |
383 | if (collection.isValid()) { |
384 | r += " " AKONADI_PARAM_COLLECTIONID " " + QByteArray::number(collection.id()) + ' '; |
385 | } else if (!collection.remoteId().isEmpty()) { |
386 | r += " " AKONADI_PARAM_COLLECTION " " + ImapParser::quote(collection.remoteId().toUtf8()) + ' '; |
387 | } |
388 | } |
389 | |
390 | return r; |
391 | } |
392 | |
393 | |
394 | QByteArray ProtocolHelper::hierarchicalRidToByteArray( const Collection &col ) |
395 | { |
396 | if ( col == Collection::root() ) |
397 | return QByteArray("(0 \"\")" ); |
398 | if ( col.remoteId().isEmpty() ) |
399 | return QByteArray(); |
400 | const QByteArray parentHrid = hierarchicalRidToByteArray( col.parentCollection() ); |
401 | return '(' + QByteArray::number( col.id() ) + ' ' + ImapParser::quote( col.remoteId().toUtf8() ) + ") " + parentHrid; |
402 | } |
403 | |
404 | QByteArray ProtocolHelper::hierarchicalRidToByteArray( const Item &item ) |
405 | { |
406 | const QByteArray parentHrid = hierarchicalRidToByteArray( item.parentCollection() ); |
407 | return '(' + QByteArray::number( item.id() ) + ' ' + ImapParser::quote( item.remoteId().toUtf8() ) + ") " + parentHrid; |
408 | } |
409 | |
410 | QByteArray ProtocolHelper::itemFetchScopeToByteArray( const ItemFetchScope &fetchScope ) |
411 | { |
412 | QByteArray command; |
413 | |
414 | if ( fetchScope.fullPayload() ) |
415 | command += " " AKONADI_PARAM_FULLPAYLOAD; |
416 | if ( fetchScope.allAttributes() ) |
417 | command += " " AKONADI_PARAM_ALLATTRIBUTES; |
418 | if ( fetchScope.cacheOnly() ) |
419 | command += " " AKONADI_PARAM_CACHEONLY; |
420 | if ( fetchScope.checkForCachedPayloadPartsOnly() ) |
421 | command += " " AKONADI_PARAM_CHECKCACHEDPARTSONLY; |
422 | if ( fetchScope.ignoreRetrievalErrors() ) |
423 | command += " " "IGNOREERRORS" ; |
424 | if ( fetchScope.ancestorRetrieval() != ItemFetchScope::None ) { |
425 | switch ( fetchScope.ancestorRetrieval() ) { |
426 | case ItemFetchScope::Parent: |
427 | command += " ANCESTORS 1" ; |
428 | break; |
429 | case ItemFetchScope::All: |
430 | command += " ANCESTORS INF" ; |
431 | break; |
432 | default: |
433 | Q_ASSERT( false ); |
434 | } |
435 | } |
436 | if ( fetchScope.fetchChangedSince().isValid() ) { |
437 | command += " " AKONADI_PARAM_CHANGEDSINCE " " + QByteArray::number( fetchScope.fetchChangedSince().toTime_t() ); |
438 | } |
439 | |
440 | //TODO: detect somehow if server supports external payload attribute |
441 | command += " " AKONADI_PARAM_EXTERNALPAYLOAD; |
442 | |
443 | command += " (UID COLLECTIONID FLAGS SIZE" ; |
444 | if ( fetchScope.fetchRemoteIdentification() ) |
445 | command += " " AKONADI_PARAM_REMOTEID " " AKONADI_PARAM_REMOTEREVISION; |
446 | if ( fetchScope.fetchGid() ) |
447 | command += " GID" ; |
448 | if ( fetchScope.fetchTags() ) |
449 | command += " TAGS" ; |
450 | if ( fetchScope.fetchVirtualReferences() ) |
451 | command += " VIRTREF" ; |
452 | if ( fetchScope.fetchModificationTime() ) |
453 | command += " DATETIME" ; |
454 | foreach ( const QByteArray &part, fetchScope.payloadParts() ) |
455 | command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartPayload, part ); |
456 | foreach ( const QByteArray &part, fetchScope.attributes() ) |
457 | command += ' ' + ProtocolHelper::encodePartIdentifier( ProtocolHelper::PartAttribute, part ); |
458 | command += ")\n" ; |
459 | |
460 | return command; |
461 | } |
462 | |
463 | void ProtocolHelper::parseItemFetchResult( const QList<QByteArray> &lineTokens, Item &item, ProtocolHelperValuePool *valuePool ) |
464 | { |
465 | // create a new item object |
466 | Item::Id uid = -1; |
467 | int rev = -1; |
468 | QString rid; |
469 | QString remoteRevision; |
470 | QString gid; |
471 | QString mimeType; |
472 | Entity::Id cid = -1; |
473 | |
474 | for ( int i = 0; i < lineTokens.count() - 1; i += 2 ) { |
475 | const QByteArray key = lineTokens.value( i ); |
476 | const QByteArray value = lineTokens.value( i + 1 ); |
477 | |
478 | if ( key == "UID" ) |
479 | uid = value.toLongLong(); |
480 | else if ( key == "REV" ) |
481 | rev = value.toInt(); |
482 | else if ( key == "REMOTEID" ) { |
483 | if ( !value.isEmpty() ) |
484 | rid = QString::fromUtf8( value ); |
485 | else |
486 | rid.clear(); |
487 | } else if ( key == "REMOTEREVISION" ) { |
488 | remoteRevision = QString::fromUtf8( value ); |
489 | } else if ( key == "GID" ) { |
490 | gid = QString::fromUtf8( value ); |
491 | } else if ( key == "COLLECTIONID" ) { |
492 | cid = value.toInt(); |
493 | } else if ( key == "MIMETYPE" ) { |
494 | if ( valuePool ) |
495 | mimeType = valuePool->mimeTypePool.sharedValue( QString::fromLatin1( value ) ); |
496 | else |
497 | mimeType = QString::fromLatin1( value ); |
498 | } |
499 | } |
500 | |
501 | if ( uid < 0 || rev < 0 || mimeType.isEmpty() ) { |
502 | kWarning() << "Broken fetch response: UID, REV or MIMETYPE missing!" ; |
503 | return; |
504 | } |
505 | |
506 | item = Item( uid ); |
507 | item.setRemoteId( rid ); |
508 | item.setRevision( rev ); |
509 | item.setRemoteRevision( remoteRevision ); |
510 | item.setGid( gid ); |
511 | item.setMimeType( mimeType ); |
512 | item.setStorageCollectionId( cid ); |
513 | if ( !item.isValid() ) |
514 | return; |
515 | |
516 | // parse fetch response fields |
517 | for ( int i = 0; i < lineTokens.count() - 1; i += 2 ) { |
518 | const QByteArray key = lineTokens.value( i ); |
519 | // skip stuff we dealt with already |
520 | if ( key == "UID" || key == "REV" || key == "REMOTEID" || |
521 | key == "MIMETYPE" || key == "COLLECTIONID" || key == "REMOTEREVISION" || key == "GID" ) |
522 | continue; |
523 | // flags |
524 | if ( key == "FLAGS" ) { |
525 | QList<QByteArray> flags; |
526 | ImapParser::parseParenthesizedList( lineTokens[i + 1], flags ); |
527 | if ( !flags.isEmpty() ) { |
528 | Item::Flags convertedFlags; |
529 | convertedFlags.reserve( flags.size() ); |
530 | foreach ( const QByteArray &flag, flags ) { |
531 | if ( valuePool ) |
532 | convertedFlags.insert( valuePool->flagPool.sharedValue( flag ) ); |
533 | else |
534 | convertedFlags.insert( flag ); |
535 | } |
536 | item.setFlags( convertedFlags ); |
537 | } |
538 | } else if ( key == "TAGS" ) { |
539 | ImapSet set; |
540 | ImapParser::parseSequenceSet( lineTokens[i + 1], set ); |
541 | Tag::List tags; |
542 | Q_FOREACH ( const ImapInterval &interval, set.intervals() ) { |
543 | Q_ASSERT( interval.hasDefinedBegin() ); |
544 | Q_ASSERT( interval.hasDefinedEnd() ); |
545 | for ( qint64 i = interval.begin(); i <= interval.end(); i++ ) { |
546 | //TODO use value pool when tag is shared data |
547 | tags << Tag( i ); |
548 | } |
549 | } |
550 | item.setTags( tags ); |
551 | } else if ( key == "VIRTREF" ) { |
552 | ImapSet set; |
553 | ImapParser::parseSequenceSet( lineTokens[i + 1], set ); |
554 | Collection::List collections; |
555 | Q_FOREACH ( const ImapInterval &interval, set.intervals() ) { |
556 | Q_ASSERT( interval.hasDefinedBegin() ); |
557 | Q_ASSERT( interval.hasDefinedEnd() ); |
558 | for ( qint64 i = interval.begin(); i <= interval.end(); i++ ) { |
559 | collections << Collection(i); |
560 | } |
561 | } |
562 | item.setVirtualReferences(collections); |
563 | } else if ( key == "CACHEDPARTS" ) { |
564 | QSet<QByteArray> partsSet; |
565 | QList<QByteArray> parts; |
566 | ImapParser::parseParenthesizedList( lineTokens[i + 1], parts ); |
567 | foreach ( const QByteArray &part, parts ) { |
568 | partsSet.insert(part.mid(4)); |
569 | } |
570 | item.setCachedPayloadParts( partsSet ); |
571 | } else if ( key == "SIZE" ) { |
572 | const quint64 size = lineTokens[i + 1].toLongLong(); |
573 | item.setSize( size ); |
574 | } else if ( key == "DATETIME" ) { |
575 | QDateTime datetime; |
576 | ImapParser::parseDateTime( lineTokens[i + 1], datetime ); |
577 | item.setModificationTime( datetime ); |
578 | } else if ( key == "ANCESTORS" ) { |
579 | ProtocolHelper::parseAncestorsCached( lineTokens[i + 1], &item, cid, valuePool ); |
580 | } else { |
581 | int version = 0; |
582 | QByteArray plainKey( key ); |
583 | ProtocolHelper::PartNamespace ns; |
584 | |
585 | ImapParser::splitVersionedKey( key, plainKey, version ); |
586 | plainKey = ProtocolHelper::decodePartIdentifier( plainKey, ns ); |
587 | |
588 | switch ( ns ) { |
589 | case ProtocolHelper::PartPayload: |
590 | { |
591 | bool isExternal = false; |
592 | const QByteArray fileKey = lineTokens.value( i + 1 ); |
593 | if ( fileKey == "[FILE]" ) { |
594 | isExternal = true; |
595 | i++; |
596 | //kDebug() << "Payload is external: " << isExternal << " filename: " << lineTokens.value( i + 1 ); |
597 | } |
598 | ItemSerializer::deserialize( item, plainKey, lineTokens.value( i + 1 ), version, isExternal ); |
599 | break; |
600 | } |
601 | case ProtocolHelper::PartAttribute: |
602 | { |
603 | Attribute* attr = AttributeFactory::createAttribute( plainKey ); |
604 | Q_ASSERT( attr ); |
605 | if ( lineTokens.value( i + 1 ) == "[FILE]" ) { |
606 | ++i; |
607 | QFile file( QString::fromUtf8( lineTokens.value( i + 1 ) ) ); |
608 | if ( file.open( QFile::ReadOnly ) ) |
609 | attr->deserialize( file.readAll() ); |
610 | else { |
611 | kWarning() << "Failed to open attribute file: " << lineTokens.value( i + 1 ); |
612 | delete attr; |
613 | attr = 0; |
614 | } |
615 | } else { |
616 | attr->deserialize( lineTokens.value( i + 1 ) ); |
617 | } |
618 | if ( attr ) |
619 | item.addAttribute( attr ); |
620 | break; |
621 | } |
622 | case ProtocolHelper::PartGlobal: |
623 | default: |
624 | kWarning() << "Unknown item part type:" << key; |
625 | } |
626 | } |
627 | } |
628 | |
629 | item.d_ptr->resetChangeLog(); |
630 | } |
631 | |
632 | void ProtocolHelper::parseTagFetchResult( const QList<QByteArray> &lineTokens, Tag &tag ) |
633 | { |
634 | for (int i = 0; i < lineTokens.count() - 1; i += 2) { |
635 | const QByteArray key = lineTokens.value(i); |
636 | const QByteArray value = lineTokens.value(i + 1); |
637 | |
638 | if (key == "UID" ) { |
639 | tag.setId(value.toLongLong()); |
640 | } else if (key == "GID" ) { |
641 | tag.setGid(value); |
642 | } else if (key == "REMOTEID" ) { |
643 | tag.setRemoteId(value); |
644 | } else if (key == "PARENT" ) { |
645 | tag.setParent(Tag(value.toLongLong())); |
646 | } else if ( key == "MIMETYPE" ) { |
647 | tag.setType(value); |
648 | } else { |
649 | Attribute *attr = AttributeFactory::createAttribute(key); |
650 | if (!attr) { |
651 | kWarning() << "Unknown tag attribute" << key; |
652 | continue; |
653 | } |
654 | attr->deserialize(value); |
655 | tag.addAttribute(attr); |
656 | } |
657 | } |
658 | } |
659 | |
660 | QString ProtocolHelper::akonadiStoragePath() |
661 | { |
662 | QString fullRelPath = QLatin1String("akonadi" ); |
663 | if (Akonadi::ServerManager::hasInstanceIdentifier()) { |
664 | fullRelPath += QDir::separator() + QLatin1String("instance" ) + QDir::separator() + Akonadi::ServerManager::instanceIdentifier(); |
665 | } |
666 | return XdgBaseDirs::saveDir("data" , fullRelPath); |
667 | } |
668 | |
669 | QString ProtocolHelper::absolutePayloadFilePath(const QString &fileName) |
670 | { |
671 | QFileInfo fi(fileName); |
672 | if (!fi.isAbsolute()) { |
673 | return akonadiStoragePath() + QDir::separator() + QLatin1String("file_db_data" ) + QDir::separator() + fileName; |
674 | } |
675 | |
676 | return fileName; |
677 | } |
678 | |
679 | bool ProtocolHelper::streamPayloadToFile(const QByteArray &command, const QByteArray &data, QByteArray &error) |
680 | { |
681 | const int fnStart = command.indexOf("[FILE " ) + 6; |
682 | if (fnStart == -1) { |
683 | kDebug() << "Unexpected response" ; |
684 | return false; |
685 | } |
686 | const int fnEnd = command.indexOf("]" , fnStart); |
687 | const QByteArray fn = command.mid(fnStart, fnEnd - fnStart); |
688 | const QString fileName = ProtocolHelper::absolutePayloadFilePath(QString::fromLatin1(fn)); |
689 | if (!fileName.startsWith(akonadiStoragePath())) { |
690 | kWarning() << "Invalid file path" << fileName; |
691 | error = "Invalid file path" ; |
692 | return false; |
693 | } |
694 | QFile file(fileName); |
695 | if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { |
696 | kWarning() << "Failed to open destination payload file" << file.errorString(); |
697 | error = "Failed to store payload into file" ; |
698 | return false; |
699 | } |
700 | if (file.write(data) != data.size()) { |
701 | kWarning() << "Failed to write all payload data to file" ; |
702 | error = "Failed to store payload into file" ; |
703 | return false; |
704 | } |
705 | kDebug() << "Wrote" << data.size() << "bytes to " << file.fileName(); |
706 | |
707 | // Make sure stuff is written to disk |
708 | file.close(); |
709 | return true; |
710 | } |
711 | |
712 | |
713 | QByteArray ProtocolHelper::listPreference(Collection::ListPurpose purpose, Collection::ListPreference preference) |
714 | { |
715 | QByteArray command; |
716 | switch(purpose) { |
717 | case Collection::ListDisplay: |
718 | command += "DISPLAY " ; |
719 | break; |
720 | case Collection::ListSync: |
721 | command += "SYNC " ; |
722 | break; |
723 | case Collection::ListIndex: |
724 | command += "INDEX " ; |
725 | break; |
726 | } |
727 | switch(preference) { |
728 | case Collection::ListEnabled: |
729 | command += "TRUE" ; |
730 | break; |
731 | case Collection::ListDisabled: |
732 | command += "FALSE" ; |
733 | break; |
734 | case Collection::ListDefault: |
735 | command += "DEFAULT" ; |
736 | break; |
737 | } |
738 | return command; |
739 | } |
740 | |
741 | QByteArray ProtocolHelper::enabled(bool state) |
742 | { |
743 | if (state) { |
744 | return "ENABLED TRUE" ; |
745 | } |
746 | return "ENABLED FALSE" ; |
747 | } |
748 | |
749 | QByteArray ProtocolHelper::referenced(bool state) |
750 | { |
751 | if (state) { |
752 | return "REFERENCED TRUE" ; |
753 | } |
754 | return "REFERENCED FALSE" ; |
755 | } |
756 | |