summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/src/nsMsgTagService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/base/src/nsMsgTagService.cpp')
-rw-r--r--comm/mailnews/base/src/nsMsgTagService.cpp458
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;
+}