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/. */
6 #include "mozPersonalDictionary.h"
10 #include "nsAppDirectoryServiceDefs.h"
13 #include "nsIInputStream.h"
14 #include "nsIObserverService.h"
15 #include "nsIOutputStream.h"
16 #include "nsIRunnable.h"
17 #include "nsISafeOutputStream.h"
18 #include "nsIUnicharInputStream.h"
19 #include "nsIWeakReference.h"
21 #include "nsNetUtil.h"
22 #include "nsProxyRelease.h"
23 #include "nsReadableUtils.h"
24 #include "nsStringEnumerator.h"
26 #include "nsThreadUtils.h"
27 #include "nsUnicharInputStream.h"
30 #define MOZ_PERSONAL_DICT_NAME u"persdict.dat"
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
38 * Allowing personal words to be associated with only certain dictionaries
42 * Implement the suggestion record.
45 NS_IMPL_ADDREF(mozPersonalDictionary
)
46 NS_IMPL_RELEASE(mozPersonalDictionary
)
48 NS_INTERFACE_MAP_BEGIN(mozPersonalDictionary
)
49 NS_INTERFACE_MAP_ENTRY(mozIPersonalDictionary
)
50 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
51 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
52 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, mozIPersonalDictionary
)
55 class mozPersonalDictionaryLoader final
: public mozilla::Runnable
{
57 explicit mozPersonalDictionaryLoader(mozPersonalDictionary
* dict
)
58 : mozilla::Runnable("mozPersonalDictionaryLoader"), mDict(dict
) {}
60 NS_IMETHOD
Run() override
{
63 // Release the dictionary on the main thread
64 NS_ReleaseOnMainThread("mozPersonalDictionaryLoader::mDict",
65 mDict
.forget().downcast
<mozIPersonalDictionary
>());
71 RefPtr
<mozPersonalDictionary
> mDict
;
74 class mozPersonalDictionarySave final
: public mozilla::Runnable
{
76 explicit mozPersonalDictionarySave(mozPersonalDictionary
* aDict
,
77 nsCOMPtr
<nsIFile
> aFile
,
78 nsTArray
<nsString
>&& aDictWords
)
79 : mozilla::Runnable("mozPersonalDictionarySave"),
80 mDictWords(std::move(aDictWords
)),
84 NS_IMETHOD
Run() override
{
87 MOZ_ASSERT(!NS_IsMainThread());
90 mozilla::MonitorAutoLock
mon(mDict
->mMonitorSave
);
92 nsCOMPtr
<nsIOutputStream
> outStream
;
93 NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStream
), mFile
,
94 PR_CREATE_FILE
| PR_WRONLY
| PR_TRUNCATE
,
97 // Get a buffered output stream 4096 bytes big, to optimize writes.
98 nsCOMPtr
<nsIOutputStream
> bufferedOutputStream
;
99 res
= NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream
),
100 outStream
.forget(), 4096);
101 if (NS_FAILED(res
)) {
105 uint32_t bytesWritten
;
106 nsAutoCString utf8Key
;
107 for (uint32_t i
= 0; i
< mDictWords
.Length(); ++i
) {
108 CopyUTF16toUTF8(mDictWords
[i
], utf8Key
);
110 bufferedOutputStream
->Write(utf8Key
.get(), utf8Key
.Length(),
112 bufferedOutputStream
->Write("\n", 1, &bytesWritten
);
114 nsCOMPtr
<nsISafeOutputStream
> safeStream
=
115 do_QueryInterface(bufferedOutputStream
);
116 NS_ASSERTION(safeStream
, "expected a safe output stream!");
118 res
= safeStream
->Finish();
119 if (NS_FAILED(res
)) {
121 "failed to save personal dictionary file! possible data loss");
125 // Save is done, reset the state variable and notify those who are
127 mDict
->mSavePending
= false;
130 // Leaving the block where 'mon' was declared will call the destructor
134 // Release the dictionary on the main thread.
135 NS_ReleaseOnMainThread("mozPersonalDictionarySave::mDict",
136 mDict
.forget().downcast
<mozIPersonalDictionary
>());
142 nsTArray
<nsString
> mDictWords
;
143 nsCOMPtr
<nsIFile
> mFile
;
144 RefPtr
<mozPersonalDictionary
> mDict
;
147 mozPersonalDictionary::mozPersonalDictionary()
150 mMonitor("mozPersonalDictionary::mMonitor"),
151 mMonitorSave("mozPersonalDictionary::mMonitorSave") {}
153 mozPersonalDictionary::~mozPersonalDictionary() {}
155 nsresult
mozPersonalDictionary::Init() {
156 nsCOMPtr
<nsIObserverService
> svc
=
157 do_GetService("@mozilla.org/observer-service;1");
159 NS_ENSURE_STATE(svc
);
160 // we want to reload the dictionary if the profile switches
161 nsresult rv
= svc
->AddObserver(this, "profile-do-change", true);
162 if (NS_WARN_IF(NS_FAILED(rv
))) {
166 rv
= svc
->AddObserver(this, "profile-before-change", true);
167 if (NS_WARN_IF(NS_FAILED(rv
))) {
176 void mozPersonalDictionary::WaitForLoad() {
177 // If the dictionary is already loaded, we return straight away.
182 // If the dictionary hasn't been loaded, we try to lock the same monitor
183 // that the thread uses that does the load. This way the main thread will
184 // be suspended until the monitor becomes available.
185 mozilla::MonitorAutoLock
mon(mMonitor
);
187 // The monitor has become available. This can have two reasons:
188 // 1: The thread that does the load has finished.
189 // 2: The thread that does the load hasn't even started.
190 // In this case we need to wait.
196 nsresult
mozPersonalDictionary::LoadInternal() {
198 mozilla::MonitorAutoLock
mon(mMonitor
);
205 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
, getter_AddRefs(mFile
));
206 if (NS_WARN_IF(NS_FAILED(rv
))) {
211 return NS_ERROR_FAILURE
;
214 rv
= mFile
->Append(nsLiteralString(MOZ_PERSONAL_DICT_NAME
));
215 if (NS_WARN_IF(NS_FAILED(rv
))) {
219 nsCOMPtr
<nsIEventTarget
> target
=
220 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
, &rv
);
221 if (NS_WARN_IF(NS_FAILED(rv
))) {
225 nsCOMPtr
<nsIRunnable
> runnable
= new mozPersonalDictionaryLoader(this);
226 rv
= target
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
227 if (NS_WARN_IF(NS_FAILED(rv
))) {
234 NS_IMETHODIMP
mozPersonalDictionary::Load() {
235 nsresult rv
= LoadInternal();
244 void mozPersonalDictionary::SyncLoad() {
245 MOZ_ASSERT(!NS_IsMainThread());
247 mozilla::MonitorAutoLock
mon(mMonitor
);
258 void mozPersonalDictionary::SyncLoadInternal() {
259 MOZ_ASSERT(!NS_IsMainThread());
261 // FIXME Deinst -- get dictionary name from prefs;
265 rv
= mFile
->Exists(&dictExists
);
271 // Nothing is really wrong...
275 nsCOMPtr
<nsIInputStream
> inStream
;
276 NS_NewLocalFileInputStream(getter_AddRefs(inStream
), mFile
);
278 nsCOMPtr
<nsIUnicharInputStream
> convStream
;
279 rv
= NS_NewUnicharInputStream(inStream
, getter_AddRefs(convStream
));
284 // we're rereading to get rid of the old data -- we shouldn't have any,
286 mDictionaryTable
.Clear();
291 do { // read each line of text into the string array.
292 if ((NS_OK
!= convStream
->Read(&c
, 1, &nRead
)) || (nRead
!= 1)) break;
293 while (!done
&& ((c
== '\n') || (c
== '\r'))) {
294 if ((NS_OK
!= convStream
->Read(&c
, 1, &nRead
)) || (nRead
!= 1))
299 while ((c
!= '\n') && (c
!= '\r') && !done
) {
301 if ((NS_OK
!= convStream
->Read(&c
, 1, &nRead
)) || (nRead
!= 1))
304 mDictionaryTable
.Insert(word
);
309 void mozPersonalDictionary::WaitForSave() {
310 // If no save is pending, we return straight away.
315 // If a save is pending, we try to lock the same monitor that the thread uses
316 // that does the save. This way the main thread will be suspended until the
317 // monitor becomes available.
318 mozilla::MonitorAutoLock
mon(mMonitorSave
);
320 // The monitor has become available. This can have two reasons:
321 // 1: The thread that does the save has finished.
322 // 2: The thread that does the save hasn't even started.
323 // In this case we need to wait.
329 NS_IMETHODIMP
mozPersonalDictionary::Save() {
330 nsCOMPtr
<nsIFile
> theFile
;
337 // FIXME Deinst -- get dictionary name from prefs;
338 res
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
,
339 getter_AddRefs(theFile
));
340 if (NS_FAILED(res
)) return res
;
341 if (!theFile
) return NS_ERROR_FAILURE
;
342 res
= theFile
->Append(nsLiteralString(MOZ_PERSONAL_DICT_NAME
));
343 if (NS_FAILED(res
)) return res
;
345 nsCOMPtr
<nsIEventTarget
> target
=
346 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
, &res
);
347 if (NS_WARN_IF(NS_FAILED(res
))) {
351 nsCOMPtr
<nsIRunnable
> runnable
= new mozPersonalDictionarySave(
352 this, theFile
, mozilla::ToTArray
<nsTArray
<nsString
>>(mDictionaryTable
));
353 res
= target
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
354 if (NS_WARN_IF(NS_FAILED(res
))) {
360 NS_IMETHODIMP
mozPersonalDictionary::GetWordList(nsIStringEnumerator
** aWords
) {
361 NS_ENSURE_ARG_POINTER(aWords
);
366 nsTArray
<nsString
>* array
= new nsTArray
<nsString
>(
367 mozilla::ToTArray
<nsTArray
<nsString
>>(mDictionaryTable
));
371 return NS_NewAdoptingStringEnumerator(aWords
, array
);
375 mozPersonalDictionary::Check(const nsAString
& aWord
, bool* aResult
) {
376 NS_ENSURE_ARG_POINTER(aResult
);
380 *aResult
= (mDictionaryTable
.Contains(aWord
) || mIgnoreTable
.Contains(aWord
));
385 mozPersonalDictionary::AddWord(const nsAString
& aWord
) {
389 mDictionaryTable
.Insert(aWord
);
395 mozPersonalDictionary::RemoveWord(const nsAString
& aWord
) {
399 mDictionaryTable
.Remove(aWord
);
405 mozPersonalDictionary::IgnoreWord(const nsAString
& aWord
) {
406 // avoid adding duplicate words to the ignore list
407 mIgnoreTable
.EnsureInserted(aWord
);
411 NS_IMETHODIMP
mozPersonalDictionary::EndSession() {
415 mIgnoreTable
.Clear();
419 NS_IMETHODIMP
mozPersonalDictionary::Observe(nsISupports
* aSubject
,
421 const char16_t
* aData
) {
422 if (!nsCRT::strcmp(aTopic
, "profile-do-change")) {
423 // The observer is registered in Init() which calls Load and in turn
424 // LoadInternal(); i.e. Observe() can't be called before Load().
427 Load(); // load automatically clears out the existing dictionary table
428 } else if (!nsCRT::strcmp(aTopic
, "profile-before-change")) {