OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "chrome/browser/spellchecker/spellcheck_custom_dictionary.h" | 5 #include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h" |
6 | 6 |
7 #include <functional> | 7 #include <functional> |
8 | 8 |
9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
10 #include "base/files/important_file_writer.h" | |
11 #include "base/md5.h" | |
10 #include "base/string_split.h" | 12 #include "base/string_split.h" |
11 #include "chrome/browser/profiles/profile.h" | 13 #include "chrome/browser/profiles/profile.h" |
12 #include "chrome/common/chrome_constants.h" | 14 #include "chrome/common/chrome_constants.h" |
13 #include "chrome/common/spellcheck_messages.h" | 15 #include "chrome/common/spellcheck_messages.h" |
14 #include "content/public/browser/browser_thread.h" | 16 #include "content/public/browser/browser_thread.h" |
15 #include "content/public/browser/render_process_host.h" | 17 #include "content/public/browser/render_process_host.h" |
16 | 18 |
17 using content::BrowserThread; | 19 using content::BrowserThread; |
18 using chrome::spellcheck_common::WordList; | 20 using chrome::spellcheck_common::WordList; |
19 | 21 |
20 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) | 22 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) |
21 : SpellcheckDictionary(profile), | 23 : SpellcheckDictionary(profile), |
22 custom_dictionary_path_(), | 24 custom_dictionary_path_(), |
23 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { | 25 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
26 backup_extension_(FILE_PATH_LITERAL("backup")), | |
27 checksum_prefix_("checksum_v1 = ") { | |
24 DCHECK(profile); | 28 DCHECK(profile); |
25 custom_dictionary_path_ = | 29 custom_dictionary_path_ = |
26 profile_->GetPath().Append(chrome::kCustomDictionaryFileName); | 30 profile_->GetPath().Append(chrome::kCustomDictionaryFileName); |
27 } | 31 } |
28 | 32 |
29 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() { | 33 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() { |
30 } | 34 } |
31 | 35 |
32 void SpellcheckCustomDictionary::Load() { | 36 void SpellcheckCustomDictionary::Load() { |
33 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 37 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
34 | 38 |
35 BrowserThread::PostTaskAndReplyWithResult<WordList*>( | 39 BrowserThread::PostTaskAndReplyWithResult<WordList*>( |
36 BrowserThread::FILE, | 40 BrowserThread::FILE, |
37 FROM_HERE, | 41 FROM_HERE, |
38 base::Bind(&SpellcheckCustomDictionary::LoadDictionary, | 42 base::Bind(&SpellcheckCustomDictionary::LoadDictionary, |
39 base::Unretained(this)), | 43 base::Unretained(this)), |
40 base::Bind(&SpellcheckCustomDictionary::SetCustomWordListAndDelete, | 44 base::Bind(&SpellcheckCustomDictionary::SetCustomWordListAndDelete, |
41 weak_ptr_factory_.GetWeakPtr())); | 45 weak_ptr_factory_.GetWeakPtr())); |
42 } | 46 } |
43 | 47 |
44 const WordList& SpellcheckCustomDictionary::GetWords() const { | 48 const WordList& SpellcheckCustomDictionary::GetWords() const { |
45 return words_; | 49 return words_; |
46 } | 50 } |
47 | 51 |
48 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList( | 52 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList( |
49 WordList* custom_words) { | 53 WordList* custom_words) { |
50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 54 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
51 | 55 |
52 std::string contents; | 56 LoadDictionaryFileReliably(custom_words); |
53 file_util::ReadFileToString(custom_dictionary_path_, &contents); | 57 if (custom_words->empty()) |
54 if (contents.empty()) { | |
55 custom_words->clear(); | |
56 return; | 58 return; |
57 } | |
58 | 59 |
59 base::SplitString(contents, '\n', custom_words); | 60 // Clean up the dictionary file contents by removing duplicates and empty |
60 | 61 // words. |
61 // Erase duplicates. | |
62 std::sort(custom_words->begin(), custom_words->end()); | 62 std::sort(custom_words->begin(), custom_words->end()); |
63 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), | 63 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), |
64 custom_words->end()); | 64 custom_words->end()); |
65 if (custom_words->at(0).length() == 0) | |
groby-ooo-7-16
2012/12/05 00:56:19
at(0).empty()
please use gerrit instead
2012/12/05 16:51:26
Done.
| |
66 custom_words->erase(custom_words->begin()); | |
65 | 67 |
66 // Clear out empty words. | 68 SaveDictionaryFileReliably(*custom_words); |
67 custom_words->erase(remove_if(custom_words->begin(), custom_words->end(), | |
68 mem_fun_ref(&std::string::empty)), custom_words->end()); | |
69 | |
70 // Write out the clean file. | |
71 std::stringstream ss; | |
72 for (WordList::iterator it = custom_words->begin(); | |
73 it != custom_words->end(); | |
74 ++it) { | |
75 ss << *it << '\n'; | |
76 } | |
77 contents = ss.str(); | |
78 file_util::WriteFile(custom_dictionary_path_, | |
79 contents.c_str(), | |
80 contents.length()); | |
81 } | 69 } |
82 | 70 |
83 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { | 71 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { |
84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 72 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
85 | 73 |
86 words_.clear(); | 74 words_.clear(); |
87 if (custom_words) | 75 if (custom_words) |
88 std::swap(words_, *custom_words); | 76 std::swap(words_, *custom_words); |
89 | 77 |
90 std::vector<Observer*>::iterator it; | 78 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded()); |
91 for (it = observers_.begin(); it != observers_.end(); ++it) | |
92 (*it)->OnCustomDictionaryLoaded(); | |
93 } | 79 } |
94 | 80 |
95 bool SpellcheckCustomDictionary::AddWord(const std::string& word) { | 81 bool SpellcheckCustomDictionary::AddWord(const std::string& word) { |
96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 82 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
97 | 83 |
98 if (!CustomWordAddedLocally(word)) | 84 if (!CustomWordAddedLocally(word)) |
99 return false; | 85 return false; |
100 | 86 |
101 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 87 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
102 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary, | 88 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary, |
103 base::Unretained(this), word)); | 89 base::Unretained(this), word)); |
104 | 90 |
105 for (content::RenderProcessHost::iterator i( | 91 for (content::RenderProcessHost::iterator i( |
106 content::RenderProcessHost::AllHostsIterator()); | 92 content::RenderProcessHost::AllHostsIterator()); |
107 !i.IsAtEnd(); i.Advance()) { | 93 !i.IsAtEnd(); i.Advance()) { |
108 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word)); | 94 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word)); |
109 } | 95 } |
110 | 96 |
111 std::vector<Observer*>::iterator it; | 97 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordAdded(word)); |
112 for (it = observers_.begin(); it != observers_.end(); ++it) | |
113 (*it)->OnCustomDictionaryWordAdded(word); | |
114 | 98 |
115 return true; | 99 return true; |
116 } | 100 } |
117 | 101 |
118 bool SpellcheckCustomDictionary::CustomWordAddedLocally( | 102 bool SpellcheckCustomDictionary::CustomWordAddedLocally( |
119 const std::string& word) { | 103 const std::string& word) { |
120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 104 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
121 | 105 |
122 WordList::iterator it = std::find(words_.begin(), words_.end(), word); | 106 WordList::iterator it = std::find(words_.begin(), words_.end(), word); |
123 if (it == words_.end()) { | 107 if (it == words_.end()) { |
124 words_.push_back(word); | 108 words_.push_back(word); |
125 return true; | 109 return true; |
126 } | 110 } |
127 return false; | 111 return false; |
128 // TODO(rlp): record metrics on custom word size | 112 // TODO(rlp): record metrics on custom word size |
129 } | 113 } |
130 | 114 |
131 void SpellcheckCustomDictionary::WriteWordToCustomDictionary( | 115 void SpellcheckCustomDictionary::WriteWordToCustomDictionary( |
132 const std::string& word) { | 116 const std::string& word) { |
133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
134 | |
135 // Stored in UTF-8. | |
136 DCHECK(IsStringUTF8(word)); | 118 DCHECK(IsStringUTF8(word)); |
137 | 119 |
138 std::string word_to_add(word + "\n"); | 120 WordList custom_words; |
139 if (!file_util::PathExists(custom_dictionary_path_)) { | 121 LoadDictionaryFileReliably(&custom_words); |
140 file_util::WriteFile(custom_dictionary_path_, word_to_add.c_str(), | 122 custom_words.push_back(word); |
141 word_to_add.length()); | 123 SaveDictionaryFileReliably(custom_words); |
142 } else { | |
143 file_util::AppendToFile(custom_dictionary_path_, word_to_add.c_str(), | |
144 word_to_add.length()); | |
145 } | |
146 } | 124 } |
147 | 125 |
148 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { | 126 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { |
149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 127 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
150 | 128 |
151 if (!CustomWordRemovedLocally(word)) | 129 if (!CustomWordRemovedLocally(word)) |
152 return false; | 130 return false; |
153 | 131 |
154 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 132 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
155 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary, | 133 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary, |
156 base::Unretained(this), word)); | 134 base::Unretained(this), word)); |
157 | 135 |
158 for (content::RenderProcessHost::iterator i( | 136 for (content::RenderProcessHost::iterator i( |
159 content::RenderProcessHost::AllHostsIterator()); | 137 content::RenderProcessHost::AllHostsIterator()); |
160 !i.IsAtEnd(); i.Advance()) { | 138 !i.IsAtEnd(); i.Advance()) { |
161 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word)); | 139 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word)); |
162 } | 140 } |
163 | 141 |
164 std::vector<Observer*>::iterator it; | 142 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordRemoved(word)); |
165 for (it = observers_.begin(); it != observers_.end(); ++it) | |
166 (*it)->OnCustomDictionaryWordRemoved(word); | |
167 | 143 |
168 return true; | 144 return true; |
169 } | 145 } |
170 | 146 |
171 bool SpellcheckCustomDictionary::CustomWordRemovedLocally( | 147 bool SpellcheckCustomDictionary::CustomWordRemovedLocally( |
172 const std::string& word) { | 148 const std::string& word) { |
173 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
174 | 150 |
175 WordList::iterator it = std::find(words_.begin(), words_.end(), word); | 151 WordList::iterator it = std::find(words_.begin(), words_.end(), word); |
176 if (it != words_.end()) { | 152 if (it != words_.end()) { |
177 words_.erase(it); | 153 words_.erase(it); |
178 return true; | 154 return true; |
179 } | 155 } |
180 return false; | 156 return false; |
181 } | 157 } |
182 | 158 |
183 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( | 159 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( |
184 const std::string& word) { | 160 const std::string& word) { |
185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 161 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
186 DCHECK(IsStringUTF8(word)); | 162 DCHECK(IsStringUTF8(word)); |
187 | 163 |
188 WordList custom_words; | 164 WordList custom_words; |
189 LoadDictionaryIntoCustomWordList(&custom_words); | 165 LoadDictionaryFileReliably(&custom_words); |
166 if (custom_words.empty()) | |
167 return; | |
190 | 168 |
191 const char empty[] = {'\0'}; | 169 WordList::iterator it = std::find(custom_words.begin(), |
192 const char separator[] = {'\n', '\0'}; | 170 custom_words.end(), |
193 file_util::WriteFile(custom_dictionary_path_, empty, 0); | 171 word); |
194 for (WordList::iterator it = custom_words.begin(); | 172 if (it != custom_words.end()) |
195 it != custom_words.end(); | 173 custom_words.erase(it); |
196 ++it) { | 174 |
197 std::string word_to_add = *it; | 175 SaveDictionaryFileReliably(custom_words); |
198 if (word.compare(word_to_add) != 0) { | |
199 file_util::AppendToFile(custom_dictionary_path_, word_to_add.c_str(), | |
200 word_to_add.length()); | |
201 file_util::AppendToFile(custom_dictionary_path_, separator, 1); | |
202 } | |
203 } | |
204 } | 176 } |
205 | 177 |
206 void SpellcheckCustomDictionary::AddObserver(Observer* observer) { | 178 void SpellcheckCustomDictionary::AddObserver(Observer* observer) { |
207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 179 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
208 | 180 |
209 observers_.push_back(observer); | 181 observers_.AddObserver(observer); |
210 } | 182 } |
211 | 183 |
212 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { | 184 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { |
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
214 | 186 |
215 std::vector<Observer*>::iterator it = std::find(observers_.begin(), | 187 observers_.RemoveObserver(observer); |
216 observers_.end(), | |
217 observer); | |
218 if (it != observers_.end()) | |
219 observers_.erase(it); | |
220 } | 188 } |
221 | 189 |
222 WordList* SpellcheckCustomDictionary::LoadDictionary() { | 190 WordList* SpellcheckCustomDictionary::LoadDictionary() { |
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
224 | 192 |
225 WordList* custom_words = new WordList; | 193 WordList* custom_words = new WordList; |
226 LoadDictionaryIntoCustomWordList(custom_words); | 194 LoadDictionaryIntoCustomWordList(custom_words); |
227 return custom_words; | 195 return custom_words; |
228 } | 196 } |
229 | 197 |
230 void SpellcheckCustomDictionary::SetCustomWordListAndDelete( | 198 void SpellcheckCustomDictionary::SetCustomWordListAndDelete( |
231 WordList* custom_words) { | 199 WordList* custom_words) { |
232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 200 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
233 | 201 |
234 SetCustomWordList(custom_words); | 202 SetCustomWordList(custom_words); |
235 delete custom_words; | 203 delete custom_words; |
236 } | 204 } |
205 | |
206 void SpellcheckCustomDictionary::LoadDictionaryFileReliably( | |
207 WordList* custom_words) { | |
208 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
209 | |
210 // Load the contents and verify the checksum. | |
211 std::string contents; | |
212 file_util::ReadFileToString(custom_dictionary_path_, &contents); | |
213 std::string checksum = GetChecksum(&contents); | |
214 base::SplitString(contents, '\n', custom_words); | |
215 if (!custom_words->empty() && (*(custom_words->end() - 1)).empty()) | |
groby-ooo-7-16
2012/12/05 00:56:19
Why? Just to remove trailing newlines? In that cas
please use gerrit instead
2012/12/05 16:51:26
Removed.
| |
216 custom_words->erase(custom_words->end() - 1); | |
217 if (checksum.empty() || checksum == base::MD5String(contents)) | |
218 return; | |
219 | |
220 // Checksum is not valid. See if there's a backup. | |
221 FilePath backup_path = | |
222 custom_dictionary_path_.AddExtension(backup_extension_); | |
223 if (!file_util::PathExists(backup_path)) | |
224 return; | |
225 | |
226 // Load the backup and verify its checksum. | |
227 std::string backup; | |
groby-ooo-7-16
2012/12/05 00:56:19
Extract that into a function - 210-217 and 228-236
please use gerrit instead
2012/12/05 16:51:26
Done.
| |
228 file_util::ReadFileToString(backup_path, &backup); | |
229 std::string backup_checksum = GetChecksum(&backup); | |
230 if (backup_checksum != base::MD5String(backup)) | |
231 return; | |
232 | |
233 // Backup checksum is valid. Use that instead. | |
234 base::SplitString(backup, '\n', custom_words); | |
235 if (!custom_words->empty() && (*(custom_words->end() - 1)).empty()) | |
236 custom_words->erase(custom_words->end() - 1); | |
237 file_util::CopyFile(backup_path, custom_dictionary_path_); | |
238 } | |
239 | |
240 void SpellcheckCustomDictionary::SaveDictionaryFileReliably( | |
241 const WordList& custom_words) { | |
242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
243 | |
244 std::stringstream content; | |
245 for (WordList::const_iterator it = custom_words.begin(); | |
246 it != custom_words.end(); | |
247 ++it) { | |
248 content << *it << '\n'; | |
249 } | |
250 std::string content_str = content.str(); | |
251 std::stringstream file; | |
252 file << checksum_prefix_ << base::MD5String(content_str) << '\n' | |
253 << content_str; | |
254 | |
255 FilePath backup_path = | |
256 custom_dictionary_path_.AddExtension(backup_extension_); | |
257 if (file_util::PathExists(custom_dictionary_path_)) | |
groby-ooo-7-16
2012/12/05 00:56:19
Do you need to check? Can't we just call CopyFile,
please use gerrit instead
2012/12/05 16:51:26
Done.
| |
258 file_util::CopyFile(custom_dictionary_path_, backup_path); | |
259 base::ImportantFileWriter::WriteFileAtomically(custom_dictionary_path_, | |
260 file.str()); | |
261 } | |
262 | |
263 std::string SpellcheckCustomDictionary::GetChecksum(std::string* contents) { | |
264 std::string checksum; | |
groby-ooo-7-16
2012/12/05 00:56:19
nit: You can save a bit of string rebuilding here
please use gerrit instead
2012/12/05 16:51:26
Done.
| |
265 if (contents->substr(0, checksum_prefix_.length()) == checksum_prefix_) { | |
266 size_t after_checksum = contents->find('\n', checksum_prefix_.length()); | |
267 if (after_checksum == std::string::npos) { | |
268 checksum = contents->substr( | |
269 checksum_prefix_.length(), | |
270 contents->length() - checksum_prefix_.length()); | |
271 contents->clear(); | |
272 } else { | |
273 checksum = contents->substr(checksum_prefix_.length(), | |
274 after_checksum - checksum_prefix_.length()); | |
275 *contents = contents->substr(after_checksum + 1); | |
276 } | |
277 } | |
278 return checksum; | |
279 } | |
OLD | NEW |