Allow plugins to indicate to OpenInv that a Player is in use

This allows API users to prevent issues caused by multiple different copies of the Player being loaded, such as #49. Multiple instances of the same player could be obtained by calling IOpenInv#loadPlayer, waiting for OpenInv to remove it from the cache, then calling the method again.
This commit is contained in:
Jikoo 2016-12-14 19:49:18 -05:00
parent 802ce28103
commit d24827ffcb
3 changed files with 73 additions and 2 deletions

View file

@ -7,6 +7,7 @@ import com.lishid.openinv.internal.ISpecialPlayerInventory;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
/**
* Interface defining behavior for the OpenInv plugin.
@ -150,4 +151,37 @@ public interface IOpenInv {
*/
public Player loadPlayer(final OfflinePlayer offline);
/**
* Mark a Player as in use by a Plugin to prevent it from being removed from the cache. Used to
* prevent issues with multiple copies of the same Player being loaded such as lishid#49.
* Changes made to loaded copies overwrite changes to the others when saved, leading to
* duplication bugs and more.
* <p>
* When finished with the Player object, be sure to call {@link #releasePlayer(Player, Plugin)}
* to prevent the cache from keeping it stored until the plugin is disabled.
* <p>
* When using a Player object from OpenInv, you must handle the Player coming online, replacing
* your Player reference with the Player from the PlayerJoinEvent. In addition, you must change
* any values in the Player to reflect any unsaved alterations to the existing Player which do
* not affect the inventory or ender chest contents.
* <p>
* OpenInv only saves player data when unloading a Player from the cache, and then only if
* {@link #disableSaving()} returns false. If you are making changes that OpenInv does not cause
* to persist when a Player logs in as noted above, it is suggested that you manually call
* {@link Player#saveData()} when releasing your reference to ensure your changes persist.
*
* @param player the Player
* @param plugin the Plugin holding the reference to the Player
*/
public void retainPlayer(Player player, Plugin plugin);
/**
* Mark a Player as no longer in use by a Plugin to allow OpenInv to remove it from the cache
* when eligible.
*
* @param player the Player
* @param plugin the Plugin no longer holding a reference to the Player
*/
public void releasePlayer(Player player, Plugin plugin);
}

View file

@ -44,12 +44,16 @@ import com.lishid.openinv.util.Function;
import com.lishid.openinv.util.InternalAccessor;
import com.lishid.openinv.util.Permissions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
@ -63,13 +67,17 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
private final Map<String, ISpecialPlayerInventory> inventories = new HashMap<String, ISpecialPlayerInventory>();
private final Map<String, ISpecialEnderChest> enderChests = new HashMap<String, ISpecialEnderChest>();
// TODO: handle plugin unload
private final Multimap<String, Class<? extends Plugin>> pluginUsage = HashMultimap.create();
private final Cache<String, Player> playerCache = new Cache<String, Player>(300000L,
new Function<Player>() {
@Override
public boolean run(Player value) {
String key = playerLoader.getPlayerDataID(value);
return inventories.containsKey(key) && inventories.get(key).isInUse()
|| enderChests.containsKey(key) && enderChests.get(key).isInUse();
|| enderChests.containsKey(key) && enderChests.get(key).isInUse()
|| pluginUsage.containsKey(key);
}
},
new Function<Player>() {
@ -427,6 +435,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
return this.playerCache.get(key);
}
// TODO: wrap Player to ensure all methods can safely be called offline
Player loaded;
if (offline.isOnline()) {
@ -479,6 +488,34 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
return loaded;
}
/**
* @see com.lishid.openinv.IOpenInv#retainPlayer(org.bukkit.entity.Player, org.bukkit.plugin.Plugin)
*/
@Override
public void retainPlayer(Player player, Plugin plugin) {
String key = this.playerLoader.getPlayerDataID(player);
if (this.pluginUsage.containsEntry(key, plugin.getClass())) {
return;
}
this.pluginUsage.put(key, plugin.getClass());
}
/**
* @see com.lishid.openinv.IOpenInv#releasePlayer(org.bukkit.entity.Player, org.bukkit.plugin.Plugin)
*/
@Override
public void releasePlayer(Player player, Plugin plugin) {
String key = this.playerLoader.getPlayerDataID(player);
if (!this.pluginUsage.containsEntry(key, plugin.getClass())) {
return;
}
this.pluginUsage.remove(key, plugin.getClass());
}
/**
* Method for handling a Player coming online.
*

View file

@ -12,7 +12,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<openinv.version>3.0.3-SNAPSHOT</openinv.version>
<openinv.version>3.0.4-SNAPSHOT</openinv.version>
</properties>
<modules>