michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozPersonalDictionary.h" michael@0: #include "nsIUnicharInputStream.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsIFile.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "nsICharsetConverterManager.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIWeakReference.h" michael@0: #include "nsCRT.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsStringEnumerator.h" michael@0: #include "nsUnicharInputStream.h" michael@0: michael@0: #define MOZ_PERSONAL_DICT_NAME "persdict.dat" michael@0: michael@0: const int kMaxWordLen=256; michael@0: michael@0: /** michael@0: * This is the most braindead implementation of a personal dictionary possible. michael@0: * There is not much complexity needed, though. It could be made much faster, michael@0: * and probably should, but I don't see much need for more in terms of interface. michael@0: * michael@0: * Allowing personal words to be associated with only certain dictionaries maybe. michael@0: * michael@0: * TODO: michael@0: * Implement the suggestion record. michael@0: */ michael@0: michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(mozPersonalDictionary) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(mozPersonalDictionary) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(mozPersonalDictionary) michael@0: NS_INTERFACE_MAP_ENTRY(mozIPersonalDictionary) michael@0: NS_INTERFACE_MAP_ENTRY(nsIObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIPersonalDictionary) michael@0: NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozPersonalDictionary) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(mozPersonalDictionary, mEncoder) michael@0: michael@0: mozPersonalDictionary::mozPersonalDictionary() michael@0: : mDirty(false) michael@0: { michael@0: } michael@0: michael@0: mozPersonalDictionary::~mozPersonalDictionary() michael@0: { michael@0: } michael@0: michael@0: nsresult mozPersonalDictionary::Init() michael@0: { michael@0: nsCOMPtr svc = michael@0: do_GetService("@mozilla.org/observer-service;1"); michael@0: michael@0: NS_ENSURE_STATE(svc); michael@0: // we want to reload the dictionary if the profile switches michael@0: nsresult rv = svc->AddObserver(this, "profile-do-change", true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = svc->AddObserver(this, "profile-before-change", true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: Load(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void Load (); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::Load() michael@0: { michael@0: //FIXME Deinst -- get dictionary name from prefs; michael@0: nsresult res; michael@0: nsCOMPtr theFile; michael@0: bool dictExists; michael@0: michael@0: res = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(theFile)); michael@0: if(NS_FAILED(res)) return res; michael@0: if(!theFile)return NS_ERROR_FAILURE; michael@0: res = theFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME)); michael@0: if(NS_FAILED(res)) return res; michael@0: res = theFile->Exists(&dictExists); michael@0: if(NS_FAILED(res)) return res; michael@0: michael@0: if (!dictExists) { michael@0: // Nothing is really wrong... michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr inStream; michael@0: NS_NewLocalFileInputStream(getter_AddRefs(inStream), theFile); michael@0: michael@0: nsCOMPtr convStream; michael@0: res = nsSimpleUnicharStreamFactory::GetInstance()-> michael@0: CreateInstanceFromUTF8Stream(inStream, getter_AddRefs(convStream)); michael@0: if(NS_FAILED(res)) return res; michael@0: michael@0: // we're rereading to get rid of the old data -- we shouldn't have any, but... michael@0: mDictionaryTable.Clear(); michael@0: michael@0: char16_t c; michael@0: uint32_t nRead; michael@0: bool done = false; michael@0: do{ // read each line of text into the string array. michael@0: if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) break; michael@0: while(!done && ((c == '\n') || (c == '\r'))){ michael@0: if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) done = true; michael@0: } michael@0: if (!done){ michael@0: nsAutoString word; michael@0: while((c != '\n') && (c != '\r') && !done){ michael@0: word.Append(c); michael@0: if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) done = true; michael@0: } michael@0: mDictionaryTable.PutEntry(word.get()); michael@0: } michael@0: } while(!done); michael@0: mDirty = false; michael@0: michael@0: return res; michael@0: } michael@0: michael@0: // A little helper function to add the key to the list. michael@0: // This is not threadsafe, and only safe if the consumer does not michael@0: // modify the list. michael@0: static PLDHashOperator michael@0: AddHostToStringArray(nsUnicharPtrHashKey *aEntry, void *aArg) michael@0: { michael@0: static_cast*>(aArg)->AppendElement(nsDependentString(aEntry->GetKey())); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: /* void Save (); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::Save() michael@0: { michael@0: nsCOMPtr theFile; michael@0: nsresult res; michael@0: michael@0: if(!mDirty) return NS_OK; michael@0: michael@0: //FIXME Deinst -- get dictionary name from prefs; michael@0: res = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(theFile)); michael@0: if(NS_FAILED(res)) return res; michael@0: if(!theFile)return NS_ERROR_FAILURE; michael@0: res = theFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME)); michael@0: if(NS_FAILED(res)) return res; michael@0: michael@0: nsCOMPtr outStream; michael@0: NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStream), theFile, PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE ,0664); michael@0: michael@0: // get a buffered output stream 4096 bytes big, to optimize writes michael@0: nsCOMPtr bufferedOutputStream; michael@0: res = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream), outStream, 4096); michael@0: if (NS_FAILED(res)) return res; michael@0: michael@0: nsTArray array(mDictionaryTable.Count()); michael@0: mDictionaryTable.EnumerateEntries(AddHostToStringArray, &array); michael@0: michael@0: uint32_t bytesWritten; michael@0: nsAutoCString utf8Key; michael@0: for (uint32_t i = 0; i < array.Length(); ++i ) { michael@0: CopyUTF16toUTF8(array[i], utf8Key); michael@0: michael@0: bufferedOutputStream->Write(utf8Key.get(), utf8Key.Length(), &bytesWritten); michael@0: bufferedOutputStream->Write("\n", 1, &bytesWritten); michael@0: } michael@0: nsCOMPtr safeStream = do_QueryInterface(bufferedOutputStream); michael@0: NS_ASSERTION(safeStream, "expected a safe output stream!"); michael@0: if (safeStream) { michael@0: res = safeStream->Finish(); michael@0: if (NS_FAILED(res)) { michael@0: NS_WARNING("failed to save personal dictionary file! possible data loss"); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: /* readonly attribute nsIStringEnumerator GetWordList() */ michael@0: NS_IMETHODIMP mozPersonalDictionary::GetWordList(nsIStringEnumerator **aWords) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aWords); michael@0: *aWords = nullptr; michael@0: michael@0: nsTArray *array = new nsTArray(mDictionaryTable.Count()); michael@0: if (!array) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: mDictionaryTable.EnumerateEntries(AddHostToStringArray, array); michael@0: michael@0: array->Sort(); michael@0: michael@0: return NS_NewAdoptingStringEnumerator(aWords, array); michael@0: } michael@0: michael@0: /* boolean Check (in wstring word, in wstring language); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::Check(const char16_t *aWord, const char16_t *aLanguage, bool *aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aWord); michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: michael@0: *aResult = (mDictionaryTable.GetEntry(aWord) || mIgnoreTable.GetEntry(aWord)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void AddWord (in wstring word); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::AddWord(const char16_t *aWord, const char16_t *aLang) michael@0: { michael@0: mDictionaryTable.PutEntry(aWord); michael@0: mDirty = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void RemoveWord (in wstring word); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::RemoveWord(const char16_t *aWord, const char16_t *aLang) michael@0: { michael@0: mDictionaryTable.RemoveEntry(aWord); michael@0: mDirty = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void IgnoreWord (in wstring word); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::IgnoreWord(const char16_t *aWord) michael@0: { michael@0: // avoid adding duplicate words to the ignore list michael@0: if (aWord && !mIgnoreTable.GetEntry(aWord)) michael@0: mIgnoreTable.PutEntry(aWord); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void EndSession (); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::EndSession() michael@0: { michael@0: Save(); // save any custom words at the end of a spell check session michael@0: mIgnoreTable.Clear(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void AddCorrection (in wstring word, in wstring correction); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::AddCorrection(const char16_t *word, const char16_t *correction, const char16_t *lang) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: /* void RemoveCorrection (in wstring word, in wstring correction); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::RemoveCorrection(const char16_t *word, const char16_t *correction, const char16_t *lang) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: /* void GetCorrection (in wstring word, [array, size_is (count)] out wstring words, out uint32_t count); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::GetCorrection(const char16_t *word, char16_t ***words, uint32_t *count) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */ michael@0: NS_IMETHODIMP mozPersonalDictionary::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) michael@0: { michael@0: if (!nsCRT::strcmp(aTopic, "profile-do-change")) { michael@0: Load(); // load automatically clears out the existing dictionary table michael@0: } else if (!nsCRT::strcmp(aTopic, "profile-before-change")) { michael@0: Save(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: