1 /*
2     SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "kolabresource.h"
8 
9 #include "kolabresource_debug.h"
10 #include "kolabresource_trace.h"
11 #include "sessionpool.h"
12 #include "sessionuiproxy.h"
13 #include "settingspasswordrequester.h"
14 #include "setupserver.h"
15 
16 #include <Akonadi/Calendar/BlockAlarmsAttribute>
17 
18 #include <Akonadi/AttributeFactory>
19 #include <Akonadi/CollectionColorAttribute>
20 
21 #include <changecollectiontask.h>
22 #include <collectionannotationsattribute.h>
23 #include <resourcestateinterface.h>
24 #include <retrieveitemstask.h>
25 
26 #include <KLocalizedString>
27 #include <KWindowSystem>
28 #include <QIcon>
29 
30 #include "kolabaddtagtask.h"
31 #include "kolabchangeitemsrelationstask.h"
32 #include "kolabchangeitemstagstask.h"
33 #include "kolabchangetagtask.h"
34 #include "kolabhelpers.h"
35 #include "kolabremovetagtask.h"
36 #include "kolabresourcestate.h"
37 #include "kolabretrievecollectionstask.h"
38 #include "kolabretrievetagstask.h"
39 #include "kolabsettings.h"
40 
KolabResource(const QString & id)41 KolabResource::KolabResource(const QString &id)
42     : ImapResourceBase(id)
43 {
44     m_pool->setPasswordRequester(new SettingsPasswordRequester(this, m_pool));
45     m_pool->setSessionUiProxy(SessionUiProxy::Ptr(new SessionUiProxy));
46     m_pool->setClientId(clientId());
47 
48     Akonadi::AttributeFactory::registerAttribute<Akonadi::CollectionColorAttribute>();
49     // Ensure we have up-to date metadata before attempting to sync folder
50     setScheduleAttributeSyncBeforeItemSync(true);
51     setKeepLocalCollectionChanges(QSet<QByteArray>() << "ENTITYDISPLAY" << Akonadi::BlockAlarmsAttribute().type());
52 
53     settings(); // make sure the D-Bus settings interface is up
54 }
55 
~KolabResource()56 KolabResource::~KolabResource()
57 {
58 }
59 
settings() const60 Settings *KolabResource::settings() const
61 {
62     if (!m_settings) {
63         m_settings = new KolabSettings;
64     }
65 
66     return m_settings;
67 }
68 
delayedInit()69 void KolabResource::delayedInit()
70 {
71     ImapResourceBase::delayedInit();
72     settings()->setRetrieveMetadataOnFolderListing(false);
73     Q_ASSERT(!settings()->retrieveMetadataOnFolderListing());
74 }
75 
defaultName() const76 QString KolabResource::defaultName() const
77 {
78     return i18n("Kolab Resource");
79 }
80 
clientId() const81 QByteArray KolabResource::clientId() const
82 {
83     return QByteArrayLiteral("Kontact Kolab Resource 5/KOLAB");
84 }
85 
createConfigureDialog(WId windowId)86 QDialog *KolabResource::createConfigureDialog(WId windowId)
87 {
88     auto dlg = new SetupServer(this, windowId);
89     dlg->setAttribute(Qt::WA_NativeWindow, true);
90     KWindowSystem::setMainWindow(dlg->windowHandle(), windowId);
91     dlg->setWindowTitle(i18nc("@title:window", "Kolab Account Settings"));
92     dlg->setWindowIcon(QIcon::fromTheme(QStringLiteral("kolab")));
93     connect(dlg, &QDialog::finished, this, &KolabResource::onConfigurationDone);
94     return dlg;
95 }
96 
onConfigurationDone(int result)97 void KolabResource::onConfigurationDone(int result)
98 {
99     auto dlg = qobject_cast<SetupServer *>(sender());
100     if (result) {
101         if (dlg->shouldClearCache()) {
102             clearCache();
103         }
104         settings()->save();
105     }
106     dlg->deleteLater();
107 }
108 
createResourceState(const TaskArguments & args)109 ResourceStateInterface::Ptr KolabResource::createResourceState(const TaskArguments &args)
110 {
111     return ResourceStateInterface::Ptr(new KolabResourceState(this, args));
112 }
113 
retrieveCollections()114 void KolabResource::retrieveCollections()
115 {
116     qCDebug(KOLABRESOURCE_TRACE);
117     Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Retrieving folders"));
118 
119     startTask(new KolabRetrieveCollectionsTask(createResourceState(TaskArguments()), this));
120     synchronizeTags();
121     synchronizeRelations();
122 }
123 
itemAdded(const Akonadi::Item & item,const Akonadi::Collection & collection)124 void KolabResource::itemAdded(const Akonadi::Item &item, const Akonadi::Collection &collection)
125 {
126     qCDebug(KOLABRESOURCE_TRACE) << item.id() << collection.id();
127     bool ok = true;
128     const Akonadi::Item imapItem = KolabHelpers::translateToImap(item, ok);
129     if (!ok) {
130         qCWarning(KOLABRESOURCE_LOG) << "Failed to convert item";
131         cancelTask();
132         return;
133     }
134     ImapResourceBase::itemAdded(imapItem, collection);
135 }
136 
itemChanged(const Akonadi::Item & item,const QSet<QByteArray> & parts)137 void KolabResource::itemChanged(const Akonadi::Item &item, const QSet<QByteArray> &parts)
138 {
139     qCDebug(KOLABRESOURCE_TRACE) << item.id() << parts;
140     bool ok = true;
141     const Akonadi::Item imapItem = KolabHelpers::translateToImap(item, ok);
142     if (!ok) {
143         qCWarning(KOLABRESOURCE_LOG) << "Failed to convert item";
144         cancelTask();
145         return;
146     }
147     ImapResourceBase::itemChanged(imapItem, parts);
148 }
149 
itemsMoved(const Akonadi::Item::List & items,const Akonadi::Collection & source,const Akonadi::Collection & destination)150 void KolabResource::itemsMoved(const Akonadi::Item::List &items, const Akonadi::Collection &source, const Akonadi::Collection &destination)
151 {
152     qCDebug(KOLABRESOURCE_TRACE) << items.size() << source.id() << destination.id();
153     bool ok = true;
154     const Akonadi::Item::List imapItems = KolabHelpers::translateToImap(items, ok);
155     if (!ok) {
156         qCWarning(KOLABRESOURCE_LOG) << "Failed to convert item";
157         cancelTask();
158         return;
159     }
160     ImapResourceBase::itemsMoved(imapItems, source, destination);
161 }
162 
updateAnnotations(const Akonadi::Collection & collection)163 static Akonadi::Collection updateAnnotations(const Akonadi::Collection &collection)
164 {
165     qCDebug(KOLABRESOURCE_TRACE) << collection.id();
166     // Set the annotations on new folders
167     const QByteArray kolabType = KolabHelpers::kolabTypeForMimeType(collection.contentMimeTypes());
168     Akonadi::Collection col = collection;
169     auto attr = col.attribute<Akonadi::CollectionAnnotationsAttribute>(Akonadi::Collection::AddIfMissing);
170     QMap<QByteArray, QByteArray> annotations = attr->annotations();
171 
172     bool changed = false;
173     auto colorAttribute = col.attribute<Akonadi::CollectionColorAttribute>();
174     if (colorAttribute) {
175         const QColor color = colorAttribute->color();
176         if (color.isValid()) {
177             KolabHelpers::setFolderColor(annotations, color);
178             changed = true;
179         }
180     }
181 
182     if (!kolabType.isEmpty()) {
183         KolabHelpers::setFolderTypeAnnotation(annotations, kolabType);
184         changed = true;
185     }
186 
187     if (changed) {
188         attr->setAnnotations(annotations);
189         return col;
190     }
191     return collection;
192 }
193 
collectionAdded(const Akonadi::Collection & collection,const Akonadi::Collection & parent)194 void KolabResource::collectionAdded(const Akonadi::Collection &collection, const Akonadi::Collection &parent)
195 {
196     qCDebug(KOLABRESOURCE_TRACE) << collection.id() << parent.id();
197     // Set the annotations on new folders
198     const Akonadi::Collection col = updateAnnotations(collection);
199     // TODO we need to save the collections as well if the annotations have changed
200     // or we simply don't have the annotations locally, which perhaps is also not required?
201     ImapResourceBase::collectionAdded(col, parent);
202 }
203 
collectionChanged(const Akonadi::Collection & collection,const QSet<QByteArray> & parts)204 void KolabResource::collectionChanged(const Akonadi::Collection &collection, const QSet<QByteArray> &parts)
205 {
206     qCDebug(KOLABRESOURCE_TRACE) << collection.id() << parts;
207     QSet<QByteArray> p = parts;
208     // Update annotations if necessary
209     // FIXME col ?????
210     const Akonadi::Collection col = updateAnnotations(collection);
211     if (parts.contains(Akonadi::CollectionColorAttribute().type())) {
212         p << Akonadi::CollectionAnnotationsAttribute().type();
213     }
214 
215     // TODO we need to save the collections as well if the annotations have changed
216     Q_EMIT status(AgentBase::Running, i18nc("@info:status", "Updating folder '%1'", collection.name()));
217     auto task = new ChangeCollectionTask(createResourceState(TaskArguments(collection, p)), this);
218     task->syncEnabledState(true);
219     startTask(task);
220 }
221 
tagAdded(const Akonadi::Tag & tag)222 void KolabResource::tagAdded(const Akonadi::Tag &tag)
223 {
224     qCDebug(KOLABRESOURCE_TRACE) << tag.id();
225     auto task = new KolabAddTagTask(createResourceState(TaskArguments(tag)), this);
226     startTask(task);
227 }
228 
tagChanged(const Akonadi::Tag & tag)229 void KolabResource::tagChanged(const Akonadi::Tag &tag)
230 {
231     qCDebug(KOLABRESOURCE_TRACE) << tag.id();
232     auto task = new KolabChangeTagTask(createResourceState(TaskArguments(tag)), QSharedPointer<TagConverter>(new TagConverter), this);
233     startTask(task);
234 }
235 
tagRemoved(const Akonadi::Tag & tag)236 void KolabResource::tagRemoved(const Akonadi::Tag &tag)
237 {
238     qCDebug(KOLABRESOURCE_TRACE) << tag.id();
239     auto task = new KolabRemoveTagTask(createResourceState(TaskArguments(tag)), this);
240     startTask(task);
241 }
242 
itemsTagsChanged(const Akonadi::Item::List & items,const QSet<Akonadi::Tag> & addedTags,const QSet<Akonadi::Tag> & removedTags)243 void KolabResource::itemsTagsChanged(const Akonadi::Item::List &items, const QSet<Akonadi::Tag> &addedTags, const QSet<Akonadi::Tag> &removedTags)
244 {
245     qCDebug(KOLABRESOURCE_TRACE) << items.size() << addedTags.size() << removedTags.size();
246     auto task =
247         new KolabChangeItemsTagsTask(createResourceState(TaskArguments(items, addedTags, removedTags)), QSharedPointer<TagConverter>(new TagConverter), this);
248     startTask(task);
249 }
250 
retrieveTags()251 void KolabResource::retrieveTags()
252 {
253     qCDebug(KOLABRESOURCE_TRACE);
254     auto task = new KolabRetrieveTagTask(createResourceState(TaskArguments()), KolabRetrieveTagTask::RetrieveTags, this);
255     startTask(task);
256 }
257 
retrieveRelations()258 void KolabResource::retrieveRelations()
259 {
260     qCDebug(KOLABRESOURCE_TRACE);
261     auto task = new KolabRetrieveTagTask(createResourceState(TaskArguments()), KolabRetrieveTagTask::RetrieveRelations, this);
262     startTask(task);
263 }
264 
itemsRelationsChanged(const Akonadi::Item::List & items,const Akonadi::Relation::List & addedRelations,const Akonadi::Relation::List & removedRelations)265 void KolabResource::itemsRelationsChanged(const Akonadi::Item::List &items,
266                                           const Akonadi::Relation::List &addedRelations,
267                                           const Akonadi::Relation::List &removedRelations)
268 {
269     qCDebug(KOLABRESOURCE_TRACE) << items.size() << addedRelations.size() << removedRelations.size();
270     auto task = new KolabChangeItemsRelationsTask(createResourceState(TaskArguments(items, addedRelations, removedRelations)));
271     startTask(task);
272 }
273 
274 AKONADI_RESOURCE_MAIN(KolabResource)
275