1/*
2 Copyright (c) 2014 Daniel Vr??til <dvratil@redhat.com>
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 "tagmodel_p.h"
21#include "tagmodel.h"
22
23#include "monitor.h"
24#include <akonadi/tagfetchjob.h>
25#include <QDebug>
26
27using namespace Akonadi;
28
29TagModelPrivate::TagModelPrivate(TagModel *parent)
30 : mMonitor(0)
31 , q_ptr(parent)
32{
33 // Root tag
34 mTags.insert(0, Tag(0));
35}
36
37TagModelPrivate::~TagModelPrivate()
38{
39}
40
41void TagModelPrivate::init(Monitor *monitor)
42{
43 Q_Q(TagModel);
44
45 mMonitor = monitor;
46
47 q->connect(mMonitor, SIGNAL(tagAdded(Akonadi::Tag)),
48 q, SLOT(monitoredTagAdded(Akonadi::Tag)));
49 q->connect(mMonitor, SIGNAL(tagChanged(Akonadi::Tag)),
50 q, SLOT(monitoredTagChanged(Akonadi::Tag)));
51 q->connect(mMonitor, SIGNAL(tagRemoved(Akonadi::Tag)),
52 q, SLOT(monitoredTagRemoved(Akonadi::Tag)));
53
54 TagFetchJob *fetchJob = new TagFetchJob(q);
55 fetchJob->setFetchScope(mMonitor->tagFetchScope());
56 q->connect(fetchJob, SIGNAL(tagsReceived(Akonadi::Tag::List)),
57 q, SLOT(tagsFetched(Akonadi::Tag::List)));
58 q->connect(fetchJob, SIGNAL(finished(KJob*)),
59 q, SLOT(tagsFetchDone(KJob*)));
60}
61
62QModelIndex TagModelPrivate::indexForTag(const qint64 tagId) const
63{
64 Q_Q(const TagModel);
65
66 if (!mTags.contains(tagId)) {
67 return QModelIndex();
68 }
69
70 const Tag tag = mTags.value(tagId);
71 if (!tag.isValid()) {
72 return QModelIndex();
73 }
74
75 const Tag::Id parentId = tag.parent().id();
76 const int row = mChildTags.value(parentId).indexOf(tag);
77 if (row != -1) {
78 return q->createIndex(row, 0, (int) parentId);
79 }
80
81 return QModelIndex();
82}
83
84Tag TagModelPrivate::tagForIndex(const QModelIndex &index) const
85{
86 if (!index.isValid()) {
87 return Tag();
88 }
89
90 const Tag::Id parentId = index.internalId();
91 const Tag::List &children = mChildTags.value(parentId);
92 return children.at(index.row());
93}
94
95void TagModelPrivate::monitoredTagAdded(const Tag &tag)
96{
97 Q_Q(TagModel);
98
99 const qint64 parentId = tag.parent().id();
100
101 // Parent not yet in model, defer for later
102 if (!mTags.contains(parentId)) {
103 Tag::List &list = mPendingTags[parentId];
104 list.append(tag);
105 return;
106 }
107
108 Tag::List &children = mChildTags[parentId];
109
110 q->beginInsertRows(indexForTag(parentId), children.count(), children.count());
111 mTags.insert(tag.id(), tag);
112 children.append(tag);
113 q->endInsertRows();
114
115 // If there are any child tags waiting for this parent, insert them
116 if (mPendingTags.contains(tag.id())) {
117 const Tag::List pendingChildren = mPendingTags.take(tag.id());
118 Tag::List &children = mChildTags[tag.id()];
119 q->beginInsertRows(indexForTag(tag.id()), 0, pendingChildren.count() - 1);
120 Q_FOREACH (const Tag &child, pendingChildren) {
121 mTags.insert(child.id(), child);
122 children.append(child);
123 }
124 q->endInsertRows();
125 }
126}
127
128void TagModelPrivate::removeTagsRecursively(qint64 tagId)
129{
130 const Tag tag = mTags.value(tagId);
131
132 // Remove all children first
133 const Tag::List childTags = mChildTags.take(tagId);
134 Q_FOREACH (const Tag &child, childTags) {
135 removeTagsRecursively(child.id());
136 }
137
138 // Remove the actual tag
139 Tag::List &siblings = mChildTags[tag.parent().id()];
140 siblings.removeOne(tag);
141 mTags.remove(tag.id());
142}
143
144void TagModelPrivate::monitoredTagRemoved(const Tag &tag)
145{
146 Q_Q(TagModel);
147
148 // Better lookup parent in our cache
149 qint64 parentId = mTags.value(tag.id()).parent().id();
150 if (parentId == -1) {
151 kWarning() << "Got removal notification for unknown tag" << tag.id();
152 return;
153 }
154
155 Tag::List &siblings = mChildTags[parentId];
156 const int pos = siblings.indexOf(tag);
157 Q_ASSERT(pos != -1);
158
159 q->beginRemoveRows(indexForTag(parentId), pos, pos);
160 removeTagsRecursively(tag.id());
161 q->endRemoveRows();
162}
163
164void TagModelPrivate::monitoredTagChanged(const Tag &tag)
165{
166 Q_Q(TagModel);
167
168 if (!mTags.contains(tag.id())) {
169 kWarning() << "Got change notifications for unknown tag" << tag.id();
170 return;
171 }
172
173 const Tag oldTag = mTags.value(tag.id());
174 // Replace existing tag in cache
175 mTags.insert(tag.id(), tag);
176
177 // Check whether the tag has been reparented
178 const qint64 oldParent = oldTag.parent().id();
179 const qint64 newParent = tag.parent().id();
180 if (oldParent != newParent) {
181 const QModelIndex sourceParent = indexForTag(oldParent);
182 const int sourcePos = mChildTags.value(oldParent).indexOf(oldTag);
183 const QModelIndex destParent = indexForTag(newParent);
184 const int destPos = mChildTags.value(newParent).count();
185
186 q->beginMoveRows(sourceParent, sourcePos, sourcePos, destParent, destPos);
187 Tag::List &oldSiblings = mChildTags[oldParent];
188 oldSiblings.removeAt(sourcePos);
189 Tag::List &newSiblings = mChildTags[newParent];
190 newSiblings.append(tag);
191 q->endMoveRows();
192 } else {
193 Tag::List &children = mChildTags[oldParent];
194 const int sourcePos = children.indexOf(oldTag);
195 if (sourcePos != -1) {
196 children[sourcePos] = tag;
197 }
198
199 const QModelIndex index = indexForTag(tag.id());
200 q->dataChanged(index, index);
201 }
202}
203
204void TagModelPrivate::tagsFetched(const Tag::List &tags)
205{
206 Q_FOREACH (const Tag &tag, tags) {
207 monitoredTagAdded(tag);
208 }
209}
210
211void TagModelPrivate::tagsFetchDone(KJob *job)
212{
213 if (job->error()) {
214 kWarning() << job->errorString();
215 return;
216 }
217
218 if (!mPendingTags.isEmpty()) {
219 kWarning() << "Fetched all tags from server, but there are still" << mPendingTags.count() << "orphan tags";
220 return;
221 }
222}
223