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
40using namespace Akonadi;
41
42int 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
70QByteArray 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
86void 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
105void 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 = &current->parentCollection();
130 }
131}
132
133static 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
144int 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
231QByteArray 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
241QByteArray 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
251QByteArray 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
267QByteArray 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
281QByteArray 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
317QByteArray 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
337QByteArray 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
364QByteArray 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
394QByteArray 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
404QByteArray 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
410QByteArray 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
463void 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
632void 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
660QString 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
669QString 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
679bool 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
713QByteArray 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
741QByteArray ProtocolHelper::enabled(bool state)
742{
743 if (state) {
744 return "ENABLED TRUE";
745 }
746 return "ENABLED FALSE";
747}
748
749QByteArray ProtocolHelper::referenced(bool state)
750{
751 if (state) {
752 return "REFERENCED TRUE";
753 }
754 return "REFERENCED FALSE";
755}
756