diff -r c18f9fa7f42e -r 640d30f4fb64 phonebookui/cntlistmodel/cntnamefetcher.cpp --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + \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 &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 &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(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 &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 &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 &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(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 +} +