| 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));
|
| }
|
|
|