Rewrite the way user instances are cleaned up and unloaded - towards #674

This commit is contained in:
Luck
2018-01-10 21:28:33 +00:00
Unverified
parent ea94bd8696
commit 3201d10bdd
36 changed files with 725 additions and 468 deletions
@@ -74,7 +74,7 @@ public class LuckPermsApiProvider implements LuckPermsApi {
this.plugin = plugin;
this.platformInfo = new ApiPlatformInfo(plugin);
this.userManager = new ApiUserManager(plugin, plugin.getUserManager());
this.userManager = new ApiUserManager(plugin.getUserManager());
this.groupManager = new ApiGroupManager(plugin.getGroupManager());
this.trackManager = new ApiTrackManager(plugin.getTrackManager());
this.actionLogger = new ApiActionLogger(plugin);
@@ -35,9 +35,9 @@ import java.util.stream.Collectors;
import javax.annotation.Nonnull;
public class ApiGroupManager implements GroupManager {
private final me.lucko.luckperms.common.managers.GroupManager handle;
private final me.lucko.luckperms.common.managers.group.GroupManager<?> handle;
public ApiGroupManager(me.lucko.luckperms.common.managers.GroupManager handle) {
public ApiGroupManager(me.lucko.luckperms.common.managers.group.GroupManager<?> handle) {
this.handle = handle;
}
@@ -35,9 +35,9 @@ import java.util.stream.Collectors;
import javax.annotation.Nonnull;
public class ApiTrackManager implements TrackManager {
private final me.lucko.luckperms.common.managers.TrackManager handle;
private final me.lucko.luckperms.common.managers.track.TrackManager<?> handle;
public ApiTrackManager(me.lucko.luckperms.common.managers.TrackManager handle) {
public ApiTrackManager(me.lucko.luckperms.common.managers.track.TrackManager<?> handle) {
this.handle = handle;
}
@@ -28,7 +28,6 @@ package me.lucko.luckperms.common.api.delegates.manager;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.api.manager.UserManager;
import me.lucko.luckperms.common.api.delegates.model.ApiUser;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.references.UserIdentifier;
import java.util.Objects;
@@ -39,11 +38,9 @@ import java.util.stream.Collectors;
import javax.annotation.Nonnull;
public class ApiUserManager implements UserManager {
private final LuckPermsPlugin plugin;
private final me.lucko.luckperms.common.managers.UserManager handle;
private final me.lucko.luckperms.common.managers.user.UserManager<?> handle;
public ApiUserManager(LuckPermsPlugin plugin, me.lucko.luckperms.common.managers.UserManager handle) {
this.plugin = plugin;
public ApiUserManager(me.lucko.luckperms.common.managers.user.UserManager<?> handle) {
this.handle = handle;
}
@@ -76,6 +73,6 @@ public class ApiUserManager implements UserManager {
@Override
public void cleanupUser(@Nonnull User user) {
Objects.requireNonNull(user, "user");
this.handle.scheduleUnload(this.plugin.getUuidCache().getExternalUUID(ApiUser.cast(user).getUuid()));
this.handle.getHouseKeeper().clearApiUsage(ApiUser.cast(user).getUuid());
}
}
@@ -99,6 +99,11 @@ public class ApiStorage implements Storage {
@Override
public CompletableFuture<Boolean> loadUser(@Nonnull UUID uuid, String username) {
Objects.requireNonNull(uuid, "uuid");
if (this.plugin.getUserManager().getIfLoaded(uuid) == null) {
this.plugin.getUserManager().getHouseKeeper().registerApiUsage(uuid);
}
return this.handle.noBuffer().loadUser(uuid, username == null ? null : checkUsername(username)).thenApply(Objects::nonNull);
}
@@ -40,9 +40,10 @@ import javax.annotation.Nonnull;
* An abstract manager class
*
* @param <I> the class used to identify each object held in this manager
* @param <T> the class this manager is "managing"
* @param <C> the super class being managed
* @param <T> the implementation class this manager is "managing"
*/
public abstract class AbstractManager<I, T extends Identifiable<I>> implements Manager<I, T> {
public abstract class AbstractManager<I, C extends Identifiable<I>, T extends C> implements Manager<I, C, T> {
private final LoadingCache<I, T> objects = Caffeine.newBuilder()
.build(new CacheLoader<I, T>() {
@@ -85,9 +86,9 @@ public abstract class AbstractManager<I, T extends Identifiable<I>> implements M
}
@Override
public void unload(T t) {
if (t != null) {
unload(t.getId());
public void unload(C object) {
if (object != null) {
unload(object.getId());
}
}
@@ -34,16 +34,17 @@ import java.util.function.Function;
* A class which manages instances of a class
*
* @param <I> the class used to identify each object held in this manager
* @param <T> the class this manager is "managing"
* @param <C> the super class being managed
* @param <T> the implementation class this manager is "managing"
*/
public interface Manager<I, T extends Identifiable<I>> extends Function<I, T> {
public interface Manager<I, C extends Identifiable<I>, T extends C> extends Function<I, T> {
/**
* Gets a map containing all cached instances held by this manager.
*
* @return all instances held in this manager
*/
Map<I, ? extends T> getAll();
Map<I, T> getAll();
/**
* Gets or creates an object by id
@@ -81,9 +82,9 @@ public interface Manager<I, T extends Identifiable<I>> extends Function<I, T> {
/**
* Removes and unloads the object from the manager
*
* @param t The object to unload
* @param object The object to unload
*/
void unload(T t);
void unload(C object);
/**
* Unloads all objects from the manager
@@ -23,40 +23,30 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.managers;
package me.lucko.luckperms.common.managers.group;
import me.lucko.luckperms.common.managers.AbstractManager;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
public class GenericGroupManager extends AbstractManager<String, Group> implements GroupManager {
private final LuckPermsPlugin plugin;
public GenericGroupManager(LuckPermsPlugin plugin) {
this.plugin = plugin;
}
public abstract class AbstractGroupManager<T extends Group> extends AbstractManager<String, Group, T> implements GroupManager<T> {
@Override
public Group apply(String name) {
return new Group(name, this.plugin);
}
@Override
public Group getByDisplayName(String name) {
public T getByDisplayName(String name) {
// try to get an exact match first
Group g = getIfLoaded(name);
T g = getIfLoaded(name);
if (g != null) {
return g;
}
// then try exact display name matches
for (Group group : getAll().values()) {
for (T group : getAll().values()) {
if (group.getDisplayName().isPresent() && group.getDisplayName().get().equals(name)) {
return group;
}
}
// then try case insensitive name matches
for (Group group : getAll().values()) {
for (T group : getAll().values()) {
if (group.getDisplayName().isPresent() && group.getDisplayName().get().equalsIgnoreCase(name)) {
return group;
}
@@ -23,11 +23,12 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.managers;
package me.lucko.luckperms.common.managers.group;
import me.lucko.luckperms.common.managers.Manager;
import me.lucko.luckperms.common.model.Group;
public interface GroupManager extends Manager<String, Group> {
public interface GroupManager<T extends Group> extends Manager<String, Group, T> {
/**
* Get a group object by display name
@@ -35,6 +36,6 @@ public interface GroupManager extends Manager<String, Group> {
* @param name The name to search by
* @return a {@link Group} object if the group is loaded, returns null if the group is not loaded
*/
Group getByDisplayName(String name);
T getByDisplayName(String name);
}
@@ -0,0 +1,42 @@
/*
* 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.managers.group;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
public class StandardGroupManager extends AbstractGroupManager<Group> {
private final LuckPermsPlugin plugin;
public StandardGroupManager(LuckPermsPlugin plugin) {
this.plugin = plugin;
}
@Override
public Group apply(String name) {
return new Group(name, this.plugin);
}
}
@@ -0,0 +1,37 @@
/*
* 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.managers.track;
import me.lucko.luckperms.common.managers.AbstractManager;
import me.lucko.luckperms.common.model.Track;
public abstract class AbstractTrackManager<T extends Track> extends AbstractManager<String, Track, T> implements TrackManager<T> {
@Override
protected String sanitizeIdentifier(String s) {
return s.toLowerCase();
}
}
@@ -23,15 +23,15 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.managers;
package me.lucko.luckperms.common.managers.track;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
public class GenericTrackManager extends AbstractManager<String, Track> implements TrackManager {
public class StandardTrackManager extends AbstractTrackManager<Track> {
private final LuckPermsPlugin plugin;
public GenericTrackManager(LuckPermsPlugin plugin) {
public StandardTrackManager(LuckPermsPlugin plugin) {
this.plugin = plugin;
}
@@ -39,9 +39,4 @@ public class GenericTrackManager extends AbstractManager<String, Track> implemen
public Track apply(String name) {
return new Track(name, this.plugin);
}
@Override
protected String sanitizeIdentifier(String s) {
return s.toLowerCase();
}
}
@@ -23,10 +23,11 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.managers;
package me.lucko.luckperms.common.managers.track;
import me.lucko.luckperms.common.managers.Manager;
import me.lucko.luckperms.common.model.Track;
public interface TrackManager extends Manager<String, Track> {
public interface TrackManager<T extends Track> extends Manager<String, Track, T> {
}
@@ -23,11 +23,12 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.managers;
package me.lucko.luckperms.common.managers.user;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.managers.AbstractManager;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.NodeFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -37,17 +38,20 @@ import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class GenericUserManager extends AbstractManager<UserIdentifier, User> implements UserManager {
public abstract class AbstractUserManager<T extends User> extends AbstractManager<UserIdentifier, User, T> implements UserManager<T> {
private final LuckPermsPlugin plugin;
private final UserHousekeeper housekeeper;
public GenericUserManager(LuckPermsPlugin plugin) {
public AbstractUserManager(LuckPermsPlugin plugin, UserHousekeeper.TimeoutSettings timeoutSettings) {
this.plugin = plugin;
this.housekeeper = new UserHousekeeper(plugin, this, timeoutSettings);
this.plugin.getScheduler().asyncRepeating(this.housekeeper, 200L); // every 10 seconds
}
@Override
public User getOrMake(UserIdentifier id) {
User ret = super.getOrMake(id);
public T getOrMake(UserIdentifier id) {
T ret = super.getOrMake(id);
if (id.getUsername().isPresent()) {
ret.setName(id.getUsername().get(), false);
}
@@ -55,15 +59,8 @@ public class GenericUserManager extends AbstractManager<UserIdentifier, User> im
}
@Override
public User apply(UserIdentifier id) {
return !id.getUsername().isPresent() ?
new User(id.getUuid(), this.plugin) :
new User(id.getUuid(), id.getUsername().get(), this.plugin);
}
@Override
public User getByUsername(String name) {
for (User user : getAll().values()) {
public T getByUsername(String name) {
for (T user : getAll().values()) {
Optional<String> n = user.getName();
if (n.isPresent() && n.get().equalsIgnoreCase(name)) {
return user;
@@ -73,59 +70,16 @@ public class GenericUserManager extends AbstractManager<UserIdentifier, User> im
}
@Override
public User getIfLoaded(UUID uuid) {
public T getIfLoaded(UUID uuid) {
return getIfLoaded(UserIdentifier.of(uuid, null));
}
@Override
public boolean giveDefaultIfNeeded(User user, boolean save) {
return giveDefaultIfNeeded(user, save, this.plugin);
}
@Override
public boolean cleanup(User user) {
if (!this.plugin.isPlayerOnline(this.plugin.getUuidCache().getExternalUUID(user.getUuid()))) {
unload(user);
return true;
} else {
return false;
}
}
@Override
public void scheduleUnload(UUID uuid) {
this.plugin.getScheduler().asyncLater(() -> {
// check once to see if the user can be unloaded.
if (getIfLoaded(this.plugin.getUuidCache().getUUID(uuid)) != null && !this.plugin.isPlayerOnline(uuid)) {
// check again in 40 ticks, we want to be sure the player won't have re-logged before we unload them.
this.plugin.getScheduler().asyncLater(() -> {
User user = getIfLoaded(this.plugin.getUuidCache().getUUID(uuid));
if (user != null && !this.plugin.isPlayerOnline(uuid)) {
user.getCachedData().invalidateCaches();
unload(user);
this.plugin.getUuidCache().clearCache(uuid);
}
}, 40L);
}
}, 40L);
}
@Override
public CompletableFuture<Void> updateAllUsers() {
return CompletableFuture.runAsync(
() -> this.plugin.getOnlinePlayers()
.map(u -> this.plugin.getUuidCache().getUUID(u))
.forEach(u -> this.plugin.getStorage().loadUser(u, null).join()),
this.plugin.getScheduler().async()
);
}
public static boolean giveDefaultIfNeeded(User user, boolean save, LuckPermsPlugin plugin) {
boolean work = false;
// check that they are actually a member of their primary group, otherwise remove it
if (plugin.getConfiguration().get(ConfigKeys.PRIMARY_GROUP_CALCULATION_METHOD).equals("stored")) {
if (this.plugin.getConfiguration().get(ConfigKeys.PRIMARY_GROUP_CALCULATION_METHOD).equals("stored")) {
String pg = user.getPrimaryGroup().getValue();
boolean has = false;
@@ -174,19 +128,40 @@ public class GenericUserManager extends AbstractManager<UserIdentifier, User> im
}
if (work && save) {
plugin.getStorage().saveUser(user);
this.plugin.getStorage().saveUser(user);
}
return work;
}
@Override
public UserHousekeeper getHouseKeeper() {
return this.housekeeper;
}
@Override
public void cleanup(User user) {
this.housekeeper.cleanup(user.getId());
}
@Override
public CompletableFuture<Void> updateAllUsers() {
return CompletableFuture.runAsync(
() -> this.plugin.getOnlinePlayers()
.map(u -> this.plugin.getUuidCache().getUUID(u))
.forEach(u -> this.plugin.getStorage().loadUser(u, null).join()),
this.plugin.getScheduler().async()
);
}
/**
* 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
*/
public static boolean shouldSave(User user) {
@Override
public boolean shouldSave(User user) {
if (user.getEnduringNodes().size() != 1) {
return true;
}
@@ -0,0 +1,48 @@
/*
* 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.managers.user;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.references.UserIdentifier;
import java.util.concurrent.TimeUnit;
public class StandardUserManager extends AbstractUserManager<User> {
private final LuckPermsPlugin plugin;
public StandardUserManager(LuckPermsPlugin plugin) {
super(plugin, UserHousekeeper.timeoutSettings(1, TimeUnit.MINUTES));
this.plugin = plugin;
}
@Override
public User apply(UserIdentifier id) {
return !id.getUsername().isPresent() ?
new User(id.getUuid(), this.plugin) :
new User(id.getUuid(), id.getUsername().get(), this.plugin);
}
}
@@ -0,0 +1,100 @@
/*
* 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.managers.user;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.references.UserIdentifier;
import me.lucko.luckperms.common.utils.ExpiringSet;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* The instance responsible for unloading users which are no longer needed.
*/
public class UserHousekeeper implements Runnable {
private final LuckPermsPlugin plugin;
private final UserManager<?> userManager;
// contains the uuids of users who have recently logged in / out
private final ExpiringSet<UUID> recentlyUsed;
// contains the uuids of users who have recently been retrieved from the API
private final ExpiringSet<UUID> recentlyUsedApi;
public UserHousekeeper(LuckPermsPlugin plugin, UserManager<?> userManager, TimeoutSettings timeoutSettings) {
this.plugin = plugin;
this.userManager = userManager;
this.recentlyUsed = new ExpiringSet<>(timeoutSettings.duration, timeoutSettings.unit);
this.recentlyUsedApi = new ExpiringSet<>(5, TimeUnit.MINUTES);
}
// called when a player attempts a connection or logs out
public void registerUsage(UUID uuid) {
this.recentlyUsed.add(uuid);
}
public void registerApiUsage(UUID uuid) {
this.recentlyUsedApi.add(uuid);
}
public void clearApiUsage(UUID uuid) {
this.recentlyUsedApi.remove(uuid);
}
@Override
public void run() {
for (UserIdentifier entry : this.userManager.getAll().keySet()) {
cleanup(entry);
}
}
public void cleanup(UserIdentifier identifier) {
UUID uuid = identifier.getUuid();
// unload users which aren't online and who haven't been online (or tried to login) recently
if (this.recentlyUsed.contains(uuid) || this.recentlyUsedApi.contains(uuid) || this.plugin.isPlayerOnline(this.plugin.getUuidCache().getExternalUUID(uuid))) {
return;
}
// unload them
this.userManager.unload(identifier);
}
public static TimeoutSettings timeoutSettings(long duration, TimeUnit unit) {
return new TimeoutSettings(duration, unit);
}
public static final class TimeoutSettings {
private final long duration;
private final TimeUnit unit;
private TimeoutSettings(long duration, TimeUnit unit) {
this.duration = duration;
this.unit = unit;
}
}
}
@@ -23,16 +23,16 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.managers;
package me.lucko.luckperms.common.managers.user;
import me.lucko.luckperms.common.managers.Manager;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.references.Identifiable;
import me.lucko.luckperms.common.references.UserIdentifier;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public interface UserManager extends Manager<UserIdentifier, User> {
public interface UserManager<T extends User> extends Manager<UserIdentifier, User, T> {
/**
* Get a user object by name
@@ -40,7 +40,7 @@ public interface UserManager extends Manager<UserIdentifier, User> {
* @param name The name to search by
* @return a {@link User} object if the user is loaded, returns null if the user is not loaded
*/
User getByUsername(String name);
T getByUsername(String name);
/**
* Get a user object by uuid
@@ -48,7 +48,7 @@ public interface UserManager extends Manager<UserIdentifier, User> {
* @param uuid The uuid to search by
* @return a {@link User} object if the user is loaded, returns null if the user is not loaded
*/
User getIfLoaded(UUID uuid);
T getIfLoaded(UUID uuid);
/**
* Gives the user the default group if necessary.
@@ -58,18 +58,26 @@ public interface UserManager extends Manager<UserIdentifier, User> {
boolean giveDefaultIfNeeded(User user, boolean save);
/**
* Checks to see if the user is online, and if they are not, runs {@link #unload(Identifiable)}
* Check whether the user's state indicates that they should be persisted to storage.
*
* @param user The user to be cleaned up
* @param user the user to check
* @return true if the user should be saved
*/
boolean cleanup(User user);
boolean shouldSave(User user);
/**
* Schedules a task to cleanup a user after a certain period of time, if they're not on the server anymore.
* Gets the instance responsible for unloading unneeded users.
*
* @param uuid external uuid of the player
* @return the housekeeper
*/
void scheduleUnload(UUID uuid);
UserHousekeeper getHouseKeeper();
/**
* Unloads the user if a corresponding player is not online
*
* @param user the user
*/
void cleanup(User user);
/**
* Reloads the data of all online users
@@ -42,9 +42,9 @@ import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.logging.Logger;
import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.UserManager;
import me.lucko.luckperms.common.managers.group.GroupManager;
import me.lucko.luckperms.common.managers.track.TrackManager;
import me.lucko.luckperms.common.managers.user.UserManager;
import me.lucko.luckperms.common.messaging.ExtendedMessagingService;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.storage.Storage;
@@ -75,21 +75,21 @@ public interface LuckPermsPlugin {
*
* @return the user manager
*/
UserManager getUserManager();
UserManager<?> getUserManager();
/**
* Gets the group manager instance for the platform
*
* @return the group manager
*/
GroupManager getGroupManager();
GroupManager<?> getGroupManager();
/**
* Gets the track manager instance for the platform
*
* @return the track manager
*/
TrackManager getTrackManager();
TrackManager<?> getTrackManager();
/**
* Gets the plugin's configuration
@@ -65,28 +65,28 @@ public interface SchedulerAdapter {
* @param runnable the runnable
* @param intervalTicks the interval in ticks.
*/
void asyncRepeating(Runnable runnable, long intervalTicks);
SchedulerTask asyncRepeating(Runnable runnable, long intervalTicks);
/**
* Runs a runnable repeatedly until the plugin disables. Will wait for the interval before the first iteration of the task is ran.
* @param runnable the runnable
* @param intervalTicks the interval in ticks.
*/
void syncRepeating(Runnable runnable, long intervalTicks);
SchedulerTask syncRepeating(Runnable runnable, long intervalTicks);
/**
* Runs a runnable with a delay
* @param runnable the runnable
* @param delayTicks the delay in ticks
*/
void asyncLater(Runnable runnable, long delayTicks);
SchedulerTask asyncLater(Runnable runnable, long delayTicks);
/**
* Runs a runnable with a delay
* @param runnable the runnable
* @param delayTicks the delay in ticks
*/
void syncLater(Runnable runnable, long delayTicks);
SchedulerTask syncLater(Runnable runnable, long delayTicks);
/**
* Shuts down this executor instance
@@ -0,0 +1,38 @@
/*
* 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.plugin;
/**
* Represents a scheduled task
*/
public interface SchedulerTask {
/**
* Cancels the task.
*/
void cancel();
}
@@ -37,9 +37,8 @@ import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.commands.CommandManager;
import me.lucko.luckperms.common.commands.utils.CommandUtils;
import me.lucko.luckperms.common.contexts.ContextSetConfigurateSerializer;
import me.lucko.luckperms.common.managers.GenericUserManager;
import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.group.GroupManager;
import me.lucko.luckperms.common.managers.track.TrackManager;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
@@ -400,7 +399,7 @@ public abstract class ConfigurateDao extends AbstractDao {
saveUser(user);
}
} else {
if (GenericUserManager.shouldSave(user)) {
if (this.plugin.getUserManager().shouldSave(user)) {
user.clearNodes();
user.getPrimaryGroup().setStoredValue(null);
this.plugin.getUserManager().giveDefaultIfNeeded(user, false);
@@ -419,7 +418,7 @@ public abstract class ConfigurateDao extends AbstractDao {
public void saveUser(User user) throws Exception {
user.getIoLock().lock();
try {
if (!GenericUserManager.shouldSave(user)) {
if (!this.plugin.getUserManager().shouldSave(user)) {
saveFile(StorageLocation.USER, user.getUuid().toString(), null);
} else {
ConfigurationNode data = SimpleConfigurationNode.root();
@@ -562,7 +561,7 @@ public abstract class ConfigurateDao extends AbstractDao {
throw new RuntimeException("Exception occurred whilst loading a group");
}
GroupManager gm = this.plugin.getGroupManager();
GroupManager<?> gm = this.plugin.getGroupManager();
gm.getAll().values().stream()
.filter(g -> !groups.contains(g.getName()))
.forEach(gm::unload);
@@ -718,7 +717,7 @@ public abstract class ConfigurateDao extends AbstractDao {
throw new RuntimeException("Exception occurred whilst loading a track");
}
TrackManager tm = this.plugin.getTrackManager();
TrackManager<?> tm = this.plugin.getTrackManager();
tm.getAll().values().stream()
.filter(t -> !tracks.contains(t.getName()))
.forEach(tm::unload);
@@ -44,9 +44,8 @@ import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.managers.GenericUserManager;
import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.group.GroupManager;
import me.lucko.luckperms.common.managers.track.TrackManager;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
@@ -268,7 +267,7 @@ public class MongoDao extends AbstractDao {
c.replaceOne(new Document("_id", user.getUuid()), userToDoc(user));
}
} else {
if (GenericUserManager.shouldSave(user)) {
if (this.plugin.getUserManager().shouldSave(user)) {
user.clearNodes();
user.getPrimaryGroup().setStoredValue(null);
this.plugin.getUserManager().giveDefaultIfNeeded(user, false);
@@ -287,7 +286,7 @@ public class MongoDao extends AbstractDao {
user.getIoLock().lock();
try {
MongoCollection<Document> c = this.database.getCollection(this.prefix + "users");
if (!GenericUserManager.shouldSave(user)) {
if (!this.plugin.getUserManager().shouldSave(user)) {
c.deleteOne(new Document("_id", user.getUuid()));
} else {
c.replaceOne(new Document("_id", user.getUuid()), userToDoc(user), new UpdateOptions().upsert(true));
@@ -409,7 +408,7 @@ public class MongoDao extends AbstractDao {
throw new RuntimeException("Exception occurred whilst loading a group");
}
GroupManager gm = this.plugin.getGroupManager();
GroupManager<?> gm = this.plugin.getGroupManager();
gm.getAll().values().stream()
.filter(g -> !groups.contains(g.getName()))
.forEach(gm::unload);
@@ -535,7 +534,7 @@ public class MongoDao extends AbstractDao {
throw new RuntimeException("Exception occurred whilst loading a track");
}
TrackManager tm = this.plugin.getTrackManager();
TrackManager<?> tm = this.plugin.getTrackManager();
tm.getAll().values().stream()
.filter(t -> !tracks.contains(t.getName()))
.forEach(tm::unload);
@@ -37,9 +37,8 @@ import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.contexts.ContextSetJsonSerializer;
import me.lucko.luckperms.common.managers.GenericUserManager;
import me.lucko.luckperms.common.managers.GroupManager;
import me.lucko.luckperms.common.managers.TrackManager;
import me.lucko.luckperms.common.managers.group.GroupManager;
import me.lucko.luckperms.common.managers.track.TrackManager;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
@@ -364,7 +363,7 @@ public class SqlDao extends AbstractDao {
} else {
// User has no data in storage.
if (GenericUserManager.shouldSave(user)) {
if (this.plugin.getUserManager().shouldSave(user)) {
user.clearNodes();
user.getPrimaryGroup().setStoredValue(null);
this.plugin.getUserManager().giveDefaultIfNeeded(user, false);
@@ -382,7 +381,7 @@ public class SqlDao extends AbstractDao {
user.getIoLock().lock();
try {
// Empty data - just delete from the DB.
if (!GenericUserManager.shouldSave(user)) {
if (!this.plugin.getUserManager().shouldSave(user)) {
try (Connection c = this.provider.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.prefix.apply(USER_PERMISSIONS_DELETE))) {
ps.setString(1, user.getUuid().toString());
@@ -643,7 +642,7 @@ public class SqlDao extends AbstractDao {
throw new RuntimeException("Exception occurred whilst loading a group");
}
GroupManager gm = this.plugin.getGroupManager();
GroupManager<?> gm = this.plugin.getGroupManager();
gm.getAll().values().stream()
.filter(g -> !groups.contains(g.getName()))
.forEach(gm::unload);
@@ -880,7 +879,7 @@ public class SqlDao extends AbstractDao {
throw new RuntimeException("Exception occurred whilst loading a track");
}
TrackManager tm = this.plugin.getTrackManager();
TrackManager<?> tm = this.plugin.getTrackManager();
tm.getAll().values().stream()
.filter(t -> !tracks.contains(t.getName()))
.forEach(tm::unload);
@@ -25,6 +25,7 @@
package me.lucko.luckperms.common.utils;
import me.lucko.luckperms.api.platform.PlatformType;
import me.lucko.luckperms.common.assignments.AssignmentRule;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.User;
@@ -34,47 +35,61 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* Utilities for use in platform listeners
* Abstract listener utility for handling new player connections
*/
public final class LoginHelper {
public abstract class AbstractLoginListener {
private final LuckPermsPlugin plugin;
public static User loadUser(LuckPermsPlugin plugin, UUID u, String username, boolean joinUuidSave) {
// if we should #join the uuid save future.
// this is only really necessary on BungeeCord, as the data may be needed
// on the backend, depending on uuid config options
private final boolean joinUuidSave;
protected AbstractLoginListener(LuckPermsPlugin plugin) {
this.plugin = plugin;
this.joinUuidSave = plugin.getServerType() == PlatformType.BUNGEE;
}
public User loadUser(UUID u, String username) {
final long startTime = System.currentTimeMillis();
final UuidCache cache = plugin.getUuidCache();
if (!plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUIDS)) {
UUID uuid = plugin.getStorage().noBuffer().getUUID(username).join();
// register with the housekeeper to avoid accidental unloads
this.plugin.getUserManager().getHouseKeeper().registerUsage(u);
final UuidCache cache = this.plugin.getUuidCache();
if (!this.plugin.getConfiguration().get(ConfigKeys.USE_SERVER_UUIDS)) {
UUID uuid = this.plugin.getStorage().noBuffer().getUUID(username).join();
if (uuid != null) {
cache.addToCache(u, uuid);
} else {
// No previous data for this player
plugin.getEventFactory().handleUserFirstLogin(u, username);
this.plugin.getEventFactory().handleUserFirstLogin(u, username);
cache.addToCache(u, u);
CompletableFuture<Void> future = plugin.getStorage().noBuffer().saveUUIDData(u, username);
if (joinUuidSave) {
CompletableFuture<Void> future = this.plugin.getStorage().noBuffer().saveUUIDData(u, username);
if (this.joinUuidSave) {
future.join();
}
}
} else {
String name = plugin.getStorage().noBuffer().getName(u).join();
String name = this.plugin.getStorage().noBuffer().getName(u).join();
if (name == null) {
plugin.getEventFactory().handleUserFirstLogin(u, username);
this.plugin.getEventFactory().handleUserFirstLogin(u, username);
}
// Online mode, no cache needed. This is just for name -> uuid lookup.
CompletableFuture<Void> future = plugin.getStorage().noBuffer().saveUUIDData(u, username);
if (joinUuidSave) {
CompletableFuture<Void> future = this.plugin.getStorage().noBuffer().saveUUIDData(u, username);
if (this.joinUuidSave) {
future.join();
}
}
User user = plugin.getStorage().noBuffer().loadUser(cache.getUUID(u), username).join();
User user = this.plugin.getStorage().noBuffer().loadUser(cache.getUUID(u), username).join();
if (user == null) {
throw new NullPointerException("User is null");
} else {
// Setup defaults for the user
boolean save = false;
for (AssignmentRule rule : plugin.getConfiguration().get(ConfigKeys.DEFAULT_ASSIGNMENTS)) {
for (AssignmentRule rule : this.plugin.getConfiguration().get(ConfigKeys.DEFAULT_ASSIGNMENTS)) {
if (rule.apply(user)) {
save = true;
}
@@ -82,7 +97,7 @@ public final class LoginHelper {
// If they were given a default, persist the new assignments back to the storage.
if (save) {
plugin.getStorage().noBuffer().saveUser(user).join();
this.plugin.getStorage().noBuffer().saveUser(user).join();
}
// Does some minimum pre-calculations to (maybe) speed things up later.
@@ -91,12 +106,10 @@ public final class LoginHelper {
final long time = System.currentTimeMillis() - startTime;
if (time >= 1000) {
plugin.getLog().warn("Processing login for " + username + " took " + time + "ms.");
this.plugin.getLog().warn("Processing login for " + username + " took " + time + "ms.");
}
return user;
}
private LoginHelper() {}
}
@@ -0,0 +1,90 @@
/*
* 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.utils;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ForwardingSet;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
/**
* A bad expiring set implementation using Caffeine caches
*
* @param <E> element type
*/
public class ExpiringSet<E> extends ForwardingSet<E> {
private final LoadingCache<E, Boolean> cache;
private final Set<E> setView;
public ExpiringSet(long duration, TimeUnit unit) {
this.cache = Caffeine.newBuilder().expireAfterAccess(duration, unit).build(key -> Boolean.TRUE);
this.setView = this.cache.asMap().keySet();
}
@Override
public boolean add(E element) {
this.cache.get(element); // simply requesting the element from the cache is sufficient.
// we don't care about the return value
return true;
}
@Override
public boolean addAll(@Nonnull Collection<? extends E> collection) {
for (E element : collection) {
add(element);
}
// we don't care about the return value
return true;
}
@Override
public boolean remove(Object key) {
this.cache.invalidate(key);
// we don't care about the return value
return true;
}
@Override
public boolean removeAll(@Nonnull Collection<?> keys) {
this.cache.invalidateAll(keys);
// we don't care about the return value
return true;
}
@Override
protected Set<E> delegate() {
return this.setView;
}
}