summaryrefslogtreecommitdiffstats
path: root/mailnews/base/src/nsMsgTagService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/src/nsMsgTagService.cpp')
-rw-r--r--mailnews/base/src/nsMsgTagService.cpp560
1 files changed, 560 insertions, 0 deletions
diff --git a/mailnews/base/src/nsMsgTagService.cpp b/mailnews/base/src/nsMsgTagService.cpp
new file mode 100644
index 000000000..75480c21a
--- /dev/null
+++ b/mailnews/base/src/nsMsgTagService.cpp
@@ -0,0 +1,560 @@
+/* -*- 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<const char* const*>(aTagPref1),
+ *static_cast<const char* const*>(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<nsIPrefService> 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 "<key>.<tag_data_type>", 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<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);
+ 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 <key>.<info=tag|color|ordinal>
+ 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<nsISupportsString> 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<nsISupportsString> 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<nsIPrefBranch> prefRoot(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ nsCOMPtr<nsIPrefLocalizedString> 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;
+}