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

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

Issue 11445002: Sync user's custom spellcheck dictionary (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" 10 #include "base/files/important_file_writer.h"
11 #include "base/md5.h" 11 #include "base/md5.h"
12 #include "base/string_number_conversions.h"
12 #include "base/string_split.h" 13 #include "base/string_split.h"
13 #include "chrome/browser/profiles/profile.h" 14 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/common/chrome_constants.h" 15 #include "chrome/common/chrome_constants.h"
15 #include "chrome/common/spellcheck_messages.h" 16 #include "chrome/common/spellcheck_messages.h"
16 #include "content/public/browser/browser_thread.h" 17 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/render_process_host.h" 18 #include "sync/api/sync_change.h"
19 #include "sync/api/sync_data.h"
20 #include "sync/api/sync_error_factory.h"
21 #include "sync/protocol/sync.pb.h"
18 22
19 using content::BrowserThread; 23 using content::BrowserThread;
20 using chrome::spellcheck_common::WordList; 24 using chrome::spellcheck_common::WordList;
21 25
22 namespace { 26 namespace {
23 27
28 const size_t MAX_SYNC_SIZE = 1300;
groby-ooo-7-16 2012/12/19 22:16:00 Please document vars. (Specifically, what does "13
please use gerrit instead 2012/12/22 03:20:19 Done. I renamed the variable, commented it, and mo
24 const FilePath::CharType BACKUP_EXTENSION[] = FILE_PATH_LITERAL("backup"); 29 const FilePath::CharType BACKUP_EXTENSION[] = FILE_PATH_LITERAL("backup");
25 const char CHECKSUM_PREFIX[] = "checksum_v1 = "; 30 const char CHECKSUM_PREFIX[] = "checksum_v1 = ";
26 31
32 const std::string DictionaryTooBigToSyncErrorMessage(size_t dictionary_size) {
groby-ooo-7-16 2012/12/19 22:16:00 Shouldn't we i18n error messages?
please use gerrit instead 2012/12/22 03:20:19 This message is for server-side logging. It will b
33 return "Turning off sync for custom spelling dictionary with " +
34 base::Uint64ToString(dictionary_size) + " words. Sync limit is " +
35 base::Uint64ToString(MAX_SYNC_SIZE) + " words.";
36 }
37
27 // Loads the lines from the file at |file_path| into the |lines| container. If 38 // 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 39 // the file has a valid checksum, then returns |true|. If the file has an
29 // invalid checksum, then returns |false| and clears |lines|. 40 // invalid checksum, then returns |false| and clears |lines|.
30 bool LoadFile(FilePath file_path, std::vector<std::string>* lines) { 41 bool LoadFile(FilePath file_path, std::vector<std::string>* lines) {
31 lines->clear(); 42 lines->clear();
32 std::string contents; 43 std::string contents;
33 file_util::ReadFileToString(file_path, &contents); 44 file_util::ReadFileToString(file_path, &contents);
34 size_t pos = contents.rfind(CHECKSUM_PREFIX); 45 size_t pos = contents.rfind(CHECKSUM_PREFIX);
35 if (pos != std::string::npos) { 46 if (pos != std::string::npos) {
36 std::string checksum = contents.substr(pos + strlen(CHECKSUM_PREFIX)); 47 std::string checksum = contents.substr(pos + strlen(CHECKSUM_PREFIX));
37 contents = contents.substr(0, pos); 48 contents = contents.substr(0, pos);
38 if (checksum != base::MD5String(contents)) 49 if (checksum != base::MD5String(contents))
39 return false; 50 return false;
40 } 51 }
41 TrimWhitespaceASCII(contents, TRIM_ALL, &contents); 52 TrimWhitespaceASCII(contents, TRIM_ALL, &contents);
42 base::SplitString(contents, '\n', lines); 53 base::SplitString(contents, '\n', lines);
43 return true; 54 return true;
44 } 55 }
45 56
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) { 57 bool IsInvalidWord(const std::string& word) {
52 return !IsValidWord(word); 58 return !IsStringUTF8(word) || word.length() > 128 || word.empty() ||
groby-ooo-7-16 2012/12/19 22:16:00 128 is a magic number. Is there a constant for thi
please use gerrit instead 2012/12/22 03:20:19 Great catch! The length check needs to match Hunsp
59 word.find_first_of(kWhitespaceASCII) != std::string::npos;
53 } 60 }
54 61
55 } // namespace 62 } // namespace
56 63
57 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) 64 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile)
58 : SpellcheckDictionary(profile), 65 : SpellcheckDictionary(profile),
59 custom_dictionary_path_(), 66 custom_dictionary_path_(),
60 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { 67 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
68 is_loaded_(false) {
61 DCHECK(profile); 69 DCHECK(profile);
62 custom_dictionary_path_ = 70 custom_dictionary_path_ =
63 profile_->GetPath().Append(chrome::kCustomDictionaryFileName); 71 profile_->GetPath().Append(chrome::kCustomDictionaryFileName);
64 } 72 }
65 73
66 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() { 74 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() {
67 } 75 }
68 76
69 void SpellcheckCustomDictionary::Load() { 77 void SpellcheckCustomDictionary::Load() {
70 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 78 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
71 79
72 BrowserThread::PostTaskAndReplyWithResult<WordList*>( 80 BrowserThread::PostTaskAndReplyWithResult<WordList*>(
73 BrowserThread::FILE, 81 BrowserThread::FILE,
74 FROM_HERE, 82 FROM_HERE,
75 base::Bind(&SpellcheckCustomDictionary::LoadDictionary, 83 base::Bind(&SpellcheckCustomDictionary::LoadDictionary,
76 base::Unretained(this)), 84 custom_dictionary_path_),
77 base::Bind(&SpellcheckCustomDictionary::SetCustomWordListAndDelete, 85 base::Bind(&SpellcheckCustomDictionary::SetCustomWordListAndDelete,
78 weak_ptr_factory_.GetWeakPtr())); 86 weak_ptr_factory_.GetWeakPtr()));
79 } 87 }
80 88
81 const WordList& SpellcheckCustomDictionary::GetWords() const { 89 const WordList& SpellcheckCustomDictionary::GetWords() const {
82 return words_; 90 return words_;
83 } 91 }
84 92
93 // static
85 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList( 94 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList(
86 WordList* custom_words) { 95 WordList* custom_words,
96 const FilePath& path) {
87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 97 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
88 98
89 LoadDictionaryFileReliably(custom_words); 99 LoadDictionaryFileReliably(custom_words, path);
90 if (custom_words->empty()) 100 if (custom_words->empty())
91 return; 101 return;
92 102
93 // Clean up the dictionary file contents by removing duplicates and invalid 103 // Clean up the dictionary file contents by removing duplicates and invalid
94 // words. 104 // words.
95 std::sort(custom_words->begin(), custom_words->end()); 105 std::sort(custom_words->begin(), custom_words->end());
96 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), 106 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()),
97 custom_words->end()); 107 custom_words->end());
98 custom_words->erase(std::remove_if(custom_words->begin(), 108 custom_words->erase(std::remove_if(custom_words->begin(),
99 custom_words->end(), 109 custom_words->end(),
100 IsInvalidWord), 110 IsInvalidWord),
101 custom_words->end()); 111 custom_words->end());
102 112
103 SaveDictionaryFileReliably(*custom_words); 113 SaveDictionaryFileReliably(*custom_words, path);
104 } 114 }
105 115
106 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { 116 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) {
107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
108 118
109 words_.clear(); 119 words_.clear();
110 if (custom_words) 120 if (custom_words)
111 std::swap(words_, *custom_words); 121 std::swap(words_, *custom_words);
112
113 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded());
114 } 122 }
115 123
116 bool SpellcheckCustomDictionary::AddWord(const std::string& word) { 124 bool SpellcheckCustomDictionary::AddWord(const std::string& word) {
117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 125 WordList to_add;
118 if (!IsValidWord(word)) 126 to_add.push_back(word);
119 return false; 127 WordList to_remove;
120 128 return UpdateWords(&to_add, &to_remove);
121 if (!CustomWordAddedLocally(word))
122 return false;
123
124 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
125 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary,
126 base::Unretained(this), word));
127
128 for (content::RenderProcessHost::iterator i(
129 content::RenderProcessHost::AllHostsIterator());
130 !i.IsAtEnd(); i.Advance()) {
131 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word));
132 }
133
134 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordAdded(word));
135
136 return true;
137 } 129 }
138 130
139 bool SpellcheckCustomDictionary::CustomWordAddedLocally( 131 bool SpellcheckCustomDictionary::CustomWordAddedLocally(
140 const std::string& word) { 132 const std::string& word) {
141 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 133 WordList to_add;
142 DCHECK(IsValidWord(word)); 134 to_add.push_back(word);
143 135 WordList to_remove;
144 WordList::iterator it = std::find(words_.begin(), words_.end(), word); 136 return CustomWordsUpdatedLocally(&to_add, &to_remove);
145 if (it == words_.end()) { 137 }
146 words_.push_back(word); 138
147 return true; 139 // static
148 }
149 return false;
150 // TODO(rlp): record metrics on custom word size
151 }
152
153 void SpellcheckCustomDictionary::WriteWordToCustomDictionary( 140 void SpellcheckCustomDictionary::WriteWordToCustomDictionary(
154 const std::string& word) { 141 const std::string& word,
155 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 142 const FilePath& path) {
156 DCHECK(IsValidWord(word)); 143 WordList to_write;
157 144 to_write.push_back(word);
158 WordList custom_words; 145 WordList to_erase;
159 LoadDictionaryFileReliably(&custom_words); 146 WriteAndEraseWordsInCustomDictionary(&to_write, &to_erase, path);
160 custom_words.push_back(word);
161 SaveDictionaryFileReliably(custom_words);
162 } 147 }
163 148
164 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { 149 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) {
165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 150 WordList to_add;
166 if (!IsValidWord(word)) 151 WordList to_remove;
167 return false; 152 to_remove.push_back(word);
168 153 return UpdateWords(&to_add, &to_remove);
169 if (!CustomWordRemovedLocally(word))
170 return false;
171
172 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
173 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary,
174 base::Unretained(this), word));
175
176 for (content::RenderProcessHost::iterator i(
177 content::RenderProcessHost::AllHostsIterator());
178 !i.IsAtEnd(); i.Advance()) {
179 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word));
180 }
181
182 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordRemoved(word));
183
184 return true;
185 } 154 }
186 155
187 bool SpellcheckCustomDictionary::CustomWordRemovedLocally( 156 bool SpellcheckCustomDictionary::CustomWordRemovedLocally(
188 const std::string& word) { 157 const std::string& word) {
189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 158 WordList to_add;
groby-ooo-7-16 2012/12/19 22:16:00 There's a potential refactor here, since we have s
please use gerrit instead 2012/12/22 03:20:19 Done.
190 DCHECK(IsValidWord(word)); 159 WordList to_remove;
191 160 to_remove.push_back(word);
192 WordList::iterator it = std::find(words_.begin(), words_.end(), word); 161 return CustomWordsUpdatedLocally(&to_add, &to_remove);
193 if (it != words_.end()) { 162 }
194 words_.erase(it); 163
195 return true; 164 // static
196 }
197 return false;
198 }
199
200 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( 165 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary(
201 const std::string& word) { 166 const std::string& word,
167 const FilePath& path) {
168 WordList to_write;
169 WordList to_erase;
170 to_erase.push_back(word);
171 WriteAndEraseWordsInCustomDictionary(&to_write, &to_erase, path);
172 }
173
174 void SpellcheckCustomDictionary::AddObserver(Observer* observer) {
175 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
176
177 observers_.AddObserver(observer);
178 }
179
180 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) {
181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
182
183 observers_.RemoveObserver(observer);
184 }
185
186 syncer::SyncMergeResult SpellcheckCustomDictionary::MergeDataAndStartSyncing(
187 syncer::ModelType type,
188 const syncer::SyncDataList& initial_sync_data,
189 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
190 scoped_ptr<syncer::SyncErrorFactory> sync_error_handler) {
191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
192 DCHECK(!sync_processor_.get());
193 DCHECK(!sync_error_handler_.get());
194 DCHECK(sync_processor.get());
195 DCHECK(sync_error_handler.get());
196 DCHECK_EQ(syncer::DICTIONARY, type);
197
198 sync_processor_ = sync_processor.Pass();
199 sync_error_handler_ = sync_error_handler.Pass();
200
201 WordList to_add_locally;
groby-ooo-7-16 2012/12/19 22:16:00 Please comment what you're actually doing here. (G
please use gerrit instead 2012/12/22 03:20:19 Done.
202 WordList to_add_remotely(words_);
203 std::string word;
204 for (syncer::SyncDataList::const_iterator it = initial_sync_data.begin();
205 it != initial_sync_data.end();
206 ++it) {
207 DCHECK_EQ(syncer::DICTIONARY, it->GetDataType());
208 word = it->GetSpecifics().dictionary().word();
209 WordList::iterator found = std::find(
groby-ooo-7-16 2012/12/19 22:16:00 I might be wrong, but I think most of this can be
please use gerrit instead 2012/12/22 03:20:19 Done. Used std::set_difference and std::set_inters
210 to_add_remotely.begin(), to_add_remotely.end(), word);
211 if (found == to_add_remotely.end())
212 to_add_locally.push_back(word);
213 else
214 to_add_remotely.erase(found);
215 }
216
217 WordList to_remove;
218 UpdateWords(&to_add_locally, &to_remove, true);
219
220 syncer::SyncChangeList local_changes;
221 size_t i = initial_sync_data.size();
222 for (WordList::iterator it = to_add_remotely.begin();
223 it != to_add_remotely.end() && i < MAX_SYNC_SIZE;
224 ++it, ++i) {
225 word = *it;
226 sync_pb::EntitySpecifics specifics;
227 specifics.mutable_dictionary()->set_word(word);
228 local_changes.push_back(syncer::SyncChange(
229 FROM_HERE,
230 syncer::SyncChange::ACTION_ADD,
231 syncer::SyncData::CreateLocalData(word, word, specifics)));
232 }
233
234 syncer::SyncMergeResult result(type);
235 result.set_error(sync_processor_->ProcessSyncChanges(FROM_HERE,
236 local_changes));
237 if (result.error().IsSet())
238 return result;
239
240 if (words_.size() > MAX_SYNC_SIZE) {
241 result.set_error(sync_error_handler_->CreateAndUploadError(
242 FROM_HERE,
243 DictionaryTooBigToSyncErrorMessage(words_.size())));
244 StopSyncing(syncer::DICTIONARY);
245 }
246
247 return result;
248 }
249
250 void SpellcheckCustomDictionary::StopSyncing(syncer::ModelType type) {
251 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
252 DCHECK_EQ(syncer::DICTIONARY, type);
253
254 sync_processor_.reset();
255 sync_error_handler_.reset();
256 }
257
258 void SpellcheckCustomDictionary::StopSyncingForTesting() {
259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
260
261 base::IgnoreResult(sync_processor_.release());
groby-ooo-7-16 2012/12/19 22:16:00 Why not sync_processor.reset()?
please use gerrit instead 2012/12/22 03:20:19 Removed StopSyncingForTesting(). Done.
262 sync_error_handler_.reset();
263 }
264
265 bool SpellcheckCustomDictionary::IsLoaded() {
266 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
267 return is_loaded_;
268 }
269
270 syncer::SyncDataList SpellcheckCustomDictionary::GetAllSyncData(
271 syncer::ModelType type) const {
272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
273 DCHECK_EQ(syncer::DICTIONARY, type);
274
275 syncer::SyncDataList data;
276 std::string word;
277 size_t i = 0;
278 for (WordList::const_iterator it = words_.begin();
279 it != words_.end() && i < MAX_SYNC_SIZE;
280 ++it, ++i) {
281 word = *it;
282 sync_pb::EntitySpecifics specifics;
283 specifics.mutable_dictionary()->set_word(word);
284 data.push_back(syncer::SyncData::CreateLocalData(word, word, specifics));
285 }
286 return data;
287 }
288
289 syncer::SyncError SpellcheckCustomDictionary::ProcessSyncChanges(
290 const tracked_objects::Location& from_here,
291 const syncer::SyncChangeList& change_list) {
292 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
293
294 WordList to_add;
295 WordList to_remove;
296 for (syncer::SyncChangeList::const_iterator it = change_list.begin();
297 it != change_list.end();
298 ++it) {
299 DCHECK(it->IsValid());
300 std::string word = it->sync_data().GetSpecifics().dictionary().word();
301 switch (it->change_type()) {
302 case syncer::SyncChange::ACTION_ADD:
303 to_add.push_back(word);
304 break;
305 case syncer::SyncChange::ACTION_DELETE:
306 to_remove.push_back(word);
307 break;
308 default:
309 NOTREACHED() << "Unexpected sync change state.";
groby-ooo-7-16 2012/12/19 22:16:00 I think we should either do a NOTREACHED _or_ the
please use gerrit instead 2012/12/22 03:20:19 Done. Handling error condition normally.
310 return sync_error_handler_->CreateAndUploadError(
311 FROM_HERE,
312 "Processing sync changes failed on change type " +
313 syncer::SyncChange::ChangeTypeToString(it->change_type()));
314 }
315 }
316
317 UpdateWords(&to_add, &to_remove, true);
318
319 return syncer::SyncError();
320 }
321
322 // static
323 WordList* SpellcheckCustomDictionary::LoadDictionary(const FilePath& path) {
202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
203 DCHECK(IsValidWord(word));
204
205 WordList custom_words;
206 LoadDictionaryFileReliably(&custom_words);
207 if (custom_words.empty())
208 return;
209
210 WordList::iterator it = std::find(custom_words.begin(),
211 custom_words.end(),
212 word);
213 if (it != custom_words.end())
214 custom_words.erase(it);
215
216 SaveDictionaryFileReliably(custom_words);
217 }
218
219 void SpellcheckCustomDictionary::AddObserver(Observer* observer) {
220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
221
222 observers_.AddObserver(observer);
223 }
224
225 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) {
226 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
227
228 observers_.RemoveObserver(observer);
229 }
230
231 WordList* SpellcheckCustomDictionary::LoadDictionary() {
232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
233 325
234 WordList* custom_words = new WordList; 326 WordList* custom_words = new WordList;
235 LoadDictionaryIntoCustomWordList(custom_words); 327 LoadDictionaryIntoCustomWordList(custom_words, path);
groby-ooo-7-16 2012/12/19 22:16:00 Can we get rid of LoadDictionary and have API cons
please use gerrit instead 2012/12/22 03:20:19 Got rid of LoadDictionaryIntoCustomWordList in fav
236 return custom_words; 328 return custom_words;
237 } 329 }
238 330
239 void SpellcheckCustomDictionary::SetCustomWordListAndDelete( 331 void SpellcheckCustomDictionary::SetCustomWordListAndDelete(
240 WordList* custom_words) { 332 WordList* custom_words) {
241 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 333 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
242 334
243 SetCustomWordList(custom_words); 335 if (sync_processor_.get()) {
336 WordList to_remove;
337 CustomWordsUpdatedLocally(custom_words, &to_remove);
338 } else {
339 SetCustomWordList(custom_words);
340 }
341
244 delete custom_words; 342 delete custom_words;
343
344 is_loaded_ = true;
345 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded());
245 } 346 }
246 347
348 // static
247 void SpellcheckCustomDictionary::LoadDictionaryFileReliably( 349 void SpellcheckCustomDictionary::LoadDictionaryFileReliably(
248 WordList* custom_words) { 350 WordList* custom_words,
351 const FilePath& path) {
249 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
250 353
251 // Load the contents and verify the checksum. 354 // Load the contents and verify the checksum.
252 if (LoadFile(custom_dictionary_path_, custom_words)) 355 if (LoadFile(path, custom_words))
253 return; 356 return;
254 357
255 // Checksum is not valid. See if there's a backup. 358 // Checksum is not valid. See if there's a backup.
256 FilePath backup = custom_dictionary_path_.AddExtension(BACKUP_EXTENSION); 359 FilePath backup = path.AddExtension(BACKUP_EXTENSION);
257 if (!file_util::PathExists(backup)) 360 if (!file_util::PathExists(backup))
258 return; 361 return;
259 362
260 // Load the backup and verify its checksum. 363 // Load the backup and verify its checksum.
261 if (!LoadFile(backup, custom_words)) 364 if (!LoadFile(backup, custom_words))
262 return; 365 return;
263 366
264 // Backup checksum is valid. Restore the backup. 367 // Backup checksum is valid. Restore the backup.
265 file_util::CopyFile(backup, custom_dictionary_path_); 368 file_util::CopyFile(backup, path);
266 } 369 }
267 370
371 // static
268 void SpellcheckCustomDictionary::SaveDictionaryFileReliably( 372 void SpellcheckCustomDictionary::SaveDictionaryFileReliably(
269 const WordList& custom_words) { 373 const WordList& custom_words,
374 const FilePath& path) {
270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 375 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
271 376
272 std::stringstream content; 377 std::stringstream content;
273 for (WordList::const_iterator it = custom_words.begin(); 378 for (WordList::const_iterator it = custom_words.begin();
274 it != custom_words.end(); 379 it != custom_words.end();
275 ++it) { 380 ++it) {
276 content << *it << '\n'; 381 content << *it << '\n';
277 } 382 }
278 std::string checksum = base::MD5String(content.str()); 383 std::string checksum = base::MD5String(content.str());
279 content << CHECKSUM_PREFIX << checksum; 384 content << CHECKSUM_PREFIX << checksum;
280 385
281 file_util::CopyFile(custom_dictionary_path_, 386 file_util::CopyFile(path,path.AddExtension(BACKUP_EXTENSION));
282 custom_dictionary_path_.AddExtension(BACKUP_EXTENSION)); 387 base::ImportantFileWriter::WriteFileAtomically(path, content.str());
283 base::ImportantFileWriter::WriteFileAtomically(custom_dictionary_path_,
284 content.str());
285 } 388 }
389
390 bool SpellcheckCustomDictionary::UpdateWords(
391 WordList* to_add,
392 WordList* to_remove) {
393 return UpdateWords(to_add, to_remove, false);
394 }
395
396 bool SpellcheckCustomDictionary::UpdateWords(
397 WordList* to_add,
398 WordList* to_remove,
399 bool is_sync_update) {
400 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
401 DCHECK(to_add);
402 DCHECK(to_remove);
403
404 bool success = CustomWordsUpdatedLocally(to_add, to_remove, is_sync_update);
405
406 WordList* to_write = new WordList(*to_add);
407 WordList* to_erase = new WordList(*to_remove);
408 BrowserThread::PostTaskAndReply(
groby-ooo-7-16 2012/12/19 22:16:00 Since the reply task does nothing but delete to_wr
please use gerrit instead 2012/12/22 03:20:19 Posting a task with base::Passed(scoped_ptr).
409 BrowserThread::FILE,
410 FROM_HERE,
411 base::Bind(
412 &SpellcheckCustomDictionary::WriteAndEraseWordsInCustomDictionary,
413 to_write,
414 to_erase,
415 custom_dictionary_path_),
416 base::Bind(
417 &SpellcheckCustomDictionary::OnWriteAndEraseFinished,
418 base::Unretained(this),
419 to_write,
420 to_erase));
421
422 return success;
423 }
424
425 bool SpellcheckCustomDictionary::CustomWordsUpdatedLocally(
426 WordList* to_add,
427 WordList* to_remove) {
428 return CustomWordsUpdatedLocally(to_add, to_remove, false);
429 }
430
431 // TODO(rlp): record metrics on custom word size
432 bool SpellcheckCustomDictionary::CustomWordsUpdatedLocally(
433 WordList* to_add,
434 WordList* to_remove,
435 bool is_sync_update) {
436 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
437 DCHECK(to_add);
438 DCHECK(to_remove);
439
440 std::string word;
441 WordList cannot_add;
groby-ooo-7-16 2012/12/19 22:16:00 Why not use std::remove_if?
please use gerrit instead 2012/12/22 03:20:19 Done.
442 for (WordList::iterator it = to_add->begin(); it != to_add->end(); ++it) {
443 word = *it;
444 if (IsInvalidWord(word) ||
445 std::find(words_.begin(), words_.end(), word) != words_.end()) {
446 cannot_add.push_back(word);
447 }
448 }
449
450 for (WordList::iterator it = cannot_add.begin(); it != cannot_add.end(); ++it)
451 to_add->erase(std::find(to_add->begin(), to_add->end(), *it));
452
453 syncer::SyncChangeList local_changes;
454 for (WordList::iterator it = to_add->begin(); it != to_add->end(); ++it) {
455 word = *it;
456 words_.push_back(word);
457 sync_pb::EntitySpecifics specifics;
458 specifics.mutable_dictionary()->set_word(word);
459 local_changes.push_back(syncer::SyncChange(
460 FROM_HERE,
461 syncer::SyncChange::ACTION_ADD,
462 syncer::SyncData::CreateLocalData(word, word, specifics)));
463 }
464
465 WordList cannot_remove;
groby-ooo-7-16 2012/12/19 22:16:00 remove_if
please use gerrit instead 2012/12/22 03:20:19 Done.
466 for (WordList::iterator it = to_remove->begin();
467 it != to_remove->end();
468 ++it) {
469 word = *it;
470 if (std::find(words_.begin(), words_.end(), word) == words_.end())
471 cannot_remove.push_back(word);
472 }
473
474 for (WordList::iterator it = cannot_remove.begin();
475 it != cannot_remove.end();
476 ++it) {
477 to_remove->erase(std::find(to_remove->begin(), to_remove->end(), *it));
478 }
479
480 for (WordList::iterator it = to_remove->begin();
481 it != to_remove->end();
482 ++it) {
483 word = *it;
484 words_.erase(std::find(words_.begin(), words_.end(), word));
485 sync_pb::EntitySpecifics specifics;
486 specifics.mutable_dictionary()->set_word(word);
487 local_changes.push_back(syncer::SyncChange(
488 FROM_HERE,
489 syncer::SyncChange::ACTION_DELETE,
490 syncer::SyncData::CreateLocalData(word, word, specifics)));
491 }
492
493 FOR_EACH_OBSERVER(Observer,
groby-ooo-7-16 2012/12/19 22:16:00 If you go with the DictionaryChange object, just o
please use gerrit instead 2012/12/22 03:20:19 Done.
494 observers_,
495 OnCustomDictionaryWordsAdded(*to_add));
496 FOR_EACH_OBSERVER(Observer,
497 observers_,
498 OnCustomDictionaryWordsRemoved(*to_remove));
499
500 bool success = cannot_add.empty() && cannot_remove.empty();
501 if (is_sync_update || !sync_processor_.get())
502 return success;
503
504 if (words_.size() <= MAX_SYNC_SIZE) {
505 sync_processor_->ProcessSyncChanges(FROM_HERE, local_changes);
506 } else {
507 sync_error_handler_->CreateAndUploadError(
508 FROM_HERE,
509 DictionaryTooBigToSyncErrorMessage(words_.size()));
510 StopSyncing(syncer::DICTIONARY);
511 }
512
513 return success;
514 }
515
516 // static
517 void SpellcheckCustomDictionary::WriteAndEraseWordsInCustomDictionary(
518 const WordList* to_write,
519 const WordList* to_erase,
520 const FilePath& path) {
521 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
522
523 WordList custom_words;
524 LoadDictionaryFileReliably(&custom_words, path);
525
526 for (WordList::const_iterator it = to_write->begin();
groby-ooo-7-16 2012/12/19 22:16:00 custom_words.insert(custom_words.end(), to_write.b
please use gerrit instead 2012/12/22 03:20:19 Done.
527 it != to_write->end();
528 ++it) {
529 custom_words.push_back(*it);
530 }
531
532 for (WordList::const_iterator it = to_erase->begin();
groby-ooo-7-16 2012/12/19 22:16:00 If custom_words is sorted, set_difference does the
please use gerrit instead 2012/12/22 03:20:19 Done.
533 it != to_erase->end();
534 ++it) {
535 custom_words.erase(std::find(custom_words.begin(),
536 custom_words.end(),
537 *it));
538 }
539
540 SaveDictionaryFileReliably(custom_words, path);
541 }
542
543 void SpellcheckCustomDictionary::OnWriteAndEraseFinished(
groby-ooo-7-16 2012/12/19 22:16:00 Can be killed - see above.
please use gerrit instead 2012/12/22 03:20:19 Done.
544 const WordList* written,
545 const WordList* erased) {
546 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
547 delete written;
548 delete erased;
549 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698