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 <QPluginLoader> |
|
19 #include <QDir> |
|
20 |
|
21 #include <qtcontacts.h> |
|
22 #include <qcontactmanager.h> |
|
23 #include <hbapplication.h> |
|
24 #include <thumbnailmanager_qt.h> |
|
25 #include <hbicon.h> |
|
26 #include <QTimer> |
|
27 |
|
28 #include "cntcache.h" |
|
29 #include "cntcache_p.h" |
|
30 #include <cntinfoproviderfactory.h> |
|
31 #include <cntinfoprovider.h> |
|
32 #include "cntdefaultinfoprovider.h" |
|
33 #include "cntpresenceinfoprovider.h" |
|
34 #include <cntdebug.h> |
|
35 |
|
36 // maximum amount of info and icon jobs respectively -- if there are more jobs, |
|
37 // then the oldest job is skipped and the client informed that this happened |
|
38 // in this way the client can request the job again if wanted |
|
39 static const int CntMaxInfoJobs = 20; |
|
40 static const int CntMaxIconJobs = 20; |
|
41 // the event for starting to process all outstanding jobs |
|
42 static const QEvent::Type ProcessJobsEvent = QEvent::User; |
|
43 // the id that states that no icon is currently pending from thumbnail manager |
|
44 static const int NoIconRequest = -1; |
|
45 // the id that states that there is no job with that key |
|
46 static const int NoSuchJob = -1; |
|
47 // different states of postponement |
|
48 static const int JobsNotPostponed = 0; |
|
49 static const int JobsPostponedForDuration = 1; |
|
50 static const int JobsPostponedUntilResume = 2; |
|
51 |
|
52 const char *CNT_INFO_PROVIDER_EXTENSION_PLUGIN_DIRECTORY = "/resource/qt/plugins/contacts/infoproviders/"; |
|
53 |
|
54 // TODO: Provide a way (cenrep keys?) for UI to set which provider to use for |
|
55 // what info field (and what info fields are indeed even in use). |
|
56 |
|
57 /*! |
|
58 Creates a new thread for fetching contact info and icons in the background. |
|
59 */ |
|
60 CntCacheThread::CntCacheThread() |
|
61 : mContactManager(new QContactManager()), |
|
62 mProcessingJobs(false), |
|
63 mJobsPostponed(JobsNotPostponed), |
|
64 mIconRequestId(NoIconRequest), |
|
65 mTimer(new QTimer()) |
|
66 { |
|
67 CNT_ENTRY |
|
68 |
|
69 // create static provider plugins |
|
70 mInfoProviders.insert(new CntDefaultInfoProvider(), ContactInfoAllFields); |
|
71 mInfoProviders.insert(new CntPresenceInfoProvider(), ContactInfoIcon2Field); |
|
72 |
|
73 // load dynamic provider plugins |
|
74 QDir pluginsDir(CNT_INFO_PROVIDER_EXTENSION_PLUGIN_DIRECTORY); |
|
75 foreach (QString fileName, pluginsDir.entryList(QDir::Files)) { |
|
76 // Create plugin loader |
|
77 QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName)); |
|
78 if (pluginLoader.load()) { |
|
79 CntInfoProviderFactory *factory = qobject_cast<CntInfoProviderFactory*>(pluginLoader.instance()); |
|
80 |
|
81 if (factory) { |
|
82 CntInfoProvider *provider = factory->infoProvider(); |
|
83 mInfoProviders.insert(provider, provider->supportedFields()); |
|
84 } |
|
85 } |
|
86 } |
|
87 |
|
88 // connect the providers |
|
89 QMapIterator<CntInfoProvider*, ContactInfoFields> i(mInfoProviders); |
|
90 while (i.hasNext()) { |
|
91 i.next(); |
|
92 connect(static_cast<CntInfoProvider*>(i.key()), |
|
93 SIGNAL(infoFieldReady(CntInfoProvider*, int, ContactInfoField, const QString&)), |
|
94 this, |
|
95 SLOT(onInfoFieldReady(CntInfoProvider*, int, ContactInfoField, const QString&))); |
|
96 } |
|
97 |
|
98 // create & connect the thumbnail manager |
|
99 mThumbnailManager = new ThumbnailManager(this); |
|
100 mThumbnailManager->setMode(ThumbnailManager::Default); |
|
101 mThumbnailManager->setQualityPreference(ThumbnailManager::OptimizeForPerformance); |
|
102 mThumbnailManager->setThumbnailSize(ThumbnailManager::ThumbnailSmall); |
|
103 connect(mThumbnailManager, SIGNAL(thumbnailReady(QPixmap, void *, int, int)), |
|
104 this, SLOT(onIconReady(QPixmap, void *, int, int))); |
|
105 |
|
106 mTimer->setSingleShot(true); |
|
107 connect(mTimer, SIGNAL(timeout()), this, SLOT(resumeJobs())); |
|
108 |
|
109 CNT_EXIT |
|
110 } |
|
111 |
|
112 /*! |
|
113 Cleans up and destructs the thread. |
|
114 */ |
|
115 CntCacheThread::~CntCacheThread() |
|
116 { |
|
117 CNT_ENTRY |
|
118 |
|
119 delete mContactManager; |
|
120 disconnect(this); |
|
121 |
|
122 mInfoJobs.clear(); |
|
123 mCancelledInfoJobs.clear(); |
|
124 mIconJobs.clear(); |
|
125 mCancelledIconJobs.clear(); |
|
126 |
|
127 if (mIconRequestId != NoIconRequest) { |
|
128 mThumbnailManager->cancelRequest(mIconRequestId); |
|
129 mIconRequestId = NoIconRequest; |
|
130 } |
|
131 |
|
132 delete mThumbnailManager; |
|
133 mThumbnailManager = NULL; |
|
134 |
|
135 qDeleteAll(mInfoProviders.keys()); |
|
136 mInfoProviders.clear(); |
|
137 |
|
138 CNT_EXIT |
|
139 } |
|
140 |
|
141 /*! |
|
142 Schedules a info to be fetched for a contact. When info has been fetched |
|
143 infoFieldUpdated() signals will be emitted, once for each field. |
|
144 |
|
145 /param contactId the contact for which the info is wanted |
|
146 */ |
|
147 void CntCacheThread::scheduleInfoJob(int contactId, int priority) |
|
148 { |
|
149 CNT_ENTRY_ARGS( contactId ) |
|
150 |
|
151 if (contactId <= 0) |
|
152 return; |
|
153 |
|
154 int index = infoJobIndex(contactId); |
|
155 if (index != NoSuchJob) { |
|
156 // if the job already exists, update the priority |
|
157 if (priority < mInfoJobs.at(index).second) { |
|
158 mInfoJobs[index] = QPair<int,int>(contactId,priority); |
|
159 } |
|
160 return; |
|
161 } |
|
162 |
|
163 if (!mProcessingJobs) { |
|
164 // new job => start processing jobs |
|
165 mProcessingJobs = true; |
|
166 HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent)); |
|
167 } |
|
168 |
|
169 if (mInfoJobs.count() >= CntMaxInfoJobs) { |
|
170 // the queue of jobs is full, so remove the oldest job |
|
171 mCancelledInfoJobs.append(mInfoJobs.takeFirst().first); |
|
172 CNT_LOG_ARGS( mCancelledInfoJobs.last() << "removed from joblist" ) |
|
173 } |
|
174 |
|
175 mInfoJobs.append(QPair<int,int>(contactId, priority)); |
|
176 CNT_LOG_ARGS( contactId << "(prio:" << priority << ") appended @" << (mInfoJobs.count() - 1) ); |
|
177 |
|
178 // since this job has now been scheduled, remove it from the list of |
|
179 // cancelled jobs in case it is there |
|
180 mCancelledInfoJobs.removeOne(contactId); |
|
181 |
|
182 CNT_EXIT |
|
183 } |
|
184 |
|
185 /*! |
|
186 Schedules an icon to be fetched. An iconUpdated() signal will be emitted when the icon |
|
187 has been fetched. |
|
188 |
|
189 /param iconName the name of the icon to be fetched |
|
190 */ |
|
191 void CntCacheThread::scheduleIconJob(const QString& iconName, int priority) |
|
192 { |
|
193 CNT_ENTRY_ARGS( iconName ) |
|
194 |
|
195 if (iconName.isEmpty()) |
|
196 return; |
|
197 |
|
198 int index = iconJobIndex(iconName); |
|
199 if (index != NoSuchJob) { |
|
200 // if the job already exists, update the priority |
|
201 if (priority < mIconJobs.at(index).second) { |
|
202 mIconJobs[index] = QPair<QString,int>(iconName,priority); |
|
203 } |
|
204 return; |
|
205 } |
|
206 |
|
207 if (!mProcessingJobs) { |
|
208 // new job, so restart job loop |
|
209 mProcessingJobs = true; |
|
210 HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent)); |
|
211 } |
|
212 |
|
213 if (mIconJobs.count() >= CntMaxIconJobs) { |
|
214 // the queue of jobs is full, so remove the oldest job |
|
215 mCancelledIconJobs.append(mIconJobs.takeLast().first); |
|
216 CNT_LOG_ARGS( mCancelledIconJobs.last() << "removed from joblist" ); |
|
217 } |
|
218 |
|
219 mIconJobs.append(QPair<QString,int>(iconName, priority)); |
|
220 CNT_LOG_ARGS( iconName << "(prio:" << priority << ") appended @" << (mIconJobs.count() - 1) ); |
|
221 |
|
222 // since this job has now been rescheduled, remove it from the list of |
|
223 // cancelled jobs in case it is there |
|
224 mCancelledIconJobs.removeOne(iconName); |
|
225 |
|
226 CNT_EXIT |
|
227 } |
|
228 |
|
229 /*! |
|
230 Postpones outstanding jobs until milliseconds ms has passed or resumeJobs() is called. |
|
231 This should be called if the client wants to reserve more CPU time for some urgent tasks. |
|
232 |
|
233 \param milliseconds The duration of the delay; 0, which is the default, means to delay |
|
234 until resumeJobs() is called |
|
235 */ |
|
236 void CntCacheThread::postponeJobs(int milliseconds) |
|
237 { |
|
238 CNT_ENTRY_ARGS("ms =" << milliseconds << " type =" << mJobsPostponed) |
|
239 |
|
240 Q_ASSERT(milliseconds >= 0); |
|
241 |
|
242 if (milliseconds == 0) { |
|
243 mTimer->stop(); |
|
244 mJobsPostponed = JobsPostponedUntilResume; |
|
245 } else if (mJobsPostponed != JobsPostponedUntilResume) { |
|
246 mTimer->stop(); |
|
247 mJobsPostponed = JobsPostponedForDuration; |
|
248 mTimer->start(milliseconds); |
|
249 } |
|
250 |
|
251 CNT_EXIT |
|
252 } |
|
253 |
|
254 /*! |
|
255 Postpones outstanding jobs until resumeJobs() is called. This must always be called after |
|
256 postponeJobs. |
|
257 */ |
|
258 void CntCacheThread::resumeJobs() |
|
259 { |
|
260 CNT_ENTRY |
|
261 |
|
262 mTimer->stop(); |
|
263 mJobsPostponed = JobsNotPostponed; |
|
264 HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent)); |
|
265 |
|
266 CNT_EXIT |
|
267 } |
|
268 |
|
269 /*! |
|
270 Handles a class-specific event that is sent by the scheduleOrUpdate functions |
|
271 when there are jobs. |
|
272 */ |
|
273 bool CntCacheThread::event(QEvent* event) |
|
274 { |
|
275 if (event->type() == ProcessJobsEvent) { |
|
276 processJobs(); |
|
277 return true; |
|
278 } |
|
279 |
|
280 return QObject::event(event); |
|
281 } |
|
282 |
|
283 /*! |
|
284 Processes all scheduled jobs. The loop runs until all jobs are done. |
|
285 It pauses for a while if new info jobs appear -- this means that the |
|
286 UI is updating and so the CPU is yielded to the UI. If there are |
|
287 again new jobs after the pause, then it pauses again, and so on. |
|
288 */ |
|
289 void CntCacheThread::processJobs() |
|
290 { |
|
291 CNT_ENTRY |
|
292 |
|
293 bool hasDoneJobs = false; |
|
294 |
|
295 forever { |
|
296 int infoJobs = mInfoJobs.count(); |
|
297 int iconJobs = mIconJobs.count(); |
|
298 int totalJobs = infoJobs + iconJobs + mCancelledInfoJobs.count() + mCancelledIconJobs.count(); |
|
299 |
|
300 if (totalJobs == 0 || totalJobs == iconJobs && mIconRequestId != NoIconRequest || mJobsPostponed != JobsNotPostponed) { |
|
301 if (mJobsPostponed == JobsNotPostponed || totalJobs == 0) { |
|
302 mProcessingJobs = false; |
|
303 } |
|
304 |
|
305 if (totalJobs == 0 && hasDoneJobs) { |
|
306 emit allJobsDone(); |
|
307 } |
|
308 |
|
309 break; |
|
310 } |
|
311 |
|
312 if (infoJobs > 0) { |
|
313 // get next job |
|
314 int contactId = takeNextInfoJob(); |
|
315 |
|
316 // fetch qcontact |
|
317 QContactFetchHint restrictions; |
|
318 restrictions.setOptimizationHints(QContactFetchHint::NoRelationships); |
|
319 QContact contact = mContactManager->contact(contactId, restrictions); |
|
320 |
|
321 // request contact info from providers |
|
322 QMapIterator<CntInfoProvider*, ContactInfoFields> i(mInfoProviders); |
|
323 while (i.hasNext()) { |
|
324 i.next(); |
|
325 if (i.value() != 0) { |
|
326 i.key()->requestInfo(contact, i.value()); |
|
327 } |
|
328 } |
|
329 } |
|
330 else if (iconJobs > 0 && mIconRequestId == NoIconRequest) { |
|
331 // request icon from thumbnail manager |
|
332 QString iconName = takeNextIconJob(); |
|
333 mIconRequestId = mThumbnailManager->getThumbnail(iconName, NULL, 0); |
|
334 mIconRequestName = iconName; |
|
335 } |
|
336 else { |
|
337 if (mCancelledInfoJobs.count() > 0) { |
|
338 int contactId = mCancelledInfoJobs.takeLast(); |
|
339 emit infoCancelled(contactId); |
|
340 } |
|
341 else if (mCancelledIconJobs.count() > 0) { |
|
342 QString iconName = mCancelledIconJobs.takeFirst(); |
|
343 emit iconCancelled(iconName); |
|
344 } |
|
345 } |
|
346 |
|
347 hasDoneJobs = true; |
|
348 |
|
349 // allow signals to be passed from providers and from the client |
|
350 HbApplication::processEvents(); |
|
351 } |
|
352 |
|
353 CNT_EXIT |
|
354 } |
|
355 |
|
356 /*! |
|
357 Passes an info field from a data provider up to the client via signals. The |
|
358 client is not in the same thread, so Qt passes the signal as an event. |
|
359 */ |
|
360 void CntCacheThread::onInfoFieldReady(CntInfoProvider* sender, int contactId, |
|
361 ContactInfoField field, const QString& text) |
|
362 { |
|
363 CNT_ENTRY |
|
364 |
|
365 // there can be 3rd party providers, so we cannot blindly trust them; |
|
366 // info is emitted only if: |
|
367 // 1) the sender is in the list of providers |
|
368 // 2) exactly one field bit is set in parameter 'field' |
|
369 // 3) the field bit has been assigned to this provider |
|
370 if (mInfoProviders.contains(sender) |
|
371 && ((field & (field - 1)) == 0) |
|
372 && ((field & mInfoProviders.value(sender)) != 0)) { |
|
373 emit infoFieldUpdated(contactId, field, text); |
|
374 } |
|
375 |
|
376 CNT_EXIT |
|
377 } |
|
378 |
|
379 /*! |
|
380 Passes an icon from thumbnail manager up to the client via a signal. The |
|
381 client is not in the same thread, so Qt passes the signal as an event. |
|
382 */ |
|
383 void CntCacheThread::onIconReady(const QPixmap& pixmap, void *data, int id, int error) |
|
384 { |
|
385 CNT_ENTRY |
|
386 |
|
387 Q_UNUSED(id); |
|
388 Q_UNUSED(data); |
|
389 |
|
390 Q_ASSERT(id == mIconRequestId && !mIconRequestName.isEmpty()); |
|
391 if (!mProcessingJobs) { |
|
392 // job loop quit while waiting for this icon, so restart it |
|
393 mProcessingJobs = true; |
|
394 HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent)); |
|
395 } |
|
396 mIconRequestId = NoIconRequest; |
|
397 |
|
398 if (error == 0) { |
|
399 emit iconUpdated(mIconRequestName, HbIcon(pixmap)); |
|
400 } |
|
401 |
|
402 CNT_EXIT |
|
403 } |
|
404 |
|
405 /*! |
|
406 Finds out the index of an info job in the job list. |
|
407 |
|
408 \return index of the contact in the job list, or NoSuchJob if no job is scheduled for the contact |
|
409 */ |
|
410 int CntCacheThread::infoJobIndex(int contactId) |
|
411 { |
|
412 int jobCount = mInfoJobs.count(); |
|
413 for (int i = 0; i < jobCount; ++i) { |
|
414 if (mInfoJobs.at(i).first == contactId) { |
|
415 return i; |
|
416 } |
|
417 } |
|
418 |
|
419 return NoSuchJob; |
|
420 } |
|
421 |
|
422 /*! |
|
423 Picks the next job from the info job list (the one with the highest priority). |
|
424 |
|
425 \return the id of the contact for which the info should be fetched |
|
426 */ |
|
427 int CntCacheThread::takeNextInfoJob() |
|
428 { |
|
429 int selectionIndex = -1; |
|
430 int selectionPriority = -1; |
|
431 |
|
432 int jobCount = mInfoJobs.count(); |
|
433 if (jobCount == 0) { |
|
434 return NoSuchJob; |
|
435 } |
|
436 |
|
437 for (int i = 0; i < jobCount; ++i) { |
|
438 int jobPriority = mInfoJobs.at(i).second; |
|
439 if (jobPriority < selectionPriority || selectionPriority == -1) { |
|
440 selectionIndex = i; |
|
441 selectionPriority = jobPriority; |
|
442 } |
|
443 } |
|
444 |
|
445 return mInfoJobs.takeAt(selectionIndex).first; |
|
446 } |
|
447 |
|
448 /*! |
|
449 Picks the next job from the icon job list (the one with the highest priority). |
|
450 |
|
451 \return the name of the icon that should be fetched |
|
452 */ |
|
453 QString CntCacheThread::takeNextIconJob() |
|
454 { |
|
455 int selectionIndex = -1; |
|
456 int selectionPriority = -1; |
|
457 |
|
458 int jobCount = mIconJobs.count(); |
|
459 if (jobCount == 0) { |
|
460 return QString(); |
|
461 } |
|
462 |
|
463 for (int i = 0; i < jobCount; ++i) { |
|
464 int jobPriority = mIconJobs.at(i).second; |
|
465 if (jobPriority < selectionPriority || selectionPriority == -1) { |
|
466 selectionIndex = i; |
|
467 selectionPriority = jobPriority; |
|
468 } |
|
469 } |
|
470 |
|
471 return mIconJobs.takeAt(selectionIndex).first; |
|
472 } |
|
473 |
|
474 /*! |
|
475 Finds out the index of an icon job in the job list. |
|
476 |
|
477 \return index of the icon in the job list, or NoSuchJob if a job for the icon is not scheduled. |
|
478 */ |
|
479 int CntCacheThread::iconJobIndex(QString iconName) |
|
480 { |
|
481 int jobCount = mIconJobs.count(); |
|
482 for (int i = 0; i < jobCount; ++i) { |
|
483 if (mIconJobs.at(i).first == iconName) { |
|
484 return i; |
|
485 } |
|
486 } |
|
487 |
|
488 return NoSuchJob; |
|
489 } |
|
490 |
|
491 |
|