diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdate.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdate.java new file mode 100644 index 000000000..a0e907b62 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdate.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import me.lucko.luckperms.common.bulkupdate.action.Action; +import me.lucko.luckperms.common.bulkupdate.constraint.Constraint; +import me.lucko.luckperms.common.core.NodeModel; + +import java.util.List; + +/** + * Represents a query to be applied to a set of data. + * Queries can either be applied to im-memory sets of data, or converted to SQL syntax to be executed remotely. + */ +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor +public class BulkUpdate { + + // the data types which this query should apply to + private final DataType dataType; + + // the action to apply to the data which matches the constraints + private final Action action; + + // a set of constraints which data must match to be acted upon + private final List constraints; + + /** + * Check to see if a Node instance satisfies the constrints of this query + * + * @param node the node to check + * @return true if satisfied + */ + public boolean satisfiesConstraints(NodeModel node) { + for (Constraint constraint : constraints) { + if (!constraint.isSatisfiedBy(node)) { + return false; + } + } + return true; + } + + /** + * Applies this query to the given NodeModel, and returns the result. + * + * @param from the node to base changes from + * @return the new nodemodel instance, or null if the node should be deleted. + */ + public NodeModel apply(NodeModel from) { + if (!satisfiesConstraints(from)) { + return from; // make no change + } + + return action.apply(from); + } + + /** + * Converts this {@link BulkUpdate} to SQL syntax + * + * @return this query in SQL form + */ + public String buildAsSql() { + // DELETE FROM {table} WHERE ... + // UPDATE {table} SET ... WHERE ... + + StringBuilder sb = new StringBuilder(); + + // add the action + // (DELETE FROM or UPDATE) + sb.append(action.getAsSql()); + + // if there are no constraints, just return without a WHERE clause + if (constraints.isEmpty()) { + return sb.append(";").toString(); + } + + // append constraints + sb.append(" WHERE"); + for (int i = 0; i < constraints.size(); i++) { + Constraint constraint = constraints.get(i); + + sb.append(" "); + if (i != 0) { + sb.append("AND "); + } + + sb.append(constraint.getAsSql()); + } + + return sb.append(";").toString(); + } + + /** + * Utility to appropriately escape a string for use in a query. + * + * @param s the string to escape + * @return an escaped string + */ + public static String escapeStringForSql(String s) { + if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false")) { + return s.toLowerCase(); + } + + try { + Integer.parseInt(s); + return s; + } catch (NumberFormatException e) { + // ignored + } + + return "\"" + s + "\""; + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateBuilder.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateBuilder.java new file mode 100644 index 000000000..9cbb3974f --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/BulkUpdateBuilder.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate; + +import lombok.NoArgsConstructor; +import lombok.ToString; + +import com.google.common.collect.ImmutableList; + +import me.lucko.luckperms.common.bulkupdate.action.Action; +import me.lucko.luckperms.common.bulkupdate.constraint.Constraint; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Responsible for building a {@link BulkUpdate} + */ +@ToString +@NoArgsConstructor(staticName = "create") +public class BulkUpdateBuilder { + + // the data type this query should affect + private DataType dataType = DataType.ALL; + + // the action to apply to the data which matches the constraints + private Action action = null; + + // a set of constraints which data must match to be acted upon + private Set constraints = new LinkedHashSet<>(); + + public BulkUpdateBuilder action(Action action) { + this.action = action; + return this; + } + + public BulkUpdateBuilder dataType(DataType dataType) { + this.dataType = dataType; + return this; + } + + public BulkUpdateBuilder constraint(Constraint constraint) { + constraints.add(constraint); + return this; + } + + public BulkUpdate build() { + if (action == null) { + throw new IllegalStateException("no action specified"); + } + + return new BulkUpdate(dataType, action, ImmutableList.copyOf(constraints)); + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/DataType.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/DataType.java new file mode 100644 index 000000000..ff157d2a4 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/DataType.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Represents the data sets a query should apply to + */ +@Getter +@AllArgsConstructor +public enum DataType { + + ALL("all", true, true), + USERS("users", true, false), + GROUPS("groups", false, true); + + private final String name; + private final boolean includingUsers; + private final boolean includingGroups; + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/action/Action.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/action/Action.java new file mode 100644 index 000000000..81210097d --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/action/Action.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.action; + +import me.lucko.luckperms.common.core.NodeModel; + +public interface Action { + + String getName(); + + /** + * Applies this action to the given NodeModel, and returns the result. + * + * @param from the node to base changes from + * @return the new nodemodel instance, or null if the node should be deleted. + */ + NodeModel apply(NodeModel from); + + String getAsSql(); + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/action/DeleteAction.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/action/DeleteAction.java new file mode 100644 index 000000000..6f0895693 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/action/DeleteAction.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.action; + +import lombok.AllArgsConstructor; + +import me.lucko.luckperms.common.core.NodeModel; + +@AllArgsConstructor(staticName = "create") +public class DeleteAction implements Action { + + @Override + public String getName() { + return "delete"; + } + + @Override + public NodeModel apply(NodeModel from) { + return null; + } + + @Override + public String getAsSql() { + return "DELETE FROM {table}"; + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/action/UpdateAction.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/action/UpdateAction.java new file mode 100644 index 000000000..6f5eef9b6 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/action/UpdateAction.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.action; + +import lombok.AllArgsConstructor; + +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; +import me.lucko.luckperms.common.bulkupdate.constraint.QueryField; +import me.lucko.luckperms.common.core.NodeModel; + +@AllArgsConstructor(staticName = "of") +public class UpdateAction implements Action { + + private final QueryField field; + private final String value; + + @Override + public String getName() { + return "update"; + } + + @Override + public NodeModel apply(NodeModel from) { + switch (field) { + case PERMISSION: + return from.setPermission(value); + case SERVER: + return from.setServer(value); + case WORLD: + return from.setWorld(value); + default: + throw new RuntimeException(); + } + } + + @Override + public String getAsSql() { + return "UPDATE {table} SET " + field.getSqlName() + "=" + BulkUpdate.escapeStringForSql(value); + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/Comparison.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/Comparison.java new file mode 100644 index 000000000..3f5a20635 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/Comparison.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.comparisons; + +public interface Comparison { + + String getSymbol(); + + boolean matches(String str, String expr); + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/ComparisonType.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/ComparisonType.java new file mode 100644 index 000000000..ca2b49d5a --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/ComparisonType.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.comparisons; + +import lombok.AllArgsConstructor; + +import me.lucko.luckperms.common.bulkupdate.comparisons.impl.ComparisonEqual; +import me.lucko.luckperms.common.bulkupdate.comparisons.impl.ComparisonNotEqual; +import me.lucko.luckperms.common.bulkupdate.comparisons.impl.ComparisonNotSimilar; +import me.lucko.luckperms.common.bulkupdate.comparisons.impl.ComparisonSimilar; + +@AllArgsConstructor +public enum ComparisonType { + + EQUAL("==", new ComparisonEqual()), + NOT_EQUAL("!=", new ComparisonNotEqual()), + SIMILAR("~~", new ComparisonSimilar()), + NOT_SIMILAR("!~", new ComparisonNotSimilar()); + + private final String symbol; + private final Comparison instance; + + public String getSymbol() { + return symbol; + } + + public Comparison get() { + return instance; + } + + public static ComparisonType parseComparison(String s) { + for (ComparisonType t : values()) { + if (t.getSymbol().equals(s)) { + return t; + } + } + return null; + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonEqual.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonEqual.java new file mode 100644 index 000000000..108733d59 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonEqual.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.comparisons.impl; + +import me.lucko.luckperms.common.bulkupdate.comparisons.Comparison; + +public class ComparisonEqual implements Comparison { + + @Override + public String getSymbol() { + return "=="; + } + + @Override + public boolean matches(String str, String expr) { + return str.equalsIgnoreCase(expr); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonNotEqual.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonNotEqual.java new file mode 100644 index 000000000..08994a74a --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonNotEqual.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.comparisons.impl; + +import me.lucko.luckperms.common.bulkupdate.comparisons.Comparison; + +public class ComparisonNotEqual implements Comparison { + + @Override + public String getSymbol() { + return "!="; + } + + @Override + public boolean matches(String str, String expr) { + return !str.equalsIgnoreCase(expr); + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonNotSimilar.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonNotSimilar.java new file mode 100644 index 000000000..a44f1fe7e --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonNotSimilar.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.comparisons.impl; + +import me.lucko.luckperms.common.bulkupdate.comparisons.Comparison; + +public class ComparisonNotSimilar implements Comparison { + + @Override + public String getSymbol() { + return "!~"; + } + + @Override + public boolean matches(String str, String expr) { + // form expression + expr = expr.toLowerCase(); + expr = expr.replace(".", "\\."); + + // convert from SQL LIKE syntax to regex + expr = expr.replace("_", "."); + expr = expr.replace("%", ".*"); + + return !str.toLowerCase().matches(expr); + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonSimilar.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonSimilar.java new file mode 100644 index 000000000..2cddc1c09 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/comparisons/impl/ComparisonSimilar.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.comparisons.impl; + +import me.lucko.luckperms.common.bulkupdate.comparisons.Comparison; + +public class ComparisonSimilar implements Comparison { + @Override + public String getSymbol() { + return "~~"; + } + + @Override + public boolean matches(String str, String expr) { + // form expression + expr = expr.toLowerCase(); + expr = expr.replace(".", "\\."); + + // convert from SQL LIKE syntax to regex + expr = expr.replace("_", "."); + expr = expr.replace("%", ".*"); + + return str.toLowerCase().matches(expr); + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/constraint/Constraint.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/constraint/Constraint.java new file mode 100644 index 000000000..b026c18ee --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/constraint/Constraint.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.constraint; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; +import me.lucko.luckperms.common.bulkupdate.comparisons.ComparisonType; +import me.lucko.luckperms.common.core.NodeModel; + +/** + * Represents a query constraint + */ +@Getter +@AllArgsConstructor(staticName = "of") +public class Constraint { + + // the field this constraint is comparing against + private final QueryField field; + + // the comparison type being used in this constraint + private final ComparisonType comparison; + + // the expression being compared against + private final String value; + + /** + * Returns if the given node satisfies this constraint + * + * @param node the node + * @return true if satisfied + */ + public boolean isSatisfiedBy(NodeModel node) { + switch (field) { + case PERMISSION: + return comparison.get().matches(node.getPermission(), value); + case SERVER: + return comparison.get().matches(node.getServer(), value); + case WORLD: + return comparison.get().matches(node.getWorld(), value); + default: + throw new RuntimeException(); + } + } + + public String getAsSql() { + // WHERE permission = "thing" + // WHERE permission != "" + // WHERE permission LIKE "" + // WHERE permission NOT LIKE "" + + switch (comparison) { + case EQUAL: + return field.getSqlName() + " = " + BulkUpdate.escapeStringForSql(value); + case NOT_EQUAL: + return field.getSqlName() + " != " + BulkUpdate.escapeStringForSql(value); + case SIMILAR: + return field.getSqlName() + " LIKE " + BulkUpdate.escapeStringForSql(value); + case NOT_SIMILAR: + return field.getSqlName() + " NOT LIKE " + BulkUpdate.escapeStringForSql(value); + default: + throw new RuntimeException(); + } + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/bulkupdate/constraint/QueryField.java b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/constraint/QueryField.java new file mode 100644 index 000000000..3d48246fb --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/bulkupdate/constraint/QueryField.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.bulkupdate.constraint; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Represents a field being used in an update + */ +@Getter +@AllArgsConstructor +public enum QueryField { + + PERMISSION("permission"), + SERVER("server"), + WORLD("world"); + + private final String sqlName; + + public static QueryField of(String s) { + try { + return valueOf(s.toUpperCase()); + } catch (IllegalArgumentException e) { + return null; + } + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java b/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java index e439f7b05..770e04b49 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/CommandManager.java @@ -33,6 +33,7 @@ import me.lucko.luckperms.common.commands.impl.group.GroupMainCommand; import me.lucko.luckperms.common.commands.impl.group.ListGroups; import me.lucko.luckperms.common.commands.impl.log.LogMainCommand; import me.lucko.luckperms.common.commands.impl.migration.MigrationMainCommand; +import me.lucko.luckperms.common.commands.impl.misc.BulkUpdateCommand; import me.lucko.luckperms.common.commands.impl.misc.CheckCommand; import me.lucko.luckperms.common.commands.impl.misc.ExportCommand; import me.lucko.luckperms.common.commands.impl.misc.ImportCommand; @@ -97,6 +98,7 @@ public class CommandManager { .add(new ImportCommand()) .add(new ExportCommand()) .add(new ReloadConfigCommand()) + .add(new BulkUpdateCommand()) .add(new MigrationMainCommand()) .add(new CreateGroup()) .add(new DeleteGroup()) @@ -160,7 +162,7 @@ public class CommandManager { // Check the correct number of args were given for the main command if (main.getArgumentCheck().test(arguments.size())) { - main.sendUsage(sender, label); + main.sendDetailedUsage(sender, label); return CommandResult.INVALID_ARGS; } diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/abstraction/MainCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/abstraction/MainCommand.java index c449ebf03..b792b2e61 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/abstraction/MainCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/abstraction/MainCommand.java @@ -170,7 +170,7 @@ public abstract class MainCommand extends Command { @Override public void sendDetailedUsage(Sender sender, String label) { - + sendUsage(sender, label); } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/impl/misc/BulkUpdateCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/impl/misc/BulkUpdateCommand.java new file mode 100644 index 000000000..cc4ffa126 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/commands/impl/misc/BulkUpdateCommand.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2017 Lucko (Luck) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.commands.impl.misc; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; + +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; +import me.lucko.luckperms.common.bulkupdate.BulkUpdateBuilder; +import me.lucko.luckperms.common.bulkupdate.DataType; +import me.lucko.luckperms.common.bulkupdate.action.DeleteAction; +import me.lucko.luckperms.common.bulkupdate.action.UpdateAction; +import me.lucko.luckperms.common.bulkupdate.comparisons.ComparisonType; +import me.lucko.luckperms.common.bulkupdate.constraint.Constraint; +import me.lucko.luckperms.common.bulkupdate.constraint.QueryField; +import me.lucko.luckperms.common.commands.Arg; +import me.lucko.luckperms.common.commands.CommandException; +import me.lucko.luckperms.common.commands.CommandResult; +import me.lucko.luckperms.common.commands.abstraction.SingleCommand; +import me.lucko.luckperms.common.commands.sender.Sender; +import me.lucko.luckperms.common.commands.utils.ArgumentUtils; +import me.lucko.luckperms.common.constants.Message; +import me.lucko.luckperms.common.constants.Permission; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.utils.Predicates; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +public class BulkUpdateCommand extends SingleCommand { + private final Cache pendingOperations = Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build(); + + public BulkUpdateCommand() { + super("BulkUpdate", "Execute bulk change queries on all data", "/%s bulkupdate", Permission.BULK_UPDATE, Predicates.alwaysFalse(), + Arg.list( + Arg.create("data type", true, "the type of data being changed. ('all', 'users' or 'groups')"), + Arg.create("action", true, "the action to perform on the data. ('update' or 'delete')"), + Arg.create("action field", false, "the field to act upon. only required for 'update'. ('permission', 'server' or 'world')"), + Arg.create("action value", false, "the value to replace with. only required for 'update'."), + Arg.create("constraint...", false, "the constraints required for the update") + ) + ); + } + + @Override + public CommandResult execute(LuckPermsPlugin plugin, Sender sender, List args, String label) throws CommandException { + if (args.size() == 2 && args.get(0).equalsIgnoreCase("confirm")) { + + String id = args.get(1); + BulkUpdate operation = pendingOperations.asMap().remove(id); + + if (operation == null) { + Message.BULK_UPDATE_UNKNOWN_ID.send(sender, id); + return CommandResult.INVALID_ARGS; + } + + Message.BULK_UPDATE_STARTING.send(sender); + plugin.getStorage().applyBulkUpdate(operation).thenAccept(b -> { + if (b) { + Message.BULK_UPDATE_SUCCESS.send(sender); + } else { + Message.BULK_UPDATE_FAILURE.send(sender); + } + }); + return CommandResult.SUCCESS; + } + + if (args.size() < 3) { + throw new ArgumentUtils.DetailedUsageException(); + } + + BulkUpdateBuilder bulkUpdateBuilder = BulkUpdateBuilder.create(); + + try { + bulkUpdateBuilder.dataType(DataType.valueOf(args.remove(0).toUpperCase())); + } catch (IllegalArgumentException e) { + Message.BULK_UPDATE_INVALID_DATA_TYPE.send(sender); + return CommandResult.INVALID_ARGS; + } + + String action = args.remove(0).toLowerCase(); + if (action.equals("delete")) { + bulkUpdateBuilder.action(DeleteAction.create()); + } else if (action.equals("update")) { + if (args.size() < 2) { + throw new ArgumentUtils.DetailedUsageException(); + } + + String field = args.remove(0); + QueryField queryField = QueryField.of(field); + if (queryField == null) { + throw new ArgumentUtils.DetailedUsageException(); + } + String value = args.remove(0); + + bulkUpdateBuilder.action(UpdateAction.of(queryField, value)); + } else { + throw new ArgumentUtils.DetailedUsageException(); + } + + for (String constraint : args) { + String[] parts = constraint.split(" "); + if (parts.length != 3) { + Message.BULK_UPDATE_INVALID_CONSTRAINT.send(sender, constraint); + return CommandResult.INVALID_ARGS; + } + + QueryField field = QueryField.of(parts[0]); + if (field == null) { + Message.BULK_UPDATE_INVALID_CONSTRAINT.send(sender, constraint); + return CommandResult.INVALID_ARGS; + } + + ComparisonType comparison = ComparisonType.parseComparison(parts[1]); + if (comparison == null) { + Message.BULK_UPDATE_INVALID_COMPARISON.send(sender, parts[1]); + return CommandResult.INVALID_ARGS; + } + + String expr = parts[2]; + bulkUpdateBuilder.constraint(Constraint.of(field, comparison, expr)); + } + + String id = "" + ThreadLocalRandom.current().nextInt(9) + + ThreadLocalRandom.current().nextInt(9) + + ThreadLocalRandom.current().nextInt(9) + + ThreadLocalRandom.current().nextInt(9); + + BulkUpdate bulkUpdate = bulkUpdateBuilder.build(); + + pendingOperations.put(id, bulkUpdate); + + Message.BULK_UPDATE_QUEUED.send(sender, bulkUpdate.buildAsSql().replace("{table}", bulkUpdate.getDataType().getName())); + Message.BULK_UPDATE_CONFIRM.send(sender, label, id); + + return CommandResult.SUCCESS; + } + + @Override + public boolean isAuthorized(Sender sender) { + return sender.isConsole(); // we only want console to be able to use this command + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/constants/Message.java b/common/src/main/java/me/lucko/luckperms/common/constants/Message.java index 638c1988a..54226f581 100644 --- a/common/src/main/java/me/lucko/luckperms/common/constants/Message.java +++ b/common/src/main/java/me/lucko/luckperms/common/constants/Message.java @@ -261,6 +261,17 @@ public enum Message { UNSET_META_SUCCESS("&aUnset meta value with key &f\"{0}&f\"&a for &b{1}&a in context {2}&a.", true), UNSET_META_TEMP_SUCCESS("&aUnset temporary meta value with key &f\"{0}&f\"&a for &b{1}&a in context {2}&a.", true), + BULK_UPDATE_INVALID_DATA_TYPE("Invalid type. Was expecting 'all', 'users' or 'groups'.", true), + BULK_UPDATE_INVALID_CONSTRAINT("Invalid constraint &4{0}&c. Constraints should be in the format '&f &c'.", true), + BULK_UPDATE_INVALID_COMPARISON("Invalid comparison operator '&4{0}&c'. Expected one of the following: &f== != ~~ ~!", true), + BULK_UPDATE_QUEUED("&aBulk update operation was queued. &7(&f{0}&7)", true), + BULK_UPDATE_CONFIRM("&aRun &b/{0} bulkupdate confirm {1} &ato execute the update.", true), + BULK_UPDATE_UNKNOWN_ID("&aOperation with id &b{0}&a does not exist or has expired.", true), + + BULK_UPDATE_STARTING("&aRunning bulk update.", true), + BULK_UPDATE_SUCCESS("&bBulk update completed successfully.", true), + BULK_UPDATE_FAILURE("&cBulk update failed. Check the console for errors.", true), + BULK_CHANGE_TYPE_ERROR("Invalid type. Was expecting 'server' or 'world'.", true), BULK_CHANGE_SUCCESS("&aApplied bulk change successfully. {0} records were changed.", true), diff --git a/common/src/main/java/me/lucko/luckperms/common/constants/Permission.java b/common/src/main/java/me/lucko/luckperms/common/constants/Permission.java index 22fd43e3d..be14d0ebc 100644 --- a/common/src/main/java/me/lucko/luckperms/common/constants/Permission.java +++ b/common/src/main/java/me/lucko/luckperms/common/constants/Permission.java @@ -44,6 +44,7 @@ public enum Permission { IMPORT(list("import"), Type.NONE), EXPORT(list("export"), Type.NONE), RELOAD_CONFIG(list("reloadconfig"), Type.NONE), + BULK_UPDATE(list("bulkupdate"), Type.NONE), MIGRATION(list("migration"), Type.NONE), CREATE_GROUP(list("creategroup"), Type.NONE), @@ -88,7 +89,6 @@ public enum Permission { USER_SHOWTRACKS(list("showtracks"), Type.USER), USER_PROMOTE(list("promote"), Type.USER), USER_DEMOTE(list("demote"), Type.USER), - USER_BULKCHANGE(list("bulkchange"), Type.USER), USER_CLEAR(list("clear"), Type.USER), GROUP_INFO(list("info"), Type.GROUP), @@ -123,7 +123,6 @@ public enum Permission { GROUP_LISTMEMBERS(list("listmembers"), Type.GROUP), GROUP_SHOWTRACKS(list("showtracks"), Type.GROUP), GROUP_SETWEIGHT(list("setweight"), Type.GROUP), - GROUP_BULKCHANGE(list("bulkchange"), Type.GROUP), GROUP_CLEAR(list("clear"), Type.GROUP), GROUP_RENAME(list("rename"), Type.GROUP), GROUP_CLONE(list("clone"), Type.GROUP), diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/AbstractStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/AbstractStorage.java index ee8221674..5b9d20029 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/AbstractStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/AbstractStorage.java @@ -32,6 +32,7 @@ import me.lucko.luckperms.api.LogEntry; import me.lucko.luckperms.api.event.cause.CreationCause; import me.lucko.luckperms.api.event.cause.DeletionCause; import me.lucko.luckperms.common.api.delegates.StorageDelegate; +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.User; @@ -91,6 +92,11 @@ public class AbstractStorage implements Storage { return makeFuture(backing::getLog); } + @Override + public CompletableFuture applyBulkUpdate(BulkUpdate bulkUpdate) { + return makeFuture(() -> backing.applyBulkUpdate(bulkUpdate)); + } + @Override public CompletableFuture loadUser(UUID uuid, String username) { return makeFuture(() -> { diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/SplitBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/SplitBacking.java index 6023cae89..bc1d8426b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/SplitBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/SplitBacking.java @@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableMap; import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.LogEntry; +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.User; @@ -76,6 +77,20 @@ public class SplitBacking extends AbstractBacking { return backing.get(types.get("log")).getLog(); } + @Override + public boolean applyBulkUpdate(BulkUpdate bulkUpdate) { + String userType = types.get("user"); + String groupType = types.get("group"); + + boolean ret = backing.get(userType).applyBulkUpdate(bulkUpdate); + if (!userType.equals(groupType)) { + if (!backing.get(groupType).applyBulkUpdate(bulkUpdate)) { + ret = false; + } + } + return ret; + } + @Override public boolean loadUser(UUID uuid, String username) { return backing.get(types.get("user")).loadUser(uuid, username); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java b/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java index e44617e35..de438540c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/Storage.java @@ -27,6 +27,7 @@ import me.lucko.luckperms.api.LogEntry; import me.lucko.luckperms.api.event.cause.CreationCause; import me.lucko.luckperms.api.event.cause.DeletionCause; import me.lucko.luckperms.common.api.delegates.StorageDelegate; +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.User; @@ -60,6 +61,8 @@ public interface Storage { CompletableFuture getLog(); + CompletableFuture applyBulkUpdate(BulkUpdate bulkUpdate); + CompletableFuture loadUser(UUID uuid, String username); CompletableFuture saveUser(User user); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/AbstractBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/AbstractBacking.java index effd86da0..24a3c2a73 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/AbstractBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/AbstractBacking.java @@ -29,6 +29,7 @@ import lombok.Setter; import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.LogEntry; +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.User; @@ -60,6 +61,8 @@ public abstract class AbstractBacking { public abstract Log getLog(); + public abstract boolean applyBulkUpdate(BulkUpdate bulkUpdate); + public abstract boolean loadUser(UUID uuid, String username); public abstract boolean saveUser(User user); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java index 9603e5ed4..6172f8283 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java @@ -34,6 +34,7 @@ import com.google.gson.JsonPrimitive; import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.core.NodeModel; import me.lucko.luckperms.common.core.PriorityComparator; import me.lucko.luckperms.common.core.UserIdentifier; @@ -55,9 +56,9 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.function.Function; import java.util.stream.Collectors; @SuppressWarnings("ResultOfMethodCallIgnored") @@ -81,16 +82,69 @@ public class JSONBacking extends FlatfileBacking { } } - public boolean readObjectFromFile(File file, Function readOperation) { - boolean success = false; + public JsonObject readObjectFromFile(File file) { try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { - JsonObject object = gson.fromJson(reader, JsonObject.class); - success = readOperation.apply(object); + return gson.fromJson(reader, JsonObject.class); } catch (Throwable t) { plugin.getLog().warn("Exception whilst reading from file: " + file.getAbsolutePath()); t.printStackTrace(); + return null; } - return success; + } + + @Override + public boolean applyBulkUpdate(BulkUpdate bulkUpdate) { + return call(() -> { + if (bulkUpdate.getDataType().isIncludingUsers()) { + File[] files = usersDir.listFiles((dir, name1) -> name1.endsWith(".json")); + if (files == null) return false; + + for (File file : files) { + registerFileAction("users", file); + + JsonObject object = readObjectFromFile(file); + + Set nodes = new HashSet<>(); + nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray())); + + Set results = nodes.stream() + .map(n -> Optional.ofNullable(bulkUpdate.apply(n))) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + + object.add("permissions", serializePermissions(results)); + + writeElementToFile(file, object); + } + } + + if (bulkUpdate.getDataType().isIncludingGroups()) { + File[] files = groupsDir.listFiles((dir, name1) -> name1.endsWith(".json")); + if (files == null) return false; + + for (File file : files) { + registerFileAction("groups", file); + + JsonObject object = readObjectFromFile(file); + + Set nodes = new HashSet<>(); + nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray())); + + Set results = nodes.stream() + .map(n -> Optional.ofNullable(bulkUpdate.apply(n))) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + + object.add("permissions", serializePermissions(results)); + + writeElementToFile(file, object); + } + } + + return true; + }, false); } @Override @@ -103,30 +157,29 @@ public class JSONBacking extends FlatfileBacking { registerFileAction("users", userFile); if (userFile.exists()) { - return readObjectFromFile(userFile, object -> { - String name = object.get("name").getAsString(); - user.getPrimaryGroup().setStoredValue(object.get("primaryGroup").getAsString()); + JsonObject object = readObjectFromFile(userFile); + String name = object.get("name").getAsString(); + user.getPrimaryGroup().setStoredValue(object.get("primaryGroup").getAsString()); - Set data = deserializePermissions(object.get("permissions").getAsJsonArray()); - Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); - user.setNodes(nodes); + Set data = deserializePermissions(object.get("permissions").getAsJsonArray()); + Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); + user.setNodes(nodes); - boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false); + boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false); - if (user.getName() == null || user.getName().equalsIgnoreCase("null")) { - user.setName(name); - } else { - if (!name.equalsIgnoreCase(user.getName())) { - save = true; - } + if (user.getName() == null || user.getName().equalsIgnoreCase("null")) { + user.setName(name); + } else { + if (!name.equalsIgnoreCase(user.getName())) { + save = true; } + } - if (save) { - saveUser(user); - } + if (save) { + saveUser(user); + } - return true; - }); + return true; } else { if (GenericUserManager.shouldSave(user)) { user.clearNodes(); @@ -190,11 +243,10 @@ public class JSONBacking extends FlatfileBacking { for (File file : files) { registerFileAction("users", file); + JsonObject object = readObjectFromFile(file); + Set nodes = new HashSet<>(); - readObjectFromFile(file, object -> { - nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray())); - return true; - }); + nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray())); boolean shouldDelete = false; if (nodes.size() == 1) { @@ -223,12 +275,11 @@ public class JSONBacking extends FlatfileBacking { registerFileAction("users", file); UUID holder = UUID.fromString(file.getName().substring(0, file.getName().length() - 5)); - Set nodes = new HashSet<>(); - readObjectFromFile(file, object -> { - nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray())); - return true; - }); + JsonObject object = readObjectFromFile(file); + + Set nodes = new HashSet<>(); + nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray())); for (NodeModel e : nodes) { if (!e.getPermission().equalsIgnoreCase(permission)) { @@ -253,12 +304,11 @@ public class JSONBacking extends FlatfileBacking { registerFileAction("groups", groupFile); if (groupFile.exists()) { - return readObjectFromFile(groupFile, object -> { - Set data = deserializePermissions(object.get("permissions").getAsJsonArray()); - Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); - group.setNodes(nodes); - return true; - }); + JsonObject object = readObjectFromFile(groupFile); + Set data = deserializePermissions(object.get("permissions").getAsJsonArray()); + Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); + group.setNodes(nodes); + return true; } else { try { groupFile.createNewFile(); @@ -290,12 +340,15 @@ public class JSONBacking extends FlatfileBacking { File groupFile = new File(groupsDir, name + ".json"); registerFileAction("groups", groupFile); - return groupFile.exists() && readObjectFromFile(groupFile, object -> { - Set data = deserializePermissions(object.get("permissions").getAsJsonArray()); - Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); - group.setNodes(nodes); - return true; - }); + if (!groupFile.exists()) { + return false; + } + + JsonObject object = readObjectFromFile(groupFile); + Set data = deserializePermissions(object.get("permissions").getAsJsonArray()); + Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); + group.setNodes(nodes); + return true; }, false); } finally { group.getIoLock().unlock(); @@ -341,11 +394,11 @@ public class JSONBacking extends FlatfileBacking { registerFileAction("groups", file); String holder = file.getName().substring(0, file.getName().length() - 5); + + JsonObject object = readObjectFromFile(file); + Set nodes = new HashSet<>(); - readObjectFromFile(file, element -> { - nodes.addAll(deserializePermissions(element.get("permissions").getAsJsonArray())); - return true; - }); + nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray())); for (NodeModel e : nodes) { if (!e.getPermission().equalsIgnoreCase(permission)) { @@ -370,14 +423,13 @@ public class JSONBacking extends FlatfileBacking { registerFileAction("tracks", trackFile); if (trackFile.exists()) { - return readObjectFromFile(trackFile, element -> { - List groups = new ArrayList<>(); - for (JsonElement g : element.get("groups").getAsJsonArray()) { - groups.add(g.getAsString()); - } - track.setGroups(groups); - return true; - }); + JsonObject object = readObjectFromFile(trackFile); + List groups = new ArrayList<>(); + for (JsonElement g : object.get("groups").getAsJsonArray()) { + groups.add(g.getAsString()); + } + track.setGroups(groups); + return true; } else { try { trackFile.createNewFile(); @@ -411,14 +463,17 @@ public class JSONBacking extends FlatfileBacking { File trackFile = new File(tracksDir, name + ".json"); registerFileAction("tracks", trackFile); - return trackFile.exists() && readObjectFromFile(trackFile, element -> { - List groups = new ArrayList<>(); - for (JsonElement g : element.get("groups").getAsJsonArray()) { - groups.add(g.getAsString()); - } - track.setGroups(groups); - return true; - }); + if (!trackFile.exists()) { + return false; + } + + JsonObject object = readObjectFromFile(trackFile); + List groups = new ArrayList<>(); + for (JsonElement g : object.get("groups").getAsJsonArray()) { + groups.add(g.getAsString()); + } + track.setGroups(groups); + return true; }, false); } finally { diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java index e9ea757f4..7eb4192bd 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java @@ -34,7 +34,9 @@ import com.mongodb.client.model.InsertOneOptions; import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.LogEntry; import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.core.NodeFactory; +import me.lucko.luckperms.common.core.NodeModel; import me.lucko.luckperms.common.core.UserIdentifier; import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.Track; @@ -55,6 +57,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; @@ -224,6 +227,81 @@ public class MongoDBBacking extends AbstractBacking { }, null); } + @Override + public boolean applyBulkUpdate(BulkUpdate bulkUpdate) { + return call(() -> { + if (bulkUpdate.getDataType().isIncludingUsers()) { + MongoCollection c = database.getCollection("users"); + + try (MongoCursor cursor = c.find().iterator()) { + while (cursor.hasNext()) { + Document d = cursor.next(); + + UUID uuid = UUID.fromString(d.getString("_id")); + Map perms = revert((Map) d.get("perms")); + + Set nodes = new HashSet<>(); + for (Map.Entry e : perms.entrySet()) { + Node node = NodeFactory.fromSerializedNode(e.getKey(), e.getValue()); + nodes.add(NodeModel.fromNode(node)); + } + + Set results = nodes.stream() + .map(n -> Optional.ofNullable(bulkUpdate.apply(n))) + .filter(Optional::isPresent) + .map(Optional::get) + .map(NodeModel::toNode) + .collect(Collectors.toSet()); + + Document permsDoc = new Document(); + for (Map.Entry e : convert(exportToLegacy(results)).entrySet()) { + permsDoc.append(e.getKey(), e.getValue()); + } + + d.put("perms", perms); + c.replaceOne(new Document("_id", uuid), d); + } + } + } + + if (bulkUpdate.getDataType().isIncludingGroups()) { + MongoCollection c = database.getCollection("groups"); + + try (MongoCursor cursor = c.find().iterator()) { + while (cursor.hasNext()) { + Document d = cursor.next(); + + String holder = d.getString("_id"); + Map perms = revert((Map) d.get("perms")); + + Set nodes = new HashSet<>(); + for (Map.Entry e : perms.entrySet()) { + Node node = NodeFactory.fromSerializedNode(e.getKey(), e.getValue()); + nodes.add(NodeModel.fromNode(node)); + } + + Set results = nodes.stream() + .map(n -> Optional.ofNullable(bulkUpdate.apply(n))) + .filter(Optional::isPresent) + .map(Optional::get) + .map(NodeModel::toNode) + .collect(Collectors.toSet()); + + Document permsDoc = new Document(); + for (Map.Entry e : convert(exportToLegacy(results)).entrySet()) { + permsDoc.append(e.getKey(), e.getValue()); + } + + d.put("perms", perms); + c.replaceOne(new Document("_id", holder), d); + } + } + } + + return true; + }, false); + } + @Override public boolean loadUser(UUID uuid, String username) { User user = plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username)); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java index 53c835fc5..7d1306325 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java @@ -32,6 +32,7 @@ import com.google.gson.reflect.TypeToken; import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.LogEntry; import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.core.NodeModel; import me.lucko.luckperms.common.core.UserIdentifier; import me.lucko.luckperms.common.core.model.Group; @@ -248,6 +249,42 @@ public class SQLBacking extends AbstractBacking { return log.build(); } + @Override + public boolean applyBulkUpdate(BulkUpdate bulkUpdate) { + boolean success = true; + String queryString = bulkUpdate.buildAsSql(); + + try (Connection c = provider.getConnection()) { + if (bulkUpdate.getDataType().isIncludingUsers()) { + String table = prefix.apply("{prefix}user_permissions"); + + try (Statement s = c.createStatement()) { + s.execute(queryString.replace("{table}", table)); + } catch (SQLException e) { + e.printStackTrace(); + success = false; + } + } + + if (bulkUpdate.getDataType().isIncludingGroups()) { + String table = prefix.apply("{prefix}group_permissions"); + + try (Statement s = c.createStatement()) { + s.execute(queryString.replace("{table}", table)); + } catch (SQLException e) { + e.printStackTrace(); + success = false; + } + } + + } catch (SQLException e) { + e.printStackTrace(); + success = false; + } + + return success; + } + @Override public boolean loadUser(UUID uuid, String username) { User user = plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username)); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java index 2f898d79f..a10ac4ee0 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java @@ -29,6 +29,7 @@ import com.google.common.collect.Iterables; import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.core.NodeModel; import me.lucko.luckperms.common.core.UserIdentifier; import me.lucko.luckperms.common.core.model.Group; @@ -55,9 +56,9 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.function.Function; import java.util.stream.Collectors; @SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"}) @@ -85,15 +86,67 @@ public class YAMLBacking extends FlatfileBacking { } } - public boolean readMapFromFile(File file, Function, Boolean> readOperation) { - boolean success = false; + public Map readMapFromFile(File file) { try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { - success = readOperation.apply((Map) getYaml().load(reader)); + return (Map) getYaml().load(reader); } catch (Throwable t) { plugin.getLog().warn("Exception whilst reading from file: " + file.getAbsolutePath()); t.printStackTrace(); + return null; } - return success; + } + + @Override + public boolean applyBulkUpdate(BulkUpdate bulkUpdate) { + return call(() -> { + if (bulkUpdate.getDataType().isIncludingUsers()) { + File[] files = usersDir.listFiles((dir, name1) -> name1.endsWith(".yml")); + if (files == null) return false; + + for (File file : files) { + registerFileAction("users", file); + + Map values = readMapFromFile(file); + + Set nodes = new HashSet<>(); + nodes.addAll(deserializePermissions((List) values.get("permissions"))); + + Set results = nodes.stream() + .map(n -> Optional.ofNullable(bulkUpdate.apply(n))) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + + values.put("permissions", serializePermissions(results)); + writeMapToFile(file, values); + } + } + + if (bulkUpdate.getDataType().isIncludingGroups()) { + File[] files = groupsDir.listFiles((dir, name1) -> name1.endsWith(".yml")); + if (files == null) return false; + + for (File file : files) { + registerFileAction("groups", file); + + Map values = readMapFromFile(file); + + Set nodes = new HashSet<>(); + nodes.addAll(deserializePermissions((List) values.get("permissions"))); + + Set results = nodes.stream() + .map(n -> Optional.ofNullable(bulkUpdate.apply(n))) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + + values.put("permissions", serializePermissions(results)); + writeMapToFile(file, values); + } + } + + return true; + }, false); } @Override @@ -105,30 +158,30 @@ public class YAMLBacking extends FlatfileBacking { File userFile = new File(usersDir, uuid.toString() + ".yml"); registerFileAction("users", userFile); if (userFile.exists()) { - return readMapFromFile(userFile, values -> { - // User exists, let's load. - String name = (String) values.get("name"); - user.getPrimaryGroup().setStoredValue((String) values.get("primary-group")); + Map values = readMapFromFile(userFile); - Set data = deserializePermissions((List) values.get("permissions")); - Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); - user.setNodes(nodes); + // User exists, let's load. + String name = (String) values.get("name"); + user.getPrimaryGroup().setStoredValue((String) values.get("primary-group")); - boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false); + Set data = deserializePermissions((List) values.get("permissions")); + Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); + user.setNodes(nodes); - if (user.getName() == null || user.getName().equalsIgnoreCase("null")) { - user.setName(name); - } else { - if (!name.equalsIgnoreCase(user.getName())) { - save = true; - } + boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false); + + if (user.getName() == null || user.getName().equalsIgnoreCase("null")) { + user.setName(name); + } else { + if (!name.equalsIgnoreCase(user.getName())) { + save = true; } + } - if (save) { - saveUser(user); - } - return true; - }); + if (save) { + saveUser(user); + } + return true; } else { if (GenericUserManager.shouldSave(user)) { user.clearNodes(); @@ -191,11 +244,10 @@ public class YAMLBacking extends FlatfileBacking { for (File file : files) { registerFileAction("users", file); + Map values = readMapFromFile(file); + Set nodes = new HashSet<>(); - readMapFromFile(file, values -> { - nodes.addAll(deserializePermissions((List) values.get("permissions"))); - return true; - }); + nodes.addAll(deserializePermissions((List) values.get("permissions"))); boolean shouldDelete = false; if (nodes.size() == 1) { @@ -224,11 +276,11 @@ public class YAMLBacking extends FlatfileBacking { registerFileAction("users", file); UUID holder = UUID.fromString(file.getName().substring(0, file.getName().length() - 4)); + + Map values = readMapFromFile(file); + Set nodes = new HashSet<>(); - readMapFromFile(file, values -> { - nodes.addAll(deserializePermissions((List) values.get("permissions"))); - return true; - }); + nodes.addAll(deserializePermissions((List) values.get("permissions"))); for (NodeModel e : nodes) { if (!e.getPermission().equalsIgnoreCase(permission)) { @@ -253,12 +305,11 @@ public class YAMLBacking extends FlatfileBacking { registerFileAction("groups", groupFile); if (groupFile.exists()) { - return readMapFromFile(groupFile, values -> { - Set data = deserializePermissions((List) values.get("permissions")); - Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); - group.setNodes(nodes); - return true; - }); + Map values = readMapFromFile(groupFile); + Set data = deserializePermissions((List) values.get("permissions")); + Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); + group.setNodes(nodes); + return true; } else { try { groupFile.createNewFile(); @@ -288,12 +339,15 @@ public class YAMLBacking extends FlatfileBacking { File groupFile = new File(groupsDir, name + ".yml"); registerFileAction("groups", groupFile); - return groupFile.exists() && readMapFromFile(groupFile, values -> { - Set data = deserializePermissions((List) values.get("permissions")); - Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); - group.setNodes(nodes); - return true; - }); + if (!groupFile.exists()) { + return false; + } + + Map values = readMapFromFile(groupFile); + Set data = deserializePermissions((List) values.get("permissions")); + Set nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); + group.setNodes(nodes); + return true; }, false); } finally { group.getIoLock().unlock(); @@ -339,11 +393,11 @@ public class YAMLBacking extends FlatfileBacking { registerFileAction("groups", file); String holder = file.getName().substring(0, file.getName().length() - 4); + + Map values = readMapFromFile(file); + Set nodes = new HashSet<>(); - readMapFromFile(file, values -> { - nodes.addAll(deserializePermissions((List) values.get("permissions"))); - return true; - }); + nodes.addAll(deserializePermissions((List) values.get("permissions"))); for (NodeModel e : nodes) { if (!e.getPermission().equalsIgnoreCase(permission)) { @@ -368,10 +422,9 @@ public class YAMLBacking extends FlatfileBacking { registerFileAction("tracks", trackFile); if (trackFile.exists()) { - return readMapFromFile(trackFile, values -> { - track.setGroups((List) values.get("groups")); - return true; - }); + Map values = readMapFromFile(trackFile); + track.setGroups((List) values.get("groups")); + return true; } else { try { trackFile.createNewFile(); @@ -401,10 +454,13 @@ public class YAMLBacking extends FlatfileBacking { File trackFile = new File(tracksDir, name + ".yml"); registerFileAction("tracks", trackFile); - return trackFile.exists() && readMapFromFile(trackFile, values -> { - track.setGroups((List) values.get("groups")); - return true; - }); + if (!trackFile.exists()) { + return false; + } + + Map values = readMapFromFile(trackFile); + track.setGroups((List) values.get("groups")); + return true; }, false); } finally { track.getIoLock().unlock(); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/LegacyJSONSchemaMigration.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/LegacyJSONSchemaMigration.java index e12039d48..cc8bf0cc1 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/LegacyJSONSchemaMigration.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/LegacyJSONSchemaMigration.java @@ -39,7 +39,6 @@ import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @SuppressWarnings("unchecked") @@ -69,16 +68,15 @@ public class LegacyJSONSchemaMigration implements Runnable { try { File replacementFile = new File(newGroupsDir, oldFile.getName()); - AtomicReference name = new AtomicReference<>(null); + JsonObject values = backing.readObjectFromFile(oldFile); + Map perms = new HashMap<>(); - backing.readObjectFromFile(oldFile, values -> { - name.set(values.get("name").getAsString()); - JsonObject permsSection = values.get("perms").getAsJsonObject(); - for (Map.Entry e : permsSection.entrySet()) { - perms.put(e.getKey(), e.getValue().getAsBoolean()); - } - return true; - }); + String name = values.get("name").getAsString(); + JsonObject permsSection = values.get("perms").getAsJsonObject(); + for (Map.Entry e : permsSection.entrySet()) { + perms.put(e.getKey(), e.getValue().getAsBoolean()); + } + Set nodes = perms.entrySet().stream() .map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue())) @@ -94,7 +92,7 @@ public class LegacyJSONSchemaMigration implements Runnable { } JsonObject data = new JsonObject(); - data.addProperty("name", name.get()); + data.addProperty("name", name); data.add("permissions", JSONBacking.serializePermissions(nodes)); backing.writeElementToFile(replacementFile, data); @@ -119,20 +117,16 @@ public class LegacyJSONSchemaMigration implements Runnable { try { File replacementFile = new File(newUsersDir, oldFile.getName()); - AtomicReference uuid = new AtomicReference<>(null); - AtomicReference name = new AtomicReference<>(null); - AtomicReference primaryGroup = new AtomicReference<>(null); + JsonObject values = backing.readObjectFromFile(oldFile); + Map perms = new HashMap<>(); - backing.readObjectFromFile(oldFile, values -> { - uuid.set(values.get("uuid").getAsString()); - name.set(values.get("name").getAsString()); - primaryGroup.set(values.get("primaryGroup").getAsString()); - JsonObject permsSection = values.get("perms").getAsJsonObject(); - for (Map.Entry e : permsSection.entrySet()) { - perms.put(e.getKey(), e.getValue().getAsBoolean()); - } - return true; - }); + String uuid = values.get("uuid").getAsString(); + String name = values.get("name").getAsString(); + String primaryGroup = values.get("primaryGroup").getAsString(); + JsonObject permsSection = values.get("perms").getAsJsonObject(); + for (Map.Entry e : permsSection.entrySet()) { + perms.put(e.getKey(), e.getValue().getAsBoolean()); + } Set nodes = perms.entrySet().stream() .map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue())) @@ -148,9 +142,9 @@ public class LegacyJSONSchemaMigration implements Runnable { } JsonObject data = new JsonObject(); - data.addProperty("uuid", uuid.get()); - data.addProperty("name", name.get()); - data.addProperty("primaryGroup", primaryGroup.get()); + data.addProperty("uuid", uuid); + data.addProperty("name", name); + data.addProperty("primaryGroup", primaryGroup); data.add("permissions", JSONBacking.serializePermissions(nodes)); backing.writeElementToFile(replacementFile, data); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/LegacyYAMLSchemaMigration.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/LegacyYAMLSchemaMigration.java index 3bfc69612..6537164bf 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/LegacyYAMLSchemaMigration.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/LegacyYAMLSchemaMigration.java @@ -37,7 +37,6 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @SuppressWarnings("unchecked") @@ -67,13 +66,11 @@ public class LegacyYAMLSchemaMigration implements Runnable { try { File replacementFile = new File(newGroupsDir, oldFile.getName()); - AtomicReference name = new AtomicReference<>(null); + Map data = backing.readMapFromFile(oldFile); + Map perms = new HashMap<>(); - backing.readMapFromFile(oldFile, values -> { - name.set((String) values.get("name")); - perms.putAll((Map) values.get("perms")); - return true; - }); + String name = (String) data.get("name"); + perms.putAll((Map) data.get("perms")); Set nodes = perms.entrySet().stream() .map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue())) @@ -89,7 +86,7 @@ public class LegacyYAMLSchemaMigration implements Runnable { } Map values = new LinkedHashMap<>(); - values.put("name", name.get()); + values.put("name", name); values.put("permissions", YAMLBacking.serializePermissions(nodes)); backing.writeMapToFile(replacementFile, values); @@ -114,17 +111,13 @@ public class LegacyYAMLSchemaMigration implements Runnable { try { File replacementFile = new File(newUsersDir, oldFile.getName()); - AtomicReference uuid = new AtomicReference<>(null); - AtomicReference name = new AtomicReference<>(null); - AtomicReference primaryGroup = new AtomicReference<>(null); + Map data = backing.readMapFromFile(oldFile); + Map perms = new HashMap<>(); - backing.readMapFromFile(oldFile, values -> { - uuid.set((String) values.get("uuid")); - name.set((String) values.get("name")); - primaryGroup.set((String) values.get("primary-group")); - perms.putAll((Map) values.get("perms")); - return true; - }); + String uuid = (String) data.get("uuid"); + String name = (String) data.get("name"); + String primaryGroup = (String) data.get("primary-group"); + perms.putAll((Map) data.get("perms")); Set nodes = perms.entrySet().stream() .map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue())) @@ -140,9 +133,9 @@ public class LegacyYAMLSchemaMigration implements Runnable { } Map values = new LinkedHashMap<>(); - values.put("uuid", uuid.get()); - values.put("name", name.get()); - values.put("primary-group", primaryGroup.get()); + values.put("uuid", uuid); + values.put("name", name); + values.put("primary-group", primaryGroup); values.put("permissions", YAMLBacking.serializePermissions(nodes)); backing.writeMapToFile(replacementFile, values); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/wrappings/TolerantStorage.java b/common/src/main/java/me/lucko/luckperms/common/storage/wrappings/TolerantStorage.java index 347e40c69..92c8e73a2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/wrappings/TolerantStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/wrappings/TolerantStorage.java @@ -31,6 +31,7 @@ import me.lucko.luckperms.api.LogEntry; import me.lucko.luckperms.api.event.cause.CreationCause; import me.lucko.luckperms.api.event.cause.DeletionCause; import me.lucko.luckperms.common.api.delegates.StorageDelegate; +import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.User; @@ -96,6 +97,16 @@ public class TolerantStorage implements Storage { } } + @Override + public CompletableFuture applyBulkUpdate(BulkUpdate bulkUpdate) { + phaser.register(); + try { + return backing.applyBulkUpdate(bulkUpdate); + } finally { + phaser.arriveAndDeregister(); + } + } + @Override public CompletableFuture loadUser(UUID uuid, String username) { phaser.register();