--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/phonebookui/cntlistmodel/cntnamefetcher.cpp Fri Oct 15 12:24:46 2010 +0300
@@ -0,0 +1,563 @@
+/*
+* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: Private data and helper classes used by class CntCache.
+*
+*/
+
+#include <QEvent>
+#include <QFile>
+#include <QDir>
+#include <hbapplication.h>
+#include <hbstringutil.h>
+#include <xqutils.h>
+#include <xqsettingsmanager.h>
+#include <xqsettingskey.h>
+#include <e32base.h>
+#include <s32mem.h>
+#include <e32std.h>
+#include <cntdb.h>
+#include <cntuids.h>
+#include <cntcacheitems.h>
+#include <cntnamefetcher.h>
+#include <cntdebug.h>
+
+/*!
+ \class CntAllNamesJob
+ \brief Holds info about one job that fetches all names from the database.
+
+ \class CntSrvConnection
+ \brief Issues requests to CntSrv.
+
+ \class CntNameFetcher
+ \brief CntNameFetcher asynchronously fetches all contact names.
+
+ CntNameFetcher can only do one asynchronous job: fetch all contact names.
+ In addition, the class provides a number of synchronous functions:
+ - fetch one name
+ - read/write all names to/from a cache file
+ - compare two names; sort a list of names
+
+ Fetching from file cache is about 10 times faster than fetching from the
+ database, but the file cache may be out-of-date. The intended use is to
+ first fetch from the file cache and later synchronize with the database.
+ */
+
+// constants used when fetching names from CntSrv
+#define KCntSearchResultList 99
+#define KCntOpenDataBase 100
+_LIT(KCntServerExe, "CNTSRV.EXE");
+_LIT(KCntServerName, "CNTSRV");
+const TInt KAsyncMessageSlots = 6;
+const TInt KCntServerMajorVersionNumber=1;
+const TInt KCntServerMinorVersionNumber=1;
+const TInt KCntServerBuildVersionNumber=1;
+static const QEvent::Type CntAsynchOperation = QEvent::User;
+
+// constants used for file cache
+const QString cacheFolder = "20022EF9";
+const QString cacheFilename = "contactcache.dat";
+
+// maximum number of scheduled jobs for name fetcher
+const int MaxNameJobs = 1;
+
+/*!
+ Internal class used by CntSrvConnection to issues requests to CntSrv.
+ */
+class CntSrvSession : public RSessionBase
+{
+public:
+ CntSrvSession() { mConnected = false; }
+ ~CntSrvSession() { RHandleBase::Close(); }
+ void executeSqlQueryL(const TDesC &sqlQuery, QList<CntNameCacheItem *> &names, CntNameOrder nameFormat, int sizeHintKB);
+
+private:
+ void connectCntSrvL();
+
+private:
+ bool mConnected;
+};
+
+CntSrvConnection::CntSrvConnection()
+ : mSession(NULL),
+ mIsAsynchronous(false)
+{
+}
+
+CntSrvConnection::~CntSrvConnection()
+{
+ disconnect();
+
+ if (mThread.isRunning()) {
+ mThread.quit();
+ mThread.wait();
+ }
+
+ delete mSession;
+
+ mNames.clear();
+}
+
+void CntSrvConnection::setAsynchronous()
+{
+ mIsAsynchronous = true;
+ mThread.start();
+ moveToThread(&mThread);
+}
+
+bool CntSrvConnection::executeSqlQuery(const QString &sqlQuery, CntNameOrder nameFormat, int sizeHintKB)
+{
+ CNT_ENTRY
+
+ if (!mSession) {
+ mSession = new CntSrvSession();
+ }
+
+ if (mIsAsynchronous) {
+ mSqlQuery = sqlQuery;
+ mNameFormat = nameFormat;
+ mSizeHintKB = sizeHintKB;
+ HbApplication::instance()->postEvent(this, new QEvent(CntAsynchOperation));
+ } else {
+ mNames.clear();
+ TPtrC queryPtr(sqlQuery.utf16(), sqlQuery.length());
+ TRAPD(err, mSession->executeSqlQueryL(queryPtr, mNames, nameFormat, sizeHintKB));
+ if (err != KErrNone) {
+ qDeleteAll(mNames);
+ mNames.clear();
+ CNT_EXIT
+ return false;
+ }
+ }
+
+ CNT_EXIT
+
+ return true;
+}
+
+bool CntSrvConnection::event(QEvent *event)
+{
+ if (event->type() == CntAsynchOperation) {
+ CNT_ENTRY
+
+ mNames.clear();
+ TPtrC ptr(mSqlQuery.utf16(), mSqlQuery.length());
+ TRAPD(err, mSession->executeSqlQueryL(ptr, mNames, mNameFormat, mSizeHintKB));
+ if (err != KErrNone) {
+ qDeleteAll(mNames);
+ mNames.clear();
+ }
+ emit namesRead();
+ qStableSort(mNames.begin(), mNames.end(), CntNameFetcher::compareNames);
+ delete mSession;
+ mSession = NULL;
+ emit namesSorted();
+
+ CNT_EXIT
+
+ return true;
+ }
+
+ return QObject::event(event);
+}
+
+/*!
+ Executes a special SQL query: the first column must be the contact id and
+ the subsequent columns must be varchar fields.
+
+ \param sqlQuery the SQL to execute
+ \param names the list where the results will be stored
+ \param nameFormat the format the names should be stored in
+ \param sizeHintKB the expected size of the buffer needed to fit the results; a too
+ small value will effectively double the fetch time, since the
+ buffer is then resized and the data refetched a second time
+ */
+void CntSrvSession::executeSqlQueryL(const TDesC& sqlQuery, QList<CntNameCacheItem*> &names, CntNameOrder nameFormat, int sizeHintKB)
+{
+ int listSize = 0;
+
+ // read the ids and names from the database
+ if (!mConnected) {
+ connectCntSrvL();
+ }
+
+ // allocate tmeporary buffer
+ TInt bufferSize = sizeHintKB * 1024;
+ CBufFlat* buffer = CBufFlat::NewL(256);
+ CleanupStack::PushL(buffer);
+
+ // try to fetch the results, if the fetch fails with
+ // a positive value, it means the buffer was too small
+ // in this case the buffer is resized and the results
+ // are fetched again
+ for (TInt tries = 0; tries < 2 && bufferSize > 0; ++tries) {
+ buffer->ResizeL(bufferSize);
+ TPtr8 bufferPtr = buffer->Ptr(0);
+ TIpcArgs args;
+ args.Set(0, &bufferPtr);
+ args.Set(1, &sqlQuery);
+ bufferSize = SendReceive(KCntSearchResultList, args);
+ CNT_LOG_ARGS("buffer size =" << bufferSize)
+ User::LeaveIfError(bufferSize);
+ }
+
+ // store the formatted names into the list
+ RBufReadStream readStream;
+ TInt id;
+ TBuf<256> firstName;
+ TBuf<256> lastName;
+
+ readStream.Open(*buffer);
+ for (int i = 0; (id = readStream.ReadInt32L()) != 0; ++i) {
+ readStream >> firstName;
+ readStream >> lastName;
+ CntNameCacheItem* item = new (ELeave) CntNameCacheItem(
+ id,
+ QString::fromUtf16(firstName.Ptr(), firstName.Length()),
+ QString::fromUtf16(lastName.Ptr(), lastName.Length()),
+ nameFormat);
+ if (i >= listSize - 1) {
+ // if the list is runnning out of space, resize it;
+ // initial size is 1000 and after that it doubles
+ // every time it runs out of space
+ if (listSize == 0) {
+ listSize = 1000;
+ } else {
+ listSize *= 2;
+ }
+ QT_TRY {
+ names.reserve(listSize);
+ } QT_CATCH (...) {
+ // clean up and return
+ CleanupStack::PopAndDestroy(buffer);
+ qDeleteAll(names);
+ names.clear();
+ return;
+ }
+ }
+ names.append(item);
+ }
+
+ CleanupStack::PopAndDestroy(buffer);
+}
+
+/*!
+ Connect to / create a contacts server session.
+ */
+void CntSrvSession::connectCntSrvL()
+{
+ // Assume the server is already running and attempt to create a session
+ // with a maximum of KAsyncMessageSlots message slots.
+ TInt err = CreateSession(KCntServerName,
+ TVersion(KCntServerMajorVersionNumber, KCntServerMinorVersionNumber, KCntServerBuildVersionNumber),
+ KAsyncMessageSlots);
+
+ // Server is not running
+ if (err == KErrNotFound) {
+ // Use the RProcess API to start the server.
+ RProcess server;
+ User::LeaveIfError(server.Create(KCntServerExe, KNullDesC));
+
+ // Enforce server to be at system default priority EPriorityForeground
+ server.SetPriority(EPriorityForeground);
+
+ // Synchronize with the server.
+ TRequestStatus reqStatus;
+ server.Rendezvous(reqStatus);
+ server.Resume();
+
+ // Server will call the reciprocal static synchronization call.
+ User::WaitForRequest(reqStatus);
+ server.Close();
+ User::LeaveIfError(reqStatus.Int());
+
+ // Create the server session.
+ User::LeaveIfError(CreateSession(KCntServerName,
+ TVersion(KCntServerMajorVersionNumber, KCntServerMinorVersionNumber, KCntServerBuildVersionNumber),
+ KAsyncMessageSlots));
+ } else {
+ User::LeaveIfError(err);
+ }
+
+ TIpcArgs args;
+ args.Set(0, &KNullDesC);
+ User::LeaveIfError(SendReceive(KCntOpenDataBase, args));
+
+ mConnected = true;
+}
+
+/*!
+ Creates a CntNameFetcher object.
+ */
+CntNameFetcher::CntNameFetcher()
+ : CntAbstractFetcher(MaxNameJobs),
+ mDbConnection(NULL),
+ mAsynchDbConnection(NULL),
+ mSettingsManager(NULL),
+ mNameFormatSetting(NULL),
+ mBufferSizeEstimate(0)
+{
+ CNT_ENTRY
+
+ // get name format setting and listen to changes
+ mSettingsManager = new XQSettingsManager();
+ mNameFormatSetting = new XQSettingsKey(XQSettingsKey::TargetCentralRepository, KCRCntSettings.iUid, KCntNameOrdering);
+ mNameFormat = static_cast<CntNameOrder>(mSettingsManager->readItemValue(*mNameFormatSetting, XQSettingsManager::TypeInt).toInt());
+ mSettingsManager->startMonitoring(*mNameFormatSetting, XQSettingsManager::TypeInt);
+ connect(mSettingsManager, SIGNAL(valueChanged(const XQSettingsKey&, const QVariant&)), this, SLOT(setNameFormat(const XQSettingsKey&, const QVariant&)));
+
+ // connect to contacts server
+ mDbConnection = new CntSrvConnection();
+
+ CNT_EXIT
+}
+
+/*!
+ Destroys a CntNameFetcher object.
+ */
+CntNameFetcher::~CntNameFetcher()
+{
+ CNT_ENTRY
+
+ delete mSettingsManager;
+ delete mNameFormatSetting;
+ delete mDbConnection;
+ delete mAsynchDbConnection;
+
+ CNT_EXIT
+}
+
+/*!
+ Reads names from the file cache.
+
+ \return true if the names were read successfully from the cache file
+
+ */
+bool CntNameFetcher::readNamesFromCache(QList<CntNameCacheItem*> &names)
+{
+ CNT_ENTRY
+
+ bool success = true;
+ quint32 itemCount;
+ quint32 nameFormat;
+
+ QFile cacheFile(XQUtils::phoneMemoryRootPath() + cacheFolder + "\\" + cacheFilename);
+ if (!cacheFile.open(QIODevice::ReadOnly)) {
+ return false;
+ }
+
+ QDataStream in(&cacheFile);
+
+ mBufferSizeEstimate = 0;
+ QT_TRY {
+ // read header: nr of items, name format
+ in >> itemCount;
+ in >> nameFormat;
+ names.reserve(itemCount);
+
+ // populate list with names
+ while (itemCount-- > 0) {
+ CntNameCacheItem *item = CntNameCacheItem::internalize(in, (CntNameOrder) nameFormat);
+ names.append(item);
+ mBufferSizeEstimate += 4 + 2 * item->name().length();
+ }
+ } QT_CATCH (...) {
+ qDeleteAll(names);
+ names.clear();
+ success = false;
+ }
+
+ cacheFile.close();
+
+ CNT_EXIT
+
+ return success;
+}
+
+/*!
+ Write names to the file cache.
+ */
+bool CntNameFetcher::writeNamesToCache(const QList<CntNameCacheItem*> &names) const
+{
+ CNT_ENTRY
+
+ bool success = true;
+
+ // create folder for cache file if it does not already exist
+ QString path = XQUtils::phoneMemoryRootPath() + cacheFolder;
+ if (!QDir(path).exists()) {
+ QDir dir(XQUtils::phoneMemoryRootPath());
+ if (!dir.mkdir(cacheFolder)) {
+ CNT_EXIT_ARGS("failed to create folder: " << path)
+ return false;
+ }
+
+ // have to use native Symbian code to make the dir hidden
+ RFs fs;
+ fs.Connect();
+ TPtrC pathPtr(path.utf16(), path.length());
+ if (fs.SetAtt(pathPtr, KEntryAttHidden, 0) != KErrNone) {
+ fs.Close();
+ return false;
+ }
+ fs.Close();
+ }
+
+ // open cache file for writing
+ QFile cacheFile(XQUtils::phoneMemoryRootPath() + cacheFolder + "\\" + cacheFilename);
+ if (!cacheFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ CNT_EXIT_ARGS("failed to create file")
+ return false;
+ }
+ QDataStream out(&cacheFile);
+
+ // write the names to the cache file
+ QT_TRY {
+ // write header
+ out << names.size();
+ out << (quint32) mNameFormat;
+
+ // write list with names
+ foreach (CntNameCacheItem* name, names) {
+ name->externalize(out);
+ }
+ } QT_CATCH (...) {
+ success = false;
+ }
+
+ cacheFile.close();
+
+ CNT_EXIT
+
+ return success;
+}
+
+/*!
+ Reads the name of one contact from the contact database synchronously.
+
+ \param contactId the id of the contact
+ */
+CntNameCacheItem* CntNameFetcher::fetchOneName(QContactLocalId contactId) const
+{
+ CNT_ENTRY
+
+ QString sqlQuery = QString("SELECT contact_id, first_name, last_name FROM contact WHERE (type_flags>>24)<=1 AND contact_id=%1").arg(contactId);
+ mDbConnection->executeSqlQuery(sqlQuery, mNameFormat, 2);
+
+ if (mDbConnection->names().size() == 0) {
+ return NULL;
+ }
+
+ CNT_EXIT
+
+ return mDbConnection->names().at(0);
+}
+
+/*!
+ Reads the names of all contacts from the contact database asynchronously.
+ */
+void CntNameFetcher::processNextJob()
+{
+ CNT_ENTRY
+
+ if (isProcessingJob() || !hasScheduledJobs()) {
+ return;
+ }
+
+ // there is only one type of job, so we can delete it without inspection
+ delete takeNextJob();
+
+ if (mBufferSizeEstimate == 0) {
+ mBufferSizeEstimate = 240 * 1024;
+ }
+
+ CNT_LOG_ARGS("buffer size =" << mBufferSizeEstimate)
+
+ mAsynchDbConnection = new CntSrvConnection();
+ mAsynchDbConnection->setAsynchronous();
+ connect(mAsynchDbConnection, SIGNAL(namesRead()), this, SIGNAL(databaseAccessComplete()));
+ connect(mAsynchDbConnection, SIGNAL(namesSorted()), this, SLOT(sendCompletionSignal()));
+ mAsynchDbConnection->executeSqlQuery("SELECT contact_id, first_name, last_name FROM contact WHERE (type_flags>>24)<=1", mNameFormat, 16 + mBufferSizeEstimate / 1024);
+
+ CNT_EXIT
+}
+
+bool CntNameFetcher::isProcessingJob()
+{
+ return (mAsynchDbConnection != NULL);
+}
+
+/*!
+ Sorts the names quickly and in a locale aware manner.
+ */
+void CntNameFetcher::sortNames(QList<CntNameCacheItem *> &names) const
+{
+ CNT_ENTRY
+
+ qStableSort(names.begin(), names.end(), CntNameFetcher::compareNames);
+
+ CNT_EXIT
+}
+
+/*!
+ Compares a pair of contact names and returns true if the first
+ one should be presented before the second one in a list. This
+ static function is used e.g. when sorting lists of names.
+ */
+bool CntNameFetcher::compareNames(const CntNameCacheItem* a, const CntNameCacheItem* b)
+{
+ QString aName = a->name();
+ QString bName = b->name();
+
+ if (aName.isEmpty()) {
+ return false;
+ } else if (bName.isEmpty()) {
+ return true;
+ }
+
+ return (HbStringUtil::compareC(aName, bName) < 0);
+}
+
+/*!
+ Notifies clients that the name format has changed. This function is called by the framework
+ if the name format settings is changed, see the constructor.
+ */
+void CntNameFetcher::setNameFormat(const XQSettingsKey &/*key*/, const QVariant &value)
+{
+ CNT_ENTRY
+
+ bool ok = false;
+ CntNameOrder newNameFormat = static_cast<CntNameOrder>(value.toInt(&ok));
+ if (ok && newNameFormat != mNameFormat) {
+ mNameFormat = newNameFormat;
+ emit nameFormatChanged(mNameFormat);
+ }
+
+ CNT_EXIT
+}
+
+/*!
+ Emits the results of a completed asynch database operation.
+ */
+void CntNameFetcher::sendCompletionSignal()
+{
+ CNT_ENTRY
+
+ emit namesAvailable(mAsynchDbConnection->names());
+
+ delete mAsynchDbConnection;
+ mAsynchDbConnection = NULL;
+
+ CNT_EXIT
+}
+