]> git.lizzy.rs Git - minetest.git/blob - src/database/database-files.cpp
9021ae61b2b3abf6d34c3fe778db4f361ce6fc32
[minetest.git] / src / database / database-files.cpp
1 /*
2 Minetest
3 Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <cassert>
21 #include "convert_json.h"
22 #include "database-files.h"
23 #include "remoteplayer.h"
24 #include "settings.h"
25 #include "porting.h"
26 #include "filesys.h"
27 #include "server/player_sao.h"
28 #include "util/string.h"
29
30 // !!! WARNING !!!
31 // This backend is intended to be used on Minetest 0.4.16 only for the transition backend
32 // for player files
33
34 PlayerDatabaseFiles::PlayerDatabaseFiles(const std::string &savedir) : m_savedir(savedir)
35 {
36         fs::CreateDir(m_savedir);
37 }
38
39 void PlayerDatabaseFiles::deSerialize(RemotePlayer *p, std::istream &is,
40          const std::string &playername, PlayerSAO *sao)
41 {
42         Settings args("PlayerArgsEnd");
43
44         if (!args.parseConfigLines(is)) {
45                 throw SerializationError("PlayerArgsEnd of player " + playername + " not found!");
46         }
47
48         p->m_dirty = true;
49         //args.getS32("version"); // Version field value not used
50         const std::string &name = args.get("name");
51         strlcpy(p->m_name, name.c_str(), PLAYERNAME_SIZE);
52
53         if (sao) {
54                 try {
55                         sao->setHPRaw(args.getU16("hp"));
56                 } catch(SettingNotFoundException &e) {
57                         sao->setHPRaw(PLAYER_MAX_HP_DEFAULT);
58                 }
59
60                 try {
61                         sao->setBasePosition(args.getV3F("position"));
62                 } catch (SettingNotFoundException &e) {}
63
64                 try {
65                         sao->setLookPitch(args.getFloat("pitch"));
66                 } catch (SettingNotFoundException &e) {}
67                 try {
68                         sao->setPlayerYaw(args.getFloat("yaw"));
69                 } catch (SettingNotFoundException &e) {}
70
71                 try {
72                         sao->setBreath(args.getU16("breath"), false);
73                 } catch (SettingNotFoundException &e) {}
74
75                 try {
76                         const std::string &extended_attributes = args.get("extended_attributes");
77                         std::istringstream iss(extended_attributes);
78                         Json::CharReaderBuilder builder;
79                         builder.settings_["collectComments"] = false;
80                         std::string errs;
81
82                         Json::Value attr_root;
83                         Json::parseFromStream(builder, iss, &attr_root, &errs);
84
85                         const Json::Value::Members attr_list = attr_root.getMemberNames();
86                         for (const auto &it : attr_list) {
87                                 Json::Value attr_value = attr_root[it];
88                                 sao->getMeta().setString(it, attr_value.asString());
89                         }
90                         sao->getMeta().setModified(false);
91                 } catch (SettingNotFoundException &e) {}
92         }
93
94         try {
95                 p->inventory.deSerialize(is);
96         } catch (SerializationError &e) {
97                 errorstream << "Failed to deserialize player inventory. player_name="
98                         << name << " " << e.what() << std::endl;
99         }
100
101         if (!p->inventory.getList("craftpreview") && p->inventory.getList("craftresult")) {
102                 // Convert players without craftpreview
103                 p->inventory.addList("craftpreview", 1);
104
105                 bool craftresult_is_preview = true;
106                 if(args.exists("craftresult_is_preview"))
107                         craftresult_is_preview = args.getBool("craftresult_is_preview");
108                 if(craftresult_is_preview)
109                 {
110                         // Clear craftresult
111                         p->inventory.getList("craftresult")->changeItem(0, ItemStack());
112                 }
113         }
114 }
115
116 void PlayerDatabaseFiles::serialize(RemotePlayer *p, std::ostream &os)
117 {
118         // Utilize a Settings object for storing values
119         Settings args("PlayerArgsEnd");
120         args.setS32("version", 1);
121         args.set("name", p->m_name);
122
123         PlayerSAO *sao = p->getPlayerSAO();
124         // This should not happen
125         sanity_check(sao);
126         args.setU16("hp", sao->getHP());
127         args.setV3F("position", sao->getBasePosition());
128         args.setFloat("pitch", sao->getLookPitch());
129         args.setFloat("yaw", sao->getRotation().Y);
130         args.setU16("breath", sao->getBreath());
131
132         std::string extended_attrs;
133         {
134                 // serializeExtraAttributes
135                 Json::Value json_root;
136
137                 const StringMap &attrs = sao->getMeta().getStrings();
138                 for (const auto &attr : attrs) {
139                         json_root[attr.first] = attr.second;
140                 }
141
142                 extended_attrs = fastWriteJson(json_root);
143         }
144         args.set("extended_attributes", extended_attrs);
145
146         args.writeLines(os);
147
148         p->inventory.serialize(os);
149 }
150
151 void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
152 {
153         fs::CreateDir(m_savedir);
154
155         std::string savedir = m_savedir + DIR_DELIM;
156         std::string path = savedir + player->getName();
157         bool path_found = false;
158         RemotePlayer testplayer("", NULL);
159
160         for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES && !path_found; i++) {
161                 if (!fs::PathExists(path)) {
162                         path_found = true;
163                         continue;
164                 }
165
166                 // Open and deserialize file to check player name
167                 std::ifstream is(path.c_str(), std::ios_base::binary);
168                 if (!is.good()) {
169                         errorstream << "Failed to open " << path << std::endl;
170                         return;
171                 }
172
173                 deSerialize(&testplayer, is, path, NULL);
174                 is.close();
175                 if (strcmp(testplayer.getName(), player->getName()) == 0) {
176                         path_found = true;
177                         continue;
178                 }
179
180                 path = savedir + player->getName() + itos(i);
181         }
182
183         if (!path_found) {
184                 errorstream << "Didn't find free file for player " << player->getName()
185                                 << std::endl;
186                 return;
187         }
188
189         // Open and serialize file
190         std::ostringstream ss(std::ios_base::binary);
191         serialize(player, ss);
192         if (!fs::safeWriteToFile(path, ss.str())) {
193                 infostream << "Failed to write " << path << std::endl;
194         }
195
196         player->onSuccessfulSave();
197 }
198
199 bool PlayerDatabaseFiles::removePlayer(const std::string &name)
200 {
201         std::string players_path = m_savedir + DIR_DELIM;
202         std::string path = players_path + name;
203
204         RemotePlayer temp_player("", NULL);
205         for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
206                 // Open file and deserialize
207                 std::ifstream is(path.c_str(), std::ios_base::binary);
208                 if (!is.good())
209                         continue;
210
211                 deSerialize(&temp_player, is, path, NULL);
212                 is.close();
213
214                 if (temp_player.getName() == name) {
215                         fs::DeleteSingleFileOrEmptyDirectory(path);
216                         return true;
217                 }
218
219                 path = players_path + name + itos(i);
220         }
221
222         return false;
223 }
224
225 bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
226 {
227         std::string players_path = m_savedir + DIR_DELIM;
228         std::string path = players_path + player->getName();
229
230         const std::string player_to_load = player->getName();
231         for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
232                 // Open file and deserialize
233                 std::ifstream is(path.c_str(), std::ios_base::binary);
234                 if (!is.good())
235                         continue;
236
237                 deSerialize(player, is, path, sao);
238                 is.close();
239
240                 if (player->getName() == player_to_load)
241                         return true;
242
243                 path = players_path + player_to_load + itos(i);
244         }
245
246         infostream << "Player file for player " << player_to_load << " not found" << std::endl;
247         return false;
248 }
249
250 void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
251 {
252         std::vector<fs::DirListNode> files = fs::GetDirListing(m_savedir);
253         // list files into players directory
254         for (std::vector<fs::DirListNode>::const_iterator it = files.begin(); it !=
255                 files.end(); ++it) {
256                 // Ignore directories
257                 if (it->dir)
258                         continue;
259
260                 const std::string &filename = it->name;
261                 std::string full_path = m_savedir + DIR_DELIM + filename;
262                 std::ifstream is(full_path.c_str(), std::ios_base::binary);
263                 if (!is.good())
264                         continue;
265
266                 RemotePlayer player(filename.c_str(), NULL);
267                 // Null env & dummy peer_id
268                 PlayerSAO playerSAO(NULL, &player, 15789, false);
269
270                 deSerialize(&player, is, "", &playerSAO);
271                 is.close();
272
273                 res.emplace_back(player.getName());
274         }
275 }
276
277 AuthDatabaseFiles::AuthDatabaseFiles(const std::string &savedir) : m_savedir(savedir)
278 {
279         readAuthFile();
280 }
281
282 bool AuthDatabaseFiles::getAuth(const std::string &name, AuthEntry &res)
283 {
284         const auto res_i = m_auth_list.find(name);
285         if (res_i == m_auth_list.end()) {
286                 return false;
287         }
288         res = res_i->second;
289         return true;
290 }
291
292 bool AuthDatabaseFiles::saveAuth(const AuthEntry &authEntry)
293 {
294         m_auth_list[authEntry.name] = authEntry;
295
296         // save entire file
297         return writeAuthFile();
298 }
299
300 bool AuthDatabaseFiles::createAuth(AuthEntry &authEntry)
301 {
302         m_auth_list[authEntry.name] = authEntry;
303
304         // save entire file
305         return writeAuthFile();
306 }
307
308 bool AuthDatabaseFiles::deleteAuth(const std::string &name)
309 {
310         if (!m_auth_list.erase(name)) {
311                 // did not delete anything -> hadn't existed
312                 return false;
313         }
314         return writeAuthFile();
315 }
316
317 void AuthDatabaseFiles::listNames(std::vector<std::string> &res)
318 {
319         res.clear();
320         res.reserve(m_auth_list.size());
321         for (const auto &res_pair : m_auth_list) {
322                 res.push_back(res_pair.first);
323         }
324 }
325
326 void AuthDatabaseFiles::reload()
327 {
328         readAuthFile();
329 }
330
331 bool AuthDatabaseFiles::readAuthFile()
332 {
333         std::string path = m_savedir + DIR_DELIM + "auth.txt";
334         std::ifstream file(path, std::ios::binary);
335         if (!file.good()) {
336                 return false;
337         }
338         m_auth_list.clear();
339         while (file.good()) {
340                 std::string line;
341                 std::getline(file, line);
342                 std::vector<std::string> parts = str_split(line, ':');
343                 if (parts.size() < 3) // also: empty line at end
344                         continue;
345                 const std::string &name = parts[0];
346                 const std::string &password = parts[1];
347                 std::vector<std::string> privileges = str_split(parts[2], ',');
348                 s64 last_login = parts.size() > 3 ? atol(parts[3].c_str()) : 0;
349
350                 m_auth_list[name] = {
351                                 1,
352                                 name,
353                                 password,
354                                 privileges,
355                                 last_login,
356                 };
357         }
358         return true;
359 }
360
361 bool AuthDatabaseFiles::writeAuthFile()
362 {
363         std::string path = m_savedir + DIR_DELIM + "auth.txt";
364         std::ostringstream output(std::ios_base::binary);
365         for (const auto &auth_i : m_auth_list) {
366                 const AuthEntry &authEntry = auth_i.second;
367                 output << authEntry.name << ":" << authEntry.password << ":";
368                 output << str_join(authEntry.privileges, ",");
369                 output << ":" << authEntry.last_login;
370                 output << std::endl;
371         }
372         if (!fs::safeWriteToFile(path, output.str())) {
373                 infostream << "Failed to write " << path << std::endl;
374                 return false;
375         }
376         return true;
377 }
378
379 ModMetadataDatabaseFiles::ModMetadataDatabaseFiles(const std::string &savedir):
380         m_storage_dir(savedir + DIR_DELIM + "mod_storage")
381 {
382 }
383
384 bool ModMetadataDatabaseFiles::getModEntries(const std::string &modname, StringMap *storage)
385 {
386         Json::Value *meta = getOrCreateJson(modname);
387         if (!meta)
388                 return false;
389
390         const Json::Value::Members attr_list = meta->getMemberNames();
391         for (const auto &it : attr_list) {
392                 Json::Value attr_value = (*meta)[it];
393                 (*storage)[it] = attr_value.asString();
394         }
395
396         return true;
397 }
398
399 bool ModMetadataDatabaseFiles::setModEntry(const std::string &modname,
400         const std::string &key, const std::string &value)
401 {
402         Json::Value *meta = getOrCreateJson(modname);
403         if (!meta)
404                 return false;
405
406         (*meta)[key] = Json::Value(value);
407         m_modified.insert(modname);
408
409         return true;
410 }
411
412 bool ModMetadataDatabaseFiles::removeModEntry(const std::string &modname,
413                 const std::string &key)
414 {
415         Json::Value *meta = getOrCreateJson(modname);
416         if (!meta)
417                 return false;
418
419         Json::Value removed;
420         if (meta->removeMember(key, &removed)) {
421                 m_modified.insert(modname);
422                 return true;
423         }
424         return false;
425 }
426
427 void ModMetadataDatabaseFiles::beginSave()
428 {
429 }
430
431 void ModMetadataDatabaseFiles::endSave()
432 {
433         if (!fs::CreateAllDirs(m_storage_dir)) {
434                 errorstream << "ModMetadataDatabaseFiles: Unable to save. '" << m_storage_dir
435                                 << "' tree cannot be created." << std::endl;
436                 return;
437         }
438
439         for (auto it = m_modified.begin(); it != m_modified.end();) {
440                 const std::string &modname = *it;
441
442                 if (!fs::PathExists(m_storage_dir)) {
443                         if (!fs::CreateAllDirs(m_storage_dir)) {
444                                 errorstream << "ModMetadataDatabaseFiles[" << modname
445                                                 << "]: Unable to save. '" << m_storage_dir
446                                                 << "' tree cannot be created." << std::endl;
447                                 ++it;
448                                 continue;
449                         }
450                 } else if (!fs::IsDir(m_storage_dir)) {
451                         errorstream << "ModMetadataDatabaseFiles[" << modname << "]: Unable to save. '"
452                                         << m_storage_dir << "' is not a directory." << std::endl;
453                         ++it;
454                         continue;
455                 }
456
457                 const Json::Value &json = m_mod_meta[modname];
458
459                 if (!fs::safeWriteToFile(m_storage_dir + DIR_DELIM + modname, fastWriteJson(json))) {
460                         errorstream << "ModMetadataDatabaseFiles[" << modname
461                                         << "]: failed write file." << std::endl;
462                         ++it;
463                         continue;
464                 }
465
466                 it = m_modified.erase(it);
467         }
468 }
469
470 void ModMetadataDatabaseFiles::listMods(std::vector<std::string> *res)
471 {
472         // List in-memory metadata first.
473         for (const auto &pair : m_mod_meta) {
474                 res->push_back(pair.first);
475         }
476
477         // List other metadata present in the filesystem.
478         for (const auto &entry : fs::GetDirListing(m_storage_dir)) {
479                 if (!entry.dir && m_mod_meta.count(entry.name) == 0)
480                         res->push_back(entry.name);
481         }
482 }
483
484 Json::Value *ModMetadataDatabaseFiles::getOrCreateJson(const std::string &modname)
485 {
486         auto found = m_mod_meta.find(modname);
487         if (found == m_mod_meta.end()) {
488                 fs::CreateAllDirs(m_storage_dir);
489
490                 Json::Value meta(Json::objectValue);
491
492                 std::string path = m_storage_dir + DIR_DELIM + modname;
493                 if (fs::PathExists(path)) {
494                         std::ifstream is(path.c_str(), std::ios_base::binary);
495
496                         Json::CharReaderBuilder builder;
497                         builder.settings_["collectComments"] = false;
498                         std::string errs;
499
500                         if (!Json::parseFromStream(builder, is, &meta, &errs)) {
501                                 errorstream << "ModMetadataDatabaseFiles[" << modname
502                                                 << "]: failed read data (Json decoding failure). Message: "
503                                                 << errs << std::endl;
504                                 return nullptr;
505                         }
506                 }
507
508                 return &(m_mod_meta[modname] = meta);
509         } else {
510                 return &found->second;
511         }
512 }