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

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_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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698