/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "msgCore.h" #include "nsMsgTagService.h" #include "nsMsgBaseCID.h" #include "nsIPrefService.h" #include "nsISupportsPrimitives.h" #include "nsMsgI18N.h" #include "nsIPrefLocalizedString.h" #include "nsMsgDBView.h" // for labels migration #include "nsQuickSort.h" #include "nsMsgUtils.h" #include "nsComponentManagerUtils.h" #include "nsServiceManagerUtils.h" #include "nsMemory.h" #define STRLEN(s) (sizeof(s) - 1) #define TAG_PREF_VERSION "version" #define TAG_PREF_SUFFIX_TAG ".tag" #define TAG_PREF_SUFFIX_COLOR ".color" #define TAG_PREF_SUFFIX_ORDINAL ".ordinal" #define TAG_CMP_LESSER -1 #define TAG_CMP_EQUAL 0 #define TAG_CMP_GREATER 1 static bool gMigratingKeys = false; // comparison functions for nsQuickSort static int CompareMsgTagKeys(const void* aTagPref1, const void* aTagPref2, void* aData) { return strcmp(*static_cast(aTagPref1), *static_cast(aTagPref2)); } static int CompareMsgTags(const void* aTagPref1, const void* aTagPref2, void* aData) { // Sort nsMsgTag objects by ascending order, using their ordinal or key. // The "smallest" value will be first in the sorted array, // thus being the most important element. nsMsgTag *element1 = *(nsMsgTag**) aTagPref1; nsMsgTag *element2 = *(nsMsgTag**) aTagPref2; // if we have only one element, it wins if (!element1 && !element2) return TAG_CMP_EQUAL; if (!element2) return TAG_CMP_LESSER; if (!element1) return TAG_CMP_GREATER; // only use the key if the ordinal is not defined or empty nsAutoCString value1, value2; element1->GetOrdinal(value1); if (value1.IsEmpty()) element1->GetKey(value1); element2->GetOrdinal(value2); if (value2.IsEmpty()) element2->GetKey(value2); return strcmp(value1.get(), value2.get()); } // // nsMsgTag // NS_IMPL_ISUPPORTS(nsMsgTag, nsIMsgTag) nsMsgTag::nsMsgTag(const nsACString &aKey, const nsAString &aTag, const nsACString &aColor, const nsACString &aOrdinal) : mTag(aTag), mKey(aKey), mColor(aColor), mOrdinal(aOrdinal) { } nsMsgTag::~nsMsgTag() { } /* readonly attribute ACString key; */ NS_IMETHODIMP nsMsgTag::GetKey(nsACString & aKey) { aKey = mKey; return NS_OK; } /* readonly attribute AString tag; */ NS_IMETHODIMP nsMsgTag::GetTag(nsAString & aTag) { aTag = mTag; return NS_OK; } /* readonly attribute ACString color; */ NS_IMETHODIMP nsMsgTag::GetColor(nsACString & aColor) { aColor = mColor; return NS_OK; } /* readonly attribute ACString ordinal; */ NS_IMETHODIMP nsMsgTag::GetOrdinal(nsACString & aOrdinal) { aOrdinal = mOrdinal; return NS_OK; } // // nsMsgTagService // NS_IMPL_ISUPPORTS(nsMsgTagService, nsIMsgTagService) nsMsgTagService::nsMsgTagService() { m_tagPrefBranch = nullptr; nsCOMPtr prefService(do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefService) prefService->GetBranch("mailnews.tags.", getter_AddRefs(m_tagPrefBranch)); // need to figure out how to migrate the tags only once. MigrateLabelsToTags(); RefreshKeyCache(); } nsMsgTagService::~nsMsgTagService() { /* destructor code */ } /* wstring getTagForKey (in string key); */ NS_IMETHODIMP nsMsgTagService::GetTagForKey(const nsACString &key, nsAString &_retval) { nsAutoCString prefName(key); if (!gMigratingKeys) ToLowerCase(prefName); prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG); return GetUnicharPref(prefName.get(), _retval); } /* void setTagForKey (in string key); */ NS_IMETHODIMP nsMsgTagService::SetTagForKey(const nsACString &key, const nsAString &tag ) { nsAutoCString prefName(key); ToLowerCase(prefName); prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG); return SetUnicharPref(prefName.get(), tag); } /* void getKeyForTag (in wstring tag); */ NS_IMETHODIMP nsMsgTagService::GetKeyForTag(const nsAString &aTag, nsACString &aKey) { uint32_t count; char **prefList; nsresult rv = m_tagPrefBranch->GetChildList("", &count, &prefList); NS_ENSURE_SUCCESS(rv, rv); // traverse the list, and look for a pref with the desired tag value. for (uint32_t i = count; i--;) { // We are returned the tag prefs in the form ".", but // since we only want the tags, just check that the string ends with "tag". nsDependentCString prefName(prefList[i]); if (StringEndsWith(prefName, NS_LITERAL_CSTRING(TAG_PREF_SUFFIX_TAG))) { nsAutoString curTag; GetUnicharPref(prefList[i], curTag); if (aTag.Equals(curTag)) { aKey = Substring(prefName, 0, prefName.Length() - STRLEN(TAG_PREF_SUFFIX_TAG)); break; } } } NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, prefList); ToLowerCase(aKey); return NS_OK; } /* ACString getTopKey (in ACString keylist); */ NS_IMETHODIMP nsMsgTagService::GetTopKey(const nsACString & keyList, nsACString & _retval) { _retval.Truncate(); // find the most important key nsTArray keyArray; ParseString(keyList, ' ', keyArray); uint32_t keyCount = keyArray.Length(); nsCString *topKey = nullptr, *key, topOrdinal, ordinal; for (uint32_t i = 0; i < keyCount; ++i) { key = &keyArray[i]; if (key->IsEmpty()) continue; // ignore unknown keywords nsAutoString tagValue; nsresult rv = GetTagForKey(*key, tagValue); if (NS_FAILED(rv) || tagValue.IsEmpty()) continue; // new top key, judged by ordinal order? rv = GetOrdinalForKey(*key, ordinal); if (NS_FAILED(rv) || ordinal.IsEmpty()) ordinal = *key; if ((ordinal < topOrdinal) || topOrdinal.IsEmpty()) { topOrdinal = ordinal; topKey = key; // copy actual result key only once - later } } // return the most important key - if any if (topKey) _retval = *topKey; return NS_OK; } /* void addTagForKey (in string key, in wstring tag, in string color, in string ordinal); */ NS_IMETHODIMP nsMsgTagService::AddTagForKey(const nsACString &key, const nsAString &tag, const nsACString &color, const nsACString &ordinal) { nsAutoCString prefName(key); ToLowerCase(prefName); prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG); nsresult rv = SetUnicharPref(prefName.get(), tag); NS_ENSURE_SUCCESS(rv, rv); rv = SetColorForKey(key, color); NS_ENSURE_SUCCESS(rv, rv); rv = RefreshKeyCache(); NS_ENSURE_SUCCESS(rv, rv); return SetOrdinalForKey(key, ordinal); } /* void addTag (in wstring tag, in long color); */ NS_IMETHODIMP nsMsgTagService::AddTag(const nsAString &tag, const nsACString &color, const nsACString &ordinal) { // figure out key from tag. Apply transformation stripping out // illegal characters like and then convert to imap mod utf7. // Then, check if we have a tag with that key yet, and if so, // make it unique by appending A, AA, etc. // Should we use an iterator? nsAutoString transformedTag(tag); MsgReplaceChar(transformedTag, " ()/{%*<>\\\"", '_'); nsAutoCString key; CopyUTF16toMUTF7(transformedTag, key); // We have an imap server that converts keys to upper case so we're going // to normalize all keys to lower case (upper case looks ugly in prefs.js) ToLowerCase(key); nsAutoCString prefName(key); while (true) { nsAutoString tagValue; nsresult rv = GetTagForKey(prefName, tagValue); if (NS_FAILED(rv) || tagValue.IsEmpty() || tagValue.Equals(tag)) return AddTagForKey(prefName, tag, color, ordinal); prefName.Append('A'); } NS_ASSERTION(false, "can't get here"); return NS_ERROR_FAILURE; } /* long getColorForKey (in string key); */ NS_IMETHODIMP nsMsgTagService::GetColorForKey(const nsACString &key, nsACString &_retval) { nsAutoCString prefName(key); if (!gMigratingKeys) ToLowerCase(prefName); prefName.AppendLiteral(TAG_PREF_SUFFIX_COLOR); nsCString color; nsresult rv = m_tagPrefBranch->GetCharPref(prefName.get(), getter_Copies(color)); if (NS_SUCCEEDED(rv)) _retval = color; return NS_OK; } /* void setColorForKey (in ACString key, in ACString color); */ NS_IMETHODIMP nsMsgTagService::SetColorForKey(const nsACString & key, const nsACString & color) { nsAutoCString prefName(key); ToLowerCase(prefName); prefName.AppendLiteral(TAG_PREF_SUFFIX_COLOR); if (color.IsEmpty()) { m_tagPrefBranch->ClearUserPref(prefName.get()); return NS_OK; } return m_tagPrefBranch->SetCharPref(prefName.get(), nsCString(color).get()); } /* ACString getOrdinalForKey (in ACString key); */ NS_IMETHODIMP nsMsgTagService::GetOrdinalForKey(const nsACString & key, nsACString & _retval) { nsAutoCString prefName(key); if (!gMigratingKeys) ToLowerCase(prefName); prefName.AppendLiteral(TAG_PREF_SUFFIX_ORDINAL); nsCString ordinal; nsresult rv = m_tagPrefBranch->GetCharPref(prefName.get(), getter_Copies(ordinal)); _retval = ordinal; return rv; } /* void setOrdinalForKey (in ACString key, in ACString ordinal); */ NS_IMETHODIMP nsMsgTagService::SetOrdinalForKey(const nsACString & key, const nsACString & ordinal) { nsAutoCString prefName(key); ToLowerCase(prefName); prefName.AppendLiteral(TAG_PREF_SUFFIX_ORDINAL); if (ordinal.IsEmpty()) { m_tagPrefBranch->ClearUserPref(prefName.get()); return NS_OK; } return m_tagPrefBranch->SetCharPref(prefName.get(), nsCString(ordinal).get()); } /* void deleteTag (in wstring tag); */ NS_IMETHODIMP nsMsgTagService::DeleteKey(const nsACString &key) { // clear the associated prefs nsAutoCString prefName(key); if (!gMigratingKeys) ToLowerCase(prefName); nsresult rv = m_tagPrefBranch->DeleteBranch(prefName.get()); NS_ENSURE_SUCCESS(rv, rv); return RefreshKeyCache(); } /* void getAllTags (out unsigned long count, [array, size_is (count), retval] out nsIMsgTag tagArray); */ NS_IMETHODIMP nsMsgTagService::GetAllTags(uint32_t *aCount, nsIMsgTag ***aTagArray) { NS_ENSURE_ARG_POINTER(aCount); NS_ENSURE_ARG_POINTER(aTagArray); // preset harmless default values *aCount = 0; *aTagArray = nullptr; // get the actual tag definitions nsresult rv; uint32_t prefCount; char **prefList; rv = m_tagPrefBranch->GetChildList("", &prefCount, &prefList); NS_ENSURE_SUCCESS(rv, rv); // sort them by key for ease of processing NS_QuickSort(prefList, prefCount, sizeof(char*), CompareMsgTagKeys, nullptr); // build an array of nsIMsgTag elements from the orderered list // it's at max the same size as the preflist, but usually only about half nsIMsgTag** tagArray = (nsIMsgTag**) NS_Alloc(sizeof(nsIMsgTag*) * prefCount); if (!tagArray) { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList); return NS_ERROR_OUT_OF_MEMORY; } uint32_t currentTagIndex = 0; nsMsgTag *newMsgTag; nsString tag; nsCString lastKey, color, ordinal; for (uint32_t i = prefCount; i--;) { // extract just the key from . char *info = strrchr(prefList[i], '.'); if (info) { nsAutoCString key(Substring(prefList[i], info)); if (key != lastKey) { if (!key.IsEmpty()) { // .tag MUST exist (but may be empty) rv = GetTagForKey(key, tag); if (NS_SUCCEEDED(rv)) { // .color MAY exist color.Truncate(); GetColorForKey(key, color); // .ordinal MAY exist rv = GetOrdinalForKey(key, ordinal); if (NS_FAILED(rv)) ordinal.Truncate(); // store the tag info in our array newMsgTag = new nsMsgTag(key, tag, color, ordinal); if (!newMsgTag) { NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(currentTagIndex, tagArray); NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList); return NS_ERROR_OUT_OF_MEMORY; } NS_ADDREF(tagArray[currentTagIndex++] = newMsgTag); } } lastKey = key; } } } NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList); // sort the non-null entries by ordinal NS_QuickSort(tagArray, currentTagIndex, sizeof(nsMsgTag*), CompareMsgTags, nullptr); // All done, now return the values (the idl's size_is(count) parameter // ensures that the array is cut accordingly). *aCount = currentTagIndex; *aTagArray = tagArray; return NS_OK; } nsresult nsMsgTagService::SetUnicharPref(const char *prefName, const nsAString &val) { nsresult rv = NS_OK; if (!val.IsEmpty()) { nsCOMPtr supportsString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); if (supportsString) { supportsString->SetData(val); rv = m_tagPrefBranch->SetComplexValue(prefName, NS_GET_IID(nsISupportsString), supportsString); } } else { m_tagPrefBranch->ClearUserPref(prefName); } return rv; } nsresult nsMsgTagService::GetUnicharPref(const char *prefName, nsAString &prefValue) { nsresult rv; nsCOMPtr supportsString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); if (supportsString) { rv = m_tagPrefBranch->GetComplexValue(prefName, NS_GET_IID(nsISupportsString), getter_AddRefs(supportsString)); if (supportsString) rv = supportsString->GetData(prefValue); else prefValue.Truncate(); } return rv; } nsresult nsMsgTagService::MigrateLabelsToTags() { nsCString prefString; int32_t prefVersion = 0; nsresult rv = m_tagPrefBranch->GetIntPref(TAG_PREF_VERSION, &prefVersion); if (NS_SUCCEEDED(rv) && prefVersion > 1) return rv; else if (prefVersion == 1) { gMigratingKeys = true; // need to convert the keys to lower case nsIMsgTag **tagArray; uint32_t numTags; GetAllTags(&numTags, &tagArray); for (uint32_t tagIndex = 0; tagIndex < numTags; tagIndex++) { nsAutoCString key, color, ordinal; nsAutoString tagStr; nsIMsgTag *tag = tagArray[tagIndex]; tag->GetKey(key); tag->GetTag(tagStr); tag->GetOrdinal(ordinal); tag->GetColor(color); DeleteKey(key); ToLowerCase(key); AddTagForKey(key, tagStr, color, ordinal); } NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numTags, tagArray); gMigratingKeys = false; } else { nsCOMPtr prefRoot(do_GetService(NS_PREFSERVICE_CONTRACTID)); nsCOMPtr pls; nsString ucsval; nsAutoCString labelKey("$label1"); for(int32_t i = 0; i < PREF_LABELS_MAX; ) { prefString.Assign(PREF_LABELS_DESCRIPTION); prefString.AppendInt(i + 1); rv = prefRoot->GetComplexValue(prefString.get(), NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls)); NS_ENSURE_SUCCESS(rv, rv); pls->ToString(getter_Copies(ucsval)); prefString.Assign(PREF_LABELS_COLOR); prefString.AppendInt(i + 1); nsCString csval; rv = prefRoot->GetCharPref(prefString.get(), getter_Copies(csval)); NS_ENSURE_SUCCESS(rv, rv); rv = AddTagForKey(labelKey, ucsval, csval, EmptyCString()); NS_ENSURE_SUCCESS(rv, rv); labelKey.SetCharAt(++i + '1', 6); } } m_tagPrefBranch->SetIntPref(TAG_PREF_VERSION, 2); return rv; } NS_IMETHODIMP nsMsgTagService::IsValidKey(const nsACString &aKey, bool *aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = m_keys.Contains(aKey); return NS_OK; } // refresh the local tag key array m_keys from preferences nsresult nsMsgTagService::RefreshKeyCache() { nsIMsgTag **tagArray; uint32_t numTags; nsresult rv = GetAllTags(&numTags, &tagArray); NS_ENSURE_SUCCESS(rv, rv); m_keys.Clear(); for (uint32_t tagIndex = 0; tagIndex < numTags; tagIndex++) { nsIMsgTag *tag = tagArray[tagIndex]; if (!tag) { rv = NS_ERROR_FAILURE; break; } nsAutoCString key; tag->GetKey(key); if (!m_keys.InsertElementAt(tagIndex, key)) { rv = NS_ERROR_FAILURE; break; } } NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numTags, tagArray); return rv; }