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

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
22 namespace {
23
24 const FilePath::CharType BACKUP_EXTENSION[] = FILE_PATH_LITERAL("backup");
25 const char CHECKSUM_PREFIX[] = "checksum_v1 = ";
26
27 // Loads the lines from the file at |file_path| into the |lines| container. If
28 // the file has a valid checksum, then returns |true|. If the file has an
29 // invalid checksum, then returns |false| and clears |lines|.
30 bool LoadFile(FilePath file_path, std::vector<std::string>* lines) {
31 lines->clear();
32 std::string contents;
33 file_util::ReadFileToString(file_path, &contents);
34 size_t pos = contents.rfind(CHECKSUM_PREFIX);
35 if (pos != std::string::npos) {
36 std::string checksum = contents.substr(pos + strlen(CHECKSUM_PREFIX));
37 contents = contents.substr(0, pos);
38 if (checksum != base::MD5String(contents))
39 return false;
40 }
41 TrimWhitespaceASCII(contents, TRIM_ALL, &contents);
42 base::SplitString(contents, '\n', lines);
43 return true;
44 }
45
46 bool IsValidWord(const std::string& word) {
47 return IsStringUTF8(word) && word.length() <= 128 && word.length() > 0 &&
48 std::string::npos == word.find_first_of(kWhitespaceASCII);
49 }
50
51 bool IsInvalidWord(const std::string& word) {
52 return !IsValidWord(word);
53 }
54
55 } // namespace
56
20 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) 57 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile)
21 : SpellcheckDictionary(profile), 58 : SpellcheckDictionary(profile),
22 custom_dictionary_path_(), 59 custom_dictionary_path_(),
23 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { 60 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
24 DCHECK(profile); 61 DCHECK(profile);
25 custom_dictionary_path_ = 62 custom_dictionary_path_ =
26 profile_->GetPath().Append(chrome::kCustomDictionaryFileName); 63 profile_->GetPath().Append(chrome::kCustomDictionaryFileName);
27 } 64 }
28 65
29 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() { 66 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() {
(...skipping 12 matching lines...) Expand all
42 } 79 }
43 80
44 const WordList& SpellcheckCustomDictionary::GetWords() const { 81 const WordList& SpellcheckCustomDictionary::GetWords() const {
45 return words_; 82 return words_;
46 } 83 }
47 84
48 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList( 85 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList(
49 WordList* custom_words) { 86 WordList* custom_words) {
50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
51 88
52 std::string contents; 89 LoadDictionaryFileReliably(custom_words);
53 file_util::ReadFileToString(custom_dictionary_path_, &contents); 90 if (custom_words->empty())
54 if (contents.empty()) {
55 custom_words->clear();
56 return; 91 return;
57 }
58 92
59 base::SplitString(contents, '\n', custom_words); 93 // Clean up the dictionary file contents by removing duplicates and invalid
60 94 // words.
61 // Erase duplicates.
62 std::sort(custom_words->begin(), custom_words->end()); 95 std::sort(custom_words->begin(), custom_words->end());
63 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), 96 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()),
64 custom_words->end()); 97 custom_words->end());
98 custom_words->erase(std::remove_if(custom_words->begin(),
99 custom_words->end(),
100 IsInvalidWord),
101 custom_words->end());
65 102
66 // Clear out empty words. 103 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 } 104 }
82 105
83 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { 106 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) {
84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
85 108
86 words_.clear(); 109 words_.clear();
87 if (custom_words) 110 if (custom_words)
88 std::swap(words_, *custom_words); 111 std::swap(words_, *custom_words);
89 112
90 std::vector<Observer*>::iterator it; 113 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded());
91 for (it = observers_.begin(); it != observers_.end(); ++it)
92 (*it)->OnCustomDictionaryLoaded();
93 } 114 }
94 115
95 bool SpellcheckCustomDictionary::AddWord(const std::string& word) { 116 bool SpellcheckCustomDictionary::AddWord(const std::string& word) {
96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
118 if (!IsValidWord(word)) {
119 NOTREACHED();
groby-ooo-7-16 2012/12/11 19:25:48 nit: Why is this NOTREACHED? Are we really guarant
please use gerrit instead 2012/12/11 22:32:58 Removed.
120 return false;
121 }
97 122
98 if (!CustomWordAddedLocally(word)) 123 if (!CustomWordAddedLocally(word))
99 return false; 124 return false;
100 125
101 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 126 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
102 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary, 127 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary,
103 base::Unretained(this), word)); 128 base::Unretained(this), word));
104 129
105 for (content::RenderProcessHost::iterator i( 130 for (content::RenderProcessHost::iterator i(
106 content::RenderProcessHost::AllHostsIterator()); 131 content::RenderProcessHost::AllHostsIterator());
107 !i.IsAtEnd(); i.Advance()) { 132 !i.IsAtEnd(); i.Advance()) {
108 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word)); 133 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word));
109 } 134 }
110 135
111 std::vector<Observer*>::iterator it; 136 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordAdded(word));
112 for (it = observers_.begin(); it != observers_.end(); ++it)
113 (*it)->OnCustomDictionaryWordAdded(word);
114 137
115 return true; 138 return true;
116 } 139 }
117 140
118 bool SpellcheckCustomDictionary::CustomWordAddedLocally( 141 bool SpellcheckCustomDictionary::CustomWordAddedLocally(
119 const std::string& word) { 142 const std::string& word) {
120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 143 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
144 DCHECK(IsValidWord(word));
121 145
122 WordList::iterator it = std::find(words_.begin(), words_.end(), word); 146 WordList::iterator it = std::find(words_.begin(), words_.end(), word);
123 if (it == words_.end()) { 147 if (it == words_.end()) {
124 words_.push_back(word); 148 words_.push_back(word);
125 return true; 149 return true;
126 } 150 }
127 return false; 151 return false;
128 // TODO(rlp): record metrics on custom word size 152 // TODO(rlp): record metrics on custom word size
129 } 153 }
130 154
131 void SpellcheckCustomDictionary::WriteWordToCustomDictionary( 155 void SpellcheckCustomDictionary::WriteWordToCustomDictionary(
132 const std::string& word) { 156 const std::string& word) {
133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
158 DCHECK(IsValidWord(word));
134 159
135 // Stored in UTF-8. 160 WordList custom_words;
136 DCHECK(IsStringUTF8(word)); 161 LoadDictionaryFileReliably(&custom_words);
137 162 custom_words.push_back(word);
138 std::string word_to_add(word + "\n"); 163 SaveDictionaryFileReliably(custom_words);
139 if (!file_util::PathExists(custom_dictionary_path_)) {
140 file_util::WriteFile(custom_dictionary_path_, word_to_add.c_str(),
141 word_to_add.length());
142 } else {
143 file_util::AppendToFile(custom_dictionary_path_, word_to_add.c_str(),
144 word_to_add.length());
145 }
146 } 164 }
147 165
148 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { 166 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) {
149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 167 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
168 if (!IsValidWord(word)) {
169 NOTREACHED();
170 return false;
171 }
150 172
151 if (!CustomWordRemovedLocally(word)) 173 if (!CustomWordRemovedLocally(word))
152 return false; 174 return false;
153 175
154 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 176 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
155 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary, 177 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary,
156 base::Unretained(this), word)); 178 base::Unretained(this), word));
157 179
158 for (content::RenderProcessHost::iterator i( 180 for (content::RenderProcessHost::iterator i(
groby-ooo-7-16 2012/12/11 19:25:48 Curious: Shouldn't we notify once the FILE task ha
please use gerrit instead 2012/12/11 22:32:58 Renderers receive the removed and added words in t
159 content::RenderProcessHost::AllHostsIterator()); 181 content::RenderProcessHost::AllHostsIterator());
160 !i.IsAtEnd(); i.Advance()) { 182 !i.IsAtEnd(); i.Advance()) {
161 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word)); 183 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word));
162 } 184 }
163 185
164 std::vector<Observer*>::iterator it; 186 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordRemoved(word));
165 for (it = observers_.begin(); it != observers_.end(); ++it)
166 (*it)->OnCustomDictionaryWordRemoved(word);
167 187
168 return true; 188 return true;
169 } 189 }
170 190
171 bool SpellcheckCustomDictionary::CustomWordRemovedLocally( 191 bool SpellcheckCustomDictionary::CustomWordRemovedLocally(
172 const std::string& word) { 192 const std::string& word) {
173 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 193 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
194 DCHECK(IsValidWord(word));
174 195
175 WordList::iterator it = std::find(words_.begin(), words_.end(), word); 196 WordList::iterator it = std::find(words_.begin(), words_.end(), word);
176 if (it != words_.end()) { 197 if (it != words_.end()) {
177 words_.erase(it); 198 words_.erase(it);
178 return true; 199 return true;
179 } 200 }
180 return false; 201 return false;
181 } 202 }
182 203
183 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( 204 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary(
184 const std::string& word) { 205 const std::string& word) {
185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 206 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
186 DCHECK(IsStringUTF8(word)); 207 DCHECK(IsValidWord(word));
187 208
188 WordList custom_words; 209 WordList custom_words;
189 LoadDictionaryIntoCustomWordList(&custom_words); 210 LoadDictionaryFileReliably(&custom_words);
211 if (custom_words.empty())
212 return;
190 213
191 const char empty[] = {'\0'}; 214 WordList::iterator it = std::find(custom_words.begin(),
192 const char separator[] = {'\n', '\0'}; 215 custom_words.end(),
193 file_util::WriteFile(custom_dictionary_path_, empty, 0); 216 word);
194 for (WordList::iterator it = custom_words.begin(); 217 if (it != custom_words.end())
195 it != custom_words.end(); 218 custom_words.erase(it);
196 ++it) { 219
197 std::string word_to_add = *it; 220 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 } 221 }
205 222
206 void SpellcheckCustomDictionary::AddObserver(Observer* observer) { 223 void SpellcheckCustomDictionary::AddObserver(Observer* observer) {
207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 224 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
208 225
209 observers_.push_back(observer); 226 observers_.AddObserver(observer);
210 } 227 }
211 228
212 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { 229 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) {
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 230 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
214 231
215 std::vector<Observer*>::iterator it = std::find(observers_.begin(), 232 observers_.RemoveObserver(observer);
216 observers_.end(),
217 observer);
218 if (it != observers_.end())
219 observers_.erase(it);
220 } 233 }
221 234
222 WordList* SpellcheckCustomDictionary::LoadDictionary() { 235 WordList* SpellcheckCustomDictionary::LoadDictionary() {
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 236 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
224 237
225 WordList* custom_words = new WordList; 238 WordList* custom_words = new WordList;
226 LoadDictionaryIntoCustomWordList(custom_words); 239 LoadDictionaryIntoCustomWordList(custom_words);
227 return custom_words; 240 return custom_words;
228 } 241 }
229 242
230 void SpellcheckCustomDictionary::SetCustomWordListAndDelete( 243 void SpellcheckCustomDictionary::SetCustomWordListAndDelete(
231 WordList* custom_words) { 244 WordList* custom_words) {
232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
233 246
234 SetCustomWordList(custom_words); 247 SetCustomWordList(custom_words);
235 delete custom_words; 248 delete custom_words;
236 } 249 }
250
251 void SpellcheckCustomDictionary::LoadDictionaryFileReliably(
252 WordList* custom_words) {
253 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
254
255 // Load the contents and verify the checksum.
256 if (LoadFile(custom_dictionary_path_, custom_words))
257 return;
258
259 // Checksum is not valid. See if there's a backup.
260 FilePath backup = custom_dictionary_path_.AddExtension(BACKUP_EXTENSION);
261 if (!file_util::PathExists(backup))
262 return;
263
264 // Load the backup and verify its checksum.
265 if (!LoadFile(backup, custom_words))
266 return;
267
268 // Backup checksum is valid. Restore the backup.
269 file_util::CopyFile(backup, custom_dictionary_path_);
270 }
271
272 void SpellcheckCustomDictionary::SaveDictionaryFileReliably(
273 const WordList& custom_words) {
274 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
275
276 std::stringstream content;
277 for (WordList::const_iterator it = custom_words.begin();
278 it != custom_words.end();
279 ++it) {
280 content << *it << '\n';
281 }
282 std::string checksum = base::MD5String(content.str());
283 content << CHECKSUM_PREFIX << checksum;
284
285 file_util::CopyFile(custom_dictionary_path_,
286 custom_dictionary_path_.AddExtension(BACKUP_EXTENSION));
287 base::ImportantFileWriter::WriteFileAtomically(custom_dictionary_path_,
288 content.str());
289 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698