Re-implement bulk updates

This commit is contained in:
Luck 2017-04-10 19:33:23 +01:00
parent 4de8165c95
commit f6f9840eb7
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
30 changed files with 1399 additions and 173 deletions

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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<Constraint> 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 + "\"";
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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<Constraint> 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));
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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;
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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();
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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}";
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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);
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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);
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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;
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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);
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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);
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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);
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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);
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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();
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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;
}
}
}

View File

@ -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.group.ListGroups;
import me.lucko.luckperms.common.commands.impl.log.LogMainCommand; 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.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.CheckCommand;
import me.lucko.luckperms.common.commands.impl.misc.ExportCommand; import me.lucko.luckperms.common.commands.impl.misc.ExportCommand;
import me.lucko.luckperms.common.commands.impl.misc.ImportCommand; import me.lucko.luckperms.common.commands.impl.misc.ImportCommand;
@ -97,6 +98,7 @@ public class CommandManager {
.add(new ImportCommand()) .add(new ImportCommand())
.add(new ExportCommand()) .add(new ExportCommand())
.add(new ReloadConfigCommand()) .add(new ReloadConfigCommand())
.add(new BulkUpdateCommand())
.add(new MigrationMainCommand()) .add(new MigrationMainCommand())
.add(new CreateGroup()) .add(new CreateGroup())
.add(new DeleteGroup()) .add(new DeleteGroup())
@ -160,7 +162,7 @@ public class CommandManager {
// Check the correct number of args were given for the main command // Check the correct number of args were given for the main command
if (main.getArgumentCheck().test(arguments.size())) { if (main.getArgumentCheck().test(arguments.size())) {
main.sendUsage(sender, label); main.sendDetailedUsage(sender, label);
return CommandResult.INVALID_ARGS; return CommandResult.INVALID_ARGS;
} }

View File

@ -170,7 +170,7 @@ public abstract class MainCommand<T> extends Command<Void, T> {
@Override @Override
public void sendDetailedUsage(Sender sender, String label) { public void sendDetailedUsage(Sender sender, String label) {
sendUsage(sender, label);
} }
@Override @Override

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) 2017 Lucko (Luck) <luck@lucko.me>
*
* 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<String, BulkUpdate> 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<String> 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
}
}

View File

@ -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_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), 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<field> <comparison operator> <value>&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_TYPE_ERROR("Invalid type. Was expecting 'server' or 'world'.", true),
BULK_CHANGE_SUCCESS("&aApplied bulk change successfully. {0} records were changed.", true), BULK_CHANGE_SUCCESS("&aApplied bulk change successfully. {0} records were changed.", true),

View File

@ -44,6 +44,7 @@ public enum Permission {
IMPORT(list("import"), Type.NONE), IMPORT(list("import"), Type.NONE),
EXPORT(list("export"), Type.NONE), EXPORT(list("export"), Type.NONE),
RELOAD_CONFIG(list("reloadconfig"), Type.NONE), RELOAD_CONFIG(list("reloadconfig"), Type.NONE),
BULK_UPDATE(list("bulkupdate"), Type.NONE),
MIGRATION(list("migration"), Type.NONE), MIGRATION(list("migration"), Type.NONE),
CREATE_GROUP(list("creategroup"), Type.NONE), CREATE_GROUP(list("creategroup"), Type.NONE),
@ -88,7 +89,6 @@ public enum Permission {
USER_SHOWTRACKS(list("showtracks"), Type.USER), USER_SHOWTRACKS(list("showtracks"), Type.USER),
USER_PROMOTE(list("promote"), Type.USER), USER_PROMOTE(list("promote"), Type.USER),
USER_DEMOTE(list("demote"), Type.USER), USER_DEMOTE(list("demote"), Type.USER),
USER_BULKCHANGE(list("bulkchange"), Type.USER),
USER_CLEAR(list("clear"), Type.USER), USER_CLEAR(list("clear"), Type.USER),
GROUP_INFO(list("info"), Type.GROUP), GROUP_INFO(list("info"), Type.GROUP),
@ -123,7 +123,6 @@ public enum Permission {
GROUP_LISTMEMBERS(list("listmembers"), Type.GROUP), GROUP_LISTMEMBERS(list("listmembers"), Type.GROUP),
GROUP_SHOWTRACKS(list("showtracks"), Type.GROUP), GROUP_SHOWTRACKS(list("showtracks"), Type.GROUP),
GROUP_SETWEIGHT(list("setweight"), Type.GROUP), GROUP_SETWEIGHT(list("setweight"), Type.GROUP),
GROUP_BULKCHANGE(list("bulkchange"), Type.GROUP),
GROUP_CLEAR(list("clear"), Type.GROUP), GROUP_CLEAR(list("clear"), Type.GROUP),
GROUP_RENAME(list("rename"), Type.GROUP), GROUP_RENAME(list("rename"), Type.GROUP),
GROUP_CLONE(list("clone"), Type.GROUP), GROUP_CLONE(list("clone"), Type.GROUP),

View File

@ -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.CreationCause;
import me.lucko.luckperms.api.event.cause.DeletionCause; import me.lucko.luckperms.api.event.cause.DeletionCause;
import me.lucko.luckperms.common.api.delegates.StorageDelegate; 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.Group;
import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.Track;
import me.lucko.luckperms.common.core.model.User; import me.lucko.luckperms.common.core.model.User;
@ -91,6 +92,11 @@ public class AbstractStorage implements Storage {
return makeFuture(backing::getLog); return makeFuture(backing::getLog);
} }
@Override
public CompletableFuture<Boolean> applyBulkUpdate(BulkUpdate bulkUpdate) {
return makeFuture(() -> backing.applyBulkUpdate(bulkUpdate));
}
@Override @Override
public CompletableFuture<Boolean> loadUser(UUID uuid, String username) { public CompletableFuture<Boolean> loadUser(UUID uuid, String username) {
return makeFuture(() -> { return makeFuture(() -> {

View File

@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableMap;
import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.api.LogEntry; 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.Group;
import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.Track;
import me.lucko.luckperms.common.core.model.User; import me.lucko.luckperms.common.core.model.User;
@ -76,6 +77,20 @@ public class SplitBacking extends AbstractBacking {
return backing.get(types.get("log")).getLog(); 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 @Override
public boolean loadUser(UUID uuid, String username) { public boolean loadUser(UUID uuid, String username) {
return backing.get(types.get("user")).loadUser(uuid, username); return backing.get(types.get("user")).loadUser(uuid, username);

View File

@ -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.CreationCause;
import me.lucko.luckperms.api.event.cause.DeletionCause; import me.lucko.luckperms.api.event.cause.DeletionCause;
import me.lucko.luckperms.common.api.delegates.StorageDelegate; 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.Group;
import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.Track;
import me.lucko.luckperms.common.core.model.User; import me.lucko.luckperms.common.core.model.User;
@ -60,6 +61,8 @@ public interface Storage {
CompletableFuture<Log> getLog(); CompletableFuture<Log> getLog();
CompletableFuture<Boolean> applyBulkUpdate(BulkUpdate bulkUpdate);
CompletableFuture<Boolean> loadUser(UUID uuid, String username); CompletableFuture<Boolean> loadUser(UUID uuid, String username);
CompletableFuture<Boolean> saveUser(User user); CompletableFuture<Boolean> saveUser(User user);

View File

@ -29,6 +29,7 @@ import lombok.Setter;
import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.api.LogEntry; 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.Group;
import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.Track;
import me.lucko.luckperms.common.core.model.User; import me.lucko.luckperms.common.core.model.User;
@ -60,6 +61,8 @@ public abstract class AbstractBacking {
public abstract Log getLog(); public abstract Log getLog();
public abstract boolean applyBulkUpdate(BulkUpdate bulkUpdate);
public abstract boolean loadUser(UUID uuid, String username); public abstract boolean loadUser(UUID uuid, String username);
public abstract boolean saveUser(User user); public abstract boolean saveUser(User user);

View File

@ -34,6 +34,7 @@ import com.google.gson.JsonPrimitive;
import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.ImmutableContextSet; 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.NodeModel;
import me.lucko.luckperms.common.core.PriorityComparator; import me.lucko.luckperms.common.core.PriorityComparator;
import me.lucko.luckperms.common.core.UserIdentifier; import me.lucko.luckperms.common.core.UserIdentifier;
@ -55,9 +56,9 @@ import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@SuppressWarnings("ResultOfMethodCallIgnored") @SuppressWarnings("ResultOfMethodCallIgnored")
@ -81,16 +82,69 @@ public class JSONBacking extends FlatfileBacking {
} }
} }
public boolean readObjectFromFile(File file, Function<JsonObject, Boolean> readOperation) { public JsonObject readObjectFromFile(File file) {
boolean success = false;
try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
JsonObject object = gson.fromJson(reader, JsonObject.class); return gson.fromJson(reader, JsonObject.class);
success = readOperation.apply(object);
} catch (Throwable t) { } catch (Throwable t) {
plugin.getLog().warn("Exception whilst reading from file: " + file.getAbsolutePath()); plugin.getLog().warn("Exception whilst reading from file: " + file.getAbsolutePath());
t.printStackTrace(); 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<NodeModel> nodes = new HashSet<>();
nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray()));
Set<NodeModel> 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<NodeModel> nodes = new HashSet<>();
nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray()));
Set<NodeModel> 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 @Override
@ -103,30 +157,29 @@ public class JSONBacking extends FlatfileBacking {
registerFileAction("users", userFile); registerFileAction("users", userFile);
if (userFile.exists()) { if (userFile.exists()) {
return readObjectFromFile(userFile, object -> { JsonObject object = readObjectFromFile(userFile);
String name = object.get("name").getAsString(); String name = object.get("name").getAsString();
user.getPrimaryGroup().setStoredValue(object.get("primaryGroup").getAsString()); user.getPrimaryGroup().setStoredValue(object.get("primaryGroup").getAsString());
Set<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray()); Set<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray());
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
user.setNodes(nodes); 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")) { if (user.getName() == null || user.getName().equalsIgnoreCase("null")) {
user.setName(name); user.setName(name);
} else { } else {
if (!name.equalsIgnoreCase(user.getName())) { if (!name.equalsIgnoreCase(user.getName())) {
save = true; save = true;
}
} }
}
if (save) { if (save) {
saveUser(user); saveUser(user);
} }
return true; return true;
});
} else { } else {
if (GenericUserManager.shouldSave(user)) { if (GenericUserManager.shouldSave(user)) {
user.clearNodes(); user.clearNodes();
@ -190,11 +243,10 @@ public class JSONBacking extends FlatfileBacking {
for (File file : files) { for (File file : files) {
registerFileAction("users", file); registerFileAction("users", file);
JsonObject object = readObjectFromFile(file);
Set<NodeModel> nodes = new HashSet<>(); Set<NodeModel> nodes = new HashSet<>();
readObjectFromFile(file, object -> { nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray()));
nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray()));
return true;
});
boolean shouldDelete = false; boolean shouldDelete = false;
if (nodes.size() == 1) { if (nodes.size() == 1) {
@ -223,12 +275,11 @@ public class JSONBacking extends FlatfileBacking {
registerFileAction("users", file); registerFileAction("users", file);
UUID holder = UUID.fromString(file.getName().substring(0, file.getName().length() - 5)); UUID holder = UUID.fromString(file.getName().substring(0, file.getName().length() - 5));
Set<NodeModel> nodes = new HashSet<>();
readObjectFromFile(file, object -> { JsonObject object = readObjectFromFile(file);
nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray()));
return true; Set<NodeModel> nodes = new HashSet<>();
}); nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray()));
for (NodeModel e : nodes) { for (NodeModel e : nodes) {
if (!e.getPermission().equalsIgnoreCase(permission)) { if (!e.getPermission().equalsIgnoreCase(permission)) {
@ -253,12 +304,11 @@ public class JSONBacking extends FlatfileBacking {
registerFileAction("groups", groupFile); registerFileAction("groups", groupFile);
if (groupFile.exists()) { if (groupFile.exists()) {
return readObjectFromFile(groupFile, object -> { JsonObject object = readObjectFromFile(groupFile);
Set<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray()); Set<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray());
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
group.setNodes(nodes); group.setNodes(nodes);
return true; return true;
});
} else { } else {
try { try {
groupFile.createNewFile(); groupFile.createNewFile();
@ -290,12 +340,15 @@ public class JSONBacking extends FlatfileBacking {
File groupFile = new File(groupsDir, name + ".json"); File groupFile = new File(groupsDir, name + ".json");
registerFileAction("groups", groupFile); registerFileAction("groups", groupFile);
return groupFile.exists() && readObjectFromFile(groupFile, object -> { if (!groupFile.exists()) {
Set<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray()); return false;
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); }
group.setNodes(nodes);
return true; JsonObject object = readObjectFromFile(groupFile);
}); Set<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray());
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
group.setNodes(nodes);
return true;
}, false); }, false);
} finally { } finally {
group.getIoLock().unlock(); group.getIoLock().unlock();
@ -341,11 +394,11 @@ public class JSONBacking extends FlatfileBacking {
registerFileAction("groups", file); registerFileAction("groups", file);
String holder = file.getName().substring(0, file.getName().length() - 5); String holder = file.getName().substring(0, file.getName().length() - 5);
JsonObject object = readObjectFromFile(file);
Set<NodeModel> nodes = new HashSet<>(); Set<NodeModel> nodes = new HashSet<>();
readObjectFromFile(file, element -> { nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray()));
nodes.addAll(deserializePermissions(element.get("permissions").getAsJsonArray()));
return true;
});
for (NodeModel e : nodes) { for (NodeModel e : nodes) {
if (!e.getPermission().equalsIgnoreCase(permission)) { if (!e.getPermission().equalsIgnoreCase(permission)) {
@ -370,14 +423,13 @@ public class JSONBacking extends FlatfileBacking {
registerFileAction("tracks", trackFile); registerFileAction("tracks", trackFile);
if (trackFile.exists()) { if (trackFile.exists()) {
return readObjectFromFile(trackFile, element -> { JsonObject object = readObjectFromFile(trackFile);
List<String> groups = new ArrayList<>(); List<String> groups = new ArrayList<>();
for (JsonElement g : element.get("groups").getAsJsonArray()) { for (JsonElement g : object.get("groups").getAsJsonArray()) {
groups.add(g.getAsString()); groups.add(g.getAsString());
} }
track.setGroups(groups); track.setGroups(groups);
return true; return true;
});
} else { } else {
try { try {
trackFile.createNewFile(); trackFile.createNewFile();
@ -411,14 +463,17 @@ public class JSONBacking extends FlatfileBacking {
File trackFile = new File(tracksDir, name + ".json"); File trackFile = new File(tracksDir, name + ".json");
registerFileAction("tracks", trackFile); registerFileAction("tracks", trackFile);
return trackFile.exists() && readObjectFromFile(trackFile, element -> { if (!trackFile.exists()) {
List<String> groups = new ArrayList<>(); return false;
for (JsonElement g : element.get("groups").getAsJsonArray()) { }
groups.add(g.getAsString());
} JsonObject object = readObjectFromFile(trackFile);
track.setGroups(groups); List<String> groups = new ArrayList<>();
return true; for (JsonElement g : object.get("groups").getAsJsonArray()) {
}); groups.add(g.getAsString());
}
track.setGroups(groups);
return true;
}, false); }, false);
} finally { } finally {

View File

@ -34,7 +34,9 @@ import com.mongodb.client.model.InsertOneOptions;
import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.api.LogEntry; import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.Node; 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.NodeFactory;
import me.lucko.luckperms.common.core.NodeModel;
import me.lucko.luckperms.common.core.UserIdentifier; import me.lucko.luckperms.common.core.UserIdentifier;
import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.Group;
import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.Track;
@ -55,6 +57,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@ -224,6 +227,81 @@ public class MongoDBBacking extends AbstractBacking {
}, null); }, null);
} }
@Override
public boolean applyBulkUpdate(BulkUpdate bulkUpdate) {
return call(() -> {
if (bulkUpdate.getDataType().isIncludingUsers()) {
MongoCollection<Document> c = database.getCollection("users");
try (MongoCursor<Document> cursor = c.find().iterator()) {
while (cursor.hasNext()) {
Document d = cursor.next();
UUID uuid = UUID.fromString(d.getString("_id"));
Map<String, Boolean> perms = revert((Map<String, Boolean>) d.get("perms"));
Set<NodeModel> nodes = new HashSet<>();
for (Map.Entry<String, Boolean> e : perms.entrySet()) {
Node node = NodeFactory.fromSerializedNode(e.getKey(), e.getValue());
nodes.add(NodeModel.fromNode(node));
}
Set<Node> 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<String, Boolean> 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<Document> c = database.getCollection("groups");
try (MongoCursor<Document> cursor = c.find().iterator()) {
while (cursor.hasNext()) {
Document d = cursor.next();
String holder = d.getString("_id");
Map<String, Boolean> perms = revert((Map<String, Boolean>) d.get("perms"));
Set<NodeModel> nodes = new HashSet<>();
for (Map.Entry<String, Boolean> e : perms.entrySet()) {
Node node = NodeFactory.fromSerializedNode(e.getKey(), e.getValue());
nodes.add(NodeModel.fromNode(node));
}
Set<Node> 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<String, Boolean> 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 @Override
public boolean loadUser(UUID uuid, String username) { public boolean loadUser(UUID uuid, String username) {
User user = plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username)); User user = plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username));

View File

@ -32,6 +32,7 @@ import com.google.gson.reflect.TypeToken;
import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.api.LogEntry; import me.lucko.luckperms.api.LogEntry;
import me.lucko.luckperms.api.Node; 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.NodeModel;
import me.lucko.luckperms.common.core.UserIdentifier; import me.lucko.luckperms.common.core.UserIdentifier;
import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.Group;
@ -248,6 +249,42 @@ public class SQLBacking extends AbstractBacking {
return log.build(); 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 @Override
public boolean loadUser(UUID uuid, String username) { public boolean loadUser(UUID uuid, String username) {
User user = plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username)); User user = plugin.getUserManager().getOrMake(UserIdentifier.of(uuid, username));

View File

@ -29,6 +29,7 @@ import com.google.common.collect.Iterables;
import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.ImmutableContextSet; 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.NodeModel;
import me.lucko.luckperms.common.core.UserIdentifier; import me.lucko.luckperms.common.core.UserIdentifier;
import me.lucko.luckperms.common.core.model.Group; import me.lucko.luckperms.common.core.model.Group;
@ -55,9 +56,9 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"}) @SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"})
@ -85,15 +86,67 @@ public class YAMLBacking extends FlatfileBacking {
} }
} }
public boolean readMapFromFile(File file, Function<Map<String, Object>, Boolean> readOperation) { public Map<String, Object> readMapFromFile(File file) {
boolean success = false;
try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
success = readOperation.apply((Map<String, Object>) getYaml().load(reader)); return (Map<String, Object>) getYaml().load(reader);
} catch (Throwable t) { } catch (Throwable t) {
plugin.getLog().warn("Exception whilst reading from file: " + file.getAbsolutePath()); plugin.getLog().warn("Exception whilst reading from file: " + file.getAbsolutePath());
t.printStackTrace(); 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<String, Object> values = readMapFromFile(file);
Set<NodeModel> nodes = new HashSet<>();
nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
Set<NodeModel> 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<String, Object> values = readMapFromFile(file);
Set<NodeModel> nodes = new HashSet<>();
nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
Set<NodeModel> 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 @Override
@ -105,30 +158,30 @@ public class YAMLBacking extends FlatfileBacking {
File userFile = new File(usersDir, uuid.toString() + ".yml"); File userFile = new File(usersDir, uuid.toString() + ".yml");
registerFileAction("users", userFile); registerFileAction("users", userFile);
if (userFile.exists()) { if (userFile.exists()) {
return readMapFromFile(userFile, values -> { Map<String, Object> values = readMapFromFile(userFile);
// User exists, let's load.
String name = (String) values.get("name");
user.getPrimaryGroup().setStoredValue((String) values.get("primary-group"));
Set<NodeModel> data = deserializePermissions((List<Object>) values.get("permissions")); // User exists, let's load.
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); String name = (String) values.get("name");
user.setNodes(nodes); user.getPrimaryGroup().setStoredValue((String) values.get("primary-group"));
boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false); Set<NodeModel> data = deserializePermissions((List<Object>) values.get("permissions"));
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
user.setNodes(nodes);
if (user.getName() == null || user.getName().equalsIgnoreCase("null")) { boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false);
user.setName(name);
} else { if (user.getName() == null || user.getName().equalsIgnoreCase("null")) {
if (!name.equalsIgnoreCase(user.getName())) { user.setName(name);
save = true; } else {
} if (!name.equalsIgnoreCase(user.getName())) {
save = true;
} }
}
if (save) { if (save) {
saveUser(user); saveUser(user);
} }
return true; return true;
});
} else { } else {
if (GenericUserManager.shouldSave(user)) { if (GenericUserManager.shouldSave(user)) {
user.clearNodes(); user.clearNodes();
@ -191,11 +244,10 @@ public class YAMLBacking extends FlatfileBacking {
for (File file : files) { for (File file : files) {
registerFileAction("users", file); registerFileAction("users", file);
Map<String, Object> values = readMapFromFile(file);
Set<NodeModel> nodes = new HashSet<>(); Set<NodeModel> nodes = new HashSet<>();
readMapFromFile(file, values -> { nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
return true;
});
boolean shouldDelete = false; boolean shouldDelete = false;
if (nodes.size() == 1) { if (nodes.size() == 1) {
@ -224,11 +276,11 @@ public class YAMLBacking extends FlatfileBacking {
registerFileAction("users", file); registerFileAction("users", file);
UUID holder = UUID.fromString(file.getName().substring(0, file.getName().length() - 4)); UUID holder = UUID.fromString(file.getName().substring(0, file.getName().length() - 4));
Map<String, Object> values = readMapFromFile(file);
Set<NodeModel> nodes = new HashSet<>(); Set<NodeModel> nodes = new HashSet<>();
readMapFromFile(file, values -> { nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
return true;
});
for (NodeModel e : nodes) { for (NodeModel e : nodes) {
if (!e.getPermission().equalsIgnoreCase(permission)) { if (!e.getPermission().equalsIgnoreCase(permission)) {
@ -253,12 +305,11 @@ public class YAMLBacking extends FlatfileBacking {
registerFileAction("groups", groupFile); registerFileAction("groups", groupFile);
if (groupFile.exists()) { if (groupFile.exists()) {
return readMapFromFile(groupFile, values -> { Map<String, Object> values = readMapFromFile(groupFile);
Set<NodeModel> data = deserializePermissions((List<Object>) values.get("permissions")); Set<NodeModel> data = deserializePermissions((List<Object>) values.get("permissions"));
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
group.setNodes(nodes); group.setNodes(nodes);
return true; return true;
});
} else { } else {
try { try {
groupFile.createNewFile(); groupFile.createNewFile();
@ -288,12 +339,15 @@ public class YAMLBacking extends FlatfileBacking {
File groupFile = new File(groupsDir, name + ".yml"); File groupFile = new File(groupsDir, name + ".yml");
registerFileAction("groups", groupFile); registerFileAction("groups", groupFile);
return groupFile.exists() && readMapFromFile(groupFile, values -> { if (!groupFile.exists()) {
Set<NodeModel> data = deserializePermissions((List<Object>) values.get("permissions")); return false;
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet()); }
group.setNodes(nodes);
return true; Map<String, Object> values = readMapFromFile(groupFile);
}); Set<NodeModel> data = deserializePermissions((List<Object>) values.get("permissions"));
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
group.setNodes(nodes);
return true;
}, false); }, false);
} finally { } finally {
group.getIoLock().unlock(); group.getIoLock().unlock();
@ -339,11 +393,11 @@ public class YAMLBacking extends FlatfileBacking {
registerFileAction("groups", file); registerFileAction("groups", file);
String holder = file.getName().substring(0, file.getName().length() - 4); String holder = file.getName().substring(0, file.getName().length() - 4);
Map<String, Object> values = readMapFromFile(file);
Set<NodeModel> nodes = new HashSet<>(); Set<NodeModel> nodes = new HashSet<>();
readMapFromFile(file, values -> { nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
return true;
});
for (NodeModel e : nodes) { for (NodeModel e : nodes) {
if (!e.getPermission().equalsIgnoreCase(permission)) { if (!e.getPermission().equalsIgnoreCase(permission)) {
@ -368,10 +422,9 @@ public class YAMLBacking extends FlatfileBacking {
registerFileAction("tracks", trackFile); registerFileAction("tracks", trackFile);
if (trackFile.exists()) { if (trackFile.exists()) {
return readMapFromFile(trackFile, values -> { Map<String, Object> values = readMapFromFile(trackFile);
track.setGroups((List<String>) values.get("groups")); track.setGroups((List<String>) values.get("groups"));
return true; return true;
});
} else { } else {
try { try {
trackFile.createNewFile(); trackFile.createNewFile();
@ -401,10 +454,13 @@ public class YAMLBacking extends FlatfileBacking {
File trackFile = new File(tracksDir, name + ".yml"); File trackFile = new File(tracksDir, name + ".yml");
registerFileAction("tracks", trackFile); registerFileAction("tracks", trackFile);
return trackFile.exists() && readMapFromFile(trackFile, values -> { if (!trackFile.exists()) {
track.setGroups((List<String>) values.get("groups")); return false;
return true; }
});
Map<String, Object> values = readMapFromFile(trackFile);
track.setGroups((List<String>) values.get("groups"));
return true;
}, false); }, false);
} finally { } finally {
track.getIoLock().unlock(); track.getIoLock().unlock();

View File

@ -39,7 +39,6 @@ import java.util.HashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -69,16 +68,15 @@ public class LegacyJSONSchemaMigration implements Runnable {
try { try {
File replacementFile = new File(newGroupsDir, oldFile.getName()); File replacementFile = new File(newGroupsDir, oldFile.getName());
AtomicReference<String> name = new AtomicReference<>(null); JsonObject values = backing.readObjectFromFile(oldFile);
Map<String, Boolean> perms = new HashMap<>(); Map<String, Boolean> perms = new HashMap<>();
backing.readObjectFromFile(oldFile, values -> { String name = values.get("name").getAsString();
name.set(values.get("name").getAsString()); JsonObject permsSection = values.get("perms").getAsJsonObject();
JsonObject permsSection = values.get("perms").getAsJsonObject(); for (Map.Entry<String, JsonElement> e : permsSection.entrySet()) {
for (Map.Entry<String, JsonElement> e : permsSection.entrySet()) { perms.put(e.getKey(), e.getValue().getAsBoolean());
perms.put(e.getKey(), e.getValue().getAsBoolean()); }
}
return true;
});
Set<NodeModel> nodes = perms.entrySet().stream() Set<NodeModel> nodes = perms.entrySet().stream()
.map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue())) .map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue()))
@ -94,7 +92,7 @@ public class LegacyJSONSchemaMigration implements Runnable {
} }
JsonObject data = new JsonObject(); JsonObject data = new JsonObject();
data.addProperty("name", name.get()); data.addProperty("name", name);
data.add("permissions", JSONBacking.serializePermissions(nodes)); data.add("permissions", JSONBacking.serializePermissions(nodes));
backing.writeElementToFile(replacementFile, data); backing.writeElementToFile(replacementFile, data);
@ -119,20 +117,16 @@ public class LegacyJSONSchemaMigration implements Runnable {
try { try {
File replacementFile = new File(newUsersDir, oldFile.getName()); File replacementFile = new File(newUsersDir, oldFile.getName());
AtomicReference<String> uuid = new AtomicReference<>(null); JsonObject values = backing.readObjectFromFile(oldFile);
AtomicReference<String> name = new AtomicReference<>(null);
AtomicReference<String> primaryGroup = new AtomicReference<>(null);
Map<String, Boolean> perms = new HashMap<>(); Map<String, Boolean> perms = new HashMap<>();
backing.readObjectFromFile(oldFile, values -> { String uuid = values.get("uuid").getAsString();
uuid.set(values.get("uuid").getAsString()); String name = values.get("name").getAsString();
name.set(values.get("name").getAsString()); String primaryGroup = values.get("primaryGroup").getAsString();
primaryGroup.set(values.get("primaryGroup").getAsString()); JsonObject permsSection = values.get("perms").getAsJsonObject();
JsonObject permsSection = values.get("perms").getAsJsonObject(); for (Map.Entry<String, JsonElement> e : permsSection.entrySet()) {
for (Map.Entry<String, JsonElement> e : permsSection.entrySet()) { perms.put(e.getKey(), e.getValue().getAsBoolean());
perms.put(e.getKey(), e.getValue().getAsBoolean()); }
}
return true;
});
Set<NodeModel> nodes = perms.entrySet().stream() Set<NodeModel> nodes = perms.entrySet().stream()
.map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue())) .map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue()))
@ -148,9 +142,9 @@ public class LegacyJSONSchemaMigration implements Runnable {
} }
JsonObject data = new JsonObject(); JsonObject data = new JsonObject();
data.addProperty("uuid", uuid.get()); data.addProperty("uuid", uuid);
data.addProperty("name", name.get()); data.addProperty("name", name);
data.addProperty("primaryGroup", primaryGroup.get()); data.addProperty("primaryGroup", primaryGroup);
data.add("permissions", JSONBacking.serializePermissions(nodes)); data.add("permissions", JSONBacking.serializePermissions(nodes));
backing.writeElementToFile(replacementFile, data); backing.writeElementToFile(replacementFile, data);

View File

@ -37,7 +37,6 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -67,13 +66,11 @@ public class LegacyYAMLSchemaMigration implements Runnable {
try { try {
File replacementFile = new File(newGroupsDir, oldFile.getName()); File replacementFile = new File(newGroupsDir, oldFile.getName());
AtomicReference<String> name = new AtomicReference<>(null); Map<String, Object> data = backing.readMapFromFile(oldFile);
Map<String, Boolean> perms = new HashMap<>(); Map<String, Boolean> perms = new HashMap<>();
backing.readMapFromFile(oldFile, values -> { String name = (String) data.get("name");
name.set((String) values.get("name")); perms.putAll((Map<String, Boolean>) data.get("perms"));
perms.putAll((Map<String, Boolean>) values.get("perms"));
return true;
});
Set<NodeModel> nodes = perms.entrySet().stream() Set<NodeModel> nodes = perms.entrySet().stream()
.map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue())) .map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue()))
@ -89,7 +86,7 @@ public class LegacyYAMLSchemaMigration implements Runnable {
} }
Map<String, Object> values = new LinkedHashMap<>(); Map<String, Object> values = new LinkedHashMap<>();
values.put("name", name.get()); values.put("name", name);
values.put("permissions", YAMLBacking.serializePermissions(nodes)); values.put("permissions", YAMLBacking.serializePermissions(nodes));
backing.writeMapToFile(replacementFile, values); backing.writeMapToFile(replacementFile, values);
@ -114,17 +111,13 @@ public class LegacyYAMLSchemaMigration implements Runnable {
try { try {
File replacementFile = new File(newUsersDir, oldFile.getName()); File replacementFile = new File(newUsersDir, oldFile.getName());
AtomicReference<String> uuid = new AtomicReference<>(null); Map<String, Object> data = backing.readMapFromFile(oldFile);
AtomicReference<String> name = new AtomicReference<>(null);
AtomicReference<String> primaryGroup = new AtomicReference<>(null);
Map<String, Boolean> perms = new HashMap<>(); Map<String, Boolean> perms = new HashMap<>();
backing.readMapFromFile(oldFile, values -> { String uuid = (String) data.get("uuid");
uuid.set((String) values.get("uuid")); String name = (String) data.get("name");
name.set((String) values.get("name")); String primaryGroup = (String) data.get("primary-group");
primaryGroup.set((String) values.get("primary-group")); perms.putAll((Map<String, Boolean>) data.get("perms"));
perms.putAll((Map<String, Boolean>) values.get("perms"));
return true;
});
Set<NodeModel> nodes = perms.entrySet().stream() Set<NodeModel> nodes = perms.entrySet().stream()
.map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue())) .map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue()))
@ -140,9 +133,9 @@ public class LegacyYAMLSchemaMigration implements Runnable {
} }
Map<String, Object> values = new LinkedHashMap<>(); Map<String, Object> values = new LinkedHashMap<>();
values.put("uuid", uuid.get()); values.put("uuid", uuid);
values.put("name", name.get()); values.put("name", name);
values.put("primary-group", primaryGroup.get()); values.put("primary-group", primaryGroup);
values.put("permissions", YAMLBacking.serializePermissions(nodes)); values.put("permissions", YAMLBacking.serializePermissions(nodes));
backing.writeMapToFile(replacementFile, values); backing.writeMapToFile(replacementFile, values);

View File

@ -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.CreationCause;
import me.lucko.luckperms.api.event.cause.DeletionCause; import me.lucko.luckperms.api.event.cause.DeletionCause;
import me.lucko.luckperms.common.api.delegates.StorageDelegate; 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.Group;
import me.lucko.luckperms.common.core.model.Track; import me.lucko.luckperms.common.core.model.Track;
import me.lucko.luckperms.common.core.model.User; import me.lucko.luckperms.common.core.model.User;
@ -96,6 +97,16 @@ public class TolerantStorage implements Storage {
} }
} }
@Override
public CompletableFuture<Boolean> applyBulkUpdate(BulkUpdate bulkUpdate) {
phaser.register();
try {
return backing.applyBulkUpdate(bulkUpdate);
} finally {
phaser.arriveAndDeregister();
}
}
@Override @Override
public CompletableFuture<Boolean> loadUser(UUID uuid, String username) { public CompletableFuture<Boolean> loadUser(UUID uuid, String username) {
phaser.register(); phaser.register();