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 | |
31 | using namespace Akonadi; |
32 | |
33 | class Akonadi::ItemSearchJobPrivate : public JobPrivate |
34 | { |
35 | public: |
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 | |
99 | QThreadStorage<Session *> instances; |
100 | |
101 | static 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 | |
110 | static QObject *sessionForJob(QObject *parent) |
111 | { |
112 | if (qobject_cast<Job *>(parent) || qobject_cast<Session *>(parent)) { |
113 | return parent; |
114 | } |
115 | return defaultSearchSession(); |
116 | } |
117 | |
118 | ItemSearchJob::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 | |
126 | ItemSearchJob::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 | |
134 | ItemSearchJob::~ItemSearchJob() |
135 | { |
136 | } |
137 | |
138 | void ItemSearchJob::setQuery(const QString &query) |
139 | { |
140 | Q_D(ItemSearchJob); |
141 | |
142 | d->mQuery = SearchQuery::fromJSON(query.toUtf8()); |
143 | } |
144 | |
145 | void ItemSearchJob::setQuery(const SearchQuery &query) |
146 | { |
147 | Q_D(ItemSearchJob); |
148 | |
149 | d->mQuery = query; |
150 | } |
151 | |
152 | void ItemSearchJob::setFetchScope(const ItemFetchScope &fetchScope) |
153 | { |
154 | Q_D(ItemSearchJob); |
155 | |
156 | d->mFetchScope = fetchScope; |
157 | } |
158 | |
159 | ItemFetchScope &ItemSearchJob::fetchScope() |
160 | { |
161 | Q_D(ItemSearchJob); |
162 | |
163 | return d->mFetchScope; |
164 | } |
165 | |
166 | void ItemSearchJob::setSearchCollections(const Collection::List &collections) |
167 | { |
168 | Q_D(ItemSearchJob); |
169 | |
170 | d->mCollections = collections; |
171 | } |
172 | |
173 | Collection::List ItemSearchJob::searchCollections() const |
174 | { |
175 | return d_func()->mCollections; |
176 | } |
177 | |
178 | void ItemSearchJob::setMimeTypes(const QStringList &mimeTypes) |
179 | { |
180 | Q_D(ItemSearchJob); |
181 | |
182 | d->mMimeTypes = mimeTypes; |
183 | } |
184 | |
185 | QStringList ItemSearchJob::mimeTypes() const |
186 | { |
187 | return d_func()->mMimeTypes; |
188 | } |
189 | |
190 | void ItemSearchJob::setRecursive(bool recursive) |
191 | { |
192 | Q_D(ItemSearchJob); |
193 | |
194 | d->mRecursive = recursive; |
195 | } |
196 | |
197 | bool ItemSearchJob::isRecursive() const |
198 | { |
199 | return d_func()->mRecursive; |
200 | } |
201 | |
202 | void ItemSearchJob::setRemoteSearchEnabled(bool enabled) |
203 | { |
204 | Q_D(ItemSearchJob); |
205 | |
206 | d->mRemote = enabled; |
207 | } |
208 | |
209 | bool ItemSearchJob::isRemoteSearchEnabled() const |
210 | { |
211 | return d_func()->mRemote; |
212 | } |
213 | |
214 | void 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 | |
242 | void 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 | |
271 | Item::List ItemSearchJob::items() const |
272 | { |
273 | Q_D(const ItemSearchJob); |
274 | |
275 | return d->mItems; |
276 | } |
277 | |
278 | QUrl ItemSearchJob::akonadiItemIdUri() |
279 | { |
280 | return QUrl(QLatin1String("http://akonadi-project.org/ontologies/aneo#akonadiItemId" )); |
281 | } |
282 | |
283 | #include "moc_itemsearchjob.cpp" |
284 | |