diff --git a/bans-api/pom.xml b/bans-api/pom.xml index 1d313a296..bd24bb965 100644 --- a/bans-api/pom.xml +++ b/bans-api/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-parent - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-api @@ -14,7 +14,11 @@ src/main/java - ${project.name}_v${project.version} + + + + ${project.groupId}.${project.name}_${project.version} + org.codehaus.mojo @@ -62,17 +66,22 @@ space.arim.omnibus - omnibus-core + omnibus space.arim.uuidvault api + - org.junit.jupiter - junit-jupiter-api + org.slf4j + slf4j-api test + + org.slf4j + slf4j-simple + \ No newline at end of file diff --git a/bans-api/src/main/java/space/arim/libertybans/api/AbstractPunishment.java b/bans-api/src/main/java/space/arim/libertybans/api/AbstractPunishment.java deleted file mode 100644 index 2544a3deb..000000000 --- a/bans-api/src/main/java/space/arim/libertybans/api/AbstractPunishment.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * LibertyBans-api is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-api is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-api. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.api; - -import java.util.Objects; - -/** - * Abstract implementation of {@link PunishmentBase} so that {@link Punishment} implementations - * and {@link DraftPunishment} itself may avoid boilerplate. - * - * @author A248 - * - */ -public abstract class AbstractPunishment implements PunishmentBase { - - // Package visible so DraftPunishment may use them in equals/hashCode - - final PunishmentType type; - final Victim victim; - final Operator operator; - final String reason; - final Scope scope; - final long start; - final long end; - - protected AbstractPunishment(PunishmentType type, Victim victim, Operator operator, String reason, Scope scope, long start, long end) { - this.type = Objects.requireNonNull(type, "PunishmentType must not be null"); - this.victim = Objects.requireNonNull(victim, "Victim must not be null"); - this.operator = Objects.requireNonNull(operator, "Operator must not be null"); - this.reason = Objects.requireNonNull(reason, "Reason must not be null"); - this.scope = Objects.requireNonNull(scope, "Scope must not be null"); - this.start = start; - if (start < 0L) { - throw new IllegalArgumentException("Start time must be greater than or equal to 0"); - } - this.end = end; - if (end < -1L) { - throw new IllegalArgumentException("End time must be greater than or equal to 0, or -1 for permanent"); - } - } - - @Override - public PunishmentType getType() { - return type; - } - - @Override - public Victim getVictim() { - return victim; - } - - @Override - public Operator getOperator() { - return operator; - } - - @Override - public String getReason() { - return reason; - } - - @Override - public Scope getScope() { - return scope; - } - - @Override - public long getStart() { - return start; - } - - @Override - public long getEnd() { - return end; - } - - @Override - public String toString() { - if (this instanceof Punishment) { - return getClass().getSimpleName() + " [id=" + ((Punishment) this).getID() + ", type=" + type + ", victim=" - + victim + ", operator=" + operator + ", reason=" + reason + ", scope=" + scope + ", start=" + start - + ", end=" + end + "]"; - } else { - return getClass().getSimpleName() + " [type=" + type + ", victim=" + victim + ", operator=" + operator - + ", reason=" + reason + ", scope=" + scope + ", start=" + start + ", end=" + end + "]"; - } - } - - @Override - public abstract int hashCode(); - - @Override - public abstract boolean equals(Object object); - -} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/AddressVictim.java b/bans-api/src/main/java/space/arim/libertybans/api/AddressVictim.java index 5c9c47b8b..db0a3f051 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/AddressVictim.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/AddressVictim.java @@ -21,6 +21,8 @@ import java.net.InetAddress; import java.util.Objects; +import space.arim.libertybans.api.Victim.VictimType; + /** * An IP address as the victim of a punishment * @@ -32,7 +34,6 @@ public final class AddressVictim extends Victim { private final NetworkAddress address; private AddressVictim(NetworkAddress address) { - super(VictimType.ADDRESS); this.address = address; } @@ -69,6 +70,15 @@ public static AddressVictim of(byte[] address) { return new AddressVictim(NetworkAddress.of(address)); } + /** + * Gets this victim's type: {@link VictimType#ADDRESS} + * + */ + @Override + public VictimType getType() { + return VictimType.ADDRESS; + } + /** * Gets the network address represented by this victim * diff --git a/bans-api/src/main/java/space/arim/libertybans/api/ConsoleOperator.java b/bans-api/src/main/java/space/arim/libertybans/api/ConsoleOperator.java index 812c0486e..2a9a8078d 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/ConsoleOperator.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/ConsoleOperator.java @@ -32,10 +32,27 @@ public final class ConsoleOperator extends Operator { */ public static final ConsoleOperator INSTANCE = new ConsoleOperator(); - private ConsoleOperator() { - super(OperatorType.CONSOLE); + private ConsoleOperator() {} + + /** + * Gets this operator's type: {@link OperatorType#CONSOLE} + * + */ + @Override + public OperatorType getType() { + return OperatorType.CONSOLE; } + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public boolean equals(Object object) { + return this == object; + } + @Override public String toString() { return "ConsoleOperator.INSTANCE"; diff --git a/bans-api/src/main/java/space/arim/libertybans/api/DraftPunishment.java b/bans-api/src/main/java/space/arim/libertybans/api/DraftPunishment.java deleted file mode 100644 index 8c5f2479b..000000000 --- a/bans-api/src/main/java/space/arim/libertybans/api/DraftPunishment.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * LibertyBans-api is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-api is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-api. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.api; - -import java.util.Objects; - -/** - * A punishment ready to be created, which does not yet have an ID - * - * @author A248 - * - */ -public final class DraftPunishment extends AbstractPunishment { - - private DraftPunishment(PunishmentType type, Victim victim, Operator operator, String reason, Scope scope, long start, long end) { - super(type, victim, operator, reason, scope, start, end); - } - - /** - * Builder of draft punishments. Should not be shared across threads - * - * @author A248 - * - */ - public static class Builder { - - private PunishmentType type; - private Victim victim; - private Operator operator; - private String reason; - private Scope scope; - private long start = -1L; - private long end = 0L; - - /** - * Creates the builder - * - */ - public Builder() { - - } - - /** - * Sets the type of this builder to the given one - * - * @param type the punishment type - * @return this builder - * @throws NullPointerException if {@code type} is null - */ - public Builder type(PunishmentType type) { - this.type = Objects.requireNonNull(type, "type"); - return this; - } - - /** - * Sets the victim of this builder to the given one - * - * @param victim the victim - * @return this builder - * @throws NullPointerException if {@code victim} is null - */ - public Builder victim(Victim victim) { - this.victim = Objects.requireNonNull(victim, "victim"); - return this; - } - - /** - * Sets the operator of this builder to the given one - * - * @param operator the operator - * @return this builder - * @throws NullPointerException if {@code operator} is null - */ - public Builder operator(Operator operator) { - this.operator = Objects.requireNonNull(operator, "operator"); - return this; - } - - /** - * Sets the reason of this builder to the given one - * - * @param reason the reason - * @return this builder - * @throws NullPointerException if {@code reason} is null - */ - public Builder reason(String reason) { - this.reason = Objects.requireNonNull(reason, "reason"); - return this; - } - - /** - * Sets the scope of this builder to the given one - * - * @param scope the scope - * @return this builder - * @throws NullPointerException if {@code scope} is null - */ - public Builder scope(Scope scope) { - this.scope = Objects.requireNonNull(scope, "scope"); - return this; - } - - /** - * Sets the start time of this builder to the given one. The time is in unix seconds, - * NOT miliseconds.
- *
- * To use the current time as the start time, don't call this method! - * The API will fill in the current time automatically. - * - * @param start the start time in unix seconds - * @return this builder - */ - public Builder start(long start) { - if (start < 0L) { - throw new IllegalArgumentException("Start time must be greater than or equal to 0"); - } - this.start = start; - return this; - } - - /** - * Sets the end time of this builder to permanent. This is the default - * - * @return this builder - */ - public Builder permanent() { - return end(0L); - } - - /** - * Sets the end time of this builder to the given one. The time is in unix seconds, - * NOT milliseconds.
- *
- * For a permanent punishment, don't call this method. The API will make the punishment - * permanent unless specified otherwise. - * - * @param end the end time in unix seconds, or 0 for permanent - * @return this builder - */ - public Builder end(long end) { - if (end < 0L) { - throw new IllegalArgumentException("End time must be greater than or equal to 0"); - } - this.end = end; - return this; - } - - /** - * Builds into a {@link DraftPunishment}.
- *
- * Requires that this builder's type, victim, operator, reason, and scope are set. If any are unset, - * {@code IllegalArgumentException} is thrown - * - * @return the draft punishment - * @throws IllegalArgumentException if any detail has not been set - */ - public DraftPunishment build() { - if (type == null || victim == null || operator == null || reason == null || scope == null) { - throw new IllegalArgumentException("Punishment details have not been set"); - } - return new DraftPunishment(type, victim, operator, reason, scope, - (start == -1L) ? (System.currentTimeMillis() / 1_000L) : start, end); - } - - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + type.hashCode(); - result = prime * result + victim.hashCode(); - result = prime * result + operator.hashCode(); - result = prime * result + reason.hashCode(); - result = prime * result + scope.hashCode(); - result = prime * result + (int) (start ^ (start >>> 32)); - result = prime * result + (int) (end ^ (end >>> 32)); - return result; - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (!(object instanceof DraftPunishment)) { - return false; - } - DraftPunishment that = (DraftPunishment) object; - return type == that.type && victim.equals(that.victim) && operator.equals(that.operator) - && reason.equals(that.reason) && scope.equals(that.scope) && start == that.start && end == that.end; - } - -} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java b/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java index 15e87bcc9..9f941308e 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/LibertyBans.java @@ -21,20 +21,66 @@ import space.arim.omnibus.Omnibus; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; +import space.arim.libertybans.api.manager.PunishmentDatabase; +import space.arim.libertybans.api.manager.PunishmentFormatter; +import space.arim.libertybans.api.manager.ScopeManager; +import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.revoke.PunishmentRevoker; +import space.arim.libertybans.api.select.PunishmentSelector; + +/** + * The main entry point to the LibertyBans API + * + * @author A248 + * + */ public interface LibertyBans { Omnibus getOmnibus(); FactoryOfTheFuture getFuturesFactory(); - PunishmentDatabase getDatabase(); + /** + * Gets the punishment drafter used for creating punishments + * + * @return the punishment drafter + */ + PunishmentDrafter getDrafter(); + + /** + * Gets the punishment revoker used for undoing punishments + * + * @return the punishment revoker + */ + PunishmentRevoker getRevoker(); + /** + * Gets the punishment selector used to select punishments from the database + * + * @return the punishment selector + */ PunishmentSelector getSelector(); - PunishmentEnactor getEnactor(); + /** + * Gets the punishment database. Per the class javadoc of {@link PunishmentDatabase}, + * use of this method should be avoided in all but a few cases + * + * @return the punishment database + */ + PunishmentDatabase getDatabase(); - PunishmentEnforcer getEnforcer(); + /** + * Gets the punishment formatter + * + * @return the formatter manager + */ + PunishmentFormatter getFormatter(); + /** + * Gets the scope manager used to create scopes + * + * @return the scope manager + */ ScopeManager getScopeManager(); } diff --git a/bans-api/src/main/java/space/arim/libertybans/api/Operator.java b/bans-api/src/main/java/space/arim/libertybans/api/Operator.java index 1498bc2fb..442c1fbf8 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/Operator.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/Operator.java @@ -27,20 +27,14 @@ */ public abstract class Operator { - private final OperatorType type; - - Operator(OperatorType type) { - this.type = type; - } + Operator() {} /** * Gets the type of the operator * * @return the operator type */ - public OperatorType getType() { - return type; - } + public abstract OperatorType getType(); /** * A type of operator. Corresponds to the subclasses of {@code Operator} @@ -63,6 +57,16 @@ public enum OperatorType { } + @Override + public abstract int hashCode(); + + /** + * Evaluates whether this operator is the same as another + * + */ + @Override + public abstract boolean equals(Object object); + @Override public abstract String toString(); diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PlayerOperator.java b/bans-api/src/main/java/space/arim/libertybans/api/PlayerOperator.java index 08906d89d..5f52a8060 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/PlayerOperator.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/PlayerOperator.java @@ -32,7 +32,6 @@ public final class PlayerOperator extends Operator { private final UUID uuid; private PlayerOperator(UUID uuid) { - super(OperatorType.PLAYER); this.uuid = uuid; } @@ -41,12 +40,20 @@ private PlayerOperator(UUID uuid) { * * @param uuid the player UUID * @return the operator representation of the player - * @throws NullPointerException if {@code uuid} is null */ public static PlayerOperator of(UUID uuid) { return new PlayerOperator(Objects.requireNonNull(uuid, "uuid")); } + /** + * Gets this operator's type: {@link OperatorType#PLAYER} + * + */ + @Override + public OperatorType getType() { + return OperatorType.PLAYER; + } + /** * Gets the UUID of this player operator * diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PlayerVictim.java b/bans-api/src/main/java/space/arim/libertybans/api/PlayerVictim.java index f77c1e902..09bee07e8 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/PlayerVictim.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/PlayerVictim.java @@ -32,8 +32,7 @@ public final class PlayerVictim extends Victim { private final UUID uuid; private PlayerVictim(UUID uuid) { - super(VictimType.PLAYER); - this.uuid = uuid; + this.uuid = Objects.requireNonNull(uuid, "uuid"); } /** @@ -41,10 +40,18 @@ private PlayerVictim(UUID uuid) { * * @param uuid the player UUID * @return the victim representation of the player - * @throws NullPointerException if {@code uuid} is null */ public static PlayerVictim of(UUID uuid) { - return new PlayerVictim(Objects.requireNonNull(uuid, "uuid")); + return new PlayerVictim(uuid); + } + + /** + * Gets this victim's type: {@link VictimType#PLAYER} + * + */ + @Override + public VictimType getType() { + return VictimType.PLAYER; } /** diff --git a/bans-api/src/main/java/space/arim/libertybans/api/Punishment.java b/bans-api/src/main/java/space/arim/libertybans/api/Punishment.java deleted file mode 100644 index 93a8f9af0..000000000 --- a/bans-api/src/main/java/space/arim/libertybans/api/Punishment.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * LibertyBans-api is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-api is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-api. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.api; - -/** - * A full punishment, identifiable by its ID - * - * @author A248 - * - */ -public interface Punishment extends PunishmentBase { - - /** - * Gets the unique ID of this punishment - * - * @return the ID of the punishment - */ - int getID(); - - /** - * Whether this punishment is equal to another. Implementations - * need only check {@link #getID()} since IDs must always be unique. - * - * @param object the other object - * @return true if the objects are equal, false otherwise - */ - @Override - boolean equals(Object object); - -} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnactor.java b/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnactor.java deleted file mode 100644 index f01203ec5..000000000 --- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnactor.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * LibertyBans-api is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-api is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-api. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.api; - -import space.arim.omnibus.util.concurrent.CentralisedFuture; - -/** - * Guardkeeper of adding punishments while enforcing constraints and reporting such enforcement. - * For example, one victim cannot have more than 1 ban.
- *
- * Generally, methods to undo punishments taking more information are more efficient than those taking less. - * For example, the "undoAndGet" methods are less efficient than the similar and simpler "undo" methods which - * do not return the undone {@code Punishment}, since the latter do not need to additionally fetch the punishment. - * - * @author A248 - * - */ -public interface PunishmentEnactor { - - /** - * Enacts a punishment, adding it to the database.
- * If the punishment type is a ban or mute, and there is already a ban or mute for the victim, - * the future will yield {@code null}.
- *
- * Assuming the caller wants the punishment to be enforced, {@link PunishmentEnforcer#enforce(Punishment)} - * should be called after enaction. - * - * @param draftPunishment the draft punishment to enact - * @return a centralised future which yields the punishment or {@code null} if there was a conflict - */ - CentralisedFuture enactPunishment(DraftPunishment draftPunishment); - - /** - * Undoes an existing punishment in the database.
- * If the punishment existed and was removed, the future yields {@code true}, else {@code false}. - * - * @param punishment the punishment to undo - * @return a centralised future which yields {@code true} if the punishment existed and was removed, {@code false} otherwise - */ - CentralisedFuture undoPunishment(Punishment punishment); - - /** - * Undoes a punishment by its ID and type.
- * If the punishment with the ID and of the specified type existed and was removed, the future yields {@code true}, - * else {@code false}.
- *
- * Efficiency implications
- * When the full {@code Punishment} is known, {@link #undoPunishment(Punishment)} should be used. - * - * @param id the id of the punishment to undo - * @param type the type of the punishment to undo - * @return a centralised future which yields {@code true} if the punishment existed and was removed, {@code false} otherwise - */ - CentralisedFuture undoPunishmentByIdAndType(int id, PunishmentType type); - - /** - * Undoes and gets a punishment by its ID and type.
- * If the punishment with the ID and of the specified type existed and was removed, the future yields it, - * else {@code null}.
- *
- * Efficiency implications
- * When the undone {@code Punishment} is not needed, {@link #undoPunishmentByIdAndType(int, PunishmentType)} should be used. - * - * @param id the id of the punishment to undo - * @param type the type of the punishment to undo - * @return a centralised future which yields the punishment if it existed and was removed, {@code null} otherwise - */ - CentralisedFuture undoAndGetPunishmentByIdAndType(int id, PunishmentType type); - - /** - * Undoes a punishment according to its ID.
- * If the punishment with the ID existed and was removed, the future yields {@code true}, else {@code false}.
- *
- * Efficiency implications
- * When the full {@code Punishment} is known, {@link #undoPunishment(Punishment)} should be used.
- * When the punishment type is known, {@link #undoPunishmentByIdAndType(int, PunishmentType)} should be used. - * - * @param id the id of the punishment to undo - * @return a centralised future which yields {@code true} if the punishment existed and was removed, {@code false} otherwise - */ - CentralisedFuture undoPunishmentById(int id); - - /** - * Undoes and gets a punishment according to its ID.
- * If the punishment with the ID existed and was removed, the future yields it, else {@code null}.
- *
- * Efficiency implications
- * When the undone {@code Punishment} is not needed, {@link #undoPunishmentById(int)} should be used.
- * When the punishment type is known, {@link #undoAndGetPunishmentByIdAndType(int, PunishmentType)} should be used. - * - * @param id the id of the punishment to undo - * @return a centralised future which yields the punishment if it existed and was removed, {@code null} otherwise - */ - CentralisedFuture undoAndGetPunishmentById(int id); - - /** - * Undoes a single punishment by its type and victim. This may only be used for singular punishments - * (bans and mutes), since it relies on the fact that a single victim cannot have more than 1 such punishment. - * - * @param type the punishment type, must be singular per {@link PunishmentType#isSingular()} - * @param victim the victim whose punishment to undo - * @return a centralised future which yields {@code true} if the punishment existed and was removed, {@code false} otherwise - * @throws IllegalArgumentException if {@code type} is not singular (BAN or MUTE) - */ - CentralisedFuture undoPunishmentByTypeAndVictim(PunishmentType type, Victim victim); - - /** - * Undoes and gets a single punishment by its type and victim. This may only be used for singular punishments - * (bans and mutes), since it relies on the fact that a single victim cannot have more than 1 such punishment.
- *
- * Efficiency implications
- * When the undone {@code Punishment} is not needed, {@link #undoPunishmentByTypeAndVictim(PunishmentType, Victim)} - * should be used. - * - * @param type the punishment type, must be singular per {@link PunishmentType#isSingular()} - * @param victim the victim whose punishment to undo - * @return a centralised future which yields the punishment if it existed and was removed, {@code null} otherwise - * @throws IllegalArgumentException if {@code type} is not singular (BAN or MUTE) - */ - CentralisedFuture undoAndGetPunishmentByTypeAndVictim(PunishmentType type, Victim victim); - -} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnforcer.java b/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnforcer.java deleted file mode 100644 index c09fd7249..000000000 --- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentEnforcer.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * LibertyBans-api is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-api is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-api. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.api; - -import space.arim.omnibus.util.concurrent.CentralisedFuture; - -/** - * Enforcer of punishments - * - * @author A248 - * - */ -public interface PunishmentEnforcer { - - /** - * Enforces a punishment.
- *
- * For bans and mutes, this will kick players matching the punishment's - * victim. For mutes and warn, the players will be sent a warning message.
- * Additionally for mutes, the player will be unable to chat (depending on the implementation) - * until the mute cache expires, at which point the database is re-queried for a mute. - * - * @param punishment the punishment to enforce - * @return a future completed when enforcement has been conducted - */ - CentralisedFuture enforce(Punishment punishment); - - /** - * "Unenforces" a punishment.
- *
- * This will update local punishment caches, such as the mute cache. - * - * @param punishment the punishment to unenforce - * @return a future completed when unenforcement has been conducted - */ - CentralisedFuture unenforce(Punishment punishment); - -} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelection.java b/bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelection.java deleted file mode 100644 index 835121b47..000000000 --- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelection.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * LibertyBans-api is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-api is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-api. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.api; - -/** - * A selection which will match punishments in the database with certain details.
- * To retrieve an instance, use the {@link Builder} - * - * @author A248 - * - */ -public final class PunishmentSelection { - - private final PunishmentType type; - private final Victim victim; - private final Operator operator; - private final Scope scope; - private final boolean selectActiveOnly; - - PunishmentSelection(PunishmentType type, Victim victim, Operator operator, Scope scope, boolean selectActiveOnly) { - this.type = type; - this.victim = victim; - this.operator = operator; - this.scope = scope; - this.selectActiveOnly = selectActiveOnly; - } - - /** - * Gets the punishment type matched, or {@code null} to match all types - * - * @return the punishment type or {@code null} for all types - */ - public PunishmentType getType() { - return type; - } - - /** - * Gets the victim matched, or {@code null} to match all victims - * - * @return the victim or {@code null} for all victims - */ - public Victim getVictim() { - return victim; - } - - /** - * Gets the operator matched, or {@code null} to match all operators - * - * @return the operator or {@code null} for all operators - */ - public Operator getOperator() { - return operator; - } - - /** - * Gets the scope matched, or {@code null} to match all scopes - * - * @return the scope or {@code null} for all scopes - */ - public Scope getScope() { - return scope; - } - - /** - * Whether this selection will match only active, non-expired punishments. If {@code false}, - * then this selection may match undone or expired punishments as drawn from the punishments - * history. - * - * @return true to select only active and non-expired punishments, false to select all punishments - */ - public boolean selectActiveOnly() { - return selectActiveOnly; - } - - /** - * Builder of {@link PunishmentSelection}s - * - * @author A248 - * - */ - public static class Builder { - - private PunishmentType type; - private Victim victim; - private Operator operator; - private Scope scope; - private boolean selectActiveOnly = true; - - /** - * Sets the punishment type matched, or {@code null} to match all types - * - * @param type the punishment type or {@code null} for all types - * @return this builder - */ - public Builder type(PunishmentType type) { - this.type = type; - return this; - } - - /** - * Sets the victim matched, or {@code null} to match all victims - * - * @param victim the victim or {@code null} for all victims - * @return this builder - */ - public Builder victim(Victim victim) { - this.victim = victim; - return this; - } - - /** - * Sets the operator matched, or {@code null} to match all operators - * - * @param operator the operator or {@code null} for all operators - * @return this builder - */ - public Builder operator(Operator operator) { - this.operator = operator; - return this; - } - - /** - * Sets the scope matched, or {@code null} to match all scopes - * - * @param scope the scope or {@code null} for all scopes - * @return this builder - */ - public Builder scope(Scope scope) { - this.scope = scope; - return this; - } - - /** - * Sets to match only active, non-expired punishments. - * - * @return this builder - */ - public Builder selectActiveOnly() { - selectActiveOnly = true; - return this; - } - - /** - * Sets to match all punishments, including those undone or expired - * - * @return this builder - */ - public Builder selectAll() { - selectActiveOnly = false; - return this; - } - - /** - * Builds into a full {@link PunishmentSelection} - * - * @return a selection based on this builder's details - */ - public PunishmentSelection build() { - return new PunishmentSelection(type, victim, operator, scope, selectActiveOnly); - } - - } - -} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentType.java b/bans-api/src/main/java/space/arim/libertybans/api/PunishmentType.java index 9c42bc656..d8bfa0ca5 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentType.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/PunishmentType.java @@ -28,16 +28,10 @@ */ public enum PunishmentType { - BAN(true), - MUTE(true), - WARN(false), - KICK(false); - - private final boolean singular; - - private PunishmentType(boolean singular) { - this.singular = singular; - } + BAN, + MUTE, + WARN, + KICK; /** * If this punishment type is singular, i.e., whether there can only be 1 kind of it @@ -47,16 +41,16 @@ private PunishmentType(boolean singular) { * @return true if the type is singular, false otherwise */ public boolean isSingular() { - return singular; + return this == BAN || this == MUTE; } /** - * Shortcut for {@link #name()} lowercased using the english locale + * Shortcut for {@link #name()} lowercased using the root locale * * @return the lowercased name */ public String getLowercaseName() { - return name().toLowerCase(Locale.ENGLISH); + return name().toLowerCase(Locale.ROOT); } /** @@ -68,28 +62,6 @@ public String getLowercaseNamePlural() { return getLowercaseName() + 's'; } - /** - * Gets a PunishmentType from an ordinal, or {@code null} if no such - * ordinal exists in the enum - * - * @param ordinal the ordinal, 0, 1, 2, 3 - * @return the corresponding punishment type, or {@code null} - */ - public static PunishmentType fromOrdinal(int ordinal) { - switch (ordinal) { - case 0: - return BAN; - case 1: - return MUTE; - case 2: - return WARN; - case 3: - return KICK; - default: - return null; - } - } - @Override public String toString() { String name = name(); diff --git a/bans-api/src/main/java/space/arim/libertybans/api/Scope.java b/bans-api/src/main/java/space/arim/libertybans/api/ServerScope.java similarity index 80% rename from bans-api/src/main/java/space/arim/libertybans/api/Scope.java rename to bans-api/src/main/java/space/arim/libertybans/api/ServerScope.java index ec83667b0..2f66dae15 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/Scope.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/ServerScope.java @@ -18,13 +18,15 @@ */ package space.arim.libertybans.api; +import space.arim.libertybans.api.manager.ScopeManager; + /** * The server-specific scope of a punishment. To get a scope, see {@link ScopeManager} * * @author A248 * */ -public interface Scope { +public interface ServerScope { /** * Whether this scope applies to a server @@ -35,9 +37,8 @@ public interface Scope { boolean appliesTo(String server); /** - * Determines equality with a given object. The contract of the {@code equals} method for {@code Scope} - * is that the other object must be an instance of the same implementation type, and it must apply to the - * same possible server strings as described in {@link #appliesTo(String)} + * Determines equality with a given object. The other object must be a scope applying to the same servers + * as described in {@link #appliesTo(String)} * * @param object the object to determine equality with * @return true if equal, false otherwise diff --git a/bans-api/src/main/java/space/arim/libertybans/api/Victim.java b/bans-api/src/main/java/space/arim/libertybans/api/Victim.java index 729026854..1dfef2e20 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/Victim.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/Victim.java @@ -27,20 +27,14 @@ */ public abstract class Victim { - private final VictimType type; - - Victim(VictimType type) { - this.type = type; - } + Victim() {} /** * Gets the type of the victim * * @return the victim type */ - public VictimType getType() { - return type; - } + public abstract VictimType getType(); /** * A victim type. Corresponds to the subclasses of {@code Victim} @@ -51,7 +45,7 @@ public VictimType getType() { public enum VictimType { /** - * A player + * A player, identified by UUID * */ PLAYER, @@ -61,29 +55,15 @@ public enum VictimType { */ ADDRESS; - /** - * Gets a VictimType from an ordinal, or {@code null} if no such - * ordinal exists in the enum - * - * @param ordinal the ordinal, 0 or 1 - * @return the corresponding victim type, or {@code null} - */ - public static VictimType fromOrdinal(int ordinal) { - switch (ordinal) { - case 0: - return PLAYER; - case 1: - return ADDRESS; - default: - return null; - } - } - } @Override public abstract int hashCode(); + /** + * Evaluates whether this victim is the same as another + * + */ @Override public abstract boolean equals(Object object); diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentDatabase.java b/bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentDatabase.java similarity index 87% rename from bans-api/src/main/java/space/arim/libertybans/api/PunishmentDatabase.java rename to bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentDatabase.java index aa568a02b..fde46bede 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentDatabase.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentDatabase.java @@ -16,12 +16,14 @@ * along with LibertyBans-api. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.api; +package space.arim.libertybans.api.manager; import java.sql.Connection; import java.sql.SQLException; import java.util.concurrent.Executor; +import space.arim.libertybans.api.select.PunishmentSelector; + /** * Access to the raw database. Use of this should be avoided at great cost. Executing SQL queries * is not considered traditional API.
@@ -33,19 +35,20 @@ * API Status
* Use of the SQL backend directly is not considered traditional API. Although attempts will be made to limit * the number of breaking changes, LibertyBans's major version will not necessarily change to match the SQL backend. - * Unlike other plugins, LibertyBans makes use of a wide swath of queries.
+ * Unlike other plugins, LibertyBans makes use of a wide swath of SQL queries and operations, so a rigid table + * structure is not feasible.
*
- * Rather, a separate version is maintained specifically for the SQL backend. This version is maintained returned by + * Rather, a separate version is maintained specifically for the SQL backend. This version is returned by * {@link #getMajorRevision()} and {@link #getMinorRevision()}. Additionally, it is located in the table * libertybans_revision, with the {@code major} and {@code minor} columns indicating the major version * and minor version, respectively (both columns use the INT data type).
*
- * Whenever the tables and/or views definitions change incompatibly, the major version will be incremented. The minor + * Whenever the tables and/or views definitions change incompatibly, this major version will be incremented. This minor * version will be incremented as new tables and views are added or existing ones changed, but not in ways which * break backwards compatibility.
*
- * Stored routines are not supported within any versioning context. Use of any SQL functions or procedures created - * by LibertyBans is strictly unsupported. + * Stored routines are not supported within any versioning context. Use of any SQL functions or procedures created + * by LibertyBans is strictly unsupported. * * @author A248 * @@ -58,7 +61,7 @@ public interface PunishmentDatabase { * Use of this should be avoided at great cost. Executing SQL queries is not considered traditional API. * See the class javadoc for more information. * - * @return a connection from the plugin pool, which should be closed after immediately use + * @return a connection from the plugin pool, which should be closed immediately after use * @throws SQLException as relayed from JDBC */ Connection getConnection() throws SQLException; diff --git a/bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentFormatter.java b/bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentFormatter.java new file mode 100644 index 000000000..fd98e0385 --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/manager/PunishmentFormatter.java @@ -0,0 +1,62 @@ +/* + * LibertyBans-api + * Copyright © 2020 Anand Beh + * + * LibertyBans-api is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-api. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.api.manager; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +/** + * Contains what little API exists for formatting punishments + * + */ +public interface PunishmentFormatter { + + /** + * Gets the configured timezone + * + * @return the zone id used for displaying dates + */ + ZoneId getTimezone(); + + /** + * Gets the configured date time formatter used for displaying dates + * + * @return the date time formatter + */ + DateTimeFormatter getDateTimeFormatter(); + + /** + * Formats an absolute date in conjunction with {@link #getTimezone()} and {@link #getDateTimeFormatter()} + * + * @param date the date to format + * @return the formatted string + */ + String formatAbsoluteDate(Instant date); + + /** + * Formats a duration + * + * @param duration the duration + * @return the formatted string + */ + String formatDuration(Duration duration); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/ScopeManager.java b/bans-api/src/main/java/space/arim/libertybans/api/manager/ScopeManager.java similarity index 77% rename from bans-api/src/main/java/space/arim/libertybans/api/ScopeManager.java rename to bans-api/src/main/java/space/arim/libertybans/api/manager/ScopeManager.java index 862b3b9e9..143deeab3 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/ScopeManager.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/manager/ScopeManager.java @@ -16,10 +16,12 @@ * along with LibertyBans-api. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.api; +package space.arim.libertybans.api.manager; + +import space.arim.libertybans.api.ServerScope; /** - * Factory which produces {@link Scope}s + * Factory which produces {@link ServerScope}s * * @author A248 * @@ -34,13 +36,20 @@ public interface ScopeManager { * @return a scope applying to the server * @throws IllegalArgumentException if server.length() {@literal >} 32 */ - Scope specificScope(String server); + ServerScope specificScope(String server); /** * Gets the global scope, applying to all servers * * @return the global scope */ - Scope globalScope(); + ServerScope globalScope(); + + /** + * Gets a scope applying to the current server + * + * @return a scope applying to the current server + */ + ServerScope currentServerScope(); } diff --git a/bans-api/src/main/java/space/arim/libertybans/api/package-info.java b/bans-api/src/main/java/space/arim/libertybans/api/package-info.java new file mode 100644 index 000000000..81325ab2f --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/package-info.java @@ -0,0 +1,16 @@ +/** + * The base of the LibertyBans API.
+ *
+ * Nullability
+ * Unless stated otherwise, no method will return {@code null}. All nullability is explicitly mentioned in the javadoc, + * therefore the lack of such mention implies the return value or method parameter in question will be or must be nonnull.
+ *
+ * This further implies all methods not declared to accept {@code null} parameters throw {@code NullPointerException} + * on null input.
+ *
+ * Thread Safety
+ * Generally, objects are immutable and thread safe. The only exception to this rule are builder objects, which + * are mutable and unsafe for concurrent use. It is only intended for builders to be used from a single thread. + * + */ +package space.arim.libertybans.api; \ No newline at end of file diff --git a/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishment.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishment.java new file mode 100644 index 000000000..0e0375b5f --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishment.java @@ -0,0 +1,77 @@ +/* + * LibertyBans-api + * Copyright © 2020 Anand Beh + * + * LibertyBans-api is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-api. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.api.punish; + +import java.time.Duration; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +/** + * A punishment ready to be created. Does not yet have an ID or start and end time, but does contain a duration.
+ *
+ * To obtain an instance, use a {@link DraftPunishmentBuilder} obtained from {@link PunishmentDrafter} + * + * @author A248 + * + */ +public interface DraftPunishment extends PunishmentBase { + + /** + * Gets the duration of this draft punishment + * + * @return the duration + */ + Duration getDuration(); + + /** + * Enacts this punishment, adding it to the database, then enforces it.
+ *
+ * If the punishment type is a ban or mute, and there is already an active ban or mute for the victim, + * the future will yield {@code null}. See {@link space.arim.libertybans.api.punish} for a description of + * active and historical punishments. + * + * @return a centralised future which yields the punishment or {@code null} if there was a conflict + */ + CentralisedFuture enactPunishment(); + + /** + * Enacts this punishment, adding it to the database.
+ *
+ * If the punishment type is a ban or mute, and there is already an active ban or mute for the victim, + * the future will yield {@code null}. See {@link space.arim.libertybans.api.punish} for a description of + * active and historical punishments.
+ *
+ * Most callers will want to use {@link #enactPunishment()} instead, which has the added effect + * of enforcing the punishment. + * + * @return a centralised future which yields the punishment or {@code null} if there was a conflict + */ + CentralisedFuture enactPunishmentWithoutEnforcement(); + + /** + * Whether this draft punishment is equal to another. The other draft punishment + * must have the same details as this one + * + * @param object the object to determine equality with + * @return true if equal, false otherwise + */ + @Override + boolean equals(Object object); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishmentBuilder.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishmentBuilder.java new file mode 100644 index 000000000..29a294ac8 --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/DraftPunishmentBuilder.java @@ -0,0 +1,104 @@ +/* + * LibertyBans-api + * Copyright © 2020 Anand Beh + * + * LibertyBans-api is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-api. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.api.punish; + +import java.time.Duration; + +import space.arim.libertybans.api.ConsoleOperator; +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; + +/** + * Builder of draft punishments. It is required to set the type, victim, and reason. + * + * @author A248 + * + */ +public interface DraftPunishmentBuilder { + + /** + * Sets the punishment type of this builder to the specified one. Required operation + * + * @param type the type of the punishment + * @return this builder + */ + DraftPunishmentBuilder type(PunishmentType type); + + /** + * Sets the victim of this builder to the specified one. Required operation + * + * @param victim the victim of the punishmnt + * @return this builder + */ + DraftPunishmentBuilder victim(Victim victim); + + /** + * Sets the operator of this builder to the specified one. If unspecified, the console operator + * is used ({@link ConsoleOperator#INSTANCE}) + * + * @param operator the operator of the punishment, by default the console + * @return this builder + */ + DraftPunishmentBuilder operator(Operator operator); + + /** + * Sets the reason of this builder to the specified one. Required operation + * + * @param reason the reason of the punishment + * @return this builder + */ + DraftPunishmentBuilder reason(String reason); + + /** + * Sets the duration of this builder to the specified one. If unspecified, a duration of zero, + * indicating a permanent duration, is used + * + * @param duration the duration of the punishment, or zero for a permanent punishment + * @return this builder + * @throws IllegalArgumentException if {@code duration} is negative + */ + DraftPunishmentBuilder duration(Duration duration); + + /** + * Sets the scope of this builder to the specified one. If unspecified, the global scope + * is used + * + * @param scope the scope of the punishment + * @return this builder + */ + DraftPunishmentBuilder scope(ServerScope scope); + + /** + * Builds into a full draft punishment. All the required information must be set + * on this builder.
+ *
+ * May be used repeatedly without side effects. + * + * @return a draft punishment from this builder's details + * @throws IllegalStateException if any of the required details on this + * builder are not set + * @throws IllegalArgumentException if the punishment type of this builder is + * {@link PunishmentType#KICK} and the duration + * is not permanent + */ + DraftPunishment build(); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/punish/Punishment.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/Punishment.java new file mode 100644 index 000000000..e6b88cdc1 --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/Punishment.java @@ -0,0 +1,163 @@ +/* + * LibertyBans-api + * Copyright © 2020 Anand Beh + * + * LibertyBans-api is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-api. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.api.punish; + +import java.time.Instant; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +/** + * A full punishment, identifiable by its ID.
+ *
+ * See {@link space.arim.libertybans.api.punish} for a description of active and historical punishments. + * + * @author A248 + * + */ +public interface Punishment extends PunishmentBase { + + /** + * Gets the unique ID of this punishment + * + * @return the ID of the punishment + */ + int getID(); + + /** + * Gets the start time of the punishment + * + * @return the state date + */ + Instant getStartDate(); + + /** + * Gets the start date of the punishment, in unix seconds + * + * @return the start date seconds + */ + default long getStartDateSeconds() { + return getStartDate().getEpochSecond(); + } + + /** + * Gets the end time of the punishment. {@link Instant#MAX} is used for a permanent punishment + * + * @return the end date + */ + Instant getEndDate(); + + /** + * Gets the end time of the punishment, in unix seconds. {@code 0} is used for a permanent punishment + * + * @return the end date seconds + */ + default long getEndDateSeconds() { + Instant endDate = getEndDate(); + return endDate.equals(Instant.MAX) ? 0L : endDate.getEpochSecond(); + } + + /** + * Convenience method to determine if this punishment is permanent + * + * @return true if this punishment is permanent, false otherwise + */ + default boolean isPermanent() { + return Instant.MAX.equals(getEndDate()); + } + + /** + * Convenience method to determine if this punishment is temporary (opposite of {@link #isPermanent()}) + * + * @return true if this punishment is temporary, false otherwise + */ + default boolean isTemporary() { + return !isPermanent(); + } + + /** + * Convenience method to determine if this punishment has expired. If permanent, will always be {@code false} + * + * @return true if this is a temporary punishment which has expired + */ + default boolean isExpired() { + if (isPermanent()) { + return false; + } + return Instant.now().compareTo(getEndDate()) > 0; + } + + /** + * Enforces this punishment.
+ *
+ * For bans and mutes, this will kick players matching the punishment's + * victim. For mutes and warn, the players will be sent a warning message.
+ * Additionally for mutes, the player will be unable to chat (depending on the implementation) + * until the mute cache expires, at which point the database is re-queried for a mute. + * + * @return a future completed when enforcement has been conducted + */ + CentralisedFuture enforcePunishment(); + + /** + * Undoes and "unenforces" this punishment assuming it active and in the database.
+ * If the punishment was active then was removed, the future yields {@code true}, + * else {@code false}.
+ *
+ * Unenforcement implies purging of this punishment from any local caches. + * + * @return a centralised future which yields {@code true} if this punishment + * existed and was removed and unenforced, {@code false} otherwise + */ + CentralisedFuture undoPunishment(); + + /** + * Undoes this punishment assuming it is in the database.
+ * If the punishment was active then was removed, the future yields {@code true}, + * else {@code false}.
+ *
+ * Most callers will want to use {@link #undoPunishment()} instead, which has the added effect + * of "unenforcing" the punishment. Unenforcement implies purging of this punishment from any local caches. + * + * @return a centralised future which yields {@code true} if this punishment + * existed and was removed, {@code false} otherwise + */ + CentralisedFuture undoPunishmentWithoutUnenforcement(); + + /** + * "Unenforces" this punishment.
+ *
+ * This will update local punishment caches, such as the mute cache. + * + * @return a future completed when unenforcement has been conducted + */ + CentralisedFuture unenforcePunishment(); + + /** + * Whether this punishment is equal to another. The other punishment must represent the + * same punishment stored in the database.
+ *
+ * Implementations need only check {@link #getID()} since IDs must always be unique. + * + * @param object the other object + * @return true if the objects are equal, false otherwise + */ + @Override + boolean equals(Object object); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentBase.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentBase.java similarity index 81% rename from bans-api/src/main/java/space/arim/libertybans/api/PunishmentBase.java rename to bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentBase.java index 08d2a8f40..a11ef9a20 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentBase.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentBase.java @@ -16,7 +16,12 @@ * along with LibertyBans-api. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.api; +package space.arim.libertybans.api.punish; + +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; /** * Base interface for {@link DraftPunishment} and {@link Punishment} @@ -59,21 +64,6 @@ public interface PunishmentBase { * * @return the scope */ - Scope getScope(); - - /** - * Gets the start time, in unix seconds, of the punishment - * - * @return the start time - */ - long getStart(); - - /** - * Gets the end time, in unix seconds, of the punishment.
- * 0 represents a permanent punishment. - * - * @return the end time - */ - long getEnd(); + ServerScope getScope(); } diff --git a/bans-api/src/test/java/space/arim/libertybans/api/PunishmentTypeTest.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentDrafter.java similarity index 71% rename from bans-api/src/test/java/space/arim/libertybans/api/PunishmentTypeTest.java rename to bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentDrafter.java index 78beff99c..4c367bc5f 100644 --- a/bans-api/src/test/java/space/arim/libertybans/api/PunishmentTypeTest.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/PunishmentDrafter.java @@ -16,19 +16,21 @@ * along with LibertyBans-api. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.api; +package space.arim.libertybans.api.punish; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -public class PunishmentTypeTest { +/** + * Initial point for drafting punishments + * + * @author A248 + * + */ +public interface PunishmentDrafter { - @Test - public void testOrdinals() { - for (PunishmentType type : PunishmentType.values()) { - assertEquals(type, PunishmentType.fromOrdinal(type.ordinal())); - } - } + /** + * Begins creating a draft punishment by returning a {@link DraftPunishmentBuilder} + * + * @return a draft punishment builder + */ + DraftPunishmentBuilder draftBuilder(); } diff --git a/bans-api/src/main/java/space/arim/libertybans/api/punish/package-info.java b/bans-api/src/main/java/space/arim/libertybans/api/punish/package-info.java new file mode 100644 index 000000000..82182d565 --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/punish/package-info.java @@ -0,0 +1,11 @@ +/** + * Defines API for drafting punishments as well as punishments themselves.
+ *
+ * Active versus Historical Punishments + * An active punishment is neither expired nor undone.After being undone, a punishment is no longer active. + * After a temporary punishment expires, it is no longer active.
+ *
+ * 'Historial' punishments include both those active, those expired, and those undone. All punishments, + * regardless of whether they are active, are historical punishments. + */ +package space.arim.libertybans.api.punish; \ No newline at end of file diff --git a/bans-api/src/main/java/space/arim/libertybans/api/revoke/PunishmentRevoker.java b/bans-api/src/main/java/space/arim/libertybans/api/revoke/PunishmentRevoker.java new file mode 100644 index 000000000..30b7a33d2 --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/revoke/PunishmentRevoker.java @@ -0,0 +1,70 @@ +/* + * LibertyBans-api + * Copyright © 2020 Anand Beh + * + * LibertyBans-api is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-api. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.api.revoke; + +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.Punishment; + +/** + * Agent of removing active punishments in accordance with constraints. For example, one victim + * cannot have more than 1 ban, therefore a ban may be undone if the victim is known.
+ *
+ * Only active punishments can be undone. Historical punishments cannot be removed. See + * {@link space.arim.libertybans.api.punish} for a description of active and historical punishments.
+ *
+ * Note that {@link Punishment#undoPunishment()} should be used instead of a revocation order if a punishment + * instance is already obtained. + * + * @author A248 + * + */ +public interface PunishmentRevoker { + + /** + * Gets a {@link RevocationOrder} to undo a punishment by its ID and type. + * + * @param id the id of the punishment to undo + * @param type the type of the punishment to undo + * @return a revocation order using the ID and type + */ + RevocationOrder revokeByIdAndType(int id, PunishmentType type); + + /** + * Gets a {@link RevocationOrder} to undo a punishment according to its ID.
+ *
+ * When the punishment type is known, {@link #revokeByIdAndType(int, PunishmentType)} should be used. + * + * @param id the id of the punishment to undo + * @return a revocation order using the ID + */ + RevocationOrder revokeById(int id); + + /** + * Gets a {@link RevocationOrder} to undo a punishment by its type and victim. This may only be used for singular punishments + * (bans and mutes), since it relies on the fact that a single victim cannot have more than 1 such punishment. + * + * @param type the punishment type, must be singular per {@link PunishmentType#isSingular()} + * @param victim the victim whose punishment to undo + * @return a revocation order using the type and victim + * @throws IllegalArgumentException if {@code type} is not singular (BAN or MUTE) + */ + RevocationOrder revokeByTypeAndVictim(PunishmentType type, Victim victim); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/revoke/RevocationOrder.java b/bans-api/src/main/java/space/arim/libertybans/api/revoke/RevocationOrder.java new file mode 100644 index 000000000..f20b507e7 --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/revoke/RevocationOrder.java @@ -0,0 +1,107 @@ +/* + * LibertyBans-api + * Copyright © 2020 Anand Beh + * + * LibertyBans-api is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-api. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.api.revoke; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.Punishment; + +/** + * An order to undo a punishment by some specific details. Obtained from {@link PunishmentRevoker}.
+ *
+ * See {@link space.arim.libertybans.api.punish} for a description of active and historical punishments. + * + * @author A248 + * + */ +public interface RevocationOrder { + + /** + * Gets the ID of the punishment which will be revoked, or {@code null} if no ID + * is known + * + * @return the ID of the punishment to be revoked, or null + */ + Integer getID(); + + /** + * Gets the punishment type of the punishment which will be revoked, or {@code null} + * if the type is not known + * + * @return the type of the punishment to be revoked, or null + */ + PunishmentType getType(); + + /** + * Gets the victim whose punishment will be revoked, or {@code null} if the victim + * is not known + * + * @return the victim, or null + */ + Victim getVictim(); + + /** + * Revokes the punishment matching this revocation order, and "unenforces" it.
+ *
+ * If no active punishment in the database matched this revocation order, the future yields {@code false}.
+ *
+ * Unenforcement implies purging of this punishment from any local caches. + * + * @return a future which yields {@code true} if the punishment existed and has been revoked, {@code false} otherwise + */ + CentralisedFuture undoPunishment(); + + /** + * Revokes the punishment matching this revocation order.
+ *
+ * If no active punishment in the database matched this revocation order, the future yields {@code false}.
+ *
+ * Most callers will want to use {@link #undoPunishment()} instead, which has the added effect + * of "unenforcing" the punishment. Unenforcement implies purging of this punishment from any local caches. + * + * @return a future which yields {@code true} if the punishment existed and has been revoked, {@code false} otherwise + */ + CentralisedFuture undoPunishmentWithoutUnenforcement(); + + /** + * Revokes the punishment matching this revocation order, "unenforces" it, and gets the punishment revoked.
+ *
+ * If no active punishment in the database matched this revocation order, the future yields {@code null}.
+ *
+ * Unenforcement implies purging of this punishment from any local caches. + * + * @return a future which yields the punishment if it existed and was revoked, null otherwise + */ + CentralisedFuture undoAndGetPunishment(); + + /** + * Revokes the punishment matching this revocation order, and gets the punishment revoked.
+ *
+ * If no active punishment in the database matched this revocation order, the future yields {@code null}.
+ *
+ * Most callers will want to use {@link #undoAndGetPunishment()} instead, which has the added effect + * of "unenforcing" the punishment. Unenforcement implies purging of this punishment from any local caches. + * + * @return a future which yields the punishment if it existed and was revoked, null otherwise + */ + CentralisedFuture undoAndGetPunishmentWithoutUnenforcement(); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelector.java b/bans-api/src/main/java/space/arim/libertybans/api/select/PunishmentSelector.java similarity index 52% rename from bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelector.java rename to bans-api/src/main/java/space/arim/libertybans/api/select/PunishmentSelector.java index dac7b459f..f14217512 100644 --- a/bans-api/src/main/java/space/arim/libertybans/api/PunishmentSelector.java +++ b/bans-api/src/main/java/space/arim/libertybans/api/select/PunishmentSelector.java @@ -16,57 +16,37 @@ * along with LibertyBans-api. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.api; +package space.arim.libertybans.api.select; -import java.util.Set; import java.util.UUID; import space.arim.omnibus.util.concurrent.CentralisedFuture; +import space.arim.libertybans.api.NetworkAddress; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.punish.Punishment; + /** * A manager for selecting punishments with specific details from the database. This, - * along with {@link PunishmentSelection}, essentially provide an API for efficient - * database queries.
+ * along with {@link SelectionOrder}, provide an API for efficient punishment queries.
*
- * Some methods refer to 'active' and/or 'historical' punishments. All punishments in the database - * are historical, including those which have been undone. Only punishments which have not expired - * and have not been undone are active. + * It is possible to select active and historical punishments. See + * {@link space.arim.libertybans.api.punish} for a description of active and historical punishments. * * @author A248 * */ public interface PunishmentSelector { - - /** - * Gets the first punishment matching the given punishment selection, i.e. with the specified details.
- *
- * A punishment is said to match if it meets ALL of the following:
- * Its type matches that of the selection, or the selection's type is unspecified (null)
- * Its victim matches that of the selection, or the selection's victim is unspecified (null)
- * Its operator matches that of the selection, or the selection's operator is unspecified (null)
- * Its scope matches that of the selection, or the selection's scope is unspecified (null) - * - * @param selection the punishment selection whose details to match against - * @return a future which yields the first punishment matching the selection, or {@code null} if none matched - */ - CentralisedFuture getFirstSpecificPunishment(PunishmentSelection selection); /** - * Gets all punishments matching the given punishment selection, i.e. with the specified details.
- *
- * A punishment is said to match if it meets ALL of the following:
- * Its type matches that of the selection, or the selection's type is unspecified (null)
- * Its victim matches that of the selection, or the selection's victim is unspecified (null)
- * Its operator matches that of the selection, or the selection's operator is unspecified (null)
- * Its scope matches that of the selection, or the selection's scope is unspecified (null) + * Begins creating a selection order by returning a {@link SelectionOrderBuilder} * - * @param selection the punishment selection whose details to match against - * @return a future which yields all punishments matching the selection, or an empty set if none matched + * @return a selection order builder */ - CentralisedFuture> getSpecificPunishments(PunishmentSelection selection); + SelectionOrderBuilder selectionBuilder(); /** - * Gets a punishment matching a specific ID, if a punishment with such ID exists.
+ * Gets an active punishment matching a specific ID, if a punishment with such ID exists.
*
* When the type of the punishment is known, {@link #getActivePunishmentByIdAndType(int, PunishmentType)} * should be preferred. @@ -77,7 +57,7 @@ public interface PunishmentSelector { CentralisedFuture getActivePunishmentById(int id); /** - * Gets a punishment matching a specific ID and type, if one exists with matching type and ID.
+ * Gets an active punishment matching a specific ID and type, if one exists with matching type and ID.
*
* Unlike {@link #getActivePunishmentById(int)}, this method may be more efficient when the type of * the punishment is known beforehand. @@ -95,22 +75,6 @@ public interface PunishmentSelector { * @return a future which yields the historical punishment with the ID, or {@code null} if there is none */ CentralisedFuture getHistoricalPunishmentById(int id); - - /** - * Gets all historical punishments applying to a specific {@link Victim} - * - * @param victim the victim - * @return a future which yields all historical punishments applying to the victim, or an empty set - */ - CentralisedFuture> getHistoryForVictim(Victim victim); - - /** - * Gets all active punishments of a certain punishment type - * - * @param type the punishment type - * @return a future which yields all active punishments matching the type, or an empty set - */ - CentralisedFuture> getActivePunishmentsForType(PunishmentType type); /** * Gets the first punishment, of a certain type, which is applicable to a UUID and IP address, @@ -121,17 +85,17 @@ public interface PunishmentSelector { * banned, the player's UUID is banned, or the player has played on a banned IP and that IP is banned while strict * address enforcement is enabled. That is, this method takes into account configuration options related to enforcement. * - * @param uuid the UUID - * @param address the IP address + * @param uuid the player's UUID + * @param address the player's current address * @param type the punishment type * @return a future which yields the first applicable punishment for the id and address or {@code null} if there is none */ - CentralisedFuture getApplicablePunishment(UUID uuid, byte[] address, PunishmentType type); + CentralisedFuture getApplicablePunishment(UUID uuid, NetworkAddress address, PunishmentType type); /** * Gets a cached mute for an online player, including the player's UUID and address.
* The mute will be applicable to the combination of UUID and address, see - * {@link #getApplicablePunishment(UUID, byte[], PunishmentType)} for a description of applicability.
+ * {@link #getApplicablePunishment(UUID, NetworkAddress, PunishmentType)} for a description of applicability.
*
* If the mute is cached, a completed future is returned. Else, an in-progress future is returned. * @@ -139,6 +103,6 @@ public interface PunishmentSelector { * @param address the player's current address * @return a future yielding either a cached value or the queried mute */ - CentralisedFuture getCachedMute(UUID uuid, byte[] address); + CentralisedFuture getCachedMute(UUID uuid, NetworkAddress address); } diff --git a/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrder.java b/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrder.java new file mode 100644 index 000000000..037e1044e --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrder.java @@ -0,0 +1,114 @@ +/* + * LibertyBans-api + * Copyright © 2020 Anand Beh + * + * LibertyBans-api is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-api. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.api.select; + +import java.util.Set; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.Punishment; + +/** + * A selection which will match punishments in the database with certain details.
+ *
+ * To retrieve an instance, use the {@link SelectionOrderBuilder} obtained from + * {@link PunishmentSelector#selectionBuilder()} + * + * @author A248 + * + */ +public interface SelectionOrder { + + /** + * Gets the punishment type matched, or {@code null} to match all types + * + * @return the punishment type or {@code null} for all types + */ + PunishmentType getType(); + + /** + * Gets the victim matched, or {@code null} to match all victims + * + * @return the victim or {@code null} for all victims + */ + Victim getVictim(); + + /** + * Gets the operator matched, or {@code null} to match all operators + * + * @return the operator or {@code null} for all operators + */ + Operator getOperator(); + + /** + * Gets the scope matched, or {@code null} to match all scopes + * + * @return the scope or {@code null} for all scopes + */ + ServerScope getScope(); + + /** + * Whether this selection will match only active, non-expired punishments. If {@code false}, + * then this selection may match any punishments, including undone or expired punishments. + * + * @return true to select only active and non-expired punishments, false to select all punishments + */ + boolean selectActiveOnly(); + + /** + * Gets the first punishment matching this selection, i.e. with the specified details.
+ *
+ * A punishment is said to match if it meets ALL of the following:
+ * Its type matches that of this selection, or this selection's type is unspecified (null)
+ * Its victim matches that of this selection, or this selection's victim is unspecified (null)
+ * Its operator matches that of this selection, or this selection's operator is unspecified (null)
+ * Its scope matches that of this selection, or this selection's scope is unspecified (null) + * + * @return a future which yields the first punishment matching this selection, or {@code null} if none matched + */ + CentralisedFuture getFirstSpecificPunishment(); + + /** + * Gets all punishments matching the given punishment selection, i.e. with the specified details.
+ *
+ * A punishment is said to match if it meets ALL of the following:
+ * Its type matches that of this selection, or this selection's type is unspecified (null)
+ * Its victim matches that of this selection, or this selection's victim is unspecified (null)
+ * Its operator matches that of this selection, or this selection's operator is unspecified (null)
+ * Its scope matches that of this selection, or this selection's scope is unspecified (null) + * + * @return a future which yields all punishments matching this selection, or an empty set if none matched + */ + CentralisedFuture> getAllSpecificPunishments(); + + /** + * Whether this punishment selection is equal to another, i.e. if the other selection would match + * the same punishments in all circumstances. + * + * @param object the other object + * @return true if the objects are equal, false otherwise + */ + @Override + boolean equals(Object object); + +} diff --git a/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrderBuilder.java b/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrderBuilder.java new file mode 100644 index 000000000..55f15438b --- /dev/null +++ b/bans-api/src/main/java/space/arim/libertybans/api/select/SelectionOrderBuilder.java @@ -0,0 +1,114 @@ +/* + * LibertyBans-api + * Copyright © 2020 Anand Beh + * + * LibertyBans-api is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-api. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.api.select; + +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; + +/** + * Builder of {@link SelectionOrder}s + * + * @author A248 + * + */ +public interface SelectionOrderBuilder { + + /** + * Sets the punishment type matched. Use {@code null} to match all types, + * which is the default behaviour + * + * @param type the punishment type or {@code null} for all types + * @return this builder + */ + SelectionOrderBuilder type(PunishmentType type); + + /** + * Sets the victim matched. Use {@code null} to match all victims, + * which is the default behaviour + * + * @param victim the victim or {@code null} for all victims + * @return this builder + */ + SelectionOrderBuilder victim(Victim victim); + + /** + * Sets the operator matched. Use {@code null} to match all operators, + * which is the default behaviour + * + * @param operator the operator or {@code null} for all operators + * @return this builder + */ + SelectionOrderBuilder operator(Operator operator); + + /** + * Sets the scope matched. Use {@code null} to match all scopes, + * which is the default behaviour + * + * @param scope the scope or {@code null} for all scopes + * @return this builder + */ + SelectionOrderBuilder scope(ServerScope scope); + + /** + * Sets whether only active punishments should be matched. True by default, + * meaning only active punishments are selected.
+ *
+ * Active punishments are those not expired and not undone. + * + * @param selectActiveOnly whether to select active punishments only + * @return this builder + */ + SelectionOrderBuilder selectActiveOnly(boolean selectActiveOnly); + + /** + * Sets that only active punishments should be matched. Enabled by default, + * meaning only active punishments are selected.
+ *
+ * Active punishments are those not expired and not undone. + * + * @return this builder + */ + default SelectionOrderBuilder selectActiveOnly() { + return selectActiveOnly(true); + } + + + /** + * Sets that all punishments, not just those active, should be matched. When this + * method is used, historical punishments and expired punishments are selected.
+ *
+ * Active punishments are those not expired and not undone. + * + * @return this builder + */ + default SelectionOrderBuilder selectAll() { + return selectActiveOnly(false); + } + + /** + * Builds a {@link SelectionOrder} from the details of this builder. May be used repeatedly + * without side effects. + * + * @return a selection order from this builder's details + */ + SelectionOrder build(); + +} diff --git a/bans-api/src/test/java/space/arim/libertybans/api/VictimTest.java b/bans-api/src/test/java/space/arim/libertybans/api/VictimTest.java deleted file mode 100644 index 750c26128..000000000 --- a/bans-api/src/test/java/space/arim/libertybans/api/VictimTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * LibertyBans-api - * Copyright © 2020 Anand Beh - * - * LibertyBans-api is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-api is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-api. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.api; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import space.arim.libertybans.api.Victim.VictimType; - -public class VictimTest { - - @Test - public void testVictimTypeOrdinals() { - for (VictimType type : VictimType.values()) { - assertEquals(type, VictimType.fromOrdinal(type.ordinal())); - } - } - -} diff --git a/bans-api/src/test/java/space/arim/libertybans/api/example/WikiExamples.java b/bans-api/src/test/java/space/arim/libertybans/api/example/WikiExamples.java new file mode 100644 index 000000000..162d66557 --- /dev/null +++ b/bans-api/src/test/java/space/arim/libertybans/api/example/WikiExamples.java @@ -0,0 +1,98 @@ +/* + * LibertyBans-api + * Copyright © 2020 Anand Beh + * + * LibertyBans-api is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-api. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.api.example; + +import java.util.Set; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import space.arim.omnibus.Omnibus; +import space.arim.omnibus.OmnibusProvider; +import space.arim.omnibus.util.ThisClass; +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import space.arim.libertybans.api.LibertyBans; +import space.arim.libertybans.api.PlayerOperator; +import space.arim.libertybans.api.PlayerVictim; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.punish.DraftPunishment; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.revoke.PunishmentRevoker; +import space.arim.libertybans.api.revoke.RevocationOrder; +import space.arim.libertybans.api.select.PunishmentSelector; + +public class WikiExamples { + + private final LibertyBans libertyBans = findLibertyBansInstance(); + + private static LibertyBans findLibertyBansInstance() { + Omnibus omnibus = OmnibusProvider.getOmnibus(); + LibertyBans instance = omnibus.getRegistry().getProvider(LibertyBans.class); + if (instance == null) { + throw new IllegalStateException("LibertyBans not found"); + } + return instance; + } + + private final Logger logger = LoggerFactory.getLogger(ThisClass.get()); + + public void banPlayerUsingLibertyBans(UUID uuidToBan) { + + PunishmentDrafter drafter = libertyBans.getDrafter(); + + DraftPunishment draftBan = drafter.draftBuilder().type(PunishmentType.BAN).victim(PlayerVictim.of(uuidToBan)) + .reason("Because I said so").build(); + + draftBan.enactPunishment().thenAcceptSync((punishment) -> { + + // In this example it is assumed you have a logger + // You should not copy and paste examples verbatim + if (punishment == null) { + logger.info("UUID {} is already banned", uuidToBan); + return; + } + logger.info("ID of the enacted punishment is {}", punishment.getID()); + }); + } + + public CentralisedFuture> getMutesFrom(UUID staffMemberUuid) { + PunishmentSelector selector = libertyBans.getSelector(); + + return selector.selectionBuilder().operator(PlayerOperator.of(staffMemberUuid)).type(PunishmentType.MUTE) + .build().getAllSpecificPunishments(); + } + + public CentralisedFuture revokeBanFor(UUID bannedPlayer) { + PunishmentRevoker revoker = libertyBans.getRevoker(); + + // Relies on the fact a single victim can only have 1 active ban + RevocationOrder revocationOrder = revoker.revokeByTypeAndVictim(PunishmentType.BAN, PlayerVictim.of(bannedPlayer)); + return revocationOrder.undoPunishment().thenAccept((undone) -> { + if (undone) { + // ban existed and was undone + } else { + // there was no ban + } + }); + } + +} diff --git a/bans-bootstrap/pom.xml b/bans-bootstrap/pom.xml index 9f39bcba9..7447f0d3f 100644 --- a/bans-bootstrap/pom.xml +++ b/bans-bootstrap/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-parent - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-bootstrap @@ -35,19 +35,21 @@
+ + org.apache.maven.plugins + maven-failsafe-plugin +
- space.arim.libertybans - bans-api + org.junit.jupiter + junit-jupiter-api - org.junit.jupiter - junit-jupiter-api - test + junit-jupiter-params \ No newline at end of file diff --git a/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/InternalDependency.java b/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/InternalDependency.java index a55c37bd9..6e201c8b5 100644 --- a/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/InternalDependency.java +++ b/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/InternalDependency.java @@ -20,7 +20,7 @@ import space.arim.libertybans.bootstrap.depend.Repository; -public enum InternalDependency { +enum InternalDependency { HIKARICP("HikariCP", "com.zaxxer.hikari.HikariConfig", "hikaricp"), @@ -30,10 +30,12 @@ public enum InternalDependency { CAFFEINE("Caffeine", "com.github.benmanes.caffeine.cache.Caffeine", "caffeine"), JDBCAESAR("jdbcaesar", Repositories.ARIM_LESSER_GPL3), + DAZZLECONF_CORE("dazzleconf-core", Repositories.ARIM_LESSER_GPL3), + DAZZLECONF_EXT_SNAKEYAML("dazzleconf-ext-snakeyaml", Repositories.ARIM_LESSER_GPL3), ARIMAPI("arimapi", Repositories.ARIM_GPL3), MOREPAPERLIB("morepaperlib", Repositories.ARIM_LESSER_GPL3), - SELF("self", Repositories.ARIM_AFFERO_GPL3); + SELF_CORE("self-core", Repositories.ARIM_AFFERO_GPL3); final String name; final String clazz; diff --git a/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/LibertyBansLauncher.java b/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/LibertyBansLauncher.java index 7ec8a99ae..b139d7919 100644 --- a/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/LibertyBansLauncher.java +++ b/bans-bootstrap/src/main/java/space/arim/libertybans/bootstrap/LibertyBansLauncher.java @@ -18,8 +18,10 @@ */ package space.arim.libertybans.bootstrap; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; @@ -29,7 +31,9 @@ import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.util.ArrayList; import java.util.EnumMap; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -58,7 +62,7 @@ public LibertyBansLauncher(DependencyPlatform platform, Path folder, Executor ex DependencyLoaderBuilder internalDepLoader = new DependencyLoaderBuilder() .setExecutor(executor).setOutputDirectory(libsFolder.resolve("internal")); - var internalDeps = addInternalDepsStart(); + Map> internalDeps = addInternalDepsStart(); addApiDeps(apiDepLoader); addInternalDepsFinish(internalDepLoader, internalDeps); @@ -109,19 +113,28 @@ private static boolean classExists(String clazzName) { private void warnRelocation(String libName, String clazzName) { Class libClass = forName(clazzName); if (libClass != null) { - String plugin = getPluginFor.apply(libClass); - warn("Plugin '" + ((plugin == null) ? "Unknown" : plugin) + "' has shaded the library '" + libName - + "' but did not relocate it. This may or may not pose any problems. " + String pluginName = getPluginFor.apply(libClass); + warn("Plugin '" + ((pluginName.isEmpty()) ? "Unknown" : pluginName) + "' has shaded the library '" + + libName + "' but did not relocate it. This may or may not pose any problems. " + "Contact the author of this plugin and tell them to relocate their dependencies."); } } private Dependency readDependency0(String simpleDependencyName) { URL url = getClass().getClassLoader().getResource("dependencies/" + simpleDependencyName); - try (InputStream is = url.openStream()) { + try (InputStream inputStream = url.openStream(); + InputStreamReader inputReader = new InputStreamReader(inputStream, StandardCharsets.US_ASCII); + BufferedReader buffReader = new BufferedReader(inputReader)) { - String[] value = new String(is.readAllBytes(), StandardCharsets.US_ASCII).split("\n"); - return Dependency.of(value[0], value[1], value[2], value[3]); + List lines = new ArrayList<>(); + String line; + while ((line = buffReader.readLine()) != null) { + lines.add(line); + } + if (lines.size() < 4) { + throw new IllegalArgumentException("Dependency file for " + simpleDependencyName + " is malformatted"); + } + return Dependency.of(lines.get(0), lines.get(1), lines.get(2), lines.get(3)); } catch (IOException ex) { throw new UncheckedIOException(ex); @@ -138,12 +151,16 @@ private CompletableFuture readDependency(String simpleDependencyName * */ private void addApiDeps(DependencyLoaderBuilder loader) { + CompletableFuture selfApi = readDependency("self-api"); if (!classExists("space.arim.omnibus.Omnibus")) { - loader.addPair(readDependency0("omnibus"), Repositories.ARIM_GPL3); + loader.addPair(readDependency0("omnibus"), Repositories.ARIM_LESSER_GPL3); } if (!classExists("space.arim.uuidvault.api.UUIDVault")) { loader.addPair(readDependency0("uuidvault"), Repositories.ARIM_LESSER_GPL3); } + if (!skipSelfDependencies()) { + loader.addPair(selfApi.join(), Repositories.ARIM_AFFERO_GPL3); + } } /* @@ -174,10 +191,18 @@ private void addInternalDepsFinish(DependencyLoaderBuilder loader, if (internalDep.clazz != null) { warnRelocation(internalDep.name, internalDep.clazz); } + if (skipSelfDependencies() && internalDep == InternalDependency.SELF_CORE) { + continue; + } loader.addPair(internalDeps.get(internalDep).join(), internalDep.repo); } } + /* Used in testing */ + protected boolean skipSelfDependencies() { + return false; + } + // Must use System.err because we do not know whether the platform uses slf4j or JUL private void warn(String message) { System.err.println("[LibertyBans] " + message); diff --git a/bans-bootstrap/src/main/resources/dependencies/arimapi b/bans-bootstrap/src/main/resources/dependencies/arimapi index fdd331844..6658eab7e 100644 --- a/bans-bootstrap/src/main/resources/dependencies/arimapi +++ b/bans-bootstrap/src/main/resources/dependencies/arimapi @@ -1,4 +1,4 @@ space.arim.api -arimapi-assemble-noplugin -0.19.13-SNAPSHOT -06ae2044af08804a1e7b4a3f69a864f4d8f6fb3cbe059435871179660985a88e944d5d9e89a83767cb0121cdc7e8e7243eb1038742b85acf692c7ea431634722 +arimapi-all +${arimapi.version} +743efbe65da49ddee948b7bbd1be616b89cdba4743a2ac3ee9363c6a3e09c6caf8aa469ced08674521ac262df0cd0df87521339e656e834f4ac194563bf8057d diff --git a/bans-bootstrap/src/main/resources/dependencies/caffeine b/bans-bootstrap/src/main/resources/dependencies/caffeine index 577226bec..c60852aff 100644 --- a/bans-bootstrap/src/main/resources/dependencies/caffeine +++ b/bans-bootstrap/src/main/resources/dependencies/caffeine @@ -1,4 +1,4 @@ com.github.ben-manes.caffeine caffeine -2.8.5 +${caffeine.version} 9351c0e57aefd58f468ad5eced9c828254e7d19692654eac81cfeba0d72d4d91f5021eaaad533d6c292553f0487a31f92d73b0ee711296ab76ba6d168056a5c6 diff --git a/bans-bootstrap/src/main/resources/dependencies/dazzleconf-core b/bans-bootstrap/src/main/resources/dependencies/dazzleconf-core new file mode 100644 index 000000000..7616b70b1 --- /dev/null +++ b/bans-bootstrap/src/main/resources/dependencies/dazzleconf-core @@ -0,0 +1,4 @@ +space.arim.dazzleconf +dazzleconf-core +${dazzleconf.version} +f7bd1349151f7bd6f2fd1fbc7474cd841095e54b0ca143e2e4d0dde2ba5c22da0e143dc136a5a37c4801d4a2b2763ad01be846e83cb947d393954e54fcdec339 diff --git a/bans-bootstrap/src/main/resources/dependencies/dazzleconf-ext-snakeyaml b/bans-bootstrap/src/main/resources/dependencies/dazzleconf-ext-snakeyaml new file mode 100644 index 000000000..d4e8e08f4 --- /dev/null +++ b/bans-bootstrap/src/main/resources/dependencies/dazzleconf-ext-snakeyaml @@ -0,0 +1,4 @@ +space.arim.dazzleconf +dazzleconf-ext-snakeyaml +${dazzleconf.version} +f90e0550309e4e6e50ad4589d9b00b37a0454725d914596b5b2899c1dffda2854758f54301bb74e149ca2aad4ab142a218302eff2f1d989f5189810a3709bb8d diff --git a/bans-bootstrap/src/main/resources/dependencies/hikaricp b/bans-bootstrap/src/main/resources/dependencies/hikaricp index 712f7586a..9c90f2cc7 100644 --- a/bans-bootstrap/src/main/resources/dependencies/hikaricp +++ b/bans-bootstrap/src/main/resources/dependencies/hikaricp @@ -1,4 +1,4 @@ com.zaxxer HikariCP -3.4.5 +${hikari.version} 4e70e08544e199eed09f5f3681ab5a39681d8d6eb6c9ca2cb6a78535aedbcc66b4292330bbcb40bfb8bf8a6f284d013e55a039542ffc85222d6ac26f816eafd3 diff --git a/bans-bootstrap/src/main/resources/dependencies/hsqldb b/bans-bootstrap/src/main/resources/dependencies/hsqldb index 407e29cbe..07a35eba5 100644 --- a/bans-bootstrap/src/main/resources/dependencies/hsqldb +++ b/bans-bootstrap/src/main/resources/dependencies/hsqldb @@ -1,4 +1,4 @@ org.hsqldb hsqldb -2.5.1 +${hsqldb.version} 5539ef60987d6bd801c4ced80ede3138578307122be92dedbf2c6a48ea58db6e5e6c064d36de6f1fc0ccb6991213eb801ec036957edde48a358657e0cb8d4e62 diff --git a/bans-bootstrap/src/main/resources/dependencies/jdbcaesar b/bans-bootstrap/src/main/resources/dependencies/jdbcaesar index 909827fe0..7768d13dd 100644 --- a/bans-bootstrap/src/main/resources/dependencies/jdbcaesar +++ b/bans-bootstrap/src/main/resources/dependencies/jdbcaesar @@ -1,4 +1,4 @@ space.arim.jdbcaesar jdbcaesar -0.5.0 +${jdbcaesar.version} 122f9bcf21c0211c7168d9584c3cea6dbd7640c00a844a26e101980fe33f2bff355edd79337135f73352a3a2fe975c01d9aeb31a8bbe740b47507eac81c0445a diff --git a/bans-bootstrap/src/main/resources/dependencies/mariadb-connector b/bans-bootstrap/src/main/resources/dependencies/mariadb-connector index b00f9dab7..e02dc235c 100644 --- a/bans-bootstrap/src/main/resources/dependencies/mariadb-connector +++ b/bans-bootstrap/src/main/resources/dependencies/mariadb-connector @@ -1,4 +1,4 @@ org.mariadb.jdbc mariadb-java-client -2.6.2 +${mariadb-connector.version} d96c4529e5547ea02a0d0b66c518741a63e427156b422bcb6d879b48e42b107adf9186d67f4cf00e1a99fcb6190e398a893d3623c841e2dedf0b0781f0daa363 diff --git a/bans-bootstrap/src/main/resources/dependencies/morepaperlib b/bans-bootstrap/src/main/resources/dependencies/morepaperlib index d37877c54..d4c1e5599 100644 --- a/bans-bootstrap/src/main/resources/dependencies/morepaperlib +++ b/bans-bootstrap/src/main/resources/dependencies/morepaperlib @@ -1,4 +1,4 @@ space.arim.morepaperlib morepaperlib -0.1.0-SNAPSHOT +${morepaperlib.version} 289bd60f519d1151ca6957a839339a168828cd983f30321ccc785f48223b5282b0bd6f6516871c0424948373ddd0e3cda824f6ce0e48ebc3593d4890101752a6 diff --git a/bans-bootstrap/src/main/resources/dependencies/omnibus b/bans-bootstrap/src/main/resources/dependencies/omnibus index 88931b12b..190057a00 100644 --- a/bans-bootstrap/src/main/resources/dependencies/omnibus +++ b/bans-bootstrap/src/main/resources/dependencies/omnibus @@ -1,4 +1,4 @@ space.arim.omnibus -omnibus-all-shaded -0.15.5-SNAPSHOT -3067ddde771f4d80e54f4ec036b6c7e24f79596a7028134d1248ddae6aae700748ea60b99f38f9ff7c57122c5cdb286c0417c3025328c932a04fe56b64a24d14 +omnibus +${omnibus.version} +93b85e4bd8bca9de875af176cac546153405f0018382ad2de061f633796e2277632bd0b3d0db0abd6f32313bab91474eebdd40a5c8a25f4a789926524c11f856 diff --git a/bans-bootstrap/src/main/resources/dependencies/self b/bans-bootstrap/src/main/resources/dependencies/self deleted file mode 100644 index 0987e2522..000000000 --- a/bans-bootstrap/src/main/resources/dependencies/self +++ /dev/null @@ -1,4 +0,0 @@ -space.arim.libertybans -bans-dl -${project.version} -abd2f6503a35d4c7d21b22fa21868154f736ac212af19e3cdb89b83803a8074217399bc6527205720c9d1dd2b207b7b69aec4a7e823bd20880d4c1cfdf9b5d32 diff --git a/bans-bootstrap/src/main/resources/dependencies/self-api b/bans-bootstrap/src/main/resources/dependencies/self-api new file mode 100644 index 000000000..42ab4ea2f --- /dev/null +++ b/bans-bootstrap/src/main/resources/dependencies/self-api @@ -0,0 +1,4 @@ +space.arim.libertybans +bans-api +${project.version} +7372160e3521b3d72a5a59248b5c67ea23f1c86c9b925229e041848c65355fcaf3e5907fb6d77252b2d26244a8d6b5fc759c676a480a30ce500d66809995083e diff --git a/bans-bootstrap/src/main/resources/dependencies/self-core b/bans-bootstrap/src/main/resources/dependencies/self-core new file mode 100644 index 000000000..6190e191a --- /dev/null +++ b/bans-bootstrap/src/main/resources/dependencies/self-core @@ -0,0 +1,4 @@ +space.arim.libertybans +bans-dl +${project.version} +e63a68bc4a1568dd5767ac739dbc5c1ddf95b864082b364989032e626889cddea034bdc3c59d3ca786b877180d77ab5f5cace062a04e1a4a517c91e6d0139ac4 diff --git a/bans-bootstrap/src/main/resources/dependencies/slf4j-api b/bans-bootstrap/src/main/resources/dependencies/slf4j-api index c0d894aaf..78b206013 100644 --- a/bans-bootstrap/src/main/resources/dependencies/slf4j-api +++ b/bans-bootstrap/src/main/resources/dependencies/slf4j-api @@ -1,4 +1,4 @@ org.slf4j slf4j-api -1.7.30 +${slf4j.version} e5435852569dda596ba46138af8ee9c4ecba8a7a43f4f1e7897aeb4430523a0f037088a7b63877df5734578f19d331f03d7b0f32d5ae6c425df211947b3e6173 diff --git a/bans-bootstrap/src/main/resources/dependencies/slf4j-jdk14 b/bans-bootstrap/src/main/resources/dependencies/slf4j-jdk14 index 081b787e1..553064f0d 100644 --- a/bans-bootstrap/src/main/resources/dependencies/slf4j-jdk14 +++ b/bans-bootstrap/src/main/resources/dependencies/slf4j-jdk14 @@ -1,4 +1,4 @@ org.slf4j slf4j-jdk14 -1.7.30 +${slf4j.version} e6d624b0c891809ae6d62fb2785b1562f01ea92048ef39df0b2cd1aa607135c62eaa4c1964c0a399c7a8f329be55edff6cf572a910af4547d82ae43ff3c7727a diff --git a/bans-bootstrap/src/main/resources/dependencies/uuidvault b/bans-bootstrap/src/main/resources/dependencies/uuidvault index cf6978dc5..a1b2adfbc 100644 --- a/bans-bootstrap/src/main/resources/dependencies/uuidvault +++ b/bans-bootstrap/src/main/resources/dependencies/uuidvault @@ -1,4 +1,4 @@ space.arim.uuidvault assemble -0.5.2-SNAPSHOT -a91af1c02b604b4f16273ab0a76cefa9c24ea1cc8c06f1c383b8ae608ddd405661bd9ebd4c7f8cbf37dbd68e84e81755471cc441d8b73215a6c517786a3425f5 +${uuidvault.version} +fb4ba1ffa24d1f0bba8ebbc2de93849d53278cc6b54cc686559cf1f53d4155c5bb0d7688072bdc6bdf65439a27adc51ae8c16d0bacff93630c7f2ed1f8a22106 diff --git a/bans-bootstrap/src/test/java/space/arim/libertybans/bootstrap/DownloadDependenciesIT.java b/bans-bootstrap/src/test/java/space/arim/libertybans/bootstrap/DownloadDependenciesIT.java new file mode 100644 index 000000000..28300021b --- /dev/null +++ b/bans-bootstrap/src/test/java/space/arim/libertybans/bootstrap/DownloadDependenciesIT.java @@ -0,0 +1,72 @@ +/* + * LibertyBans-bootstrap + * Copyright © 2020 Anand Beh + * + * LibertyBans-bootstrap is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-bootstrap is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-bootstrap. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.bootstrap; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +public class DownloadDependenciesIT { + + @TempDir + public Path folder; + + private ExecutorService executor; + + @BeforeEach + public void setup() { + executor = Executors.newCachedThreadPool(); + } + + @ParameterizedTest + @EnumSource(DependencyPlatform.class) + public void testDownloadAllDependencies(DependencyPlatform platform) { + LibertyBansLauncher launcher = new LibertyBansLauncher(platform, folder, executor, (clazz) -> "") { + @Override + protected boolean addUrlsToExternalClassLoader(ClassLoader apiClassLoader, Path[] paths) { + for (Path path : paths) { + System.out.println("Downloaded to " + path.getFileName()); + } + return true; + } + @Override + protected boolean skipSelfDependencies() { + return true; + } + }; + ClassLoader classLoader = launcher.attemptLaunch().join(); + assertNotNull(classLoader, "Failed to download dependencies"); + } + + @AfterEach + public void tearDown() throws InterruptedException { + executor.shutdown(); + executor.awaitTermination(3, TimeUnit.SECONDS); + } + +} diff --git a/bans-core/pom.xml b/bans-core/pom.xml index ae0cbbcaa..5c3c48ba2 100644 --- a/bans-core/pom.xml +++ b/bans-core/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-parent - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-core @@ -26,14 +26,14 @@ space.arim.libertybans bans-bootstrap - - space.arim.libertybans - bans-database - space.arim.jdbcaesar jdbcaesar + + space.arim.dazzleconf + dazzleconf-ext-snakeyaml + space.arim.api arimapi-all diff --git a/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansCore.java b/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansCore.java index f9a8ba12e..f1770d5fb 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansCore.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/LibertyBansCore.java @@ -19,29 +19,30 @@ package space.arim.libertybans.core; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.concurrent.ThreadFactory; import space.arim.omnibus.Omnibus; -import space.arim.omnibus.util.ThisClass; +import space.arim.omnibus.util.concurrent.CentralisedFuture; +import space.arim.omnibus.util.concurrent.EnhancedExecutor; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import space.arim.libertybans.api.LibertyBans; +import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.api.revoke.PunishmentRevoker; import space.arim.libertybans.core.commands.Commands; import space.arim.libertybans.core.config.Configs; import space.arim.libertybans.core.config.Formatter; +import space.arim.libertybans.core.config.MainConfig; +import space.arim.libertybans.core.config.MessagesConfig; import space.arim.libertybans.core.database.DatabaseManager; import space.arim.libertybans.core.database.Database; import space.arim.libertybans.core.env.AbstractEnv; import space.arim.libertybans.core.env.EnvironmentManager; +import space.arim.libertybans.core.punish.EnforcementCenter; +import space.arim.libertybans.core.punish.Scoper; import space.arim.libertybans.core.selector.MuteCacher; import space.arim.libertybans.core.selector.Selector; -import space.arim.libertybans.core.uuid.UUIDMaster; +import space.arim.libertybans.core.uuid.UUIDManager; public class LibertyBansCore implements LibertyBans, Part { @@ -52,23 +53,17 @@ public class LibertyBansCore implements LibertyBans, Part { private final Resources resources; private final Configs configs; private final DatabaseManager databaseManager; - private final UUIDMaster uuidMaster; + private final UUIDManager uuidMaster; private final EnvironmentManager envManager; + private final EnforcementCenter enforcement; private final Selector selector; private final MuteCacher cacher; - private final Enactor enactor; - private final Enforcer enforcer; private final Scoper scoper; private final Formatter formatter; private final Commands commands; - /** Delayed shutdown hooks, must be synchronized on before access */ - private final List delayedShutdownHooks = new ArrayList<>(); - - private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); - public LibertyBansCore(Omnibus omnibus, Path folder, AbstractEnv environment) { this.omnibus = omnibus; @@ -76,15 +71,14 @@ public LibertyBansCore(Omnibus omnibus, this.environment = environment; resources = new Resources(this); - configs = new Configs(this); + configs = new Configs(folder); databaseManager = new DatabaseManager(this); - uuidMaster = new UUIDMaster(this); + uuidMaster = new UUIDManager(this); envManager = new EnvironmentManager(this); selector = new Selector(this); cacher = new MuteCacher(this); - enactor = new Enactor(this); - enforcer = new Enforcer(this); + enforcement = new EnforcementCenter(this); scoper = new Scoper(); formatter = new Formatter(this); @@ -93,39 +87,31 @@ public LibertyBansCore(Omnibus omnibus, @Override public void startup() { - getResources().startup(); + resources.startup(); getConfigs().startup(); - getDatabaseManager().startup(); - getUUIDMaster().startup(); + databaseManager.startup(); + uuidMaster.startup(); envManager.startup(); } @Override public void restart() { envManager.shutdown(); - getResources().restart(); - getUUIDMaster().restart(); + resources.restart(); + uuidMaster.restart(); getConfigs().restart(); - getDatabaseManager().restart(); + databaseManager.restart(); envManager.startup(); } @Override public void shutdown() { envManager.shutdown(); - getResources().shutdown(); - getUUIDMaster().shutdown(); + uuidMaster.shutdown(); getConfigs().shutdown(); - getDatabaseManager().shutdown(); + databaseManager.shutdown(); - // 4 seconds should be sufficient - CompletableFuture.runAsync(() -> { - synchronized (delayedShutdownHooks) { - for (Runnable hook : delayedShutdownHooks) { - hook.run(); - } - } - }, CompletableFuture.delayedExecutor(4, TimeUnit.SECONDS)).join(); + resources.shutdown(); } public Path getFolder() { @@ -143,17 +129,25 @@ public Omnibus getOmnibus() { @Override public FactoryOfTheFuture getFuturesFactory() { - return getResources().getFuturesFactory(); + return resources.getFuturesFactory(); } - public Resources getResources() { - return resources; + public EnhancedExecutor getEnhancedExecutor() { + return resources.getEnhancedExecutor(); } public Configs getConfigs() { return configs; } + public MainConfig getMainConfig() { + return getConfigs().getMainConfig(); + } + + public MessagesConfig getMessagesConfig() { + return getConfigs().getMessagesConfig(); + } + @Override public Database getDatabase() { return databaseManager.getCurrentDatabase(); @@ -163,7 +157,7 @@ public DatabaseManager getDatabaseManager() { return databaseManager; } - public UUIDMaster getUUIDMaster() { + public UUIDManager getUUIDMaster() { return uuidMaster; } @@ -177,20 +171,25 @@ public MuteCacher getMuteCacher() { } @Override - public Enactor getEnactor() { - return enactor; + public PunishmentDrafter getDrafter() { + return enforcement; } + public EnforcementCenter getEnforcementCenter() { + return enforcement; + } + @Override - public Enforcer getEnforcer() { - return enforcer; + public PunishmentRevoker getRevoker() { + return enforcement.getRevoker(); } - + @Override public Scoper getScopeManager() { return scoper; } + @Override public Formatter getFormatter() { return formatter; } @@ -199,16 +198,12 @@ public Commands getCommands() { return commands; } - public void addDelayedShutdownHook(Runnable hook) { - synchronized (delayedShutdownHooks) { - delayedShutdownHooks.add(hook); - } + public void postFuture(CentralisedFuture future) { + resources.postFuture(future); } - public void debugFuture(@SuppressWarnings("unused") T value, Throwable ex) { - if (ex != null) { - logger.warn("Miscellaneous issue executing asynchronous computation", ex); - } + public ThreadFactory newThreadFactory(String component) { + return new ThreadFactoryImpl("LibertyBans-" + component + "-"); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/Resources.java b/bans-core/src/main/java/space/arim/libertybans/core/Resources.java index ea730d624..fd0bd4b3e 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/Resources.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/Resources.java @@ -18,52 +18,55 @@ */ package space.arim.libertybans.core; -import space.arim.omnibus.resourcer.ResourceHook; -import space.arim.omnibus.resourcer.Resourcer; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.omnibus.util.concurrent.EnhancedExecutor; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; -import space.arim.api.env.PlatformHandle; +import space.arim.libertybans.bootstrap.ShutdownException; -public class Resources implements Part { +class Resources implements Part { private final LibertyBansCore core; - private volatile HookBundle bundle; + private FactoryOfTheFuture futuresFactory; + private EnhancedExecutor enhancedExecutor; + + /** + * Awaits the execution of all independent asynchronous execution chains. + * Ensures all plugin operations can be shutdown. + */ + private final ExecutorService universalJoiner; Resources(LibertyBansCore core) { this.core = core; + universalJoiner = Executors.newFixedThreadPool(1, core.newThreadFactory("Joiner")); } FactoryOfTheFuture getFuturesFactory() { - return bundle.futuresFactory.getResource(); + return futuresFactory; } - public EnhancedExecutor getEnhancedExecutor() { - return bundle.enhancedExecutor.getResource(); - } - - private Resourcer getResourcer() { - return core.getOmnibus().getResourcer(); + synchronized EnhancedExecutor getEnhancedExecutor() { + if (enhancedExecutor == null) { + enhancedExecutor = core.getEnvironment().getPlatformHandle().createEnhancedExecutor(); + } + return enhancedExecutor; } - private static class HookBundle { - final ResourceHook futuresFactory; - final ResourceHook enhancedExecutor; - - HookBundle(ResourceHook futuresFactory, ResourceHook enhancedExecutor) { - this.futuresFactory = futuresFactory; - this.enhancedExecutor = enhancedExecutor; - } + void postFuture(CentralisedFuture future) { + universalJoiner.execute(future::join); } @Override public void startup() { - Resourcer resourcer = getResourcer(); - PlatformHandle handle = core.getEnvironment().getPlatformHandle(); - ResourceHook futuresFactory = handle.hookPlatformResource(resourcer, FactoryOfTheFuture.class); - ResourceHook enhancedExecutor = handle.hookPlatformResource(resourcer, EnhancedExecutor.class); - bundle = new HookBundle(futuresFactory, enhancedExecutor); + futuresFactory = core.getEnvironment().getPlatformHandle().createFuturesFactory(); } @Override @@ -71,13 +74,41 @@ public void restart() {} @Override public void shutdown() { - final HookBundle bundle = this.bundle; - Resourcer resourcer = getResourcer(); - core.addDelayedShutdownHook(() -> { - resourcer.unhookUsage(bundle.futuresFactory); - resourcer.unhookUsage(bundle.enhancedExecutor); - }); - this.bundle = null; + universalJoiner.shutdown(); + /* + * On Bukkit, this prevents deadlocks. + * + * By awaiting termination through the futures factory, the managed wait + * implementation breaks dependencies arising from tasks needing the main thread + * The main thread executes this shutdown() method, so if it is simply blocked, + * tasks depending on it cannot complete. + * + * On any platform other than Bukkit, this is uninmportant. + */ + boolean termination = futuresFactory.supplyAsync(() -> { + try { + return universalJoiner.awaitTermination(6L, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + getLogger().warn("Failed to shutdown all chains of asynchronous execution (Interrupted)", ex); + return true; + } + }).join(); + + if (!termination) { + getLogger().warn("Failed to shutdown all chains of asynchronous execution"); + } + if (futuresFactory instanceof AutoCloseable) { + try { + ((AutoCloseable) futuresFactory).close(); + } catch (Exception ex) { + throw new ShutdownException("Cannot shutdown factory of the future", ex); + } + } + } + + private Logger getLogger() { + return LoggerFactory.getLogger(getClass()); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/SecurePunishment.java b/bans-core/src/main/java/space/arim/libertybans/core/SecurePunishment.java deleted file mode 100644 index cfba05434..000000000 --- a/bans-core/src/main/java/space/arim/libertybans/core/SecurePunishment.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-core is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-core. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.core; - -import space.arim.libertybans.api.AbstractPunishment; -import space.arim.libertybans.api.Operator; -import space.arim.libertybans.api.Punishment; -import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Scope; -import space.arim.libertybans.api.Victim; - -public class SecurePunishment extends AbstractPunishment implements Punishment { - - private final int id; - - public SecurePunishment(int id, PunishmentType type, Victim victim, Operator operator, String reason, Scope scope, - long start, long end) { - super(type, victim, operator, reason, scope, start, end); - this.id = id; - } - - @Override - public int getID() { - return id; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + id; - return result; - } - - @Override - public boolean equals(Object object) { - return this == object || object instanceof SecurePunishment && id == ((SecurePunishment) object).id; - } - -} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/ThreadFactoryImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/ThreadFactoryImpl.java new file mode 100644 index 000000000..75f0db68a --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/ThreadFactoryImpl.java @@ -0,0 +1,50 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core; + +import java.util.concurrent.ThreadFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import space.arim.omnibus.util.ThisClass; + +class ThreadFactoryImpl implements ThreadFactory { + + private final String prefix; + private int threadId; + + private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); + + ThreadFactoryImpl(String prefix) { + this.prefix = prefix; + } + + private synchronized int nextId() { + return ++threadId; + } + + @Override + public Thread newThread(Runnable r) { + String name = prefix + nextId(); + logger.debug("Spawning new thread {}", name); + return new Thread(r, name); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractCommandExecution.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractCommandExecution.java new file mode 100644 index 000000000..4f1c8f523 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractCommandExecution.java @@ -0,0 +1,41 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.commands; + +import space.arim.libertybans.core.env.CmdSender; + +abstract class AbstractCommandExecution implements CommandExecution { + + private final CmdSender sender; + private final CommandPackage command; + + AbstractCommandExecution(CmdSender sender, CommandPackage command) { + this.sender = sender; + this.command = command; + } + + CmdSender sender() { + return sender; + } + + CommandPackage command() { + return command; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractSubCommandGroup.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractSubCommandGroup.java index 90d9d9f72..d85136e76 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractSubCommandGroup.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/AbstractSubCommandGroup.java @@ -19,12 +19,14 @@ package space.arim.libertybans.core.commands; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import space.arim.omnibus.util.concurrent.CentralisedFuture; -import space.arim.api.configure.ConfigAccessor; - import space.arim.libertybans.core.LibertyBansCore; +import space.arim.libertybans.core.config.MainConfig; +import space.arim.libertybans.core.config.MessagesConfig; public abstract class AbstractSubCommandGroup implements SubCommandGroup { @@ -35,9 +37,17 @@ public abstract class AbstractSubCommandGroup implements SubCommandGroup { */ private final Set matches; - AbstractSubCommandGroup(Commands commands, String...matches) { + AbstractSubCommandGroup(Commands commands, Set matches) { this.commands = commands; - this.matches = Set.of(matches); + this.matches = Set.copyOf(matches); + } + + AbstractSubCommandGroup(Commands commands, String...matches) { + this(commands, Set.of(matches)); + } + + AbstractSubCommandGroup(Commands commands, Stream matches) { + this(commands, matches.collect(Collectors.toUnmodifiableSet())); } @Override @@ -52,19 +62,19 @@ public boolean matches(String arg) { */ CentralisedFuture completedFuture(T value) { - return commands.core.getFuturesFactory().completedFuture(value); + return core().getFuturesFactory().completedFuture(value); } LibertyBansCore core() { return commands.core; } - ConfigAccessor config() { - return commands.core.getConfigs().getConfig(); + MainConfig config() { + return core().getConfigs().getMainConfig(); } - ConfigAccessor messages() { - return commands.core.getConfigs().getMessages(); + MessagesConfig messages() { + return core().getMessagesConfig(); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/AdminCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/AdminCommands.java new file mode 100644 index 000000000..c80c5345e --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/AdminCommands.java @@ -0,0 +1,93 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.commands; + +import java.util.concurrent.CompletableFuture; + +import space.arim.libertybans.core.config.MessagesConfig; +import space.arim.libertybans.core.env.CmdSender; + +public class AdminCommands extends AbstractSubCommandGroup { + + AdminCommands(Commands commands) { + super(commands, "reload", "restart"); + } + + private MessagesConfig.Admin cfg() { + return messages().admin(); + } + + @Override + public CommandExecution execute(CmdSender sender, CommandPackage command, String arg) { + return new Execution(sender, command, arg); + } + + private class Execution extends AbstractCommandExecution { + + private final String arg; + + Execution(CmdSender sender, CommandPackage command, String arg) { + super(sender, command); + this.arg = arg; + } + + @Override + public void execute() { + if (!sender().hasPermission("libertybans.admin." + arg)) { + sender().sendMessage(cfg().noPermission()); + return; + } + switch (arg) { + case "restart": + restartCmd(); + break; + case "reload": + reloadCmd(); + break; + default: + throw new IllegalArgumentException("Command mismatch"); + } + } + + private void restartCmd() { + sender().sendMessage(cfg().ellipses()); + boolean restarted = core().getEnvironment().fullRestart(); + if (restarted) { + sender().sendMessage(cfg().restarted()); + } else { + sender().sendLiteralMessage("Not restarting because loading already in process"); + } + } + + private void reloadCmd() { + sender().sendMessage(cfg().ellipses()); + CompletableFuture reloadFuture = core().getConfigs().reloadConfigs().thenAccept((result) -> { + if (result) { + sender().sendMessage(cfg().reloaded()); + } else { + sender().sendLiteralMessage( + "&cAn error occurred reloading the configuration. Please check the server console."); + } + }); + core().postFuture(core().getFuturesFactory().copyFuture(reloadFuture)); + } + + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandExecution.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandExecution.java new file mode 100644 index 000000000..5917a9ebe --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/CommandExecution.java @@ -0,0 +1,29 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.commands; + +interface CommandExecution { + + void execute(); + + static CommandExecution empty() { + return () -> {}; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/Commands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/Commands.java index 23c6510ed..ca06f692f 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/Commands.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/Commands.java @@ -40,7 +40,7 @@ public class Commands { public Commands(LibertyBansCore core) { this.core = core; - subCommands = List.of(new PunishCommands(this), new UnpunishCommands(this), new ReloadCommands(this)); + subCommands = List.of(new PunishCommands(this), new UnpunishCommands(this), new AdminCommands(this)); } /* @@ -49,21 +49,45 @@ public Commands(LibertyBansCore core) { public void execute(CmdSender sender, CommandPackage command) { if (!sender.hasPermission("libertybans.commands")) { - sender.parseThenSend(core.getConfigs().getMessages().getString("all.base-permission-message")); + sender.sendMessage(core.getMessagesConfig().all().basePermissionMessage()); return; } if (!command.hasNext()) { - sender.parseThenSend("&7&lLibertyBans version " + PluginInfo.VERSION); + sender.sendLiteralMessage("&7&lLibertyBans version " + PluginInfo.VERSION); return; } - String firstArg = command.next().toLowerCase(Locale.ENGLISH); + String firstArg = command.next().toLowerCase(Locale.ROOT); for (SubCommandGroup subCommand : subCommands) { if (subCommand.matches(firstArg)) { - subCommand.execute(sender, command, firstArg); + CommandExecution execution = subCommand.execute(sender, command, firstArg); + execution.execute(); return; } } - sender.parseThenSend(core.getConfigs().getMessages().getString("all.usage")); + sendUsage(sender, command, firstArg.equals("usage")); + } + + /* + * Usage + */ + + private void sendUsage(CmdSender sender, CommandPackage command, boolean explicit) { + if (!explicit) { + sender.sendMessage(core.getMessagesConfig().all().usage()); + } + UsageSection[] sections = UsageSection.values(); + int page = 1; + if (command.hasNext()) { + try { + page = Integer.parseInt(command.next()); + } catch (NumberFormatException ignored) {} + if (page <= 0 || page > sections.length) { + page = 1; + } + } + UsageSection section = sections[page - 1]; + sender.sendMessage(section.getContent()); + sender.sendLiteralMessage("&ePage " + page + "/4. &7Use /libertybans usage to navigate"); } /* @@ -92,8 +116,7 @@ CentralisedFuture parseVictim(CmdSender sender, String targetArg) { } return core.getUUIDMaster().fullLookupUUID(targetArg).thenApply((uuid) -> { if (uuid == null) { - sender.parseThenSend(core.getConfigs().getMessages().getString("all.not-found.uuid") - .replace("%TARGET%", targetArg)); + sender.sendMessage(core.getMessagesConfig().all().notFound().uuid().replaceText("%TARGET%", targetArg)); return null; } return PlayerVictim.of(uuid); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/DurationParser.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/DurationParser.java new file mode 100644 index 000000000..5f81d16a5 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/DurationParser.java @@ -0,0 +1,69 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.commands; + +import java.time.Duration; +import java.util.Set; + +class DurationParser { + + private final String argument; + private final Set permanentArguments; + + DurationParser(String argument, Set permanentArguments) { + this.argument = argument; + this.permanentArguments = permanentArguments; + } + + Duration parse() { + if (permanentArguments.contains(argument)) { + return Duration.ZERO; + } + char[] characters = argument.toCharArray(); + int unitIndex = 0; + for (int n = 0; n < characters.length; n++) { + if (!Character.isDigit(characters[n])) { + unitIndex = n; + break; + } + } + if (unitIndex == 0) { + return Duration.ZERO; + } + long number = Long.parseLong(argument.substring(0, unitIndex)); + switch (argument.substring(unitIndex)) { + case "Y": + case "y": + return Duration.ofDays(365L * number); + case "MO": + case "mo": + return Duration.ofDays(30L * number); + case "D": + case "d": + return Duration.ofDays(number); + case "M": + case "m": + return Duration.ofMinutes(number); + default: + break; + } + return Duration.ZERO; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java index 43e1c3cad..31040877c 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/PunishCommands.java @@ -18,95 +18,133 @@ */ package space.arim.libertybans.core.commands; -import java.util.Arrays; +import java.time.Duration; import java.util.Locale; -import java.util.concurrent.CompletableFuture; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.api.chat.SendableMessage; -import space.arim.libertybans.api.DraftPunishment; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.core.MiscUtil; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.DraftPunishment; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.core.config.AdditionsSection.PunishmentAddition; +import space.arim.libertybans.core.config.AdditionsSection.ExclusivePunishmentAddition; +import space.arim.libertybans.core.config.MainConfig; import space.arim.libertybans.core.env.CmdSender; +import space.arim.libertybans.core.punish.MiscUtil; public class PunishCommands extends AbstractSubCommandGroup { PunishCommands(Commands commands) { - super(commands, Arrays.stream(MiscUtil.punishmentTypes()).map((type) -> type.name().toLowerCase(Locale.ENGLISH)) - .toArray(String[]::new)); + super(commands, MiscUtil.punishmentTypes().stream().map((type) -> type.name().toLowerCase(Locale.ROOT))); } @Override - public void execute(CmdSender sender, CommandPackage command, String arg) { - execute(sender, command, PunishmentType.valueOf(arg.toUpperCase(Locale.ENGLISH))); + public CommandExecution execute(CmdSender sender, CommandPackage command, String arg) { + return new Execution(sender, command, PunishmentType.valueOf(arg.toUpperCase(Locale.ROOT))); } - private void execute(CmdSender sender, CommandPackage command, PunishmentType type) { - if (!sender.hasPermission("libertybans." + type.getLowercaseName() + ".do")) { // libertybans.ban.do - String path = "additions." + type.getLowercaseNamePlural() + ".permission.command"; // additions.bans.permission.command - sender.parseThenSend(messages().getString(path)); - return; + private class Execution extends TypeSpecificExecution { + + private final PunishmentAddition section; + + Execution(CmdSender sender, CommandPackage command, PunishmentType type) { + super(sender, command, type); + section = messages().additions().forType(type); + } + + @Override + public void execute() { + if (!sender().hasPermission("libertybans." + type().getLowercaseName() + ".command")) { // libertybans.ban.command + sender().sendMessage(section.permissionCommand()); + return; + } + if (!command().hasNext()) { + sender().sendMessage(section.usage()); + return; + } + execute0(); } - if (!command.hasNext()) { - sender.parseThenSend(messages().getString( - "additions." + type.getLowercaseNamePlural() + ".usage")); // additions.bans.usage - return; + + private void execute0() { + String targetArg = command().next(); + CentralisedFuture future = commands.parseVictim(sender(), targetArg).thenCompose((victim) -> { + if (victim == null) { + return completedFuture(null); + } + return performEnact(victim, targetArg); + + }).thenCompose((punishment) -> { + if (punishment == null) { + return completedFuture(null); + } + return enforceAndSendSuccess(punishment); + }); + core().postFuture(future); } - String targetArg = command.next(); - commands.parseVictim(sender, targetArg).thenCompose((victim) -> { - if (victim == null) { - return completedFuture(null); + + private CentralisedFuture performEnact(Victim victim, String targetArg) { + final Duration duration; + if (type() != PunishmentType.KICK && command().hasNext()) { + String time = command().peek(); + duration = new DurationParser(time, messages().formatting().permanentArguments()).parse(); + if (!duration.isZero()) { + command().next(); + } + } else { + duration = Duration.ZERO; } String reason; - if (command.hasNext()) { - reason = command.allRemaining(); - } else if (core().getConfigs().getConfig().getBoolean("reasons.permit-blank")) { + MainConfig.Reasons reasonsCfg; + if (command().hasNext()) { + reason = command().allRemaining(); + } else if ((reasonsCfg = core().getMainConfig().reasons()).permitBlank()) { reason = ""; } else { - reason = core().getConfigs().getConfig().getString("reasons.default-reason"); + reason = reasonsCfg.defaultReason(); } - DraftPunishment draftPunishment = new DraftPunishment.Builder().victim(victim) - .operator(sender.getOperator()).type(type).reason(reason) - .scope(core().getScopeManager().globalScope()).build(); + DraftPunishment draftPunishment = core().getDrafter().draftBuilder() + .victim(victim).operator(sender().getOperator()).type(type()).reason(reason) + .duration(duration) + .build(); - return core().getEnactor().enactPunishment(draftPunishment).thenApply((nullIfConflict) -> { + return draftPunishment.enactPunishmentWithoutEnforcement().thenApply((nullIfConflict) -> { if (nullIfConflict == null) { - if (type.isSingular()) { - String configPath = "additions." + type.getLowercaseNamePlural() + ".error.conflicting"; // additions.bans.error.conflicting - sender.parseThenSend(messages().getString(configPath).replace("%TARGET%", targetArg)); - } else { - sender.parseThenSend(messages().getString("misc.unknown-error")); - } + sendConflict(targetArg); } return nullIfConflict; }); - }).thenCompose((punishment) -> { - if (punishment == null) { - return completedFuture(null); + } + + private void sendConflict(String targetArg) { + SendableMessage message; + if (type().isSingular()) { + message = ((ExclusivePunishmentAddition) section).conflicting().replaceText("%TARGET%", targetArg); + } else { + message = messages().misc().unknownError(); } - // Success message - String rawMsg = messages().getString( - "additions." + type.getLowercaseNamePlural() + ".successful.message"); // additions.bans.successful.message - CentralisedFuture futureMsg = core().getFormatter().formatWithPunishment(rawMsg, punishment); - - // Enforcement - CentralisedFuture enforcement = core().getEnforcer().enforce(punishment); + sender().sendMessage(message); + } + + private CentralisedFuture enforceAndSendSuccess(Punishment punishment) { - // Notification - String configMsgPath = "additions." + type.getLowercaseNamePlural() + ".successful.notification"; // addition.bans.successful.notification - String rawNotify = messages().getString(configMsgPath); - CentralisedFuture futureNotify = core().getFormatter().formatWithPunishment(rawNotify, punishment); + CentralisedFuture enforcement = punishment.enforcePunishment(); + CentralisedFuture successMessage = core().getFormatter().formatWithPunishment( + section.successMessage(), punishment); + CentralisedFuture successNotify = core().getFormatter().formatWithPunishment( + section.successNotification(), punishment); - // Conclusion - return CompletableFuture.allOf(futureMsg, enforcement, futureNotify).thenAccept((ignore) -> { - sender.sendMessage(futureMsg.join()); + return core().getFuturesFactory().allOf(enforcement, successMessage, successNotify).thenAccept((ignore) -> { + sender().sendMessage(successMessage.join()); - String notifyPerm = "libertybans." + type.getLowercaseName() + ".notify"; // libertybans.ban.notify - core().getEnvironment().getEnforcer().sendToThoseWithPermission(notifyPerm, futureNotify.join()); + SendableMessage fullNotify = core().getFormatter().prefix(successNotify.join()); + String notifyPerm = "libertybans." + type().getLowercaseName() + ".notify"; + core().getEnvironment().getEnforcer().sendToThoseWithPermission(notifyPerm, fullNotify); }); - }).whenComplete(core()::debugFuture); + } + } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/ReloadCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/ReloadCommands.java deleted file mode 100644 index f588c6d41..000000000 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/ReloadCommands.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-core is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-core. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.core.commands; - -import space.arim.api.chat.SendableMessage; - -import space.arim.libertybans.core.env.CmdSender; - -public class ReloadCommands extends AbstractSubCommandGroup { - - ReloadCommands(Commands commands) { - super(commands, "reload", "restart"); - } - - private SendableMessage ellipses() { - return core().getFormatter().parseMessage(messages().getString("admin.ellipses")); - } - - @Override - public void execute(CmdSender sender, CommandPackage command, String arg) { - if (!sender.hasPermission("libertybans.admin." + arg)) { - sender.parseThenSend(messages().getString("admin.no-permission")); - return; - } - switch (arg) { - case "restart": - restartCmd(sender); - break; - case "reload": - reloadCmd(sender); - break; - default: - throw new IllegalStateException("Command mismatch"); - } - } - - private void restartCmd(CmdSender sender) { - sender.sendMessage(ellipses()); - boolean restarted = core().getEnvironment().fullRestart(); - sender.parseThenSend((restarted) ? messages().getString("admin.restarted") - : "Not restarting because loading already in process"); - } - - private void reloadCmd(CmdSender sender) { - sender.sendMessage(ellipses()); - core().getConfigs().reloadConfigs().thenAccept((result) -> { - String message = (result) ? messages().getString("admin.reloaded") - : "&cAn error occurred reloading the configuration. Please check the server console."; - sender.parseThenSend(message); - }); - } - -} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/SubCommandGroup.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/SubCommandGroup.java index 5c1962c2b..6f507e6a9 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/SubCommandGroup.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/SubCommandGroup.java @@ -28,7 +28,7 @@ * @author A248 * */ -public interface SubCommandGroup { +interface SubCommandGroup { /** * Whether this subcommand object matches the specified subcommand argument @@ -44,8 +44,9 @@ public interface SubCommandGroup { * @param sender the command sender * @param command the command * @param arg the argument, lowercased + * @return the execution */ - void execute(CmdSender sender, CommandPackage command, String arg); + CommandExecution execute(CmdSender sender, CommandPackage command, String arg); /** * Gets tab complete suggestions for the subcommand diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/TypeSpecificExecution.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/TypeSpecificExecution.java new file mode 100644 index 000000000..bd71d638f --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/TypeSpecificExecution.java @@ -0,0 +1,37 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.commands; + +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.core.env.CmdSender; + +abstract class TypeSpecificExecution extends AbstractCommandExecution { + + private final PunishmentType type; + + TypeSpecificExecution(CmdSender sender, CommandPackage command, PunishmentType type) { + super(sender, command); + this.type = type; + } + + PunishmentType type() { + return type; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/UnpunishCommands.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/UnpunishCommands.java index 51c1c0a54..868129520 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/commands/UnpunishCommands.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/UnpunishCommands.java @@ -20,52 +20,82 @@ import java.util.Arrays; import java.util.Locale; -import java.util.concurrent.CompletableFuture; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.api.chat.SendableMessage; +import space.arim.api.chat.manipulator.SendableMessageManipulator; -import space.arim.libertybans.api.Punishment; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.core.MiscUtil; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.core.config.RemovalsSection.PunishmentRemoval; +import space.arim.libertybans.core.config.RemovalsSection.WarnRemoval; import space.arim.libertybans.core.env.CmdSender; +import space.arim.libertybans.core.punish.MiscUtil; public class UnpunishCommands extends AbstractSubCommandGroup { UnpunishCommands(Commands commands) { super(commands, Arrays.stream(MiscUtil.punishmentTypesExcludingKick()) - .map((type) -> "un" + type.name().toLowerCase(Locale.ENGLISH)).toArray(String[]::new)); + .map((type) -> "un" + type.name().toLowerCase(Locale.ROOT))); } @Override - public void execute(CmdSender sender, CommandPackage command, String arg) { - execute(sender, command, PunishmentType.valueOf(arg.substring(2).toUpperCase(Locale.ENGLISH))); + public CommandExecution execute(CmdSender sender, CommandPackage command, String arg) { + return new Execution(sender, command, PunishmentType.valueOf(arg.substring(2).toUpperCase(Locale.ROOT))); } - private void execute(CmdSender sender, CommandPackage command, - PunishmentType type) { - if (!sender.hasPermission("libertybans." + type.getLowercaseName() + ".undo")) { // libertybans.ban.undo - sender.parseThenSend(messages().getString( - "removals." + type.getLowercaseNamePlural() + ".permission.command")); // removals.bans.permission.command - return; - } - if (!command.hasNext()) { - sender.parseThenSend(messages().getString( - "removals." + type.getLowercaseNamePlural() + ".usage")); // removals.bans.usage - return; + private class Execution extends TypeSpecificExecution { + + private final PunishmentRemoval section; + + Execution(CmdSender sender, CommandPackage command, PunishmentType type) { + super(sender, command, type); + section = messages().removals().forType(type); } - String targetArg = command.next(); - commands.parseVictim(sender, targetArg).thenCompose((victim) -> { - if (victim == null) { - return completedFuture(null); + + @Override + public void execute() { + if (!sender().hasPermission("libertybans.un" + type().getLowercaseName() + ".command")) { // libertybans.unban.command + sender().sendMessage(section.permissionCommand()); + return; + } + if (!command().hasNext()) { + sender().sendMessage(section.usage()); + return; } + execute0(); + } + + private void execute0() { + String targetArg = command().next(); + CentralisedFuture future = commands.parseVictim(sender(), targetArg).thenCompose((victim) -> { + if (victim == null) { + return completedFuture(null); + } + return performUndo(victim, targetArg); + + }).thenCompose((punishment) -> { + if (punishment == null) { + return completedFuture(null); + } + return sendSuccess(punishment); + }); + core().postFuture(future); + } + + private CentralisedFuture performUndo(Victim victim, String targetArg) { + CmdSender sender = sender(); + CommandPackage command = command(); + PunishmentType type = type(); + CentralisedFuture futureUndo = null; final int finalId; if (type == PunishmentType.WARN) { if (!command.hasNext()) { - sender.parseThenSend(messages().getString("removals.warns.usage")); + sender.sendMessage(section.usage()); return completedFuture(null); } String idArg = command.next(); @@ -73,53 +103,44 @@ private void execute(CmdSender sender, CommandPackage command, try { id = Integer.parseInt(idArg); } catch (NumberFormatException ignored) { - sender.parseThenSend(messages().getString("removals.warns.not-a-number").replace("%ID_ARG%", idArg)); + sender.sendMessage(((WarnRemoval) section).notANumber().replaceText("%ID_ARG%", idArg)); return completedFuture(null); } - futureUndo = core().getEnactor().undoAndGetPunishmentByIdAndType(id, type); + futureUndo = core().getRevoker().revokeByIdAndType(id, type).undoAndGetPunishmentWithoutUnenforcement(); finalId = id; } else { assert type.isSingular() : type; - futureUndo = core().getEnactor().undoAndGetPunishmentByTypeAndVictim(type, victim); + futureUndo = core().getRevoker().revokeByTypeAndVictim(type, victim).undoAndGetPunishmentWithoutUnenforcement(); finalId = -1; } return futureUndo.thenApply((nullIfNotFound) -> { if (nullIfNotFound == null) { - String configPath = "removals." + type.getLowercaseNamePlural() + ".not-found"; // removals.bans.not-found - String rawMessage = messages().getString(configPath).replace("%TARGET%", targetArg); + SendableMessage notFound = section.notFound().replaceText("%TARGET%", targetArg); if (type == PunishmentType.WARN) { - rawMessage = rawMessage.replace("%ID%", Integer.toString(finalId)); + notFound = SendableMessageManipulator.create(notFound).replaceText("%ID%", Integer.toString(finalId)); } - sender.parseThenSend(rawMessage); + sender.sendMessage(notFound); } return nullIfNotFound; }); - }).thenCompose((punishment) -> { - if (punishment == null) { - return completedFuture(null); - } - // Success message - String rawMsg = messages().getString( - "removals." + type.getLowercaseNamePlural() + ".successful.message"); // removals.bans.successful.message - CentralisedFuture futureSuccessMessage = core().getFormatter().formatWithPunishment(rawMsg, punishment); + } + + private CentralisedFuture sendSuccess(Punishment punishment) { - // Notification - CentralisedFuture futureNotify = core().getFormatter().formatOperator(sender.getOperator()) - .thenCompose((operatorFormatted) -> { - String rawNotify = messages() - .getString("removals." + type.getLowercaseNamePlural() + ".successful.notification"); // removals.bans.successful.notification - rawNotify = rawNotify.replace("%UNOPERATOR%", operatorFormatted); - return core().getFormatter().formatWithPunishment(rawNotify, punishment); - }); + CentralisedFuture unenforcement = punishment.unenforcePunishment(); + CentralisedFuture successMessage = core().getFormatter().formatWithPunishment( + section.successMessage(), punishment); + CentralisedFuture successNotify = core().getFormatter().formatWithPunishmentAndUnoperator( + section.successNotification(), punishment, sender().getOperator()); - // Conclusion - return CompletableFuture.allOf(futureSuccessMessage, futureNotify).thenAccept((ignore) -> { - sender.sendMessage(futureSuccessMessage.join()); + return core().getFuturesFactory().allOf(unenforcement, successMessage, successNotify).thenAccept((ignore) -> { + sender().sendMessage(successMessage.join()); - core().getEnvironment().getEnforcer().sendToThoseWithPermission( - "libertybans." + type.getLowercaseName() + ".unnotify", futureNotify.join()); // libertybans.ban.unnotify + SendableMessage fullNotify = core().getFormatter().prefix(successNotify.join()); + String notifyPerm = "libertybans." + type().getLowercaseName() + ".unnotify"; + core().getEnvironment().getEnforcer().sendToThoseWithPermission(notifyPerm, fullNotify); }); - }).whenComplete(core()::debugFuture); + } } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/commands/UsageSection.java b/bans-core/src/main/java/space/arim/libertybans/core/commands/UsageSection.java new file mode 100644 index 000000000..4abaeb897 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/commands/UsageSection.java @@ -0,0 +1,59 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.commands; + +import space.arim.api.chat.SendableMessage; +import space.arim.api.chat.serialiser.LegacyCodeSerialiser; +import space.arim.api.chat.serialiser.SendableMessageSerialiser; + +enum UsageSection { + + PUNISH("&e/ban &7- ban players", + "&e/mute &7- mute players", + "&e/warn &7- warn players", + "&e/kick &7- kick players"), + UNPUNISH("&e/unban &7- undo a ban", + "&e/unmute &7- undo a mute", + "&e/unwarn &7- undo a warn"), + LIST("&b/banlist &7- view active bans", + "&b/mutelist &7- view active mutes", + "&b/history &7- view a player's history", + "&b/warns &7- view a player's warns", + "&b/blame &7- view another staff member's punishments"), + OTHER("&7/libertybans usage - shows this", + "&7/libertybans reload - reload config.yml and messages configuration", + "&7/libertybans restart - perform a full restart, reloads everything including database connections"); + + private final SendableMessage content; + + UsageSection(String...commands) { + String name = name(); + String headerString = "&b" + Character.toUpperCase(name.charAt(0)) + name.substring(1) + " commands:"; + + SendableMessageSerialiser serialiser = LegacyCodeSerialiser.getInstance('&'); + SendableMessage header = serialiser.deserialise(headerString); + SendableMessage body = serialiser.deserialise("\n" + String.join("\n", commands)); + content = header.concatenate(body); + } + + SendableMessage getContent() { + return content; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/AdditionsSection.java b/bans-core/src/main/java/space/arim/libertybans/core/config/AdditionsSection.java new file mode 100644 index 000000000..9c0a816a1 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/AdditionsSection.java @@ -0,0 +1,228 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import space.arim.api.chat.SendableMessage; +import space.arim.api.chat.manipulator.SendableMessageManipulator; + +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.core.punish.MiscUtil; + +import space.arim.dazzleconf.annote.ConfDefault.DefaultString; +import space.arim.dazzleconf.annote.ConfDefault.DefaultStrings; +import space.arim.dazzleconf.annote.ConfHeader; +import space.arim.dazzleconf.annote.ConfKey; +import space.arim.dazzleconf.annote.SubSection; + +@ConfHeader({ + "", + "Messages regarding /ban, /mute, /warn, /kick", + "Includes punishment layouts", + "", + ""}) +public interface AdditionsSection { + + interface PunishmentAddition { + + SendableMessage usage(); + + SendableMessage permissionCommand(); + + SendableMessageManipulator successMessage(); + + SendableMessageManipulator successNotification(); + + SendableMessageManipulator layout(); + + } + + interface ExclusivePunishmentAddition extends PunishmentAddition { + + SendableMessageManipulator conflicting(); + + } + + interface BanAddition extends ExclusivePunishmentAddition { + + @Override + @DefaultString("&cUsage: /ban &e [time] &c.") + SendableMessage usage(); + + @Override + @ConfKey("permission.command") + @DefaultString("&cYou may not ban other players.") + SendableMessage permissionCommand(); + + @Override + @DefaultString("&c&o%TARGET%&r&7 is already banned.") + SendableMessageManipulator conflicting(); + + @Override + @ConfKey("success.message") + @DefaultString("&aBanned &c&o%VICTIM%&r&a for &a&o%DURATION%&r&a because of &e&o%REASON%&r&a.") + SendableMessageManipulator successMessage(); + + @Override + @ConfKey("success.notification") + @DefaultString("&c&o%OPERATOR%&r&7 banned &c&o%VICTIM%&r&7 for &a&o%DURATION%&r&7 because of &e&o%REASON%&r&7.") + SendableMessageManipulator successNotification(); + + @Override + @DefaultStrings({ + "&7&lBanned", + "&cDuration: &e%TIME_REMAINING%", + "&7", + "&c&lReason", + "&7%REASON%", + "&7", + "&3&lAppeal Your Punishment", + "&cWebsite: &7website", + "&cDiscord: &7discord"}) + SendableMessageManipulator layout(); + + } + + interface MuteAddition extends ExclusivePunishmentAddition { + + @Override + @DefaultString("&cUsage: /mute &e [time] &c.") + SendableMessage usage(); + + @Override + @ConfKey("permission.command") + @DefaultString("&cYou may not mute other players.") + SendableMessage permissionCommand(); + + @Override + @DefaultString("&c&o%TARGET%&r&7 is already muted.") + SendableMessageManipulator conflicting(); + + @Override + @ConfKey("success.message") + @DefaultString("&aMuted &c&o%VICTIM%&r&a for &a&o%DURATION%&r&a because of &e&o%REASON%&r&a.") + SendableMessageManipulator successMessage(); + + @Override + @ConfKey("success.notification") + @DefaultString("&c&o%OPERATOR%&r&7 muted &c&o%VICTIM%&r&7 for &a&o%DURATION%&r&7 because of &e&o%REASON%&r&7.") + SendableMessageManipulator successNotification(); + + @Override + @DefaultStrings({ + "&7&lMuted", + "&cDuration: &e%TIME_REMAINING%", + "&7", + "&c&lReason", + "&7%REASON%"}) + SendableMessageManipulator layout(); + + } + + interface WarnAddition extends PunishmentAddition { + + @Override + @DefaultString("&cUsage: /warn &e [time] &c.") + SendableMessage usage(); + + @Override + @ConfKey("permission.command") + @DefaultString("&cYou may not warn other players.") + SendableMessage permissionCommand(); + + @Override + @ConfKey("success.message") + @DefaultString("&aWarned &c&o%VICTIM%&r&a for &a&o%DURATION%&r&a because of &e&o%REASON%&r&a.") + SendableMessageManipulator successMessage(); + + @Override + @ConfKey("success.notification") + @DefaultString("&c&o%OPERATOR%&r&7 warned &c&o%VICTIM%&r&7 for &a&o%DURATION%&r&7 because of &e&o%REASON%&r&7.") + SendableMessageManipulator successNotification(); + + @Override + @DefaultStrings({ + "&7&lWarned", + "&cDuration: &e%TIME_REMAINING%", + "&7", + "&c&lReason", + "&7%REASON%"}) + SendableMessageManipulator layout(); + + } + + interface KickAddition extends PunishmentAddition { + + @Override + @DefaultString("&cUsage: /kick &e &c.") + SendableMessage usage(); + + @Override + @ConfKey("permission.command") + @DefaultString("&cYou may not kick other players.") + SendableMessage permissionCommand(); + + @Override + @ConfKey("success.message") + @DefaultString("&aKicked &c&o%VICTIM%&r&a because of &e&o%REASON%&r&a.") + SendableMessageManipulator successMessage(); + + @Override + @ConfKey("success.notification") + @DefaultString("&c&o%OPERATOR%&r&7 kicked &c&o%VICTIM%&r&7 because of &e&o%REASON%&r&7.") + SendableMessageManipulator successNotification(); + + @Override + @DefaultStrings({ + "&7&lKicked", + "&cDuration: &e%TIME_REMAINING%", + "&7", + "&c&lReason", + "&7%REASON%"}) + SendableMessageManipulator layout(); + + } + + @SubSection + BanAddition bans(); + + @SubSection + MuteAddition mutes(); + + @SubSection + WarnAddition warns(); + + @SubSection + KickAddition kicks(); + + default PunishmentAddition forType(PunishmentType type) { + switch (type) { + case BAN: + return bans(); + case MUTE: + return mutes(); + case WARN: + return warns(); + case KICK: + return kicks(); + default: + throw MiscUtil.unknownType(type); + } + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigHolder.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigHolder.java new file mode 100644 index 000000000..8bd652cb4 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigHolder.java @@ -0,0 +1,122 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.CompletableFuture; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import space.arim.omnibus.util.ThisClass; + +import space.arim.dazzleconf.AuxiliaryKeys; +import space.arim.dazzleconf.ConfigurationFactory; +import space.arim.dazzleconf.ConfigurationOptions; +import space.arim.dazzleconf.error.ConfigFormatSyntaxException; +import space.arim.dazzleconf.error.InvalidConfigException; +import space.arim.dazzleconf.ext.snakeyaml.SnakeYamlConfigurationFactory; +import space.arim.dazzleconf.ext.snakeyaml.SnakeYamlOptions; + +class ConfigHolder { + + private final ConfigurationFactory factory; + + private volatile C instance; + + private static final ConfigurationOptions CONFIG_OPTIONS; + private static final SnakeYamlOptions YAML_OPTIONS = new SnakeYamlOptions.Builder().useCommentingWriter(true).build(); + private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); + + static { + ConfigurationOptions.Builder optionsBuilder = new ConfigurationOptions.Builder() + .setCreateSingleElementCollections(true); + ConfigSerialisers.addTo(optionsBuilder); + CONFIG_OPTIONS = optionsBuilder.build(); + } + + ConfigHolder(Class configClass) { + factory = new SnakeYamlConfigurationFactory<>(configClass, CONFIG_OPTIONS, YAML_OPTIONS); + } + + C getConfigData() { + return instance; + } + + CompletableFuture reload(Path path) { + return CompletableFuture.supplyAsync(() -> { + C instance; + try { + instance = reload0(path); + } catch (IOException ex) { + logger.warn("Encountered I/O error while reloading the configuration", ex); + return false; + } + this.instance = instance; + return true; + }); + } + + private C reload0(Path path) throws IOException { + C defaults = factory.loadDefaults(); + if (!Files.exists(path)) { + try (FileChannel fileChannel = FileChannel.open(path, + StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { + + factory.write(defaults, fileChannel); + } + return defaults; + } + C config; + // Load existing configuration + try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) { + + config = factory.load(fileChannel, defaults); + + } catch (ConfigFormatSyntaxException ex) { + logger.warn( + "The YAML syntax in your configuration is invalid. " + + "Please use a YAML validator such as https://yaml-online-parser.appspot.com/. " + + "Paste your configuration there and use it to work through errors. " + + "Run /libertybans reload when done. For now, the default configuration will be used.", ex); + return defaults; + + } catch (InvalidConfigException ex) { + logger.warn( + "The values in your configuration are invalid. " + + "Please correct the issue and run /libertybans reload. " + + "For now, the default configuration will be used.", ex); + return defaults; + } + if (config instanceof AuxiliaryKeys) { + // Update existing configuration with missing keys + try (FileChannel fileChannel = FileChannel.open(path, + StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) { + + factory.write(config, fileChannel); + } + } + return config; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigPackage.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigPackage.java deleted file mode 100644 index 6622c9bec..000000000 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigPackage.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-core is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-core. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.core.config; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.Executor; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import space.arim.omnibus.util.ThisClass; - -import space.arim.api.configure.ConfigResult; -import space.arim.api.configure.Configuration; -import space.arim.api.configure.configs.SingularConfig; -import space.arim.api.configure.yaml.YamlSyntaxException; - -public class ConfigPackage { - - final SingularConfig sql; - final SingularConfig config; - final Configuration messages; - - /** - * The executor or executor service for config IO
- *
- * LibertyBans tries to use a platform thread pool for this. However, - * not all platforms make this easy. In the worst case, an own thread pool - * is created and this field is an instance of ExecutorService.
- *
- * At shutdown, check if the field is ExecutorService and if so shut it down. - * - */ - final Executor readWriteService; - - private final Path langFolder; - - private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); - - ConfigPackage(SingularConfig sql, SingularConfig config, Configuration messages, - Executor readWriteService, Path langFolder) { - this.sql = sql; - this.config = config; - this.messages = messages; - this.readWriteService = readWriteService; - this.langFolder = langFolder; - } - - public CompletableFuture reloadConfigs() { - Set> futureLangFiles = new HashSet<>(); - for (Translation translation : Translation.values()) { - final String name = "messages_" + translation.name().toLowerCase(Locale.ENGLISH) + ".yml"; - Path messagesPath = langFolder.resolve(name); - if (!Files.exists(messagesPath)) { - futureLangFiles.add(CompletableFuture.runAsync(() -> { - - try (InputStream inputStream = getClass().getResource("/lang/" + name).openStream(); - ReadableByteChannel source = Channels.newChannel(inputStream); - FileChannel dest = FileChannel.open(messagesPath, StandardOpenOption.WRITE, - StandardOpenOption.CREATE_NEW)) { - - dest.transferFrom(source, 0, Long.MAX_VALUE); - } catch (IOException ex) { - logger.warn("Unable to copy language file for language {}", name, ex); - } - }, readWriteService)); - } - } - var langFilesFuture = CompletableFuture.allOf(futureLangFiles.toArray(CompletableFuture[]::new)); - var readSqlFuture = throwIfFailed(sql.saveDefaultConfig()).thenCompose((copyResult) -> throwIfFailed(sql.readConfig())); - var readConfFuture = throwIfFailed(config.saveDefaultConfig()).thenCompose((copyResult) -> throwIfFailed(config.readConfig())); - var readMsgsFuture = readConfFuture.thenCompose((readConfResult) -> { - String langFileOption = readConfResult.getReadData().getString("lang-file"); - if (langFileOption == null) { - logger.warn("Unspecified language file, using default (en)"); - langFileOption = "en"; - } - Path messagesPath = langFolder.resolve("messages_" + langFileOption + ".yml"); - return langFilesFuture.thenCompose((ignore) -> throwIfFailed(messages.readConfig(messagesPath))); - }); - return CompletableFuture.allOf(readSqlFuture, readConfFuture, readMsgsFuture).handle((ignore, ex) -> { - if (ex != null) { - Throwable cause = ex.getCause(); - if (cause instanceof YamlSyntaxException) { - logger.warn("One or more of your YML files has invalid syntax. " - + "Please use a YAML validator such as https://yaml-online-parser.appspot.com/ " - + "and paste your config files there to check them.", cause); - } else { - logger.warn("An unexpected issue occurred while reloading the configuration.", ex); - } - return false; - } - return true; - }); - } - - private static CompletableFuture throwIfFailed(CompletableFuture futureResult) { - return futureResult.thenApply((result) -> { - if (!result.getResultDefinition().isSuccess()) { - Exception ex = result.getException(); - if (ex instanceof RuntimeException) { - throw (RuntimeException) ex; - } - throw new CompletionException(ex); - } - return result; - }); - } - -} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java new file mode 100644 index 000000000..fdbeb4fc6 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigSerialisers.java @@ -0,0 +1,148 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import java.time.DateTimeException; +import java.time.ZoneId; +import java.time.zone.ZoneRulesException; +import java.util.List; +import java.util.stream.Collectors; + +import space.arim.api.chat.SendableMessage; +import space.arim.api.chat.manipulator.SendableMessageManipulator; +import space.arim.api.chat.serialiser.JsonSkSerialiser; + +import space.arim.dazzleconf.ConfigurationOptions; +import space.arim.dazzleconf.error.BadValueException; +import space.arim.dazzleconf.serialiser.Decomposer; +import space.arim.dazzleconf.serialiser.FlexibleType; +import space.arim.dazzleconf.serialiser.ValueSerialiser; + +final class ConfigSerialisers { + + private ConfigSerialisers() {} + + static void addTo(ConfigurationOptions.Builder builder) { + List> serialisers = List.of(new MessageManipulatorSerialiser(), new MessageSerialiser(), + new DateTimeFormatterSerialiser(), new ZoneIdSerialiser()); + for (ValueSerialiser serialiser : serialisers) { + builder.addSerialiser(serialiser); + } + } + + private static class MessageManipulatorSerialiser implements ValueSerialiser { + + @Override + public Class getTargetClass() { + return SendableMessageManipulator.class; + } + + @Override + public SendableMessageManipulator deserialise(FlexibleType flexibleType) throws BadValueException { + return SendableMessageManipulator.create(flexibleType.getObject(SendableMessage.class)); + } + + @Override + public Object serialise(SendableMessageManipulator value, Decomposer decomposer) { + return decomposer.decompose(SendableMessage.class, value.getMessage()); + } + + } + + private static class MessageSerialiser implements ValueSerialiser { + + @Override + public Class getTargetClass() { + return SendableMessage.class; + } + + @Override + public SendableMessage deserialise(FlexibleType flexibleType) throws BadValueException { + String content = String.join("\n", flexibleType.getList((flexibleElement) -> flexibleElement.getString())); + return JsonSkSerialiser.getInstance().deserialise(content); + } + + @Override + public Object serialise(SendableMessage value, Decomposer decomposer) { + String content = JsonSkSerialiser.getInstance().serialise(value); + List lines = content.lines().collect(Collectors.toList()); + if (lines.size() == 1) { + return lines.get(0); + } + return lines; + } + + } + + private static class DateTimeFormatterSerialiser implements ValueSerialiser { + + @Override + public Class getTargetClass() { + return DateTimeFormatterWithPattern.class; + } + + @Override + public DateTimeFormatterWithPattern deserialise(FlexibleType flexibleType) throws BadValueException { + String format = flexibleType.getString(); + try { + return new DateTimeFormatterWithPattern(format); + } catch (IllegalArgumentException ex) { + throw flexibleType.badValueExceptionBuilder().message("Bad date format " + format).cause(ex).build(); + } + } + + @Override + public String serialise(DateTimeFormatterWithPattern value, Decomposer decomposer) { + return value.getPattern(); + } + + } + + private static class ZoneIdSerialiser implements ValueSerialiser { + + @Override + public Class getTargetClass() { + return ZoneId.class; + } + + @Override + public ZoneId deserialise(FlexibleType flexibleType) throws BadValueException { + String zone = flexibleType.getString(); + if (zone.equalsIgnoreCase("default")) { + return ZoneId.systemDefault(); + } + try { + return ZoneId.of(zone); + } catch (ZoneRulesException ex) { + throw flexibleType.badValueExceptionBuilder().message( + "Unknown region ID in " + zone + " (ZoneRulesException)").cause(ex).build(); + } catch (DateTimeException ex) { + throw flexibleType.badValueExceptionBuilder().message( + "Invalid timezone in " + zone + " (DateTimeException)").cause(ex).build(); + } + } + + @Override + public Object serialise(ZoneId value, Decomposer decomposer) { + return value.getId(); + } + + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigUtil.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigUtil.java deleted file mode 100644 index c4cff7bb1..000000000 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/ConfigUtil.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-core is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-core. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.core.config; - -import java.time.DateTimeException; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.time.zone.ZoneRulesException; -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import space.arim.omnibus.util.ThisClass; - -import space.arim.api.chat.SendableMessage; -import space.arim.api.chat.parser.SendableMessageParser; -import space.arim.api.chat.parser.StandardSendableMessageParser; -import space.arim.api.chat.parser.SendableMessageParser.ColourMode; -import space.arim.api.chat.parser.SendableMessageParser.JsonMode; -import space.arim.api.configure.SingleKeyValueTransformer; -import space.arim.api.configure.ValueTransformer; - -import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.core.MiscUtil; -import space.arim.libertybans.core.uuid.UUIDMaster; - -final class ConfigUtil { - - private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); - - private static final SendableMessageParser parser = new StandardSendableMessageParser(); - - private ConfigUtil() {} - - private static ValueTransformer dateFormatTransformer() { - return SingleKeyValueTransformer.create("date-formatting.format", (value) -> { - DateTimeFormatter result = null; - if (value instanceof String) { - String timeF = (String) value; - try { - result = DateTimeFormatter.ofPattern(timeF); - } catch (IllegalArgumentException ignored) {} - } - if (result == null) { - //result = DateTimeFormatter.ofPattern("dd/MM/yyyy kk:mm"); - logger.info("Config option date-formatting.format invalid: {}", value); - } - return result; - }); - } - - private static ValueTransformer dateZoneTransformer() { - return SingleKeyValueTransformer.create("date-formatting.timezone", (value) -> { - ZoneId result = null; - if (value instanceof String) { - String timeZ = (String) value; - if (timeZ.equalsIgnoreCase("default")) { - return ZoneId.systemDefault(); - } - try { - return ZoneId.of(timeZ); - } catch (ZoneRulesException ex) { - logger.warn("Unknown region ID {} (ZoneRulesException)", timeZ); - } catch (DateTimeException ex) { - logger.warn("Invalid timezone {} (DateTimeException)", timeZ); - } - return null; - } - logger.info("Config option date-formatting.timezone invalid: {}", value); - return result; - }); - } - - private static ValueTransformer strictnessTransformer() { - return SingleKeyValueTransformer.create("enforcement.address-strictness", (value) -> { - if (value instanceof AddressStrictness) { - return value; - } - AddressStrictness result = null; - if (value instanceof String) { - String addrS = (String) value; - try { - result = AddressStrictness.valueOf(addrS); - } catch (IllegalArgumentException ignored) {} - } - if (result == null) { - //result = AddressStrictness.NORMAL; - logger.info("Config option enforcement.address-strictness invalid: {}", value); - } - return result; - }); - } - - private static ValueTransformer syncEnforcementTransformer() { - return SingleKeyValueTransformer.create("enforcement.sync-events-strategy", (value) -> { - SyncEnforcement result = null; - if (value instanceof String) { - String syncE = (String) value; - try { - result = SyncEnforcement.valueOf(syncE); - } catch (IllegalArgumentException ignored) {} - } - if (result == null) { - //result = SyncEnforcement.ALLOW; - logger.info("Config option enforcement.sync-events-strategy invalid: {}", value); - } - return result; - }); - } - - private static ValueTransformer prefixTransformer() { - return SingleKeyValueTransformer.create("all.prefix.value", (value) -> { - if (value instanceof String) { - return parseMessage(false, (String) value); - } - logger.info("Bad config value for all.prefix.value {}", value); - return null; - }); - } - - private static ValueTransformer combinedListStringTransformer(String key) { - return SingleKeyValueTransformer.create(key, (value) -> { - if (value instanceof List) { - List messages = (List) value; - - StringBuilder result = new StringBuilder(); - for (int n = 0; n < messages.size(); n++) { - if (n != 0) { - result.append('\n'); - } - Object element = messages.get(n); - if (!(element instanceof String)) { - logger.info("Bad list element for {} {}", key, element); - return null; - } - result.append(element); - } - return result.toString(); - } - logger.info("Bad config value for {} {}", key, value); - return null; - }); - } - - static List configTransformers() { - List result = new ArrayList<>(); - result.add(dateFormatTransformer()); - result.add(dateZoneTransformer()); - result.add(strictnessTransformer()); - result.add(syncEnforcementTransformer()); - result.addAll(UUIDMaster.createValueTransformers()); - return result; - } - - static List messagesTransformers() { - List result = new ArrayList<>(); - result.add(prefixTransformer()); - result.add(combinedListStringTransformer("all.usage")); - for (PunishmentType type : MiscUtil.punishmentTypes()) { - result.add(combinedListStringTransformer("additions." + type.getLowercaseNamePlural() + ".layout")); - } - return result; - } - - static SendableMessage parseMessage(boolean useJson, String rawMessage) { - JsonMode jsonMode = (useJson) ? JsonMode.JSON_SK : JsonMode.NONE; - return parser.parseMessage(rawMessage, ColourMode.ALL_COLOURS, jsonMode); - } - -} - -enum Translation { - EN -} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java b/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java index b359634f2..cd2aac7a8 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/Configs.java @@ -19,134 +19,117 @@ package space.arim.libertybans.core.config; import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; +import java.nio.file.StandardOpenOption; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import space.arim.api.configure.ConfigAccessor; -import space.arim.api.configure.ConfigSerialiser; -import space.arim.api.configure.Configuration; -import space.arim.api.configure.JarResources; -import space.arim.api.configure.ValueTransformer; -import space.arim.api.configure.configs.ConfigurationBuilder; -import space.arim.api.configure.configs.SingularConfig; -import space.arim.api.configure.yaml.YamlConfigSerialiser; import space.arim.libertybans.bootstrap.StartupException; -import space.arim.libertybans.core.LibertyBansCore; import space.arim.libertybans.core.Part; -import space.arim.libertybans.core.database.DatabaseManager; -import space.arim.libertybans.core.database.IOUtils; public class Configs implements Part { - private final LibertyBansCore core; + private final Path folder; - private volatile ConfigPackage configPackage; + private final ConfigHolder mainHolder = new ConfigHolder<>(MainConfig.class); + private final ConfigHolder messagesHolder = new ConfigHolder<>(MessagesConfig.class); + private final ConfigHolder sqlHolder = new ConfigHolder<>(SqlConfig.class); - public Configs(LibertyBansCore core) { - this.core = core; + public Configs(Path folder) { + this.folder = folder; } - private static CompletableFuture getFor(ConfigSerialiser serialiser, String resourceName, - List transformers, Executor readWriteService) { - return new ConfigurationBuilder() - .defaultResource(JarResources.forCallerClass(resourceName)) - .executor(readWriteService).serialiser(serialiser) - .addTransformers(transformers).buildMergingConfig(); + public MainConfig getMainConfig() { + return mainHolder.getConfigData(); } - private ConfigPackage recreateConfig(Executor existingReadWriteService) { - Executor executor; - if (existingReadWriteService == null) { - executor = core.getEnvironment().getPlatformHandle().getRealExecutorFinder().findExecutor(); - if (executor instanceof ExecutorService) { - // Danger! Don't shut down the platform's combined thread pool - // readWriteService must NOT be an instance of ExecutorService unless shutdown is desired - executor = new IOUtils.SafeExecutorWrapper(executor); - - } else if (executor == null) { - executor = Executors.newCachedThreadPool(new IOUtils.ThreadFactoryImpl("LibertyBans-Config-")); - } - } else { - executor = existingReadWriteService; - } - - Path folder = core.getFolder(); + public MessagesConfig getMessagesConfig() { + return messagesHolder.getConfigData(); + } + + public SqlConfig getSqlConfig() { + return sqlHolder.getConfigData(); + } + + public CompletableFuture reloadConfigs() { Path langFolder = folder.resolve("lang"); try { Files.createDirectories(langFolder); } catch (IOException ex) { - throw new StartupException("Unable to create plugin directories", ex); + throw new UncheckedIOException("Unable to create plugin directories", ex); } - ConfigSerialiser serialiser = new YamlConfigSerialiser(); - var futureSql = getFor(serialiser, "sql.yml", sqlValueTransformers(), executor); - var futureConfig = getFor(serialiser, "config.yml", configValueTransformers(), executor); - var futureMessages = getFor(serialiser, "lang/messages_en.yml", ConfigUtil.messagesTransformers(), executor); - SingularConfig sql = new SingularConfig(futureSql.join(), folder.resolve("sql.yml")); - SingularConfig config = new SingularConfig(futureConfig.join(), folder.resolve("config.yml")); - Configuration messages = futureMessages.join(); + // Save default language files + CompletableFuture futureLangFiles = createLangFiles(langFolder); - return new ConfigPackage(sql, config, messages, executor, langFolder); + // Reload main config + CompletableFuture reloadMain = mainHolder.reload(folder.resolve("config.yml")); + // Reload sql config + CompletableFuture reloadSql = sqlHolder.reload(folder.resolve("sql.yml")); + + // Reload messages config from specified language file + CompletableFuture reloadMessages = CompletableFuture.allOf(futureLangFiles, reloadMain) + .thenCompose((ignore) -> { + String langFileOption = mainHolder.getConfigData().langFile(); + return messagesHolder.reload(langFolder.resolve("messages_" + langFileOption + ".yml")); + }); + return CompletableFuture.allOf(reloadMessages, reloadSql).thenApply((ignore) -> { + return reloadMain.join() && reloadMessages.join() && reloadSql.join(); + }); } - List sqlValueTransformers() { - return DatabaseManager.createConfigTransformers(); + private CompletableFuture createLangFiles(Path langFolder) { + Set> futureLangFiles = new HashSet<>(); + for (Translation translation : Translation.values()) { + + final String name = "messages_" + translation.name().toLowerCase(Locale.ROOT) + ".yml"; + Path messagesPath = langFolder.resolve(name); + if (!Files.exists(messagesPath)) { + futureLangFiles.add(CompletableFuture.runAsync(() -> { + + try (InputStream inputStream = getClass().getResource("/lang/" + name).openStream(); + ReadableByteChannel source = Channels.newChannel(inputStream); + FileChannel dest = FileChannel.open(messagesPath, StandardOpenOption.WRITE, + StandardOpenOption.CREATE_NEW)) { + + dest.transferFrom(source, 0, Long.MAX_VALUE); + } catch (IOException ex) { + throw new UncheckedIOException("Unable to copy language file for language " + name, ex); + } + })); + } + } + return CompletableFuture.allOf(futureLangFiles.toArray(CompletableFuture[]::new)); } - List configValueTransformers() { - return ConfigUtil.configTransformers(); + private enum Translation { + } @Override public void startup() { - ConfigPackage configPackage = recreateConfig(null); - if (!configPackage.reloadConfigs().join()) { + if (!reloadConfigs().join()) { throw new StartupException("Issue while loading configuration"); } - this.configPackage = configPackage; } @Override public void restart() { - ConfigPackage existing = this.configPackage; - ConfigPackage configPackage = recreateConfig(existing.readWriteService); - if (!configPackage.reloadConfigs().join()) { + if (!reloadConfigs().join()) { throw new StartupException("Issue while reloading configuration"); } - this.configPackage = configPackage; } @Override public void shutdown() { - Executor executor = configPackage.readWriteService; - if (executor instanceof ExecutorService) { - ((ExecutorService) executor).shutdown(); - } - } - - public CompletableFuture reloadConfigs() { - return configPackage.reloadConfigs(); - } - - public ConfigAccessor getSql() { - return configPackage.sql.getAccessor(); - } - - public ConfigAccessor getConfig() { - return configPackage.config.getAccessor(); - } - - public ConfigAccessor getMessages() { - return configPackage.messages.getAccessor(); - } - - public AddressStrictness getAddressStrictness() { - return getConfig().getObject("enforcement.address-strictness", AddressStrictness.class); + } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/DateTimeFormatterWithPattern.java b/bans-core/src/main/java/space/arim/libertybans/core/config/DateTimeFormatterWithPattern.java new file mode 100644 index 000000000..e2074f867 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/DateTimeFormatterWithPattern.java @@ -0,0 +1,81 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import java.time.format.DateTimeFormatter; + +/** + * {@link DateTimeFormatter} does not retain the pattern string it was created with, + * therefore this wrapper is required for reserialisation.
+ *
+ * This class is public so that it may be used in dynamic proxies + * + * @author A248 + * + */ +public class DateTimeFormatterWithPattern { + + private final String pattern; + private transient final DateTimeFormatter formatter; + + /** + * Creates from a pattern string + * + * @param pattern the pattern string + * @throws IllegalArgumentException if the pattern is not valid + */ + DateTimeFormatterWithPattern(String pattern) { + this.pattern = pattern; + formatter = DateTimeFormatter.ofPattern(pattern); + } + + String getPattern() { + return pattern; + } + + DateTimeFormatter getFormatter() { + return formatter; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + pattern.hashCode(); + return result; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof DateTimeFormatterWithPattern)) { + return false; + } + DateTimeFormatterWithPattern other = (DateTimeFormatterWithPattern) object; + return pattern.equals(other.pattern); + } + + @Override + public String toString() { + return "DateTimeFormatterWithPattern [pattern=" + pattern + "]"; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java b/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java index d39e9d559..ecbd8bcc5 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/Formatter.java @@ -18,39 +18,41 @@ */ package space.arim.libertybans.core.config; +import java.time.Duration; import java.time.Instant; import java.time.ZoneId; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.EnumMap; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.UnaryOperator; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.uuidvault.api.UUIDUtil; import space.arim.api.chat.SendableMessage; -import space.arim.api.configure.ConfigAccessor; +import space.arim.api.chat.manipulator.SendableMessageManipulator; +import space.arim.api.chat.serialiser.JsonSkSerialiser; import space.arim.libertybans.api.AddressVictim; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.PlayerOperator; import space.arim.libertybans.api.PlayerVictim; -import space.arim.libertybans.api.Punishment; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Scope; +import space.arim.libertybans.api.ServerScope; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.manager.PunishmentFormatter; +import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.core.LibertyBansCore; -import space.arim.libertybans.core.MiscUtil; +import space.arim.libertybans.core.punish.MiscUtil; -public class Formatter { +public class Formatter implements PunishmentFormatter { private final LibertyBansCore core; - - private static final Entry[] timeUnits; private static final long MARGIN_OF_INITIATION = 10; // seconds @@ -58,115 +60,199 @@ public Formatter(LibertyBansCore core) { this.core = core; } - public SendableMessage parseMessage(String rawMessage) { - SendableMessage message = parseMessageNoPrefix(rawMessage); - ConfigAccessor messages = core.getConfigs().getMessages(); - if (messages.getBoolean("all.prefix.enable")) { - SendableMessage prefix = messages.getObject("all.prefix.value", SendableMessage.class); - message = prefix.concatenate(message); - } - return message; + private MessagesConfig messages() { + return core.getMessagesConfig(); } - private SendableMessage parseMessageNoPrefix(String rawMessage) { - return ConfigUtil.parseMessage(isJsonEnabled(), rawMessage); + /** + * Parses a message to send including the prefix + * + * @param messageToParse the message to parse + * @return the message parsed including the prefix + */ + public SendableMessage parseMessageWithPrefix(String messageToParse) { + return prefix(JsonSkSerialiser.getInstance().deserialise(messageToParse)); } - public boolean isJsonEnabled() { - return core.getConfigs().getMessages().getBoolean("formatting.enable-json"); + /** + * Prefixes a message and returns the prefixed result + * + * @param message the message + * @return the message including the prefix + */ + public SendableMessage prefix(SendableMessage message) { + return messages().all().prefix().concatenate(message); } /** - * Gets, formats, and parses the punishment message for a punishment. Punishment messages - * do not use the global prefix. + * Gets, formats, and parses the punishment message for a punishment. * * @param punishment the punishment * @return a future yielding the formatted sendable message */ public CentralisedFuture getPunishmentMessage(Punishment punishment) { - CentralisedFuture futureVictimFormatted = formatVictim(punishment.getVictim()); - CentralisedFuture futureOperatorFormatted = formatOperator(punishment.getOperator()); + return formatWithPunishment(messages().additions().forType(punishment.getType()).layout(), punishment); + } + + /** + * Parses and formats a message with a punishment + * + * @param manipulator the message manipulator + * @param punishment the punishment + * @return a future of the resulting formatted sendable message + */ + public CentralisedFuture formatWithPunishment(SendableMessageManipulator manipulator, + Punishment punishment) { + return formatWithPunishment(manipulator, punishment, null); + } + + /** + * Parses and formats a message with a punishment and an undoing operator. Used when punishments are revoked. + * + * @param manipulator the message manipulator + * @param punishment the punishment + * @param unOperator the operator undoing the punishment + * @return a future of the resulting formatted sendable message + */ + public CentralisedFuture formatWithPunishmentAndUnoperator(SendableMessageManipulator manipulator, + Punishment punishment, Operator unOperator) { + return formatWithPunishment(manipulator, punishment, Objects.requireNonNull(unOperator, "unOperator")); + } - return futureVictimFormatted.thenCombineAsync(futureOperatorFormatted, (victimFormatted, operatorFormatted) -> { - String path = "additions." + punishment.getType().getLowercaseNamePlural() + ".layout"; - String message = core.getConfigs().getMessages().getString(path); - return parseMessageNoPrefix(formatWithPunishment0(message, punishment, victimFormatted, operatorFormatted)); + private CentralisedFuture formatWithPunishment(SendableMessageManipulator manipulator, + Punishment punishment, Operator unOperator) { + + EnumMap> futureReplacements = new EnumMap<>(FutureReplaceable.class); + for (FutureReplaceable futureReplaceable : FutureReplaceable.values()) { + + if (unOperator == null && futureReplaceable == FutureReplaceable.UNOPERATOR) { + continue; + } + if (manipulator.contains(futureReplaceable.getVariable())) { + CentralisedFuture replacement = getFutureReplacement(futureReplaceable, punishment, unOperator); + futureReplacements.put(futureReplaceable, replacement); + } + } + return core.getFuturesFactory().supplyAsync(() -> { + return formatWithPunishment0(manipulator, punishment, unOperator, futureReplacements); }); } - private String formatWithPunishment0(String message, Punishment punishment, String victimFormatted, - String operatorFormatted) { + private enum SimpleReplaceable { + ID, + TYPE, + VICTIM_ID, + OPERATOR_ID, + UNOPERATOR_ID, + REASON, + SCOPE, + DURATION, + START_DATE, + TIME_PASSED, + END_DATE, + TIME_REMAINING; + + String getVariable() { + return "%" + name() + "%"; + } + + } + + private enum FutureReplaceable { + VICTIM, + OPERATOR, + UNOPERATOR; + + String getVariable() { + return "%" + name() + "%"; + } + } + + private SendableMessage formatWithPunishment0(SendableMessageManipulator manipulator, Punishment punishment, + Operator unOperator, EnumMap> futureReplacements) { final long now = MiscUtil.currentTime(); - final long start = punishment.getStart(); - final long end = punishment.getEnd(); + final long start = punishment.getStartDateSeconds(); - long timePassed = now - start; + final long timePassed = now - start; - String durationFormatted; - String timeEndRelFormatted; + final String durationFormatted; + final String relativeEndFormatted; - if (end == 0L) { + if (punishment.isPermanent()) { // Permanent punishment - ConfigAccessor config = core.getConfigs().getMessages(); - durationFormatted = config.getString("formatting.permanent-display.relative"); - timeEndRelFormatted = config.getString("formatting.permanent-display.absolute"); + MessagesConfig.Formatting.PermanentDisplay display = messages().formatting().permanentDisplay(); + durationFormatted = display.duration(); + relativeEndFormatted = display.relative(); + } else { + final long end = punishment.getEndDateSeconds(); + assert end != 0 : end; // Temporary punishment long duration = end - start; durationFormatted = formatRelative(duration); - long timeRemaining; // Using a margin of initiation prevents the "29 days, 23 hours, 59 minutes" issue if (timePassed < MARGIN_OF_INITIATION) { - timeRemaining = duration; + relativeEndFormatted = durationFormatted; + } else { - timeRemaining = end - now; + long timeRemaining = end - now; + relativeEndFormatted = formatRelative(timeRemaining); } - timeEndRelFormatted = formatRelative(timeRemaining); } - return message - .replace("%ID%", Integer.toString(punishment.getID())) - .replace("%TYPE%", formatType(punishment.getType())) - .replace("%VICTIM%", victimFormatted) - .replace("%VICTIM_ID%", formatVictimId(punishment.getVictim())) - .replace("%OPERATOR%", operatorFormatted) - .replace("%REASON%", punishment.getReason()) - .replace("%SCOPE%", formatScope(punishment.getScope())) - .replace("%DURATION%", durationFormatted) - .replace("%TIME_START_ABS%", formatAbsolute(start)) - .replace("%TIME_START_REL%", formatRelative(timePassed)) - .replace("%TIME_END_ABS%", formatAbsolute(end)) - .replace("%TIME_END_REL%", timeEndRelFormatted); + EnumMap simpleReplacements = new EnumMap<>(SimpleReplaceable.class); + simpleReplacements.put(SimpleReplaceable.ID, Integer.toString(punishment.getID())); + simpleReplacements.put(SimpleReplaceable.TYPE, formatType(punishment.getType())); + simpleReplacements.put(SimpleReplaceable.VICTIM_ID, formatVictimId(punishment.getVictim())); + simpleReplacements.put(SimpleReplaceable.OPERATOR_ID, formatOperatorId(punishment.getOperator())); + if (unOperator != null) + simpleReplacements.put(SimpleReplaceable.UNOPERATOR_ID, formatOperatorId(unOperator)); + simpleReplacements.put(SimpleReplaceable.REASON, punishment.getReason()); + simpleReplacements.put(SimpleReplaceable.SCOPE, formatScope(punishment.getScope())); + simpleReplacements.put(SimpleReplaceable.DURATION, durationFormatted); + simpleReplacements.put(SimpleReplaceable.START_DATE, formatAbsoluteDate(punishment.getStartDate())); + simpleReplacements.put(SimpleReplaceable.TIME_PASSED, formatRelative(timePassed)); + simpleReplacements.put(SimpleReplaceable.END_DATE, formatAbsoluteDate(punishment.getEndDate())); + simpleReplacements.put(SimpleReplaceable.TIME_REMAINING, relativeEndFormatted); + + class Replacer implements UnaryOperator { + @Override + public String apply(String text) { + for (Map.Entry simpleReplacement : simpleReplacements.entrySet()) { + text = text.replace( + simpleReplacement.getKey().getVariable(), + simpleReplacement.getValue()); + } + for (Map.Entry> futureReplacement : futureReplacements.entrySet()) { + text = text.replace( + futureReplacement.getKey().getVariable(), + futureReplacement.getValue().join()); + } + return text; + } + } + return manipulator.replaceText(new Replacer()); } - private String formatScope(Scope scope) { - String scopeDisplay = core.getScopeManager().getServer(scope); - if (scopeDisplay == null) { - scopeDisplay = core.getConfigs().getMessages().getString("formatting.global-scope-display"); + + private CentralisedFuture getFutureReplacement(FutureReplaceable futureReplaceable, Punishment punishment, + Operator unOperator) { + switch (futureReplaceable) { + case VICTIM: + return formatVictim(punishment.getVictim()); + case OPERATOR: + return formatOperator(punishment.getOperator()); + case UNOPERATOR: + return formatOperator(unOperator); + default: + throw new IllegalArgumentException("Unknown replaceable " + futureReplaceable); } - return scopeDisplay; } - /** - * Parses and formats a message with a punishment - * - * @param message the message - * @param punishment the punishment - * @return a future of the resulting formatted sendable message - */ - public CentralisedFuture formatWithPunishment(String message, Punishment punishment) { - CentralisedFuture futureVictimFormatted = formatVictim(punishment.getVictim()); - CentralisedFuture futureOperatorFormatted = formatOperator(punishment.getOperator()); - - return futureVictimFormatted.thenCombineAsync(futureOperatorFormatted, (victimFormatted, operatorFormatted) -> { - return parseMessage(formatWithPunishment0(message, punishment, victimFormatted, operatorFormatted)); - }); - } - private String formatType(PunishmentType type) { - return type.toString(); + return messages().formatting().punishmentTypeDisplay().forType(type); } private String formatVictimId(Victim victim) { @@ -176,7 +262,7 @@ private String formatVictimId(Victim victim) { case ADDRESS: return formatAddressVictim((AddressVictim) victim); default: - throw new IllegalStateException("Unknown victim type " + victim.getType()); + throw MiscUtil.unknownVictimType(victim.getType()); } } @@ -191,14 +277,25 @@ private CentralisedFuture formatVictim(Victim victim) { case ADDRESS: return core.getFuturesFactory().completedFuture(formatAddressVictim((AddressVictim) victim)); default: - throw new IllegalStateException("Unknown victim type " + victim.getType()); + throw MiscUtil.unknownVictimType(victim.getType()); } } - public CentralisedFuture formatOperator(Operator operator) { + private String formatOperatorId(Operator operator) { switch (operator.getType()) { case CONSOLE: - return core.getFuturesFactory().completedFuture(core.getConfigs().getMessages().getString("formatting.console-display")); + return messages().formatting().consoleDisplay(); + case PLAYER: + return UUIDUtil.toShortString(((PlayerOperator) operator).getUUID()); + default: + throw MiscUtil.unknownOperatorType(operator.getType()); + } + } + + private CentralisedFuture formatOperator(Operator operator) { + switch (operator.getType()) { + case CONSOLE: + return core.getFuturesFactory().completedFuture(messages().formatting().consoleDisplay()); case PLAYER: /* * Similarly in #formatVictim, this should be a complete future every time we call this ourselves, @@ -206,7 +303,7 @@ public CentralisedFuture formatOperator(Operator operator) { */ return core.getUUIDMaster().fullLookupName(((PlayerOperator) operator).getUUID()); default: - throw new IllegalStateException("Unknown operator type " + operator.getType()); + throw MiscUtil.unknownOperatorType(operator.getType()); } } @@ -214,54 +311,76 @@ private String formatAddressVictim(AddressVictim addressVictim) { return addressVictim.getAddress().toString(); } - private String formatAbsolute(long time) { - ZoneId zoneId = core.getConfigs().getConfig().getObject("date-formatting.timezone", ZoneId.class); - ZonedDateTime zonedDate = Instant.ofEpochSecond(time).atZone(zoneId); - DateTimeFormatter formatter = core.getConfigs().getConfig().getObject("date-formatting.format", DateTimeFormatter.class); - return formatter.format(zonedDate); - } - - static { - List> list = List.of( - Map.entry("years", 31_536_000L), Map.entry("months", 2_592_000L), - Map.entry("weeks", 604_800L), Map.entry("days", 86_400L), - Map.entry("hours", 3_600L), Map.entry("minutes", 60L)); - @SuppressWarnings("unchecked") - Entry[] asArray = (Entry[]) list.toArray(new Map.Entry[] {}); - timeUnits = asArray; - } - private String formatRelative(long diff) { if (diff < 0) { return formatRelative(-diff); } - List segments = new ArrayList<>(); - for (Entry unit : timeUnits) { - String unitName = unit.getKey(); - long unitLength = unit.getValue(); - if (diff > unitLength && core.getConfigs().getMessages().getBoolean("misc.time." + unitName + ".enable")) { + MessagesConfig.Misc.Time timeConfig = messages().misc().time(); + Map fragments = timeConfig.fragments(); + List segments = new ArrayList<>(fragments.size()); + for (Map.Entry fragment : fragments.entrySet()) { + long unitLength = fragment.getKey().getDuration().toSeconds(); + if (diff > unitLength) { long amount = (diff / unitLength); diff -= (amount * unitLength); - segments.add(core.getConfigs().getMessages().getString("misc.time." + unitName + ".message") - .replace('%' + unitName.toUpperCase(Locale.ENGLISH) + '%', Long.toString(amount))); + segments.add(fragment.getValue().replace("%VALUE%", Long.toString(amount))); } } + if (segments.isEmpty()) { + return timeConfig.fallbackSeconds().replace("%VALUE%", Long.toString(diff)); + } + if (segments.size() == 1) { + return segments.get(0); + } StringBuilder builder = new StringBuilder(); - final boolean comma = core.getConfigs().getMessages().getBoolean("misc.time.grammar.comma"); + final boolean comma = timeConfig.useComma(); for (int n = 0; n < segments.size(); n++) { boolean lastElement = n == segments.size() - 1; if (lastElement) { - builder.append(core.getConfigs().getMessages().getString("misc.time.grammar.and")); - } else if (comma) { - builder.append(','); + builder.append(timeConfig.and()); } - if (n != 0) { + builder.append(segments.get(n)); + if (!lastElement) { + if (comma) { + builder.append(','); + } builder.append(" "); } - builder.append(segments.get(n)); } return builder.toString(); } + private String formatScope(ServerScope scope) { + String scopeDisplay = core.getScopeManager().getServer(scope); + if (scopeDisplay == null) { + scopeDisplay = messages().formatting().globalScopeDisplay(); + } + return scopeDisplay; + } + + @Override + public ZoneId getTimezone() { + return core.getMainConfig().dateFormatting().zoneId(); + } + + @Override + public DateTimeFormatter getDateTimeFormatter() { + return core.getConfigs().getMainConfig().dateFormatting().formatAndPattern().getFormatter(); + } + + @Override + public String formatAbsoluteDate(Instant date) { + if (date.equals(Instant.MAX)) { // implicit null check + return messages().formatting().permanentDisplay().absolute(); + } + return getDateTimeFormatter().format(date.atZone(getTimezone())); + } + + @Override + public String formatDuration(Duration duration) { + Objects.requireNonNull(duration, "duration"); + return formatRelative(duration.toSeconds()); + } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/ListSection.java b/bans-core/src/main/java/space/arim/libertybans/core/config/ListSection.java new file mode 100644 index 000000000..d87a8514f --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/ListSection.java @@ -0,0 +1,312 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import space.arim.api.chat.SendableMessage; +import space.arim.api.chat.manipulator.SendableMessageManipulator; + +import space.arim.dazzleconf.annote.ConfDefault.DefaultInteger; +import space.arim.dazzleconf.annote.ConfDefault.DefaultString; +import space.arim.dazzleconf.annote.ConfDefault.DefaultStrings; +import space.arim.dazzleconf.annote.ConfHeader; +import space.arim.dazzleconf.annote.ConfKey; +import space.arim.dazzleconf.annote.SubSection; + +@ConfHeader("Used for /banlist, /mutelist, /history, /warns, /blame") +public interface ListSection { + + interface PunishmentList { + + SendableMessage usage(); + + int perPage(); + + SendableMessageManipulator noPages(); + + SendableMessageManipulator maxPages(); + + SendableMessage permissionCommand(); + + SendableMessageManipulator layoutHeader(); + + SendableMessageManipulator layoutBody(); + + SendableMessageManipulator layoutFooter(); + + } + + interface BanList extends PunishmentList { + + @Override + @DefaultString("&cUsage: /banlist &e[page]") + SendableMessage usage(); + + @Override + @DefaultInteger(10) + int perPage(); + + @Override + @DefaultString("&7There are no active bans.") + SendableMessageManipulator noPages(); + + @Override + @DefaultString("&7Page &e%PAGE%&7 does not exist. Max pages: &e%MAXPAGE%&7.") + SendableMessageManipulator maxPages(); + + @Override + @ConfKey("permission.command") + @DefaultString("&7You may not view the banlist.") + SendableMessage permissionCommand(); + + @Override + @ConfKey("layout.header") + @DefaultStrings({"&7[&eID&7] &e&oSubject", + "&7Operator &8/ &7Reason &8/ &7Time Remaining", + "&7"}) + SendableMessageManipulator layoutHeader(); + + @Override + @ConfKey("layout.body") + @DefaultStrings({"&7[&e%ID%&7] &e&o%VICTIM%", + "&7%OPERATOR% &8/ &7%REASON% &8/ &7%TIME_END_REL%", + "&7"}) + SendableMessageManipulator layoutBody(); + + @Override + @ConfKey("layout.footer") + @DefaultStrings({"&7Page &e%PAGE%&7/&e%MAXPAGE%&7.||ttp:Click for next page||cmd:/libertybans banlist %NEXTPAGE%"}) + SendableMessageManipulator layoutFooter(); + + } + + interface MuteList extends PunishmentList { + + @Override + @DefaultString("&cUsage: /mutelist &e[page]") + SendableMessage usage(); + + @Override + @DefaultInteger(10) + int perPage(); + + @Override + @DefaultString("&7There are no active mutes.") + SendableMessageManipulator noPages(); + + @Override + @DefaultString("&7Page &e%PAGE%&7 does not exist. Max pages: &e%MAXPAGE%&7.") + SendableMessageManipulator maxPages(); + + @Override + @ConfKey("permission.command") + @DefaultString("&7You may not view the mutelist.") + SendableMessage permissionCommand(); + + @Override + @ConfKey("layout.header") + @DefaultStrings({"&7[&eID&7] &e&oSubject", + "&7Operator &8/ &7Reason &8/ &7Time Remaining", + "&7"}) + SendableMessageManipulator layoutHeader(); + + @Override + @ConfKey("layout.body") + @DefaultStrings({"&7[&e%ID%&7] &e&o%VICTIM%", + "&7%OPERATOR% &8/ &7%REASON% &8/ &7%TIME_END_REL%", + "&7"}) + SendableMessageManipulator layoutBody(); + + @Override + @ConfKey("layout.footer") + @DefaultStrings({"&7Page &e%PAGE%&7/&e%MAXPAGE%&7.||ttp:Click for next page||cmd:/libertybans mutelist %NEXTPAGE%"}) + SendableMessageManipulator layoutFooter(); + + } + + interface History extends PunishmentList { + + @Override + @DefaultString("&cUsage: /history &e [page]") + SendableMessage usage(); + + @Override + @DefaultInteger(10) + int perPage(); + + @Override + @DefaultString("&c&o%TARGET%&r&7 has no history.") + SendableMessageManipulator noPages(); + + @Override + @DefaultString("&7Page &e%PAGE%&7 does not exist. Max pages: &e%MAXPAGE%&7.") + SendableMessageManipulator maxPages(); + + @Override + @ConfKey("permission.command") + @DefaultString("&7You may not view history.") + SendableMessage permissionCommand(); + + @Override + @ConfKey("layout.header") + @DefaultStrings({"&7[&eID&7] &r&8/ &7Punishment Type", + "&7Operator &8/ &7Reason &8/ &7Date Enacted", + "&7"}) + SendableMessageManipulator layoutHeader(); + + @Override + @ConfKey("layout.body") + @DefaultStrings({"&7[&e%ID%&7] &r&7/ &7%TYPE%", + "&7%OPERATOR% &8/ &7REASON% &8/ &7%TIME_START_ABS%", + "&7"}) + SendableMessageManipulator layoutBody(); + + @Override + @ConfKey("layout.footer") + @DefaultStrings({"&7Page &e%PAGE%&7/&e%MAXPAGE%&7.||ttp:Click for next page||cmd:/libertybans history %TARGET% %NEXTPAGE%"}) + SendableMessageManipulator layoutFooter(); + + } + + interface Warns extends PunishmentList { + + @Override + @DefaultString("&cUsage: /warns &e [page]") + SendableMessage usage(); + + @Override + @DefaultInteger(10) + int perPage(); + + @Override + @DefaultString("&c&o%TARGET%&r&7 has no warns.") + SendableMessageManipulator noPages(); + + @Override + @DefaultString("&7Page &e%PAGE%&7 does not exist. Max pages: &e%MAXPAGE%&7.") + SendableMessageManipulator maxPages(); + + @Override + @ConfKey("permission.command") + @DefaultString("&7You may not view warns.") + SendableMessage permissionCommand(); + + @Override + @ConfKey("layout.header") + @DefaultStrings({"&7[&eID&7] Operator &8/ &7Reason &8/ &7Time Remaining", + "&7"}) + SendableMessageManipulator layoutHeader(); + + @Override + @ConfKey("layout.body") + @DefaultStrings({"&7[&e%ID%&7] %OPERATOR% &8/ &7%REASON% &8/ &7%TIME_END_REL%", + "&7"}) + SendableMessageManipulator layoutBody(); + + @Override + @ConfKey("layout.footer") + @DefaultStrings({"&7Page &e%PAGE%&7/&e%MAXPAGE%&7.||ttp:Click for next page||cmd:/libertybans warns %TARGET% %NEXTPAGE%"}) + SendableMessageManipulator layoutFooter(); + + } + + interface Blame extends PunishmentList { + + @Override + @DefaultString("&cUsage: /blame &e [page]") + SendableMessage usage(); + + @Override + @DefaultInteger(10) + int perPage(); + + @Override + @DefaultString("&c&o%TARGET%&r&7 has not punished any players.") + SendableMessageManipulator noPages(); + + @Override + @DefaultString("&7Page &e%PAGE%&7 does not exist. Max pages: &e%MAXPAGE%&7.") + SendableMessageManipulator maxPages(); + + @Override + @ConfKey("permission.command") + @DefaultString("&7You may not use blame.") + SendableMessage permissionCommand(); + + @Override + @ConfKey("layout.header") + @DefaultStrings({"&7[&eID&7] &e&oSubject &r&8/ &7Punishment Type", + "&7Reason &8/ &7Date Enacted", + "&7"}) + SendableMessageManipulator layoutHeader(); + + @Override + @ConfKey("layout.body") + @DefaultStrings({"&7[&e%ID%&7] &e&o%SUBJECT% &r&8 / &7%TYPE%", + "&7%REASON% &8/ &7%TIME_START_ABS%", + "&7"}) + SendableMessageManipulator layoutBody(); + + @Override + @ConfKey("layout.footer") + @DefaultStrings({"&7Page &e%PAGE%&7/&e%MAXPAGE%&7.||ttp:Click for next page||cmd:/libertybans warns %TARGET% %NEXTPAGE%"}) + SendableMessageManipulator layoutFooter(); + + } + + enum ListType { + BANLIST, + MUTELIST, + HISTORY, + WARNS, + BLAME; + } + + default PunishmentList forType(ListType type) { + switch (type) { + case BANLIST: + return banList(); + case MUTELIST: + return muteList(); + case HISTORY: + return history(); + case WARNS: + return warns(); + case BLAME: + return blame(); + default: + throw new IllegalArgumentException("Unknown list type " + type); + } + } + + @SubSection + BanList banList(); + + @SubSection + MuteList muteList(); + + @SubSection + History history(); + + @SubSection + Warns warns(); + + @SubSection + Blame blame(); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java new file mode 100644 index 000000000..ceffb8d16 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/MainConfig.java @@ -0,0 +1,110 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import java.time.ZoneId; +import java.util.Set; + +import space.arim.libertybans.core.selector.EnforcementConfig; +import space.arim.libertybans.core.uuid.UUIDResolutionConfig; + +import space.arim.dazzleconf.annote.ConfComments; +import space.arim.dazzleconf.annote.ConfDefault.DefaultBoolean; +import space.arim.dazzleconf.annote.ConfDefault.DefaultString; +import space.arim.dazzleconf.annote.ConfDefault.DefaultStrings; +import space.arim.dazzleconf.annote.ConfHeader; +import space.arim.dazzleconf.annote.ConfKey; +import space.arim.dazzleconf.annote.SubSection; + +@ConfHeader({ + "", + "", + "The main LibertyBans configuration", + "All options here can be updated with /libertybans reload", + "", + ""}) +public interface MainConfig { + + @ConfKey("lang-file") + @ConfComments({"What language file should be used for messages?", + "For example, 'en' means LibertyBans will look for a file called 'messages_en.yml'"}) + @DefaultString("en") + String langFile(); + + @ConfKey("date-formatting") + @SubSection + DateFormatting dateFormatting(); + + @ConfHeader("Formatting of absolute dates") + interface DateFormatting { + + @ConfKey("format") + @ConfComments("How should dates be formatted? Follows Java's DateTimeFormatter.ofPattern") + @DefaultString("dd/MM/yyyy kk:mm") + DateTimeFormatterWithPattern formatAndPattern(); + + @ConfKey("timezone") + @ConfComments({"Do you want to override the timezone? If 'default', the system default timezone is used", + "The value must be in continent/city format, such as 'America/New_York'. Uses Java's ZoneId.of"}) + @DefaultString("default") + ZoneId zoneId(); + + } + + @SubSection + Reasons reasons(); + + interface Reasons { + + @ConfKey("permit-blank") + @ConfComments({"Are blank reasons permitted?", + "If blank reasons are not permitted, staff must specify the full reason", + "If blank reasons are permitted, the default reason is used"}) + @DefaultBoolean(false) + boolean permitBlank(); + + @ConfKey("default-reason:") + @ConfComments("If the above is true, what is the default reason to use when staff do not specify a reason?") + @DefaultString("No reason stated.") + String defaultReason(); + + } + + @SubSection + EnforcementConfig enforcement(); // Sensitive name used in integration testing + + @ConfKey("player-uuid-resolution") + @SubSection + UUIDResolutionConfig uuidResolution(); + + @ConfKey("commands.aliases") + @ConfComments({"What commands should be registered as aliases for libertybans commands?", + "For each command listed here, '/' will be equal to '/libertybans '"}) + @DefaultStrings({ + "ban", + "mute", + "warn", + "kick", + "unban", + "unmute", + "unwarn" + }) + Set commandAliases(); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java new file mode 100644 index 000000000..6c774ac97 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/MessagesConfig.java @@ -0,0 +1,280 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import java.time.temporal.ChronoUnit; +import java.util.Map; +import java.util.Set; + +import space.arim.api.chat.SendableMessage; +import space.arim.api.chat.manipulator.SendableMessageManipulator; + +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.core.punish.MiscUtil; + +import space.arim.dazzleconf.annote.ConfComments; +import space.arim.dazzleconf.annote.ConfDefault.DefaultBoolean; +import space.arim.dazzleconf.annote.ConfDefault.DefaultMap; +import space.arim.dazzleconf.annote.ConfDefault.DefaultString; +import space.arim.dazzleconf.annote.ConfDefault.DefaultStrings; +import space.arim.dazzleconf.annote.ConfHeader; +import space.arim.dazzleconf.annote.ConfKey; +import space.arim.dazzleconf.annote.SubSection; + +@ConfHeader({ + "", + "Messages configuration", + "", + "", + "In most cases, the variables inside the default messages are those available", + "in that specific message. The exception to this is messages which are related", + "to a certain punishment.", + "", + "When message has an associated punishment, multiple variables are available:", + "", + "%ID% - punishment ID number", + "%TYPE% - punishment type, e.g. 'Ban'", + "%VICTIM% - display name of the victim of the punishment", + "%VICTIM_ID% - internal identifier of victim", + "%OPERATOR% - display name of the staff member who made the punishment", + "%OPERATOR_ID% - internal identifier of the operator", + "%UNOPERATOR% - staff member undoing the punishment. available only when the punishment is undone", + "%UNOPERATOR_ID% - internal identifier of staff member undoing the punishment", + "%REASON% - reason for the punishment", + "%SCOPE% - scope of the punishment", + "%DURATION% - original duration (how long the punishment was made for)", + "%START_DATE% - the date the punishment was created", + "%TIME_PASSED% - the time since the punishment was created", + "%END_DATE% - the date the punishment will end, or formatting.permanent-display.absolute for permanent punishments", + "%TIME_REMAINING% - the time until the punishment ends, or formatting.permanent-display.relative for permanent punishments", + "", + ""}) +public interface MessagesConfig { + + @SubSection + All all(); + + interface All { + + @ConfKey("prefix.enable") + @ConfComments("If enabled, all messages will be prefixed") + @DefaultBoolean(true) + boolean enablePrefix(); + + @ConfKey("prefix.value") + @ConfComments("The prefix to use") + @DefaultString("&6&lLibertyBans &r&8»&7 ") + SendableMessage rawPrefix(); + + default SendableMessage prefix() { + return (enablePrefix()) ? rawPrefix() : SendableMessage.create(); + } + + @ConfKey("base-permission-message") + @ConfComments("If a player types /libertybans but does not have the permission 'libertybans.commands', " + + "this is the denial message") + @DefaultString("&cYou may not use this.") + SendableMessage basePermissionMessage(); + + @ConfKey("not-found") + @ConfComments("When issuing commands, if the specified player or IP was not found, what should the error message be?") + @SubSection + NotFound notFound(); + + interface NotFound { + + @DefaultString("&c&o%TARGET%&r&7 was not found online or offline.") + SendableMessageManipulator uuid(); + + @DefaultString("&c&o%TARGET%&r&7 is not a valid IP address.") + SendableMessageManipulator ip(); + + @DefaultString("&c&o%TARGET%&r&7 is not a valid player or IP address.") + SendableMessageManipulator either(); + + } + + @DefaultString("&cUnknown sub command. Displaying usage:") + SendableMessage usage(); + + } + + @SubSection + Admin admin(); + + interface Admin { + + @ConfKey("no-permission") + @DefaultString("&cSorry, you cannot use this.") + SendableMessage noPermission(); + + @DefaultString("&a...") + SendableMessage ellipses(); + + @DefaultString("&aReloaded") + SendableMessage reloaded(); + + @DefaultString("&aRestarted") + SendableMessage restarted(); + + } + + @ConfComments("Specific formatting options") + @SubSection + Formatting formatting(); + + interface Formatting { + + @ConfKey("permanent-arguments") + @ConfComments({ + "There are 2 ways to make permanent punishments. The first is to not specify a time (/ban ).", + "The second is to specify a permanent amount of time (/ban perm ).", + "When typing commands, what time arguments will be counted as permanent?"}) + @DefaultStrings({"perm", "permanent", "permanently"}) + Set permanentArguments(); + + @ConfKey("permanent-display") + @ConfComments("How should 'permanent' be displayed as a length of time?") + @SubSection + PermanentDisplay permanentDisplay(); + + interface PermanentDisplay { + + @ConfComments("What do you call a permanent duration?") + @DefaultString("Infinite") + String duration(); + + @ConfComments("How do you describe the time remaining in a permanent punishment?") + @DefaultString("Permanent") + String relative(); + + @ConfComments("When does a permanent punishment end?") + @DefaultString("Never") + String absolute(); + + } + + @ConfKey("console-display") + @ConfComments("How should the console be displayed?") + @DefaultString("Console") + String consoleDisplay(); + + @ConfKey("global-scope-display") + @ConfComments("How should the global scope be displayed?") + @DefaultString("All servers") + String globalScopeDisplay(); + + @ConfKey("punishment-type-display") + @ConfComments("How should punishment types be displayed?") + @SubSection + PunishmentTypeDisplay punishmentTypeDisplay(); + + interface PunishmentTypeDisplay { + + @DefaultString("Ban") + String ban(); + + @DefaultString("Mute") + String mute(); + + @DefaultString("Warn") + String warn(); + + @DefaultString("Kick") + String kick(); + + default String forType(PunishmentType type) { + switch (type) { + case BAN: + return ban(); + case MUTE: + return mute(); + case WARN: + return warn(); + case KICK: + return kick(); + default: + throw MiscUtil.unknownType(type); + } + } + + } + } + + @SubSection + AdditionsSection additions(); + + @SubSection + RemovalsSection removals(); + + @SubSection + ListSection lists(); + + @SubSection + Misc misc(); + + interface Misc { + + @ConfKey("unknown-error") + @DefaultString("&cAn unknown error occurred.") + SendableMessage unknownError(); + + @ConfKey("sync-chat-denial-message") + @ConfComments("Only applicable if synchronous enforcement strategy is DENY in the main config") + @DefaultString("&cSynchronous chat denied. &7Please try again.") + SendableMessage syncDenialMessage(); + + @SubSection + @ConfComments("Concerns formatting of relative times and durations") + Time time(); + + interface Time { + + @DefaultMap({ + "YEARS", "%VALUE% years", + "MONTHS", "%VALUE% months", + "WEEKS", "%VALUE% weeks", + "DAYS", "%VALUE% days", + "HOURS", "%VALUE% hours", + "MINUTES", "%VALUE% minutes"}) + Map fragments(); + + @ConfKey("fallback-seconds") + @ConfComments({ + "Times are formatted to seconds accuracy, but you may not want to display seconds ", + "for most times. However, for very small durations, you need to display a value in seconds.", + "If you are using SECONDS in the above section, this value is meaningless."}) + @DefaultString("%VALUE% seconds") + String fallbackSeconds(); + + @ConfKey("grammar.comma") + @ConfComments("If enabled, places commas after each time fragment, except the last one") + @DefaultBoolean(true) + boolean useComma(); + + @ConfKey("grammar.and") + @ConfComments("What should come before the last fragment? Set to empty text to disable") + @DefaultString("and ") + String and(); + + } + + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/RemovalsSection.java b/bans-core/src/main/java/space/arim/libertybans/core/config/RemovalsSection.java new file mode 100644 index 000000000..40d0d8b08 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/RemovalsSection.java @@ -0,0 +1,161 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import space.arim.api.chat.SendableMessage; +import space.arim.api.chat.manipulator.SendableMessageManipulator; + +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.core.punish.MiscUtil; + +import space.arim.dazzleconf.annote.ConfDefault.DefaultString; +import space.arim.dazzleconf.annote.ConfHeader; +import space.arim.dazzleconf.annote.ConfKey; +import space.arim.dazzleconf.annote.SubSection; + +@ConfHeader("Regards /unban, /unmute, /unwarn") +public interface RemovalsSection { + + interface PunishmentRemoval { + + SendableMessage usage(); + + SendableMessage permissionCommand(); + + SendableMessageManipulator notFound(); + + SendableMessageManipulator successMessage(); + + SendableMessageManipulator successNotification(); + + } + + interface BanRemoval extends PunishmentRemoval { + + @Override + @DefaultString("&cUsage: /unban &e&c.") + SendableMessage usage(); + + @Override + @ConfKey("permission.command") + @DefaultString("&cYou may not unban other players.") + SendableMessage permissionCommand(); + + @Override + @ConfKey("not-found") + @DefaultString("&c&o%TARGET%&r&7 is not banned.") + SendableMessageManipulator notFound(); + + @Override + @ConfKey("success.message") + @DefaultString("&7Unbanned &c&o%VICTIM%&r&7.") + SendableMessageManipulator successMessage(); + + @Override + @ConfKey("success.notification") + @DefaultString("&c&o%UNOPERATOR%&r&7 unbanned &c&o%TARGET%&r&7.") + SendableMessageManipulator successNotification(); + + } + + interface MuteRemoval extends PunishmentRemoval { + + @Override + @DefaultString("&cUsage: /unmute &e&c.") + SendableMessage usage(); + + @Override + @ConfKey("permission.command") + @DefaultString("&cYou may not unmute other players.") + SendableMessage permissionCommand(); + + @Override + @ConfKey("not-found") + @DefaultString("&c&o%TARGET%&r&7 is not muted.") + SendableMessageManipulator notFound(); + + @Override + @ConfKey("success.message") + @DefaultString("&7Unmuted &c&o%VICTIM%&r&7.") + SendableMessageManipulator successMessage(); + + @Override + @ConfKey("success.notification") + @DefaultString("&c&o%UNOPERATOR%&r&7 unmuted &c&o%TARGET%&r&7.") + SendableMessageManipulator successNotification(); + + } + + interface WarnRemoval extends PunishmentRemoval { + + @Override + @DefaultString("&cUsage: /unwarn &e &c.") + SendableMessage usage(); + + @Override + @ConfKey("permission.command") + @DefaultString("&cYou may not unwarn other players.") + SendableMessage permissionCommand(); + + @Override + @ConfKey("not-found") + @DefaultString("&c&o%TARGET%&r&7 does not have a warn by &c&o%ID%&r&7.") + SendableMessageManipulator notFound(); + + @ConfKey("not-a-number") + @DefaultString("&c&o%ID_ARG%&r&7 is not a number.") + SendableMessageManipulator notANumber(); + + @Override + @ConfKey("success.message") + @DefaultString("&7Unmuted &c&o%VICTIM%&r&7.") + SendableMessageManipulator successMessage(); + + @Override + @ConfKey("success.notification") + @DefaultString("&c&o%UNOPERATOR%&r&7 unmuted &c&o%VICTIM%&r&7.") + SendableMessageManipulator successNotification(); + + } + + @SubSection + BanRemoval bans(); + + @SubSection + MuteRemoval mutes(); + + @SubSection + WarnRemoval warns(); + + default PunishmentRemoval forType(PunishmentType type) { + switch (type) { + case BAN: + return bans(); + case MUTE: + return mutes(); + case WARN: + return warns(); + case KICK: + throw new IllegalArgumentException("Cannot undo kicks"); + default: + throw MiscUtil.unknownType(type); + } + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/SqlConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/config/SqlConfig.java new file mode 100644 index 000000000..12cb9dd83 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/config/SqlConfig.java @@ -0,0 +1,142 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import java.util.Map; + +import space.arim.libertybans.core.database.Vendor; + +import space.arim.dazzleconf.annote.ConfComments; +import space.arim.dazzleconf.annote.ConfDefault.DefaultBoolean; +import space.arim.dazzleconf.annote.ConfDefault.DefaultInteger; +import space.arim.dazzleconf.annote.ConfDefault.DefaultMap; +import space.arim.dazzleconf.annote.ConfDefault.DefaultString; +import space.arim.dazzleconf.annote.ConfHeader; +import space.arim.dazzleconf.annote.ConfKey; +import space.arim.dazzleconf.annote.SubSection; + +@ConfHeader({ + "", + "SQL Database settings", + "", + "For most servers, the default options here are perfect. Most users will not need to bother here.", + "", + "However, for servers wishing to use MariaDB / MySQL, or for large servers wishing to tweak performance,", + "further setup is required.", + "", + "", + "Note well:", + "To apply changes made here, use '/libertybans restart' or restart the server.", + "Using '/libertybans reload' will NOT update your database settings!", + "", + "", + ""}) +public interface SqlConfig { + + @ConfKey("rdms-vendor") + @ConfComments({ + "What RDMS vendor will you be using?", + "Available options:", + "'HSQLDB' - Local HyperSQL database. No additional setup required.", + "'MARIADB' - Requires a separate MariaDB or MySQL database."}) + @DefaultString("HSQLDB") + Vendor vendor(); // Sensitive name used in integration testing + + @ConfKey("connection-pool-size") + @ConfComments({ + "", + "How large should the connection pool be?", + "A thread pool of similar size is derived from the connection pool size.", + "For most servers, the default option is suitable."}) + @DefaultInteger(6) + int poolSize(); + + @SubSection + @ConfComments({ + "", + "Connection timeout settings", + "LibertyBans uses HikariCP for connection pooling. The following settings control connection timeouts."}) + Timeouts timeouts(); + + interface Timeouts { + + @ConfKey("connection-timeout-seconds") + @ConfComments({ + "How long, at maximum, should LibertyBans wait when acquiring new connections, " + + "if no existing connection is available?"}) + @DefaultInteger(14) + int connectionTimeoutSeconds(); + + @ConfKey("max-lifetime-minutes") + @ConfComments({ + "How long, at maxium, should a connection in the pool last before having to be recreated?", + "\"This value should be set for MariaDB or MySQL. HikariCP notes:", + "\"It should be several seconds shorter than any database or infrastructure imposed connection time limit\""}) + @DefaultInteger(25) + int maxLifetimeMinutes(); + + } + + @ConfKey("auth-details") + @SubSection + @ConfComments({ + "MariaDB / MySQL authentication details", + "These must be changed to the proper credentials in order to use MariaDB/MySQL"}) + AuthDetails authDetails(); // Sensitive name used in integration testing + + interface AuthDetails { + + @DefaultString("localhost") + String host(); + + @DefaultInteger(3306) + int port(); + + @DefaultString("bans") + String database(); + + @ConfKey("user") + @DefaultString("username") + String username(); + + @DefaultString("defaultpass") + String password(); + + } + + @ConfKey("mariadb-connection-properties") + @ConfComments({ + "Connection properties to be applied to database connections to the MariaDB / MySQL database", + "These are specified in key-value format. Has no effect on HSQLDB" + }) + @DefaultMap({ + "useUnicode", "true", + "characterEncoding", "UTF-8", + "useServerPrepStmts", "true", + "cachePrepStmts", "true", + "prepStmtCacheSize", "25", + "prepStmtCacheSqlLimit", "256"}) + Map connectionProperties(); + + @ConfKey("use-traditional-jdbc-url") + @ConfComments("Legacy option. Don't touch this unless you understand it or you're told to enable it.") + @DefaultBoolean(false) + boolean useTraditionalJdbcUrl(); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/Database.java b/bans-core/src/main/java/space/arim/libertybans/core/database/Database.java index 3f24e130f..6b2836bcf 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/Database.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/Database.java @@ -43,14 +43,16 @@ import space.arim.libertybans.api.AddressVictim; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.PlayerVictim; -import space.arim.libertybans.api.PunishmentDatabase; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Scope; +import space.arim.libertybans.api.ServerScope; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.Victim.VictimType; +import space.arim.libertybans.api.manager.PunishmentDatabase; import space.arim.libertybans.core.LibertyBansCore; +import space.arim.libertybans.core.punish.MiscUtil; import space.arim.jdbcaesar.JdbCaesar; +import space.arim.jdbcaesar.QuerySource; import space.arim.jdbcaesar.adapter.EnumNameAdapter; import space.arim.jdbcaesar.builder.JdbCaesarBuilder; import space.arim.jdbcaesar.transact.IsolationLevel; @@ -80,7 +82,7 @@ public class Database implements PunishmentDatabase { this.core = core; this.vendor = vendor; - executor = Executors.newFixedThreadPool(poolSize, new IOUtils.ThreadFactoryImpl("LibertyBans-Database-")); + executor = Executors.newFixedThreadPool(poolSize, core.newThreadFactory("Database")); HikariWrapper connectionSource = new HikariWrapper(new HikariDataSource(hikariConf)); JdbCaesarBuilder jdbCaesarBuilder = new JdbCaesarBuilder() @@ -88,6 +90,7 @@ public class Database implements PunishmentDatabase { .exceptionHandler((ex) -> { logger.error("Error while executing a database query", ex); }) + .defaultFetchSize(1000) .defaultIsolation(IsolationLevel.REPEATABLE_READ) .rewrapExceptions(true) .addAdapters( @@ -97,14 +100,14 @@ public class Database implements PunishmentDatabase { new JdbCaesarHelper.OperatorAdapter(), new JdbCaesarHelper.ScopeAdapter(core.getScopeManager()), new JdbCaesarHelper.UUIDBytesAdapter(), - new JdbCaesarHelper.InetAddressAdapter()); + new JdbCaesarHelper.NetworkAddressAdapter()); jdbCaesar = jdbCaesarBuilder.build(); } void startRefreshTaskIfNecessary() { if (getVendor() != Vendor.MARIADB) { synchronized (this) { - hyperSqlRefreshTask = core.getResources().getEnhancedExecutor().scheduleRepeating( + hyperSqlRefreshTask = core.getEnhancedExecutor().scheduleRepeating( new RefreshTaskRunnable(core.getDatabaseManager(), this), Duration.ofHours(1L), DelayCalculators.fixedDelay()); } @@ -194,15 +197,15 @@ public PunishmentType getTypeFromResult(ResultSet resultSet) throws SQLException } public Victim getVictimFromResult(ResultSet resultSet) throws SQLException { - VictimType vType = VictimType.valueOf(resultSet.getString("victim_type")); + VictimType victimType = VictimType.valueOf(resultSet.getString("victim_type")); byte[] bytes = resultSet.getBytes("victim"); - switch (vType) { + switch (victimType) { case PLAYER: return PlayerVictim.of(UUIDUtil.fromByteArray(bytes)); case ADDRESS: return AddressVictim.of(bytes); default: - throw new IllegalStateException("Unknown victim type " + vType); + throw MiscUtil.unknownVictimType(victimType); } } @@ -214,7 +217,7 @@ public String getReasonFromResult(ResultSet resultSet) throws SQLException { return resultSet.getString("reason"); } - public Scope getScopeFromResult(ResultSet resultSet) throws SQLException { + public ServerScope getScopeFromResult(ResultSet resultSet) throws SQLException { String server = resultSet.getString("scope"); if (server != null) { return core.getScopeManager().specificScope(server); @@ -229,5 +232,20 @@ public long getStartFromResult(ResultSet resultSet) throws SQLException { public long getEndFromResult(ResultSet resultSet) throws SQLException { return resultSet.getLong("end"); } + + public void clearExpiredPunishments(QuerySource querySource, PunishmentType type, long currentTime) { + assert type != PunishmentType.KICK; + String query; + if (getVendor().hasDeleteFromJoin()) { + query = "DELETE `thetype` FROM `libertybans_" + type.getLowercaseNamePlural() + "` `thetype` " + + "INNER JOIN `libertybans_punishments` `puns` ON `puns`.`id` = `thetype`.`id` WHERE " + + "`puns`.`end` != 0 AND `puns`.`end` < ?"; + + } else { + query = "DELETE FROM `libertybans_" + type.getLowercaseNamePlural() + "` WHERE `id` IN " + + "(SELECT `id` FROM `libertybans_punishments` `puns` WHERE `puns`.`end` != 0 AND `puns`.`end` < ?)"; + } + querySource.query(query).params(currentTime).voidResult().execute(); + } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseManager.java b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseManager.java index d3ef56da9..e5aaadb96 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseManager.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseManager.java @@ -18,19 +18,6 @@ */ package space.arim.libertybans.core.database; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import space.arim.omnibus.util.ThisClass; - -import space.arim.api.configure.SingleKeyValueTransformer; -import space.arim.api.configure.ValueTransformer; - import space.arim.libertybans.bootstrap.StartupException; import space.arim.libertybans.core.LibertyBansCore; import space.arim.libertybans.core.Part; @@ -41,8 +28,6 @@ public class DatabaseManager implements Part { private volatile Database database; - private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); - public DatabaseManager(LibertyBansCore core) { this.core = core; } @@ -74,9 +59,12 @@ public void restart() { throw new StartupException("Database restart failed"); } currentDatabase.cancelRefreshTaskIfNecessary(); - CompletableFuture.delayedExecutor(8, TimeUnit.SECONDS).execute( - (currentDatabase.getVendor() == database.getVendor()) ? - currentDatabase::close : currentDatabase::closeCompletely); + + if (currentDatabase.getVendor() == database.getVendor()) { + currentDatabase.close(); + } else { + currentDatabase.closeCompletely(); + } database.startRefreshTaskIfNecessary(); this.database = database; @@ -86,62 +74,7 @@ public void restart() { public void shutdown() { Database database = this.database; database.cancelRefreshTaskIfNecessary(); - core.addDelayedShutdownHook(database::closeCompletely); - } - - /* - * - * Configuration - * - */ - - private static boolean isAtLeast(Object value, int amount) { - return value instanceof Integer && ((Integer) value) >= amount; - } - - public static List createConfigTransformers() { - var vendorTransformer = SingleKeyValueTransformer.create("rdms-vendor", (value) -> { - if (value instanceof Vendor) { - return value; - } - if (value instanceof String) { - String vendorName = (String) value; - switch (vendorName.toUpperCase(Locale.ENGLISH)) { - case "HYPERSQL": - case "HSQLDB": - return Vendor.HSQLDB; - case "MYSQL": - case "MARIADB": - return Vendor.MARIADB; - default: - break; - } - } - logger.warn("Unknown RDMS vendor {}", value); - return null; - }); - var poolSizeTransformer = SingleKeyValueTransformer.createPredicate("connection-pool-size", (value) -> { - if (!isAtLeast(value, 0)) { - logger.warn("Bad connection pool size {}", value); - return true; - } - return false; - }); - var timeoutTransformer = SingleKeyValueTransformer.createPredicate("timeouts.connection-timeout-seconds", (value) -> { - if (!isAtLeast(value, 1)) { - logger.warn("Bad connection timeout setting {}", value); - return true; - } - return false; - }); - var lifetimeTransformer = SingleKeyValueTransformer.createPredicate("timeouts.max-lifetime-minutes", (value) -> { - if (!isAtLeast(value, 1)) { - logger.warn("Bad lifetime timeout setting {}", value); - return true; - } - return false; - }); - return List.of(vendorTransformer, poolSizeTransformer, timeoutTransformer, lifetimeTransformer); + database.closeCompletely(); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java index 3fccf317e..bc7ba4fc6 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/DatabaseSettings.java @@ -21,8 +21,10 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,11 +34,10 @@ import space.arim.omnibus.util.ThisClass; import space.arim.omnibus.util.concurrent.CentralisedFuture; -import space.arim.api.configure.ConfigAccessor; - import space.arim.libertybans.bootstrap.StartupException; import space.arim.libertybans.core.LibertyBansCore; -import space.arim.libertybans.driver.DriverCreator; +import space.arim.libertybans.core.config.SqlConfig; +import space.arim.libertybans.core.punish.MiscUtil; /** * Database settings creator, NOT thread safe! @@ -48,7 +49,7 @@ class DatabaseSettings { private final LibertyBansCore core; - private ConfigAccessor config; + private SqlConfig config; private Vendor vendor; private HikariConfig hikariConf; @@ -64,7 +65,7 @@ class DatabaseSettings { * @return a database result, which should be checked for success or failure */ CentralisedFuture create() { - return create(core.getConfigs().getSql()); + return create(core.getConfigs().getSqlConfig()); } /** @@ -73,9 +74,9 @@ CentralisedFuture create() { * @param config the config accessor * @return a database result, which should be checked for success or failure */ - CentralisedFuture create(ConfigAccessor config) { + CentralisedFuture create(SqlConfig config) { this.config = config; - vendor = config.getObject("rdms-vendor", Vendor.class); + vendor = config.vendor(); hikariConf = new HikariConfig(); setHikariConfig(); @@ -86,9 +87,32 @@ CentralisedFuture create(ConfigAccessor config) { return tablesAndViewsCreation.thenApply((success) -> new DatabaseResult(database, success)); } + private void setHikariConfig() { + setUsernameAndPassword(); + setConfiguredDriver(); + + // Timeouts + SqlConfig.Timeouts timeouts = config.timeouts(); + Duration connectionTimeout = Duration.ofSeconds(timeouts.connectionTimeoutSeconds()); + Duration maxLifetime = Duration.ofMinutes(timeouts.maxLifetimeMinutes()); + hikariConf.setConnectionTimeout(connectionTimeout.toMillis()); + hikariConf.setMaxLifetime(maxLifetime.toMillis()); + + // Pool size + int poolSize = config.poolSize(); + hikariConf.setMinimumIdle(poolSize); + hikariConf.setMaximumPoolSize(poolSize); + + // Other settings + hikariConf.setAutoCommit(false); + hikariConf.setTransactionIsolation("TRANSACTION_REPEATABLE_READ"); + hikariConf.setPoolName("LibertyBans-HikariCP-" + vendor.displayName()); + } + private void setUsernameAndPassword() { - String username = config.getString("auth-details.user"); - String password = config.getString("auth-details.password"); + SqlConfig.AuthDetails authDetails = config.authDetails(); + String username = authDetails.username(); + String password = authDetails.password(); if (vendor == Vendor.MARIADB && (username.equals("username") || password.equals("defaultpass"))) { logger.warn("Not using MariaDB/MySQL because authentication details are still default"); vendor = Vendor.HSQLDB; @@ -101,62 +125,70 @@ private void setUsernameAndPassword() { hikariConf.setPassword(password); } - private void setHikariConfig() { - setUsernameAndPassword(); - - int poolSize = config.getInteger("connection-pool-size"); - hikariConf.setMinimumIdle(poolSize); - hikariConf.setMaximumPoolSize(poolSize); - - int connectionTimeout = config.getInteger("timeouts.connection-timeout-seconds"); - int maxLifetime = config.getInteger("timeouts.max-lifetime-minutes"); - hikariConf.setConnectionTimeout(TimeUnit.MILLISECONDS.convert(connectionTimeout, TimeUnit.SECONDS)); - hikariConf.setMaxLifetime(TimeUnit.MILLISECONDS.convert(maxLifetime, TimeUnit.MINUTES)); - - setConfiguredDriver(); - - hikariConf.setAutoCommit(false); - - @SuppressWarnings("unchecked") - Map connectProps = config.getObject("connection-properties", Map.class); - for (Map.Entry property : connectProps.entrySet()) { - String propName = property.getKey(); - Object propValue = property.getValue(); - logger.trace("Setting data source property {} to {}", propName, propValue); - hikariConf.addDataSourceProperty(propName, propValue); - } - - hikariConf.setPoolName("LibertyBans-HikariCP-" + vendor.displayName()); - } - private void setConfiguredDriver() { - boolean jdbcUrl = config.getBoolean("use-traditional-jdbc-url"); - DriverCreator driverCreator = new DriverCreator(hikariConf, jdbcUrl); - + final String jdbcUrl; switch (vendor) { case MARIADB: - String host = config.getString("auth-details.host"); - int port = config.getInteger("auth-details.port"); - String database = config.getString("auth-details.database"); - driverCreator.createMariaDb(host, port, database); + SqlConfig.AuthDetails authDetails = config.authDetails(); + String host = authDetails.host(); + int port = authDetails.port(); + String database = authDetails.database(); + + hikariConf.addDataSourceProperty("databaseName", database); + jdbcUrl = "jdbc:mariadb://" + host + ":" + port + "/" + database + getMariaDbUrlProperties(); break; case HSQLDB: Path databaseFolder = core.getFolder().resolve("hypersql"); - if (!Files.isDirectory(databaseFolder)) { - try { - Files.createDirectory(databaseFolder); - } catch (IOException ex) { - throw new StartupException("Cannot create database folder", ex); - } + try { + Files.createDirectories(databaseFolder); + } catch (IOException ex) { + throw new StartupException("Cannot create database folder", ex); } - driverCreator.createHsqldb(databaseFolder + "/punishments-database"); + Path databaseFile = databaseFolder.resolve("punishments-database").toAbsolutePath(); + jdbcUrl = "jdbc:hsqldb:file:" + databaseFile; break; default: - throw new IllegalStateException("Unknown database vendor " + vendor); - } + throw MiscUtil.unknownVendor(vendor); + } + if (config.useTraditionalJdbcUrl()) { + setDriverClassName(vendor.driverClassName()); + hikariConf.setJdbcUrl(jdbcUrl); + + } else { + hikariConf.setDataSourceClassName(vendor.dataSourceClassName()); + hikariConf.addDataSourceProperty("url", jdbcUrl); + } + } + + private String getMariaDbUrlProperties() { + List connectProps = new ArrayList<>(); + for (Map.Entry property : config.connectionProperties().entrySet()) { + String propName = property.getKey(); + String propValue = property.getValue(); + logger.trace("Adding connection property {} with value {}", propName, propValue); + connectProps.add(propName + "=" + propValue); + } + String properties = String.join("&", connectProps); + return (properties.isEmpty()) ? "" : "?" + properties; + } + + /** + * Sets the driver class name utilising the context classloader + * + * @param driverClassName the driver class name + */ + private void setDriverClassName(String driverClassName) { + Thread currentThread = Thread.currentThread(); + ClassLoader initialContextLoader = currentThread.getContextClassLoader(); + try { + currentThread.setContextClassLoader(getClass().getClassLoader()); + hikariConf.setDriverClassName(driverClassName); + } finally { + currentThread.setContextClassLoader(initialContextLoader); + } } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/IOUtils.java b/bans-core/src/main/java/space/arim/libertybans/core/database/IOUtils.java index bbe81afdd..1f04c6796 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/IOUtils.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/IOUtils.java @@ -23,26 +23,18 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.UncheckedIOException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.ThreadFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import space.arim.omnibus.util.ThisClass; - -public final class IOUtils { - - private static final Class THIS_CLASS = ThisClass.get(); +final class IOUtils { private IOUtils() {} private static InputStream getResource(String resourceName) throws IOException { - URL url = THIS_CLASS.getResource('/' + resourceName); + URL url = IOUtils.class.getResource('/' + resourceName); if (url == null) { throw new IllegalArgumentException("Resource " + resourceName + " not found"); } @@ -54,7 +46,7 @@ private static InputStream getResource(String resourceName) throws IOException { * * @param resourceName the resource name * @return the resource content - * @throws IllegalStateException if an IO error occurred + * @throws UncheckedIOException if an IO error occurred */ static ByteArrayOutputStream readResource(String resourceName) { try (InputStream is = getResource(resourceName)) { @@ -63,7 +55,7 @@ static ByteArrayOutputStream readResource(String resourceName) { is.transferTo(baos); return baos; } catch (IOException ex) { - throw new IllegalStateException("Failed to read internal resource " + resourceName, ex); + throw new UncheckedIOException("Failed to read internal resource " + resourceName, ex); } } @@ -76,7 +68,7 @@ static ByteArrayOutputStream readResource(String resourceName) { * * @param resourceName the resource name * @return a mutable list of SQL queries - * @throws IllegalStateException if an IO error occurred + * @throws UncheckedIOException if an IO error occurred */ static List readSqlQueries(String resourceName) { try (InputStream inputStream = getResource(resourceName); @@ -102,47 +94,8 @@ static List readSqlQueries(String resourceName) { } return result; } catch (IOException ex) { - throw new IllegalStateException("Failed to read internal resource " + resourceName, ex); - } - } - - public static class ThreadFactoryImpl implements ThreadFactory { - - private final String prefix; - private int threadId = 1; - - private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); - - public ThreadFactoryImpl(String prefix) { - this.prefix = prefix; - } - - private synchronized int nextId() { - return threadId++; - } - - @Override - public Thread newThread(Runnable r) { - String name = prefix + nextId(); - logger.debug("Spawning new thread {}", name); - return new Thread(r, name); - } - - } - - public static class SafeExecutorWrapper implements Executor { - - private final Executor delegate; - - public SafeExecutorWrapper(Executor delegate) { - this.delegate = delegate; - } - - @Override - public void execute(Runnable command) { - delegate.execute(command); + throw new UncheckedIOException("Failed to read internal resource " + resourceName, ex); } - } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/JdbCaesarHelper.java b/bans-core/src/main/java/space/arim/libertybans/core/database/JdbCaesarHelper.java index 5f931b69d..3c8da3a3d 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/JdbCaesarHelper.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/JdbCaesarHelper.java @@ -18,7 +18,6 @@ */ package space.arim.libertybans.core.database; -import java.net.InetAddress; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; @@ -28,13 +27,14 @@ import space.arim.libertybans.api.AddressVictim; import space.arim.libertybans.api.ConsoleOperator; +import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.PlayerOperator; import space.arim.libertybans.api.PlayerVictim; -import space.arim.libertybans.api.Scope; +import space.arim.libertybans.api.ServerScope; import space.arim.libertybans.api.Victim; import space.arim.libertybans.api.Victim.VictimType; -import space.arim.libertybans.core.Scoper; +import space.arim.libertybans.core.punish.Scoper; import space.arim.jdbcaesar.adapter.DataTypeAdapter; @@ -116,8 +116,8 @@ static class ScopeAdapter implements DataTypeAdapter { @Override public Object adaptObject(Object parameter) { - if (parameter instanceof Scope) { - return scoper.getServer((Scope) parameter); + if (parameter instanceof ServerScope) { + return scoper.getServer((ServerScope) parameter); } return parameter; } @@ -136,12 +136,12 @@ public Object adaptObject(Object parameter) { } - static class InetAddressAdapter implements DataTypeAdapter { + static class NetworkAddressAdapter implements DataTypeAdapter { @Override public Object adaptObject(Object parameter) { - if (parameter instanceof InetAddress) { - return ((InetAddress) parameter).getAddress(); + if (parameter instanceof NetworkAddress) { + return ((NetworkAddress) parameter).getRawAddress(); } return parameter; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/RefreshTaskRunnable.java b/bans-core/src/main/java/space/arim/libertybans/core/database/RefreshTaskRunnable.java index 9f36413d2..72c12886c 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/RefreshTaskRunnable.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/RefreshTaskRunnable.java @@ -24,7 +24,7 @@ import space.arim.omnibus.util.ThisClass; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.core.MiscUtil; +import space.arim.libertybans.core.punish.MiscUtil; class RefreshTaskRunnable implements Runnable { @@ -49,11 +49,7 @@ public void run() { database.jdbCaesar().transaction().body((querySource, controller) -> { for (PunishmentType type : MiscUtil.punishmentTypesExcludingKick()) { - querySource.query( - "DELETE FROM `libertybans_" + type.getLowercaseNamePlural() + "` WHERE `id` IN " - + "(SELECT `id` FROM `libertybans_punishments` `puns` WHERE (`puns`.`end` != 0 AND `puns`.`end` < ?))") - .params(currentTime) - .voidResult().execute(); + database.clearExpiredPunishments(querySource, type, currentTime); } return (Void) null; }).onError(() -> null).execute(); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/TableDefinitions.java b/bans-core/src/main/java/space/arim/libertybans/core/database/TableDefinitions.java index 6372d3432..5a05da9f8 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/TableDefinitions.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/TableDefinitions.java @@ -37,7 +37,7 @@ import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; import space.arim.libertybans.core.LibertyBansCore; -import space.arim.libertybans.core.MiscUtil; +import space.arim.libertybans.core.punish.MiscUtil; import space.arim.jdbcaesar.mapper.UpdateCountMapper; import space.arim.jdbcaesar.transact.TransactionQuerySource; diff --git a/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java b/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java index e8b9de4cf..60f8483dd 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/database/Vendor.java @@ -20,19 +20,31 @@ public enum Vendor { - MARIADB("MariaDB"), - HSQLDB("HyperSQL"); + MARIADB("MariaDB", "org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MariaDbDataSource"), + HSQLDB("HyperSQL", "org.hsqldb.jdbc.JDBCDriver", "org.hsqldb.jdbc.JDBCDataSource"); private final String displayName; + private final String driverClassName; + private final String dataSourceClassName; - private Vendor(String displayName) { + private Vendor(String displayName, String driverClassName, String dataSourceClassName) { this.displayName = displayName; + this.driverClassName = driverClassName; + this.dataSourceClassName = dataSourceClassName; } String displayName() { return displayName; } + String driverClassName() { + return driverClassName; + } + + String dataSourceClassName() { + return dataSourceClassName; + } + public boolean useStoredRoutines() { return this == MARIADB; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractCmdSender.java b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractCmdSender.java index 654a6c339..d62b8e6ae 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractCmdSender.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractCmdSender.java @@ -36,6 +36,10 @@ protected AbstractCmdSender(LibertyBansCore core, Object rawSender, Operator ope this.operator = operator; } + protected LibertyBansCore core() { + return core; + } + @Override public Operator getOperator() { return operator; @@ -43,12 +47,14 @@ public Operator getOperator() { @Override public void sendMessage(SendableMessage message) { + SendableMessage prefix = core.getMessagesConfig().all().prefix(); + message = prefix.concatenate(message); core.getEnvironment().getPlatformHandle().sendMessage(rawSender, message); } @Override - public void parseThenSend(String message) { - sendMessage(core.getFormatter().parseMessage(message)); + public void sendLiteralMessage(String message) { + sendMessage(core.getFormatter().parseMessageWithPrefix(message)); } @Override diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java index 1b779ad39..b316ffb49 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/AbstractEnvEnforcer.java @@ -21,17 +21,28 @@ import java.util.UUID; import space.arim.api.chat.SendableMessage; +import space.arim.libertybans.core.LibertyBansCore; + public abstract class AbstractEnvEnforcer implements EnvEnforcer { + private final LibertyBansCore core; private final Environment env; - protected AbstractEnvEnforcer(Environment env) { + protected AbstractEnvEnforcer(LibertyBansCore core, Environment env) { + this.core = core; this.env = env; } protected Environment env() { return env; } + + @Override + public final void sendToThoseWithPermission(String permission, SendableMessage message) { + sendToThoseWithPermission0(permission, core.getFormatter().prefix(message)); + } + + protected abstract void sendToThoseWithPermission0(String permission, SendableMessage message); @Override public void kickByUUID(UUID uuid, SendableMessage message) { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/CmdSender.java b/bans-core/src/main/java/space/arim/libertybans/core/env/CmdSender.java index aadff7627..90b0537ea 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/CmdSender.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/CmdSender.java @@ -30,7 +30,7 @@ public interface CmdSender { void sendMessage(SendableMessage message); - void parseThenSend(String message); + void sendLiteralMessage(String messageToParse); Object getRawSender(); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java index 3dc7aba28..9648d0c86 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvEnforcer.java @@ -18,6 +18,7 @@ */ package space.arim.libertybans.core.env; +import java.net.InetAddress; import java.util.UUID; import java.util.function.Consumer; @@ -68,6 +69,6 @@ public interface EnvEnforcer { UUID getUniqueIdFor(@PlatformPlayer Object player); - byte[] getAddressFor(@PlatformPlayer Object player); + InetAddress getAddressFor(@PlatformPlayer Object player); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java index f1a22b070..b65fc05b7 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/EnvironmentManager.java @@ -37,7 +37,7 @@ public EnvironmentManager(LibertyBansCore core) { @Override public void startup() { listeners.addAll(core.getEnvironment().createListeners()); - for (String alias : core.getConfigs().getConfig().getStringList("commands.aliases")) { + for (String alias : core.getMainConfig().commandAliases()) { listeners.add(core.getEnvironment().createAliasCommand(alias)); } listeners.forEach(PlatformListener::register); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/env/TargetMatcher.java b/bans-core/src/main/java/space/arim/libertybans/core/env/TargetMatcher.java index 02d7dd598..cb917e4b5 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/env/TargetMatcher.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/env/TargetMatcher.java @@ -32,17 +32,13 @@ public class TargetMatcher { private final Consumer<@PlatformPlayer Object> callback; public TargetMatcher(Set uuids, Set addresses, Consumer<@PlatformPlayer Object> callback) { - this.uuids = uuids; - this.addresses = addresses; + this.uuids = Set.copyOf(uuids); + this.addresses = Set.copyOf(addresses); this.callback = callback; } - public Set uuids() { - return uuids; - } - - public Set addresses() { - return addresses; + public boolean matches(UUID uuid, InetAddress address) { + return uuids.contains(uuid) || addresses.contains(address); } public Consumer<@PlatformPlayer Object> callback() { diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/AbstractPunishmentBase.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/AbstractPunishmentBase.java new file mode 100644 index 000000000..ea1d86fba --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/AbstractPunishmentBase.java @@ -0,0 +1,103 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.punish; + +import java.util.Objects; + +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.PunishmentBase; + +abstract class AbstractPunishmentBase implements PunishmentBase { + + transient final EnforcementCenter center; + + private final PunishmentType type; + private final Victim victim; + private final Operator operator; + private final String reason; + private final ServerScope scope; + + AbstractPunishmentBase(EnforcementCenter center, + PunishmentType type, Victim victim, Operator operator, String reason, ServerScope scope) { + this.center = center; + + this.type = Objects.requireNonNull(type, "type"); + this.victim = Objects.requireNonNull(victim, "victim"); + this.operator = Objects.requireNonNull(operator, "operator"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.scope = Objects.requireNonNull(scope, "scope"); + } + + @Override + public PunishmentType getType() { + return type; + } + + @Override + public Victim getVictim() { + return victim; + } + + @Override + public Operator getOperator() { + return operator; + } + + @Override + public String getReason() { + return reason; + } + + @Override + public ServerScope getScope() { + return scope; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + type.hashCode(); + result = prime * result + victim.hashCode(); + result = prime * result + operator.hashCode(); + result = prime * result + reason.hashCode(); + result = prime * result + scope.hashCode(); + return result; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof AbstractPunishmentBase)) { + return false; + } + AbstractPunishmentBase other = (AbstractPunishmentBase) object; + return type == other.type + && victim.equals(other.victim) + && operator.equals(other.operator) + && reason.equals(other.reason) + && scope.equals(other.scope); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java new file mode 100644 index 000000000..12a2209f5 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentBuilderImpl.java @@ -0,0 +1,99 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.punish; + +import java.time.Duration; +import java.util.Objects; + +import space.arim.libertybans.api.ConsoleOperator; +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.DraftPunishment; +import space.arim.libertybans.api.punish.DraftPunishmentBuilder; + +class DraftPunishmentBuilderImpl implements DraftPunishmentBuilder { + + private final EnforcementCenter center; + + PunishmentType type; + Victim victim; + Operator operator = ConsoleOperator.INSTANCE; + String reason; + ServerScope scope; + Duration duration = Duration.ZERO; + + DraftPunishmentBuilderImpl(EnforcementCenter center) { + this.center = center; + scope = center.core().getScopeManager().globalScope(); + } + + @Override + public DraftPunishmentBuilder type(PunishmentType type) { + this.type = Objects.requireNonNull(type, "type"); + return this; + } + + @Override + public DraftPunishmentBuilder victim(Victim victim) { + this.victim = Objects.requireNonNull(victim, "victim"); + return this; + } + + @Override + public DraftPunishmentBuilder operator(Operator operator) { + this.operator = Objects.requireNonNull(operator, "operator"); + return this; + } + + @Override + public DraftPunishmentBuilder reason(String reason) { + this.reason = Objects.requireNonNull(reason, "reason"); + return this; + } + + @Override + public DraftPunishmentBuilder scope(ServerScope scope) { + this.scope = MiscUtil.checkScope(scope); + return this; + } + + @Override + public DraftPunishmentBuilder duration(Duration duration) { + Objects.requireNonNull(duration, "duration"); + if (duration.isNegative()) { + throw new IllegalArgumentException("duration cannot be negative"); + } + this.duration = duration; + return this; + } + + @Override + public DraftPunishment build() { + if (type == null || victim == null || reason == null) { + throw new IllegalStateException("Builder details have not been set"); + } + if (type == PunishmentType.KICK && !duration.isZero()) { + throw new IllegalArgumentException("Kicks cannot be temporary"); + } + return new DraftPunishmentImpl(center, this); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentImpl.java new file mode 100644 index 000000000..877a415eb --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/DraftPunishmentImpl.java @@ -0,0 +1,85 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.punish; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import space.arim.libertybans.api.punish.DraftPunishment; +import space.arim.libertybans.api.punish.Punishment; + +class DraftPunishmentImpl extends AbstractPunishmentBase implements DraftPunishment { + + private final Duration duration; + + DraftPunishmentImpl(EnforcementCenter center, DraftPunishmentBuilderImpl builder) { + super(center, builder.type, builder.victim, builder.operator, builder.reason, builder.scope); + duration = builder.duration; + } + + @Override + public Duration getDuration() { + return duration; + } + + @Override + public CentralisedFuture enactPunishment() { + return enactPunishmentWithoutEnforcement().thenCompose((punishment) -> { + return (punishment == null) ? + CompletableFuture.completedStage(null) : + punishment.enforcePunishment().thenApply((ignore) -> punishment); + }); + } + + @Override + public CentralisedFuture enactPunishmentWithoutEnforcement() { + return center.getEnactor().enactPunishment(this); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + duration.hashCode(); + return result; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!super.equals(object)) { + return false; + } + if (!(object instanceof DraftPunishmentImpl)) { + return false; + } + DraftPunishmentImpl other = (DraftPunishmentImpl) object; + return duration.equals(other.duration); + } + + @Override + public String toString() { + return "DraftPunishmentImpl [duration=" + duration + ", toString()=" + super.toString() + "]"; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java new file mode 100644 index 000000000..9efc82b68 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enactor.java @@ -0,0 +1,116 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.punish; + +import java.time.Duration; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.DraftPunishment; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.core.database.Database; + +import space.arim.jdbcaesar.mapper.UpdateCountMapper; + +class Enactor extends EnforcementCenterMember { + + Enactor(EnforcementCenter center) { + super(center); + } + + CentralisedFuture enactPunishment(DraftPunishment draftPunishment) { + Database database = core().getDatabase(); + return database.selectAsync(() -> { + + final PunishmentType type = draftPunishment.getType(); + final Victim victim = draftPunishment.getVictim(); + final Operator operator = draftPunishment.getOperator(); + final String reason = draftPunishment.getReason(); + final ServerScope scope = draftPunishment.getScope(); + final Duration duration = draftPunishment.getDuration(); + final long start = MiscUtil.currentTime(); + final long end = (duration.isZero()) ? 0L : start + duration.toSeconds(); + MiscUtil.checkRange(start, end); + + return database.jdbCaesar().transaction().body((querySource, controller) -> { + + if (type != PunishmentType.KICK) { + database.clearExpiredPunishments(querySource, type, MiscUtil.currentTime()); + } + + if (database.getVendor().useStoredRoutines()) { + String enactmentProcedure = MiscUtil.getEnactmentProcedure(type); + + return querySource.query( + "{CALL `libertybans_" + enactmentProcedure + "` (?, ?, ?, ?, ?, ?, ?)}") + .params(victim, victim.getType().ordinal() + 1, operator, reason, + scope, start, end) + + .singleResult((resultSet) -> { + int id = resultSet.getInt("id"); + return center.createPunishment(id, type, victim, operator, reason, scope, start, end); + + }).execute(); + } else { + int id = querySource.query( + "INSERT INTO `libertybans_punishments` (`type`, `operator`, `reason`, `scope`, `start`, `end`) " + + "VALUES (?, ?, ?, ?, ?, ?)") + .params(type, operator, draftPunishment.getReason(), scope, start, end) + .updateGenKeys((updateCount, genKeys) -> { + if (!genKeys.next()) { + throw new IllegalStateException("No punishment ID generated for insertion query"); + } + return genKeys.getInt("id"); + }).execute(); + + if (type != PunishmentType.KICK) { // Kicks are pure history, so they jump straight to the end + + String enactStatement = "%INSERT% INTO `libertybans_" + type.getLowercaseNamePlural() + "` " + + "(`id`, `victim`, `victim_type`) VALUES (?, ?, ?)"; + Object[] enactArgs = new Object[] {id, victim, victim.getType()}; + + if (type.isSingular()) { + enactStatement = enactStatement.replace("%INSERT%", "INSERT IGNORE"); + int updateCount = querySource.query(enactStatement).params(enactArgs) + .updateCount(UpdateCountMapper.identity()).execute(); + if (updateCount == 0) { + controller.rollback(); + return null; + } + } else { + enactStatement = enactStatement.replace("%INSERT%", "INSERT"); + querySource.query(enactStatement).params(enactArgs).voidResult().execute(); + } + } + + querySource.query( + "INSERT INTO `libertybans_history` (`id`, `victim`, `victim_type`) VALUES (?, ?, ?)") + .params(id, victim, victim.getType()) + .voidResult().execute(); + return center.createPunishment(id, type, victim, operator, reason,scope, start, end); + } + }).onError(() -> null).execute(); + }); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenter.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenter.java new file mode 100644 index 000000000..db488942f --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenter.java @@ -0,0 +1,112 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.punish; + +import java.net.InetAddress; +import java.time.Instant; +import java.util.UUID; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import space.arim.api.chat.SendableMessage; + +import space.arim.libertybans.api.NetworkAddress; +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.DraftPunishmentBuilder; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.punish.PunishmentDrafter; +import space.arim.libertybans.core.LibertyBansCore; + +public class EnforcementCenter implements PunishmentDrafter { + + private final LibertyBansCore core; + + private final Enactor enactor; + private final Enforcer enforcer; + private final Revoker revoker; + + public EnforcementCenter(LibertyBansCore core) { + this.core = core; + enactor = new Enactor(this); + enforcer = new Enforcer(this); + revoker = new Revoker(this); + } + + public Punishment createPunishment(int id, PunishmentType type, Victim victim, Operator operator, String reason, + ServerScope scope, long start, long end) { + Instant startDate = Instant.ofEpochSecond(start); + Instant endDate = (end == 0L) ? Instant.MAX : Instant.ofEpochSecond(end); + return new SecurePunishment(this, id, type, victim, operator, reason, scope, startDate, endDate); + } + + LibertyBansCore core() { + return core; + } + + Enactor getEnactor() { + return enactor; + } + + Enforcer getEnforcer() { + return enforcer; + } + + public Revoker getRevoker() { + return revoker; + } + + /** + * Enforces an incoming connection, returning a punishment message if denied, null if allowed.
+ *
+ * Adds the UUID and name to the local fast cache, queries for an applicable ban, and formats the + * ban reason as the punishment message. + * + * @param uuid the player's UUID + * @param name the player's name + * @param address the player's network address + * @return a future which yields the punishment message if denied, else null if allowed + */ + public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, InetAddress address) { + return getEnforcer().executeAndCheckConnection(uuid, name, NetworkAddress.of(address)); + } + + /** + * Enforces a chat message or executed command, returning a punishment message if denied, null if allowed.
+ *
+ * If this corresponds to an executed command, the configured commands whose access to muted players to block + * are taken into account. + * + * @param uuid the player's UUID + * @param address the player's network address + * @param command the command executed, or {@code null} if this is a chat message + * @return a future which yields the punishment message if denied, else null if allowed + */ + public CentralisedFuture checkChat(UUID uuid, InetAddress address, String command) { + return getEnforcer().checkChat(uuid, NetworkAddress.of(address), command); + } + + @Override + public DraftPunishmentBuilder draftBuilder() { + return new DraftPunishmentBuilderImpl(this); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenterMember.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenterMember.java new file mode 100644 index 000000000..30b568737 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/EnforcementCenterMember.java @@ -0,0 +1,35 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.punish; + +import space.arim.libertybans.core.LibertyBansCore; + +class EnforcementCenterMember { + + final EnforcementCenter center; + + EnforcementCenterMember(EnforcementCenter center) { + this.center = center; + } + + LibertyBansCore core() { + return center.core(); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/Enforcer.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enforcer.java similarity index 57% rename from bans-core/src/main/java/space/arim/libertybans/core/Enforcer.java rename to bans-core/src/main/java/space/arim/libertybans/core/punish/Enforcer.java index c56182f6a..ee28df6e1 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/Enforcer.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Enforcer.java @@ -16,7 +16,7 @@ * along with LibertyBans-core. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.core; +package space.arim.libertybans.core.punish; import java.util.Set; import java.util.UUID; @@ -28,52 +28,40 @@ import space.arim.api.chat.SendableMessage; import space.arim.api.env.PlatformHandle; +import space.arim.api.env.annote.PlatformPlayer; import space.arim.libertybans.api.AddressVictim; import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PlayerVictim; -import space.arim.libertybans.api.Punishment; -import space.arim.libertybans.api.PunishmentEnforcer; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.core.config.AddressStrictness; +import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.core.database.Database; import space.arim.libertybans.core.env.EnvEnforcer; import space.arim.libertybans.core.env.TargetMatcher; +import space.arim.libertybans.core.selector.AddressStrictness; -public class Enforcer implements PunishmentEnforcer { +class Enforcer extends EnforcementCenterMember { - private final LibertyBansCore core; - - Enforcer(LibertyBansCore core) { - this.core = core; + Enforcer(EnforcementCenter center) { + super(center); } - - @Override - public CentralisedFuture enforce(Punishment punishment) { - MiscUtil.validate(punishment); - CentralisedFuture futureMessage = core.getFormatter().getPunishmentMessage(punishment); + + CentralisedFuture enforce(Punishment punishment) { + CentralisedFuture futureMessage = core().getFormatter().getPunishmentMessage(punishment); switch (punishment.getVictim().getType()) { case PLAYER: UUID uuid = ((PlayerVictim) punishment.getVictim()).getUUID(); CentralisedFuture enforceFuture = futureMessage.thenAccept((message) -> { - core.getEnvironment().getEnforcer().doForPlayerIfOnline(uuid, enforcementCallback(punishment, message)); + core().getEnvironment().getEnforcer().doForPlayerIfOnline(uuid, enforcementCallback(punishment, message)); }); return enforceFuture; - case ADDRESS: return futureMessage.thenCompose((message) -> enforceAddressPunishment(punishment, message)); default: - throw new IllegalStateException("Unknown victim type " + punishment.getVictim().getType()); + throw MiscUtil.unknownVictimType(punishment.getVictim().getType()); } } - @Override - public CentralisedFuture unenforce(Punishment punishment) { - MiscUtil.validate(punishment); - core.getMuteCacher().clearCachedMute(punishment); - return core.getFuturesFactory().completedFuture(null); - } - private static boolean shouldKick(PunishmentType type) { switch (type) { case BAN: @@ -83,16 +71,16 @@ private static boolean shouldKick(PunishmentType type) { case WARN: return false; default: - throw new IllegalStateException("Unknown punishment type " + type); + throw MiscUtil.unknownType(type); } } - private Consumer enforcementCallback(Punishment punishment, SendableMessage message) { + private Consumer<@PlatformPlayer Object> enforcementCallback(Punishment punishment, SendableMessage message) { PunishmentType type = punishment.getType(); boolean shouldKick = shouldKick(type); return (playerObj) -> { - PlatformHandle handle = core.getEnvironment().getPlatformHandle(); + PlatformHandle handle = core().getEnvironment().getPlatformHandle(); if (shouldKick) { handle.disconnectUser(playerObj, message); } else { @@ -102,9 +90,10 @@ private Consumer enforcementCallback(Punishment punishment, SendableMess * Mute enforcement must additionally take into account the mute cache */ if (type == PunishmentType.MUTE) { - EnvEnforcer envEnforcer = core.getEnvironment().getEnforcer(); - core.getMuteCacher().setCachedMute(envEnforcer.getUniqueIdFor(playerObj), - envEnforcer.getAddressFor(playerObj), punishment); + EnvEnforcer envEnforcer = core().getEnvironment().getEnforcer(); + UUID uuid = envEnforcer.getUniqueIdFor(playerObj); + NetworkAddress address = NetworkAddress.of(envEnforcer.getAddressFor(playerObj)); + core().getMuteCacher().setCachedMute(uuid, address, punishment); } } }; @@ -113,11 +102,12 @@ private Consumer enforcementCallback(Punishment punishment, SendableMess private CentralisedFuture enforceAddressPunishment(Punishment punishment, SendableMessage message) { NetworkAddress address = ((AddressVictim) punishment.getVictim()).getAddress(); CentralisedFuture futureMatcher; - AddressStrictness strictness = core.getConfigs().getAddressStrictness(); + AddressStrictness strictness = core().getMainConfig().enforcement().addressStrictness(); switch (strictness) { case LENIENT: - var matcher = new TargetMatcher(Set.of(), Set.of(address.toInetAddress()), enforcementCallback(punishment, message)); - futureMatcher = core.getFuturesFactory().completedFuture(matcher); + Consumer<@PlatformPlayer Object> callback = enforcementCallback(punishment, message); + TargetMatcher matcher = new TargetMatcher(Set.of(), Set.of(address.toInetAddress()), callback); + futureMatcher = completedFuture(matcher); break; case NORMAL: futureMatcher = matchAddressPunishmentNormal(address, punishment, message); @@ -126,14 +116,14 @@ private CentralisedFuture enforceAddressPunishment(Punishment punishment, Sen futureMatcher = matchAddressPunishmentStrict(address, punishment, message); break; default: - throw new IllegalStateException("Unknown address strictness " + strictness); + throw MiscUtil.unknownAddressStrictness(strictness); } - return futureMatcher.thenAccept(core.getEnvironment().getEnforcer()::enforceMatcher); + return futureMatcher.thenAccept(core().getEnvironment().getEnforcer()::enforceMatcher); } private CentralisedFuture matchAddressPunishmentNormal(NetworkAddress address, Punishment punishment, SendableMessage message) { - Database database = core.getDatabase(); + Database database = core().getDatabase(); return database.selectAsync(() -> { Set uuids = database.jdbCaesar().query( "SELECT `uuid` FROM `libertybans_addresses` WHERE `address` = ?") @@ -144,11 +134,12 @@ private CentralisedFuture matchAddressPunishmentNormal(NetworkAdd }); } - private CentralisedFuture matchAddressPunishmentStrict(NetworkAddress address, Punishment punishment, SendableMessage message) { - Database database = core.getDatabase(); + private CentralisedFuture matchAddressPunishmentStrict(NetworkAddress address, Punishment punishment, + SendableMessage message) { + Database database = core().getDatabase(); return database.selectAsync(() -> { Set uuids = database.jdbCaesar().query( - "SELECT `addrs`.`uuid` FROM `libertybans_addresses` `addrs` INNER JOIN `libertybans_address` `addrsAlso` " + "SELECT `addrs`.`uuid` FROM `libertybans_addresses` `addrs` INNER JOIN `libertybans_addresses` `addrsAlso` " + "ON `addrs`.`address` = `addrsAlso`.`address` WHERE `addrsAlso`.`uuid` = ?") .params(address) .setResult((resultSet) -> UUIDUtil.fromByteArray(resultSet.getBytes("uuid"))) @@ -157,47 +148,29 @@ private CentralisedFuture matchAddressPunishmentStrict(NetworkAdd }); } - /** - * Enforces an incoming connection, returning a punishment message if denied, null if allowed.
- *
- * Adds the UUID and name to the local fast cache, queries for an applicable ban, and formats the - * ban reason as the punishment message. - * - * @param uuid the player's UUID - * @param name the player's name - * @param address the player's network address - * @return a future which yields the punishment message if denied, else null if allowed - */ - public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, byte[] address) { - core.getUUIDMaster().addCache(uuid, name); - return core.getSelector().executeAndCheckConnection(uuid, name, address).thenCompose((ban) -> { + private CentralisedFuture completedFuture(T value) { + return core().getFuturesFactory().completedFuture(value); + } + + CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address) { + core().getUUIDMaster().addCache(uuid, name); + return core().getSelector().executeAndCheckConnection(uuid, name, address).thenCompose((ban) -> { if (ban == null) { - return core.getFuturesFactory().completedFuture(null); + return completedFuture(null); } - return core.getFormatter().getPunishmentMessage(ban); + return core().getFormatter().getPunishmentMessage(ban); }); } - /** - * Enforces a chat message or executed command, returning a punishment message if denied, null if allowed.
- *
- * If this corresponds to an executed command, the configured commands whose access to muted players to block - * are taken into account. - * - * @param uuid the player's UUID - * @param address the player's network address - * @param command the command executed, or {@code null} if this is a chat message - * @return a future which yields the punishment message if denied, else null if allowed - */ - public CentralisedFuture checkChat(UUID uuid, byte[] address, String command) { + CentralisedFuture checkChat(UUID uuid, NetworkAddress address, String command) { if (command != null && !blockForMuted(command)) { - return core.getFuturesFactory().completedFuture(null); + return completedFuture(null); } - return core.getMuteCacher().getCachedMute(uuid, address).thenCompose((mute) -> { + return core().getMuteCacher().getCachedMute(uuid, address).thenCompose((mute) -> { if (mute == null) { - return core.getFuturesFactory().completedFuture(null); + return completedFuture(null); } - return core.getFormatter().getPunishmentMessage(mute); + return core().getFormatter().getPunishmentMessage(mute); }); } @@ -207,7 +180,7 @@ private boolean blockForMuted(String command) { if (words[0].indexOf(':') != -1) { words[0] = words[0].split(":", 2)[1]; } - for (String muteCommand : core.getConfigs().getConfig().getStringList("enforcement.mute-commands")) { + for (String muteCommand : core().getMainConfig().enforcement().muteCommands()) { if (muteCommandMatches(words, muteCommand)) { return true; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/MiscUtil.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/MiscUtil.java similarity index 50% rename from bans-core/src/main/java/space/arim/libertybans/core/MiscUtil.java rename to bans-core/src/main/java/space/arim/libertybans/core/punish/MiscUtil.java index 6e7391af5..ee11ff7f7 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/MiscUtil.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/MiscUtil.java @@ -16,36 +16,39 @@ * along with LibertyBans-api. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.core; +package space.arim.libertybans.core.punish; -import java.util.Arrays; +import java.util.List; import java.util.Objects; -import space.arim.libertybans.api.DraftPunishment; -import space.arim.libertybans.api.Punishment; +import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.core.database.Vendor; +import space.arim.libertybans.core.selector.AddressStrictness; +import space.arim.libertybans.core.selector.SyncEnforcement; public final class MiscUtil { /** - * The biggest value can be stored in a 32bit unsigned integer + * The biggest value which can be stored in a 32bit unsigned integer */ private static final long INT_UNSIGNED_MAX_VALUE = ((long) Integer.MAX_VALUE) - ((long) Integer.MIN_VALUE); - private static final PunishmentType[] PUNISHMENT_TYPES = PunishmentType.values(); + private static final List PUNISHMENT_TYPES = List.of(PunishmentType.values()); - private static final PunishmentType[] PUNISHMENT_TYPES_EXCLUDING_KICK = Arrays.stream(PUNISHMENT_TYPES) + private static final PunishmentType[] PUNISHMENT_TYPES_EXCLUDING_KICK = PUNISHMENT_TYPES.stream() .filter((type) -> type != PunishmentType.KICK).toArray(PunishmentType[]::new); private MiscUtil() {} /** - * Gets a cached PunishmentType array based on {@link PunishmentType#values()}.
- * DO NOT MUTATE the result + * Gets an immutable list of PunishmentTypes based on {@link PunishmentType#values()} * - * @return the cached result of {@link PunishmentType#values()} + * @return an immutable list of {@link PunishmentType#values()} */ - public static PunishmentType[] punishmentTypes() { + public static List punishmentTypes() { return PUNISHMENT_TYPES; } @@ -90,36 +93,32 @@ public static long currentTime() { } /** - * Validates a Punishment is an own implementation + * Validates that start and ends values are within range of a 32bit unsigned integer. * - * @param punishment the punishment to validate - * @throws NullPointerException if null - * @throws IllegalArgumentException if a foreign implementation + * @param start the start time + * @param end the end time + * @throws IllegalArgumentException if either the start or end value would not fit into an INT UNSIGNED */ - static void validate(Punishment punishment) { - if (punishment == null) { - throw new NullPointerException("punishment"); - - } else if (!(punishment instanceof SecurePunishment)) { - throw new IllegalArgumentException("Foreign implementation " + punishment.getClass()); + static void checkRange(long start, long end) { + if (start > INT_UNSIGNED_MAX_VALUE) { + throw new IllegalArgumentException("Start time is after 2106. start=" + start); + } + if (end > INT_UNSIGNED_MAX_VALUE) { + throw new IllegalArgumentException("End time is after 2106. end=" + end); } } /** - * Validates that start and ends values are within range of a 32bit unsigned integer. + * Checks that a server scope is nonnull and of the right implementation class * - * @param draftPunishment the draft punishment - * @throws NullPointerException if {@code draftPunishment} is null - * @throws IllegalArgumentException if either the start or end value would not fit into an INT UNSIGNED + * @param scope the server scope + * @return the same scope, for convenience + * @throws NullPointerException if {@code scope} is null + * @throws IllegalArgumentException if {@code scope} is a foreign implementation */ - static void validate(DraftPunishment draftPunishment) { - Objects.requireNonNull(draftPunishment, "draftPunishment"); - if (draftPunishment.getStart() > INT_UNSIGNED_MAX_VALUE) { - throw new IllegalArgumentException("DraftPunishment starts after 2106! start=" + draftPunishment.getStart()); - } - if (draftPunishment.getEnd() > INT_UNSIGNED_MAX_VALUE) { - throw new IllegalArgumentException("DraftPunishment ends after 2106! end=" + draftPunishment.getEnd()); - } + static ServerScope checkScope(ServerScope scope) { + Scoper.checkScope(scope); + return scope; } /** @@ -141,8 +140,69 @@ public static String getEnactmentProcedure(PunishmentType type) { * @param end the end time * @return true if expired, false otherwise */ - public static boolean isExpired(long currentTime, long end) { + static boolean isExpired(long currentTime, long end) { return end != 0 && end < currentTime; } + /** + * Checks that {@link PunishmentType#isSingular()} is true + * + * @param type the punishment type + * @return the same punishment type, for convenience + * @throws NullPointerException if {@code type} is null + * @throws IllegalArgumentException if {@code type} is not singular + */ + static PunishmentType checkSingular(PunishmentType type) { + Objects.requireNonNull(type, "type"); + if (!type.isSingular()) { + throw new IllegalArgumentException("The punishment type " + type + " is not singular"); + } + return type; + } + + /* + * Create exceptions indicating an enum entry was not identified, typically in a switch statement + */ + + public static RuntimeException unknownType(PunishmentType type) { + return unknownEnumEntry(type); + } + + public static RuntimeException unknownVictimType(Victim.VictimType victimType) { + return unknownEnumEntry(victimType); + } + + public static RuntimeException unknownOperatorType(Operator.OperatorType operatorType) { + return unknownEnumEntry(operatorType); + } + + public static RuntimeException unknownAddressStrictness(AddressStrictness strictness) { + return unknownEnumEntry(strictness); + } + + public static RuntimeException unknownSyncEnforcement(SyncEnforcement strategy) { + return unknownEnumEntry(strategy); + } + + public static RuntimeException unknownVendor(Vendor vendor) { + return unknownEnumEntry(vendor); + } + + static UnknownEnumEntryException unknownEnumEntry(Enum value) { + return new UnknownEnumEntryException(value); + } + + private static class UnknownEnumEntryException extends RuntimeException { + + /** + * Serial version uid + */ + private static final long serialVersionUID = 5616757616849475684L; + + UnknownEnumEntryException(Enum value) { + super("Unknown enum entry " + value.name() + " in " + value.getDeclaringClass().getName()); + } + + } + } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/RevocationOrderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/RevocationOrderImpl.java new file mode 100644 index 000000000..0bb419edb --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/RevocationOrderImpl.java @@ -0,0 +1,190 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.punish; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.revoke.RevocationOrder; +import space.arim.libertybans.core.selector.MuteCacher; + +class RevocationOrderImpl implements RevocationOrder { + + private transient final EnforcementCenter center; + + private final int id; + private final PunishmentType type; + private final Victim victim; + + private RevocationOrderImpl(EnforcementCenter center, int id, PunishmentType type, Victim victim) { + this.center = center; + + this.id = id; + this.type = type; + this.victim = victim; + } + + RevocationOrderImpl(EnforcementCenter center, int id) { + this(center, id, null, null); + assert getApproach() == Approach.ID; + } + + RevocationOrderImpl(EnforcementCenter center, int id, PunishmentType type) { + this(center, id, Objects.requireNonNull(type, "type"), null); + assert getApproach() == Approach.ID_TYPE; + } + + RevocationOrderImpl(EnforcementCenter center, PunishmentType type, Victim victim) { + this(center, -1, MiscUtil.checkSingular(type), Objects.requireNonNull(victim, "victim")); + assert getApproach() == Approach.TYPE_VICTIM; + } + + private enum Approach { + ID, + ID_TYPE, + TYPE_VICTIM, + } + + private Approach getApproach() { + if (type == null) { + return Approach.ID; + } + if (victim != null) { + return Approach.TYPE_VICTIM; + } + return Approach.ID_TYPE; + } + + @Override + public Integer getID() { + return id; + } + + @Override + public PunishmentType getType() { + return type; + } + + @Override + public Victim getVictim() { + return victim; + } + + @Override + public CentralisedFuture undoPunishment() { + return undoPunishmentWithoutUnenforcement().thenApply((revoked) -> { + if (revoked) { + MuteCacher muteCacher = center.core().getMuteCacher(); + switch (getApproach()) { + case ID: + muteCacher.clearCachedMute(id); + break; + case ID_TYPE: + if (type == PunishmentType.MUTE) { + muteCacher.clearCachedMute(id); + } + break; + case TYPE_VICTIM: + if (type == PunishmentType.MUTE) { + muteCacher.clearCachedMute(victim); + } + break; + default: + throw MiscUtil.unknownEnumEntry(getApproach()); + } + } + return revoked; + }); + } + + @Override + public CentralisedFuture undoPunishmentWithoutUnenforcement() { + Revoker revoker = center.getRevoker(); + switch (getApproach()) { + case ID: + return revoker.undoPunishmentById(id); + case ID_TYPE: + return revoker.undoPunishmentByIdAndType(id, type); + case TYPE_VICTIM: + return revoker.undoPunishmentByTypeAndVictim(type, victim); + default: + throw MiscUtil.unknownEnumEntry(getApproach()); + } + } + + @Override + public CentralisedFuture undoAndGetPunishment() { + return undoAndGetPunishmentWithoutUnenforcement().thenCompose((punishment) -> { + if (punishment == null) { + return CompletableFuture.completedStage(null); + } + return punishment.unenforcePunishment().thenApply((ignore) -> punishment); + }); + } + + @Override + public CentralisedFuture undoAndGetPunishmentWithoutUnenforcement() { + Revoker revoker = center.getRevoker(); + switch (getApproach()) { + case ID: + return revoker.undoAndGetPunishmentById(id); + case ID_TYPE: + return revoker.undoAndGetPunishmentByIdAndType(id, type); + case TYPE_VICTIM: + return revoker.undoAndGetPunishmentByTypeAndVictim(type, victim); + default: + throw MiscUtil.unknownEnumEntry(getApproach()); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + result = prime * result + Objects.hashCode(type); + result = prime * result + Objects.hashCode(victim); + return result; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof RevocationOrderImpl)) { + return false; + } + RevocationOrderImpl other = (RevocationOrderImpl) object; + return id == other.id + && type == other.type + && Objects.equals(victim, other.victim); + } + + @Override + public String toString() { + return "RevocationOrderImpl [id=" + id + ", type=" + type + ", victim=" + victim + "]"; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/Enactor.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java similarity index 57% rename from bans-core/src/main/java/space/arim/libertybans/core/Enactor.java rename to bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java index 8639e80c6..56817efc3 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/Enactor.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Revoker.java @@ -16,9 +16,7 @@ * along with LibertyBans-core. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.core; - -import java.util.Objects; +package space.arim.libertybans.core.punish; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,130 +24,64 @@ import space.arim.omnibus.util.ThisClass; import space.arim.omnibus.util.concurrent.CentralisedFuture; -import space.arim.libertybans.api.DraftPunishment; -import space.arim.libertybans.api.Operator; -import space.arim.libertybans.api.Punishment; -import space.arim.libertybans.api.PunishmentEnactor; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Scope; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.revoke.PunishmentRevoker; +import space.arim.libertybans.api.revoke.RevocationOrder; import space.arim.libertybans.core.database.Database; -import space.arim.jdbcaesar.mapper.UpdateCountMapper; import space.arim.jdbcaesar.query.ResultSetConcurrency; -public class Enactor implements PunishmentEnactor { - - private final LibertyBansCore core; +class Revoker extends EnforcementCenterMember implements PunishmentRevoker { private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); - Enactor(LibertyBansCore core) { - this.core = core; - } - - @Override - public CentralisedFuture enactPunishment(DraftPunishment draftPunishment) { - MiscUtil.validate(draftPunishment); - Database database = core.getDatabase(); - return database.selectAsync(() -> { - - PunishmentType type = draftPunishment.getType(); - Victim victim = draftPunishment.getVictim(); - Operator operator = draftPunishment.getOperator(); - String reason = draftPunishment.getReason(); - Scope scope = draftPunishment.getScope(); - - if (database.getVendor().useStoredRoutines()) { - String enactmentProcedure = MiscUtil.getEnactmentProcedure(type); - - return database.jdbCaesar().query( - "{CALL `libertybans_" + enactmentProcedure + "` (?, ?, ?, ?, ?, ?, ?)}") - .params(victim, victim.getType().ordinal() + 1, operator, reason, - scope, draftPunishment.getStart(), draftPunishment.getEnd()) - - .singleResult((resultSet) -> { - int id = resultSet.getInt("id"); - return new SecurePunishment(id, type, victim, operator, reason, - scope, draftPunishment.getStart(), draftPunishment.getEnd()); - }).onError(() -> null).execute(); - } else { - return database.jdbCaesar().transaction().body((querySource, controller) -> { - - int id = querySource.query( - "INSERT INTO `libertybans_punishments` (`type`, `operator`, `reason`, `scope`, `start`, `end`) " - + "VALUES (?, ?, ?, ?, ?, ?)") - .params(type, operator, draftPunishment.getReason(), scope, - draftPunishment.getStart(), draftPunishment.getEnd()) - .updateGenKeys((updateCount, genKeys) -> { - if (!genKeys.next()) { - throw new IllegalStateException("No punishment ID generated for insertion query"); - } - return genKeys.getInt("id"); - }).execute(); - - String enactStatement = "%INSERT% INTO `libertybans_" + type.getLowercaseNamePlural() + "` " - + "(`id`, `victim`, `victim_type`) VALUES (?, ?, ?)"; - Object[] enactArgs = new Object[] {id, victim, victim.getType()}; - - if (type.isSingular()) { - enactStatement = enactStatement.replace("%INSERT%", "INSERT IGNORE"); - int updateCount = querySource.query(enactStatement).params(enactArgs) - .updateCount(UpdateCountMapper.identity()).execute(); - if (updateCount == 0) { - controller.rollback(); - return null; - } - } else if (type != PunishmentType.KICK) { - enactStatement = enactStatement.replace("%INSERT%", "INSERT"); - querySource.query(enactStatement).params(enactArgs).voidResult().execute(); - } - - querySource.query( - "INSERT INTO `libertybans_history` (`id`, `victim`, `victim_type`) VALUES (?, ?, ?)") - .params(id, victim, victim.getType()) - .voidResult().execute(); - return new SecurePunishment(id, type, victim, operator, reason, - scope, draftPunishment.getStart(), draftPunishment.getEnd()); - }).onError(() -> null).execute(); - } - }); + Revoker(EnforcementCenter center) { + super(center); } - @Override - public CentralisedFuture undoPunishment(final Punishment punishment) { - MiscUtil.validate(punishment); - PunishmentType type = punishment.getType(); - if (type == PunishmentType.KICK) { - // Kicks are never active - return core.getFuturesFactory().completedFuture(false); - } - final long currentTime = MiscUtil.currentTime(); - if (MiscUtil.isExpired(currentTime, punishment.getEnd())) { + CentralisedFuture undoPunishment(final Punishment punishment) { + if (punishment.isExpired()) { // Already expired - return core.getFuturesFactory().completedFuture(false); + return core().getFuturesFactory().completedFuture(false); } - Database database = core.getDatabase(); - return database.selectAsync(() -> { - - return database.jdbCaesar().query( - "DELETE FROM `libertybans_" + type.getLowercaseNamePlural() + "` WHERE `id` = ?") - .params(punishment.getID()) - .updateCount((updateCount) -> updateCount == 1) - .onError(() -> false).execute(); - }); + return undoPunishmentByIdAndType(punishment.getID(), punishment.getType()); } @Override - public CentralisedFuture undoPunishmentByIdAndType(final int id, final PunishmentType type) { - Objects.requireNonNull(type, "type"); + public RevocationOrder revokeByIdAndType(int id, PunishmentType type) { + return new RevocationOrderImpl(center, id, type); + } + + @Override + public RevocationOrder revokeById(int id) { + return new RevocationOrderImpl(center, id); + } + + @Override + public RevocationOrder revokeByTypeAndVictim(PunishmentType type, Victim victim) { + return new RevocationOrderImpl(center, type, victim); + } + + CentralisedFuture undoPunishmentByIdAndType(final int id, final PunishmentType type) { if (type == PunishmentType.KICK) { // Kicks are never active - return core.getFuturesFactory().completedFuture(false); + return core().getFuturesFactory().completedFuture(false); } - Database database = core.getDatabase(); + Database database = core().getDatabase(); return database.selectAsync(() -> { final long currentTime = MiscUtil.currentTime(); + + if (database.getVendor().hasDeleteFromJoin()) { + return database.jdbCaesar().query( + "DELETE `thetype` FROM `libertybans_" + type.getLowercaseNamePlural() + "` `thetype` " + + "INNER JOIN `libertybans_punishments` `puns` ON `thetype`.`id` = `puns`.`id` " + + "WHERE `thetype`.`id` = ? AND (`puns`.`end` = 0 OR `puns`.`end` > ?)") + .params(id, currentTime) + .updateCount((updateCount) -> updateCount == 1) + .execute(); + } return database.jdbCaesar().transaction().body((querySource, controller) -> { boolean deleted = querySource.query( @@ -173,14 +105,12 @@ public CentralisedFuture undoPunishmentByIdAndType(final int id, final }); } - @Override - public CentralisedFuture undoAndGetPunishmentByIdAndType(final int id, final PunishmentType type) { - Objects.requireNonNull(type, "type"); + CentralisedFuture undoAndGetPunishmentByIdAndType(final int id, final PunishmentType type) { if (type == PunishmentType.KICK) { // Kicks are never active - return core.getFuturesFactory().completedFuture(null); + return core().getFuturesFactory().completedFuture(null); } - Database database = core.getDatabase(); + Database database = core().getDatabase(); return database.selectAsync(() -> { final long currentTime = MiscUtil.currentTime(); return database.jdbCaesar().transaction().body((querySource, controller) -> { @@ -209,7 +139,7 @@ public CentralisedFuture undoAndGetPunishmentByIdAndType(final int i + "WHERE `id` = ? AND (`end` = 0 OR `end` > ?)") .params(id, currentTime) .singleResult((resultSet) -> { - return new SecurePunishment(id, type, victim, database.getOperatorFromResult(resultSet), + return center.createPunishment(id, type, victim, database.getOperatorFromResult(resultSet), database.getReasonFromResult(resultSet), database.getScopeFromResult(resultSet), database.getStartFromResult(resultSet), database.getEndFromResult(resultSet)); }).execute(); @@ -219,29 +149,43 @@ public CentralisedFuture undoAndGetPunishmentByIdAndType(final int i }); } - @Override - public CentralisedFuture undoPunishmentById(final int id) { - Database database = core.getDatabase(); + CentralisedFuture undoPunishmentById(final int id) { + Database database = core().getDatabase(); return database.selectAsync(() -> { final long currentTime = MiscUtil.currentTime(); + return database.jdbCaesar().transaction().body((querySource, controller) -> { + boolean hasDeleteFromJoin = database.getVendor().hasDeleteFromJoin(); for (PunishmentType type : MiscUtil.punishmentTypesExcludingKick()) { - boolean deleted = querySource.query( - "DELETE FROM `libertybans_" + type.getLowercaseNamePlural() + "` WHERE `id` = ?") - .params(id) - .updateCount((updateCount) -> updateCount == 1) - .execute(); - logger.trace("deleted={} in undoPunishmentById", deleted); - if (deleted) { - long end = querySource.query( - "SELECT `end` FROM `libertybans_punishments` WHERE `id` = ?") + if (hasDeleteFromJoin) { + boolean deleted = querySource.query( + "DELETE `thetype` FROM `libertybans_" + type.getLowercaseNamePlural() + "` `thetype` " + + "INNER JOIN `libertybans_punishments` `puns` ON `thetype`.`id` = `puns`.`id` " + + "WHERE `thetype`.`id` = ? AND (`end` = 0 OR `end` > ?)") + .params(id, currentTime) + .updateCount((updateCount) -> updateCount == 1) + .execute(); + if (deleted) { + return true; + } + } else { + boolean deleted = querySource.query( + "DELETE FROM `libertybans_" + type.getLowercaseNamePlural() + "` WHERE `id` = ?") .params(id) - .singleResult(database::getEndFromResult).execute(); - boolean expired = MiscUtil.isExpired(currentTime, end); - logger.trace("expired={} in undoPunishmentById", expired); - return !expired; + .updateCount((updateCount) -> updateCount == 1) + .execute(); + logger.trace("deleted={} in undoPunishmentById", deleted); + if (deleted) { + long end = querySource.query( + "SELECT `end` FROM `libertybans_punishments` WHERE `id` = ?") + .params(id) + .singleResult(database::getEndFromResult).execute(); + boolean expired = MiscUtil.isExpired(currentTime, end); + logger.trace("expired={} in undoPunishmentById", expired); + return !expired; + } } } return false; @@ -249,9 +193,8 @@ public CentralisedFuture undoPunishmentById(final int id) { }); } - @Override - public CentralisedFuture undoAndGetPunishmentById(final int id) { - Database database = core.getDatabase(); + CentralisedFuture undoAndGetPunishmentById(final int id) { + Database database = core().getDatabase(); return database.selectAsync(() -> { return database.jdbCaesar().transaction().body((querySource, controller) -> { @@ -280,7 +223,7 @@ public CentralisedFuture undoAndGetPunishmentById(final int id) { + "WHERE `id` = ? AND (`end` = 0 OR `end` > ?)") .params(id, currentTime) .singleResult((resultSet) -> { - return new SecurePunishment(id, type, victim, database.getOperatorFromResult(resultSet), + return center.createPunishment(id, type, victim, database.getOperatorFromResult(resultSet), database.getReasonFromResult(resultSet), database.getScopeFromResult(resultSet), database.getStartFromResult(resultSet), database.getEndFromResult(resultSet)); }).execute(); @@ -293,24 +236,20 @@ public CentralisedFuture undoAndGetPunishmentById(final int id) { }); } - private static void checkSingular(PunishmentType type) { - Objects.requireNonNull(type, "type"); - if (!type.isSingular()) { - throw new IllegalArgumentException( - "undoPunishmentByTypeAndVictim may only be used for singular punishments, not " + type); - } - } - - @Override - public CentralisedFuture undoPunishmentByTypeAndVictim(final PunishmentType type, final Victim victim) { - Objects.requireNonNull(victim, "victim"); - checkSingular(type); - - Database database = core.getDatabase(); + CentralisedFuture undoPunishmentByTypeAndVictim(final PunishmentType type, final Victim victim) { + Database database = core().getDatabase(); return database.selectAsync(() -> { final long currentTime = MiscUtil.currentTime(); + if (database.getVendor().hasDeleteFromJoin()) { - + return database.jdbCaesar().query( + "DELETE `thetype` FROM `libertybans_" + type.getLowercaseNamePlural() + "` `thetype` " + + "INNER JOIN `libertybans_punishments` `puns` ON `thetype`.`id` = `puns`.`id` " + + "WHERE `thetype`.`victim` = ? AND `thetype`.`victim_type` = ? " + + "AND (`end` = 0 OR `end` > ?)") + .params(victim, victim.getType(), currentTime) + .updateCount((updateCount) -> updateCount == 1) + .execute(); } return database.jdbCaesar().transaction().body((querySource, controller) -> { @@ -342,12 +281,8 @@ public CentralisedFuture undoPunishmentByTypeAndVictim(final Punishment }); } - @Override - public CentralisedFuture undoAndGetPunishmentByTypeAndVictim(final PunishmentType type, final Victim victim) { - Objects.requireNonNull(victim, "victim"); - checkSingular(type); - - Database database = core.getDatabase(); + CentralisedFuture undoAndGetPunishmentByTypeAndVictim(final PunishmentType type, final Victim victim) { + Database database = core().getDatabase(); return database.selectAsync(() -> { final long currentTime = MiscUtil.currentTime(); return database.jdbCaesar().transaction().body((querySource, controller) -> { @@ -375,7 +310,7 @@ public CentralisedFuture undoAndGetPunishmentByTypeAndVictim(final P + "WHERE `id` = ? AND (`end` = 0 OR `end` > ?)") .params(id, currentTime) .singleResult((resultSet) -> { - return new SecurePunishment(id, type, + return center.createPunishment(id, type, victim, database.getOperatorFromResult(resultSet), database.getReasonFromResult(resultSet), database.getScopeFromResult(resultSet), database.getStartFromResult(resultSet), database.getEndFromResult(resultSet)); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/Scoper.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/Scoper.java similarity index 52% rename from bans-core/src/main/java/space/arim/libertybans/core/Scoper.java rename to bans-core/src/main/java/space/arim/libertybans/core/punish/Scoper.java index 58bd42439..11883f4cd 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/Scoper.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/Scoper.java @@ -16,37 +16,55 @@ * along with LibertyBans-core. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.core; +package space.arim.libertybans.core.punish; import java.util.Objects; -import space.arim.libertybans.api.Scope; -import space.arim.libertybans.api.ScopeManager; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.manager.ScopeManager; public class Scoper implements ScopeManager { + public Scoper() { + + } + @Override - public Scope specificScope(String server) { + public ServerScope specificScope(String server) { return ScopeImpl.of(server); } @Override - public Scope globalScope() { + public ServerScope globalScope() { return ScopeImpl.GLOBAL; } - public String getServer(Scope scope) { + @Override + public ServerScope currentServerScope() { + return ScopeImpl.GLOBAL; // TODO implement scopes + } + + public String getServer(ServerScope scope) { if (!(scope instanceof ScopeImpl)) { - throw new IllegalStateException("Foreign implementation of Scope: " + scope.getClass()); + throw new IllegalArgumentException("Foreign implementation of Scope: " + scope.getClass()); } return ((ScopeImpl) scope).server; } - public static class ScopeImpl implements Scope { + static void checkScope(ServerScope scope) { + if (scope == null) { + throw new NullPointerException("scope"); + } + if (!(scope instanceof ScopeImpl)) { + throw new IllegalArgumentException("Foreign implementation of scope " + scope.getClass()); + } + } + + private static class ScopeImpl implements ServerScope { final String server; - static final Scope GLOBAL = new ScopeImpl(null); + static final ServerScope GLOBAL = new ScopeImpl(null); private ScopeImpl(String server) { this.server = server; @@ -58,11 +76,12 @@ private ScopeImpl(String server) { * @param server the server name * @return a scope applying to the server */ - static Scope of(String server) { + static ServerScope of(String server) { + Objects.requireNonNull(server, "server"); if (server.length() > 32) { throw new IllegalArgumentException("Server must be 32 or less chars"); } - return new ScopeImpl(Objects.requireNonNull(server, "Server must not be null")); + return new ScopeImpl(server); } @Override @@ -70,9 +89,29 @@ public boolean appliesTo(String server) { return this.server == null || this.server.equals(server); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Objects.hashCode(server); + return result; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof ScopeImpl)) { + return false; + } + ScopeImpl other = (ScopeImpl) object; + return Objects.equals(server, other.server); + } + @Override public String toString() { - return server; + return (server == null) ? "global" : "server:" + server; } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishment.java b/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishment.java new file mode 100644 index 000000000..45a64d590 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/punish/SecurePunishment.java @@ -0,0 +1,108 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.punish; + +import java.time.Instant; +import java.util.concurrent.CompletableFuture; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.Punishment; + +class SecurePunishment extends AbstractPunishmentBase implements Punishment { + + private final int id; + private final Instant startDate; + private final Instant endDate; + + SecurePunishment(EnforcementCenter center, + int id, PunishmentType type, Victim victim, Operator operator, + String reason, ServerScope scope, Instant startDate, Instant endDate) { + super(center, type, victim, operator, reason, scope); + + this.id = id; + this.startDate = startDate; + this.endDate = endDate; + } + + @Override + public int getID() { + return id; + } + + @Override + public Instant getStartDate() { + return startDate; + } + + @Override + public Instant getEndDate() { + return endDate; + } + + @Override + public CentralisedFuture enforcePunishment() { + return center.getEnforcer().enforce(this); + } + + @Override + public CentralisedFuture undoPunishment() { + return undoPunishmentWithoutUnenforcement().thenCompose((undone) -> { + return (undone) ? + unenforcePunishment().thenApply((ignore) -> true) + : CompletableFuture.completedStage(false); + }); + } + + @Override + public CentralisedFuture undoPunishmentWithoutUnenforcement() { + return center.getRevoker().undoPunishment(this); + } + + @Override + public CentralisedFuture unenforcePunishment() { + if (getType() == PunishmentType.MUTE) { + center.core().getMuteCacher().clearCachedMute(this); + } + return center.core().getFuturesFactory().completedFuture(null); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object object) { + return this == object || object instanceof SecurePunishment && id == ((SecurePunishment) object).id; + } + + @Override + public String toString() { + return "SecurePunishment [id=" + id + "]"; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/AddressStrictness.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/AddressStrictness.java similarity index 96% rename from bans-core/src/main/java/space/arim/libertybans/core/config/AddressStrictness.java rename to bans-core/src/main/java/space/arim/libertybans/core/selector/AddressStrictness.java index b2d14d5fa..6a5193afa 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/AddressStrictness.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/AddressStrictness.java @@ -16,7 +16,7 @@ * along with LibertyBans-core. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.core.config; +package space.arim.libertybans.core.selector; /** * When an address based punishment is enforced, how should it be? diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/ApplicableImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/ApplicableImpl.java index 697c406fd..ec1e4c142 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/ApplicableImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/ApplicableImpl.java @@ -24,50 +24,52 @@ import space.arim.omnibus.util.concurrent.CentralisedFuture; -import space.arim.libertybans.api.Punishment; +import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.core.MiscUtil; -import space.arim.libertybans.core.SecurePunishment; -import space.arim.libertybans.core.config.AddressStrictness; +import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.core.database.Database; +import space.arim.libertybans.core.punish.MiscUtil; -class ApplicableImpl extends SelectorImplGroup { +class ApplicableImpl extends SelectorImplMember { ApplicableImpl(Selector selector) { super(selector); } - private Map.Entry getApplicabilityQuery(UUID uuid, byte[] address, PunishmentType type) { + private Map.Entry getApplicabilityQuery(UUID uuid, NetworkAddress address, PunishmentType type) { String statement; Object[] args; - AddressStrictness strictness = core().getConfigs().getAddressStrictness(); + long currentTime = MiscUtil.currentTime(); + AddressStrictness strictness = core().getMainConfig().enforcement().addressStrictness(); switch (strictness) { case LENIENT: statement = "SELECT `id`, `victim`, `victim_type`, `operator`, `reason`, `scope`, `start`, `end` " + "FROM `libertybans_simple_" + type.getLowercaseNamePlural() + "` " - + "WHERE `type` = ? AND ((`victim_type` = 'PLAYER' AND `victim` = ?) OR (`victim_type` = 'ADDRESS' AND `victim` = ?))"; - args = new Object[] {type, uuid, address}; + + "WHERE (`end` = 0 OR `end` > ?) AND `type` = ? AND ((`victim_type` = 'PLAYER' AND `victim` = ?) OR (`victim_type` = 'ADDRESS' AND `victim` = ?))"; + args = new Object[] {currentTime, type, uuid, address}; break; case NORMAL: statement = "SELECT `id`, `victim`, `victim_type`, `operator`, `reason`, `scope`, `start`, `end`, `uuid` " - + "FROM `libertybans_applicable_" + type.getLowercaseNamePlural() + "` WHERE `type` = ? AND `uuid` = ?"; - args = new Object[] {type, uuid}; + + "FROM `libertybans_applicable_" + type.getLowercaseNamePlural() + "` " + + "WHERE (`end` = 0 OR `end` > ?) AND `type` = ? AND `uuid` = ?"; + args = new Object[] {currentTime, type, uuid}; break; case STRICT: statement = "SELECT `appl`.`id`, `appl`.`victim`, `appl`.`victim_type`, `appl`.`operator`, `appl`.`reason`, " + "`appl`.`scope`, `appl`.`start`, `appl`.`end`, `appl`.`uuid`, `appl`.`address` FROM " + "`libertybans_applicable_" + type.getLowercaseNamePlural() + "` `appl` INNER JOIN `libertybans_addresses` `addrs` " - + "ON `appl`.`address` = `addrs`.`address` WHERE `appl`.`type` = ? AND `appl`.`address` = ?"; - args = new Object[] {type, address}; + + "ON `appl`.`address` = `addrs`.`address` " + + "WHERE (`appl`.`end` = 0 OR `appl`.`end` > ?) AND `appl`.`type` = ? AND `appl`.`address` = ?"; + args = new Object[] {currentTime, type, address}; break; default: - throw new IllegalStateException("Unknown AddressStrictness " + strictness); + throw MiscUtil.unknownAddressStrictness(strictness); } return Map.entry(statement, args); } - CentralisedFuture executeAndCheckConnection(UUID uuid, String name, byte[] address) { + CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address) { Database database = core().getDatabase(); return database.selectAsync(() -> { @@ -89,7 +91,7 @@ CentralisedFuture executeAndCheckConnection(UUID uuid, String name, query.getKey()) .params(query.getValue()) .singleResult((resultSet) -> { - return new SecurePunishment(resultSet.getInt("id"), PunishmentType.BAN, + return core().getEnforcementCenter().createPunishment(resultSet.getInt("id"), PunishmentType.BAN, database.getVictimFromResult(resultSet), database.getOperatorFromResult(resultSet), database.getReasonFromResult(resultSet), database.getScopeFromResult(resultSet), database.getStartFromResult(resultSet), database.getEndFromResult(resultSet)); @@ -99,7 +101,7 @@ CentralisedFuture executeAndCheckConnection(UUID uuid, String name, }); } - private CentralisedFuture getApplicablePunishment0(UUID uuid, byte[] address, PunishmentType type) { + private CentralisedFuture getApplicablePunishment0(UUID uuid, NetworkAddress address, PunishmentType type) { Database database = core().getDatabase(); return database.selectAsync(() -> { @@ -108,7 +110,7 @@ private CentralisedFuture getApplicablePunishment0(UUID uuid, byte[] query.getKey()) .params(query.getValue()) .singleResult((resultSet) -> { - return new SecurePunishment(resultSet.getInt("id"), type, + return core().getEnforcementCenter().createPunishment(resultSet.getInt("id"), type, database.getVictimFromResult(resultSet), database.getOperatorFromResult(resultSet), database.getReasonFromResult(resultSet), database.getScopeFromResult(resultSet), database.getStartFromResult(resultSet), database.getEndFromResult(resultSet)); @@ -116,16 +118,16 @@ private CentralisedFuture getApplicablePunishment0(UUID uuid, byte[] }); } - CentralisedFuture getApplicablePunishment(UUID uuid, byte[] address, PunishmentType type) { + CentralisedFuture getApplicablePunishment(UUID uuid, NetworkAddress address, PunishmentType type) { Objects.requireNonNull(type, "type"); if (type == PunishmentType.KICK) { // Kicks are never active return core().getFuturesFactory().completedFuture(null); } - return getApplicablePunishment0(uuid, address.clone(), type); + return getApplicablePunishment0(uuid, address, type); } - CentralisedFuture getApplicableMute(UUID uuid, byte[] address) { + CentralisedFuture getApplicableMute(UUID uuid, NetworkAddress address) { return getApplicablePunishment0(uuid, address, PunishmentType.MUTE); } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/EnforcementConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/EnforcementConfig.java new file mode 100644 index 000000000..b5036b648 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/EnforcementConfig.java @@ -0,0 +1,78 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.selector; + +import java.util.Set; + +import space.arim.dazzleconf.annote.ConfComments; +import space.arim.dazzleconf.annote.ConfKey; +import space.arim.dazzleconf.annote.ConfDefault.DefaultString; +import space.arim.dazzleconf.annote.ConfDefault.DefaultStrings; + +public interface EnforcementConfig { + + @ConfKey("address-strictness") + @ConfComments({"", + "How strict should IP-based punishments be?", + "Available options are LENIENT, NORMAL, and STRICT", + "", + "LENIENT - If the player's current address matches the punished address, the punishment applies to the player", + "NORMAL - If any of player's past addresses matches the punished address, the punishment applies to the player", + "STRICT - If any of player's past addresses match any related address linked by a common player,", + "the punishment applies to the player"}) + @DefaultString("NORMAL") + AddressStrictness addressStrictness(); // Sensitive name used in integration testing + + @ConfKey("mute-commands") + @ConfComments({"", + "A list of commands muted players will not be able to execute", + "", + "This list supports subcommands, which will be enforced if the executed command starts with the list entry.", + "Additionally, colons in commands, such as\"pluginname:cmd\", cannot be used to bypass this."}) + @DefaultStrings({ + "me", + "say", + "msg", + "reply", + "r", + "whisper", + "tell"}) + Set muteCommands(); + + @ConfKey("sync-events-strategy") + @ConfComments({"", + "Occasionally, LibertyBans may have to deal with a platform event synchronously", + "", + "Currently, this happens rarely, and only on Bukkit, with the enforcement of mutes.", + "Most of the time, chat events are async. However, if another plugin forces a player", + "to chat, the event will happen sync. Also, the command event is run sync.", + "", + "In these situations, it is possible LibertyBans has no cached mute result and cannot", + "query the database quickly enough. The following strategies are available:", + "", + "* WAIT - wait for the database query to complete. This can block the main thread", + "* DENY - deny the event, using the 'misc.sync-event-denial' message", + "* ALLOW - allow the event", + "", + "This situation can be avoided by using a proxy, where there is no \"main thread\".", + "Velocity and BungeeCord are therefore not affected by synchronous events."}) + @DefaultString("ALLOW") + SyncEnforcement syncEnforcement(); + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java index 84794f9ca..3c56df3b4 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/IDImpl.java @@ -20,13 +20,12 @@ import space.arim.omnibus.util.concurrent.CentralisedFuture; -import space.arim.libertybans.api.Punishment; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.core.MiscUtil; -import space.arim.libertybans.core.SecurePunishment; +import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.core.database.Database; +import space.arim.libertybans.core.punish.MiscUtil; -class IDImpl extends SelectorImplGroup { +class IDImpl extends SelectorImplMember { IDImpl(Selector selector) { super(selector); @@ -46,7 +45,7 @@ CentralisedFuture getActivePunishmentById(int id) { + "WHERE `id` = ? AND (`end` = 0 OR `end` > ?)") .params(id, currentTime) .singleResult((resultSet) -> { - return new SecurePunishment(id, type, + return core().getEnforcementCenter().createPunishment(id, type, database.getVictimFromResult(resultSet), database.getOperatorFromResult(resultSet), database.getReasonFromResult(resultSet), database.getScopeFromResult(resultSet), database.getStartFromResult(resultSet), database.getEndFromResult(resultSet)); @@ -73,7 +72,7 @@ CentralisedFuture getActivePunishmentByIdAndType(int id, PunishmentT + "WHERE `id` = ? AND (`end` = 0 OR `end` > ?)") .params(id, MiscUtil.currentTime()) .singleResult((resultSet) -> { - return new SecurePunishment(id, type, + return core().getEnforcementCenter().createPunishment(id, type, database.getVictimFromResult(resultSet), database.getOperatorFromResult(resultSet), database.getReasonFromResult(resultSet), database.getScopeFromResult(resultSet), database.getStartFromResult(resultSet), database.getEndFromResult(resultSet)); @@ -89,7 +88,7 @@ CentralisedFuture getHistoricalPunishmentById(int id) { + "FROM `libertybans_simple_history` WHERE `id` = ?") .params(id) .singleResult((resultSet) -> { - return new SecurePunishment(id, database.getTypeFromResult(resultSet), + return core().getEnforcementCenter().createPunishment(id, database.getTypeFromResult(resultSet), database.getVictimFromResult(resultSet), database.getOperatorFromResult(resultSet), database.getReasonFromResult(resultSet), database.getScopeFromResult(resultSet), database.getStartFromResult(resultSet), database.getEndFromResult(resultSet)); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/MuteCacher.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/MuteCacher.java index 6caa13524..e29095b8d 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/MuteCacher.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/MuteCacher.java @@ -18,14 +18,16 @@ */ package space.arim.libertybans.core.selector; -import java.util.Arrays; import java.util.Optional; import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import org.checkerframework.checker.nullness.qual.NonNull; +import org.slf4j.LoggerFactory; + import com.github.benmanes.caffeine.cache.AsyncCacheLoader; import com.github.benmanes.caffeine.cache.AsyncLoadingCache; import com.github.benmanes.caffeine.cache.Caffeine; @@ -33,8 +35,10 @@ import space.arim.omnibus.util.concurrent.CentralisedFuture; -import space.arim.libertybans.api.Punishment; +import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.Punishment; import space.arim.libertybans.core.LibertyBansCore; public class MuteCacher { @@ -54,50 +58,75 @@ public MuteCacher(LibertyBansCore core) { @Override public @NonNull CentralisedFuture> asyncLoad(@NonNull MuteCacheKey key, @NonNull Executor executor) { - return core.getSelector().getApplicableMute(key.uuid, key.address).thenApply(Optional::ofNullable); + return core.getSelector().getApplicableMute(key.uuid, key.address) + .thenApply(Optional::ofNullable) + .exceptionally((ex) -> { + LoggerFactory.getLogger(getClass()).warn("Exception while computing cached mute", ex); + return Optional.empty(); + }); } }; muteCache = Caffeine.newBuilder().expireAfterAccess(4L, TimeUnit.MINUTES).initialCapacity(32) .scheduler(Scheduler.systemScheduler()).buildAsync(cacheLoader); } - public CentralisedFuture getCachedMute(UUID uuid, byte[] address) { + public CentralisedFuture getCachedMute(UUID uuid, NetworkAddress address) { return (CentralisedFuture) muteCache.get(new MuteCacheKey(uuid, address)) - .thenApply((optPunishment) -> optPunishment.orElse(null)); + .thenApply((optPunishment) -> { + Punishment punishment = optPunishment.orElse(null); + if (punishment == null || punishment.isExpired()) { + return null; + } + return punishment; + }); } - public void setCachedMute(UUID uuid, byte[] address, Punishment punishment) { + public void setCachedMute(UUID uuid, NetworkAddress address, Punishment punishment) { if (punishment.getType() != PunishmentType.MUTE) { throw new IllegalArgumentException("Cannot set cached mute to a punishment which is not a mute"); } muteCache.put(new MuteCacheKey(uuid, address), core.getFuturesFactory().completedFuture(Optional.of(punishment))); } - public void clearCachedMute(Punishment punishment) { - if (punishment.getType() != PunishmentType.MUTE) { - throw new IllegalArgumentException("Cannot set cached mute to a punishment which is not a mute"); - } + private void clearCachedMuteIf(Predicate filter) { /* * Any matching entries must be fully removed so that they may be recalculated afresh, * and not merely set to an empty Optional, the reason being there may be multiple * applicable mutes for the UUID/address combination. */ - muteCache.synchronous().asMap().values().removeIf((optPunishment) -> punishment.equals(optPunishment.orElse(null))); + muteCache.synchronous().asMap().values().removeIf((optPunishment) -> { + return optPunishment.isPresent() && filter.test(optPunishment.get()); + }); + } + + public void clearCachedMute(Punishment punishment) { + if (punishment.getType() != PunishmentType.MUTE) { + throw new IllegalArgumentException("Cannot clear cached mute of a punishment which is not a mute"); + } + clearCachedMuteIf(punishment::equals); + } + + public void clearCachedMute(int id) { + clearCachedMuteIf((punishment) -> punishment.getID() == id); + } + + public void clearCachedMute(Victim victim) { + clearCachedMuteIf((punishment) -> punishment.getVictim().equals(victim)); } private static class MuteCacheKey { final UUID uuid; - final byte[] address; + final NetworkAddress address; - MuteCacheKey(UUID uuid, byte[] address) { + MuteCacheKey(UUID uuid, NetworkAddress address) { this.uuid = uuid; this.address = address; } @Override public String toString() { - return "MuteCacheKey [uuid=" + uuid + ", address=" + Arrays.toString(address) + "]"; + return "MuteCacheKey [uuid=" + uuid + ", address=" + address + "]"; } @Override @@ -105,7 +134,7 @@ public int hashCode() { final int prime = 31; int result = 1; result = prime * result + uuid.hashCode(); - result = prime * result + Arrays.hashCode(address); + result = prime * result + address.hashCode(); return result; } @@ -118,7 +147,7 @@ public boolean equals(Object object) { return false; } MuteCacheKey other = (MuteCacheKey) object; - return uuid.equals(other.uuid) && Arrays.equals(address, other.address); + return uuid.equals(other.uuid) && address.equals(other.address); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/OtherImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/OtherImpl.java deleted file mode 100644 index f0d705ed0..000000000 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/OtherImpl.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-core is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-core. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.core.selector; - -import java.util.Set; - -import space.arim.omnibus.util.concurrent.CentralisedFuture; - -import space.arim.libertybans.api.Punishment; -import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Victim; -import space.arim.libertybans.core.MiscUtil; -import space.arim.libertybans.core.SecurePunishment; -import space.arim.libertybans.core.database.Database; - -class OtherImpl extends SelectorImplGroup { - - OtherImpl(Selector selector) { - super(selector); - } - - CentralisedFuture> getHistoryForVictim(Victim victim) { - Database database = core().getDatabase(); - return database.selectAsync(() -> { - - Set result = database.jdbCaesar().query( - "SELECT `id`, `type`, `operator`, `reason`, `scope`, `start`, `end`, `undone` FROM " - + "`libertybans_history` WHERE `victim` = ? AND `victim_type` = ?") - .params(victim, victim.getType()) - .setResult((resultSet) -> { - return (Punishment) new SecurePunishment(resultSet.getInt("id"), - database.getTypeFromResult(resultSet), victim, database.getOperatorFromResult(resultSet), - database.getReasonFromResult(resultSet), database.getScopeFromResult(resultSet), - database.getStartFromResult(resultSet), database.getEndFromResult(resultSet)); - }).onError(Set::of).execute(); - return Set.copyOf(result); - }); - } - - CentralisedFuture> getActivePunishmentsForType(PunishmentType type) { - if (type == PunishmentType.KICK) { - // Kicks are never active - return core().getFuturesFactory().completedFuture(Set.of()); - } - Database database = core().getDatabase(); - return database.selectAsync(() -> { - - String table = "`libertybans_" + type.getLowercaseNamePlural() + '`'; - Set result = database.jdbCaesar().query( - "SELECT `id`, `victim`, `victim_type`, `operator`, `reason`, `scope`, `start`, `end` FROM " - + table + " WHERE `end` = 0 OR `end` > ?") - .params(MiscUtil.currentTime()) - .setResult((resultSet) -> { - return (Punishment) new SecurePunishment(resultSet.getInt("id"), type, - database.getVictimFromResult(resultSet), database.getOperatorFromResult(resultSet), - database.getReasonFromResult(resultSet), database.getScopeFromResult(resultSet), - database.getStartFromResult(resultSet), database.getEndFromResult(resultSet)); - }).onError(Set::of).execute(); - return Set.copyOf(result); - }); - } - -} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionImpl.java index de418cd89..079f2bbfd 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionImpl.java @@ -28,108 +28,80 @@ import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.libertybans.api.Operator; -import space.arim.libertybans.api.Punishment; -import space.arim.libertybans.api.PunishmentSelection; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Scope; +import space.arim.libertybans.api.ServerScope; import space.arim.libertybans.api.Victim; -import space.arim.libertybans.core.MiscUtil; -import space.arim.libertybans.core.SecurePunishment; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.select.SelectionOrder; import space.arim.libertybans.core.database.Database; +import space.arim.libertybans.core.punish.MiscUtil; -class SelectionImpl extends SelectorImplGroup { +class SelectionImpl extends SelectorImplMember { SelectionImpl(Selector selector) { super(selector); } - private static StringBuilder getColumns(PunishmentSelection selection) { + private static String getColumns(SelectionOrder selection) { List columns = new ArrayList<>(); - columns.add("id"); + columns.add("`id`"); if (selection.getType() == null) { - columns.add("type"); + columns.add("`type`"); } if (selection.getVictim() == null) { - columns.add("victim"); - columns.add("victim_type"); + columns.add("`victim`"); + columns.add("`victim_type`"); } if (selection.getOperator() == null) { - columns.add("operator"); + columns.add("`operator`"); } - columns.add("reason"); + columns.add("`reason`"); if (selection.getScope() == null) { - columns.add("scope"); + columns.add("`scope`"); } - columns.add("start"); - columns.add("end"); - StringBuilder builder = new StringBuilder(); - String[] columnArray = columns.toArray(new String[] {}); - for (int n = 0; n < columnArray.length; n++) { - if (n != 0) { - builder.append(", "); - } - builder.append('`').append(columnArray[n]).append('`'); - } - return builder; + columns.add("`start`"); + columns.add("`end`"); + + return String.join(", ", columns); } - private static Map.Entry getPredication(PunishmentSelection selection) { - boolean foundAny = false; + private static Map.Entry getPredication(SelectionOrder selection) { + List predicates = new ArrayList<>(); List params = new ArrayList<>(); - StringBuilder builder = new StringBuilder(); + if (selection.getType() != null) { - builder.append("`type` = ?"); + predicates.add("`type` = ?"); params.add(selection.getType()); - if (!foundAny) { - foundAny = true; - } } if (selection.getVictim() != null) { - if (foundAny) { - builder.append(" AND "); - } else { - foundAny = true; - } - builder.append("`victim` = ? AND `victim_type` = ?"); + predicates.add("`victim` = ? AND `victim_type` = ?"); Victim victim = selection.getVictim(); params.add(victim); params.add(victim.getType()); } if (selection.getOperator() != null) { - if (foundAny) { - builder.append(" AND "); - } else { - foundAny = true; - } - builder.append("`operator` = ?"); + predicates.add("`operator` = ?"); params.add(selection.getOperator()); } if (selection.getScope() != null) { - if (foundAny) { - builder.append(" AND "); - } else { - foundAny = true; - } - builder.append("`scope` = ?"); + predicates.add("`scope` = ?"); params.add(selection.getScope()); } if (selection.selectActiveOnly()) { - if (foundAny) { - builder.append(" AND "); - } - builder.append("(`end` = 0 OR `end` > ?)"); + predicates.add("(`end` = 0 OR `end` > ?)"); params.add(MiscUtil.currentTime()); } - return Map.entry(builder, params.toArray()); + return Map.entry(String.join(" AND ", predicates), params.toArray()); } - private static SecurePunishment fromResultSetAndSelection(Database database, ResultSet resultSet, - PunishmentSelection selection) throws SQLException { + private Punishment fromResultSetAndSelection(Database database, ResultSet resultSet, + SelectionOrder selection) throws SQLException { PunishmentType type = selection.getType(); Victim victim = selection.getVictim(); Operator operator = selection.getOperator(); - Scope scope = selection.getScope(); - return new SecurePunishment(resultSet.getInt("id"), + ServerScope scope = selection.getScope(); + return core().getEnforcementCenter().createPunishment( + resultSet.getInt("id"), (type == null) ? database.getTypeFromResult(resultSet) : type, (victim == null) ? database.getVictimFromResult(resultSet) : victim, (operator == null) ? database.getOperatorFromResult(resultSet) : operator, @@ -139,9 +111,9 @@ private static SecurePunishment fromResultSetAndSelection(Database database, Res database.getEndFromResult(resultSet)); } - private static Map.Entry getSelectionQuery(PunishmentSelection selection) { - StringBuilder columns = getColumns(selection); - Map.Entry predication = getPredication(selection); + private static Map.Entry getSelectionQuery(SelectionOrder selection) { + String columns = getColumns(selection); + Map.Entry predication = getPredication(selection); StringBuilder statementBuilder = new StringBuilder("SELECT "); statementBuilder.append(columns).append(" FROM `libertybans_"); @@ -155,14 +127,14 @@ private static Map.Entry getSelectionQuery(PunishmentSelection } statementBuilder.append('`'); - String predicates = predication.getKey().toString(); + String predicates = predication.getKey(); if (!predicates.isEmpty()) { statementBuilder.append(" WHERE ").append(predicates); } return Map.entry(statementBuilder.toString(), predication.getValue()); } - CentralisedFuture getFirstSpecificPunishment(PunishmentSelection selection) { + CentralisedFuture getFirstSpecificPunishment(SelectionOrder selection) { if (selection.selectActiveOnly() && selection.getType() == PunishmentType.KICK) { // Kicks cannot possibly be active. They are all history return core().getFuturesFactory().completedFuture(null); @@ -179,7 +151,7 @@ CentralisedFuture getFirstSpecificPunishment(PunishmentSelection sel }); } - CentralisedFuture> getSpecificPunishments(PunishmentSelection selection) { + CentralisedFuture> getSpecificPunishments(SelectionOrder selection) { if (selection.selectActiveOnly() && selection.getType() == PunishmentType.KICK) { // Kicks cannot possibly be active. They are all history return core().getFuturesFactory().completedFuture(Set.of()); @@ -191,7 +163,7 @@ CentralisedFuture> getSpecificPunishments(PunishmentSelection se query.getKey()) .params(query.getValue()) .setResult((resultSet) -> { - return (Punishment) fromResultSetAndSelection(database, resultSet, selection); + return fromResultSetAndSelection(database, resultSet, selection); }).onError(Set::of).execute(); return Set.copyOf(result); }); diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderBuilderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderBuilderImpl.java new file mode 100644 index 000000000..5971ba829 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderBuilderImpl.java @@ -0,0 +1,77 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.selector; + +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.select.SelectionOrder; +import space.arim.libertybans.api.select.SelectionOrderBuilder; + +class SelectionOrderBuilderImpl implements SelectionOrderBuilder { + + private final Selector selector; + + private PunishmentType type; + private Victim victim; + private Operator operator; + private ServerScope scope; + private boolean selectActiveOnly = true; + + SelectionOrderBuilderImpl(Selector selector) { + this.selector = selector; + } + + @Override + public SelectionOrderBuilder type(PunishmentType type) { + this.type = type; + return this; + } + + @Override + public SelectionOrderBuilder victim(Victim victim) { + this.victim = victim; + return this; + } + + @Override + public SelectionOrderBuilder operator(Operator operator) { + this.operator = operator; + return this; + } + + @Override + public SelectionOrderBuilder scope(ServerScope scope) { + this.scope = scope; + return this; + } + + @Override + public SelectionOrderBuilder selectActiveOnly(boolean selectActiveOnly) { + this.selectActiveOnly = selectActiveOnly; + return this; + } + + @Override + public SelectionOrder build() { + return new SelectionOrderImpl(selector, type, victim, operator, scope, selectActiveOnly); + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java new file mode 100644 index 000000000..52fdaee19 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectionOrderImpl.java @@ -0,0 +1,123 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.selector; + +import java.util.Objects; +import java.util.Set; + +import space.arim.omnibus.util.concurrent.CentralisedFuture; + +import space.arim.libertybans.api.Operator; +import space.arim.libertybans.api.PunishmentType; +import space.arim.libertybans.api.ServerScope; +import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.select.SelectionOrder; + +class SelectionOrderImpl implements SelectionOrder { + + private transient final Selector selector; + + private final PunishmentType type; + private final Victim victim; + private final Operator operator; + private final ServerScope scope; + private final boolean selectActiveOnly; + + SelectionOrderImpl(Selector selector, + PunishmentType type, Victim victim, Operator operator, ServerScope scope, boolean selectActiveOnly) { + this.selector = selector; + + this.type = type; + this.victim = victim; + this.operator = operator; + this.scope = scope; + this.selectActiveOnly = selectActiveOnly; + } + + @Override + public PunishmentType getType() { + return type; + } + + @Override + public Victim getVictim() { + return victim; + } + + @Override + public Operator getOperator() { + return operator; + } + + @Override + public ServerScope getScope() { + return scope; + } + + @Override + public boolean selectActiveOnly() { + return selectActiveOnly; + } + + @Override + public CentralisedFuture getFirstSpecificPunishment() { + return selector.getFirstSpecificPunishment(this); + } + + @Override + public CentralisedFuture> getAllSpecificPunishments() { + return selector.getSpecificPunishments(this); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Objects.hashCode(type); + result = prime * result + Objects.hashCode(victim); + result = prime * result + Objects.hashCode(operator); + result = prime * result + Objects.hashCode(scope); + result = prime * result + (selectActiveOnly ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof SelectionOrderImpl)) { + return false; + } + SelectionOrderImpl other = (SelectionOrderImpl) object; + return type == other.type + && Objects.equals(victim, other.victim) + && Objects.equals(operator, other.operator) + && Objects.equals(scope, other.scope) + && selectActiveOnly == other.selectActiveOnly; + } + + @Override + public String toString() { + return "SelectionOrderImpl [type=" + type + ", victim=" + victim + ", operator=" + operator + ", scope=" + scope + + ", selectActiveOnly=" + selectActiveOnly + "]"; + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/Selector.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/Selector.java index e16069e78..76ed8ccc4 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/Selector.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/Selector.java @@ -24,11 +24,12 @@ import space.arim.omnibus.util.concurrent.CentralisedFuture; -import space.arim.libertybans.api.Punishment; -import space.arim.libertybans.api.PunishmentSelection; -import space.arim.libertybans.api.PunishmentSelector; +import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.PunishmentType; -import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.select.SelectionOrder; +import space.arim.libertybans.api.select.SelectionOrderBuilder; +import space.arim.libertybans.api.select.PunishmentSelector; import space.arim.libertybans.core.LibertyBansCore; public class Selector implements PunishmentSelector { @@ -38,7 +39,6 @@ public class Selector implements PunishmentSelector { private final SelectionImpl selectionImpl; private final IDImpl idImpl; private final ApplicableImpl applicableImpl; - private final OtherImpl otherImpl; public Selector(LibertyBansCore core) { this.core = core; @@ -46,7 +46,11 @@ public Selector(LibertyBansCore core) { selectionImpl = new SelectionImpl(this); idImpl = new IDImpl(this); applicableImpl = new ApplicableImpl(this); - otherImpl = new OtherImpl(this); + } + + @Override + public SelectionOrderBuilder selectionBuilder() { + return new SelectionOrderBuilderImpl(this); } /* @@ -55,13 +59,11 @@ public Selector(LibertyBansCore core) { * */ - @Override - public CentralisedFuture getFirstSpecificPunishment(PunishmentSelection selection) { + CentralisedFuture getFirstSpecificPunishment(SelectionOrder selection) { return selectionImpl.getFirstSpecificPunishment(selection); } - @Override - public CentralisedFuture> getSpecificPunishments(PunishmentSelection selection) { + CentralisedFuture> getSpecificPunishments(SelectionOrder selection) { return selectionImpl.getSpecificPunishments(selection); } @@ -97,42 +99,26 @@ public CentralisedFuture getHistoricalPunishmentById(int id) { * * @param uuid the player UUID * @param name the player name - * @param address the player IP address + * @param address the player address * @return a future which yields the ban itself, or null if there is none */ - public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, byte[] address) { + public CentralisedFuture executeAndCheckConnection(UUID uuid, String name, NetworkAddress address) { return applicableImpl.executeAndCheckConnection(uuid, name, address); } @Override - public CentralisedFuture getApplicablePunishment(UUID uuid, byte[] address, PunishmentType type) { + public CentralisedFuture getApplicablePunishment(UUID uuid, NetworkAddress address, PunishmentType type) { return applicableImpl.getApplicablePunishment(uuid, address, type); } @Override - public CentralisedFuture getCachedMute(UUID uuid, byte[] address) { + public CentralisedFuture getCachedMute(UUID uuid, NetworkAddress address) { Objects.requireNonNull(uuid, "uuid"); - return core.getMuteCacher().getCachedMute(uuid, address.clone()); + return core.getMuteCacher().getCachedMute(uuid, address); } - CentralisedFuture getApplicableMute(UUID uuid, byte[] address) { + CentralisedFuture getApplicableMute(UUID uuid, NetworkAddress address) { return applicableImpl.getApplicableMute(uuid, address); } - /* - * - * Other methods - * - */ - - @Override - public CentralisedFuture> getHistoryForVictim(Victim victim) { - return otherImpl.getHistoryForVictim(victim); - } - - @Override - public CentralisedFuture> getActivePunishmentsForType(PunishmentType type) { - return otherImpl.getActivePunishmentsForType(type); - } - } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImplGroup.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImplMember.java similarity index 93% rename from bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImplGroup.java rename to bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImplMember.java index 775772ae8..e8cb970fc 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImplGroup.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SelectorImplMember.java @@ -20,11 +20,11 @@ import space.arim.libertybans.core.LibertyBansCore; -abstract class SelectorImplGroup { +abstract class SelectorImplMember { private final Selector selector; - SelectorImplGroup(Selector selector) { + SelectorImplMember(Selector selector) { this.selector = selector; } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/config/SyncEnforcement.java b/bans-core/src/main/java/space/arim/libertybans/core/selector/SyncEnforcement.java similarity index 96% rename from bans-core/src/main/java/space/arim/libertybans/core/config/SyncEnforcement.java rename to bans-core/src/main/java/space/arim/libertybans/core/selector/SyncEnforcement.java index 7ac719a12..9761f36d2 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/config/SyncEnforcement.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/selector/SyncEnforcement.java @@ -16,7 +16,7 @@ * along with LibertyBans-core. If not, see * and navigate to version 3 of the GNU Affero General Public License. */ -package space.arim.libertybans.core.config; +package space.arim.libertybans.core.selector; /** * How to handle events run synchronously, when an asynchronous computation has not yet completed. diff --git a/bans-core/src/main/java/space/arim/libertybans/core/uuid/RemoteApi.java b/bans-core/src/main/java/space/arim/libertybans/core/uuid/RemoteApi.java deleted file mode 100644 index 984b7e3ea..000000000 --- a/bans-core/src/main/java/space/arim/libertybans/core/uuid/RemoteApi.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * LibertyBans-core - * Copyright © 2020 Anand Beh - * - * LibertyBans-core is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-core is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-core. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.core.uuid; - -import java.net.http.HttpClient; -import java.util.Locale; - -import space.arim.api.util.web.HttpAshconApi; -import space.arim.api.util.web.HttpMcHeadsApi; -import space.arim.api.util.web.HttpMojangApi; -import space.arim.api.util.web.RemoteNameHistoryApi; - -/** - * Pseudo enum of remote web APIs - * - * @author A248 - * - */ -class RemoteApi { - - private static final RemoteApi MOJANG; - private static final RemoteApi ASHCON; - private static final RemoteApi MCHEADS; - - static { - HttpClient httpClient = HttpClient.newHttpClient(); - MOJANG = new RemoteApi(new HttpMojangApi(httpClient)); - ASHCON = new RemoteApi(new HttpAshconApi(httpClient)); - MCHEADS = new RemoteApi(new HttpMcHeadsApi(httpClient)); - } - - private final RemoteNameHistoryApi remote; - - private RemoteApi(RemoteNameHistoryApi remote) { - this.remote = remote; - } - - RemoteNameHistoryApi getRemote() { - return remote; - } - - @Override - public String toString() { - String name = remote.getClass().getSimpleName(); - return name.substring(4, name.length() - 3); - } - - static RemoteApi nullableValueOf(String configOpt) { - switch (configOpt.toUpperCase(Locale.ENGLISH)) { - case "MOJANG": - return MOJANG; - case "ASHCON": - return ASHCON; - case "MCHEADS": - return MCHEADS; - default: - return null; - } - } - -} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/uuid/RemoteApiBundle.java b/bans-core/src/main/java/space/arim/libertybans/core/uuid/RemoteApiBundle.java new file mode 100644 index 000000000..4bd697dff --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/uuid/RemoteApiBundle.java @@ -0,0 +1,132 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.uuid; + +import java.net.http.HttpClient; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import space.arim.omnibus.util.ThisClass; + +import space.arim.api.util.web.HttpAshconApi; +import space.arim.api.util.web.HttpMcHeadsApi; +import space.arim.api.util.web.HttpMojangApi; +import space.arim.api.util.web.RemoteApiResult; +import space.arim.api.util.web.RemoteApiResult.ResultType; +import space.arim.api.util.web.RemoteNameHistoryApi; +import space.arim.api.util.web.RemoteNameUUIDApi; + +import space.arim.dazzleconf.error.BadValueException; +import space.arim.dazzleconf.serialiser.Decomposer; +import space.arim.dazzleconf.serialiser.FlexibleType; +import space.arim.dazzleconf.serialiser.ValueSerialiser; + +public class RemoteApiBundle { + + private final List remotes; + + private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); + + RemoteApiBundle(List remotes) { + this.remotes = List.copyOf(remotes); + } + + CompletableFuture lookup(CompletableFuture future, + Function>> intermediateResultFunction) { + for (RemoteNameHistoryApi remoteApi : remotes) { + future = future.thenCompose((result) -> { + if (result != null) { + return CompletableFuture.completedFuture(result); + } + return intermediateResultFunction.apply(remoteApi).thenApply((remoteApiResult) -> { + if (remoteApiResult.getResultType() != ResultType.FOUND) { + Exception ex = remoteApiResult.getException(); + if (ex == null) { + logger.warn("Request for name to remote web API {} failed", remoteApi); + } else { + logger.warn("Request for name to remote web API {} failed", remoteApi, ex); + } + return null; + } + return remoteApiResult.getValue(); + }); + }); + } + return future; + } + + private enum RemoteType { + ASHCON(HttpAshconApi::new), + MCHEADS(HttpMcHeadsApi::new), + MOJANG(HttpMojangApi::new); + + final Function creator; + + private RemoteType(Function creator) { + this.creator = creator; + } + + static RemoteType fromInstance(RemoteNameHistoryApi remote) { + if (remote instanceof HttpAshconApi) { + return ASHCON; + } + if (remote instanceof HttpMcHeadsApi) { + return MCHEADS; + } + if (remote instanceof HttpMojangApi) { + return MOJANG; + } + throw new IllegalArgumentException("Unknown implementing instance of " + remote); + } + } + + public static class SerialiserImpl implements ValueSerialiser { + + @Override + public Class getTargetClass() { + return RemoteApiBundle.class; + } + + @Override + public RemoteApiBundle deserialise(FlexibleType flexibleType) throws BadValueException { + HttpClient httpClient = HttpClient.newHttpClient(); + List remotes = flexibleType.getList((flexibleElement) -> { + RemoteType remoteType = flexibleElement.getObject(RemoteType.class); + return remoteType.creator.apply(httpClient); + }); + return new RemoteApiBundle(remotes); + } + + @Override + public Object serialise(RemoteApiBundle value, Decomposer decomposer) { + List result = new ArrayList<>(); + for (RemoteNameHistoryApi remote : value.remotes) { + result.add(RemoteType.fromInstance(remote).name()); + } + return result; + } + + } + +} diff --git a/bans-core/src/main/java/space/arim/libertybans/core/uuid/ResolverImpl.java b/bans-core/src/main/java/space/arim/libertybans/core/uuid/ResolverImpl.java index 2e43e6fb6..2fc4cf07c 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/uuid/ResolverImpl.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/uuid/ResolverImpl.java @@ -30,15 +30,15 @@ class ResolverImpl implements UUIDResolver { - private final UUIDMaster um; + private final UUIDManager manager; - ResolverImpl(UUIDMaster um) { - this.um = um; + ResolverImpl(UUIDManager manager) { + this.manager = manager; } @Override public CentralisedFuture resolve(String name) { - Database helper = um.core.getDatabase(); + Database helper = manager.core.getDatabase(); return helper.selectAsync(() -> { return helper.jdbCaesar().query( "SELECT `uuid` FROM `libertybans_names` WHERE `name` = ? ORDER BY `updated` DESC LIMIT 1") @@ -51,7 +51,7 @@ public CentralisedFuture resolve(String name) { @Override public CentralisedFuture resolve(UUID uuid) { - Database helper = um.core.getDatabase(); + Database helper = manager.core.getDatabase(); return helper.selectAsync(() -> { return helper.jdbCaesar().query( "SELECT `name` FROM `libertybans_names` WHERE `uuid` = ? ORDER BY `updated` DESC LIMIT 1") @@ -65,11 +65,11 @@ public CentralisedFuture resolve(UUID uuid) { @Override public UUID resolveImmediately(String name) { // Caffeine specifies that operations on the entry set do not refresh the expiration timer - for (Map.Entry entry : um.fastCache.asMap().entrySet()) { + for (Map.Entry entry : manager.fastCache.asMap().entrySet()) { if (entry.getValue().equalsIgnoreCase(name)) { UUID uuid = entry.getKey(); // Manual cache refresh - um.fastCache.getIfPresent(uuid); + manager.fastCache.getIfPresent(uuid); return uuid; } } @@ -78,7 +78,7 @@ public UUID resolveImmediately(String name) { @Override public String resolveImmediately(UUID uuid) { - return um.fastCache.getIfPresent(uuid); + return manager.fastCache.getIfPresent(uuid); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDVaultStuff.java b/bans-core/src/main/java/space/arim/libertybans/core/uuid/ServerType.java similarity index 68% rename from bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDVaultStuff.java rename to bans-core/src/main/java/space/arim/libertybans/core/uuid/ServerType.java index 76fafcf80..54ba69753 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDVaultStuff.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/uuid/ServerType.java @@ -18,17 +18,10 @@ */ package space.arim.libertybans.core.uuid; -import space.arim.uuidvault.api.CollectiveUUIDResolver; -import space.arim.uuidvault.api.UUIDVaultRegistration; - -class UUIDVaultStuff { - - final UUIDVaultRegistration registration; - final CollectiveUUIDResolver collectiveResolver; +enum ServerType { - UUIDVaultStuff(UUIDVaultRegistration registration, CollectiveUUIDResolver collectiveResolver) { - this.registration = registration; - this.collectiveResolver = collectiveResolver; - } + ONLINE, + OFFLINE, + MIXED } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDMaster.java b/bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDManager.java similarity index 65% rename from bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDMaster.java rename to bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDManager.java index 76924c5dd..8e71d3604 100644 --- a/bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDMaster.java +++ b/bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDManager.java @@ -18,37 +18,28 @@ */ package space.arim.libertybans.core.uuid; -import java.util.List; -import java.util.ListIterator; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.regex.Pattern; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import space.arim.omnibus.util.ThisClass; import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.uuidvault.api.CollectiveUUIDResolver; import space.arim.uuidvault.api.UUIDVault; import space.arim.uuidvault.api.UUIDVaultRegistration; -import space.arim.api.configure.SingleKeyValueTransformer; -import space.arim.api.configure.ValueTransformer; import space.arim.api.util.web.RemoteApiResult; -import space.arim.api.util.web.RemoteApiResult.ResultType; import space.arim.api.util.web.RemoteNameUUIDApi; import space.arim.libertybans.core.LibertyBansCore; import space.arim.libertybans.core.Part; -public class UUIDMaster implements Part { +public class UUIDManager implements Part { final LibertyBansCore core; @@ -59,13 +50,23 @@ public class UUIDMaster implements Part { private static final Pattern VALID_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]*+"); - private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); - - public UUIDMaster(LibertyBansCore core) { + public UUIDManager(LibertyBansCore core) { this.core = core; resolverImpl = new ResolverImpl(this); } + private static class UUIDVaultStuff { + + final UUIDVaultRegistration registration; + final CollectiveUUIDResolver collectiveResolver; + + UUIDVaultStuff(UUIDVaultRegistration registration, CollectiveUUIDResolver collectiveResolver) { + this.registration = registration; + this.collectiveResolver = collectiveResolver; + } + + } + @Override public void startup() { Class pluginClass = core.getEnvironment().getPlatformHandle().getImplementingPluginInfo().getPlugin().getClass(); @@ -176,88 +177,11 @@ private CompletableFuture externalLookup(final UUID uuid) { private CompletableFuture webLookup(CompletableFuture result, Function>> resultFunction) { - ServerType serverType = getServerType(); - if (serverType != ServerType.ONLINE) { + UUIDResolutionConfig uuidResolution = core.getMainConfig().uuidResolution(); + if (uuidResolution.serverType() != ServerType.ONLINE) { return result; } - for (RemoteApi remoteApi : getRemoteApis()) { - result = result.thenCompose((name) -> { - if (name != null) { - return completedFuture(name); - } - return resultFunction.apply(remoteApi.getRemote()).thenApply((remoteApiResult) -> { - if (remoteApiResult.getResultType() != ResultType.FOUND) { - Exception ex = remoteApiResult.getException(); - if (ex == null) { - logger.warn("Request for name to remote web API {} failed", remoteApi); - } else { - logger.warn("Request for name to remote web API {} failed", remoteApi, ex); - } - return null; - } - return remoteApiResult.getValue(); - }); - }); - } - return result; - } - - private ServerType getServerType() { - return core.getConfigs().getConfig().getObject("player-uuid-resolution.server-type", ServerType.class); - } - - private List getRemoteApis() { - return core.getConfigs().getConfig().getList("player-uuid-resolution.web-api-resolvers", RemoteApi.class); - } - - public static List createValueTransformers() { - ValueTransformer serverTypeTransformer = SingleKeyValueTransformer.create("player-uuid-resolution.server-type", - (value) -> { - - if (value instanceof String) { - try { - return ServerType.valueOf((String) value); - } catch (IllegalArgumentException ignored) { - } - } - logger.warn("Invalid option for server-type: {}", value); - return null; - }); - ValueTransformer webApiResolversTransformer = SingleKeyValueTransformer.create("player-uuid-resolution.web-api-resolvers", - (value) -> { - - if (!(value instanceof List)) { - logger.warn("Invalid list for web-api-resolvers: {}", value); - return null; - } - @SuppressWarnings("unchecked") - List asList = (List) value; - for (ListIterator it = asList.listIterator(); it.hasNext();) { - Object element = it.next(); - if (element instanceof String) { - RemoteApi remoteApi = RemoteApi.nullableValueOf((String) element); - if (remoteApi != null) { - it.set(remoteApi); - continue; - } - } - logger.warn("Invalid list element in web-api-resolvers: {}", element); - return null; - } - /* - * By now, value will be a List where each element is an instance of RemoteApi - */ - return value; - }); - return List.of(serverTypeTransformer, webApiResolversTransformer); - } - - enum ServerType { - - ONLINE, - OFFLINE, - MIXED - + return uuidResolution.remoteApis().lookup(result, resultFunction); } } diff --git a/bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDResolutionConfig.java b/bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDResolutionConfig.java new file mode 100644 index 000000000..c0f97e372 --- /dev/null +++ b/bans-core/src/main/java/space/arim/libertybans/core/uuid/UUIDResolutionConfig.java @@ -0,0 +1,53 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.uuid; + +import space.arim.dazzleconf.annote.ConfComments; +import space.arim.dazzleconf.annote.ConfDefault.DefaultString; +import space.arim.dazzleconf.annote.ConfDefault.DefaultStrings; +import space.arim.dazzleconf.annote.ConfHeader; +import space.arim.dazzleconf.annote.ConfKey; +import space.arim.dazzleconf.annote.ConfSerialisers; + +@ConfHeader({"Options relating to finding UUIDs from names, and vice-versa", + "LibertyBans will first check its own caches before using these resources"}) +@ConfSerialisers(RemoteApiBundle.SerialiserImpl.class) +public interface UUIDResolutionConfig { + + @ConfKey("server-type") + @ConfComments({"", + "What kind of UUIDs do your players have?", + "Available options are ONLINE, OFFLINE, MIXED", + "", + "For most servers this will be 'ONLINE'", + "If you are running an offline server you should know whether you are using OFFLINE or MIXED UUIDs", + "" + }) + @DefaultString("ONLINE") + ServerType serverType(); + + @ConfKey("web-api-resolvers") + @ConfComments({"", + "As a last resort, when LibertyBans cannot find a UUID or name, it will use an external web API", + "Available options are 'MOJANG', 'ASHCON', and 'MCHEADS'. They will be queried sequentially in the order specified.", + "(If the server is not in ONLINE mode, this option is ignored)"}) + @DefaultStrings("MOJANG") + RemoteApiBundle remoteApis(); + +} diff --git a/bans-core/src/main/resources/lang/messages_en.yml b/bans-core/src/main/resources/lang/messages_en.yml index 967dae340..c12cb9646 100644 --- a/bans-core/src/main/resources/lang/messages_en.yml +++ b/bans-core/src/main/resources/lang/messages_en.yml @@ -50,8 +50,7 @@ additions: usage: '&cUsage: /ban &e [time] &c.' permission: command: '&cYou may not ban other players.' - error: - conflicting: '&c&o%TARGET%&r&7 is already banned!' + conflicting: '&c&o%TARGET%&r&7 is already banned!' successful: message: '&aBanned &c&o%VICTIM%&r&a for &a&o%DURATION%&r&a because of &e&o%REASON%&r&a.' notification: '&c&o%OPERATOR%&r&7 banned &c&o%VICTIM%&r&7 for &a&o%DURATION%&r&7 because of &e&o%REASON%&r&7.' @@ -69,8 +68,7 @@ additions: usage: '&cUsage: /mute &e [time] &c.' permission: command: '&cYou may not mute other players.' - error: - conflicting: '&c&o%TARGET%&r&7 is already muted!' + conflicting: '&c&o%TARGET%&r&7 is already muted!' successful: message: '&aMuted &c&o%VICTIM%&r&a for &a&o%DURATION%&r&a because of &e&o%REASON%&r&a.' notification: '&c&o%OPERATOR%&r&7 muted &c&o%VICTIM%&r&7 for &a&o%DURATION%&r&7 because of &e&o%REASON%&r&7.' @@ -134,7 +132,7 @@ removals: message: '&7Unmuted &c&o%VICTIM%&r&7.' notification: '&c&o%UNOPERATOR%&r&7 unmuted &c&o%VICTIM%&r&7.' # Messages regarding /banlist, /mutelist, /history, /warns, /blame -list: +lists: banlist: usage: '&cUsage: /banlist [page].' per-page: 10 diff --git a/bans-core/src/main/resources/sql.yml b/bans-core/src/main/resources/sql.yml index 5fc475302..ca0601166 100644 --- a/bans-core/src/main/resources/sql.yml +++ b/bans-core/src/main/resources/sql.yml @@ -16,7 +16,7 @@ # What RDMS vendor category will you be using? # Available options: # - 'HSQLDB' - Local HyperSQL database. No additional setup required. -# - 'MARIADB' - Requires a separate MariaDB or MySQL database. 'MYSQL' is accepted as an alias for this option. +# - 'MARIADB' - Requires a separate MariaDB or MySQL database. rdms-vendor: 'HSQLDB' # # How large should the connection pool be? diff --git a/bans-core/src/test/java/space/arim/libertybans/core/config/ConfigsTest.java b/bans-core/src/test/java/space/arim/libertybans/core/config/ConfigsTest.java new file mode 100644 index 000000000..3ecc2b685 --- /dev/null +++ b/bans-core/src/test/java/space/arim/libertybans/core/config/ConfigsTest.java @@ -0,0 +1,40 @@ +/* + * LibertyBans-core + * Copyright © 2020 Anand Beh + * + * LibertyBans-core is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * LibertyBans-core is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-core. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class ConfigsTest { + + @TempDir + public Path folder; + + @Test + public void testLoadAndReloadDefaults() { + Configs configs = new Configs(folder); + assertTrue(configs.reloadConfigs().join()); + assertTrue(configs.reloadConfigs().join()); + } + +} diff --git a/bans-core/src/test/java/space/arim/libertybans/core/uuid/UUIDMasterTest.java b/bans-core/src/test/java/space/arim/libertybans/core/uuid/UUIDMasterTest.java index cf13f929f..3204b2988 100644 --- a/bans-core/src/test/java/space/arim/libertybans/core/uuid/UUIDMasterTest.java +++ b/bans-core/src/test/java/space/arim/libertybans/core/uuid/UUIDMasterTest.java @@ -28,16 +28,16 @@ public class UUIDMasterTest { @Test public void testBadNameArguments() { // Valid name - assertTrue(UUIDMaster.validateNameArgument0("A248")); + assertTrue(UUIDManager.validateNameArgument0("A248")); // Invalid name with non alphanumeric/underscore - assertFalse(UUIDMaster.validateNameArgument0("NameWith=Sign")); + assertFalse(UUIDManager.validateNameArgument0("NameWith=Sign")); // Valid name with underscore - assertTrue(UUIDMaster.validateNameArgument0("Name_Underscored")); + assertTrue(UUIDManager.validateNameArgument0("Name_Underscored")); // Invalid name of too much length - assertFalse(UUIDMaster.validateNameArgument0("ThisNameHasMoreThan16Characters")); + assertFalse(UUIDManager.validateNameArgument0("ThisNameHasMoreThan16Characters")); } } diff --git a/bans-database/pom.xml b/bans-database/pom.xml deleted file mode 100644 index 516984b61..000000000 --- a/bans-database/pom.xml +++ /dev/null @@ -1,36 +0,0 @@ - - 4.0.0 - - - space.arim.libertybans - bans-parent - 0.1.4-SNAPSHOT - - - bans-database - Small component of LibertyBans to isolate driver dependencies. - - - src/main/java - ${project.name}_v${project.version} - - - - - com.zaxxer - HikariCP - - - org.mariadb.jdbc - mariadb-java-client - provided - - - org.hsqldb - hsqldb - provided - - - \ No newline at end of file diff --git a/bans-database/src/main/java/space/arim/libertybans/driver/DriverCreator.java b/bans-database/src/main/java/space/arim/libertybans/driver/DriverCreator.java deleted file mode 100644 index 4f7139b6d..000000000 --- a/bans-database/src/main/java/space/arim/libertybans/driver/DriverCreator.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * LibertyBans-database - * Copyright © 2020 Anand Beh - * - * LibertyBans-database is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LibertyBans-database is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LibertyBans-database. If not, see - * and navigate to version 3 of the GNU General Public License. - */ -package space.arim.libertybans.driver; - -import org.hsqldb.jdbc.JDBCDataSource; -import org.mariadb.jdbc.MariaDbDataSource; - -import com.zaxxer.hikari.HikariConfig; - -public class DriverCreator { - - private final HikariConfig hikariConf; - private final boolean jdbcUrl; - - public DriverCreator(HikariConfig hikariConf, boolean jdbcUrl) { - this.hikariConf = hikariConf; - this.jdbcUrl = jdbcUrl; - } - - public void createMariaDb(String host, int port, String database) { - if (jdbcUrl) { - hikariConf.setJdbcUrl("jdbc:mariadb://" + host + ":" + port + "/" + database); - setDriverClassName("org.mariadb.jdbc.Driver"); - } else { - MariaDbDataSource mariaDbDs = new MariaDbDataSource(host, port, database); - hikariConf.setDataSource(mariaDbDs); - } - /* - hikariConf.addDataSourceProperty("cachePrepStmts", "true"); - hikariConf.addDataSourceProperty("prepStmtCacheSize", "25"); - hikariConf.addDataSourceProperty("prepStmtCacheSqlLimit", "256"); - hikariConf.addDataSourceProperty("useServerPrepStmts", "true"); - */ - } - - public void createHsqldb(String file) { - if (jdbcUrl) { - hikariConf.setJdbcUrl("jdbc:hsqldb:file:" + file); - setDriverClassName("org.hsqldb.jdbc.JDBCDriver"); - } else { - JDBCDataSource hsqldbDs = new JDBCDataSource(); - hsqldbDs.setUrl("jdbc:hsqldb:file:" + file); - hikariConf.setDataSource(hsqldbDs); - } - } - - private void setDriverClassName(String driverClassName) { - Thread currentThread = Thread.currentThread(); - ClassLoader initialContextLoader = currentThread.getContextClassLoader(); - try { - currentThread.setContextClassLoader(getClass().getClassLoader()); - hikariConf.setDriverClassName(driverClassName); - } finally { - currentThread.setContextClassLoader(initialContextLoader); - } - } - -} diff --git a/bans-distribution/pom.xml b/bans-distribution/pom.xml index 6e9943993..9b86923e9 100644 --- a/bans-distribution/pom.xml +++ b/bans-distribution/pom.xml @@ -6,11 +6,11 @@ space.arim.libertybans bans-parent - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-distribution - Shaded combination of LibertyBans API, main plugin classes, and launcher. + Shaded combination of main plugin classes, and launcher. src/main/java @@ -27,11 +27,6 @@ false - - - space.arim.libertybans:* - - @@ -48,12 +43,6 @@ space.arim.libertybans bans-bootstrap - - - * - * - - space.arim.libertybans diff --git a/bans-dl/pom.xml b/bans-dl/pom.xml index 595283914..7893d3b06 100644 --- a/bans-dl/pom.xml +++ b/bans-dl/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-parent - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-dl @@ -33,8 +33,11 @@ false - space.arim.libertybans:* space.arim.licenser:licenser-agpl3 + space.arim.libertybans:bans-core + space.arim.libertybans:bans-env-spigot + space.arim.libertybans:bans-env-bungee + space.arim.libertybans:bans-env-velocity @@ -61,65 +64,21 @@ licenser-agpl3 true - - space.arim.libertybans - bans-database - - - * - * - - - - - space.arim.libertybans - bans-api - - - * - * - - - space.arim.libertybans bans-core - - - * - * - - space.arim.libertybans bans-env-spigot - - - * - * - - space.arim.libertybans bans-env-bungee - - - * - * - - space.arim.libertybans bans-env-velocity - - - * - * - - \ No newline at end of file diff --git a/bans-env/bungee/pom.xml b/bans-env/bungee/pom.xml index 578e7ad32..1af7355a2 100644 --- a/bans-env/bungee/pom.xml +++ b/bans-env/bungee/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-env - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-env-bungee diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java index ac4dd4fa6..11fc23804 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnforcer.java @@ -36,7 +36,7 @@ class BungeeEnforcer extends AbstractEnvEnforcer { BungeeEnforcer(BungeeEnv env) { - super(env); + super(env.core, env); } @Override @@ -45,7 +45,7 @@ protected BungeeEnv env() { } @Override - public void sendToThoseWithPermission(String permission, SendableMessage message) { + protected void sendToThoseWithPermission0(String permission, SendableMessage message) { for (ProxiedPlayer player : env().getPlugin().getProxy().getPlayers()) { if (player.hasPermission(permission)) { env().getPlatformHandle().sendMessage(player, message); @@ -64,8 +64,7 @@ public void doForPlayerIfOnline(UUID uuid, Consumer<@PlatformPlayer Object> call @Override public void enforceMatcher(TargetMatcher matcher) { for (ProxiedPlayer player : env().getPlugin().getProxy().getPlayers()) { - if (matcher.uuids().contains(player.getUniqueId()) - || matcher.addresses().contains(getAddress(player))) { + if (matcher.matches(player.getUniqueId(), getAddress(player))) { matcher.callback().accept(player); } } @@ -85,8 +84,8 @@ public UUID getUniqueIdFor(@PlatformPlayer Object player) { } @Override - public byte[] getAddressFor(@PlatformPlayer Object player) { - return getAddress((ProxiedPlayer) player).getAddress(); + public InetAddress getAddressFor(@PlatformPlayer Object player) { + return getAddress((ProxiedPlayer) player); } } diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnv.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnv.java index 9eb595909..56085e6b0 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnv.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeEnv.java @@ -44,12 +44,25 @@ public class BungeeEnv extends AbstractEnv { private final BungeeEnforcer enforcer; public BungeeEnv(Plugin plugin, Path folder) { + setUUIDVaultIfNecessary(plugin); + handle = new BungeePlatformHandle(plugin); core = new LibertyBansCore(OmnibusProvider.getOmnibus(), folder, this); enforcer = new BungeeEnforcer(this); } + private static void setUUIDVaultIfNecessary(Plugin plugin) { + if (UUIDVault.get() == null) { + new UUIDVaultBungee(plugin) { + @Override + protected boolean setInstancePassive() { + return super.setInstancePassive(); + } + }.setInstancePassive(); + } + } + Plugin getPlugin() { return handle.getPlugin(); } @@ -66,14 +79,6 @@ public BungeeEnforcer getEnforcer() { @Override protected void startup0() { - if (UUIDVault.get() == null) { - new UUIDVaultBungee(getPlugin()) { - @Override - protected boolean setInstancePassive() { - return super.setInstancePassive(); - } - }.setInstancePassive(); - } core.startup(); } diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeOnlineTarget.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeOnlineTarget.java deleted file mode 100644 index bf7cb63d6..000000000 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/BungeeOnlineTarget.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * LibertyBans-env-bungee - * Copyright © 2020 Anand Beh - * - * LibertyBans-env-bungee is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-env-bungee is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-env-bungee. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.env.bungee; - -import java.net.InetSocketAddress; -import java.util.UUID; - -import net.md_5.bungee.api.connection.ProxiedPlayer; - -import space.arim.libertybans.core.env.AbstractOnlineTarget; - -class BungeeOnlineTarget extends AbstractOnlineTarget { - - BungeeOnlineTarget(BungeeEnv env, ProxiedPlayer player) { - super(env, player); - } - - @Override - public boolean hasPermission(String permission) { - return getRawPlayer().hasPermission(permission); - } - - @Override - public UUID getUniqueId() { - return getRawPlayer().getUniqueId(); - } - - @Override - public byte[] getAddress() { - return ((InetSocketAddress) getRawPlayer().getSocketAddress()).getAddress().getAddress(); - } - - @Override - public ProxiedPlayer getRawPlayer() { - return (ProxiedPlayer) super.getRawPlayer(); - } - -} diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ChatListener.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ChatListener.java index 81da8ec6a..f5407576c 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ChatListener.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ChatListener.java @@ -18,6 +18,8 @@ */ package space.arim.libertybans.env.bungee; +import java.net.InetAddress; + import space.arim.api.chat.SendableMessage; import space.arim.libertybans.core.env.ParallelisedListener; @@ -54,9 +56,9 @@ public void onChatLow(ChatEvent evt) { return; } ProxiedPlayer player = (ProxiedPlayer) sender; - byte[] address = env.getEnforcer().getAddress(player).getAddress(); + InetAddress address = env.getEnforcer().getAddress(player); String command = (evt.isCommand()) ? evt.getMessage().substring(1) : evt.getMessage(); - begin(evt, env.core.getEnforcer().checkChat(player.getUniqueId(), address, command)); + begin(evt, env.core.getEnforcementCenter().checkChat(player.getUniqueId(), address, command)); } @EventHandler(priority = EventPriority.HIGH) diff --git a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java index 14967ad2f..153d7bf63 100644 --- a/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java +++ b/bans-env/bungee/src/main/java/space/arim/libertybans/env/bungee/ConnectionListener.java @@ -18,6 +18,7 @@ */ package space.arim.libertybans.env.bungee; +import java.net.InetAddress; import java.util.UUID; import org.slf4j.Logger; @@ -29,6 +30,7 @@ import space.arim.libertybans.core.env.PlatformListener; +import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.plugin.Listener; @@ -61,18 +63,19 @@ public void onConnect(LoginEvent evt) { logger.debug("Player '{}' is already blocked by the server or another plugin", evt.getConnection().getName()); return; } + PendingConnection connection = evt.getConnection(); + UUID uuid = connection.getUniqueId(); + String name = connection.getName(); + InetAddress address = env.getEnforcer().getAddress(connection); + evt.registerIntent(env.getPlugin()); - PendingConnection conn = evt.getConnection(); - UUID uuid = conn.getUniqueId(); - String name = conn.getName(); - byte[] address = env.getEnforcer().getAddress(conn).getAddress(); - env.core.getEnforcer().executeAndCheckConnection(uuid, name, address).thenAccept((message) -> { + env.core.getEnforcementCenter().executeAndCheckConnection(uuid, name, address).thenAccept((message) -> { if (message == null) { logger.trace("Letting '{}' through the gates", name); } else { evt.setCancelled(true); - evt.setCancelReason(new BungeeComponentConverter().convertFrom(message)); + evt.setCancelReason(new BungeeComponentConverter().convert(message).toArray(BaseComponent[]::new)); } }).whenComplete((ignore, ex) -> { if (ex != null) { diff --git a/bans-env/bungeeplugin/pom.xml b/bans-env/bungeeplugin/pom.xml index faaf669cb..3ea26f3a7 100644 --- a/bans-env/bungeeplugin/pom.xml +++ b/bans-env/bungeeplugin/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-env - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-env-bungeeplugin @@ -25,7 +25,6 @@ space.arim.libertybans bans-bootstrap - provided net.md-5 diff --git a/bans-env/bungeeplugin/src/main/java/space/arim/libertybans/env/bungee/PluginClassLoaderReflection.java b/bans-env/bungeeplugin/src/main/java/space/arim/libertybans/env/bungee/PluginClassLoaderReflection.java index e17e07cf6..1ccee0f44 100644 --- a/bans-env/bungeeplugin/src/main/java/space/arim/libertybans/env/bungee/PluginClassLoaderReflection.java +++ b/bans-env/bungeeplugin/src/main/java/space/arim/libertybans/env/bungee/PluginClassLoaderReflection.java @@ -34,7 +34,7 @@ static String getProvidingPlugin(Class clazz) { return desc.getName() + " v" + desc.getVersion(); } } catch (IllegalArgumentException | NoSuchFieldException | SecurityException | IllegalAccessException ignored) {} - return null; + return ""; } } diff --git a/bans-env/pom.xml b/bans-env/pom.xml index 92ad775e8..7c6fbee07 100644 --- a/bans-env/pom.xml +++ b/bans-env/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-parent - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-env @@ -15,10 +15,7 @@ 1.8.8-R0.1-SNAPSHOT 1.15-SNAPSHOT - 7.2.0 1.1.0-SNAPSHOT - - 0.1.0-SNAPSHOT @@ -26,7 +23,6 @@ spigotplugin bungee bungeeplugin - sponge velocity velocityplugin @@ -43,17 +39,12 @@ bungeecord-api ${bungeecord.version} - - org.spongepowered - spongeapi - ${sponge.version} - com.velocitypowered velocity-api ${velocity.version} - + space.arim.morepaperlib morepaperlib diff --git a/bans-env/spigot/pom.xml b/bans-env/spigot/pom.xml index de26dd0f4..69ef6e01c 100644 --- a/bans-env/spigot/pom.xml +++ b/bans-env/spigot/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-env - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-env-spigot diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChatListener.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChatListener.java index ae1896df5..5b331b3dd 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChatListener.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ChatListener.java @@ -18,11 +18,14 @@ */ package space.arim.libertybans.env.spigot; +import java.net.InetAddress; + import space.arim.omnibus.util.concurrent.CentralisedFuture; import space.arim.api.chat.SendableMessage; -import space.arim.libertybans.core.config.SyncEnforcement; +import space.arim.libertybans.core.punish.MiscUtil; +import space.arim.libertybans.core.selector.SyncEnforcement; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; @@ -51,8 +54,8 @@ public void onCommandLow(PlayerCommandPreprocessEvent evt) { private void combinedBegin(PlayerEvent evt, String command) { Player player = evt.getPlayer(); - byte[] address = player.getAddress().getAddress().getAddress(); - begin(evt, env.core.getEnforcer().checkChat(player.getUniqueId(), address, command)); + InetAddress address = player.getAddress().getAddress(); + begin(evt, env.core.getEnforcementCenter().checkChat(player.getUniqueId(), address, command)); } @EventHandler(priority = EventPriority.HIGH) @@ -68,24 +71,23 @@ public void onCommandHigh(PlayerCommandPreprocessEvent evt) { private void combinedWithdraw(PlayerEvent evt) { CentralisedFuture futureMessage = withdrawRaw(evt); SendableMessage message; - if (futureMessage.isDone() || evt.isAsynchronous()) { + if (evt.isAsynchronous() || futureMessage.isDone()) { message = futureMessage.join(); } else { - SyncEnforcement strategy = env.core.getConfigs().getConfig().getObject( - "enforcement.sync-events-strategy", SyncEnforcement.class); + SyncEnforcement strategy = env.core.getMainConfig().enforcement().syncEnforcement(); switch (strategy) { case WAIT: message = futureMessage.join(); break; case ALLOW: - futureMessage.whenComplete(env.core::debugFuture); + env.core.postFuture(futureMessage); return; case DENY: - futureMessage.whenComplete(env.core::debugFuture); - message = env.core.getFormatter().parseMessage(env.core.getConfigs().getMessages().getString("misc.sync-denial-message")); + env.core.postFuture(futureMessage); + message = env.core.getMessagesConfig().misc().syncDenialMessage(); break; default: - throw new IllegalStateException("Unknown SyncEnforcement strategy " + strategy); + throw MiscUtil.unknownSyncEnforcement(strategy); } } if (message == null) { diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/CommandHandler.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/CommandHandler.java index 615299c87..2ad5c7f6c 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/CommandHandler.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/CommandHandler.java @@ -39,7 +39,9 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; +import org.bukkit.command.SimpleCommandMap; import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; class CommandHandler extends BukkitCommandSkeleton implements PlatformListener { @@ -48,8 +50,8 @@ class CommandHandler extends BukkitCommandSkeleton implements PlatformListener { private static final Logger logger = LoggerFactory.getLogger(ThisClass.get()); - static final String COMMAND_MAP_WARNING = - "LibertyBans has limited support for this situation - the plugin will continue to work, but restarting it may" + private static final String COMMAND_MAP_WARNING = + "LibertyBans has limited support for this situation - the plugin will continue to work, but restarting it may " + "encounter weird issues with command registration, as well as potential memory leaks."; CommandHandler(SpigotEnv env, String command, boolean alias) { @@ -58,6 +60,37 @@ class CommandHandler extends BukkitCommandSkeleton implements PlatformListener { this.alias = alias; } + static Field getKnownCommandsField(CommandMap commandMap) { + if (!(commandMap instanceof SimpleCommandMap)) { + /* + * CommandMap was replaced by a criminal plugin + */ + Class replacementClass = commandMap.getClass(); + String pluginName = "Unknown"; + try { + pluginName = JavaPlugin.getProvidingPlugin(replacementClass).getDescription().getFullName(); + } catch (IllegalArgumentException ignored) {} + logger.warn( + "Your server's CommandMap is not an instance of SimpleCommandMap. Rather, it is {} from plugin {}. " + + "This could be disastrous and you should remove the offending plugin or speak to its author(s), " + + "as many plugins assume SimpleCommandMap as the norm. " + + COMMAND_MAP_WARNING, + replacementClass, pluginName); + return null; + } + Field knownCommandsField; + try { + knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + + } catch (NoSuchFieldException | SecurityException ex) { + logger.warn( + "Unable to find your server's CommandMap's 'knownCommands' field. " + + COMMAND_MAP_WARNING, ex); + knownCommandsField = null; + } + return knownCommandsField; + } + @Override public void register() { env.getCommandMap().register(getName(), env.getPlugin().getName().toLowerCase(Locale.ENGLISH), this); diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ConnectionListener.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ConnectionListener.java index 224b1efe6..f48562ce9 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ConnectionListener.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/ConnectionListener.java @@ -18,6 +18,7 @@ */ package space.arim.libertybans.env.spigot; +import java.net.InetAddress; import java.util.UUID; import org.slf4j.Logger; @@ -26,6 +27,7 @@ import space.arim.omnibus.util.ThisClass; import space.arim.api.chat.SendableMessage; +import space.arim.api.chat.serialiser.LegacyCodeSerialiser; import org.bukkit.ChatColor; import org.bukkit.event.EventHandler; @@ -49,13 +51,13 @@ public void onConnectLow(AsyncPlayerPreLoginEvent evt) { } UUID uuid = evt.getUniqueId(); String name = evt.getName(); - byte[] address = evt.getAddress().getAddress(); - begin(evt, env.core.getEnforcer().executeAndCheckConnection(uuid, name, address)); + InetAddress address = evt.getAddress(); + begin(evt, env.core.getEnforcementCenter().executeAndCheckConnection(uuid, name, address)); } @Override protected void absentFutureHandler(AsyncPlayerPreLoginEvent evt) { - if (evt.getLoginResult() != Result.ALLOWED) { + if (evt.getLoginResult() == Result.ALLOWED) { logger.error( "Critical: Player ({}, {}, {}) was previously blocked by the server or another plugin, " + "but since then, some plugin has *uncancelled* the blocking. " @@ -73,7 +75,7 @@ public void onConnectHigh(AsyncPlayerPreLoginEvent evt) { logger.trace("Letting '{}' through the gates", evt.getName()); return; } - evt.disallow(Result.KICK_BANNED, message.toLegacyMessageString(ChatColor.COLOR_CHAR)); + evt.disallow(Result.KICK_BANNED, LegacyCodeSerialiser.getInstance(ChatColor.COLOR_CHAR).serialise(message)); } } diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotCmdSender.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotCmdSender.java index 5229f0bc2..d13d5396a 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotCmdSender.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotCmdSender.java @@ -34,7 +34,7 @@ abstract class SpigotCmdSender extends AbstractCmdSender { @Override public boolean hasPermission(String permission) { - return getRawSender().hasPermission(permission); + return core().getFuturesFactory().supplySync(() -> getRawSender().hasPermission(permission)).join(); } @Override diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java index 76c4f351a..b08179cb7 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnforcer.java @@ -18,6 +18,7 @@ */ package space.arim.libertybans.env.spigot; +import java.net.InetAddress; import java.util.UUID; import java.util.function.Consumer; @@ -32,7 +33,7 @@ class SpigotEnforcer extends AbstractEnvEnforcer { SpigotEnforcer(SpigotEnv env) { - super(env); + super(env.core, env); } @Override @@ -45,7 +46,7 @@ private void runSyncNow(Runnable command) { } @Override - public void sendToThoseWithPermission(String permission, SendableMessage message) { + protected void sendToThoseWithPermission0(String permission, SendableMessage message) { runSyncNow(() -> { for (Player player : env().getPlugin().getServer().getOnlinePlayers()) { if (player.hasPermission(permission)) { @@ -69,8 +70,7 @@ public void doForPlayerIfOnline(UUID uuid, Consumer<@PlatformPlayer Object> call public void enforceMatcher(TargetMatcher matcher) { runSyncNow(() -> { for (Player player : env().getPlugin().getServer().getOnlinePlayers()) { - if (matcher.uuids().contains(player.getUniqueId()) - || matcher.addresses().contains(player.getAddress().getAddress())) { + if (matcher.matches(player.getUniqueId(), player.getAddress().getAddress())) { matcher.callback().accept(player); } } @@ -83,8 +83,8 @@ public UUID getUniqueIdFor(@PlatformPlayer Object player) { } @Override - public byte[] getAddressFor(@PlatformPlayer Object player) { - return ((Player) player).getAddress().getAddress().getAddress(); + public InetAddress getAddressFor(@PlatformPlayer Object player) { + return ((Player) player).getAddress().getAddress(); } } diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java index 11e0c8042..99abf7231 100644 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java +++ b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotEnv.java @@ -42,8 +42,6 @@ import space.arim.morepaperlib.MorePaperLib; import org.bukkit.command.CommandMap; -import org.bukkit.command.SimpleCommandMap; -import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; public class SpigotEnv extends AbstractEnv { @@ -59,41 +57,25 @@ public class SpigotEnv extends AbstractEnv { private final SpigotEnforcer enforcer; public SpigotEnv(JavaPlugin plugin, Path folder) { + setUUIDVaultIfNecessary(plugin); + core = new LibertyBansCore(OmnibusProvider.getOmnibus(), folder, this); handle = new BukkitPlatformHandle(plugin); enforcer = new SpigotEnforcer(this); commandMap = new MorePaperLib(plugin).getServerCommandMap(); - knownCommandsField = getKnownCommandsField(commandMap); + knownCommandsField = CommandHandler.getKnownCommandsField(commandMap); } - private static Field getKnownCommandsField(CommandMap commandMap) { - if (commandMap instanceof SimpleCommandMap) { - Field knownCommandsField; - try { - knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); - - } catch (NoSuchFieldException | SecurityException ex) { - logger.warn( - "Unable to find your server's CommandMap's 'knownCommands' field. " - + CommandHandler.COMMAND_MAP_WARNING, ex); - knownCommandsField = null; - } - return knownCommandsField; - } else { - Class replacementClass = commandMap.getClass(); - String pluginName = "Unknown"; - try { - pluginName = JavaPlugin.getProvidingPlugin(replacementClass).getDescription().getFullName(); - } catch (IllegalArgumentException ignored) {} - logger.warn( - "Your server's CommandMap is not an instance of SimpleCommandMap. Rather, it is {} from plugin {}. " - + "This could be disastrous and you should remove the offending plugin or speak to its author(s), " - + "as many plugins assume SimpleCommandMap as the norm. " - + CommandHandler.COMMAND_MAP_WARNING, - replacementClass, pluginName); - return null; + private static void setUUIDVaultIfNecessary(JavaPlugin plugin) { + if (UUIDVault.get() == null) { + new UUIDVaultSpigot(plugin) { + @Override + protected boolean setInstancePassive() { + return super.setInstancePassive(); + } + }.setInstancePassive(); } } @@ -121,14 +103,6 @@ public SpigotEnforcer getEnforcer() { @Override protected void startup0() { - if (UUIDVault.get() == null) { - new UUIDVaultSpigot(getPlugin()) { - @Override - protected boolean setInstancePassive() { - return super.setInstancePassive(); - } - }.setInstancePassive(); - } core.startup(); } @@ -146,11 +120,6 @@ protected void shutdown0() { protected void infoMessage(String message) { logger.info(message); } - - boolean hasPermissionSafe(Player player, String permission) { - // TODO: Account for non-thread safe permission plugins - return player.hasPermission(permission); - } @Override public Set createListeners() { diff --git a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotOnlineTarget.java b/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotOnlineTarget.java deleted file mode 100644 index bf3113f44..000000000 --- a/bans-env/spigot/src/main/java/space/arim/libertybans/env/spigot/SpigotOnlineTarget.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * LibertyBans-env-spigot - * Copyright © 2020 Anand Beh - * - * LibertyBans-env-spigot is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-env-spigot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-env-spigot. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.env.spigot; - -import java.util.UUID; - -import org.bukkit.entity.Player; - -import space.arim.api.chat.SendableMessage; - -import space.arim.libertybans.core.env.AbstractOnlineTarget; - -class SpigotOnlineTarget extends AbstractOnlineTarget { - - private final SpigotEnv env; - /* - * Construction happens on main thread in SpigotEnv, but calls to interface methods - * need to be thread safe. So these values must be cached - */ - private final UUID uuid; - private final byte[] address; - - SpigotOnlineTarget(SpigotEnv env, Player player) { - super(env, player); - this.env = env; - uuid = player.getUniqueId(); - address = player.getAddress().getAddress().getAddress(); - } - - @Override - public boolean hasPermission(String permission) { - return env.hasPermissionSafe(getRawPlayer(), permission); - } - - @Override - public UUID getUniqueId() { - return uuid; - } - - @Override - public byte[] getAddress() { - return address; - } - - @Override - public void kick(SendableMessage message) { - env.core.getFuturesFactory().executeSync(() -> super.kick(message)); - } - - @Override - public Player getRawPlayer() { - return (Player) super.getRawPlayer(); - } - -} diff --git a/bans-env/spigotplugin/pom.xml b/bans-env/spigotplugin/pom.xml index 017bbca1e..a7e716589 100644 --- a/bans-env/spigotplugin/pom.xml +++ b/bans-env/spigotplugin/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-env - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-env-spigotplugin @@ -25,7 +25,6 @@ space.arim.libertybans bans-bootstrap - provided org.spigotmc diff --git a/bans-env/spigotplugin/src/main/java/space/arim/libertybans/env/spigot/SpigotPlugin.java b/bans-env/spigotplugin/src/main/java/space/arim/libertybans/env/spigot/SpigotPlugin.java index 6a4c115a0..0826210a1 100644 --- a/bans-env/spigotplugin/src/main/java/space/arim/libertybans/env/spigot/SpigotPlugin.java +++ b/bans-env/spigotplugin/src/main/java/space/arim/libertybans/env/spigot/SpigotPlugin.java @@ -57,7 +57,7 @@ public synchronized void onEnable() { JavaPlugin potential = JavaPlugin.getProvidingPlugin(clazz); return potential.getDescription().getFullName(); } catch (IllegalArgumentException ignored) {} - return null; + return ""; }); launchLoader = launcher.attemptLaunch().join(); } finally { diff --git a/bans-env/sponge/pom.xml b/bans-env/sponge/pom.xml deleted file mode 100644 index 6f41af90f..000000000 --- a/bans-env/sponge/pom.xml +++ /dev/null @@ -1,32 +0,0 @@ - - 4.0.0 - - - space.arim.libertybans - bans-env - 0.1.4-SNAPSHOT - - - bans-env-sponge - - src/main/java - ${project.name}_${project.version} - - - - - org.spongepowered - spongeapi - provided - - - - - - sponge-repo - https://repo.spongepowered.org/maven - - - \ No newline at end of file diff --git a/bans-env/spongeplugin/pom.xml b/bans-env/spongeplugin/pom.xml deleted file mode 100644 index 3206e5e67..000000000 --- a/bans-env/spongeplugin/pom.xml +++ /dev/null @@ -1,52 +0,0 @@ - - 4.0.0 - - - space.arim.libertybans - bans-env - 0.1.4-SNAPSHOT - - - bans-env-spongeplugin - - src/main/java - ${project.name}_${project.version} - - - org.codehaus.mojo - templating-maven-plugin - 1.0.0 - - - add-sponge-info - - filter-sources - - - - - - - - - - space.arim.libertybans - bans-bootstrap - provided - - - org.spongepowered - spongeapi - provided - - - - - - sponge-repo - https://repo.spongepowered.org/maven - - - \ No newline at end of file diff --git a/bans-env/spongeplugin/src/main/java/space/arim/libertybans/env/sponge/SpongePlugin.java b/bans-env/spongeplugin/src/main/java/space/arim/libertybans/env/sponge/SpongePlugin.java deleted file mode 100644 index b74a29cd7..000000000 --- a/bans-env/spongeplugin/src/main/java/space/arim/libertybans/env/sponge/SpongePlugin.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * LibertyBans-env-spongeplugin - * Copyright © 2020 Anand Beh - * - * LibertyBans-env-spongeplugin is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-env-spongeplugin is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-env-spongeplugin. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.env.sponge; - -import space.arim.libertybans.bootstrap.plugin.PluginInfo; - -import org.spongepowered.api.plugin.Plugin; - -@Plugin(id = PluginInfo.ANNOTE_ID, name = PluginInfo.NAME, version = PluginInfo.VERSION, authors = { - "A248" }, description = PluginInfo.DESCRIPTION, url = PluginInfo.URL) -public class SpongePlugin { - -} diff --git a/bans-env/velocity/pom.xml b/bans-env/velocity/pom.xml index 07b515bf0..b10a0a222 100644 --- a/bans-env/velocity/pom.xml +++ b/bans-env/velocity/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-env - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-env-velocity diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java index dbcccea95..a78480227 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ChatListener.java @@ -40,7 +40,7 @@ public void register() { @Override protected CentralisedFuture beginFor(PlayerChatEvent evt) { Player player = evt.getPlayer(); - return env.core.getEnforcer().checkChat(player.getUniqueId(), player.getRemoteAddress().getAddress().getAddress(), null); + return env.core.getEnforcementCenter().checkChat(player.getUniqueId(), player.getRemoteAddress().getAddress(), null); } @Override diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java index 7966178ed..cde7f3866 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/CommandListener.java @@ -45,7 +45,7 @@ protected CentralisedFuture beginFor(CommandExecuteEvent evt) { return null; } Player player = (Player) source; - return env.core.getEnforcer().checkChat(player.getUniqueId(), player.getRemoteAddress().getAddress().getAddress(), evt.getCommand()); + return env.core.getEnforcementCenter().checkChat(player.getUniqueId(), player.getRemoteAddress().getAddress(), evt.getCommand()); } @Override diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java index fce25d9e2..e0486c105 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/ConnectionListener.java @@ -18,6 +18,7 @@ */ package space.arim.libertybans.env.velocity; +import java.net.InetAddress; import java.util.UUID; import org.slf4j.Logger; @@ -55,8 +56,8 @@ protected CentralisedFuture beginFor(LoginEvent evt) { Player player = evt.getPlayer(); UUID uuid = player.getUniqueId(); String name = player.getUsername(); - byte[] address = player.getRemoteAddress().getAddress().getAddress(); - return env.core.getEnforcer().executeAndCheckConnection(uuid, name, address); + InetAddress address = player.getRemoteAddress().getAddress(); + return env.core.getEnforcementCenter().executeAndCheckConnection(uuid, name, address); } @Override @@ -80,7 +81,7 @@ protected void withdrawFor(LoginEvent evt) { logger.trace("Letting '{}' through the gates", evt.getPlayer().getUsername()); return; } - evt.setResult(ComponentResult.denied(new AdventureTextConverter().convertFrom(message))); + evt.setResult(ComponentResult.denied(new AdventureTextConverter().convert(message))); } } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java index 7773cf77b..ac62be6a8 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnforcer.java @@ -18,6 +18,7 @@ */ package space.arim.libertybans.env.velocity; +import java.net.InetAddress; import java.util.UUID; import java.util.function.Consumer; @@ -32,7 +33,7 @@ class VelocityEnforcer extends AbstractEnvEnforcer { VelocityEnforcer(VelocityEnv env) { - super(env); + super(env.core, env); } @Override @@ -41,7 +42,7 @@ protected VelocityEnv env() { } @Override - public void sendToThoseWithPermission(String permission, SendableMessage message) { + protected void sendToThoseWithPermission0(String permission, SendableMessage message) { for (Player player : env().getServer().getAllPlayers()) { if (player.hasPermission(permission)) { env().getPlatformHandle().sendMessage(player, message); @@ -57,8 +58,7 @@ public void doForPlayerIfOnline(UUID uuid, Consumer<@PlatformPlayer Object> call @Override public void enforceMatcher(TargetMatcher matcher) { for (Player player : env().getServer().getAllPlayers()) { - if (matcher.uuids().contains(player.getUniqueId()) - || matcher.addresses().contains(player.getRemoteAddress().getAddress())) { + if (matcher.matches(player.getUniqueId(), player.getRemoteAddress().getAddress())) { matcher.callback().accept(player); } } @@ -70,8 +70,8 @@ public UUID getUniqueIdFor(@PlatformPlayer Object player) { } @Override - public byte[] getAddressFor(@PlatformPlayer Object player) { - return ((Player) player).getRemoteAddress().getAddress().getAddress(); + public InetAddress getAddressFor(@PlatformPlayer Object player) { + return ((Player) player).getRemoteAddress().getAddress(); } } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java index 2e1fc1c22..ee6e13e85 100644 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java +++ b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityEnv.java @@ -54,6 +54,7 @@ public class VelocityEnv extends AbstractEnv { public VelocityEnv(Entry pluginAndServer, Path folder) { PluginContainer plugin = pluginAndServer.getKey(); ProxyServer server = pluginAndServer.getValue(); + setUUIDVaultIfNecessary(server); core = new LibertyBansCore(OmnibusProvider.getOmnibus(), folder, this); handle = new VelocityPlatformHandle(plugin, server); @@ -61,6 +62,17 @@ public VelocityEnv(Entry pluginAndServer, Path fol enforcer = new VelocityEnforcer(this); } + private static void setUUIDVaultIfNecessary(ProxyServer server) { + if (UUIDVault.get() == null) { + new UUIDVaultVelocity(server, logger) { + @Override + protected boolean setInstancePassive() { + return super.setInstancePassive(); + } + }.setInstancePassive(); + } + } + PluginContainer getPlugin() { return handle.getPlugin(); } @@ -81,14 +93,6 @@ public VelocityEnforcer getEnforcer() { @Override protected void startup0() { - if (UUIDVault.get() == null) { - new UUIDVaultVelocity(getServer(), logger) { - @Override - protected boolean setInstancePassive() { - return super.setInstancePassive(); - } - }.setInstancePassive(); - } core.startup(); } diff --git a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityOnlineTarget.java b/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityOnlineTarget.java deleted file mode 100644 index 903148f1b..000000000 --- a/bans-env/velocity/src/main/java/space/arim/libertybans/env/velocity/VelocityOnlineTarget.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * LibertyBans-env-velocity - * Copyright © 2020 Anand Beh - * - * LibertyBans-env-velocity is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * LibertyBans-env-velocity is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with LibertyBans-env-velocity. If not, see - * and navigate to version 3 of the GNU Affero General Public License. - */ -package space.arim.libertybans.env.velocity; - -import java.util.UUID; - -import space.arim.libertybans.core.env.AbstractOnlineTarget; -import space.arim.libertybans.core.env.Environment; - -import com.velocitypowered.api.proxy.Player; - -class VelocityOnlineTarget extends AbstractOnlineTarget { - - VelocityOnlineTarget(Environment env, Player player) { - super(env, player); - } - - @Override - public boolean hasPermission(String permission) { - return getRawPlayer().hasPermission(permission); - } - - @Override - public UUID getUniqueId() { - return getRawPlayer().getUniqueId(); - } - - @Override - public byte[] getAddress() { - return getRawPlayer().getRemoteAddress().getAddress().getAddress(); - } - - @Override - public Player getRawPlayer() { - return (Player) super.getRawPlayer(); - } - -} diff --git a/bans-env/velocityplugin/pom.xml b/bans-env/velocityplugin/pom.xml index e990b9a32..0e8b7c19d 100644 --- a/bans-env/velocityplugin/pom.xml +++ b/bans-env/velocityplugin/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-env - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-env-velocityplugin @@ -34,7 +34,6 @@ space.arim.libertybans bans-bootstrap - provided com.velocitypowered diff --git a/bans-env/velocityplugin/src/main/java/space/arim/libertybans/env/velocity/LibertyBansLauncherVelocity.java b/bans-env/velocityplugin/src/main/java/space/arim/libertybans/env/velocity/LibertyBansLauncherVelocity.java index 234fa6f5e..2e9b5c739 100644 --- a/bans-env/velocityplugin/src/main/java/space/arim/libertybans/env/velocity/LibertyBansLauncherVelocity.java +++ b/bans-env/velocityplugin/src/main/java/space/arim/libertybans/env/velocity/LibertyBansLauncherVelocity.java @@ -32,7 +32,7 @@ public class LibertyBansLauncherVelocity extends LibertyBansLauncher { private final VelocityPlugin plugin; public LibertyBansLauncherVelocity(VelocityPlugin plugin, Executor executor) { - super(DependencyPlatform.VELOCITY, plugin.folder, executor, (c) -> null); + super(DependencyPlatform.VELOCITY, plugin.folder, executor, (c) -> ""); this.plugin = plugin; } diff --git a/bans-integrationtest/pom.xml b/bans-integrationtest/pom.xml index 9623323f3..2a1d10457 100644 --- a/bans-integrationtest/pom.xml +++ b/bans-integrationtest/pom.xml @@ -6,7 +6,7 @@ space.arim.libertybans bans-parent - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT bans-integrationtest @@ -47,22 +47,14 @@ ch.vorburger.mariaDB4j mariaDB4j - test org.hsqldb hsqldb - test org.mariadb.jdbc mariadb-java-client - test - - - space.arim.omnibus - omnibus-all-shaded - test \ No newline at end of file diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/core/LibertyBansCoreOverride.java b/bans-integrationtest/src/test/java/space/arim/libertybans/core/LibertyBansCoreOverride.java index 908ba81ad..2f0889581 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/core/LibertyBansCoreOverride.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/core/LibertyBansCoreOverride.java @@ -33,7 +33,7 @@ public class LibertyBansCoreOverride extends LibertyBansCore { public LibertyBansCoreOverride(Omnibus omnibus, Path folder, AbstractEnv environment, ConfigSpec spec) { super(omnibus, folder, environment); - configs = new ConfigsOverride(this, spec); + configs = new ConfigsOverride(folder, spec); } @Override diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/core/config/ConfigsOverride.java b/bans-integrationtest/src/test/java/space/arim/libertybans/core/config/ConfigsOverride.java index 754bf8b52..e07cac6a7 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/core/config/ConfigsOverride.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/core/config/ConfigsOverride.java @@ -18,47 +18,80 @@ */ package space.arim.libertybans.core.config; -import java.util.ArrayList; -import java.util.List; +import java.nio.file.Path; -import space.arim.api.configure.SingleKeyValueTransformer; -import space.arim.api.configure.ValueTransformer; - -import space.arim.libertybans.core.LibertyBansCoreOverride; +import space.arim.libertybans.core.selector.EnforcementConfig; import space.arim.libertybans.it.ConfigSpec; public class ConfigsOverride extends Configs { private final ConfigSpec spec; - public ConfigsOverride(LibertyBansCoreOverride core, ConfigSpec spec) { - super(core); + public ConfigsOverride(Path folder, ConfigSpec spec) { + super(folder); this.spec = spec; } @Override - List sqlValueTransformers() { - return prepend(super.sqlValueTransformers(), - SingleKeyValueTransformer.create("rdms-vendor", (oldVendor) -> spec.getVendor()), - SingleKeyValueTransformer.create("auth-details.port", (oldPort) -> spec.getPort()), - SingleKeyValueTransformer.create("auth-details.user", (oldUser) -> "root"), - SingleKeyValueTransformer.create("auth-details.password", (oldPass) -> ""), - SingleKeyValueTransformer.create("auth-details.database", (oldDb) -> spec.getDatabase())); + public SqlConfig getSqlConfig() { + return new Delegator<>(SqlConfig.class, super.getSqlConfig()) { + @Override + Object replacementFor(SqlConfig original, String methodName) { + if (methodName.equals("vendor")) { + return spec.getVendor(); + } + if (methodName.equals("authDetails")) { + return new SqlConfig.AuthDetails() { + @Override + public String host() { + return "localhost"; + } + @Override + public int port() { + return spec.getPort(); + } + @Override + public String database() { + return spec.getDatabase(); + } + @Override + public String username() { + return "root"; + } + @Override + public String password() { + return ""; + } + }; + } + return null; + } + }.proxy(); } @Override - List configValueTransformers() { - var replacement = SingleKeyValueTransformer.create("enforcement.address-strictness", - (oldStrictness) -> spec.getAddressStrictness()); - return prepend(super.configValueTransformers(), replacement); - } - - private static List prepend(List transformers, ValueTransformer...elements) { - List result = new ArrayList<>(transformers); - for (ValueTransformer prepend : elements) { - result.add(0, prepend); - } - return List.copyOf(result); + public MainConfig getMainConfig() { + return new Delegator<>(MainConfig.class, super.getMainConfig()) { + @Override + Object replacementFor(MainConfig original, String methodName) { + if (methodName.equals("enforcement")) { + return enforcement(original); + } + return null; + } + private EnforcementConfig enforcement(MainConfig original) { + return new Delegator<>(EnforcementConfig.class, original.enforcement()) { + @Override + Object replacementFor(EnforcementConfig original, String methodName) { + if (methodName.equals("addressStrictness")) { + return spec.getAddressStrictness(); + } + return null; + } + }.proxy(); + } + + }.proxy(); } } diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/core/config/Delegator.java b/bans-integrationtest/src/test/java/space/arim/libertybans/core/config/Delegator.java new file mode 100644 index 000000000..2647c293b --- /dev/null +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/core/config/Delegator.java @@ -0,0 +1,50 @@ +/* + * LibertyBans-integrationtest + * Copyright © 2020 Anand Beh + * + * LibertyBans-integrationtest is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LibertyBans-integrationtest is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with LibertyBans-integrationtest. If not, see + * and navigate to version 3 of the GNU Affero General Public License. + */ +package space.arim.libertybans.core.config; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +abstract class Delegator implements InvocationHandler { + + private final Class clazz; + private final T delegate; + + Delegator(Class clazz, T delegate) { + this.clazz = clazz; + this.delegate = delegate; + } + + T proxy() { + return clazz.cast(Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] {clazz}, this)); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object replacement = replacementFor(delegate, method.getName()); + if (replacement != null) { + return replacement; + } + return method.invoke(delegate, args); + } + + abstract Object replacementFor(T original, String methodName); + +} diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/ConfigConstraints.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/ConfigConstraints.java index 402f2f0ff..221a42f0a 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/ConfigConstraints.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/ConfigConstraints.java @@ -23,7 +23,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; import space.arim.libertybans.core.database.Vendor; -import space.arim.libertybans.core.config.AddressStrictness; +import space.arim.libertybans.core.selector.AddressStrictness; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/ConfigSpec.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/ConfigSpec.java index a7ac65236..0c537381b 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/ConfigSpec.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/ConfigSpec.java @@ -22,8 +22,8 @@ import java.util.Objects; import java.util.Set; -import space.arim.libertybans.core.config.AddressStrictness; import space.arim.libertybans.core.database.Vendor; +import space.arim.libertybans.core.selector.AddressStrictness; public class ConfigSpec { diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackEnv.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackEnv.java index de692921e..e971ab3e7 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackEnv.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackEnv.java @@ -26,7 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import space.arim.omnibus.defaultimpl.DefaultOmnibus; +import space.arim.omnibus.OmnibusProvider; import space.arim.omnibus.util.ThisClass; import space.arim.uuidvault.api.UUIDVault; @@ -44,7 +44,7 @@ public class QuackEnv extends AbstractEnv implements CloseableResource { - private final LibertyBansCore core; + final LibertyBansCore core; private final QuackPlatform quackPlatform; private final QuackHandle handle; private final QuackEnvEnforcer enforcer; @@ -54,7 +54,7 @@ public class QuackEnv extends AbstractEnv implements CloseableResource { public QuackEnv(Path folder, ConfigSpec spec) { quackPlatform = new QuackPlatform(); - core = new LibertyBansCoreOverride(new DefaultOmnibus(), folder, this, spec); + core = new LibertyBansCoreOverride(OmnibusProvider.getOmnibus(), folder, this, spec); handle = new QuackHandle(quackPlatform); enforcer = new QuackEnvEnforcer(this, quackPlatform); uuidVault = new QuackUUIDVault(quackPlatform); diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackEnvEnforcer.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackEnvEnforcer.java index 77d533e17..d0732f311 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackEnvEnforcer.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackEnvEnforcer.java @@ -18,6 +18,7 @@ */ package space.arim.libertybans.it.env; +import java.net.InetAddress; import java.util.UUID; import java.util.function.Consumer; @@ -34,12 +35,12 @@ public class QuackEnvEnforcer extends AbstractEnvEnforcer { private final QuackPlatform platform; QuackEnvEnforcer(QuackEnv env, QuackPlatform platform) { - super(env); + super(env.core, env); this.platform = platform; } @Override - public void sendToThoseWithPermission(String permission, SendableMessage message) { + protected void sendToThoseWithPermission0(String permission, SendableMessage message) { for (QuackPlayer player : platform.getAllPlayers()) { if (player.hasPermission(permission)) { player.sendMessage(message); @@ -58,7 +59,7 @@ public void doForPlayerIfOnline(UUID uuid, Consumer<@PlatformPlayer Object> call @Override public void enforceMatcher(TargetMatcher matcher) { for (QuackPlayer player : platform.getAllPlayers()) { - if (matcher.uuids().contains(player.getUniqueId()) || matcher.addresses().contains(player.getAddress())) { + if (matcher.matches(player.getUniqueId(), player.getAddress())) { matcher.callback().accept(player); } } @@ -70,8 +71,8 @@ public UUID getUniqueIdFor(@PlatformPlayer Object player) { } @Override - public byte[] getAddressFor(@PlatformPlayer Object player) { - return ((QuackPlayer) player).getAddress().getAddress(); + public InetAddress getAddressFor(@PlatformPlayer Object player) { + return ((QuackPlayer) player).getAddress(); } } diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackHandle.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackHandle.java index af6fedcdf..383f715ea 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackHandle.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/QuackHandle.java @@ -21,10 +21,6 @@ import java.util.concurrent.Executor; import java.util.function.Consumer; -import space.arim.omnibus.resourcer.ResourceHook; -import space.arim.omnibus.resourcer.ResourceInfo; -import space.arim.omnibus.resourcer.Resourcer; -import space.arim.omnibus.resourcer.ShutdownHandler; import space.arim.omnibus.util.concurrent.EnhancedExecutor; import space.arim.omnibus.util.concurrent.FactoryOfTheFuture; import space.arim.omnibus.util.concurrent.impl.IndifferentFactoryOfTheFuture; @@ -33,7 +29,6 @@ import space.arim.api.chat.SendableMessage; import space.arim.api.env.PlatformHandle; import space.arim.api.env.PlatformPluginInfo; -import space.arim.api.env.PlatformType; import space.arim.api.env.annote.PlatformCommandSender; import space.arim.api.env.annote.PlatformPlayer; import space.arim.api.env.realexecutor.RealExecutorFinder; @@ -48,29 +43,6 @@ public class QuackHandle implements PlatformHandle { QuackHandle(QuackPlatform platform) { this.platform = platform; } - - @SuppressWarnings("unchecked") - @Override - public ResourceHook hookPlatformResource(Resourcer resourcer, Class resourceClass) { - if (resourceClass == EnhancedExecutor.class) { - return resourcer.hookUsage(resourceClass, () -> { - EnhancedExecutor ee = new SimplifiedEnhancedExecutor() { - @Override - public void execute(Runnable command) { - new Thread(command).start(); - } - }; - return new ResourceInfo("QuackPlatformEnhancedExecutor", (T) ee, ShutdownHandler.none()); - }); - } - if (resourceClass == FactoryOfTheFuture.class) { - return resourcer.hookUsage(resourceClass, () -> { - return new ResourceInfo("QuackPlatformFuturesFactory", - (T) new IndifferentFactoryOfTheFuture(), ShutdownHandler.none()); - }); - } - throw new IllegalArgumentException(); - } @Override public void sendMessage(@PlatformCommandSender Object recipient, SendableMessage message) { @@ -94,16 +66,24 @@ public Executor findExecutor(Consumer exceptionHandler) { }; } - @SuppressWarnings("deprecation") - @Deprecated @Override - public PlatformType getPlatformType() { - throw new UnsupportedOperationException(); + public PlatformPluginInfo getImplementingPluginInfo() { + return new PlatformPluginInfo(this, platform); } @Override - public PlatformPluginInfo getImplementingPluginInfo() { - return new PlatformPluginInfo(this, platform); + public FactoryOfTheFuture createFuturesFactory() { + return new IndifferentFactoryOfTheFuture(); + } + + @Override + public EnhancedExecutor createEnhancedExecutor() { + return new SimplifiedEnhancedExecutor() { + @Override + public void execute(Runnable command) { + new Thread(command).start(); + } + }; } } diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/platform/QuackPlatform.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/platform/QuackPlatform.java index 976bbb48f..992cd7532 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/platform/QuackPlatform.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/platform/QuackPlatform.java @@ -29,6 +29,7 @@ import space.arim.omnibus.util.ThisClass; import space.arim.api.chat.SendableMessage; +import space.arim.api.chat.serialiser.LegacyCodeSerialiser; public class QuackPlatform { @@ -55,7 +56,11 @@ public Collection getAllPlayers() { void remove(QuackPlayer player, SendableMessage msg) { players.values().remove(player); - logger.info("{} was kicked for '{}'", player.getName(), msg.toLegacyMessageString('&')); + logger.info("{} was kicked for '{}'", player.getName(), toDisplay(msg)); + } + + String toDisplay(SendableMessage msg) { + return LegacyCodeSerialiser.getInstance('&').serialise(msg); } } diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java index bdf15ece0..2d6ee3372 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/env/platform/QuackPlayer.java @@ -81,7 +81,7 @@ public boolean hasPermission(String permission) { } public void sendMessage(SendableMessage msg) { - logger.info("{} received '{}'", name, msg.toLegacyMessageString('&')); + logger.info("{} received '{}'", name, platform.toDisplay(msg)); } public void kickPlayer(SendableMessage msg) { diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/ApplicabilityIT.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/ApplicabilityIT.java index ff116b7c2..540360019 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/ApplicabilityIT.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/ApplicabilityIT.java @@ -29,7 +29,7 @@ import space.arim.libertybans.api.PlayerOperator; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.core.LibertyBansCore; -import space.arim.libertybans.core.MiscUtil; +import space.arim.libertybans.core.punish.MiscUtil; import space.arim.libertybans.it.ProfligateInstanceProvider; import space.arim.libertybans.it.test.applicable.ApplicabilityTesting; import space.arim.libertybans.it.test.applicable.NonapplicabilityTesting; diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/ApplicabilityTesting.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/ApplicabilityTesting.java index 4bc201da3..ae9b0081a 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/ApplicabilityTesting.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/ApplicabilityTesting.java @@ -22,16 +22,21 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.Random; +import java.net.InetAddress; import java.util.UUID; +import org.junit.jupiter.api.Assertions; + import space.arim.libertybans.api.AddressVictim; -import space.arim.libertybans.api.DraftPunishment; +import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.PlayerVictim; -import space.arim.libertybans.api.Punishment; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.punish.DraftPunishment; +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.revoke.PunishmentRevoker; +import space.arim.libertybans.api.revoke.RevocationOrder; import space.arim.libertybans.core.LibertyBansCore; import space.arim.libertybans.it.util.TestingUtil; @@ -49,26 +54,32 @@ public void doTest() { private void testApplicableUUID() { UUID uuid = UUID.randomUUID(); - byte[] address = TestingUtil.randomAddress(); - assertNull(core.getEnforcer().executeAndCheckConnection(uuid, "whoknows", address).join()); + InetAddress address = TestingUtil.randomAddress(); + assertNull(core.getEnforcementCenter().executeAndCheckConnection(uuid, "whoknows", address).join()); Victim victim = PlayerVictim.of(uuid); - testApplicable(victim, uuid, address); + testApplicable(victim, uuid, NetworkAddress.of(address)); } private void testApplicableAddress() { UUID uuid = UUID.randomUUID(); - byte[] address = TestingUtil.randomAddress(); - assertNull(core.getEnforcer().executeAndCheckConnection(uuid, "another", address).join()); - Victim victim = AddressVictim.of(address); - testApplicable(victim, uuid, address); + InetAddress address = TestingUtil.randomAddress(); + assertNull(core.getEnforcementCenter().executeAndCheckConnection(uuid, "another", address).join()); + AddressVictim victim = AddressVictim.of(address); + testApplicable(victim, uuid, victim.getAddress()); } - private void testApplicable(Victim victim, UUID uuid, byte[] address) { + private void testApplicable(Victim victim, UUID uuid, NetworkAddress address) { String reason = TestingUtil.randomString(TestingUtil.random().nextInt(100)); - DraftPunishment draft = new DraftPunishment.Builder().type(type).victim(victim) - .operator(operator).reason(reason).scope(core.getScopeManager().globalScope()).build(); - Punishment enacted = core.getEnactor().enactPunishment(draft).join(); + + DraftPunishment draft = core.getDrafter().draftBuilder().type(type).victim(victim) + .operator(operator).reason(reason).build(); + Punishment enacted; + if (TestingUtil.randomBoolean()) { + enacted = draft.enactPunishment().join(); + } else { + enacted = draft.enactPunishmentWithoutEnforcement().join(); + } assertNotNull(enacted, "Initial enactment failed for " + info); TestingUtil.assertEqualDetails(draft, enacted); @@ -81,35 +92,56 @@ private void testApplicable(Victim victim, UUID uuid, byte[] address) { assertNotNull(selected, "Applicability selection failed for " + info); TestingUtil.assertEqualDetails(enacted, selected); - testUndo(victim, enacted); + testUndo(enacted); } - private void testUndo(Victim victim, Punishment enacted) { - Random random = TestingUtil.random(); - if (random.nextBoolean()) { - assertTrue(core.getEnactor().undoPunishment(enacted).join()); + private void testUndo(Punishment enacted) { + int randomFrom1To4 = TestingUtil.random().nextInt(4) + 1; + PunishmentRevoker revoker = core.getRevoker(); + RevocationOrder order; + + switch (randomFrom1To4) { + case 1: + if (TestingUtil.randomBoolean()) { + assertTrue(enacted.undoPunishment().join()); + } else { + assertTrue(enacted.undoPunishmentWithoutUnenforcement().join()); + } return; - } - boolean singularAndChance = type.isSingular() && random.nextBoolean(); - if (random.nextBoolean()) { - boolean result; - if (singularAndChance) { - result = core.getEnactor().undoPunishmentByTypeAndVictim(type, victim).join(); + case 2: + order = revoker.revokeById(enacted.getID()); + break; + case 3: + order = revoker.revokeByIdAndType(enacted.getID(), type); + break; + case 4: + if (type.isSingular()) { + order = revoker.revokeByTypeAndVictim(type, enacted.getVictim()); } else { - result = core.getEnactor().undoPunishmentByIdAndType(enacted.getID(), enacted.getType()).join(); + order = revoker.revokeByIdAndType(enacted.getID(), type); } - assertTrue(result, "Undoing of enacted punishment failed for " + info); - - } else { - + break; + default: + throw Assertions.fail("Testing code is broken"); + } + if (TestingUtil.randomBoolean()) { Punishment retrieved; - if (singularAndChance) { - retrieved = core.getEnactor().undoAndGetPunishmentByTypeAndVictim(type, victim).join(); + if (TestingUtil.randomBoolean()) { + retrieved = order.undoAndGetPunishment().join(); } else { - retrieved = core.getEnactor().undoAndGetPunishmentByIdAndType(enacted.getID(), enacted.getType()).join(); + retrieved = order.undoAndGetPunishmentWithoutUnenforcement().join(); } assertNotNull(retrieved, "Undoing and retrieving of enacted punishment failed for " + info); TestingUtil.assertEqualDetails(enacted, retrieved); + + } else { + boolean result; + if (TestingUtil.randomBoolean()) { + result = order.undoPunishment().join(); + } else { + result = order.undoPunishmentWithoutUnenforcement().join(); + } + assertTrue(result, "Undoing of enacted punishment failed for " + info); } } diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/ApplicabilityTestingBase.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/ApplicabilityTestingBase.java index 0e7976fe2..f15cea478 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/ApplicabilityTestingBase.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/ApplicabilityTestingBase.java @@ -33,7 +33,7 @@ abstract class ApplicabilityTestingBase { this.core = core; this.type = type; this.operator = operator; - info = type + "/" + core.getDatabase().getVendor() + '/' + core.getConfigs().getAddressStrictness(); + info = type + "/" + core.getDatabase().getVendor() + '/' + core.getMainConfig().enforcement().addressStrictness(); } public abstract void doTest(); diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/NonapplicabilityTesting.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/NonapplicabilityTesting.java index d34ac4937..0df1b9199 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/NonapplicabilityTesting.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/test/applicable/NonapplicabilityTesting.java @@ -20,13 +20,16 @@ import static org.junit.jupiter.api.Assertions.*; +import java.net.InetAddress; import java.util.UUID; import space.arim.libertybans.api.AddressVictim; +import space.arim.libertybans.api.NetworkAddress; import space.arim.libertybans.api.Operator; import space.arim.libertybans.api.PlayerVictim; import space.arim.libertybans.api.PunishmentType; import space.arim.libertybans.api.Victim; +import space.arim.libertybans.api.revoke.RevocationOrder; import space.arim.libertybans.core.LibertyBansCore; import space.arim.libertybans.it.util.TestingUtil; @@ -40,18 +43,20 @@ public NonapplicabilityTesting(LibertyBansCore core, PunishmentType type, Operat public void doTest() { final UUID uuid = UUID.randomUUID(); final String name = TestingUtil.randomString(16); - final byte[] address = TestingUtil.randomAddress(); + final InetAddress address = TestingUtil.randomAddress(); - assertNull(core.getEnforcer().executeAndCheckConnection(uuid, name, address).join()); - assertNull(core.getEnforcer().checkChat(uuid, address, null).join()); + assertNull(core.getEnforcementCenter().executeAndCheckConnection(uuid, name, address).join()); + assertNull(core.getEnforcementCenter().checkChat(uuid, address, null).join()); - assertNull(core.getSelector().getApplicablePunishment(uuid, address, type).join()); - assertNull(core.getMuteCacher().getCachedMute(uuid, address).join()); + NetworkAddress netAddress = NetworkAddress.of(address); + assertNull(core.getSelector().getApplicablePunishment(uuid, netAddress, type).join()); + assertNull(core.getSelector().getCachedMute(uuid, netAddress).join()); if (type.isSingular()) { for (Victim victim : new Victim[] {PlayerVictim.of(uuid), AddressVictim.of(address)}) { - assertFalse(core.getEnactor().undoPunishmentByTypeAndVictim(type, victim).join()); - assertNull(core.getEnactor().undoAndGetPunishmentByTypeAndVictim(type, victim).join()); + RevocationOrder order = core.getRevoker().revokeByTypeAndVictim(type, victim); + assertFalse(order.undoPunishment().join()); + assertNull(order.undoAndGetPunishment().join()); } } } diff --git a/bans-integrationtest/src/test/java/space/arim/libertybans/it/util/TestingUtil.java b/bans-integrationtest/src/test/java/space/arim/libertybans/it/util/TestingUtil.java index 7b7b06027..91c5231be 100644 --- a/bans-integrationtest/src/test/java/space/arim/libertybans/it/util/TestingUtil.java +++ b/bans-integrationtest/src/test/java/space/arim/libertybans/it/util/TestingUtil.java @@ -20,11 +20,15 @@ import static org.junit.jupiter.api.Assertions.*; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; -import space.arim.libertybans.api.Punishment; -import space.arim.libertybans.api.PunishmentBase; +import org.junit.jupiter.api.Assertions; + +import space.arim.libertybans.api.punish.Punishment; +import space.arim.libertybans.api.punish.PunishmentBase; public final class TestingUtil { @@ -46,15 +50,32 @@ private static byte[] randomBytes(int length) { return result; } + public static boolean randomBoolean() { + return random().nextBoolean(); + } + /** * Random Ipv4 or Ipv6 address bytes * * @return network address bytes */ - public static byte[] randomAddress() { + private static byte[] randomAddressBytes() { return randomBytes((random().nextBoolean()) ? 4 : 16); } + /** + * Random Ipv4 or Ipv6 InetAddress + * + * @return the network address + */ + public static InetAddress randomAddress() { + try { + return InetAddress.getByAddress(randomAddressBytes()); + } catch (UnknownHostException ex) { + throw Assertions.fail(ex); + } + } + /** * Asserts the qualities of the punishment objects are equal * @@ -67,12 +88,16 @@ public static void assertEqualDetails(PunishmentBase expected, PunishmentBase ac assertEquals(expected.getOperator(), actual.getOperator()); assertEquals(expected.getReason(), actual.getReason()); assertEquals(expected.getScope(), actual.getScope()); - assertEquals(expected.getStart(), actual.getStart()); - assertEquals(expected.getEnd(), actual.getEnd()); if (expected instanceof Punishment && actual instanceof Punishment) { - assertEquals(((Punishment) expected).getID(), ((Punishment) actual).getID()); + assertEqualDetailsFinish((Punishment) expected, (Punishment) actual); } } + + private static void assertEqualDetailsFinish(Punishment expected, Punishment actual) { + assertEquals(expected.getID(), actual.getID()); + assertEquals(expected.getStartDate(), actual.getStartDate()); + assertEquals(expected.getEndDate(), actual.getEndDate()); + } /** * Generates a random string diff --git a/pom.xml b/pom.xml index cf25c732a..2529e9520 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ space.arim.libertybans bans-parent - 0.1.4-SNAPSHOT + 0.2.0-SNAPSHOT pom LibertyBans @@ -15,32 +15,32 @@ UTF-8 - 11 - 11 + 2020-10-17T0:40:06Z - 5.6.2 + 5.7.0 2.4.0 - + - 0.15.5-SNAPSHOT + 1.0.0-RC4 0.5.4-SNAPSHOT 0.5.0 - 0.19.13-SNAPSHOT + 1.0.0-RC4 + 0.20.6-SNAPSHOT 1.7.30 3.4.5 2.5.1 2.6.2 2.8.5 - + 0.1.0-SNAPSHOT LibertyBans - libertybans + libertybans ${project.version} A248 ${project.parent.url} @@ -138,7 +138,6 @@ bans-api bans-bootstrap - bans-database bans-core bans-env bans-dl @@ -170,11 +169,6 @@ bans-bootstrap ${project.version} - - space.arim.libertybans - bans-database - ${project.version} - space.arim.libertybans bans-core @@ -190,11 +184,6 @@ bans-env-bungee ${project.version} - - space.arim.libertybans - bans-env-sponge - ${project.version} - space.arim.libertybans bans-env-velocity @@ -232,15 +221,22 @@ test - space.arim.omnibus - omnibus-all-shaded - ${omnibus.version} + org.mariadb.jdbc + mariadb-java-client + ${mariadb-connector.version} + test + + + org.hsqldb + hsqldb + ${hsqldb.version} + test space.arim.omnibus - omnibus-core + omnibus ${omnibus.version} @@ -255,6 +251,11 @@ jdbcaesar ${jdbcaesar.version} + + space.arim.dazzleconf + dazzleconf-ext-snakeyaml + ${dazzleconf.version} + space.arim.api arimapi-all @@ -270,16 +271,6 @@ HikariCP ${hikari.version} - - org.mariadb.jdbc - mariadb-java-client - ${mariadb-connector.version} - - - org.hsqldb - hsqldb - ${hsqldb.version} - com.github.ben-manes.caffeine caffeine