]> git.lizzy.rs Git - minetest.git/blob - src/database/database-files.cpp
Add keybind to swap items between hands
[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 ModStorageDatabaseFiles::ModStorageDatabaseFiles(const std::string &savedir):
380         m_storage_dir(savedir + DIR_DELIM + "mod_storage")
381 {
382 }
383
384 void ModStorageDatabaseFiles::getModEntries(const std::string &modname, StringMap *storage)
385 {
386         Json::Value *meta = getOrCreateJson(modname);
387         if (!meta)
388                 return;
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
397 void ModStorageDatabaseFiles::getModKeys(const std::string &modname,
398                 std::vector<std::string> *storage)
399 {
400         Json::Value *meta = getOrCreateJson(modname);
401         if (!meta)
402                 return;
403
404         std::vector<std::string> keys = meta->getMemberNames();
405         storage->reserve(storage->size() + keys.size());
406         for (std::string &key : keys)
407                 storage->push_back(std::move(key));
408 }
409
410 bool ModStorageDatabaseFiles::getModEntry(const std::string &modname,
411         const std::string &key, std::string *value)
412 {
413         Json::Value *meta = getOrCreateJson(modname);
414         if (!meta)
415                 return false;
416
417         if (meta->isMember(key)) {
418                 *value = (*meta)[key].asString();
419                 return true;
420         }
421         return false;
422 }
423
424 bool ModStorageDatabaseFiles::hasModEntry(const std::string &modname, const std::string &key)
425 {
426         Json::Value *meta = getOrCreateJson(modname);
427         return meta && meta->isMember(key);
428 }
429
430 bool ModStorageDatabaseFiles::setModEntry(const std::string &modname,
431         const std::string &key, const std::string &value)
432 {
433         Json::Value *meta = getOrCreateJson(modname);
434         if (!meta)
435                 return false;
436
437         (*meta)[key] = Json::Value(value);
438         m_modified.insert(modname);
439
440         return true;
441 }
442
443 bool ModStorageDatabaseFiles::removeModEntry(const std::string &modname,
444                 const std::string &key)
445 {
446         Json::Value *meta = getOrCreateJson(modname);
447         if (!meta)
448                 return false;
449
450         Json::Value removed;
451         if (meta->removeMember(key, &removed)) {
452                 m_modified.insert(modname);
453                 return true;
454         }
455         return false;
456 }
457
458 bool ModStorageDatabaseFiles::removeModEntries(const std::string &modname)
459 {
460         Json::Value *meta = getOrCreateJson(modname);
461         if (!meta || meta->empty())
462                 return false;
463
464         meta->clear();
465         m_modified.insert(modname);
466         return true;
467 }
468
469 void ModStorageDatabaseFiles::beginSave()
470 {
471 }
472
473 void ModStorageDatabaseFiles::endSave()
474 {
475         if (m_modified.empty())
476                 return;
477
478         if (!fs::CreateAllDirs(m_storage_dir)) {
479                 errorstream << "ModStorageDatabaseFiles: Unable to save. '"
480                                 << m_storage_dir << "' cannot be created." << std::endl;
481                 return;
482         }
483         if (!fs::IsDir(m_storage_dir)) {
484                 errorstream << "ModStorageDatabaseFiles: Unable to save. '"
485                                 << m_storage_dir << "' is not a directory." << std::endl;
486                 return;
487         }
488
489         for (auto it = m_modified.begin(); it != m_modified.end();) {
490                 const std::string &modname = *it;
491
492                 const Json::Value &json = m_mod_storage[modname];
493
494                 if (!fs::safeWriteToFile(m_storage_dir + DIR_DELIM + modname, fastWriteJson(json))) {
495                         errorstream << "ModStorageDatabaseFiles[" << modname
496                                         << "]: failed to write file." << std::endl;
497                         ++it;
498                         continue;
499                 }
500
501                 it = m_modified.erase(it);
502         }
503 }
504
505 void ModStorageDatabaseFiles::listMods(std::vector<std::string> *res)
506 {
507         // List in-memory metadata first.
508         for (const auto &pair : m_mod_storage) {
509                 res->push_back(pair.first);
510         }
511
512         // List other metadata present in the filesystem.
513         for (const auto &entry : fs::GetDirListing(m_storage_dir)) {
514                 if (!entry.dir && m_mod_storage.count(entry.name) == 0)
515                         res->push_back(entry.name);
516         }
517 }
518
519 Json::Value *ModStorageDatabaseFiles::getOrCreateJson(const std::string &modname)
520 {
521         auto found = m_mod_storage.find(modname);
522         if (found != m_mod_storage.end())
523                 return &found->second;
524
525         Json::Value meta(Json::objectValue);
526
527         std::string path = m_storage_dir + DIR_DELIM + modname;
528         if (fs::PathExists(path)) {
529                 std::ifstream is(path.c_str(), std::ios_base::binary);
530
531                 Json::CharReaderBuilder builder;
532                 builder.settings_["collectComments"] = false;
533                 std::string errs;
534
535                 if (!Json::parseFromStream(builder, is, &meta, &errs)) {
536                         errorstream << "ModStorageDatabaseFiles[" << modname
537                                         << "]: failed to decode data: " << errs << std::endl;
538                         return nullptr;
539                 }
540         }
541
542         return &(m_mod_storage[modname] = std::move(meta));
543 }