qtcontactsmobility/plugins/contacts/symbian/src/filtering/cntfilterdetail.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Thu, 27 May 2010 12:45:19 +0300
changeset 37 fd64c38c277d
parent 27 de1630741fbe
child 40 b46a585f6909
permissions -rw-r--r--
Revision: 201019 Kit: 2010121

/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <centralrepository.h>

#include "cntfilterdetail.h"
#include "cntfilterdetaildisplaylabel.h" //todo rename class to follow naming pattern CntFilterDetailDisplayLabel
#include "cntsqlsearch.h"

// Telephony Configuration API
// Keys under this category are used in defining telephony configuration.
const TUid KCRUidTelConfiguration = {0x102828B8};
// Amount of digits to be used in contact matching.
// This allows a customer to variate the amount of digits to be matched.
const TUint32 KTelMatchDigits                               = 0x00000001;
// Default match length
const TInt KDefaultMatchLength(7);


CntFilterDetail::CntFilterDetail(CContactDatabase& contactDatabase,CntSymbianSrvConnection &cntServer,CntDbInfo& dbInfo) 
                                        : m_contactdatabase(contactDatabase),
                                          m_srvConnection(cntServer),
                                          m_dbInfo(dbInfo)
{
}

CntFilterDetail::~CntFilterDetail()
{
}


QList<QContactLocalId> CntFilterDetail::contacts(
        const QContactFilter &filter,
        const QList<QContactSortOrder> &sortOrders,
        bool &filterSupportedflag,
        QContactManager::Error* error)
{
    Q_UNUSED(filterSupportedflag);
    //Check if any invalid filter is passed 
    if (!filterSupported(filter) ) {
        *error =  QContactManager::NotSupportedError;
        return QList<QContactLocalId>();
    }
    QList<QContactLocalId> idList;
    QContactDetailFilter detailFilter(filter);
    QString sqlQuery;
    //Check for phonenumber. Special handling needed
    if ( (detailFilter.detailDefinitionName() == QContactPhoneNumber::DefinitionName ) &&
            (detailFilter.detailFieldName() != QContactPhoneNumber::FieldSubTypes)) {
        //Handle phonenumber ...
        createMatchPhoneNumberQuery(filter,sqlQuery,error);
        if (*error == QContactManager::NoError) {
            //fetch the contacts
            idList =  m_srvConnection.searchContacts(sqlQuery, error);
        }
        
    }
    else if (detailFilter.matchFlags() == QContactFilter::MatchKeypadCollation) {
        //predictive search filter
        idList = HandlePredictiveSearchFilter(filter,error);
    }
            
    // handle other cases
    else {
        createSelectQuery(filter,sqlQuery,error);
        QString sortQuery = m_dbInfo.getSortQuery(sortOrders, sqlQuery, error);
        
        if (*error == QContactManager::NoError) {
            //fetch the contacts
            idList =  m_srvConnection.searchContacts(sortQuery, error);
        }
    }
    return idList;
}

bool CntFilterDetail::filterSupported(const QContactFilter& filter) 
{
    bool result = false;
    if (QContactFilter::ContactDetailFilter == filter.type()) {
        result = true;
    }
    return result;
}

void CntFilterDetail::createSelectQuery(const QContactFilter& filter,
                                        QString& sqlQuery,
                                        QContactManager::Error* error)

{
    if (!filterSupported(filter)) {
      *error = QContactManager::NotSupportedError;
      return;
    }
    QContactDetailFilter detailFilter(filter);
    //display label
    if (detailFilter.detailDefinitionName() == QContactDisplayLabel::DefinitionName) {
      CntFilterDetailDisplayLabel displayLabelFilter;
      displayLabelFilter.createSelectQuery(filter, sqlQuery, error);
    }
    //type
    else if (detailFilter.detailDefinitionName() == QContactType::DefinitionName) {
       if (detailFilter.value().toString() == QContactType::TypeContact)
           sqlQuery = "SELECT contact_id FROM contact WHERE (type_flags>>24)=0";
       else if (detailFilter.value().toString() == QContactType::TypeGroup)
           sqlQuery = "SELECT contact_id FROM contact WHERE (type_flags>>24)=3";
    }
    else if (detailFilter.detailDefinitionName() == QContactGuid::DefinitionName) {
       if (detailFilter.detailFieldName() == QContactGuid::FieldGuid) {
           QStringList fullGuidValue = detailFilter.value().toString().split('-');
           if (fullGuidValue.count() == 3) {
               QString localGuidValue = fullGuidValue.at(1);
               sqlQuery = "SELECT contact_id FROM contact WHERE guid_string = '" + localGuidValue + '\'';
           }
       }
    }
    //everything else
    else {   
       QString tableName;
       QString sqlWhereClause;
       getTableNameWhereClause(detailFilter,tableName,sqlWhereClause,error);
       //Create the sql query
       sqlQuery += "SELECT DISTINCT contact_id FROM " + tableName + " WHERE " + sqlWhereClause;
    }
}

/*!
 * Updates match flags for columns.
 */
void CntFilterDetail::updateForMatchFlag(const QContactDetailFilter& filter,
                                         QString& fieldToUpdate ,
                                         QContactManager::Error* error) const
{
    // Modify the filed depending on the query
    switch (filter.matchFlags()) {
        case QContactFilter::MatchExactly: {
            // Pattern for MatchExactly:
            // " ='xyz'"
            fieldToUpdate = " ='"
                           + filter.value().toString() + '\'';
            *error = QContactManager::NoError;
            break;
        }
        case QContactFilter::MatchContains: {
            // Pattern for MatchContains:
            // " LIKE '%xyz%'"
            fieldToUpdate = " LIKE '%" + filter.value().toString() + "%'" ;
            *error = QContactManager::NoError;
            break;
        }
        case QContactFilter::MatchStartsWith: {
            // Pattern for MatchStartsWith:
            // " LIKE 'xyz%'"
            fieldToUpdate = " LIKE '" +  filter.value().toString() + "%'"  ;
            *error = QContactManager::NoError;
            break;
        }
        case QContactFilter::MatchEndsWith: {
            // Pattern for MatchEndsWith:
            // " LIKE '%xyz'"
            fieldToUpdate = " LIKE '%" + filter.value().toString() + '\'' ;
            *error = QContactManager::NoError;
            break;
        }
        case QContactFilter::MatchFixedString: {
            *error = QContactManager::NotSupportedError;
            break;
        }
        case QContactFilter::MatchCaseSensitive: {
            *error = QContactManager::NotSupportedError;
            break;
        }
        default: {
            *error = QContactManager::NotSupportedError;
            break;
        }
    }
}

void CntFilterDetail::getTableNameWhereClause(const QContactDetailFilter& detailfilter,
                                              QString& tableName,
                                              QString& sqlWhereClause ,
                                              QContactManager::Error* error) const
{
    //Get the table name and the column name
    QString columnName;
    bool isSubType;

    m_dbInfo.getDbTableAndColumnName(detailfilter.detailDefinitionName(), detailfilter.detailFieldName(), tableName, columnName, isSubType);

    // return if tableName is empty
    if (tableName.isEmpty()) {
        *error = QContactManager::NotSupportedError;
        return;
    }

    //check columnName
    if (columnName.isEmpty()) {
        *error = QContactManager::NotSupportedError;
        return;
    }
    else if (isSubType) {
        sqlWhereClause += columnName;
        sqlWhereClause += " NOT NULL ";
    }
    else {
        sqlWhereClause += ' ' + columnName + ' ';
        QString fieldToUpdate;
        //Update the value depending on the match flag
        updateForMatchFlag(detailfilter,fieldToUpdate,error);
        sqlWhereClause +=  fieldToUpdate;
    }
}

QList<QContactLocalId>  CntFilterDetail::HandlePredictiveSearchFilter(const QContactFilter& filter,
                                                                      QContactManager::Error* error)
{
    QString sqlQuery;
    
    if (filter.type() == QContactFilter::ContactDetailFilter) {
       const QContactDetailFilter detailFilter(filter);
       if (detailFilter.matchFlags() == QContactFilter::MatchKeypadCollation) {
           CntSqlSearch sqlSearch;
           //convert string to numeric format
            QString pattern = detailFilter.value().toString();
            sqlQuery = sqlSearch.CreatePredictiveSearch(pattern);
            return  m_srvConnection.searchContacts(sqlQuery, error);  
       }
       else {
           return QList<QContactLocalId>();
       }
    }
    else {
        return QList<QContactLocalId>();
    }
}

/*
 * Creates an sql query to fetch contact item IDs for all the contact items
 * which may contain the specified telephone number in a telephone, fax
 * or SMS type field.
 *
 * The comparison method used is not exact.  The number is compared starting from
 * the right side of the number and the method returns an array of candidate
 * matches.  Punctuation (e.g. spaces) and other alphabetic characters are ignored
 * when comparing.
 */
void CntFilterDetail::createMatchPhoneNumberQuery(
                                      const QContactFilter& filter,
                                      QString& sqlQuery,
                                      QContactManager::Error* error)

{
  if (!filterSupported(filter) ) {
      *error = QContactManager::NotSupportedError;
      return;
  }
  
  QContactDetailFilter detailFilter(filter);
  QString number((detailFilter.value()).toString());
  TPtrC numberPtr(reinterpret_cast<const TUint16*>(number.utf16()));

  TInt matchLengthFromRight(KDefaultMatchLength);
  // no need to propagate error, we can use the default match length
  TRAP_IGNORE(getMatchLengthL(matchLengthFromRight));
  
  TInt numLowerDigits = matchLengthFromRight;
  TInt numUpperDigits = 0;

  if (numLowerDigits > KLowerSevenDigits) {
      // New style matching.
      numLowerDigits = KLowerSevenDigits;
      numUpperDigits = matchLengthFromRight - KLowerSevenDigits;
  }

  TMatch phoneDigits = createPaddedPhoneDigits(
                          numberPtr, numLowerDigits, numUpperDigits, error);

  if (*error == QContactManager::NoError) {
      // select fields for contacts that match phone lookup
      //  SELECT contact_id FROM comm_addr
      //      WHERE value = [value string] AND type = [type value];
      //
      QString type =  QString(" type = %1").arg(CntDbInfo::EPhoneNumber);
      QString value =  QString(" value = %1").arg(phoneDigits.iLowerSevenDigits);
      QString extraValue =  QString(" extra_value = %1").arg(phoneDigits.iUpperDigits);
      QString whereClause = " WHERE" + value + " AND" + type;
      if (matchLengthFromRight <= KLowerSevenDigits) {
          // Matching 7 or less digits...
          sqlQuery = "SELECT contact_id FROM comm_addr" + whereClause;
      }
      else {
          // Checking the upper digits...
          whereClause += " AND" + extraValue;
          sqlQuery = "SELECT contact_id FROM comm_addr" + whereClause;
      }
  }
}
/*
 * Get the match length setting. Digits to be used in matching (counted from
 * right).
 */
bool CntFilterDetail::getMatchLengthL(TInt& matchLength)
{
    //Get number of digits used to match
    bool result = false;
    CRepository* repository = CRepository::NewL(KCRUidTelConfiguration);
    TInt err = repository->Get(KTelMatchDigits, matchLength);
    delete repository;
    
    result = (err == KErrNone);
    return result;
}

/*
 * Convert the supplied string to a matchable phone number.
 *
 * \param text Descriptor containing phone number.
 * \param lowerMatchlength Number of least significant phone digits to use.
 * \param upperMatchLength Number of most significant phone digits to use.
 * \param error Qt error code.
 * \return The hash code(s) to use when matching the supplied phone number.
 */
CntFilterDetail::TMatch CntFilterDetail::createPaddedPhoneDigits(
                                            const TDesC& number, 
                                            const TInt numLowerDigits, 
                                            const TInt numUpperDigits,
                                            QContactManager::Error* error)
{
    TMatch phoneNumber = createPhoneMatchNumber(
                                            number, numLowerDigits, numUpperDigits, error);
    if (*error == QContactManager::NoError) {
        if (phoneNumber.iNumLowerDigits + phoneNumber.iUpperDigits == 0) {
            // No digits, do nothing
        }
        else if (phoneNumber.iNumLowerDigits < KLowerSevenDigits) {
            // Only the lower-digits hash is used, pad out the number to
            // KLowerSevenDigits.
            TInt pad = KLowerSevenDigits - phoneNumber.iNumLowerDigits;
            phoneNumber.iLowerSevenDigits = TMatch::padOutPhoneMatchNumber(phoneNumber.iLowerSevenDigits,pad);
        }
        else if (phoneNumber.iNumUpperDigits < (KMaxPhoneMatchLength - KLowerSevenDigits) ) {
            // The lower-digits hash is full, pad out the upper hash if less than 15
            // digits total.
            TInt pad = KMaxPhoneMatchLength - KLowerSevenDigits - phoneNumber.iNumUpperDigits;
            phoneNumber.iUpperDigits = TMatch::padOutPhoneMatchNumber(phoneNumber.iUpperDigits,pad);
        }
    }
    return phoneNumber;
}

/*
 * Returns the hash code(s) to use when matching the supplied phone number.  If the
 * number supplied has more actual phone digits (i.e. not including spaces) than
 * KLowerSevenDigits, a second hash is generated to hold the remaining most
 * significant phone digits. Removes the non-digit characters.

 * \param text Descriptor containing contacts phone number field.
 * \param lowerMatchlength Number of least significant phone digits to use.
 * \param upperMatchLength Number of most significant phone digits to use.
 * \param error Qt error code.
 * \return The hash code(s) to use when matching the supplied phone number.
 */
CntFilterDetail::TMatch CntFilterDetail::createPhoneMatchNumber(
                                            const TDesC& text, 
                                            TInt lowerMatchLength, 
                                            TInt upperMatchLength,
                                            QContactManager::Error* error)
{
    const TInt KBufLength = KCntMaxTextFieldLength+1;
    TBuf<KBufLength> buf;
    
    if (text.Length() <= KBufLength) {
        buf = text;
    }
    else {
        buf = text.Right(KBufLength);
    }
    TMatch::stripOutNonDigitChars(buf);
    
    TMatch phoneNumber;
    if (buf.Length() == 0) {
        *error = QContactManager::BadArgumentError;
        return phoneNumber;
    }
    
    // Generate a hash for the upper digits only if the phone number string is
    // large enough and more than 7 digits are to be matched.
    TInt phoneNumberlength = buf.Length();
    if ((phoneNumberlength > KLowerSevenDigits) && (upperMatchLength > 0)) {
        TPtrC upperPart = buf.Left(phoneNumberlength - KLowerSevenDigits);
        phoneNumber.iUpperDigits = TMatch::createHash(upperPart,
            upperMatchLength, phoneNumber.iNumUpperDigits);
    }
    // Generate a hash of the lower digits.
    phoneNumber.iLowerSevenDigits = TMatch::createHash(buf, 
            lowerMatchLength, phoneNumber.iNumLowerDigits);
    
    return phoneNumber;
}

//CntFilterDetail::TMatch constructor.
CntFilterDetail::TMatch::TMatch()
    :
    iLowerSevenDigits(0),
    iUpperDigits(0),
    iNumLowerDigits(0),
    iNumUpperDigits(0)
{
}

/*
 * Generates a hash value by reversing the matchLength least significant digits,
 * ignoring non-digits and zeroes at the end of the number.  Returns error if no phone
 * digits are supplied.

 * \param phoneNumberString A descriptor containing a phone number.
 * \param matchLength The number of digits from the right of the phone number to use.
 * \param numPhoneDigits The number of digits found in the phone number string.
 * \param error Qt error code.*
 * \return An integer representation of the phone number string in reverse.
 */
TInt32 CntFilterDetail::TMatch::createHash(
                                    const TDesC& phoneNumberString, 
                                    TInt matchLength, 
                                    TInt& numPhoneDigits)
{
    TInt phoneNumberLength = phoneNumberString.Length();
    TInt startIndex = 0;
    if (phoneNumberLength > matchLength) {
        startIndex = phoneNumberLength - matchLength;
    }
    
    numPhoneDigits = 0;
    TUint reversedDigits = 0;
    TInt mult = 1;
    
    for (TInt chrIndex = startIndex; (numPhoneDigits < matchLength) && (chrIndex < phoneNumberLength); chrIndex++) {
        TChar chr = phoneNumberString[chrIndex];
        if (chr.IsDigit()) {
            reversedDigits += (chr.GetNumericValue()) * mult;
            mult = mult * 10;
            ++numPhoneDigits;
        }
    }
    return reversedDigits ;
}

void CntFilterDetail::TMatch::stripOutNonDigitChars(TDes& text)
{
    for (TInt chrPos = 0; chrPos < text.Length(); ++chrPos) {
        TChar chr = text[chrPos];
        if (!chr.IsDigit()) {
            text.Delete(chrPos, 1);
            --chrPos;
        }
    }
}

TInt32 CntFilterDetail::TMatch::padOutPhoneMatchNumber(TInt32& phoneNumber,
                                                       TInt padOutLength)
{
    TInt32 result(phoneNumber);
    const TInt KMult(10);
    for (TInt i = 0; i < padOutLength; ++i) {
        result *= KMult;
    }
    phoneNumber = result;
    return result;
}