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 = ") { | |
groby-ooo-7-16
2012/12/04 00:16:30
Why a prefix? Why not just make sure it's a valid
| |
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 std::string contents; |
53 file_util::ReadFileToString(custom_dictionary_path_, &contents); | 57 LoadDictionaryContentsReliably(&contents); |
58 | |
54 if (contents.empty()) { | 59 if (contents.empty()) { |
55 custom_words->clear(); | 60 custom_words->clear(); |
groby-ooo-7-16
2012/12/04 00:16:30
It's not your CL, but I suggest just calling custo
please use gerrit instead
2012/12/04 05:46:40
Done.
| |
56 return; | 61 return; |
57 } | 62 } |
58 | 63 |
59 base::SplitString(contents, '\n', custom_words); | 64 base::SplitString(contents, '\n', custom_words); |
60 | 65 |
61 // Erase duplicates. | 66 // Clean up the dictionary file contents by removing duplicates and empty |
67 // words. | |
62 std::sort(custom_words->begin(), custom_words->end()); | 68 std::sort(custom_words->begin(), custom_words->end()); |
63 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), | 69 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), |
64 custom_words->end()); | 70 custom_words->end()); |
65 | 71 custom_words->erase(std::remove_if(custom_words->begin(), |
groby-ooo-7-16
2012/12/04 00:16:30
While this works, it just occurred to me that only
please use gerrit instead
2012/12/04 05:46:40
Done.
| |
66 // Clear out empty words. | 72 custom_words->end(), |
67 custom_words->erase(remove_if(custom_words->begin(), custom_words->end(), | 73 std::mem_fun_ref(&std::string::empty)), |
68 mem_fun_ref(&std::string::empty)), custom_words->end()); | 74 custom_words->end()); |
69 | |
70 // Write out the clean file. | |
71 std::stringstream ss; | 75 std::stringstream ss; |
72 for (WordList::iterator it = custom_words->begin(); | 76 for (WordList::iterator it = custom_words->begin(); |
73 it != custom_words->end(); | 77 it != custom_words->end(); |
74 ++it) { | 78 ++it) { |
75 ss << *it << '\n'; | 79 ss << *it << '\n'; |
76 } | 80 } |
77 contents = ss.str(); | 81 |
78 file_util::WriteFile(custom_dictionary_path_, | 82 SaveDictionryContentsReliably(ss.str()); |
79 contents.c_str(), | |
80 contents.length()); | |
81 } | 83 } |
82 | 84 |
83 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { | 85 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { |
84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 86 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
85 | 87 |
86 words_.clear(); | 88 words_.clear(); |
87 if (custom_words) | 89 if (custom_words) |
88 std::swap(words_, *custom_words); | 90 std::swap(words_, *custom_words); |
89 | 91 |
90 std::vector<Observer*>::iterator it; | 92 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded()); |
groby-ooo-7-16
2012/12/04 00:16:30
+1 for awesome macros!
| |
91 for (it = observers_.begin(); it != observers_.end(); ++it) | |
92 (*it)->OnCustomDictionaryLoaded(); | |
93 } | 93 } |
94 | 94 |
95 bool SpellcheckCustomDictionary::AddWord(const std::string& word) { | 95 bool SpellcheckCustomDictionary::AddWord(const std::string& word) { |
96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
97 | 97 |
98 if (!CustomWordAddedLocally(word)) | 98 if (!CustomWordAddedLocally(word)) |
99 return false; | 99 return false; |
100 | 100 |
101 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 101 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
102 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary, | 102 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary, |
103 base::Unretained(this), word)); | 103 base::Unretained(this), word)); |
104 | 104 |
105 for (content::RenderProcessHost::iterator i( | 105 for (content::RenderProcessHost::iterator i( |
106 content::RenderProcessHost::AllHostsIterator()); | 106 content::RenderProcessHost::AllHostsIterator()); |
107 !i.IsAtEnd(); i.Advance()) { | 107 !i.IsAtEnd(); i.Advance()) { |
108 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word)); | 108 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word)); |
109 } | 109 } |
110 | 110 |
111 std::vector<Observer*>::iterator it; | 111 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordAdded(word)); |
112 for (it = observers_.begin(); it != observers_.end(); ++it) | |
113 (*it)->OnCustomDictionaryWordAdded(word); | |
114 | 112 |
115 return true; | 113 return true; |
116 } | 114 } |
117 | 115 |
118 bool SpellcheckCustomDictionary::CustomWordAddedLocally( | 116 bool SpellcheckCustomDictionary::CustomWordAddedLocally( |
119 const std::string& word) { | 117 const std::string& word) { |
120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 118 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
121 | 119 |
122 WordList::iterator it = std::find(words_.begin(), words_.end(), word); | 120 WordList::iterator it = std::find(words_.begin(), words_.end(), word); |
123 if (it == words_.end()) { | 121 if (it == words_.end()) { |
124 words_.push_back(word); | 122 words_.push_back(word); |
125 return true; | 123 return true; |
126 } | 124 } |
127 return false; | 125 return false; |
128 // TODO(rlp): record metrics on custom word size | 126 // TODO(rlp): record metrics on custom word size |
129 } | 127 } |
130 | 128 |
131 void SpellcheckCustomDictionary::WriteWordToCustomDictionary( | 129 void SpellcheckCustomDictionary::WriteWordToCustomDictionary( |
132 const std::string& word) { | 130 const std::string& word) { |
133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 131 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
134 | |
135 // Stored in UTF-8. | |
136 DCHECK(IsStringUTF8(word)); | 132 DCHECK(IsStringUTF8(word)); |
137 | 133 |
138 std::string word_to_add(word + "\n"); | 134 std::string contents; |
139 if (!file_util::PathExists(custom_dictionary_path_)) { | 135 LoadDictionaryContentsReliably(&contents); |
140 file_util::WriteFile(custom_dictionary_path_, word_to_add.c_str(), | 136 |
141 word_to_add.length()); | 137 std::stringstream ss; |
142 } else { | 138 ss << contents; |
143 file_util::AppendToFile(custom_dictionary_path_, word_to_add.c_str(), | 139 if (contents.length() > 0 && contents[contents.length() - 1] != '\n') |
144 word_to_add.length()); | 140 ss << '\n'; |
groby-ooo-7-16
2012/12/04 00:16:30
Doesn't "LoadReliably" guarantee proper format?
A
please use gerrit instead
2012/12/04 05:46:40
Done.
| |
145 } | 141 ss << word << '\n'; |
142 | |
143 SaveDictionryContentsReliably(ss.str()); | |
146 } | 144 } |
147 | 145 |
148 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { | 146 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { |
149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 147 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
150 | 148 |
151 if (!CustomWordRemovedLocally(word)) | 149 if (!CustomWordRemovedLocally(word)) |
152 return false; | 150 return false; |
153 | 151 |
154 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 152 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
155 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary, | 153 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary, |
156 base::Unretained(this), word)); | 154 base::Unretained(this), word)); |
157 | 155 |
158 for (content::RenderProcessHost::iterator i( | 156 for (content::RenderProcessHost::iterator i( |
159 content::RenderProcessHost::AllHostsIterator()); | 157 content::RenderProcessHost::AllHostsIterator()); |
160 !i.IsAtEnd(); i.Advance()) { | 158 !i.IsAtEnd(); i.Advance()) { |
161 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word)); | 159 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word)); |
162 } | 160 } |
163 | 161 |
164 std::vector<Observer*>::iterator it; | 162 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordRemoved(word)); |
165 for (it = observers_.begin(); it != observers_.end(); ++it) | |
166 (*it)->OnCustomDictionaryWordRemoved(word); | |
167 | 163 |
168 return true; | 164 return true; |
169 } | 165 } |
170 | 166 |
171 bool SpellcheckCustomDictionary::CustomWordRemovedLocally( | 167 bool SpellcheckCustomDictionary::CustomWordRemovedLocally( |
172 const std::string& word) { | 168 const std::string& word) { |
173 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 169 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
174 | 170 |
175 WordList::iterator it = std::find(words_.begin(), words_.end(), word); | 171 WordList::iterator it = std::find(words_.begin(), words_.end(), word); |
176 if (it != words_.end()) { | 172 if (it != words_.end()) { |
177 words_.erase(it); | 173 words_.erase(it); |
178 return true; | 174 return true; |
179 } | 175 } |
180 return false; | 176 return false; |
181 } | 177 } |
182 | 178 |
183 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( | 179 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( |
184 const std::string& word) { | 180 const std::string& word) { |
185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
186 DCHECK(IsStringUTF8(word)); | 182 DCHECK(IsStringUTF8(word)); |
187 | 183 |
184 std::string contents; | |
185 LoadDictionaryContentsReliably(&contents); | |
186 if (contents.empty()) | |
187 return; | |
188 | |
188 WordList custom_words; | 189 WordList custom_words; |
189 LoadDictionaryIntoCustomWordList(&custom_words); | 190 base::SplitString(contents, '\n', &custom_words); |
190 | 191 |
191 const char empty[] = {'\0'}; | 192 std::stringstream ss; |
192 const char separator[] = {'\n', '\0'}; | |
193 file_util::WriteFile(custom_dictionary_path_, empty, 0); | |
194 for (WordList::iterator it = custom_words.begin(); | 193 for (WordList::iterator it = custom_words.begin(); |
groby-ooo-7-16
2012/12/04 00:16:30
This would make the code easier to read:
it = cu
please use gerrit instead
2012/12/04 05:46:40
Done.
| |
195 it != custom_words.end(); | 194 it != custom_words.end(); |
groby-ooo-7-16
2012/12/04 00:16:30
The above remark (std::erase) brings up another po
please use gerrit instead
2012/12/04 05:46:40
Done.
| |
196 ++it) { | 195 ++it) { |
197 std::string word_to_add = *it; | 196 if (!(*it).empty() && *it != word) |
198 if (word.compare(word_to_add) != 0) { | 197 ss << *it << '\n'; |
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 } | 198 } |
199 | |
200 SaveDictionryContentsReliably(ss.str()); | |
204 } | 201 } |
205 | 202 |
206 void SpellcheckCustomDictionary::AddObserver(Observer* observer) { | 203 void SpellcheckCustomDictionary::AddObserver(Observer* observer) { |
207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
208 | 205 |
209 observers_.push_back(observer); | 206 observers_.AddObserver(observer); |
210 } | 207 } |
211 | 208 |
212 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { | 209 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { |
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 210 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
214 | 211 |
215 std::vector<Observer*>::iterator it = std::find(observers_.begin(), | 212 observers_.RemoveObserver(observer); |
216 observers_.end(), | |
217 observer); | |
218 if (it != observers_.end()) | |
219 observers_.erase(it); | |
220 } | 213 } |
221 | 214 |
222 WordList* SpellcheckCustomDictionary::LoadDictionary() { | 215 WordList* SpellcheckCustomDictionary::LoadDictionary() { |
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 216 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
224 | 217 |
225 WordList* custom_words = new WordList; | 218 WordList* custom_words = new WordList; |
226 LoadDictionaryIntoCustomWordList(custom_words); | 219 LoadDictionaryIntoCustomWordList(custom_words); |
227 return custom_words; | 220 return custom_words; |
228 } | 221 } |
229 | 222 |
230 void SpellcheckCustomDictionary::SetCustomWordListAndDelete( | 223 void SpellcheckCustomDictionary::SetCustomWordListAndDelete( |
231 WordList* custom_words) { | 224 WordList* custom_words) { |
232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
233 | 226 |
234 SetCustomWordList(custom_words); | 227 SetCustomWordList(custom_words); |
235 delete custom_words; | 228 delete custom_words; |
236 } | 229 } |
230 | |
231 void SpellcheckCustomDictionary::LoadDictionaryContentsReliably( | |
232 std::string* contents) { | |
233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
234 | |
235 file_util::ReadFileToString(custom_dictionary_path_, contents); | |
236 std::string checksum = GetChecksum(contents); | |
237 if (checksum.empty()) | |
groby-ooo-7-16
2012/12/04 00:16:30
Hm. This looks like it will fail for any dictionar
please use gerrit instead
2012/12/04 05:46:40
Empty checksum most likely indicates that this is
| |
238 return; | |
groby-ooo-7-16
2012/12/04 00:16:30
If you go with the one-file-to-rule-them all appro
please use gerrit instead
2012/12/04 05:46:40
Done.
| |
239 | |
240 base::MD5Digest digest; | |
241 base::MD5Sum(contents->c_str(), contents->length(), &digest); | |
242 if (checksum == base::MD5DigestToBase16(digest)) | |
243 return; | |
244 | |
245 FilePath backup_path = | |
246 custom_dictionary_path_.AddExtension(backup_extension_); | |
247 if (!file_util::PathExists(backup_path)) | |
248 return; | |
249 | |
250 std::string backup; | |
groby-ooo-7-16
2012/12/04 00:16:30
Backup is just the last copy saved before this one
please use gerrit instead
2012/12/04 05:46:40
Done.
| |
251 file_util::ReadFileToString(backup_path, &backup); | |
252 std::string backup_checksum = GetChecksum(&backup); | |
253 base::MD5Digest backup_digest; | |
254 base::MD5Sum(backup.c_str(), backup.length(), &backup_digest); | |
255 if (backup_checksum != base::MD5DigestToBase16(backup_digest)) | |
256 return; | |
257 | |
258 *contents = backup; | |
259 file_util::CopyFile(backup_path, custom_dictionary_path_); | |
260 } | |
261 | |
262 void SpellcheckCustomDictionary::SaveDictionryContentsReliably( | |
263 const std::string& contents) { | |
264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
265 | |
266 FilePath backup_path = | |
267 custom_dictionary_path_.AddExtension(backup_extension_); | |
268 if (file_util::PathExists(custom_dictionary_path_)) | |
269 file_util::CopyFile(custom_dictionary_path_, backup_path); | |
270 | |
271 base::MD5Digest digest; | |
272 base::MD5Sum(contents.c_str(), contents.length(), &digest); | |
273 std::stringstream ss; | |
274 ss << checksum_prefix_ << base::MD5DigestToBase16(digest) << '\n' << contents; | |
275 base::ImportantFileWriter::WriteFileAtomically(custom_dictionary_path_, | |
groby-ooo-7-16
2012/12/04 00:16:30
I might be wrong, but doesn't this just write the
please use gerrit instead
2012/12/04 05:46:40
It writes the prefix, the checksum, a newline, and
| |
276 ss.str()); | |
277 } | |
278 | |
279 std::string SpellcheckCustomDictionary::GetChecksum(std::string* contents) { | |
280 std::string checksum; | |
groby-ooo-7-16
2012/12/04 00:16:30
You can check if something is a checksum much easi
please use gerrit instead
2012/12/04 05:46:40
Done.
| |
281 if (contents->substr(0, checksum_prefix_.length()) == checksum_prefix_) { | |
282 size_t after_checksum = contents->find('\n', checksum_prefix_.length()); | |
283 if (after_checksum == std::string::npos) { | |
284 checksum = contents->substr( | |
285 checksum_prefix_.length(), | |
286 contents->length() - checksum_prefix_.length()); | |
287 contents->clear(); | |
288 } else { | |
289 checksum = contents->substr(checksum_prefix_.length(), | |
290 after_checksum - checksum_prefix_.length()); | |
291 *contents = contents->substr(after_checksum + 1); | |
292 } | |
293 } | |
294 return checksum; | |
295 } | |
OLD | NEW |