blob: 1c0b266a1aeb50ef502e092b4f2863551a22b0fd [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]c27324b2009-11-19 22:44:2914#include "chrome/browser/profile.h"
[email protected]052313b2010-02-19 09:43:0815#include "chrome/browser/pref_member.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
34FilePath GetFallbackFilePath(const FilePath& first_choice) {
35 FilePath dict_dir;
36 PathService::Get(chrome::DIR_USER_DATA, &dict_dir);
37 return dict_dir.Append(first_choice.BaseName());
38}
39
[email protected]85c55dc2009-11-06 03:05:4640} // namespace
41
42// Constructed on UI thread.
[email protected]9c92d192009-12-02 08:03:1643SpellCheckHost::SpellCheckHost(SpellCheckHostObserver* observer,
[email protected]85c55dc2009-11-06 03:05:4644 const std::string& language,
45 URLRequestContextGetter* request_context_getter)
46 : observer_(observer),
47 language_(language),
[email protected]cb6037d2009-11-16 22:55:1748 file_(base::kInvalidPlatformFileValue),
[email protected]85c55dc2009-11-06 03:05:4649 tried_to_download_(false),
[email protected]c27324b2009-11-19 22:44:2950 use_platform_spellchecker_(false),
[email protected]85c55dc2009-11-06 03:05:4651 request_context_getter_(request_context_getter) {
52 DCHECK(observer_);
53 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
54
[email protected]85c55dc2009-11-06 03:05:4655 FilePath personal_file_directory;
56 PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory);
57 custom_dictionary_file_ =
58 personal_file_directory.Append(chrome::kCustomDictionaryFileName);
[email protected]cb6037d2009-11-16 22:55:1759
60 bdict_file_path_ = GetFirstChoiceFilePath(language);
[email protected]85c55dc2009-11-06 03:05:4661}
62
63SpellCheckHost::~SpellCheckHost() {
[email protected]cb6037d2009-11-16 22:55:1764 if (file_ != base::kInvalidPlatformFileValue)
65 base::ClosePlatformFile(file_);
[email protected]85c55dc2009-11-06 03:05:4666}
67
[email protected]f41301e2009-11-16 21:30:0768void SpellCheckHost::Initialize() {
[email protected]c27324b2009-11-19 22:44:2969 if (SpellCheckerPlatform::SpellCheckerAvailable() &&
70 SpellCheckerPlatform::PlatformSupportsLanguage(language_)) {
71 use_platform_spellchecker_ = true;
72 SpellCheckerPlatform::SetLanguage(language_);
73 MessageLoop::current()->PostTask(FROM_HERE,
74 NewRunnableMethod(this,
75 &SpellCheckHost::InformObserverOfInitialization));
76 return;
77 }
78
[email protected]f41301e2009-11-16 21:30:0779 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
[email protected]cb6037d2009-11-16 22:55:1780 NewRunnableMethod(this, &SpellCheckHost::InitializeDictionaryLocation));
[email protected]f41301e2009-11-16 21:30:0781}
82
[email protected]85c55dc2009-11-06 03:05:4683void SpellCheckHost::UnsetObserver() {
84 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
85
86 observer_ = NULL;
[email protected]a0ef9e12009-11-17 00:15:4987 request_context_getter_ = NULL;
88 fetcher_.reset();
[email protected]85c55dc2009-11-06 03:05:4689}
90
91void SpellCheckHost::AddWord(const std::string& word) {
92 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
93
94 custom_words_.push_back(word);
95 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
96 NewRunnableMethod(this,
97 &SpellCheckHost::WriteWordToCustomDictionary, word));
98 NotificationService::current()->Notify(
99 NotificationType::SPELLCHECK_WORD_ADDED,
100 Source<SpellCheckHost>(this), NotificationService::NoDetails());
101}
102
[email protected]c27324b2009-11-19 22:44:29103// static
104int SpellCheckHost::GetSpellCheckLanguages(
105 Profile* profile,
106 std::vector<std::string>* languages) {
107 StringPrefMember accept_languages_pref;
108 StringPrefMember dictionary_language_pref;
109 accept_languages_pref.Init(prefs::kAcceptLanguages, profile->GetPrefs(),
110 NULL);
111 dictionary_language_pref.Init(prefs::kSpellCheckDictionary,
112 profile->GetPrefs(), NULL);
113 std::string dictionary_language =
114 WideToASCII(dictionary_language_pref.GetValue());
115
116 // The current dictionary language should be there.
117 languages->push_back(dictionary_language);
118
119 // Now scan through the list of accept languages, and find possible mappings
120 // from this list to the existing list of spell check languages.
121 std::vector<std::string> accept_languages;
122
123 if (SpellCheckerPlatform::SpellCheckerAvailable()) {
124 SpellCheckerPlatform::GetAvailableLanguages(&accept_languages);
125 } else {
126 SplitString(WideToASCII(accept_languages_pref.GetValue()), ',',
127 &accept_languages);
128 }
129 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]cb6037d2009-11-16 22:55:17169 file_ = base::CreatePlatformFile(bdict_file_path_,
170 base::PLATFORM_FILE_READ | base::PLATFORM_FILE_OPEN,
171 NULL);
[email protected]85c55dc2009-11-06 03:05:46172
173 // File didn't exist. Download it.
[email protected]a0ef9e12009-11-17 00:15:49174 if (file_ == base::kInvalidPlatformFileValue && !tried_to_download_ &&
175 request_context_getter_) {
176 // We download from the ui thread because we need to know that
177 // |request_context_getter_| is still valid before initiating the download.
178 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
179 NewRunnableMethod(this, &SpellCheckHost::DownloadDictionary));
[email protected]85c55dc2009-11-06 03:05:46180 return;
181 }
182
[email protected]a0ef9e12009-11-17 00:15:49183 request_context_getter_ = NULL;
184
[email protected]cb6037d2009-11-16 22:55:17185 if (file_ != base::kInvalidPlatformFileValue) {
[email protected]85c55dc2009-11-06 03:05:46186 // Load custom dictionary.
187 std::string contents;
188 file_util::ReadFileToString(custom_dictionary_file_, &contents);
189 std::vector<std::string> list_of_words;
190 SplitString(contents, '\n', &list_of_words);
191 for (size_t i = 0; i < list_of_words.size(); ++i)
192 custom_words_.push_back(list_of_words[i]);
193 }
194
195 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
196 NewRunnableMethod(this,
197 &SpellCheckHost::InformObserverOfInitialization));
198}
199
[email protected]a0ef9e12009-11-17 00:15:49200void SpellCheckHost::InitializeOnFileThread() {
201 DCHECK(!ChromeThread::CurrentlyOn(ChromeThread::FILE));
202
203 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
204 NewRunnableMethod(this, &SpellCheckHost::Initialize));
205}
206
[email protected]85c55dc2009-11-06 03:05:46207void SpellCheckHost::InformObserverOfInitialization() {
208 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
209
210 if (observer_)
211 observer_->SpellCheckHostInitialized();
212}
213
214void SpellCheckHost::DownloadDictionary() {
[email protected]a0ef9e12009-11-17 00:15:49215 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
216
217 if (!request_context_getter_) {
218 InitializeOnFileThread();
219 return;
220 }
[email protected]85c55dc2009-11-06 03:05:46221
222 // Determine URL of file to download.
223 static const char kDownloadServerUrl[] =
224 "http://cache.pack.google.com/edgedl/chrome/dict/";
225 GURL url = GURL(std::string(kDownloadServerUrl) + WideToUTF8(
[email protected]cb6037d2009-11-16 22:55:17226 l10n_util::ToLower(bdict_file_path_.BaseName().ToWStringHack())));
[email protected]85c55dc2009-11-06 03:05:46227 fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this));
[email protected]a0ef9e12009-11-17 00:15:49228 fetcher_->set_request_context(request_context_getter_);
[email protected]85c55dc2009-11-06 03:05:46229 tried_to_download_ = true;
230 fetcher_->Start();
[email protected]a0ef9e12009-11-17 00:15:49231 request_context_getter_ = NULL;
[email protected]85c55dc2009-11-06 03:05:46232}
233
234void SpellCheckHost::WriteWordToCustomDictionary(const std::string& word) {
235 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
236
237 // Stored in UTF-8.
238 std::string word_to_add(word + "\n");
239 FILE* f = file_util::OpenFile(custom_dictionary_file_, "a+");
240 if (f != NULL)
241 fputs(word_to_add.c_str(), f);
242 file_util::CloseFile(f);
243}
244
245void SpellCheckHost::OnURLFetchComplete(const URLFetcher* source,
246 const GURL& url,
247 const URLRequestStatus& status,
248 int response_code,
249 const ResponseCookies& cookies,
250 const std::string& data) {
251 DCHECK(source);
[email protected]a0ef9e12009-11-17 00:15:49252 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
253 fetcher_.reset();
[email protected]85c55dc2009-11-06 03:05:46254
255 if ((response_code / 100) != 2) {
256 // Initialize will not try to download the file a second time.
257 LOG(ERROR) << "Failure to download dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49258 InitializeOnFileThread();
[email protected]85c55dc2009-11-06 03:05:46259 return;
260 }
261
262 // Basic sanity check on the dictionary.
263 // There's the small chance that we might see a 200 status code for a body
264 // that represents some form of failure.
265 if (data.size() < 4 || data[0] != 'B' || data[1] != 'D' || data[2] != 'i' ||
266 data[3] != 'c') {
267 LOG(ERROR) << "Failure to download dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49268 InitializeOnFileThread();
[email protected]85c55dc2009-11-06 03:05:46269 return;
270 }
271
[email protected]a0ef9e12009-11-17 00:15:49272 data_ = data;
273 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
274 NewRunnableMethod(this, &SpellCheckHost::SaveDictionaryData));
275}
276
277void SpellCheckHost::SaveDictionaryData() {
278 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
279
[email protected]85c55dc2009-11-06 03:05:46280 size_t bytes_written =
[email protected]a0ef9e12009-11-17 00:15:49281 file_util::WriteFile(bdict_file_path_, data_.data(), data_.length());
282 if (bytes_written != data_.length()) {
[email protected]cb6037d2009-11-16 22:55:17283 bool success = false;
284#if defined(OS_WIN)
285 bdict_file_path_ = GetFallbackFilePath(bdict_file_path_);
286 bytes_written =
287 file_util::WriteFile(GetFallbackFilePath(bdict_file_path_),
[email protected]a0ef9e12009-11-17 00:15:49288 data_.data(), data_.length());
289 if (bytes_written == data_.length())
[email protected]cb6037d2009-11-16 22:55:17290 success = true;
291#endif
[email protected]a0ef9e12009-11-17 00:15:49292 data_.clear();
[email protected]cb6037d2009-11-16 22:55:17293
294 if (!success) {
295 LOG(ERROR) << "Failure to save dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49296 file_util::Delete(bdict_file_path_, false);
[email protected]cb6037d2009-11-16 22:55:17297 // To avoid trying to load a partially saved dictionary, shortcut the
298 // Initialize() call.
299 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
300 NewRunnableMethod(this,
301 &SpellCheckHost::InformObserverOfInitialization));
302 return;
303 }
[email protected]85c55dc2009-11-06 03:05:46304 }
305
[email protected]a0ef9e12009-11-17 00:15:49306 data_.clear();
[email protected]85c55dc2009-11-06 03:05:46307 Initialize();
308}