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.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;
}

View File

@ -170,7 +170,7 @@ public abstract class MainCommand<T> extends Command<Void, T> {
@Override
public void sendDetailedUsage(Sender sender, String label) {
sendUsage(sender, label);
}
@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_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_SUCCESS("&aApplied bulk change successfully. {0} records were changed.", true),

View File

@ -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),

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.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<Boolean> applyBulkUpdate(BulkUpdate bulkUpdate) {
return makeFuture(() -> backing.applyBulkUpdate(bulkUpdate));
}
@Override
public CompletableFuture<Boolean> loadUser(UUID uuid, String username) {
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.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);

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.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<Log> getLog();
CompletableFuture<Boolean> applyBulkUpdate(BulkUpdate bulkUpdate);
CompletableFuture<Boolean> loadUser(UUID uuid, String username);
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.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);

View File

@ -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<JsonObject, Boolean> 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<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
@ -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<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray());
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
user.setNodes(nodes);
Set<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray());
Set<Node> 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<NodeModel> 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<NodeModel> nodes = new HashSet<>();
readObjectFromFile(file, object -> {
nodes.addAll(deserializePermissions(object.get("permissions").getAsJsonArray()));
return true;
});
JsonObject object = readObjectFromFile(file);
Set<NodeModel> 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<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray());
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;
} 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<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray());
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
group.setNodes(nodes);
return true;
});
if (!groupFile.exists()) {
return false;
}
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);
} 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<NodeModel> 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<String> groups = new ArrayList<>();
for (JsonElement g : element.get("groups").getAsJsonArray()) {
groups.add(g.getAsString());
}
track.setGroups(groups);
return true;
});
JsonObject object = readObjectFromFile(trackFile);
List<String> 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<String> 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<String> groups = new ArrayList<>();
for (JsonElement g : object.get("groups").getAsJsonArray()) {
groups.add(g.getAsString());
}
track.setGroups(groups);
return true;
}, false);
} finally {

View File

@ -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<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
public boolean loadUser(UUID uuid, String 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.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));

View File

@ -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<Map<String, Object>, Boolean> readOperation) {
boolean success = false;
public Map<String, Object> readMapFromFile(File file) {
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) {
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<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
@ -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<String, Object> values = readMapFromFile(userFile);
Set<NodeModel> data = deserializePermissions((List<Object>) values.get("permissions"));
Set<Node> 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<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")) {
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<String, Object> values = readMapFromFile(file);
Set<NodeModel> nodes = new HashSet<>();
readMapFromFile(file, values -> {
nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
return true;
});
nodes.addAll(deserializePermissions((List<Object>) 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<String, Object> values = readMapFromFile(file);
Set<NodeModel> nodes = new HashSet<>();
readMapFromFile(file, values -> {
nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
return true;
});
nodes.addAll(deserializePermissions((List<Object>) 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<NodeModel> data = deserializePermissions((List<Object>) values.get("permissions"));
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;
} 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<NodeModel> data = deserializePermissions((List<Object>) values.get("permissions"));
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
group.setNodes(nodes);
return true;
});
if (!groupFile.exists()) {
return false;
}
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);
} 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<String, Object> values = readMapFromFile(file);
Set<NodeModel> nodes = new HashSet<>();
readMapFromFile(file, values -> {
nodes.addAll(deserializePermissions((List<Object>) values.get("permissions")));
return true;
});
nodes.addAll(deserializePermissions((List<Object>) 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<String>) values.get("groups"));
return true;
});
Map<String, Object> values = readMapFromFile(trackFile);
track.setGroups((List<String>) 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<String>) values.get("groups"));
return true;
});
if (!trackFile.exists()) {
return false;
}
Map<String, Object> values = readMapFromFile(trackFile);
track.setGroups((List<String>) values.get("groups"));
return true;
}, false);
} finally {
track.getIoLock().unlock();

View File

@ -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<String> name = new AtomicReference<>(null);
JsonObject values = backing.readObjectFromFile(oldFile);
Map<String, Boolean> perms = new HashMap<>();
backing.readObjectFromFile(oldFile, values -> {
name.set(values.get("name").getAsString());
JsonObject permsSection = values.get("perms").getAsJsonObject();
for (Map.Entry<String, JsonElement> 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<String, JsonElement> e : permsSection.entrySet()) {
perms.put(e.getKey(), e.getValue().getAsBoolean());
}
Set<NodeModel> 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<String> uuid = new AtomicReference<>(null);
AtomicReference<String> name = new AtomicReference<>(null);
AtomicReference<String> primaryGroup = new AtomicReference<>(null);
JsonObject values = backing.readObjectFromFile(oldFile);
Map<String, Boolean> 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<String, JsonElement> 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<String, JsonElement> e : permsSection.entrySet()) {
perms.put(e.getKey(), e.getValue().getAsBoolean());
}
Set<NodeModel> 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);

View File

@ -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<String> name = new AtomicReference<>(null);
Map<String, Object> data = backing.readMapFromFile(oldFile);
Map<String, Boolean> perms = new HashMap<>();
backing.readMapFromFile(oldFile, values -> {
name.set((String) values.get("name"));
perms.putAll((Map<String, Boolean>) values.get("perms"));
return true;
});
String name = (String) data.get("name");
perms.putAll((Map<String, Boolean>) data.get("perms"));
Set<NodeModel> nodes = perms.entrySet().stream()
.map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue()))
@ -89,7 +86,7 @@ public class LegacyYAMLSchemaMigration implements Runnable {
}
Map<String, Object> 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<String> uuid = new AtomicReference<>(null);
AtomicReference<String> name = new AtomicReference<>(null);
AtomicReference<String> primaryGroup = new AtomicReference<>(null);
Map<String, Object> data = backing.readMapFromFile(oldFile);
Map<String, Boolean> 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<String, Boolean>) 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<String, Boolean>) data.get("perms"));
Set<NodeModel> nodes = perms.entrySet().stream()
.map(e -> NodeFactory.fromSerializedNode(e.getKey(), e.getValue()))
@ -140,9 +133,9 @@ public class LegacyYAMLSchemaMigration implements Runnable {
}
Map<String, Object> 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);

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.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<Boolean> applyBulkUpdate(BulkUpdate bulkUpdate) {
phaser.register();
try {
return backing.applyBulkUpdate(bulkUpdate);
} finally {
phaser.arriveAndDeregister();
}
}
@Override
public CompletableFuture<Boolean> loadUser(UUID uuid, String username) {
phaser.register();