diff --git a/api/src/main/java/me/lucko/luckperms/api/PermissionHolder.java b/api/src/main/java/me/lucko/luckperms/api/PermissionHolder.java index 9e82d4c31..8916adee6 100644 --- a/api/src/main/java/me/lucko/luckperms/api/PermissionHolder.java +++ b/api/src/main/java/me/lucko/luckperms/api/PermissionHolder.java @@ -475,6 +475,31 @@ public interface PermissionHolder { @Nonnull DataMutateResult setPermission(@Nonnull Node node); + /** + * Sets a permission node for the permission holder. + * + *

Although this method is named setPermission, it can be used for all node types.

+ * + *

The effect of this mutate operation will not persist in storage unless changes are + * explicitly saved. If changes are not saved, the effect will only be observed until the next + * time the holders permission data is (re)loaded. Changes to {@link User}s should be saved + * using {@link UserManager#saveUser(User)}, and changes to {@link Group}s should be saved + * using {@link GroupManager#saveGroup(Group)}.

+ * + *

Before making changes to a user or group, it may be a good idea to load a fresh copy of + * the backing data from the storage if you haven't done so already, to avoid overwriting changes + * made already. This can be done via {@link UserManager#loadUser(UUID)} or + * {@link GroupManager#loadGroup(String)} respectively.

+ * + * @param node The node to be set + * @param temporaryMergeBehaviour The behaviour used to merge temporary permission entries + * @return the result of the operation + * @throws NullPointerException if the node is null + * @since 4.3 + */ + @Nonnull + TemporaryDataMutateResult setPermission(@Nonnull Node node, @Nonnull TemporaryMergeBehaviour temporaryMergeBehaviour); + /** * Sets a transient permission for the permission holder. * @@ -499,6 +524,31 @@ public interface PermissionHolder { @Nonnull DataMutateResult setTransientPermission(@Nonnull Node node); + /** + * Sets a transient permission for the permission holder. + * + *

A transient node is a permission that does not persist. + * Whenever a user logs out of the server, or the server restarts, this permission will + * disappear. It is never saved to the datastore, and therefore will not apply on other + * servers.

+ * + *

This is useful if you want to temporarily set a permission for a user while they're + * online, but don't want it to persist, and have to worry about removing it when they log + * out.

+ * + *

For unsetting a transient permission, see {@link #unsetTransientPermission(Node)}.

+ * + *

Although this method is named setTransientPermission, it can be used for all node types.

+ * + * @param node The node to be se + * @param temporaryMergeBehaviour The behaviour used to merge temporary permission entries + * @return the result of the operation + * @throws NullPointerException if the node is null + * @since 4.3 + */ + @Nonnull + TemporaryDataMutateResult setTransientPermission(@Nonnull Node node, @Nonnull TemporaryMergeBehaviour temporaryMergeBehaviour); + /** * Unsets a permission for the permission holder. * diff --git a/api/src/main/java/me/lucko/luckperms/api/TemporaryDataMutateResult.java b/api/src/main/java/me/lucko/luckperms/api/TemporaryDataMutateResult.java new file mode 100644 index 000000000..8600a0809 --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/TemporaryDataMutateResult.java @@ -0,0 +1,32 @@ +package me.lucko.luckperms.api; + +import javax.annotation.Nonnull; + +/** + * Extension of {@link DataMutateResult} for temporary set operations. + * + * @since 4.3 + */ +public interface TemporaryDataMutateResult { + + /** + * Gets the underlying result. + * + * @return the result + */ + @Nonnull + DataMutateResult getResult(); + + /** + * Gets the node that resulted from any {@link TemporaryMergeBehaviour} + * processing. + * + *

If no processing took place, the same instance will be returned by + * this method.

+ * + * @return the resultant node + */ + @Nonnull + Node getMergedNode(); + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/model/TemporaryModifier.java b/api/src/main/java/me/lucko/luckperms/api/TemporaryMergeBehaviour.java similarity index 56% rename from common/src/main/java/me/lucko/luckperms/common/model/TemporaryModifier.java rename to api/src/main/java/me/lucko/luckperms/api/TemporaryMergeBehaviour.java index 7caaa2782..5d6b1a8d7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/TemporaryModifier.java +++ b/api/src/main/java/me/lucko/luckperms/api/TemporaryMergeBehaviour.java @@ -23,26 +23,36 @@ * SOFTWARE. */ -package me.lucko.luckperms.common.model; +package me.lucko.luckperms.api; /** - * Controls how temporary permissions/parents/meta should be set + * Controls how the implementation should behave when new temporary nodes are set + * that would otherwise conflict with existing entries. + * + *

The default behaviour of {@link PermissionHolder#setPermission(Node)} is + * to return a result of {@link DataMutateResult#ALREADY_HAS} when an equivalent + * node is found. This can be replicated using {@link #FAIL_WITH_ALREADY_HAS}.

+ * + *

However, the {@link PermissionHolder#setPermission(Node, TemporaryMergeBehaviour)} + * method allows this behaviour to be customized for temporary permissions.

+ * + * @since 4.3 */ -public enum TemporaryModifier { +public enum TemporaryMergeBehaviour { /** - * Durations will be added to the existing expiry time of a permission + * Expiry durations will be added to the existing expiry time of a permission. */ - ACCUMULATE, + ADD_NEW_DURATION_TO_EXISTING, /** - * Durations will be replaced if the new duration is later than the current expiration + * Expiry durations will be replaced if the new duration is longer than the current one. */ - REPLACE, + REPLACE_EXISTING_IF_DURATION_LONGER, /** - * The command will just fail if you try to add another permission with the same expiry + * The operation will fail if an existing temporary node is present. */ - DENY + FAIL_WITH_ALREADY_HAS } diff --git a/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiPermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiPermissionHolder.java index 711ff279a..0ae371a66 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiPermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiPermissionHolder.java @@ -37,6 +37,8 @@ import me.lucko.luckperms.api.LocalizedNode; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.NodeEqualityPredicate; import me.lucko.luckperms.api.StandardNodeEquality; +import me.lucko.luckperms.api.TemporaryDataMutateResult; +import me.lucko.luckperms.api.TemporaryMergeBehaviour; import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.caching.CachedData; import me.lucko.luckperms.api.context.ContextSet; @@ -266,6 +268,14 @@ public class ApiPermissionHolder implements me.lucko.luckperms.api.PermissionHol return this.handle.setPermission(node); } + @Nonnull + @Override + public TemporaryDataMutateResult setPermission(@Nonnull Node node, @Nonnull TemporaryMergeBehaviour temporaryMergeBehaviour) { + Objects.requireNonNull(node, "node"); + Objects.requireNonNull(temporaryMergeBehaviour, "temporaryMergeBehaviour"); + return this.handle.setPermission(node, temporaryMergeBehaviour); + } + @Nonnull @Override public DataMutateResult setTransientPermission(@Nonnull Node node) { @@ -273,6 +283,14 @@ public class ApiPermissionHolder implements me.lucko.luckperms.api.PermissionHol return this.handle.setTransientPermission(node); } + @Nonnull + @Override + public TemporaryDataMutateResult setTransientPermission(@Nonnull Node node, @Nonnull TemporaryMergeBehaviour temporaryMergeBehaviour) { + Objects.requireNonNull(node, "node"); + Objects.requireNonNull(temporaryMergeBehaviour, "temporaryMergeBehaviour"); + return this.handle.setTransientPermission(node, temporaryMergeBehaviour); + } + @Nonnull @Override public DataMutateResult unsetPermission(@Nonnull Node node) { diff --git a/common/src/main/java/me/lucko/luckperms/common/command/utils/ArgumentParser.java b/common/src/main/java/me/lucko/luckperms/common/command/utils/ArgumentParser.java index 1247f2d5e..4fa822875 100644 --- a/common/src/main/java/me/lucko/luckperms/common/command/utils/ArgumentParser.java +++ b/common/src/main/java/me/lucko/luckperms/common/command/utils/ArgumentParser.java @@ -26,11 +26,11 @@ package me.lucko.luckperms.common.command.utils; import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.TemporaryMergeBehaviour; import me.lucko.luckperms.api.context.ImmutableContextSet; import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.command.abstraction.CommandException; import me.lucko.luckperms.common.commands.user.UserMainCommand; -import me.lucko.luckperms.common.model.TemporaryModifier; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; import me.lucko.luckperms.common.storage.DataConstraints; @@ -121,14 +121,27 @@ public class ArgumentParser { return unixTime < (System.currentTimeMillis() / 1000L); } - public static Optional parseTemporaryModifier(int index, List args) { + public static TemporaryMergeBehaviour parseTemporaryModifier(String s) { + switch (s.toLowerCase()) { + case "accumulate": + return TemporaryMergeBehaviour.ADD_NEW_DURATION_TO_EXISTING; + case "replace": + return TemporaryMergeBehaviour.REPLACE_EXISTING_IF_DURATION_LONGER; + case "deny": + return TemporaryMergeBehaviour.FAIL_WITH_ALREADY_HAS; + default: + throw new IllegalArgumentException("Unknown value: " + s); + } + } + + public static Optional parseTemporaryModifier(int index, List args) { if (index < 0 || index >= args.size()) { return Optional.empty(); } String s = args.get(index); try { - Optional ret = Optional.of(TemporaryModifier.valueOf(s.toUpperCase())); + Optional ret = Optional.of(parseTemporaryModifier(s)); args.remove(index); return ret; } catch (IllegalArgumentException e) { diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaAddTempChatMeta.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaAddTempChatMeta.java index d4570ef6b..799893967 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaAddTempChatMeta.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaAddTempChatMeta.java @@ -26,8 +26,8 @@ package me.lucko.luckperms.common.commands.generic.meta; import me.lucko.luckperms.api.ChatMetaType; -import me.lucko.luckperms.api.DataMutateResult; -import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.TemporaryDataMutateResult; +import me.lucko.luckperms.api.TemporaryMergeBehaviour; import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.actionlog.ExtendedLogEntry; import me.lucko.luckperms.common.command.CommandResult; @@ -43,7 +43,6 @@ import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.TemporaryModifier; import me.lucko.luckperms.common.node.factory.NodeFactory; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; @@ -55,7 +54,6 @@ import net.kyori.text.TextComponent; import net.kyori.text.event.HoverEvent; import java.util.List; -import java.util.Map; public class MetaAddTempChatMeta extends SharedSubCommand { private final ChatMetaType type; @@ -81,7 +79,7 @@ public class MetaAddTempChatMeta extends SharedSubCommand { int priority = ArgumentParser.parsePriority(0, args); String meta = ArgumentParser.parseString(1, args); long duration = ArgumentParser.parseDuration(2, args); - TemporaryModifier modifier = ArgumentParser.parseTemporaryModifier(3, args).orElseGet(() -> plugin.getConfiguration().get(ConfigKeys.TEMPORARY_ADD_BEHAVIOUR)); + TemporaryMergeBehaviour modifier = ArgumentParser.parseTemporaryModifier(3, args).orElseGet(() -> plugin.getConfiguration().get(ConfigKeys.TEMPORARY_ADD_BEHAVIOUR)); MutableContextSet context = ArgumentParser.parseContext(3, args, plugin); if (ArgumentPermissions.checkContext(plugin, sender, permission, context)) { @@ -89,10 +87,10 @@ public class MetaAddTempChatMeta extends SharedSubCommand { return CommandResult.NO_PERMISSION; } - Map.Entry ret = holder.setPermission(NodeFactory.buildChatMetaNode(this.type, priority, meta).setExpiry(duration).withExtraContext(context).build(), modifier); + TemporaryDataMutateResult ret = holder.setPermission(NodeFactory.buildChatMetaNode(this.type, priority, meta).setExpiry(duration).withExtraContext(context).build(), modifier); - if (ret.getKey().asBoolean()) { - duration = ret.getValue().getExpiryUnixTime(); + if (ret.getResult().asBoolean()) { + duration = ret.getMergedNode().getExpiryUnixTime(); TextComponent.Builder builder = Message.ADD_TEMP_CHATMETA_SUCCESS.asComponent(plugin.getLocaleManager(), holder.getFriendlyName(), this.type.name().toLowerCase(), meta, priority, DurationFormatter.LONG.formatDateDiff(duration), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder(); HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy( diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaSetTemp.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaSetTemp.java index 7e2ce344e..ce8d10ac1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaSetTemp.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaSetTemp.java @@ -27,6 +27,7 @@ package me.lucko.luckperms.common.commands.generic.meta; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.StandardNodeEquality; +import me.lucko.luckperms.api.TemporaryMergeBehaviour; import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.actionlog.ExtendedLogEntry; import me.lucko.luckperms.common.command.CommandResult; @@ -43,7 +44,6 @@ import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; import me.lucko.luckperms.common.model.NodeMapType; import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.TemporaryModifier; import me.lucko.luckperms.common.node.factory.NodeFactory; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; @@ -71,7 +71,7 @@ public class MetaSetTemp extends SharedSubCommand { String key = args.get(0); String value = args.get(1); long duration = ArgumentParser.parseDuration(2, args); - TemporaryModifier modifier = ArgumentParser.parseTemporaryModifier(3, args).orElseGet(() -> plugin.getConfiguration().get(ConfigKeys.TEMPORARY_ADD_BEHAVIOUR)); + TemporaryMergeBehaviour modifier = ArgumentParser.parseTemporaryModifier(3, args).orElseGet(() -> plugin.getConfiguration().get(ConfigKeys.TEMPORARY_ADD_BEHAVIOUR)); MutableContextSet context = ArgumentParser.parseContext(3, args, plugin); if (ArgumentPermissions.checkContext(plugin, sender, permission, context)) { @@ -92,7 +92,7 @@ public class MetaSetTemp extends SharedSubCommand { } holder.clearMetaKeys(key, context, true); - duration = holder.setPermission(n, modifier).getValue().getExpiryUnixTime(); + duration = holder.setPermission(n, modifier).getMergedNode().getExpiryUnixTime(); TextComponent.Builder builder = Message.SET_META_TEMP_SUCCESS.asComponent(plugin.getLocaleManager(), key, value, holder.getFriendlyName(), DurationFormatter.LONG.formatDateDiff(duration), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder(); HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy( diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaSetTempChatMeta.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaSetTempChatMeta.java index bc7d1789c..d2dde7980 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaSetTempChatMeta.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/meta/MetaSetTempChatMeta.java @@ -27,8 +27,8 @@ package me.lucko.luckperms.common.commands.generic.meta; import me.lucko.luckperms.api.ChatMetaType; import me.lucko.luckperms.api.Contexts; -import me.lucko.luckperms.api.DataMutateResult; -import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.TemporaryDataMutateResult; +import me.lucko.luckperms.api.TemporaryMergeBehaviour; import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.actionlog.ExtendedLogEntry; import me.lucko.luckperms.common.caching.type.MetaAccumulator; @@ -46,7 +46,6 @@ import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.TemporaryModifier; import me.lucko.luckperms.common.node.factory.NodeFactory; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; @@ -58,7 +57,6 @@ import net.kyori.text.TextComponent; import net.kyori.text.event.HoverEvent; import java.util.List; -import java.util.Map; import java.util.OptionalInt; public class MetaSetTempChatMeta extends SharedSubCommand { @@ -85,7 +83,7 @@ public class MetaSetTempChatMeta extends SharedSubCommand { int priority = ArgumentParser.parseIntOrElse(0, args, Integer.MIN_VALUE); String meta; long duration; - TemporaryModifier modifier; + TemporaryMergeBehaviour modifier; MutableContextSet context; if (priority == Integer.MIN_VALUE) { @@ -129,10 +127,10 @@ public class MetaSetTempChatMeta extends SharedSubCommand { } } - Map.Entry ret = holder.setPermission(NodeFactory.buildChatMetaNode(this.type, priority, meta).setExpiry(duration).withExtraContext(context).build(), modifier); + TemporaryDataMutateResult ret = holder.setPermission(NodeFactory.buildChatMetaNode(this.type, priority, meta).setExpiry(duration).withExtraContext(context).build(), modifier); - if (ret.getKey().asBoolean()) { - duration = ret.getValue().getExpiryUnixTime(); + if (ret.getResult().asBoolean()) { + duration = ret.getMergedNode().getExpiryUnixTime(); TextComponent.Builder builder = Message.ADD_TEMP_CHATMETA_SUCCESS.asComponent(plugin.getLocaleManager(), holder.getFriendlyName(), this.type.name().toLowerCase(), meta, priority, DurationFormatter.LONG.formatDateDiff(duration), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder(); HoverEvent event = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy( diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentAddTemp.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentAddTemp.java index edefd09ab..d47277c0b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentAddTemp.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/parent/ParentAddTemp.java @@ -25,8 +25,8 @@ package me.lucko.luckperms.common.commands.generic.parent; -import me.lucko.luckperms.api.DataMutateResult; -import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.TemporaryDataMutateResult; +import me.lucko.luckperms.api.TemporaryMergeBehaviour; import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.actionlog.ExtendedLogEntry; import me.lucko.luckperms.common.command.CommandResult; @@ -45,7 +45,6 @@ import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.TemporaryModifier; import me.lucko.luckperms.common.node.factory.NodeFactory; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; @@ -53,7 +52,6 @@ import me.lucko.luckperms.common.utils.DurationFormatter; import me.lucko.luckperms.common.utils.Predicates; import java.util.List; -import java.util.Map; public class ParentAddTemp extends SharedSubCommand { public ParentAddTemp(LocaleManager locale) { @@ -69,7 +67,7 @@ public class ParentAddTemp extends SharedSubCommand { String groupName = ArgumentParser.parseName(0, args); long duration = ArgumentParser.parseDuration(1, args); - TemporaryModifier modifier = ArgumentParser.parseTemporaryModifier(2, args).orElseGet(() -> plugin.getConfiguration().get(ConfigKeys.TEMPORARY_ADD_BEHAVIOUR)); + TemporaryMergeBehaviour modifier = ArgumentParser.parseTemporaryModifier(2, args).orElseGet(() -> plugin.getConfiguration().get(ConfigKeys.TEMPORARY_ADD_BEHAVIOUR)); MutableContextSet context = ArgumentParser.parseContext(2, args, plugin); Group group = StorageAssistant.loadGroup(groupName, sender, plugin, false); @@ -92,10 +90,10 @@ public class ParentAddTemp extends SharedSubCommand { return CommandResult.STATE_ERROR; } - Map.Entry ret = holder.setPermission(NodeFactory.buildGroupNode(group.getName()).setExpiry(duration).withExtraContext(context).build(), modifier); + TemporaryDataMutateResult ret = holder.setPermission(NodeFactory.buildGroupNode(group.getName()).setExpiry(duration).withExtraContext(context).build(), modifier); - if (ret.getKey().asBoolean()) { - duration = ret.getValue().getExpiryUnixTime(); + if (ret.getResult().asBoolean()) { + duration = ret.getMergedNode().getExpiryUnixTime(); Message.SET_TEMP_INHERIT_SUCCESS.send(sender, holder.getFriendlyName(), group.getFriendlyName(), DurationFormatter.LONG.formatDateDiff(duration), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)); ExtendedLogEntry.build().actor(sender).acted(holder) diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionSetTemp.java b/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionSetTemp.java index 1d1e749fe..2e8f6d4fd 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionSetTemp.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/generic/permission/PermissionSetTemp.java @@ -25,8 +25,8 @@ package me.lucko.luckperms.common.commands.generic.permission; -import me.lucko.luckperms.api.DataMutateResult; -import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.TemporaryDataMutateResult; +import me.lucko.luckperms.api.TemporaryMergeBehaviour; import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.actionlog.ExtendedLogEntry; import me.lucko.luckperms.common.command.CommandResult; @@ -44,7 +44,6 @@ import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.command.CommandSpec; import me.lucko.luckperms.common.locale.message.Message; import me.lucko.luckperms.common.model.PermissionHolder; -import me.lucko.luckperms.common.model.TemporaryModifier; import me.lucko.luckperms.common.node.factory.NodeFactory; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; import me.lucko.luckperms.common.sender.Sender; @@ -52,7 +51,6 @@ import me.lucko.luckperms.common.utils.DurationFormatter; import me.lucko.luckperms.common.utils.Predicates; import java.util.List; -import java.util.Map; public class PermissionSetTemp extends SharedSubCommand { public PermissionSetTemp(LocaleManager locale) { @@ -69,7 +67,7 @@ public class PermissionSetTemp extends SharedSubCommand { String node = ArgumentParser.parseString(0, args); boolean value = ArgumentParser.parseBoolean(1, args); long duration = ArgumentParser.parseDuration(2, args); - TemporaryModifier modifier = ArgumentParser.parseTemporaryModifier(3, args).orElseGet(() -> plugin.getConfiguration().get(ConfigKeys.TEMPORARY_ADD_BEHAVIOUR)); + TemporaryMergeBehaviour modifier = ArgumentParser.parseTemporaryModifier(3, args).orElseGet(() -> plugin.getConfiguration().get(ConfigKeys.TEMPORARY_ADD_BEHAVIOUR)); MutableContextSet context = ArgumentParser.parseContext(3, args, plugin); if (ArgumentPermissions.checkContext(plugin, sender, permission, context)) { @@ -82,10 +80,10 @@ public class PermissionSetTemp extends SharedSubCommand { return CommandResult.NO_PERMISSION; } - Map.Entry result = holder.setPermission(NodeFactory.builder(node).setValue(value).withExtraContext(context).setExpiry(duration).build(), modifier); + TemporaryDataMutateResult result = holder.setPermission(NodeFactory.builder(node).setValue(value).withExtraContext(context).setExpiry(duration).build(), modifier); - if (result.getKey().asBoolean()) { - duration = result.getValue().getExpiryUnixTime(); + if (result.getResult().asBoolean()) { + duration = result.getMergedNode().getExpiryUnixTime(); Message.SETPERMISSION_TEMP_SUCCESS.send(sender, node, value, holder.getFriendlyName(), DurationFormatter.LONG.formatDateDiff(duration), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)); ExtendedLogEntry.build().actor(sender).acted(holder) 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 2af532efb..dc1fdf45b 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 @@ -30,9 +30,11 @@ import com.google.common.collect.ImmutableMap; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.LookupSetting; +import me.lucko.luckperms.api.TemporaryMergeBehaviour; import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.api.metastacking.MetaStackDefinition; import me.lucko.luckperms.common.assignments.AssignmentRule; +import me.lucko.luckperms.common.command.utils.ArgumentParser; import me.lucko.luckperms.common.config.keys.BooleanKey; import me.lucko.luckperms.common.config.keys.CustomKey; import me.lucko.luckperms.common.config.keys.EnduringKey; @@ -42,7 +44,6 @@ import me.lucko.luckperms.common.config.keys.StringKey; import me.lucko.luckperms.common.graph.TraversalAlgorithm; import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition; import me.lucko.luckperms.common.metastacking.StandardStackElements; -import me.lucko.luckperms.common.model.TemporaryModifier; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.primarygroup.AllParentsByWeightHolder; import me.lucko.luckperms.common.primarygroup.ParentsByWeightHolder; @@ -133,13 +134,13 @@ public final class ConfigKeys { /** * Controls how temporary add commands should behave */ - public static final ConfigKey TEMPORARY_ADD_BEHAVIOUR = CustomKey.of(c -> { + public static final ConfigKey TEMPORARY_ADD_BEHAVIOUR = CustomKey.of(c -> { String option = c.getString("temporary-add-behaviour", "deny").toLowerCase(); if (!option.equals("deny") && !option.equals("replace") && !option.equals("accumulate")) { option = "deny"; } - return TemporaryModifier.valueOf(option.toUpperCase()); + return ArgumentParser.parseTemporaryModifier(option); }); /** diff --git a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java index c25775243..596a5a7da 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/PermissionHolder.java @@ -27,7 +27,6 @@ package me.lucko.luckperms.common.model; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import me.lucko.luckperms.api.Contexts; @@ -37,6 +36,8 @@ import me.lucko.luckperms.api.LookupSetting; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.NodeEqualityPredicate; import me.lucko.luckperms.api.StandardNodeEquality; +import me.lucko.luckperms.api.TemporaryDataMutateResult; +import me.lucko.luckperms.api.TemporaryMergeBehaviour; import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.api.context.ImmutableContextSet; @@ -67,6 +68,8 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Predicate; +import javax.annotation.Nonnull; + /** * Represents an object that can hold permissions, (a user or group) * @@ -519,22 +522,10 @@ public abstract class PermissionHolder { return InheritanceInfo.empty(); } - /** - * Check if the holder inherits a node - * - * @param node the node to check - * @param equalityPredicate how to match - * @return the Tristate result - */ public Tristate inheritsPermission(Node node, NodeEqualityPredicate equalityPredicate) { return searchForInheritedMatch(node, equalityPredicate).getResult(); } - /** - * Sets a permission node - * - * @param node the node to set - */ public DataMutateResult setPermission(Node node) { return setPermission(node, true); } @@ -555,85 +546,99 @@ public abstract class PermissionHolder { return DataMutateResult.SUCCESS; } - /** - * Sets a permission node, applying a temporary modifier if the node is temporary. - * @param node the node to set - * @param modifier the modifier to use for the operation - * @return the node that was actually set, respective of the modifier - */ - public Map.Entry setPermission(Node node, TemporaryModifier modifier) { - // If the node is temporary, we should take note of the modifier - if (node.isTemporary()) { - if (modifier == TemporaryModifier.ACCUMULATE) { - // Try to accumulate with an existing node - Optional existing = searchForMatch(NodeMapType.ENDURING, node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE); - - // An existing node was found - if (existing.isPresent()) { - Node previous = existing.get(); - - // Create a new node with the same properties, but add the expiry dates together - Node newNode = node.toBuilder().setExpiry(previous.getExpiryUnixTime() + node.getSecondsTilExpiry()).build(); - - // Remove the old node & add the new one. - ImmutableCollection before = enduringData().immutable().values(); - this.enduringNodes.replace(newNode, previous); - invalidateCache(); - ImmutableCollection after = enduringData().immutable().values(); - - this.plugin.getEventFactory().handleNodeAdd(newNode, this, before, after); - return Maps.immutableEntry(DataMutateResult.SUCCESS, newNode); - } - - } else if (modifier == TemporaryModifier.REPLACE) { - // Try to replace an existing node - Optional existing = searchForMatch(NodeMapType.ENDURING, node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE); - - // An existing node was found - if (existing.isPresent()) { - Node previous = existing.get(); - - // Only replace if the new expiry time is greater than the old one. - if (node.getExpiryUnixTime() > previous.getExpiryUnixTime()) { - - ImmutableCollection before = enduringData().immutable().values(); - this.enduringNodes.replace(node, previous); - invalidateCache(); - ImmutableCollection after = enduringData().immutable().values(); - - this.plugin.getEventFactory().handleNodeAdd(node, this, before, after); - return Maps.immutableEntry(DataMutateResult.SUCCESS, node); - } - } - } - - // DENY behaviour is the default anyways. + public TemporaryDataMutateResult setPermission(Node node, TemporaryMergeBehaviour modifier) { + TemporaryDataMutateResult result = handleTemporaryMergeBehaviour(NodeMapType.ENDURING, node, modifier); + if (result != null) { + return result; } // Fallback to the normal handling. - return Maps.immutableEntry(setPermission(node), node); + return new TemporaryResult(setPermission(node), node); } - /** - * Sets a transient permission node - * - * @param node the node to set - */ public DataMutateResult setTransientPermission(Node node) { if (hasPermission(NodeMapType.TRANSIENT, node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE) != Tristate.UNDEFINED) { return DataMutateResult.ALREADY_HAS; } + // don't call any events for transient operations this.transientNodes.add(node); invalidateCache(); return DataMutateResult.SUCCESS; } - /** - * Unsets a permission node - * - * @param node the node to unset - */ + public TemporaryDataMutateResult setTransientPermission(Node node, TemporaryMergeBehaviour modifier) { + TemporaryDataMutateResult result = handleTemporaryMergeBehaviour(NodeMapType.TRANSIENT, node, modifier); + if (result != null) { + return result; + } + + // Fallback to the normal handling. + return new TemporaryResult(setTransientPermission(node), node); + } + + private TemporaryDataMutateResult handleTemporaryMergeBehaviour(NodeMapType nodeMapType, Node node, TemporaryMergeBehaviour mergeBehaviour) { + // If the node is temporary, we should take note of the modifier + if (!node.isTemporary() || mergeBehaviour == TemporaryMergeBehaviour.FAIL_WITH_ALREADY_HAS) { + return null; + } + + Node previous = searchForMatch(nodeMapType, node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE).orElse(null); + if (previous == null) { + return null; + } + + NodeMap data = getData(nodeMapType); + boolean callEvents = nodeMapType == NodeMapType.ENDURING; + + switch (mergeBehaviour) { + case ADD_NEW_DURATION_TO_EXISTING: { + // Create a new node with the same properties, but add the expiry dates together + Node newNode = node.toBuilder().setExpiry(previous.getExpiryUnixTime() + node.getSecondsTilExpiry()).build(); + + // Remove the old node & add the new one. + ImmutableCollection before = null; + if (callEvents) { + before = data.immutable().values(); + } + + data.replace(newNode, previous); + invalidateCache(); + + if (callEvents) { + ImmutableCollection after = data.immutable().values(); + this.plugin.getEventFactory().handleNodeAdd(newNode, this, before, after); + } + + return new TemporaryResult(DataMutateResult.SUCCESS, newNode); + } + case REPLACE_EXISTING_IF_DURATION_LONGER: { + // Only replace if the new expiry time is greater than the old one. + if (node.getExpiryUnixTime() <= previous.getExpiryUnixTime()) { + break; + } + + ImmutableCollection before = null; + if (callEvents) { + before = data.immutable().values(); + } + + data.replace(node, previous); + invalidateCache(); + + if (callEvents) { + ImmutableCollection after = data.immutable().values(); + this.plugin.getEventFactory().handleNodeAdd(node, this, before, after); + } + + return new TemporaryResult(DataMutateResult.SUCCESS, node); + } + default: + break; + } + return null; + } + public DataMutateResult unsetPermission(Node node) { if (hasPermission(NodeMapType.ENDURING, node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE) == Tristate.UNDEFINED) { return DataMutateResult.LACKS; @@ -648,16 +653,12 @@ public abstract class PermissionHolder { return DataMutateResult.SUCCESS; } - /** - * Unsets a transient permission node - * - * @param node the node to unset - */ public DataMutateResult unsetTransientPermission(Node node) { if (hasPermission(NodeMapType.TRANSIENT, node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE) == Tristate.UNDEFINED) { return DataMutateResult.LACKS; } + // don't call any events for transient operations this.transientNodes.remove(node); invalidateCache(); return DataMutateResult.SUCCESS; @@ -743,4 +744,26 @@ public abstract class PermissionHolder { public OptionalInt getWeight() { return OptionalInt.empty(); } + + private static final class TemporaryResult implements TemporaryDataMutateResult { + private final DataMutateResult result; + private final Node mergedNode; + + private TemporaryResult(DataMutateResult result, Node mergedNode) { + this.result = result; + this.mergedNode = mergedNode; + } + + @Nonnull + @Override + public DataMutateResult getResult() { + return this.result; + } + + @Nonnull + @Override + public Node getMergedNode() { + return this.mergedNode; + } + } }