spellcheck_host_impl.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/spellcheck_host_impl.h" 6 7#include <set> 8 9#include "base/file_util.h" 10#include "base/logging.h" 11#include "base/path_service.h" 12#include "base/string_split.h" 13#include "base/threading/thread_restrictions.h" 14#include "base/utf_string_conversions.h" 15#include "chrome/browser/spellcheck_host_observer.h" 16#include "chrome/browser/spellchecker_platform_engine.h" 17#include "chrome/common/chrome_constants.h" 18#include "chrome/common/chrome_paths.h" 19#include "chrome/common/spellcheck_common.h" 20#include "content/common/notification_service.h" 21#include "googleurl/src/gurl.h" 22#include "net/url_request/url_request_context_getter.h" 23#include "third_party/hunspell/google/bdict.h" 24#include "ui/base/l10n/l10n_util.h" 25#if defined(OS_MACOSX) 26#include "base/metrics/histogram.h" 27#endif 28 29namespace { 30 31FilePath GetFirstChoiceFilePath(const std::string& language) { 32 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 33 34 FilePath dict_dir; 35 PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir); 36 return SpellCheckCommon::GetVersionedFileName(language, dict_dir); 37} 38 39#if defined(OS_MACOSX) 40// Collect metrics on how often Hunspell is used on OS X vs the native 41// spellchecker. 42void RecordSpellCheckStats(bool native_spellchecker_used, 43 const std::string& language) { 44 static std::set<std::string> languages_seen; 45 46 // Only count a language code once for each session.. 47 if (languages_seen.find(language) != languages_seen.end()) { 48 return; 49 } 50 languages_seen.insert(language); 51 52 enum { 53 SPELLCHECK_OSX_NATIVE_SPELLCHECKER_USED = 0, 54 SPELLCHECK_HUNSPELL_USED = 1 55 }; 56 57 bool engine_used = native_spellchecker_used ? 58 SPELLCHECK_OSX_NATIVE_SPELLCHECKER_USED : 59 SPELLCHECK_HUNSPELL_USED; 60 61 UMA_HISTOGRAM_COUNTS("SpellCheck.OSXEngineUsed", engine_used); 62} 63#endif 64 65#if defined(OS_WIN) 66FilePath GetFallbackFilePath(const FilePath& first_choice) { 67 FilePath dict_dir; 68 PathService::Get(chrome::DIR_USER_DATA, &dict_dir); 69 return dict_dir.Append(first_choice.BaseName()); 70} 71#endif 72 73} // namespace 74 75// Constructed on UI thread. 76SpellCheckHostImpl::SpellCheckHostImpl( 77 SpellCheckHostObserver* observer, 78 const std::string& language, 79 net::URLRequestContextGetter* request_context_getter) 80 : observer_(observer), 81 language_(language), 82 file_(base::kInvalidPlatformFileValue), 83 tried_to_download_(false), 84 use_platform_spellchecker_(false), 85 request_context_getter_(request_context_getter) { 86 DCHECK(observer_); 87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 88 89 FilePath personal_file_directory; 90 PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory); 91 custom_dictionary_file_ = 92 personal_file_directory.Append(chrome::kCustomDictionaryFileName); 93} 94 95SpellCheckHostImpl::~SpellCheckHostImpl() { 96 if (file_ != base::kInvalidPlatformFileValue) 97 base::ClosePlatformFile(file_); 98} 99 100void SpellCheckHostImpl::Initialize() { 101 if (SpellCheckerPlatform::SpellCheckerAvailable() && 102 SpellCheckerPlatform::PlatformSupportsLanguage(language_)) { 103#if defined(OS_MACOSX) 104 RecordSpellCheckStats(true, language_); 105#endif 106 use_platform_spellchecker_ = true; 107 SpellCheckerPlatform::SetLanguage(language_); 108 MessageLoop::current()->PostTask(FROM_HERE, 109 NewRunnableMethod(this, 110 &SpellCheckHostImpl::InformObserverOfInitialization)); 111 return; 112 } 113 114#if defined(OS_MACOSX) 115 RecordSpellCheckStats(false, language_); 116#endif 117 118 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 119 NewRunnableMethod(this, 120 &SpellCheckHostImpl::InitializeDictionaryLocation)); 121} 122 123void SpellCheckHostImpl::UnsetObserver() { 124 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 125 126 observer_ = NULL; 127 request_context_getter_ = NULL; 128 fetcher_.reset(); 129} 130 131void SpellCheckHostImpl::AddWord(const std::string& word) { 132 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 133 134 custom_words_.push_back(word); 135 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 136 NewRunnableMethod(this, 137 &SpellCheckHostImpl::WriteWordToCustomDictionary, word)); 138 NotificationService::current()->Notify( 139 NotificationType::SPELLCHECK_WORD_ADDED, 140 Source<SpellCheckHost>(this), NotificationService::NoDetails()); 141} 142 143void SpellCheckHostImpl::InitializeDictionaryLocation() { 144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 145 146 // Initialize the BDICT path. This initialization should be in the FILE thread 147 // because it checks if there is a "Dictionaries" directory and create it. 148 if (bdict_file_path_.empty()) 149 bdict_file_path_ = GetFirstChoiceFilePath(language_); 150 151#if defined(OS_WIN) 152 // Check if the dictionary exists in the fallback location. If so, use it 153 // rather than downloading anew. 154 FilePath fallback = GetFallbackFilePath(bdict_file_path_); 155 if (!file_util::PathExists(bdict_file_path_) && 156 file_util::PathExists(fallback)) { 157 bdict_file_path_ = fallback; 158 } 159#endif 160 161 InitializeInternal(); 162} 163 164void SpellCheckHostImpl::InitializeInternal() { 165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 166 167 if (!observer_) 168 return; 169 170 file_ = base::CreatePlatformFile( 171 bdict_file_path_, 172 base::PLATFORM_FILE_READ | base::PLATFORM_FILE_OPEN, 173 NULL, NULL); 174 175 // File didn't exist. Download it. 176 if (file_ == base::kInvalidPlatformFileValue && !tried_to_download_ && 177 request_context_getter_) { 178 // We download from the ui thread because we need to know that 179 // |request_context_getter_| is still valid before initiating the download. 180 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 181 NewRunnableMethod(this, &SpellCheckHostImpl::DownloadDictionary)); 182 return; 183 } 184 185 request_context_getter_ = NULL; 186 187 if (file_ != base::kInvalidPlatformFileValue) { 188 // Load custom dictionary. 189 std::string contents; 190 file_util::ReadFileToString(custom_dictionary_file_, &contents); 191 std::vector<std::string> list_of_words; 192 base::SplitString(contents, '\n', &list_of_words); 193 for (size_t i = 0; i < list_of_words.size(); ++i) 194 custom_words_.push_back(list_of_words[i]); 195 } 196 197 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 198 NewRunnableMethod(this, 199 &SpellCheckHostImpl::InformObserverOfInitialization)); 200} 201 202void SpellCheckHostImpl::InitializeOnFileThread() { 203 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); 204 205 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 206 NewRunnableMethod(this, &SpellCheckHostImpl::Initialize)); 207} 208 209void SpellCheckHostImpl::InformObserverOfInitialization() { 210 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 211 212 if (observer_) 213 observer_->SpellCheckHostInitialized(); 214} 215 216void SpellCheckHostImpl::DownloadDictionary() { 217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 218 219 if (!request_context_getter_) { 220 InitializeOnFileThread(); 221 return; 222 } 223 224 // Determine URL of file to download. 225 static const char kDownloadServerUrl[] = 226 "http://cache.pack.google.com/edgedl/chrome/dict/"; 227 std::string bdict_file = bdict_file_path_.BaseName().MaybeAsASCII(); 228 if (bdict_file.empty()) { 229 NOTREACHED(); 230 return; 231 } 232 GURL url = GURL(std::string(kDownloadServerUrl) + 233 StringToLowerASCII(bdict_file)); 234 fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this)); 235 fetcher_->set_request_context(request_context_getter_); 236 tried_to_download_ = true; 237 fetcher_->Start(); 238 request_context_getter_ = NULL; 239} 240 241void SpellCheckHostImpl::WriteWordToCustomDictionary(const std::string& word) { 242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 243 244 // Stored in UTF-8. 245 std::string word_to_add(word + "\n"); 246 FILE* f = file_util::OpenFile(custom_dictionary_file_, "a+"); 247 if (f) 248 fputs(word_to_add.c_str(), f); 249 file_util::CloseFile(f); 250} 251 252void SpellCheckHostImpl::OnURLFetchComplete(const URLFetcher* source, 253 const GURL& url, 254 const net::URLRequestStatus& status, 255 int response_code, 256 const ResponseCookies& cookies, 257 const std::string& data) { 258 DCHECK(source); 259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 260 fetcher_.reset(); 261 262 if ((response_code / 100) != 2) { 263 // Initialize will not try to download the file a second time. 264 LOG(ERROR) << "Failure to download dictionary."; 265 InitializeOnFileThread(); 266 return; 267 } 268 269 // Basic sanity check on the dictionary. 270 // There's the small chance that we might see a 200 status code for a body 271 // that represents some form of failure. 272 if (data.size() < 4 || data[0] != 'B' || data[1] != 'D' || data[2] != 'i' || 273 data[3] != 'c') { 274 LOG(ERROR) << "Failure to download dictionary."; 275 InitializeOnFileThread(); 276 return; 277 } 278 279 data_ = data; 280 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 281 NewRunnableMethod(this, &SpellCheckHostImpl::SaveDictionaryData)); 282} 283 284void SpellCheckHostImpl::SaveDictionaryData() { 285 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 286 287 // To prevent corrupted dictionary data from causing a renderer crash, scan 288 // the dictionary data and verify it is sane before save it to a file. 289 if (!hunspell::BDict::Verify(data_.data(), data_.size())) { 290 LOG(ERROR) << "Failure to verify the downloaded dictionary."; 291 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 292 NewRunnableMethod(this, 293 &SpellCheckHostImpl::InformObserverOfInitialization)); 294 return; 295 } 296 297 size_t bytes_written = 298 file_util::WriteFile(bdict_file_path_, data_.data(), data_.length()); 299 if (bytes_written != data_.length()) { 300 bool success = false; 301#if defined(OS_WIN) 302 bdict_file_path_ = GetFallbackFilePath(bdict_file_path_); 303 bytes_written = 304 file_util::WriteFile(GetFallbackFilePath(bdict_file_path_), 305 data_.data(), data_.length()); 306 if (bytes_written == data_.length()) 307 success = true; 308#endif 309 data_.clear(); 310 311 if (!success) { 312 LOG(ERROR) << "Failure to save dictionary."; 313 file_util::Delete(bdict_file_path_, false); 314 // To avoid trying to load a partially saved dictionary, shortcut the 315 // Initialize() call. 316 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 317 NewRunnableMethod(this, 318 &SpellCheckHostImpl::InformObserverOfInitialization)); 319 return; 320 } 321 } 322 323 data_.clear(); 324 Initialize(); 325} 326 327const base::PlatformFile& SpellCheckHostImpl::GetDictionaryFile() const { 328 return file_; 329} 330 331const std::vector<std::string>& SpellCheckHostImpl::GetCustomWords() const { 332 return custom_words_; 333} 334 335const std::string& SpellCheckHostImpl::GetLastAddedFile() const { 336 return custom_words_.back(); 337} 338 339const std::string& SpellCheckHostImpl::GetLanguage() const { 340 return language_; 341} 342 343bool SpellCheckHostImpl::IsUsingPlatformChecker() const { 344 return use_platform_spellchecker_; 345} 346