Revert "Plantlike: Fix visual_scale being applied squared (#5115)"
[oweals/minetest.git] / src / 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 }
57
58 Database_Redis::~Database_Redis()
59 {
60         redisFree(ctx);
61 }
62
63 void Database_Redis::beginSave() {
64         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "MULTI"));
65         if (!reply) {
66                 throw DatabaseException(std::string(
67                         "Redis command 'MULTI' failed: ") + ctx->errstr);
68         }
69         freeReplyObject(reply);
70 }
71
72 void Database_Redis::endSave() {
73         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "EXEC"));
74         if (!reply) {
75                 throw DatabaseException(std::string(
76                         "Redis command 'EXEC' failed: ") + ctx->errstr);
77         }
78         freeReplyObject(reply);
79 }
80
81 bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data)
82 {
83         std::string tmp = i64tos(getBlockAsInteger(pos));
84
85         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HSET %s %s %b",
86                         hash.c_str(), tmp.c_str(), data.c_str(), data.size()));
87         if (!reply) {
88                 warningstream << "saveBlock: redis command 'HSET' failed on "
89                         "block " << PP(pos) << ": " << ctx->errstr << std::endl;
90                 freeReplyObject(reply);
91                 return false;
92         }
93
94         if (reply->type == REDIS_REPLY_ERROR) {
95                 warningstream << "saveBlock: saving block " << PP(pos)
96                         << " failed: " << std::string(reply->str, reply->len) << std::endl;
97                 freeReplyObject(reply);
98                 return false;
99         }
100
101         freeReplyObject(reply);
102         return true;
103 }
104
105 void Database_Redis::loadBlock(const v3s16 &pos, std::string *block)
106 {
107         std::string tmp = i64tos(getBlockAsInteger(pos));
108         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
109                         "HGET %s %s", hash.c_str(), tmp.c_str()));
110
111         if (!reply) {
112                 throw DatabaseException(std::string(
113                         "Redis command 'HGET %s %s' failed: ") + ctx->errstr);
114         }
115
116         switch (reply->type) {
117         case REDIS_REPLY_STRING: {
118                 *block = std::string(reply->str, reply->len);
119                 // std::string copies the memory so this won't cause any problems
120                 freeReplyObject(reply);
121                 return;
122         }
123         case REDIS_REPLY_ERROR: {
124                 std::string errstr(reply->str, reply->len);
125                 freeReplyObject(reply);
126                 errorstream << "loadBlock: loading block " << PP(pos)
127                         << " failed: " << errstr << std::endl;
128                 throw DatabaseException(std::string(
129                         "Redis command 'HGET %s %s' errored: ") + errstr);
130         }
131         case REDIS_REPLY_NIL: {
132                 *block = "";
133                 // block not found in database
134                 freeReplyObject(reply);
135                 return;
136         }
137         }
138
139         errorstream << "loadBlock: loading block " << PP(pos)
140                 << " returned invalid reply type " << reply->type
141                 << ": " << std::string(reply->str, reply->len) << std::endl;
142         freeReplyObject(reply);
143         throw DatabaseException(std::string(
144                 "Redis command 'HGET %s %s' gave invalid reply."));
145 }
146
147 bool Database_Redis::deleteBlock(const v3s16 &pos)
148 {
149         std::string tmp = i64tos(getBlockAsInteger(pos));
150
151         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
152                 "HDEL %s %s", hash.c_str(), tmp.c_str()));
153         if (!reply) {
154                 throw DatabaseException(std::string(
155                         "Redis command 'HDEL %s %s' failed: ") + ctx->errstr);
156         } else if (reply->type == REDIS_REPLY_ERROR) {
157                 warningstream << "deleteBlock: deleting block " << PP(pos)
158                         << " failed: " << std::string(reply->str, reply->len) << std::endl;
159                 freeReplyObject(reply);
160                 return false;
161         }
162
163         freeReplyObject(reply);
164         return true;
165 }
166
167 void Database_Redis::listAllLoadableBlocks(std::vector<v3s16> &dst)
168 {
169         redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HKEYS %s", hash.c_str()));
170         if (!reply) {
171                 throw DatabaseException(std::string(
172                         "Redis command 'HKEYS %s' failed: ") + ctx->errstr);
173         }
174         switch (reply->type) {
175         case REDIS_REPLY_ARRAY:
176                 dst.reserve(reply->elements);
177                 for (size_t i = 0; i < reply->elements; i++) {
178                         assert(reply->element[i]->type == REDIS_REPLY_STRING);
179                         dst.push_back(getIntegerAsBlock(stoi64(reply->element[i]->str)));
180                 }
181                 break;
182         case REDIS_REPLY_ERROR:
183                 throw DatabaseException(std::string(
184                         "Failed to get keys from database: ") +
185                         std::string(reply->str, reply->len));
186         }
187         freeReplyObject(reply);
188 }
189
190 #endif // USE_REDIS
191