Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(413)

Side by Side Diff: chrome/browser/spellchecker.cc

Issue 165175: [chromium-reviews] Fix a spell check dictionary download bug, where killing the spell checker wh... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« chrome/browser/spellchecker.h ('K') | « chrome/browser/spellchecker.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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 }
OLDNEW
« chrome/browser/spellchecker.h ('K') | « chrome/browser/spellchecker.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698