From 3201d10bdddb84c0b4c5487944a1d70831cbb178 Mon Sep 17 00:00:00 2001 From: Luck Date: Wed, 10 Jan 2018 21:28:33 +0000 Subject: [PATCH] Rewrite the way user instances are cleaned up and unloaded - towards #674 --- .../bukkit/BukkitSchedulerAdapter.java | 42 ++++-- .../luckperms/bukkit/LPBukkitPlugin.java | 33 +++-- .../listeners/BukkitConnectionListener.java | 20 +-- .../bungee/BungeeSchedulerAdapter.java | 57 ++++++-- .../luckperms/bungee/LPBungeePlugin.java | 27 ++-- .../listeners/BungeeConnectionListener.java | 16 +-- .../common/api/LuckPermsApiProvider.java | 2 +- .../delegates/manager/ApiGroupManager.java | 4 +- .../delegates/manager/ApiTrackManager.java | 4 +- .../api/delegates/manager/ApiUserManager.java | 9 +- .../api/delegates/model/ApiStorage.java | 5 + .../common/managers/AbstractManager.java | 11 +- .../luckperms/common/managers/Manager.java | 11 +- .../AbstractGroupManager.java} | 24 +--- .../managers/{ => group}/GroupManager.java | 7 +- .../managers/group/StandardGroupManager.java | 42 ++++++ .../managers/track/AbstractTrackManager.java | 37 ++++++ .../StandardTrackManager.java} | 11 +- .../managers/{ => track}/TrackManager.java | 5 +- .../AbstractUserManager.java} | 97 +++++--------- .../managers/user/StandardUserManager.java | 48 +++++++ .../common/managers/user/UserHousekeeper.java | 100 ++++++++++++++ .../managers/{ => user}/UserManager.java | 30 +++-- .../common/plugin/LuckPermsPlugin.java | 12 +- .../common/plugin/SchedulerAdapter.java | 8 +- .../common/plugin/SchedulerTask.java | 38 ++++++ .../storage/dao/file/ConfigurateDao.java | 13 +- .../common/storage/dao/mongodb/MongoDao.java | 13 +- .../common/storage/dao/sql/SqlDao.java | 13 +- ...Helper.java => AbstractLoginListener.java} | 51 ++++--- .../luckperms/common/utils/ExpiringSet.java | 90 +++++++++++++ .../luckperms/sponge/LPSpongePlugin.java | 9 +- .../sponge/SpongeSchedulerAdapter.java | 74 +++++++++-- .../listeners/SpongeConnectionListener.java | 11 +- .../sponge/managers/SpongeGroupManager.java | 94 ++----------- .../sponge/managers/SpongeUserManager.java | 125 ++---------------- 36 files changed, 725 insertions(+), 468 deletions(-) rename common/src/main/java/me/lucko/luckperms/common/managers/{GenericGroupManager.java => group/AbstractGroupManager.java} (75%) rename common/src/main/java/me/lucko/luckperms/common/managers/{ => group}/GroupManager.java (87%) create mode 100644 common/src/main/java/me/lucko/luckperms/common/managers/group/StandardGroupManager.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/managers/track/AbstractTrackManager.java rename common/src/main/java/me/lucko/luckperms/common/managers/{GenericTrackManager.java => track/StandardTrackManager.java} (83%) rename common/src/main/java/me/lucko/luckperms/common/managers/{ => track}/TrackManager.java (87%) rename common/src/main/java/me/lucko/luckperms/common/managers/{GenericUserManager.java => user/AbstractUserManager.java} (72%) create mode 100644 common/src/main/java/me/lucko/luckperms/common/managers/user/StandardUserManager.java create mode 100644 common/src/main/java/me/lucko/luckperms/common/managers/user/UserHousekeeper.java rename common/src/main/java/me/lucko/luckperms/common/managers/{ => user}/UserManager.java (74%) create mode 100644 common/src/main/java/me/lucko/luckperms/common/plugin/SchedulerTask.java rename common/src/main/java/me/lucko/luckperms/common/utils/{LoginHelper.java => AbstractLoginListener.java} (58%) create mode 100644 common/src/main/java/me/lucko/luckperms/common/utils/ExpiringSet.java diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/BukkitSchedulerAdapter.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/BukkitSchedulerAdapter.java index cd11ef5f..7e7985f7 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/BukkitSchedulerAdapter.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/BukkitSchedulerAdapter.java @@ -28,7 +28,10 @@ package me.lucko.luckperms.bukkit; import com.google.common.util.concurrent.ThreadFactoryBuilder; import me.lucko.luckperms.common.plugin.SchedulerAdapter; +import me.lucko.luckperms.common.plugin.SchedulerTask; +import me.lucko.luckperms.common.utils.SafeIterator; +import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; import java.util.Set; @@ -51,7 +54,7 @@ public class BukkitSchedulerAdapter implements SchedulerAdapter { private boolean useFallback = true; - private final Set tasks = ConcurrentHashMap.newKeySet(); + private final Set tasks = ConcurrentHashMap.newKeySet(); public BukkitSchedulerAdapter(LPBukkitPlugin plugin) { this.plugin = plugin; @@ -62,6 +65,10 @@ public class BukkitSchedulerAdapter implements SchedulerAdapter { this.async = new AsyncExecutor(); } + private BukkitScheduler scheduler() { + return this.plugin.getServer().getScheduler(); + } + @Override public void doAsync(Runnable runnable) { async().execute(runnable); @@ -73,30 +80,32 @@ public class BukkitSchedulerAdapter implements SchedulerAdapter { } @Override - public void asyncRepeating(Runnable runnable, long intervalTicks) { - BukkitTask task = this.plugin.getServer().getScheduler().runTaskTimerAsynchronously(this.plugin, runnable, intervalTicks, intervalTicks); + public SchedulerTask asyncRepeating(Runnable runnable, long intervalTicks) { + SchedulerTask task = new BukkitSchedulerTask(scheduler().runTaskTimerAsynchronously(this.plugin, runnable, intervalTicks, intervalTicks)); this.tasks.add(task); + return task; } @Override - public void syncRepeating(Runnable runnable, long intervalTicks) { - BukkitTask task = this.plugin.getServer().getScheduler().runTaskTimer(this.plugin, runnable, intervalTicks, intervalTicks); + public SchedulerTask syncRepeating(Runnable runnable, long intervalTicks) { + SchedulerTask task = new BukkitSchedulerTask(scheduler().runTaskTimer(this.plugin, runnable, intervalTicks, intervalTicks)); this.tasks.add(task); + return task; } @Override - public void asyncLater(Runnable runnable, long delayTicks) { - this.plugin.getServer().getScheduler().runTaskLaterAsynchronously(this.plugin, runnable, delayTicks); + public SchedulerTask asyncLater(Runnable runnable, long delayTicks) { + return new BukkitSchedulerTask(scheduler().runTaskLaterAsynchronously(this.plugin, runnable, delayTicks)); } @Override - public void syncLater(Runnable runnable, long delayTicks) { - this.plugin.getServer().getScheduler().scheduleSyncDelayedTask(this.plugin, runnable, delayTicks); + public SchedulerTask syncLater(Runnable runnable, long delayTicks) { + return new BukkitSchedulerTask(scheduler().runTaskLater(this.plugin, runnable, delayTicks)); } @Override public void shutdown() { - this.tasks.forEach(BukkitTask::cancel); + SafeIterator.iterate(this.tasks, SchedulerTask::cancel); // wait for executor this.asyncFallback.shutdown(); @@ -161,4 +170,17 @@ public class BukkitSchedulerAdapter implements SchedulerAdapter { } } + private static final class BukkitSchedulerTask implements SchedulerTask { + private final BukkitTask task; + + private BukkitSchedulerTask(BukkitTask task) { + this.task = task; + } + + @Override + public void cancel() { + this.task.cancel(); + } + } + } diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java index dcd973ef..bd568674 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java @@ -63,12 +63,9 @@ import me.lucko.luckperms.common.locale.NoopLocaleManager; import me.lucko.luckperms.common.locale.SimpleLocaleManager; import me.lucko.luckperms.common.logging.Logger; import me.lucko.luckperms.common.logging.SenderLogger; -import me.lucko.luckperms.common.managers.GenericGroupManager; -import me.lucko.luckperms.common.managers.GenericTrackManager; -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.UserManager; +import me.lucko.luckperms.common.managers.group.StandardGroupManager; +import me.lucko.luckperms.common.managers.track.StandardTrackManager; +import me.lucko.luckperms.common.managers.user.StandardUserManager; import me.lucko.luckperms.common.messaging.ExtendedMessagingService; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; @@ -80,7 +77,6 @@ import me.lucko.luckperms.common.tasks.CacheHousekeepingTask; import me.lucko.luckperms.common.tasks.ExpireTemporaryTask; import me.lucko.luckperms.common.tasks.UpdateTask; import me.lucko.luckperms.common.treeview.PermissionVault; -import me.lucko.luckperms.common.utils.LoginHelper; import me.lucko.luckperms.common.utils.UuidCache; import me.lucko.luckperms.common.verbose.VerboseHandler; @@ -115,9 +111,9 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { private BukkitCommandExecutor commandManager; private VaultHookManager vaultHookManager = null; private LuckPermsConfiguration configuration; - private UserManager userManager; - private GroupManager groupManager; - private TrackManager trackManager; + private StandardUserManager userManager; + private StandardGroupManager groupManager; + private StandardTrackManager trackManager; private Storage storage; private FileWatcher fileWatcher = null; private ExtendedMessagingService messagingService = null; @@ -205,7 +201,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { this.scheduler.syncLater(new BukkitProcessorsSetupTask(this), 1L); // register events - getServer().getPluginManager().registerEvents(new BukkitConnectionListener(this), this); + BukkitConnectionListener connectionListener = new BukkitConnectionListener(this); + getServer().getPluginManager().registerEvents(connectionListener, this); getServer().getPluginManager().registerEvents(new BukkitPlatformListener(this), this); if (getConfiguration().get(ConfigKeys.WATCH_FILES)) { @@ -237,9 +234,9 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { // load internal managers getLog().info("Loading internal permission managers..."); this.uuidCache = new UuidCache(this); - this.userManager = new GenericUserManager(this); - this.groupManager = new GenericGroupManager(this); - this.trackManager = new GenericTrackManager(this); + this.userManager = new StandardUserManager(this); + this.groupManager = new StandardGroupManager(this); + this.trackManager = new StandardTrackManager(this); this.calculatorFactory = new BukkitCalculatorFactory(this); this.cachedStateManager = new CachedStateManager(); @@ -312,7 +309,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { for (Player player : getServer().getOnlinePlayers()) { this.scheduler.doAsync(() -> { try { - LoginHelper.loadUser(this, player.getUniqueId(), player.getName(), false); + connectionListener.loadUser(player.getUniqueId(), player.getName()); User user = getUserManager().getIfLoaded(getUuidCache().getUUID(player.getUniqueId())); if (user != null) { this.scheduler.doSync(() -> { @@ -584,17 +581,17 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { } @Override - public UserManager getUserManager() { + public StandardUserManager getUserManager() { return this.userManager; } @Override - public GroupManager getGroupManager() { + public StandardGroupManager getGroupManager() { return this.groupManager; } @Override - public TrackManager getTrackManager() { + public StandardTrackManager getTrackManager() { return this.trackManager; } diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/listeners/BukkitConnectionListener.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/listeners/BukkitConnectionListener.java index 8d26f023..b9cfecd9 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/listeners/BukkitConnectionListener.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/listeners/BukkitConnectionListener.java @@ -31,7 +31,7 @@ import me.lucko.luckperms.bukkit.model.PermissibleInjector; import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.utils.LoginHelper; +import me.lucko.luckperms.common.utils.AbstractLoginListener; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -47,13 +47,14 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; -public class BukkitConnectionListener implements Listener { +public class BukkitConnectionListener extends AbstractLoginListener implements Listener { private final LPBukkitPlugin plugin; private final Set deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>()); private final Set deniedLogin = Collections.synchronizedSet(new HashSet<>()); public BukkitConnectionListener(LPBukkitPlugin plugin) { + super(plugin); this.plugin = plugin; } @@ -86,7 +87,7 @@ public class BukkitConnectionListener implements Listener { - creating a user instance in the UserManager for this connection. - setting up cached data. */ try { - User user = LoginHelper.loadUser(this.plugin, e.getUniqueId(), e.getName(), false); + User user = loadUser(e.getUniqueId(), e.getName()); this.plugin.getEventFactory().handleUserLoginProcess(e.getUniqueId(), e.getName(), user); } catch (Exception ex) { this.plugin.getLog().severe("Exception occured whilst loading data for " + e.getUniqueId() + " - " + e.getName()); @@ -115,12 +116,6 @@ public class BukkitConnectionListener implements Listener { return; } - - // Login event was cancelled by another plugin, but it wasn't cancelled when we handled it at LOW - if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { - // Schedule cleanup of this user. - this.plugin.getUserManager().scheduleUnload(e.getUniqueId()); - } } @EventHandler(priority = EventPriority.LOWEST) @@ -180,8 +175,6 @@ public class BukkitConnectionListener implements Listener { // Login event was cancelled by another plugin since we first loaded their data if (denied || e.getResult() != PlayerLoginEvent.Result.ALLOWED) { - // Schedule cleanup of this user. - this.plugin.getUserManager().scheduleUnload(e.getPlayer().getUniqueId()); return; } @@ -206,8 +199,9 @@ public class BukkitConnectionListener implements Listener { player.setOp(false); } - // Request that the users data is unloaded. - this.plugin.getUserManager().scheduleUnload(player.getUniqueId()); + // Register with the housekeeper, so the User's instance will stick + // around for a bit after they disconnect + this.plugin.getUserManager().getHouseKeeper().registerUsage(player.getUniqueId()); } } diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeSchedulerAdapter.java b/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeSchedulerAdapter.java index 40c93999..96277a3c 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeSchedulerAdapter.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeSchedulerAdapter.java @@ -26,8 +26,11 @@ package me.lucko.luckperms.bungee; import me.lucko.luckperms.common.plugin.SchedulerAdapter; +import me.lucko.luckperms.common.plugin.SchedulerTask; +import me.lucko.luckperms.common.utils.SafeIterator; import net.md_5.bungee.api.scheduler.ScheduledTask; +import net.md_5.bungee.api.scheduler.TaskScheduler; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -35,16 +38,32 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; public class BungeeSchedulerAdapter implements SchedulerAdapter { + + // the number of ticks which occur in a second - this is a server implementation detail + public static final int TICKS_PER_SECOND = 20; + // the number of milliseconds in a second - constant + public static final int MILLISECONDS_PER_SECOND = 1000; + // the number of milliseconds in a tick - assuming the server runs at a perfect tick rate + public static final int MILLISECONDS_PER_TICK = MILLISECONDS_PER_SECOND / TICKS_PER_SECOND; + + private static long ticksToMillis(long ticks) { + return ticks * MILLISECONDS_PER_TICK; + } + private final LPBungeePlugin plugin; private final Executor asyncExecutor; - private final Set tasks = ConcurrentHashMap.newKeySet(); + private final Set tasks = ConcurrentHashMap.newKeySet(); public BungeeSchedulerAdapter(LPBungeePlugin plugin) { this.plugin = plugin; this.asyncExecutor = r -> plugin.getProxy().getScheduler().runAsync(plugin, r); } + private TaskScheduler scheduler() { + return this.plugin.getProxy().getScheduler(); + } + @Override public Executor async() { return this.asyncExecutor; @@ -66,30 +85,44 @@ public class BungeeSchedulerAdapter implements SchedulerAdapter { } @Override - public void asyncRepeating(Runnable runnable, long intervalTicks) { - long millis = intervalTicks * 50L; // convert from ticks to milliseconds - ScheduledTask task = this.plugin.getProxy().getScheduler().schedule(this.plugin, runnable, millis, millis, TimeUnit.MILLISECONDS); + public SchedulerTask asyncRepeating(Runnable runnable, long intervalTicks) { + long millis = ticksToMillis(intervalTicks); + SchedulerTask task = new BungeeSchedulerTask(scheduler().schedule(this.plugin, runnable, millis, millis, TimeUnit.MILLISECONDS)); this.tasks.add(task); + return task; } @Override - public void syncRepeating(Runnable runnable, long intervalTicks) { - asyncRepeating(runnable, intervalTicks); + public SchedulerTask syncRepeating(Runnable runnable, long intervalTicks) { + return asyncRepeating(runnable, intervalTicks); } @Override - public void asyncLater(Runnable runnable, long delayTicks) { - long millis = delayTicks * 50L; // convert from ticks to milliseconds - this.plugin.getProxy().getScheduler().schedule(this.plugin, runnable, millis, TimeUnit.MILLISECONDS); + public SchedulerTask asyncLater(Runnable runnable, long delayTicks) { + return new BungeeSchedulerTask(scheduler().schedule(this.plugin, runnable, ticksToMillis(delayTicks), TimeUnit.MILLISECONDS)); } @Override - public void syncLater(Runnable runnable, long delayTicks) { - asyncLater(runnable, delayTicks); + public SchedulerTask syncLater(Runnable runnable, long delayTicks) { + return asyncLater(runnable, delayTicks); } @Override public void shutdown() { - this.tasks.forEach(ScheduledTask::cancel); + SafeIterator.iterate(this.tasks, SchedulerTask::cancel); } + + private static final class BungeeSchedulerTask implements SchedulerTask { + private final ScheduledTask task; + + private BungeeSchedulerTask(ScheduledTask task) { + this.task = task; + } + + @Override + public void cancel() { + this.task.cancel(); + } + } + } diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java index 692b0242..6db8cd13 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java @@ -57,12 +57,9 @@ import me.lucko.luckperms.common.locale.NoopLocaleManager; import me.lucko.luckperms.common.locale.SimpleLocaleManager; import me.lucko.luckperms.common.logging.Logger; import me.lucko.luckperms.common.logging.SenderLogger; -import me.lucko.luckperms.common.managers.GenericGroupManager; -import me.lucko.luckperms.common.managers.GenericTrackManager; -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.UserManager; +import me.lucko.luckperms.common.managers.group.StandardGroupManager; +import me.lucko.luckperms.common.managers.track.StandardTrackManager; +import me.lucko.luckperms.common.managers.user.StandardUserManager; import me.lucko.luckperms.common.messaging.ExtendedMessagingService; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; @@ -101,9 +98,9 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { private SchedulerAdapter scheduler; private CommandManager commandManager; private LuckPermsConfiguration configuration; - private UserManager userManager; - private GroupManager groupManager; - private TrackManager trackManager; + private StandardUserManager userManager; + private StandardGroupManager groupManager; + private StandardTrackManager trackManager; private Storage storage; private FileWatcher fileWatcher = null; private ExtendedMessagingService messagingService = null; @@ -183,9 +180,9 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { // load internal managers getLog().info("Loading internal permission managers..."); this.uuidCache = new UuidCache(this); - this.userManager = new GenericUserManager(this); - this.groupManager = new GenericGroupManager(this); - this.trackManager = new GenericTrackManager(this); + this.userManager = new StandardUserManager(this); + this.groupManager = new StandardGroupManager(this); + this.trackManager = new StandardTrackManager(this); this.calculatorFactory = new BungeeCalculatorFactory(this); this.cachedStateManager = new CachedStateManager(); @@ -394,17 +391,17 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { } @Override - public UserManager getUserManager() { + public StandardUserManager getUserManager() { return this.userManager; } @Override - public GroupManager getGroupManager() { + public StandardGroupManager getGroupManager() { return this.groupManager; } @Override - public TrackManager getTrackManager() { + public StandardTrackManager getTrackManager() { return this.trackManager; } diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/listeners/BungeeConnectionListener.java b/bungee/src/main/java/me/lucko/luckperms/bungee/listeners/BungeeConnectionListener.java index 1d06011e..2a3a4c3a 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/listeners/BungeeConnectionListener.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/listeners/BungeeConnectionListener.java @@ -29,7 +29,7 @@ import me.lucko.luckperms.bungee.LPBungeePlugin; import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.utils.LoginHelper; +import me.lucko.luckperms.common.utils.AbstractLoginListener; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.PendingConnection; @@ -43,10 +43,11 @@ import net.md_5.bungee.event.EventPriority; import java.util.concurrent.TimeUnit; -public class BungeeConnectionListener implements Listener { +public class BungeeConnectionListener extends AbstractLoginListener implements Listener { private final LPBungeePlugin plugin; public BungeeConnectionListener(LPBungeePlugin plugin) { + super(plugin); this.plugin = plugin; } @@ -82,7 +83,7 @@ public class BungeeConnectionListener implements Listener { - creating a user instance in the UserManager for this connection. - setting up cached data. */ try { - User user = LoginHelper.loadUser(this.plugin, c.getUniqueId(), c.getName(), true); + User user = loadUser(c.getUniqueId(), c.getName()); this.plugin.getEventFactory().handleUserLoginProcess(c.getUniqueId(), c.getName(), user); } catch (Exception ex) { this.plugin.getLog().severe("Exception occured whilst loading data for " + c.getUniqueId() + " - " + c.getName()); @@ -98,10 +99,6 @@ public class BungeeConnectionListener implements Listener { // finally, complete our intent to modify state, so the proxy can continue handling the connection. e.completeIntent(this.plugin); - - // schedule a cleanup of the users data in a few seconds. - // this should cover the eventuality that the login fails. - this.plugin.getUserManager().scheduleUnload(c.getUniqueId()); }); } @@ -135,8 +132,9 @@ public class BungeeConnectionListener implements Listener { // Wait until the last priority to unload, so plugins can still perform permission checks on this event @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerQuit(PlayerDisconnectEvent e) { - // Request that the users data is unloaded. - this.plugin.getUserManager().scheduleUnload(e.getPlayer().getUniqueId()); + // Register with the housekeeper, so the User's instance will stick + // around for a bit after they disconnect + this.plugin.getUserManager().getHouseKeeper().registerUsage(e.getPlayer().getUniqueId()); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/api/LuckPermsApiProvider.java b/common/src/main/java/me/lucko/luckperms/common/api/LuckPermsApiProvider.java index 77205cba..fcfd9a89 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/LuckPermsApiProvider.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/LuckPermsApiProvider.java @@ -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); diff --git a/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiGroupManager.java b/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiGroupManager.java index d2dab478..063fa73f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiGroupManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiGroupManager.java @@ -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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiTrackManager.java b/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiTrackManager.java index fb27b4d9..aa7406d9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiTrackManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiTrackManager.java @@ -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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiUserManager.java b/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiUserManager.java index 0ccf582d..24d23a94 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiUserManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/delegates/manager/ApiUserManager.java @@ -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()); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiStorage.java b/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiStorage.java index affee9eb..62c00ecd 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiStorage.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/delegates/model/ApiStorage.java @@ -99,6 +99,11 @@ public class ApiStorage implements Storage { @Override public CompletableFuture 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); } diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/AbstractManager.java b/common/src/main/java/me/lucko/luckperms/common/managers/AbstractManager.java index 9a8bfc54..be71a71b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/managers/AbstractManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/managers/AbstractManager.java @@ -40,9 +40,10 @@ import javax.annotation.Nonnull; * An abstract manager class * * @param the class used to identify each object held in this manager - * @param the class this manager is "managing" + * @param the super class being managed + * @param the implementation class this manager is "managing" */ -public abstract class AbstractManager> implements Manager { +public abstract class AbstractManager, T extends C> implements Manager { private final LoadingCache objects = Caffeine.newBuilder() .build(new CacheLoader() { @@ -85,9 +86,9 @@ public abstract class AbstractManager> 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()); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/Manager.java b/common/src/main/java/me/lucko/luckperms/common/managers/Manager.java index 35dd44af..0a222286 100644 --- a/common/src/main/java/me/lucko/luckperms/common/managers/Manager.java +++ b/common/src/main/java/me/lucko/luckperms/common/managers/Manager.java @@ -34,16 +34,17 @@ import java.util.function.Function; * A class which manages instances of a class * * @param the class used to identify each object held in this manager - * @param the class this manager is "managing" + * @param the super class being managed + * @param the implementation class this manager is "managing" */ -public interface Manager> extends Function { +public interface Manager, T extends C> extends Function { /** * Gets a map containing all cached instances held by this manager. * * @return all instances held in this manager */ - Map getAll(); + Map getAll(); /** * Gets or creates an object by id @@ -81,9 +82,9 @@ public interface Manager> extends Function { /** * 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 diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/GenericGroupManager.java b/common/src/main/java/me/lucko/luckperms/common/managers/group/AbstractGroupManager.java similarity index 75% rename from common/src/main/java/me/lucko/luckperms/common/managers/GenericGroupManager.java rename to common/src/main/java/me/lucko/luckperms/common/managers/group/AbstractGroupManager.java index e9b15100..fc53b7bf 100644 --- a/common/src/main/java/me/lucko/luckperms/common/managers/GenericGroupManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/managers/group/AbstractGroupManager.java @@ -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 implements GroupManager { - private final LuckPermsPlugin plugin; - - public GenericGroupManager(LuckPermsPlugin plugin) { - this.plugin = plugin; - } +public abstract class AbstractGroupManager extends AbstractManager implements GroupManager { @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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/GroupManager.java b/common/src/main/java/me/lucko/luckperms/common/managers/group/GroupManager.java similarity index 87% rename from common/src/main/java/me/lucko/luckperms/common/managers/GroupManager.java rename to common/src/main/java/me/lucko/luckperms/common/managers/group/GroupManager.java index 46200502..fb4ef00d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/managers/GroupManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/managers/group/GroupManager.java @@ -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 { +public interface GroupManager extends Manager { /** * Get a group object by display name @@ -35,6 +36,6 @@ public interface GroupManager extends Manager { * @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); } diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/group/StandardGroupManager.java b/common/src/main/java/me/lucko/luckperms/common/managers/group/StandardGroupManager.java new file mode 100644 index 00000000..7aa020b2 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/managers/group/StandardGroupManager.java @@ -0,0 +1,42 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 { + private final LuckPermsPlugin plugin; + + public StandardGroupManager(LuckPermsPlugin plugin) { + this.plugin = plugin; + } + + @Override + public Group apply(String name) { + return new Group(name, this.plugin); + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/track/AbstractTrackManager.java b/common/src/main/java/me/lucko/luckperms/common/managers/track/AbstractTrackManager.java new file mode 100644 index 00000000..65ff15a6 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/managers/track/AbstractTrackManager.java @@ -0,0 +1,37 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 extends AbstractManager implements TrackManager { + + @Override + protected String sanitizeIdentifier(String s) { + return s.toLowerCase(); + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/GenericTrackManager.java b/common/src/main/java/me/lucko/luckperms/common/managers/track/StandardTrackManager.java similarity index 83% rename from common/src/main/java/me/lucko/luckperms/common/managers/GenericTrackManager.java rename to common/src/main/java/me/lucko/luckperms/common/managers/track/StandardTrackManager.java index 3de89a49..1a982902 100644 --- a/common/src/main/java/me/lucko/luckperms/common/managers/GenericTrackManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/managers/track/StandardTrackManager.java @@ -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 implements TrackManager { +public class StandardTrackManager extends AbstractTrackManager { private final LuckPermsPlugin plugin; - public GenericTrackManager(LuckPermsPlugin plugin) { + public StandardTrackManager(LuckPermsPlugin plugin) { this.plugin = plugin; } @@ -39,9 +39,4 @@ public class GenericTrackManager extends AbstractManager implemen public Track apply(String name) { return new Track(name, this.plugin); } - - @Override - protected String sanitizeIdentifier(String s) { - return s.toLowerCase(); - } } diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/TrackManager.java b/common/src/main/java/me/lucko/luckperms/common/managers/track/TrackManager.java similarity index 87% rename from common/src/main/java/me/lucko/luckperms/common/managers/TrackManager.java rename to common/src/main/java/me/lucko/luckperms/common/managers/track/TrackManager.java index 22f6488b..97b04c12 100644 --- a/common/src/main/java/me/lucko/luckperms/common/managers/TrackManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/managers/track/TrackManager.java @@ -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 { +public interface TrackManager extends Manager { } diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/GenericUserManager.java b/common/src/main/java/me/lucko/luckperms/common/managers/user/AbstractUserManager.java similarity index 72% rename from common/src/main/java/me/lucko/luckperms/common/managers/GenericUserManager.java rename to common/src/main/java/me/lucko/luckperms/common/managers/user/AbstractUserManager.java index b5346c05..ef1be8d2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/managers/GenericUserManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/managers/user/AbstractUserManager.java @@ -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 implements UserManager { +public abstract class AbstractUserManager extends AbstractManager implements UserManager { 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 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 n = user.getName(); if (n.isPresent() && n.get().equalsIgnoreCase(name)) { return user; @@ -73,59 +70,16 @@ public class GenericUserManager extends AbstractManager 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 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 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 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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/user/StandardUserManager.java b/common/src/main/java/me/lucko/luckperms/common/managers/user/StandardUserManager.java new file mode 100644 index 00000000..26b4d3b6 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/managers/user/StandardUserManager.java @@ -0,0 +1,48 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 { + 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); + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/user/UserHousekeeper.java b/common/src/main/java/me/lucko/luckperms/common/managers/user/UserHousekeeper.java new file mode 100644 index 00000000..839c9036 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/managers/user/UserHousekeeper.java @@ -0,0 +1,100 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 recentlyUsed; + + // contains the uuids of users who have recently been retrieved from the API + private final ExpiringSet 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; + } + } +} diff --git a/common/src/main/java/me/lucko/luckperms/common/managers/UserManager.java b/common/src/main/java/me/lucko/luckperms/common/managers/user/UserManager.java similarity index 74% rename from common/src/main/java/me/lucko/luckperms/common/managers/UserManager.java rename to common/src/main/java/me/lucko/luckperms/common/managers/user/UserManager.java index 28658a79..e0061047 100644 --- a/common/src/main/java/me/lucko/luckperms/common/managers/UserManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/managers/user/UserManager.java @@ -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 { +public interface UserManager extends Manager { /** * Get a user object by name @@ -40,7 +40,7 @@ public interface UserManager extends Manager { * @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 { * @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 { 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 diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java index 950afa28..df8e44ff 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java @@ -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 diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/SchedulerAdapter.java b/common/src/main/java/me/lucko/luckperms/common/plugin/SchedulerAdapter.java index 1236d96b..c23aacc9 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/SchedulerAdapter.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/SchedulerAdapter.java @@ -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 diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/SchedulerTask.java b/common/src/main/java/me/lucko/luckperms/common/plugin/SchedulerTask.java new file mode 100644 index 00000000..2180b2c1 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/SchedulerTask.java @@ -0,0 +1,38 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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(); + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/ConfigurateDao.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/ConfigurateDao.java index 5be08799..9f2f841e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/ConfigurateDao.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/file/ConfigurateDao.java @@ -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); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/mongodb/MongoDao.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/mongodb/MongoDao.java index 60214dec..0f6ac5d8 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/mongodb/MongoDao.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/mongodb/MongoDao.java @@ -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 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); diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/SqlDao.java b/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/SqlDao.java index 34320da7..0dc2ace5 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/SqlDao.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/dao/sql/SqlDao.java @@ -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); diff --git a/common/src/main/java/me/lucko/luckperms/common/utils/LoginHelper.java b/common/src/main/java/me/lucko/luckperms/common/utils/AbstractLoginListener.java similarity index 58% rename from common/src/main/java/me/lucko/luckperms/common/utils/LoginHelper.java rename to common/src/main/java/me/lucko/luckperms/common/utils/AbstractLoginListener.java index de75dc47..4bd75957 100644 --- a/common/src/main/java/me/lucko/luckperms/common/utils/LoginHelper.java +++ b/common/src/main/java/me/lucko/luckperms/common/utils/AbstractLoginListener.java @@ -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 future = plugin.getStorage().noBuffer().saveUUIDData(u, username); - if (joinUuidSave) { + CompletableFuture 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 future = plugin.getStorage().noBuffer().saveUUIDData(u, username); - if (joinUuidSave) { + CompletableFuture 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() {} - } diff --git a/common/src/main/java/me/lucko/luckperms/common/utils/ExpiringSet.java b/common/src/main/java/me/lucko/luckperms/common/utils/ExpiringSet.java new file mode 100644 index 00000000..28144a67 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/utils/ExpiringSet.java @@ -0,0 +1,90 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * 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 element type + */ +public class ExpiringSet extends ForwardingSet { + private final LoadingCache cache; + private final Set 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 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 delegate() { + return this.setView; + } +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java index f566ef00..80b87be7 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java @@ -52,8 +52,7 @@ import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.NoopLocaleManager; import me.lucko.luckperms.common.locale.SimpleLocaleManager; import me.lucko.luckperms.common.logging.SenderLogger; -import me.lucko.luckperms.common.managers.GenericTrackManager; -import me.lucko.luckperms.common.managers.TrackManager; +import me.lucko.luckperms.common.managers.track.StandardTrackManager; import me.lucko.luckperms.common.messaging.ExtendedMessagingService; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; @@ -168,7 +167,7 @@ public class LPSpongePlugin implements LuckPermsPlugin { private LuckPermsConfiguration configuration; private SpongeUserManager userManager; private SpongeGroupManager groupManager; - private TrackManager trackManager; + private StandardTrackManager trackManager; private Storage storage; private FileWatcher fileWatcher = null; private ExtendedMessagingService messagingService = null; @@ -243,7 +242,7 @@ public class LPSpongePlugin implements LuckPermsPlugin { this.uuidCache = new UuidCache(this); this.userManager = new SpongeUserManager(this); this.groupManager = new SpongeGroupManager(this); - this.trackManager = new GenericTrackManager(this); + this.trackManager = new StandardTrackManager(this); this.calculatorFactory = new SpongeCalculatorFactory(this); this.cachedStateManager = new CachedStateManager(); @@ -561,7 +560,7 @@ public class LPSpongePlugin implements LuckPermsPlugin { } @Override - public TrackManager getTrackManager() { + public StandardTrackManager getTrackManager() { return this.trackManager; } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeSchedulerAdapter.java b/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeSchedulerAdapter.java index 7e03f6ab..b59c9150 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeSchedulerAdapter.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeSchedulerAdapter.java @@ -26,7 +26,10 @@ package me.lucko.luckperms.sponge; import me.lucko.luckperms.common.plugin.SchedulerAdapter; +import me.lucko.luckperms.common.plugin.SchedulerTask; +import me.lucko.luckperms.common.utils.SafeIterator; +import org.spongepowered.api.scheduler.Scheduler; import org.spongepowered.api.scheduler.Task; import java.util.Set; @@ -35,12 +38,16 @@ import java.util.concurrent.Executor; public class SpongeSchedulerAdapter implements SchedulerAdapter { private final LPSpongePlugin plugin; - private final Set tasks = ConcurrentHashMap.newKeySet(); + private final Set tasks = ConcurrentHashMap.newKeySet(); public SpongeSchedulerAdapter(LPSpongePlugin plugin) { this.plugin = plugin; } + private Scheduler scheduler() { + return this.plugin.getSpongeScheduler(); + } + @Override public Executor async() { return this.plugin.getAsyncExecutorService(); @@ -62,29 +69,72 @@ public class SpongeSchedulerAdapter implements SchedulerAdapter { } @Override - public void asyncRepeating(Runnable runnable, long intervalTicks) { - Task task = this.plugin.getSpongeScheduler().createTaskBuilder().async().intervalTicks(intervalTicks).delayTicks(intervalTicks).execute(runnable).submit(this.plugin); - this.tasks.add(task); + public SchedulerTask asyncRepeating(Runnable runnable, long intervalTicks) { + Task task = scheduler().createTaskBuilder() + .async() + .intervalTicks(intervalTicks) + .delayTicks(intervalTicks) + .execute(runnable) + .submit(this.plugin); + + SchedulerTask wrapped = new SpongeSchedulerTask(task); + this.tasks.add(wrapped); + return wrapped; } @Override - public void syncRepeating(Runnable runnable, long intervalTicks) { - Task task = this.plugin.getSpongeScheduler().createTaskBuilder().intervalTicks(intervalTicks).delayTicks(intervalTicks).execute(runnable).submit(this.plugin); - this.tasks.add(task); + public SchedulerTask syncRepeating(Runnable runnable, long intervalTicks) { + Task task = scheduler().createTaskBuilder() + .intervalTicks(intervalTicks) + .delayTicks(intervalTicks) + .execute(runnable) + .submit(this.plugin); + + SchedulerTask wrapped = new SpongeSchedulerTask(task); + this.tasks.add(wrapped); + return wrapped; } @Override - public void asyncLater(Runnable runnable, long delayTicks) { - this.plugin.getSpongeScheduler().createTaskBuilder().async().delayTicks(delayTicks).execute(runnable).submit(this.plugin); + public SchedulerTask asyncLater(Runnable runnable, long delayTicks) { + Task task = scheduler().createTaskBuilder() + .async() + .delayTicks(delayTicks) + .execute(runnable) + .submit(this.plugin); + + SchedulerTask wrapped = new SpongeSchedulerTask(task); + this.tasks.add(wrapped); + return wrapped; } @Override - public void syncLater(Runnable runnable, long delayTicks) { - this.plugin.getSpongeScheduler().createTaskBuilder().delayTicks(delayTicks).execute(runnable).submit(this.plugin); + public SchedulerTask syncLater(Runnable runnable, long delayTicks) { + Task task = scheduler().createTaskBuilder() + .delayTicks(delayTicks) + .execute(runnable) + .submit(this.plugin); + + SchedulerTask wrapped = new SpongeSchedulerTask(task); + this.tasks.add(wrapped); + return wrapped; } @Override public void shutdown() { - this.tasks.forEach(Task::cancel); + SafeIterator.iterate(this.tasks, SchedulerTask::cancel); + } + + private static final class SpongeSchedulerTask implements SchedulerTask { + private final Task task; + + private SpongeSchedulerTask(Task task) { + this.task = task; + } + + @Override + public void cancel() { + this.task.cancel(); + } } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/listeners/SpongeConnectionListener.java b/sponge/src/main/java/me/lucko/luckperms/sponge/listeners/SpongeConnectionListener.java index 4c948857..a068178d 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/listeners/SpongeConnectionListener.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/listeners/SpongeConnectionListener.java @@ -28,7 +28,7 @@ package me.lucko.luckperms.sponge.listeners; import me.lucko.luckperms.common.config.ConfigKeys; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.model.User; -import me.lucko.luckperms.common.utils.LoginHelper; +import me.lucko.luckperms.common.utils.AbstractLoginListener; import me.lucko.luckperms.common.utils.UuidCache; import me.lucko.luckperms.sponge.LPSpongePlugin; @@ -45,13 +45,14 @@ import java.util.HashSet; import java.util.Set; import java.util.UUID; -public class SpongeConnectionListener { +public class SpongeConnectionListener extends AbstractLoginListener { private final LPSpongePlugin plugin; private final Set deniedAsyncLogin = Collections.synchronizedSet(new HashSet<>()); private final Set deniedLogin = Collections.synchronizedSet(new HashSet<>()); public SpongeConnectionListener(LPSpongePlugin plugin) { + super(plugin); this.plugin = plugin; } @@ -80,7 +81,7 @@ public class SpongeConnectionListener { - creating a user instance in the UserManager for this connection. - setting up cached data. */ try { - User user = LoginHelper.loadUser(this.plugin, p.getUniqueId(), username, false); + User user = loadUser(p.getUniqueId(), username); this.plugin.getEventFactory().handleUserLoginProcess(p.getUniqueId(), username, user); } catch (Exception ex) { this.plugin.getLog().severe("Exception occured whilst loading data for " + p.getUniqueId() + " - " + p.getName()); @@ -165,6 +166,10 @@ public class SpongeConnectionListener { // Unload the user from memory when they disconnect cache.clearCache(e.getTargetEntity().getUniqueId()); + + // Register with the housekeeper, so the User's instance will stick + // around for a bit after they disconnect + this.plugin.getUserManager().getHouseKeeper().registerUsage(e.getTargetEntity().getUniqueId()); } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeGroupManager.java b/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeGroupManager.java index 706264e9..335cc82d 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeGroupManager.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeGroupManager.java @@ -36,8 +36,7 @@ import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.context.ImmutableContextSet; import me.lucko.luckperms.api.event.cause.CreationCause; -import me.lucko.luckperms.common.managers.GroupManager; -import me.lucko.luckperms.common.model.Group; +import me.lucko.luckperms.common.managers.group.AbstractGroupManager; import me.lucko.luckperms.common.storage.DataConstraints; import me.lucko.luckperms.common.utils.ImmutableCollectors; import me.lucko.luckperms.sponge.LPSpongePlugin; @@ -61,15 +60,10 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; -public class SpongeGroupManager implements GroupManager, LPSubjectCollection { - +public class SpongeGroupManager extends AbstractGroupManager implements LPSubjectCollection { private final LPSpongePlugin plugin; - private SubjectCollection spongeProxy = null; - private final LoadingCache objects = Caffeine.newBuilder() - .build(this::apply); - private final LoadingCache subjectLoadingCache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.MINUTES) .build(s -> { @@ -104,53 +98,6 @@ public class SpongeGroupManager implements GroupManager, LPSubjectCollection { return new SpongeGroup(name, this.plugin); } - /* ------------------------------------------ - * Manager methods - * ------------------------------------------ */ - - @Override - public Map getAll() { - return ImmutableMap.copyOf(this.objects.asMap()); - } - - @Override - public SpongeGroup getOrMake(String id) { - return this.objects.get(id.toLowerCase()); - } - - @Override - public SpongeGroup getIfLoaded(String id) { - return this.objects.getIfPresent(id.toLowerCase()); - } - - @Override - public boolean isLoaded(String id) { - return this.objects.asMap().containsKey(id.toLowerCase()); - } - - @Override - public void unload(String id) { - if (id != null) { - this.objects.invalidate(id.toLowerCase()); - } - } - - @Override - public void unload(Group t) { - if (t != null) { - unload(t.getId()); - } - } - - @Override - public void unloadAll() { - this.objects.invalidateAll(); - } - - /* ------------------------------------------ - * SubjectCollection methods - * ------------------------------------------ */ - @Override public synchronized SubjectCollection sponge() { if (this.spongeProxy == null) { @@ -160,6 +107,10 @@ public class SpongeGroupManager implements GroupManager, LPSubjectCollection { return this.spongeProxy; } + public LPSpongePlugin getPlugin() { + return this.plugin; + } + @Override public LuckPermsService getService() { return this.plugin.getService(); @@ -261,7 +212,7 @@ public class SpongeGroupManager implements GroupManager, LPSubjectCollection { @Override public ImmutableMap getLoadedWithPermission(String permission) { - return this.objects.asMap().values().stream() + return getAll().values().stream() .map(SpongeGroup::sponge) .map(sub -> Maps.immutableEntry(sub, sub.getPermissionValue(ImmutableContextSet.empty(), permission))) .filter(pair -> pair.getValue() != Tristate.UNDEFINED) @@ -270,7 +221,7 @@ public class SpongeGroupManager implements GroupManager, LPSubjectCollection { @Override public ImmutableMap getLoadedWithPermission(ImmutableContextSet contexts, String permission) { - return this.objects.asMap().values().stream() + return getAll().values().stream() .map(SpongeGroup::sponge) .map(sub -> Maps.immutableEntry(sub, sub.getPermissionValue(contexts, permission))) .filter(pair -> pair.getValue() != Tristate.UNDEFINED) @@ -282,33 +233,4 @@ public class SpongeGroupManager implements GroupManager, LPSubjectCollection { return getService().getDefaultSubjects().loadSubject(getIdentifier()).join(); } - @Override - public Group getByDisplayName(String name) { - // try to get an exact match first - Group g = getIfLoaded(name); - if (g != null) { - return g; - } - - // then try exact display name matches - for (Group 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()) { - if (group.getDisplayName().isPresent() && group.getDisplayName().get().equalsIgnoreCase(name)) { - return group; - } - } - - return null; - } - - public LPSpongePlugin getPlugin() { - return this.plugin; - } - } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeUserManager.java b/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeUserManager.java index f7163933..906e2b49 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeUserManager.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/managers/SpongeUserManager.java @@ -35,9 +35,8 @@ import com.google.common.collect.Maps; import me.lucko.luckperms.api.HeldPermission; import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.context.ImmutableContextSet; -import me.lucko.luckperms.common.managers.GenericUserManager; -import me.lucko.luckperms.common.managers.UserManager; -import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.managers.user.AbstractUserManager; +import me.lucko.luckperms.common.managers.user.UserHousekeeper; import me.lucko.luckperms.common.references.UserIdentifier; import me.lucko.luckperms.common.utils.ImmutableCollectors; import me.lucko.luckperms.sponge.LPSpongePlugin; @@ -62,18 +61,16 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; -public class SpongeUserManager implements UserManager, LPSubjectCollection { - +public class SpongeUserManager extends AbstractUserManager implements LPSubjectCollection { private final LPSpongePlugin plugin; - private SubjectCollection spongeProxy = null; - private final LoadingCache objects = Caffeine.newBuilder() - .build(this::apply); - private final LoadingCache subjectLoadingCache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.MINUTES) .build(u -> { + // clock in with the housekeeper + getHouseKeeper().registerUsage(u); + // check if the user instance is already loaded. SpongeUser user = getIfLoaded(u); if (user != null) { @@ -100,6 +97,7 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection { }); public SpongeUserManager(LPSpongePlugin plugin) { + super(plugin, UserHousekeeper.timeoutSettings(10, TimeUnit.MINUTES)); this.plugin = plugin; } @@ -110,103 +108,6 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection { new SpongeUser(id.getUuid(), id.getUsername().get(), this.plugin); } - /* ------------------------------------------ - * Manager methods - * ------------------------------------------ */ - - @Override - public Map getAll() { - return ImmutableMap.copyOf(this.objects.asMap()); - } - - @Override - public SpongeUser getOrMake(UserIdentifier id) { - SpongeUser ret = this.objects.get(id); - if (id.getUsername().isPresent()) { - ret.setName(id.getUsername().get(), false); - } - return ret; - } - - @Override - public SpongeUser getIfLoaded(UserIdentifier id) { - return this.objects.getIfPresent(id); - } - - @Override - public boolean isLoaded(UserIdentifier id) { - return this.objects.asMap().containsKey(id); - } - - @Override - public void unload(UserIdentifier id) { - if (id != null) { - this.objects.invalidate(id); - } - } - - @Override - public void unload(User t) { - if (t != null) { - unload(t.getId()); - } - } - - @Override - public void unloadAll() { - this.objects.invalidateAll(); - } - - /* ------------------------------------------ - * UserManager methods - * ------------------------------------------ */ - - @Override - public SpongeUser getByUsername(String name) { - for (SpongeUser user : getAll().values()) { - Optional n = user.getName(); - if (n.isPresent() && n.get().equalsIgnoreCase(name)) { - return user; - } - } - return null; - } - - @Override - public SpongeUser getIfLoaded(UUID uuid) { - return getIfLoaded(UserIdentifier.of(uuid, null)); - } - - @Override - public boolean giveDefaultIfNeeded(User user, boolean save) { - return GenericUserManager.giveDefaultIfNeeded(user, save, this.plugin); - } - - @Override - public boolean cleanup(User user) { - // Do nothing - this instance uses other means in order to cleanup - return false; - } - - @Override - public void scheduleUnload(UUID uuid) { - // Do nothing - this instance uses other means in order to cleanup - } - - @Override - public CompletableFuture 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() - ); - } - - /* ------------------------------------------ - * SubjectCollection methods - * ------------------------------------------ */ - @Override public synchronized SubjectCollection sponge() { if (this.spongeProxy == null) { @@ -216,6 +117,10 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection { return this.spongeProxy; } + public LPSpongePlugin getPlugin() { + return this.plugin; + } + @Override public LuckPermsService getService() { return this.plugin.getService(); @@ -358,7 +263,7 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection { @Override public ImmutableMap getLoadedWithPermission(String permission) { - return this.objects.asMap().values().stream() + return getAll().values().stream() .map(SpongeUser::sponge) .map(sub -> Maps.immutableEntry(sub, sub.getPermissionValue(ImmutableContextSet.empty(), permission))) .filter(pair -> pair.getValue() != Tristate.UNDEFINED) @@ -367,7 +272,7 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection { @Override public ImmutableMap getLoadedWithPermission(ImmutableContextSet contexts, String permission) { - return this.objects.asMap().values().stream() + return getAll().values().stream() .map(SpongeUser::sponge) .map(sub -> Maps.immutableEntry(sub, sub.getPermissionValue(contexts, permission))) .filter(pair -> pair.getValue() != Tristate.UNDEFINED) @@ -379,8 +284,4 @@ public class SpongeUserManager implements UserManager, LPSubjectCollection { return getService().getDefaultSubjects().loadSubject(getIdentifier()).join(); } - public LPSpongePlugin getPlugin() { - return this.plugin; - } - }