diff -r 2a11b5b00470 -r fd64c38c277d phonebookengines/mobcntmodel/src/cntcache_p.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/phonebookengines/mobcntmodel/src/cntcache_p.cpp Thu May 27 12:45:19 2010 +0300 @@ -0,0 +1,384 @@ +/* +* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: Private data and helper classes used by class CntCache. +* +*/ + +#include +#include +#include +#include +#include +#include +#include "cntcache.h" +#include "cntcache_p.h" +#include "cntinfoprovider.h" +#include "cntdefaultinfoprovider.h" + +// maximum amount of info and icon jobs respectively -- if there are more jobs, +// then the oldest job is skipped and the client informed that this happened +// in this way the client can request the job again if wanted +static const int CntMaxInfoJobs = 20; +static const int CntMaxIconJobs = 20; +// amount of milliseconds to postpone the jobs if the UI is very active +static const int PostponeJobsMilliSeconds = 300; +// the event for starting to do all the outstanding jobs +static const QEvent::Type DoAllJobsEvent = QEvent::User; +// the id that states that no icon is currently pending from thumbnail manager +static const int NoIconRequest = -1; + +// TODO: Provide a way (cenrep keys?) for UI to set which provider to use for +// what info field (and what info fields are indeed even in use). + +/*! + Creates a new thread for fetching contact info and icons in the background. + */ +CntCacheThread::CntCacheThread() + : mContactManager(new QContactManager()), + mJobLoopRunning(false), + mPostponeJobs(false), + mIconRequestId(NoIconRequest) +{ + DP_IN("CntCacheThread::CntCacheThread()"); + + // create static provider plugins + mDataProviders.insert(new CntDefaultInfoProvider(), ContactInfoAllFields); + // TODO: create more static provider plugins + + // TODO: load dynamic provider plugins using QPluginLoader + + // connect the providers + QMapIterator i(mDataProviders); + while (i.hasNext()) { + i.next(); + connect(qobject_cast(i.key()), + SIGNAL(infoFieldReady(CntInfoProvider*, int, ContactInfoField, const QString&)), + this, + SLOT(onInfoFieldReady(CntInfoProvider*, int, ContactInfoField, const QString&))); + } + + // create & connect the thumbnail manager + mThumbnailManager = new ThumbnailManager(this); + mThumbnailManager->setMode(ThumbnailManager::Default); + mThumbnailManager->setQualityPreference(ThumbnailManager::OptimizeForPerformance); + mThumbnailManager->setThumbnailSize(ThumbnailManager::ThumbnailSmall); + connect(mThumbnailManager, SIGNAL(thumbnailReady(QPixmap, void *, int, int)), + this, SLOT(onIconReady(QPixmap, void *, int, int))); + + // this thread should interfere as little as possible with more time-critical tasks, + // like updating the UI during scrolling + start(QThread::IdlePriority); + + DP_OUT("CntCacheThread::CntCacheThread()"); +} + +/*! + Cleans up and destructs the thread. + */ +CntCacheThread::~CntCacheThread() +{ + DP_IN("CntCacheThread::~CntCacheThread()"); + + disconnect(this); + + mJobMutex.lock(); + mInfoJobs.clear(); + mCancelledInfoJobs.clear(); + mIconJobs.clear(); + mCancelledIconJobs.clear(); + + if (mIconRequestId != NoIconRequest) { + mThumbnailManager->cancelRequest(mIconRequestId); + mIconRequestId = NoIconRequest; + } + + QMapIterator i(mDataProviders); + while (i.hasNext()) { + i.next(); + delete i.key(); + } + mDataProviders.clear(); + + mJobMutex.unlock(); + + exit(); + wait(); + + DP_OUT("CntCacheThread::~CntCacheThread()"); +} + +/*! + Starts the event loop for this thread. + */ +void CntCacheThread::run() +{ + DP_IN("CntCacheThread::run()"); + exec(); + DP_OUT("CntCacheThread::run()"); +} + +/*! + Schedules a info to be fetched for a contact. When info has been fetched + infoFieldUpdated() signals will be emitted, once for each field. + + /param contactId the contact for which the info is wanted + */ +void CntCacheThread::scheduleInfoJob(int contactId) +{ + DP_IN("CntCacheThread::scheduleInfoJob(" << contactId << ")"); + + Q_ASSERT(contactId > 0 && !mInfoJobs.contains(contactId)); + + mJobMutex.lock(); + + if (!mJobLoopRunning) { + // new job => restart job loop + mJobLoopRunning = true; + HbApplication::instance()->postEvent(this, new QEvent(DoAllJobsEvent)); + } + + if (mInfoJobs.count() >= CntMaxInfoJobs) { + // the queue of jobs is full, so remove the oldest job + mCancelledInfoJobs.append(mInfoJobs.takeFirst()); + DP("CntCacheThread::scheduleInfoJob() :" << mCancelledInfoJobs.last() << "removed from joblist"); + } + + mInfoJobs.append(contactId); + DP("CntCacheThread::scheduleInfoJob() :" << contactId << "appended @" << mInfoJobs.indexOf(contactId)); + + // since this job has now been scheduled, remove it from the list of + // cancelled jobs in case it is there + mCancelledInfoJobs.removeOne(contactId); + + mJobMutex.unlock(); + + DP_OUT("CntCacheThread::scheduleInfoJob(" << contactId << ")"); +} + +/*! + Schedules an icon to be fetched. An iconUpdated() signal will be emitted when the icon + has been fetched. + + /param iconName the name of the icon to be fetched + */ +void CntCacheThread::scheduleIconJob(const QString& iconName) +{ + DP_IN("CntCacheThread::scheduleIconJob(" << iconName << ")"); + + mJobMutex.lock(); + + Q_ASSERT(!iconName.isEmpty() && !mIconJobs.contains(iconName)); + + if (!mJobLoopRunning) { + // new job, so restart job loop + mJobLoopRunning = true; + HbApplication::instance()->postEvent(this, new QEvent(DoAllJobsEvent)); + } + + if (mIconJobs.count() >= CntMaxIconJobs) { + // the queue of jobs is full, so remove the oldest job + mCancelledIconJobs.append(mIconJobs.takeLast()); + DP("CntCacheThread::scheduleIconJob() :" << mCancelledIconJobs.last() << "removed from joblist"); + } + + mIconJobs.append(iconName); + DP("CntCacheThread::scheduleIconJob() :" << iconName << "appended @" << mIconJobs.indexOf(iconName)); + + // since this job has now been rescheduled, remove it from the list of + // cancelled jobs in case it is there + mCancelledIconJobs.removeOne(iconName); + + mJobMutex.unlock(); + + DP_OUT("CntCacheThread::scheduleIconJob(" << iconName << ")"); +} + +/*! + Postpones outstanding jobs for a few tenths of a second. This should be called if + the client wants to reserve more CPU time for some urgent task. + */ +void CntCacheThread::postponeJobs() +{ + DP_IN("CntCacheThread::postponeJobs()"); + + mPostponeJobs = true; + + DP_OUT("CntCacheThread::postponeJobs()"); +} + +/*! + Handles a class-specific event that is sent by the scheduleOrUpdate functions + when there are jobs. + */ +bool CntCacheThread::event(QEvent* event) +{ + if (event->type() == DoAllJobsEvent) { + doAllJobs(); + return true; + } + + return QThread::event(event); +} + +/*! + Does the jobs. The loop runs until all jobs are done. It pauses + for a while if new info jobs appear -- this means that the UI is + updating and so the CPU is yielded to the UI. If there are again + new jobs after the pause, then it pauses again, and so on. + */ +void CntCacheThread::doAllJobs() +{ + DP_IN("CntCacheThread::doAllJobs()"); + + forever { + mJobMutex.lock(); + int infoJobs = mInfoJobs.count(); + int iconJobs = mIconJobs.count(); + int totalJobs = infoJobs + iconJobs + mCancelledInfoJobs.count() + mCancelledIconJobs.count(); + DP_IN("CntCacheThread::doAllJobs() : infojobs=" << infoJobs << ", iconjobs=" << iconJobs << ",icon_request=" << mIconRequestId << ", cancelledinfojobs=" << mCancelledInfoJobs.count() << ", cancellediconjobs=" << mCancelledIconJobs.count()); + + if (totalJobs == 0 || totalJobs == iconJobs && mIconRequestId != NoIconRequest || mPostponeJobs) { + if (mPostponeJobs) { + // client has requested a pause in activies (e.g. due to high UI activity) + mPostponeJobs = false; + if (totalJobs > 0) { + QTimer::singleShot(PostponeJobsMilliSeconds, this, SLOT(doAllJobs())); + DP("CntCacheThread::doAllJobs() : postponing for" << PostponeJobsMilliSeconds << "ms"); + } + else { + mJobLoopRunning = false; + } + } + else { + mJobLoopRunning = false; + } + + mJobMutex.unlock(); + + if (totalJobs == 0) { + DP("CntCacheThread::doAllJobs() : emitting all jobs done"); + emit allJobsDone(); + } + + break; + } + + bool doInfoJobs = infoJobs > 0 && (iconJobs == 0 || mIconRequestId != NoIconRequest || qrand() % (infoJobs + iconJobs) < infoJobs); + + if (doInfoJobs) { + // get next job + int contactId = mInfoJobs.takeLast(); + mJobMutex.unlock(); + + // fetch qcontact + QStringList definitionRestrictions; + definitionRestrictions.append(QContactName::DefinitionName); + definitionRestrictions.append(QContactAvatar::DefinitionName); + definitionRestrictions.append(QContactPhoneNumber::DefinitionName); + definitionRestrictions.append(QContactOrganization::DefinitionName); + QContactFetchHint restrictions; + restrictions.setDetailDefinitionsHint(definitionRestrictions); + restrictions.setOptimizationHints(QContactFetchHint::NoRelationships); + QContact contact = mContactManager->contact(contactId, restrictions); + + // request contact info from providers + DP("CntCacheThread::doAllJobs() : fetching info for" << contact.displayLabel() << " (id=" << contactId << ")"); + QMapIterator i(mDataProviders); + while (i.hasNext()) { + i.next(); + if (i.value() != 0) { + i.key()->requestInfo(contact, i.value()); + } + } + } + else if (iconJobs > 0 && mIconRequestId == NoIconRequest) { + // request icon from thumbnail manager + QString iconName = mIconJobs.takeFirst(); + DP("CntCacheThread::doAllJobs() : fetching icon" << iconName); + mIconRequestId = mThumbnailManager->getThumbnail(iconName, NULL, 0); + mIconRequestName = iconName; + mJobMutex.unlock(); + } + else { + if (mCancelledInfoJobs.count() > 0) { + int contactId = mCancelledInfoJobs.takeLast(); + mJobMutex.unlock(); + DP("CntCacheThread::doAllJobs() : emitting cancelled info job" << contactId); + emit infoCancelled(contactId); + } + else if (mCancelledIconJobs.count() > 0) { + QString iconName = mCancelledIconJobs.takeFirst(); + mJobMutex.unlock(); + DP("CntCacheThread::doAllJobs() : emitting cancelled icon job" << iconName); + emit iconCancelled(iconName); + } + } + + // allow signals to be passed from providers and from the client + HbApplication::processEvents(); + } + + DP_OUT("CntCacheThread::doAllJobs()"); +} + +/*! + Passes an info field from a data provider up to the client via signals. The + client is not in the same thread, so Qt passes the signal as an event. + */ +void CntCacheThread::onInfoFieldReady(CntInfoProvider* sender, int contactId, + ContactInfoField field, const QString& text) +{ + DP_IN("CntCacheThread::onInfoFieldReady( CntInfoProvider*," << contactId << "," << field << "," << text << ")"); + + // there can be 3rd party providers, so we cannot blindly trust them; + // info is emitted only if: + // 1) the sender is in the list of providers + // 2) exactly one field bit is set in parameter 'field' + // 3) the field bit has been assigned to this provider + if (mDataProviders.contains(sender) + && ((field & (field - 1)) == 0) + && ((field & mDataProviders.value(sender)) != 0)) { + DP("CntCacheThread::onInfoFieldReady(" << contactId << "," << field << "," << text << ") : emitting infoFieldUpdated()"); + emit infoFieldUpdated(contactId, field, text); + } + + DP_OUT("CntCacheThread::onInfoFieldReady(" << contactId << "," << field << "," << text << ")"); +} + +/*! + Passes an icon from thumbnail manager up to the client via a signal. The + client is not in the same thread, so Qt passes the signal as an event. + */ +void CntCacheThread::onIconReady(const QPixmap& pixmap, void *data, int id, int error) +{ + DP_IN("CntCacheThread::onIconReady( QPixMap, void*, " << id << "," << error << ")"); + Q_UNUSED(data); + + mJobMutex.lock(); + Q_ASSERT(id == mIconRequestId && !mIconRequestName.isEmpty()); + if (!mJobLoopRunning) { + // job loop quit while waiting for this icon, so restart it + mJobLoopRunning = true; + HbApplication::instance()->postEvent(this, new QEvent(DoAllJobsEvent)); + } + mIconRequestId = NoIconRequest; + mJobMutex.unlock(); + + if (error == 0) { + DP("CntCacheThread::onIconReady() : emitting iconUpdated(" << mIconRequestName << ")"); + emit iconUpdated(mIconRequestName, HbIcon(pixmap)); + } + + DP_OUT("CntCacheThread::onIconReady( QPixMap, void*, " << id << "," << error << ")"); +}