1 //
2 // Copyright RIME Developers
3 // Distributed under the BSD License
4 //
5 // 2011-11-02 GONG Chen <chen.sst@gmail.com>
6 //
7 #include <cstdlib>
8 #include <boost/algorithm/string.hpp>
9 #include <boost/format.hpp>
10 #include <boost/lexical_cast.hpp>
11 #include <rime/service.h>
12 #include <rime/algo/dynamics.h>
13 #include <rime/dict/text_db.h>
14 #include <rime/dict/user_db.h>
15
16 namespace rime {
17
UserDbValue(const string & value)18 UserDbValue::UserDbValue(const string& value) {
19 Unpack(value);
20 }
21
Pack() const22 string UserDbValue::Pack() const {
23 return boost::str(boost::format("c=%1% d=%2% t=%3%") %
24 commits % dee % tick);
25 }
26
Unpack(const string & value)27 bool UserDbValue::Unpack(const string& value) {
28 vector<string> kv;
29 boost::split(kv, value, boost::is_any_of(" "));
30 for (const string& k_eq_v : kv) {
31 size_t eq = k_eq_v.find('=');
32 if (eq == string::npos)
33 continue;
34 string k(k_eq_v.substr(0, eq));
35 string v(k_eq_v.substr(eq + 1));
36 try {
37 if (k == "c") {
38 commits = boost::lexical_cast<int>(v);
39 }
40 else if (k == "d") {
41 dee = (std::min)(10000.0, boost::lexical_cast<double>(v));
42 }
43 else if (k == "t") {
44 tick = boost::lexical_cast<TickCount>(v);
45 }
46 }
47 catch (...) {
48 LOG(ERROR) << "failed in parsing key-value from userdb entry '"
49 << k_eq_v << "'.";
50 return false;
51 }
52 }
53 return true;
54 }
55
56 static const string plain_userdb_extension(".userdb.txt");
57
58 template <>
extension() const59 string UserDbComponent<TextDb>::extension() const {
60 return plain_userdb_extension;
61 }
62
snapshot_extension()63 string UserDb::snapshot_extension() {
64 return plain_userdb_extension;
65 }
66
67 // key ::= code <space> <Tab> phrase
68
userdb_entry_parser(const Tsv & row,string * key,string * value)69 static bool userdb_entry_parser(const Tsv& row,
70 string* key,
71 string* value) {
72 if (row.size() < 2 ||
73 row[0].empty() || row[1].empty()) {
74 return false;
75 }
76 string code(row[0]);
77 // fix invalid keys created by a buggy version
78 if (code[code.length() - 1] != ' ')
79 code += ' ';
80 *key = code + "\t" + row[1];
81 if (row.size() >= 3)
82 *value = row[2];
83 else
84 value->clear();
85 return true;
86 }
87
userdb_entry_formatter(const string & key,const string & value,Tsv * tsv)88 static bool userdb_entry_formatter(const string& key,
89 const string& value,
90 Tsv* tsv) {
91 Tsv& row(*tsv);
92 boost::algorithm::split(row, key,
93 boost::algorithm::is_any_of("\t"));
94 if (row.size() != 2 ||
95 row[0].empty() || row[1].empty())
96 return false;
97 row.push_back(value);
98 return true;
99 }
100
101 static TextFormat plain_userdb_format = {
102 userdb_entry_parser,
103 userdb_entry_formatter,
104 "Rime user dictionary",
105 };
106
107 template <>
UserDbWrapper(const string & file_name,const string & db_name)108 UserDbWrapper<TextDb>::UserDbWrapper(const string& file_name,
109 const string& db_name)
110 : TextDb(file_name, db_name, "userdb", plain_userdb_format) {
111 }
112
UpdateUserInfo()113 bool UserDbHelper::UpdateUserInfo() {
114 Deployer& deployer(Service::instance().deployer());
115 return db_->MetaUpdate("/user_id", deployer.user_id);
116 }
117
IsUniformFormat(const string & file_name)118 bool UserDbHelper::IsUniformFormat(const string& file_name) {
119 return boost::ends_with(file_name, plain_userdb_extension);
120 }
121
UniformBackup(const string & snapshot_file)122 bool UserDbHelper::UniformBackup(const string& snapshot_file) {
123 LOG(INFO) << "backing up userdb '" << db_->name() << "' to "
124 << snapshot_file;
125 TsvWriter writer(snapshot_file, plain_userdb_format.formatter);
126 writer.file_description = plain_userdb_format.file_description;
127 DbSource source(db_);
128 try {
129 writer << source;
130 }
131 catch (std::exception& ex) {
132 LOG(ERROR) << ex.what();
133 return false;
134 }
135 return true;
136 }
137
UniformRestore(const string & snapshot_file)138 bool UserDbHelper::UniformRestore(const string& snapshot_file) {
139 LOG(INFO) << "restoring userdb '" << db_->name() << "' from "
140 << snapshot_file;
141 TsvReader reader(snapshot_file, plain_userdb_format.parser);
142 DbSink sink(db_);
143 try {
144 reader >> sink;
145 }
146 catch (std::exception& ex) {
147 LOG(ERROR) << ex.what();
148 return false;
149 }
150 return true;
151 }
152
IsUserDb()153 bool UserDbHelper::IsUserDb() {
154 string db_type;
155 return db_->MetaFetch("/db_type", &db_type) && (db_type == "userdb");
156 }
157
GetDbName()158 string UserDbHelper::GetDbName() {
159 string name;
160 if (!db_->MetaFetch("/db_name", &name))
161 return name;
162 auto ext = boost::find_last(name, ".userdb");
163 if (!ext.empty()) {
164 // remove ".userdb.*"
165 name.erase(ext.begin(), name.end());
166 }
167 return name;
168 }
169
GetUserId()170 string UserDbHelper::GetUserId() {
171 string user_id("unknown");
172 db_->MetaFetch("/user_id", &user_id);
173 return user_id;
174 }
175
GetRimeVersion()176 string UserDbHelper::GetRimeVersion() {
177 string version;
178 db_->MetaFetch("/rime_version", &version);
179 return version;
180 }
181
get_tick_count(Db * db)182 static TickCount get_tick_count(Db* db) {
183 string tick;
184 if (db && db->MetaFetch("/tick", &tick)) {
185 try {
186 return boost::lexical_cast<TickCount>(tick);
187 }
188 catch (...) {
189 }
190 }
191 return 1;
192 }
193
UserDbMerger(Db * db)194 UserDbMerger::UserDbMerger(Db* db) : db_(db) {
195 our_tick_ = get_tick_count(db);
196 their_tick_ = 0;
197 max_tick_ = our_tick_;
198 }
199
~UserDbMerger()200 UserDbMerger::~UserDbMerger() {
201 CloseMerge();
202 }
203
MetaPut(const string & key,const string & value)204 bool UserDbMerger::MetaPut(const string& key, const string& value) {
205 if (key == "/tick") {
206 try {
207 their_tick_ = boost::lexical_cast<TickCount>(value);
208 max_tick_ = (std::max)(our_tick_, their_tick_);
209 }
210 catch (...) {
211 }
212 }
213 return true;
214 }
215
Put(const string & key,const string & value)216 bool UserDbMerger::Put(const string& key, const string& value) {
217 if (!db_) return false;
218 UserDbValue v(value);
219 if (v.tick < their_tick_) {
220 v.dee = algo::formula_d(0, (double)their_tick_, v.dee, (double)v.tick);
221 }
222 UserDbValue o;
223 string our_value;
224 if (db_->Fetch(key, &our_value)) {
225 o.Unpack(our_value);
226 }
227 if (o.tick < our_tick_) {
228 o.dee = algo::formula_d(0, (double)our_tick_, o.dee, (double)o.tick);
229 }
230 if (std::abs(o.commits) < std::abs(v.commits))
231 o.commits = v.commits;
232 o.dee = (std::max)(o.dee, v.dee);
233 o.tick = max_tick_;
234 return db_->Update(key, o.Pack()) && ++merged_entries_;
235 }
236
CloseMerge()237 void UserDbMerger::CloseMerge() {
238 if (!db_ || !merged_entries_)
239 return;
240 Deployer& deployer(Service::instance().deployer());
241 try {
242 db_->MetaUpdate("/tick", boost::lexical_cast<string>(max_tick_));
243 db_->MetaUpdate("/user_id", deployer.user_id);
244 }
245 catch (...) {
246 LOG(ERROR) << "failed to update tick count.";
247 return;
248 }
249 LOG(INFO) << "total " << merged_entries_ << " entries merged, tick = "
250 << max_tick_;
251 merged_entries_ = 0;
252 }
253
UserDbImporter(Db * db)254 UserDbImporter::UserDbImporter(Db* db)
255 : db_(db) {
256 }
257
MetaPut(const string & key,const string & value)258 bool UserDbImporter::MetaPut(const string& key, const string& value) {
259 return true;
260 }
261
Put(const string & key,const string & value)262 bool UserDbImporter::Put(const string& key, const string& value) {
263 if (!db_) return false;
264 UserDbValue v(value);
265 UserDbValue o;
266 string old_value;
267 if (db_->Fetch(key, &old_value)) {
268 o.Unpack(old_value);
269 }
270 if (v.commits > 0) {
271 o.commits = (std::max)(o.commits, v.commits);
272 o.dee = (std::max)(o.dee, v.dee);
273 }
274 else if (v.commits < 0) { // mark as deleted
275 o.commits = (std::min)(v.commits, -std::abs(o.commits));
276 }
277 return db_->Update(key, o.Pack());
278 }
279
280 } // namespace rime
281