Index: chrome/browser/spellchecker/spellcheck_custom_dictionary.cc |
diff --git a/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc b/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc |
index 588e3675c2fa3d8e22620f578cd497abef322e16..aa56a8887f68929a56cfba7bb17ea823da89cc68 100644 |
--- a/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc |
+++ b/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc |
@@ -9,26 +9,55 @@ |
#include "base/file_util.h" |
#include "base/files/important_file_writer.h" |
#include "base/md5.h" |
+#include "base/string_number_conversions.h" |
#include "base/string_split.h" |
#include "chrome/browser/profiles/profile.h" |
#include "chrome/common/chrome_constants.h" |
#include "chrome/common/spellcheck_messages.h" |
#include "content/public/browser/browser_thread.h" |
-#include "content/public/browser/render_process_host.h" |
+#include "sync/api/sync_change.h" |
+#include "sync/api/sync_data.h" |
+#include "sync/api/sync_error_factory.h" |
+#include "sync/protocol/sync.pb.h" |
using content::BrowserThread; |
using chrome::spellcheck_common::WordList; |
namespace { |
+// Filename extension for backup dictionary file. |
const FilePath::CharType BACKUP_EXTENSION[] = FILE_PATH_LITERAL("backup"); |
+ |
+// Prefix for the checksum in the dictionary file. |
const char CHECKSUM_PREFIX[] = "checksum_v1 = "; |
-// Loads the lines from the file at |file_path| into the |lines| container. If |
-// the file has a valid checksum, then returns |true|. If the file has an |
-// invalid checksum, then returns |false| and clears |lines|. |
-bool LoadFile(FilePath file_path, std::vector<std::string>* lines) { |
- lines->clear(); |
+// The status of the checksum in a custom spellcheck dictionary. |
+enum ChecksumStatus { |
+ VALID_CHECKSUM, |
+ INVALID_CHECKSUM, |
+}; |
+ |
+// The result of a dictionary sanitation. Can be used as a bitmap. |
+enum ChangeSanitationResult { |
+ // The change is valid and can be applied as-is. |
+ VALID_CHANGE = 0, |
+ |
+ // The change contained words to be added that are not valid. |
+ DETECTED_INVALID_WORDS = 1, |
+ |
+ // The change contained words to be added that are already in the dictionary. |
+ DETECTED_DUPLICATE_WORDS = 2, |
+ |
+ // The change contained words to be removed that are not in the dictionary. |
+ DETECTED_MISSING_WORDS = 4, |
+}; |
+ |
+// Loads the file at |file_path| into the |words| container. If the file has a |
+// valid checksum, then returns ChecksumStatus::VALID. If the file has an |
+// invalid checksum, then returns ChecksumStatus::INVALID and clears |words|. |
+ChecksumStatus LoadFile(const FilePath& file_path, WordList& words) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ words.clear(); |
std::string contents; |
file_util::ReadFileToString(file_path, &contents); |
size_t pos = contents.rfind(CHECKSUM_PREFIX); |
@@ -36,250 +65,488 @@ bool LoadFile(FilePath file_path, std::vector<std::string>* lines) { |
std::string checksum = contents.substr(pos + strlen(CHECKSUM_PREFIX)); |
contents = contents.substr(0, pos); |
if (checksum != base::MD5String(contents)) |
- return false; |
+ return INVALID_CHECKSUM; |
} |
TrimWhitespaceASCII(contents, TRIM_ALL, &contents); |
- base::SplitString(contents, '\n', lines); |
- return true; |
+ base::SplitString(contents, '\n', &words); |
+ return VALID_CHECKSUM; |
} |
-bool IsValidWord(const std::string& word) { |
- return IsStringUTF8(word) && word.length() <= 128 && word.length() > 0 && |
- std::string::npos == word.find_first_of(kWhitespaceASCII); |
+// Returns true for invalid words and false for valid words. |
+bool IsInvalidWord(const std::string& word) { |
+ return !IsStringUTF8(word) || |
+ word.length() > |
+ chrome::spellcheck_common::MAX_CUSTOM_DICTIONARY_WORD_BYTES || |
+ word.empty() || |
+ word.find_first_of(kWhitespaceASCII) != std::string::npos; |
} |
-bool IsInvalidWord(const std::string& word) { |
- return !IsValidWord(word); |
+// Loads the custom spellcheck dictionary from |path| into |custom_words|. If |
+// the dictionary checksum is not valid, but backup checksum is valid, then |
+// restores the backup and loads that into |custom_words| instead. If the backup |
+// is invalid too, then clears |custom_words|. Must be called on the file |
+// thread. |
+void LoadDictionaryFileReliably(WordList& custom_words, const FilePath& path) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ // Load the contents and verify the checksum. |
+ if (LoadFile(path, custom_words) == VALID_CHECKSUM) |
+ return; |
+ // Checksum is not valid. See if there's a backup. |
+ FilePath backup = path.AddExtension(BACKUP_EXTENSION); |
+ if (!file_util::PathExists(backup)) |
+ return; |
+ // Load the backup and verify its checksum. |
+ if (LoadFile(backup, custom_words) != VALID_CHECKSUM) |
+ return; |
+ // Backup checksum is valid. Restore the backup. |
+ file_util::CopyFile(backup, path); |
} |
-} // namespace |
+// Backs up the original dictionary, saves |custom_words| and its checksum into |
+// the custom spellcheck dictionary at |path|. |
+void SaveDictionaryFileReliably( |
+ const WordList& custom_words, |
+ const FilePath& path) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ std::stringstream content; |
+ for (WordList::const_iterator it = custom_words.begin(); |
+ it != custom_words.end(); |
+ ++it) { |
+ content << *it << '\n'; |
+ } |
+ std::string checksum = base::MD5String(content.str()); |
+ content << CHECKSUM_PREFIX << checksum; |
+ file_util::CopyFile(path, path.AddExtension(BACKUP_EXTENSION)); |
+ base::ImportantFileWriter::WriteFileAtomically(path, content.str()); |
+} |
-SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) |
- : SpellcheckDictionary(profile), |
- custom_dictionary_path_(), |
- weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { |
- DCHECK(profile); |
- custom_dictionary_path_ = |
- profile_->GetPath().Append(chrome::kCustomDictionaryFileName); |
+// Removes duplicate and invalid words from |to_add| word list and sorts it. |
+// Looks for duplicates in both |to_add| and |existing| word lists. Returns a |
+// bitmap of |ChangeSanitationResult| values. |
+int SanitizeWordsToAdd(const WordList& existing, WordList& to_add) { |
+ // Do not add duplicate words. |
+ std::sort(to_add.begin(), to_add.end()); |
+ WordList new_words; |
+ std::set_difference(to_add.begin(), |
+ to_add.end(), |
+ existing.begin(), |
+ existing.end(), |
+ std::back_inserter(new_words)); |
+ new_words.erase(std::unique(new_words.begin(), new_words.end()), |
+ new_words.end()); |
+ int result = VALID_CHANGE; |
+ if (to_add.size() != new_words.size()) |
+ result |= DETECTED_DUPLICATE_WORDS; |
+ // Do not add invalid words. |
+ size_t size = new_words.size(); |
+ new_words.erase(std::remove_if(new_words.begin(), |
+ new_words.end(), |
+ IsInvalidWord), |
+ new_words.end()); |
+ if (size != new_words.size()) |
+ result |= DETECTED_INVALID_WORDS; |
+ // Save the sanitized words to be added. |
+ std::swap(to_add, new_words); |
+ return result; |
} |
-SpellcheckCustomDictionary::~SpellcheckCustomDictionary() { |
+// Removes word from |to_remove| that are missing from |existing| word list and |
+// sorts |to_remove|. Returns a bitmap of |ChangeSanitationResult| values. |
+int SanitizeWordsToRemove(const WordList& existing, WordList& to_remove) { |
+ // Do not remove words that are missing from the dictionary. |
+ std::sort(to_remove.begin(), to_remove.end()); |
+ WordList found_words; |
+ std::set_intersection(existing.begin(), |
+ existing.end(), |
+ to_remove.begin(), |
+ to_remove.end(), |
+ std::back_inserter(found_words)); |
+ int result = VALID_CHANGE; |
+ if (to_remove.size() > found_words.size()) |
+ result |= DETECTED_MISSING_WORDS; |
+ // Save the sanitized words to be removed. |
+ std::swap(to_remove, found_words); |
+ return result; |
} |
-void SpellcheckCustomDictionary::Load() { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+} // namespace |
- BrowserThread::PostTaskAndReplyWithResult<WordList*>( |
- BrowserThread::FILE, |
- FROM_HERE, |
- base::Bind(&SpellcheckCustomDictionary::LoadDictionary, |
- base::Unretained(this)), |
- base::Bind(&SpellcheckCustomDictionary::SetCustomWordListAndDelete, |
- weak_ptr_factory_.GetWeakPtr())); |
-} |
-const WordList& SpellcheckCustomDictionary::GetWords() const { |
- return words_; |
+SpellcheckCustomDictionary::Change::Change() { |
} |
-void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList( |
- WordList* custom_words) { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
- |
- LoadDictionaryFileReliably(custom_words); |
- if (custom_words->empty()) |
- return; |
+SpellcheckCustomDictionary::Change::Change( |
+ const SpellcheckCustomDictionary::Change& other) |
+ : to_add_(other.to_add()), |
+ to_remove_(other.to_remove()) { |
+} |
- // Clean up the dictionary file contents by removing duplicates and invalid |
- // words. |
- std::sort(custom_words->begin(), custom_words->end()); |
- custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), |
- custom_words->end()); |
- custom_words->erase(std::remove_if(custom_words->begin(), |
- custom_words->end(), |
- IsInvalidWord), |
- custom_words->end()); |
+SpellcheckCustomDictionary::Change::Change(const WordList& to_add) |
+ : to_add_(to_add) { |
+} |
- SaveDictionaryFileReliably(*custom_words); |
+SpellcheckCustomDictionary::Change::~Change() { |
} |
-void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+void SpellcheckCustomDictionary::Change::AddWord(const std::string& word) { |
+ to_add_.push_back(word); |
+} |
- words_.clear(); |
- if (custom_words) |
- std::swap(words_, *custom_words); |
+void SpellcheckCustomDictionary::Change::RemoveWord(const std::string& word) { |
+ to_remove_.push_back(word); |
+} |
- FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded()); |
+int SpellcheckCustomDictionary::Change::Sanitize(const WordList& words) { |
+ int result = VALID_CHANGE; |
+ if (!to_add_.empty()) |
+ result |= SanitizeWordsToAdd(words, to_add_); |
+ if (!to_remove_.empty()) |
+ result |= SanitizeWordsToRemove(words, to_remove_); |
+ return result; |
} |
-bool SpellcheckCustomDictionary::AddWord(const std::string& word) { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- if (!IsValidWord(word)) |
- return false; |
+const WordList& SpellcheckCustomDictionary::Change::to_add() const { |
+ return to_add_; |
+} |
- if (!CustomWordAddedLocally(word)) |
- return false; |
+const WordList& SpellcheckCustomDictionary::Change::to_remove() const { |
+ return to_remove_; |
+} |
- BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
- base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary, |
- base::Unretained(this), word)); |
+bool SpellcheckCustomDictionary::Change::empty() const { |
+ return to_add_.empty() && to_remove_.empty(); |
+} |
- for (content::RenderProcessHost::iterator i( |
- content::RenderProcessHost::AllHostsIterator()); |
- !i.IsAtEnd(); i.Advance()) { |
- i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word)); |
- } |
+SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) |
+ : SpellcheckDictionary(profile), |
+ custom_dictionary_path_(), |
+ weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
+ is_loaded_(false) { |
+ DCHECK(profile); |
+ custom_dictionary_path_ = |
+ profile_->GetPath().Append(chrome::kCustomDictionaryFileName); |
+} |
- FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordAdded(word)); |
+SpellcheckCustomDictionary::~SpellcheckCustomDictionary() { |
+} |
- return true; |
+const WordList& SpellcheckCustomDictionary::GetWords() const { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ return words_; |
} |
-bool SpellcheckCustomDictionary::CustomWordAddedLocally( |
- const std::string& word) { |
+bool SpellcheckCustomDictionary::AddWord(const std::string& word) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- DCHECK(IsValidWord(word)); |
+ std::sort(words_.begin(), words_.end()); |
+ Change dictionary_change; |
+ dictionary_change.AddWord(word); |
+ int result = dictionary_change.Sanitize(GetWords()); |
+ Apply(dictionary_change); |
+ Notify(dictionary_change); |
+ Sync(dictionary_change); |
+ Save(dictionary_change); |
+ return result == VALID_CHANGE; |
+} |
- WordList::iterator it = std::find(words_.begin(), words_.end(), word); |
- if (it == words_.end()) { |
- words_.push_back(word); |
- return true; |
- } |
- return false; |
- // TODO(rlp): record metrics on custom word size |
+bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ std::sort(words_.begin(), words_.end()); |
+ Change dictionary_change; |
+ dictionary_change.RemoveWord(word); |
+ int result = dictionary_change.Sanitize(GetWords()); |
+ Apply(dictionary_change); |
+ Notify(dictionary_change); |
+ Sync(dictionary_change); |
+ Save(dictionary_change); |
+ return result == VALID_CHANGE; |
} |
-void SpellcheckCustomDictionary::WriteWordToCustomDictionary( |
- const std::string& word) { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
- DCHECK(IsValidWord(word)); |
+void SpellcheckCustomDictionary::AddObserver(Observer* observer) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ observers_.AddObserver(observer); |
+} |
- WordList custom_words; |
- LoadDictionaryFileReliably(&custom_words); |
- custom_words.push_back(word); |
- SaveDictionaryFileReliably(custom_words); |
+void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ observers_.RemoveObserver(observer); |
} |
-bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { |
+bool SpellcheckCustomDictionary::IsLoaded() { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- if (!IsValidWord(word)) |
- return false; |
+ return is_loaded_; |
+} |
- if (!CustomWordRemovedLocally(word)) |
- return false; |
+bool SpellcheckCustomDictionary::IsSyncing() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ return !!sync_processor_.get(); |
+} |
- BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
- base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary, |
- base::Unretained(this), word)); |
+void SpellcheckCustomDictionary::Load() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ BrowserThread::PostTaskAndReplyWithResult( |
+ BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&SpellcheckCustomDictionary::LoadDictionaryFile, |
+ custom_dictionary_path_), |
+ base::Bind(&SpellcheckCustomDictionary::OnLoaded, |
+ weak_ptr_factory_.GetWeakPtr())); |
+} |
- for (content::RenderProcessHost::iterator i( |
- content::RenderProcessHost::AllHostsIterator()); |
- !i.IsAtEnd(); i.Advance()) { |
- i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word)); |
+syncer::SyncMergeResult SpellcheckCustomDictionary::MergeDataAndStartSyncing( |
+ syncer::ModelType type, |
+ const syncer::SyncDataList& initial_sync_data, |
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor, |
+ scoped_ptr<syncer::SyncErrorFactory> sync_error_handler) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ DCHECK(!sync_processor_.get()); |
+ DCHECK(!sync_error_handler_.get()); |
+ DCHECK(sync_processor.get()); |
+ DCHECK(sync_error_handler.get()); |
+ DCHECK_EQ(syncer::DICTIONARY, type); |
+ sync_processor_ = sync_processor.Pass(); |
+ sync_error_handler_ = sync_error_handler.Pass(); |
+ |
+ // Build a list of words to add locally. |
+ WordList to_add_locally; |
+ for (syncer::SyncDataList::const_iterator it = initial_sync_data.begin(); |
+ it != initial_sync_data.end(); |
+ ++it) { |
+ DCHECK_EQ(syncer::DICTIONARY, it->GetDataType()); |
+ to_add_locally.push_back(it->GetSpecifics().dictionary().word()); |
} |
- FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordRemoved(word)); |
- |
- return true; |
+ // Add remote words locally. |
+ std::sort(words_.begin(), words_.end()); |
+ Change to_change_locally(to_add_locally); |
+ to_change_locally.Sanitize(GetWords()); |
+ Apply(to_change_locally); |
+ Notify(to_change_locally); |
+ Save(to_change_locally); |
+ |
+ // Add as many as possible local words remotely. |
+ std::sort(words_.begin(), words_.end()); |
+ std::sort(to_add_locally.begin(), to_add_locally.end()); |
+ WordList to_add_remotely; |
+ std::set_difference(words_.begin(), |
+ words_.end(), |
+ to_add_locally.begin(), |
+ to_add_locally.end(), |
+ std::back_inserter(to_add_remotely)); |
+ |
+ // Send local changes to the sync server. |
+ Change to_change_remotely(to_add_remotely); |
+ syncer::SyncMergeResult result(type); |
+ result.set_error(Sync(to_change_remotely)); |
+ return result; |
} |
-bool SpellcheckCustomDictionary::CustomWordRemovedLocally( |
- const std::string& word) { |
+void SpellcheckCustomDictionary::StopSyncing(syncer::ModelType type) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- DCHECK(IsValidWord(word)); |
+ DCHECK_EQ(syncer::DICTIONARY, type); |
+ sync_processor_.reset(); |
+ sync_error_handler_.reset(); |
+} |
- WordList::iterator it = std::find(words_.begin(), words_.end(), word); |
- if (it != words_.end()) { |
- words_.erase(it); |
- return true; |
+syncer::SyncDataList SpellcheckCustomDictionary::GetAllSyncData( |
+ syncer::ModelType type) const { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ DCHECK_EQ(syncer::DICTIONARY, type); |
+ syncer::SyncDataList data; |
+ std::string word; |
+ size_t i = 0; |
+ for (WordList::const_iterator it = words_.begin(); |
+ it != words_.end() && |
+ i < chrome::spellcheck_common::MAX_SYNCABLE_DICTIONARY_WORDS; |
+ ++it, ++i) { |
+ word = *it; |
+ sync_pb::EntitySpecifics specifics; |
+ specifics.mutable_dictionary()->set_word(word); |
+ data.push_back(syncer::SyncData::CreateLocalData(word, word, specifics)); |
} |
- return false; |
+ return data; |
} |
-void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( |
- const std::string& word) { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
- DCHECK(IsValidWord(word)); |
+syncer::SyncError SpellcheckCustomDictionary::ProcessSyncChanges( |
+ const tracked_objects::Location& from_here, |
+ const syncer::SyncChangeList& change_list) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ Change dictionary_change; |
+ for (syncer::SyncChangeList::const_iterator it = change_list.begin(); |
+ it != change_list.end(); |
+ ++it) { |
+ DCHECK(it->IsValid()); |
+ std::string word = it->sync_data().GetSpecifics().dictionary().word(); |
+ switch (it->change_type()) { |
+ case syncer::SyncChange::ACTION_ADD: |
+ dictionary_change.AddWord(word); |
+ break; |
+ case syncer::SyncChange::ACTION_DELETE: |
+ dictionary_change.RemoveWord(word); |
+ break; |
+ default: |
+ return sync_error_handler_->CreateAndUploadError( |
+ FROM_HERE, |
+ "Processing sync changes failed on change type " + |
+ syncer::SyncChange::ChangeTypeToString(it->change_type())); |
+ } |
+ } |
- WordList custom_words; |
- LoadDictionaryFileReliably(&custom_words); |
- if (custom_words.empty()) |
- return; |
+ std::sort(words_.begin(), words_.end()); |
+ dictionary_change.Sanitize(GetWords()); |
+ Apply(dictionary_change); |
+ Notify(dictionary_change); |
+ Save(dictionary_change); |
- WordList::iterator it = std::find(custom_words.begin(), |
- custom_words.end(), |
- word); |
- if (it != custom_words.end()) |
- custom_words.erase(it); |
+ return syncer::SyncError(); |
+} |
- SaveDictionaryFileReliably(custom_words); |
+// static |
+WordList SpellcheckCustomDictionary::LoadDictionaryFile(const FilePath& path) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ WordList words; |
+ LoadDictionaryFileReliably(words, path); |
+ if (!words.empty() && VALID_CHANGE != SanitizeWordsToAdd(WordList(), words)) |
+ SaveDictionaryFileReliably(words, path); |
+ return words; |
} |
-void SpellcheckCustomDictionary::AddObserver(Observer* observer) { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+// static |
+void SpellcheckCustomDictionary::UpdateDictionaryFile( |
+ const SpellcheckCustomDictionary::Change& dictionary_change, |
+ const FilePath& path) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ if (dictionary_change.empty()) |
+ return; |
- observers_.AddObserver(observer); |
+ WordList custom_words; |
+ LoadDictionaryFileReliably(custom_words, path); |
+ |
+ // Add words. |
+ custom_words.insert(custom_words.end(), |
+ dictionary_change.to_add().begin(), |
+ dictionary_change.to_add().end()); |
+ |
+ // Remove words. |
+ std::sort(custom_words.begin(), custom_words.end()); |
+ WordList remaining; |
+ std::set_difference(custom_words.begin(), |
+ custom_words.end(), |
+ dictionary_change.to_remove().begin(), |
+ dictionary_change.to_remove().end(), |
+ std::back_inserter(remaining)); |
+ std::swap(custom_words, remaining); |
+ |
+ SaveDictionaryFileReliably(custom_words, path); |
} |
-void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { |
+void SpellcheckCustomDictionary::OnLoaded(WordList custom_words) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- |
- observers_.RemoveObserver(observer); |
+ std::sort(words_.begin(), words_.end()); |
+ Change dictionary_change(custom_words); |
+ dictionary_change.Sanitize(GetWords()); |
+ Apply(dictionary_change); |
+ Sync(dictionary_change); |
+ is_loaded_ = true; |
+ FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded()); |
} |
-WordList* SpellcheckCustomDictionary::LoadDictionary() { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
- |
- WordList* custom_words = new WordList; |
- LoadDictionaryIntoCustomWordList(custom_words); |
- return custom_words; |
+// TODO(rlp): record metrics on custom word size |
+void SpellcheckCustomDictionary::Apply( |
+ const SpellcheckCustomDictionary::Change& dictionary_change) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ if (!dictionary_change.to_add().empty()) { |
+ words_.insert(words_.end(), |
+ dictionary_change.to_add().begin(), |
+ dictionary_change.to_add().end()); |
+ } |
+ if (!dictionary_change.to_remove().empty()) { |
+ std::sort(words_.begin(), words_.end()); |
+ WordList updated_words; |
+ std::set_difference(words_.begin(), |
+ words_.end(), |
+ dictionary_change.to_remove().begin(), |
+ dictionary_change.to_remove().end(), |
+ std::back_inserter(updated_words)); |
+ std::swap(words_, updated_words); |
+ } |
} |
-void SpellcheckCustomDictionary::SetCustomWordListAndDelete( |
- WordList* custom_words) { |
+void SpellcheckCustomDictionary::Save( |
+ const SpellcheckCustomDictionary::Change& dictionary_change) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
- |
- SetCustomWordList(custom_words); |
- delete custom_words; |
+ BrowserThread::PostTask( |
+ BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&SpellcheckCustomDictionary::UpdateDictionaryFile, |
+ dictionary_change, |
+ custom_dictionary_path_)); |
} |
-void SpellcheckCustomDictionary::LoadDictionaryFileReliably( |
- WordList* custom_words) { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+syncer::SyncError SpellcheckCustomDictionary::Sync( |
+ const SpellcheckCustomDictionary::Change& dictionary_change) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ syncer::SyncError error; |
+ if (!IsSyncing() || dictionary_change.empty()) |
+ return error; |
+ |
+ // The number of words on the sync server should not exceed the limits. |
+ int server_size = static_cast<int>(words_.size()) - |
+ static_cast<int>(dictionary_change.to_add().size()); |
+ int max_upload_size = std::max( |
+ 0, |
+ static_cast<int>( |
+ chrome::spellcheck_common::MAX_SYNCABLE_DICTIONARY_WORDS) - |
+ server_size); |
+ int upload_size = std::min( |
+ static_cast<int>(dictionary_change.to_add().size()), |
+ max_upload_size); |
+ |
+ syncer::SyncChangeList sync_change_list; |
+ int i = 0; |
+ |
+ for (WordList::const_iterator it = dictionary_change.to_add().begin(); |
+ it != dictionary_change.to_add().end() && i < upload_size; |
+ ++it, ++i) { |
+ std::string word = *it; |
+ sync_pb::EntitySpecifics specifics; |
+ specifics.mutable_dictionary()->set_word(word); |
+ sync_change_list.push_back(syncer::SyncChange( |
+ FROM_HERE, |
+ syncer::SyncChange::ACTION_ADD, |
+ syncer::SyncData::CreateLocalData(word, word, specifics))); |
+ } |
- // Load the contents and verify the checksum. |
- if (LoadFile(custom_dictionary_path_, custom_words)) |
- return; |
+ for (WordList::const_iterator it = dictionary_change.to_remove().begin(); |
+ it != dictionary_change.to_remove().end(); |
+ ++it) { |
+ std::string word = *it; |
+ sync_pb::EntitySpecifics specifics; |
+ specifics.mutable_dictionary()->set_word(word); |
+ sync_change_list.push_back(syncer::SyncChange( |
+ FROM_HERE, |
+ syncer::SyncChange::ACTION_DELETE, |
+ syncer::SyncData::CreateLocalData(word, word, specifics))); |
+ } |
- // Checksum is not valid. See if there's a backup. |
- FilePath backup = custom_dictionary_path_.AddExtension(BACKUP_EXTENSION); |
- if (!file_util::PathExists(backup)) |
- return; |
+ // Send the changes to the sync processor. |
+ error = sync_processor_->ProcessSyncChanges(FROM_HERE, sync_change_list); |
+ if (error.IsSet()) |
+ return error; |
- // Load the backup and verify its checksum. |
- if (!LoadFile(backup, custom_words)) |
- return; |
+ // Turn off syncing of this dictionary if the server already has the maximum |
+ // number of words. |
+ if (words_.size() > chrome::spellcheck_common::MAX_SYNCABLE_DICTIONARY_WORDS) |
+ StopSyncing(syncer::DICTIONARY); |
- // Backup checksum is valid. Restore the backup. |
- file_util::CopyFile(backup, custom_dictionary_path_); |
+ return error; |
} |
-void SpellcheckCustomDictionary::SaveDictionaryFileReliably( |
- const WordList& custom_words) { |
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
- |
- std::stringstream content; |
- for (WordList::const_iterator it = custom_words.begin(); |
- it != custom_words.end(); |
- ++it) { |
- content << *it << '\n'; |
- } |
- std::string checksum = base::MD5String(content.str()); |
- content << CHECKSUM_PREFIX << checksum; |
- |
- file_util::CopyFile(custom_dictionary_path_, |
- custom_dictionary_path_.AddExtension(BACKUP_EXTENSION)); |
- base::ImportantFileWriter::WriteFileAtomically(custom_dictionary_path_, |
- content.str()); |
+void SpellcheckCustomDictionary::Notify( |
+ const SpellcheckCustomDictionary::Change& dictionary_change) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ if (!IsLoaded() || dictionary_change.empty()) |
+ return; |
+ FOR_EACH_OBSERVER(Observer, |
+ observers_, |
+ OnCustomDictionaryChanged(dictionary_change)); |
} |