|
1 /* |
|
2 * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: Private data and helper classes used by class CntCache. |
|
15 * |
|
16 */ |
|
17 |
|
18 #include <qtcontacts.h> |
|
19 #include <qcontactmanager.h> |
|
20 #include <hbapplication.h> |
|
21 #include <thumbnailmanager_qt.h> |
|
22 #include <hbicon.h> |
|
23 #include <QTimer> |
|
24 #include "cntcache.h" |
|
25 #include "cntcache_p.h" |
|
26 #include "cntinfoprovider.h" |
|
27 #include "cntdefaultinfoprovider.h" |
|
28 |
|
29 // maximum amount of info and icon jobs respectively -- if there are more jobs, |
|
30 // then the oldest job is skipped and the client informed that this happened |
|
31 // in this way the client can request the job again if wanted |
|
32 static const int CntMaxInfoJobs = 20; |
|
33 static const int CntMaxIconJobs = 20; |
|
34 // amount of milliseconds to postpone the jobs if the UI is very active |
|
35 static const int PostponeJobsMilliSeconds = 300; |
|
36 // the event for starting to do all the outstanding jobs |
|
37 static const QEvent::Type DoAllJobsEvent = QEvent::User; |
|
38 // the id that states that no icon is currently pending from thumbnail manager |
|
39 static const int NoIconRequest = -1; |
|
40 |
|
41 // TODO: Provide a way (cenrep keys?) for UI to set which provider to use for |
|
42 // what info field (and what info fields are indeed even in use). |
|
43 |
|
44 /*! |
|
45 Creates a new thread for fetching contact info and icons in the background. |
|
46 */ |
|
47 CntCacheThread::CntCacheThread() |
|
48 : mContactManager(new QContactManager()), |
|
49 mJobLoopRunning(false), |
|
50 mPostponeJobs(false), |
|
51 mIconRequestId(NoIconRequest) |
|
52 { |
|
53 DP_IN("CntCacheThread::CntCacheThread()"); |
|
54 |
|
55 // create static provider plugins |
|
56 mDataProviders.insert(new CntDefaultInfoProvider(), ContactInfoAllFields); |
|
57 // TODO: create more static provider plugins |
|
58 |
|
59 // TODO: load dynamic provider plugins using QPluginLoader |
|
60 |
|
61 // connect the providers |
|
62 QMapIterator<CntInfoProvider*, ContactInfoFields> i(mDataProviders); |
|
63 while (i.hasNext()) { |
|
64 i.next(); |
|
65 connect(qobject_cast<CntInfoProvider*>(i.key()), |
|
66 SIGNAL(infoFieldReady(CntInfoProvider*, int, ContactInfoField, const QString&)), |
|
67 this, |
|
68 SLOT(onInfoFieldReady(CntInfoProvider*, int, ContactInfoField, const QString&))); |
|
69 } |
|
70 |
|
71 // create & connect the thumbnail manager |
|
72 mThumbnailManager = new ThumbnailManager(this); |
|
73 mThumbnailManager->setMode(ThumbnailManager::Default); |
|
74 mThumbnailManager->setQualityPreference(ThumbnailManager::OptimizeForPerformance); |
|
75 mThumbnailManager->setThumbnailSize(ThumbnailManager::ThumbnailSmall); |
|
76 connect(mThumbnailManager, SIGNAL(thumbnailReady(QPixmap, void *, int, int)), |
|
77 this, SLOT(onIconReady(QPixmap, void *, int, int))); |
|
78 |
|
79 // this thread should interfere as little as possible with more time-critical tasks, |
|
80 // like updating the UI during scrolling |
|
81 start(QThread::IdlePriority); |
|
82 |
|
83 DP_OUT("CntCacheThread::CntCacheThread()"); |
|
84 } |
|
85 |
|
86 /*! |
|
87 Cleans up and destructs the thread. |
|
88 */ |
|
89 CntCacheThread::~CntCacheThread() |
|
90 { |
|
91 DP_IN("CntCacheThread::~CntCacheThread()"); |
|
92 |
|
93 disconnect(this); |
|
94 |
|
95 mJobMutex.lock(); |
|
96 mInfoJobs.clear(); |
|
97 mCancelledInfoJobs.clear(); |
|
98 mIconJobs.clear(); |
|
99 mCancelledIconJobs.clear(); |
|
100 |
|
101 if (mIconRequestId != NoIconRequest) { |
|
102 mThumbnailManager->cancelRequest(mIconRequestId); |
|
103 mIconRequestId = NoIconRequest; |
|
104 } |
|
105 |
|
106 QMapIterator<CntInfoProvider*, ContactInfoFields> i(mDataProviders); |
|
107 while (i.hasNext()) { |
|
108 i.next(); |
|
109 delete i.key(); |
|
110 } |
|
111 mDataProviders.clear(); |
|
112 |
|
113 mJobMutex.unlock(); |
|
114 |
|
115 exit(); |
|
116 wait(); |
|
117 |
|
118 DP_OUT("CntCacheThread::~CntCacheThread()"); |
|
119 } |
|
120 |
|
121 /*! |
|
122 Starts the event loop for this thread. |
|
123 */ |
|
124 void CntCacheThread::run() |
|
125 { |
|
126 DP_IN("CntCacheThread::run()"); |
|
127 exec(); |
|
128 DP_OUT("CntCacheThread::run()"); |
|
129 } |
|
130 |
|
131 /*! |
|
132 Schedules a info to be fetched for a contact. When info has been fetched |
|
133 infoFieldUpdated() signals will be emitted, once for each field. |
|
134 |
|
135 /param contactId the contact for which the info is wanted |
|
136 */ |
|
137 void CntCacheThread::scheduleInfoJob(int contactId) |
|
138 { |
|
139 DP_IN("CntCacheThread::scheduleInfoJob(" << contactId << ")"); |
|
140 |
|
141 Q_ASSERT(contactId > 0 && !mInfoJobs.contains(contactId)); |
|
142 |
|
143 mJobMutex.lock(); |
|
144 |
|
145 if (!mJobLoopRunning) { |
|
146 // new job => restart job loop |
|
147 mJobLoopRunning = true; |
|
148 HbApplication::instance()->postEvent(this, new QEvent(DoAllJobsEvent)); |
|
149 } |
|
150 |
|
151 if (mInfoJobs.count() >= CntMaxInfoJobs) { |
|
152 // the queue of jobs is full, so remove the oldest job |
|
153 mCancelledInfoJobs.append(mInfoJobs.takeFirst()); |
|
154 DP("CntCacheThread::scheduleInfoJob() :" << mCancelledInfoJobs.last() << "removed from joblist"); |
|
155 } |
|
156 |
|
157 mInfoJobs.append(contactId); |
|
158 DP("CntCacheThread::scheduleInfoJob() :" << contactId << "appended @" << mInfoJobs.indexOf(contactId)); |
|
159 |
|
160 // since this job has now been scheduled, remove it from the list of |
|
161 // cancelled jobs in case it is there |
|
162 mCancelledInfoJobs.removeOne(contactId); |
|
163 |
|
164 mJobMutex.unlock(); |
|
165 |
|
166 DP_OUT("CntCacheThread::scheduleInfoJob(" << contactId << ")"); |
|
167 } |
|
168 |
|
169 /*! |
|
170 Schedules an icon to be fetched. An iconUpdated() signal will be emitted when the icon |
|
171 has been fetched. |
|
172 |
|
173 /param iconName the name of the icon to be fetched |
|
174 */ |
|
175 void CntCacheThread::scheduleIconJob(const QString& iconName) |
|
176 { |
|
177 DP_IN("CntCacheThread::scheduleIconJob(" << iconName << ")"); |
|
178 |
|
179 mJobMutex.lock(); |
|
180 |
|
181 Q_ASSERT(!iconName.isEmpty() && !mIconJobs.contains(iconName)); |
|
182 |
|
183 if (!mJobLoopRunning) { |
|
184 // new job, so restart job loop |
|
185 mJobLoopRunning = true; |
|
186 HbApplication::instance()->postEvent(this, new QEvent(DoAllJobsEvent)); |
|
187 } |
|
188 |
|
189 if (mIconJobs.count() >= CntMaxIconJobs) { |
|
190 // the queue of jobs is full, so remove the oldest job |
|
191 mCancelledIconJobs.append(mIconJobs.takeLast()); |
|
192 DP("CntCacheThread::scheduleIconJob() :" << mCancelledIconJobs.last() << "removed from joblist"); |
|
193 } |
|
194 |
|
195 mIconJobs.append(iconName); |
|
196 DP("CntCacheThread::scheduleIconJob() :" << iconName << "appended @" << mIconJobs.indexOf(iconName)); |
|
197 |
|
198 // since this job has now been rescheduled, remove it from the list of |
|
199 // cancelled jobs in case it is there |
|
200 mCancelledIconJobs.removeOne(iconName); |
|
201 |
|
202 mJobMutex.unlock(); |
|
203 |
|
204 DP_OUT("CntCacheThread::scheduleIconJob(" << iconName << ")"); |
|
205 } |
|
206 |
|
207 /*! |
|
208 Postpones outstanding jobs for a few tenths of a second. This should be called if |
|
209 the client wants to reserve more CPU time for some urgent task. |
|
210 */ |
|
211 void CntCacheThread::postponeJobs() |
|
212 { |
|
213 DP_IN("CntCacheThread::postponeJobs()"); |
|
214 |
|
215 mPostponeJobs = true; |
|
216 |
|
217 DP_OUT("CntCacheThread::postponeJobs()"); |
|
218 } |
|
219 |
|
220 /*! |
|
221 Handles a class-specific event that is sent by the scheduleOrUpdate functions |
|
222 when there are jobs. |
|
223 */ |
|
224 bool CntCacheThread::event(QEvent* event) |
|
225 { |
|
226 if (event->type() == DoAllJobsEvent) { |
|
227 doAllJobs(); |
|
228 return true; |
|
229 } |
|
230 |
|
231 return QThread::event(event); |
|
232 } |
|
233 |
|
234 /*! |
|
235 Does the jobs. The loop runs until all jobs are done. It pauses |
|
236 for a while if new info jobs appear -- this means that the UI is |
|
237 updating and so the CPU is yielded to the UI. If there are again |
|
238 new jobs after the pause, then it pauses again, and so on. |
|
239 */ |
|
240 void CntCacheThread::doAllJobs() |
|
241 { |
|
242 DP_IN("CntCacheThread::doAllJobs()"); |
|
243 |
|
244 forever { |
|
245 mJobMutex.lock(); |
|
246 int infoJobs = mInfoJobs.count(); |
|
247 int iconJobs = mIconJobs.count(); |
|
248 int totalJobs = infoJobs + iconJobs + mCancelledInfoJobs.count() + mCancelledIconJobs.count(); |
|
249 DP_IN("CntCacheThread::doAllJobs() : infojobs=" << infoJobs << ", iconjobs=" << iconJobs << ",icon_request=" << mIconRequestId << ", cancelledinfojobs=" << mCancelledInfoJobs.count() << ", cancellediconjobs=" << mCancelledIconJobs.count()); |
|
250 |
|
251 if (totalJobs == 0 || totalJobs == iconJobs && mIconRequestId != NoIconRequest || mPostponeJobs) { |
|
252 if (mPostponeJobs) { |
|
253 // client has requested a pause in activies (e.g. due to high UI activity) |
|
254 mPostponeJobs = false; |
|
255 if (totalJobs > 0) { |
|
256 QTimer::singleShot(PostponeJobsMilliSeconds, this, SLOT(doAllJobs())); |
|
257 DP("CntCacheThread::doAllJobs() : postponing for" << PostponeJobsMilliSeconds << "ms"); |
|
258 } |
|
259 else { |
|
260 mJobLoopRunning = false; |
|
261 } |
|
262 } |
|
263 else { |
|
264 mJobLoopRunning = false; |
|
265 } |
|
266 |
|
267 mJobMutex.unlock(); |
|
268 |
|
269 if (totalJobs == 0) { |
|
270 DP("CntCacheThread::doAllJobs() : emitting all jobs done"); |
|
271 emit allJobsDone(); |
|
272 } |
|
273 |
|
274 break; |
|
275 } |
|
276 |
|
277 bool doInfoJobs = infoJobs > 0 && (iconJobs == 0 || mIconRequestId != NoIconRequest || qrand() % (infoJobs + iconJobs) < infoJobs); |
|
278 |
|
279 if (doInfoJobs) { |
|
280 // get next job |
|
281 int contactId = mInfoJobs.takeLast(); |
|
282 mJobMutex.unlock(); |
|
283 |
|
284 // fetch qcontact |
|
285 QStringList definitionRestrictions; |
|
286 definitionRestrictions.append(QContactName::DefinitionName); |
|
287 definitionRestrictions.append(QContactAvatar::DefinitionName); |
|
288 definitionRestrictions.append(QContactPhoneNumber::DefinitionName); |
|
289 definitionRestrictions.append(QContactOrganization::DefinitionName); |
|
290 QContactFetchHint restrictions; |
|
291 restrictions.setDetailDefinitionsHint(definitionRestrictions); |
|
292 restrictions.setOptimizationHints(QContactFetchHint::NoRelationships); |
|
293 QContact contact = mContactManager->contact(contactId, restrictions); |
|
294 |
|
295 // request contact info from providers |
|
296 DP("CntCacheThread::doAllJobs() : fetching info for" << contact.displayLabel() << " (id=" << contactId << ")"); |
|
297 QMapIterator<CntInfoProvider*, ContactInfoFields> i(mDataProviders); |
|
298 while (i.hasNext()) { |
|
299 i.next(); |
|
300 if (i.value() != 0) { |
|
301 i.key()->requestInfo(contact, i.value()); |
|
302 } |
|
303 } |
|
304 } |
|
305 else if (iconJobs > 0 && mIconRequestId == NoIconRequest) { |
|
306 // request icon from thumbnail manager |
|
307 QString iconName = mIconJobs.takeFirst(); |
|
308 DP("CntCacheThread::doAllJobs() : fetching icon" << iconName); |
|
309 mIconRequestId = mThumbnailManager->getThumbnail(iconName, NULL, 0); |
|
310 mIconRequestName = iconName; |
|
311 mJobMutex.unlock(); |
|
312 } |
|
313 else { |
|
314 if (mCancelledInfoJobs.count() > 0) { |
|
315 int contactId = mCancelledInfoJobs.takeLast(); |
|
316 mJobMutex.unlock(); |
|
317 DP("CntCacheThread::doAllJobs() : emitting cancelled info job" << contactId); |
|
318 emit infoCancelled(contactId); |
|
319 } |
|
320 else if (mCancelledIconJobs.count() > 0) { |
|
321 QString iconName = mCancelledIconJobs.takeFirst(); |
|
322 mJobMutex.unlock(); |
|
323 DP("CntCacheThread::doAllJobs() : emitting cancelled icon job" << iconName); |
|
324 emit iconCancelled(iconName); |
|
325 } |
|
326 } |
|
327 |
|
328 // allow signals to be passed from providers and from the client |
|
329 HbApplication::processEvents(); |
|
330 } |
|
331 |
|
332 DP_OUT("CntCacheThread::doAllJobs()"); |
|
333 } |
|
334 |
|
335 /*! |
|
336 Passes an info field from a data provider up to the client via signals. The |
|
337 client is not in the same thread, so Qt passes the signal as an event. |
|
338 */ |
|
339 void CntCacheThread::onInfoFieldReady(CntInfoProvider* sender, int contactId, |
|
340 ContactInfoField field, const QString& text) |
|
341 { |
|
342 DP_IN("CntCacheThread::onInfoFieldReady( CntInfoProvider*," << contactId << "," << field << "," << text << ")"); |
|
343 |
|
344 // there can be 3rd party providers, so we cannot blindly trust them; |
|
345 // info is emitted only if: |
|
346 // 1) the sender is in the list of providers |
|
347 // 2) exactly one field bit is set in parameter 'field' |
|
348 // 3) the field bit has been assigned to this provider |
|
349 if (mDataProviders.contains(sender) |
|
350 && ((field & (field - 1)) == 0) |
|
351 && ((field & mDataProviders.value(sender)) != 0)) { |
|
352 DP("CntCacheThread::onInfoFieldReady(" << contactId << "," << field << "," << text << ") : emitting infoFieldUpdated()"); |
|
353 emit infoFieldUpdated(contactId, field, text); |
|
354 } |
|
355 |
|
356 DP_OUT("CntCacheThread::onInfoFieldReady(" << contactId << "," << field << "," << text << ")"); |
|
357 } |
|
358 |
|
359 /*! |
|
360 Passes an icon from thumbnail manager up to the client via a signal. The |
|
361 client is not in the same thread, so Qt passes the signal as an event. |
|
362 */ |
|
363 void CntCacheThread::onIconReady(const QPixmap& pixmap, void *data, int id, int error) |
|
364 { |
|
365 DP_IN("CntCacheThread::onIconReady( QPixMap, void*, " << id << "," << error << ")"); |
|
366 Q_UNUSED(data); |
|
367 |
|
368 mJobMutex.lock(); |
|
369 Q_ASSERT(id == mIconRequestId && !mIconRequestName.isEmpty()); |
|
370 if (!mJobLoopRunning) { |
|
371 // job loop quit while waiting for this icon, so restart it |
|
372 mJobLoopRunning = true; |
|
373 HbApplication::instance()->postEvent(this, new QEvent(DoAllJobsEvent)); |
|
374 } |
|
375 mIconRequestId = NoIconRequest; |
|
376 mJobMutex.unlock(); |
|
377 |
|
378 if (error == 0) { |
|
379 DP("CntCacheThread::onIconReady() : emitting iconUpdated(" << mIconRequestName << ")"); |
|
380 emit iconUpdated(mIconRequestName, HbIcon(pixmap)); |
|
381 } |
|
382 |
|
383 DP_OUT("CntCacheThread::onIconReady( QPixMap, void*, " << id << "," << error << ")"); |
|
384 } |