1/*
2 Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>
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 "itemsearchjob.h"
21
22#include "imapparser_p.h"
23#include "itemfetchscope.h"
24#include "job_p.h"
25#include "protocolhelper_p.h"
26#include "searchquery.h"
27
28#include <QtCore/QTimer>
29#include <QThreadStorage>
30
31using namespace Akonadi;
32
33class Akonadi::ItemSearchJobPrivate : public JobPrivate
34{
35public:
36 ItemSearchJobPrivate(ItemSearchJob *parent, const SearchQuery &query)
37 : JobPrivate(parent)
38 , mQuery(query)
39 , mRecursive(false)
40 , mRemote(false)
41 , mEmitTimer(0)
42 {
43 }
44
45 void init()
46 {
47 Q_Q(ItemSearchJob);
48 mEmitTimer = new QTimer(q);
49 mEmitTimer->setSingleShot(true);
50 mEmitTimer->setInterval(100);
51 q->connect(mEmitTimer, SIGNAL(timeout()), q, SLOT(timeout()));
52 q->connect(q, SIGNAL(result(KJob*)), q, SLOT(timeout()));
53 }
54
55 void timeout()
56 {
57 Q_Q(Akonadi::ItemSearchJob);
58
59 mEmitTimer->stop(); // in case we are called by result()
60 if (!mPendingItems.isEmpty()) {
61 if (!q->error()) {
62 emit q->itemsReceived(mPendingItems);
63 }
64 mPendingItems.clear();
65 }
66 }
67
68 QString jobDebuggingString() const /*Q_DECL_OVERRIDE*/ {
69 QStringList flags;
70 if ( mRecursive ) {
71 flags.append( QLatin1String( "recursive" ) );
72 }
73 if ( mRemote ) {
74 flags.append( QLatin1String( "remote" ) );
75 }
76 if ( mCollections.isEmpty() ) {
77 flags.append( QLatin1String( "all collections" ) );
78 } else {
79 flags.append( QString::fromLatin1( "%1 collections" ).arg( mCollections.count() ) );
80 }
81 return QString::fromLatin1( "%1,json=%2" ).arg( flags.join(QLatin1String(","))).arg(QString::fromUtf8(mQuery.toJSON()));
82 }
83
84 Q_DECLARE_PUBLIC(ItemSearchJob)
85
86 SearchQuery mQuery;
87 Collection::List mCollections;
88 QStringList mMimeTypes;
89 bool mRecursive;
90 bool mRemote;
91 ItemFetchScope mFetchScope;
92
93 Item::List mItems;
94 Item::List mPendingItems; // items pending for emitting itemsReceived()
95
96 QTimer *mEmitTimer;
97};
98
99QThreadStorage<Session *> instances;
100
101static Session *defaultSearchSession()
102{
103 if (!instances.hasLocalData()) {
104 const QByteArray sessionName = Session::defaultSession()->sessionId() + "-SearchSession";
105 instances.setLocalData(new Session(sessionName));
106 }
107 return instances.localData();
108}
109
110static QObject *sessionForJob(QObject *parent)
111{
112 if (qobject_cast<Job *>(parent) || qobject_cast<Session *>(parent)) {
113 return parent;
114 }
115 return defaultSearchSession();
116}
117
118ItemSearchJob::ItemSearchJob(const SearchQuery &query, QObject *parent)
119 : Job(new ItemSearchJobPrivate(this, query), sessionForJob(parent))
120{
121 Q_D(ItemSearchJob);
122
123 d->init();
124}
125
126ItemSearchJob::ItemSearchJob(const QString &query, QObject *parent)
127 : Job(new ItemSearchJobPrivate(this, SearchQuery::fromJSON(query.toUtf8())), sessionForJob(parent))
128{
129 Q_D(ItemSearchJob);
130
131 d->init();
132}
133
134ItemSearchJob::~ItemSearchJob()
135{
136}
137
138void ItemSearchJob::setQuery(const QString &query)
139{
140 Q_D(ItemSearchJob);
141
142 d->mQuery = SearchQuery::fromJSON(query.toUtf8());
143}
144
145void ItemSearchJob::setQuery(const SearchQuery &query)
146{
147 Q_D(ItemSearchJob);
148
149 d->mQuery = query;
150}
151
152void ItemSearchJob::setFetchScope(const ItemFetchScope &fetchScope)
153{
154 Q_D(ItemSearchJob);
155
156 d->mFetchScope = fetchScope;
157}
158
159ItemFetchScope &ItemSearchJob::fetchScope()
160{
161 Q_D(ItemSearchJob);
162
163 return d->mFetchScope;
164}
165
166void ItemSearchJob::setSearchCollections(const Collection::List &collections)
167{
168 Q_D(ItemSearchJob);
169
170 d->mCollections = collections;
171}
172
173Collection::List ItemSearchJob::searchCollections() const
174{
175 return d_func()->mCollections;
176}
177
178void ItemSearchJob::setMimeTypes(const QStringList &mimeTypes)
179{
180 Q_D(ItemSearchJob);
181
182 d->mMimeTypes = mimeTypes;
183}
184
185QStringList ItemSearchJob::mimeTypes() const
186{
187 return d_func()->mMimeTypes;
188}
189
190void ItemSearchJob::setRecursive(bool recursive)
191{
192 Q_D(ItemSearchJob);
193
194 d->mRecursive = recursive;
195}
196
197bool ItemSearchJob::isRecursive() const
198{
199 return d_func()->mRecursive;
200}
201
202void ItemSearchJob::setRemoteSearchEnabled(bool enabled)
203{
204 Q_D(ItemSearchJob);
205
206 d->mRemote = enabled;
207}
208
209bool ItemSearchJob::isRemoteSearchEnabled() const
210{
211 return d_func()->mRemote;
212}
213
214void ItemSearchJob::doStart()
215{
216 Q_D(ItemSearchJob);
217
218 QByteArray command = d->newTag() + " SEARCH ";
219 if (!d->mMimeTypes.isEmpty()) {
220 command += "MIMETYPE (" + d->mMimeTypes.join(QLatin1String(" ")).toLatin1() + ") ";
221 }
222 if (!d->mCollections.isEmpty()) {
223 command += "COLLECTIONS (";
224 Q_FOREACH (const Collection &collection, d->mCollections) {
225 command += QByteArray::number(collection.id()) + ' ';
226 }
227 command += ") ";
228 }
229 if (d->mRecursive) {
230 command += "RECURSIVE ";
231 }
232 if (d->mRemote) {
233 command += "REMOTE ";
234 }
235
236 command += "QUERY " + ImapParser::quote(d->mQuery.toJSON());
237 command += ' ' + ProtocolHelper::itemFetchScopeToByteArray(d->mFetchScope);
238 command += '\n';
239 d->writeData(command);
240}
241
242void ItemSearchJob::doHandleResponse(const QByteArray &tag, const QByteArray &data)
243{
244 Q_D(ItemSearchJob);
245
246 if (tag == "*") {
247 int begin = data.indexOf("SEARCH");
248 if (begin >= 0) {
249
250 // split fetch response into key/value pairs
251 QList<QByteArray> fetchResponse;
252 ImapParser::parseParenthesizedList(data, fetchResponse, begin + 7);
253
254 Item item;
255 ProtocolHelper::parseItemFetchResult(fetchResponse, item);
256 if (!item.isValid()) {
257 return;
258 }
259
260 d->mItems.append(item);
261 d->mPendingItems.append(item);
262 if (!d->mEmitTimer->isActive()) {
263 d->mEmitTimer->start();
264 }
265 return;
266 }
267 }
268 kDebug() << "Unhandled response: " << tag << data;
269}
270
271Item::List ItemSearchJob::items() const
272{
273 Q_D(const ItemSearchJob);
274
275 return d->mItems;
276}
277
278QUrl ItemSearchJob::akonadiItemIdUri()
279{
280 return QUrl(QLatin1String("http://akonadi-project.org/ontologies/aneo#akonadiItemId"));
281}
282
283#include "moc_itemsearchjob.cpp"
284