diff -r 59984e68247d -r 6abfb1094884 phonebookengines/cntlistmodel/src/cntcache.cpp --- a/phonebookengines/cntlistmodel/src/cntcache.cpp Thu Sep 09 16:37:32 2010 +0300 +++ b/phonebookengines/cntlistmodel/src/cntcache.cpp Tue Sep 21 17:07:25 2010 +0300 @@ -19,10 +19,12 @@ #include #include #include +#include + #include #include "cntcache.h" +#include "cntnamefetcher.h" #include "cntcache_p.h" -#include "cntinfoprovider.h" // set the singleton instance pointer to NULL CntCache* CntCache::mInstance = NULL; @@ -32,7 +34,7 @@ // for avoiding wrap around with cache orders static const int MaxCacheOrderValue = 10000000; // number of items to read quickly when a new instance is requested or cache is cleared -static const int ItemsToReadUrgently = 12; +static const int ItemsToReadUrgently = 13; // 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) @@ -46,62 +48,12 @@ static const QString EmptyTextField = " "; /*! - Creates the CntCache singleton instance. - */ -CntCache::CntCache() - : mContactManager(new QContactManager()), - mWorker(new CntCacheThread()), - mNextInfoCacheOrder(CacheOrderStartValue), - mNextIconCacheOrder(CacheOrderStartValue), - mEmittedContactId(-1), - mUrgentContacts(0) -{ - CNT_ENTRY - - // 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(updateContactsInCache(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())); - - CNT_EXIT -} - -/*! - Destructs the CntCache singleton instance. - */ -CntCache::~CntCache() -{ - CNT_ENTRY - - delete mWorker; - delete mContactManager; - - qDeleteAll(mInfoCache); - mInfoCache.clear(); - qDeleteAll(mIconCache); - mIconCache.clear(); - - CNT_EXIT -} - -/*! Provides a pointer to the CntCache singleton instance. */ -CntCache* CntCache::instance() +CntCache* CntCache::instance(QContactManager *manager) { - if (mInstance == NULL) { - mInstance = new CntCache(); + if (!mInstance) { + mInstance = new CntCache(manager); } // whenever a client requests an instance the client will want to get all info @@ -146,9 +98,8 @@ // 2) update read ahead cache to contain all IDs of all items near this item if (mUrgentContacts > 0) { --mUrgentContacts; - } - else { - mWorker->postponeJobs(); + } else { + mWorker->postponeJobs(150); } updateReadAheadCache(row, idList); } @@ -172,8 +123,7 @@ // also reschedule it mWorker->scheduleIconJob(iconName, row); } - } - else { + } else { // needed icon is not in cache, so schedule it for retrieval CntIconCacheItem* iconItem = createIconCacheItem(iconName); iconItem->contactIds.insert(contactId); @@ -182,20 +132,17 @@ } } + // set return text + text = infoItem->text; + // update cache order infoItem->cacheOrder = mNextInfoCacheOrder++; infoItem->latestRow = row; - - 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)) { + } else { + // the contact info is not in cache, schedule it for retrieval + if (contactExists(contactId)) { // contact found, so add new entry to cache CntInfoCacheItem* item = createInfoCacheItem(contactId); - item->name = name; item->text = text; item->latestRow = row; @@ -204,24 +151,188 @@ } } + name = contactName(contactId); CNT_EXIT_ARGS("name:" << name << "sec:" << text) return CntContactInfo(contactId, name, text, icons[0], icons[1]); } /*! - Clears the cache of names (not icons). This function can be useful - for example when the format of contact names changes. + Creates a list of contact ids sorted according the corresponding contact names. + + \param idFilter the IDs to be returned; if NULL, all contact IDs are returned + \return the list of ids, sorted according the contact name */ -void CntCache::clearCache() +QList CntCache::sortIdsByName(const QSet* idFilter) const { CNT_ENTRY - // clear info cache + QList sortedIds; + + // allocate memory in advance to avoid repeated reallocation during population + // an extra 16 items are allocated to leave room for a few more contacts + // before reallocation is needed + if (!idFilter) { + sortedIds.reserve(mSortedNames.count() + 16); + } else { + sortedIds.reserve(idFilter->count() + 16); + } + + // the entries in mSortedNames are already sorted, so just pick + // out the ids from that list in the order that they appear + if (!idFilter) { + foreach (CntNameCacheItem* item, mSortedNames) { + sortedIds.append(item->contactId()); + } + } else { + foreach (CntNameCacheItem* item, mSortedNames) { + if (idFilter->contains(item->contactId())) { + sortedIds.append(item->contactId()); + } + } + } + + CNT_EXIT + + return sortedIds; +} + +/*! + Overloaded version of the function for string based searching of contact names. + Currently for multi part names only space and dash variations are used for filtering, + e.g. "Axx Bxx" or "Axx-Bxx" are the only possible matches along with the original string. + + \param searchList list of strings which are used for search + \return the list of ids, sorted according the contact name + */ +QList CntCache::sortIdsByName(const QStringList searchList) const +{ + CNT_ENTRY + + QList sortedIds; + int iterNames = 0; + int iterList = 0; + QString firstName = 0; + QString lastName = 0; + QString tempString = 0; + QString tempDash = 0; + QString tempSpace = 0; + int matchesFound = 0; + const QChar dash = '-'; + const QChar space = ' '; + QStringList searchVariations; + + for (iterList = 0; iterList < searchList.size(); iterList++) + { + tempString = searchList.at(iterList); + tempDash = tempString; + tempSpace = tempString; + tempDash.insert(0, dash); + tempSpace.insert(0, space); + + searchVariations.append(tempString); + searchVariations.append(tempDash); + searchVariations.append(tempSpace); + } + + for (iterNames = 0; iterNames < mSortedNames.size(); iterNames++) + { + matchesFound = 0; + firstName = (mSortedNames.at(iterNames))->firstName(); + lastName = (mSortedNames.at(iterNames))->lastName(); + for (iterList = 0; iterList < searchVariations.size(); iterList += 3) + { + // if the current name doesn't contain any of the possible variations then it can be skipped + if ( !( firstName.startsWith(searchVariations.at(iterList), Qt::CaseInsensitive) || + lastName.startsWith(searchVariations.at(iterList), Qt::CaseInsensitive) || + firstName.contains(searchVariations.at(iterList+1), Qt::CaseInsensitive) || + lastName.contains(searchVariations.at(iterList+1), Qt::CaseInsensitive) || + firstName.contains(searchVariations.at(iterList+2), Qt::CaseInsensitive) || + lastName.contains(searchVariations.at(iterList+2), Qt::CaseInsensitive) ) ) + { + break; + } + } + if (iterList == searchVariations.size()) + { + sortedIds.append(mSortedNames.at(iterNames)->contactId()); + } + } + + CNT_EXIT + + return sortedIds; +} + +/*! + Creates the CntCache singleton instance. + */ +CntCache::CntCache(QContactManager *manager) + : mContactManager(manager), + mWorker(new CntCacheThread()), + mNameFetcher(new CntNameFetcher()), + mNextInfoCacheOrder(CacheOrderStartValue), + mNextIconCacheOrder(CacheOrderStartValue), + mEmittedContactId(-1), + mUrgentContacts(0), + mHasModifiedNames(false), + mAllNamesFetchStarted(false) +{ + CNT_ENTRY + + // listen to name fetcher + connect(mNameFetcher, SIGNAL(nameFormatChanged(CntNameOrder)), this, SLOT(reformatNames(CntNameOrder))); + connect(mNameFetcher, SIGNAL(databaseAccessComplete()), mWorker, SLOT(resumeJobs())); + connect(mNameFetcher, SIGNAL(namesAvailable(QList)), this, SLOT(setNameList(QList))); + + // listen to info fetcher + connect(mWorker, SIGNAL(infoFieldUpdated(int, const ContactInfoField&, const QString&)), + this, SLOT(onNewInfo(int, const ContactInfoField&, const QString&))); + connect(mWorker, SIGNAL(infoCancelled(int)), this, SLOT(onInfoCancelled(int))); + + // listen to icon fetcher + connect(mWorker, SIGNAL(iconUpdated(const QString&, const HbIcon&)), + this, SLOT(onNewIcon(const QString&, const HbIcon&))); + connect(mWorker, SIGNAL(iconCancelled(const QString&)), this, SLOT(onIconCancelled(const QString&))); + connect(mWorker, SIGNAL(allJobsDone()), this, SLOT(scheduleOneReadAheadItem())); + + // listen to contact manager + connect(mContactManager, SIGNAL(contactsChanged(const QList&)), this, SLOT(updateContacts(const QList&))); + connect(mContactManager, SIGNAL(contactsRemoved(const QList&)), this, SLOT(removeContacts(const QList&))); + connect(mContactManager, SIGNAL(contactsAdded(const QList&)), this, SLOT(addContacts(const QList&))); + + // listen to application -- shut down cache only when the whole application quits + connect(HbApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(onShutdown())); + + // load all names to RAM + loadNames(); + + CNT_EXIT +} + +/*! + Destructs the CntCache singleton instance. + */ +CntCache::~CntCache() +{ + CNT_ENTRY + + if (mHasModifiedNames) { + mNameFetcher->writeNamesToCache(mSortedNames); + } + + delete mWorker; + delete mNameFetcher; + qDeleteAll(mInfoCache); mInfoCache.clear(); - mNextInfoCacheOrder = CacheOrderStartValue; - mUrgentContacts = ItemsToReadUrgently; + + qDeleteAll(mIconCache); + mIconCache.clear(); + + qDeleteAll(mNameCache); + mNameCache.clear(); + mSortedNames.clear(); CNT_EXIT } @@ -373,84 +484,58 @@ CNT_EXIT } -/*! - Update contacts in cache. - - /param contactIds ids of the contact that will be updated +/*! + Fetch the names of all contacts. */ -void CntCache::updateContactsInCache(const QList& contactIds) +void CntCache::loadNames() { CNT_ENTRY - - QString name; + + // read names from file cache + mNameFetcher->readNamesFromCache(mSortedNames); - foreach (QContactLocalId contactId, contactIds) { - if (mInfoCache.contains(contactId) && fetchContactName(contactId, name)) { - CntInfoCacheItem* infoItem = mInfoCache.value(contactId); - infoItem->name = name; - mWorker->scheduleInfoJob(contactId, infoItem->latestRow); - } + // insert the names into the id-to-name map + foreach (CntNameCacheItem* item, mSortedNames) { + mNameCache.insert(item->contactId(), item); } - foreach (QContactLocalId contactId, contactIds) { - emitContactInfoUpdated(contactId); + // if there are no names in file cache, start the asynch + // read of all names immediately (normally it is done + // after secondary info has been read) + if (mSortedNames.count() == 0) { + mWorker->postponeJobs(); + mAllNamesFetchStarted = true; + mNameFetcher->readAllNamesAsynch(); } CNT_EXIT } -/*! - Removes contacts from cache. - - /param contactIds ids of the contact that will be removed +/*! + Checks whether a contact exists. */ -void CntCache::removeContactsFromCache(const QList& contactIds) +bool CntCache::contactExists(QContactLocalId contactId) const +{ + return mNameCache.contains(contactId); +} + +/*! + Fetch the name of one contact. + */ +QString CntCache::contactName(QContactLocalId contactId) const { CNT_ENTRY - foreach (QContactLocalId contactId, contactIds) { - if (mInfoCache.contains(contactId)) { - CntInfoCacheItem* item = mInfoCache.take(contactId); - delete item; - } - } + QString name; - foreach (QContactLocalId contactId, contactIds) { - emitContactInfoUpdated(contactId); + QHash::const_iterator i = mNameCache.find(contactId); + if (i != mNameCache.end()) { + name = i.value()->name(); } CNT_EXIT -} -/*! - 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) -{ - CNT_ENTRY_ARGS( contactId ) - - bool foundContact = false; - - QContactFetchHint nameOnlyFetchHint; - /*QStringList details; - details << QContactDisplayLabel::DefinitionName; - nameOnlyFetchHint.setDetailDefinitionsHint(details);*/ - nameOnlyFetchHint.setOptimizationHints(QContactFetchHint::NoRelationships); - QContact contact = mContactManager->contact(contactId, nameOnlyFetchHint); - - if (mContactManager->error() == QContactManager::NoError) { - contactName = contact.displayLabel(); - foundContact = true; - } - - CNT_EXIT_ARGS( foundContact ) - - return foundContact; + return name; } /*! @@ -509,20 +594,26 @@ QString name; + // fetch all names from the database if it hasn't been done yet + if (!mAllNamesFetchStarted) { + mWorker->postponeJobs(); + mAllNamesFetchStarted = true; + mNameFetcher->readAllNamesAsynch(); + } + // pick the first contact from the read ahead cache and schedule it while (mReadAheadCache.count() > 0) { int contactId = mReadAheadCache.first().first; int contactRow = mReadAheadCache.takeFirst().second; if (!mInfoCache.contains(contactId)) { // contact is not in cache, so schedule it for retreival - if (fetchContactName(contactId, name)) { + if (contactExists(contactId)) { // contact found, so add new entry to cache CntInfoCacheItem* item = createInfoCacheItem(contactId); - item->name = name; item->text = EmptyTextField; item->latestRow = contactRow; - // schedule the info for retrieval + // schedule the info mWorker->scheduleInfoJob(contactId, contactRow); break; } @@ -650,14 +741,188 @@ CNT_ENTRY mInstance = NULL; + + disconnect(mContactManager, SIGNAL(contactsChanged(const QList&)), this, SLOT(updateContacts(const QList&))); + disconnect(mContactManager, SIGNAL(contactsRemoved(const QList&)), this, SLOT(removeContacts(const QList&))); + disconnect(mContactManager, SIGNAL(contactsAdded(const QList&)), this, SLOT(addContacts(const QList&))); + deleteLater(); CNT_EXIT } +/*! + Updates the names in cache according to newFormat. + + This slot is called when name fetcher signals that the format of + names has been changed. + */ +void CntCache::reformatNames(CntNameOrder newFormat) +{ + foreach (CntNameCacheItem* item, mSortedNames) { + item->setNameFormat(newFormat); + } + + mNameFetcher->sortNames(mSortedNames); + + mNameFetcher->writeNamesToCache(mSortedNames); + mHasModifiedNames = false; + + emit dataChanged(); +} + +/*! + Replaces the names in cache with the ones in this list. + + \param newSortedNames the sorted list with names; this list will be cleared and + ownership will be taken of the items in the list + */ +void CntCache::setNameList(QList newSortedNames) +{ + CNT_ENTRY + + bool hasModifiedContacts = false; + int count = newSortedNames.count(); + + // check if there have been any changes + if (mSortedNames.count() != count) { + hasModifiedContacts = true; + } else { + for (int i = 0; i < count; ++i) { + CntNameCacheItem *oldItem = mSortedNames.at(i); + CntNameCacheItem *newItem = newSortedNames.at(i); + if (oldItem->contactId() != newItem->contactId() || oldItem->name() != newItem->name()) { + hasModifiedContacts = true; + break; + } + } + } + + // the list has changed, so use the new list instead + if (hasModifiedContacts) { + qDeleteAll(mSortedNames); + mNameCache.clear(); + mSortedNames.clear(); + + foreach (CntNameCacheItem* item, newSortedNames) { + mSortedNames.append(item); + mNameCache.insert(item->contactId(), item); + } + + // write names to file cache + mNameFetcher->writeNamesToCache(mSortedNames); + + // notify clients that the list of names has changed + emit dataChanged(); + } else { + qDeleteAll(newSortedNames); + } + + CNT_EXIT +} /*! - Creates an empty object. + Updates data in response to some contacts having changed and + then notifies observers that these contacts have changed. + */ +void CntCache::updateContacts(const QList &changedContacts) +{ + QString name; + QList items; + + // reloads the names of the changed contacts and updates the + // list of sorted names accordingly + foreach (QContactLocalId contactId, changedContacts) { + CntNameCacheItem *newItem = mNameFetcher->readOneName(contactId); + if (newItem != NULL) { + CntNameCacheItem *oldItem = mNameCache.value(contactId); + if (oldItem->name() != newItem->name()) { + QList::iterator oldPos = qLowerBound(mSortedNames.begin(), mSortedNames.end(), oldItem, CntNameFetcher::compareNames); + while (*oldPos != oldItem && oldPos != mSortedNames.end()) { + ++oldPos; + } + QList::iterator newPos = qUpperBound(mSortedNames.begin(), mSortedNames.end(), newItem, CntNameFetcher::compareNames); + if (oldPos < newPos) { + mSortedNames.move(oldPos - mSortedNames.begin(), (newPos - mSortedNames.begin()) - 1); + } else { + mSortedNames.move(oldPos - mSortedNames.begin(), newPos - mSortedNames.begin()); + } + *oldItem = *newItem; + mHasModifiedNames = true; + } + } + } + + // if any of the changed items have cached info, the info + // is scheduled for refreshing + foreach (QContactLocalId contactId, changedContacts) { + if (mInfoCache.contains(contactId)) { + CntInfoCacheItem* infoItem = mInfoCache.value(contactId); + mWorker->scheduleInfoJob(contactId, infoItem->latestRow); + } + } + + // inform clients about these changes + emit contactsChanged(changedContacts); +} + +/*! + Updates data in response to some contacts having been removed + and then notifies observers that the contacts have been removed. + */ +void CntCache::removeContacts(const QList &removedContacts) +{ + // removed the deleted contacts from the name cache and from the + // list of sorted names + foreach (QContactLocalId contactId, removedContacts) { + CntNameCacheItem *item = mNameCache.take(contactId); + if (item) { + QList::iterator pos = qLowerBound(mSortedNames.begin(), mSortedNames.end(), item, CntNameFetcher::compareNames); + while (*pos != item && pos != mSortedNames.end()) { + ++pos; + } + mSortedNames.erase(pos); + delete item; + mHasModifiedNames = true; + } + } + + // info for these deleted items should be removed from cache + foreach (QContactLocalId contactId, removedContacts) { + if (mInfoCache.contains(contactId)) { + CntInfoCacheItem* item = mInfoCache.take(contactId); + delete item; + } + } + + // inform clients about these deleted contacts + emit contactsRemoved(removedContacts); +} + +/*! + Updates data in response to some contacts having been added + and then notifies observers that the contacts have been added. + */ +void CntCache::addContacts(const QList &addedContacts) +{ + // add the new contacts to the name cache and to the + // list of sorted names + foreach (QContactLocalId contactId, addedContacts) { + CntNameCacheItem *item = mNameFetcher->readOneName(contactId); + if (item != NULL) { + mNameCache.insert(contactId, item); + QList::iterator i = qUpperBound(mSortedNames.begin(), mSortedNames.end(), item, CntNameFetcher::compareNames); + mSortedNames.insert(i, item); + mHasModifiedNames = true; + } + } + + // inform clients about the new contacts + emit contactsAdded(addedContacts); +} + +/*! + Creates an empty CntContactInfo object. */ CntContactInfo::CntContactInfo() : d(new CntContactInfoData()) @@ -665,7 +930,7 @@ } /*! - Creates an object with all info fields set. + Creates a CntContactInfo 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()) @@ -740,4 +1005,3 @@ { return d->icon2; } -