Server: Improve some log messages (#9820)
[oweals/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 <json/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::serialize(std::ostringstream &os, RemotePlayer *player)
40 {
41         // Utilize a Settings object for storing values
42         Settings args;
43         args.setS32("version", 1);
44         args.set("name", player->getName());
45
46         sanity_check(player->getPlayerSAO());
47         args.setU16("hp", player->getPlayerSAO()->getHP());
48         args.setV3F("position", player->getPlayerSAO()->getBasePosition());
49         args.setFloat("pitch", player->getPlayerSAO()->getLookPitch());
50         args.setFloat("yaw", player->getPlayerSAO()->getRotation().Y);
51         args.setU16("breath", player->getPlayerSAO()->getBreath());
52
53         std::string extended_attrs;
54         player->serializeExtraAttributes(extended_attrs);
55         args.set("extended_attributes", extended_attrs);
56
57         args.writeLines(os);
58
59         os << "PlayerArgsEnd\n";
60
61         player->inventory.serialize(os);
62 }
63
64 void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
65 {
66         fs::CreateDir(m_savedir);
67
68         std::string savedir = m_savedir + DIR_DELIM;
69         std::string path = savedir + player->getName();
70         bool path_found = false;
71         RemotePlayer testplayer("", NULL);
72
73         for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES && !path_found; i++) {
74                 if (!fs::PathExists(path)) {
75                         path_found = true;
76                         continue;
77                 }
78
79                 // Open and deserialize file to check player name
80                 std::ifstream is(path.c_str(), std::ios_base::binary);
81                 if (!is.good()) {
82                         errorstream << "Failed to open " << path << std::endl;
83                         return;
84                 }
85
86                 testplayer.deSerialize(is, path, NULL);
87                 is.close();
88                 if (strcmp(testplayer.getName(), player->getName()) == 0) {
89                         path_found = true;
90                         continue;
91                 }
92
93                 path = savedir + player->getName() + itos(i);
94         }
95
96         if (!path_found) {
97                 errorstream << "Didn't find free file for player " << player->getName()
98                                 << std::endl;
99                 return;
100         }
101
102         // Open and serialize file
103         std::ostringstream ss(std::ios_base::binary);
104         serialize(ss, player);
105         if (!fs::safeWriteToFile(path, ss.str())) {
106                 infostream << "Failed to write " << path << std::endl;
107         }
108
109         player->onSuccessfulSave();
110 }
111
112 bool PlayerDatabaseFiles::removePlayer(const std::string &name)
113 {
114         std::string players_path = m_savedir + DIR_DELIM;
115         std::string path = players_path + name;
116
117         RemotePlayer temp_player("", NULL);
118         for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
119                 // Open file and deserialize
120                 std::ifstream is(path.c_str(), std::ios_base::binary);
121                 if (!is.good())
122                         continue;
123
124                 temp_player.deSerialize(is, path, NULL);
125                 is.close();
126
127                 if (temp_player.getName() == name) {
128                         fs::DeleteSingleFileOrEmptyDirectory(path);
129                         return true;
130                 }
131
132                 path = players_path + name + itos(i);
133         }
134
135         return false;
136 }
137
138 bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
139 {
140         std::string players_path = m_savedir + DIR_DELIM;
141         std::string path = players_path + player->getName();
142
143         const std::string player_to_load = player->getName();
144         for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
145                 // Open file and deserialize
146                 std::ifstream is(path.c_str(), std::ios_base::binary);
147                 if (!is.good())
148                         continue;
149
150                 player->deSerialize(is, path, sao);
151                 is.close();
152
153                 if (player->getName() == player_to_load)
154                         return true;
155
156                 path = players_path + player_to_load + itos(i);
157         }
158
159         infostream << "Player file for player " << player_to_load << " not found" << std::endl;
160         return false;
161 }
162
163 void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
164 {
165         std::vector<fs::DirListNode> files = fs::GetDirListing(m_savedir);
166         // list files into players directory
167         for (std::vector<fs::DirListNode>::const_iterator it = files.begin(); it !=
168                 files.end(); ++it) {
169                 // Ignore directories
170                 if (it->dir)
171                         continue;
172
173                 const std::string &filename = it->name;
174                 std::string full_path = m_savedir + DIR_DELIM + filename;
175                 std::ifstream is(full_path.c_str(), std::ios_base::binary);
176                 if (!is.good())
177                         continue;
178
179                 RemotePlayer player(filename.c_str(), NULL);
180                 // Null env & dummy peer_id
181                 PlayerSAO playerSAO(NULL, &player, 15789, false);
182
183                 player.deSerialize(is, "", &playerSAO);
184                 is.close();
185
186                 res.emplace_back(player.getName());
187         }
188 }
189
190 AuthDatabaseFiles::AuthDatabaseFiles(const std::string &savedir) : m_savedir(savedir)
191 {
192         readAuthFile();
193 }
194
195 bool AuthDatabaseFiles::getAuth(const std::string &name, AuthEntry &res)
196 {
197         const auto res_i = m_auth_list.find(name);
198         if (res_i == m_auth_list.end()) {
199                 return false;
200         }
201         res = res_i->second;
202         return true;
203 }
204
205 bool AuthDatabaseFiles::saveAuth(const AuthEntry &authEntry)
206 {
207         m_auth_list[authEntry.name] = authEntry;
208
209         // save entire file
210         return writeAuthFile();
211 }
212
213 bool AuthDatabaseFiles::createAuth(AuthEntry &authEntry)
214 {
215         m_auth_list[authEntry.name] = authEntry;
216
217         // save entire file
218         return writeAuthFile();
219 }
220
221 bool AuthDatabaseFiles::deleteAuth(const std::string &name)
222 {
223         if (!m_auth_list.erase(name)) {
224                 // did not delete anything -> hadn't existed
225                 return false;
226         }
227         return writeAuthFile();
228 }
229
230 void AuthDatabaseFiles::listNames(std::vector<std::string> &res)
231 {
232         res.clear();
233         res.reserve(m_auth_list.size());
234         for (const auto &res_pair : m_auth_list) {
235                 res.push_back(res_pair.first);
236         }
237 }
238
239 void AuthDatabaseFiles::reload()
240 {
241         readAuthFile();
242 }
243
244 bool AuthDatabaseFiles::readAuthFile()
245 {
246         std::string path = m_savedir + DIR_DELIM + "auth.txt";
247         std::ifstream file(path, std::ios::binary);
248         if (!file.good()) {
249                 return false;
250         }
251         m_auth_list.clear();
252         while (file.good()) {
253                 std::string line;
254                 std::getline(file, line);
255                 std::vector<std::string> parts = str_split(line, ':');
256                 if (parts.size() < 3) // also: empty line at end
257                         continue;
258                 const std::string &name = parts[0];
259                 const std::string &password = parts[1];
260                 std::vector<std::string> privileges = str_split(parts[2], ',');
261                 s64 last_login = parts.size() > 3 ? atol(parts[3].c_str()) : 0;
262
263                 m_auth_list[name] = {
264                                 1,
265                                 name,
266                                 password,
267                                 privileges,
268                                 last_login,
269                 };
270         }
271         return true;
272 }
273
274 bool AuthDatabaseFiles::writeAuthFile()
275 {
276         std::string path = m_savedir + DIR_DELIM + "auth.txt";
277         std::ostringstream output(std::ios_base::binary);
278         for (const auto &auth_i : m_auth_list) {
279                 const AuthEntry &authEntry = auth_i.second;
280                 output << authEntry.name << ":" << authEntry.password << ":";
281                 output << str_join(authEntry.privileges, ",");
282                 output << ":" << authEntry.last_login;
283                 output << std::endl;
284         }
285         if (!fs::safeWriteToFile(path, output.str())) {
286                 infostream << "Failed to write " << path << std::endl;
287                 return false;
288         }
289         return true;
290 }