diff options
Diffstat (limited to 'comm/mailnews/base/src/nsMsgTagService.cpp')
-rw-r--r-- | comm/mailnews/base/src/nsMsgTagService.cpp | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/comm/mailnews/base/src/nsMsgTagService.cpp b/comm/mailnews/base/src/nsMsgTagService.cpp new file mode 100644 index 0000000000..83ced5fdeb --- /dev/null +++ b/comm/mailnews/base/src/nsMsgTagService.cpp @@ -0,0 +1,458 @@ +/* -*- 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 "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" + +static bool gMigratingKeys = false; + +// Comparator to set sort order in GetAllTags(). +struct CompareMsgTags { + private: + int cmp(RefPtr<nsIMsgTag> element1, RefPtr<nsIMsgTag> element2) const { + // 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. + + // 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()); + } + + public: + bool Equals(RefPtr<nsIMsgTag> element1, RefPtr<nsIMsgTag> element2) const { + return cmp(element1, element2) == 0; + } + bool LessThan(RefPtr<nsIMsgTag> element1, RefPtr<nsIMsgTag> element2) const { + return cmp(element1, element2) < 0; + } +}; + +// +// 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<nsIPrefService> prefService( + do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefService) + prefService->GetBranch("mailnews.tags.", getter_AddRefs(m_tagPrefBranch)); + SetupLabelTags(); + 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) { + nsTArray<nsCString> prefList; + nsresult rv = m_tagPrefBranch->GetChildList("", prefList); + NS_ENSURE_SUCCESS(rv, rv); + // traverse the list, and look for a pref with the desired tag value. + // XXXbz is there a good reason to reverse the list here, or did the + // old code do it just to be clever and save some characters in the + // for loop header? + for (auto& prefName : mozilla::Reversed(prefList)) { + // We are returned the tag prefs in the form "<key>.<tag_data_type>", but + // since we only want the tags, just check that the string ends with "tag". + if (StringEndsWith(prefName, nsLiteralCString(TAG_PREF_SUFFIX_TAG))) { + nsAutoString curTag; + GetUnicharPref(prefName.get(), curTag); + if (aTag.Equals(curTag)) { + aKey = Substring(prefName, 0, + prefName.Length() - STRLEN(TAG_PREF_SUFFIX_TAG)); + break; + } + } + } + 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<nsCString> 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 <SP> 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); + transformedTag.ReplaceChar(u" ()/{%*<>\\\"", u'_'); + 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(), color); + if (NS_SUCCEEDED(rv)) _retval = color; + return NS_OK; +} + +/* long getSelectorForKey (in ACString key, out AString selector); */ +NS_IMETHODIMP nsMsgTagService::GetSelectorForKey(const nsACString& key, + nsAString& _retval) { + // Our keys are the result of MUTF-7 encoding. For CSS selectors we need + // to reduce this to 0-9A-Za-z_ with a leading alpha character. + // We encode non-alphanumeric characters using _ as an escape character + // and start with a leading T in all cases. This way users defining tags + // "selected" or "focus" don't collide with inbuilt "selected" or "focus". + + // Calculate length of selector string. + const char* in = key.BeginReading(); + size_t outLen = 1; + while (*in) { + if (('0' <= *in && *in <= '9') || ('A' <= *in && *in <= 'Z') || + ('a' <= *in && *in <= 'z')) { + outLen++; + } else { + outLen += 3; + } + in++; + } + + // Now fill selector string. + _retval.SetCapacity(outLen); + _retval.Assign('T'); + in = key.BeginReading(); + while (*in) { + if (('0' <= *in && *in <= '9') || ('A' <= *in && *in <= 'Z') || + ('a' <= *in && *in <= 'z')) { + _retval.Append(*in); + } else { + _retval.AppendPrintf("_%02x", *in); + } + in++; + } + + 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(), color); +} + +/* 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(), 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(), ordinal); +} + +/* void deleteTag (in wstring tag); */ +NS_IMETHODIMP nsMsgTagService::DeleteKey(const nsACString& key) { + // clear the associated prefs + nsAutoCString prefName(key); + if (!gMigratingKeys) ToLowerCase(prefName); + prefName.Append('.'); + + nsTArray<nsCString> prefNames; + nsresult rv = m_tagPrefBranch->GetChildList(prefName.get(), prefNames); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto& prefName : prefNames) { + m_tagPrefBranch->ClearUserPref(prefName.get()); + } + + return RefreshKeyCache(); +} + +/* Array<nsIMsgTag> getAllTags(); */ +NS_IMETHODIMP nsMsgTagService::GetAllTags( + nsTArray<RefPtr<nsIMsgTag>>& aTagArray) { + aTagArray.Clear(); + + // get the actual tag definitions + nsresult rv; + nsTArray<nsCString> prefList; + rv = m_tagPrefBranch->GetChildList("", prefList); + NS_ENSURE_SUCCESS(rv, rv); + // sort them by key for ease of processing + prefList.Sort(); + + nsString tag; + nsCString lastKey, color, ordinal; + for (auto& pref : mozilla::Reversed(prefList)) { + // extract just the key from <key>.<info=tag|color|ordinal> + int32_t dotLoc = pref.RFindChar('.'); + if (dotLoc != kNotFound) { + auto& key = Substring(pref, 0, dotLoc); + 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 + aTagArray.AppendElement(new nsMsgTag(key, tag, color, ordinal)); + } + } + lastKey = key; + } + } + } + + // sort the non-null entries by ordinal + aTagArray.Sort(CompareMsgTags()); + return NS_OK; +} + +nsresult nsMsgTagService::SetUnicharPref(const char* prefName, + const nsAString& val) { + nsresult rv = NS_OK; + if (!val.IsEmpty()) { + rv = m_tagPrefBranch->SetStringPref(prefName, NS_ConvertUTF16toUTF8(val)); + } else { + m_tagPrefBranch->ClearUserPref(prefName); + } + return rv; +} + +nsresult nsMsgTagService::GetUnicharPref(const char* prefName, + nsAString& prefValue) { + nsCString valueUtf8; + nsresult rv = + m_tagPrefBranch->GetStringPref(prefName, EmptyCString(), 0, valueUtf8); + CopyUTF8toUTF16(valueUtf8, prefValue); + return rv; +} + +nsresult nsMsgTagService::SetupLabelTags() { + nsCString prefString; + + int32_t prefVersion = 0; + nsresult rv = m_tagPrefBranch->GetIntPref(TAG_PREF_VERSION, &prefVersion); + if (NS_SUCCEEDED(rv) && prefVersion > 1) { + return rv; + } + nsCOMPtr<nsIPrefBranch> prefRoot(do_GetService(NS_PREFSERVICE_CONTRACTID)); + nsCOMPtr<nsIPrefLocalizedString> pls; + nsString ucsval; + nsAutoCString labelKey("$label1"); + for (int32_t i = 0; i < 5;) { + prefString.AssignLiteral("mailnews.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.AssignLiteral("mailnews.labels.color."); + prefString.AppendInt(i + 1); + nsCString csval; + rv = prefRoot->GetCharPref(prefString.get(), 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() { + nsTArray<RefPtr<nsIMsgTag>> tagArray; + nsresult rv = GetAllTags(tagArray); + NS_ENSURE_SUCCESS(rv, rv); + m_keys.Clear(); + + uint32_t numTags = tagArray.Length(); + m_keys.SetCapacity(numTags); + for (uint32_t tagIndex = 0; tagIndex < numTags; tagIndex++) { + nsAutoCString key; + tagArray[tagIndex]->GetKey(key); + m_keys.InsertElementAt(tagIndex, key); + } + return rv; +} |