phonebookui/cntlistmodel/cntcache.cpp
changeset 81 640d30f4fb64
equal deleted inserted replaced
77:c18f9fa7f42e 81:640d30f4fb64
       
     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: Asynchronously fetches and caches basic contact info
       
    15 *              and icons.
       
    16 *
       
    17 */
       
    18 
       
    19 #include <hbapplication.h>
       
    20 #include <qtcontacts.h>
       
    21 #include <qcontactmanager.h>
       
    22 #include <QTimer>
       
    23 #include <cntcache.h>
       
    24 #include <cntcacheitems.h>
       
    25 #include <cntnamefetcher.h>
       
    26 #include <cntinfofetcher.h>
       
    27 #include <cnticonfetcher.h>
       
    28 #include <cntdebug.h>
       
    29 
       
    30 /*!
       
    31     \class CntContactInfo
       
    32     \brief Info about one contact, intended for list views.
       
    33 
       
    34    This class contains info about one contact. The info is mainly intended
       
    35    to be used in list views:
       
    36      - name: the name of the contact, formatted for displaying
       
    37      - text: secondary information like a phone number
       
    38      - icon1: the main icon
       
    39      - icon2: the smaller secondary icon
       
    40 
       
    41     \class CntCache
       
    42     \brief Asynchronously fetches contact info and icons.
       
    43 
       
    44     Singleton class that acts as a proxy to get CntContactInfo objects for
       
    45     contacts. It also implements caching for faster access. This is why the
       
    46     fetchContactInfo() function takes a row number and the full list of
       
    47     contact IDs rather than just a contact ID -- it allows precaching.
       
    48 
       
    49     The usage pattern for clients is to call fetchContactInfo() to get at
       
    50     least the name of the contact. If all the info is cached then it will be
       
    51     provided. If not, then the uncached info is fetched asynchronously and
       
    52     contactInfoUpdated() signals are emitted as the pieces of information
       
    53     arrive -- up to three times per contact; once for text, once for icon1
       
    54     and once for icon2.
       
    55 
       
    56     Internally CntCache uses three fetchers; one for names, one for info and
       
    57     one for icons.
       
    58  */
       
    59 
       
    60 // set the singleton instance pointer to NULL
       
    61 CntCache* CntCache::mInstance = NULL;
       
    62 
       
    63 // the event for starting to process all outstanding jobs
       
    64 const QEvent::Type ProcessJobsEvent = QEvent::User;
       
    65 
       
    66 // different states of postponement 
       
    67 const int JobsNotPostponed = 0;
       
    68 const int JobsPostponedForDuration = 1;
       
    69 const int JobsPostponedUntilResume = 2;
       
    70 
       
    71 // number of items to read ahead into cache; this number is for one direction
       
    72 const int ItemsToCacheAhead = 24;
       
    73 
       
    74 // cache size for info items
       
    75 const int InfoCacheSize = 128;
       
    76 
       
    77 // cache size for icon items; must be larger than 2 * ItemsToCacheAhead
       
    78 const int IconCacheSize = 60;
       
    79 
       
    80 // duration of urgency mode in milliseconds
       
    81 const int UrgencyModeDuration = 100;
       
    82 
       
    83 // duration of a postponement in milliseconds
       
    84 const int PostponeJobsDuration = 300;
       
    85 
       
    86 // number of icons in a CntContactInfo object
       
    87 const int IconsInCntContactInfo = 2;
       
    88 
       
    89 // default empty text info field for a contact; it cannot be empty
       
    90 // as the listview will then ignore it, causing rendering problems
       
    91 const QString EmptyTextField = " ";
       
    92 
       
    93 /*!
       
    94     Provides a pointer to the CntCache singleton instance.
       
    95     
       
    96     \param client a pointer to the client
       
    97     \param manager  
       
    98  */
       
    99 CntCache* CntCache::createSession(void *client, QContactManager *manager)
       
   100 {
       
   101 	CNT_STATIC_ENTRY_ARGS("client =" << client << ", mngr =" << (void*) manager)
       
   102 
       
   103     if (!mInstance) {
       
   104         mInstance = new CntCache(manager);
       
   105     }
       
   106 
       
   107     // increase reference counter for cache clients
       
   108     mInstance->mClients.insert(client);
       
   109 
       
   110     // whenever a client requests an instance, the client will want to get all info
       
   111     // for the first screenful of contacts urgently
       
   112     mInstance->startUrgencyMode();
       
   113 
       
   114 	CNT_EXIT_ARGS("instance =" << (void*) mInstance << ", refCount =" << mInstance->mClients.count())
       
   115 
       
   116     return mInstance;
       
   117 }
       
   118 
       
   119 /*!
       
   120     Disconnects from CntCache.
       
   121  */
       
   122 void CntCache::closeSession(void *client)
       
   123 {
       
   124 	CNT_ENTRY
       
   125 
       
   126     // delete singleton instance if there are no more clients
       
   127     mInstance->mClients.remove(client);
       
   128     if (mInstance->mClients.count() == 0) {
       
   129     	CNT_LOG_ARGS("no more clients, so deleting singleton instance")
       
   130         mInstance = NULL;
       
   131         delete this;
       
   132     }
       
   133 
       
   134 	CNT_EXIT
       
   135 }
       
   136 
       
   137 /*! 
       
   138     Fetches visuals for a contact: name, text (e.g. phone number or social
       
   139     status) and two icons (e.g. avatar, presence). Previously cached content,
       
   140     at the very least the name, will be returned immediately. Availability of
       
   141     more information will be checked asynchronously and sent to clients via
       
   142     contactInfoUpdated() signals.
       
   143     
       
   144     The function takes a row and a list rather than just a contact ID because
       
   145     of read ahead caching - contacts near the requested contacts are expected
       
   146     to be needed soon and are therefore precached.
       
   147     
       
   148     \param row the row of the contact to fetch
       
   149     \param idList a list with all the IDs in the list
       
   150     \return a contact with some details filled in
       
   151  */
       
   152 CntContactInfo* CntCache::fetchContactInfo(int row, const QList<QContactLocalId>& idList)
       
   153 {
       
   154     CNT_ENTRY_ARGS(row << "/" << idList.count())
       
   155 
       
   156     Q_ASSERT(row >= 0 && row < idList.count());
       
   157 
       
   158     QString name;
       
   159     QString text = EmptyTextField;
       
   160     HbIcon icons[IconsInCntContactInfo];
       
   161 
       
   162     QContactLocalId contactId = idList.at(row);
       
   163 
       
   164     if (contactId != mLastEmittedContactId) {
       
   165         // this is a new request from the UI (rather than a response to
       
   166         // a change that the cache just emitted)
       
   167         if (!mIsInUrgencyMode) {
       
   168             postponeJobs(JobsPostponedForDuration, PostponeJobsDuration);
       
   169         }
       
   170         updateReadAheadCache(row, idList);
       
   171     }
       
   172 
       
   173     // fetch the contact
       
   174     if (mInfoCache.contains(contactId)) {
       
   175         // the contact's info is cached
       
   176         CntInfoCacheItem* infoItem = mInfoCache.value(contactId);
       
   177         for (int i = 0; i < IconsInCntContactInfo; ++i) {
       
   178             QString iconName = infoItem->icons[i];
       
   179             if (!iconName.isEmpty()) {
       
   180                 if (mIconCache.contains(iconName)) {
       
   181                     CntIconCacheItem* iconItem = mIconCache.value(iconName);
       
   182                     if (iconItem->requestedBy.count() > 0) {
       
   183                     // icon is being fetched -> add this contact to list of requestors
       
   184                         iconItem->requestedBy << contactId;
       
   185                     }
       
   186                     iconItem->lastRequest = QTime::currentTime();
       
   187                     icons[i] = iconItem->icon;
       
   188                 } else {
       
   189                     // needed icon is not in cache, so schedule it for retrieval
       
   190                     CntIconCacheItem* iconItem = createIconCacheItem(iconName);
       
   191                     iconItem->requestedBy << contactId;
       
   192                     mIconFetcher->scheduleJob(new CntIconJob(iconName), row);
       
   193                 }
       
   194             }
       
   195         }
       
   196 
       
   197         // set return text
       
   198         text = infoItem->text;
       
   199 
       
   200         // update cache order
       
   201         infoItem->lastRequest = QTime::currentTime();
       
   202     } else if (contactExists(contactId)) {
       
   203         // contact exists but info is not in cache, so schedule it for retrieval
       
   204         CntInfoCacheItem* item = createInfoCacheItem(contactId);
       
   205         item->text = text;
       
   206         mInfoFetcher->scheduleJob(new CntInfoJob(contactId), row);
       
   207     } else {
       
   208         return NULL;
       
   209     }
       
   210 
       
   211     name = contactName(contactId);
       
   212 
       
   213     if (!mProcessingJobs && mJobsPostponed == JobsNotPostponed) {
       
   214         // there might be new jobs now
       
   215         mProcessingJobs = true;
       
   216         HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent));
       
   217     }
       
   218 
       
   219     CNT_EXIT_ARGS("name:" << name << "text:" << text)
       
   220 
       
   221     return new CntContactInfo(contactId, name, text, icons[0], icons[1]);
       
   222 }
       
   223 
       
   224 /*! 
       
   225     Creates a list of contact ids sorted according the corresponding contact names.
       
   226 
       
   227     \param idFilter the IDs to be returned; if NULL, all contact IDs are returned
       
   228     \return the list of ids, sorted by contact name
       
   229  */
       
   230 QList<QContactLocalId> CntCache::sortIdsByName(const QSet<QContactLocalId>* idFilter) const
       
   231 {
       
   232     CNT_ENTRY
       
   233 
       
   234     QList<QContactLocalId> sortedIds;
       
   235     
       
   236     // allocate memory in advance to avoid repeated reallocation during population
       
   237     // an extra 16 items are allocated to leave room for a few more contacts
       
   238     // before reallocation is needed
       
   239     if (!idFilter) {
       
   240         sortedIds.reserve(mSortedNames.count() + 16);
       
   241     } else {
       
   242         sortedIds.reserve(idFilter->count() + 16);
       
   243     }
       
   244 
       
   245     // the entries in mSortedNames are already sorted, so just pick
       
   246     // out the ids from that list in the order that they appear
       
   247     if (!idFilter) {
       
   248         foreach (CntNameCacheItem* item, mSortedNames) {
       
   249             sortedIds.append(item->contactId());
       
   250         }
       
   251     } else {
       
   252         foreach (CntNameCacheItem* item, mSortedNames) {
       
   253             if (idFilter->contains(item->contactId())) {
       
   254                 sortedIds.append(item->contactId());
       
   255             }
       
   256         }
       
   257     }
       
   258 
       
   259     CNT_EXIT
       
   260 
       
   261     return sortedIds;
       
   262 }
       
   263 
       
   264 /*!
       
   265     Overloaded version of the function for string based searching of contact
       
   266     names. Currently for multi part names only space and dash variations are
       
   267     used for filtering, e.g. "A B" matches "Beta, Alfa" and "Alfa, Beta",
       
   268     but also "Gamma, Alfa-Beta" and "Gamma, Alfa Beta" and "Alfa Beta, Gamma".
       
   269     
       
   270     \param searchList list of strings to search for
       
   271     \return the list of ids, sorted by contact name
       
   272  */
       
   273 QList<QContactLocalId> CntCache::sortIdsByName(const QStringList &searchList) const
       
   274 {
       
   275     CNT_ENTRY_ARGS("time:" << User::FastCounter());
       
   276 
       
   277     QList<QContactLocalId> sortedIds;
       
   278     QSet<int> checkedNames;
       
   279     QStringList searchListSorted;
       
   280 
       
   281     // the given search string must be ordered to descending order according to word length
       
   282     // so the search algorithm finds the correct contacts, this prevents cases where search string
       
   283     // is e.g. "ax axx" so names starting with "axxyyz axyz" don't cause any problems for the search
       
   284     foreach (QString oneString, searchList) {
       
   285         searchListSorted.append(oneString.toLower());
       
   286     }
       
   287     qSort(searchListSorted.begin(), searchListSorted.end(), qGreater<QString>());
       
   288 
       
   289     for (int iter = 0; iter < mSortedNames.size(); iter++) {
       
   290         int searchIndex;
       
   291         QString currentName = (mSortedNames.at(iter))->name();
       
   292         checkedNames.clear();
       
   293 
       
   294         for (searchIndex = 0; searchIndex < searchListSorted.size(); searchIndex++) {
       
   295             int currentPos;
       
   296             int tempStartPos = 0;
       
   297             for (currentPos = 0; currentPos <= currentName.length(); currentPos++) {
       
   298                 // at the moment only differentiating character is the space (" ")
       
   299                 if (currentPos == currentName.length() || currentName.at(currentPos) == ' ') {
       
   300                     QString tempName = currentName.mid(tempStartPos, currentPos - tempStartPos);
       
   301 
       
   302                     if (!checkedNames.contains(tempStartPos)
       
   303                         && tempName.startsWith(searchListSorted.at(searchIndex), Qt::CaseInsensitive)) {
       
   304                         checkedNames.insert(tempStartPos);
       
   305                         break;
       
   306                     }
       
   307                     tempStartPos = ++currentPos;
       
   308                 }
       
   309             }
       
   310             // if the name is parsed completely through then it can't be a match
       
   311             if (currentPos > currentName.length()) {
       
   312                 break;
       
   313             }
       
   314         }
       
   315         // if the whole search parameter list is parsed, then the name must match the given search string
       
   316         if (searchIndex == searchListSorted.size()) {
       
   317             sortedIds.append(mSortedNames.at(iter)->contactId());
       
   318         }
       
   319     }
       
   320 
       
   321     CNT_EXIT_ARGS("time:" << User::FastCounter());
       
   322 
       
   323     return sortedIds;
       
   324 }
       
   325 
       
   326 /*!
       
   327     Creates the CntCache singleton instance.
       
   328  */
       
   329 CntCache::CntCache(QContactManager *manager)
       
   330     : mContactManager(manager),
       
   331       mNameFetcher(new CntNameFetcher()),
       
   332       mInfoFetcher(new CntInfoFetcher(mContactManager)),
       
   333       mIconFetcher(new CntIconFetcher()),
       
   334       mProcessingJobs(false),
       
   335       mJobsPostponed(JobsNotPostponed),
       
   336       mIsInUrgencyMode(false),
       
   337       mLastEmittedContactId(-1),
       
   338       mHasModifiedNames(false),
       
   339       mAllNamesFetchStarted(false)
       
   340 {
       
   341     CNT_ENTRY
       
   342 
       
   343     // listen to name fetcher
       
   344     QObject::connect(mNameFetcher, SIGNAL(nameFormatChanged(CntNameOrder)),
       
   345             this, SLOT(reformatNames(CntNameOrder)));
       
   346     QObject::connect(mNameFetcher, SIGNAL(databaseAccessComplete()),
       
   347             this, SLOT(resumeJobs()));
       
   348     QObject::connect(mNameFetcher, SIGNAL(namesAvailable(QList<CntNameCacheItem *>)),
       
   349             this, SLOT(setNameList(QList<CntNameCacheItem *>)));
       
   350 
       
   351     // listen to info fetcher
       
   352     QObject::connect(mInfoFetcher, SIGNAL(infoUpdated(QContactLocalId, const ContactInfoField &, const QString &)),
       
   353             this, SLOT(updateCachedInfo(QContactLocalId, const ContactInfoField &, const QString &)));
       
   354     QObject::connect(mInfoFetcher, SIGNAL(infoCancelled(QContactLocalId)),
       
   355             this, SLOT(cancelInfoFetch(QContactLocalId)));
       
   356 
       
   357     // listen to icon fetcher
       
   358     QObject::connect(mIconFetcher, SIGNAL(iconFetched(const QString &, const HbIcon &)),
       
   359             this, SLOT(updateCachedIcon(const QString &, const HbIcon &)));
       
   360     QObject::connect(mIconFetcher, SIGNAL(iconCancelled(const QString &)),
       
   361             this, SLOT(cancelIconFetch(const QString &)));
       
   362 
       
   363     // listen to contact manager
       
   364     QObject::connect(mContactManager, SIGNAL(contactsChanged(const QList<QContactLocalId>&)),
       
   365             this, SLOT(updateContacts(const QList<QContactLocalId>&)));
       
   366     QObject::connect(mContactManager, SIGNAL(contactsRemoved(const QList<QContactLocalId>&)),
       
   367             this, SLOT(removeContacts(const QList<QContactLocalId>&)));
       
   368     QObject::connect(mContactManager, SIGNAL(contactsAdded(const QList<QContactLocalId>&)),
       
   369             this, SLOT(addContacts(const QList<QContactLocalId>&)));
       
   370 
       
   371     // listen to timer events; this is for postponing and resuming jobs
       
   372     mResumeJobsTimer.setSingleShot(true);
       
   373     QObject::connect(&mResumeJobsTimer, SIGNAL(timeout()), this, SLOT(resumeJobs()));
       
   374 
       
   375     // load all names to cache
       
   376     loadNames();
       
   377 
       
   378     CNT_EXIT
       
   379 }
       
   380 
       
   381 /*!
       
   382     Destructs the CntCache singleton instance.
       
   383  */
       
   384 CntCache::~CntCache()
       
   385 {
       
   386     CNT_ENTRY
       
   387 
       
   388     QObject::disconnect(this);
       
   389 
       
   390     if (mHasModifiedNames) {
       
   391         mNameFetcher->writeNamesToCache(mSortedNames);
       
   392     }
       
   393 
       
   394     delete mNameFetcher;
       
   395     delete mInfoFetcher;
       
   396     delete mIconFetcher;
       
   397 
       
   398     qDeleteAll(mInfoCache);
       
   399     mInfoCache.clear();
       
   400 
       
   401     qDeleteAll(mIconCache);
       
   402     mIconCache.clear();
       
   403 
       
   404     qDeleteAll(mNameCache);
       
   405     mNameCache.clear();
       
   406 
       
   407     mSortedNames.clear();   // contains same data as mNameCache, so no qDeleteAll
       
   408 
       
   409     CNT_EXIT
       
   410 }
       
   411 
       
   412 /*!
       
   413     Postpones outstanding jobs until milliseconds ms has passed or resumeJobs() is called.
       
   414     
       
   415     \param postponement Type the type of postponement; UntilResume or ForDuration
       
   416     \param milliseconds The duration of the delay
       
   417  */
       
   418 void CntCache::postponeJobs(int postponementType, int duration)
       
   419 {
       
   420     CNT_ENTRY_ARGS("ms =" << duration)
       
   421 
       
   422     Q_ASSERT((postponementType == JobsPostponedUntilResume
       
   423              || postponementType == JobsPostponedForDuration)
       
   424              && duration >= 0);
       
   425 
       
   426     mJobsPostponed = postponementType;
       
   427     mResumeJobsTimer.stop();
       
   428 
       
   429     if (postponementType == JobsPostponedForDuration) {
       
   430         mResumeJobsTimer.start(duration);
       
   431     }
       
   432 
       
   433     CNT_EXIT_ARGS("type =" << mJobsPostponed)
       
   434 }
       
   435 
       
   436 /*!
       
   437     Postpones outstanding jobs until resumeJobs() is called. This must always be called after
       
   438     postponeJobs.
       
   439  */
       
   440 void CntCache::resumeJobs()
       
   441 {
       
   442     CNT_ENTRY
       
   443 
       
   444     Q_ASSERT(!mProcessingJobs && mJobsPostponed != JobsNotPostponed);
       
   445 
       
   446     mResumeJobsTimer.stop();
       
   447     mJobsPostponed = JobsNotPostponed;
       
   448     mProcessingJobs = true;
       
   449     HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent));
       
   450 
       
   451     CNT_EXIT
       
   452 }
       
   453 
       
   454 /*!
       
   455     Listens for ProcessJobsEvents and calls processJobs() if there is such an event.
       
   456  */
       
   457 bool CntCache::event(QEvent* event)
       
   458 {
       
   459     if (event->type() == ProcessJobsEvent) {
       
   460         processJobs();
       
   461         return true;
       
   462     }
       
   463 
       
   464     return QObject::event(event);
       
   465 }
       
   466 
       
   467 /*!
       
   468     Processes all scheduled jobs in all fetchers. The loop runs until all
       
   469     jobs are done or postponed.
       
   470  */
       
   471 void CntCache::processJobs()
       
   472 {
       
   473     CNT_ENTRY
       
   474 
       
   475     // process fetcher jobs in order of priority
       
   476     forever {
       
   477         // 1: has all jobs been postponed?
       
   478         if (mJobsPostponed != JobsNotPostponed) {
       
   479             CNT_EXIT_ARGS("jobs postponed")
       
   480             mProcessingJobs = false;
       
   481             return;
       
   482 
       
   483         // 2: is there a request to fetch info?
       
   484         } else if (mInfoFetcher->hasScheduledJobs()) {
       
   485             mInfoFetcher->processNextJob();
       
   486 
       
   487         // 3: is there a request to fetch an icon?
       
   488         } else if (mIconFetcher->hasScheduledJobs()) {
       
   489             // quit the loop; it will be started again when the icon has been fetched
       
   490             if (!mIconFetcher->isProcessingJob()) {
       
   491                 mIconFetcher->processNextJob();
       
   492             }
       
   493             mProcessingJobs = false;
       
   494             CNT_EXIT_ARGS("jobs postponed until icon fetch returns")
       
   495             return;
       
   496 
       
   497         // 4: are there any cancelled info jobs?
       
   498         } else if (mInfoFetcher->hasCancelledJobs() && !mInfoFetcher->isProcessingJob()) {
       
   499             mInfoFetcher->processNextJob();
       
   500 
       
   501         // 5: are there any cancelled icon jobs?
       
   502         } else if (mIconFetcher->hasCancelledJobs() && !mIconFetcher->isProcessingJob()) {
       
   503             mIconFetcher->processNextJob();
       
   504 
       
   505         // 6: is there an "all names" job?
       
   506         } else if (mNameFetcher->hasScheduledJobs()) {
       
   507             // fetch all contact names from the database so that the current
       
   508             // list of names (from the file cache) can be synched with the
       
   509             // database
       
   510             if (!mNameFetcher->isProcessingJob()) {
       
   511                 mNameFetcher->processNextJob();
       
   512             }
       
   513             mProcessingJobs = false;
       
   514             postponeJobs(JobsPostponedUntilResume);
       
   515             CNT_EXIT_ARGS("jobs postponed while fetching all names")
       
   516             return;
       
   517 
       
   518         // 7: are there contacts left to precache?
       
   519         } else if (mReadAheadCache.count() > 0) {
       
   520             int contactId = mReadAheadCache.first().first;
       
   521             int contactRow = mReadAheadCache.takeFirst().second;
       
   522             if (!mInfoCache.contains(contactId) && contactExists(contactId)) {
       
   523                 // contact exists, but is not in cache, so schedule it for retrieval
       
   524                 CntInfoCacheItem* item = createInfoCacheItem(contactId);
       
   525                 item->text = EmptyTextField;
       
   526                 mInfoFetcher->scheduleJob(new CntInfoJob(contactId), contactRow);
       
   527             }
       
   528         // nothing more to do, so exit loop
       
   529         } else {
       
   530             mProcessingJobs = false;
       
   531             CNT_EXIT_ARGS("no more jobs")
       
   532             return;
       
   533         }
       
   534 
       
   535         // allow events to be handled before continuing with the next job
       
   536         HbApplication::processEvents();
       
   537     }
       
   538 }
       
   539 
       
   540 /*! 
       
   541     Processes a new info field that has arrived from the info fetcher.
       
   542     If the contact is in the info cache, then the info cache is updated
       
   543     accordingly.
       
   544     
       
   545     A contactInfoUpdated() signal is usually also emitted. The exception is if
       
   546     the info is the name of an icon and that icon is not in the icon cache. In
       
   547     this case the icon is scheduled to be fetched and a signal will eventually
       
   548     be emitted when the icon has been fetched (or cancelled).
       
   549  */
       
   550 void CntCache::updateCachedInfo(QContactLocalId contactId, const ContactInfoField& infoField, const QString& infoValue)
       
   551 {
       
   552     CNT_ENTRY_ARGS( "id:" << contactId   << "infotype:" << infoField   << "infovalue:" << infoValue )
       
   553 
       
   554     Q_ASSERT(infoField == ContactInfoTextField || infoField == ContactInfoIcon1Field || infoField == ContactInfoIcon2Field);
       
   555 
       
   556     bool hasNewInfo;
       
   557 
       
   558     if (!mInfoCache.contains(contactId)) {
       
   559         // contact is not in cache, so nothing needs to be done except notify
       
   560         // clients that this contact has (possibly) been changed
       
   561         hasNewInfo = true;
       
   562     } else if (infoField == ContactInfoTextField) {
       
   563         // update cache with new text for contact
       
   564         mInfoCache.value(contactId)->text = infoValue;
       
   565         hasNewInfo = true;
       
   566     } else {
       
   567         // update cache with new icon name for contact
       
   568         int iconIndex = (infoField == ContactInfoIcon1Field ? 0 : 1);
       
   569         CntInfoCacheItem* item = mInfoCache.value(contactId);
       
   570         QString iconName = infoValue;
       
   571         if (item->icons[iconIndex] != iconName) {
       
   572             item->icons[iconIndex] = iconName;
       
   573             if (iconName.isEmpty()) {
       
   574                 hasNewInfo = true;
       
   575             } else if (mIconCache.contains(iconName)) {
       
   576                 hasNewInfo = true;
       
   577             } else if (iconName.startsWith("qtg_", Qt::CaseInsensitive)) {
       
   578                 CntIconCacheItem* iconItem = createIconCacheItem(iconName);
       
   579                 iconItem->icon = HbIcon(iconName);
       
   580                 hasNewInfo = true;
       
   581             } else {
       
   582                 CntIconCacheItem* iconItem = createIconCacheItem(iconName);
       
   583                 iconItem->requestedBy << contactId;
       
   584                 QList<CntNameCacheItem*>::iterator pos = qLowerBound(mSortedNames.begin(), mSortedNames.end(), mNameCache.value(contactId), CntNameFetcher::compareNames);
       
   585                 while (pos != mSortedNames.end() && (*pos)->contactId() != contactId) {
       
   586                     ++pos;
       
   587                 }
       
   588                 mIconFetcher->scheduleJob(new CntIconJob(iconName), pos - mSortedNames.begin());
       
   589                 hasNewInfo = false;
       
   590             }
       
   591         } else {
       
   592             hasNewInfo = false;
       
   593         }
       
   594     }
       
   595 
       
   596     if (hasNewInfo) {
       
   597         emitContactInfoUpdated(contactId);
       
   598     }
       
   599 
       
   600     if (!mProcessingJobs && mJobsPostponed == JobsNotPostponed) {
       
   601         // there might be new jobs now
       
   602         mProcessingJobs = true;
       
   603         HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent));
       
   604     }
       
   605 
       
   606     CNT_EXIT
       
   607 }
       
   608 
       
   609 /*! 
       
   610     Handle the case where a request for contact info is cancelled by the
       
   611     info fetcher because of too many scheduled jobs.
       
   612  */
       
   613 void CntCache::cancelInfoFetch(QContactLocalId contactId)
       
   614 {
       
   615     CNT_ENTRY_ARGS( "cid =" << contactId )
       
   616 
       
   617     if (mInfoCache.contains(contactId)) {
       
   618         delete mInfoCache.take(contactId);
       
   619     }
       
   620 
       
   621     emitContactInfoUpdated(contactId);
       
   622 
       
   623     CNT_EXIT
       
   624 }
       
   625 
       
   626 /*! 
       
   627     Processes a new icon that has arrived from the icon fetcher.
       
   628     The icon cache is updated and a contactInfoUpdated() signal is
       
   629     emitted.
       
   630  */
       
   631 void CntCache::updateCachedIcon(const QString& iconName, const HbIcon& icon)
       
   632 {
       
   633     CNT_ENTRY_ARGS( "icon =" << iconName )
       
   634 
       
   635     if (mIconCache.contains(iconName)) {
       
   636         CntIconCacheItem* item = mIconCache.value(iconName);
       
   637         item->icon = icon;
       
   638         foreach (QContactLocalId contactId, item->requestedBy) {
       
   639             emitContactInfoUpdated(contactId);
       
   640         }
       
   641         item->requestedBy.clear();
       
   642 
       
   643         if (!mProcessingJobs && mJobsPostponed == JobsNotPostponed) {
       
   644             // there might still be unfinished icon jobs; only one icon job is
       
   645             // done at a time
       
   646             mProcessingJobs = true;
       
   647             HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent));
       
   648         }
       
   649     }
       
   650 
       
   651     CNT_EXIT
       
   652 }
       
   653 
       
   654 /*! 
       
   655     Handle the case where a request for an icon is cancelled by the icon
       
   656     fetcher because of too many scheduled jobs.
       
   657  */
       
   658 void CntCache::cancelIconFetch(const QString& iconName)
       
   659 {
       
   660     CNT_ENTRY_ARGS(iconName)
       
   661 
       
   662     if (mIconCache.contains(iconName)) {
       
   663         CntIconCacheItem* item = mIconCache.take(iconName);
       
   664         foreach (QContactLocalId contactId, item->requestedBy) {
       
   665             emitContactInfoUpdated(contactId);
       
   666         }
       
   667         delete item;
       
   668     }
       
   669 
       
   670     CNT_EXIT
       
   671 }
       
   672 
       
   673 /*! 
       
   674     Creates a new item in the info cache. If the cache is full,
       
   675     then the least recently accessed item is removed from cache.
       
   676     
       
   677     /param contactId id of contact for which to create the new cache item
       
   678     /return the newly created cache item
       
   679  */
       
   680 CntInfoCacheItem * CntCache::createInfoCacheItem(QContactLocalId contactId)
       
   681 {
       
   682     CNT_ENTRY_ARGS(contactId)
       
   683 
       
   684     if (mInfoCache.count() >= InfoCacheSize) {
       
   685         // cache is full, so remove the oldest contact
       
   686         CntInfoCacheItem* oldestItem = NULL;
       
   687         QTime oldestRequest;
       
   688 
       
   689         foreach (CntInfoCacheItem* i, mInfoCache) {
       
   690             if (oldestItem == NULL || i->lastRequest < oldestRequest) {
       
   691                 oldestRequest = i->lastRequest;
       
   692                 oldestItem = i;
       
   693             }
       
   694         }
       
   695 
       
   696         if (oldestItem != NULL) {
       
   697             mInfoCache.remove(oldestItem->contactId);
       
   698             delete oldestItem;
       
   699         }
       
   700     }
       
   701     
       
   702     // create and insert the new item
       
   703     CntInfoCacheItem* item = new CntInfoCacheItem();
       
   704     item->contactId = contactId;
       
   705     item->lastRequest = QTime::currentTime();
       
   706     mInfoCache.insert(contactId, item);
       
   707     
       
   708     CNT_EXIT
       
   709 
       
   710     return item;
       
   711 }
       
   712 
       
   713 /*! 
       
   714     Creates a new item in the icon cache. If the cache is full,
       
   715     then the least recently accessed item is removed from cache.
       
   716     
       
   717     /param iconName name of the icon for which to create the new cache item
       
   718     /return the newly created cache item
       
   719  */
       
   720 CntIconCacheItem* CntCache::createIconCacheItem(const QString& iconName)
       
   721 {
       
   722     CNT_ENTRY_ARGS(iconName)
       
   723 
       
   724     if (mIconCache.count() >= IconCacheSize) {
       
   725         // cache is full, so remove the oldest icon
       
   726         CntIconCacheItem* oldestItem = NULL;
       
   727         QTime oldestRequest;
       
   728 
       
   729         foreach (CntIconCacheItem* i, mIconCache) {
       
   730             if (oldestItem == NULL || i->lastRequest < oldestRequest) {
       
   731                 oldestRequest = i->lastRequest;
       
   732                 oldestItem = i;
       
   733             }
       
   734         }
       
   735 
       
   736         if (oldestItem) {
       
   737             mIconCache.remove(oldestItem->iconName);
       
   738             delete oldestItem;
       
   739         }
       
   740     }
       
   741 
       
   742     // create and insert the new item
       
   743     CntIconCacheItem* item = new CntIconCacheItem();
       
   744     item->iconName = iconName;
       
   745     item->lastRequest = QTime::currentTime();
       
   746     mIconCache.insert(iconName, item);
       
   747 
       
   748     CNT_EXIT
       
   749 
       
   750     return item;
       
   751 }
       
   752 
       
   753 /*!
       
   754     Notifies clients that a contact might have changed.
       
   755     Clients can then request the info via fetchContactInfo() 
       
   756     if they are interested.
       
   757  */
       
   758 void CntCache::emitContactInfoUpdated(QContactLocalId contactId)
       
   759 {
       
   760 	CNT_ENTRY_ARGS(contactId)
       
   761 
       
   762     mLastEmittedContactId = contactId;
       
   763     emit contactInfoUpdated(contactId);
       
   764     mLastEmittedContactId = -1;
       
   765 
       
   766 	CNT_EXIT
       
   767 }
       
   768 
       
   769 /*! 
       
   770     Collects all contact IDs near the latest fetch from the UI. These will be fetched and
       
   771     precached when UI activity slows down.
       
   772 
       
   773     \param mostRecentRow the row of the contact that was most recently fetched
       
   774     \param idList a list with all the IDs in the list
       
   775  */
       
   776 void CntCache::updateReadAheadCache(int mostRecentRow, const QList<QContactLocalId>& idList)
       
   777 {
       
   778     CNT_ENTRY_ARGS(mostRecentRow)
       
   779 
       
   780     int row;
       
   781 
       
   782     mReadAheadCache.clear();
       
   783 
       
   784     // step through the area near to last fetch item and make sure all
       
   785     // contacts in it are also in cache or in the read ahead list
       
   786     for (int i = 1; i <= ItemsToCacheAhead; ++i) {
       
   787         for (int j = 0; j < 2; ++j) {
       
   788             if (j == 0) {
       
   789                 row = mostRecentRow - i;
       
   790                 if (row < 0) {
       
   791                     continue;
       
   792                 }
       
   793             } else {
       
   794                 row = mostRecentRow + i;
       
   795                 if (row >= idList.count()) {
       
   796                     continue;
       
   797                 }
       
   798             }
       
   799             
       
   800             int contactId = idList.at(row);
       
   801             if (!mInfoCache.contains(contactId)) {
       
   802                 // contact is not in cache, so put the id to items to read into cache
       
   803                 mReadAheadCache.append(QPair<QContactLocalId, int>(contactId, row));
       
   804             } else {
       
   805                 // contact is in cache; update lastRequest as we want to keep this item in cache
       
   806                 mInfoCache.value(contactId)->lastRequest = QTime::currentTime();
       
   807             }
       
   808         }
       
   809     }
       
   810 
       
   811     CNT_EXIT
       
   812 }
       
   813 
       
   814 /*!
       
   815     Starts the urgency mode, where all contact info is fetched immediately,
       
   816     regardless of whether there is activity in the UI or not.
       
   817  */
       
   818 void CntCache::startUrgencyMode()
       
   819 {
       
   820     CNT_ENTRY
       
   821 
       
   822     mIsInUrgencyMode = true;
       
   823     QTimer::singleShot(UrgencyModeDuration, this, SLOT(stopUrgencyMode()));
       
   824 
       
   825     CNT_EXIT
       
   826 }
       
   827 
       
   828 /*!
       
   829     Starts the urgency mode, where all contact info is fetched immediately,
       
   830     regardless of whether there is activity in the UI or not.
       
   831  */
       
   832 void CntCache::stopUrgencyMode()
       
   833 {
       
   834     CNT_ENTRY
       
   835 
       
   836     mIsInUrgencyMode = false;
       
   837 
       
   838     CNT_EXIT
       
   839 }
       
   840 
       
   841 /*!
       
   842     Fetch the names of all contacts.
       
   843  */
       
   844 void CntCache::loadNames()
       
   845 {
       
   846     CNT_ENTRY
       
   847     
       
   848     // read names from file cache
       
   849     mNameFetcher->readNamesFromCache(mSortedNames);
       
   850 
       
   851     // insert the names into the id-to-name map
       
   852     foreach (CntNameCacheItem* item, mSortedNames) {
       
   853         mNameCache.insert(item->contactId(), item);
       
   854     }
       
   855 
       
   856     // schedule the job for reading all names from the database; it will be processed once
       
   857     // info and icons for the first screenful of contacts have been read
       
   858     mNameFetcher->scheduleJob(new CntAllNamesJob());
       
   859     mProcessingJobs = true;
       
   860     HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent));
       
   861 
       
   862     CNT_EXIT
       
   863 }
       
   864 
       
   865 /*!
       
   866     Checks whether a contact exists.
       
   867  */
       
   868 bool CntCache::contactExists(QContactLocalId contactId) const
       
   869 {
       
   870     CNT_ENTRY_ARGS(contactId)
       
   871     CNT_EXIT_ARGS(mNameCache.contains(contactId))
       
   872 
       
   873     return mNameCache.contains(contactId);
       
   874 }
       
   875 
       
   876 /*!
       
   877     Fetch the name of one contact.
       
   878  */
       
   879 QString CntCache::contactName(QContactLocalId contactId) const
       
   880 {
       
   881     CNT_ENTRY_ARGS(contactId)
       
   882 
       
   883     QString name;
       
   884 
       
   885     QHash<QContactLocalId, CntNameCacheItem*>::const_iterator i = mNameCache.find(contactId);
       
   886     if (i != mNameCache.end()) {
       
   887         name = i.value()->name();
       
   888     }
       
   889 
       
   890     CNT_EXIT_ARGS(name)
       
   891 
       
   892     return name;
       
   893 }
       
   894 
       
   895 /*! 
       
   896     Updates the names in cache according to newFormat.
       
   897 
       
   898     \param newFormat the new name format, e.g. "Lastname, Firstname"
       
   899  */
       
   900 void CntCache::reformatNames(CntNameOrder newFormat)
       
   901 {
       
   902 	CNT_ENTRY
       
   903 
       
   904     foreach (CntNameCacheItem* item, mSortedNames) {
       
   905         item->setNameFormat(newFormat);
       
   906     }
       
   907 
       
   908     mNameFetcher->sortNames(mSortedNames);
       
   909     mNameFetcher->writeNamesToCache(mSortedNames);
       
   910     mHasModifiedNames = false;
       
   911 
       
   912     emit dataChanged();
       
   913 
       
   914 	CNT_EXIT
       
   915 }
       
   916 
       
   917 /*! 
       
   918     Replaces the names in cache with the ones in this list.
       
   919     
       
   920     \param newSortedNames the sorted list with names; this list will be cleared and
       
   921                           ownership will be taken of the items in the list
       
   922  */
       
   923 void CntCache::setNameList(QList<CntNameCacheItem *> newSortedNames)
       
   924 {
       
   925     CNT_ENTRY
       
   926     
       
   927     bool hasModifiedContacts = false;
       
   928     int count = newSortedNames.count();
       
   929 
       
   930     CNT_LOG_ARGS("curr_count=" << mSortedNames.count() << "db_count=" << count);
       
   931 
       
   932     // check if there have been any changes
       
   933     if (mSortedNames.count() != count) {
       
   934         hasModifiedContacts = true;
       
   935     } else {
       
   936         for (int i = 0; i < count; ++i) {
       
   937             CntNameCacheItem *oldItem = mSortedNames.at(i);
       
   938             CntNameCacheItem *newItem = newSortedNames.at(i);
       
   939             CNT_LOG_ARGS("name=" << oldItem->name());
       
   940             if (oldItem->contactId() != newItem->contactId() || oldItem->name() != newItem->name()) {
       
   941                 hasModifiedContacts = true;
       
   942                 break;
       
   943             }
       
   944         }
       
   945     }
       
   946 
       
   947     // the list has changed, so use the new list instead
       
   948     if (hasModifiedContacts) {
       
   949     	CNT_LOG_ARGS("has modified contacts -> use new list")
       
   950         qDeleteAll(mSortedNames);
       
   951         mNameCache.clear();
       
   952         mSortedNames.clear();
       
   953         
       
   954         foreach (CntNameCacheItem* item, newSortedNames) {
       
   955             mSortedNames.append(item);
       
   956             mNameCache.insert(item->contactId(), item);
       
   957         }
       
   958         
       
   959         // write names to file cache
       
   960         mNameFetcher->writeNamesToCache(mSortedNames);
       
   961         
       
   962         // notify clients that the list of names has changed
       
   963         emit dataChanged();
       
   964     } else {
       
   965         qDeleteAll(newSortedNames);
       
   966     }
       
   967     
       
   968     CNT_EXIT
       
   969 }
       
   970 
       
   971 /*! 
       
   972     Updates data in response to some contacts having changed and
       
   973     then notifies observers that these contacts have changed.
       
   974  */
       
   975 void CntCache::updateContacts(const QList<QContactLocalId> &changedContacts)
       
   976 {
       
   977 	CNT_ENTRY
       
   978 
       
   979     QString name;
       
   980     QList<CntNameCacheItem*> items;
       
   981 
       
   982     // reloads the names of the changed contacts and updates the
       
   983     // list of sorted names accordingly
       
   984     foreach (QContactLocalId contactId, changedContacts) {
       
   985         CntNameCacheItem *newItem = mNameFetcher->fetchOneName(contactId);
       
   986         if (newItem != NULL) {
       
   987             CntNameCacheItem *oldItem = mNameCache.value(contactId);
       
   988             if (oldItem->name() != newItem->name()) {
       
   989                 QList<CntNameCacheItem*>::iterator oldPos = qLowerBound(mSortedNames.begin(), mSortedNames.end(), oldItem, CntNameFetcher::compareNames);
       
   990                 while (oldPos != mSortedNames.end() && *oldPos != oldItem) {
       
   991                      ++oldPos;
       
   992                 }
       
   993                 QList<CntNameCacheItem*>::iterator newPos = qUpperBound(mSortedNames.begin(), mSortedNames.end(), newItem, CntNameFetcher::compareNames);
       
   994                 if (oldPos < newPos) {
       
   995                     mSortedNames.move(oldPos - mSortedNames.begin(), (newPos - mSortedNames.begin()) - 1);
       
   996                 } else {
       
   997                     mSortedNames.move(oldPos - mSortedNames.begin(), newPos - mSortedNames.begin());
       
   998                 }
       
   999                 *oldItem = *newItem;
       
  1000                 mHasModifiedNames = true;
       
  1001             }
       
  1002         }
       
  1003     }
       
  1004 
       
  1005     // if any of the changed items have cached info, the info
       
  1006     // is scheduled for refreshing
       
  1007     foreach (QContactLocalId contactId, changedContacts) {
       
  1008         if (mInfoCache.contains(contactId)) {
       
  1009             QList<CntNameCacheItem*>::iterator pos = qLowerBound(mSortedNames.begin(), mSortedNames.end(), mNameCache.value(contactId), CntNameFetcher::compareNames);
       
  1010             while (pos != mSortedNames.end() && (*pos)->contactId() != contactId) {
       
  1011                 ++pos;
       
  1012             }
       
  1013             mInfoFetcher->scheduleJob(new CntInfoJob(contactId), pos - mSortedNames.begin());
       
  1014         }
       
  1015     }
       
  1016 
       
  1017     // inform clients about these changes
       
  1018     emit contactsChanged(changedContacts);
       
  1019 
       
  1020     if (!mProcessingJobs && mJobsPostponed == JobsNotPostponed) {
       
  1021         // there might be new jobs now
       
  1022         mProcessingJobs = true;
       
  1023         HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent));
       
  1024     }
       
  1025 
       
  1026 	CNT_EXIT
       
  1027 }
       
  1028 
       
  1029 /*! 
       
  1030     Updates data in response to some contacts having been removed
       
  1031     and then notifies observers that the contacts have been removed.
       
  1032  */
       
  1033 void CntCache::removeContacts(const QList<QContactLocalId> &removedContacts)
       
  1034 {
       
  1035 	CNT_ENTRY
       
  1036 
       
  1037     // removed the deleted contacts from the name cache and from the
       
  1038     // list of sorted names
       
  1039     foreach (QContactLocalId contactId, removedContacts) {
       
  1040         if (mNameCache.contains(contactId)) {
       
  1041             CntNameCacheItem *item = mNameCache.take(contactId);
       
  1042             QList<CntNameCacheItem*>::iterator pos = qLowerBound(mSortedNames.begin(), mSortedNames.end(), item, CntNameFetcher::compareNames);
       
  1043             while (*pos != item && pos != mSortedNames.end()) {
       
  1044                 ++pos;
       
  1045             }
       
  1046             mSortedNames.erase(pos);
       
  1047             delete item;
       
  1048             mHasModifiedNames = true;
       
  1049         }
       
  1050     }
       
  1051 
       
  1052     // info for these deleted items should be removed from cache
       
  1053     foreach (QContactLocalId contactId, removedContacts) {
       
  1054         if (mInfoCache.contains(contactId)) {
       
  1055             CntInfoCacheItem* item = mInfoCache.take(contactId);
       
  1056             delete item;
       
  1057         }
       
  1058     }
       
  1059 
       
  1060     // inform clients about these deleted contacts
       
  1061     emit contactsRemoved(removedContacts);
       
  1062 
       
  1063 	CNT_EXIT
       
  1064 }
       
  1065 
       
  1066 /*! 
       
  1067     Updates data in response to some contacts having been added
       
  1068     and then notifies observers that the contacts have been added.
       
  1069  */
       
  1070 void CntCache::addContacts(const QList<QContactLocalId> &addedContacts)
       
  1071 {
       
  1072 	CNT_ENTRY
       
  1073 
       
  1074     // add the new contacts to the name cache and to the
       
  1075     // list of sorted names
       
  1076     foreach (QContactLocalId contactId, addedContacts) {
       
  1077         CntNameCacheItem *item = mNameFetcher->fetchOneName(contactId);
       
  1078         if (item != NULL) {
       
  1079             mNameCache.insert(contactId, item);
       
  1080             QList<CntNameCacheItem*>::iterator i = qUpperBound(mSortedNames.begin(), mSortedNames.end(), item, CntNameFetcher::compareNames);
       
  1081             mSortedNames.insert(i, item);
       
  1082             mHasModifiedNames = true;
       
  1083         }
       
  1084     }
       
  1085 
       
  1086     // inform clients about the new contacts
       
  1087     emit contactsAdded(addedContacts);
       
  1088 
       
  1089 	CNT_EXIT
       
  1090 }