phonebookengines/cntlistmodel/src/cntcache_p.cpp
branchRCL_3
changeset 62 5b6f26637ad3
equal deleted inserted replaced
58:d4f567ce2e7c 62:5b6f26637ad3
       
     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: Private data and helper classes used by class CntCache.
       
    15 *
       
    16 */
       
    17 
       
    18 #include <QPluginLoader>
       
    19 #include <QDir>
       
    20 
       
    21 #include <qtcontacts.h>
       
    22 #include <qcontactmanager.h>
       
    23 #include <hbapplication.h>
       
    24 #include <thumbnailmanager_qt.h>
       
    25 #include <hbicon.h>
       
    26 #include <QTimer>
       
    27 #include "cntcache.h"
       
    28 #include "cntcache_p.h"
       
    29 #include <cntinfoproviderfactory.h>
       
    30 #include <cntinfoprovider.h>
       
    31 #include "cntdefaultinfoprovider.h"
       
    32 #include "cntpresenceinfoprovider.h"
       
    33 #include <cntdebug.h>
       
    34 
       
    35 // maximum amount of info and icon jobs respectively -- if there are more jobs,
       
    36 // then the oldest job is skipped and the client informed that this happened
       
    37 // in this way the client can request the job again if wanted
       
    38 static const int CntMaxInfoJobs = 20;
       
    39 static const int CntMaxIconJobs = 20;
       
    40 // amount of milliseconds to postpone the jobs if the UI is very active
       
    41 static const int PostponeJobsMilliSeconds = 300;
       
    42 // the event for starting to process all outstanding jobs
       
    43 static const QEvent::Type ProcessJobsEvent = QEvent::User;
       
    44 // the id that states that no icon is currently pending from thumbnail manager
       
    45 static const int NoIconRequest = -1;
       
    46 // the id that states that there is no job with that key
       
    47 static const int NoSuchJob = -1;
       
    48 
       
    49 const char *CNT_INFO_PROVIDER_EXTENSION_PLUGIN_DIRECTORY = "/resource/qt/plugins/contacts/infoproviders/";
       
    50     
       
    51 // TODO: Provide a way (cenrep keys?) for UI to set which provider to use for
       
    52 //       what info field (and what info fields are indeed even in use).
       
    53 
       
    54 /*!
       
    55     Creates a new thread for fetching contact info and icons in the background.
       
    56  */
       
    57 CntCacheThread::CntCacheThread()
       
    58     : mContactManager(new QContactManager()),
       
    59       mStarted(false),
       
    60       mProcessingJobs(false),
       
    61       mPostponeJobs(false),
       
    62       mIconRequestId(NoIconRequest)
       
    63 {
       
    64     CNT_ENTRY
       
    65 
       
    66     // create static provider plugins
       
    67     mDataProviders.insert(new CntDefaultInfoProvider(), ContactInfoAllFields);
       
    68     mDataProviders.insert(new CntPresenceInfoProvider(), ContactInfoIcon2Field);
       
    69 
       
    70     // load dynamic provider plugins
       
    71     QDir pluginsDir(CNT_INFO_PROVIDER_EXTENSION_PLUGIN_DIRECTORY);
       
    72     foreach (QString fileName, pluginsDir.entryList(QDir::Files))
       
    73     {
       
    74         // Create plugin loader
       
    75         QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
       
    76         if ( pluginLoader.load() )
       
    77         {
       
    78             CntInfoProviderFactory *factory = qobject_cast<CntInfoProviderFactory*>(pluginLoader.instance());
       
    79             
       
    80             if (factory)
       
    81             {
       
    82                 CntInfoProvider *provider = factory->infoProvider();
       
    83                 mDataProviders.insert(provider, provider->supportedFields());
       
    84             }
       
    85         }
       
    86     }
       
    87     
       
    88     // connect the providers
       
    89     QMapIterator<CntInfoProvider*, ContactInfoFields> i(mDataProviders);
       
    90     while (i.hasNext()) {
       
    91         i.next();
       
    92         connect(static_cast<CntInfoProvider*>(i.key()),
       
    93                 SIGNAL(infoFieldReady(CntInfoProvider*, int, ContactInfoField, const QString&)),
       
    94                 this,
       
    95                 SLOT(onInfoFieldReady(CntInfoProvider*, int, ContactInfoField, const QString&)));
       
    96     }
       
    97 
       
    98     // create & connect the thumbnail manager
       
    99     mThumbnailManager = new ThumbnailManager(this);
       
   100     mThumbnailManager->setMode(ThumbnailManager::Default);
       
   101     mThumbnailManager->setQualityPreference(ThumbnailManager::OptimizeForPerformance);
       
   102     mThumbnailManager->setThumbnailSize(ThumbnailManager::ThumbnailSmall);
       
   103     connect(mThumbnailManager, SIGNAL(thumbnailReady(QPixmap, void *, int, int)),
       
   104              this, SLOT(onIconReady(QPixmap, void *, int, int)));
       
   105 
       
   106     CNT_EXIT
       
   107 }
       
   108 
       
   109 /*!
       
   110     Cleans up and destructs the thread.
       
   111  */
       
   112 CntCacheThread::~CntCacheThread()
       
   113 {
       
   114     CNT_ENTRY
       
   115     
       
   116     delete mContactManager;
       
   117     disconnect(this);
       
   118 
       
   119     mJobMutex.lock();
       
   120     mInfoJobs.clear();
       
   121     mCancelledInfoJobs.clear();
       
   122     mIconJobs.clear();
       
   123     mCancelledIconJobs.clear();
       
   124 
       
   125     if (mIconRequestId != NoIconRequest) {
       
   126         mThumbnailManager->cancelRequest(mIconRequestId);
       
   127         mIconRequestId = NoIconRequest;
       
   128     }
       
   129 
       
   130     delete mThumbnailManager;
       
   131     mThumbnailManager = NULL;
       
   132 
       
   133     qDeleteAll(mDataProviders.keys());
       
   134     mDataProviders.clear();
       
   135 
       
   136     mJobMutex.unlock();
       
   137 
       
   138     exit();
       
   139     wait();
       
   140 
       
   141     CNT_EXIT
       
   142 }
       
   143 
       
   144 /*!
       
   145     Starts the event loop for this thread.
       
   146  */
       
   147 void CntCacheThread::run()
       
   148 {
       
   149     CNT_ENTRY
       
   150 
       
   151     exec();
       
   152 
       
   153     CNT_EXIT
       
   154 }
       
   155 
       
   156 /*!
       
   157     Schedules a info to be fetched for a contact. When info has been fetched
       
   158     infoFieldUpdated() signals will be emitted, once for each field.
       
   159     
       
   160     /param contactId the contact for which the info is wanted
       
   161  */
       
   162 void CntCacheThread::scheduleInfoJob(int contactId, int priority)
       
   163 {
       
   164     CNT_ENTRY_ARGS( contactId )
       
   165 
       
   166     if (contactId <= 0)
       
   167         return;
       
   168 
       
   169     mJobMutex.lock();
       
   170 
       
   171     int index = infoJobIndex(contactId);
       
   172     if (index != NoSuchJob) {
       
   173         // if the job already exists, update the priority
       
   174         if (priority < mInfoJobs.at(index).second) {
       
   175             mInfoJobs[index] = QPair<int,int>(contactId,priority);
       
   176         }
       
   177         mJobMutex.unlock();
       
   178         return;
       
   179     }
       
   180 
       
   181     if (!mStarted) {
       
   182         // starting the event loop; minimum priority is used as this thread
       
   183         // should interfere as little as possible with more time-critical tasks,
       
   184         // like updating the UI during scrolling
       
   185         start(QThread::IdlePriority);
       
   186         mStarted = true;
       
   187     }
       
   188 
       
   189     if (!mProcessingJobs) {
       
   190         // new job => start processing jobs
       
   191         mProcessingJobs = true;
       
   192         HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent));
       
   193     }
       
   194     
       
   195     if (mInfoJobs.count() >= CntMaxInfoJobs) {
       
   196         // the queue of jobs is full, so remove the oldest job
       
   197         mCancelledInfoJobs.append(mInfoJobs.takeFirst().first);
       
   198         CNT_LOG_ARGS( mCancelledInfoJobs.last() << "removed from joblist" )
       
   199     }
       
   200 
       
   201     mInfoJobs.append(QPair<int,int>(contactId, priority));
       
   202     CNT_LOG_ARGS( contactId << "(prio:" << priority << ") appended @" << (mInfoJobs.count() - 1) );
       
   203 
       
   204     // since this job has now been scheduled, remove it from the list of
       
   205     // cancelled jobs in case it is there
       
   206     mCancelledInfoJobs.removeOne(contactId);
       
   207 
       
   208     mJobMutex.unlock();
       
   209 
       
   210     CNT_EXIT
       
   211 }
       
   212 
       
   213 /*!
       
   214     Schedules an icon to be fetched. An iconUpdated() signal will be emitted when the icon
       
   215     has been fetched.
       
   216     
       
   217     /param iconName the name of the icon to be fetched
       
   218  */
       
   219 void CntCacheThread::scheduleIconJob(const QString& iconName, int priority)
       
   220 {
       
   221     CNT_ENTRY_ARGS( iconName )
       
   222 
       
   223     if (iconName.isEmpty())
       
   224         return;
       
   225 
       
   226     mJobMutex.lock();
       
   227 
       
   228     int index = iconJobIndex(iconName);
       
   229     if (index != NoSuchJob) {
       
   230         // if the job already exists, update the priority
       
   231         if (priority < mIconJobs.at(index).second) {
       
   232             mIconJobs[index] = QPair<QString,int>(iconName,priority);
       
   233         }
       
   234         mJobMutex.unlock();
       
   235         return;
       
   236     }
       
   237 
       
   238     if (!mProcessingJobs) {
       
   239         // new job, so restart job loop
       
   240         mProcessingJobs = true;
       
   241         HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent));
       
   242     }
       
   243 
       
   244     if (mIconJobs.count() >= CntMaxIconJobs) {
       
   245         // the queue of jobs is full, so remove the oldest job
       
   246         mCancelledIconJobs.append(mIconJobs.takeLast().first);
       
   247         CNT_LOG_ARGS( mCancelledIconJobs.last() << "removed from joblist" );
       
   248     }
       
   249 
       
   250     mIconJobs.append(QPair<QString,int>(iconName, priority));
       
   251     CNT_LOG_ARGS( iconName << "(prio:" << priority << ") appended @" << (mIconJobs.count() - 1) );
       
   252 
       
   253     // since this job has now been rescheduled, remove it from the list of
       
   254     // cancelled jobs in case it is there
       
   255     mCancelledIconJobs.removeOne(iconName);
       
   256 
       
   257     mJobMutex.unlock();
       
   258 
       
   259     CNT_EXIT
       
   260 }
       
   261 
       
   262 /*!
       
   263     Postpones outstanding jobs for a few tenths of a second. This should be called if
       
   264     the client wants to reserve more CPU time for some urgent task.
       
   265  */
       
   266 void CntCacheThread::postponeJobs()
       
   267 {
       
   268     CNT_ENTRY
       
   269     
       
   270     mPostponeJobs = true;
       
   271     
       
   272     CNT_EXIT
       
   273 }
       
   274 
       
   275 /*!
       
   276     Handles a class-specific event that is sent by the scheduleOrUpdate functions
       
   277     when there are jobs.
       
   278  */
       
   279 bool CntCacheThread::event(QEvent* event)
       
   280 {
       
   281     if (event->type() == ProcessJobsEvent) {
       
   282         processJobs();
       
   283         return true;
       
   284     }
       
   285 
       
   286     return QThread::event(event);
       
   287 }
       
   288 
       
   289 /*!
       
   290     Processes all scheduled jobs. The loop runs until all jobs are done.
       
   291     It pauses for a while if new info jobs appear -- this means that the
       
   292     UI is updating and so the CPU is yielded to the UI. If there are
       
   293     again new jobs after the pause, then it pauses again, and so on.
       
   294  */
       
   295 void CntCacheThread::processJobs()
       
   296 {
       
   297     CNT_ENTRY
       
   298 
       
   299     forever {
       
   300         mJobMutex.lock();
       
   301         int infoJobs = mInfoJobs.count();
       
   302         int iconJobs = mIconJobs.count();
       
   303         int totalJobs = infoJobs + iconJobs + mCancelledInfoJobs.count() + mCancelledIconJobs.count();
       
   304 
       
   305         if (totalJobs == 0 || totalJobs == iconJobs && mIconRequestId != NoIconRequest || mPostponeJobs) {
       
   306             if (mPostponeJobs) {
       
   307                 // client has requested a pause in activies (e.g. due to high UI activity)
       
   308                 mPostponeJobs = false;
       
   309                 if (totalJobs > 0) {
       
   310                     QTimer::singleShot(PostponeJobsMilliSeconds, this, SLOT(processJobs()));
       
   311                 }
       
   312                 else {
       
   313                     mProcessingJobs = false;
       
   314                 }
       
   315             }
       
   316             else {
       
   317                 mProcessingJobs = false;
       
   318             }
       
   319 
       
   320             mJobMutex.unlock();
       
   321 
       
   322             if (totalJobs == 0) {
       
   323                 emit allJobsDone();
       
   324             }
       
   325 
       
   326             break;
       
   327         }
       
   328 
       
   329         bool doInfoJob = infoJobs > 0; // && (iconJobs == 0 || mIconRequestId != NoIconRequest || qrand() % (infoJobs + iconJobs) < infoJobs);
       
   330         
       
   331         if (doInfoJob) {
       
   332             // get next job
       
   333             int contactId = takeNextInfoJob();
       
   334             mJobMutex.unlock();
       
   335     
       
   336             // fetch qcontact
       
   337             QContactFetchHint restrictions;
       
   338             restrictions.setOptimizationHints(QContactFetchHint::NoRelationships);
       
   339 			QContact contact = mContactManager->contact(contactId, restrictions);
       
   340 
       
   341             // request contact info from providers
       
   342             QMapIterator<CntInfoProvider*, ContactInfoFields> i(mDataProviders);
       
   343             while (i.hasNext()) {
       
   344                 i.next();
       
   345                 if (i.value() != 0) {
       
   346                     i.key()->requestInfo(contact, i.value());
       
   347                 }
       
   348             }
       
   349         }
       
   350         else if (iconJobs > 0 && mIconRequestId == NoIconRequest) {
       
   351             // request icon from thumbnail manager
       
   352             QString iconName  = takeNextIconJob();
       
   353             mIconRequestId = mThumbnailManager->getThumbnail(iconName, NULL, 0);
       
   354             mIconRequestName = iconName;
       
   355             mJobMutex.unlock();
       
   356         }
       
   357         else {
       
   358             if (mCancelledInfoJobs.count() > 0) {
       
   359                 int contactId = mCancelledInfoJobs.takeLast();
       
   360                 mJobMutex.unlock();
       
   361                 emit infoCancelled(contactId);
       
   362             }
       
   363             else if (mCancelledIconJobs.count() > 0) {
       
   364                 QString iconName = mCancelledIconJobs.takeFirst();
       
   365                 mJobMutex.unlock();
       
   366                 emit iconCancelled(iconName);
       
   367             }
       
   368         }
       
   369     
       
   370         // allow signals to be passed from providers and from the client
       
   371         HbApplication::processEvents();
       
   372     }
       
   373 
       
   374     CNT_EXIT
       
   375 }
       
   376 
       
   377 /*!
       
   378     Passes an info field from a data provider up to the client via signals. The
       
   379     client is not in the same thread, so Qt passes the signal as an event.
       
   380  */
       
   381 void CntCacheThread::onInfoFieldReady(CntInfoProvider* sender, int contactId,
       
   382                                       ContactInfoField field, const QString& text)
       
   383 {
       
   384     CNT_ENTRY
       
   385 
       
   386     // there can be 3rd party providers, so we cannot blindly trust them;
       
   387     // info is emitted only if:
       
   388     // 1) the sender is in the list of providers
       
   389     // 2) exactly one field bit is set in parameter 'field'
       
   390     // 3) the field bit has been assigned to this provider
       
   391     if (mDataProviders.contains(sender)
       
   392         && ((field & (field - 1)) == 0)
       
   393         && ((field & mDataProviders.value(sender)) != 0)) {
       
   394         emit infoFieldUpdated(contactId, field, text);
       
   395     }
       
   396 
       
   397     CNT_EXIT
       
   398 }
       
   399 
       
   400 /*!
       
   401     Passes an icon from thumbnail manager up to the client via a signal. The
       
   402     client is not in the same thread, so Qt passes the signal as an event.
       
   403  */
       
   404 void CntCacheThread::onIconReady(const QPixmap& pixmap, void *data, int id, int error)
       
   405 {
       
   406     CNT_ENTRY
       
   407 
       
   408     Q_UNUSED(id);
       
   409     Q_UNUSED(data);
       
   410 
       
   411     mJobMutex.lock();
       
   412     Q_ASSERT(id == mIconRequestId && !mIconRequestName.isEmpty());
       
   413     if (!mProcessingJobs) {
       
   414         // job loop quit while waiting for this icon, so restart it
       
   415         mProcessingJobs = true;
       
   416         HbApplication::instance()->postEvent(this, new QEvent(ProcessJobsEvent));
       
   417     }
       
   418     mIconRequestId = NoIconRequest;
       
   419     mJobMutex.unlock();
       
   420 
       
   421     if (error == 0) {
       
   422         emit iconUpdated(mIconRequestName, HbIcon(pixmap));
       
   423     }
       
   424 
       
   425     CNT_EXIT
       
   426 }
       
   427 
       
   428 /*!
       
   429     Finds out the index of an info job in the job list. Job mutex must be held when calling this function.
       
   430 
       
   431     \return index of the contact in the job list, or NoSuchJob if no job is scheduled for the contact
       
   432  */
       
   433 int CntCacheThread::infoJobIndex(int contactId)
       
   434 {
       
   435     int jobCount = mInfoJobs.count();
       
   436     for (int i = 0; i < jobCount; ++i) {
       
   437         if (mInfoJobs.at(i).first == contactId) {
       
   438             return i;
       
   439         }
       
   440     }
       
   441     
       
   442     return NoSuchJob;
       
   443 }
       
   444 
       
   445 /*!
       
   446     Picks the next job from the info job list (the one with the highest priority).
       
   447 
       
   448     \return the id of the contact for which the info should be fetched
       
   449  */
       
   450 int CntCacheThread::takeNextInfoJob()
       
   451 {
       
   452     int selectionIndex = -1;
       
   453     int selectionPriority = -1;
       
   454 
       
   455     int jobCount = mInfoJobs.count();
       
   456     if (jobCount == 0) {
       
   457         return NoSuchJob;
       
   458     }
       
   459 
       
   460     for (int i = 0; i < jobCount; ++i) {
       
   461         int jobPriority = mInfoJobs.at(i).second;
       
   462         if (jobPriority < selectionPriority || selectionPriority == -1) {
       
   463             selectionIndex = i;
       
   464             selectionPriority = jobPriority;
       
   465         }
       
   466     }
       
   467     
       
   468     return mInfoJobs.takeAt(selectionIndex).first;
       
   469 }
       
   470 
       
   471 /*!
       
   472     Picks the next job from the icon job list (the one with the highest priority).
       
   473 
       
   474     \return the name of the icon that should be fetched
       
   475  */
       
   476 QString CntCacheThread::takeNextIconJob()
       
   477 {
       
   478     int selectionIndex = -1;
       
   479     int selectionPriority = -1;
       
   480 
       
   481     int jobCount = mIconJobs.count();
       
   482     if (jobCount == 0) {
       
   483         return QString();
       
   484     }
       
   485 
       
   486     for (int i = 0; i < jobCount; ++i) {
       
   487         int jobPriority = mIconJobs.at(i).second;
       
   488         if (jobPriority < selectionPriority || selectionPriority == -1) {
       
   489             selectionIndex = i;
       
   490             selectionPriority = jobPriority;
       
   491         }
       
   492     }
       
   493     
       
   494     return mIconJobs.takeAt(selectionIndex).first;
       
   495 }
       
   496 
       
   497 /*!
       
   498     Finds out the index of an icon job in the job list. Job mutex must be held when calling this function.
       
   499 
       
   500     \return index of the icon in the job list, or NoSuchJob if a job for the icon is not scheduled.
       
   501  */
       
   502 int CntCacheThread::iconJobIndex(QString iconName)
       
   503 {
       
   504     int jobCount = mIconJobs.count();
       
   505     for (int i = 0; i < jobCount; ++i) {
       
   506         if (mIconJobs.at(i).first == iconName) {
       
   507             return i;
       
   508         }
       
   509     }
       
   510     
       
   511     return NoSuchJob;
       
   512 }