blob: 5413ce51b70661d626b9622104564ae2afb29895 [file] [log] [blame]
[email protected]85c55dc2009-11-06 03:05:461// Copyright (c) 2009 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.h"
6
7#include <fcntl.h>
8
9#include "app/l10n_util.h"
10#include "base/file_util.h"
11#include "base/logging.h"
12#include "base/path_service.h"
[email protected]1cb2dac2010-03-08 21:49:1513#include "base/utf_string_conversions.h"
[email protected]37858e52010-08-26 00:22:0214#include "chrome/browser/prefs/pref_member.h"
[email protected]c27324b2009-11-19 22:44:2915#include "chrome/browser/profile.h"
[email protected]9c92d192009-12-02 08:03:1616#include "chrome/browser/spellcheck_host_observer.h"
[email protected]c27324b2009-11-19 22:44:2917#include "chrome/browser/spellchecker_platform_engine.h"
[email protected]85c55dc2009-11-06 03:05:4618#include "chrome/common/chrome_constants.h"
19#include "chrome/common/chrome_paths.h"
[email protected]68d2a05f2010-05-07 21:39:5520#include "chrome/common/net/url_request_context_getter.h"
[email protected]85c55dc2009-11-06 03:05:4621#include "chrome/common/notification_service.h"
[email protected]c27324b2009-11-19 22:44:2922#include "chrome/common/pref_names.h"
23#include "chrome/common/spellcheck_common.h"
[email protected]85c55dc2009-11-06 03:05:4624#include "googleurl/src/gurl.h"
25
26namespace {
27
[email protected]cb6037d2009-11-16 22:55:1728FilePath GetFirstChoiceFilePath(const std::string& language) {
29 FilePath dict_dir;
30 PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
[email protected]c27324b2009-11-19 22:44:2931 return SpellCheckCommon::GetVersionedFileName(language, dict_dir);
[email protected]cb6037d2009-11-16 22:55:1732}
33
[email protected]16f765632010-09-21 21:31:2734#if defined(OS_WIN)
[email protected]cb6037d2009-11-16 22:55:1735FilePath GetFallbackFilePath(const FilePath& first_choice) {
36 FilePath dict_dir;
37 PathService::Get(chrome::DIR_USER_DATA, &dict_dir);
38 return dict_dir.Append(first_choice.BaseName());
39}
[email protected]16f765632010-09-21 21:31:2740#endif
[email protected]cb6037d2009-11-16 22:55:1741
[email protected]85c55dc2009-11-06 03:05:4642} // namespace
43
44// Constructed on UI thread.
[email protected]9c92d192009-12-02 08:03:1645SpellCheckHost::SpellCheckHost(SpellCheckHostObserver* observer,
[email protected]85c55dc2009-11-06 03:05:4646 const std::string& language,
47 URLRequestContextGetter* request_context_getter)
48 : observer_(observer),
49 language_(language),
[email protected]cb6037d2009-11-16 22:55:1750 file_(base::kInvalidPlatformFileValue),
[email protected]85c55dc2009-11-06 03:05:4651 tried_to_download_(false),
[email protected]c27324b2009-11-19 22:44:2952 use_platform_spellchecker_(false),
[email protected]85c55dc2009-11-06 03:05:4653 request_context_getter_(request_context_getter) {
54 DCHECK(observer_);
55 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
56
[email protected]85c55dc2009-11-06 03:05:4657 FilePath personal_file_directory;
58 PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory);
59 custom_dictionary_file_ =
60 personal_file_directory.Append(chrome::kCustomDictionaryFileName);
[email protected]cb6037d2009-11-16 22:55:1761
62 bdict_file_path_ = GetFirstChoiceFilePath(language);
[email protected]85c55dc2009-11-06 03:05:4663}
64
65SpellCheckHost::~SpellCheckHost() {
[email protected]cb6037d2009-11-16 22:55:1766 if (file_ != base::kInvalidPlatformFileValue)
67 base::ClosePlatformFile(file_);
[email protected]85c55dc2009-11-06 03:05:4668}
69
[email protected]f41301e2009-11-16 21:30:0770void SpellCheckHost::Initialize() {
[email protected]c27324b2009-11-19 22:44:2971 if (SpellCheckerPlatform::SpellCheckerAvailable() &&
72 SpellCheckerPlatform::PlatformSupportsLanguage(language_)) {
73 use_platform_spellchecker_ = true;
74 SpellCheckerPlatform::SetLanguage(language_);
75 MessageLoop::current()->PostTask(FROM_HERE,
76 NewRunnableMethod(this,
77 &SpellCheckHost::InformObserverOfInitialization));
78 return;
79 }
80
[email protected]f41301e2009-11-16 21:30:0781 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
[email protected]cb6037d2009-11-16 22:55:1782 NewRunnableMethod(this, &SpellCheckHost::InitializeDictionaryLocation));
[email protected]f41301e2009-11-16 21:30:0783}
84
[email protected]85c55dc2009-11-06 03:05:4685void SpellCheckHost::UnsetObserver() {
86 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
87
88 observer_ = NULL;
[email protected]a0ef9e12009-11-17 00:15:4989 request_context_getter_ = NULL;
90 fetcher_.reset();
[email protected]85c55dc2009-11-06 03:05:4691}
92
93void SpellCheckHost::AddWord(const std::string& word) {
94 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
95
96 custom_words_.push_back(word);
97 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
98 NewRunnableMethod(this,
99 &SpellCheckHost::WriteWordToCustomDictionary, word));
100 NotificationService::current()->Notify(
101 NotificationType::SPELLCHECK_WORD_ADDED,
102 Source<SpellCheckHost>(this), NotificationService::NoDetails());
103}
104
[email protected]c27324b2009-11-19 22:44:29105// static
106int SpellCheckHost::GetSpellCheckLanguages(
107 Profile* profile,
108 std::vector<std::string>* languages) {
109 StringPrefMember accept_languages_pref;
110 StringPrefMember dictionary_language_pref;
111 accept_languages_pref.Init(prefs::kAcceptLanguages, profile->GetPrefs(),
112 NULL);
113 dictionary_language_pref.Init(prefs::kSpellCheckDictionary,
114 profile->GetPrefs(), NULL);
[email protected]ddd231e2010-06-29 20:35:19115 std::string dictionary_language = dictionary_language_pref.GetValue();
[email protected]c27324b2009-11-19 22:44:29116
117 // The current dictionary language should be there.
118 languages->push_back(dictionary_language);
119
120 // Now scan through the list of accept languages, and find possible mappings
121 // from this list to the existing list of spell check languages.
122 std::vector<std::string> accept_languages;
123
[email protected]ddd231e2010-06-29 20:35:19124 if (SpellCheckerPlatform::SpellCheckerAvailable())
[email protected]c27324b2009-11-19 22:44:29125 SpellCheckerPlatform::GetAvailableLanguages(&accept_languages);
[email protected]ddd231e2010-06-29 20:35:19126 else
127 SplitString(accept_languages_pref.GetValue(), ',', &accept_languages);
128
[email protected]c27324b2009-11-19 22:44:29129 for (std::vector<std::string>::const_iterator i = accept_languages.begin();
130 i != accept_languages.end(); ++i) {
131 std::string language =
132 SpellCheckCommon::GetCorrespondingSpellCheckLanguage(*i);
133 if (!language.empty() &&
134 std::find(languages->begin(), languages->end(), language) ==
135 languages->end()) {
136 languages->push_back(language);
137 }
138 }
139
140 for (size_t i = 0; i < languages->size(); ++i) {
141 if ((*languages)[i] == dictionary_language)
142 return i;
143 }
144 return -1;
145}
146
[email protected]cb6037d2009-11-16 22:55:17147void SpellCheckHost::InitializeDictionaryLocation() {
148 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
149
150#if defined(OS_WIN)
151 // Check if the dictionary exists in the fallback location. If so, use it
152 // rather than downloading anew.
153 FilePath fallback = GetFallbackFilePath(bdict_file_path_);
154 if (!file_util::PathExists(bdict_file_path_) &&
155 file_util::PathExists(fallback)) {
156 bdict_file_path_ = fallback;
157 }
158#endif
159
160 InitializeInternal();
161}
162
[email protected]f41301e2009-11-16 21:30:07163void SpellCheckHost::InitializeInternal() {
[email protected]85c55dc2009-11-06 03:05:46164 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
165
166 if (!observer_)
167 return;
168
[email protected]ed65fec2010-08-31 19:30:27169 file_ = base::CreatePlatformFile(
170 bdict_file_path_,
[email protected]cb6037d2009-11-16 22:55:17171 base::PLATFORM_FILE_READ | base::PLATFORM_FILE_OPEN,
[email protected]ed65fec2010-08-31 19:30:27172 NULL, NULL);
[email protected]85c55dc2009-11-06 03:05:46173
174 // File didn't exist. Download it.
[email protected]a0ef9e12009-11-17 00:15:49175 if (file_ == base::kInvalidPlatformFileValue && !tried_to_download_ &&
176 request_context_getter_) {
177 // We download from the ui thread because we need to know that
178 // |request_context_getter_| is still valid before initiating the download.
179 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
180 NewRunnableMethod(this, &SpellCheckHost::DownloadDictionary));
[email protected]85c55dc2009-11-06 03:05:46181 return;
182 }
183
[email protected]a0ef9e12009-11-17 00:15:49184 request_context_getter_ = NULL;
185
[email protected]cb6037d2009-11-16 22:55:17186 if (file_ != base::kInvalidPlatformFileValue) {
[email protected]85c55dc2009-11-06 03:05:46187 // Load custom dictionary.
188 std::string contents;
189 file_util::ReadFileToString(custom_dictionary_file_, &contents);
190 std::vector<std::string> list_of_words;
191 SplitString(contents, '\n', &list_of_words);
192 for (size_t i = 0; i < list_of_words.size(); ++i)
193 custom_words_.push_back(list_of_words[i]);
194 }
195
196 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
197 NewRunnableMethod(this,
198 &SpellCheckHost::InformObserverOfInitialization));
199}
200
[email protected]a0ef9e12009-11-17 00:15:49201void SpellCheckHost::InitializeOnFileThread() {
202 DCHECK(!ChromeThread::CurrentlyOn(ChromeThread::FILE));
203
204 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
205 NewRunnableMethod(this, &SpellCheckHost::Initialize));
206}
207
[email protected]85c55dc2009-11-06 03:05:46208void SpellCheckHost::InformObserverOfInitialization() {
209 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
210
211 if (observer_)
212 observer_->SpellCheckHostInitialized();
213}
214
215void SpellCheckHost::DownloadDictionary() {
[email protected]a0ef9e12009-11-17 00:15:49216 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
217
218 if (!request_context_getter_) {
219 InitializeOnFileThread();
220 return;
221 }
[email protected]85c55dc2009-11-06 03:05:46222
223 // Determine URL of file to download.
224 static const char kDownloadServerUrl[] =
225 "http://cache.pack.google.com/edgedl/chrome/dict/";
[email protected]e5a8c472010-08-04 19:47:20226 GURL url = GURL(std::string(kDownloadServerUrl) +
227 StringToLowerASCII(WideToUTF8(
228 bdict_file_path_.BaseName().ToWStringHack())));
[email protected]85c55dc2009-11-06 03:05:46229 fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this));
[email protected]a0ef9e12009-11-17 00:15:49230 fetcher_->set_request_context(request_context_getter_);
[email protected]85c55dc2009-11-06 03:05:46231 tried_to_download_ = true;
232 fetcher_->Start();
[email protected]a0ef9e12009-11-17 00:15:49233 request_context_getter_ = NULL;
[email protected]85c55dc2009-11-06 03:05:46234}
235
236void SpellCheckHost::WriteWordToCustomDictionary(const std::string& word) {
237 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
238
239 // Stored in UTF-8.
240 std::string word_to_add(word + "\n");
241 FILE* f = file_util::OpenFile(custom_dictionary_file_, "a+");
242 if (f != NULL)
243 fputs(word_to_add.c_str(), f);
244 file_util::CloseFile(f);
245}
246
247void SpellCheckHost::OnURLFetchComplete(const URLFetcher* source,
248 const GURL& url,
249 const URLRequestStatus& status,
250 int response_code,
251 const ResponseCookies& cookies,
252 const std::string& data) {
253 DCHECK(source);
[email protected]a0ef9e12009-11-17 00:15:49254 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
255 fetcher_.reset();
[email protected]85c55dc2009-11-06 03:05:46256
257 if ((response_code / 100) != 2) {
258 // Initialize will not try to download the file a second time.
259 LOG(ERROR) << "Failure to download dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49260 InitializeOnFileThread();
[email protected]85c55dc2009-11-06 03:05:46261 return;
262 }
263
264 // Basic sanity check on the dictionary.
265 // There's the small chance that we might see a 200 status code for a body
266 // that represents some form of failure.
267 if (data.size() < 4 || data[0] != 'B' || data[1] != 'D' || data[2] != 'i' ||
268 data[3] != 'c') {
269 LOG(ERROR) << "Failure to download dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49270 InitializeOnFileThread();
[email protected]85c55dc2009-11-06 03:05:46271 return;
272 }
273
[email protected]a0ef9e12009-11-17 00:15:49274 data_ = data;
275 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
276 NewRunnableMethod(this, &SpellCheckHost::SaveDictionaryData));
277}
278
279void SpellCheckHost::SaveDictionaryData() {
280 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
281
[email protected]85c55dc2009-11-06 03:05:46282 size_t bytes_written =
[email protected]a0ef9e12009-11-17 00:15:49283 file_util::WriteFile(bdict_file_path_, data_.data(), data_.length());
284 if (bytes_written != data_.length()) {
[email protected]cb6037d2009-11-16 22:55:17285 bool success = false;
286#if defined(OS_WIN)
287 bdict_file_path_ = GetFallbackFilePath(bdict_file_path_);
288 bytes_written =
289 file_util::WriteFile(GetFallbackFilePath(bdict_file_path_),
[email protected]a0ef9e12009-11-17 00:15:49290 data_.data(), data_.length());
291 if (bytes_written == data_.length())
[email protected]cb6037d2009-11-16 22:55:17292 success = true;
293#endif
[email protected]a0ef9e12009-11-17 00:15:49294 data_.clear();
[email protected]cb6037d2009-11-16 22:55:17295
296 if (!success) {
297 LOG(ERROR) << "Failure to save dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49298 file_util::Delete(bdict_file_path_, false);
[email protected]cb6037d2009-11-16 22:55:17299 // To avoid trying to load a partially saved dictionary, shortcut the
300 // Initialize() call.
301 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
302 NewRunnableMethod(this,
303 &SpellCheckHost::InformObserverOfInitialization));
304 return;
305 }
[email protected]85c55dc2009-11-06 03:05:46306 }
307
[email protected]a0ef9e12009-11-17 00:15:49308 data_.clear();
[email protected]85c55dc2009-11-06 03:05:46309 Initialize();
310}