|
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 "nsICharsetConverterManager.h" |
|
12 #include "nsIObserverService.h" |
|
13 #include "nsIPrefService.h" |
|
14 #include "nsIPrefBranch.h" |
|
15 #include "nsIWeakReference.h" |
|
16 #include "nsCRT.h" |
|
17 #include "nsNetUtil.h" |
|
18 #include "nsStringEnumerator.h" |
|
19 #include "nsUnicharInputStream.h" |
|
20 |
|
21 #define MOZ_PERSONAL_DICT_NAME "persdict.dat" |
|
22 |
|
23 const int kMaxWordLen=256; |
|
24 |
|
25 /** |
|
26 * This is the most braindead implementation of a personal dictionary possible. |
|
27 * There is not much complexity needed, though. It could be made much faster, |
|
28 * and probably should, but I don't see much need for more in terms of interface. |
|
29 * |
|
30 * Allowing personal words to be associated with only certain dictionaries maybe. |
|
31 * |
|
32 * TODO: |
|
33 * Implement the suggestion record. |
|
34 */ |
|
35 |
|
36 |
|
37 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozPersonalDictionary) |
|
38 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozPersonalDictionary) |
|
39 |
|
40 NS_INTERFACE_MAP_BEGIN(mozPersonalDictionary) |
|
41 NS_INTERFACE_MAP_ENTRY(mozIPersonalDictionary) |
|
42 NS_INTERFACE_MAP_ENTRY(nsIObserver) |
|
43 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
|
44 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIPersonalDictionary) |
|
45 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozPersonalDictionary) |
|
46 NS_INTERFACE_MAP_END |
|
47 |
|
48 NS_IMPL_CYCLE_COLLECTION(mozPersonalDictionary, mEncoder) |
|
49 |
|
50 mozPersonalDictionary::mozPersonalDictionary() |
|
51 : mDirty(false) |
|
52 { |
|
53 } |
|
54 |
|
55 mozPersonalDictionary::~mozPersonalDictionary() |
|
56 { |
|
57 } |
|
58 |
|
59 nsresult mozPersonalDictionary::Init() |
|
60 { |
|
61 nsCOMPtr<nsIObserverService> svc = |
|
62 do_GetService("@mozilla.org/observer-service;1"); |
|
63 |
|
64 NS_ENSURE_STATE(svc); |
|
65 // we want to reload the dictionary if the profile switches |
|
66 nsresult rv = svc->AddObserver(this, "profile-do-change", true); |
|
67 NS_ENSURE_SUCCESS(rv, rv); |
|
68 rv = svc->AddObserver(this, "profile-before-change", true); |
|
69 NS_ENSURE_SUCCESS(rv, rv); |
|
70 |
|
71 Load(); |
|
72 |
|
73 return NS_OK; |
|
74 } |
|
75 |
|
76 /* void Load (); */ |
|
77 NS_IMETHODIMP mozPersonalDictionary::Load() |
|
78 { |
|
79 //FIXME Deinst -- get dictionary name from prefs; |
|
80 nsresult res; |
|
81 nsCOMPtr<nsIFile> theFile; |
|
82 bool dictExists; |
|
83 |
|
84 res = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(theFile)); |
|
85 if(NS_FAILED(res)) return res; |
|
86 if(!theFile)return NS_ERROR_FAILURE; |
|
87 res = theFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME)); |
|
88 if(NS_FAILED(res)) return res; |
|
89 res = theFile->Exists(&dictExists); |
|
90 if(NS_FAILED(res)) return res; |
|
91 |
|
92 if (!dictExists) { |
|
93 // Nothing is really wrong... |
|
94 return NS_OK; |
|
95 } |
|
96 |
|
97 nsCOMPtr<nsIInputStream> inStream; |
|
98 NS_NewLocalFileInputStream(getter_AddRefs(inStream), theFile); |
|
99 |
|
100 nsCOMPtr<nsIUnicharInputStream> convStream; |
|
101 res = nsSimpleUnicharStreamFactory::GetInstance()-> |
|
102 CreateInstanceFromUTF8Stream(inStream, getter_AddRefs(convStream)); |
|
103 if(NS_FAILED(res)) return res; |
|
104 |
|
105 // we're rereading to get rid of the old data -- we shouldn't have any, but... |
|
106 mDictionaryTable.Clear(); |
|
107 |
|
108 char16_t c; |
|
109 uint32_t nRead; |
|
110 bool done = false; |
|
111 do{ // read each line of text into the string array. |
|
112 if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) break; |
|
113 while(!done && ((c == '\n') || (c == '\r'))){ |
|
114 if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) done = true; |
|
115 } |
|
116 if (!done){ |
|
117 nsAutoString word; |
|
118 while((c != '\n') && (c != '\r') && !done){ |
|
119 word.Append(c); |
|
120 if( (NS_OK != convStream->Read(&c, 1, &nRead)) || (nRead != 1)) done = true; |
|
121 } |
|
122 mDictionaryTable.PutEntry(word.get()); |
|
123 } |
|
124 } while(!done); |
|
125 mDirty = false; |
|
126 |
|
127 return res; |
|
128 } |
|
129 |
|
130 // A little helper function to add the key to the list. |
|
131 // This is not threadsafe, and only safe if the consumer does not |
|
132 // modify the list. |
|
133 static PLDHashOperator |
|
134 AddHostToStringArray(nsUnicharPtrHashKey *aEntry, void *aArg) |
|
135 { |
|
136 static_cast<nsTArray<nsString>*>(aArg)->AppendElement(nsDependentString(aEntry->GetKey())); |
|
137 return PL_DHASH_NEXT; |
|
138 } |
|
139 |
|
140 /* void Save (); */ |
|
141 NS_IMETHODIMP mozPersonalDictionary::Save() |
|
142 { |
|
143 nsCOMPtr<nsIFile> theFile; |
|
144 nsresult res; |
|
145 |
|
146 if(!mDirty) return NS_OK; |
|
147 |
|
148 //FIXME Deinst -- get dictionary name from prefs; |
|
149 res = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(theFile)); |
|
150 if(NS_FAILED(res)) return res; |
|
151 if(!theFile)return NS_ERROR_FAILURE; |
|
152 res = theFile->Append(NS_LITERAL_STRING(MOZ_PERSONAL_DICT_NAME)); |
|
153 if(NS_FAILED(res)) return res; |
|
154 |
|
155 nsCOMPtr<nsIOutputStream> outStream; |
|
156 NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStream), theFile, PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE ,0664); |
|
157 |
|
158 // get a buffered output stream 4096 bytes big, to optimize writes |
|
159 nsCOMPtr<nsIOutputStream> bufferedOutputStream; |
|
160 res = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream), outStream, 4096); |
|
161 if (NS_FAILED(res)) return res; |
|
162 |
|
163 nsTArray<nsString> array(mDictionaryTable.Count()); |
|
164 mDictionaryTable.EnumerateEntries(AddHostToStringArray, &array); |
|
165 |
|
166 uint32_t bytesWritten; |
|
167 nsAutoCString utf8Key; |
|
168 for (uint32_t i = 0; i < array.Length(); ++i ) { |
|
169 CopyUTF16toUTF8(array[i], utf8Key); |
|
170 |
|
171 bufferedOutputStream->Write(utf8Key.get(), utf8Key.Length(), &bytesWritten); |
|
172 bufferedOutputStream->Write("\n", 1, &bytesWritten); |
|
173 } |
|
174 nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(bufferedOutputStream); |
|
175 NS_ASSERTION(safeStream, "expected a safe output stream!"); |
|
176 if (safeStream) { |
|
177 res = safeStream->Finish(); |
|
178 if (NS_FAILED(res)) { |
|
179 NS_WARNING("failed to save personal dictionary file! possible data loss"); |
|
180 } |
|
181 } |
|
182 return res; |
|
183 } |
|
184 |
|
185 /* readonly attribute nsIStringEnumerator GetWordList() */ |
|
186 NS_IMETHODIMP mozPersonalDictionary::GetWordList(nsIStringEnumerator **aWords) |
|
187 { |
|
188 NS_ENSURE_ARG_POINTER(aWords); |
|
189 *aWords = nullptr; |
|
190 |
|
191 nsTArray<nsString> *array = new nsTArray<nsString>(mDictionaryTable.Count()); |
|
192 if (!array) |
|
193 return NS_ERROR_OUT_OF_MEMORY; |
|
194 |
|
195 mDictionaryTable.EnumerateEntries(AddHostToStringArray, array); |
|
196 |
|
197 array->Sort(); |
|
198 |
|
199 return NS_NewAdoptingStringEnumerator(aWords, array); |
|
200 } |
|
201 |
|
202 /* boolean Check (in wstring word, in wstring language); */ |
|
203 NS_IMETHODIMP mozPersonalDictionary::Check(const char16_t *aWord, const char16_t *aLanguage, bool *aResult) |
|
204 { |
|
205 NS_ENSURE_ARG_POINTER(aWord); |
|
206 NS_ENSURE_ARG_POINTER(aResult); |
|
207 |
|
208 *aResult = (mDictionaryTable.GetEntry(aWord) || mIgnoreTable.GetEntry(aWord)); |
|
209 return NS_OK; |
|
210 } |
|
211 |
|
212 /* void AddWord (in wstring word); */ |
|
213 NS_IMETHODIMP mozPersonalDictionary::AddWord(const char16_t *aWord, const char16_t *aLang) |
|
214 { |
|
215 mDictionaryTable.PutEntry(aWord); |
|
216 mDirty = true; |
|
217 return NS_OK; |
|
218 } |
|
219 |
|
220 /* void RemoveWord (in wstring word); */ |
|
221 NS_IMETHODIMP mozPersonalDictionary::RemoveWord(const char16_t *aWord, const char16_t *aLang) |
|
222 { |
|
223 mDictionaryTable.RemoveEntry(aWord); |
|
224 mDirty = true; |
|
225 return NS_OK; |
|
226 } |
|
227 |
|
228 /* void IgnoreWord (in wstring word); */ |
|
229 NS_IMETHODIMP mozPersonalDictionary::IgnoreWord(const char16_t *aWord) |
|
230 { |
|
231 // avoid adding duplicate words to the ignore list |
|
232 if (aWord && !mIgnoreTable.GetEntry(aWord)) |
|
233 mIgnoreTable.PutEntry(aWord); |
|
234 return NS_OK; |
|
235 } |
|
236 |
|
237 /* void EndSession (); */ |
|
238 NS_IMETHODIMP mozPersonalDictionary::EndSession() |
|
239 { |
|
240 Save(); // save any custom words at the end of a spell check session |
|
241 mIgnoreTable.Clear(); |
|
242 return NS_OK; |
|
243 } |
|
244 |
|
245 /* void AddCorrection (in wstring word, in wstring correction); */ |
|
246 NS_IMETHODIMP mozPersonalDictionary::AddCorrection(const char16_t *word, const char16_t *correction, const char16_t *lang) |
|
247 { |
|
248 return NS_ERROR_NOT_IMPLEMENTED; |
|
249 } |
|
250 |
|
251 /* void RemoveCorrection (in wstring word, in wstring correction); */ |
|
252 NS_IMETHODIMP mozPersonalDictionary::RemoveCorrection(const char16_t *word, const char16_t *correction, const char16_t *lang) |
|
253 { |
|
254 return NS_ERROR_NOT_IMPLEMENTED; |
|
255 } |
|
256 |
|
257 /* void GetCorrection (in wstring word, [array, size_is (count)] out wstring words, out uint32_t count); */ |
|
258 NS_IMETHODIMP mozPersonalDictionary::GetCorrection(const char16_t *word, char16_t ***words, uint32_t *count) |
|
259 { |
|
260 return NS_ERROR_NOT_IMPLEMENTED; |
|
261 } |
|
262 |
|
263 /* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */ |
|
264 NS_IMETHODIMP mozPersonalDictionary::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) |
|
265 { |
|
266 if (!nsCRT::strcmp(aTopic, "profile-do-change")) { |
|
267 Load(); // load automatically clears out the existing dictionary table |
|
268 } else if (!nsCRT::strcmp(aTopic, "profile-before-change")) { |
|
269 Save(); |
|
270 } |
|
271 |
|
272 return NS_OK; |
|
273 } |
|
274 |