Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Side by Side Diff: chrome/browser/spellchecker/spellcheck_custom_dictionary.cc

Issue 11414282: Improve reliability of custom spelling dictionary file. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698