Remove cache, add disable-offline-access config node
Fixes various permissions not being respected during login/logout with inventories already open. This will result in a performance hit for repeated open/closes - there will be significantly more disk I/O. Closes lishid#171 Closes #56
This commit is contained in:
		@@ -16,6 +16,7 @@
 | 
			
		||||
 | 
			
		||||
package com.lishid.openinv;
 | 
			
		||||
 | 
			
		||||
import com.lishid.openinv.internal.ISpecialInventory;
 | 
			
		||||
import com.lishid.openinv.internal.ISpecialPlayerInventory;
 | 
			
		||||
import com.lishid.openinv.util.InventoryAccess;
 | 
			
		||||
import com.lishid.openinv.util.Permissions;
 | 
			
		||||
@@ -47,7 +48,7 @@ import org.jetbrains.annotations.Nullable;
 | 
			
		||||
record InventoryListener(OpenInv plugin) implements Listener {
 | 
			
		||||
 | 
			
		||||
    @EventHandler
 | 
			
		||||
    public void onInventoryClose(@NotNull final InventoryCloseEvent event) {
 | 
			
		||||
    private void onInventoryClose(@NotNull final InventoryCloseEvent event) {
 | 
			
		||||
        if (!(event.getPlayer() instanceof Player player)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@@ -55,10 +56,20 @@ record InventoryListener(OpenInv plugin) implements Listener {
 | 
			
		||||
        if (this.plugin.getPlayerSilentChestStatus(player)) {
 | 
			
		||||
            this.plugin.getAnySilentContainer().deactivateContainer(player);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ISpecialInventory specialInventory = InventoryAccess.getEnderChest(event.getInventory());
 | 
			
		||||
        if (specialInventory != null) {
 | 
			
		||||
            this.plugin.handleCloseInventory(event.getPlayer(), specialInventory);
 | 
			
		||||
        } else {
 | 
			
		||||
            specialInventory = InventoryAccess.getPlayerInventory(event.getInventory());
 | 
			
		||||
            if (specialInventory != null) {
 | 
			
		||||
                this.plugin.handleCloseInventory(event.getPlayer(), specialInventory);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @EventHandler(priority = EventPriority.LOWEST)
 | 
			
		||||
    public void onInventoryClick(@NotNull final InventoryClickEvent event) {
 | 
			
		||||
    private void onInventoryClick(@NotNull final InventoryClickEvent event) {
 | 
			
		||||
        if (handleInventoryInteract(event)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@@ -92,7 +103,7 @@ record InventoryListener(OpenInv plugin) implements Listener {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @EventHandler(priority = EventPriority.LOWEST)
 | 
			
		||||
    public void onInventoryDrag(@NotNull final InventoryDragEvent event) {
 | 
			
		||||
    private void onInventoryDrag(@NotNull final InventoryDragEvent event) {
 | 
			
		||||
        if (handleInventoryInteract(event)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								plugin/src/main/java/com/lishid/openinv/OfflineHandler.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								plugin/src/main/java/com/lishid/openinv/OfflineHandler.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2011-2022 lishid. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, version 3.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.lishid.openinv;
 | 
			
		||||
 | 
			
		||||
import com.lishid.openinv.internal.ISpecialInventory;
 | 
			
		||||
import com.lishid.openinv.util.Permissions;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
record OfflineHandler(
 | 
			
		||||
        @NotNull BiFunction<Map<UUID, ? extends ISpecialInventory>, UUID, ISpecialInventory> fetch,
 | 
			
		||||
        @NotNull Consumer<@NotNull ISpecialInventory> handle) {
 | 
			
		||||
 | 
			
		||||
    static final OfflineHandler REMOVE_AND_CLOSE = new OfflineHandler(
 | 
			
		||||
            Map::remove,
 | 
			
		||||
            inventory -> OpenInv.ejectViewers(inventory, viewer -> true)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    static final OfflineHandler REQUIRE_PERMISSIONS = new OfflineHandler(
 | 
			
		||||
            Map::get,
 | 
			
		||||
            inventory -> OpenInv.ejectViewers(inventory, viewer -> !Permissions.OPENOFFLINE.hasPermission(viewer))
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2011-2021 lishid. All rights reserved.
 | 
			
		||||
 * Copyright (C) 2011-2022 lishid. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -16,8 +16,6 @@
 | 
			
		||||
 | 
			
		||||
package com.lishid.openinv;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.HashMultimap;
 | 
			
		||||
import com.google.common.collect.Multimap;
 | 
			
		||||
import com.lishid.openinv.commands.ContainerSettingCommand;
 | 
			
		||||
import com.lishid.openinv.commands.OpenInvCommand;
 | 
			
		||||
import com.lishid.openinv.commands.SearchContainerCommand;
 | 
			
		||||
@@ -27,23 +25,19 @@ import com.lishid.openinv.internal.IAnySilentContainer;
 | 
			
		||||
import com.lishid.openinv.internal.ISpecialEnderChest;
 | 
			
		||||
import com.lishid.openinv.internal.ISpecialInventory;
 | 
			
		||||
import com.lishid.openinv.internal.ISpecialPlayerInventory;
 | 
			
		||||
import com.lishid.openinv.listeners.InventoryListener;
 | 
			
		||||
import com.lishid.openinv.listeners.PlayerListener;
 | 
			
		||||
import com.lishid.openinv.listeners.PluginListener;
 | 
			
		||||
import com.lishid.openinv.util.Cache;
 | 
			
		||||
import com.lishid.openinv.util.ConfigUpdater;
 | 
			
		||||
import com.lishid.openinv.util.InternalAccessor;
 | 
			
		||||
import com.lishid.openinv.util.LanguageManager;
 | 
			
		||||
import com.lishid.openinv.util.Permissions;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Future;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
import net.md_5.bungee.api.ChatMessageType;
 | 
			
		||||
import net.md_5.bungee.api.chat.TextComponent;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
@@ -56,10 +50,8 @@ import org.bukkit.entity.HumanEntity;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.bukkit.inventory.Inventory;
 | 
			
		||||
import org.bukkit.inventory.InventoryView;
 | 
			
		||||
import org.bukkit.plugin.Plugin;
 | 
			
		||||
import org.bukkit.plugin.PluginManager;
 | 
			
		||||
import org.bukkit.plugin.java.JavaPlugin;
 | 
			
		||||
import org.bukkit.scheduler.BukkitRunnable;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
@@ -70,63 +62,21 @@ import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 */
 | 
			
		||||
public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
 | 
			
		||||
    private final Map<String, ISpecialPlayerInventory> inventories = new HashMap<>();
 | 
			
		||||
    private final Map<String, ISpecialEnderChest> enderChests = new HashMap<>();
 | 
			
		||||
    private final Multimap<String, Class<? extends Plugin>> pluginUsage = HashMultimap.create();
 | 
			
		||||
 | 
			
		||||
    private final Cache<String, Player> playerCache = new Cache<>(300000L,
 | 
			
		||||
            value -> {
 | 
			
		||||
                String key = OpenInv.this.getPlayerID(value);
 | 
			
		||||
 | 
			
		||||
                return OpenInv.this.inventories.containsKey(key)
 | 
			
		||||
                        && OpenInv.this.inventories.get(key).isInUse()
 | 
			
		||||
                        || OpenInv.this.enderChests.containsKey(key)
 | 
			
		||||
                        && OpenInv.this.enderChests.get(key).isInUse()
 | 
			
		||||
                        || OpenInv.this.pluginUsage.containsKey(key);
 | 
			
		||||
            },
 | 
			
		||||
            value -> {
 | 
			
		||||
                String key = OpenInv.this.getPlayerID(value);
 | 
			
		||||
 | 
			
		||||
                // Check if inventory is stored, and if it is, remove it and eject all viewers
 | 
			
		||||
                if (OpenInv.this.inventories.containsKey(key)) {
 | 
			
		||||
                    Inventory inv = OpenInv.this.inventories.remove(key).getBukkitInventory();
 | 
			
		||||
                    List<HumanEntity> viewers = new ArrayList<>(inv.getViewers());
 | 
			
		||||
                    for (HumanEntity entity : viewers.toArray(new HumanEntity[0])) {
 | 
			
		||||
                        entity.closeInventory();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Check if ender chest is stored, and if it is, remove it and eject all viewers
 | 
			
		||||
                if (OpenInv.this.enderChests.containsKey(key)) {
 | 
			
		||||
                    Inventory inv = OpenInv.this.enderChests.remove(key).getBukkitInventory();
 | 
			
		||||
                    List<HumanEntity> viewers = new ArrayList<>(inv.getViewers());
 | 
			
		||||
                    for (HumanEntity entity : viewers.toArray(new HumanEntity[0])) {
 | 
			
		||||
                        entity.closeInventory();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!OpenInv.this.disableSaving() && !value.isOnline()) {
 | 
			
		||||
                    value.saveData();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
    private final Map<UUID, ISpecialPlayerInventory> inventories = new ConcurrentHashMap<>();
 | 
			
		||||
    private final Map<UUID, ISpecialEnderChest> enderChests = new ConcurrentHashMap<>();
 | 
			
		||||
 | 
			
		||||
    private InternalAccessor accessor;
 | 
			
		||||
    private LanguageManager languageManager;
 | 
			
		||||
    private boolean isSpigot = false;
 | 
			
		||||
    private OfflineHandler offlineHandler;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Evicts all viewers lacking cross-world permissions from a Player's inventory.
 | 
			
		||||
     * Evict all viewers lacking cross-world permissions from a Player's inventory.
 | 
			
		||||
     *
 | 
			
		||||
     * @param player the Player
 | 
			
		||||
     */
 | 
			
		||||
    public void changeWorld(final Player player) {
 | 
			
		||||
 | 
			
		||||
        String key = this.getPlayerID(player);
 | 
			
		||||
 | 
			
		||||
        // Check if the player is cached. If not, neither of their inventories is open.
 | 
			
		||||
        if (!this.playerCache.containsKey(key)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    void changeWorld(@NotNull Player player) {
 | 
			
		||||
        UUID key = player.getUniqueId();
 | 
			
		||||
 | 
			
		||||
        if (this.inventories.containsKey(key)) {
 | 
			
		||||
            kickCrossWorldViewers(player, this.inventories.get(key));
 | 
			
		||||
@@ -137,15 +87,19 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void kickCrossWorldViewers(Player player, ISpecialInventory inventory) {
 | 
			
		||||
        List<HumanEntity> viewers = new ArrayList<>(inventory.getBukkitInventory().getViewers());
 | 
			
		||||
        for (HumanEntity human : viewers) {
 | 
			
		||||
            // If player has permission or is in the same world, allow continued access
 | 
			
		||||
            // Just in case, also allow null worlds.
 | 
			
		||||
            if (Permissions.CROSSWORLD.hasPermission(human) || Objects.equals(human.getWorld(), player.getWorld())) {
 | 
			
		||||
                continue;
 | 
			
		||||
    private void kickCrossWorldViewers(@NotNull Player player, @NotNull ISpecialInventory inventory) {
 | 
			
		||||
        ejectViewers(
 | 
			
		||||
                inventory,
 | 
			
		||||
                viewer ->
 | 
			
		||||
                        !Permissions.CROSSWORLD.hasPermission(viewer)
 | 
			
		||||
                                && Objects.equals(viewer.getWorld(), player.getWorld()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void ejectViewers(@NotNull ISpecialInventory inventory, Predicate<HumanEntity> predicate) {
 | 
			
		||||
        for (HumanEntity viewer : new ArrayList<>(inventory.getBukkitInventory().getViewers())) {
 | 
			
		||||
            if (predicate.test(viewer)) {
 | 
			
		||||
                viewer.closeInventory();
 | 
			
		||||
            }
 | 
			
		||||
            human.closeInventory();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -159,7 +113,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
     * @param rawSlot the raw slot in the view
 | 
			
		||||
     * @return the converted slot number
 | 
			
		||||
     */
 | 
			
		||||
    public int convertToPlayerSlot(InventoryView view, int rawSlot) {
 | 
			
		||||
    int convertToPlayerSlot(InventoryView view, int rawSlot) {
 | 
			
		||||
        return this.accessor.getPlayerDataManager().convertToPlayerSlot(view, rawSlot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -168,9 +122,13 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
        return this.getConfig().getBoolean("settings.disable-saving", false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NotNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public IAnySilentContainer getAnySilentContainer() {
 | 
			
		||||
    public boolean disableOfflineAccess() {
 | 
			
		||||
        return this.getConfig().getBoolean("settings.disable-offline-access", false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @NotNull IAnySilentContainer getAnySilentContainer() {
 | 
			
		||||
        return this.accessor.getAnySilentContainer();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -202,31 +160,31 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
        return this.getConfig().getBoolean("toggles.silent-chest." + this.getPlayerID(offline), defaultState);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NotNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ISpecialEnderChest getSpecialEnderChest(@NotNull final Player player, final boolean online)
 | 
			
		||||
    public @NotNull ISpecialEnderChest getSpecialEnderChest(@NotNull final Player player, final boolean online)
 | 
			
		||||
            throws InstantiationException {
 | 
			
		||||
        String id = this.getPlayerID(player);
 | 
			
		||||
        if (this.enderChests.containsKey(id)) {
 | 
			
		||||
            return this.enderChests.get(id);
 | 
			
		||||
        UUID key = player.getUniqueId();
 | 
			
		||||
 | 
			
		||||
        if (this.enderChests.containsKey(key)) {
 | 
			
		||||
            return this.enderChests.get(key);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ISpecialEnderChest inv = this.accessor.newSpecialEnderChest(player, online);
 | 
			
		||||
        this.enderChests.put(id, inv);
 | 
			
		||||
        this.playerCache.put(id, player);
 | 
			
		||||
        this.enderChests.put(key, inv);
 | 
			
		||||
        return inv;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NotNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public ISpecialPlayerInventory getSpecialInventory(@NotNull final Player player, final boolean online)
 | 
			
		||||
    public @NotNull ISpecialPlayerInventory getSpecialInventory(@NotNull final Player player, final boolean online)
 | 
			
		||||
            throws InstantiationException {
 | 
			
		||||
        String id = this.getPlayerID(player);
 | 
			
		||||
        if (this.inventories.containsKey(id)) {
 | 
			
		||||
            return this.inventories.get(id);
 | 
			
		||||
        UUID key = player.getUniqueId();
 | 
			
		||||
 | 
			
		||||
        if (this.inventories.containsKey(key)) {
 | 
			
		||||
            return this.inventories.get(key);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ISpecialPlayerInventory inv = this.accessor.newSpecialPlayerInventory(player, online);
 | 
			
		||||
        this.inventories.put(id, inv);
 | 
			
		||||
        this.playerCache.put(id, player);
 | 
			
		||||
        this.inventories.put(key, inv);
 | 
			
		||||
        return inv;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -236,20 +194,28 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
 | 
			
		||||
    public boolean isPlayerLoaded(@NotNull UUID playerUuid) {
 | 
			
		||||
        return this.inventories.containsKey(playerUuid) || this.enderChests.containsKey(playerUuid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        String key = this.getPlayerID(offline);
 | 
			
		||||
        if (this.playerCache.containsKey(key)) {
 | 
			
		||||
            return this.playerCache.get(key);
 | 
			
		||||
    @Override
 | 
			
		||||
    public @Nullable Player loadPlayer(@NotNull final OfflinePlayer offline) {
 | 
			
		||||
        UUID key = offline.getUniqueId();
 | 
			
		||||
 | 
			
		||||
        if (this.inventories.containsKey(key)) {
 | 
			
		||||
            return (Player) this.inventories.get(key).getPlayer();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.enderChests.containsKey(key)) {
 | 
			
		||||
            return (Player) this.enderChests.get(key).getPlayer();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Player player = offline.getPlayer();
 | 
			
		||||
        if (player != null) {
 | 
			
		||||
            this.playerCache.put(key, player);
 | 
			
		||||
            return player;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.isSupportedVersion()) {
 | 
			
		||||
        if (disableOfflineAccess() || !this.isSupportedVersion()) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -267,10 +233,6 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (player != null) {
 | 
			
		||||
            this.playerCache.put(key, player);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return player;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -338,9 +300,23 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.isSupportedVersion()) {
 | 
			
		||||
            this.playerCache.invalidateAll();
 | 
			
		||||
        }
 | 
			
		||||
        Stream.concat(inventories.values().stream(), enderChests.values().stream())
 | 
			
		||||
                .map(inventory -> {
 | 
			
		||||
                    // Cheat a bit - rather than stream twice, evict all viewers during remapping.
 | 
			
		||||
                    ejectViewers(inventory, viewer -> true);
 | 
			
		||||
                    if (inventory.getPlayer() instanceof Player player) {
 | 
			
		||||
                        return player;
 | 
			
		||||
                    }
 | 
			
		||||
                    return null;
 | 
			
		||||
                })
 | 
			
		||||
                .filter(Objects::nonNull)
 | 
			
		||||
                .distinct()
 | 
			
		||||
                .forEach(player -> {
 | 
			
		||||
                    if (!player.isOnline()) {
 | 
			
		||||
                        player = accessor.getPlayerDataManager().inject(player);
 | 
			
		||||
                    }
 | 
			
		||||
                    player.saveData();
 | 
			
		||||
                });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -355,6 +331,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
        this.accessor = new InternalAccessor(this);
 | 
			
		||||
 | 
			
		||||
        this.languageManager = new LanguageManager(this, "en_us");
 | 
			
		||||
        this.offlineHandler = disableOfflineAccess() ? OfflineHandler.REMOVE_AND_CLOSE : OfflineHandler.REQUIRE_PERMISSIONS;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            Class.forName("org.bukkit.entity.Player$Spigot");
 | 
			
		||||
@@ -371,7 +348,6 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
 | 
			
		||||
            // Register listeners
 | 
			
		||||
            pm.registerEvents(new PlayerListener(this), this);
 | 
			
		||||
            pm.registerEvents(new PluginListener(this), this);
 | 
			
		||||
            pm.registerEvents(new InventoryListener(this), this);
 | 
			
		||||
 | 
			
		||||
            // Register commands to their executors
 | 
			
		||||
@@ -390,7 +366,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
    private void sendVersionError(Consumer<String> messageMethod) {
 | 
			
		||||
        if (!this.accessor.isSupported()) {
 | 
			
		||||
            messageMethod.accept("Your server version (" + this.accessor.getVersion() + ") is not supported.");
 | 
			
		||||
            messageMethod.accept("Please obtain an appropriate version here: " + this.accessor.getReleasesLink());
 | 
			
		||||
            messageMethod.accept("Please download the correct version of OpenInv here: " + this.accessor.getReleasesLink());
 | 
			
		||||
        }
 | 
			
		||||
        if (!isSpigot) {
 | 
			
		||||
            messageMethod.accept("OpenInv requires that you use Spigot or a Spigot fork. Per the 1.14 update thread");
 | 
			
		||||
@@ -418,40 +394,10 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void releaseAllPlayers(final Plugin plugin) {
 | 
			
		||||
        Iterator<Map.Entry<String, Class<? extends Plugin>>> iterator = this.pluginUsage.entries().iterator();
 | 
			
		||||
 | 
			
		||||
        if (!iterator.hasNext()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (Map.Entry<String, Class<? extends Plugin>> entry = iterator.next(); iterator.hasNext(); entry = iterator.next()) {
 | 
			
		||||
            if (entry.getValue().equals(plugin.getClass())) {
 | 
			
		||||
                iterator.remove();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void releasePlayer(@NotNull final Player player, @NotNull final Plugin plugin) {
 | 
			
		||||
        String key = this.getPlayerID(player);
 | 
			
		||||
 | 
			
		||||
        if (!this.pluginUsage.containsEntry(key, plugin.getClass())) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.pluginUsage.remove(key, plugin.getClass());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void retainPlayer(@NotNull final Player player, @NotNull final Plugin plugin) {
 | 
			
		||||
        String key = this.getPlayerID(player);
 | 
			
		||||
 | 
			
		||||
        if (this.pluginUsage.containsEntry(key, plugin.getClass())) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.pluginUsage.put(key, plugin.getClass());
 | 
			
		||||
    public void reloadConfig() {
 | 
			
		||||
        super.reloadConfig();
 | 
			
		||||
        this.offlineHandler = disableOfflineAccess() ? OfflineHandler.REMOVE_AND_CLOSE : OfflineHandler.REQUIRE_PERMISSIONS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -464,27 +410,71 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
     * Method for handling a Player going offline.
 | 
			
		||||
     *
 | 
			
		||||
     * @param player the Player
 | 
			
		||||
     * @throws IllegalStateException if the server version is unsupported
 | 
			
		||||
     */
 | 
			
		||||
    public void setPlayerOffline(final Player player) {
 | 
			
		||||
    void setPlayerOffline(@NotNull Player player) {
 | 
			
		||||
        setPlayerOffline(player, offlineHandler);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        String key = this.getPlayerID(player);
 | 
			
		||||
    private void setPlayerOffline(@NotNull OfflinePlayer player, @NotNull OfflineHandler handler) {
 | 
			
		||||
        UUID key = player.getUniqueId();
 | 
			
		||||
 | 
			
		||||
        // Check if the player is cached. If not, neither of their inventories is open.
 | 
			
		||||
        if (!this.playerCache.containsKey(key)) {
 | 
			
		||||
        setPlayerOffline(inventories, key, handler);
 | 
			
		||||
        setPlayerOffline(enderChests, key, handler);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setPlayerOffline(
 | 
			
		||||
            @NotNull Map<UUID, ? extends ISpecialInventory> map,
 | 
			
		||||
            @NotNull UUID key,
 | 
			
		||||
            @NotNull OfflineHandler handler) {
 | 
			
		||||
        ISpecialInventory inventory = handler.fetch().apply(map, key);
 | 
			
		||||
        if (inventory == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        inventory.setPlayerOffline();
 | 
			
		||||
        if (!inventory.isInUse()) {
 | 
			
		||||
            map.remove(key);
 | 
			
		||||
        } else {
 | 
			
		||||
            handler.handle().accept(inventory);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void handleCloseInventory(@NotNull HumanEntity exViewer, @NotNull ISpecialInventory inventory) {
 | 
			
		||||
        Map<UUID, ? extends ISpecialInventory> map = inventory instanceof ISpecialPlayerInventory ? inventories : enderChests;
 | 
			
		||||
        UUID key = inventory.getPlayer().getUniqueId();
 | 
			
		||||
        @Nullable ISpecialInventory loaded = map.get(key);
 | 
			
		||||
 | 
			
		||||
        if (loaded == null) {
 | 
			
		||||
            // Loaded inventory has already been removed. Removal will handle saving if necessary.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Replace stored player with our own version
 | 
			
		||||
        this.playerCache.put(key, this.accessor.getPlayerDataManager().inject(player));
 | 
			
		||||
 | 
			
		||||
        if (this.inventories.containsKey(key)) {
 | 
			
		||||
            this.inventories.get(key).setPlayerOffline();
 | 
			
		||||
        if (loaded != inventory) {
 | 
			
		||||
            Inventory bukkitInventory = inventory.getBukkitInventory();
 | 
			
		||||
            // Just in case, respect contents of the inventory that was just used.
 | 
			
		||||
            loaded.getBukkitInventory().setContents(bukkitInventory.getContents());
 | 
			
		||||
            // We need to close this inventory to reduce risk of duplication bugs if the user is offline.
 | 
			
		||||
            // We don't want to risk recursively closing the same inventory repeatedly, so we schedule dumping viewers.
 | 
			
		||||
            // Worst case we schedule a couple redundant tasks if several people had the inventory open.
 | 
			
		||||
            if (!bukkitInventory.getViewers().isEmpty()) {
 | 
			
		||||
                getServer().getScheduler().runTask(this, () -> ejectViewers(inventory, viewer -> true));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.enderChests.containsKey(key)) {
 | 
			
		||||
            this.enderChests.get(key).setPlayerOffline();
 | 
			
		||||
        }
 | 
			
		||||
        // Schedule task to check in use status later this tick. Closing user is still in viewer list.
 | 
			
		||||
        getServer().getScheduler().runTask(this, () -> {
 | 
			
		||||
            if (loaded.isInUse()) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Re-fetch from map - prevents duplicate saves on multi-close.
 | 
			
		||||
            ISpecialInventory current = map.remove(key);
 | 
			
		||||
 | 
			
		||||
            if (!disableSaving()
 | 
			
		||||
                    && current != null
 | 
			
		||||
                    && current.getPlayer() instanceof Player player && !player.isOnline()) {
 | 
			
		||||
                this.accessor.getPlayerDataManager().inject(player).saveData();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -493,31 +483,34 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
     * @param player the Player
 | 
			
		||||
     * @throws IllegalStateException if the server version is unsupported
 | 
			
		||||
     */
 | 
			
		||||
    public void setPlayerOnline(final Player player) {
 | 
			
		||||
    void setPlayerOnline(@NotNull Player player) {
 | 
			
		||||
        setPlayerOnline(inventories, player, player::updateInventory);
 | 
			
		||||
        setPlayerOnline(enderChests, player, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        String key = this.getPlayerID(player);
 | 
			
		||||
    private void setPlayerOnline(
 | 
			
		||||
            @NotNull Map<UUID, ? extends ISpecialInventory> map,
 | 
			
		||||
            @NotNull Player player,
 | 
			
		||||
            @Nullable Runnable task) {
 | 
			
		||||
        ISpecialInventory inventory = map.get(player.getUniqueId());
 | 
			
		||||
 | 
			
		||||
        // Check if the player is cached. If not, neither of their inventories is open.
 | 
			
		||||
        if (!this.playerCache.containsKey(key)) {
 | 
			
		||||
        if (inventory == null) {
 | 
			
		||||
            // Inventory not open.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.playerCache.put(key, player);
 | 
			
		||||
        inventory.setPlayerOnline(player);
 | 
			
		||||
 | 
			
		||||
        if (this.inventories.containsKey(key)) {
 | 
			
		||||
            this.inventories.get(key).setPlayerOnline(player);
 | 
			
		||||
            new BukkitRunnable() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void run() {
 | 
			
		||||
                    if (player.isOnline()) {
 | 
			
		||||
                        player.updateInventory();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }.runTask(this);
 | 
			
		||||
        }
 | 
			
		||||
        // Eject viewers lacking permission.
 | 
			
		||||
        ejectViewers(
 | 
			
		||||
                inventory,
 | 
			
		||||
                viewer ->
 | 
			
		||||
                        !Permissions.OPENONLINE.hasPermission(viewer)
 | 
			
		||||
                                || !Permissions.CROSSWORLD.hasPermission(viewer)
 | 
			
		||||
                                && !Objects.equals(viewer.getWorld(), inventory.getPlayer().getWorld()));
 | 
			
		||||
 | 
			
		||||
        if (this.enderChests.containsKey(key)) {
 | 
			
		||||
            this.enderChests.get(key).setPlayerOnline(player);
 | 
			
		||||
        if (task != null) {
 | 
			
		||||
            getServer().getScheduler().runTask(this, task);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -529,7 +522,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unload(@NotNull final OfflinePlayer offline) {
 | 
			
		||||
        this.playerCache.invalidate(this.getPlayerID(offline));
 | 
			
		||||
        setPlayerOffline(offline, OfflineHandler.REMOVE_AND_CLOSE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,22 +32,22 @@ import org.jetbrains.annotations.NotNull;
 | 
			
		||||
record PlayerListener(OpenInv plugin) implements Listener {
 | 
			
		||||
 | 
			
		||||
    @EventHandler(priority = EventPriority.LOWEST)
 | 
			
		||||
    public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
 | 
			
		||||
    private void onPlayerJoin(@NotNull PlayerJoinEvent event) {
 | 
			
		||||
        plugin.setPlayerOnline(event.getPlayer());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @EventHandler(priority = EventPriority.MONITOR)
 | 
			
		||||
    public void onPlayerQuit(@NotNull PlayerQuitEvent event) {
 | 
			
		||||
    private void onPlayerQuit(@NotNull PlayerQuitEvent event) {
 | 
			
		||||
        plugin.setPlayerOffline(event.getPlayer());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @EventHandler
 | 
			
		||||
    public void onWorldChange(@NotNull PlayerChangedWorldEvent event) {
 | 
			
		||||
    private void onWorldChange(@NotNull PlayerChangedWorldEvent event) {
 | 
			
		||||
        plugin.changeWorld(event.getPlayer());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
 | 
			
		||||
    public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
 | 
			
		||||
    private void onPlayerInteract(@NotNull PlayerInteractEvent event) {
 | 
			
		||||
 | 
			
		||||
        // Do not cancel 3rd party plugins' custom events
 | 
			
		||||
        if (!PlayerInteractEvent.class.equals(event.getClass())) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2011-2021 lishid. All rights reserved.
 | 
			
		||||
 * Copyright (C) 2011-2022 lishid. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -133,7 +133,7 @@ public class OpenInvCommand implements TabExecutor {
 | 
			
		||||
        boolean online = target.isOnline();
 | 
			
		||||
 | 
			
		||||
        if (!online) {
 | 
			
		||||
            if (Permissions.OPENOFFLINE.hasPermission(player)) {
 | 
			
		||||
            if (!plugin.disableOfflineAccess() && Permissions.OPENOFFLINE.hasPermission(player)) {
 | 
			
		||||
                // Try loading the player's data
 | 
			
		||||
                onlineTarget = this.plugin.loadPlayer(target);
 | 
			
		||||
            } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2011-2021 lishid. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, version 3.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.lishid.openinv.listeners;
 | 
			
		||||
 | 
			
		||||
import com.lishid.openinv.OpenInv;
 | 
			
		||||
import org.bukkit.event.EventHandler;
 | 
			
		||||
import org.bukkit.event.Listener;
 | 
			
		||||
import org.bukkit.event.server.PluginDisableEvent;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Listener for plugin-related events.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Jikoo
 | 
			
		||||
 */
 | 
			
		||||
public class PluginListener implements Listener {
 | 
			
		||||
 | 
			
		||||
    private final OpenInv plugin;
 | 
			
		||||
 | 
			
		||||
    public PluginListener(OpenInv plugin) {
 | 
			
		||||
        this.plugin = plugin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @EventHandler
 | 
			
		||||
    public void onPluginDisable(PluginDisableEvent event) {
 | 
			
		||||
        plugin.releaseAllPlayers(event.getPlugin());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,187 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2011-2021 lishid. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, version 3.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.lishid.openinv.util;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Multimap;
 | 
			
		||||
import com.google.common.collect.TreeMultimap;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A minimal thread-safe time-based cache implementation backed by a HashMap and TreeMultimap.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Jikoo
 | 
			
		||||
 */
 | 
			
		||||
public class Cache<K, V> {
 | 
			
		||||
 | 
			
		||||
    private final Map<K, V> internal;
 | 
			
		||||
    private final Multimap<Long, K> expiry;
 | 
			
		||||
    private final long retention;
 | 
			
		||||
    private final Predicate<V> inUseCheck;
 | 
			
		||||
    private final Consumer<V> postRemoval;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a Cache with the specified retention duration, in use function, and post-removal function.
 | 
			
		||||
     *
 | 
			
		||||
     * @param retention duration after which keys are automatically invalidated if not in use
 | 
			
		||||
     * @param inUseCheck Predicate used to check if a key is considered in use
 | 
			
		||||
     * @param postRemoval Consumer used to perform any operations required when a key is invalidated
 | 
			
		||||
     */
 | 
			
		||||
    public Cache(final long retention, final Predicate<V> inUseCheck, final Consumer<V> postRemoval) {
 | 
			
		||||
        this.internal = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
        this.expiry = TreeMultimap.create(Long::compareTo, (k1, k2) -> Objects.equals(k1, k2) ? 0 : 1);
 | 
			
		||||
 | 
			
		||||
        this.retention = retention;
 | 
			
		||||
        this.inUseCheck = inUseCheck;
 | 
			
		||||
        this.postRemoval = postRemoval;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set a key and value pair. Keys are unique. Using an existing key will cause the old value to
 | 
			
		||||
     * be overwritten and the expiration timer to be reset.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key key with which the specified value is to be associated
 | 
			
		||||
     * @param value value to be associated with the specified key
 | 
			
		||||
     */
 | 
			
		||||
    public void put(final K key, final V value) {
 | 
			
		||||
        // Invalidate key - runs lazy check and ensures value won't be cleaned up early
 | 
			
		||||
        this.invalidate(key);
 | 
			
		||||
 | 
			
		||||
        synchronized (this.internal) {
 | 
			
		||||
            this.internal.put(key, value);
 | 
			
		||||
            this.expiry.put(System.currentTimeMillis() + this.retention, key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the value to which the specified key is mapped, or null if no value is mapped for the key.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key the key whose associated value is to be returned
 | 
			
		||||
     * @return the value to which the specified key is mapped, or null if no value is mapped for the key
 | 
			
		||||
     */
 | 
			
		||||
    public V get(final K key) {
 | 
			
		||||
        // Run lazy check to clean cache
 | 
			
		||||
        this.lazyCheck();
 | 
			
		||||
 | 
			
		||||
        synchronized (this.internal) {
 | 
			
		||||
            return this.internal.get(key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns true if the specified key is mapped to a value.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key key to check if a mapping exists for
 | 
			
		||||
     * @return true if a mapping exists for the specified key
 | 
			
		||||
     */
 | 
			
		||||
    public boolean containsKey(final K key) {
 | 
			
		||||
        // Run lazy check to clean cache
 | 
			
		||||
        this.lazyCheck();
 | 
			
		||||
 | 
			
		||||
        synchronized (this.internal) {
 | 
			
		||||
            return this.internal.containsKey(key);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forcibly invalidates a key, even if it is considered to be in use.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key key to invalidate
 | 
			
		||||
     */
 | 
			
		||||
    public void invalidate(final K key) {
 | 
			
		||||
        // Run lazy check to clean cache
 | 
			
		||||
        this.lazyCheck();
 | 
			
		||||
 | 
			
		||||
        synchronized (this.internal) {
 | 
			
		||||
            if (!this.internal.containsKey(key)) {
 | 
			
		||||
                // Value either not present or cleaned by lazy check. Either way, we're good
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Remove stored object
 | 
			
		||||
            this.internal.remove(key);
 | 
			
		||||
 | 
			
		||||
            // Remove expiration entry - prevents more work later, plus prevents issues with values invalidating early
 | 
			
		||||
            for (Iterator<Map.Entry<Long, K>> iterator = this.expiry.entries().iterator(); iterator.hasNext();) {
 | 
			
		||||
                if (key.equals(iterator.next().getValue())) {
 | 
			
		||||
                    iterator.remove();
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forcibly invalidates all keys, even if they are considered to be in use.
 | 
			
		||||
     */
 | 
			
		||||
    public void invalidateAll() {
 | 
			
		||||
        synchronized (this.internal) {
 | 
			
		||||
            for (V value : this.internal.values()) {
 | 
			
		||||
                this.postRemoval.accept(value);
 | 
			
		||||
            }
 | 
			
		||||
            this.expiry.clear();
 | 
			
		||||
            this.internal.clear();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidate all expired keys that are not considered in use. If a key is expired but is
 | 
			
		||||
     * considered in use by the provided Function, its expiration time is reset.
 | 
			
		||||
     */
 | 
			
		||||
    private void lazyCheck() {
 | 
			
		||||
        long now = System.currentTimeMillis();
 | 
			
		||||
        synchronized (this.internal) {
 | 
			
		||||
            List<K> inUse = new ArrayList<>();
 | 
			
		||||
            for (Iterator<Map.Entry<Long, K>> iterator = this.expiry.entries().iterator(); iterator
 | 
			
		||||
                    .hasNext();) {
 | 
			
		||||
                Map.Entry<Long, K> entry = iterator.next();
 | 
			
		||||
 | 
			
		||||
                if (entry.getKey() > now) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                iterator.remove();
 | 
			
		||||
 | 
			
		||||
                if (this.inUseCheck.test(this.internal.get(entry.getValue()))) {
 | 
			
		||||
                    inUse.add(entry.getValue());
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                V value = this.internal.remove(entry.getValue());
 | 
			
		||||
 | 
			
		||||
                if (value == null) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.postRemoval.accept(value);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            long nextExpiry = now + this.retention;
 | 
			
		||||
            for (K value : inUse) {
 | 
			
		||||
                this.expiry.put(nextExpiry, value);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2011-2021 lishid. All rights reserved.
 | 
			
		||||
 * Copyright (C) 2011-2022 lishid. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -25,13 +25,7 @@ import java.util.Set;
 | 
			
		||||
import org.bukkit.OfflinePlayer;
 | 
			
		||||
import org.bukkit.configuration.ConfigurationSection;
 | 
			
		||||
 | 
			
		||||
public class ConfigUpdater {
 | 
			
		||||
 | 
			
		||||
    private final OpenInv plugin;
 | 
			
		||||
 | 
			
		||||
    public ConfigUpdater(OpenInv plugin) {
 | 
			
		||||
        this.plugin = plugin;
 | 
			
		||||
    }
 | 
			
		||||
public record ConfigUpdater(OpenInv plugin) {
 | 
			
		||||
 | 
			
		||||
    public void checkForUpdates() {
 | 
			
		||||
        final int version = plugin.getConfig().getInt("config-version", 1);
 | 
			
		||||
@@ -60,6 +54,9 @@ public class ConfigUpdater {
 | 
			
		||||
            if (version < 4) {
 | 
			
		||||
                updateConfig3To4();
 | 
			
		||||
            }
 | 
			
		||||
            if (version < 5) {
 | 
			
		||||
                updateConfig4To5();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            plugin.getServer().getScheduler().runTask(plugin, () -> {
 | 
			
		||||
                plugin.saveConfig();
 | 
			
		||||
@@ -68,6 +65,13 @@ public class ConfigUpdater {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateConfig4To5() {
 | 
			
		||||
        plugin.getServer().getScheduler().runTask(plugin, () -> {
 | 
			
		||||
            plugin.getConfig().set("settings.disable-offline-access", false);
 | 
			
		||||
            plugin.getConfig().set("config-version", 5);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void updateConfig3To4() {
 | 
			
		||||
        plugin.getServer().getScheduler().runTask(plugin, () -> {
 | 
			
		||||
            plugin.getConfig().set("notify", null);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2011-2021 lishid. All rights reserved.
 | 
			
		||||
 * Copyright (C) 2011-2022 lishid. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -17,6 +17,7 @@
 | 
			
		||||
package com.lishid.openinv.util;
 | 
			
		||||
 | 
			
		||||
import org.bukkit.permissions.Permissible;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
public enum Permissions {
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +51,7 @@ public enum Permissions {
 | 
			
		||||
        this.uninheritable = uninheritable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean hasPermission(Permissible permissible) {
 | 
			
		||||
    public boolean hasPermission(@NotNull Permissible permissible) {
 | 
			
		||||
 | 
			
		||||
        boolean hasPermission = permissible.hasPermission(permission);
 | 
			
		||||
        if (uninheritable || hasPermission || permissible.isPermissionSet(permission)) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user