LCOV - code coverage report
Current view: top level - extensions/spellcheck/src - mozPersonalDictionary.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 60 208 28.8 %
Date: 2017-07-14 16:53:18 Functions: 13 32 40.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       4             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       5             : 
       6             : #include "mozPersonalDictionary.h"
       7             : #include "nsIUnicharInputStream.h"
       8             : #include "nsReadableUtils.h"
       9             : #include "nsIFile.h"
      10             : #include "nsAppDirectoryServiceDefs.h"
      11             : #include "nsIObserverService.h"
      12             : #include "nsIPrefService.h"
      13             : #include "nsIPrefBranch.h"
      14             : #include "nsIWeakReference.h"
      15             : #include "nsCRT.h"
      16             : #include "nsNetUtil.h"
      17             : #include "nsNetCID.h"
      18             : #include "nsIInputStream.h"
      19             : #include "nsIOutputStream.h"
      20             : #include "nsISafeOutputStream.h"
      21             : #include "nsTArray.h"
      22             : #include "nsStringEnumerator.h"
      23             : #include "nsUnicharInputStream.h"
      24             : #include "nsIRunnable.h"
      25             : #include "nsThreadUtils.h"
      26             : #include "nsProxyRelease.h"
      27             : #include "prio.h"
      28             : #include "mozilla/Move.h"
      29             : 
      30             : #define MOZ_PERSONAL_DICT_NAME "persdict.dat"
      31             : 
      32             : /**
      33             :  * This is the most braindead implementation of a personal dictionary possible.
      34             :  * There is not much complexity needed, though.  It could be made much faster,
      35             :  *  and probably should, but I don't see much need for more in terms of interface.
      36             :  *
      37             :  * Allowing personal words to be associated with only certain dictionaries maybe.
      38             :  *
      39             :  * TODO:
      40             :  * Implement the suggestion record.
      41             :  */
      42             : 
      43           7 : NS_IMPL_ADDREF(mozPersonalDictionary)
      44           5 : NS_IMPL_RELEASE(mozPersonalDictionary)
      45             : 
      46           4 : NS_INTERFACE_MAP_BEGIN(mozPersonalDictionary)
      47           4 :   NS_INTERFACE_MAP_ENTRY(mozIPersonalDictionary)
      48           3 :   NS_INTERFACE_MAP_ENTRY(nsIObserver)
      49           3 :   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
      50           1 :   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIPersonalDictionary)
      51           0 : NS_INTERFACE_MAP_END
      52             : 
      53           3 : class mozPersonalDictionaryLoader final : public mozilla::Runnable
      54             : {
      55             : public:
      56           1 :   explicit mozPersonalDictionaryLoader(mozPersonalDictionary* dict)
      57           1 :     : mozilla::Runnable("mozPersonalDictionaryLoader")
      58           1 :     , mDict(dict)
      59             :   {
      60           1 :   }
      61             : 
      62           1 :   NS_IMETHOD Run() override
      63             :   {
      64           1 :     mDict->SyncLoad();
      65             : 
      66             :     // Release the dictionary on the main thread
      67             :     NS_ReleaseOnMainThread(
      68             :       "mozPersonalDictionaryLoader::mDict",
      69           1 :       mDict.forget().downcast<mozIPersonalDictionary>());
      70             : 
      71           1 :     return NS_OK;
      72             :   }
      73             : 
      74             : private:
      75             :   RefPtr<mozPersonalDictionary> mDict;
      76             : };
      77             : 
      78           0 : class mozPersonalDictionarySave final : public mozilla::Runnable
      79             : {
      80             : public:
      81           0 :   explicit mozPersonalDictionarySave(mozPersonalDictionary* aDict,
      82             :                                      nsCOMPtr<nsIFile> aFile,
      83             :                                      nsTArray<nsString>&& aDictWords)
      84           0 :     : mozilla::Runnable("mozPersonalDictionarySave")
      85             :     , mDictWords(aDictWords)
      86             :     , mFile(aFile)
      87           0 :     , mDict(aDict)
      88             :   {
      89           0 :   }
      90             : 
      91           0 :   NS_IMETHOD Run() override
      92             :   {
      93             :     nsresult res;
      94             : 
      95           0 :     MOZ_ASSERT(!NS_IsMainThread());
      96             : 
      97             :     {
      98           0 :       mozilla::MonitorAutoLock mon(mDict->mMonitorSave);
      99             : 
     100           0 :       nsCOMPtr<nsIOutputStream> outStream;
     101           0 :       NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStream), mFile,
     102             :                                       PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
     103           0 :                                       0664);
     104             : 
     105             :       // Get a buffered output stream 4096 bytes big, to optimize writes.
     106           0 :       nsCOMPtr<nsIOutputStream> bufferedOutputStream;
     107           0 :       res = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream),
     108           0 :                                        outStream, 4096);
     109           0 :       if (NS_FAILED(res)) {
     110           0 :         return res;
     111             :       }
     112             : 
     113             :       uint32_t bytesWritten;
     114           0 :       nsAutoCString utf8Key;
     115           0 :       for (uint32_t i = 0; i < mDictWords.Length(); ++i) {
     116           0 :         CopyUTF16toUTF8(mDictWords[i], utf8Key);
     117             : 
     118           0 :         bufferedOutputStream->Write(utf8Key.get(), utf8Key.Length(),
     119           0 :                                     &bytesWritten);
     120           0 :         bufferedOutputStream->Write("\n", 1, &bytesWritten);
     121             :       }
     122             :       nsCOMPtr<nsISafeOutputStream> safeStream =
     123           0 :         do_QueryInterface(bufferedOutputStream);
     124           0 :       NS_ASSERTION(safeStream, "expected a safe output stream!");
     125           0 :       if (safeStream) {
     126           0 :         res = safeStream->Finish();
     127           0 :         if (NS_FAILED(res)) {
     128           0 :           NS_WARNING("failed to save personal dictionary file! possible data loss");
     129             :         }
     130             :       }
     131             : 
     132             :       // Save is done, reset the state variable and notify those who are waiting.
     133           0 :       mDict->mSavePending = false;
     134           0 :       mon.Notify();
     135             : 
     136             :       // Leaving the block where 'mon' was declared will call the destructor
     137             :       // and unlock.
     138             :     }
     139             : 
     140             :     // Release the dictionary on the main thread.
     141             :     NS_ReleaseOnMainThread(
     142             :       "mozPersonalDictionarySave::mDict",
     143           0 :       mDict.forget().downcast<mozIPersonalDictionary>());
     144             : 
     145           0 :     return NS_OK;
     146             :   }
     147             : 
     148             : private:
     149             :   nsTArray<nsString> mDictWords;
     150             :   nsCOMPtr<nsIFile> mFile;
     151             :   RefPtr<mozPersonalDictionary> mDict;
     152             : };
     153             : 
     154           1 : mozPersonalDictionary::mozPersonalDictionary()
     155             :  : mIsLoaded(false),
     156             :    mSavePending(false),
     157             :    mMonitor("mozPersonalDictionary::mMonitor"),
     158           1 :    mMonitorSave("mozPersonalDictionary::mMonitorSave")
     159             : {
     160           1 : }
     161             : 
     162           0 : mozPersonalDictionary::~mozPersonalDictionary()
     163             : {
     164           0 : }
     165             : 
     166           1 : nsresult mozPersonalDictionary::Init()
     167             : {
     168             :   nsCOMPtr<nsIObserverService> svc =
     169           2 :     do_GetService("@mozilla.org/observer-service;1");
     170             : 
     171           1 :   NS_ENSURE_STATE(svc);
     172             :   // we want to reload the dictionary if the profile switches
     173           1 :   nsresult rv = svc->AddObserver(this, "profile-do-change", true);
     174           1 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     175           0 :     return rv;
     176             :   }
     177             : 
     178           1 :   rv = svc->AddObserver(this, "profile-before-change", true);
     179           1 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     180           0 :     return rv;
     181             :   }
     182             : 
     183           1 :   Load();
     184             : 
     185           1 :   return NS_OK;
     186             : }
     187             : 
     188           0 : void mozPersonalDictionary::WaitForLoad()
     189             : {
     190             :   // If the dictionary is already loaded, we return straight away.
     191           0 :   if (mIsLoaded) {
     192           0 :     return;
     193             :   }
     194             : 
     195             :   // If the dictionary hasn't been loaded, we try to lock the same monitor
     196             :   // that the thread uses that does the load. This way the main thread will
     197             :   // be suspended until the monitor becomes available.
     198           0 :   mozilla::MonitorAutoLock mon(mMonitor);
     199             : 
     200             :   // The monitor has become available. This can have two reasons:
     201             :   // 1: The thread that does the load has finished.
     202             :   // 2: The thread that does the load hasn't even started.
     203             :   //    In this case we need to wait.
     204           0 :   if (!mIsLoaded) {
     205           0 :     mon.Wait();
     206             :   }
     207             : }
     208             : 
     209           1 : nsresult mozPersonalDictionary::LoadInternal()
     210             : {
     211             :   nsresult rv;
     212           2 :   mozilla::MonitorAutoLock mon(mMonitor);
     213             : 
     214           1 :   if (mIsLoaded) {
     215           0 :     return NS_OK;
     216             :   }
     217             : 
     218           1 :   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mFile));
     219           1 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     220           0 :     return rv;
     221             :   }
     222             : 
     223           1 :   if (!mFile) {
     224           0 :     return NS_ERROR_FAILURE;
     225             :   }
     226             : 
     227           1 :   rv = mFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME));
     228           1 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     229           0 :     return rv;
     230             :   }
     231             : 
     232           2 :   nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
     233           1 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     234           0 :     return rv;
     235             :   }
     236             : 
     237           2 :   nsCOMPtr<nsIRunnable> runnable = new mozPersonalDictionaryLoader(this);
     238           1 :   rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
     239           1 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     240           0 :     return rv;
     241             :   }
     242             : 
     243           1 :   return NS_OK;
     244             : }
     245             : 
     246           1 : NS_IMETHODIMP mozPersonalDictionary::Load()
     247             : {
     248           1 :   nsresult rv = LoadInternal();
     249             : 
     250           1 :   if (NS_FAILED(rv)) {
     251           0 :     mIsLoaded = true;
     252             :   }
     253             : 
     254           1 :   return rv;
     255             : }
     256             : 
     257           1 : void mozPersonalDictionary::SyncLoad()
     258             : {
     259           1 :   MOZ_ASSERT(!NS_IsMainThread());
     260             : 
     261           2 :   mozilla::MonitorAutoLock mon(mMonitor);
     262             : 
     263           1 :   if (mIsLoaded) {
     264           0 :     return;
     265             :   }
     266             : 
     267           1 :   SyncLoadInternal();
     268           1 :   mIsLoaded = true;
     269           1 :   mon.Notify();
     270             : }
     271             : 
     272           1 : void mozPersonalDictionary::SyncLoadInternal()
     273             : {
     274           1 :   MOZ_ASSERT(!NS_IsMainThread());
     275             : 
     276             :   //FIXME Deinst  -- get dictionary name from prefs;
     277             :   nsresult rv;
     278             :   bool dictExists;
     279             : 
     280           1 :   rv = mFile->Exists(&dictExists);
     281           1 :   if (NS_FAILED(rv)) {
     282           1 :     return;
     283             :   }
     284             : 
     285           1 :   if (!dictExists) {
     286             :     // Nothing is really wrong...
     287           1 :     return;
     288             :   }
     289             : 
     290           0 :   nsCOMPtr<nsIInputStream> inStream;
     291           0 :   NS_NewLocalFileInputStream(getter_AddRefs(inStream), mFile);
     292             : 
     293           0 :   nsCOMPtr<nsIUnicharInputStream> convStream;
     294           0 :   rv = NS_NewUnicharInputStream(inStream, getter_AddRefs(convStream));
     295           0 :   if (NS_FAILED(rv)) {
     296           0 :     return;
     297             :   }
     298             : 
     299             :   // we're rereading to get rid of the old data  -- we shouldn't have any, but...
     300           0 :   mDictionaryTable.Clear();
     301             : 
     302             :   char16_t c;
     303             :   uint32_t nRead;
     304           0 :   bool done = false;
     305           0 :   do{  // read each line of text into the string array.
     306           0 :     if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) break;
     307           0 :     while(!done && ((c == '\n') || (c == '\r'))){
     308           0 :       if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) done = true;
     309             :     }
     310           0 :     if (!done){
     311           0 :       nsAutoString word;
     312           0 :       while((c != '\n') && (c != '\r') && !done){
     313           0 :         word.Append(c);
     314           0 :         if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) done = true;
     315             :       }
     316           0 :       mDictionaryTable.PutEntry(word.get());
     317             :     }
     318           0 :   } while(!done);
     319             : }
     320             : 
     321           0 : void mozPersonalDictionary::WaitForSave()
     322             : {
     323             :   // If no save is pending, we return straight away.
     324           0 :   if (!mSavePending) {
     325           0 :     return;
     326             :   }
     327             : 
     328             :   // If a save is pending, we try to lock the same monitor that the thread uses
     329             :   // that does the save. This way the main thread will be suspended until the
     330             :   // monitor becomes available.
     331           0 :   mozilla::MonitorAutoLock mon(mMonitorSave);
     332             : 
     333             :   // The monitor has become available. This can have two reasons:
     334             :   // 1: The thread that does the save has finished.
     335             :   // 2: The thread that does the save hasn't even started.
     336             :   //    In this case we need to wait.
     337           0 :   if (mSavePending) {
     338           0 :     mon.Wait();
     339             :   }
     340             : }
     341             : 
     342           0 : NS_IMETHODIMP mozPersonalDictionary::Save()
     343             : {
     344           0 :   nsCOMPtr<nsIFile> theFile;
     345             :   nsresult res;
     346             : 
     347           0 :   WaitForSave();
     348             : 
     349           0 :   mSavePending = true;
     350             : 
     351             :   //FIXME Deinst  -- get dictionary name from prefs;
     352           0 :   res = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(theFile));
     353           0 :   if(NS_FAILED(res)) return res;
     354           0 :   if(!theFile)return NS_ERROR_FAILURE;
     355           0 :   res = theFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME));
     356           0 :   if(NS_FAILED(res)) return res;
     357             : 
     358             :   nsCOMPtr<nsIEventTarget> target =
     359           0 :     do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &res);
     360           0 :   if (NS_WARN_IF(NS_FAILED(res))) {
     361           0 :     return res;
     362             :   }
     363             : 
     364           0 :   nsTArray<nsString> array;
     365           0 :   nsString* elems = array.AppendElements(mDictionaryTable.Count());
     366           0 :   for (auto iter = mDictionaryTable.Iter(); !iter.Done(); iter.Next()) {
     367           0 :     elems->Assign(iter.Get()->GetKey());
     368           0 :     elems++;
     369             :   }
     370             : 
     371             :   nsCOMPtr<nsIRunnable> runnable =
     372           0 :     new mozPersonalDictionarySave(this, theFile, mozilla::Move(array));
     373           0 :   res = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
     374           0 :   if (NS_WARN_IF(NS_FAILED(res))) {
     375           0 :     return res;
     376             :   }
     377           0 :   return res;
     378             : }
     379             : 
     380           0 : NS_IMETHODIMP mozPersonalDictionary::GetWordList(nsIStringEnumerator **aWords)
     381             : {
     382           0 :   NS_ENSURE_ARG_POINTER(aWords);
     383           0 :   *aWords = nullptr;
     384             : 
     385           0 :   WaitForLoad();
     386             : 
     387           0 :   nsTArray<nsString> *array = new nsTArray<nsString>();
     388           0 :   nsString* elems = array->AppendElements(mDictionaryTable.Count());
     389           0 :   for (auto iter = mDictionaryTable.Iter(); !iter.Done(); iter.Next()) {
     390           0 :     elems->Assign(iter.Get()->GetKey());
     391           0 :     elems++;
     392             :   }
     393             : 
     394           0 :   array->Sort();
     395             : 
     396           0 :   return NS_NewAdoptingStringEnumerator(aWords, array);
     397             : }
     398             : 
     399           0 : NS_IMETHODIMP mozPersonalDictionary::Check(const char16_t *aWord, const char16_t *aLanguage, bool *aResult)
     400             : {
     401           0 :   NS_ENSURE_ARG_POINTER(aWord);
     402           0 :   NS_ENSURE_ARG_POINTER(aResult);
     403             : 
     404           0 :   WaitForLoad();
     405             : 
     406           0 :   *aResult = (mDictionaryTable.GetEntry(aWord) || mIgnoreTable.GetEntry(aWord));
     407           0 :   return NS_OK;
     408             : }
     409             : 
     410           0 : NS_IMETHODIMP mozPersonalDictionary::AddWord(const char16_t *aWord, const char16_t *aLang)
     411             : {
     412             :   nsresult res;
     413           0 :   WaitForLoad();
     414             : 
     415           0 :   mDictionaryTable.PutEntry(aWord);
     416           0 :   res = Save();
     417           0 :   return res;
     418             : }
     419             : 
     420           0 : NS_IMETHODIMP mozPersonalDictionary::RemoveWord(const char16_t *aWord, const char16_t *aLang)
     421             : {
     422             :   nsresult res;
     423           0 :   WaitForLoad();
     424             : 
     425           0 :   mDictionaryTable.RemoveEntry(aWord);
     426           0 :   res = Save();
     427           0 :   return res;
     428             : }
     429             : 
     430           0 : NS_IMETHODIMP mozPersonalDictionary::IgnoreWord(const char16_t *aWord)
     431             : {
     432             :   // avoid adding duplicate words to the ignore list
     433           0 :   if (aWord && !mIgnoreTable.GetEntry(aWord))
     434           0 :     mIgnoreTable.PutEntry(aWord);
     435           0 :   return NS_OK;
     436             : }
     437             : 
     438           0 : NS_IMETHODIMP mozPersonalDictionary::EndSession()
     439             : {
     440           0 :   WaitForLoad();
     441             : 
     442           0 :   WaitForSave();
     443           0 :   mIgnoreTable.Clear();
     444           0 :   return NS_OK;
     445             : }
     446             : 
     447           0 : NS_IMETHODIMP mozPersonalDictionary::AddCorrection(const char16_t *word, const char16_t *correction, const char16_t *lang)
     448             : {
     449           0 :     return NS_ERROR_NOT_IMPLEMENTED;
     450             : }
     451             : 
     452           0 : NS_IMETHODIMP mozPersonalDictionary::RemoveCorrection(const char16_t *word, const char16_t *correction, const char16_t *lang)
     453             : {
     454           0 :     return NS_ERROR_NOT_IMPLEMENTED;
     455             : }
     456             : 
     457           0 : NS_IMETHODIMP mozPersonalDictionary::GetCorrection(const char16_t *word, char16_t ***words, uint32_t *count)
     458             : {
     459           0 :     return NS_ERROR_NOT_IMPLEMENTED;
     460             : }
     461             : 
     462           0 : NS_IMETHODIMP mozPersonalDictionary::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
     463             : {
     464           0 :   if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
     465             :     // The observer is registered in Init() which calls Load and in turn
     466             :     // LoadInternal(); i.e. Observe() can't be called before Load().
     467           0 :     WaitForLoad();
     468           0 :     mIsLoaded = false;
     469           0 :     Load(); // load automatically clears out the existing dictionary table
     470           0 :   } else if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
     471           0 :     WaitForSave();
     472             :   }
     473             : 
     474           0 :   return NS_OK;
     475             : }

Generated by: LCOV version 1.13