| OLD | NEW |
| 1 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "app/l10n_util.h" | 5 #include "app/l10n_util.h" |
| 6 #include "chrome/browser/spellchecker.h" | 6 #include "chrome/browser/spellchecker.h" |
| 7 #include "chrome/browser/spellchecker_common.h" | 7 #include "chrome/browser/spellchecker_common.h" |
| 8 #include "chrome/browser/spellchecker_platform_engine.h" | 8 #include "chrome/browser/spellchecker_platform_engine.h" |
| 9 #include "base/basictypes.h" | 9 #include "base/basictypes.h" |
| 10 #include "base/compiler_specific.h" | 10 #include "base/compiler_specific.h" |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 79 | 79 |
| 80 // Get the fallback folder (currently chrome::DIR_USER_DATA) where the | 80 // Get the fallback folder (currently chrome::DIR_USER_DATA) where the |
| 81 // dictionary is downloaded in case of system-wide installations. | 81 // dictionary is downloaded in case of system-wide installations. |
| 82 FilePath GetFallbackDictionaryDownloadDirectory() { | 82 FilePath GetFallbackDictionaryDownloadDirectory() { |
| 83 FilePath dict_dir_userdata; | 83 FilePath dict_dir_userdata; |
| 84 PathService::Get(chrome::DIR_USER_DATA, &dict_dir_userdata); | 84 PathService::Get(chrome::DIR_USER_DATA, &dict_dir_userdata); |
| 85 dict_dir_userdata = dict_dir_userdata.AppendASCII("Dictionaries"); | 85 dict_dir_userdata = dict_dir_userdata.AppendASCII("Dictionaries"); |
| 86 return dict_dir_userdata; | 86 return dict_dir_userdata; |
| 87 } | 87 } |
| 88 | 88 |
| 89 bool SaveBufferToFile(const std::string& data, |
| 90 FilePath file_to_write) { |
| 91 int num_bytes = data.length(); |
| 92 return file_util::WriteFile(file_to_write, data.data(), num_bytes) == |
| 93 num_bytes; |
| 94 } |
| 95 |
| 89 } | 96 } |
| 90 | 97 |
| 91 void SpellChecker::SpellCheckLanguages(std::vector<std::string>* languages) { | 98 void SpellChecker::SpellCheckLanguages(std::vector<std::string>* languages) { |
| 92 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages); | 99 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages); |
| 93 ++i) | 100 ++i) |
| 94 languages->push_back(g_supported_spellchecker_languages[i].language); | 101 languages->push_back(g_supported_spellchecker_languages[i].language); |
| 95 } | 102 } |
| 96 | 103 |
| 97 // This function returns the language-region version of language name. | 104 // This function returns the language-region version of language name. |
| 98 // e.g. returns hi-IN for hi. | 105 // e.g. returns hi-IN for hi. |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 197 languages->push_back(language); | 204 languages->push_back(language); |
| 198 } | 205 } |
| 199 | 206 |
| 200 for (size_t i = 0; i < languages->size(); ++i) { | 207 for (size_t i = 0; i < languages->size(); ++i) { |
| 201 if ((*languages)[i] == dictionary_language) | 208 if ((*languages)[i] == dictionary_language) |
| 202 return i; | 209 return i; |
| 203 } | 210 } |
| 204 return -1; | 211 return -1; |
| 205 } | 212 } |
| 206 | 213 |
| 207 // This is a helper class which acts as a proxy for invoking a task from the | |
| 208 // file loop back to the IO loop. Invoking a task from file loop to the IO | |
| 209 // loop directly is not safe as during browser shutdown, the IO loop tears | |
| 210 // down before the file loop. To avoid a crash, this object is invoked in the | |
| 211 // UI loop from the file loop, from where it gets the IO thread directly from | |
| 212 // g_browser_process and invokes the given task in the IO loop if it is not | |
| 213 // NULL. This object also takes ownership of the given task. | |
| 214 class UIProxyForIOTask : public Task { | |
| 215 public: | |
| 216 explicit UIProxyForIOTask(Task* spellchecker_flag_set_task) | |
| 217 : spellchecker_flag_set_task_(spellchecker_flag_set_task) { | |
| 218 } | |
| 219 | |
| 220 private: | |
| 221 void Run() { | |
| 222 // This has been invoked in the UI thread. | |
| 223 base::Thread* io_thread = g_browser_process->io_thread(); | |
| 224 if (io_thread) { // io_thread has not been torn down yet. | |
| 225 MessageLoop* io_loop = io_thread->message_loop(); | |
| 226 if (io_loop) { | |
| 227 io_loop->PostTask(FROM_HERE, spellchecker_flag_set_task_); | |
| 228 spellchecker_flag_set_task_ = NULL; | |
| 229 } | |
| 230 } | |
| 231 } | |
| 232 | |
| 233 Task* spellchecker_flag_set_task_; | |
| 234 DISALLOW_COPY_AND_ASSIGN(UIProxyForIOTask); | |
| 235 }; | |
| 236 | |
| 237 // ############################################################################ | |
| 238 // This part of the spellchecker code is used for downloading spellchecking | |
| 239 // dictionary if required. This code is included in this file since dictionary | |
| 240 // is an integral part of spellchecker. | |
| 241 | |
| 242 // Design: The spellchecker initializes hunspell_ in the Initialize() method. | |
| 243 // This is done using the dictionary file on disk, for example, "en-US.bdic". | |
| 244 // If this file is missing, a |DictionaryDownloadController| object is used to | |
| 245 // download the missing files asynchronously (using URLFetcher) in the file | |
| 246 // thread. Initialization of hunspell_ is held off during this process. After | |
| 247 // the dictionary downloads (or fails to download), corresponding flags are set | |
| 248 // in spellchecker - in the IO thread. Since IO thread goes first during closing | |
| 249 // of browser, a proxy task |UIProxyForIOTask| is created in the UI thread, | |
| 250 // which obtains the IO thread independently and invokes the task in the IO | |
| 251 // thread if it's not NULL. After the flags are cleared, a (final) attempt is | |
| 252 // made to initialize hunspell_. If it fails even then (dictionary could not | |
| 253 // download), no more attempts are made to initialize it. | |
| 254 | |
| 255 // ############################################################################ | |
| 256 | |
| 257 // This object downloads the dictionary files asynchronously by first | |
| 258 // fetching it to memory using URL fetcher and then writing it to | |
| 259 // disk using file_util::WriteFile. | |
| 260 | |
| 261 class SpellChecker::DictionaryDownloadController | |
| 262 : public URLFetcher::Delegate, | |
| 263 public base::RefCountedThreadSafe<DictionaryDownloadController> { | |
| 264 public: | |
| 265 DictionaryDownloadController( | |
| 266 Task* spellchecker_flag_set_task, | |
| 267 const FilePath& dic_file_path, | |
| 268 URLRequestContext* url_request_context, | |
| 269 MessageLoop* ui_loop) | |
| 270 : spellchecker_flag_set_task_(spellchecker_flag_set_task), | |
| 271 url_request_context_(url_request_context), | |
| 272 ui_loop_(ui_loop) { | |
| 273 // Determine dictionary file path and name. | |
| 274 dic_zip_file_path_ = dic_file_path.DirName(); | |
| 275 file_name_ = dic_file_path.BaseName(); | |
| 276 } | |
| 277 | |
| 278 // Save the file in memory buffer to the designated dictionary file. | |
| 279 // returns the number of bytes it could save. | |
| 280 // Invoke this on the file thread. | |
| 281 void StartDownload() { | |
| 282 static const char kDownloadServerUrl[] = | |
| 283 "http://cache.pack.google.com/edgedl/chrome/dict/"; | |
| 284 | |
| 285 GURL url = GURL(std::string(kDownloadServerUrl) + WideToUTF8( | |
| 286 l10n_util::ToLower(file_name_.ToWStringHack()))); | |
| 287 fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this)); | |
| 288 fetcher_->set_request_context(url_request_context_); | |
| 289 fetcher_->Start(); | |
| 290 } | |
| 291 | |
| 292 private: | |
| 293 // The file has been downloaded in memory - need to write it down to file. | |
| 294 bool SaveBufferToFile(const std::string& data, | |
| 295 FilePath file_to_write) { | |
| 296 int num_bytes = data.length(); | |
| 297 return file_util::WriteFile(file_to_write, data.data(), num_bytes) == | |
| 298 num_bytes; | |
| 299 } | |
| 300 | |
| 301 // URLFetcher::Delegate interface. | |
| 302 virtual void OnURLFetchComplete(const URLFetcher* source, | |
| 303 const GURL& url, | |
| 304 const URLRequestStatus& status, | |
| 305 int response_code, | |
| 306 const ResponseCookies& cookies, | |
| 307 const std::string& data) { | |
| 308 DCHECK(source); | |
| 309 if ((response_code / 100) == 2 || | |
| 310 response_code == 401 || | |
| 311 response_code == 407) { | |
| 312 FilePath file_to_write = dic_zip_file_path_.Append(file_name_); | |
| 313 if (!SaveBufferToFile(data, file_to_write)) { | |
| 314 // Try saving it to user data/Dictionaries, which almost surely has | |
| 315 // write permission. If even this fails, there is nothing to be done. | |
| 316 FilePath user_data_dir = GetFallbackDictionaryDownloadDirectory(); | |
| 317 | |
| 318 // Create the directory if it does not exist. | |
| 319 if (!file_util::PathExists(user_data_dir)) | |
| 320 file_util::CreateDirectory(user_data_dir); | |
| 321 | |
| 322 file_to_write = user_data_dir.Append(file_name_); | |
| 323 SaveBufferToFile(data, file_to_write); | |
| 324 } | |
| 325 } // Unsuccessful save is taken care of in SpellChecker::Initialize(). | |
| 326 | |
| 327 // Set Flag that dictionary is not downloading anymore. | |
| 328 ui_loop_->PostTask(FROM_HERE, | |
| 329 new UIProxyForIOTask(spellchecker_flag_set_task_)); | |
| 330 fetcher_.reset(NULL); | |
| 331 } | |
| 332 | |
| 333 // factory object to invokelater back to spellchecker in io thread on | |
| 334 // download completion to change appropriate flags. | |
| 335 Task* spellchecker_flag_set_task_; | |
| 336 | |
| 337 // URLRequestContext to be used by URLFetcher. This is obtained from profile. | |
| 338 // The ownership remains with the profile. | |
| 339 URLRequestContext* url_request_context_; | |
| 340 | |
| 341 // URLFetcher to fetch the file in memory. | |
| 342 scoped_ptr<URLFetcher> fetcher_; | |
| 343 | |
| 344 // The file path where both the dic files have to be written locally. | |
| 345 FilePath dic_zip_file_path_; | |
| 346 | |
| 347 // The name of the file which has to be stored locally. | |
| 348 FilePath file_name_; | |
| 349 | |
| 350 // this invokes back to io loop when downloading is over. | |
| 351 MessageLoop* ui_loop_; | |
| 352 DISALLOW_COPY_AND_ASSIGN(DictionaryDownloadController); | |
| 353 }; | |
| 354 | |
| 355 void SpellChecker::set_file_is_downloading(bool value) { | |
| 356 dic_is_downloading_ = value; | |
| 357 } | |
| 358 | |
| 359 // ################################################################ | |
| 360 // This part of the code is used for spell checking. | |
| 361 // ################################################################ | |
| 362 | |
| 363 FilePath SpellChecker::GetVersionedFileName(const std::string& input_language, | 214 FilePath SpellChecker::GetVersionedFileName(const std::string& input_language, |
| 364 const FilePath& dict_dir) { | 215 const FilePath& dict_dir) { |
| 365 // The default dictionary version is 1-2. These versions have been augmented | 216 // The default dictionary version is 1-2. These versions have been augmented |
| 366 // with additional words found by the translation team. | 217 // with additional words found by the translation team. |
| 367 static const char kDefaultVersionString[] = "-1-2"; | 218 static const char kDefaultVersionString[] = "-1-2"; |
| 368 | 219 |
| 369 // The following dictionaries have either not been augmented with additional | 220 // The following dictionaries have either not been augmented with additional |
| 370 // words (version 1-1) or have new words, as well as an upgraded dictionary | 221 // words (version 1-1) or have new words, as well as an upgraded dictionary |
| 371 // as of Feb 2009 (version 1-3). | 222 // as of Feb 2009 (version 1-3). |
| 372 static const struct { | 223 static const struct { |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 418 language_(language), | 269 language_(language), |
| 419 #ifndef NDEBUG | 270 #ifndef NDEBUG |
| 420 worker_loop_(NULL), | 271 worker_loop_(NULL), |
| 421 #endif | 272 #endif |
| 422 tried_to_download_dictionary_file_(false), | 273 tried_to_download_dictionary_file_(false), |
| 423 file_loop_(NULL), | 274 file_loop_(NULL), |
| 424 url_request_context_(request_context), | 275 url_request_context_(request_context), |
| 425 dic_is_downloading_(false), | 276 dic_is_downloading_(false), |
| 426 auto_spell_correct_turned_on_(false), | 277 auto_spell_correct_turned_on_(false), |
| 427 is_using_platform_spelling_engine_(false), | 278 is_using_platform_spelling_engine_(false), |
| 428 ALLOW_THIS_IN_INITIALIZER_LIST( | 279 fetcher_(NULL) { |
| 429 dic_download_state_changer_factory_(this)) { | |
| 430 if (SpellCheckerPlatform::SpellCheckerAvailable()) { | 280 if (SpellCheckerPlatform::SpellCheckerAvailable()) { |
| 431 SpellCheckerPlatform::Init(); | 281 SpellCheckerPlatform::Init(); |
| 432 if (SpellCheckerPlatform::PlatformSupportsLanguage(language)) { | 282 if (SpellCheckerPlatform::PlatformSupportsLanguage(language)) { |
| 433 // If we have reached here, then we know that the current platform | 283 // If we have reached here, then we know that the current platform |
| 434 // supports the given language and we will use it instead of hunspell. | 284 // supports the given language and we will use it instead of hunspell. |
| 435 SpellCheckerPlatform::SetLanguage(language); | 285 SpellCheckerPlatform::SetLanguage(language); |
| 436 is_using_platform_spelling_engine_ = true; | 286 is_using_platform_spelling_engine_ = true; |
| 437 } | 287 } |
| 438 } | 288 } |
| 439 | 289 |
| 440 // Remember UI loop to later use this as a proxy to get IO loop. | 290 // Get the corresponding BDIC file name. |
| 441 ui_loop_ = MessageLoop::current(); | 291 bdic_file_name_ = GetVersionedFileName(language, dict_dir).BaseName(); |
| 442 | 292 |
| 443 // Get File Loop - hunspell gets initialized here. | 293 // Get File Loop - hunspell gets initialized here. |
| 444 base::Thread* file_thread = g_browser_process->file_thread(); | 294 base::Thread* file_thread = g_browser_process->file_thread(); |
| 445 if (file_thread) | 295 if (file_thread) |
| 446 file_loop_ = file_thread->message_loop(); | 296 file_loop_ = file_thread->message_loop(); |
| 447 | 297 |
| 448 // Get the path to the custom dictionary file. | 298 // Get the path to the custom dictionary file. |
| 449 if (custom_dictionary_file_name_.empty()) { | 299 if (custom_dictionary_file_name_.empty()) { |
| 450 FilePath personal_file_directory; | 300 FilePath personal_file_directory; |
| 451 PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory); | 301 PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory); |
| 452 custom_dictionary_file_name_ = | 302 custom_dictionary_file_name_ = |
| 453 personal_file_directory.Append(chrome::kCustomDictionaryFileName); | 303 personal_file_directory.Append(chrome::kCustomDictionaryFileName); |
| 454 } | 304 } |
| 455 | 305 |
| 456 // Use this dictionary language as the default one of the | 306 // Use this dictionary language as the default one of the |
| 457 // SpellcheckCharAttribute object. | 307 // SpellcheckCharAttribute object. |
| 458 character_attributes_.SetDefaultLanguage(language); | 308 character_attributes_.SetDefaultLanguage(language); |
| 459 } | 309 } |
| 460 | 310 |
| 461 SpellChecker::~SpellChecker() { | 311 SpellChecker::~SpellChecker() { |
| 462 #ifndef NDEBUG | 312 #ifndef NDEBUG |
| 463 // This must be deleted on the I/O thread (see the header). This is the same | 313 // This must be deleted on the I/O thread (see the header). This is the same |
| 464 // thread thatSpellCheckWord is called on, so we verify that they were all the | 314 // thread thatSpellCheckWord is called on, so we verify that they were all the |
| 465 // same thread. | 315 // same thread. |
| 466 if (worker_loop_) | 316 if (worker_loop_) |
| 467 DCHECK(MessageLoop::current() == worker_loop_); | 317 DCHECK(MessageLoop::current() == worker_loop_); |
| 468 #endif | 318 #endif |
| 469 } | 319 } |
| 470 | 320 |
| 471 void SpellChecker::StartDictionaryDownloadInFileThread( | 321 void SpellChecker::StartDictionaryDownload(const FilePath& file_name) { |
| 472 const FilePath& file_name) { | 322 // Determine URL of file to download. |
| 473 Task* dic_task = dic_download_state_changer_factory_.NewRunnableMethod( | 323 static const char kDownloadServerUrl[] = |
| 474 &SpellChecker::set_file_is_downloading, false); | 324 "http://cache.pack.google.com/edgedl/chrome/dict/"; |
| 475 ddc_dic_ = new DictionaryDownloadController(dic_task, file_name, | 325 GURL url = GURL(std::string(kDownloadServerUrl) + WideToUTF8( |
| 476 url_request_context_, ui_loop_); | 326 l10n_util::ToLower(bdic_file_name_.ToWStringHack()))); |
| 477 set_file_is_downloading(true); | 327 fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this)); |
| 478 file_loop_->PostTask(FROM_HERE, NewRunnableMethod(ddc_dic_.get(), | 328 fetcher_->set_request_context(url_request_context_); |
| 479 &DictionaryDownloadController::StartDownload)); | 329 dic_is_downloading_ = true; |
| 330 fetcher_->Start(); |
| 480 } | 331 } |
| 481 | 332 |
| 482 // Initialize SpellChecker. In this method, if the dicitonary is not present | 333 void SpellChecker::OnURLFetchComplete(const URLFetcher* source, |
| 334 const GURL& url, |
| 335 const URLRequestStatus& status, |
| 336 int response_code, |
| 337 const ResponseCookies& cookies, |
| 338 const std::string& data) { |
| 339 DCHECK(source); |
| 340 if ((response_code / 100) == 2 || |
| 341 response_code == 401 || |
| 342 response_code == 407) { |
| 343 FilePath file_to_write = given_dictionary_directory_.Append( |
| 344 bdic_file_name_); |
| 345 if (!SaveBufferToFile(data, file_to_write)) { |
| 346 // Try saving it to user data/Dictionaries, which almost surely has |
| 347 // write permission. If even this fails, there is nothing to be done. |
| 348 FilePath user_data_dir = GetFallbackDictionaryDownloadDirectory(); |
| 349 |
| 350 // Create the directory if it does not exist. |
| 351 if (!file_util::PathExists(user_data_dir)) |
| 352 file_util::CreateDirectory(user_data_dir); |
| 353 |
| 354 file_to_write = user_data_dir.Append(bdic_file_name_); |
| 355 SaveBufferToFile(data, file_to_write); |
| 356 } |
| 357 } // Unsuccessful save is taken care of in SpellChecker::Initialize(). |
| 358 |
| 359 dic_is_downloading_ = false; |
| 360 } |
| 361 |
| 362 // Initialize SpellChecker. In this method, if the dictionary is not present |
| 483 // in the local disk, it is fetched asynchronously. | 363 // in the local disk, it is fetched asynchronously. |
| 484 // TODO(sidchat): After dictionary is downloaded, initialize hunspell in | 364 // TODO(sidchat): After dictionary is downloaded, initialize hunspell in |
| 485 // file loop - this is currently being done in the io loop. | 365 // file loop - this is currently being done in the io loop. |
| 486 // Bug: http://b/issue?id=1123096 | 366 // Bug: http://b/issue?id=1123096 |
| 487 bool SpellChecker::Initialize() { | 367 bool SpellChecker::Initialize() { |
| 488 // Return false if the dictionary files are downloading. | 368 // Return false if the dictionary files are downloading. |
| 489 if (dic_is_downloading_) | 369 if (dic_is_downloading_) |
| 490 return false; | 370 return false; |
| 491 | 371 |
| 492 // Return false if tried to init and failed - don't try multiple times in | 372 // Return false if tried to init and failed - don't try multiple times in |
| (...skipping 26 matching lines...) Expand all Loading... |
| 519 // already resides in one of these. | 399 // already resides in one of these. |
| 520 FilePath bdic_file_name; | 400 FilePath bdic_file_name; |
| 521 if (file_util::PathExists(dictionary_file_name_app)) { | 401 if (file_util::PathExists(dictionary_file_name_app)) { |
| 522 bdic_file_name = dictionary_file_name_app; | 402 bdic_file_name = dictionary_file_name_app; |
| 523 } else if (file_util::PathExists(dictionary_file_name_usr)) { | 403 } else if (file_util::PathExists(dictionary_file_name_usr)) { |
| 524 bdic_file_name = dictionary_file_name_usr; | 404 bdic_file_name = dictionary_file_name_usr; |
| 525 } else { | 405 } else { |
| 526 // Download the dictionary file. | 406 // Download the dictionary file. |
| 527 if (file_loop_ && url_request_context_) { | 407 if (file_loop_ && url_request_context_) { |
| 528 if (!tried_to_download_dictionary_file_) { | 408 if (!tried_to_download_dictionary_file_) { |
| 529 StartDictionaryDownloadInFileThread(dictionary_file_name_app); | 409 StartDictionaryDownload(dictionary_file_name_app); |
| 530 tried_to_download_dictionary_file_ = true; | 410 tried_to_download_dictionary_file_ = true; |
| 531 return false; | 411 return false; |
| 532 } else { // There is no dictionary even after trying to download it. | 412 } else { // There is no dictionary even after trying to download it. |
| 533 // Stop trying to download the dictionary in this session. | 413 // Stop trying to download the dictionary in this session. |
| 534 tried_to_init_ = true; | 414 tried_to_init_ = true; |
| 535 return false; | 415 return false; |
| 536 } | 416 } |
| 537 } | 417 } |
| 538 } | 418 } |
| 539 | 419 |
| (...skipping 247 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 787 | 667 |
| 788 // Populate the vector of WideStrings. | 668 // Populate the vector of WideStrings. |
| 789 for (int i = 0; i < number_of_suggestions; i++) { | 669 for (int i = 0; i < number_of_suggestions; i++) { |
| 790 if (i < kMaxSuggestions) | 670 if (i < kMaxSuggestions) |
| 791 optional_suggestions->push_back(UTF8ToWide(suggestions[i])); | 671 optional_suggestions->push_back(UTF8ToWide(suggestions[i])); |
| 792 free(suggestions[i]); | 672 free(suggestions[i]); |
| 793 } | 673 } |
| 794 if (suggestions != NULL) | 674 if (suggestions != NULL) |
| 795 free(suggestions); | 675 free(suggestions); |
| 796 } | 676 } |
| OLD | NEW |