summaryrefslogtreecommitdiffstats
path: root/extensions/spellcheck/src/mozPersonalDictionary.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/spellcheck/src/mozPersonalDictionary.cpp')
-rw-r--r--extensions/spellcheck/src/mozPersonalDictionary.cpp471
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;
+}