Back to index

lightning-sunbird  0.9+nobinonly
nsMsgTagService.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 2006
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   David Bienvenu <bienvenu@mozilla.org>
00024  *   Karsten Düsterloh <mnyromyr@tprac.de>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "msgCore.h"
00041 #include "nsMsgTagService.h"
00042 #include "nsMsgBaseCID.h"
00043 #include "nsIPrefService.h"
00044 #include "nsISupportsPrimitives.h"
00045 #include "nsMsgI18N.h"
00046 #include "nsIPrefLocalizedString.h"
00047 #include "nsMsgDBView.h" // for labels migration
00048 #include "nsQuickSort.h"
00049 
00050 #define STRLEN(s) (sizeof(s) - 1)
00051 
00052 #define TAG_PREF_VERSION        "version"
00053 #define TAG_PREF_SUFFIX_TAG     ".tag"
00054 #define TAG_PREF_SUFFIX_COLOR   ".color"
00055 #define TAG_PREF_SUFFIX_ORDINAL ".ordinal"
00056 #define TAG_CMP_LESSER          -1
00057 #define TAG_CMP_EQUAL           0
00058 #define TAG_CMP_GREATER         1
00059 
00060 static PRBool gMigratingKeys = PR_FALSE;
00061 
00062 // comparison functions for nsQuickSort
00063 PR_STATIC_CALLBACK(int)
00064 CompareMsgTagKeys(const void* aTagPref1, const void* aTagPref2, void* aData)
00065 {
00066   return strcmp(*NS_STATIC_CAST(const char* const*, aTagPref1),
00067                 *NS_STATIC_CAST(const char* const*, aTagPref2));
00068 }
00069 
00070 PR_STATIC_CALLBACK(int)
00071 CompareMsgTags(const void* aTagPref1, const void* aTagPref2, void* aData)
00072 {
00073   // Sort nsMsgTag objects by ascending order, using their ordinal or key.
00074   // The "smallest" value will be first in the sorted array,
00075   // thus being the most important element.
00076   nsMsgTag *element1 = *(nsMsgTag**) aTagPref1;
00077   nsMsgTag *element2 = *(nsMsgTag**) aTagPref2;
00078 
00079   // if we have only one element, it wins
00080   if (!element1 && !element2)
00081     return TAG_CMP_EQUAL;
00082   if (!element2)
00083     return TAG_CMP_LESSER;
00084   if (!element1)
00085     return TAG_CMP_GREATER;
00086 
00087   // only use the key if the ordinal is not defined or empty
00088   nsCAutoString value1, value2;
00089   element1->GetOrdinal(value1);
00090   if (value1.IsEmpty())
00091     element1->GetKey(value1);
00092   element2->GetOrdinal(value2);
00093   if (value2.IsEmpty())
00094     element2->GetKey(value2);
00095 
00096   return strcmp(value1.get(), value2.get());
00097 }
00098 
00099 
00100 //
00101 //  nsMsgTag
00102 //
00103 NS_IMPL_ISUPPORTS1(nsMsgTag, nsIMsgTag)
00104 
00105 nsMsgTag::nsMsgTag(const nsACString &aKey,
00106                    const nsAString  &aTag,
00107                    const nsACString &aColor,
00108                    const nsACString &aOrdinal)
00109 : mTag(aTag),
00110   mKey(aKey),
00111   mColor(aColor),
00112   mOrdinal(aOrdinal)
00113 {
00114 }
00115 
00116 nsMsgTag::~nsMsgTag()
00117 {
00118 }
00119 
00120 /* readonly attribute ACString key; */
00121 NS_IMETHODIMP nsMsgTag::GetKey(nsACString & aKey)
00122 {
00123   aKey = mKey;
00124   return NS_OK;
00125 }
00126 
00127 /* readonly attribute AString tag; */
00128 NS_IMETHODIMP nsMsgTag::GetTag(nsAString & aTag)
00129 {
00130   aTag = mTag;
00131   return NS_OK;
00132 }
00133 
00134 /* readonly attribute ACString color; */
00135 NS_IMETHODIMP nsMsgTag::GetColor(nsACString & aColor)
00136 {
00137   aColor = mColor;
00138   return NS_OK;
00139 }
00140 
00141 /* readonly attribute ACString ordinal; */
00142 NS_IMETHODIMP nsMsgTag::GetOrdinal(nsACString & aOrdinal)
00143 {
00144   aOrdinal = mOrdinal;
00145   return NS_OK;
00146 }
00147 
00148 
00149 //
00150 //  nsMsgTagService
00151 //
00152 NS_IMPL_ISUPPORTS1(nsMsgTagService, nsIMsgTagService)
00153 
00154 nsMsgTagService::nsMsgTagService()
00155 {
00156   m_tagPrefBranch = nsnull;
00157   nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID));
00158   if (prefService)
00159     prefService->GetBranch("mailnews.tags.", getter_AddRefs(m_tagPrefBranch));
00160   // need to figure out how to migrate the tags only once.
00161   MigrateLabelsToTags();
00162 }
00163 
00164 nsMsgTagService::~nsMsgTagService()
00165 {
00166   /* destructor code */
00167 }
00168 
00169 /* wstring getTagForKey (in string key); */
00170 NS_IMETHODIMP nsMsgTagService::GetTagForKey(const nsACString &key, nsAString &_retval)
00171 {
00172   nsCAutoString prefName(key);
00173   if (!gMigratingKeys)
00174     ToLowerCase(prefName);
00175   prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG);
00176   return GetUnicharPref(prefName.get(), _retval);
00177 }
00178 
00179 /* void setTagForKey (in string key); */
00180 NS_IMETHODIMP nsMsgTagService::SetTagForKey(const nsACString &key, const nsAString &tag )
00181 {
00182   nsCAutoString prefName(key);
00183   ToLowerCase(prefName);
00184   prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG);
00185   return SetUnicharPref(prefName.get(), tag);
00186 }
00187 
00188 /* void getKeyForTag (in wstring tag); */
00189 NS_IMETHODIMP nsMsgTagService::GetKeyForTag(const nsAString &aTag, nsACString &aKey)
00190 {
00191   PRUint32 count;
00192   char **prefList;
00193   nsresult rv = m_tagPrefBranch->GetChildList("", &count, &prefList);
00194   NS_ENSURE_SUCCESS(rv, rv);
00195   // traverse the list, and look for a pref with the desired tag value.
00196   for (PRUint32 i = count; i--;)
00197   {
00198     // We are returned the tag prefs in the form "<key>.<tag_data_type>", but
00199     // since we only want the tags, just check that the string ends with "tag".
00200     nsDependentCString prefName(prefList[i]);
00201     if (StringEndsWith(prefName, NS_LITERAL_CSTRING(TAG_PREF_SUFFIX_TAG)))
00202     {
00203       nsAutoString curTag;
00204       GetUnicharPref(prefList[i], curTag);
00205       if (aTag.Equals(curTag))
00206       {
00207         aKey = Substring(prefName, 0, prefName.Length() - STRLEN(TAG_PREF_SUFFIX_TAG));
00208         break;
00209       }
00210     }
00211   }
00212   NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, prefList);
00213   ToLowerCase(aKey);
00214   return NS_OK;
00215 }
00216 
00217 /* ACString getTopKey (in string keylist); */
00218 NS_IMETHODIMP nsMsgTagService::GetTopKey(const char * keyList, nsACString & _retval)
00219 {
00220   _retval.Truncate();
00221   // find the most important key
00222   nsCStringArray keyArray;
00223   keyArray.ParseString(keyList, " ");
00224   PRUint32 keyCount = keyArray.Count();
00225   nsCString *topKey = nsnull, *key, topOrdinal, ordinal;
00226   for (PRUint32 i = 0; i < keyCount; ++i)
00227   {
00228     key = keyArray[i];
00229     if (key->IsEmpty())
00230       continue;
00231 
00232     // ignore unknown keywords
00233     nsAutoString tagValue;
00234     nsresult rv = GetTagForKey(*key, tagValue);
00235     if (NS_FAILED(rv) || tagValue.IsEmpty())
00236       continue;
00237 
00238     // new top key, judged by ordinal order?
00239     rv = GetOrdinalForKey(*key, ordinal);
00240     if (NS_FAILED(rv) || ordinal.IsEmpty())
00241       ordinal = *key;
00242     if ((ordinal < topOrdinal) || topOrdinal.IsEmpty())
00243     {
00244       topOrdinal = ordinal;
00245       topKey = key; // copy actual result key only once - later
00246     }
00247   }
00248   // return the most important key - if any
00249   if (topKey)
00250     _retval = *topKey;
00251   return NS_OK;
00252 }
00253 
00254 /* void addTagForKey (in string key, in wstring tag, in string color, in string ordinal); */
00255 NS_IMETHODIMP nsMsgTagService::AddTagForKey(const nsACString &key,
00256                                             const nsAString  &tag,
00257                                             const nsACString &color,
00258                                             const nsACString &ordinal)
00259 {
00260   nsCAutoString prefName(key);
00261   ToLowerCase(prefName);
00262   prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG);
00263   nsresult rv = SetUnicharPref(prefName.get(), tag);
00264   NS_ENSURE_SUCCESS(rv, rv);
00265   rv = SetColorForKey(key, color);
00266   NS_ENSURE_SUCCESS(rv, rv);
00267   return SetOrdinalForKey(key, ordinal);
00268 }
00269 
00270 /* void addTag (in wstring tag, in long color); */
00271 NS_IMETHODIMP nsMsgTagService::AddTag(const nsAString  &tag,
00272                                       const nsACString &color,
00273                                       const nsACString &ordinal)
00274 {
00275   // figure out key from tag. Apply transformation stripping out
00276   // illegal characters like <SP> and then convert to imap mod utf7.
00277   // Then, check if we have a tag with that key yet, and if so,
00278   // make it unique by appending A, AA, etc.
00279   // Should we use an iterator?
00280   nsAutoString transformedTag(tag);
00281   transformedTag.ReplaceChar(" ()/{%*<>\\\"", '_');
00282   nsCAutoString key;
00283   CopyUTF16toMUTF7(transformedTag, key);
00284   // We have an imap server that converts keys to upper case so we're going
00285   // to normalize all keys to lower case (upper case looks ugly in prefs.js)
00286   ToLowerCase(key);
00287   nsCAutoString prefName(key);
00288   while (PR_TRUE)
00289   {
00290     nsAutoString tagValue;
00291     nsresult rv = GetTagForKey(prefName, tagValue);
00292     if (NS_FAILED(rv) || tagValue.IsEmpty() || tagValue.Equals(tag))
00293       return AddTagForKey(prefName, tag, color, ordinal);
00294     prefName.Append('A');
00295   }
00296   NS_ASSERTION(PR_FALSE, "can't get here");
00297   return NS_ERROR_FAILURE;
00298 }
00299 
00300 /* long getColorForKey (in string key); */
00301 NS_IMETHODIMP nsMsgTagService::GetColorForKey(const nsACString &key, nsACString  &_retval)
00302 {
00303   nsCAutoString prefName(key);
00304   if (!gMigratingKeys)
00305     ToLowerCase(prefName);
00306   prefName.AppendLiteral(TAG_PREF_SUFFIX_COLOR);
00307   nsXPIDLCString color;
00308   nsresult rv = m_tagPrefBranch->GetCharPref(prefName.get(), getter_Copies(color));
00309   if (NS_SUCCEEDED(rv))
00310     _retval = color;
00311   return NS_OK;
00312 }
00313 
00314 /* void setColorForKey (in ACString key, in ACString color); */
00315 NS_IMETHODIMP nsMsgTagService::SetColorForKey(const nsACString & key, const nsACString & color)
00316 {
00317   nsCAutoString prefName(key);
00318   ToLowerCase(prefName);
00319   prefName.AppendLiteral(TAG_PREF_SUFFIX_COLOR);
00320   if (color.IsEmpty())
00321   {
00322     m_tagPrefBranch->ClearUserPref(prefName.get());
00323     return NS_OK;
00324   }
00325   return m_tagPrefBranch->SetCharPref(prefName.get(), PromiseFlatCString(color).get());
00326 }
00327 
00328 /* ACString getOrdinalForKey (in ACString key); */
00329 NS_IMETHODIMP nsMsgTagService::GetOrdinalForKey(const nsACString & key, nsACString & _retval)
00330 {
00331   nsCAutoString prefName(key);
00332   if (!gMigratingKeys)
00333     ToLowerCase(prefName);
00334   prefName.AppendLiteral(TAG_PREF_SUFFIX_ORDINAL);
00335   nsXPIDLCString ordinal;
00336   nsresult rv = m_tagPrefBranch->GetCharPref(prefName.get(), getter_Copies(ordinal));
00337   _retval = ordinal;
00338   return rv;
00339 }
00340 
00341 /* void setOrdinalForKey (in ACString key, in ACString ordinal); */
00342 NS_IMETHODIMP nsMsgTagService::SetOrdinalForKey(const nsACString & key, const nsACString & ordinal)
00343 {
00344   nsCAutoString prefName(key);
00345   ToLowerCase(prefName);
00346   prefName.AppendLiteral(TAG_PREF_SUFFIX_ORDINAL);
00347   if (ordinal.IsEmpty())
00348   {
00349     m_tagPrefBranch->ClearUserPref(prefName.get());
00350     return NS_OK;
00351   }
00352   return m_tagPrefBranch->SetCharPref(prefName.get(), PromiseFlatCString(ordinal).get());
00353 }
00354 
00355 /* void deleteTag (in wstring tag); */
00356 NS_IMETHODIMP nsMsgTagService::DeleteKey(const nsACString &key)
00357 {
00358   // clear the associated prefs
00359   nsCAutoString prefName(key);
00360   if (!gMigratingKeys)
00361     ToLowerCase(prefName);
00362   return m_tagPrefBranch->DeleteBranch(prefName.get());
00363 }
00364 
00365 /* void getAllTags (out unsigned long count, [array, size_is (count), retval] out nsIMsgTag tagArray); */
00366 NS_IMETHODIMP nsMsgTagService::GetAllTags(PRUint32 *aCount, nsIMsgTag ***aTagArray)
00367 {
00368   // preset harmless default values
00369   *aCount = 0;
00370   *aTagArray = nsnull;
00371 
00372   // get the actual tag definitions
00373   nsresult rv;
00374   PRUint32 prefCount;
00375   char **prefList;
00376   rv = m_tagPrefBranch->GetChildList("", &prefCount, &prefList);
00377   NS_ENSURE_SUCCESS(rv, rv);
00378   // sort them by key for ease of processing
00379   NS_QuickSort(prefList, prefCount, sizeof(char*), CompareMsgTagKeys, nsnull);
00380 
00381   // build an array of nsIMsgTag elements from the orderered list
00382   // it's at max the same size as the preflist, but usually only about half
00383   *aTagArray = (nsIMsgTag**) NS_Alloc(sizeof(nsIMsgTag*) * prefCount);
00384   if (!*aTagArray)
00385     return NS_ERROR_OUT_OF_MEMORY;
00386   PRUint32 currentTagIndex = 0;
00387   nsMsgTag *newMsgTag;
00388   nsString tag;
00389   nsCString lastKey, color, ordinal;
00390   for (PRUint32 i = prefCount; i--;)
00391   {
00392     // extract just the key from <key>.<info=tag|color|ordinal>
00393     char *info = strrchr(prefList[i], '.');
00394     if (info)
00395     {
00396       nsCAutoString key(Substring(prefList[i], info));
00397       if (key != lastKey)
00398       {
00399         if (!key.IsEmpty())
00400         {
00401           // .tag MUST exist (but may be empty)
00402           rv = GetTagForKey(key, tag);
00403           if (NS_SUCCEEDED(rv))
00404           {
00405             // .color MAY exist
00406             color.Truncate();
00407             GetColorForKey(key, color);
00408             // .ordinal MAY exist
00409             rv = GetOrdinalForKey(key, ordinal);
00410             if (NS_FAILED(rv))
00411               ordinal.Truncate();
00412             // store the tag info in our array
00413             newMsgTag = new nsMsgTag(key, tag, color, ordinal);
00414             if (!newMsgTag)
00415               return NS_ERROR_OUT_OF_MEMORY;
00416             (*aTagArray)[currentTagIndex++] = newMsgTag;
00417             NS_ADDREF(newMsgTag);
00418           }
00419         }
00420         lastKey = key;
00421       }
00422     }
00423   }
00424   NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList);
00425 
00426   // return the number of tags
00427   // (the idl's size_is(count) parameter ensures that the array is cut accordingly)
00428   *aCount = currentTagIndex;
00429 
00430   // sort the non-null entries by ordinal
00431   NS_QuickSort(*aTagArray, *aCount, sizeof(nsMsgTag*), CompareMsgTags, nsnull);
00432   return NS_OK;
00433 }
00434 
00435 nsresult nsMsgTagService::SetUnicharPref(const char *prefName,
00436                               const nsAString &val)
00437 {
00438   nsresult rv = NS_OK;
00439   if (!val.IsEmpty())
00440   {
00441     nsCOMPtr<nsISupportsString> supportsString =
00442       do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
00443     if (supportsString)
00444     {
00445       supportsString->SetData(val);
00446       rv = m_tagPrefBranch->SetComplexValue(prefName,
00447                                             NS_GET_IID(nsISupportsString),
00448                                             supportsString);
00449     }
00450   }
00451   else
00452   {
00453     m_tagPrefBranch->ClearUserPref(prefName);
00454   }
00455   return rv;
00456 }
00457 
00458 nsresult nsMsgTagService::GetUnicharPref(const char *prefName,
00459                               nsAString &prefValue)
00460 {
00461   nsresult rv;
00462   nsCOMPtr<nsISupportsString> supportsString =
00463     do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
00464   if (supportsString)
00465   {
00466     rv = m_tagPrefBranch->GetComplexValue(prefName,
00467                                           NS_GET_IID(nsISupportsString),
00468                                           getter_AddRefs(supportsString));
00469     if (supportsString)
00470       rv = supportsString->GetData(prefValue);
00471     else
00472       prefValue.Truncate();
00473   }
00474   return rv;
00475 }
00476 
00477 
00478 nsresult nsMsgTagService::MigrateLabelsToTags()
00479 {
00480   nsCString prefString;
00481 
00482   PRInt32 prefVersion = 0;
00483   nsresult rv = m_tagPrefBranch->GetIntPref(TAG_PREF_VERSION, &prefVersion);
00484   if (NS_SUCCEEDED(rv) && prefVersion > 1)
00485     return rv;
00486   else if (prefVersion == 1)
00487   {
00488     gMigratingKeys = PR_TRUE;
00489   // need to convert the keys to lower case
00490     nsIMsgTag **tagArray;
00491     PRUint32 numTags;
00492     GetAllTags(&numTags, &tagArray);
00493     for (PRUint32 tagIndex = 0; tagIndex < numTags; tagIndex++)
00494     {
00495       nsCAutoString key, color, ordinal;
00496       nsAutoString tagStr;
00497       nsIMsgTag *tag = tagArray[tagIndex];
00498       tag->GetKey(key);
00499       tag->GetTag(tagStr);
00500       tag->GetOrdinal(ordinal);
00501       tag->GetColor(color);
00502       DeleteKey(key);
00503       ToLowerCase(key);
00504       AddTagForKey(key, tagStr, color, ordinal);
00505     }
00506     NS_Free(tagArray);
00507     gMigratingKeys = PR_FALSE;
00508   }
00509   else 
00510   {
00511     nsCOMPtr<nsIPrefBranch> prefRoot(do_GetService(NS_PREFSERVICE_CONTRACTID));
00512     nsCOMPtr<nsIPrefLocalizedString> pls;
00513     nsXPIDLString ucsval;
00514     nsCAutoString labelKey("$label1");
00515     for(PRInt32 i = 0; i < PREF_LABELS_MAX; )
00516     {
00517       prefString.Assign(PREF_LABELS_DESCRIPTION);
00518       prefString.AppendInt(i + 1);
00519       rv = prefRoot->GetComplexValue(prefString.get(),
00520                                      NS_GET_IID(nsIPrefLocalizedString),
00521                                      getter_AddRefs(pls));
00522       NS_ENSURE_SUCCESS(rv, rv);
00523       pls->ToString(getter_Copies(ucsval));
00524 
00525       prefString.Assign(PREF_LABELS_COLOR);
00526       prefString.AppendInt(i + 1);
00527       nsXPIDLCString csval;
00528       rv = prefRoot->GetCharPref(prefString.get(), getter_Copies(csval));
00529       NS_ENSURE_SUCCESS(rv, rv);
00530 
00531       rv = AddTagForKey(labelKey, ucsval, csval, EmptyCString());
00532       NS_ENSURE_SUCCESS(rv, rv);
00533       labelKey.SetCharAt(++i + '1', 6);
00534     }
00535   }
00536   m_tagPrefBranch->SetIntPref(TAG_PREF_VERSION, 2);
00537   return rv;
00538 }