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