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 : }
|