diff -r fd64c38c277d -r b46a585f6909 phonebookengines/cntlistmodel/src/cntcache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/phonebookengines/cntlistmodel/src/cntcache.cpp Fri Jun 11 13:29:23 2010 +0300 @@ -0,0 +1,687 @@ +/* +* 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: Class for asynchronously fetching and caching basic +* contact info (see CntContactInfo) for list views. +* +*/ + +#include +#include +#include +#include "cntcache.h" +#include "cntcache_p.h" +#include "cntinfoprovider.h" + +// set the singleton instance pointer to NULL +CntCache* CntCache::mInstance = NULL; + +// value for first cache order to be assigned +static const int CacheOrderStartValue = 1; +// for avoiding wrap around with cache orders +static const int MaxCacheOrderValue = 10000000; +// number of items to read ahead into cache; this number is for one direction +static const int ItemsToCacheAhead = 24; +// cache size for info items (name, text, icon1name, icon2name) +static const int InfoCacheSize = 128; +// cache size for icon items (iconName and HbIcon) +static const int IconCacheSize = 50; +// number of icons in a CntContactInfo object +static const int IconsInCntContactInfo = 2; +// default empty text info field for a contact; it cannot be empty +// as the listview will then ignore it, causing rendering problems +static const QString EmptyTextField = " "; + +/*! + Provides a pointer to the CntCache singleton instance. + */ +CntCache* CntCache::instance() +{ + if (mInstance == NULL) { + mInstance = new CntCache(); + } + + return mInstance; +} + +/*! + Creates the CntCache singleton instance. + */ +CntCache::CntCache() + : mContactManager(new QContactManager()), + mWorker(new CntCacheThread()), + mNextInfoCacheOrder(CacheOrderStartValue), + mNextIconCacheOrder(CacheOrderStartValue), + mEmittedContactId(-1) +{ + DP_IN("CntCache::CntCache()"); + + // listen to worker updates + connect(mWorker, SIGNAL(infoFieldUpdated(int, const ContactInfoField&, const QString&)), + this, SLOT(onNewInfo(int, const ContactInfoField&, const QString&))); + connect(mWorker, SIGNAL(iconUpdated(const QString&, const HbIcon&)), + this, SLOT(onNewIcon(const QString&, const HbIcon&))); + connect(mWorker, SIGNAL(infoCancelled(int)), this, SLOT(onInfoCancelled(int))); + connect(mWorker, SIGNAL(iconCancelled(const QString&)), this, SLOT(onIconCancelled(const QString&))); + connect(mWorker, SIGNAL(allJobsDone()), this, SLOT(scheduleOneReadAheadItem())); + + // listen to the database for changes to contacts + connect(mContactManager, SIGNAL(contactsChanged(const QList&)), this, SLOT(removeContactsFromCache(const QList&))); + connect(mContactManager, SIGNAL(contactsRemoved(const QList&)), this, SLOT(removeContactsFromCache(const QList&))); + + // shutdown only when the whole application shuts down + connect(HbApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(onShutdown())); + + DP_OUT("CntCache::CntCache()"); +} + +/*! + Destructs the CntCache singleton instance. + */ +CntCache::~CntCache() +{ + DP_IN("CntCache::~CntCache()"); + + delete mWorker; + delete mContactManager; + + DP_OUT("CntCache::~CntCache()"); +} + +/*! + Fetches information about a contact: name, text (e.g. phone number or + social status) and two icons (e.g. avatar, presence). Previously cached + content - at the very least the name - will be returned immediately. + Availability of more information will be checked asynchronously and + sent to clients via contactInfoUpdated() signals. + + The function takes a row and a list rather than just a contact id because + of read ahead caching - contacts near the requested contacts are expected + to be needed soon and are therefore also scheduled for caching. + + \param row the row of the contact to fetch + \param idList a list with all the IDs in the list + \return a contact with some details filled in + */ +CntContactInfo CntCache::fetchContactInfo(int row, const QList& idList) +{ + DP_IN("CntCache::fetchContactInfo(" << row << ", idlist[" << idList.count() << "])"); + + Q_ASSERT(row >= 0 && row < idList.count()); + + QString name; + QString text = EmptyTextField; + HbIcon icons[IconsInCntContactInfo]; + + int contactId = idList.at(row); + + if (contactId != mEmittedContactId) { + // this request comes from the UI in response to some scrolling activity => + // 1) postpone all jobs so the UI can use as much of the CPU as possible + // 2) update read ahead cache to contain all IDs of all items near this item + mWorker->postponeJobs(); + updateReadAheadCache(row, idList); + } + + // fetch contact + if (mInfoCache.contains(contactId)) { + // the item is in the cache + CntInfoCacheItem* infoItem = mInfoCache.value(contactId); + for (int i = 0; i < IconsInCntContactInfo; ++i) { + QString iconName = infoItem->icons[i]; + if (!iconName.isEmpty()) { + if (mIconCache.contains(iconName)) { + CntIconCacheItem* iconItem = mIconCache.value(iconName); + iconItem->cacheOrder = mNextIconCacheOrder++; + icons[i] = iconItem->icon; + if (!iconItem->isFetched) { + // if icon has not yet been received from backend, add + // this id to the list of contacts that want to be + // notified when the icon is received + iconItem->contactIds.insert(contactId); + } + } + else { + // needed icon is not in cache, so schedule it for retrieval + CntIconCacheItem* iconItem = createIconCacheItem(iconName); + iconItem->contactIds.insert(contactId); + mWorker->scheduleIconJob(iconName); + } + } + } + + // update cache order + infoItem->cacheOrder = mNextInfoCacheOrder++; + + name = infoItem->name; + text = infoItem->text; + } + else { + // the item is not in cache, so fetch the name and schedule the rest + // of the info for retrieval + if (fetchContactName(contactId, name)) { + // contact found, so add new entry to cache + CntInfoCacheItem* item = createInfoCacheItem(contactId); + item->name = name; + item->text = text; + + // ask the worker thread to fetch the information asynchronously + mWorker->scheduleInfoJob(contactId); + } + } + + // cache read-ahead -- items near this fetched item should also be in cache + // updateReadAhead(row, idList); + + DP_OUT("CntCache::fetchContactInfo(" << row << ", idlist[" << idList.count() << "]) : name =" << name); + + return CntContactInfo(contactId, name, text, icons[0], icons[1]); +} + +/*! + Clears the cache - both names and icons. This function can be useful + for example if application goes to the background and memory needs to + be freed, or if the format of contact names change. + */ +void CntCache::clearCache() +{ + DP_IN("CntCache::clearCache()"); + + // clear info cache + foreach (CntInfoCacheItem* item, mInfoCache) { + delete item; + } + mInfoCache.clear(); + mNextInfoCacheOrder = CacheOrderStartValue; + + // clear icon cache + foreach (CntIconCacheItem* item, mIconCache) { + delete item; + } + mIconCache.clear(); + mNextIconCacheOrder = CacheOrderStartValue; + + DP_OUT("CntCache::clearCache()"); +} + +/*! + Processes a new info field that has arrived from the worker thread. + If the contact is in the info cache, then the info cache is updated + accordingly. + + A contactInfoUpdated() signal is usually also emitted. The exception + is if the info is the name of an icon and that icon is not in the icon + cache. In this case the icon is fetched before a signal is emitted. + */ +void CntCache::onNewInfo(int contactId, const ContactInfoField& infoField, const QString& infoValue) +{ + DP_IN("CntCache::onNewInfo(" << contactId << "," << infoField << "," << infoValue << ")"); + + Q_ASSERT(infoField == ContactInfoTextField || infoField == ContactInfoIcon1Field || infoField == ContactInfoIcon2Field); + + bool hasNewInfo; + + if (!mInfoCache.contains(contactId)) { + // contact is not in cache, so nothing needs to be done + // except notify clients that this contact has (possibly) + // been changed + hasNewInfo = true; + } + else if (infoField == ContactInfoTextField) { + // update cache with new text for contact + if (!infoValue.isEmpty()) + mInfoCache.value(contactId)->text = infoValue; + else + mInfoCache.value(contactId)->text = " "; + hasNewInfo = true; + } + else { + // update cache with new icon name for contact + int iconIndex = (infoField == ContactInfoIcon1Field ? 0 : 1); + + CntInfoCacheItem* item = mInfoCache.value(contactId); + QString iconName = infoValue; + if (item->icons[iconIndex] != iconName) { + item->icons[iconIndex] = iconName; + if (iconName.isEmpty()) { + hasNewInfo = true; + } + else if (mIconCache.contains(iconName)) { + CntIconCacheItem* iconItem = mIconCache.value(iconName); + if (!iconItem->isFetched) { + iconItem->contactIds.insert(contactId); + hasNewInfo = false; + } + else { + hasNewInfo = true; + } + } + else { + CntIconCacheItem* iconItem = createIconCacheItem(iconName); + iconItem->contactIds.insert(contactId); + mWorker->scheduleIconJob(iconName); + hasNewInfo = false; + } + } + else { + hasNewInfo = false; + } + } + + if (hasNewInfo) { + DP("CntCache::onNewInfo() : new info => emitting contactInfoUpdated(" << contactId << ")"); + emitContactInfoUpdated(contactId); + } + + DP_OUT("CntCache::onNewInfo(" << contactId << "," << infoField << "," << infoValue << ")"); +} + +/*! + Handle the case where a request for contact info is cancelled by the + worker because of too many subsequent requests. + */ +void CntCache::onInfoCancelled(int contactId) +{ + DP_IN("CntCache::onInfoCancelled(" << contactId << ")"); + + if (mInfoCache.contains(contactId)) { + CntInfoCacheItem* item = mInfoCache.take(contactId); + delete item; + } + + DP("CntCache::onInfoCancelled() : info cancelled => emitting contactInfoUpdated(" << contactId << ")"); + emitContactInfoUpdated(contactId); + + DP_OUT("CntCache::onInfoCancelled(" << contactId << ")"); +} + +/*! + Processes a new icon that has arrived from the worker thread. + The icon cache is updated and a contactInfoUpdated() signal is + emitted for all contacts that use this icon. + */ +void CntCache::onNewIcon(const QString& iconName, const HbIcon& icon) +{ + DP_IN("CntCache::onNewIcon(" << iconName << ", HbIcon)"); + + QSet contactsToNotify; + + if (mIconCache.contains(iconName)) { + CntIconCacheItem* item = mIconCache.value(iconName); + item->icon = icon; + item->isFetched = true; + contactsToNotify = item->contactIds; + item->contactIds.clear(); + } + + foreach (int contactId, contactsToNotify) { + DP("CntCache::onNewIcon() : new icon => emitting contactInfoUpdated(" << contactId << ")"); + emitContactInfoUpdated(contactId); + } + + DP_OUT("CntCache::onNewIcon(" << iconName << ", HbIcon)"); +} + +/*! + Handle the case where a request for an icon is cancelled by the worker because + of too many subsequent requests. + */ +void CntCache::onIconCancelled(const QString& iconName) +{ + DP_IN("CntCache::onIconCancelled(" << iconName << ")"); + + QSet contactsToNotify; + + if (mIconCache.contains(iconName)) { + CntIconCacheItem* item = mIconCache.take(iconName); + contactsToNotify = item->contactIds; + item->contactIds.clear(); + delete item; + } + + foreach (int contactId, contactsToNotify) { + DP("CntCache::onIconCancelled() : icon cancelled => emitting contactInfoUpdated(" << contactId << ")"); + emitContactInfoUpdated(contactId); + } + + DP_OUT("CntCache::onIconCancelled(" << iconName << ")"); +} + +/*! + Removes contacts from cache. + + /param contactIds ids of the contact that will be removed + */ +void CntCache::removeContactsFromCache(const QList& contactIds) +{ + DP_IN("CntCache::removeContactsFromCache(idList[" << contactIds.count() << "])"); + + foreach (QContactLocalId contactId, contactIds) { + if (mInfoCache.contains(contactId)) { + CntInfoCacheItem* item = mInfoCache.take(contactId); + delete item; + } + } + + foreach (QContactLocalId contactId, contactIds) { + emitContactInfoUpdated(contactId); + } + + DP_OUT("CntCache::removeContactsFromCache(idList[" << contactIds.count() << "])"); +} + +/*! + Uses an optimized function to fetch the name of a contact from + the database. + + /param contactId the id of the contact to fetch + /param contactName the name will be stored here if the function is successful + /return true if the name was fetched successfully + */ +bool CntCache::fetchContactName(int contactId, QString& contactName) +{ + bool foundContact = false; + DP_IN("CntCache::fetchContactName(" << contactId << "," << contactName << ")"); + + QContactFetchHint nameOnlyFetchHint; + QStringList details; + details << QContactDisplayLabel::DefinitionName; + nameOnlyFetchHint.setDetailDefinitionsHint(details); + QContact contact = mContactManager->contact(contactId, nameOnlyFetchHint); + + if (mContactManager->error() == QContactManager::NoError) { + contactName = contact.displayLabel(); + foundContact = true; + // TODO: this can be removed once qt mobility is updated (~wk20/10) + if (contactName == "Unnamed") { + contactName = ""; + } + } + + DP_OUT("CntCache::fetchContactName(" << contactId << "," << contactName << ") : " << foundContact); + + return foundContact; +} + +/*! + Collects all contact IDs near the latest fetch from the UI. These will be fetched and + precached when UI activity slows down. + + \param mostRecentRow the row of the contact that was most recently fetched + \param idList a list with all the IDs in the list + */ +void CntCache::updateReadAheadCache(int mostRecentRow, const QList& idList) +{ + DP_IN("CntCache::updateReadAheadCache(" << mostRecentRow << ", idList[" << idList.count() << "] )"); + int row; + + mReadAheadCache.clear(); + + // step through the area near to last fetch item and make sure all + // contacts in it are also in cache or in the read ahead list + for (int i = 1; i <= ItemsToCacheAhead; ++i) { + for (int j = 0; j < 2; ++j) { + if (j == 0) { + row = mostRecentRow - i; + if (row <= 0) { + continue; + } + } + else { + row = mostRecentRow + i; + if (row >= idList.count()) { + continue; + } + } + + int contactId = idList.at(row); + if (!mInfoCache.contains(contactId)) { + // contact is not in cache, so put the id to items to read into cache + mReadAheadCache.append(contactId); + } + else { + // contact is in cache; update cache order as we want to keep this item in cache + mInfoCache.value(contactId)->cacheOrder = mNextInfoCacheOrder++; + } + } + } + + DP_OUT("CntCache::updateReadAheadCache(" << mostRecentRow << ", idList[" << idList.count() << "] )"); +} + +/*! + Schedules one uncached item in the read-ahead list for retrieval. + */ +void CntCache::scheduleOneReadAheadItem() +{ + DP_IN("CntCache::scheduleOneReadAheadItem()"); + + QString name; + + while (mReadAheadCache.count() > 0) { + int contactId = mReadAheadCache.takeFirst(); + if (!mInfoCache.contains(contactId)) { + // contact is not in cache, so schedule it for retreival + if (fetchContactName(contactId, name)) { + // contact found, so add new entry to cache + CntInfoCacheItem* item = createInfoCacheItem(contactId); + item->name = name; + item->text = EmptyTextField; + + // schedule the info for retrieval + mWorker->scheduleInfoJob(contactId); + break; + } + } + } + + DP_OUT("CntCache::scheduleOneReadAheadItem()"); +} + +/*! + Creates a new item in the info cache. If the cache is full, + then the least recently accessed item is removed from cache. + + /param contactId id of contact for which to create the new cache item + /return the newly created cache item + */ +CntInfoCacheItem* CntCache::createInfoCacheItem(int contactId) +{ + DP_IN("CntCache::createInfoCacheItem(" << contactId << ")"); + + if (mInfoCache.count() >= InfoCacheSize) { + // cache is full, so remove the oldest contact + int minCacheOrder = mNextInfoCacheOrder; + CntInfoCacheItem* oldestItem = NULL; + foreach (CntInfoCacheItem* i, mInfoCache) { + if (i->cacheOrder < minCacheOrder) { + minCacheOrder = i->cacheOrder; + oldestItem = i; + } + } + mInfoCache.remove(oldestItem->contactId); + delete oldestItem; + + // cache maintenance: if the cache ids become too large, + // reduce all of them by MaxCacheOrderValue + if (mNextInfoCacheOrder >= MaxCacheOrderValue) { + mNextInfoCacheOrder -= MaxCacheOrderValue; + foreach (CntInfoCacheItem* i, mInfoCache) { + i->cacheOrder -= MaxCacheOrderValue; + } + } + } + + // create and insert the new item + CntInfoCacheItem* item = new CntInfoCacheItem(); + item->cacheOrder = mNextInfoCacheOrder++; + item->contactId = contactId; + mInfoCache.insert(contactId, item); + + DP_OUT("CntCache::createInfoCacheItem(" << contactId << ")"); + + return item; +} + +/*! + Creates a new item in the icon cache. If the cache is full, + then the least recently accessed item is removed from cache. + + /param iconName name of the icon for which to create the new cache item + /return the newly created cache item + */ +CntIconCacheItem* CntCache::createIconCacheItem(const QString& iconName) +{ + DP_IN("CntCache::createIconCacheItem(" << iconName << ")"); + + if (mIconCache.count() >= IconCacheSize) { + // cache is full, so remove the oldest icon + int minCacheOrder = mNextIconCacheOrder; + CntIconCacheItem* oldestItem = NULL; + foreach (CntIconCacheItem* i, mIconCache) { + if (i->cacheOrder < minCacheOrder) { + minCacheOrder = i->cacheOrder; + oldestItem = i; + } + } + mIconCache.remove(oldestItem->iconName); + delete oldestItem; + + // cache maintenance: if the cache orders become too large, + // reduce all of them by MaxCacheOrderValue + if (mNextIconCacheOrder >= MaxCacheOrderValue) { + mNextIconCacheOrder -= MaxCacheOrderValue; + foreach (CntIconCacheItem* i, mIconCache) { + i->cacheOrder -= MaxCacheOrderValue; + } + } + } + + // create and insert the new item + CntIconCacheItem* item = new CntIconCacheItem(); + item->cacheOrder = mNextIconCacheOrder++; + item->iconName = iconName; + item->isFetched = false; + mIconCache.insert(iconName, item); + + DP_OUT("CntCache::createIconCacheItem(" << iconName << ")"); + + return item; +} + +/*! + Notifies clients that a contact might have changed. + Clients can then request the info via fetchContactInfo() + if they are interested. + */ +void CntCache::emitContactInfoUpdated(int contactId) +{ + mEmittedContactId = contactId; + emit contactInfoUpdated(contactId); + mEmittedContactId = -1; +} + +/*! + Deletes the cache. + */ +void CntCache::onShutdown() +{ + delete this; +} + + +/*! + Creates an empty object. + */ +CntContactInfo::CntContactInfo() + : d(new CntContactInfoData()) +{ +} + +/*! + Creates an object with all info fields set. + */ +CntContactInfo::CntContactInfo(int id, const QString& name, const QString& text, const HbIcon& icon1, const HbIcon& icon2) + : d(new CntContactInfoData()) +{ + d->id = id; + d->name = name; + d->text = text; + d->icon1 = icon1; + d->icon2 = icon2; +} + +/*! + Destroys the object. + */ +CntContactInfo::~CntContactInfo() +{ +} + +/*! + Copy constructor. + */ +CntContactInfo::CntContactInfo(const CntContactInfo& other) + : d(other.d) +{ +} + +/*! + Assignment operator. + */ +CntContactInfo& CntContactInfo::operator=(const CntContactInfo& other) +{ + d = other.d; + return *this; +} + +/*! + Getter function for the id. + */ +int CntContactInfo::id() const +{ + return d->id; +} + +/*! + Getter function for the name. + */ +QString CntContactInfo::name() const +{ + return d->name; +} + +/*! + Getter function for the text. + */ +QString CntContactInfo::text() const +{ + return d->text; +} + +/*! + Getter function for the first icon. + */ +HbIcon CntContactInfo::icon1() const +{ + return d->icon1; +} + +/*! + Getter function for the second icon. + */ +HbIcon CntContactInfo::icon2() const +{ + return d->icon2; +} +