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

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: Address comments Created 7 years, 11 months 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 // Filename extension for backup dictionary file.
24 const FilePath::CharType BACKUP_EXTENSION[] = FILE_PATH_LITERAL("backup"); 29 const FilePath::CharType BACKUP_EXTENSION[] = FILE_PATH_LITERAL("backup");
30
31 // Prefix for the checksum in the dictionary file.
25 const char CHECKSUM_PREFIX[] = "checksum_v1 = "; 32 const char CHECKSUM_PREFIX[] = "checksum_v1 = ";
26 33
34 // The status of the checksum in a custom spellcheck dictionary.
35 enum ChecksumStatus {
36 VALID_CHECKSUM,
37 INVALID_CHECKSUM,
38 };
39
40 // The result of a dictionary change. Can be used as a bitmap.
41 enum ChangeResult {
42 // The change was valid and applied as-is.
43 CHANGE_SUCCEEDED = 0,
44
45 // The change contains words to be added that are not valid.
46 DETECTED_INVALID_WORDS = 1,
47
48 // The change contains words to be added that are already in the
49 // dictionary.
50 DETECTED_DUPLICATE_WORDS = 2,
51
52 // The change contains words to be removed that are not in the dictionary.
53 DETECTED_MISSING_WORDS = 4,
54 };
55
27 // Loads the lines from the file at |file_path| into the |lines| container. If 56 // 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 57 // the file has a valid checksum, then returns ChecksumStatus::VALID. If the
29 // invalid checksum, then returns |false| and clears |lines|. 58 // file has an invalid checksum, then returns ChecksumStatus::INVALID and clears
30 bool LoadFile(FilePath file_path, std::vector<std::string>* lines) { 59 // |lines|.
60 ChecksumStatus LoadFile(FilePath file_path, std::vector<std::string>* lines) {
groby-ooo-7-16 2013/01/15 01:19:20 const FilePath&
please use gerrit instead 2013/01/16 02:06:05 Done.
61 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
62 DCHECK(lines);
31 lines->clear(); 63 lines->clear();
32 std::string contents; 64 std::string contents;
33 file_util::ReadFileToString(file_path, &contents); 65 file_util::ReadFileToString(file_path, &contents);
34 size_t pos = contents.rfind(CHECKSUM_PREFIX); 66 size_t pos = contents.rfind(CHECKSUM_PREFIX);
35 if (pos != std::string::npos) { 67 if (pos != std::string::npos) {
36 std::string checksum = contents.substr(pos + strlen(CHECKSUM_PREFIX)); 68 std::string checksum = contents.substr(pos + strlen(CHECKSUM_PREFIX));
37 contents = contents.substr(0, pos); 69 contents = contents.substr(0, pos);
38 if (checksum != base::MD5String(contents)) 70 if (checksum != base::MD5String(contents))
39 return false; 71 return INVALID_CHECKSUM;
40 } 72 }
41 TrimWhitespaceASCII(contents, TRIM_ALL, &contents); 73 TrimWhitespaceASCII(contents, TRIM_ALL, &contents);
42 base::SplitString(contents, '\n', lines); 74 base::SplitString(contents, '\n', lines);
43 return true; 75 return VALID_CHECKSUM;
44 } 76 }
45 77
46 bool IsValidWord(const std::string& word) { 78 // Returns true for invalid words and false for valid words. Useful for
groby-ooo-7-16 2013/01/15 01:19:20 Kill "Useful for", save a line :)
please use gerrit instead 2013/01/16 02:06:05 Done.
47 return IsStringUTF8(word) && word.length() <= 128 && word.length() > 0 && 79 // std::remove_if() calls.
48 std::string::npos == word.find_first_of(kWhitespaceASCII);
49 }
50
51 bool IsInvalidWord(const std::string& word) { 80 bool IsInvalidWord(const std::string& word) {
52 return !IsValidWord(word); 81 return !IsStringUTF8(word) ||
82 word.length() >
83 chrome::spellcheck_common::MAX_CUSTOM_DICTIONARY_WORD_BYTES ||
84 word.empty() ||
85 word.find_first_of(kWhitespaceASCII) != std::string::npos;
86 }
87
88 // Loads the custom spellcheck dictionary from |path| into |custom_words|. If
89 // the dictionary checksum is not valid, but backup checksum is valid, then
90 // restores the backup and loads that into |custom_words| instead. If the backup
91 // is invalid too, then clears |custom_words|. Does not take ownership of
92 // |custom_words|. Must be called on the file thread.
93 void LoadDictionaryFileReliably(WordList* custom_words, const FilePath& path) {
94 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
95 DCHECK(custom_words);
96 // Load the contents and verify the checksum.
97 if (LoadFile(path, custom_words) == VALID_CHECKSUM)
98 return;
99 // Checksum is not valid. See if there's a backup.
100 FilePath backup = path.AddExtension(BACKUP_EXTENSION);
101 if (!file_util::PathExists(backup))
102 return;
103 // Load the backup and verify its checksum.
104 if (LoadFile(backup, custom_words) != VALID_CHECKSUM)
105 return;
106 // Backup checksum is valid. Restore the backup.
107 file_util::CopyFile(backup, path);
108 }
109
110 // Backs up the original dictionary, saves |custom_words| and its checksum into
111 // the custom spellcheck dictionary at |path|. Does not take ownership of
112 // |custom_words|.
113 void SaveDictionaryFileReliably(
114 const WordList* custom_words,
115 const FilePath& path) {
116 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
117 DCHECK(custom_words);
118 std::stringstream content;
119 for (WordList::const_iterator it = custom_words->begin();
120 it != custom_words->end();
121 ++it) {
122 content << *it << '\n';
123 }
124 std::string checksum = base::MD5String(content.str());
125 content << CHECKSUM_PREFIX << checksum;
126 file_util::CopyFile(path, path.AddExtension(BACKUP_EXTENSION));
127 base::ImportantFileWriter::WriteFileAtomically(path, content.str());
128 }
129
130 // Sanitizes words |to_add| by removing duplicate and invalid words. Assumes
131 // that |existing_sorted| is sorted.
132 int SanitizeAddData(const WordList& existing_sorted, WordList& to_add) {
133 // Do not add duplicate words.
134 std::sort(to_add.begin(), to_add.end());
135 WordList new_words;
136 std::set_difference(to_add.begin(),
137 to_add.end(),
138 existing_sorted.begin(),
139 existing_sorted.end(),
140 std::back_inserter(new_words));
141 new_words.erase(std::unique(new_words.begin(), new_words.end()),
142 new_words.end());
143 int result = CHANGE_SUCCEEDED;
144 if (to_add.size() != new_words.size())
145 result |= DETECTED_DUPLICATE_WORDS;
146 // Do not add invalid words.
147 size_t size = new_words.size();
148 new_words.erase(std::remove_if(new_words.begin(),
149 new_words.end(),
150 IsInvalidWord),
151 new_words.end());
152 if (size != new_words.size())
153 result |= DETECTED_INVALID_WORDS;
154 // Save the sanitized words.
155 std::swap(to_add, new_words);
156 return result;
53 } 157 }
54 158
55 } // namespace 159 } // namespace
56 160
161
162 SpellcheckCustomDictionary::Change::Change() {
163 }
164
165 SpellcheckCustomDictionary::Change::~Change() {
166 }
167
168 void SpellcheckCustomDictionary::Change::AddWord(const std::string& word) {
169 to_add_.push_back(word);
170 }
171
172 void SpellcheckCustomDictionary::Change::AddWords(const WordList& words) {
173 to_add_.insert(to_add_.end(), words.begin(), words.end());
174 }
175
176 void SpellcheckCustomDictionary::Change::RemoveWord(const std::string& word) {
177 to_remove_.push_back(word);
178 }
179
180 void SpellcheckCustomDictionary::Change::RemoveWords(const WordList& words) {
181 to_remove_.insert(to_remove_.end(), words.begin(), words.end());
182 }
183
184 const WordList& SpellcheckCustomDictionary::Change::to_add() const {
185 return to_add_;
186 }
187
188 WordList& SpellcheckCustomDictionary::Change::to_add() {
189 return to_add_;
190 }
191
192 const WordList& SpellcheckCustomDictionary::Change::to_remove() const {
193 return to_remove_;
194 }
195
196 WordList& SpellcheckCustomDictionary::Change::to_remove() {
197 return to_remove_;
198 }
199
200 bool SpellcheckCustomDictionary::Change::empty() const {
201 return to_add_.empty() && to_remove_.empty();
202 }
203
57 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) 204 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile)
58 : SpellcheckDictionary(profile), 205 : SpellcheckDictionary(profile),
59 custom_dictionary_path_(), 206 custom_dictionary_path_(),
60 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { 207 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
208 is_loaded_(false) {
61 DCHECK(profile); 209 DCHECK(profile);
62 custom_dictionary_path_ = 210 custom_dictionary_path_ =
63 profile_->GetPath().Append(chrome::kCustomDictionaryFileName); 211 profile_->GetPath().Append(chrome::kCustomDictionaryFileName);
64 } 212 }
65 213
66 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() { 214 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() {
67 } 215 }
68 216
217 const WordList& SpellcheckCustomDictionary::GetWords() const {
218 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
219 return words_;
220 }
221
222 bool SpellcheckCustomDictionary::AddWord(const std::string& word) {
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
224 scoped_ptr<Change> dictionary_change(new Change);
225 dictionary_change->AddWord(word);
226 int result = Apply(dictionary_change.get());
227 Notify(dictionary_change.get());
228 Sync(dictionary_change.get());
229 Save(dictionary_change.Pass());
230 return result == CHANGE_SUCCEEDED;
231 }
232
233 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) {
234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
235 scoped_ptr<Change> dictionary_change(new Change);
236 dictionary_change->RemoveWord(word);
237 int result = Apply(dictionary_change.get());
238 Notify(dictionary_change.get());
239 Sync(dictionary_change.get());
240 Save(dictionary_change.Pass());
241 return result == CHANGE_SUCCEEDED;
242 }
243
244 void SpellcheckCustomDictionary::AddObserver(Observer* observer) {
245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
246 observers_.AddObserver(observer);
247 }
248
249 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) {
250 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
251 observers_.RemoveObserver(observer);
252 }
253
254 bool SpellcheckCustomDictionary::IsLoaded() {
255 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
256 return is_loaded_;
257 }
258
259 bool SpellcheckCustomDictionary::IsSyncing() {
260 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
261 return !!sync_processor_.get();
262 }
263
69 void SpellcheckCustomDictionary::Load() { 264 void SpellcheckCustomDictionary::Load() {
70 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 265 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
71 266 BrowserThread::PostTaskAndReplyWithResult<scoped_ptr<WordList> >(
72 BrowserThread::PostTaskAndReplyWithResult<WordList*>(
73 BrowserThread::FILE, 267 BrowserThread::FILE,
74 FROM_HERE, 268 FROM_HERE,
75 base::Bind(&SpellcheckCustomDictionary::LoadDictionary, 269 base::Bind(&SpellcheckCustomDictionary::LoadDictionaryFile,
76 base::Unretained(this)), 270 custom_dictionary_path_),
77 base::Bind(&SpellcheckCustomDictionary::SetCustomWordListAndDelete, 271 base::Bind(&SpellcheckCustomDictionary::OnLoaded,
78 weak_ptr_factory_.GetWeakPtr())); 272 weak_ptr_factory_.GetWeakPtr()));
79 } 273 }
80 274
81 const WordList& SpellcheckCustomDictionary::GetWords() const { 275 syncer::SyncMergeResult SpellcheckCustomDictionary::MergeDataAndStartSyncing(
82 return words_; 276 syncer::ModelType type,
83 } 277 const syncer::SyncDataList& initial_sync_data,
84 278 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
85 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList( 279 scoped_ptr<syncer::SyncErrorFactory> sync_error_handler) {
86 WordList* custom_words) { 280 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
281 DCHECK(!sync_processor_.get());
282 DCHECK(!sync_error_handler_.get());
283 DCHECK(sync_processor.get());
284 DCHECK(sync_error_handler.get());
285 DCHECK_EQ(syncer::DICTIONARY, type);
286 sync_processor_ = sync_processor.Pass();
287 sync_error_handler_ = sync_error_handler.Pass();
288
289 // Add remote words locally.
290 WordList sync_words;
291 std::string word;
292 for (syncer::SyncDataList::const_iterator it = initial_sync_data.begin();
293 it != initial_sync_data.end();
294 ++it) {
295 DCHECK_EQ(syncer::DICTIONARY, it->GetDataType());
296 sync_words.push_back(it->GetSpecifics().dictionary().word());
297 }
298 scoped_ptr<Change> to_change_locally(new Change);
299 to_change_locally->AddWords(sync_words);
300 Apply(to_change_locally.get());
301 Notify(to_change_locally.get());
302 Save(to_change_locally.Pass());
303
304 // Add as many as possible local words remotely.
305 std::sort(words_.begin(), words_.end());
306 std::sort(sync_words.begin(), sync_words.end());
307 Change to_change_remotely;
308 std::set_difference(words_.begin(),
309 words_.end(),
310 sync_words.begin(),
311 sync_words.end(),
312 std::back_inserter(to_change_remotely.to_add()));
313 syncer::SyncMergeResult result(type);
314 result.set_error(Sync(&to_change_remotely));
315 return result;
316 }
317
318 void SpellcheckCustomDictionary::StopSyncing(syncer::ModelType type) {
319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
320 DCHECK_EQ(syncer::DICTIONARY, type);
321 sync_processor_.reset();
322 sync_error_handler_.reset();
323 }
324
325 syncer::SyncDataList SpellcheckCustomDictionary::GetAllSyncData(
326 syncer::ModelType type) const {
327 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
328 DCHECK_EQ(syncer::DICTIONARY, type);
329 syncer::SyncDataList data;
330 std::string word;
331 size_t i = 0;
332 for (WordList::const_iterator it = words_.begin();
333 it != words_.end() &&
334 i < chrome::spellcheck_common::MAX_SYNCABLE_DICTIONARY_WORDS;
335 ++it, ++i) {
336 word = *it;
337 sync_pb::EntitySpecifics specifics;
338 specifics.mutable_dictionary()->set_word(word);
339 data.push_back(syncer::SyncData::CreateLocalData(word, word, specifics));
340 }
341 return data;
342 }
343
344 syncer::SyncError SpellcheckCustomDictionary::ProcessSyncChanges(
345 const tracked_objects::Location& from_here,
346 const syncer::SyncChangeList& change_list) {
347 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
348 scoped_ptr<Change> dictionary_change(new Change);
349 for (syncer::SyncChangeList::const_iterator it = change_list.begin();
350 it != change_list.end();
351 ++it) {
352 DCHECK(it->IsValid());
353 std::string word = it->sync_data().GetSpecifics().dictionary().word();
354 switch (it->change_type()) {
355 case syncer::SyncChange::ACTION_ADD:
356 dictionary_change->AddWord(word);
357 break;
358 case syncer::SyncChange::ACTION_DELETE:
359 dictionary_change->RemoveWord(word);
360 break;
361 default:
362 return sync_error_handler_->CreateAndUploadError(
363 FROM_HERE,
364 "Processing sync changes failed on change type " +
365 syncer::SyncChange::ChangeTypeToString(it->change_type()));
366 }
367 }
368
369 Apply(dictionary_change.get());
370 Notify(dictionary_change.get());
371 Save(dictionary_change.Pass());
372
373 return syncer::SyncError();
374 }
375
376 // static
377 scoped_ptr<WordList> SpellcheckCustomDictionary::LoadDictionaryFile(
378 const FilePath& path) {
87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 379 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
88 380 scoped_ptr<WordList> custom_words(new WordList);
groby-ooo-7-16 2013/01/15 01:19:20 Since we always return custom_words, no need for s
please use gerrit instead 2013/01/16 02:06:05 How about a "WordList SpellcheckCustomDictionary::
89 LoadDictionaryFileReliably(custom_words); 381 LoadDictionaryFileReliably(custom_words.get(), path);
90 if (custom_words->empty()) 382 if (custom_words->empty())
groby-ooo-7-16 2013/01/15 01:19:20 I'd pick if(!custom_words.empty()) { Sanitize(); S
please use gerrit instead 2013/01/16 02:06:05 Done.
383 return custom_words.Pass();
384 SanitizeAddData(WordList(), *custom_words.get());
385 SaveDictionaryFileReliably(custom_words.get(), path);
386 return custom_words.Pass();
387 }
388
389 // static
390 void SpellcheckCustomDictionary::UpdateDictionaryFile(
391 scoped_ptr<SpellcheckCustomDictionary::Change> dictionary_change,
392 const FilePath& path) {
393 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
394 DCHECK(dictionary_change.get());
395 WordList custom_words;
396 LoadDictionaryFileReliably(&custom_words, path);
397
398 // Add words.
399 custom_words.insert(custom_words.end(),
400 dictionary_change->to_add().begin(),
401 dictionary_change->to_add().end());
402
403 // Remove words.
404 std::sort(custom_words.begin(), custom_words.end());
405 std::sort(dictionary_change->to_remove().begin(),
406 dictionary_change->to_remove().end());
407 WordList remaining;
408 std::set_difference(custom_words.begin(),
409 custom_words.end(),
410 dictionary_change->to_remove().begin(),
411 dictionary_change->to_remove().end(),
412 std::back_inserter(remaining));
413 std::swap(custom_words, remaining);
414
415 SaveDictionaryFileReliably(&custom_words, path);
416 }
417
418 void SpellcheckCustomDictionary::OnLoaded(scoped_ptr<WordList> custom_words) {
419 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
420 DCHECK(custom_words.get());
421 scoped_ptr<Change> dictionary_change(new Change);
422 dictionary_change->AddWords(*custom_words.get());
423 Apply(dictionary_change.get());
424 Sync(dictionary_change.get());
425 is_loaded_ = true;
426 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded());
427 }
428
429 // TODO(rlp): record metrics on custom word size
430 int SpellcheckCustomDictionary::Apply(
431 SpellcheckCustomDictionary::Change* dictionary_change) {
432 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
433 DCHECK(dictionary_change);
434 int result = CHANGE_SUCCEEDED;
435
436 if (!dictionary_change->to_add().empty()) {
437 std::sort(words_.begin(), words_.end());
438 result |= SanitizeAddData(words_, dictionary_change->to_add());
439 words_.insert(words_.end(),
440 dictionary_change->to_add().begin(),
441 dictionary_change->to_add().end());
442 }
443
444 if (!dictionary_change->to_remove().empty()) {
445 // Do not remove words that are missing from the dictionary.
446 std::sort(dictionary_change->to_remove().begin(),
447 dictionary_change->to_remove().end());
448 std::sort(words_.begin(), words_.end());
449 WordList to_remove;
450 std::set_intersection(words_.begin(),
451 words_.end(),
452 dictionary_change->to_remove().begin(),
453 dictionary_change->to_remove().end(),
454 std::back_inserter(to_remove));
455 if (dictionary_change->to_remove().size() > to_remove.size())
456 result |= DETECTED_MISSING_WORDS;
457
458 // Remove the words from the dictionary.
459 WordList updated_words;
460 std::set_difference(words_.begin(),
461 words_.end(),
462 to_remove.begin(),
463 to_remove.end(),
464 std::back_inserter(updated_words));
465 std::swap(words_, updated_words);
466
467 // Clean up the dictionary change by removing missing words.
468 std::swap(dictionary_change->to_remove(), to_remove);
469 }
470
471 return result;
472 }
473
474 void SpellcheckCustomDictionary::Save(
475 scoped_ptr<SpellcheckCustomDictionary::Change> dictionary_change) {
476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
477 DCHECK(dictionary_change.get());
478 BrowserThread::PostTask(
479 BrowserThread::FILE,
480 FROM_HERE,
481 base::Bind(&SpellcheckCustomDictionary::UpdateDictionaryFile,
482 base::Passed(dictionary_change.Pass()),
483 custom_dictionary_path_));
484 }
485
486 syncer::SyncError SpellcheckCustomDictionary::Sync(
487 const SpellcheckCustomDictionary::Change* dictionary_change) {
488 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
489 DCHECK(dictionary_change);
490 syncer::SyncError error;
491 if (!IsSyncing() || dictionary_change->empty())
492 return error;
493
494 // The number of words on the sync server should not exceed the limits.
495 int server_size = static_cast<int>(words_.size()) -
496 static_cast<int>(dictionary_change->to_add().size());
497 int max_upload_size = std::max(
498 0,
499 static_cast<int>(
500 chrome::spellcheck_common::MAX_SYNCABLE_DICTIONARY_WORDS) -
501 server_size);
502 int upload_size = std::min(
503 static_cast<int>(dictionary_change->to_add().size()),
504 max_upload_size);
505
506 syncer::SyncChangeList sync_change_list;
507 std::string word;
508 int i = 0;
509
510 for (WordList::const_iterator it = dictionary_change->to_add().begin();
511 it != dictionary_change->to_add().end() && i < upload_size;
512 ++it, ++i) {
513 word = *it;
514 sync_pb::EntitySpecifics specifics;
515 specifics.mutable_dictionary()->set_word(word);
516 sync_change_list.push_back(syncer::SyncChange(
517 FROM_HERE,
518 syncer::SyncChange::ACTION_ADD,
519 syncer::SyncData::CreateLocalData(word, word, specifics)));
520 }
521
522 for (WordList::const_iterator it = dictionary_change->to_remove().begin();
523 it != dictionary_change->to_remove().end();
524 ++it) {
525 word = *it;
526 sync_pb::EntitySpecifics specifics;
527 specifics.mutable_dictionary()->set_word(word);
528 sync_change_list.push_back(syncer::SyncChange(
529 FROM_HERE,
530 syncer::SyncChange::ACTION_DELETE,
531 syncer::SyncData::CreateLocalData(word, word, specifics)));
532 }
533
534 // Send the changes to the sync processor.
535 error = sync_processor_->ProcessSyncChanges(FROM_HERE, sync_change_list);
536 if (error.IsSet())
537 return error;
538
539 // Turn off syncing of this dictionary if the server already has the maximum
540 // number of words.
541 if (words_.size() > chrome::spellcheck_common::MAX_SYNCABLE_DICTIONARY_WORDS)
542 StopSyncing(syncer::DICTIONARY);
543
544 return error;
545 }
546
547 void SpellcheckCustomDictionary::Notify(
548 const SpellcheckCustomDictionary::Change* dictionary_change) {
549 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
550 DCHECK(dictionary_change);
551 if (!IsLoaded() || dictionary_change->empty())
91 return; 552 return;
92 553 FOR_EACH_OBSERVER(Observer,
93 // Clean up the dictionary file contents by removing duplicates and invalid 554 observers_,
94 // words. 555 OnCustomDictionaryChanged(dictionary_change));
95 std::sort(custom_words->begin(), custom_words->end()); 556 }
96 custom_words->erase(std::unique(custom_words->begin(), 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());
102
103 SaveDictionaryFileReliably(*custom_words);
104 }
105
106 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) {
107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
108
109 words_.clear();
110 if (custom_words)
111 std::swap(words_, *custom_words);
112
113 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded());
114 }
115
116 bool SpellcheckCustomDictionary::AddWord(const std::string& word) {
117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
118 if (!IsValidWord(word))
119 return false;
120
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 }
138
139 bool SpellcheckCustomDictionary::CustomWordAddedLocally(
140 const std::string& word) {
141 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
142 DCHECK(IsValidWord(word));
143
144 WordList::iterator it = std::find(words_.begin(), words_.end(), word);
145 if (it == words_.end()) {
146 words_.push_back(word);
147 return true;
148 }
149 return false;
150 // TODO(rlp): record metrics on custom word size
151 }
152
153 void SpellcheckCustomDictionary::WriteWordToCustomDictionary(
154 const std::string& word) {
155 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
156 DCHECK(IsValidWord(word));
157
158 WordList custom_words;
159 LoadDictionaryFileReliably(&custom_words);
160 custom_words.push_back(word);
161 SaveDictionaryFileReliably(custom_words);
162 }
163
164 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) {
165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
166 if (!IsValidWord(word))
167 return false;
168
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 }
186
187 bool SpellcheckCustomDictionary::CustomWordRemovedLocally(
188 const std::string& word) {
189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
190 DCHECK(IsValidWord(word));
191
192 WordList::iterator it = std::find(words_.begin(), words_.end(), word);
193 if (it != words_.end()) {
194 words_.erase(it);
195 return true;
196 }
197 return false;
198 }
199
200 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary(
201 const std::string& word) {
202 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
234 WordList* custom_words = new WordList;
235 LoadDictionaryIntoCustomWordList(custom_words);
236 return custom_words;
237 }
238
239 void SpellcheckCustomDictionary::SetCustomWordListAndDelete(
240 WordList* custom_words) {
241 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
242
243 SetCustomWordList(custom_words);
244 delete custom_words;
245 }
246
247 void SpellcheckCustomDictionary::LoadDictionaryFileReliably(
248 WordList* custom_words) {
249 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
250
251 // Load the contents and verify the checksum.
252 if (LoadFile(custom_dictionary_path_, custom_words))
253 return;
254
255 // Checksum is not valid. See if there's a backup.
256 FilePath backup = custom_dictionary_path_.AddExtension(BACKUP_EXTENSION);
257 if (!file_util::PathExists(backup))
258 return;
259
260 // Load the backup and verify its checksum.
261 if (!LoadFile(backup, custom_words))
262 return;
263
264 // Backup checksum is valid. Restore the backup.
265 file_util::CopyFile(backup, custom_dictionary_path_);
266 }
267
268 void SpellcheckCustomDictionary::SaveDictionaryFileReliably(
269 const WordList& custom_words) {
270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
271
272 std::stringstream content;
273 for (WordList::const_iterator it = custom_words.begin();
274 it != custom_words.end();
275 ++it) {
276 content << *it << '\n';
277 }
278 std::string checksum = base::MD5String(content.str());
279 content << CHECKSUM_PREFIX << checksum;
280
281 file_util::CopyFile(custom_dictionary_path_,
282 custom_dictionary_path_.AddExtension(BACKUP_EXTENSION));
283 base::ImportantFileWriter::WriteFileAtomically(custom_dictionary_path_,
284 content.str());
285 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698