Translated using Weblate (Italian)
[oweals/minetest.git] / src / database / database-redis.cpp
1 /*
2 Minetest
3 Copyright (C) 2014 celeron55, Perttu Ahola <celeron55@gmail.com>
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 "config.h"
21
22 #if USE_REDIS
23
24 #include "database-redis.h"
25
26 #include "settings.h"
27 #include "log.h"
28 #include "exceptions.h"
29 #include "util/string.h"
30
31 #include <hiredis.h>
32 #include <cassert>
33
34
35 Database_Redis::Database_Redis(Settings &conf)
36 {
37         std::string tmp;
38         try {
39                 tmp = conf.get("redis_address");
40                 hash = conf.get("redis_hash");
41         } catch (SettingNotFoundException &) {
42                 throw SettingNotFoundException("Set redis_address and "
43                         "redis_hash in world.mt to use the redis backend");
44         }
45         const char *addr = tmp.c_str();
46         int port = conf.exists("redis_port") ? conf.getU16("redis_port") : 6379;
47         // if redis_address contains '/' assume unix socket, else hostname/ip
48         ctx = tmp.find('/') != std::string::npos ? redisConnectUnix(addr) : redisConnect(addr, port);
49         if (!ctx) {
50                 throw DatabaseException("Cannot allocate redis context");
51         } else if (ctx->err) {
52                 std::string err = std::string("Connection error: ") + ctx->errstr;
53                 redisFree(ctx);
54                 throw DatabaseException(err);
55         }
56         if (conf.exists("redis_password")) {
57                 tmp = conf.get("redis_password");
58                 redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "AUTH %s", tmp.c_str()));
59                 if (!reply)
60                         throw DatabaseException("Redis authentication failed");
61                 if (reply->type == REDIS_REPLY_ERROR) {
62                         std::string err = "Redis authentication failed: " + std::string(reply->str, reply->len);
63                         freeReplyObject(reply);
64                         throw DatabaseException(err);
65                 }
66                 freeReplyObject(reply);
67         }
68 }
69
70 Database_Redis::~Database_Redis()
71 {
72         redisFree(ctx);
73 }
74
75 void Database_Redis::beginSave() {
76         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "MULTI"));
77         if (!reply) {
78                 throw DatabaseException(std::string(
79                         "Redis command 'MULTI' failed: ") + ctx->errstr);
80         }
81         freeReplyObject(reply);
82 }
83
84 void Database_Redis::endSave() {
85         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "EXEC"));
86         if (!reply) {
87                 throw DatabaseException(std::string(
88                         "Redis command 'EXEC' failed: ") + ctx->errstr);
89         }
90         freeReplyObject(reply);
91 }
92
93 bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data)
94 {
95         std::string tmp = i64tos(getBlockAsInteger(pos));
96
97         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HSET %s %s %b",
98                         hash.c_str(), tmp.c_str(), data.c_str(), data.size()));
99         if (!reply) {
100                 warningstream << "saveBlock: redis command 'HSET' failed on "
101                         "block " << PP(pos) << ": " << ctx->errstr << std::endl;
102                 freeReplyObject(reply);
103                 return false;
104         }
105
106         if (reply->type == REDIS_REPLY_ERROR) {
107                 warningstream << "saveBlock: saving block " << PP(pos)
108                         << " failed: " << std::string(reply->str, reply->len) << std::endl;
109                 freeReplyObject(reply);
110                 return false;
111         }
112
113         freeReplyObject(reply);
114         return true;
115 }
116
117 void Database_Redis::loadBlock(const v3s16 &pos, std::string *block)
118 {
119         std::string tmp = i64tos(getBlockAsInteger(pos));
120         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
121                         "HGET %s %s", hash.c_str(), tmp.c_str()));
122
123         if (!reply) {
124                 throw DatabaseException(std::string(
125                         "Redis command 'HGET %s %s' failed: ") + ctx->errstr);
126         }
127
128         switch (reply->type) {
129         case REDIS_REPLY_STRING: {
130                 *block = std::string(reply->str, reply->len);
131                 // std::string copies the memory so this won't cause any problems
132                 freeReplyObject(reply);
133                 return;
134         }
135         case REDIS_REPLY_ERROR: {
136                 std::string errstr(reply->str, reply->len);
137                 freeReplyObject(reply);
138                 errorstream << "loadBlock: loading block " << PP(pos)
139                         << " failed: " << errstr << std::endl;
140                 throw DatabaseException(std::string(
141                         "Redis command 'HGET %s %s' errored: ") + errstr);
142         }
143         case REDIS_REPLY_NIL: {
144                 *block = "";
145                 // block not found in database
146                 freeReplyObject(reply);
147                 return;
148         }
149         }
150
151         errorstream << "loadBlock: loading block " << PP(pos)
152                 << " returned invalid reply type " << reply->type
153                 << ": " << std::string(reply->str, reply->len) << std::endl;
154         freeReplyObject(reply);
155         throw DatabaseException(std::string(
156                 "Redis command 'HGET %s %s' gave invalid reply."));
157 }
158
159 bool Database_Redis::deleteBlock(const v3s16 &pos)
160 {
161         std::string tmp = i64tos(getBlockAsInteger(pos));
162
163         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
164                 "HDEL %s %s", hash.c_str(), tmp.c_str()));
165         if (!reply) {
166                 throw DatabaseException(std::string(
167                         "Redis command 'HDEL %s %s' failed: ") + ctx->errstr);
168         } else if (reply->type == REDIS_REPLY_ERROR) {
169                 warningstream << "deleteBlock: deleting block " << PP(pos)
170                         << " failed: " << std::string(reply->str, reply->len) << std::endl;
171                 freeReplyObject(reply);
172                 return false;
173         }
174
175         freeReplyObject(reply);
176         return true;
177 }
178
179 void Database_Redis::listAllLoadableBlocks(std::vector<v3s16> &dst)
180 {
181         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HKEYS %s", hash.c_str()));
182         if (!reply) {
183                 throw DatabaseException(std::string(
184                         "Redis command 'HKEYS %s' failed: ") + ctx->errstr);
185         }
186         switch (reply->type) {
187         case REDIS_REPLY_ARRAY:
188                 dst.reserve(reply->elements);
189                 for (size_t i = 0; i < reply->elements; i++) {
190                         assert(reply->element[i]->type == REDIS_REPLY_STRING);
191                         dst.push_back(getIntegerAsBlock(stoi64(reply->element[i]->str)));
192                 }
193                 break;
194         case REDIS_REPLY_ERROR:
195                 throw DatabaseException(std::string(
196                         "Failed to get keys from database: ") +
197                         std::string(reply->str, reply->len));
198         }
199         freeReplyObject(reply);
200 }
201
202 #endif // USE_REDIS
203