diff -r 000000000000 -r 876b1a06bc25 plugins/contacts/symbian/contactsmodel/cntplsql/src/cpplcommaddrtable.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/contacts/symbian/contactsmodel/cntplsql/src/cpplcommaddrtable.cpp Wed Aug 25 15:49:42 2010 +0300 @@ -0,0 +1,1153 @@ +/* +* Copyright (c) 2007-2009 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: +* +*/ + + +#include "pltables.h" +#include "dbsqlconstants.h" +#include "plplugins.h" +#include +#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS +#include +#endif + +/** +@param aDatabase A handle to the database. +@param aProperties A contact properties object. + +@return A pointer to a new CPplCommAddrTable object. +*/ +CPplCommAddrTable* CPplCommAddrTable::NewL(RSqlDatabase& aDatabase, CLplContactProperties& aProperties) + { + CPplCommAddrTable* self = CPplCommAddrTable::NewLC(aDatabase, aProperties); + CleanupStack::Pop(self); + return self; + } + + +/** +@param aDatabase A handle to the database. +@param aProperties A contact properties object. + +@return A pointer to a new CPplCommAddrTable object. +*/ +CPplCommAddrTable* CPplCommAddrTable::NewLC(RSqlDatabase& aDatabase, CLplContactProperties& aProperties) + { + CPplCommAddrTable* self = new (ELeave) CPplCommAddrTable(aDatabase, aProperties); + CleanupStack::PushL(self); + self->ConstructL(); + return self; + } + +/** +Set up the CCntSqlStatement objects held by the class. +*/ +void CPplCommAddrTable::ConstructL() + { + // Statement types + TCntSqlStatementType insertType(EInsert, KSqlContactCommAddrTableName() ); + TCntSqlStatementType selectType(ESelect, KSqlContactCommAddrTableName() ); + TCntSqlStatementType updateType(EUpdate, KSqlContactCommAddrTableName() ); + TCntSqlStatementType deleteType(EDelete, KSqlContactCommAddrTableName() ); + + // Where clauses + + // sizes of the clauses + const TInt KWhereContactIdBufSize(KCommAddrContactId().Size() + + KWhereStringEqualsStringFormatText().Size() + KCommAddrContactIdParam().Size() ); + const TInt KWhereCommAddrIdBufSize(KCommAddrId().Size() + + KWhereStringEqualsStringFormatText().Size() + KCommAddrIdParam().Size() ); + const TInt KWhereAndClauseBufSize(KWhereCommAddrIdBufSize + KSqlAnd().Size() + KWhereCommAddrIdBufSize); + + HBufC* whereContactIdClause = HBufC::NewLC(KWhereContactIdBufSize); + // for WHERE contact_id = [contact id value] + whereContactIdClause->Des().AppendFormat(KWhereStringEqualsStringFormatText, + &KCommAddrContactId, &KCommAddrContactIdParam ); + + // for WHERE comm_addr_id = [comm addr id value] + HBufC* whereCommAddrIdClause = HBufC::NewLC(KWhereCommAddrIdBufSize); + whereCommAddrIdClause->Des().AppendFormat(KWhereStringEqualsStringFormatText, + &KCommAddrId, &KCommAddrIdParam ); + + // for WHERE value = [value string] AND type = [type value] + HBufC* whereValueAndTypeClause = HBufC::NewLC(KWhereAndClauseBufSize); + TPtr whereValueAndTypeClausePtr = whereValueAndTypeClause->Des(); + whereValueAndTypeClausePtr.AppendFormat(KWhereStringEqualsStringFormatText, + &KCommAddrValue, &KCommAddrValueParam ); + whereValueAndTypeClausePtr.Append(KSqlAnd); + whereValueAndTypeClausePtr.AppendFormat(KWhereStringEqualsStringFormatText, + &KCommAddrType, &KCommAddrTypeParam ); + + // INSERT + + // insert new comm addr record + // INSERT INTO comm_addr + // (comm_addr_id, contact_id, type, value, extra_value) + // VALUES (NULL, [contact id value], [type value], + // [value string], [extra value string]); + // + iInsertStmnt = TSqlProvider::GetSqlStatementL(insertType); + iInsertStmnt->SetParamL(KCommAddrId(), KCommAddrIdParam() ); + iInsertStmnt->SetParamL(KCommAddrContactId(), KCommAddrContactIdParam() ); + iInsertStmnt->SetParamL(KCommAddrValue(), KCommAddrValueParam() ); + iInsertStmnt->SetParamL(KCommAddrExtraValue(), KCommAddrExtraValueParam() ); + iInsertStmnt->SetParamL(KCommAddrType(), KCommAddrTypeParam() ); + + // SELECT + + // select all fields for all comm addrs for a particular item id + // For a statement in the following format: + // SELECT comm_addr_id, type, value, extra_value FROM comm_addr + // WHERE contact_id = [contact id value]; + // + iWholeSelectStmnt = TSqlProvider::GetSqlStatementL(selectType); + iWholeSelectStmnt->SetParamL(KCommAddrId(), KNullDesC() ); + iWholeSelectStmnt->SetParamL(KCommAddrType(), KNullDesC() ); + iWholeSelectStmnt->SetParamL(KCommAddrValue(), KNullDesC() ); + iWholeSelectStmnt->SetParamL(KCommAddrExtraValue(), KNullDesC() ); + iWholeSelectStmnt->SetConditionL(*whereContactIdClause); + + // select fields for contacts that match phone, email or SIP lookup + // For a statement in the following format: + // SELECT contact_id, extra_value FROM comm_addr + // WHERE value = [value string] AND type = [type value]; + // + iMatchSelectStmnt = TSqlProvider::GetSqlStatementL(selectType); + iMatchSelectStmnt->SetParamL(KCommAddrContactId(), KNullDesC() ); + iMatchSelectStmnt->SetParamL(KCommAddrExtraValue(), KNullDesC() ); + iMatchSelectStmnt->SetConditionL(*whereValueAndTypeClause); + + // UPDATE + + // update comm addr record + // For a statement in the following format: + // UPDATE comm_addr SET + // contact_id = [contact id value], + // type = [type value], + // value = [value string], + // extra_value = [extra value string] + // WHERE comm_addr_id = [comm addr id value]; + // + iUpdateStmnt = TSqlProvider::GetSqlStatementL(updateType); + iUpdateStmnt->SetParamL(KCommAddrContactId(), KCommAddrContactIdParam() ); + iUpdateStmnt->SetParamL(KCommAddrValue(), KCommAddrValueParam() ); + iUpdateStmnt->SetParamL(KCommAddrExtraValue(), KCommAddrExtraValueParam() ); + iUpdateStmnt->SetParamL(KCommAddrType(), KCommAddrTypeParam() ); + iUpdateStmnt->SetConditionL(*whereCommAddrIdClause); + + // DELETE + + // delete single comm addr record + // For a statement in the following format: + // DELETE FROM comm_addr WHERE comm_addr_id = [comm addr id value]; + // + iSingleDeleteStmnt = TSqlProvider::GetSqlStatementL(deleteType); + iSingleDeleteStmnt->SetConditionL(*whereCommAddrIdClause); + + // delete all comm addrs for a particular contact item + // For a statement in the following format: + // DELETE FROM comm_addr WHERE contact_id = [contact id value]; + // + iAllForItemDeleteStmnt = TSqlProvider::GetSqlStatementL(deleteType); + iAllForItemDeleteStmnt->SetConditionL(*whereContactIdClause); + + CleanupStack::PopAndDestroy(3, whereContactIdClause); // and whereCommAddrIdClause, whereValueAndTypeClause + } + + +/** +Insert new communication addresses into the comm_addr table. +@param aItem A contact item whose communication addresses are to be added to the contacts database. +*/ +void CPplCommAddrTable::CreateInDbL(CContactItem& aItem) + { + // Check that the contact item is a card, own card or ICC entry. + const TUid KType = aItem.Type(); + if (KType != KUidContactCard && KType != KUidContactOwnCard && KType != KUidContactICCEntry) + { + return; + } + + // create lists for comm_addrs to keep track of what we have already seen so as to avoid duplicates + RArray newPhones; + RArray newEmails; + RArray newSips; + CleanupClosePushL(newPhones); + CleanupClosePushL(newEmails); + CleanupClosePushL(newSips); + + for (TInt fieldNum = aItem.CardFields().Count() - 1; fieldNum >= 0; --fieldNum) + { + CContactItemField& currField = aItem.CardFields()[fieldNum]; + const CContentType& contType = currField.ContentType(); + TBool isPhone(contType.ContainsFieldType(KUidContactFieldPhoneNumber) || + contType.ContainsFieldType(KUidContactFieldFax) || + contType.ContainsFieldType(KUidContactFieldSms) ); + TBool isEmail(contType.ContainsFieldType(KUidContactFieldEMail) ); + TBool isSip(contType.ContainsFieldType(KUidContactFieldSIPID) ); + + // check it's a field we want and that it's not empty + // insert a new address only if we haven't already seen it -- no point storing the same one twice. + if ((isPhone || isEmail || isSip) && currField.StorageType() == KStorageTypeText + && currField.TextStorage()->IsFull() ) + { + const TContactItemId KItemId(aItem.Id()); + + // get phone numbers + if (isPhone) + { + TMatch phoneNumber; + phoneNumber = CreatePaddedPhoneDigitsL(currField.TextStorage()->Text(), KLowerSevenDigits, + KMaxPhoneMatchLength - KLowerSevenDigits); + if (newPhones.Find(phoneNumber, TIdentityRelation(&TMatch::Equals) ) == KErrNotFound) + { + DoPhoneNumWriteOpL(phoneNumber, EInsert, KItemId); + newPhones.AppendL(phoneNumber); + } + } + // get email addresses + else if (isEmail && newEmails.Find(currField.TextStorage()->Text() ) == KErrNotFound) + { + DoNonPhoneWriteOpL(currField.TextStorage()->Text(), EInsert, KItemId, EEmailAddress); + newEmails.AppendL(currField.TextStorage()->Text() ); + } + // get SIP addresses + else if (newSips.Find(currField.TextStorage()->Text() ) == KErrNotFound) + { + DoNonPhoneWriteOpL(currField.TextStorage()->Text(), EInsert, KItemId, ESipAddress); + newSips.AppendL(currField.TextStorage()->Text() ); + } + } + } + + CleanupStack::PopAndDestroy(3, &newPhones); // and newSips, newEmails + } + + +/** +Updates communication addresses in the database. + +If there are the same number of items to be updated as are already in the database, the +existing records are overwritten using the update statement. However, if there is a +different number of addresses to be updated, records in the database (if there are any) +are deleted (using DeleteL() ) and the new data is inserted (using CreateInDbL() ). + +@param aItem A contact item whose communication addresses are to be updated in the contacts database. +*/ +void CPplCommAddrTable::UpdateL(const CContactItem& aItem) + { + // Check that the contact item is a card, own card or ICC entry. + const TUid type(aItem.Type() ); + if (type != KUidContactCard && type != KUidContactOwnCard && type != KUidContactICCEntry) + { + return; + } + + const TContactItemId KItemId(aItem.Id() ); + + // create lists for comm_addrs and go through contact item to populate them with any new ones we find + RArray newPhones; + RArray newEmails; + RArray newSips; + CleanupClosePushL(newPhones); + CleanupClosePushL(newEmails); + CleanupClosePushL(newSips); + + for (TInt fieldNum = aItem.CardFields().Count() - 1; fieldNum >= 0; --fieldNum) + { + CContactItemField& currField = aItem.CardFields()[fieldNum]; + const CContentType& contType = currField.ContentType(); + TBool isPhone(contType.ContainsFieldType(KUidContactFieldPhoneNumber) || + contType.ContainsFieldType(KUidContactFieldFax) || + contType.ContainsFieldType(KUidContactFieldSms) ); + TBool isEmail(contType.ContainsFieldType(KUidContactFieldEMail) ); + TBool isSip(contType.ContainsFieldType(KUidContactFieldSIPID) ); + + // check it's a field we want and that it's not empty + // store a new address if we haven't already seen it -- no point storing the same one twice. + if ((isPhone || isEmail || isSip) && currField.StorageType() == KStorageTypeText + && currField.TextStorage()->IsFull() ) + { + // get phone numbers + if (isPhone) + { + TMatch phoneNumber; + phoneNumber = CreatePaddedPhoneDigitsL(currField.TextStorage()->Text(), KLowerSevenDigits, + KMaxPhoneMatchLength - KLowerSevenDigits); + if (newPhones.Find(phoneNumber, TIdentityRelation(&TMatch::Equals) ) == KErrNotFound) + { + newPhones.AppendL(phoneNumber); + } + } + // get email addresses + else if (isEmail && newEmails.Find(currField.TextStorage()->Text() ) == KErrNotFound) + { + newEmails.AppendL(currField.TextStorage()->Text() ); + } + // get SIP addresses + else if (newSips.Find(currField.TextStorage()->Text() ) == KErrNotFound) + { + newSips.AppendL(currField.TextStorage()->Text() ); + } + } + } + + // if there are no comm addresses in the contact item, delete any from the database + if (!(newPhones.Count() + newEmails.Count() + newSips.Count() ) ) + { + TBool lowDiskErr(EFalse); + DeleteL(aItem, lowDiskErr); + CleanupStack::PopAndDestroy(3, &newPhones); // and newSips, newEmails + if (lowDiskErr) + { + User::Leave(KErrDiskFull); + } + return; + } + + // create from the database a list of comm_addr_ids that can be recycled as their + // comm_addrs are in the database but not in the new version of the contact item + RArray freeCommAddrIds; + CleanupClosePushL(freeCommAddrIds); + + // weed out addresses from the list that are already in the db but haven't changed + // and populate the freeCommAddrIds list + RemoveNonUpdatedAddrsL(newPhones, newEmails, newSips, freeCommAddrIds, KItemId); + + // do the actual updating on an address-by-address basis + DoUpdateCommAddrsL(newPhones, newEmails, newSips, freeCommAddrIds, KItemId); + + CleanupStack::PopAndDestroy(4, &newPhones); // and freeCommAddrIds, newSips, newEmails + } + + +/** +Removes comm addresses from the 3 lists that are already in the database and have been updated. +It takes the 3 lists in as parameters and modifies them accordingly. It also populates the list +of comm address ids that are free to be recycled during updating. +*/ +void CPplCommAddrTable::RemoveNonUpdatedAddrsL(RArray& aNewPhones, RArray& aNewEmails, RArray& aNewSips, + RArray& aFreeCommAddrIds, const TInt aItemId) + { + // build the RSqlStatement + RSqlStatement stmnt; + CleanupClosePushL(stmnt); + stmnt.PrepareL(iDatabase, iWholeSelectStmnt->SqlStringL() ); + const TInt KContactIdParamIndex(KFirstIndex); // first and only parameter in the query + User::LeaveIfError(stmnt.BindInt(KContactIdParamIndex, aItemId) ) ; + + // fetch the results from the query and compare them with the new comm_addrs we have + TInt err(KErrNone); + while ((err = stmnt.Next() ) == KSqlAtRow) + { + const TInt KType(stmnt.ColumnInt(iWholeSelectStmnt->ParameterIndex(KCommAddrType() ) ) ); + if (KType == EPhoneNumber) + { + TMatch phoneNumber; + TPtrC valString = stmnt.ColumnTextL(iWholeSelectStmnt->ParameterIndex(KCommAddrValue() ) ); + TPtrC extValString = stmnt.ColumnTextL(iWholeSelectStmnt->ParameterIndex(KCommAddrExtraValue() ) ); + User::LeaveIfError(TLex(valString).Val(phoneNumber.iLowerSevenDigits) ); + User::LeaveIfError(TLex(extValString).Val(phoneNumber.iUpperDigits) ); + + TInt matchIndex(aNewPhones.Find(phoneNumber, TIdentityRelation(&TMatch::Equals) ) ); + // remove any phone numbers from the new list if we already + // have them in the db and they haven't changed... + if (matchIndex != KErrNotFound) + { + aNewPhones.Remove(matchIndex); + } + // ...and add any spare ids to the recycle list + else + { + aFreeCommAddrIds.AppendL( + stmnt.ColumnInt(iWholeSelectStmnt->ParameterIndex(KCommAddrId() ) ) ); + } + } + else // is Email or SIP + { + TPtrC valString = stmnt.ColumnTextL(iWholeSelectStmnt->ParameterIndex(KCommAddrValue() ) ); + TInt matchIndex(0); + + // remove any email and sip addresses from the new list if + // we already have them in the db and they haven't changed... + if (KType == EEmailAddress) + { + matchIndex = aNewEmails.Find(valString); + if (matchIndex != KErrNotFound) + { + aNewEmails.Remove(matchIndex); + } + } + else // SIP + { + matchIndex = aNewSips.Find(valString); + if (matchIndex != KErrNotFound) + { + aNewSips.Remove(matchIndex); + } + } + + // ...and add any spare ids to the recycle list + if (matchIndex == KErrNotFound) + { + aFreeCommAddrIds.AppendL( + stmnt.ColumnInt(iWholeSelectStmnt->ParameterIndex(KCommAddrId() ) ) ); + } + } + } + // leave if we didn't complete going through the results properly + if(err != KSqlAtEnd) + { + User::Leave(err); + } + CleanupStack::PopAndDestroy(&stmnt); + } + + +/** +Does comm_addr updating on an address-by-address basis. +Takes 3 lists of new addresses and a list of free comm_addr_ids. +*/ +void CPplCommAddrTable::DoUpdateCommAddrsL(RArray& aNewPhones, RArray& aNewEmails, RArray& aNewSips, + RArray& aFreeCommAddrIds, const TInt aItemId) + { + // if we have free ids to recycle and new comm_addrs, insert them by UPDATE + const TInt KFirstElementId(0); + while (aFreeCommAddrIds.Count() && (aNewPhones.Count() || aNewEmails.Count() || aNewSips.Count() ) ) + { + if(aNewPhones.Count() ) + { + DoPhoneNumWriteOpL(aNewPhones[KFirstElementId], EUpdate, aItemId, aFreeCommAddrIds[KFirstElementId]); + aNewPhones.Remove(KFirstElementId); + aFreeCommAddrIds.Remove(KFirstElementId); + } + else if(aNewEmails.Count() ) + { + DoNonPhoneWriteOpL(aNewEmails[KFirstElementId], EUpdate, aItemId, EEmailAddress, + aFreeCommAddrIds[KFirstElementId]); + aNewEmails.Remove(KFirstElementId); + aFreeCommAddrIds.Remove(KFirstElementId); + } + else if(aNewSips.Count() ) + { + DoNonPhoneWriteOpL(aNewSips[KFirstElementId], EUpdate, aItemId, ESipAddress, + aFreeCommAddrIds[KFirstElementId]); + aNewSips.Remove(KFirstElementId); + aFreeCommAddrIds.Remove(KFirstElementId); + } + } + + // if we still have free ids to recycle but no new comm_addrs, + // delete the existing comm_addrs for these ids + const TInt KNumFreeIds(aFreeCommAddrIds.Count() ); + for (TInt i = 0; i < KNumFreeIds; ++i) + { + TBool lowDiskErr(EFalse); + DeleteSingleCommAddrL(aFreeCommAddrIds[i], lowDiskErr); + if (lowDiskErr) + { + User::Leave(KErrDiskFull); + } + } + + // if we still have new comm_addrs but no free ids to recycle, + // put them in the database using INSERT + const TInt KNumNewPhones(aNewPhones.Count() ); + const TInt KNumNewEmails(aNewEmails.Count() ); + const TInt KNumNewSips(aNewSips.Count() ); + for (TInt i = 0; i < KNumNewPhones; ++i) + { + DoPhoneNumWriteOpL(aNewPhones[i], EInsert, aItemId); + } + for (TInt i = 0; i < KNumNewEmails; ++i) + { + DoNonPhoneWriteOpL(aNewEmails[i], EInsert, aItemId, EEmailAddress); + } + for (TInt i = 0; i < KNumNewSips; ++i) + { + DoNonPhoneWriteOpL(aNewSips[i], EInsert, aItemId, ESipAddress); + } + } + + +/** +Deletes individual communication addresses from the database. In other words, deletes at +the sub-contact item level rather than deleting everything with a specific contact item ID. +*/ +void CPplCommAddrTable::DeleteSingleCommAddrL(TInt aCommAddrId, TBool& aLowDiskErrorOccurred) + { + RSqlStatement stmnt; + CleanupClosePushL(stmnt); + stmnt.PrepareL(iDatabase, iSingleDeleteStmnt->SqlStringL() ); + const TInt KCommAddrIdParamIndex(KFirstIndex); // first and only parameter in the query + User::LeaveIfError(stmnt.BindInt(KCommAddrIdParamIndex, aCommAddrId ) ); + TInt err = stmnt.Exec(); + CleanupStack::PopAndDestroy(&stmnt); + + if (err == KErrDiskFull) + { + aLowDiskErrorOccurred = ETrue; + } + else + { + User::LeaveIfError(err); + } + } + +/** +Performs write operations for individual communication addresses of type "phone number". +This version of the method does not require a CommAddrId argument. Therefore, this can only be used +for insertion and not updating. +*/ +void CPplCommAddrTable::DoPhoneNumWriteOpL(const CPplCommAddrTable::TMatch& aPhoneNum, TCntSqlStatement aType, + TInt aCntId) + { + // provide a commaddr of 0 as a default argument + DoPhoneNumWriteOpL(aPhoneNum, aType, aCntId, 0); + } + + +/** +Performs write (Insert/Update) operations for indiviual communication addresses of type "phone number". +*/ +void CPplCommAddrTable::DoPhoneNumWriteOpL(const CPplCommAddrTable::TMatch& aPhoneNum, TCntSqlStatement aType, + TInt aCntId, TInt aCommAddrId) + { + // leave if the statement type is not insert or update. + // also, we can't update if aCommAddrId is 0 as we don't know the record's id + if ((aType != EUpdate && aType != EInsert) || (aType == EUpdate && aCommAddrId == 0) ) + { + User::Leave(KErrArgument); + } + + RSqlStatement stmnt; + CleanupClosePushL(stmnt); + + // temporary reference to the CCntSqlStatements member variables to take advantage + // of the commonality between update and insert operations. + CCntSqlStatement* tempCntStmnt = iUpdateStmnt; + if (aType == EInsert) + { + tempCntStmnt = iInsertStmnt; + } + + User::LeaveIfError(stmnt.Prepare(iDatabase, tempCntStmnt->SqlStringL() ) ); + User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrContactId() ), aCntId) ); + User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrExtraValue() ), aPhoneNum.iUpperDigits) ); + User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrValue() ), aPhoneNum.iLowerSevenDigits) ); + User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrType() ), EPhoneNumber) ); + + if (aType == EInsert) + { + User::LeaveIfError(stmnt.BindNull(tempCntStmnt->ParameterIndex(KCommAddrId() ) ) ); + } + else + { + // it's the fifth parameter in the query and is in the WHERE + // clause so we can't get its index from the CCntSqlStatement + const TInt KCommAddrIdParamIndex(KFirstIndex + 4); + User::LeaveIfError(stmnt.BindInt(KCommAddrIdParamIndex, aCommAddrId) ); + } + + User::LeaveIfError(stmnt.Exec() ); + CleanupStack::PopAndDestroy(&stmnt); + } + + +/** +Performs write operations for individual communication addresses of type "email" or "SIP" address. +This version of the method does not require a CommAddrId argument. Therefore, this can only be used +for insertion and not updating. +*/ +void CPplCommAddrTable::DoNonPhoneWriteOpL(const TDesC& aAddress, const TCntSqlStatement aType, + TInt aCntId, TCommAddrType aAddrType) + { + // provide a commaddr of 0 as a default argument + DoNonPhoneWriteOpL(aAddress, aType, aCntId, aAddrType, 0); + } + + +/** +Performs write (Insert/Update) operations for indiviual communication addresses of +type "Email address" or "SIP Address". +*/ +void CPplCommAddrTable::DoNonPhoneWriteOpL(const TDesC& aAddress, const TCntSqlStatement aType, + TInt aCntId, TCommAddrType aAddrType, TInt aCommAddrId) + { + // leave if the statement type is not insert or update. + // also, we can't update if aCommAddrId is 0 as we don't know the record's id + if ((aType != EUpdate && aType != EInsert) || (aType == EUpdate && aCommAddrId == 0) ) + { + User::Leave(KErrNotFound); + } + + RSqlStatement stmnt; + CleanupClosePushL(stmnt); + + // temporary reference to the CCntSqlStatements member variables to take advantage + // of the commonality between update and insert operations. + CCntSqlStatement* tempCntStmnt = iUpdateStmnt; + if (aType == EInsert) + { + tempCntStmnt = iInsertStmnt; + } + + User::LeaveIfError(stmnt.Prepare(iDatabase, tempCntStmnt->SqlStringL() ) ); + User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrContactId() ), aCntId) ); + User::LeaveIfError(stmnt.BindText(tempCntStmnt->ParameterIndex(KCommAddrValue() ), aAddress) ); + User::LeaveIfError(stmnt.BindNull(tempCntStmnt->ParameterIndex(KCommAddrExtraValue() ) ) ); + User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrType() ), aAddrType) ); + + if (aType == EInsert) + { + User::LeaveIfError(stmnt.BindNull(tempCntStmnt->ParameterIndex(KCommAddrId() ) ) ); + } + else + { + // it's the fifth parameter in the query and is in the WHERE + // clause so we can't get its index from the CCntSqlStatement + const TInt KCommAddrIdParamIndex(KFirstIndex + 4); + User::LeaveIfError(stmnt.BindInt(KCommAddrIdParamIndex, aCommAddrId) ); + } + + User::LeaveIfError(stmnt.Exec() ); + CleanupStack::PopAndDestroy(&stmnt); + } + + +/** +Deletes all the communication addresses for a particular contact item. Should be used when +deleting a contact item from the database altogether. + +@param aItem The contact item whose communcation addresses are to be deleted. +*/ +void CPplCommAddrTable::DeleteL(const CContactItem& aItem, TBool& aLowDiskErrorOccurred) + { + const TUid KType = aItem.Type(); + if (KType != KUidContactCard && KType != KUidContactOwnCard && KType != KUidContactICCEntry) + { + return; + } + + RSqlStatement stmnt; + CleanupClosePushL(stmnt); + stmnt.PrepareL(iDatabase, iAllForItemDeleteStmnt->SqlStringL() ); + const TInt KContactIdParamIndex(KFirstIndex); // first and only parameter in query + User::LeaveIfError(stmnt.BindInt(KContactIdParamIndex, aItem.Id() ) ); + TInt err = stmnt.Exec(); + CleanupStack::PopAndDestroy(&stmnt); + + if (err == KErrDiskFull) + { + aLowDiskErrorOccurred = ETrue; + } + else + { + User::LeaveIfError(err); + } + } + + +/** +Creates the comm_addr table and its indexes in the database. +*/ +void CPplCommAddrTable::CreateTableL() + { + User::LeaveIfError(iDatabase.Exec(KCommAddrCreateStmnt) ); + } + + +/** +Returns an array of 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. + +Additionally, if the Contacts Model Phone Parser (CNTPHONE.DLL) is available, +then any DTMF digits are also excluded from the comparision. + +Note that due to the way numbers are stored in the database, it is recommended +that at least 7 match digits are specified even when matching a number +containing fewer digits. Failure to follow this guideline may (depending on the +database contents) mean that the function will not return the expected Contact +IDs. + +@param aNumber Phone number string. +@param aMatchLengthFromRight Number of digits from the right of the phone number +to use. Up to 15 digits can be specified, and it is recommended that at least 7 +match digits are specified. +@param aDatabase The database. + +@return Array of contact IDs which are candidate matches. +*/ +CContactIdArray* CPplCommAddrTable::MatchPhoneNumberL(const TDesC& aNumber, const TInt aMatchLengthFromRight) + { + CContactIdArray* phoneMatchArray = CContactIdArray::NewLC(); + + TInt numLowerDigits = aMatchLengthFromRight; + TInt numUpperDigits = 0; + + if(numLowerDigits > KLowerSevenDigits) + { + // New style matching. + numLowerDigits = KLowerSevenDigits; + numUpperDigits = aMatchLengthFromRight - KLowerSevenDigits; + } + + TMatch phoneDigits = CreatePaddedPhoneDigitsL(aNumber, numLowerDigits, numUpperDigits); + + if (phoneDigits.iNumLowerDigits + phoneDigits.iNumUpperDigits > 0) + { + // build statement + RSqlStatement stmnt; + CleanupClosePushL(stmnt); + stmnt.PrepareL(iDatabase, iMatchSelectStmnt->SqlStringL() ); + + const TInt KValueParamIndex(KFirstParam); // first parameter in query... + const TInt KTypeParamIndex(KValueParamIndex + 1); // ...and the second. + + User::LeaveIfError(stmnt.BindInt(KValueParamIndex, phoneDigits.iLowerSevenDigits )); + User::LeaveIfError(stmnt.BindInt(KTypeParamIndex, EPhoneNumber )); + + // fetch the list of any matching contact ids + TInt err(KErrNone); + const TInt KContactIdIdx(iMatchSelectStmnt->ParameterIndex(KCommAddrContactId() ) ); + const TInt KExtraValueIdx(iMatchSelectStmnt->ParameterIndex(KCommAddrExtraValue() ) ); + while ((err = stmnt.Next() ) == KSqlAtRow) + { + if (aMatchLengthFromRight <= KLowerSevenDigits) + { + // Matching 7 or less digits...we've already matched. + phoneMatchArray->AddL(stmnt.ColumnInt(KContactIdIdx) ); + } + else + { + // Check the upper digits... + TInt32 storedUpperDigits(0); + TPtrC extValString = stmnt.ColumnTextL(KExtraValueIdx); + User::LeaveIfError(TLex(extValString).Val(storedUpperDigits) ); + + const TInt KDigitsToRemove = KMaxPhoneMatchLength - KLowerSevenDigits - phoneDigits.iNumUpperDigits; + for(TInt i = 0; i < KDigitsToRemove; ++i) + { + // repeatedly divide by 10 to lop off the appropriate number of digits from the right + storedUpperDigits /= 10; + } + + storedUpperDigits = TMatch::PadOutPhoneMatchNumber(storedUpperDigits, KDigitsToRemove); + + if (phoneDigits.iUpperDigits == storedUpperDigits) + { + phoneMatchArray->AddL(stmnt.ColumnInt(KContactIdIdx) ); + } + } + } + + // leave if we didn't complete going through the results properly + if(err != KSqlAtEnd) + { + User::Leave(err); + } + CleanupStack::PopAndDestroy(&stmnt); + } + + CleanupStack::Pop(phoneMatchArray); + return phoneMatchArray; + } + +/** +Returns an array of contact item IDs for all the contact items which may contain +the specified telephone number in a telephone, fax or SMS type field. + +This is improved version of MatchPhoneNumberL method. +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. Leading zeros are removed. Digits are compared up to +the lenght of shorter number. + +@param aNumber Phone number string. +@return Array of contact IDs which are candidate matches. +*/ +CContactIdArray* CPplCommAddrTable::BestMatchingPhoneNumberL(const TDesC& aNumber) + { + const TInt KUpperMaxLength = KMaxPhoneMatchLength - KLowerSevenDigits; + + CContactIdArray* phoneMatchArray = CContactIdArray::NewLC(); + + TMatch phoneDigits = CreatePaddedPhoneDigitsL(aNumber, KLowerSevenDigits, KUpperMaxLength); + + if (phoneDigits.iNumLowerDigits + phoneDigits.iNumUpperDigits > 0) + { + // build statement + RSqlStatement stmnt; + CleanupClosePushL(stmnt); + stmnt.PrepareL(iDatabase, iMatchSelectStmnt->SqlStringL()); + + const TInt KValueParamIndex(KFirstParam); // first parameter in query... + const TInt KTypeParamIndex(KValueParamIndex + 1); // ...and the second. + + User::LeaveIfError(stmnt.BindInt(KValueParamIndex, + phoneDigits.iLowerSevenDigits)); + User::LeaveIfError(stmnt.BindInt(KTypeParamIndex, EPhoneNumber)); + + // fetch the list of any matching contact ids + TInt err(KErrNone); + const TInt KContactIdIdx(iMatchSelectStmnt->ParameterIndex( KCommAddrContactId())); + const TInt KExtraValueIdx(iMatchSelectStmnt->ParameterIndex(KCommAddrExtraValue())); + while ((err = stmnt.Next()) == KSqlAtRow) + { + // Check the upper digits... + TInt32 number = phoneDigits.iUpperDigits; + TPtrC extValString = stmnt.ColumnTextL(KExtraValueIdx); + TInt32 storedUpperDigits; + User::LeaveIfError(TLex(extValString).Val(storedUpperDigits)); + TInt32 stored = storedUpperDigits; + + TBool nonZeroInStoredFound = EFalse; + TBool nonZeroInNumberFound = EFalse; + while ((number != 0) && (stored != 0)) + { + nonZeroInNumberFound |= (number % 10 != 0); + nonZeroInStoredFound |= (stored % 10 != 0); + if (nonZeroInStoredFound && nonZeroInNumberFound) + { + break; + } + number /= 10; + stored /= 10; + } + + if ( (phoneDigits.iUpperDigits == 0) || (storedUpperDigits == 0) || + (number == stored) ) + { + phoneMatchArray->AddL(stmnt.ColumnInt(KContactIdIdx)); + } + } + + // leave if we didn't complete going through the results properly + if (err != KSqlAtEnd) + { + User::Leave(err); + } + CleanupStack::PopAndDestroy(&stmnt); + } + + CleanupStack::Pop(phoneMatchArray); + return phoneMatchArray; + } + +/** +Searches the contacts database to find any contact items with an exact match on the email address supplied. + +@param aEmailAddr A descriptor containing the email address to be found in the database. +@return An array of contact IDs which match the supplied email address. +*/ +CContactIdArray* CPplCommAddrTable::MatchEmailAddressL(const TDesC& aEmailAddr) + { + return MatchNonPhoneAddrL(aEmailAddr, EEmailAddress); + } + + +/** +Searches the contacts database to find any contact items with an exact match on the SIP address supplied. + +@param aSipAddr A descriptor containing the SIP address to be found in the database. +@return An array of contact IDs which match the supplied SIP address. +*/ +CContactIdArray* CPplCommAddrTable::MatchSipAddressL(const TDesC& aSipAddr) + { + return MatchNonPhoneAddrL(aSipAddr, ESipAddress); + } + + +/** +Searches the contacts database to find any contact items with an exact match on the address supplied. + +@param aCommAddr A descriptor containing the address to be found in the database. +@param aAddrType The type of addresses that is being sought. +@return An array of contact IDs which match the supplied address. +*/ +CContactIdArray* CPplCommAddrTable::MatchNonPhoneAddrL(const TDesC& aCommAddr, TCommAddrType aAddrType) + { + + // build statement + RSqlStatement stmnt; + CleanupClosePushL(stmnt); + stmnt.PrepareL(iDatabase, iMatchSelectStmnt->SqlStringL() ); + + const TInt KValueParamIndex(KFirstParam); // first parameter in query... + const TInt KTypeParamIndex(KValueParamIndex + 1); // ...and the second. + User::LeaveIfError(stmnt.BindText(KValueParamIndex, aCommAddr) ); + User::LeaveIfError(stmnt.BindInt(KTypeParamIndex, aAddrType) ); + + // fetch the list of any matching contact ids + CContactIdArray* idArray = CContactIdArray::NewLC(); + TInt err(KErrNone); + const TInt KContactIdIdx(iMatchSelectStmnt->ParameterIndex(KCommAddrContactId() ) ); + while ((err = stmnt.Next() ) == KSqlAtRow) + { + idArray->AddL(stmnt.ColumnInt(KContactIdIdx) ); + } + + // leave if we didn't complete going through the results properly + if(err != KSqlAtEnd) + { + User::Leave(err); + } + + CleanupStack::Pop(idArray); + CleanupStack::PopAndDestroy(&stmnt); + + return idArray; + } + + +/** +CPplCommAddrTable constructor +*/ +CPplCommAddrTable::CPplCommAddrTable(RSqlDatabase& aDatabase, CLplContactProperties& aProperties) + : iProperties(aProperties), + iDatabase(aDatabase) + { + } + + +/** +Destructor + +Tidy up CCntSqlStatement objects +*/ +CPplCommAddrTable::~CPplCommAddrTable() + { + delete iInsertStmnt; + delete iWholeSelectStmnt; + delete iMatchSelectStmnt; + delete iUpdateStmnt; + delete iSingleDeleteStmnt; + delete iAllForItemDeleteStmnt; + } + +/** +Convert the supplied string to a matchable phone number. + +@param aText Descriptor containing phone number. +@param aLowerMatchlength Number of least significant phone digits to use. +@param aUpperMatchLength Number of most significant phone digits to use. + +@return The hash code(s) to use when matching the supplied phone number. +*/ +CPplCommAddrTable::TMatch CPplCommAddrTable::CreatePaddedPhoneDigitsL(const TDesC& aNumber, const TInt aNumLowerDigits, const TInt aNumUpperDigits) + { + CPplCommAddrTable::TMatch phoneNumber = CreatePhoneMatchNumberL(aNumber, aNumLowerDigits, aNumUpperDigits); + + 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; + } + + +/** +CPplCommAddrTable::TMatch constructor. +*/ +CPplCommAddrTable::TMatch::TMatch() + : + iLowerSevenDigits(0), + iUpperDigits(0), + iNumLowerDigits(0), + iNumUpperDigits(0) + { + } + + +TBool CPplCommAddrTable::TMatch::operator==(const TMatch& aSecondMatch)const + { + return (iLowerSevenDigits == aSecondMatch.iLowerSevenDigits)&& (iUpperDigits == aSecondMatch.iUpperDigits); + } + + +TBool CPplCommAddrTable::TMatch::Equals(const TMatch& aRMatch, const TMatch& aLMatch) + { + return aRMatch == aLMatch; + } + +/** +Pad out the supplied phone digits with zeroes. + +@param aPhoneNumber The number to pad. +@param aPadOutLength The number of digits to pad out. + +@return The padded number. +*/ +TInt32 CPplCommAddrTable::TMatch::PadOutPhoneMatchNumber(TInt32& aPhoneNumber, TInt aPadOutLength) + { + TInt32 result(aPhoneNumber); + const TInt KMult(10); + for(TInt i = 0; i < aPadOutLength; ++i) + { + result *= KMult; + } + aPhoneNumber = result; + return result; + } + + +/** +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. Extracts DTMF digits from the phone number if a +parser is available, otherwise just removes the non-digit characters. + +@param aText Descriptor containing contacts phone number field. +@param aLowerMatchlength Number of least significant phone digits to use. +@param aUpperMatchLength Number of most significant phone digits to use. + +@return The hash code(s) to use when matching the supplied phone number. +*/ +CPplCommAddrTable::TMatch CPplCommAddrTable::CreatePhoneMatchNumberL(const TDesC& aText, TInt aLowerMatchLength, TInt aUpperMatchLength) + { + __ASSERT_DEBUG( ((aLowerMatchLength == KLowerSevenDigits) && (aUpperMatchLength > 0) ) // upper 8 digits + || ((aLowerMatchLength <= KLowerSevenDigits) && (aUpperMatchLength == 0) ), // lower 7 digits + User::Leave(KErrNotSupported) ); + + const TInt KBufLength = KCntMaxTextFieldLength+1; + TBuf buf; + + CContactPhoneNumberParser* parser = iProperties.ContactPhoneParserL().Parser(); + + if (parser) + { + parser->ExtractRawNumber(aText.Left(KCntMaxTextFieldLength),buf); + } + else + { + if(aText.Length() <= KBufLength) + { + buf = aText; + } + else + { + buf = aText.Right(KBufLength); + } + TMatch::StripOutNonDigitChars(buf); + } + + TMatch phoneNumber; + + if (buf.Length() == 0) + { + 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) && (aUpperMatchLength > 0) ) + { + TPtrC upperPart = buf.Left(phoneNumberlength - KLowerSevenDigits); + phoneNumber.iUpperDigits = TMatch::CreateHashL(upperPart, + aUpperMatchLength, phoneNumber.iNumUpperDigits); + } + + // Generate a hash of the lower digits. + phoneNumber.iLowerSevenDigits = TMatch::CreateHashL(buf, aLowerMatchLength, phoneNumber.iNumLowerDigits); + + return phoneNumber; + } + + +/** +Strip out any non-digit characters before we convert the phone number to an +integer. + +@param aText Phone number which on return will have any non-digit characters +removed. +*/ +void CPplCommAddrTable::TMatch::StripOutNonDigitChars(TDes& aText) + { + for(TInt chrPos = 0; chrPos < aText.Length(); ++chrPos) + { + TChar chr = aText[chrPos]; + if (!chr.IsDigit() ) + { + aText.Delete(chrPos, 1); + --chrPos; + } + } + } + + +/** +Generates a hash value by reversing the aMatchLength least significant digits, +ignoring non-digits and zeroes at the end of the number. Asserts if no phone +digits are supplied. + +@param aPhoneNumberString A descriptor containing a phone number. +@param aMatchLength The number of digits from the right of the phone number to use. +@param aNumPhoneDigits The number of digits found in the phone number string. + +@return An integer representation of the phone number string in reverse. +*/ +TInt32 CPplCommAddrTable::TMatch::CreateHashL(const TDesC& aPhoneNumberString, TInt aMatchLength, TInt& aNumPhoneDigits) + { + TInt phoneNumberLength = aPhoneNumberString.Length(); + __ASSERT_DEBUG(phoneNumberLength > 0, User::Leave(KErrNotSupported) ); + + TInt startIndex = 0; + + if (phoneNumberLength > aMatchLength) + { + startIndex = phoneNumberLength - aMatchLength; + } + + aNumPhoneDigits = 0; + TUint reversedDigits = 0; + TInt mult = 1; + + for (TInt chrIndex = startIndex; (aNumPhoneDigits < aMatchLength) && (chrIndex < phoneNumberLength); chrIndex++) + { + TChar chr = aPhoneNumberString[chrIndex]; + if (chr.IsDigit() ) + { + reversedDigits += (chr.GetNumericValue() ) * mult; + mult = mult * 10; + ++aNumPhoneDigits; + } + } + + return reversedDigits ; + }