diff --git a/bukkit/src/main/resources/config.yml b/bukkit/src/main/resources/config.yml index 89159830f..30a8bc60b 100644 --- a/bukkit/src/main/resources/config.yml +++ b/bukkit/src/main/resources/config.yml @@ -268,6 +268,16 @@ redis: address: localhost username: '' password: '' + # Settings for Redis Sentinel. + # Sentinel provides high availability for Redis by monitoring master/replica instances. + # Port 26379 is used by default for sentinel nodes. + sentinel: + enabled: false + master: mymaster + addresses: + - localhost:26379 + username: '' + password: '' # Settings for Nats. # Port 4222 is used by default; set address to "host:port" if differs diff --git a/bukkit/src/main/resources/luckperms.commodore b/bukkit/src/main/resources/luckperms.commodore index 9d9455bba..15b030924 100644 --- a/bukkit/src/main/resources/luckperms.commodore +++ b/bukkit/src/main/resources/luckperms.commodore @@ -543,7 +543,9 @@ luckperms { editor; listmembers { - page brigadier:integer; + page brigadier:integer { + context brigadier:string greedy_phrase; + } } setweight { weight brigadier:integer; diff --git a/bungee/src/main/resources/config.yml b/bungee/src/main/resources/config.yml index 878b9a82c..9b6a395ff 100644 --- a/bungee/src/main/resources/config.yml +++ b/bungee/src/main/resources/config.yml @@ -266,6 +266,16 @@ redis: address: localhost username: '' password: '' + # Settings for Redis Sentinel. + # Sentinel provides high availability for Redis by monitoring master/replica instances. + # Port 26379 is used by default for sentinel nodes. + sentinel: + enabled: false + master: mymaster + addresses: + - localhost:26379 + username: '' + password: '' # Settings for Nats. # Port 4222 is used by default; set address to "host:port" if differs diff --git a/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java b/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java index cc5050321..ecebaed73 100644 --- a/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java +++ b/common/src/main/java/me/lucko/luckperms/common/command/spec/CommandSpec.java @@ -132,7 +132,8 @@ public enum CommandSpec { GROUP_INFO, GROUP_LISTMEMBERS( - arg("page", false) + arg("page", false), + arg("context...", false) ), GROUP_SETWEIGHT( arg("weight", true) diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java b/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java index 6646dfe54..1cc50ddac 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/group/GroupListMembers.java @@ -28,9 +28,12 @@ import com.google.common.collect.Maps; import me.lucko.luckperms.common.cache.LoadingMap; import me.lucko.luckperms.common.command.abstraction.ChildCommand; +import me.lucko.luckperms.common.command.abstraction.CommandException; import me.lucko.luckperms.common.command.access.ArgumentPermissions; import me.lucko.luckperms.common.command.access.CommandPermission; import me.lucko.luckperms.common.command.spec.CommandSpec; +import me.lucko.luckperms.common.command.tabcomplete.TabCompleter; +import me.lucko.luckperms.common.command.tabcomplete.TabCompletions; import me.lucko.luckperms.common.command.utils.ArgumentList; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.model.Group; @@ -46,6 +49,7 @@ import me.lucko.luckperms.common.storage.misc.NodeEntry; import me.lucko.luckperms.common.util.Iterators; import me.lucko.luckperms.common.util.Predicates; +import net.luckperms.api.context.ImmutableContextSet; import net.luckperms.api.node.types.InheritanceNode; import java.util.ArrayList; @@ -57,11 +61,11 @@ public class GroupListMembers extends ChildCommand { public GroupListMembers() { - super(CommandSpec.GROUP_LISTMEMBERS, "listmembers", CommandPermission.GROUP_LIST_MEMBERS, Predicates.notInRange(0, 1)); + super(CommandSpec.GROUP_LISTMEMBERS, "listmembers", CommandPermission.GROUP_LIST_MEMBERS, Predicates.notInRange(0, 2)); } @Override - public void execute(LuckPermsPlugin plugin, Sender sender, Group target, ArgumentList args, String label) { + public void execute(LuckPermsPlugin plugin, Sender sender, Group target, ArgumentList args, String label) throws CommandException { if (ArgumentPermissions.checkViewPerms(plugin, sender, getPermission().get(), target)) { Message.COMMAND_NO_PERMISSION.send(sender); return; @@ -70,11 +74,13 @@ public void execute(LuckPermsPlugin plugin, Sender sender, Group target, Argumen InheritanceNode node = Inheritance.builder(target.getName()).build(); ConstraintNodeMatcher matcher = StandardNodeMatchers.key(node); int page = args.getIntOrDefault(0, 1); + ImmutableContextSet context = args.getContextOrEmpty(1); Message.SEARCH_SEARCHING_MEMBERS.send(sender, target.getName()); List> matchedUsers = plugin.getStorage().searchUserNodes(matcher).join().stream() .filter(n -> n.getNode().getValue()) + .filter(n -> context.isEmpty() || n.getNode().getContexts().isSatisfiedBy(context)) .collect(Collectors.toList()); // special handling for default group @@ -92,6 +98,7 @@ public void execute(LuckPermsPlugin plugin, Sender sender, Group target, Argumen List> matchedGroups = plugin.getStorage().searchGroupNodes(matcher).join().stream() .filter(n -> n.getNode().getValue()) + .filter(n -> context.isEmpty() || n.getNode().getContexts().isSatisfiedBy(context)) .collect(Collectors.toList()); int users = matchedUsers.size(); @@ -134,4 +141,11 @@ private static > void sendResult(Sender sender, List tabComplete(LuckPermsPlugin plugin, Sender sender, ArgumentList args) { + return TabCompleter.create() + .from(1, TabCompletions.contexts(plugin)) + .complete(args); + } } diff --git a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java index a7ce1e80f..c7d1d9b77 100644 --- a/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java +++ b/common/src/main/java/me/lucko/luckperms/common/config/ConfigKeys.java @@ -705,6 +705,31 @@ private ConfigKeys() {} */ public static final ConfigKey REDIS_SSL = notReloadable(booleanKey("redis.ssl", false)); + /** + * If redis sentinel is enabled + */ + public static final ConfigKey REDIS_SENTINEL_ENABLED = notReloadable(booleanKey("redis.sentinel.enabled", false)); + + /** + * The name of the redis sentinel master + */ + public static final ConfigKey REDIS_SENTINEL_MASTER = notReloadable(stringKey("redis.sentinel.master", "mymaster")); + + /** + * The addresses of the redis sentinel nodes + */ + public static final ConfigKey> REDIS_SENTINEL_ADDRESSES = notReloadable(stringListKey("redis.sentinel.addresses", ImmutableList.of())); + + /** + * The username to connect to the redis sentinel nodes with, or an empty string if it should use default + */ + public static final ConfigKey REDIS_SENTINEL_USERNAME = notReloadable(stringKey("redis.sentinel.username", "")); + + /** + * The password in use by the redis sentinel nodes, or an empty string if there is no password + */ + public static final ConfigKey REDIS_SENTINEL_PASSWORD = notReloadable(stringKey("redis.sentinel.password", "")); + /** * If nats messaging is enabled */ diff --git a/common/src/main/java/me/lucko/luckperms/common/messaging/MessagingFactory.java b/common/src/main/java/me/lucko/luckperms/common/messaging/MessagingFactory.java index dda65225b..e9ec391a2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/messaging/MessagingFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/messaging/MessagingFactory.java @@ -211,7 +211,21 @@ private class RedisMessengerProvider implements MessengerProvider { } boolean ssl = config.get(ConfigKeys.REDIS_SSL); - if (!addresses.isEmpty()) { + boolean sentinelEnabled = config.get(ConfigKeys.REDIS_SENTINEL_ENABLED); + if (sentinelEnabled) { + // redis sentinel + String masterName = config.get(ConfigKeys.REDIS_SENTINEL_MASTER); + List sentinelAddresses = config.get(ConfigKeys.REDIS_SENTINEL_ADDRESSES); + String sentinelUsername = config.get(ConfigKeys.REDIS_SENTINEL_USERNAME); + String sentinelPassword = config.get(ConfigKeys.REDIS_SENTINEL_PASSWORD); + if (sentinelUsername.isEmpty()) { + sentinelUsername = null; + } + if (sentinelPassword.isEmpty()) { + sentinelPassword = null; + } + redis.init(masterName, sentinelAddresses, username, password, ssl, sentinelUsername, sentinelPassword); + } else if (!addresses.isEmpty()) { // redis cluster addresses = new ArrayList<>(addresses); if (address != null) { diff --git a/common/src/main/java/me/lucko/luckperms/common/messaging/redis/RedisMessenger.java b/common/src/main/java/me/lucko/luckperms/common/messaging/redis/RedisMessenger.java index 199cb2843..e10d705c2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/messaging/redis/RedisMessenger.java +++ b/common/src/main/java/me/lucko/luckperms/common/messaging/redis/RedisMessenger.java @@ -36,6 +36,7 @@ import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisPooled; import redis.clients.jedis.JedisPubSub; +import redis.clients.jedis.JedisSentineled; import redis.clients.jedis.Protocol; import redis.clients.jedis.UnifiedJedis; @@ -48,6 +49,7 @@ */ public class RedisMessenger implements Messenger { private static final String CHANNEL = "luckperms:update"; + private static final int SENTINEL_DEFAULT_PORT = 26379; private final LuckPermsPlugin plugin; private final IncomingMessageConsumer consumer; @@ -70,6 +72,13 @@ public void init(String address, String username, String password, boolean ssl) this.init(new JedisPooled(parseAddress(address), jedisConfig(username, password, ssl))); } + public void init(String masterName, List sentinelAddresses, String username, String password, boolean ssl, String sentinelUsername, String sentinelPassword) { + Set sentinels = sentinelAddresses.stream() + .map(addr -> parseAddress(addr, SENTINEL_DEFAULT_PORT)) + .collect(Collectors.toSet()); + this.init(new JedisSentineled(masterName, jedisConfig(username, password, ssl), sentinels, jedisConfig(sentinelUsername, sentinelPassword, ssl))); + } + private void init(UnifiedJedis jedis) { this.jedis = jedis; this.sub = new Subscription(this); @@ -86,9 +95,13 @@ private static JedisClientConfig jedisConfig(String username, String password, b } private static HostAndPort parseAddress(String address) { + return parseAddress(address, Protocol.DEFAULT_PORT); + } + + private static HostAndPort parseAddress(String address, int defaultPort) { me.lucko.luckperms.common.util.HostAndPort hostAndPort = new me.lucko.luckperms.common.util.HostAndPort(address) .requireBracketsForIPv6() - .withDefaultPort(Protocol.DEFAULT_PORT); + .withDefaultPort(defaultPort); String host = hostAndPort.getHost(); int port = hostAndPort.getPort(); return new HostAndPort(host, port); @@ -162,6 +175,8 @@ private boolean isRedisAlive() { return !((JedisPooled) jedis).getPool().isClosed(); } else if (jedis instanceof JedisCluster) { return !((JedisCluster) jedis).getClusterNodes().isEmpty(); + } else if (jedis instanceof JedisSentineled) { + return true; } else { throw new RuntimeException("Unknown jedis type: " + jedis.getClass().getName()); } diff --git a/common/src/main/java/me/lucko/luckperms/common/messaging/sql/AbstractSqlMessenger.java b/common/src/main/java/me/lucko/luckperms/common/messaging/sql/AbstractSqlMessenger.java index c9488373d..0f1bd6ef1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/messaging/sql/AbstractSqlMessenger.java +++ b/common/src/main/java/me/lucko/luckperms/common/messaging/sql/AbstractSqlMessenger.java @@ -59,7 +59,7 @@ protected AbstractSqlMessenger(IncomingMessageConsumer consumer) { public void init() throws SQLException { try (Connection c = getConnection()) { // init table - String createStatement = "CREATE TABLE IF NOT EXISTS `" + getTableName() + "` (`id` INT AUTO_INCREMENT NOT NULL, `time` TIMESTAMP NOT NULL, `msg` TEXT NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET = utf8mb4"; + String createStatement = "CREATE TABLE IF NOT EXISTS `" + getTableName() + "` (`id` INT AUTO_INCREMENT NOT NULL, `time` TIMESTAMP NOT NULL, `msg` TEXT NOT NULL, PRIMARY KEY (`id`), KEY (`time`)) DEFAULT CHARSET = utf8mb4"; try (Statement s = c.createStatement()) { try { s.execute(createStatement); @@ -73,6 +73,18 @@ public void init() throws SQLException { } } + // add index for time column if it doesn't already exist + try (PreparedStatement ps = c.prepareStatement("SELECT 1 FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'time' LIMIT 1")) { + ps.setString(1, getTableName()); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + try (Statement s = c.createStatement()) { + s.execute("CREATE INDEX `time` ON `" + getTableName() + "` (`time`)"); + } + } + } + } + // pull last id try (PreparedStatement ps = c.prepareStatement("SELECT MAX(`id`) as `latest` FROM `" + getTableName() + "`")) { try (ResultSet rs = ps.executeQuery()) { @@ -112,7 +124,7 @@ public void pollMessages() { } try (Connection c = getConnection()) { - try (PreparedStatement ps = c.prepareStatement("SELECT `id`, `msg` FROM `" + getTableName() + "` WHERE `id` > ? AND (NOW() - `time` < 30)")) { + try (PreparedStatement ps = c.prepareStatement("SELECT `id`, `msg` FROM `" + getTableName() + "` WHERE `id` > ? AND `time` > (NOW() - INTERVAL 30 SECOND)")) { ps.setLong(1, this.lastId); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { @@ -139,7 +151,7 @@ public void runHousekeeping() { } try (Connection c = getConnection()) { - try (PreparedStatement ps = c.prepareStatement("DELETE FROM `" + getTableName() + "` WHERE (NOW() - `time` > 60)")) { + try (PreparedStatement ps = c.prepareStatement("DELETE FROM `" + getTableName() + "` WHERE `time` < (NOW() - INTERVAL 60 SECOND)")) { ps.execute(); } } catch (SQLException e) { diff --git a/common/src/main/resources/luckperms_en.properties b/common/src/main/resources/luckperms_en.properties index bc5117f7b..1928366e3 100644 --- a/common/src/main/resources/luckperms_en.properties +++ b/common/src/main/resources/luckperms_en.properties @@ -463,6 +463,7 @@ luckperms.usage.user-clone.argument.user=the name/uuid of the user to clone onto luckperms.usage.group-info.description=Gives info about the group luckperms.usage.group-listmembers.description=Show the users/groups who inherit from this group luckperms.usage.group-listmembers.argument.page=the page to view +luckperms.usage.group-listmembers.argument.context=the context to filter members by luckperms.usage.group-setweight.description=Set the groups weight luckperms.usage.group-setweight.argument.weight=the weight to set luckperms.usage.group-set-display-name.description=Set the groups display name diff --git a/fabric/src/main/resources/luckperms.conf b/fabric/src/main/resources/luckperms.conf index 4fb177ba1..65b27572f 100644 --- a/fabric/src/main/resources/luckperms.conf +++ b/fabric/src/main/resources/luckperms.conf @@ -271,6 +271,16 @@ redis { address = "localhost" username = "" password = "" + # Settings for Redis Sentinel. + # Sentinel provides high availability for Redis by monitoring master/replica instances. + # Port 26379 is used by default for sentinel nodes. + sentinel { + enabled = false + master = "mymaster" + addresses = ["localhost:26379"] + username = "" + password = "" + } } # Settings for nats. diff --git a/forge/src/main/resources/luckperms.conf b/forge/src/main/resources/luckperms.conf index dce36da4a..9a5550225 100644 --- a/forge/src/main/resources/luckperms.conf +++ b/forge/src/main/resources/luckperms.conf @@ -269,6 +269,16 @@ redis { address = "localhost" username = "" password = "" + # Settings for Redis Sentinel. + # Sentinel provides high availability for Redis by monitoring master/replica instances. + # Port 26379 is used by default for sentinel nodes. + sentinel { + enabled = false + master = "mymaster" + addresses = ["localhost:26379"] + username = "" + password = "" + } } # Settings for nats. diff --git a/neoforge/src/main/resources/luckperms.conf b/neoforge/src/main/resources/luckperms.conf index dce36da4a..9a5550225 100644 --- a/neoforge/src/main/resources/luckperms.conf +++ b/neoforge/src/main/resources/luckperms.conf @@ -269,6 +269,16 @@ redis { address = "localhost" username = "" password = "" + # Settings for Redis Sentinel. + # Sentinel provides high availability for Redis by monitoring master/replica instances. + # Port 26379 is used by default for sentinel nodes. + sentinel { + enabled = false + master = "mymaster" + addresses = ["localhost:26379"] + username = "" + password = "" + } } # Settings for nats. diff --git a/nukkit/src/main/resources/config.yml b/nukkit/src/main/resources/config.yml index fb00372c4..434c781a1 100644 --- a/nukkit/src/main/resources/config.yml +++ b/nukkit/src/main/resources/config.yml @@ -263,6 +263,16 @@ redis: address: localhost username: '' password: '' + # Settings for Redis Sentinel. + # Sentinel provides high availability for Redis by monitoring master/replica instances. + # Port 26379 is used by default for sentinel nodes. + sentinel: + enabled: false + master: mymaster + addresses: + - localhost:26379 + username: '' + password: '' # Settings for Nats. # Port 4222 is used by default; set address to "host:port" if differs diff --git a/settings.gradle b/settings.gradle index d0a3a06c7..c4e6bb3ca 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,7 +13,7 @@ pluginManagement { } plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version("0.8.0") + id("org.gradle.toolchains.foojay-resolver-convention") version("1.0.0") } rootProject.name = 'luckperms' diff --git a/sponge/src/main/resources/luckperms.conf b/sponge/src/main/resources/luckperms.conf index 14a99f461..fd109dc30 100644 --- a/sponge/src/main/resources/luckperms.conf +++ b/sponge/src/main/resources/luckperms.conf @@ -271,6 +271,16 @@ redis { address = "localhost" username = "" password = "" + # Settings for Redis Sentinel. + # Sentinel provides high availability for Redis by monitoring master/replica instances. + # Port 26379 is used by default for sentinel nodes. + sentinel { + enabled = false + master = "mymaster" + addresses = ["localhost:26379"] + username = "" + password = "" + } } # Settings for nats. diff --git a/standalone/src/main/resources/config.yml b/standalone/src/main/resources/config.yml index 091351d1c..2d8aa32d5 100644 --- a/standalone/src/main/resources/config.yml +++ b/standalone/src/main/resources/config.yml @@ -253,6 +253,16 @@ redis: address: localhost username: '' password: '' + # Settings for Redis Sentinel. + # Sentinel provides high availability for Redis by monitoring master/replica instances. + # Port 26379 is used by default for sentinel nodes. + sentinel: + enabled: false + master: mymaster + addresses: + - localhost:26379 + username: '' + password: '' # Settings for Nats. # Port 4222 is used by default; set address to "host:port" if differs diff --git a/velocity/src/main/resources/config.yml b/velocity/src/main/resources/config.yml index 805116d52..8fba524ef 100644 --- a/velocity/src/main/resources/config.yml +++ b/velocity/src/main/resources/config.yml @@ -257,6 +257,16 @@ redis: address: localhost username: '' password: '' + # Settings for Redis Sentinel. + # Sentinel provides high availability for Redis by monitoring master/replica instances. + # Port 26379 is used by default for sentinel nodes. + sentinel: + enabled: false + master: mymaster + addresses: + - localhost:26379 + username: '' + password: '' # Settings for Nats. # Port 4222 is used by default; set address to "host:port" if differs