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