diff options
Diffstat (limited to 'extensions/spellcheck/src/mozPersonalDictionary.cpp')
-rw-r--r-- | extensions/spellcheck/src/mozPersonalDictionary.cpp | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/extensions/spellcheck/src/mozPersonalDictionary.cpp b/extensions/spellcheck/src/mozPersonalDictionary.cpp new file mode 100644 index 000000000..efaf14356 --- /dev/null +++ b/extensions/spellcheck/src/mozPersonalDictionary.cpp @@ -0,0 +1,471 @@ +/* -*- 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 "mozPersonalDictionary.h" +#include "nsIUnicharInputStream.h" +#include "nsReadableUtils.h" +#include "nsIFile.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIObserverService.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIWeakReference.h" +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsTArray.h" +#include "nsStringEnumerator.h" +#include "nsUnicharInputStream.h" +#include "nsIRunnable.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" +#include "prio.h" +#include "mozilla/Move.h" + +#define MOZ_PERSONAL_DICT_NAME "persdict.dat" + +/** + * This is the most braindead implementation of a personal dictionary possible. + * There is not much complexity needed, though. It could be made much faster, + * and probably should, but I don't see much need for more in terms of interface. + * + * Allowing personal words to be associated with only certain dictionaries maybe. + * + * TODO: + * Implement the suggestion record. + */ + +NS_IMPL_CYCLE_COLLECTING_ADDREF(mozPersonalDictionary) +NS_IMPL_CYCLE_COLLECTING_RELEASE(mozPersonalDictionary) + +NS_INTERFACE_MAP_BEGIN(mozPersonalDictionary) + NS_INTERFACE_MAP_ENTRY(mozIPersonalDictionary) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIPersonalDictionary) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozPersonalDictionary) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(mozPersonalDictionary, mEncoder) + +class mozPersonalDictionaryLoader final : public mozilla::Runnable +{ +public: + explicit mozPersonalDictionaryLoader(mozPersonalDictionary *dict) : mDict(dict) + { + } + + NS_IMETHOD Run() override + { + mDict->SyncLoad(); + + // Release the dictionary on the main thread + NS_ReleaseOnMainThread(mDict.forget()); + + return NS_OK; + } + +private: + RefPtr<mozPersonalDictionary> mDict; +}; + +class mozPersonalDictionarySave final : public mozilla::Runnable +{ +public: + explicit mozPersonalDictionarySave(mozPersonalDictionary *aDict, + nsCOMPtr<nsIFile> aFile, + nsTArray<nsString> &&aDictWords) + : mDictWords(aDictWords), + mFile(aFile), + mDict(aDict) + { + } + + NS_IMETHOD Run() override + { + nsresult res; + + MOZ_ASSERT(!NS_IsMainThread()); + + { + mozilla::MonitorAutoLock mon(mDict->mMonitorSave); + + nsCOMPtr<nsIOutputStream> outStream; + NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStream), mFile, + PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, + 0664); + + // Get a buffered output stream 4096 bytes big, to optimize writes. + nsCOMPtr<nsIOutputStream> bufferedOutputStream; + res = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream), + outStream, 4096); + if (NS_FAILED(res)) { + return res; + } + + uint32_t bytesWritten; + nsAutoCString utf8Key; + for (uint32_t i = 0; i < mDictWords.Length(); ++i) { + CopyUTF16toUTF8(mDictWords[i], utf8Key); + + bufferedOutputStream->Write(utf8Key.get(), utf8Key.Length(), + &bytesWritten); + bufferedOutputStream->Write("\n", 1, &bytesWritten); + } + nsCOMPtr<nsISafeOutputStream> safeStream = + do_QueryInterface(bufferedOutputStream); + NS_ASSERTION(safeStream, "expected a safe output stream!"); + if (safeStream) { + res = safeStream->Finish(); + if (NS_FAILED(res)) { + NS_WARNING("failed to save personal dictionary file! possible data loss"); + } + } + + // Save is done, reset the state variable and notify those who are waiting. + mDict->mSavePending = false; + mon.Notify(); + + // Leaving the block where 'mon' was declared will call the destructor + // and unlock. + } + + // Release the dictionary on the main thread. + NS_ReleaseOnMainThread(mDict.forget()); + + return NS_OK; + } + +private: + nsTArray<nsString> mDictWords; + nsCOMPtr<nsIFile> mFile; + RefPtr<mozPersonalDictionary> mDict; +}; + +mozPersonalDictionary::mozPersonalDictionary() + : mIsLoaded(false), + mSavePending(false), + mMonitor("mozPersonalDictionary::mMonitor"), + mMonitorSave("mozPersonalDictionary::mMonitorSave") +{ +} + +mozPersonalDictionary::~mozPersonalDictionary() +{ +} + +nsresult mozPersonalDictionary::Init() +{ + nsCOMPtr<nsIObserverService> svc = + do_GetService("@mozilla.org/observer-service;1"); + + NS_ENSURE_STATE(svc); + // we want to reload the dictionary if the profile switches + nsresult rv = svc->AddObserver(this, "profile-do-change", true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = svc->AddObserver(this, "profile-before-change", true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + Load(); + + return NS_OK; +} + +void mozPersonalDictionary::WaitForLoad() +{ + // If the dictionary is already loaded, we return straight away. + if (mIsLoaded) { + return; + } + + // If the dictionary hasn't been loaded, we try to lock the same monitor + // that the thread uses that does the load. This way the main thread will + // be suspended until the monitor becomes available. + mozilla::MonitorAutoLock mon(mMonitor); + + // The monitor has become available. This can have two reasons: + // 1: The thread that does the load has finished. + // 2: The thread that does the load hasn't even started. + // In this case we need to wait. + if (!mIsLoaded) { + mon.Wait(); + } +} + +nsresult mozPersonalDictionary::LoadInternal() +{ + nsresult rv; + mozilla::MonitorAutoLock mon(mMonitor); + + if (mIsLoaded) { + return NS_OK; + } + + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!mFile) { + return NS_ERROR_FAILURE; + } + + rv = mFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIRunnable> runnable = new mozPersonalDictionaryLoader(this); + rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP mozPersonalDictionary::Load() +{ + nsresult rv = LoadInternal(); + + if (NS_FAILED(rv)) { + mIsLoaded = true; + } + + return rv; +} + +void mozPersonalDictionary::SyncLoad() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + mozilla::MonitorAutoLock mon(mMonitor); + + if (mIsLoaded) { + return; + } + + SyncLoadInternal(); + mIsLoaded = true; + mon.Notify(); +} + +void mozPersonalDictionary::SyncLoadInternal() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + //FIXME Deinst -- get dictionary name from prefs; + nsresult rv; + bool dictExists; + + rv = mFile->Exists(&dictExists); + if (NS_FAILED(rv)) { + return; + } + + if (!dictExists) { + // Nothing is really wrong... + return; + } + + nsCOMPtr<nsIInputStream> inStream; + NS_NewLocalFileInputStream(getter_AddRefs(inStream), mFile); + + nsCOMPtr<nsIUnicharInputStream> convStream; + rv = NS_NewUnicharInputStream(inStream, getter_AddRefs(convStream)); + if (NS_FAILED(rv)) { + return; + } + + // we're rereading to get rid of the old data -- we shouldn't have any, but... + mDictionaryTable.Clear(); + + char16_t c; + uint32_t nRead; + bool done = false; + do{ // read each line of text into the string array. + if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) break; + while(!done && ((c == '\n') || (c == '\r'))){ + if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) done = true; + } + if (!done){ + nsAutoString word; + while((c != '\n') && (c != '\r') && !done){ + word.Append(c); + if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) done = true; + } + mDictionaryTable.PutEntry(word.get()); + } + } while(!done); +} + +void mozPersonalDictionary::WaitForSave() +{ + // If no save is pending, we return straight away. + if (!mSavePending) { + return; + } + + // If a save is pending, we try to lock the same monitor that the thread uses + // that does the save. This way the main thread will be suspended until the + // monitor becomes available. + mozilla::MonitorAutoLock mon(mMonitorSave); + + // The monitor has become available. This can have two reasons: + // 1: The thread that does the save has finished. + // 2: The thread that does the save hasn't even started. + // In this case we need to wait. + if (mSavePending) { + mon.Wait(); + } +} + +NS_IMETHODIMP mozPersonalDictionary::Save() +{ + nsCOMPtr<nsIFile> theFile; + nsresult res; + + WaitForSave(); + + mSavePending = true; + + //FIXME Deinst -- get dictionary name from prefs; + res = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(theFile)); + if(NS_FAILED(res)) return res; + if(!theFile)return NS_ERROR_FAILURE; + res = theFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME)); + if(NS_FAILED(res)) return res; + + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &res); + if (NS_WARN_IF(NS_FAILED(res))) { + return res; + } + + nsTArray<nsString> array; + nsString* elems = array.AppendElements(mDictionaryTable.Count()); + for (auto iter = mDictionaryTable.Iter(); !iter.Done(); iter.Next()) { + elems->Assign(iter.Get()->GetKey()); + elems++; + } + + nsCOMPtr<nsIRunnable> runnable = + new mozPersonalDictionarySave(this, theFile, mozilla::Move(array)); + res = target->Dispatch(runnable, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(res))) { + return res; + } + return res; +} + +NS_IMETHODIMP mozPersonalDictionary::GetWordList(nsIStringEnumerator **aWords) +{ + NS_ENSURE_ARG_POINTER(aWords); + *aWords = nullptr; + + WaitForLoad(); + + nsTArray<nsString> *array = new nsTArray<nsString>(); + nsString* elems = array->AppendElements(mDictionaryTable.Count()); + for (auto iter = mDictionaryTable.Iter(); !iter.Done(); iter.Next()) { + elems->Assign(iter.Get()->GetKey()); + elems++; + } + + array->Sort(); + + return NS_NewAdoptingStringEnumerator(aWords, array); +} + +NS_IMETHODIMP mozPersonalDictionary::Check(const char16_t *aWord, const char16_t *aLanguage, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aWord); + NS_ENSURE_ARG_POINTER(aResult); + + WaitForLoad(); + + *aResult = (mDictionaryTable.GetEntry(aWord) || mIgnoreTable.GetEntry(aWord)); + return NS_OK; +} + +NS_IMETHODIMP mozPersonalDictionary::AddWord(const char16_t *aWord, const char16_t *aLang) +{ + nsresult res; + WaitForLoad(); + + mDictionaryTable.PutEntry(aWord); + res = Save(); + return res; +} + +NS_IMETHODIMP mozPersonalDictionary::RemoveWord(const char16_t *aWord, const char16_t *aLang) +{ + nsresult res; + WaitForLoad(); + + mDictionaryTable.RemoveEntry(aWord); + res = Save(); + return res; +} + +NS_IMETHODIMP mozPersonalDictionary::IgnoreWord(const char16_t *aWord) +{ + // avoid adding duplicate words to the ignore list + if (aWord && !mIgnoreTable.GetEntry(aWord)) + mIgnoreTable.PutEntry(aWord); + return NS_OK; +} + +NS_IMETHODIMP mozPersonalDictionary::EndSession() +{ + WaitForLoad(); + + WaitForSave(); + mIgnoreTable.Clear(); + return NS_OK; +} + +NS_IMETHODIMP mozPersonalDictionary::AddCorrection(const char16_t *word, const char16_t *correction, const char16_t *lang) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP mozPersonalDictionary::RemoveCorrection(const char16_t *word, const char16_t *correction, const char16_t *lang) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP mozPersonalDictionary::GetCorrection(const char16_t *word, char16_t ***words, uint32_t *count) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP mozPersonalDictionary::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) +{ + if (!nsCRT::strcmp(aTopic, "profile-do-change")) { + // The observer is registered in Init() which calls Load and in turn + // LoadInternal(); i.e. Observe() can't be called before Load(). + WaitForLoad(); + mIsLoaded = false; + Load(); // load automatically clears out the existing dictionary table + } else if (!nsCRT::strcmp(aTopic, "profile-before-change")) { + WaitForSave(); + } + + return NS_OK; +} |