package me.egg82.antivpn.storage; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import java.util.*; import java.util.concurrent.atomic.AtomicLong; import me.egg82.antivpn.core.*; import me.egg82.antivpn.services.StorageHandler; import me.egg82.antivpn.utils.ValidationUtil; import ninja.egg82.analytics.utils.JSONUtil; import org.json.simple.JSONObject; import org.json.simple.parser.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.*; import redis.clients.jedis.exceptions.JedisException; public class Redis implements Storage { private final Logger logger = LoggerFactory.getLogger(getClass()); private final LoadingCache<String, Long> longIPIDCache = Caffeine.newBuilder().build(this::getLongIPIDExpensive); private final LoadingCache<UUID, Long> longPlayerIDCache = Caffeine.newBuilder().build(this::getLongPlayerIDExpensive); private JedisPool pool; private AtomicLong lastVPNID; private AtomicLong lastMCLeaksID; private StorageHandler handler; protected String prefix = ""; private Redis() { } private volatile boolean closed = false; public void close() { closed = true; pool.close(); } public boolean isClosed() { return closed || pool.isClosed(); } public static Redis.Builder builder(StorageHandler handler) { return new Redis.Builder(handler); } public static class Builder { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Redis result = new Redis(); private final JedisPoolConfig config = new JedisPoolConfig(); private String address = "127.0.0.1"; private int port = 6379; private int timeout = 5000; private String pass = ""; private Builder(StorageHandler handler) { if (handler == null) { throw new IllegalArgumentException("handler cannot be null."); } result.handler = handler; } public Redis.Builder url(String address, int port, String prefix) { this.address = address; this.port = port; result.prefix = prefix; return this; } public Redis.Builder credentials(String pass) { this.pass = pass; return this; } public Redis.Builder poolSize(int min, int max) { config.setMinIdle(min); config.setMaxTotal(max); return this; } public Redis.Builder life(long lifetime, int timeout) { config.setMinEvictableIdleTimeMillis(lifetime); config.setMaxWaitMillis(timeout); this.timeout = timeout; return this; } public Redis build() throws StorageException { result.pool = new JedisPool(config, address, port, timeout, pass == null || pass.isEmpty() ? null : pass); // Warm up pool // https://partners-intl.aliyun.com/help/doc-detail/98726.htm warmup(result.pool); setDefaults(); result.lastVPNID = new AtomicLong(getLastVPNID()); result.lastMCLeaksID = new AtomicLong(getLastMCLeaksID()); return result; } private void setDefaults() { try (Jedis redis = result.pool.getResource()) { redis.setnx(result.prefix + "ips:idx", "0"); redis.setnx(result.prefix + "players:idx", "0"); redis.setnx(result.prefix + "vpn_values:idx", "0"); redis.setnx(result.prefix + "mcleaks_values:idx", "0"); } } private void warmup(JedisPool pool) throws StorageException { Jedis[] warmpupArr = new Jedis[config.getMinIdle()]; for (int i = 0; i < config.getMinIdle(); i++) { Jedis jedis; try { jedis = pool.getResource(); warmpupArr[i] = jedis; jedis.ping(); } catch (JedisException ex) { throw new StorageException(false, "Could not warm up Redis connection.", ex); } } // Two loops because we need to ensure we don't pull a freshly-created resource from the pool for (int i = 0; i < config.getMinIdle(); i++) { Jedis jedis; try { jedis = warmpupArr[i]; jedis.close(); } catch (JedisException ex) { throw new StorageException(false, "Could not close warmed Redis connection.", ex); } } } private long getLastVPNID() throws StorageException { try (Jedis redis = result.pool.getResource()) { long id = Long.parseLong(redis.get(result.prefix + "vpn_values:idx")); while (redis.exists(result.prefix + "vpn_values:" + (id + 1))) { id = redis.incr(result.prefix + "vpn_values:idx"); } return id; } catch (JedisException ex) { throw new StorageException(false, "Could not get last VPN ID."); } } private long getLastMCLeaksID() throws StorageException { try (Jedis redis = result.pool.getResource()) { long id = Long.parseLong(redis.get(result.prefix + "mcleaks_values:idx")); while (redis.exists(result.prefix + "mcleaks_values:" + (id + 1))) { id = redis.incr(result.prefix + "mcleaks_values:idx"); } return id; } catch (JedisException ex) { throw new StorageException(false, "Could not get last MCLeaks ID."); } } } public Set<VPNResult> getVPNQueue() throws StorageException { Set<VPNResult> retVal = new LinkedHashSet<>(); try (Jedis redis = pool.getResource()) { long max = Long.parseLong(redis.get(prefix + "vpn_values:idx")); while (redis.exists(prefix + "vpn_values:" + (max + 1))) { max = redis.incr(prefix + "vpn_values:idx"); } if (lastVPNID.get() >= max) { lastVPNID.set(max); return retVal; } long next; while ((next = lastVPNID.getAndIncrement()) < max) { VPNResult r = null; try { r = getVPNResult(next, redis.get(prefix + "vpn_values:" + next), redis); } catch (StorageException | JedisException | ParseException | ClassCastException ex) { logger.warn("Could not get VPN data for ID " + next + ".", ex); } if (r != null) { retVal.add(r); } } return retVal; } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public Set<MCLeaksResult> getMCLeaksQueue() throws StorageException { Set<MCLeaksResult> retVal = new LinkedHashSet<>(); try (Jedis redis = pool.getResource()) { long max = Long.parseLong(redis.get(prefix + "mcleaks_values:idx")); while (redis.exists(prefix + "mcleaks_values:" + (max + 1))) { max = redis.incr(prefix + "mcleaks_values:idx"); } if (lastMCLeaksID.get() >= max) { lastMCLeaksID.set(max); return retVal; } long next; while ((next = lastMCLeaksID.getAndIncrement()) < max) { MCLeaksResult r = null; try { r = getMCLeaksResult(next, redis.get(prefix + "mcleaks_values:" + next), redis); } catch (StorageException | JedisException | ParseException | ClassCastException ex) { logger.warn("Could not get MCLeaks data for ID " + next + ".", ex); } if (r != null) { retVal.add(r); } } return retVal; } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public VPNResult getVPNByIP(String ip, long cacheTimeMillis) throws StorageException { try (Jedis redis = pool.getResource()) { long longIPID = longIPIDCache.get(ip); try { return getVPNResultIP(longIPID, redis.get(prefix + "vpn_values:ip:" + longIPID), redis, cacheTimeMillis); } catch (StorageException | JedisException | ParseException | ClassCastException ex) { logger.warn("Could not get VPN data for IP " + longIPID + ".", ex); } return null; } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public MCLeaksResult getMCLeaksByPlayer(UUID playerID, long cacheTimeMillis) throws StorageException { try (Jedis redis = pool.getResource()) { long longPlayerID = longPlayerIDCache.get(playerID); try { return getMCLeaksResultPlayer(longPlayerID, redis.get(prefix + "mcleaks_values:player:" + longPlayerID), redis, cacheTimeMillis); } catch (StorageException | JedisException | ParseException | ClassCastException ex) { logger.warn("Could not get MCLeaks data for player " + longPlayerID + ".", ex); } return null; } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public PostVPNResult postVPN(String ip, Optional<Boolean> cascade, Optional<Double> consensus) throws StorageException { try (Jedis redis = pool.getResource()) { long longIPID = longIPIDCache.get(ip); JSONObject obj = new JSONObject(); obj.put("ipID", longIPID); obj.put("cascade", cascade.orElse(null)); obj.put("consensus", consensus.orElse(null)); long id; long created; do { do { id = redis.incr(prefix + "vpn_values:idx"); } while (redis.exists(prefix + "vpn_values:" + id)); created = getTime(redis.time()); obj.put("created", created); } while (redis.setnx(prefix + "vpn_values:" + id, obj.toJSONString()) == 0L); obj.remove("ipID"); obj.put("id", id); redis.rpush(prefix + "vpn:ip:" + longIPID, obj.toJSONString()); return new PostVPNResult( id, longIPID, ip, cascade, consensus, created ); } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public PostMCLeaksResult postMCLeaks(UUID playerID, boolean value) throws StorageException { try (Jedis redis = pool.getResource()) { long longPlayerID = longPlayerIDCache.get(playerID); JSONObject obj = new JSONObject(); obj.put("playerID", longPlayerID); obj.put("result", value); long id; long created; do { do { id = redis.incr(prefix + "mcleaks_values:idx"); } while (redis.exists(prefix + "mcleaks_values:" + id)); created = getTime(redis.time()); obj.put("created", created); } while (redis.setnx(prefix + "mcleaks_values:" + id, obj.toJSONString()) == 0L); obj.remove("playerID"); obj.put("id", id); redis.rpush(prefix + "mcleaks_values:player:" + longPlayerID, obj.toJSONString()); return new PostMCLeaksResult( id, longPlayerID, playerID, value, created ); } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public void setIPRaw(long longIPID, String ip) throws StorageException { try (Jedis redis = pool.getResource()) { JSONObject obj = new JSONObject(); obj.put("ip", ip); JSONObject obj2 = new JSONObject(); obj2.put("longID", longIPID); redis.mset( prefix + "ips:" + longIPID, obj.toJSONString(), prefix + "ip:" + ip, obj2.toJSONString() ); longIPIDCache.put(ip, longIPID); } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public void setPlayerRaw(long longPlayerID, UUID playerID) throws StorageException { try (Jedis redis = pool.getResource()) { JSONObject obj = new JSONObject(); obj.put("id", playerID.toString()); JSONObject obj2 = new JSONObject(); obj2.put("longID", longPlayerID); redis.mset( prefix + "players:" + longPlayerID, obj.toJSONString(), prefix + "players:" + playerID.toString(), obj2.toJSONString() ); longPlayerIDCache.put(playerID, longPlayerID); } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public void postVPNRaw(long id, long longIPID, Optional<Boolean> cascade, Optional<Double> consensus, long created) throws StorageException { try (Jedis redis = pool.getResource()) { JSONObject obj = new JSONObject(); obj.put("ipID", longIPID); obj.put("cascade", cascade.orElse(null)); obj.put("consensus", consensus.orElse(null)); obj.put("created", created); redis.set(prefix + "vpn_values:" + id, obj.toJSONString()); obj.remove("ipID"); obj.put("id", id); redis.rpush(prefix + "vpn_values:ip:" + longIPID, obj.toJSONString()); } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public void postMCLeaksRaw(long id, long longPlayerID, boolean value, long created) throws StorageException { try (Jedis redis = pool.getResource()) { JSONObject obj = new JSONObject(); obj.put("playerID", longPlayerID); obj.put("result", value); obj.put("created", created); redis.set(prefix + "mcleaks_values:" + id, obj.toJSONString()); obj.remove("playerID"); obj.put("id", id); redis.rpush(prefix + "mcleaks_values:player:" + longPlayerID, obj.toJSONString()); } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public long getLongIPID(String ip) { return longIPIDCache.get(ip); } public long getLongPlayerID(UUID playerID) { return longPlayerIDCache.get(playerID); } public Set<IPResult> dumpIPs(long begin, int size) throws StorageException { Set<IPResult> retVal = new LinkedHashSet<>(); try (Jedis redis = pool.getResource()) { for (long i = begin; i < begin + size; i++) { IPResult r = null; try { String json = redis.get(prefix + "ips:" + i); if (json == null) { continue; } JSONObject obj = JSONUtil.parseObject(json); String ip = (String) obj.get("ip"); if (!ValidationUtil.isValidIp(ip)) { logger.warn("Player ID " + i + " has an invalid IP \"" + ip + "\"."); continue; } r = new IPResult( i, ip ); } catch (ParseException | ClassCastException ex) { logger.warn("Could not get IP data for ID " + i + ".", ex); } if (r != null) { retVal.add(r); } } return retVal; } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public void loadIPs(Set<IPResult> ips, boolean truncate) throws StorageException { try (Jedis redis = pool.getResource()) { if (truncate) { deleteNamespace(redis, prefix + "ips:"); longIPIDCache.invalidateAll(); } long max = 0; for (IPResult ip : ips) { max = Math.max(max, ip.getLongIPID()); JSONObject obj = new JSONObject(); obj.put("ip", ip.getIP()); JSONObject obj2 = new JSONObject(); obj2.put("longID", ip.getLongIPID()); redis.mset( prefix + "ips:" + ip.getLongIPID(), obj.toJSONString(), prefix + "ips:" + ip.getIP(), obj2.toJSONString() ); longIPIDCache.put(ip.getIP(), ip.getLongIPID()); } redis.set(prefix + "ips:idx", String.valueOf(max)); } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public Set<PlayerResult> dumpPlayers(long begin, int size) throws StorageException { Set<PlayerResult> retVal = new LinkedHashSet<>(); try (Jedis redis = pool.getResource()) { for (long i = begin; i < begin + size; i++) { PlayerResult r = null; try { String json = redis.get(prefix + "players:" + i); if (json == null) { continue; } JSONObject obj = JSONUtil.parseObject(json); String pid = (String) obj.get("id"); if (!ValidationUtil.isValidUuid(pid)) { logger.warn("Player ID " + i + " has an invalid UUID \"" + pid + "\"."); continue; } r = new PlayerResult( i, UUID.fromString(pid) ); } catch (ParseException | ClassCastException ex) { logger.warn("Could not get player data for ID " + i + ".", ex); } if (r != null) { retVal.add(r); } } return retVal; } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public void loadPlayers(Set<PlayerResult> players, boolean truncate) throws StorageException { try (Jedis redis = pool.getResource()) { if (truncate) { deleteNamespace(redis, prefix + "players:"); longPlayerIDCache.invalidateAll(); } long max = 0; for (PlayerResult player : players) { max = Math.max(max, player.getLongPlayerID()); JSONObject obj = new JSONObject(); obj.put("id", player.getPlayerID().toString()); JSONObject obj2 = new JSONObject(); obj2.put("longID", player.getLongPlayerID()); redis.mset( prefix + "players:" + player.getLongPlayerID(), obj.toJSONString(), prefix + "players:" + player.getPlayerID().toString(), obj2.toJSONString() ); longPlayerIDCache.put(player.getPlayerID(), player.getLongPlayerID()); } redis.set(prefix + "players:idx", String.valueOf(max)); } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public Set<RawVPNResult> dumpVPNValues(long begin, int size) throws StorageException { Set<RawVPNResult> retVal = new LinkedHashSet<>(); try (Jedis redis = pool.getResource()) { for (long i = begin; i < begin + size; i++) { RawVPNResult r = null; try { String json = redis.get(prefix + "vpn_values:" + i); if (json == null) { continue; } JSONObject obj = JSONUtil.parseObject(json); r = new RawVPNResult( i, ((Number) obj.get("ipID")).longValue(), obj.get("cascade") == null ? Optional.empty() : Optional.of((Boolean) obj.get("cascade")), obj.get("consensus") == null ? Optional.empty() : Optional.of(((Number) obj.get("consensus")).doubleValue()), ((Number) obj.get("created")).longValue() ); } catch (ParseException | ClassCastException ex) { logger.warn("Could not get VPN data for ID " + i + ".", ex); } if (r != null) { retVal.add(r); } } return retVal; } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public void loadVPNValues(Set<RawVPNResult> values, boolean truncate) throws StorageException { try (Jedis redis = pool.getResource()) { if (truncate) { deleteNamespace(redis, prefix + "vpn_values:"); } long max = 0; for (RawVPNResult r : values) { max = Math.max(max, r.getID()); JSONObject obj = new JSONObject(); obj.put("ipID", r.getIPID()); obj.put("cascade", r.getCascade().orElse(null)); obj.put("consensus", r.getConsensus().orElse(null)); obj.put("created", r.getCreated()); redis.set(prefix + "vpn_values:" + r.getID(), obj.toJSONString()); obj.remove("ipID"); obj.put("id", r.getID()); redis.rpush(prefix + "vpn_values:ip:" + r.getIPID(), obj.toJSONString()); } redis.set(prefix + "vpn_values:idx", String.valueOf(max)); } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public Set<RawMCLeaksResult> dumpMCLeaksValues(long begin, int size) throws StorageException { Set<RawMCLeaksResult> retVal = new LinkedHashSet<>(); try (Jedis redis = pool.getResource()) { for (long i = begin; i < begin + size; i++) { RawMCLeaksResult r = null; try { String json = redis.get(prefix + "mcleaks_values:" + i); if (json == null) { continue; } JSONObject obj = JSONUtil.parseObject(json); r = new RawMCLeaksResult( i, ((Number) obj.get("playerID")).longValue(), (Boolean) obj.get("result"), ((Number) obj.get("created")).longValue() ); } catch (ParseException | ClassCastException ex) { logger.warn("Could not get MCLeaks data for ID " + i + ".", ex); } if (r != null) { retVal.add(r); } } return retVal; } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } public void loadMCLeaksValues(Set<RawMCLeaksResult> values, boolean truncate) throws StorageException { try (Jedis redis = pool.getResource()) { if (truncate) { deleteNamespace(redis, prefix + "mcleaks_values:"); } long max = 0; for (RawMCLeaksResult r : values) { max = Math.max(max, r.getID()); JSONObject obj = new JSONObject(); obj.put("playerID", r.getLongPlayerID()); obj.put("result", r.getValue()); obj.put("created", r.getCreated()); redis.set(prefix + "mcleaks_values:" + r.getID(), obj.toJSONString()); obj.remove("playerID"); obj.put("id", r.getID()); redis.rpush(prefix + "mcleaks_values:player:" + r.getLongPlayerID(), obj.toJSONString()); } redis.set(prefix + "mcleaks_values:idx", String.valueOf(max)); } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } private VPNResult getVPNResult(long id, String json, Jedis redis) throws StorageException, JedisException, ParseException, ClassCastException { if (json == null) { return null; } JSONObject obj = JSONUtil.parseObject(json); long longIPID = ((Number) obj.get("ipID")).longValue(); Optional<Boolean> cascade = obj.get("cascade") == null ? Optional.empty() : Optional.of((Boolean) obj.get("cascade")); Optional<Double> consensus = obj.get("consensus") == null ? Optional.empty() : Optional.of(((Number) obj.get("consensus")).doubleValue()); long created = ((Number) obj.get("created")).longValue(); String ipJSON = redis.get(prefix + "ips:" + longIPID); if (ipJSON == null) { throw new StorageException(false, "Could not get IP data for ID " + longIPID + "."); } JSONObject ipObj = JSONUtil.parseObject(ipJSON); String ip = (String) ipObj.get("ip"); if (!ValidationUtil.isValidIp(ip)) { redis.del(prefix + "ips:" + longIPID); throw new StorageException(false, "IP ID " + longIPID + " has an invalid IP \"" + ip + "\"."); } return new VPNResult( id, ip, cascade, consensus, created ); } private VPNResult getVPNResultIP(long longIPID, String json, Jedis redis, long cacheTimeMillis) throws StorageException, JedisException, ParseException, ClassCastException { if (json == null) { return null; } JSONObject obj = JSONUtil.parseObject(json); long id = ((Number) obj.get("id")).longValue(); Optional<Boolean> cascade = obj.get("cascade") == null ? Optional.empty() : Optional.of((Boolean) obj.get("cascade")); Optional<Double> consensus = obj.get("consensus") == null ? Optional.empty() : Optional.of(((Number) obj.get("consensus")).doubleValue()); long created = ((Number) obj.get("created")).longValue(); if (created < getTime(redis.time()) - cacheTimeMillis) { return null; } String ipJSON = redis.get(prefix + "ips:" + longIPID); if (ipJSON == null) { throw new StorageException(false, "Could not get IP data for ID " + longIPID + "."); } JSONObject ipObj = JSONUtil.parseObject(ipJSON); String ip = (String) ipObj.get("ip"); if (!ValidationUtil.isValidIp(ip)) { redis.del(prefix + "ips:" + longIPID); throw new StorageException(false, "IP ID " + longIPID + " has an invalid IP \"" + ip + "\"."); } return new VPNResult( id, ip, cascade, consensus, created ); } private MCLeaksResult getMCLeaksResult(long id, String json, Jedis redis) throws StorageException, JedisException, ParseException, ClassCastException { if (json == null) { return null; } JSONObject obj = JSONUtil.parseObject(json); long longPlayerID = ((Number) obj.get("playerID")).longValue(); boolean value = (Boolean) obj.get("result"); long created = ((Number) obj.get("created")).longValue(); String playerJSON = redis.get(prefix + "players:" + longPlayerID); if (playerJSON == null) { throw new StorageException(false, "Could not get player data for ID " + longPlayerID + "."); } JSONObject playerObj = JSONUtil.parseObject(playerJSON); String pid = (String) playerObj.get("id"); if (!ValidationUtil.isValidUuid(pid)) { redis.del(prefix + "players:" + longPlayerID); throw new StorageException(false, "Player ID " + longPlayerID + " has an invalid UUID \"" + pid + "\"."); } return new MCLeaksResult( id, UUID.fromString(pid), value, created ); } private MCLeaksResult getMCLeaksResultPlayer(long longPlayerID, String json, Jedis redis, long cacheTimeMillis) throws StorageException, JedisException, ParseException, ClassCastException { if (json == null) { return null; } JSONObject obj = JSONUtil.parseObject(json); long id = ((Number) obj.get("id")).longValue(); boolean value = (Boolean) obj.get("result"); long created = ((Number) obj.get("created")).longValue(); if (created < getTime(redis.time()) - cacheTimeMillis) { return null; } String playerJSON = redis.get(prefix + "players:" + longPlayerID); if (playerJSON == null) { throw new StorageException(false, "Could not get player data for ID " + longPlayerID + "."); } JSONObject playerObj = JSONUtil.parseObject(playerJSON); String pid = (String) playerObj.get("id"); if (!ValidationUtil.isValidUuid(pid)) { redis.del(prefix + "players:" + longPlayerID); throw new StorageException(false, "Player ID " + longPlayerID + " has an invalid UUID \"" + pid + "\"."); } return new MCLeaksResult( id, UUID.fromString(pid), value, created ); } private void deleteNamespace(Jedis redis, String namespace) throws JedisException { long current = 0; ScanParams params = new ScanParams(); params.match(namespace + "*"); params.count(50); ScanResult<String> result; do { result = redis.scan(String.valueOf(current), params); List<String> r = result.getResult(); if (!r.isEmpty()) { redis.del(r.toArray(new String[0])); } current = Long.parseLong(result.getCursor()); } while (!result.isCompleteIteration()); } private long getLongIPIDExpensive(String ip) throws StorageException { try (Jedis redis = pool.getResource()) { // A majority of the time there'll be an ID String json = redis.get(prefix + "ips:" + ip); if (json != null) { JSONObject obj = null; try { obj = JSONUtil.parseObject(json); } catch (ParseException | ClassCastException ex) { redis.del(prefix + "ips:" + ip); logger.warn("Could not parse IP data. Deleted key."); } if (obj != null) { return ((Number) obj.get("longID")).longValue(); } } // No ID, generate one JSONObject obj = new JSONObject(); obj.put("ip", ip); JSONObject obj2 = new JSONObject(); long id; do { do { id = redis.incr(prefix + "ips:idx"); } while (redis.exists(prefix + "ips:" + id)); obj2.put("longID", id); } while (redis.msetnx( prefix + "ips:" + id, obj.toJSONString(), prefix + "ips:" + ip, obj2.toJSONString() ) == 0L); handler.ipIDCreationCallback(ip, id, this); return id; } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } private long getLongPlayerIDExpensive(UUID uuid) throws StorageException { try (Jedis redis = pool.getResource()) { // A majority of the time there'll be an ID String json = redis.get(prefix + "players:" + uuid.toString()); if (json != null) { JSONObject obj = null; try { obj = JSONUtil.parseObject(json); } catch (ParseException | ClassCastException ex) { redis.del(prefix + "players:" + uuid.toString()); logger.warn("Could not parse player data. Deleted key."); } if (obj != null) { return ((Number) obj.get("longID")).longValue(); } } // No ID, generate one JSONObject obj = new JSONObject(); obj.put("id", uuid.toString()); JSONObject obj2 = new JSONObject(); long id; do { do { id = redis.incr(prefix + "players:idx"); } while (redis.exists(prefix + "players:" + id)); obj2.put("longID", id); } while (redis.msetnx( prefix + "players:" + id, obj.toJSONString(), prefix + "players:" + uuid.toString(), obj2.toJSONString() ) == 0L); handler.playerIDCreationCallback(uuid, id, this); return id; } catch (JedisException ex) { throw new StorageException(isAutomaticallyRecoverable(ex), ex); } } private boolean isAutomaticallyRecoverable(JedisException ex) { if ( ex.getMessage().startsWith("Failed connecting") || ex.getMessage().contains("broken connection") ) { return true; } return false; } // Redis returns a list // o[0] = unix time in seconds // o[1] = microseconds since last second // Therefore, to get unix time in millis we multiply seconds by 1000, divide microseconds by 1000, and add them together private long getTime(List<String> o) { return Long.parseLong(o.get(0)) * 1000L + Long.parseLong(o.get(1)) / 1000L; } }