blob: ceead6bd0d61f81625b4d13404d1dcf0a0c4bf33 [file] [log] [blame]
[email protected]4e5ae20f2010-09-24 04:52:111// Copyright (c) 2010 The Chromium Authors. All rights reserved.
[email protected]85c55dc2009-11-06 03:05:462// 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]4e5ae20f2010-09-24 04:52:1113#include "base/string_split.h"
[email protected]1cb2dac2010-03-08 21:49:1514#include "base/utf_string_conversions.h"
[email protected]37858e52010-08-26 00:22:0215#include "chrome/browser/prefs/pref_member.h"
[email protected]c27324b2009-11-19 22:44:2916#include "chrome/browser/profile.h"
[email protected]9c92d192009-12-02 08:03:1617#include "chrome/browser/spellcheck_host_observer.h"
[email protected]c27324b2009-11-19 22:44:2918#include "chrome/browser/spellchecker_platform_engine.h"
[email protected]85c55dc2009-11-06 03:05:4619#include "chrome/common/chrome_constants.h"
20#include "chrome/common/chrome_paths.h"
[email protected]68d2a05f2010-05-07 21:39:5521#include "chrome/common/net/url_request_context_getter.h"
[email protected]85c55dc2009-11-06 03:05:4622#include "chrome/common/notification_service.h"
[email protected]c27324b2009-11-19 22:44:2923#include "chrome/common/pref_names.h"
24#include "chrome/common/spellcheck_common.h"
[email protected]85c55dc2009-11-06 03:05:4625#include "googleurl/src/gurl.h"
26
27namespace {
28
[email protected]cb6037d2009-11-16 22:55:1729FilePath GetFirstChoiceFilePath(const std::string& language) {
30 FilePath dict_dir;
31 PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
[email protected]c27324b2009-11-19 22:44:2932 return SpellCheckCommon::GetVersionedFileName(language, dict_dir);
[email protected]cb6037d2009-11-16 22:55:1733}
34
[email protected]16f765632010-09-21 21:31:2735#if defined(OS_WIN)
[email protected]cb6037d2009-11-16 22:55:1736FilePath GetFallbackFilePath(const FilePath& first_choice) {
37 FilePath dict_dir;
38 PathService::Get(chrome::DIR_USER_DATA, &dict_dir);
39 return dict_dir.Append(first_choice.BaseName());
40}
[email protected]16f765632010-09-21 21:31:2741#endif
[email protected]cb6037d2009-11-16 22:55:1742
[email protected]85c55dc2009-11-06 03:05:4643} // namespace
44
45// Constructed on UI thread.
[email protected]9c92d192009-12-02 08:03:1646SpellCheckHost::SpellCheckHost(SpellCheckHostObserver* observer,
[email protected]85c55dc2009-11-06 03:05:4647 const std::string& language,
48 URLRequestContextGetter* request_context_getter)
49 : observer_(observer),
50 language_(language),
[email protected]cb6037d2009-11-16 22:55:1751 file_(base::kInvalidPlatformFileValue),
[email protected]85c55dc2009-11-06 03:05:4652 tried_to_download_(false),
[email protected]c27324b2009-11-19 22:44:2953 use_platform_spellchecker_(false),
[email protected]85c55dc2009-11-06 03:05:4654 request_context_getter_(request_context_getter) {
55 DCHECK(observer_);
56 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
57
[email protected]85c55dc2009-11-06 03:05:4658 FilePath personal_file_directory;
59 PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory);
60 custom_dictionary_file_ =
61 personal_file_directory.Append(chrome::kCustomDictionaryFileName);
[email protected]cb6037d2009-11-16 22:55:1762
63 bdict_file_path_ = GetFirstChoiceFilePath(language);
[email protected]85c55dc2009-11-06 03:05:4664}
65
66SpellCheckHost::~SpellCheckHost() {
[email protected]cb6037d2009-11-16 22:55:1767 if (file_ != base::kInvalidPlatformFileValue)
68 base::ClosePlatformFile(file_);
[email protected]85c55dc2009-11-06 03:05:4669}
70
[email protected]f41301e2009-11-16 21:30:0771void SpellCheckHost::Initialize() {
[email protected]c27324b2009-11-19 22:44:2972 if (SpellCheckerPlatform::SpellCheckerAvailable() &&
73 SpellCheckerPlatform::PlatformSupportsLanguage(language_)) {
74 use_platform_spellchecker_ = true;
75 SpellCheckerPlatform::SetLanguage(language_);
76 MessageLoop::current()->PostTask(FROM_HERE,
77 NewRunnableMethod(this,
78 &SpellCheckHost::InformObserverOfInitialization));
79 return;
80 }
81
[email protected]f41301e2009-11-16 21:30:0782 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
[email protected]cb6037d2009-11-16 22:55:1783 NewRunnableMethod(this, &SpellCheckHost::InitializeDictionaryLocation));
[email protected]f41301e2009-11-16 21:30:0784}
85
[email protected]85c55dc2009-11-06 03:05:4686void SpellCheckHost::UnsetObserver() {
87 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
88
89 observer_ = NULL;
[email protected]a0ef9e12009-11-17 00:15:4990 request_context_getter_ = NULL;
91 fetcher_.reset();
[email protected]85c55dc2009-11-06 03:05:4692}
93
94void SpellCheckHost::AddWord(const std::string& word) {
95 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
96
97 custom_words_.push_back(word);
98 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
99 NewRunnableMethod(this,
100 &SpellCheckHost::WriteWordToCustomDictionary, word));
101 NotificationService::current()->Notify(
102 NotificationType::SPELLCHECK_WORD_ADDED,
103 Source<SpellCheckHost>(this), NotificationService::NoDetails());
104}
105
[email protected]c27324b2009-11-19 22:44:29106// static
107int SpellCheckHost::GetSpellCheckLanguages(
108 Profile* profile,
109 std::vector<std::string>* languages) {
110 StringPrefMember accept_languages_pref;
111 StringPrefMember dictionary_language_pref;
112 accept_languages_pref.Init(prefs::kAcceptLanguages, profile->GetPrefs(),
113 NULL);
114 dictionary_language_pref.Init(prefs::kSpellCheckDictionary,
115 profile->GetPrefs(), NULL);
[email protected]ddd231e2010-06-29 20:35:19116 std::string dictionary_language = dictionary_language_pref.GetValue();
[email protected]c27324b2009-11-19 22:44:29117
118 // The current dictionary language should be there.
119 languages->push_back(dictionary_language);
120
121 // Now scan through the list of accept languages, and find possible mappings
122 // from this list to the existing list of spell check languages.
123 std::vector<std::string> accept_languages;
124
[email protected]ddd231e2010-06-29 20:35:19125 if (SpellCheckerPlatform::SpellCheckerAvailable())
[email protected]c27324b2009-11-19 22:44:29126 SpellCheckerPlatform::GetAvailableLanguages(&accept_languages);
[email protected]ddd231e2010-06-29 20:35:19127 else
128 SplitString(accept_languages_pref.GetValue(), ',', &accept_languages);
129
[email protected]c27324b2009-11-19 22:44:29130 for (std::vector<std::string>::const_iterator i = accept_languages.begin();
131 i != accept_languages.end(); ++i) {
132 std::string language =
133 SpellCheckCommon::GetCorrespondingSpellCheckLanguage(*i);
134 if (!language.empty() &&
135 std::find(languages->begin(), languages->end(), language) ==
136 languages->end()) {
137 languages->push_back(language);
138 }
139 }
140
141 for (size_t i = 0; i < languages->size(); ++i) {
142 if ((*languages)[i] == dictionary_language)
143 return i;
144 }
145 return -1;
146}
147
[email protected]cb6037d2009-11-16 22:55:17148void SpellCheckHost::InitializeDictionaryLocation() {
149 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
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
[email protected]f41301e2009-11-16 21:30:07164void SpellCheckHost::InitializeInternal() {
[email protected]85c55dc2009-11-06 03:05:46165 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
166
167 if (!observer_)
168 return;
169
[email protected]ed65fec2010-08-31 19:30:27170 file_ = base::CreatePlatformFile(
171 bdict_file_path_,
[email protected]cb6037d2009-11-16 22:55:17172 base::PLATFORM_FILE_READ | base::PLATFORM_FILE_OPEN,
[email protected]ed65fec2010-08-31 19:30:27173 NULL, NULL);
[email protected]85c55dc2009-11-06 03:05:46174
175 // File didn't exist. Download it.
[email protected]a0ef9e12009-11-17 00:15:49176 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 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
181 NewRunnableMethod(this, &SpellCheckHost::DownloadDictionary));
[email protected]85c55dc2009-11-06 03:05:46182 return;
183 }
184
[email protected]a0ef9e12009-11-17 00:15:49185 request_context_getter_ = NULL;
186
[email protected]cb6037d2009-11-16 22:55:17187 if (file_ != base::kInvalidPlatformFileValue) {
[email protected]85c55dc2009-11-06 03:05:46188 // Load custom dictionary.
189 std::string contents;
190 file_util::ReadFileToString(custom_dictionary_file_, &contents);
191 std::vector<std::string> list_of_words;
192 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 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
198 NewRunnableMethod(this,
199 &SpellCheckHost::InformObserverOfInitialization));
200}
201
[email protected]a0ef9e12009-11-17 00:15:49202void SpellCheckHost::InitializeOnFileThread() {
203 DCHECK(!ChromeThread::CurrentlyOn(ChromeThread::FILE));
204
205 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
206 NewRunnableMethod(this, &SpellCheckHost::Initialize));
207}
208
[email protected]85c55dc2009-11-06 03:05:46209void SpellCheckHost::InformObserverOfInitialization() {
210 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
211
212 if (observer_)
213 observer_->SpellCheckHostInitialized();
214}
215
216void SpellCheckHost::DownloadDictionary() {
[email protected]a0ef9e12009-11-17 00:15:49217 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
218
219 if (!request_context_getter_) {
220 InitializeOnFileThread();
221 return;
222 }
[email protected]85c55dc2009-11-06 03:05:46223
224 // Determine URL of file to download.
225 static const char kDownloadServerUrl[] =
226 "http://cache.pack.google.com/edgedl/chrome/dict/";
[email protected]e5a8c472010-08-04 19:47:20227 GURL url = GURL(std::string(kDownloadServerUrl) +
228 StringToLowerASCII(WideToUTF8(
229 bdict_file_path_.BaseName().ToWStringHack())));
[email protected]85c55dc2009-11-06 03:05:46230 fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this));
[email protected]a0ef9e12009-11-17 00:15:49231 fetcher_->set_request_context(request_context_getter_);
[email protected]85c55dc2009-11-06 03:05:46232 tried_to_download_ = true;
233 fetcher_->Start();
[email protected]a0ef9e12009-11-17 00:15:49234 request_context_getter_ = NULL;
[email protected]85c55dc2009-11-06 03:05:46235}
236
237void SpellCheckHost::WriteWordToCustomDictionary(const std::string& word) {
238 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
239
240 // Stored in UTF-8.
241 std::string word_to_add(word + "\n");
242 FILE* f = file_util::OpenFile(custom_dictionary_file_, "a+");
243 if (f != NULL)
244 fputs(word_to_add.c_str(), f);
245 file_util::CloseFile(f);
246}
247
248void SpellCheckHost::OnURLFetchComplete(const URLFetcher* source,
249 const GURL& url,
250 const URLRequestStatus& status,
251 int response_code,
252 const ResponseCookies& cookies,
253 const std::string& data) {
254 DCHECK(source);
[email protected]a0ef9e12009-11-17 00:15:49255 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
256 fetcher_.reset();
[email protected]85c55dc2009-11-06 03:05:46257
258 if ((response_code / 100) != 2) {
259 // Initialize will not try to download the file a second time.
260 LOG(ERROR) << "Failure to download dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49261 InitializeOnFileThread();
[email protected]85c55dc2009-11-06 03:05:46262 return;
263 }
264
265 // Basic sanity check on the dictionary.
266 // There's the small chance that we might see a 200 status code for a body
267 // that represents some form of failure.
268 if (data.size() < 4 || data[0] != 'B' || data[1] != 'D' || data[2] != 'i' ||
269 data[3] != 'c') {
270 LOG(ERROR) << "Failure to download dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49271 InitializeOnFileThread();
[email protected]85c55dc2009-11-06 03:05:46272 return;
273 }
274
[email protected]a0ef9e12009-11-17 00:15:49275 data_ = data;
276 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
277 NewRunnableMethod(this, &SpellCheckHost::SaveDictionaryData));
278}
279
280void SpellCheckHost::SaveDictionaryData() {
281 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
282
[email protected]85c55dc2009-11-06 03:05:46283 size_t bytes_written =
[email protected]a0ef9e12009-11-17 00:15:49284 file_util::WriteFile(bdict_file_path_, data_.data(), data_.length());
285 if (bytes_written != data_.length()) {
[email protected]cb6037d2009-11-16 22:55:17286 bool success = false;
287#if defined(OS_WIN)
288 bdict_file_path_ = GetFallbackFilePath(bdict_file_path_);
289 bytes_written =
290 file_util::WriteFile(GetFallbackFilePath(bdict_file_path_),
[email protected]a0ef9e12009-11-17 00:15:49291 data_.data(), data_.length());
292 if (bytes_written == data_.length())
[email protected]cb6037d2009-11-16 22:55:17293 success = true;
294#endif
[email protected]a0ef9e12009-11-17 00:15:49295 data_.clear();
[email protected]cb6037d2009-11-16 22:55:17296
297 if (!success) {
298 LOG(ERROR) << "Failure to save dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49299 file_util::Delete(bdict_file_path_, false);
[email protected]cb6037d2009-11-16 22:55:17300 // To avoid trying to load a partially saved dictionary, shortcut the
301 // Initialize() call.
302 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
303 NewRunnableMethod(this,
304 &SpellCheckHost::InformObserverOfInitialization));
305 return;
306 }
[email protected]85c55dc2009-11-06 03:05:46307 }
308
[email protected]a0ef9e12009-11-17 00:15:49309 data_.clear();
[email protected]85c55dc2009-11-06 03:05:46310 Initialize();
311}