Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h" | 5 #include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h" |
| 6 | 6 |
| 7 #include <functional> | 7 #include <functional> |
| 8 | 8 |
| 9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
| 10 #include "base/files/important_file_writer.h" | |
| 11 #include "base/md5.h" | |
| 10 #include "base/string_split.h" | 12 #include "base/string_split.h" |
| 11 #include "chrome/browser/profiles/profile.h" | 13 #include "chrome/browser/profiles/profile.h" |
| 12 #include "chrome/common/chrome_constants.h" | 14 #include "chrome/common/chrome_constants.h" |
| 13 #include "chrome/common/spellcheck_messages.h" | 15 #include "chrome/common/spellcheck_messages.h" |
| 14 #include "content/public/browser/browser_thread.h" | 16 #include "content/public/browser/browser_thread.h" |
| 15 #include "content/public/browser/render_process_host.h" | 17 #include "content/public/browser/render_process_host.h" |
| 16 | 18 |
| 17 using content::BrowserThread; | 19 using content::BrowserThread; |
| 18 using chrome::spellcheck_common::WordList; | 20 using chrome::spellcheck_common::WordList; |
| 19 | 21 |
| 20 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) | 22 SpellcheckCustomDictionary::SpellcheckCustomDictionary(Profile* profile) |
| 21 : SpellcheckDictionary(profile), | 23 : SpellcheckDictionary(profile), |
| 22 custom_dictionary_path_(), | 24 custom_dictionary_path_(), |
| 23 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { | 25 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| 26 backup_extension_(FILE_PATH_LITERAL("backup")), | |
| 27 checksum_prefix_("checksum_v1 = ") { | |
| 24 DCHECK(profile); | 28 DCHECK(profile); |
| 25 custom_dictionary_path_ = | 29 custom_dictionary_path_ = |
| 26 profile_->GetPath().Append(chrome::kCustomDictionaryFileName); | 30 profile_->GetPath().Append(chrome::kCustomDictionaryFileName); |
| 27 } | 31 } |
| 28 | 32 |
| 29 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() { | 33 SpellcheckCustomDictionary::~SpellcheckCustomDictionary() { |
| 30 } | 34 } |
| 31 | 35 |
| 32 void SpellcheckCustomDictionary::Load() { | 36 void SpellcheckCustomDictionary::Load() { |
| 33 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 37 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 34 | 38 |
| 35 BrowserThread::PostTaskAndReplyWithResult<WordList*>( | 39 BrowserThread::PostTaskAndReplyWithResult<WordList*>( |
| 36 BrowserThread::FILE, | 40 BrowserThread::FILE, |
| 37 FROM_HERE, | 41 FROM_HERE, |
| 38 base::Bind(&SpellcheckCustomDictionary::LoadDictionary, | 42 base::Bind(&SpellcheckCustomDictionary::LoadDictionary, |
| 39 base::Unretained(this)), | 43 base::Unretained(this)), |
| 40 base::Bind(&SpellcheckCustomDictionary::SetCustomWordListAndDelete, | 44 base::Bind(&SpellcheckCustomDictionary::SetCustomWordListAndDelete, |
| 41 weak_ptr_factory_.GetWeakPtr())); | 45 weak_ptr_factory_.GetWeakPtr())); |
| 42 } | 46 } |
| 43 | 47 |
| 44 const WordList& SpellcheckCustomDictionary::GetWords() const { | 48 const WordList& SpellcheckCustomDictionary::GetWords() const { |
| 45 return words_; | 49 return words_; |
| 46 } | 50 } |
| 47 | 51 |
| 48 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList( | 52 void SpellcheckCustomDictionary::LoadDictionaryIntoCustomWordList( |
| 49 WordList* custom_words) { | 53 WordList* custom_words) { |
| 50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 54 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 51 | 55 |
| 52 std::string contents; | 56 LoadDictionaryFileReliably(custom_words); |
| 53 file_util::ReadFileToString(custom_dictionary_path_, &contents); | 57 if (custom_words->empty()) |
| 54 if (contents.empty()) { | |
| 55 custom_words->clear(); | |
| 56 return; | 58 return; |
| 57 } | |
| 58 | 59 |
| 59 base::SplitString(contents, '\n', custom_words); | 60 // Clean up the dictionary file contents by removing duplicates and empty |
| 60 | 61 // words. |
| 61 // Erase duplicates. | |
| 62 std::sort(custom_words->begin(), custom_words->end()); | 62 std::sort(custom_words->begin(), custom_words->end()); |
| 63 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), | 63 custom_words->erase(std::unique(custom_words->begin(), custom_words->end()), |
| 64 custom_words->end()); | 64 custom_words->end()); |
| 65 if (custom_words->at(0).length() == 0) | |
|
groby-ooo-7-16
2012/12/05 00:56:19
at(0).empty()
please use gerrit instead
2012/12/05 16:51:26
Done.
| |
| 66 custom_words->erase(custom_words->begin()); | |
| 65 | 67 |
| 66 // Clear out empty words. | 68 SaveDictionaryFileReliably(*custom_words); |
| 67 custom_words->erase(remove_if(custom_words->begin(), custom_words->end(), | |
| 68 mem_fun_ref(&std::string::empty)), custom_words->end()); | |
| 69 | |
| 70 // Write out the clean file. | |
| 71 std::stringstream ss; | |
| 72 for (WordList::iterator it = custom_words->begin(); | |
| 73 it != custom_words->end(); | |
| 74 ++it) { | |
| 75 ss << *it << '\n'; | |
| 76 } | |
| 77 contents = ss.str(); | |
| 78 file_util::WriteFile(custom_dictionary_path_, | |
| 79 contents.c_str(), | |
| 80 contents.length()); | |
| 81 } | 69 } |
| 82 | 70 |
| 83 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { | 71 void SpellcheckCustomDictionary::SetCustomWordList(WordList* custom_words) { |
| 84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 72 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 85 | 73 |
| 86 words_.clear(); | 74 words_.clear(); |
| 87 if (custom_words) | 75 if (custom_words) |
| 88 std::swap(words_, *custom_words); | 76 std::swap(words_, *custom_words); |
| 89 | 77 |
| 90 std::vector<Observer*>::iterator it; | 78 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryLoaded()); |
| 91 for (it = observers_.begin(); it != observers_.end(); ++it) | |
| 92 (*it)->OnCustomDictionaryLoaded(); | |
| 93 } | 79 } |
| 94 | 80 |
| 95 bool SpellcheckCustomDictionary::AddWord(const std::string& word) { | 81 bool SpellcheckCustomDictionary::AddWord(const std::string& word) { |
| 96 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 82 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 97 | 83 |
| 98 if (!CustomWordAddedLocally(word)) | 84 if (!CustomWordAddedLocally(word)) |
| 99 return false; | 85 return false; |
| 100 | 86 |
| 101 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 87 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| 102 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary, | 88 base::Bind(&SpellcheckCustomDictionary::WriteWordToCustomDictionary, |
| 103 base::Unretained(this), word)); | 89 base::Unretained(this), word)); |
| 104 | 90 |
| 105 for (content::RenderProcessHost::iterator i( | 91 for (content::RenderProcessHost::iterator i( |
| 106 content::RenderProcessHost::AllHostsIterator()); | 92 content::RenderProcessHost::AllHostsIterator()); |
| 107 !i.IsAtEnd(); i.Advance()) { | 93 !i.IsAtEnd(); i.Advance()) { |
| 108 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word)); | 94 i.GetCurrentValue()->Send(new SpellCheckMsg_WordAdded(word)); |
| 109 } | 95 } |
| 110 | 96 |
| 111 std::vector<Observer*>::iterator it; | 97 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordAdded(word)); |
| 112 for (it = observers_.begin(); it != observers_.end(); ++it) | |
| 113 (*it)->OnCustomDictionaryWordAdded(word); | |
| 114 | 98 |
| 115 return true; | 99 return true; |
| 116 } | 100 } |
| 117 | 101 |
| 118 bool SpellcheckCustomDictionary::CustomWordAddedLocally( | 102 bool SpellcheckCustomDictionary::CustomWordAddedLocally( |
| 119 const std::string& word) { | 103 const std::string& word) { |
| 120 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 104 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 121 | 105 |
| 122 WordList::iterator it = std::find(words_.begin(), words_.end(), word); | 106 WordList::iterator it = std::find(words_.begin(), words_.end(), word); |
| 123 if (it == words_.end()) { | 107 if (it == words_.end()) { |
| 124 words_.push_back(word); | 108 words_.push_back(word); |
| 125 return true; | 109 return true; |
| 126 } | 110 } |
| 127 return false; | 111 return false; |
| 128 // TODO(rlp): record metrics on custom word size | 112 // TODO(rlp): record metrics on custom word size |
| 129 } | 113 } |
| 130 | 114 |
| 131 void SpellcheckCustomDictionary::WriteWordToCustomDictionary( | 115 void SpellcheckCustomDictionary::WriteWordToCustomDictionary( |
| 132 const std::string& word) { | 116 const std::string& word) { |
| 133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 134 | |
| 135 // Stored in UTF-8. | |
| 136 DCHECK(IsStringUTF8(word)); | 118 DCHECK(IsStringUTF8(word)); |
| 137 | 119 |
| 138 std::string word_to_add(word + "\n"); | 120 WordList custom_words; |
| 139 if (!file_util::PathExists(custom_dictionary_path_)) { | 121 LoadDictionaryFileReliably(&custom_words); |
| 140 file_util::WriteFile(custom_dictionary_path_, word_to_add.c_str(), | 122 custom_words.push_back(word); |
| 141 word_to_add.length()); | 123 SaveDictionaryFileReliably(custom_words); |
| 142 } else { | |
| 143 file_util::AppendToFile(custom_dictionary_path_, word_to_add.c_str(), | |
| 144 word_to_add.length()); | |
| 145 } | |
| 146 } | 124 } |
| 147 | 125 |
| 148 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { | 126 bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { |
| 149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 127 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 150 | 128 |
| 151 if (!CustomWordRemovedLocally(word)) | 129 if (!CustomWordRemovedLocally(word)) |
| 152 return false; | 130 return false; |
| 153 | 131 |
| 154 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 132 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| 155 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary, | 133 base::Bind(&SpellcheckCustomDictionary::EraseWordFromCustomDictionary, |
| 156 base::Unretained(this), word)); | 134 base::Unretained(this), word)); |
| 157 | 135 |
| 158 for (content::RenderProcessHost::iterator i( | 136 for (content::RenderProcessHost::iterator i( |
| 159 content::RenderProcessHost::AllHostsIterator()); | 137 content::RenderProcessHost::AllHostsIterator()); |
| 160 !i.IsAtEnd(); i.Advance()) { | 138 !i.IsAtEnd(); i.Advance()) { |
| 161 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word)); | 139 i.GetCurrentValue()->Send(new SpellCheckMsg_WordRemoved(word)); |
| 162 } | 140 } |
| 163 | 141 |
| 164 std::vector<Observer*>::iterator it; | 142 FOR_EACH_OBSERVER(Observer, observers_, OnCustomDictionaryWordRemoved(word)); |
| 165 for (it = observers_.begin(); it != observers_.end(); ++it) | |
| 166 (*it)->OnCustomDictionaryWordRemoved(word); | |
| 167 | 143 |
| 168 return true; | 144 return true; |
| 169 } | 145 } |
| 170 | 146 |
| 171 bool SpellcheckCustomDictionary::CustomWordRemovedLocally( | 147 bool SpellcheckCustomDictionary::CustomWordRemovedLocally( |
| 172 const std::string& word) { | 148 const std::string& word) { |
| 173 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 174 | 150 |
| 175 WordList::iterator it = std::find(words_.begin(), words_.end(), word); | 151 WordList::iterator it = std::find(words_.begin(), words_.end(), word); |
| 176 if (it != words_.end()) { | 152 if (it != words_.end()) { |
| 177 words_.erase(it); | 153 words_.erase(it); |
| 178 return true; | 154 return true; |
| 179 } | 155 } |
| 180 return false; | 156 return false; |
| 181 } | 157 } |
| 182 | 158 |
| 183 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( | 159 void SpellcheckCustomDictionary::EraseWordFromCustomDictionary( |
| 184 const std::string& word) { | 160 const std::string& word) { |
| 185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 161 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 186 DCHECK(IsStringUTF8(word)); | 162 DCHECK(IsStringUTF8(word)); |
| 187 | 163 |
| 188 WordList custom_words; | 164 WordList custom_words; |
| 189 LoadDictionaryIntoCustomWordList(&custom_words); | 165 LoadDictionaryFileReliably(&custom_words); |
| 166 if (custom_words.empty()) | |
| 167 return; | |
| 190 | 168 |
| 191 const char empty[] = {'\0'}; | 169 WordList::iterator it = std::find(custom_words.begin(), |
| 192 const char separator[] = {'\n', '\0'}; | 170 custom_words.end(), |
| 193 file_util::WriteFile(custom_dictionary_path_, empty, 0); | 171 word); |
| 194 for (WordList::iterator it = custom_words.begin(); | 172 if (it != custom_words.end()) |
| 195 it != custom_words.end(); | 173 custom_words.erase(it); |
| 196 ++it) { | 174 |
| 197 std::string word_to_add = *it; | 175 SaveDictionaryFileReliably(custom_words); |
| 198 if (word.compare(word_to_add) != 0) { | |
| 199 file_util::AppendToFile(custom_dictionary_path_, word_to_add.c_str(), | |
| 200 word_to_add.length()); | |
| 201 file_util::AppendToFile(custom_dictionary_path_, separator, 1); | |
| 202 } | |
| 203 } | |
| 204 } | 176 } |
| 205 | 177 |
| 206 void SpellcheckCustomDictionary::AddObserver(Observer* observer) { | 178 void SpellcheckCustomDictionary::AddObserver(Observer* observer) { |
| 207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 179 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 208 | 180 |
| 209 observers_.push_back(observer); | 181 observers_.AddObserver(observer); |
| 210 } | 182 } |
| 211 | 183 |
| 212 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { | 184 void SpellcheckCustomDictionary::RemoveObserver(Observer* observer) { |
| 213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 214 | 186 |
| 215 std::vector<Observer*>::iterator it = std::find(observers_.begin(), | 187 observers_.RemoveObserver(observer); |
| 216 observers_.end(), | |
| 217 observer); | |
| 218 if (it != observers_.end()) | |
| 219 observers_.erase(it); | |
| 220 } | 188 } |
| 221 | 189 |
| 222 WordList* SpellcheckCustomDictionary::LoadDictionary() { | 190 WordList* SpellcheckCustomDictionary::LoadDictionary() { |
| 223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 224 | 192 |
| 225 WordList* custom_words = new WordList; | 193 WordList* custom_words = new WordList; |
| 226 LoadDictionaryIntoCustomWordList(custom_words); | 194 LoadDictionaryIntoCustomWordList(custom_words); |
| 227 return custom_words; | 195 return custom_words; |
| 228 } | 196 } |
| 229 | 197 |
| 230 void SpellcheckCustomDictionary::SetCustomWordListAndDelete( | 198 void SpellcheckCustomDictionary::SetCustomWordListAndDelete( |
| 231 WordList* custom_words) { | 199 WordList* custom_words) { |
| 232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 200 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 233 | 201 |
| 234 SetCustomWordList(custom_words); | 202 SetCustomWordList(custom_words); |
| 235 delete custom_words; | 203 delete custom_words; |
| 236 } | 204 } |
| 205 | |
| 206 void SpellcheckCustomDictionary::LoadDictionaryFileReliably( | |
| 207 WordList* custom_words) { | |
| 208 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 209 | |
| 210 // Load the contents and verify the checksum. | |
| 211 std::string contents; | |
| 212 file_util::ReadFileToString(custom_dictionary_path_, &contents); | |
| 213 std::string checksum = GetChecksum(&contents); | |
| 214 base::SplitString(contents, '\n', custom_words); | |
| 215 if (!custom_words->empty() && (*(custom_words->end() - 1)).empty()) | |
|
groby-ooo-7-16
2012/12/05 00:56:19
Why? Just to remove trailing newlines? In that cas
please use gerrit instead
2012/12/05 16:51:26
Removed.
| |
| 216 custom_words->erase(custom_words->end() - 1); | |
| 217 if (checksum.empty() || checksum == base::MD5String(contents)) | |
| 218 return; | |
| 219 | |
| 220 // Checksum is not valid. See if there's a backup. | |
| 221 FilePath backup_path = | |
| 222 custom_dictionary_path_.AddExtension(backup_extension_); | |
| 223 if (!file_util::PathExists(backup_path)) | |
| 224 return; | |
| 225 | |
| 226 // Load the backup and verify its checksum. | |
| 227 std::string backup; | |
|
groby-ooo-7-16
2012/12/05 00:56:19
Extract that into a function - 210-217 and 228-236
please use gerrit instead
2012/12/05 16:51:26
Done.
| |
| 228 file_util::ReadFileToString(backup_path, &backup); | |
| 229 std::string backup_checksum = GetChecksum(&backup); | |
| 230 if (backup_checksum != base::MD5String(backup)) | |
| 231 return; | |
| 232 | |
| 233 // Backup checksum is valid. Use that instead. | |
| 234 base::SplitString(backup, '\n', custom_words); | |
| 235 if (!custom_words->empty() && (*(custom_words->end() - 1)).empty()) | |
| 236 custom_words->erase(custom_words->end() - 1); | |
| 237 file_util::CopyFile(backup_path, custom_dictionary_path_); | |
| 238 } | |
| 239 | |
| 240 void SpellcheckCustomDictionary::SaveDictionaryFileReliably( | |
| 241 const WordList& custom_words) { | |
| 242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 243 | |
| 244 std::stringstream content; | |
| 245 for (WordList::const_iterator it = custom_words.begin(); | |
| 246 it != custom_words.end(); | |
| 247 ++it) { | |
| 248 content << *it << '\n'; | |
| 249 } | |
| 250 std::string content_str = content.str(); | |
| 251 std::stringstream file; | |
| 252 file << checksum_prefix_ << base::MD5String(content_str) << '\n' | |
| 253 << content_str; | |
| 254 | |
| 255 FilePath backup_path = | |
| 256 custom_dictionary_path_.AddExtension(backup_extension_); | |
| 257 if (file_util::PathExists(custom_dictionary_path_)) | |
|
groby-ooo-7-16
2012/12/05 00:56:19
Do you need to check? Can't we just call CopyFile,
please use gerrit instead
2012/12/05 16:51:26
Done.
| |
| 258 file_util::CopyFile(custom_dictionary_path_, backup_path); | |
| 259 base::ImportantFileWriter::WriteFileAtomically(custom_dictionary_path_, | |
| 260 file.str()); | |
| 261 } | |
| 262 | |
| 263 std::string SpellcheckCustomDictionary::GetChecksum(std::string* contents) { | |
| 264 std::string checksum; | |
|
groby-ooo-7-16
2012/12/05 00:56:19
nit: You can save a bit of string rebuilding here
please use gerrit instead
2012/12/05 16:51:26
Done.
| |
| 265 if (contents->substr(0, checksum_prefix_.length()) == checksum_prefix_) { | |
| 266 size_t after_checksum = contents->find('\n', checksum_prefix_.length()); | |
| 267 if (after_checksum == std::string::npos) { | |
| 268 checksum = contents->substr( | |
| 269 checksum_prefix_.length(), | |
| 270 contents->length() - checksum_prefix_.length()); | |
| 271 contents->clear(); | |
| 272 } else { | |
| 273 checksum = contents->substr(checksum_prefix_.length(), | |
| 274 after_checksum - checksum_prefix_.length()); | |
| 275 *contents = contents->substr(after_checksum + 1); | |
| 276 } | |
| 277 } | |
| 278 return checksum; | |
| 279 } | |
| OLD | NEW |