1 /*
2     SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "kolabretrievetagstask.h"
8 #include "kolabresource_debug.h"
9 #include "kolabresource_trace.h"
10 #include "tagchangehelper.h"
11 
12 #include "pimkolab/kolabformat/kolabobject.h"
13 #include <KIMAP/FetchJob>
14 #include <KIMAP/SelectJob>
15 #include <imapflags.h>
16 
KolabRetrieveTagTask(const ResourceStateInterface::Ptr & resource,RetrieveType type,QObject * parent)17 KolabRetrieveTagTask::KolabRetrieveTagTask(const ResourceStateInterface::Ptr &resource, RetrieveType type, QObject *parent)
18     : KolabRelationResourceTask(resource, parent)
19     , mRetrieveType(type)
20 {
21 }
22 
startRelationTask(KIMAP::Session * session)23 void KolabRetrieveTagTask::startRelationTask(KIMAP::Session *session)
24 {
25     mSession = session;
26     const QString mailBox = mailBoxForCollection(relationCollection());
27 
28     auto select = new KIMAP::SelectJob(session);
29     select->setMailBox(mailBox);
30     connect(select, &KJob::result, this, &KolabRetrieveTagTask::onFinalSelectDone);
31     select->start();
32 }
33 
onFinalSelectDone(KJob * job)34 void KolabRetrieveTagTask::onFinalSelectDone(KJob *job)
35 {
36     if (job->error()) {
37         qCWarning(KOLABRESOURCE_LOG) << job->errorString();
38         cancelTask(job->errorString());
39         return;
40     }
41 
42     auto select = static_cast<KIMAP::SelectJob *>(job);
43     auto fetch = new KIMAP::FetchJob(select->session());
44 
45     if (select->messageCount() == 0) {
46         taskComplete();
47         return;
48     }
49 
50     KIMAP::ImapSet set;
51     set.add(KIMAP::ImapInterval(1, 0));
52     fetch->setSequenceSet(set);
53     fetch->setUidBased(false);
54 
55     KIMAP::FetchJob::FetchScope scope;
56     scope.parts.clear();
57     scope.mode = KIMAP::FetchJob::FetchScope::Full;
58     fetch->setScope(scope);
59     connect(fetch, &KIMAP::FetchJob::messagesAvailable, this, &KolabRetrieveTagTask::onMessagesAvailable);
60     connect(fetch, &KJob::result, this, &KolabRetrieveTagTask::onHeadersFetchDone);
61     fetch->start();
62 }
63 
onMessagesAvailable(const QMap<qint64,KIMAP::Message> & messages)64 void KolabRetrieveTagTask::onMessagesAvailable(const QMap<qint64, KIMAP::Message> &messages)
65 {
66     auto fetch = static_cast<KIMAP::FetchJob *>(sender());
67     Q_ASSERT(fetch);
68 
69     for (auto it = messages.cbegin(), end = messages.cend(); it != end; ++it) {
70         if (it->flags.contains(ImapFlags::Deleted)) {
71             continue;
72         }
73         const KMime::Message::Ptr msg = it->message;
74         const Kolab::KolabObjectReader reader(msg);
75         switch (reader.getType()) {
76         case Kolab::RelationConfigurationObject:
77             if (mRetrieveType == RetrieveTags && reader.isTag()) {
78                 extractTag(reader, it->uid);
79             } else if (mRetrieveType == RetrieveRelations && reader.isRelation()) {
80                 extractRelation(reader, it->uid);
81             }
82             break;
83 
84         default:
85             break;
86         }
87     }
88 }
89 
extractMember(const Kolab::RelationMember & member)90 Akonadi::Item KolabRetrieveTagTask::extractMember(const Kolab::RelationMember &member)
91 {
92     // TODO should we create a dummy item if it isn't yet available?
93     Akonadi::Item i;
94     if (!member.gid.isEmpty()) {
95         // Reference by GID
96         i.setGid(member.gid);
97     } else {
98         // Reference by imap uid
99         if (member.uid < 0) {
100             return Akonadi::Item();
101         }
102         i.setRemoteId(QString::number(member.uid));
103         qCDebug(KOLABRESOURCE_LOG) << "got member: " << member.uid << member.mailbox;
104         Akonadi::Collection parent;
105         {
106             // The root collection is not part of the mailbox path
107             Akonadi::Collection col;
108             col.setRemoteId(rootRemoteId());
109             col.setParentCollection(Akonadi::Collection::root());
110             parent = col;
111         }
112         for (const QByteArray &part : std::as_const(member.mailbox)) {
113             Akonadi::Collection col;
114             col.setRemoteId(separatorCharacter() + QString::fromLatin1(part));
115             col.setParentCollection(parent);
116             parent = col;
117         }
118         i.setParentCollection(parent);
119     }
120     return i;
121 }
122 
extractTag(const Kolab::KolabObjectReader & reader,qint64 remoteUid)123 void KolabRetrieveTagTask::extractTag(const Kolab::KolabObjectReader &reader, qint64 remoteUid)
124 {
125     Akonadi::Tag tag = reader.getTag();
126     tag.setRemoteId(QByteArray::number(remoteUid));
127     mTags << tag;
128 
129     qCDebug(KOLABRESOURCE_TRACE) << "Extracted tag: " << tag.gid() << " remoteId: " << remoteUid << tag.remoteId();
130 
131     Akonadi::Item::List members;
132     const QStringList lstMemberUrl = reader.getTagMembers();
133     for (const QString &memberUrl : lstMemberUrl) {
134         Kolab::RelationMember member = Kolab::parseMemberUrl(memberUrl);
135         const Akonadi::Item i = extractMember(member);
136         // TODO implement fallback to search if uid is not available
137         if (!i.remoteId().isEmpty() || !i.gid().isEmpty()) {
138             members << i;
139         } else {
140             qCWarning(KOLABRESOURCE_LOG) << "Failed to parse member: " << memberUrl;
141         }
142     }
143     mTagMembers.insert(QString::fromLatin1(tag.remoteId()), members);
144 }
145 
extractRelation(const Kolab::KolabObjectReader & reader,qint64 remoteUid)146 void KolabRetrieveTagTask::extractRelation(const Kolab::KolabObjectReader &reader, qint64 remoteUid)
147 {
148     Akonadi::Item::List members;
149     const QStringList lstMemberUrl = reader.getTagMembers();
150     for (const QString &memberUrl : lstMemberUrl) {
151         Kolab::RelationMember member = Kolab::parseMemberUrl(memberUrl);
152         const Akonadi::Item i = extractMember(member);
153         // TODO implement fallback to search if uid is not available
154         if (!i.remoteId().isEmpty() || !i.gid().isEmpty()) {
155             members << i;
156         } else {
157             qCWarning(KOLABRESOURCE_LOG) << "Failed to parse member: " << memberUrl;
158         }
159     }
160     if (members.size() != 2) {
161         qCWarning(KOLABRESOURCE_LOG) << "Wrong numbers of members for a relation, expected 2: " << members.size();
162         return;
163     }
164 
165     Akonadi::Relation relation = reader.getRelation();
166     relation.setType(Akonadi::Relation::GENERIC);
167     relation.setRemoteId(QByteArray::number(remoteUid));
168     relation.setLeft(members.at(0));
169     relation.setRight(members.at(1));
170     mRelations << relation;
171 }
172 
onHeadersFetchDone(KJob * job)173 void KolabRetrieveTagTask::onHeadersFetchDone(KJob *job)
174 {
175     if (job->error()) {
176         qCWarning(KOLABRESOURCE_LOG) << "Fetch job failed " << job->errorString();
177         cancelTask(job->errorString());
178         return;
179     }
180 
181     taskComplete();
182 }
183 
taskComplete()184 void KolabRetrieveTagTask::taskComplete()
185 {
186     if (mRetrieveType == RetrieveTags) {
187         qCDebug(KOLABRESOURCE_LOG) << "Fetched tags: " << mTags.size() << mTagMembers.size();
188         resourceState()->tagsRetrieved(mTags, mTagMembers);
189     } else if (mRetrieveType == RetrieveRelations) {
190         qCDebug(KOLABRESOURCE_LOG) << "Fetched relations:" << mRelations.size();
191         resourceState()->relationsRetrieved(mRelations);
192     }
193 
194     deleteLater();
195 }
196