mirror of
https://github.com/LuckPerms/LuckPerms.git
synced 2024-12-17 22:57:49 +01:00
Track individual changes to users/groups instead of writing in full on each save (#2767)
This commit is contained in:
parent
97d1deec9c
commit
478fddc486
@ -94,7 +94,7 @@ public class ParentClearTrack extends GenericChildCommand {
|
||||
target.removeIf(DataType.NORMAL, context.isEmpty() ? null : context, NodeType.INHERITANCE.predicate(n -> track.containsGroup(n.getGroupName())), false);
|
||||
|
||||
if (target.getType() == HolderType.USER) {
|
||||
plugin.getUserManager().giveDefaultIfNeeded(((User) target), false);
|
||||
plugin.getUserManager().giveDefaultIfNeeded(((User) target));
|
||||
}
|
||||
|
||||
int changed = before - target.normalData().size();
|
||||
|
@ -101,7 +101,7 @@ public class ParentRemove extends GenericChildCommand {
|
||||
.build().submit(plugin, sender);
|
||||
|
||||
if (target.getType() == HolderType.USER) {
|
||||
plugin.getUserManager().giveDefaultIfNeeded(((User) target), false);
|
||||
plugin.getUserManager().giveDefaultIfNeeded(((User) target));
|
||||
}
|
||||
|
||||
StorageAssistant.save(target, sender, plugin);
|
||||
|
@ -84,7 +84,7 @@ public class GroupListMembers extends ChildCommand<Group> {
|
||||
if (target.getName().equals(GroupManager.DEFAULT_GROUP_NAME)) {
|
||||
// include all non-saved online players in the results
|
||||
for (User user : plugin.getUserManager().getAll().values()) {
|
||||
if (!plugin.getUserManager().shouldSave(user)) {
|
||||
if (!plugin.getUserManager().isNonDefaultUser(user)) {
|
||||
matchedUsers.add(NodeEntry.of(user.getUniqueId(), node));
|
||||
}
|
||||
}
|
||||
|
@ -1,467 +0,0 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* 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.model;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import me.lucko.luckperms.common.config.ConfigKeys;
|
||||
import me.lucko.luckperms.common.context.ContextSetComparator;
|
||||
import me.lucko.luckperms.common.node.comparator.NodeComparator;
|
||||
import me.lucko.luckperms.common.node.comparator.NodeWithContextComparator;
|
||||
|
||||
import net.luckperms.api.context.ContextSatisfyMode;
|
||||
import net.luckperms.api.context.ContextSet;
|
||||
import net.luckperms.api.context.DefaultContextKeys;
|
||||
import net.luckperms.api.context.ImmutableContextSet;
|
||||
import net.luckperms.api.node.Node;
|
||||
import net.luckperms.api.node.NodeEqualityPredicate;
|
||||
import net.luckperms.api.node.NodeType;
|
||||
import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata;
|
||||
import net.luckperms.api.node.types.InheritanceNode;
|
||||
import net.luckperms.api.query.Flag;
|
||||
import net.luckperms.api.query.QueryOptions;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A map of nodes held by a {@link PermissionHolder}.
|
||||
*
|
||||
* <p>Permissions are stored in Multimaps, with the context of the node being the key, and the actual Node object being
|
||||
* the value. The keys (context sets) are ordered according to their weight {@link ContextSetComparator}, and the values
|
||||
* are ordered according to the priority of the node, according to {@link NodeComparator}.</p>
|
||||
*
|
||||
* <p>Each holder has two of these maps, one for enduring and transient nodes.</p>
|
||||
*/
|
||||
public final class NodeMap {
|
||||
private static final Function<ImmutableContextSet, SortedSet<Node>> VALUE_SET_SUPPLIER = k -> new ConcurrentSkipListSet<>(NodeComparator.reverse());
|
||||
private static final Function<ImmutableContextSet, SortedSet<InheritanceNode>> INHERITANCE_VALUE_SET_SUPPLIER = k -> new ConcurrentSkipListSet<>(NodeComparator.reverse());
|
||||
|
||||
/**
|
||||
* The holder which this map is for
|
||||
*/
|
||||
private final PermissionHolder holder;
|
||||
|
||||
/**
|
||||
* The backing data map.
|
||||
*
|
||||
* <p>Nodes are mapped by the result of {@link Node#getContexts()}, and keys are sorted by the weight of the
|
||||
* ContextSet. ContextSets are ordered first by the presence of a server key, then by the presence of a world
|
||||
* key, and finally by the overall size of the set. Nodes are ordered according to the priority rules
|
||||
* defined in {@link NodeComparator}.</p>
|
||||
*/
|
||||
private final SortedMap<ImmutableContextSet, SortedSet<Node>> map = new ConcurrentSkipListMap<>(ContextSetComparator.reverse());
|
||||
|
||||
/**
|
||||
* Copy of {@link #map} which only contains group nodes
|
||||
* @see InheritanceNode
|
||||
*/
|
||||
private final SortedMap<ImmutableContextSet, SortedSet<InheritanceNode>> inheritanceMap = new ConcurrentSkipListMap<>(ContextSetComparator.reverse());
|
||||
|
||||
NodeMap(PermissionHolder holder) {
|
||||
this.holder = holder;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.map.isEmpty();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
int size = 0;
|
||||
for (SortedSet<Node> values : this.map.values()) {
|
||||
size += values.size();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public List<Node> asList() {
|
||||
List<Node> list = new ArrayList<>();
|
||||
copyTo(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public LinkedHashSet<Node> asSet() {
|
||||
LinkedHashSet<Node> set = new LinkedHashSet<>();
|
||||
copyTo(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
public SortedSet<Node> asSortedSet() {
|
||||
SortedSet<Node> set = new TreeSet<>(NodeWithContextComparator.reverse());
|
||||
copyTo(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
public ImmutableSet<Node> asImmutableSet() {
|
||||
ImmutableSet.Builder<Node> builder = ImmutableSet.builder();
|
||||
for (SortedSet<Node> values : this.map.values()) {
|
||||
builder.addAll(values);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public Map<ImmutableContextSet, Collection<Node>> asMap() {
|
||||
Map<ImmutableContextSet, Collection<Node>> map = new HashMap<>();
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : this.map.entrySet()) {
|
||||
map.put(e.getKey(), new ArrayList<>(e.getValue()));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public List<InheritanceNode> inheritanceAsList() {
|
||||
List<InheritanceNode> set = new ArrayList<>();
|
||||
copyInheritanceNodesTo(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
public LinkedHashSet<InheritanceNode> inheritanceAsSet() {
|
||||
LinkedHashSet<InheritanceNode> set = new LinkedHashSet<>();
|
||||
copyInheritanceNodesTo(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
public SortedSet<InheritanceNode> inheritanceAsSortedSet() {
|
||||
SortedSet<InheritanceNode> set = new TreeSet<>(NodeWithContextComparator.reverse());
|
||||
copyInheritanceNodesTo(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
public Map<ImmutableContextSet, Collection<InheritanceNode>> inheritanceAsMap() {
|
||||
Map<ImmutableContextSet, Collection<InheritanceNode>> map = new HashMap<>();
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<InheritanceNode>> e : this.inheritanceMap.entrySet()) {
|
||||
map.put(e.getKey(), new ArrayList<>(e.getValue()));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static boolean flagExcludeTest(Flag flag, String contextKey, QueryOptions filter, ImmutableContextSet contextSet) {
|
||||
// return true (negative result) if the explicit *include* flag is not set, and if the context set doesn't contain the required context key.
|
||||
return !filter.flag(flag) && !contextSet.containsKey(contextKey);
|
||||
}
|
||||
|
||||
private static boolean normalNodesExcludeTest(QueryOptions filter, ImmutableContextSet contextSet) {
|
||||
// return true (negative result) if normal nodes should not be included due to the lack of a server/world context.
|
||||
return flagExcludeTest(Flag.INCLUDE_NODES_WITHOUT_SERVER_CONTEXT, DefaultContextKeys.SERVER_KEY, filter, contextSet) ||
|
||||
flagExcludeTest(Flag.INCLUDE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet);
|
||||
}
|
||||
|
||||
private static boolean inheritanceNodesIncludeTest(QueryOptions filter, ImmutableContextSet contextSet) {
|
||||
// return true (positive result) if inheritance nodes should be included, due to the lack of any flags preventing their inclusion.
|
||||
return !flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_SERVER_CONTEXT, DefaultContextKeys.SERVER_KEY, filter, contextSet) &&
|
||||
!flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet);
|
||||
}
|
||||
|
||||
private ContextSatisfyMode defaultSatisfyMode() {
|
||||
return this.holder.getPlugin().getConfiguration().get(ConfigKeys.CONTEXT_SATISFY_MODE);
|
||||
}
|
||||
|
||||
public void forEach(Consumer<? super Node> consumer) {
|
||||
for (SortedSet<Node> values : this.map.values()) {
|
||||
values.forEach(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
public void forEach(QueryOptions filter, Consumer<? super Node> consumer) {
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : this.map.entrySet()) {
|
||||
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalNodesExcludeTest(filter, e.getKey())) {
|
||||
if (inheritanceNodesIncludeTest(filter, e.getKey())) {
|
||||
// only copy inheritance nodes.
|
||||
SortedSet<InheritanceNode> inheritanceNodes = this.inheritanceMap.get(e.getKey());
|
||||
if (inheritanceNodes != null) {
|
||||
inheritanceNodes.forEach(consumer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
e.getValue().forEach(consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void copyTo(Collection<? super Node> collection) {
|
||||
for (SortedSet<Node> values : this.map.values()) {
|
||||
collection.addAll(values);
|
||||
}
|
||||
}
|
||||
|
||||
public void copyTo(Collection<? super Node> collection, QueryOptions filter) {
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : this.map.entrySet()) {
|
||||
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalNodesExcludeTest(filter, e.getKey())) {
|
||||
if (inheritanceNodesIncludeTest(filter, e.getKey())) {
|
||||
// only copy inheritance nodes.
|
||||
SortedSet<InheritanceNode> inheritanceNodes = this.inheritanceMap.get(e.getKey());
|
||||
if (inheritanceNodes != null) {
|
||||
collection.addAll(inheritanceNodes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
collection.addAll(e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends Node> void copyTo(Collection<? super T> collection, NodeType<T> type, QueryOptions filter) {
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : this.map.entrySet()) {
|
||||
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalNodesExcludeTest(filter, e.getKey())) {
|
||||
if (inheritanceNodesIncludeTest(filter, e.getKey())) {
|
||||
// only copy inheritance nodes.
|
||||
if (type == NodeType.INHERITANCE) {
|
||||
SortedSet<InheritanceNode> inheritanceNodes = this.inheritanceMap.get(e.getKey());
|
||||
if (inheritanceNodes != null) {
|
||||
for (InheritanceNode node : inheritanceNodes) {
|
||||
collection.add(type.cast(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Node node : e.getValue()) {
|
||||
if (type.matches(node)) {
|
||||
collection.add(type.cast(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void copyInheritanceNodesTo(Collection<? super InheritanceNode> collection) {
|
||||
for (SortedSet<InheritanceNode> values : this.inheritanceMap.values()) {
|
||||
collection.addAll(values);
|
||||
}
|
||||
}
|
||||
|
||||
public void copyInheritanceNodesTo(Collection<? super InheritanceNode> collection, QueryOptions filter) {
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<InheritanceNode>> e : this.inheritanceMap.entrySet()) {
|
||||
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inheritanceNodesIncludeTest(filter, e.getKey())) {
|
||||
collection.addAll(e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<Node> nodesInContext(ContextSet context) {
|
||||
final SortedSet<Node> values = this.map.get(context.immutableCopy());
|
||||
if (values == null) {
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
return new ArrayList<>(values);
|
||||
}
|
||||
|
||||
public Collection<InheritanceNode> inheritanceNodesInContext(ContextSet context) {
|
||||
final SortedSet<InheritanceNode> values = this.inheritanceMap.get(context.immutableCopy());
|
||||
if (values == null) {
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
return new ArrayList<>(values);
|
||||
}
|
||||
|
||||
private Node localise(Node node) {
|
||||
Optional<InheritanceOriginMetadata> metadata = node.getMetadata(InheritanceOriginMetadata.KEY);
|
||||
if (metadata.isPresent() && metadata.get().getOrigin().equals(this.holder.getIdentifier())) {
|
||||
return node;
|
||||
}
|
||||
|
||||
return node.toBuilder().withMetadata(InheritanceOriginMetadata.KEY, new InheritanceOrigin(this.holder.getIdentifier())).build();
|
||||
}
|
||||
|
||||
void add(Node node) {
|
||||
ImmutableContextSet context = node.getContexts();
|
||||
Node n = localise(node);
|
||||
|
||||
SortedSet<Node> nodesInContext = this.map.computeIfAbsent(context, VALUE_SET_SUPPLIER);
|
||||
nodesInContext.removeIf(e -> e.equals(node, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE));
|
||||
nodesInContext.add(n);
|
||||
|
||||
if (n instanceof InheritanceNode) {
|
||||
SortedSet<InheritanceNode> inheritanceNodesInContext = this.inheritanceMap.computeIfAbsent(context, INHERITANCE_VALUE_SET_SUPPLIER);
|
||||
inheritanceNodesInContext.removeIf(e -> e.equals(node, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE));
|
||||
if (n.getValue()) {
|
||||
inheritanceNodesInContext.add((InheritanceNode) n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void remove(Node node) {
|
||||
ImmutableContextSet context = node.getContexts();
|
||||
SortedSet<Node> nodesInContext = this.map.get(context);
|
||||
if (nodesInContext != null) {
|
||||
nodesInContext.removeIf(e -> e.equals(node, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE));
|
||||
}
|
||||
|
||||
if (node instanceof InheritanceNode) {
|
||||
SortedSet<InheritanceNode> inheritanceNodesInContext = this.inheritanceMap.get(context);
|
||||
if (inheritanceNodesInContext != null) {
|
||||
inheritanceNodesInContext.removeIf(e -> e.equals(node, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeExact(Node node) {
|
||||
ImmutableContextSet context = node.getContexts();
|
||||
SortedSet<Node> nodesInContext = this.map.get(context);
|
||||
if (nodesInContext != null) {
|
||||
nodesInContext.remove(node);
|
||||
}
|
||||
|
||||
if (node instanceof InheritanceNode && node.getValue()) {
|
||||
SortedSet<InheritanceNode> inheritanceNodesInContext = this.inheritanceMap.get(context);
|
||||
if (inheritanceNodesInContext != null) {
|
||||
inheritanceNodesInContext.remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void replace(Node node, Node previous) {
|
||||
removeExact(previous);
|
||||
add(node);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
this.map.clear();
|
||||
this.inheritanceMap.clear();
|
||||
}
|
||||
|
||||
void clear(ContextSet contextSet) {
|
||||
ImmutableContextSet context = contextSet.immutableCopy();
|
||||
this.map.remove(context);
|
||||
this.inheritanceMap.remove(context);
|
||||
}
|
||||
|
||||
void setContent(Iterable<? extends Node> set) {
|
||||
this.map.clear();
|
||||
this.inheritanceMap.clear();
|
||||
mergeContent(set);
|
||||
}
|
||||
|
||||
void setContent(Stream<? extends Node> stream) {
|
||||
this.map.clear();
|
||||
this.inheritanceMap.clear();
|
||||
mergeContent(stream);
|
||||
}
|
||||
|
||||
void mergeContent(Iterable<? extends Node> set) {
|
||||
for (Node n : set) {
|
||||
add(n);
|
||||
}
|
||||
}
|
||||
|
||||
void mergeContent(Stream<? extends Node> stream) {
|
||||
stream.forEach(this::add);
|
||||
}
|
||||
|
||||
boolean removeIf(Predicate<? super Node> predicate) {
|
||||
boolean success = false;
|
||||
for (SortedSet<Node> valueSet : this.map.values()) {
|
||||
if (valueSet.removeIf(predicate)) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
for (SortedSet<InheritanceNode> valueSet : this.inheritanceMap.values()) {
|
||||
valueSet.removeIf(predicate);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
boolean removeIf(ContextSet contextSet, Predicate<? super Node> predicate) {
|
||||
ImmutableContextSet context = contextSet.immutableCopy();
|
||||
|
||||
boolean success = false;
|
||||
|
||||
SortedSet<Node> nodesInContext = this.map.get(context);
|
||||
if (nodesInContext != null) {
|
||||
success = nodesInContext.removeIf(predicate);
|
||||
}
|
||||
|
||||
SortedSet<InheritanceNode> inheritanceNodesInContext = this.inheritanceMap.get(context);
|
||||
if (inheritanceNodesInContext != null) {
|
||||
inheritanceNodesInContext.removeIf(predicate);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
boolean auditTemporaryNodes(@Nullable Set<? super Node> removed) {
|
||||
boolean work = false;
|
||||
|
||||
for (SortedSet<Node> valueSet : this.map.values()) {
|
||||
Iterator<Node> it = valueSet.iterator();
|
||||
while (it.hasNext()) {
|
||||
Node entry = it.next();
|
||||
if (!entry.hasExpired()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// remove
|
||||
if (removed != null) {
|
||||
removed.add(entry);
|
||||
}
|
||||
if (entry instanceof InheritanceNode && entry.getValue()) {
|
||||
SortedSet<InheritanceNode> inheritanceNodesInContext = this.inheritanceMap.get(entry.getContexts());
|
||||
if (inheritanceNodesInContext != null) {
|
||||
inheritanceNodesInContext.remove(entry);
|
||||
}
|
||||
}
|
||||
it.remove();
|
||||
work = true;
|
||||
}
|
||||
}
|
||||
|
||||
return work;
|
||||
}
|
||||
|
||||
}
|
@ -32,6 +32,10 @@ import me.lucko.luckperms.common.cacheddata.HolderCachedDataManager;
|
||||
import me.lucko.luckperms.common.cacheddata.type.MetaAccumulator;
|
||||
import me.lucko.luckperms.common.inheritance.InheritanceComparator;
|
||||
import me.lucko.luckperms.common.inheritance.InheritanceGraph;
|
||||
import me.lucko.luckperms.common.model.nodemap.MutateResult;
|
||||
import me.lucko.luckperms.common.model.nodemap.NodeMap;
|
||||
import me.lucko.luckperms.common.model.nodemap.NodeMapMutable;
|
||||
import me.lucko.luckperms.common.model.nodemap.RecordedNodeMap;
|
||||
import me.lucko.luckperms.common.node.NodeEquality;
|
||||
import me.lucko.luckperms.common.node.comparator.NodeWithContextComparator;
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
@ -59,18 +63,13 @@ import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Represents an object that can hold permissions, (a user or group)
|
||||
@ -112,7 +111,7 @@ public abstract class PermissionHolder {
|
||||
*
|
||||
* @see #normalData()
|
||||
*/
|
||||
private final NodeMap normalNodes = new NodeMap(this);
|
||||
private final RecordedNodeMap normalNodes = new RecordedNodeMap(new NodeMapMutable(this));
|
||||
|
||||
/**
|
||||
* The holders transient nodes.
|
||||
@ -124,13 +123,7 @@ public abstract class PermissionHolder {
|
||||
*
|
||||
* @see #transientData()
|
||||
*/
|
||||
private final NodeMap transientNodes = new NodeMap(this);
|
||||
|
||||
/**
|
||||
* Lock used by Storage implementations to prevent concurrent read/writes
|
||||
* @see #getIoLock()
|
||||
*/
|
||||
private final Lock ioLock = new ReentrantLock();
|
||||
private final NodeMap transientNodes = new NodeMapMutable(this);
|
||||
|
||||
/**
|
||||
* Comparator used to ordering groups when calculating inheritance
|
||||
@ -152,10 +145,6 @@ public abstract class PermissionHolder {
|
||||
return this.plugin;
|
||||
}
|
||||
|
||||
public Lock getIoLock() {
|
||||
return this.ioLock;
|
||||
}
|
||||
|
||||
public Comparator<? super PermissionHolder> getInheritanceComparator() {
|
||||
return this.inheritanceComparator;
|
||||
}
|
||||
@ -171,7 +160,7 @@ public abstract class PermissionHolder {
|
||||
}
|
||||
}
|
||||
|
||||
public NodeMap normalData() {
|
||||
public RecordedNodeMap normalData() {
|
||||
return this.normalNodes;
|
||||
}
|
||||
|
||||
@ -237,18 +226,20 @@ public abstract class PermissionHolder {
|
||||
getPlugin().getEventDispatcher().dispatchDataRecalculate(this);
|
||||
}
|
||||
|
||||
public void loadNodesFromStorage(Iterable<? extends Node> set) {
|
||||
// TODO: should we attempt to "replay" existing changes on top of the new data?
|
||||
normalData().discardChanges();
|
||||
normalData().bypass().setContent(set);
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
public void setNodes(DataType type, Iterable<? extends Node> set) {
|
||||
getData(type).setContent(set);
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
public void setNodes(DataType type, Stream<? extends Node> stream) {
|
||||
getData(type).setContent(stream);
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
public void mergeNodes(DataType type, Iterable<? extends Node> set) {
|
||||
getData(type).mergeContent(set);
|
||||
getData(type).addAll(set);
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
@ -436,20 +427,19 @@ public abstract class PermissionHolder {
|
||||
|
||||
private boolean auditTemporaryNodes(DataType dataType) {
|
||||
ImmutableSet<Node> before = getData(dataType).asImmutableSet();
|
||||
Set<Node> removed = new HashSet<>();
|
||||
|
||||
boolean work = getData(dataType).auditTemporaryNodes(removed);
|
||||
if (work) {
|
||||
MutateResult result = getData(dataType).removeIf(Node::hasExpired);
|
||||
if (!result.isEmpty()) {
|
||||
// call event
|
||||
ImmutableSet<Node> after = getData(dataType).asImmutableSet();
|
||||
for (Node r : removed) {
|
||||
for (Node r : result.getRemoved()) {
|
||||
this.plugin.getEventDispatcher().dispatchNodeRemove(r, this, dataType, before, after);
|
||||
}
|
||||
|
||||
// invalidate
|
||||
invalidateCache();
|
||||
}
|
||||
return work;
|
||||
return !result.isEmpty();
|
||||
}
|
||||
|
||||
public Tristate hasNode(DataType type, Node node, NodeEqualityPredicate equalityPredicate) {
|
||||
@ -522,7 +512,7 @@ public abstract class PermissionHolder {
|
||||
if (newNode != null) {
|
||||
// Remove the old Node & add the new one.
|
||||
ImmutableSet<Node> before = data.asImmutableSet();
|
||||
data.replace(newNode, otherMatch);
|
||||
data.removeThenAdd(otherMatch, newNode);
|
||||
ImmutableSet<Node> after = data.asImmutableSet();
|
||||
|
||||
this.plugin.getEventDispatcher().dispatchNodeAdd(newNode, this, dataType, before, after);
|
||||
@ -572,7 +562,7 @@ public abstract class PermissionHolder {
|
||||
|
||||
// Remove the old Node & add the new one.
|
||||
ImmutableSet<Node> before = data.asImmutableSet();
|
||||
data.replace(newNode, otherMatch);
|
||||
data.removeThenAdd(otherMatch, newNode);
|
||||
ImmutableSet<Node> after = data.asImmutableSet();
|
||||
|
||||
this.plugin.getEventDispatcher().dispatchNodeRemove(otherMatch, this, dataType, before, after);
|
||||
@ -594,17 +584,17 @@ public abstract class PermissionHolder {
|
||||
ImmutableSet<Node> before = data.asImmutableSet();
|
||||
|
||||
if (contextSet == null) {
|
||||
if (!data.removeIf(predicate)) {
|
||||
if (data.removeIf(predicate).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!data.removeIf(contextSet, predicate)) {
|
||||
if (data.removeIf(contextSet, predicate).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (getType() == HolderType.USER && giveDefault) {
|
||||
getPlugin().getUserManager().giveDefaultIfNeeded((User) this, false);
|
||||
getPlugin().getUserManager().giveDefaultIfNeeded((User) this);
|
||||
}
|
||||
|
||||
ImmutableSet<Node> after = data.asImmutableSet();
|
||||
@ -626,7 +616,7 @@ public abstract class PermissionHolder {
|
||||
}
|
||||
|
||||
if (getType() == HolderType.USER && giveDefault) {
|
||||
getPlugin().getUserManager().giveDefaultIfNeeded((User) this, false);
|
||||
getPlugin().getUserManager().giveDefaultIfNeeded((User) this);
|
||||
}
|
||||
|
||||
ImmutableSet<Node> after = data.asImmutableSet();
|
||||
|
@ -47,8 +47,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -61,8 +59,6 @@ public final class Track {
|
||||
|
||||
private final LuckPermsPlugin plugin;
|
||||
|
||||
private final Lock ioLock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* The groups within this track
|
||||
*/
|
||||
@ -79,10 +75,6 @@ public final class Track {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Lock getIoLock() {
|
||||
return this.ioLock;
|
||||
}
|
||||
|
||||
public ApiTrack getApiProxy() {
|
||||
return this.apiProxy;
|
||||
}
|
||||
|
@ -79,8 +79,8 @@ public abstract class AbstractUserManager<T extends User> extends AbstractManage
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean giveDefaultIfNeeded(User user, boolean save) {
|
||||
boolean work = false;
|
||||
public boolean giveDefaultIfNeeded(User user) {
|
||||
boolean requireSave = false;
|
||||
|
||||
Collection<InheritanceNode> globalGroups = user.normalData().inheritanceNodesInContext(ImmutableContextSetImpl.EMPTY);
|
||||
|
||||
@ -106,7 +106,7 @@ public abstract class AbstractUserManager<T extends User> extends AbstractManage
|
||||
// if the group is null, it'll be resolved in the next step
|
||||
if (group != null) {
|
||||
user.getPrimaryGroup().setStoredValue(group);
|
||||
work = true;
|
||||
requireSave = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,14 +120,39 @@ public abstract class AbstractUserManager<T extends User> extends AbstractManage
|
||||
if (!hasGroup) {
|
||||
user.getPrimaryGroup().setStoredValue(GroupManager.DEFAULT_GROUP_NAME);
|
||||
user.setNode(DataType.NORMAL, Inheritance.builder(GroupManager.DEFAULT_GROUP_NAME).build(), false);
|
||||
work = true;
|
||||
requireSave = true;
|
||||
}
|
||||
|
||||
if (work && save) {
|
||||
this.plugin.getStorage().saveUser(user);
|
||||
return requireSave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNonDefaultUser(User user) {
|
||||
if (user.normalData().size() != 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return work;
|
||||
List<Node> nodes = user.normalData().asList();
|
||||
if (nodes.size() != 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Node onlyNode = nodes.iterator().next();
|
||||
if (!isDefaultNode(onlyNode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not in the default primary group
|
||||
return !user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME).equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefaultNode(Node node) {
|
||||
return node instanceof InheritanceNode &&
|
||||
node.getValue() &&
|
||||
!node.hasExpiry() &&
|
||||
node.getContexts().isEmpty() &&
|
||||
((InheritanceNode) node).getGroupName().equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -157,39 +182,4 @@ public abstract class AbstractUserManager<T extends User> extends AbstractManage
|
||||
getAll().values().forEach(u -> u.getCachedData().invalidatePermissionCalculators());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the user's state indicates that they should be persisted to storage.
|
||||
*
|
||||
* @param user the user to check
|
||||
* @return true if the user should be saved
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldSave(User user) {
|
||||
if (user.normalData().size() != 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<Node> nodes = user.normalData().asList();
|
||||
if (nodes.size() != 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Node onlyNode = nodes.iterator().next();
|
||||
if (!(onlyNode instanceof InheritanceNode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (onlyNode.hasExpiry() || !onlyNode.getContexts().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!((InheritanceNode) onlyNode).getGroupName().equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME)) {
|
||||
// The user's only node is not the default group one.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Not in the default primary group
|
||||
return !user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME).equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ import me.lucko.luckperms.common.calculator.PermissionCalculator;
|
||||
import me.lucko.luckperms.common.model.User;
|
||||
import me.lucko.luckperms.common.model.manager.Manager;
|
||||
|
||||
import net.luckperms.api.node.Node;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@ -49,7 +51,7 @@ public interface UserManager<T extends User> extends Manager<UUID, User, T> {
|
||||
*
|
||||
* @param user the user to give to
|
||||
*/
|
||||
boolean giveDefaultIfNeeded(User user, boolean save);
|
||||
boolean giveDefaultIfNeeded(User user);
|
||||
|
||||
/**
|
||||
* Check whether the user's state indicates that they should be persisted to storage.
|
||||
@ -57,7 +59,15 @@ public interface UserManager<T extends User> extends Manager<UUID, User, T> {
|
||||
* @param user the user to check
|
||||
* @return true if the user should be saved
|
||||
*/
|
||||
boolean shouldSave(User user);
|
||||
boolean isNonDefaultUser(User user);
|
||||
|
||||
/**
|
||||
* Gets whether the given node is a default node given by {@link #giveDefaultIfNeeded(User)}.
|
||||
*
|
||||
* @param node the node
|
||||
* @return true if it is the default node
|
||||
*/
|
||||
boolean isDefaultNode(Node node);
|
||||
|
||||
/**
|
||||
* Gets the instance responsible for unloading unneeded users.
|
||||
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* 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.model.nodemap;
|
||||
|
||||
import net.luckperms.api.node.Node;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Records a log of the changes that occur as a result of a {@link NodeMap} mutation(s).
|
||||
*/
|
||||
public class MutateResult {
|
||||
private final LinkedHashSet<Change> changes = new LinkedHashSet<>();
|
||||
|
||||
public Set<Change> getChanges() {
|
||||
return this.changes;
|
||||
}
|
||||
|
||||
public Set<Node> getChanges(ChangeType type) {
|
||||
Set<Node> changes = new LinkedHashSet<>(this.changes.size());
|
||||
for (Change change : this.changes) {
|
||||
if (change.getType() == type) {
|
||||
changes.add(change.getNode());
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
this.changes.clear();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.changes.isEmpty();
|
||||
}
|
||||
|
||||
public Set<Node> getAdded() {
|
||||
return getChanges(ChangeType.ADD);
|
||||
}
|
||||
|
||||
public Set<Node> getRemoved() {
|
||||
return getChanges(ChangeType.REMOVE);
|
||||
}
|
||||
|
||||
private void recordChange(Change change) {
|
||||
// This method is the magic of this class.
|
||||
// When tracking, we want to ignore changes that cancel each other out, and only
|
||||
// keep track of the net difference.
|
||||
// e.g. adding then removing the same node = zero net change, so ignore it.
|
||||
|
||||
if (this.changes.remove(change.inverse())) {
|
||||
return;
|
||||
}
|
||||
this.changes.add(change);
|
||||
}
|
||||
|
||||
public void recordChange(ChangeType type, Node node) {
|
||||
recordChange(new Change(type, node));
|
||||
}
|
||||
|
||||
public void recordChanges(ChangeType type, Iterable<Node> nodes) {
|
||||
for (Node node : nodes) {
|
||||
recordChange(new Change(type, node));
|
||||
}
|
||||
}
|
||||
|
||||
public MutateResult mergeFrom(MutateResult other) {
|
||||
for (Change change : other.changes) {
|
||||
recordChange(change);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MutateResult{changes=" + this.changes + '}';
|
||||
}
|
||||
|
||||
public static final class Change {
|
||||
private final ChangeType type;
|
||||
private final Node node;
|
||||
|
||||
public Change(ChangeType type, Node node) {
|
||||
this.type = type;
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public ChangeType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public Node getNode() {
|
||||
return this.node;
|
||||
}
|
||||
|
||||
public Change inverse() {
|
||||
return new Change(this.type.inverse(), this.node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Change change = (Change) o;
|
||||
return this.type == change.type && this.node.equals(change.node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.type, this.node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Change{type=" + this.type + ", node=" + this.node + '}';
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChangeType {
|
||||
ADD, REMOVE;
|
||||
|
||||
public ChangeType inverse() {
|
||||
return this == ADD ? REMOVE : ADD;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* 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.model.nodemap;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import me.lucko.luckperms.common.model.PermissionHolder;
|
||||
import me.lucko.luckperms.common.node.comparator.NodeWithContextComparator;
|
||||
|
||||
import net.luckperms.api.context.ContextSet;
|
||||
import net.luckperms.api.context.ImmutableContextSet;
|
||||
import net.luckperms.api.node.Node;
|
||||
import net.luckperms.api.node.NodeType;
|
||||
import net.luckperms.api.node.types.InheritanceNode;
|
||||
import net.luckperms.api.query.QueryOptions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A map of nodes held by a {@link PermissionHolder}.
|
||||
*/
|
||||
public interface NodeMap {
|
||||
|
||||
boolean isEmpty();
|
||||
|
||||
int size();
|
||||
|
||||
default List<Node> asList() {
|
||||
List<Node> list = new ArrayList<>();
|
||||
copyTo(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
default LinkedHashSet<Node> asSet() {
|
||||
LinkedHashSet<Node> set = new LinkedHashSet<>();
|
||||
copyTo(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
default SortedSet<Node> asSortedSet() {
|
||||
SortedSet<Node> set = new TreeSet<>(NodeWithContextComparator.reverse());
|
||||
copyTo(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
default ImmutableSet<Node> asImmutableSet() {
|
||||
ImmutableSet.Builder<Node> builder = ImmutableSet.builder();
|
||||
copyTo(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
Map<ImmutableContextSet, Collection<Node>> asMap();
|
||||
|
||||
default List<InheritanceNode> inheritanceAsList() {
|
||||
List<InheritanceNode> set = new ArrayList<>();
|
||||
copyInheritanceNodesTo(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
default LinkedHashSet<InheritanceNode> inheritanceAsSet() {
|
||||
LinkedHashSet<InheritanceNode> set = new LinkedHashSet<>();
|
||||
copyInheritanceNodesTo(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
default SortedSet<InheritanceNode> inheritanceAsSortedSet() {
|
||||
SortedSet<InheritanceNode> set = new TreeSet<>(NodeWithContextComparator.reverse());
|
||||
copyInheritanceNodesTo(set);
|
||||
return set;
|
||||
}
|
||||
|
||||
default ImmutableSet<InheritanceNode> inheritanceAsImmutableSet() {
|
||||
ImmutableSet.Builder<InheritanceNode> builder = ImmutableSet.builder();
|
||||
copyInheritanceNodesTo(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
Map<ImmutableContextSet, Collection<InheritanceNode>> inheritanceAsMap();
|
||||
|
||||
void forEach(Consumer<? super Node> consumer);
|
||||
|
||||
void forEach(QueryOptions filter, Consumer<? super Node> consumer);
|
||||
|
||||
void copyTo(Collection<? super Node> collection);
|
||||
|
||||
void copyTo(ImmutableCollection.Builder<? super Node> collection);
|
||||
|
||||
void copyTo(Collection<? super Node> collection, QueryOptions filter);
|
||||
|
||||
<T extends Node> void copyTo(Collection<? super T> collection, NodeType<T> type, QueryOptions filter);
|
||||
|
||||
void copyInheritanceNodesTo(Collection<? super InheritanceNode> collection);
|
||||
|
||||
void copyInheritanceNodesTo(ImmutableCollection.Builder<? super InheritanceNode> collection);
|
||||
|
||||
void copyInheritanceNodesTo(Collection<? super InheritanceNode> collection, QueryOptions filter);
|
||||
|
||||
Collection<Node> nodesInContext(ContextSet context);
|
||||
|
||||
Collection<InheritanceNode> inheritanceNodesInContext(ContextSet context);
|
||||
|
||||
// mutate methods
|
||||
|
||||
MutateResult add(Node nodeWithoutInheritanceOrigin);
|
||||
|
||||
MutateResult remove(Node node);
|
||||
|
||||
MutateResult removeExact(Node node);
|
||||
|
||||
MutateResult removeIf(Predicate<? super Node> predicate);
|
||||
|
||||
MutateResult removeIf(ContextSet contextSet, Predicate<? super Node> predicate);
|
||||
|
||||
MutateResult removeThenAdd(Node nodeToRemove, Node nodeToAdd);
|
||||
|
||||
MutateResult clear();
|
||||
|
||||
MutateResult clear(ContextSet contextSet);
|
||||
|
||||
MutateResult setContent(Iterable<? extends Node> set);
|
||||
|
||||
MutateResult setContent(Stream<? extends Node> stream);
|
||||
|
||||
MutateResult addAll(Iterable<? extends Node> set);
|
||||
|
||||
MutateResult addAll(Stream<? extends Node> stream);
|
||||
|
||||
}
|
@ -0,0 +1,243 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* 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.model.nodemap;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
|
||||
import net.luckperms.api.context.ContextSatisfyMode;
|
||||
import net.luckperms.api.context.ContextSet;
|
||||
import net.luckperms.api.context.DefaultContextKeys;
|
||||
import net.luckperms.api.context.ImmutableContextSet;
|
||||
import net.luckperms.api.node.Node;
|
||||
import net.luckperms.api.node.NodeType;
|
||||
import net.luckperms.api.node.types.InheritanceNode;
|
||||
import net.luckperms.api.query.Flag;
|
||||
import net.luckperms.api.query.QueryOptions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.SortedSet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Base implementation of {@link NodeMap} query methods.
|
||||
*/
|
||||
abstract class NodeMapBase implements NodeMap {
|
||||
|
||||
NodeMapBase() {
|
||||
|
||||
}
|
||||
|
||||
protected abstract SortedMap<ImmutableContextSet, SortedSet<Node>> map();
|
||||
|
||||
protected abstract SortedMap<ImmutableContextSet, SortedSet<InheritanceNode>> inheritanceMap();
|
||||
|
||||
protected abstract ContextSatisfyMode defaultSatisfyMode();
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
int size = 0;
|
||||
for (SortedSet<Node> values : map().values()) {
|
||||
size += values.size();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ImmutableContextSet, Collection<Node>> asMap() {
|
||||
Map<ImmutableContextSet, Collection<Node>> map = new HashMap<>();
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : map().entrySet()) {
|
||||
map.put(e.getKey(), new ArrayList<>(e.getValue()));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<ImmutableContextSet, Collection<InheritanceNode>> inheritanceAsMap() {
|
||||
Map<ImmutableContextSet, Collection<InheritanceNode>> map = new HashMap<>();
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<InheritanceNode>> e : inheritanceMap().entrySet()) {
|
||||
map.put(e.getKey(), new ArrayList<>(e.getValue()));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super Node> consumer) {
|
||||
for (SortedSet<Node> values : map().values()) {
|
||||
values.forEach(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(QueryOptions filter, Consumer<? super Node> consumer) {
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : map().entrySet()) {
|
||||
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalNodesExcludeTest(filter, e.getKey())) {
|
||||
if (inheritanceNodesIncludeTest(filter, e.getKey())) {
|
||||
SortedSet<InheritanceNode> inheritanceNodes = inheritanceMap().get(e.getKey());
|
||||
if (inheritanceNodes != null) {
|
||||
inheritanceNodes.forEach(consumer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
e.getValue().forEach(consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(Collection<? super Node> collection) {
|
||||
for (SortedSet<Node> values : map().values()) {
|
||||
collection.addAll(values);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(ImmutableCollection.Builder<? super Node> collection) {
|
||||
for (SortedSet<Node> values : map().values()) {
|
||||
collection.addAll(values);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(Collection<? super Node> collection, QueryOptions filter) {
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : map().entrySet()) {
|
||||
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalNodesExcludeTest(filter, e.getKey())) {
|
||||
if (inheritanceNodesIncludeTest(filter, e.getKey())) {
|
||||
SortedSet<InheritanceNode> inheritanceNodes = inheritanceMap().get(e.getKey());
|
||||
if (inheritanceNodes != null) {
|
||||
collection.addAll(inheritanceNodes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
collection.addAll(e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Node> void copyTo(Collection<? super T> collection, NodeType<T> type, QueryOptions filter) {
|
||||
if (type == NodeType.INHERITANCE) {
|
||||
//noinspection unchecked
|
||||
copyInheritanceNodesTo((Collection<? super InheritanceNode>) collection, filter);
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<Node>> e : map().entrySet()) {
|
||||
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalNodesExcludeTest(filter, e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Node node : e.getValue()) {
|
||||
if (type.matches(node)) {
|
||||
collection.add(type.cast(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyInheritanceNodesTo(Collection<? super InheritanceNode> collection) {
|
||||
for (SortedSet<InheritanceNode> values : inheritanceMap().values()) {
|
||||
collection.addAll(values);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyInheritanceNodesTo(ImmutableCollection.Builder<? super InheritanceNode> collection) {
|
||||
for (SortedSet<InheritanceNode> values : inheritanceMap().values()) {
|
||||
collection.addAll(values);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyInheritanceNodesTo(Collection<? super InheritanceNode> collection, QueryOptions filter) {
|
||||
for (Map.Entry<ImmutableContextSet, SortedSet<InheritanceNode>> e : inheritanceMap().entrySet()) {
|
||||
if (!filter.satisfies(e.getKey(), defaultSatisfyMode())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inheritanceNodesIncludeTest(filter, e.getKey())) {
|
||||
collection.addAll(e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Node> nodesInContext(ContextSet context) {
|
||||
return copy(map().get(context.immutableCopy()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<InheritanceNode> inheritanceNodesInContext(ContextSet context) {
|
||||
return copy(inheritanceMap().get(context.immutableCopy()));
|
||||
}
|
||||
|
||||
private static <T> Collection<T> copy(Collection<T> collection) {
|
||||
if (collection == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return new ArrayList<>(collection);
|
||||
}
|
||||
|
||||
private static boolean flagExcludeTest(Flag flag, String contextKey, QueryOptions filter, ImmutableContextSet contextSet) {
|
||||
// return true (negative result) if the explicit *include* flag is not set, and if the context set doesn't contain the required context key.
|
||||
return !filter.flag(flag) && !contextSet.containsKey(contextKey);
|
||||
}
|
||||
|
||||
private static boolean normalNodesExcludeTest(QueryOptions filter, ImmutableContextSet contextSet) {
|
||||
// return true (negative result) if normal nodes should not be included due to the lack of a server/world context.
|
||||
return flagExcludeTest(Flag.INCLUDE_NODES_WITHOUT_SERVER_CONTEXT, DefaultContextKeys.SERVER_KEY, filter, contextSet) ||
|
||||
flagExcludeTest(Flag.INCLUDE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet);
|
||||
}
|
||||
|
||||
private static boolean inheritanceNodesIncludeTest(QueryOptions filter, ImmutableContextSet contextSet) {
|
||||
// return true (positive result) if inheritance nodes should be included, due to the lack of any flags preventing their inclusion.
|
||||
return !flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_SERVER_CONTEXT, DefaultContextKeys.SERVER_KEY, filter, contextSet) &&
|
||||
!flagExcludeTest(Flag.APPLY_INHERITANCE_NODES_WITHOUT_WORLD_CONTEXT, DefaultContextKeys.WORLD_KEY, filter, contextSet);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,404 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* 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.model.nodemap;
|
||||
|
||||
import me.lucko.luckperms.common.config.ConfigKeys;
|
||||
import me.lucko.luckperms.common.context.ContextSetComparator;
|
||||
import me.lucko.luckperms.common.model.InheritanceOrigin;
|
||||
import me.lucko.luckperms.common.model.PermissionHolder;
|
||||
import me.lucko.luckperms.common.model.nodemap.MutateResult.ChangeType;
|
||||
import me.lucko.luckperms.common.node.comparator.NodeComparator;
|
||||
|
||||
import net.luckperms.api.context.ContextSatisfyMode;
|
||||
import net.luckperms.api.context.ContextSet;
|
||||
import net.luckperms.api.context.ImmutableContextSet;
|
||||
import net.luckperms.api.node.Node;
|
||||
import net.luckperms.api.node.NodeEqualityPredicate;
|
||||
import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata;
|
||||
import net.luckperms.api.node.types.InheritanceNode;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.SortedMap;
|
||||
import java.util.SortedSet;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class NodeMapMutable extends NodeMapBase {
|
||||
|
||||
// Used in calls to Map#computeIfAbsent to make them behave like a LoadingMap/Cache
|
||||
// The key (ImmutableContextSet) isn't actually used - these are more like suppliers than functions
|
||||
private static final Function<ImmutableContextSet, SortedSet<Node>> VALUE_SET_SUPPLIER = k -> new ConcurrentSkipListSet<>(NodeComparator.reverse());
|
||||
private static final Function<ImmutableContextSet, SortedSet<InheritanceNode>> INHERITANCE_VALUE_SET_SUPPLIER = k -> new ConcurrentSkipListSet<>(NodeComparator.reverse());
|
||||
|
||||
// Creates the Map instances used by this.map and this.inheritanceMap
|
||||
private static <N extends Node> SortedMap<ImmutableContextSet, SortedSet<N>> createMap() {
|
||||
return new ConcurrentSkipListMap<>(ContextSetComparator.reverse());
|
||||
}
|
||||
|
||||
/*
|
||||
* Nodes are inserted into the maps using Node#getContexts() as the key.
|
||||
* The context set keys are ordered according to the rules of ContextSetComparator.
|
||||
* The node values are ordered according to the priority rules defined in NodeComparator.
|
||||
*
|
||||
* We use our own "multimap"-like implementation here because guava's is not thread safe.
|
||||
*
|
||||
* The map fields aren't final because they are replaced when large updates (e.g. clear)
|
||||
* are performed. We do this so there's no risk that the read methods will see an inconsistent
|
||||
* state in the middle of an update from the DB. (see below comment about locking - we don't
|
||||
* lock for reads!)
|
||||
*/
|
||||
private SortedMap<ImmutableContextSet, SortedSet<Node>> map = createMap();
|
||||
private SortedMap<ImmutableContextSet, SortedSet<InheritanceNode>> inheritanceMap = createMap();
|
||||
|
||||
/**
|
||||
* This lock is used whilst performing mutations, but *not* reads.
|
||||
*
|
||||
* The maps themselves are thread safe, so for querying, we just allow
|
||||
* the read methods to do whatever they want without any locking.
|
||||
* However, we want mutations to be atomic, so we use the lock to ensure that happens.
|
||||
*/
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
protected final PermissionHolder holder;
|
||||
|
||||
public NodeMapMutable(PermissionHolder holder) {
|
||||
this.holder = holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SortedMap<ImmutableContextSet, SortedSet<Node>> map() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SortedMap<ImmutableContextSet, SortedSet<InheritanceNode>> inheritanceMap() {
|
||||
return this.inheritanceMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContextSatisfyMode defaultSatisfyMode() {
|
||||
return this.holder.getPlugin().getConfiguration().get(ConfigKeys.CONTEXT_SATISFY_MODE);
|
||||
}
|
||||
|
||||
private Node addInheritanceOrigin(Node node) {
|
||||
Optional<InheritanceOriginMetadata> metadata = node.getMetadata(InheritanceOriginMetadata.KEY);
|
||||
if (metadata.isPresent() && metadata.get().getOrigin().equals(this.holder.getIdentifier())) {
|
||||
return node;
|
||||
}
|
||||
|
||||
return node.toBuilder().withMetadata(InheritanceOriginMetadata.KEY, new InheritanceOrigin(this.holder.getIdentifier())).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult add(Node nodeWithoutInheritanceOrigin) {
|
||||
Node node = addInheritanceOrigin(nodeWithoutInheritanceOrigin);
|
||||
|
||||
ImmutableContextSet context = node.getContexts();
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
SortedSet<Node> nodes = this.map.computeIfAbsent(context, VALUE_SET_SUPPLIER);
|
||||
|
||||
// add the new node to the set - if it was already there, return
|
||||
if (!nodes.add(node)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// mark that we added the node in the results
|
||||
result.recordChange(ChangeType.ADD, node);
|
||||
|
||||
// remove any others that were in the set already with a different value/expiry time
|
||||
removeMatching(nodes.iterator(), node, result);
|
||||
|
||||
// update the inheritanceMap too if necessary
|
||||
if (node instanceof InheritanceNode) {
|
||||
SortedSet<InheritanceNode> inhNodes = this.inheritanceMap.computeIfAbsent(context, INHERITANCE_VALUE_SET_SUPPLIER);
|
||||
// remove existing..
|
||||
inhNodes.removeIf(el -> node.equals(el, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE));
|
||||
// .. & add
|
||||
if (node.getValue()) {
|
||||
inhNodes.add((InheritanceNode) node);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult remove(Node node) {
|
||||
ImmutableContextSet context = node.getContexts();
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
SortedSet<Node> nodes = this.map.get(context);
|
||||
if (nodes == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// remove any nodes that match, record to results
|
||||
removeMatching(nodes.iterator(), node, result);
|
||||
|
||||
// update inheritance map too
|
||||
if (node instanceof InheritanceNode) {
|
||||
SortedSet<InheritanceNode> inhNodes = this.inheritanceMap.get(context);
|
||||
if (inhNodes != null) {
|
||||
inhNodes.removeIf(el -> node.equals(el, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void removeMatching(Iterator<Node> it, Node node, MutateResult result) {
|
||||
while (it.hasNext()) {
|
||||
Node el = it.next();
|
||||
if (el != node && node.equals(el, NodeEqualityPredicate.IGNORE_EXPIRY_TIME_AND_VALUE)) {
|
||||
it.remove();
|
||||
result.recordChange(ChangeType.REMOVE, el);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult removeExact(Node node) {
|
||||
ImmutableContextSet context = node.getContexts();
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
SortedSet<Node> nodes = this.map.get(context);
|
||||
if (nodes == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// try to remove an exact match
|
||||
if (nodes.remove(node)) {
|
||||
// if we removed something, record to results
|
||||
result.recordChange(ChangeType.REMOVE, node);
|
||||
|
||||
// update inheritance map too if necessary
|
||||
if (node instanceof InheritanceNode && node.getValue()) {
|
||||
SortedSet<InheritanceNode> inhNodes = this.inheritanceMap.get(context);
|
||||
if (inhNodes != null) {
|
||||
inhNodes.remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult removeIf(Predicate<? super Node> predicate) {
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
for (SortedSet<Node> nodes : this.map.values()) {
|
||||
removeMatching(nodes.iterator(), predicate, result);
|
||||
}
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult removeIf(ContextSet contextSet, Predicate<? super Node> predicate) {
|
||||
ImmutableContextSet context = contextSet.immutableCopy();
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
SortedSet<Node> nodes = this.map.get(context);
|
||||
if (nodes == null) {
|
||||
return result;
|
||||
}
|
||||
removeMatching(nodes.iterator(), predicate, result);
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void removeMatching(Iterator<Node> it, Predicate<? super Node> predicate, MutateResult result) {
|
||||
while (it.hasNext()) {
|
||||
Node node = it.next();
|
||||
|
||||
// if the predicate passes, remove the node from the set & record to results
|
||||
if (predicate.test(node)) {
|
||||
it.remove();
|
||||
result.recordChange(ChangeType.REMOVE, node);
|
||||
|
||||
// update inheritance map too if necessary
|
||||
if (node instanceof InheritanceNode && node.getValue()) {
|
||||
SortedSet<InheritanceNode> inhNodes = this.inheritanceMap.get(node.getContexts());
|
||||
if (inhNodes != null) {
|
||||
inhNodes.remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult removeThenAdd(Node nodeToRemove, Node nodeToAdd) {
|
||||
if (nodeToAdd.equals(nodeToRemove)) {
|
||||
return new MutateResult();
|
||||
}
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
return removeExact(nodeToRemove).mergeFrom(add(nodeToAdd));
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult clear() {
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
// log removals
|
||||
for (SortedSet<Node> nodes : this.map.values()) {
|
||||
result.recordChanges(ChangeType.REMOVE, nodes);
|
||||
}
|
||||
|
||||
// replace the map - this means any client reading async won't be affected
|
||||
// by any race conditions between this call to clear and any subsequent call to setContent
|
||||
this.map = createMap();
|
||||
this.inheritanceMap = createMap();
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult clear(ContextSet contextSet) {
|
||||
ImmutableContextSet context = contextSet.immutableCopy();
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
SortedSet<Node> removed = this.map.remove(context);
|
||||
if (removed != null) {
|
||||
result.recordChanges(ChangeType.REMOVE, removed);
|
||||
this.inheritanceMap.remove(context);
|
||||
}
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult setContent(Iterable<? extends Node> set) {
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
result.mergeFrom(clear());
|
||||
result.mergeFrom(addAll(set));
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult setContent(Stream<? extends Node> stream) {
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
result.mergeFrom(clear());
|
||||
result.mergeFrom(addAll(stream));
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult addAll(Iterable<? extends Node> set) {
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
for (Node n : set) {
|
||||
result.mergeFrom(add(n));
|
||||
}
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult addAll(Stream<? extends Node> stream) {
|
||||
MutateResult result = new MutateResult();
|
||||
|
||||
this.lock.lock();
|
||||
try {
|
||||
stream.forEach(n -> result.mergeFrom(add(n)));
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* 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.model.nodemap;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import net.luckperms.api.context.ContextSet;
|
||||
import net.luckperms.api.context.ImmutableContextSet;
|
||||
import net.luckperms.api.node.Node;
|
||||
import net.luckperms.api.node.NodeType;
|
||||
import net.luckperms.api.node.types.InheritanceNode;
|
||||
import net.luckperms.api.query.QueryOptions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A forwarding {@link NodeMap} that records all mutations and keeps them in a log.
|
||||
*/
|
||||
public class RecordedNodeMap implements NodeMap {
|
||||
|
||||
private final NodeMap delegate;
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private MutateResult changes = new MutateResult();
|
||||
|
||||
public RecordedNodeMap(NodeMap delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public NodeMap bypass() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
public void discardChanges() {
|
||||
this.lock.lock();
|
||||
try {
|
||||
this.changes.clear();
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public MutateResult exportChanges(Predicate<MutateResult> onlyIf) {
|
||||
this.lock.lock();
|
||||
try {
|
||||
MutateResult existing = this.changes;
|
||||
if (onlyIf.test(existing)) {
|
||||
this.changes = new MutateResult();
|
||||
return existing;
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private MutateResult record(MutateResult result) {
|
||||
this.lock.lock();
|
||||
try {
|
||||
this.changes.mergeFrom(result);
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// delegate, but pass the result through #record(MutateResult)
|
||||
|
||||
@Override
|
||||
public MutateResult add(Node nodeWithoutInheritanceOrigin) {
|
||||
return record(this.delegate.add(nodeWithoutInheritanceOrigin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult remove(Node node) {
|
||||
return record(this.delegate.remove(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult removeExact(Node node) {
|
||||
return record(this.delegate.removeExact(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult removeIf(Predicate<? super Node> predicate) {
|
||||
return record(this.delegate.removeIf(predicate));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult removeIf(ContextSet contextSet, Predicate<? super Node> predicate) {
|
||||
return record(this.delegate.removeIf(contextSet, predicate));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult removeThenAdd(Node nodeToRemove, Node nodeToAdd) {
|
||||
return record(this.delegate.removeThenAdd(nodeToRemove, nodeToAdd));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult clear() {
|
||||
return record(this.delegate.clear());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult clear(ContextSet contextSet) {
|
||||
return record(this.delegate.clear(contextSet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult setContent(Iterable<? extends Node> set) {
|
||||
return record(this.delegate.setContent(set));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult setContent(Stream<? extends Node> stream) {
|
||||
return record(this.delegate.setContent(stream));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult addAll(Iterable<? extends Node> set) {
|
||||
return record(this.delegate.addAll(set));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutateResult addAll(Stream<? extends Node> stream) {
|
||||
return record(this.delegate.addAll(stream));
|
||||
}
|
||||
|
||||
// just plain delegation
|
||||
|
||||
@Override public boolean isEmpty() { return this.delegate.isEmpty(); }
|
||||
@Override public int size() { return this.delegate.size(); }
|
||||
@Override public List<Node> asList() { return this.delegate.asList(); }
|
||||
@Override public LinkedHashSet<Node> asSet() { return this.delegate.asSet(); }
|
||||
@Override public SortedSet<Node> asSortedSet() { return this.delegate.asSortedSet(); }
|
||||
@Override public ImmutableSet<Node> asImmutableSet() { return this.delegate.asImmutableSet(); }
|
||||
@Override public Map<ImmutableContextSet, Collection<Node>> asMap() { return this.delegate.asMap(); }
|
||||
@Override public List<InheritanceNode> inheritanceAsList() { return this.delegate.inheritanceAsList(); }
|
||||
@Override public LinkedHashSet<InheritanceNode> inheritanceAsSet() { return this.delegate.inheritanceAsSet(); }
|
||||
@Override public SortedSet<InheritanceNode> inheritanceAsSortedSet() { return this.delegate.inheritanceAsSortedSet(); }
|
||||
@Override public ImmutableSet<InheritanceNode> inheritanceAsImmutableSet() { return this.delegate.inheritanceAsImmutableSet(); }
|
||||
@Override public Map<ImmutableContextSet, Collection<InheritanceNode>> inheritanceAsMap() { return this.delegate.inheritanceAsMap(); }
|
||||
@Override public void forEach(Consumer<? super Node> consumer) { this.delegate.forEach(consumer); }
|
||||
@Override public void forEach(QueryOptions filter, Consumer<? super Node> consumer) { this.delegate.forEach(filter, consumer); }
|
||||
@Override public void copyTo(Collection<? super Node> collection) { this.delegate.copyTo(collection); }
|
||||
@Override public void copyTo(ImmutableCollection.Builder<? super Node> collection) { this.delegate.copyTo(collection); }
|
||||
@Override public void copyTo(Collection<? super Node> collection, QueryOptions filter) { this.delegate.copyTo(collection, filter); }
|
||||
@Override public <T extends Node> void copyTo(Collection<? super T> collection, NodeType<T> type, QueryOptions filter) { this.delegate.copyTo(collection, type, filter); }
|
||||
@Override public void copyInheritanceNodesTo(Collection<? super InheritanceNode> collection) { this.delegate.copyInheritanceNodesTo(collection); }
|
||||
@Override public void copyInheritanceNodesTo(ImmutableCollection.Builder<? super InheritanceNode> collection) { this.delegate.copyInheritanceNodesTo(collection); }
|
||||
@Override public void copyInheritanceNodesTo(Collection<? super InheritanceNode> collection, QueryOptions filter) { this.delegate.copyInheritanceNodesTo(collection, filter); }
|
||||
@Override public Collection<Node> nodesInContext(ContextSet context) { return this.delegate.nodesInContext(context); }
|
||||
@Override public Collection<InheritanceNode> inheritanceNodesInContext(ContextSet context) { return this.delegate.inheritanceNodesInContext(context); }
|
||||
|
||||
}
|
@ -55,7 +55,6 @@ import net.luckperms.api.actionlog.Action;
|
||||
import net.luckperms.api.context.DefaultContextKeys;
|
||||
import net.luckperms.api.context.ImmutableContextSet;
|
||||
import net.luckperms.api.model.PlayerSaveResult;
|
||||
import net.luckperms.api.model.data.DataType;
|
||||
import net.luckperms.api.node.Node;
|
||||
import net.luckperms.api.node.NodeBuilder;
|
||||
import net.luckperms.api.node.NodeType;
|
||||
@ -193,44 +192,38 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
|
||||
@Override
|
||||
public User loadUser(UUID uniqueId, String username) {
|
||||
User user = this.plugin.getUserManager().getOrMake(uniqueId, username);
|
||||
user.getIoLock().lock();
|
||||
try {
|
||||
ConfigurationNode object = readFile(StorageLocation.USER, uniqueId.toString());
|
||||
if (object != null) {
|
||||
String name = object.getNode("name").getString();
|
||||
user.getPrimaryGroup().setStoredValue(object.getNode(this.loader instanceof JsonLoader ? "primaryGroup" : "primary-group").getString());
|
||||
|
||||
user.setNodes(DataType.NORMAL, readNodes(object));
|
||||
user.setUsername(name, true);
|
||||
|
||||
boolean save = this.plugin.getUserManager().giveDefaultIfNeeded(user, false);
|
||||
if (user.getUsername().isPresent() && (name == null || !user.getUsername().get().equalsIgnoreCase(name))) {
|
||||
save = true;
|
||||
}
|
||||
user.loadNodesFromStorage(readNodes(object));
|
||||
this.plugin.getUserManager().giveDefaultIfNeeded(user);
|
||||
|
||||
if (save | user.auditTemporaryNodes()) {
|
||||
boolean updatedUsername = user.getUsername().isPresent() && (name == null || !user.getUsername().get().equalsIgnoreCase(name));
|
||||
if (updatedUsername | user.auditTemporaryNodes()) {
|
||||
saveUser(user);
|
||||
}
|
||||
} else {
|
||||
if (this.plugin.getUserManager().shouldSave(user)) {
|
||||
user.clearNodes(DataType.NORMAL, null, true);
|
||||
if (this.plugin.getUserManager().isNonDefaultUser(user)) {
|
||||
user.loadNodesFromStorage(Collections.emptyList());
|
||||
user.getPrimaryGroup().setStoredValue(null);
|
||||
this.plugin.getUserManager().giveDefaultIfNeeded(user, false);
|
||||
this.plugin.getUserManager().giveDefaultIfNeeded(user);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw reportException(uniqueId.toString(), e);
|
||||
} finally {
|
||||
user.getIoLock().unlock();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUser(User user) {
|
||||
user.getIoLock().lock();
|
||||
user.normalData().discardChanges();
|
||||
try {
|
||||
if (!this.plugin.getUserManager().shouldSave(user)) {
|
||||
if (!this.plugin.getUserManager().isNonDefaultUser(user)) {
|
||||
saveFile(StorageLocation.USER, user.getUniqueId().toString(), null);
|
||||
} else {
|
||||
ConfigurationNode data = SimpleConfigurationNode.root();
|
||||
@ -245,20 +238,17 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw reportException(user.getUniqueId().toString(), e);
|
||||
} finally {
|
||||
user.getIoLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group createAndLoadGroup(String name) {
|
||||
Group group = this.plugin.getGroupManager().getOrMake(name);
|
||||
group.getIoLock().lock();
|
||||
try {
|
||||
ConfigurationNode object = readFile(StorageLocation.GROUP, name);
|
||||
|
||||
if (object != null) {
|
||||
group.setNodes(DataType.NORMAL, readNodes(object));
|
||||
group.loadNodesFromStorage(readNodes(object));
|
||||
} else {
|
||||
ConfigurationNode data = SimpleConfigurationNode.root();
|
||||
if (this instanceof SeparatedConfigurateStorage) {
|
||||
@ -270,19 +260,12 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw reportException(name, e);
|
||||
} finally {
|
||||
group.getIoLock().unlock();
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Group> loadGroup(String name) {
|
||||
Group group = this.plugin.getGroupManager().getIfLoaded(name);
|
||||
if (group != null) {
|
||||
group.getIoLock().lock();
|
||||
}
|
||||
|
||||
try {
|
||||
ConfigurationNode object = readFile(StorageLocation.GROUP, name);
|
||||
|
||||
@ -290,26 +273,17 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (group == null) {
|
||||
group = this.plugin.getGroupManager().getOrMake(name);
|
||||
group.getIoLock().lock();
|
||||
}
|
||||
|
||||
group.setNodes(DataType.NORMAL, readNodes(object));
|
||||
|
||||
Group group = this.plugin.getGroupManager().getOrMake(name);
|
||||
group.loadNodesFromStorage(readNodes(object));
|
||||
return Optional.of(group);
|
||||
} catch (Exception e) {
|
||||
throw reportException(name, e);
|
||||
} finally {
|
||||
if (group != null) {
|
||||
group.getIoLock().unlock();
|
||||
}
|
||||
}
|
||||
return Optional.of(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveGroup(Group group) {
|
||||
group.getIoLock().lock();
|
||||
group.normalData().discardChanges();
|
||||
try {
|
||||
ConfigurationNode data = SimpleConfigurationNode.root();
|
||||
if (this instanceof SeparatedConfigurateStorage) {
|
||||
@ -320,20 +294,15 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
|
||||
saveFile(StorageLocation.GROUP, group.getName(), data);
|
||||
} catch (Exception e) {
|
||||
throw reportException(group.getName(), e);
|
||||
} finally {
|
||||
group.getIoLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteGroup(Group group) {
|
||||
group.getIoLock().lock();
|
||||
try {
|
||||
saveFile(StorageLocation.GROUP, group.getName(), null);
|
||||
} catch (Exception e) {
|
||||
throw reportException(group.getName(), e);
|
||||
} finally {
|
||||
group.getIoLock().unlock();
|
||||
}
|
||||
this.plugin.getGroupManager().unload(group.getName());
|
||||
}
|
||||
@ -341,7 +310,6 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
|
||||
@Override
|
||||
public Track createAndLoadTrack(String name) {
|
||||
Track track = this.plugin.getTrackManager().getOrMake(name);
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
ConfigurationNode object = readFile(StorageLocation.TRACK, name);
|
||||
|
||||
@ -362,19 +330,12 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
|
||||
|
||||
} catch (Exception e) {
|
||||
throw reportException(name, e);
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Track> loadTrack(String name) {
|
||||
Track track = this.plugin.getTrackManager().getIfLoaded(name);
|
||||
if (track != null) {
|
||||
track.getIoLock().lock();
|
||||
}
|
||||
|
||||
try {
|
||||
ConfigurationNode object = readFile(StorageLocation.TRACK, name);
|
||||
|
||||
@ -382,30 +343,19 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (track == null) {
|
||||
track = this.plugin.getTrackManager().getOrMake(name);
|
||||
track.getIoLock().lock();
|
||||
}
|
||||
|
||||
Track track = this.plugin.getTrackManager().getOrMake(name);
|
||||
List<String> groups = object.getNode("groups").getChildrenList().stream()
|
||||
.map(ConfigurationNode::getString)
|
||||
.collect(ImmutableCollectors.toList());
|
||||
|
||||
track.setGroups(groups);
|
||||
|
||||
return Optional.of(track);
|
||||
} catch (Exception e) {
|
||||
throw reportException(name, e);
|
||||
} finally {
|
||||
if (track != null) {
|
||||
track.getIoLock().unlock();
|
||||
}
|
||||
}
|
||||
return Optional.of(track);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveTrack(Track track) {
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
ConfigurationNode data = SimpleConfigurationNode.root();
|
||||
if (this instanceof SeparatedConfigurateStorage) {
|
||||
@ -415,20 +365,15 @@ public abstract class AbstractConfigurateStorage implements StorageImplementatio
|
||||
saveFile(StorageLocation.TRACK, track.getName(), data);
|
||||
} catch (Exception e) {
|
||||
throw reportException(track.getName(), e);
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTrack(Track track) {
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
saveFile(StorageLocation.TRACK, track.getName(), null);
|
||||
} catch (Exception e) {
|
||||
throw reportException(track.getName(), e);
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
}
|
||||
this.plugin.getTrackManager().unload(track.getName());
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ import net.luckperms.api.context.ContextSet;
|
||||
import net.luckperms.api.context.DefaultContextKeys;
|
||||
import net.luckperms.api.context.MutableContextSet;
|
||||
import net.luckperms.api.model.PlayerSaveResult;
|
||||
import net.luckperms.api.model.data.DataType;
|
||||
import net.luckperms.api.node.Node;
|
||||
import net.luckperms.api.node.NodeBuilder;
|
||||
|
||||
@ -72,6 +71,7 @@ import org.bson.Document;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@ -298,53 +298,43 @@ public class MongoStorage implements StorageImplementation {
|
||||
@Override
|
||||
public User loadUser(UUID uniqueId, String username) {
|
||||
User user = this.plugin.getUserManager().getOrMake(uniqueId, username);
|
||||
user.getIoLock().lock();
|
||||
try {
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "users");
|
||||
try (MongoCursor<Document> cursor = c.find(new Document("_id", user.getUniqueId())).iterator()) {
|
||||
if (cursor.hasNext()) {
|
||||
// User exists, let's load.
|
||||
Document d = cursor.next();
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "users");
|
||||
try (MongoCursor<Document> cursor = c.find(new Document("_id", user.getUniqueId())).iterator()) {
|
||||
if (cursor.hasNext()) {
|
||||
// User exists, let's load.
|
||||
Document d = cursor.next();
|
||||
String name = d.getString("name");
|
||||
|
||||
String name = d.getString("name");
|
||||
user.getPrimaryGroup().setStoredValue(d.getString("primaryGroup"));
|
||||
user.setNodes(DataType.NORMAL, nodesFromDoc(d));
|
||||
user.setUsername(name, true);
|
||||
user.getPrimaryGroup().setStoredValue(d.getString("primaryGroup"));
|
||||
user.setUsername(name, true);
|
||||
|
||||
boolean save = this.plugin.getUserManager().giveDefaultIfNeeded(user, false);
|
||||
if (user.getUsername().isPresent() && (name == null || !user.getUsername().get().equalsIgnoreCase(name))) {
|
||||
save = true;
|
||||
}
|
||||
user.loadNodesFromStorage(nodesFromDoc(d));
|
||||
this.plugin.getUserManager().giveDefaultIfNeeded(user);
|
||||
|
||||
if (save | user.auditTemporaryNodes()) {
|
||||
c.replaceOne(new Document("_id", user.getUniqueId()), userToDoc(user));
|
||||
}
|
||||
} else {
|
||||
if (this.plugin.getUserManager().shouldSave(user)) {
|
||||
user.clearNodes(DataType.NORMAL, null, true);
|
||||
user.getPrimaryGroup().setStoredValue(null);
|
||||
this.plugin.getUserManager().giveDefaultIfNeeded(user, false);
|
||||
}
|
||||
|
||||
boolean updatedUsername = user.getUsername().isPresent() && (name == null || !user.getUsername().get().equalsIgnoreCase(name));
|
||||
if (updatedUsername | user.auditTemporaryNodes()) {
|
||||
c.replaceOne(new Document("_id", user.getUniqueId()), userToDoc(user));
|
||||
}
|
||||
} else {
|
||||
if (this.plugin.getUserManager().isNonDefaultUser(user)) {
|
||||
user.loadNodesFromStorage(Collections.emptyList());
|
||||
user.getPrimaryGroup().setStoredValue(null);
|
||||
this.plugin.getUserManager().giveDefaultIfNeeded(user);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
user.getIoLock().unlock();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUser(User user) {
|
||||
user.getIoLock().lock();
|
||||
try {
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "users");
|
||||
if (!this.plugin.getUserManager().shouldSave(user)) {
|
||||
c.deleteOne(new Document("_id", user.getUniqueId()));
|
||||
} else {
|
||||
c.replaceOne(new Document("_id", user.getUniqueId()), userToDoc(user), new ReplaceOptions().upsert(true));
|
||||
}
|
||||
} finally {
|
||||
user.getIoLock().unlock();
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "users");
|
||||
user.normalData().discardChanges();
|
||||
if (!this.plugin.getUserManager().isNonDefaultUser(user)) {
|
||||
c.deleteOne(new Document("_id", user.getUniqueId()));
|
||||
} else {
|
||||
c.replaceOne(new Document("_id", user.getUniqueId()), userToDoc(user), new ReplaceOptions().upsert(true));
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,50 +378,31 @@ public class MongoStorage implements StorageImplementation {
|
||||
@Override
|
||||
public Group createAndLoadGroup(String name) {
|
||||
Group group = this.plugin.getGroupManager().getOrMake(name);
|
||||
group.getIoLock().lock();
|
||||
try {
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "groups");
|
||||
try (MongoCursor<Document> cursor = c.find(new Document("_id", group.getName())).iterator()) {
|
||||
if (cursor.hasNext()) {
|
||||
Document d = cursor.next();
|
||||
group.setNodes(DataType.NORMAL, nodesFromDoc(d));
|
||||
} else {
|
||||
c.insertOne(groupToDoc(group));
|
||||
}
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "groups");
|
||||
try (MongoCursor<Document> cursor = c.find(new Document("_id", group.getName())).iterator()) {
|
||||
if (cursor.hasNext()) {
|
||||
Document d = cursor.next();
|
||||
group.loadNodesFromStorage(nodesFromDoc(d));
|
||||
} else {
|
||||
c.insertOne(groupToDoc(group));
|
||||
}
|
||||
} finally {
|
||||
group.getIoLock().unlock();
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Group> loadGroup(String name) {
|
||||
Group group = this.plugin.getGroupManager().getIfLoaded(name);
|
||||
if (group != null) {
|
||||
group.getIoLock().lock();
|
||||
}
|
||||
try {
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "groups");
|
||||
try (MongoCursor<Document> cursor = c.find(new Document("_id", name)).iterator()) {
|
||||
if (!cursor.hasNext()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (group == null) {
|
||||
group = this.plugin.getGroupManager().getOrMake(name);
|
||||
group.getIoLock().lock();
|
||||
}
|
||||
|
||||
Document d = cursor.next();
|
||||
group.setNodes(DataType.NORMAL, nodesFromDoc(d));
|
||||
}
|
||||
} finally {
|
||||
if (group != null) {
|
||||
group.getIoLock().unlock();
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "groups");
|
||||
try (MongoCursor<Document> cursor = c.find(new Document("_id", name)).iterator()) {
|
||||
if (!cursor.hasNext()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Group group = this.plugin.getGroupManager().getOrMake(name);
|
||||
Document d = cursor.next();
|
||||
group.loadNodesFromStorage(nodesFromDoc(d));
|
||||
return Optional.of(group);
|
||||
}
|
||||
return Optional.of(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -454,24 +425,15 @@ public class MongoStorage implements StorageImplementation {
|
||||
|
||||
@Override
|
||||
public void saveGroup(Group group) {
|
||||
group.getIoLock().lock();
|
||||
try {
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "groups");
|
||||
c.replaceOne(new Document("_id", group.getName()), groupToDoc(group), new ReplaceOptions().upsert(true));
|
||||
} finally {
|
||||
group.getIoLock().unlock();
|
||||
}
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "groups");
|
||||
group.normalData().discardChanges();
|
||||
c.replaceOne(new Document("_id", group.getName()), groupToDoc(group), new ReplaceOptions().upsert(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteGroup(Group group) {
|
||||
group.getIoLock().lock();
|
||||
try {
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "groups");
|
||||
c.deleteOne(new Document("_id", group.getName()));
|
||||
} finally {
|
||||
group.getIoLock().unlock();
|
||||
}
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "groups");
|
||||
c.deleteOne(new Document("_id", group.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -498,53 +460,33 @@ public class MongoStorage implements StorageImplementation {
|
||||
@Override
|
||||
public Track createAndLoadTrack(String name) {
|
||||
Track track = this.plugin.getTrackManager().getOrMake(name);
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "tracks");
|
||||
try (MongoCursor<Document> cursor = c.find(new Document("_id", track.getName())).iterator()) {
|
||||
if (!cursor.hasNext()) {
|
||||
c.insertOne(trackToDoc(track));
|
||||
} else {
|
||||
Document d = cursor.next();
|
||||
//noinspection unchecked
|
||||
track.setGroups((List<String>) d.get("groups"));
|
||||
}
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "tracks");
|
||||
try (MongoCursor<Document> cursor = c.find(new Document("_id", track.getName())).iterator()) {
|
||||
if (!cursor.hasNext()) {
|
||||
c.insertOne(trackToDoc(track));
|
||||
} else {
|
||||
Document d = cursor.next();
|
||||
//noinspection unchecked
|
||||
track.setGroups((List<String>) d.get("groups"));
|
||||
}
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Track> loadTrack(String name) {
|
||||
Track track = this.plugin.getTrackManager().getIfLoaded(name);
|
||||
if (track != null) {
|
||||
track.getIoLock().lock();
|
||||
}
|
||||
|
||||
try {
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "tracks");
|
||||
try (MongoCursor<Document> cursor = c.find(new Document("_id", name)).iterator()) {
|
||||
if (!cursor.hasNext()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (track == null) {
|
||||
track = this.plugin.getTrackManager().getOrMake(name);
|
||||
track.getIoLock().lock();
|
||||
}
|
||||
|
||||
Document d = cursor.next();
|
||||
//noinspection unchecked
|
||||
track.setGroups((List<String>) d.get("groups"));
|
||||
}
|
||||
} finally {
|
||||
if (track != null) {
|
||||
track.getIoLock().unlock();
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "tracks");
|
||||
try (MongoCursor<Document> cursor = c.find(new Document("_id", name)).iterator()) {
|
||||
if (!cursor.hasNext()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Track track = this.plugin.getTrackManager().getOrMake(name);
|
||||
Document d = cursor.next();
|
||||
//noinspection unchecked
|
||||
track.setGroups((List<String>) d.get("groups"));
|
||||
return Optional.of(track);
|
||||
}
|
||||
return Optional.of(track);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -567,24 +509,14 @@ public class MongoStorage implements StorageImplementation {
|
||||
|
||||
@Override
|
||||
public void saveTrack(Track track) {
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "tracks");
|
||||
c.replaceOne(new Document("_id", track.getName()), trackToDoc(track));
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
}
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "tracks");
|
||||
c.replaceOne(new Document("_id", track.getName()), trackToDoc(track));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTrack(Track track) {
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "tracks");
|
||||
c.deleteOne(new Document("_id", track.getName()));
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
}
|
||||
MongoCollection<Document> c = this.database.getCollection(this.prefix + "tracks");
|
||||
c.deleteOne(new Document("_id", track.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,195 +0,0 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* 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.storage.implementation.sql;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import me.lucko.luckperms.common.context.ContextSetJsonSerializer;
|
||||
import me.lucko.luckperms.common.node.factory.NodeBuilders;
|
||||
import me.lucko.luckperms.common.util.gson.GsonProvider;
|
||||
|
||||
import net.luckperms.api.context.ContextSet;
|
||||
import net.luckperms.api.context.DefaultContextKeys;
|
||||
import net.luckperms.api.context.ImmutableContextSet;
|
||||
import net.luckperms.api.context.MutableContextSet;
|
||||
import net.luckperms.api.node.Node;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A version of {@link Node}, more closely following the model used by the SQL
|
||||
* datastore.
|
||||
*
|
||||
* All values are non-null.
|
||||
*/
|
||||
public final class SqlNode {
|
||||
|
||||
public static final int NULL_ID = -1;
|
||||
|
||||
public static SqlNode fromNode(Node node) {
|
||||
ContextSet contexts = node.getContexts();
|
||||
|
||||
Set<String> servers = contexts.getValues(DefaultContextKeys.SERVER_KEY);
|
||||
Optional<String> firstServer = servers.stream().sorted().findFirst();
|
||||
|
||||
String server;
|
||||
if (firstServer.isPresent()) {
|
||||
server = firstServer.get();
|
||||
MutableContextSet mutableContextSet = contexts.mutableCopy();
|
||||
mutableContextSet.remove(DefaultContextKeys.SERVER_KEY, server);
|
||||
contexts = mutableContextSet;
|
||||
} else {
|
||||
server = "global";
|
||||
}
|
||||
|
||||
Set<String> worlds = contexts.getValues(DefaultContextKeys.WORLD_KEY);
|
||||
Optional<String> firstWorld = worlds.stream().sorted().findFirst();
|
||||
|
||||
String world;
|
||||
if (firstWorld.isPresent()) {
|
||||
world = firstWorld.get();
|
||||
MutableContextSet mutableContextSet = contexts instanceof MutableContextSet ? (MutableContextSet) contexts : contexts.mutableCopy();
|
||||
mutableContextSet.remove(DefaultContextKeys.WORLD_KEY, world);
|
||||
contexts = mutableContextSet;
|
||||
} else {
|
||||
world = "global";
|
||||
}
|
||||
|
||||
|
||||
long expiry = node.hasExpiry() ? node.getExpiry().getEpochSecond() : 0L;
|
||||
return new SqlNode(node.getKey(), node.getValue(), server, world, expiry, contexts.immutableCopy(), NULL_ID);
|
||||
}
|
||||
|
||||
public static SqlNode fromSqlFields(long sqlId, String permission, boolean value, String server, String world, long expiry, String contexts) {
|
||||
if (Strings.isNullOrEmpty(server)) {
|
||||
server = "global";
|
||||
}
|
||||
if (Strings.isNullOrEmpty(world)) {
|
||||
world = "global";
|
||||
}
|
||||
|
||||
return new SqlNode(permission, value, server, world, expiry, ContextSetJsonSerializer.deserialize(GsonProvider.normal(), contexts).immutableCopy(), sqlId);
|
||||
}
|
||||
|
||||
private final String permission;
|
||||
private final boolean value;
|
||||
private final String server;
|
||||
private final String world;
|
||||
private final long expiry;
|
||||
private final ImmutableContextSet contexts;
|
||||
private final long sqlId;
|
||||
|
||||
private SqlNode(String permission, boolean value, String server, String world, long expiry, ImmutableContextSet contexts, long sqlId) {
|
||||
this.permission = Objects.requireNonNull(permission, "permission");
|
||||
this.value = value;
|
||||
this.server = Objects.requireNonNull(server, "server");
|
||||
this.world = Objects.requireNonNull(world, "world");
|
||||
this.expiry = expiry;
|
||||
this.contexts = Objects.requireNonNull(contexts, "contexts");
|
||||
this.sqlId = sqlId;
|
||||
}
|
||||
|
||||
public Node toNode() {
|
||||
return NodeBuilders.determineMostApplicable(this.permission)
|
||||
.value(this.value)
|
||||
.withContext(DefaultContextKeys.SERVER_KEY, this.server)
|
||||
.withContext(DefaultContextKeys.WORLD_KEY, this.world)
|
||||
.expiry(this.expiry)
|
||||
.withContext(this.contexts)
|
||||
.build();
|
||||
}
|
||||
|
||||
public String getPermission() {
|
||||
return this.permission;
|
||||
}
|
||||
|
||||
public boolean getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
public String getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
public long getExpiry() {
|
||||
return this.expiry;
|
||||
}
|
||||
|
||||
public ImmutableContextSet getContexts() {
|
||||
return this.contexts;
|
||||
}
|
||||
|
||||
public long getSqlId() {
|
||||
if (this.sqlId == NULL_ID) {
|
||||
throw new IllegalStateException("sql id not set");
|
||||
}
|
||||
return this.sqlId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (!(o instanceof SqlNode)) return false;
|
||||
final SqlNode other = (SqlNode) o;
|
||||
|
||||
return this.getPermission().equals(other.getPermission()) &&
|
||||
this.getValue() == other.getValue() &&
|
||||
this.getServer().equals(other.getServer()) &&
|
||||
this.getWorld().equals(other.getWorld()) &&
|
||||
this.getExpiry() == other.getExpiry() &&
|
||||
this.getContexts().equals(other.getContexts());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int PRIME = 59;
|
||||
int result = 1;
|
||||
result = result * PRIME + this.getPermission().hashCode();
|
||||
result = result * PRIME + Boolean.hashCode(this.getValue());
|
||||
result = result * PRIME + this.getServer().hashCode();
|
||||
result = result * PRIME + this.getWorld().hashCode();
|
||||
result = result * PRIME + Long.hashCode(this.getExpiry());
|
||||
result = result * PRIME + this.getContexts().hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NodeModel(" +
|
||||
"permission=" + this.getPermission() + ", " +
|
||||
"value=" + this.getValue() + ", " +
|
||||
"server=" + this.getServer() + ", " +
|
||||
"world=" + this.getWorld() + ", " +
|
||||
"expiry=" + this.getExpiry() + ", " +
|
||||
"contexts=" + this.getContexts() + ")";
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* This file is part of LuckPerms, licensed under the MIT License.
|
||||
*
|
||||
* Copyright (c) lucko (Luck) <luck@lucko.me>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* 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.storage.implementation.sql;
|
||||
|
||||
import net.luckperms.api.node.metadata.NodeMetadataKey;
|
||||
|
||||
public final class SqlRowId {
|
||||
public static final NodeMetadataKey<SqlRowId> KEY = NodeMetadataKey.of("sqlrowid", SqlRowId.class);
|
||||
|
||||
private final long rowId;
|
||||
|
||||
public SqlRowId(long rowId) {
|
||||
this.rowId = rowId;
|
||||
}
|
||||
|
||||
public long getRowId() {
|
||||
return this.rowId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || (o instanceof SqlRowId && this.rowId == ((SqlRowId) o).rowId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(this.rowId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SqlRowId{rowId=" + this.rowId + '}';
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@
|
||||
|
||||
package me.lucko.luckperms.common.storage.implementation.sql;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
@ -38,6 +39,8 @@ import me.lucko.luckperms.common.model.Group;
|
||||
import me.lucko.luckperms.common.model.Track;
|
||||
import me.lucko.luckperms.common.model.User;
|
||||
import me.lucko.luckperms.common.model.manager.group.GroupManager;
|
||||
import me.lucko.luckperms.common.model.nodemap.MutateResult;
|
||||
import me.lucko.luckperms.common.node.factory.NodeBuilders;
|
||||
import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher;
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
import me.lucko.luckperms.common.storage.implementation.StorageImplementation;
|
||||
@ -49,8 +52,9 @@ import me.lucko.luckperms.common.util.gson.GsonProvider;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.luckperms.api.actionlog.Action;
|
||||
import net.luckperms.api.context.DefaultContextKeys;
|
||||
import net.luckperms.api.context.MutableContextSet;
|
||||
import net.luckperms.api.model.PlayerSaveResult;
|
||||
import net.luckperms.api.model.data.DataType;
|
||||
import net.luckperms.api.node.Node;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -80,6 +84,7 @@ public class SqlStorage implements StorageImplementation {
|
||||
|
||||
private static final String USER_PERMISSIONS_SELECT = "SELECT id, permission, value, server, world, expiry, contexts FROM '{prefix}user_permissions' WHERE uuid=?";
|
||||
private static final String USER_PERMISSIONS_DELETE_SPECIFIC = "DELETE FROM '{prefix}user_permissions' WHERE id=?";
|
||||
private static final String USER_PERMISSIONS_DELETE_SPECIFIC_PROPS = "DELETE FROM '{prefix}user_permissions' WHERE uuid=? AND permission=? AND value=? AND server=? AND world=? AND expiry=? AND contexts=?";
|
||||
private static final String USER_PERMISSIONS_DELETE = "DELETE FROM '{prefix}user_permissions' WHERE uuid=?";
|
||||
private static final String USER_PERMISSIONS_INSERT = "INSERT INTO '{prefix}user_permissions' (uuid, permission, value, server, world, expiry, contexts) VALUES(?, ?, ?, ?, ?, ?, ?)";
|
||||
private static final String USER_PERMISSIONS_SELECT_DISTINCT = "SELECT DISTINCT uuid FROM '{prefix}user_permissions'";
|
||||
@ -99,6 +104,7 @@ public class SqlStorage implements StorageImplementation {
|
||||
private static final String GROUP_PERMISSIONS_SELECT = "SELECT id, permission, value, server, world, expiry, contexts FROM '{prefix}group_permissions' WHERE name=?";
|
||||
private static final String GROUP_PERMISSIONS_SELECT_ALL = "SELECT name, id, permission, value, server, world, expiry, contexts FROM '{prefix}group_permissions'";
|
||||
private static final String GROUP_PERMISSIONS_DELETE_SPECIFIC = "DELETE FROM '{prefix}group_permissions' WHERE id=?";
|
||||
private static final String GROUP_PERMISSIONS_DELETE_SPECIFIC_PROPS = "DELETE FROM '{prefix}group_permissions' WHERE name=? AND permission=? AND value=? AND server=? AND world=? AND expiry=? AND contexts=?";
|
||||
private static final String GROUP_PERMISSIONS_DELETE = "DELETE FROM '{prefix}group_permissions' WHERE name=?";
|
||||
private static final String GROUP_PERMISSIONS_INSERT = "INSERT INTO '{prefix}group_permissions' (name, permission, value, server, world, expiry, contexts) VALUES(?, ?, ?, ?, ?, ?, ?)";
|
||||
private static final String GROUP_PERMISSIONS_SELECT_PERMISSION = "SELECT name, id, permission, value, server, world, expiry, contexts FROM '{prefix}group_permissions' WHERE ";
|
||||
@ -313,82 +319,64 @@ public class SqlStorage implements StorageImplementation {
|
||||
@Override
|
||||
public User loadUser(UUID uniqueId, String username) throws SQLException {
|
||||
User user = this.plugin.getUserManager().getOrMake(uniqueId, username);
|
||||
user.getIoLock().lock();
|
||||
try {
|
||||
List<SqlNode> nodes;
|
||||
String primaryGroup = null;
|
||||
String savedUsername = null;
|
||||
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
nodes = selectUserPermissions(new ArrayList<>(), c, user.getUniqueId());
|
||||
List<Node> nodes;
|
||||
SqlPlayerData playerData;
|
||||
|
||||
SqlPlayerData playerData = selectPlayerData(c, user.getUniqueId());
|
||||
if (playerData != null) {
|
||||
primaryGroup = playerData.primaryGroup;
|
||||
savedUsername = playerData.username;
|
||||
}
|
||||
}
|
||||
|
||||
// update username & primary group
|
||||
if (primaryGroup == null) {
|
||||
primaryGroup = GroupManager.DEFAULT_GROUP_NAME;
|
||||
}
|
||||
user.getPrimaryGroup().setStoredValue(primaryGroup);
|
||||
|
||||
// Update their username to what was in the storage if the one in the local instance is null
|
||||
user.setUsername(savedUsername, true);
|
||||
|
||||
if (!nodes.isEmpty()) {
|
||||
user.setNodes(DataType.NORMAL, nodes.stream().map(SqlNode::toNode));
|
||||
|
||||
// Save back to the store if data they were given any defaults or had permissions expire
|
||||
if (this.plugin.getUserManager().giveDefaultIfNeeded(user, false) | user.auditTemporaryNodes()) {
|
||||
// This should be fine, as the lock will be acquired by the same thread.
|
||||
saveUser(user);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (this.plugin.getUserManager().shouldSave(user)) {
|
||||
user.clearNodes(DataType.NORMAL, null, true);
|
||||
user.getPrimaryGroup().setStoredValue(null);
|
||||
this.plugin.getUserManager().giveDefaultIfNeeded(user, false);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
user.getIoLock().unlock();
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
nodes = selectUserPermissions(c, user.getUniqueId());
|
||||
playerData = selectPlayerData(c, user.getUniqueId());
|
||||
}
|
||||
|
||||
if (playerData != null) {
|
||||
if (playerData.primaryGroup != null) {
|
||||
user.getPrimaryGroup().setStoredValue(playerData.primaryGroup);
|
||||
} else {
|
||||
user.getPrimaryGroup().setStoredValue(GroupManager.DEFAULT_GROUP_NAME);
|
||||
}
|
||||
|
||||
user.setUsername(playerData.username, true);
|
||||
}
|
||||
|
||||
user.loadNodesFromStorage(nodes);
|
||||
this.plugin.getUserManager().giveDefaultIfNeeded(user);
|
||||
|
||||
if (user.auditTemporaryNodes()) {
|
||||
saveUser(user);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUser(User user) throws SQLException {
|
||||
user.getIoLock().lock();
|
||||
try {
|
||||
if (!this.plugin.getUserManager().shouldSave(user)) {
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
deleteUser(c, user.getUniqueId());
|
||||
}
|
||||
return;
|
||||
MutateResult changes = user.normalData().exportChanges(results -> {
|
||||
if (this.plugin.getUserManager().isNonDefaultUser(user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Set<SqlNode> remote;
|
||||
// if the only change is adding the default node, we don't need to export
|
||||
if (results.getChanges().size() == 1) {
|
||||
MutateResult.Change onlyChange = results.getChanges().iterator().next();
|
||||
return !(onlyChange.getType() == MutateResult.ChangeType.ADD && this.plugin.getUserManager().isDefaultNode(onlyChange.getNode()));
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (changes == null) {
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
remote = selectUserPermissions(new HashSet<>(), c, user.getUniqueId());
|
||||
deleteUser(c, user.getUniqueId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Set<SqlNode> local = user.normalData().asList().stream().map(SqlNode::fromNode).collect(Collectors.toSet());
|
||||
Set<SqlNode> missingFromRemote = getMissingFromRemote(local, remote);
|
||||
Set<SqlNode> missingFromLocal = getMissingFromLocal(local, remote);
|
||||
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
updateUserPermissions(c, user.getUniqueId(), missingFromRemote, missingFromLocal);
|
||||
insertPlayerData(c, user.getUniqueId(), new SqlPlayerData(
|
||||
user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME),
|
||||
user.getUsername().orElse("null").toLowerCase()
|
||||
));
|
||||
}
|
||||
} finally {
|
||||
user.getIoLock().unlock();
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
updateUserPermissions(c, user.getUniqueId(), changes.getAdded(), changes.getRemoved());
|
||||
insertPlayerData(c, user.getUniqueId(), new SqlPlayerData(
|
||||
user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME),
|
||||
user.getUsername().orElse("null").toLowerCase()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,7 +409,7 @@ public class SqlStorage implements StorageImplementation {
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
UUID holder = UUID.fromString(rs.getString("uuid"));
|
||||
Node node = readNode(rs).toNode();
|
||||
Node node = readNode(rs);
|
||||
|
||||
N match = constraint.filterConstraintMatch(node);
|
||||
if (match != null) {
|
||||
@ -460,45 +448,27 @@ public class SqlStorage implements StorageImplementation {
|
||||
}
|
||||
|
||||
Group group = this.plugin.getGroupManager().getOrMake(name);
|
||||
group.getIoLock().lock();
|
||||
try {
|
||||
List<SqlNode> nodes;
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
nodes = selectGroupPermissions(new ArrayList<>(), c, group.getName());
|
||||
}
|
||||
|
||||
if (!nodes.isEmpty()) {
|
||||
group.setNodes(DataType.NORMAL, nodes.stream().map(SqlNode::toNode));
|
||||
} else {
|
||||
group.clearNodes(DataType.NORMAL, null, false);
|
||||
}
|
||||
} finally {
|
||||
group.getIoLock().unlock();
|
||||
List<Node> nodes;
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
nodes = selectGroupPermissions(c, group.getName());
|
||||
}
|
||||
|
||||
group.loadNodesFromStorage(nodes);
|
||||
return Optional.of(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAllGroups() throws SQLException {
|
||||
Map<String, Collection<SqlNode>> groups = new HashMap<>();
|
||||
Map<String, Collection<Node>> groups = new HashMap<>();
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
selectGroups(c).forEach(name -> groups.put(name, new ArrayList<>()));
|
||||
selectAllGroupPermissions(groups, c);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Collection<SqlNode>> entry : groups.entrySet()) {
|
||||
for (Map.Entry<String, Collection<Node>> entry : groups.entrySet()) {
|
||||
Group group = this.plugin.getGroupManager().getOrMake(entry.getKey());
|
||||
group.getIoLock().lock();
|
||||
try {
|
||||
Collection<SqlNode> nodes = entry.getValue();
|
||||
if (!nodes.isEmpty()) {
|
||||
group.setNodes(DataType.NORMAL, nodes.stream().map(SqlNode::toNode));
|
||||
} else {
|
||||
group.clearNodes(DataType.NORMAL, null, false);
|
||||
}
|
||||
} finally {
|
||||
group.getIoLock().unlock();
|
||||
}
|
||||
Collection<Node> nodes = entry.getValue();
|
||||
group.loadNodesFromStorage(nodes);
|
||||
}
|
||||
|
||||
this.plugin.getGroupManager().retainAll(groups.keySet());
|
||||
@ -506,48 +476,24 @@ public class SqlStorage implements StorageImplementation {
|
||||
|
||||
@Override
|
||||
public void saveGroup(Group group) throws SQLException {
|
||||
group.getIoLock().lock();
|
||||
try {
|
||||
if (group.normalData().isEmpty()) {
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
deleteGroupPermissions(c, group.getName());
|
||||
}
|
||||
return;
|
||||
}
|
||||
MutateResult changes = group.normalData().exportChanges(c -> true);
|
||||
|
||||
Set<SqlNode> remote;
|
||||
if (!changes.isEmpty()) {
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
remote = selectGroupPermissions(new HashSet<>(), c, group.getName());
|
||||
updateGroupPermissions(c, group.getName(), changes.getAdded(), changes.getRemoved());
|
||||
}
|
||||
|
||||
Set<SqlNode> local = group.normalData().asList().stream().map(SqlNode::fromNode).collect(Collectors.toSet());
|
||||
Set<SqlNode> missingFromRemote = getMissingFromRemote(local, remote);
|
||||
Set<SqlNode> missingFromLocal = getMissingFromLocal(local, remote);
|
||||
|
||||
if (!missingFromLocal.isEmpty() || !missingFromRemote.isEmpty()) {
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
updateGroupPermissions(c, group.getName(), missingFromRemote, missingFromLocal);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
group.getIoLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteGroup(Group group) throws SQLException {
|
||||
group.getIoLock().lock();
|
||||
try {
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
deleteGroupPermissions(c, group.getName());
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
deleteGroupPermissions(c, group.getName());
|
||||
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_DELETE))) {
|
||||
ps.setString(1, group.getName());
|
||||
ps.execute();
|
||||
}
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_DELETE))) {
|
||||
ps.setString(1, group.getName());
|
||||
ps.execute();
|
||||
}
|
||||
} finally {
|
||||
group.getIoLock().unlock();
|
||||
}
|
||||
|
||||
this.plugin.getGroupManager().unload(group.getName());
|
||||
@ -564,7 +510,7 @@ public class SqlStorage implements StorageImplementation {
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
String holder = rs.getString("name");
|
||||
Node node = readNode(rs).toNode();
|
||||
Node node = readNode(rs);
|
||||
|
||||
N match = constraint.filterConstraintMatch(node);
|
||||
if (match != null) {
|
||||
@ -580,22 +526,17 @@ public class SqlStorage implements StorageImplementation {
|
||||
@Override
|
||||
public Track createAndLoadTrack(String name) throws SQLException {
|
||||
Track track = this.plugin.getTrackManager().getOrMake(name);
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
List<String> groups;
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
groups = selectTrack(c, track.getName());
|
||||
}
|
||||
List<String> groups;
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
groups = selectTrack(c, track.getName());
|
||||
}
|
||||
|
||||
if (groups != null) {
|
||||
track.setGroups(groups);
|
||||
} else {
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
insertTrack(c, track.getName(), track.getGroups());
|
||||
}
|
||||
if (groups != null) {
|
||||
track.setGroups(groups);
|
||||
} else {
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
insertTrack(c, track.getName(), track.getGroups());
|
||||
}
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
}
|
||||
return track;
|
||||
}
|
||||
@ -612,17 +553,12 @@ public class SqlStorage implements StorageImplementation {
|
||||
}
|
||||
|
||||
Track track = this.plugin.getTrackManager().getOrMake(name);
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
List<String> groups;
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
groups = selectTrack(c, name);
|
||||
}
|
||||
|
||||
track.setGroups(groups);
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
List<String> groups;
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
groups = selectTrack(c, name);
|
||||
}
|
||||
|
||||
track.setGroups(groups);
|
||||
return Optional.of(track);
|
||||
}
|
||||
|
||||
@ -634,13 +570,8 @@ public class SqlStorage implements StorageImplementation {
|
||||
|
||||
for (String trackName : tracks) {
|
||||
Track track = this.plugin.getTrackManager().getOrMake(trackName);
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
List<String> groups = selectTrack(c, trackName);
|
||||
track.setGroups(groups);
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
}
|
||||
List<String> groups = selectTrack(c, trackName);
|
||||
track.setGroups(groups);
|
||||
}
|
||||
}
|
||||
|
||||
@ -649,28 +580,18 @@ public class SqlStorage implements StorageImplementation {
|
||||
|
||||
@Override
|
||||
public void saveTrack(Track track) throws SQLException {
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
updateTrack(c, track.getName(), track.getGroups());
|
||||
}
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
updateTrack(c, track.getName(), track.getGroups());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTrack(Track track) throws SQLException {
|
||||
track.getIoLock().lock();
|
||||
try {
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_DELETE))) {
|
||||
ps.setString(1, track.getName());
|
||||
ps.execute();
|
||||
}
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_DELETE))) {
|
||||
ps.setString(1, track.getName());
|
||||
ps.execute();
|
||||
}
|
||||
} finally {
|
||||
track.getIoLock().unlock();
|
||||
}
|
||||
|
||||
this.plugin.getTrackManager().unload(track.getName());
|
||||
@ -797,7 +718,7 @@ public class SqlStorage implements StorageImplementation {
|
||||
.build();
|
||||
}
|
||||
|
||||
private static SqlNode readNode(ResultSet rs) throws SQLException {
|
||||
private static Node readNode(ResultSet rs) throws SQLException {
|
||||
long id = rs.getLong("id");
|
||||
String permission = rs.getString("permission");
|
||||
boolean value = rs.getBoolean("value");
|
||||
@ -805,33 +726,105 @@ public class SqlStorage implements StorageImplementation {
|
||||
String world = rs.getString("world");
|
||||
long expiry = rs.getLong("expiry");
|
||||
String contexts = rs.getString("contexts");
|
||||
return SqlNode.fromSqlFields(id, permission, value, server, world, expiry, contexts);
|
||||
|
||||
if (Strings.isNullOrEmpty(server)) {
|
||||
server = "global";
|
||||
}
|
||||
if (Strings.isNullOrEmpty(world)) {
|
||||
world = "global";
|
||||
}
|
||||
|
||||
return NodeBuilders.determineMostApplicable(permission)
|
||||
.value(value)
|
||||
.withContext(DefaultContextKeys.SERVER_KEY, server)
|
||||
.withContext(DefaultContextKeys.WORLD_KEY, world)
|
||||
.expiry(expiry)
|
||||
.withContext(ContextSetJsonSerializer.deserialize(GsonProvider.normal(), contexts).immutableCopy())
|
||||
.withMetadata(SqlRowId.KEY, new SqlRowId(id))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static void writeNode(SqlNode nd, PreparedStatement ps) throws SQLException {
|
||||
ps.setString(2, nd.getPermission());
|
||||
ps.setBoolean(3, nd.getValue());
|
||||
ps.setString(4, nd.getServer());
|
||||
ps.setString(5, nd.getWorld());
|
||||
ps.setLong(6, nd.getExpiry());
|
||||
ps.setString(7, GsonProvider.normal().toJson(ContextSetJsonSerializer.serialize(nd.getContexts())));
|
||||
private static String getFirstContextValue(MutableContextSet set, String key) {
|
||||
Set<String> values = set.getValues(key);
|
||||
String value = values.stream().sorted().findFirst().orElse(null);
|
||||
if (value != null) {
|
||||
set.remove(key, value);
|
||||
} else {
|
||||
value = "global";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static Set<SqlNode> getMissingFromRemote(Set<SqlNode> local, Set<SqlNode> remote) {
|
||||
// entries in local but not remote need to be added
|
||||
Set<SqlNode> missingFromRemote = new HashSet<>(local);
|
||||
missingFromRemote.removeAll(remote);
|
||||
return missingFromRemote;
|
||||
private static void writeNode(Node node, PreparedStatement ps) throws SQLException {
|
||||
MutableContextSet contexts = node.getContexts().mutableCopy();
|
||||
String server = getFirstContextValue(contexts, DefaultContextKeys.SERVER_KEY);
|
||||
String world = getFirstContextValue(contexts, DefaultContextKeys.WORLD_KEY);
|
||||
long expiry = node.hasExpiry() ? node.getExpiry().getEpochSecond() : 0L;
|
||||
|
||||
ps.setString(2, node.getKey());
|
||||
ps.setBoolean(3, node.getValue());
|
||||
ps.setString(4, server);
|
||||
ps.setString(5, world);
|
||||
ps.setLong(6, expiry);
|
||||
ps.setString(7, GsonProvider.normal().toJson(ContextSetJsonSerializer.serialize(contexts)));
|
||||
}
|
||||
|
||||
private static Set<SqlNode> getMissingFromLocal(Set<SqlNode> local, Set<SqlNode> remote) {
|
||||
// entries in remote but not local need to be removed
|
||||
Set<SqlNode> missingFromLocal = new HashSet<>(remote);
|
||||
missingFromLocal.removeAll(local);
|
||||
return missingFromLocal;
|
||||
private void updateUserPermissions(Connection c, UUID user, Set<Node> add, Set<Node> delete) throws SQLException {
|
||||
updatePermissions(c, user.toString(), add, delete, USER_PERMISSIONS_DELETE_SPECIFIC, USER_PERMISSIONS_DELETE_SPECIFIC_PROPS, USER_PERMISSIONS_INSERT);
|
||||
}
|
||||
|
||||
private <T extends Collection<SqlNode>> T selectUserPermissions(T nodes, Connection c, UUID user) throws SQLException {
|
||||
private void updateGroupPermissions(Connection c, String group, Set<Node> add, Set<Node> delete) throws SQLException {
|
||||
updatePermissions(c, group, add, delete, GROUP_PERMISSIONS_DELETE_SPECIFIC, GROUP_PERMISSIONS_DELETE_SPECIFIC_PROPS, GROUP_PERMISSIONS_INSERT);
|
||||
}
|
||||
|
||||
private void updatePermissions(Connection c, String holder, Set<Node> add, Set<Node> delete, String deleteSpecificQuery, String deleteQuery, String insertQuery) throws SQLException {
|
||||
if (!delete.isEmpty()) {
|
||||
List<Long> deleteRows = new ArrayList<>(delete.size());
|
||||
List<Node> deleteNodes = new ArrayList<>(delete.size());
|
||||
for (Node node : delete) {
|
||||
SqlRowId rowId = node.getMetadata(SqlRowId.KEY).orElse(null);
|
||||
if (rowId != null) {
|
||||
deleteRows.add(rowId.getRowId());
|
||||
} else {
|
||||
deleteNodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!deleteRows.isEmpty()) {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(deleteSpecificQuery))) {
|
||||
for (Long id : deleteRows) {
|
||||
ps.setLong(1, id);
|
||||
ps.addBatch();
|
||||
}
|
||||
ps.executeBatch();
|
||||
}
|
||||
}
|
||||
if (!deleteNodes.isEmpty()) {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(deleteQuery))) {
|
||||
for (Node node : deleteNodes) {
|
||||
ps.setString(1, holder);
|
||||
writeNode(node, ps);
|
||||
ps.addBatch();
|
||||
}
|
||||
ps.executeBatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!add.isEmpty()) {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(insertQuery))) {
|
||||
for (Node node : add) {
|
||||
ps.setString(1, holder);
|
||||
writeNode(node, ps);
|
||||
ps.addBatch();
|
||||
}
|
||||
ps.executeBatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Node> selectUserPermissions(Connection c, UUID user) throws SQLException {
|
||||
List<Node> nodes = new ArrayList<>();
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_SELECT))) {
|
||||
ps.setString(1, user.toString());
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
@ -868,28 +861,6 @@ public class SqlStorage implements StorageImplementation {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUserPermissions(Connection c, UUID user, Set<SqlNode> add, Set<SqlNode> delete) throws SQLException {
|
||||
if (!delete.isEmpty()) {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_DELETE_SPECIFIC))) {
|
||||
for (SqlNode node : delete) {
|
||||
ps.setLong(1, node.getSqlId());
|
||||
ps.addBatch();
|
||||
}
|
||||
ps.executeBatch();
|
||||
}
|
||||
}
|
||||
if (!add.isEmpty()) {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_INSERT))) {
|
||||
for (SqlNode node : add) {
|
||||
ps.setString(1, user.toString());
|
||||
writeNode(node, ps);
|
||||
ps.addBatch();
|
||||
}
|
||||
ps.executeBatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void insertPlayerData(Connection c, UUID user, SqlPlayerData data) throws SQLException {
|
||||
boolean hasPrimaryGroupSaved;
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_PRIMARY_GROUP_BY_UUID))) {
|
||||
@ -929,7 +900,8 @@ public class SqlStorage implements StorageImplementation {
|
||||
return groups;
|
||||
}
|
||||
|
||||
private <T extends Collection<SqlNode>> T selectGroupPermissions(T nodes, Connection c, String group) throws SQLException {
|
||||
private List<Node> selectGroupPermissions(Connection c, String group) throws SQLException {
|
||||
List<Node> nodes = new ArrayList<>();
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_SELECT))) {
|
||||
ps.setString(1, group);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
@ -941,12 +913,12 @@ public class SqlStorage implements StorageImplementation {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private void selectAllGroupPermissions(Map<String, Collection<SqlNode>> nodes, Connection c) throws SQLException {
|
||||
private void selectAllGroupPermissions(Map<String, Collection<Node>> nodes, Connection c) throws SQLException {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_SELECT_ALL))) {
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
String holder = rs.getString("name");
|
||||
Collection<SqlNode> list = nodes.get(holder);
|
||||
Collection<Node> list = nodes.get(holder);
|
||||
if (list != null) {
|
||||
list.add(readNode(rs));
|
||||
}
|
||||
@ -962,28 +934,6 @@ public class SqlStorage implements StorageImplementation {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateGroupPermissions(Connection c, String group, Set<SqlNode> add, Set<SqlNode> delete) throws SQLException {
|
||||
if (!delete.isEmpty()) {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_DELETE_SPECIFIC))) {
|
||||
for (SqlNode node : delete) {
|
||||
ps.setLong(1, node.getSqlId());
|
||||
ps.addBatch();
|
||||
}
|
||||
ps.executeBatch();
|
||||
}
|
||||
}
|
||||
if (!add.isEmpty()) {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_INSERT))) {
|
||||
for (SqlNode node : add) {
|
||||
ps.setString(1, group);
|
||||
writeNode(node, ps);
|
||||
ps.addBatch();
|
||||
}
|
||||
ps.executeBatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> selectTrack(Connection c, String name) throws SQLException {
|
||||
String groups;
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_SELECT))) {
|
||||
|
@ -26,12 +26,9 @@
|
||||
package me.lucko.luckperms.common.tasks;
|
||||
|
||||
import me.lucko.luckperms.common.model.Group;
|
||||
import me.lucko.luckperms.common.model.PermissionHolder;
|
||||
import me.lucko.luckperms.common.model.User;
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
public class ExpireTemporaryTask implements Runnable {
|
||||
private final LuckPermsPlugin plugin;
|
||||
|
||||
@ -43,9 +40,6 @@ public class ExpireTemporaryTask implements Runnable {
|
||||
public void run() {
|
||||
boolean groupChanges = false;
|
||||
for (Group group : this.plugin.getGroupManager().getAll().values()) {
|
||||
if (shouldSkip(group)) {
|
||||
continue;
|
||||
}
|
||||
if (group.auditTemporaryNodes()) {
|
||||
this.plugin.getStorage().saveGroup(group);
|
||||
groupChanges = true;
|
||||
@ -53,9 +47,6 @@ public class ExpireTemporaryTask implements Runnable {
|
||||
}
|
||||
|
||||
for (User user : this.plugin.getUserManager().getAll().values()) {
|
||||
if (shouldSkip(user)) {
|
||||
continue;
|
||||
}
|
||||
if (user.auditTemporaryNodes()) {
|
||||
this.plugin.getStorage().saveUser(user);
|
||||
}
|
||||
@ -67,19 +58,4 @@ public class ExpireTemporaryTask implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
// return true if the holder's io lock is currently held, false otherwise
|
||||
private static boolean shouldSkip(PermissionHolder holder) {
|
||||
Lock lock = holder.getIoLock();
|
||||
|
||||
// if the holder is currently being manipulated by the storage impl,
|
||||
// don't attempt to audit temporary permissions
|
||||
if (!lock.tryLock()) {
|
||||
// if #tryLock returns false, it means it's held by something else
|
||||
return true;
|
||||
}
|
||||
|
||||
// immediately release the lock & return false
|
||||
lock.unlock();
|
||||
return false;
|
||||
}
|
||||
}
|
@ -77,11 +77,6 @@ public class SpongeGroupManager extends AbstractGroupManager<SpongeGroup> implem
|
||||
.build(s -> {
|
||||
SpongeGroup group = getIfLoaded(s);
|
||||
if (group != null) {
|
||||
// they're already loaded, but the data might not actually be there yet
|
||||
// if stuff is being loaded, then the user's i/o lock will be locked by the storage impl
|
||||
group.getIoLock().lock();
|
||||
group.getIoLock().unlock();
|
||||
|
||||
return group.sponge();
|
||||
}
|
||||
|
||||
|
@ -83,11 +83,6 @@ public class SpongeUserManager extends AbstractUserManager<SpongeUser> implement
|
||||
// check if the user instance is already loaded.
|
||||
SpongeUser user = getIfLoaded(u);
|
||||
if (user != null) {
|
||||
// they're already loaded, but the data might not actually be there yet
|
||||
// if stuff is being loaded, then the user's i/o lock will be locked by the storage impl
|
||||
user.getIoLock().lock();
|
||||
user.getIoLock().unlock();
|
||||
|
||||
return user.sponge();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user