--- 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 <hbapplication.h>
#include <qtcontacts.h>
#include <qcontactmanager.h>
+#include <QTimer>
+
#include <cntdebug.h>
#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<QContactLocalId>&)), this, SLOT(updateContactsInCache(const QList<QContactLocalId>&)));
- connect(mContactManager, SIGNAL(contactsRemoved(const QList<QContactLocalId>&)), this, SLOT(removeContactsFromCache(const QList<QContactLocalId>&)));
-
- // 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<QContactLocalId> CntCache::sortIdsByName(const QSet<QContactLocalId>* idFilter) const
{
CNT_ENTRY
- // clear info cache
+ QList<QContactLocalId> 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<QContactLocalId> CntCache::sortIdsByName(const QStringList searchList) const
+{
+ CNT_ENTRY
+
+ QList<QContactLocalId> 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<CntNameCacheItem *>)), this, SLOT(setNameList(QList<CntNameCacheItem *>)));
+
+ // 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<QContactLocalId>&)), this, SLOT(updateContacts(const QList<QContactLocalId>&)));
+ connect(mContactManager, SIGNAL(contactsRemoved(const QList<QContactLocalId>&)), this, SLOT(removeContacts(const QList<QContactLocalId>&)));
+ connect(mContactManager, SIGNAL(contactsAdded(const QList<QContactLocalId>&)), this, SLOT(addContacts(const QList<QContactLocalId>&)));
+
+ // 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<QContactLocalId>& 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<QContactLocalId>& 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<QContactLocalId, CntNameCacheItem*>::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<QContactLocalId>&)), this, SLOT(updateContacts(const QList<QContactLocalId>&)));
+ disconnect(mContactManager, SIGNAL(contactsRemoved(const QList<QContactLocalId>&)), this, SLOT(removeContacts(const QList<QContactLocalId>&)));
+ disconnect(mContactManager, SIGNAL(contactsAdded(const QList<QContactLocalId>&)), this, SLOT(addContacts(const QList<QContactLocalId>&)));
+
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<CntNameCacheItem *> 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<QContactLocalId> &changedContacts)
+{
+ QString name;
+ QList<CntNameCacheItem*> 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<CntNameCacheItem*>::iterator oldPos = qLowerBound(mSortedNames.begin(), mSortedNames.end(), oldItem, CntNameFetcher::compareNames);
+ while (*oldPos != oldItem && oldPos != mSortedNames.end()) {
+ ++oldPos;
+ }
+ QList<CntNameCacheItem*>::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<QContactLocalId> &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<CntNameCacheItem*>::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<QContactLocalId> &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<CntNameCacheItem*>::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;
}
-