blob: 7ac1a3f9691947a11a70c7282c41c60de4f98ef9 [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);
[email protected]ddd231e2010-06-29 20:35:19113 std::string dictionary_language = dictionary_language_pref.GetValue();
[email protected]c27324b2009-11-19 22:44:29114
115 // The current dictionary language should be there.
116 languages->push_back(dictionary_language);
117
118 // Now scan through the list of accept languages, and find possible mappings
119 // from this list to the existing list of spell check languages.
120 std::vector<std::string> accept_languages;
121
[email protected]ddd231e2010-06-29 20:35:19122 if (SpellCheckerPlatform::SpellCheckerAvailable())
[email protected]c27324b2009-11-19 22:44:29123 SpellCheckerPlatform::GetAvailableLanguages(&accept_languages);
[email protected]ddd231e2010-06-29 20:35:19124 else
125 SplitString(accept_languages_pref.GetValue(), ',', &accept_languages);
126
[email protected]c27324b2009-11-19 22:44:29127 for (std::vector<std::string>::const_iterator i = accept_languages.begin();
128 i != accept_languages.end(); ++i) {
129 std::string language =
130 SpellCheckCommon::GetCorrespondingSpellCheckLanguage(*i);
131 if (!language.empty() &&
132 std::find(languages->begin(), languages->end(), language) ==
133 languages->end()) {
134 languages->push_back(language);
135 }
136 }
137
138 for (size_t i = 0; i < languages->size(); ++i) {
139 if ((*languages)[i] == dictionary_language)
140 return i;
141 }
142 return -1;
143}
144
[email protected]cb6037d2009-11-16 22:55:17145void SpellCheckHost::InitializeDictionaryLocation() {
146 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
147
148#if defined(OS_WIN)
149 // Check if the dictionary exists in the fallback location. If so, use it
150 // rather than downloading anew.
151 FilePath fallback = GetFallbackFilePath(bdict_file_path_);
152 if (!file_util::PathExists(bdict_file_path_) &&
153 file_util::PathExists(fallback)) {
154 bdict_file_path_ = fallback;
155 }
156#endif
157
158 InitializeInternal();
159}
160
[email protected]f41301e2009-11-16 21:30:07161void SpellCheckHost::InitializeInternal() {
[email protected]85c55dc2009-11-06 03:05:46162 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
163
164 if (!observer_)
165 return;
166
[email protected]cb6037d2009-11-16 22:55:17167 file_ = base::CreatePlatformFile(bdict_file_path_,
168 base::PLATFORM_FILE_READ | base::PLATFORM_FILE_OPEN,
169 NULL);
[email protected]85c55dc2009-11-06 03:05:46170
171 // File didn't exist. Download it.
[email protected]a0ef9e12009-11-17 00:15:49172 if (file_ == base::kInvalidPlatformFileValue && !tried_to_download_ &&
173 request_context_getter_) {
174 // We download from the ui thread because we need to know that
175 // |request_context_getter_| is still valid before initiating the download.
176 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
177 NewRunnableMethod(this, &SpellCheckHost::DownloadDictionary));
[email protected]85c55dc2009-11-06 03:05:46178 return;
179 }
180
[email protected]a0ef9e12009-11-17 00:15:49181 request_context_getter_ = NULL;
182
[email protected]cb6037d2009-11-16 22:55:17183 if (file_ != base::kInvalidPlatformFileValue) {
[email protected]85c55dc2009-11-06 03:05:46184 // Load custom dictionary.
185 std::string contents;
186 file_util::ReadFileToString(custom_dictionary_file_, &contents);
187 std::vector<std::string> list_of_words;
188 SplitString(contents, '\n', &list_of_words);
189 for (size_t i = 0; i < list_of_words.size(); ++i)
190 custom_words_.push_back(list_of_words[i]);
191 }
192
193 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
194 NewRunnableMethod(this,
195 &SpellCheckHost::InformObserverOfInitialization));
196}
197
[email protected]a0ef9e12009-11-17 00:15:49198void SpellCheckHost::InitializeOnFileThread() {
199 DCHECK(!ChromeThread::CurrentlyOn(ChromeThread::FILE));
200
201 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
202 NewRunnableMethod(this, &SpellCheckHost::Initialize));
203}
204
[email protected]85c55dc2009-11-06 03:05:46205void SpellCheckHost::InformObserverOfInitialization() {
206 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
207
208 if (observer_)
209 observer_->SpellCheckHostInitialized();
210}
211
212void SpellCheckHost::DownloadDictionary() {
[email protected]a0ef9e12009-11-17 00:15:49213 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
214
215 if (!request_context_getter_) {
216 InitializeOnFileThread();
217 return;
218 }
[email protected]85c55dc2009-11-06 03:05:46219
220 // Determine URL of file to download.
221 static const char kDownloadServerUrl[] =
222 "http://cache.pack.google.com/edgedl/chrome/dict/";
223 GURL url = GURL(std::string(kDownloadServerUrl) + WideToUTF8(
[email protected]cb6037d2009-11-16 22:55:17224 l10n_util::ToLower(bdict_file_path_.BaseName().ToWStringHack())));
[email protected]85c55dc2009-11-06 03:05:46225 fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this));
[email protected]a0ef9e12009-11-17 00:15:49226 fetcher_->set_request_context(request_context_getter_);
[email protected]85c55dc2009-11-06 03:05:46227 tried_to_download_ = true;
228 fetcher_->Start();
[email protected]a0ef9e12009-11-17 00:15:49229 request_context_getter_ = NULL;
[email protected]85c55dc2009-11-06 03:05:46230}
231
232void SpellCheckHost::WriteWordToCustomDictionary(const std::string& word) {
233 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
234
235 // Stored in UTF-8.
236 std::string word_to_add(word + "\n");
237 FILE* f = file_util::OpenFile(custom_dictionary_file_, "a+");
238 if (f != NULL)
239 fputs(word_to_add.c_str(), f);
240 file_util::CloseFile(f);
241}
242
243void SpellCheckHost::OnURLFetchComplete(const URLFetcher* source,
244 const GURL& url,
245 const URLRequestStatus& status,
246 int response_code,
247 const ResponseCookies& cookies,
248 const std::string& data) {
249 DCHECK(source);
[email protected]a0ef9e12009-11-17 00:15:49250 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
251 fetcher_.reset();
[email protected]85c55dc2009-11-06 03:05:46252
253 if ((response_code / 100) != 2) {
254 // Initialize will not try to download the file a second time.
255 LOG(ERROR) << "Failure to download dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49256 InitializeOnFileThread();
[email protected]85c55dc2009-11-06 03:05:46257 return;
258 }
259
260 // Basic sanity check on the dictionary.
261 // There's the small chance that we might see a 200 status code for a body
262 // that represents some form of failure.
263 if (data.size() < 4 || data[0] != 'B' || data[1] != 'D' || data[2] != 'i' ||
264 data[3] != 'c') {
265 LOG(ERROR) << "Failure to download dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49266 InitializeOnFileThread();
[email protected]85c55dc2009-11-06 03:05:46267 return;
268 }
269
[email protected]a0ef9e12009-11-17 00:15:49270 data_ = data;
271 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
272 NewRunnableMethod(this, &SpellCheckHost::SaveDictionaryData));
273}
274
275void SpellCheckHost::SaveDictionaryData() {
276 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
277
[email protected]85c55dc2009-11-06 03:05:46278 size_t bytes_written =
[email protected]a0ef9e12009-11-17 00:15:49279 file_util::WriteFile(bdict_file_path_, data_.data(), data_.length());
280 if (bytes_written != data_.length()) {
[email protected]cb6037d2009-11-16 22:55:17281 bool success = false;
282#if defined(OS_WIN)
283 bdict_file_path_ = GetFallbackFilePath(bdict_file_path_);
284 bytes_written =
285 file_util::WriteFile(GetFallbackFilePath(bdict_file_path_),
[email protected]a0ef9e12009-11-17 00:15:49286 data_.data(), data_.length());
287 if (bytes_written == data_.length())
[email protected]cb6037d2009-11-16 22:55:17288 success = true;
289#endif
[email protected]a0ef9e12009-11-17 00:15:49290 data_.clear();
[email protected]cb6037d2009-11-16 22:55:17291
292 if (!success) {
293 LOG(ERROR) << "Failure to save dictionary.";
[email protected]a0ef9e12009-11-17 00:15:49294 file_util::Delete(bdict_file_path_, false);
[email protected]cb6037d2009-11-16 22:55:17295 // To avoid trying to load a partially saved dictionary, shortcut the
296 // Initialize() call.
297 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
298 NewRunnableMethod(this,
299 &SpellCheckHost::InformObserverOfInitialization));
300 return;
301 }
[email protected]85c55dc2009-11-06 03:05:46302 }
303
[email protected]a0ef9e12009-11-17 00:15:49304 data_.clear();
[email protected]85c55dc2009-11-06 03:05:46305 Initialize();
306}