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