diff --git a/mailnews/addrbook/src/nsAbAutoCompleteSearch.js b/mailnews/addrbook/src/nsAbAutoCompleteSearch.js index c54228bfcc..25dd762b8a 100644 --- a/mailnews/addrbook/src/nsAbAutoCompleteSearch.js +++ b/mailnews/addrbook/src/nsAbAutoCompleteSearch.js @@ -115,6 +115,41 @@ nsAbAutoCompleteSearch.prototype = { // Private methods + /** + * Returns the popularity index for a given card. This takes account of a + * translation bug whereby Thunderbird 2 stores its values in mork as + * hexadecimal, and Thunderbird 3 stores as decimal. + * + * @param aDirectory The directory that the card is in. + * @param aCard The card to return the popularity index for. + */ + _getPopularityIndex: function _getPopularityIndex(aDirectory, aCard) { + let popularityValue = aCard.getProperty("PopularityIndex", "0"); + let popularityIndex = parseInt(popularityValue); + + // If we haven't parsed it the first time round, parse it as hexadecimal + // and repair so that we don't have to keep repairing. + if (isNaN(popularityIndex)) { + popularityIndex = parseInt(popularityValue, 16); + + // If its still NaN, just give up, we shouldn't ever get here. + if (isNaN(popularityIndex)) + popularityIndex = 0; + + // Now store this change so that we're not changing it each time around. + if (!aDirectory.readOnly) { + aCard.setProperty("PopularityIndex", popularityIndex); + try { + aDirectory.modifyCard(aCard); + } + catch (ex) { + Components.utils.reportError(e); + } + } + } + return popularityIndex; + }, + /** * Searches cards in the given directory. It is not expected to search against * email addresses (use _searchWithinEmails). If a card is matched (and isn't @@ -138,15 +173,15 @@ nsAbAutoCompleteSearch.prototype = { if (card instanceof Components.interfaces.nsIAbCard) { if (card.isMailList) - this._addToResult(commentColumn, card, "", result); + this._addToResult(commentColumn, directory, card, "", result); else { let email = card.primaryEmail; if (email) - this._addToResult(commentColumn, card, email, result); + this._addToResult(commentColumn, directory, card, email, result); email = card.getProperty("SecondEmail", ""); if (email) - this._addToResult(commentColumn, card, email, result); + this._addToResult(commentColumn, directory, card, email, result); } } } @@ -177,17 +212,17 @@ nsAbAutoCompleteSearch.prototype = { if (card instanceof Components.interfaces.nsIAbCard) { if (card.isMailList) - this._addToResult(commentColumn, card, "", result); + this._addToResult(commentColumn, directory, card, "", result); else { let email = card.primaryEmail; if (email && email.toLocaleLowerCase() .lastIndexOf(fullString, 0) == 0) - this._addToResult(commentColumn, card, email, result); + this._addToResult(commentColumn, directory, card, email, result); email = card.getProperty("SecondEmail", ""); if (email && email.toLocaleLowerCase() .lastIndexOf(fullString, 0) == 0) - this._addToResult(commentColumn, card, email, result); + this._addToResult(commentColumn, directory, card, email, result); } } } @@ -241,15 +276,17 @@ nsAbAutoCompleteSearch.prototype = { * will remove the existing element if the popularity of the new card is * higher than the previous card. * + * @param directory The directory that the card is in. * @param card The card that could be a duplicate. * @param emailAddress The emailAddress (name/address combination) to check * for duplicates against. * @param currentResults The current results list. */ - _checkDuplicate: function _checkDuplicate(card, emailAddress, currentResults) { + _checkDuplicate: function _checkDuplicate(directory, card, emailAddress, + currentResults) { var lcEmailAddress = emailAddress.toLocaleLowerCase(); - var popIndex = parseInt(card.getProperty("PopularityIndex", "0")); + var popIndex = this._getPopularityIndex(directory, card); for (var i = 0; i < currentResults._searchResults.length; ++i) { if (currentResults._searchResults[i].value.toLocaleLowerCase() == lcEmailAddress) @@ -275,12 +312,14 @@ nsAbAutoCompleteSearch.prototype = { * * @param commentColumn The text to be displayed in the comment column * (if any). + * @param directory The directory that the card is in. * @param card The card being added to the results. * @param emailToUse The email address from the card that should be used * for this result. * @param result The result to add the new entry to. */ - _addToResult: function _addToResult(commentColumn, card, emailToUse, result) { + _addToResult: function _addToResult(commentColumn, directory, card, + emailToUse, result) { var emailAddress = this._parser.makeFullAddress(card.displayName, card.isMailList ? @@ -295,13 +334,12 @@ nsAbAutoCompleteSearch.prototype = { // If it is a duplicate, then just return and don't add it. The // _checkDuplicate function deals with it all for us. - if (this._checkDuplicate(card, emailAddress, result)) + if (this._checkDuplicate(directory, card, emailAddress, result)) return; // Find out where to insert the card. var insertPosition = 0; - // Hack - mork adds in as a string, but we want to get as an integer... - var cardPopularityIndex = parseInt(card.getProperty("PopularityIndex", "0")); + var cardPopularityIndex = this._getPopularityIndex(directory, card); while (insertPosition < result._searchResults.length && cardPopularityIndex < diff --git a/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch5.js b/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch5.js new file mode 100644 index 0000000000..2b34a0e8a2 --- /dev/null +++ b/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch5.js @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * This suite ensures that we can correctly read and re-set the popularity + * indexes on a + */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +const ACR = Components.interfaces.nsIAutoCompleteResult; + +const results = [ { email: "d ", dirName: kPABData.dirName }, + { email: "di ", dirName: kPABData.dirName }, + { email: "dis ", dirName: kPABData.dirName }, + { email: "disp ", dirName: kPABData.dirName }, + { email: "displ ", dirName: kPABData.dirName }, + { email: "t ", dirName: kPABData.dirName }, + { email: "te ", dirName: kPABData.dirName }, + { email: "tes
  • ", dirName: kPABData.dirName }, + { email: "test ", dirName: kPABData.dirName } ]; + +const firstNames = [ { search: "f", expected: [4, 0, 1, 2, 3] }, + { search: "fi", expected: [4, 0, 1, 3] }, + { search: "fir", expected: [4, 0, 1] }, + { search: "firs", expected: [0, 1] }, + { search: "first", expected: [1] } ]; + +const lastNames = [ { search: "l", expected: [4, 0, 1, 2, 3, 5, 6, 7, 8] }, + { search: "la", expected: [4, 0, 2, 3] }, + { search: "las", expected: [4, 0, 3] }, + { search: "last", expected: [4, 0] }, + { search: "lastn", expected: [0] } ]; + +const inputs = [ firstNames, lastNames]; + +function acObserver() {} + +acObserver.prototype = { + _search: null, + _result: null, + + onSearchResult: function (aSearch, aResult) { + this._search = aSearch; + this._result = aResult; + } +}; + +function run_test() { + // Copy the data files into place + let testAB = do_get_file("../../mailnews/data/tb2hexpopularity.mab"); + + testAB.copyTo(gProfileDir, kPABData.fileName); + + // Test - Create a new search component + + let acs = Components.classes["@mozilla.org/autocomplete/search;1?name=addrbook"] + .getService(Components.interfaces.nsIAutoCompleteSearch); + + let obs = new acObserver(); + + // Ensure we've got the comment column set up for extra checking. + let prefSvc = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + + prefSvc.setIntPref("mail.autoComplete.commentColumn", 1); + + // Test - Matches + + // Now check multiple matches + function checkInputItem(element, index, array) { + print("Checking " + element.search); + acs.startSearch(element.search, null, null, obs); + + do_check_eq(obs._search, acs); + do_check_eq(obs._result.searchString, element.search); + do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS); + do_check_eq(obs._result.errorDescription, null); + do_check_eq(obs._result.matchCount, element.expected.length); + do_check_eq(obs._result.defaultIndex, 0); + + for (let i = 0; i < element.expected.length; ++i) { + do_check_eq(obs._result.getValueAt(i), results[element.expected[i]].email); + do_check_eq(obs._result.getCommentAt(i), results[element.expected[i]].dirName); + do_check_eq(obs._result.getStyleAt(i), "local-abook"); + do_check_eq(obs._result.getImageAt(i), ""); + + // Card at result number 4 is the one with the TB 2 popularity set as "a" + // in the file, so check that we're now setting the popularity to 10 + // and hence future tests don't have to convert it. + if (element.expected[i] == 4) { + let result = obs._result.QueryInterface(Ci.nsIAbAutoCompleteResult); + do_check_eq(result.getCardAt(i).getProperty("PopularityIndex", -1), 10); + } + } + } + function checkInputSet(element, index, array) { + element.forEach(checkInputItem); + } + + inputs.forEach(checkInputSet); +}; diff --git a/mailnews/compose/src/nsMsgCompose.cpp b/mailnews/compose/src/nsMsgCompose.cpp index cc57fbd644..a5ead6ace7 100644 --- a/mailnews/compose/src/nsMsgCompose.cpp +++ b/mailnews/compose/src/nsMsgCompose.cpp @@ -4790,9 +4790,33 @@ nsMsgCompose::CheckAndPopulateRecipients(PRBool aPopulateMailList, // bump the popularity index for this card since we are about to send e-mail to it PRUint32 popularityIndex = 0; - if (!readOnly && NS_SUCCEEDED(existingCard->GetPropertyAsUint32( - kPopularityIndexProperty, &popularityIndex))) + if (!readOnly) { + if (NS_FAILED(existingCard->GetPropertyAsUint32( + kPopularityIndexProperty, &popularityIndex))) + { + // TB 2 wrote the popularity value as hex, so if we get here, + // then we've probably got a hex value. We'll convert it back + // to decimal, as that's the best we can do. + + nsCString hexPopularity; + if (NS_SUCCEEDED(existingCard->GetPropertyAsAUTF8String(kPopularityIndexProperty, hexPopularity))) + { +#ifdef MOZILLA_INTERNAL_API + PRInt32 errorCode = 0; +#else + nsresult errorCode = NS_OK; +#endif + popularityIndex = hexPopularity.ToInteger(&errorCode, 16); + if (errorCode) + // We failed, just set it to zero. + popularityIndex = 0; + } + else + // We couldn't get it as a string either, so just reset to + // zero. + popularityIndex = 0; + } existingCard->SetPropertyAsUint32(kPopularityIndexProperty, ++popularityIndex); diff --git a/mailnews/compose/test/unit/test_nsMsgCompose3.js b/mailnews/compose/test/unit/test_nsMsgCompose3.js new file mode 100644 index 0000000000..642c768c0a --- /dev/null +++ b/mailnews/compose/test/unit/test_nsMsgCompose3.js @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for increasing the popularity of contacts via + * checkAndPopulateRecipients. + */ + +// We need the address book details for this test +load("../../mailnews/resources/abSetup.js"); + +const MsgComposeContractID = "@mozilla.org/messengercompose/compose;1"; +const MsgComposeParamsContractID = "@mozilla.org/messengercompose/composeparams;1"; +const MsgComposeFieldsContractID = "@mozilla.org/messengercompose/composefields;1"; + +const TESTS = [ + { + email: "em@test.invalid", + // TB 2 stored popularity as hex, so we need to check correct handling. + prePopularity: "a", + postPopularity: "11" + }, + { + email: "e@test.invalid", + prePopularity: "0", + postPopularity: "1" + }, + { + email: "e@test.invalid", + prePopularity: "1", + postPopularity: "2" + }, + { + email: "em@test.invalid", + prePopularity: "11", + postPopularity: "12" + } +]; + +function checkPopulate(aTo, aNonHTMLRecipients, aPreferMailOut, aCheckTo) +{ + let msgCompose = Cc[MsgComposeContractID].createInstance(Ci.nsIMsgCompose); + + // Set up some basic fields for compose. + let fields = Cc[MsgComposeFieldsContractID].createInstance(Ci.nsIMsgCompFields); + + fields.to = aTo; + + // Set up some params + let params = Cc[MsgComposeParamsContractID] + .createInstance(Ci.nsIMsgComposeParams); + + params.composeFields = fields; + + msgCompose.Initialize(null, params); + + let nonHTMLRecipients = new Object(); + + do_check_eq(msgCompose.checkAndPopulateRecipients(true, true, + nonHTMLRecipients), + aPreferMailOut); + + do_check_eq(fields.to, aCheckTo); + + do_check_eq(nonHTMLRecipients.value, aNonHTMLRecipients); +} + +function run_test() { + // Test setup - copy the data files into place + let testAB = do_get_file("../../mailnews/data/tb2hexpopularity.mab"); + + // Copy the file to the profile directory for a PAB + testAB.copyTo(gProfileDir, kPABData.fileName); + + // Check the popularity index on a couple of cards. + let abManager = Cc["@mozilla.org/abmanager;1"].getService(Ci.nsIAbManager); + + let AB = abManager.getDirectory(kPABData.URI); + + for (let i = 0; i < TESTS.length; ++i) { + let card = AB.cardForEmailAddress(TESTS[i].email); + do_check_true(!!card); + + // Thunderbird 2 stored its popularityIndexes as hex, hence when we read it + // now we're going to get a hex value. The AB has a value of "a". + do_check_eq(card.getProperty("PopularityIndex", -1), TESTS[i].prePopularity); + + // Call the check populate function. + checkPopulate(TESTS[i].email, TESTS[i].email, + Ci.nsIAbPreferMailFormat.unknown, TESTS[i].email); + + // Now we've run check populate, check the popularityIndex has increased. + card = AB.cardForEmailAddress(TESTS[i].email); + do_check_true(!!card); + + // Thunderbird 2 stored its popularityIndexes as hex, hence when we read it + // now we're going to get a hex value. The AB has a value of "a". + do_check_eq(card.getProperty("PopularityIndex", -1), TESTS[i].postPopularity); + } +}; diff --git a/mailnews/test/data/tb2hexpopularity.mab b/mailnews/test/data/tb2hexpopularity.mab new file mode 100644 index 0000000000..5f693a95e1 --- /dev/null +++ b/mailnews/test/data/tb2hexpopularity.mab @@ -0,0 +1,92 @@ +// +< <(a=c)> // (f=iso-8859-1) + (B8=Custom2)(B9=Custom3)(BA=Custom4)(BB=Notes)(BC=LastModifiedDate) + (BD=RecordKey)(BE=AddrCharSet)(BF=LastRecordKey) + (C0=ns:addrbk:db:table:kind:pab)(C1=ListName)(C2=ListNickName) + (C3=ListDescription)(C4=ListTotalAddresses)(C5=LowercaseListName) + (C6=ns:addrbk:db:table:kind:deleted) + (80=ns:addrbk:db:row:scope:card:all) + (81=ns:addrbk:db:row:scope:list:all) + (82=ns:addrbk:db:row:scope:data:all)(83=FirstName)(84=LastName) + (85=PhoneticFirstName)(86=PhoneticLastName)(87=DisplayName) + (88=NickName)(89=PrimaryEmail)(8A=LowercasePrimaryEmail) + (8B=SecondEmail)(8C=DefaultEmail)(8D=CardType)(8E=PreferMailFormat) + (8F=PopularityIndex)(90=AllowRemoteContent)(91=WorkPhone)(92=HomePhone) + (93=FaxNumber)(94=PagerNumber)(95=CellularNumber)(96=WorkPhoneType) + (97=HomePhoneType)(98=FaxNumberType)(99=PagerNumberType) + (9A=CellularNumberType)(9B=HomeAddress)(9C=HomeAddress2)(9D=HomeCity) + (9E=HomeState)(9F=HomeZipCode)(A0=HomeCountry)(A1=WorkAddress) + (A2=WorkAddress2)(A3=WorkCity)(A4=WorkState)(A5=WorkZipCode) + (A6=WorkCountry)(A7=JobTitle)(A8=Department)(A9=Company) + (AA=_AimScreenName)(AB=AnniversaryYear)(AC=AnniversaryMonth) + (AD=AnniversaryDay)(AE=SpouseName)(AF=FamilyName)(B0=DefaultAddress) + (B1=Category)(B2=WebPage1)(B3=WebPage2)(B4=BirthYear)(B5=BirthMonth) + (B6=BirthDay)(B7=Custom1)> + +<(A8=8)(81=firs)(82=lastn)(83=)(84=d)(85=ni)(86=ema@test.invalid)(80=0) + (87=1)(88=first)(89=l)(8A=di)(8B=nic)(8C=emai@test.invalid)(8D=2)(8E=f) + (8F=la)(90=dis)(91=nick)(92=email@test.invalid)(93=3)(94=fi)(95=las) + (96=disp)(97=nickn)(98=e@test.invalid)(99=4)(9A=fir)(9B=last)(9C=displ) + (9D=n)(9E=em@test.invalid)(9F=5)(A0=t)(A1=list)(A2=6)(A3=te)(A4=lis) + (A5=7)(A6=tes)(A7=li)> +{1:^80 {(k^C0:c)(s=9)} + [1:^82(^BF=8)] + [1(^83^81)(^84^82)(^85=)(^86=)(^87=d)(^88=ni)(^89^86)(^8A^86)(^8B=) + (^8C=)(^8D=)(^8E=0)(^8F=0)(^90=0)(^91=)(^92=)(^93=)(^94=)(^95=) + (^96=)(^97=)(^98=)(^99=)(^9A=)(^9B=)(^9C=)(^9D=)(^9E=)(^9F=)(^A0=) + (^A1=)(^A2=)(^A3=)(^A4=)(^A5=)(^A6=)(^A7=)(^A8=)(^A9=)(^AA=)(^AB=) + (^AC=)(^AD=)(^AE=)(^AF=)(^B0=)(^B1=)(^B2=)(^B3=)(^B4=)(^B5=)(^B6=) + (^B7=)(^B8=)(^B9=)(^BA=)(^BB=)(^BC=0)(^BD=1)] + [2(^83^88)(^84=l)(^85=)(^86=)(^87=di)(^88^8B)(^89^8C)(^8A^8C)(^8B=) + (^8C=)(^8D=)(^8E=0)(^8F=0)(^90=0)(^91=)(^92=)(^93=)(^94=)(^95=) + (^96=)(^97=)(^98=)(^99=)(^9A=)(^9B=)(^9C=)(^9D=)(^9E=)(^9F=)(^A0=) + (^A1=)(^A2=)(^A3=)(^A4=)(^A5=)(^A6=)(^A7=)(^A8=)(^A9=)(^AA=)(^AB=) + (^AC=)(^AD=)(^AE=)(^AF=)(^B0=)(^B1=)(^B2=)(^B3=)(^B4=)(^B5=)(^B6=) + (^B7=)(^B8=)(^B9=)(^BA=)(^BB=)(^BC=0)(^BD=2)] + [3(^83=f)(^84=la)(^85=)(^86=)(^87^90)(^88^91)(^89^92)(^8A^92)(^8B=) + (^8C=)(^8D=)(^8E=0)(^8F=0)(^90=0)(^91=)(^92=)(^93=)(^94=)(^95=) + (^96=)(^97=)(^98=)(^99=)(^9A=)(^9B=)(^9C=)(^9D=)(^9E=)(^9F=)(^A0=) + (^A1=)(^A2=)(^A3=)(^A4=)(^A5=)(^A6=)(^A7=)(^A8=)(^A9=)(^AA=)(^AB=) + (^AC=)(^AD=)(^AE=)(^AF=)(^B0=)(^B1=)(^B2=)(^B3=)(^B4=)(^B5=)(^B6=) + (^B7=)(^B8=)(^B9=)(^BA=)(^BB=)(^BC=0)(^BD=3)] + [4(^83=fi)(^84^95)(^85=)(^86=)(^87^96)(^88^97)(^89^98)(^8A^98)(^8B=) + (^8C=)(^8D=)(^8E=0)(^8F=0)(^90=0)(^91=)(^92=)(^93=)(^94=)(^95=) + (^96=)(^97=)(^98=)(^99=)(^9A=)(^9B=)(^9C=)(^9D=)(^9E=)(^9F=)(^A0=) + (^A1=)(^A2=)(^A3=)(^A4=)(^A5=)(^A6=)(^A7=)(^A8=)(^A9=)(^AA=)(^AB=) + (^AC=)(^AD=)(^AE=)(^AF=)(^B0=)(^B1=)(^B2=)(^B3=)(^B4=)(^B5=)(^B6=) + (^B7=)(^B8=)(^B9=)(^BA=)(^BB=)(^BC=0)(^BD=4)] + [5(^83^9A)(^84^9B)(^85=)(^86=)(^87^9C)(^88=n)(^89^9E)(^8A^9E)(^8B=) + (^8C=)(^8D=)(^8E=0)(^8F=1)(^90=0)(^91=)(^92=)(^93=)(^94=)(^95=) + (^96=)(^97=)(^98=)(^99=)(^9A=)(^9B=)(^9C=)(^9D=)(^9E=)(^9F=)(^A0=) + (^A1=)(^A2=)(^A3=)(^A4=)(^A5=)(^A6=)(^A7=)(^A8=)(^A9=)(^AA=)(^AB=) + (^AC=)(^AD=)(^AE=)(^AF=)(^B0=)(^B1=)(^B2=)(^B3=)(^B4=)(^B5=)(^B6=) + (^B7=)(^B8=)(^B9=)(^BA=)(^BB=)(^BC=0)(^BD=5)] + [1:^81(^C1=t)(^C5=t)(^C2=)(^C3^A1)(^C4=0)(^BD=6)] + [2:^81(^C1=te)(^C5=te)(^C2=)(^C3^A4)(^C4=0)(^BD=7)] + [3:^81(^C1^A6)(^C5^A6)(^C2=)(^C3=li)(^C4=0)(^BD=8)]} + +@$${7{@ + +<(AB=9)(A9=test)(AA=abcdef)> +{1:^80 {(k^C0:c)(s=9)} + [-4:^81(^C1^A9)(^C5^A9)(^C2^AA)(^C3=l)(^C4=0)(^BD=9)]} +[1:^82(^BF=9)] +@$$}7}@ + +@$${9{@ +<(AC=4b683ded)>[-5:^80(^83^9A)(^84^9B)(^85=)(^86=)(^87^9C)(^88=n)(^89^9E) + (^8A^9E)(^8B=)(^8C=)(^8D=)(^8E=0)(^8F=9)(^90=0)(^91=)(^92=)(^93=) + (^94=)(^95=)(^96=)(^97=)(^98=)(^99=)(^9A=)(^9B=)(^9C=)(^9D=)(^9E=) + (^9F=)(^A0=)(^A1=)(^A2=)(^A3=)(^A4=)(^A5=)(^A6=)(^A7=)(^A8=)(^A9=) + (^AA=)(^AB=)(^AC=)(^AD=)(^AE=)(^AF=)(^B0=)(^B1=)(^B2=)(^B3=)(^B4=) + (^B5=)(^B6=)(^B7=)(^B8=)(^B9=)(^BA=)(^BB=)(^BC^AC)(^BD=5)] +@$$}9}@ + +@$${B{@ +<(AD=a)(AE=4b6840f5)>[-5:^80(^83^9A)(^84^9B)(^85=)(^86=)(^87^9C)(^88=n) + (^89^9E)(^8A^9E)(^8B=)(^8C=)(^8D=)(^8E=0)(^8F=a)(^90=0)(^91=)(^92=) + (^93=)(^94=)(^95=)(^96=)(^97=)(^98=)(^99=)(^9A=)(^9B=)(^9C=)(^9D=) + (^9E=)(^9F=)(^A0=)(^A1=)(^A2=)(^A3=)(^A4=)(^A5=)(^A6=)(^A7=)(^A8=) + (^A9=)(^AA=)(^AB=)(^AC=)(^AD=)(^AE=)(^AF=)(^B0=)(^B1=)(^B2=)(^B3=) + (^B4=)(^B5=)(^B6=)(^B7=)(^B8=)(^B9=)(^BA=)(^BB=)(^BC^AE)(^BD=5)] +@$$}B}@