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 "searchquery.h"
21
22#include <QtCore/QVariant>
23
24#include <KDebug>
25
26#include <qjson/parser.h>
27#include <qjson/serializer.h>
28
29using namespace Akonadi;
30
31class SearchTerm::Private : public QSharedData
32{
33public:
34 Private()
35 : QSharedData()
36 , condition(SearchTerm::CondEqual)
37 , relation(SearchTerm::RelAnd)
38 , isNegated(false)
39 {
40 }
41
42 Private(const Private &other)
43 : QSharedData(other)
44 , key(other.key)
45 , value(other.value)
46 , condition(other.condition)
47 , relation(other.relation)
48 , terms(other.terms)
49 , isNegated(other.isNegated)
50 {
51 }
52
53 bool operator==(const Private &other) const
54 {
55 return relation == other.relation
56 && isNegated == other.isNegated
57 && terms == other.terms
58 && key == other.key
59 && value == other.value
60 && condition == other.condition;
61 }
62
63 QString key;
64 QVariant value;
65 Condition condition;
66 Relation relation;
67 QList<SearchTerm> terms;
68 bool isNegated;
69};
70
71class SearchQuery::Private : public QSharedData
72{
73public:
74 Private()
75 : QSharedData()
76 , limit(-1)
77 {
78 }
79
80 Private(const Private &other)
81 : QSharedData(other)
82 , rootTerm(other.rootTerm)
83 , limit(other.limit)
84 {
85 }
86
87 bool operator==(const Private &other) const
88 {
89 return rootTerm == other.rootTerm && limit == other.limit;
90 }
91
92 static QVariantMap termToJSON(const SearchTerm &term)
93 {
94 const QList<SearchTerm> &subTerms = term.subTerms();
95 QVariantMap termJSON;
96 termJSON.insert(QLatin1String("negated"), term.isNegated());
97 if (subTerms.isEmpty()) {
98 termJSON.insert(QLatin1String("key"), term.key());
99 termJSON.insert(QLatin1String("value"), term.value());
100 termJSON.insert(QLatin1String("cond"), static_cast<int>(term.condition()));
101 } else {
102 termJSON.insert(QLatin1String("rel"), static_cast<int>(term.relation()));
103 QVariantList subTermsJSON;
104 Q_FOREACH (const SearchTerm &term, subTerms) {
105 subTermsJSON.append(termToJSON(term));
106 }
107 termJSON.insert(QLatin1String("subTerms"), subTermsJSON);
108 }
109
110 return termJSON;
111 }
112
113 static SearchTerm JSONToTerm(const QVariantMap &json)
114 {
115 if (json.contains(QLatin1String("key"))) {
116 SearchTerm term(json[QLatin1String("key")].toString(),
117 json[QLatin1String("value")],
118 static_cast<SearchTerm::Condition>(json[QLatin1String("cond")].toInt()));
119 term.setIsNegated(json[QLatin1String("negated")].toBool());
120 return term;
121 } else if (json.contains(QLatin1String("rel"))) {
122 SearchTerm term(static_cast<SearchTerm::Relation>(json[QLatin1String("rel")].toInt()));
123 term.setIsNegated(json[QLatin1String("negated")].toBool());
124 const QVariantList subTermsJSON = json[QLatin1String("subTerms")].toList();
125 Q_FOREACH (const QVariant &subTermJSON, subTermsJSON) {
126 term.addSubTerm(JSONToTerm(subTermJSON.toMap()));
127 }
128 return term;
129 } else {
130 kWarning() << "Invalid JSON for term: " << json;
131 return SearchTerm();
132 }
133 }
134
135 SearchTerm rootTerm;
136 int limit;
137};
138
139SearchTerm::SearchTerm(SearchTerm::Relation relation)
140 : d(new Private)
141{
142 d->relation = relation;
143}
144
145SearchTerm::SearchTerm(const QString &key, const QVariant &value, SearchTerm::Condition condition)
146 : d(new Private)
147{
148 d->relation = RelAnd;
149 d->key = key;
150 d->value = value;
151 d->condition = condition;
152}
153
154SearchTerm::SearchTerm(const SearchTerm &other)
155 : d(other.d)
156{
157}
158
159SearchTerm::~SearchTerm()
160{
161}
162
163SearchTerm &SearchTerm::operator=(const SearchTerm &other)
164{
165 d = other.d;
166 return *this;
167}
168
169bool SearchTerm::operator==(const SearchTerm &other) const
170{
171 return *d == *other.d;
172}
173
174bool SearchTerm::isNull() const
175{
176 return d->key.isEmpty() && d->value.isNull() && d->terms.isEmpty();
177}
178
179QString SearchTerm::key() const
180{
181 return d->key;
182}
183
184QVariant SearchTerm::value() const
185{
186 return d->value;
187}
188
189SearchTerm::Condition SearchTerm::condition() const
190{
191 return d->condition;
192}
193
194void SearchTerm::setIsNegated(bool negated)
195{
196 d->isNegated = negated;
197}
198
199bool SearchTerm::isNegated() const
200{
201 return d->isNegated;
202}
203
204void SearchTerm::addSubTerm(const SearchTerm &term)
205{
206 d->terms << term;
207}
208
209QList< SearchTerm > SearchTerm::subTerms() const
210{
211 return d->terms;
212}
213
214SearchTerm::Relation SearchTerm::relation() const
215{
216 return d->relation;
217}
218
219SearchQuery::SearchQuery(SearchTerm::Relation rel)
220 : d(new Private)
221{
222 d->rootTerm = SearchTerm(rel);
223}
224
225SearchQuery::SearchQuery(const SearchQuery &other)
226 : d(other.d)
227{
228}
229
230SearchQuery::~SearchQuery()
231{
232}
233
234SearchQuery &SearchQuery::operator=(const SearchQuery &other)
235{
236 d = other.d;
237 return *this;
238}
239
240bool SearchQuery::operator==(const SearchQuery &other) const
241{
242 return *d == *other.d;
243}
244
245bool SearchQuery::isNull() const
246{
247 return d->rootTerm.isNull();
248}
249
250SearchTerm SearchQuery::term() const
251{
252 return d->rootTerm;
253}
254
255void SearchQuery::addTerm(const QString &key, const QVariant &value, SearchTerm::Condition condition)
256{
257 addTerm(SearchTerm(key, value, condition));
258}
259
260void SearchQuery::addTerm(const SearchTerm &term)
261{
262 d->rootTerm.addSubTerm(term);
263}
264
265void SearchQuery::setTerm(const SearchTerm &term)
266{
267 d->rootTerm = term;
268}
269
270void SearchQuery::setLimit(int limit)
271{
272 d->limit = limit;
273}
274
275int SearchQuery::limit() const
276{
277 return d->limit;
278}
279
280QByteArray SearchQuery::toJSON() const
281{
282 QVariantMap root = Private::termToJSON(d->rootTerm);
283 root.insert(QLatin1String("limit"), d->limit);
284
285 QJson::Serializer serializer;
286 return serializer.serialize(root);
287}
288
289SearchQuery SearchQuery::fromJSON(const QByteArray &jsonData)
290{
291 QJson::Parser parser;
292 bool ok = false;
293 const QVariant json = parser.parse(jsonData, &ok);
294 if (!ok || json.isNull()) {
295 return SearchQuery();
296 }
297
298 const QVariantMap map = json.toMap();
299 SearchQuery query;
300 query.d->rootTerm = Private::JSONToTerm(map);
301 if (map.contains(QLatin1String("limit"))) {
302 query.d->limit = map.value(QLatin1String("limit")).toInt();
303 }
304 return query;
305}
306
307QMap<EmailSearchTerm::EmailSearchField, QString> initializeMapping()
308{
309 QMap<EmailSearchTerm::EmailSearchField, QString> mapping;
310 mapping.insert(EmailSearchTerm::Body, QLatin1String("body"));
311 mapping.insert(EmailSearchTerm::Headers, QLatin1String("headers"));
312 mapping.insert(EmailSearchTerm::Subject, QLatin1String("subject"));
313 mapping.insert(EmailSearchTerm::Message, QLatin1String("message"));
314 mapping.insert(EmailSearchTerm::HeaderFrom, QLatin1String("from"));
315 mapping.insert(EmailSearchTerm::HeaderTo, QLatin1String("to"));
316 mapping.insert(EmailSearchTerm::HeaderCC, QLatin1String("cc"));
317 mapping.insert(EmailSearchTerm::HeaderBCC, QLatin1String("bcc"));
318 mapping.insert(EmailSearchTerm::HeaderReplyTo, QLatin1String("replyto"));
319 mapping.insert(EmailSearchTerm::HeaderOrganization, QLatin1String("organization"));
320 mapping.insert(EmailSearchTerm::HeaderListId, QLatin1String("listid"));
321 mapping.insert(EmailSearchTerm::HeaderResentFrom, QLatin1String("resentfrom"));
322 mapping.insert(EmailSearchTerm::HeaderXLoop, QLatin1String("xloop"));
323 mapping.insert(EmailSearchTerm::HeaderXMailingList, QLatin1String("xmailinglist"));
324 mapping.insert(EmailSearchTerm::HeaderXSpamFlag, QLatin1String("xspamflag"));
325 mapping.insert(EmailSearchTerm::HeaderDate, QLatin1String("date"));
326 mapping.insert(EmailSearchTerm::HeaderOnlyDate, QLatin1String("onlydate"));
327 mapping.insert(EmailSearchTerm::MessageStatus, QLatin1String("messagestatus"));
328 mapping.insert(EmailSearchTerm::MessageTag, QLatin1String("messagetag"));
329 mapping.insert(EmailSearchTerm::ByteSize, QLatin1String("size"));
330 mapping.insert(EmailSearchTerm::Attachment, QLatin1String("attachment"));
331 return mapping;
332}
333
334static QMap<EmailSearchTerm::EmailSearchField, QString> emailSearchFieldMapping = initializeMapping();
335
336EmailSearchTerm::EmailSearchTerm(EmailSearchTerm::EmailSearchField field, const QVariant &value, SearchTerm::Condition condition)
337 : SearchTerm(toKey(field), value, condition)
338{
339
340}
341
342QString EmailSearchTerm::toKey(EmailSearchTerm::EmailSearchField field)
343{
344 return emailSearchFieldMapping.value(field);
345}
346
347EmailSearchTerm::EmailSearchField EmailSearchTerm::fromKey(const QString &key)
348{
349 return emailSearchFieldMapping.key(key);
350}
351
352QMap<ContactSearchTerm::ContactSearchField, QString> initializeContactMapping()
353{
354 QMap<ContactSearchTerm::ContactSearchField, QString> mapping;
355 mapping.insert(ContactSearchTerm::Name, QLatin1String("name"));
356 mapping.insert(ContactSearchTerm::Nickname, QLatin1String("nickname"));
357 mapping.insert(ContactSearchTerm::Email, QLatin1String("email"));
358 mapping.insert(ContactSearchTerm::Uid, QLatin1String("uid"));
359 mapping.insert(ContactSearchTerm::All, QLatin1String("all"));
360 return mapping;
361}
362
363static QMap<ContactSearchTerm::ContactSearchField, QString> contactSearchFieldMapping = initializeContactMapping();
364
365ContactSearchTerm::ContactSearchTerm(ContactSearchTerm::ContactSearchField field, const QVariant &value, SearchTerm::Condition condition)
366 : SearchTerm(toKey(field), value, condition)
367{
368
369}
370
371QString ContactSearchTerm::toKey(ContactSearchTerm::ContactSearchField field)
372{
373 return contactSearchFieldMapping.value(field);
374}
375
376ContactSearchTerm::ContactSearchField ContactSearchTerm::fromKey(const QString &key)
377{
378 return contactSearchFieldMapping.key(key);
379}
380