--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/phonebookengines/cntlistmodel/src/cntnamefetcher.cpp Tue Sep 21 17:07:25 2010 +0300
@@ -0,0 +1,643 @@
+/*
+* 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 <e32base.h>
+#include <s32mem.h>
+#include <e32std.h>
+
+#include <xqutils.h>
+#include <QEvent>
+#include <QFile>
+#include <QDir>
+#include <hbapplication.h>
+#include <hbstringutil.h>
+
+#include <cntdb.h>
+#include <cntuids.h>
+#include <cntdebug.h>
+
+#include "cntnamefetcher.h"
+
+// 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
+static const QString cacheFolder = "20022EF9";
+static const QString cacheFilename = "contactcache.dat";
+
+/*!
+ 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()
+ : 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::readOneName(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::readAllNamesAsynch()
+{
+ CNT_ENTRY
+
+ if (mAsynchDbConnection != NULL) {
+ // an asynch fetch is already in progress, so no need to start a new one
+ return;
+ }
+
+ 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
+}
+
+/*!
+ 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
+}
+
+/*!
+ Creates a CntNameCacheItem object.
+ */
+CntNameCacheItem::CntNameCacheItem(QContactLocalId id, const QString& firstName, const QString& lastName, CntNameOrder nameFormat)
+{
+ mContactId = id;
+ setFormattedName(firstName, lastName, nameFormat);
+}
+
+/*!
+ Destroys a CntNameCacheItem object.
+ */
+CntNameCacheItem::~CntNameCacheItem()
+{
+}
+
+/*!
+ Changes the format used to present the name.
+ */
+void CntNameCacheItem::setNameFormat(CntNameOrder newFormat)
+{
+ QString firstName = mName.mid(mFirstNamePosition&0xffff, mFirstNamePosition>>16);
+ QString lastName = mName.mid(mLastNamePosition&0xffff, mLastNamePosition>>16);
+ setFormattedName(firstName, lastName, newFormat);
+}
+
+/*!
+ Copies the contents of the other cache item to this one.
+ */
+void CntNameCacheItem::operator=(const CntNameCacheItem &other)
+{
+ mContactId = other.mContactId;
+ mFirstNamePosition = other.mFirstNamePosition;
+ mLastNamePosition = other.mLastNamePosition;
+ mName = other.mName;
+}
+
+/*!
+ Externalizes a CntNameCacheItem object.
+ */
+void CntNameCacheItem::externalize(QDataStream &stream)
+{
+ stream << mContactId;
+ stream << mFirstNamePosition;
+ stream << mLastNamePosition;
+ stream << mName;
+}
+
+/*!
+ Internalizes a CntNameCacheItem object.
+ */
+CntNameCacheItem* CntNameCacheItem::internalize(QDataStream &stream, CntNameOrder nameFormat)
+{
+ quint32 id;
+ quint32 firstNamePosition;
+ quint32 lastNamePosition;
+ QString name;
+
+ stream >> id;
+ stream >> firstNamePosition;
+ stream >> lastNamePosition;
+ stream >> name;
+
+ QString firstName = name.mid(firstNamePosition&0xffff, firstNamePosition>>16);
+ QString lastName = name.mid(lastNamePosition&0xffff, lastNamePosition>>16);
+
+ return new CntNameCacheItem(id, firstName, lastName, nameFormat);
+}
+
+/*!
+ Sets the formatted name and positions of the first name and last name,
+ according to the name format in the parameter.
+ */
+void CntNameCacheItem::setFormattedName(const QString& firstName, const QString& lastName, CntNameOrder nameFormat)
+{
+ int firstNameLength = firstName.length();
+ int lastNameLength = lastName.length();
+
+ if (lastNameLength == 0) {
+ mName = firstName;
+ mFirstNamePosition = firstNameLength << 16;
+ mLastNamePosition = 0;
+ } else if (firstNameLength == 0) {
+ mName = lastName;
+ mFirstNamePosition = 0;
+ mLastNamePosition = lastNameLength << 16;
+ } else {
+ if (nameFormat == CntOrderLastFirst) {
+ mName = lastName + " " + firstName;
+ mFirstNamePosition = (firstNameLength << 16) | (lastNameLength + 1);
+ mLastNamePosition = (lastNameLength << 16);
+ } else if (nameFormat == CntOrderLastCommaFirst) {
+ mName = lastName + ", " + firstName;
+ mFirstNamePosition = (firstNameLength << 16) | (lastNameLength + 2);
+ mLastNamePosition = (lastNameLength << 16);
+ } else {
+ mName = firstName + " " + lastName;
+ mFirstNamePosition = (firstNameLength << 16);
+ mLastNamePosition = (lastNameLength << 16) | (firstNameLength + 1);
+ }
+ }
+}
+
+QString CntNameCacheItem::firstName() const
+{
+ return mName.mid(mFirstNamePosition&0xffff, mFirstNamePosition>>16);
+}
+
+QString CntNameCacheItem::lastName() const
+{
+ return mName.mid(mLastNamePosition&0xffff, mLastNamePosition>>16);
+}