20#include "protocolhelper_p.h"
22#include "attributefactory.h"
23#include "collectionstatistics.h"
26#include "itemserializer_p.h"
27#include "itemserializerplugin.h"
28#include "servermanager.h"
29#include "tagfetchscope.h"
30#include <akonadi/private/xdgbasedirs_p.h>
32#include <QtCore/QDateTime>
33#include <QtCore/QFile>
34#include <QtCore/QVarLengthArray>
35#include <QtCore/QFileInfo>
39#include <klocalizedstring.h>
45 QVarLengthArray<QByteArray,16> params;
46 int end = Akonadi::ImapParser::parseParenthesizedList( data, params, start );
47 for (
int i = 0; i < params.count() - 1; i += 2 ) {
48 const QByteArray key = params[i];
49 const QByteArray value = params[i + 1];
51 if ( key ==
"INHERIT" )
53 else if ( key ==
"INTERVAL" )
55 else if ( key ==
"CACHETIMEOUT" )
57 else if ( key ==
"SYNCONDEMAND" )
59 else if ( key ==
"LOCALPARTS" ) {
60 QVarLengthArray<QByteArray,16> tmp;
62 Akonadi::ImapParser::parseParenthesizedList( value, tmp );
63 for (
int j=0; j<tmp.size(); j++ )
64 parts << QString::fromLatin1( tmp[j] );
73 QByteArray rv =
"CACHEPOLICY (";
77 rv +=
"INHERIT false";
79 rv +=
" CACHETIMEOUT " + QByteArray::number( policy.
cacheTimeout() );
80 rv +=
" SYNCONDEMAND " + ( policy.
syncOnDemand() ? QByteArray(
"true") : QByteArray(
"false") );
81 rv +=
" LOCALPARTS (" + policy.
localParts().join( QLatin1String(
" ") ).toLatin1() +
')';
88 ProtocolHelperValuePool *pool,
int start )
90 if ( !pool || parentCollection == -1 ) {
96 if ( pool->ancestorCollections.contains( parentCollection ) ) {
102 pool->ancestorCollections.insert( parentCollection, entity->
parentCollection() );
111 QVarLengthArray<QByteArray, 16> ancestors;
112 QVarLengthArray<QByteArray, 16> parentIds;
114 ImapParser::parseParenthesizedList( data, ancestors );
116 for (
int i = 0; i < ancestors.count(); ++i ) {
118 ImapParser::parseParenthesizedList( ancestors[ i ], parentIds );
119 if ( parentIds.size() != 2 )
123 if ( uid == rootCollectionId ) {
136 if ( value ==
"TRUE" ) {
139 if ( value ==
"FALSE" ) {
152 pos = ImapParser::parseNumber( data, colId, &ok, pos );
153 if ( !ok || colId <= 0 ) {
154 kDebug() <<
"Could not parse collection id from response:" << data;
159 pos = ImapParser::parseNumber( data, parentId, &ok, pos );
160 if ( !ok || parentId < 0 ) {
161 kDebug() <<
"Could not parse parent id from response:" << data;
166 collection.setParentCollection(
Collection( parentId ) );
169 QVarLengthArray<QByteArray,16> attributes;
170 pos = ImapParser::parseParenthesizedList( data, attributes, pos );
172 for (
int i = 0; i < attributes.count() - 1; i += 2 ) {
173 const QByteArray key = attributes[i];
174 const QByteArray value = attributes[i + 1];
176 if ( key ==
"NAME" ) {
177 collection.setName( QString::fromUtf8( value ) );
178 }
else if ( key ==
"REMOTEID" ) {
179 collection.setRemoteId( QString::fromUtf8( value ) );
180 }
else if ( key ==
"REMOTEREVISION" ) {
181 collection.setRemoteRevision( QString::fromUtf8( value ) );
182 }
else if ( key ==
"RESOURCE" ) {
183 collection.setResource( QString::fromUtf8( value ) );
184 }
else if ( key ==
"MIMETYPE" ) {
185 QVarLengthArray<QByteArray,16> ct;
186 ImapParser::parseParenthesizedList( value, ct );
188 for (
int j = 0; j < ct.size(); j++ )
189 ct2 << QString::fromLatin1( ct[j] );
190 collection.setContentMimeTypes( ct2 );
191 }
else if ( key ==
"VIRTUAL" ) {
192 collection.setVirtual( value.toUInt() != 0 );
193 }
else if ( key ==
"MESSAGES" ) {
196 collection.setStatistics( s );
197 }
else if ( key ==
"UNSEEN" ) {
200 collection.setStatistics( s );
201 }
else if ( key ==
"SIZE" ) {
203 s.
setSize( value.toLongLong() );
204 collection.setStatistics( s );
205 }
else if ( key ==
"CACHEPOLICY" ) {
208 collection.setCachePolicy( policy );
209 }
else if ( key ==
"ANCESTORS" ) {
211 }
else if ( key ==
"ENABLED" ) {
212 collection.setEnabled( value ==
"TRUE" );
213 }
else if ( key ==
"DISPLAY" ) {
215 }
else if ( key ==
"SYNC" ) {
217 }
else if ( key ==
"INDEX" ) {
219 }
else if ( key ==
"REFERENCED" ) {
220 collection.setReferenced( value ==
"TRUE" );
225 collection.addAttribute( attr );
239 return ImapParser::join( l,
" " );
249 return ImapParser::join( l,
" " );
254 const QByteArray versionString( version != 0 ? QByteArray(QByteArray(
"[") + QByteArray::number( version ) + QByteArray(
"]")) :
"" );
257 return label + versionString;
259 return "PLD:" + label + versionString;
261 return "ATR:" + label + versionString;
270 if ( data.startsWith(
"PLD:" ) ) {
272 return data.mid( 4 );
273 }
else if ( data.startsWith(
"ATR:" ) ) {
275 return data.mid( 4 );
284 if ( _objects.isEmpty() )
285 throw Exception(
"No objects specified" );
287 Item::List objects( _objects );
288 std::sort( objects.begin(), objects.end(), boost::bind( &Item::id, _1 ) < boost::bind( &Item::id, _2 ) );
289 if ( objects.first().isValid() ) {
291 return entitySetToByteArray<Item>(objects, command);
294 if ( std::find_if( objects.constBegin(), objects.constEnd(),
295 boost::bind( &QString::isEmpty, boost::bind( &Item::gid, _1 ) ) )
296 == objects.constEnd() )
298 QList<QByteArray> gids;
299 foreach (
const Item &
object, objects ) {
300 gids << ImapParser::quote(
object.gid().toUtf8() );
306 if ( !command.isEmpty() ) {
311 rv += ImapParser::join( gids,
" " );
315 return entitySetToByteArray<Item>(objects, command);
318QByteArray ProtocolHelper::tagSetToImapSequenceSet(
const Akonadi::Tag::List &_objects )
320 if ( _objects.isEmpty() )
321 throw Exception(
"No objects specified" );
323 Tag::List objects( _objects );
325 std::sort( objects.begin(), objects.end(), boost::bind( &
Tag::id, _1 ) < boost::bind( &
Tag::id, _2 ) );
326 if ( !objects.first().isValid() ) {
327 throw Exception(
"Not all tags have a uid" );
330 QVector<Tag::Id> uids;
331 foreach (
const Tag &
object, objects )
335 return set.toImapSequenceSet();
338QByteArray ProtocolHelper::tagSetToByteArray(
const Tag::List &_objects,
const QByteArray &command )
340 if ( _objects.isEmpty() )
341 throw Exception(
"No objects specified" );
343 Tag::List objects( _objects );
346 std::sort( objects.begin(), objects.end(), boost::bind( &
Tag::id, _1 ) < boost::bind( &
Tag::id, _2 ) );
347 if ( objects.first().isValid() ) {
349 rv +=
" " AKONADI_CMD_UID
" ";
350 if ( !command.isEmpty() ) {
354 QVector<Tag::Id> uids;
355 foreach (
const Tag &
object, objects )
359 rv += set.toImapSequenceSet();
362 throw Exception(
"Not all tags have a uid" );
366 const Item::List &requestedItems,
const QByteArray &command)
369 if (requestedItems.isEmpty()) {
370 r += command +
" 1:*";
376 r +=
" " AKONADI_PARAM_TAGID
" " + QByteArray::number(tag.id()) +
" ";
380 if (requestedItems.isEmpty() && !tag.isValid()) {
381 throw Exception(
"Cannot perform item operations on root collection.");
384 if (collection.isValid()) {
385 r +=
" " AKONADI_PARAM_COLLECTIONID
" " + QByteArray::number(collection.id()) +
' ';
386 }
else if (!collection.remoteId().isEmpty()) {
387 r +=
" " AKONADI_PARAM_COLLECTION
" " + ImapParser::quote(collection.remoteId().toUtf8()) +
' ';
398 return QByteArray(
"(0 \"\")");
402 return '(' + QByteArray::number( col.
id() ) +
' ' + ImapParser::quote( col.
remoteId().toUtf8() ) +
") " + parentHrid;
408 return '(' + QByteArray::number( item.id() ) +
' ' + ImapParser::quote( item.remoteId().toUtf8() ) +
") " + parentHrid;
416 command +=
" " AKONADI_PARAM_FULLPAYLOAD;
418 command +=
" " AKONADI_PARAM_ALLATTRIBUTES;
420 command +=
" " AKONADI_PARAM_CACHEONLY;
422 command +=
" " AKONADI_PARAM_CHECKCACHEDPARTSONLY;
424 command +=
" " "IGNOREERRORS";
428 command +=
" ANCESTORS 1";
431 command +=
" ANCESTORS INF";
438 command +=
" " AKONADI_PARAM_CHANGEDSINCE
" " + QByteArray::number( fetchScope.
fetchChangedSince().toTime_t() );
442 command +=
" " AKONADI_PARAM_EXTERNALPAYLOAD;
444 command +=
" (UID COLLECTIONID FLAGS SIZE";
446 command +=
" " AKONADI_PARAM_REMOTEID
" " AKONADI_PARAM_REMOTEREVISION;
456 command +=
" VIRTREF";
458 command +=
" DATETIME";
459 foreach (
const QByteArray &part, fetchScope.
payloadParts() )
461 foreach (
const QByteArray &part, fetchScope.
attributes() )
473 Q_FOREACH (
const QByteArray &part, fetchScope.
attributes()) {
480static Item::Flags convertFlags(
const QList<QByteArray>& flags, ProtocolHelperValuePool *valuePool )
482#if __cplusplus >= 201103L || defined(__GNUC__) || defined(__clang__)
486 if (flags.size() == 1 && flags.first() ==
"\\SEEN") {
487 static const Item::Flags sharedSeen = Item::Flags() << QByteArray(
"\\SEEN" );
492 Item::Flags convertedFlags;
493 convertedFlags.reserve( flags.size() );
494 foreach (
const QByteArray &flag, flags ) {
496 convertedFlags.insert( valuePool->flagPool.
sharedValue( flag ) );
498 convertedFlags.insert( flag );
500 return convertedFlags;
509 QString remoteRevision;
514 for (
int i = 0; i < lineTokens.count() - 1; i += 2 ) {
515 const QByteArray key = lineTokens.value( i );
516 const QByteArray value = lineTokens.value( i + 1 );
519 uid = value.toLongLong();
520 else if ( key ==
"REV" )
522 else if ( key ==
"REMOTEID" ) {
523 if ( !value.isEmpty() )
524 rid = QString::fromUtf8( value );
527 }
else if ( key ==
"REMOTEREVISION" ) {
528 remoteRevision = QString::fromUtf8( value );
529 }
else if ( key ==
"GID" ) {
530 gid = QString::fromUtf8( value );
531 }
else if ( key ==
"COLLECTIONID" ) {
533 }
else if ( key ==
"MIMETYPE" ) {
535 mimeType = valuePool->mimeTypePool.
sharedValue( QString::fromLatin1( value ) );
537 mimeType = QString::fromLatin1( value );
541 if ( uid < 0 || rev < 0 || mimeType.isEmpty() ) {
542 kWarning() <<
"Broken fetch response: UID, REV or MIMETYPE missing!";
547 item.setRemoteId( rid );
548 item.setRevision( rev );
549 item.setRemoteRevision( remoteRevision );
551 item.setMimeType( mimeType );
552 item.setStorageCollectionId( cid );
553 if ( !item.isValid() )
557 for (
int i = 0; i < lineTokens.count() - 1; i += 2 ) {
558 const QByteArray key = lineTokens.value( i );
560 if ( key ==
"UID" || key ==
"REV" || key ==
"REMOTEID" ||
561 key ==
"MIMETYPE" || key ==
"COLLECTIONID" || key ==
"REMOTEREVISION" || key ==
"GID" )
564 if ( key ==
"FLAGS" ) {
565 QList<QByteArray> flags;
566 ImapParser::parseParenthesizedList( lineTokens[i + 1], flags );
567 if ( !flags.isEmpty() ) {
568 item.setFlags( convertFlags( flags, valuePool ) );
570 }
else if ( key ==
"TAGS" ) {
572 if ( lineTokens[i + 1].startsWith(
"(") ) {
573 QList<QByteArray> tagsData;
574 ImapParser::parseParenthesizedList( lineTokens[i + 1], tagsData );
575 Q_FOREACH (
const QByteArray &t, tagsData) {
576 QList<QByteArray> tagParts;
577 ImapParser::parseParenthesizedList( t, tagParts );
579 parseTagFetchResult(tagParts, tag);
584 ImapParser::parseSequenceSet( lineTokens[i + 1], set );
585 Q_FOREACH (
const ImapInterval &interval, set.intervals() ) {
586 Q_ASSERT( interval.hasDefinedBegin() );
587 Q_ASSERT( interval.hasDefinedEnd() );
588 for ( qint64 i = interval.begin(); i <= interval.end(); i++ ) {
594 item.setTags( tags );
595 }
else if ( key ==
"VIRTREF" ) {
597 ImapParser::parseSequenceSet( lineTokens[i + 1], set );
599 Q_FOREACH (
const ImapInterval &interval, set.intervals() ) {
600 Q_ASSERT( interval.hasDefinedBegin() );
601 Q_ASSERT( interval.hasDefinedEnd() );
602 for ( qint64 i = interval.begin(); i <= interval.end(); i++ ) {
606 item.setVirtualReferences(collections);
607 }
else if ( key ==
"CACHEDPARTS" ) {
608 QSet<QByteArray> partsSet;
609 QList<QByteArray> parts;
610 ImapParser::parseParenthesizedList( lineTokens[i + 1], parts );
611 foreach (
const QByteArray &part, parts ) {
612 partsSet.insert(part.mid(4));
614 item.setCachedPayloadParts( partsSet );
615 }
else if ( key ==
"SIZE" ) {
616 const quint64 size = lineTokens[i + 1].toLongLong();
617 item.setSize( size );
618 }
else if ( key ==
"DATETIME" ) {
620 ImapParser::parseDateTime( lineTokens[i + 1], datetime );
621 item.setModificationTime( datetime );
622 }
else if ( key ==
"ANCESTORS" ) {
626 QByteArray plainKey( key );
629 ImapParser::splitVersionedKey( key, plainKey, version );
633 case ProtocolHelper::PartPayload:
635 bool isExternal =
false;
636 const QByteArray fileKey = lineTokens.value( i + 1 );
637 if ( fileKey ==
"[FILE]" ) {
645 case ProtocolHelper::PartAttribute:
649 if ( lineTokens.value( i + 1 ) ==
"[FILE]" ) {
651 QFile file( QString::fromUtf8( lineTokens.value( i + 1 ) ) );
652 if ( file.open( QFile::ReadOnly ) )
655 kWarning() <<
"Failed to open attribute file: " << lineTokens.value( i + 1 );
663 item.addAttribute( attr );
666 case ProtocolHelper::PartGlobal:
668 kWarning() <<
"Unknown item part type:" << key;
673 item.d_ptr->resetChangeLog();
676void ProtocolHelper::parseTagFetchResult(
const QList<QByteArray> &lineTokens,
Tag &tag )
678 for (
int i = 0; i < lineTokens.count() - 1; i += 2) {
679 const QByteArray key = lineTokens.value(i);
680 const QByteArray value = lineTokens.value(i + 1);
683 tag.setId(value.toLongLong());
684 }
else if (key ==
"GID") {
686 }
else if (key ==
"REMOTEID") {
687 tag.setRemoteId(value);
688 }
else if (key ==
"PARENT") {
689 tag.setParent(
Tag(value.toLongLong()));
690 }
else if ( key ==
"MIMETYPE" ) {
695 kWarning() <<
"Unknown tag attribute" << key;
699 tag.addAttribute(attr);
704QString ProtocolHelper::akonadiStoragePath()
706 QString fullRelPath = QLatin1String(
"akonadi");
710 return XdgBaseDirs::saveDir(
"data", fullRelPath);
713QString ProtocolHelper::absolutePayloadFilePath(
const QString &fileName)
715 QFileInfo fi(fileName);
716 if (!fi.isAbsolute()) {
717 return akonadiStoragePath() + QDir::separator() + QLatin1String(
"file_db_data") + QDir::separator() + fileName;
723bool ProtocolHelper::streamPayloadToFile(
const QByteArray &command,
const QByteArray &data, QByteArray &error)
725 const int fnStart = command.indexOf(
"[FILE ") + 6;
727 kDebug() <<
"Unexpected response";
730 const int fnEnd = command.indexOf(
"]", fnStart);
731 const QByteArray fn = command.mid(fnStart, fnEnd - fnStart);
732 const QString fileName = ProtocolHelper::absolutePayloadFilePath(QString::fromLatin1(fn));
733 if (!fileName.startsWith(akonadiStoragePath())) {
734 kWarning() <<
"Invalid file path" << fileName;
735 error =
"Invalid file path";
738 QFile file(fileName);
739 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
740 kWarning() <<
"Failed to open destination payload file" << file.errorString();
741 error =
"Failed to store payload into file";
744 if (file.write(data) != data.size()) {
745 kWarning() <<
"Failed to write all payload data to file";
746 error =
"Failed to store payload into file";
749 kDebug() <<
"Wrote" << data.size() <<
"bytes to " << file.fileName();
762 command +=
"DISPLAY ";
779 command +=
"DEFAULT";
785QByteArray ProtocolHelper::enabled(
bool state)
788 return "ENABLED TRUE";
790 return "ENABLED FALSE";
793QByteArray ProtocolHelper::referenced(
bool state)
796 return "REFERENCED TRUE";
798 return "REFERENCED FALSE";
Parent class for entities that can have attributes.
Attribute::List attributes() const
Returns a list of all attributes of the entity.
static Attribute * createAttribute(const QByteArray &type)
Creates an entity attribute object of the given type.
Provides interface for custom attributes for Entity.
virtual void deserialize(const QByteArray &data)=0
Sets the data of this attribute, using the same encoding as returned by toByteArray().
virtual QByteArray serialized() const =0
Returns a QByteArray representation of the attribute which will be storaged.
virtual QByteArray type() const =0
Returns the type of the attribute.
Represents the caching policy for a collection.
int cacheTimeout() const
Returns the cache timeout for non-permanently cached parts in minutes; -1 means indefinitely.
bool inheritFromParent() const
Returns whether it inherits cache policy from the parent collection.
int intervalCheckTime() const
Returns the interval check time in minutes, -1 for never.
void setLocalParts(const QStringList &parts)
Specifies the parts to permanently cache locally.
QStringList localParts() const
Returns the parts to permanently cache locally.
bool syncOnDemand() const
Returns whether the collection will be synced automatically when necessary, i.e.
void setIntervalCheckTime(int time)
Sets interval check time.
void setCacheTimeout(int timeout)
Sets cache timeout for non-permanently cached parts.
void setInheritFromParent(bool inherit)
Sets whether the cache policy should be inherited from the parent collection.
void setSyncOnDemand(bool enable)
Sets whether the collection shall be synced automatically when necessary, i.e.
Provides statistics information of a Collection.
void setUnreadCount(qint64 count)
Sets the number of unread items in this collection.
void setSize(qint64 size)
Sets the total size of the items in this collection.
void setCount(qint64 count)
Sets the number of items in this collection.
Represents a collection of PIM items.
ListPurpose
Describes the purpose of the listing.
@ ListSync
Listing for synchronization.
@ ListIndex
Listing for indexing the content.
@ ListDisplay
Listing for display to the user.
static Collection root()
Returns the root collection.
QList< Collection > List
Describes a list of collections.
ListPreference
Describes the list preference value.
@ ListDefault
Fallback to enabled state.
@ ListDisabled
Disable collectoin for specified purpose.
@ ListEnabled
Enable collection for specified purpose.
The base class for Item and Collection.
QString remoteId() const
Returns the remote id of the entity.
Collection parentCollection() const
Returns the parent collection of this object.
Attribute::List attributes() const
Returns a list of all attributes of the entity.
void setId(Id identifier)
Sets the unique identifier of the entity.
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
Id id() const
Returns the unique identifier of the entity.
qint64 Id
Describes the unique id type.
void setRemoteId(const QString &id)
Sets the remote id of the entity.
Base class for exceptions used by the Akonadi library.
T sharedValue(const T &value)
Returns the shared value equal to value .
Specifies which parts of an item should be fetched from the Akonadi storage.
bool fetchVirtualReferences() const
Returns whether virtual references should be retrieved.
bool allAttributes() const
Returns whether all available attributes should be fetched.
bool fetchRemoteIdentification() const
Returns whether item remote identification should be retrieved.
bool fullPayload() const
Returns whether the full payload should be fetched.
bool fetchTags() const
Returns whether tags should be retrieved.
bool cacheOnly() const
Returns whether payload data should be requested from remote sources or just from the local cache.
bool fetchModificationTime() const
Returns whether item modification time should be retrieved.
bool checkForCachedPayloadPartsOnly() const
Returns whether payload data should be fetched or only checked for presence in the cache.
QSet< QByteArray > attributes() const
Returns all explicitly fetched attributes.
bool ignoreRetrievalErrors() const
Returns whether retrieval errors should be ignored.
QSet< QByteArray > payloadParts() const
Returns the payload parts that should be fetched.
bool fetchGid() const
Returns whether item GID should be retrieved.
AncestorRetrieval ancestorRetrieval() const
Returns the ancestor retrieval depth.
TagFetchScope & tagFetchScope()
Returns the tag fetch scope.
KDateTime fetchChangedSince() const
Returns timestamp of the oldest item to fetch.
@ All
Retrieve all ancestors, up to Collection::root()
@ Parent
Only retrieve the immediate parent collection.
@ None
No ancestor retrieval at all (the default)
static void deserialize(Item &item, const QByteArray &label, const QByteArray &data, int version, bool external)
throws ItemSerializerException on failure
static QByteArray itemFetchScopeToByteArray(const ItemFetchScope &fetchScope)
Converts a given ItemFetchScope object into a protocol representation.
static int parseCachePolicy(const QByteArray &data, CachePolicy &policy, int start=0)
Parse a cache policy definition.
static QByteArray hierarchicalRidToByteArray(const Collection &col)
Converts the given collection's hierarchical RID into a protocol representation.
static void parseAncestors(const QByteArray &data, Entity *entity, int start=0)
Convert a ancestor chain from its protocol representation into an Entity object.
static QByteArray tagFetchScopeToByteArray(const TagFetchScope &fetchScope)
Converts a given TagFetchScope object into a protocol representation.
static QByteArray attributesToByteArray(const Entity &entity, bool ns=false)
Convert attributes to their protocol representation.
PartNamespace
Part namespaces.
static int parseCollection(const QByteArray &data, Collection &collection, int start=0)
Parse a collection description.
static QByteArray decodePartIdentifier(const QByteArray &data, PartNamespace &ns)
Decode part label and namespace.
static void parseItemFetchResult(const QList< QByteArray > &lineTokens, Item &item, ProtocolHelperValuePool *valuePool=0)
Parses a single line from an item fetch job result into an Item object.
static QByteArray cachePolicyToByteArray(const CachePolicy &policy)
Convert a cache policy object into its protocol representation.
static void parseAncestorsCached(const QByteArray &data, Entity *entity, Collection::Id parentCollection, ProtocolHelperValuePool *valuePool=0, int start=0)
Convert a ancestor chain from its protocol representation into an Entity object.
static QByteArray encodePartIdentifier(PartNamespace ns, const QByteArray &label, int version=0)
Encodes part label and namespace.
static QByteArray entitySetToByteArray(const QList< T > &_objects, const QByteArray &command)
Converts the given set of items into a protocol representation.
static bool hasInstanceIdentifier()
Returns true if we are connected to a non-default Akonadi server instance.
static QString instanceIdentifier()
Returns the identifier of the Akonadi instance we are connected to.
Specifies which parts of a tag should be fetched from the Akonadi storage.
bool fetchIdOnly() const
Sets wether only the id of the tags should be retieved or the complete tag.
QSet< QByteArray > attributes() const
Returns all explicitly fetched attributes.
Id id() const
Returns the unique identifier of the tag.
FreeBusyManager::Singleton.