From aaeaf19fc8e56d94a03f1c0511944d2e3e228f4d Mon Sep 17 00:00:00 2001 From: sk89q Date: Sun, 27 Jul 2014 20:44:05 -0700 Subject: [PATCH] Rewrite session code and add support for persistent sessions. --- .../worldedit/bukkit/BukkitCommandSender.java | 42 +- .../sk89q/worldedit/bukkit/BukkitPlayer.java | 53 +++ .../worldedit/bukkit/WorldEditListener.java | 15 - .../worldedit/bukkit/WorldEditPlugin.java | 3 - .../sk89q/worldedit/forge/ForgePlayer.java | 65 +++ .../sk89q/worldedit/forge/ForgeWorldEdit.java | 11 +- .../worldedit/forge/ThreadSafeCache.java | 91 +++++ .../com/sk89q/worldedit/LocalSession.java | 371 +++++++++++------- .../java/com/sk89q/worldedit/WorldEdit.java | 35 -- .../worldedit/command/WorldEditCommands.java | 2 + .../platform/ConfigurationLoadEvent.java | 53 +++ .../platform/PlatformInitializeEvent.java} | 16 +- .../platform/AbstractPlayerActor.java | 6 +- .../worldedit/extension/platform/Actor.java | 31 +- .../extension/platform/PlatformManager.java | 31 +- .../extension/platform/PlayerProxy.java | 13 + .../session/MissingSessionException.java | 42 ++ .../sk89q/worldedit/session/SessionKey.java | 62 +++ .../worldedit/session/SessionManager.java | 288 ++++++++++---- .../sk89q/worldedit/session/SessionOwner.java | 37 ++ .../session/TransientSessionException.java | 42 ++ .../session/storage/JsonFileSessionStore.java | 135 +++++++ .../session/storage/SessionStore.java | 57 +++ .../worldedit/session/storage/VoidStore.java} | 31 +- .../Identifiable.java} | 73 ++-- .../worldedit/util/YAMLConfiguration.java | 3 +- .../util/auth/AuthorizationException.java | 43 ++ .../sk89q/worldedit/util/auth/Subject.java | 51 +++ .../util/concurrency/EvenMoreExecutors.java | 54 +++ .../sk89q/worldedit/util/gson/GsonUtil.java | 44 +++ 30 files changed, 1418 insertions(+), 382 deletions(-) create mode 100644 src/forge/java/com/sk89q/worldedit/forge/ThreadSafeCache.java create mode 100644 src/main/java/com/sk89q/worldedit/event/platform/ConfigurationLoadEvent.java rename src/main/java/com/sk89q/worldedit/{WorldEditPermissionException.java => event/platform/PlatformInitializeEvent.java} (75%) create mode 100644 src/main/java/com/sk89q/worldedit/session/MissingSessionException.java create mode 100644 src/main/java/com/sk89q/worldedit/session/SessionKey.java create mode 100644 src/main/java/com/sk89q/worldedit/session/SessionOwner.java create mode 100644 src/main/java/com/sk89q/worldedit/session/TransientSessionException.java create mode 100644 src/main/java/com/sk89q/worldedit/session/storage/JsonFileSessionStore.java create mode 100644 src/main/java/com/sk89q/worldedit/session/storage/SessionStore.java rename src/{bukkit/java/com/sk89q/worldedit/bukkit/SessionTimer.java => main/java/com/sk89q/worldedit/session/storage/VoidStore.java} (53%) rename src/main/java/com/sk89q/worldedit/{SessionCheck.java => util/Identifiable.java} (75%) create mode 100644 src/main/java/com/sk89q/worldedit/util/auth/AuthorizationException.java create mode 100644 src/main/java/com/sk89q/worldedit/util/auth/Subject.java create mode 100644 src/main/java/com/sk89q/worldedit/util/concurrency/EvenMoreExecutors.java create mode 100644 src/main/java/com/sk89q/worldedit/util/gson/GsonUtil.java diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java index 74dfc2e41..df7fdd520 100644 --- a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java +++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java @@ -19,19 +19,27 @@ package com.sk89q.worldedit.bukkit; -import com.sk89q.worldedit.WorldEditPermissionException; +import com.sk89q.worldedit.session.SessionKey; +import com.sk89q.worldedit.util.auth.AuthorizationException; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.internal.cui.CUIEvent; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import javax.annotation.Nullable; import java.io.File; +import java.util.UUID; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; public class BukkitCommandSender implements Actor { + /** + * One time generated ID. + */ + private static final UUID DEFAULT_ID = UUID.fromString("a233eb4b-4cab-42cd-9fd9-7e7b9a3f74be"); + private CommandSender sender; private WorldEditPlugin plugin; @@ -44,6 +52,11 @@ public class BukkitCommandSender implements Actor { this.sender = sender; } + @Override + public UUID getUniqueId() { + return DEFAULT_ID; + } + @Override public String getName() { return sender.getName(); @@ -93,7 +106,7 @@ public class BukkitCommandSender implements Actor { } @Override - public void checkPermission(String permission) throws WorldEditPermissionException { + public void checkPermission(String permission) throws AuthorizationException { } @Override @@ -115,4 +128,29 @@ public class BukkitCommandSender implements Actor { public void dispatchCUIEvent(CUIEvent event) { } + @Override + public SessionKey getSessionKey() { + return new SessionKey() { + @Nullable + @Override + public String getName() { + return null; + } + + @Override + public boolean isActive() { + return false; + } + + @Override + public boolean isPersistent() { + return false; + } + + @Override + public UUID getUniqueId() { + return DEFAULT_ID; + } + }; + } } diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java index 519aee885..25f02915b 100644 --- a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java +++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java @@ -30,14 +30,18 @@ import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.internal.cui.CUIEvent; +import com.sk89q.worldedit.session.SessionKey; +import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import javax.annotation.Nullable; +import java.util.UUID; public class BukkitPlayer extends LocalPlayer { + private Player player; private WorldEditPlugin plugin; @@ -46,6 +50,11 @@ public class BukkitPlayer extends LocalPlayer { this.player = player; } + @Override + public UUID getUniqueId() { + return player.getUniqueId(); + } + @Override public int getItemInHand() { ItemStack itemStack = player.getItemInHand(); @@ -192,4 +201,48 @@ public class BukkitPlayer extends LocalPlayer { public T getFacet(Class cls) { return null; } + + @Override + public SessionKey getSessionKey() { + return new SessionKeyImpl(this.player.getUniqueId(), player.getName()); + } + + private static class SessionKeyImpl implements SessionKey { + // If not static, this will leak a reference + + private final UUID uuid; + private final String name; + + private SessionKeyImpl(UUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + @Override + public UUID getUniqueId() { + return uuid; + } + + @Nullable + @Override + public String getName() { + return name; + } + + @Override + public boolean isActive() { + // This is a thread safe call on CraftBukkit because it uses a + // CopyOnWrite list for the list of players, but the Bukkit + // specification doesn't require thread safety (though the + // spec is extremely incomplete) + return Bukkit.getServer().getPlayerExact(name) != null; + } + + @Override + public boolean isPersistent() { + return true; + } + + } + } diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java index 91a7adcda..4b212f7f0 100644 --- a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java +++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java @@ -37,7 +37,6 @@ import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerGameModeChangeEvent; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; /** * Handles all events thrown in relation to a Player @@ -62,20 +61,6 @@ public class WorldEditListener implements Listener { this.plugin = plugin; } - /** - * Called when a player leaves a server - * - * @param event Relevant event details - */ - @EventHandler - public void onPlayerQuit(PlayerQuitEvent event) { - if (!plugin.getInternalPlatform().isHookingEvents()) { - return; - } - - plugin.getWorldEdit().markExpire(plugin.wrapPlayer(event.getPlayer())); - } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onGamemode(PlayerGameModeChangeEvent event) { if (!plugin.getInternalPlatform().isHookingEvents()) { diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index 0a804208d..26647fcc8 100644 --- a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -110,9 +110,6 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter { // Now we can register events getServer().getPluginManager().registerEvents(new WorldEditListener(this), this); - // Register session timer - getServer().getScheduler().runTaskTimerAsynchronously(this, new SessionTimer(worldEdit, getServer()), 120, 120); - // If we are on MCPC+/Cauldron, then Forge will have already loaded // Forge WorldEdit and there's (probably) not going to be any other // platforms to be worried about... at the current time of writing diff --git a/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java b/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java index 327b03602..da592ba8f 100644 --- a/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java +++ b/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java @@ -27,6 +27,7 @@ import com.sk89q.worldedit.extension.platform.AbstractPlayerActor; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.internal.LocalWorldAdapter; import com.sk89q.worldedit.internal.cui.CUIEvent; +import com.sk89q.worldedit.session.SessionKey; import com.sk89q.worldedit.util.Location; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; @@ -34,6 +35,7 @@ import net.minecraft.network.packet.Packet250CustomPayload; import net.minecraft.util.ChatMessageComponent; import javax.annotation.Nullable; +import java.util.UUID; public class ForgePlayer extends AbstractPlayerActor { @@ -41,13 +43,21 @@ public class ForgePlayer extends AbstractPlayerActor { protected ForgePlayer(EntityPlayerMP player) { this.player = player; + ThreadSafeCache.getInstance().getOnlineIds().add(getUniqueId()); } + @Override + public UUID getUniqueId() { + return player.getUniqueID(); + } + + @Override public int getItemInHand() { ItemStack is = this.player.getCurrentEquippedItem(); return is == null ? 0 : is.itemID; } + @Override public String getName() { return this.player.username; } @@ -67,26 +77,32 @@ public class ForgePlayer extends AbstractPlayerActor { this.player.cameraPitch); } + @Override public WorldVector getPosition() { return new WorldVector(LocalWorldAdapter.adapt(ForgeWorldEdit.inst.getWorld(this.player.worldObj)), this.player.posX, this.player.posY, this.player.posZ); } + @Override public com.sk89q.worldedit.world.World getWorld() { return ForgeWorldEdit.inst.getWorld(this.player.worldObj); } + @Override public double getPitch() { return this.player.rotationPitch; } + @Override public double getYaw() { return this.player.rotationYaw; } + @Override public void giveItem(int type, int amt) { this.player.inventory.addItemStackToInventory(new ItemStack(type, amt, 0)); } + @Override public void dispatchCUIEvent(CUIEvent event) { String[] params = event.getParameters(); String send = event.getTypeId(); @@ -97,42 +113,50 @@ public class ForgePlayer extends AbstractPlayerActor { this.player.playerNetServerHandler.sendPacketToPlayer(packet); } + @Override public void printRaw(String msg) { for (String part : msg.split("\n")) { this.player.sendChatToPlayer(ChatMessageComponent.createFromText(part)); } } + @Override public void printDebug(String msg) { for (String part : msg.split("\n")) { this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a77" + part)); } } + @Override public void print(String msg) { for (String part : msg.split("\n")) { this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a7d" + part)); } } + @Override public void printError(String msg) { for (String part : msg.split("\n")) { this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a7c" + part)); } } + @Override public void setPosition(Vector pos, float pitch, float yaw) { this.player.playerNetServerHandler.setPlayerLocation(pos.getX(), pos.getY(), pos.getZ(), pitch, yaw); } + @Override public String[] getGroups() { return new String[]{}; // WorldEditMod.inst.getPermissionsResolver().getGroups(this.player.username); } + @Override public BlockBag getInventoryBlockBag() { return null; } + @Override public boolean hasPermission(String perm) { return ForgeUtil.hasPermission(this.player, perm); } @@ -143,4 +167,45 @@ public class ForgePlayer extends AbstractPlayerActor { return null; } + @Override + public SessionKey getSessionKey() { + return new SessionKeyImpl(player.getUniqueID(), player.username); + } + + private static class SessionKeyImpl implements SessionKey { + // If not static, this will leak a reference + + private final UUID uuid; + private final String name; + + private SessionKeyImpl(UUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + @Override + public UUID getUniqueId() { + return uuid; + } + + @Nullable + @Override + public String getName() { + return name; + } + + @Override + public boolean isActive() { + // We can't directly check if the player is online because + // the list of players is not thread safe + return ThreadSafeCache.getInstance().getOnlineIds().contains(uuid); + } + + @Override + public boolean isPersistent() { + return true; + } + + } + } \ No newline at end of file diff --git a/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java b/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java index 6b706a08f..d0d6f0104 100644 --- a/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java +++ b/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java @@ -32,8 +32,15 @@ import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod.EventHandler; import cpw.mods.fml.common.Mod.Instance; -import cpw.mods.fml.common.event.*; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLPostInitializationEvent; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.event.FMLServerAboutToStartEvent; +import cpw.mods.fml.common.event.FMLServerStartedEvent; +import cpw.mods.fml.common.event.FMLServerStoppingEvent; import cpw.mods.fml.common.network.NetworkMod; +import cpw.mods.fml.common.registry.TickRegistry; +import cpw.mods.fml.relauncher.Side; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; @@ -84,6 +91,8 @@ public class ForgeWorldEdit { config = new ForgeConfiguration(this); config.load(); + + TickRegistry.registerTickHandler(ThreadSafeCache.getInstance(), Side.SERVER); } @EventHandler diff --git a/src/forge/java/com/sk89q/worldedit/forge/ThreadSafeCache.java b/src/forge/java/com/sk89q/worldedit/forge/ThreadSafeCache.java new file mode 100644 index 000000000..c10ef5f1b --- /dev/null +++ b/src/forge/java/com/sk89q/worldedit/forge/ThreadSafeCache.java @@ -0,0 +1,91 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.forge; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.ITickHandler; +import cpw.mods.fml.common.TickType; +import net.minecraft.entity.player.EntityPlayerMP; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Caches data that cannot be accessed from another thread safely. + */ +class ThreadSafeCache implements ITickHandler { + + private static final long REFRESH_DELAY = 1000 * 30; + private static final ThreadSafeCache INSTANCE = new ThreadSafeCache(); + private Set onlineIds = Collections.emptySet(); + private long lastRefresh = 0; + + /** + * Get an concurrent-safe set of UUIDs of online players. + * + * @return a set of UUIDs + */ + public Set getOnlineIds() { + return onlineIds; + } + + @Override + public void tickStart(EnumSet type, Object... tickData) { + long now = System.currentTimeMillis(); + + if (now - lastRefresh > REFRESH_DELAY) { + Set onlineIds = new HashSet(); + + for (Object object : FMLCommonHandler.instance().getMinecraftServerInstance().getConfigurationManager().playerEntityList) { + if (object != null) { + EntityPlayerMP player = (EntityPlayerMP) object; + onlineIds.add(player.getUniqueID()); + } + } + + this.onlineIds = new CopyOnWriteArraySet(onlineIds); + + lastRefresh = now; + } + } + + @Override + public void tickEnd(EnumSet type, Object... tickData) { + } + + @Override + public EnumSet ticks() { + return EnumSet.of(TickType.SERVER); + } + + @Override + public String getLabel() { + return "WorldEdit Cache"; + } + + public static ThreadSafeCache getInstance() { + return INSTANCE; + } + +} diff --git a/src/main/java/com/sk89q/worldedit/LocalSession.java b/src/main/java/com/sk89q/worldedit/LocalSession.java index 03647eb1c..85b02b091 100644 --- a/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -40,9 +40,6 @@ import com.sk89q.worldedit.regions.RegionSelector; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.session.request.Request; -import com.sk89q.worldedit.util.formatting.ColorCodeBuilder; -import com.sk89q.worldedit.util.formatting.Style; -import com.sk89q.worldedit.util.formatting.StyledFragment; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.snapshot.Snapshot; @@ -52,57 +49,102 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.TimeZone; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.google.common.base.Preconditions.checkNotNull; /** - * An instance of this represents the WorldEdit session of a user. A session - * stores history and settings. Sessions are not tied particularly to any - * player and can be shuffled between players, saved, and loaded. - * - * @author sk89q + * Stores session information. */ public class LocalSession { - private static final boolean SHOW_HELP_MESSAGE = false; + public transient static int MAX_HISTORY_SIZE = 15; - public static int MAX_HISTORY_SIZE = 15; - public static int EXPIRATION_GRACE = 600000; + // Non-session related fields + private transient LocalConfiguration config; + private transient final AtomicBoolean dirty = new AtomicBoolean(); - private LocalConfiguration config; + // Session related + private transient RegionSelector selector = new CuboidRegionSelector(); + private transient boolean placeAtPos1 = false; + private transient LinkedList history = new LinkedList(); + private transient int historyPointer = 0; + private transient ClipboardHolder clipboard; + private transient boolean toolControl = true; + private transient boolean superPickaxe = false; + private transient BlockTool pickaxeMode = new SinglePickaxe(); + private transient Map tools = new HashMap(); + private transient int maxBlocksChanged = -1; + private transient boolean useInventory; + private transient Snapshot snapshot; + private transient boolean hasCUISupport = false; + private transient int cuiVersion = -1; + private transient boolean fastMode = false; + private transient Mask mask; + private transient TimeZone timezone = TimeZone.getDefault(); - private long expirationTime = System.currentTimeMillis() + EXPIRATION_GRACE; - private RegionSelector selector = new CuboidRegionSelector(); - private boolean placeAtPos1 = false; - private LinkedList history = new LinkedList(); - private int historyPointer = 0; - private ClipboardHolder clipboard; - private boolean toolControl = true; - private boolean superPickaxe = false; - private BlockTool pickaxeMode = new SinglePickaxe(); - private Map tools = new HashMap(); - private int maxBlocksChanged = -1; - private boolean useInventory; - private Snapshot snapshot; + // Saved properties private String lastScript; - private boolean beenToldVersion = false; - private boolean hasCUISupport = false; - private int cuiVersion = -1; - private boolean fastMode = false; - private Mask mask; - private TimeZone timezone = TimeZone.getDefault(); /** * Construct the object. * - * @param config + *

{@link #setConfiguration(LocalConfiguration)} should be called + * later with configuration.

*/ - public LocalSession(LocalConfiguration config) { + public LocalSession() { + } + + /** + * Construct the object. + * + * @param config the configuration + */ + public LocalSession(@Nullable LocalConfiguration config) { this.config = config; } + /** + * Set the configuration. + * + * @param config the configuration + */ + public void setConfiguration(LocalConfiguration config) { + checkNotNull(config); + this.config = config; + } + + /** + * Get whether this session is "dirty" and has changes that needs to + * be committed. + * + * @return true if dirty + */ + public boolean isDirty() { + return dirty.get(); + } + + /** + * Set this session as dirty. + */ + private void setDirty() { + dirty.set(true); + } + + /** + * Get whether this session is "dirty" and has changes that needs to + * be committed, and reset it to {@code false}. + * + * @return true if the dirty value was {@code true} + */ + public boolean compareAndResetDirty() { + return dirty.compareAndSet(true, false); + } + /** * Get the session's timezone. * - * @return + * @return the timezone */ public TimeZone getTimeZone() { return timezone; @@ -111,9 +153,10 @@ public class LocalSession { /** * Set the session's timezone. * - * @param timezone + * @param timezone the user's timezone */ public void setTimezone(TimeZone timezone) { + checkNotNull(timezone); this.timezone = timezone; } @@ -129,9 +172,11 @@ public class LocalSession { * Remember an edit session for the undo history. If the history maximum * size is reached, old edit sessions will be discarded. * - * @param editSession + * @param editSession the edit session */ public void remember(EditSession editSession) { + checkNotNull(editSession); + // Don't store anything if no changes were made if (editSession.size() == 0) return; @@ -149,8 +194,8 @@ public class LocalSession { /** * Performs an undo. * - * @param newBlockBag - * @param player + * @param newBlockBag a new block bag + * @param player the player * @return whether anything was undone */ public EditSession undo(BlockBag newBlockBag, LocalPlayer player) { @@ -160,11 +205,13 @@ public class LocalSession { /** * Performs an undo. * - * @param newBlockBag - * @param player + * @param newBlockBag a new block bag + * @param player the player * @return whether anything was undone */ public EditSession undo(BlockBag newBlockBag, Player player) { + checkNotNull(newBlockBag); + checkNotNull(player); --historyPointer; if (historyPointer >= 0) { EditSession editSession = history.get(historyPointer); @@ -183,8 +230,8 @@ public class LocalSession { /** * Performs a redo * - * @param newBlockBag - * @param player + * @param newBlockBag a new block bag + * @param player the player * @return whether anything was redone */ public EditSession redo(BlockBag newBlockBag, LocalPlayer player) { @@ -194,11 +241,13 @@ public class LocalSession { /** * Performs a redo * - * @param newBlockBag - * @param player + * @param newBlockBag a new block bag + * @param player the player * @return whether anything was redone */ public EditSession redo(BlockBag newBlockBag, Player player) { + checkNotNull(newBlockBag); + checkNotNull(player); if (historyPointer < history.size()) { EditSession editSession = history.get(historyPointer); EditSession newEditSession = WorldEdit.getInstance().getEditSessionFactory() @@ -213,6 +262,9 @@ public class LocalSession { return null; } + /** + * @deprecated Use {@link #getRegionSelector(World)} + */ @Deprecated public RegionSelector getRegionSelector(LocalWorld world) { return getRegionSelector((World) world); @@ -222,10 +274,11 @@ public class LocalSession { * Get the region selector for defining the selection. If the selection * was defined for a different world, the old selection will be discarded. * - * @param world - * @return position + * @param world the world + * @return position the position */ public RegionSelector getRegionSelector(World world) { + checkNotNull(world); if (selector.getIncompleteRegion().getWorld() == null) { selector = new CuboidRegionSelector(world); } else if (!selector.getIncompleteRegion().getWorld().equals(world)) { @@ -236,16 +289,16 @@ public class LocalSession { } /** - * Get the region selector. This won't check worlds so make sure that - * this region selector isn't used blindly. - * - * @return position + * @deprecated use {@link #getRegionSelector(World)} */ @Deprecated public RegionSelector getRegionSelector() { return selector; } + /** + * @deprecated use {@link #setRegionSelector(World, RegionSelector)} + */ @Deprecated public void setRegionSelector(LocalWorld world, RegionSelector selector) { setRegionSelector((World) world, selector); @@ -254,10 +307,12 @@ public class LocalSession { /** * Set the region selector. * - * @param world - * @param selector + * @param world the world + * @param selector the selector */ public void setRegionSelector(World world, RegionSelector selector) { + checkNotNull(world); + checkNotNull(selector); selector.getIncompleteRegion().setWorld(world); this.selector = selector; } @@ -265,13 +320,16 @@ public class LocalSession { /** * Returns true if the region is fully defined. * - * @return + * @return true if a region selection is defined */ @Deprecated public boolean isRegionDefined() { return selector.isDefined(); } + /** + * @deprecated use {@link #isSelectionDefined(World)} + */ @Deprecated public boolean isSelectionDefined(LocalWorld world) { return isSelectionDefined((World) world); @@ -280,10 +338,11 @@ public class LocalSession { /** * Returns true if the region is fully defined for the specified world. * - * @param world - * @return + * @param world the world + * @return true if a region selection is defined */ public boolean isSelectionDefined(World world) { + checkNotNull(world); if (selector.getIncompleteRegion().getWorld() == null || !selector.getIncompleteRegion().getWorld().equals(world)) { return false; } @@ -291,16 +350,16 @@ public class LocalSession { } /** - * Use getSelection(). - * - * @return region - * @throws IncompleteRegionException + * @deprecated use {@link #getSelection(World)} */ @Deprecated public Region getRegion() throws IncompleteRegionException { return selector.getRegion(); } + /** + * @deprecated use {@link #getSelection(World)} + */ @Deprecated public Region getSelection(LocalWorld world) throws IncompleteRegionException { return getSelection((World) world); @@ -309,14 +368,15 @@ public class LocalSession { /** * Get the selection region. If you change the region, you should * call learnRegionChanges(). If the selection is defined in - * a different world, the IncompleteRegionException + * a different world, the {@code IncompleteRegionException} * exception will be thrown. * - * @param world - * @return region - * @throws IncompleteRegionException + * @param world the world + * @return a region + * @throws IncompleteRegionException if no region is selected */ public Region getSelection(World world) throws IncompleteRegionException { + checkNotNull(world); if (selector.getIncompleteRegion().getWorld() == null || !selector.getIncompleteRegion().getWorld().equals(world)) { throw new IncompleteRegionException(); } @@ -326,7 +386,7 @@ public class LocalSession { /** * Get the selection world. * - * @return + * @return the the world of the selection */ public World getSelectionWorld() { return selector.getIncompleteRegion().getWorld(); @@ -335,7 +395,7 @@ public class LocalSession { /** * Gets the clipboard. * - * @return clipboard, may be null + * @return clipboard * @throws EmptyClipboardException thrown if no clipboard is set */ public ClipboardHolder getClipboard() throws EmptyClipboardException { @@ -368,7 +428,7 @@ public class LocalSession { /** * Change tool control setting. * - * @param toolControl + * @param toolControl true to enable tool control */ public void setToolControl(boolean toolControl) { this.toolControl = toolControl; @@ -386,7 +446,7 @@ public class LocalSession { /** * Set the maximum number of blocks that can be changed. * - * @param maxBlocksChanged + * @param maxBlocksChanged the maximum number of blocks changed */ public void setBlockChangeLimit(int maxBlocksChanged) { this.maxBlocksChanged = maxBlocksChanged; @@ -418,7 +478,7 @@ public class LocalSession { /** * Toggle the super pick axe. * - * @return status + * @return whether the super pick axe is now enabled */ public boolean toggleSuperPickAxe() { superPickaxe = !superPickaxe; @@ -426,13 +486,15 @@ public class LocalSession { } /** - * Get the placement position. + * Get the position use for commands that take a center point + * (i.e. //forestgen, etc.). * - * @param player - * @return position - * @throws IncompleteRegionException + * @param player the player + * @return the position to use + * @throws IncompleteRegionException thrown if a region is not fully selected */ public Vector getPlacementPosition(Player player) throws IncompleteRegionException { + checkNotNull(player); if (!placeAtPos1) { return player.getBlockIn(); } @@ -443,7 +505,7 @@ public class LocalSession { /** * Toggle placement position. * - * @return + * @return whether "place at position 1" is now enabled */ public boolean togglePlacementPosition() { placeAtPos1 = !placeAtPos1; @@ -453,10 +515,12 @@ public class LocalSession { /** * Get a block bag for a player. * - * @param player - * @return + * @param player the player to get the block bag for + * @return a block bag */ + @Nullable public BlockBag getBlockBag(Player player) { + checkNotNull(player); if (!useInventory) { return null; } @@ -468,6 +532,7 @@ public class LocalSession { * * @return the snapshot */ + @Nullable public Snapshot getSnapshot() { return snapshot; } @@ -475,34 +540,38 @@ public class LocalSession { /** * Select a snapshot. * - * @param snapshot + * @param snapshot a snapshot */ - public void setSnapshot(Snapshot snapshot) { + public void setSnapshot(@Nullable Snapshot snapshot) { this.snapshot = snapshot; } /** - * @return the superPickaxeMode + * Get the assigned block tool. + * + * @return the super pickaxe tool mode */ public BlockTool getSuperPickaxe() { return pickaxeMode; } /** - * Set the super pickaxe tool. + * Set the super pick axe tool. * - * @param tool + * @param tool the tool to set */ public void setSuperPickaxe(BlockTool tool) { + checkNotNull(tool); this.pickaxeMode = tool; } /** * Get the tool assigned to the item. * - * @param item - * @return the tool + * @param item the item type ID + * @return the tool, which may be {@link null} */ + @Nullable public Tool getTool(int item) { return tools.get(item); } @@ -512,10 +581,11 @@ public class LocalSession { * or the tool is not assigned, the slot will be replaced with the * brush tool. * - * @param item - * @return the tool - * @throws InvalidToolBindException + * @param item the item type ID + * @return the tool, or {@code null} + * @throws InvalidToolBindException if the item can't be bound to that item */ + @Nullable public BrushTool getBrushTool(int item) throws InvalidToolBindException { Tool tool = getTool(item); @@ -530,11 +600,11 @@ public class LocalSession { /** * Set the tool. * - * @param item - * @param tool the tool to set - * @throws InvalidToolBindException + * @param item the item type ID + * @param tool the tool to set, which can be {@code null} + * @throws InvalidToolBindException if the item can't be bound to that item */ - public void setTool(int item, Tool tool) throws InvalidToolBindException { + public void setTool(int item, @Nullable Tool tool) throws InvalidToolBindException { if (item > 0 && item < 255) { throw new InvalidToolBindException(item, "Blocks can't be used"); } else if (item == config.wandItem) { @@ -549,7 +619,7 @@ public class LocalSession { /** * Returns whether inventory usage is enabled for this session. * - * @return the useInventory + * @return if inventory is being used */ public boolean isUsingInventory() { return useInventory; @@ -558,7 +628,7 @@ public class LocalSession { /** * Set the state of inventory usage. * - * @param useInventory the useInventory to set + * @param useInventory if inventory is to be used */ public void setUseInventory(boolean useInventory) { this.useInventory = useInventory; @@ -567,8 +637,9 @@ public class LocalSession { /** * Get the last script used. * - * @return the lastScript + * @return the last script's name */ + @Nullable public String getLastScript() { return lastScript; } @@ -576,58 +647,55 @@ public class LocalSession { /** * Set the last script used. * - * @param lastScript the lastScript to set + * @param lastScript the last script's name */ - public void setLastScript(String lastScript) { + public void setLastScript(@Nullable String lastScript) { this.lastScript = lastScript; + setDirty(); } /** * Tell the player the WorldEdit version. * - * @param player + * @param player the player */ - @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"}) public void tellVersion(Actor player) { - if (config.showHelpInfo && SHOW_HELP_MESSAGE) { - if (!beenToldVersion) { - StyledFragment fragment = new StyledFragment(Style.GRAY_DARK); - fragment.append("Need help with WorldEdit? Ask us on IRC (irc.esper.net #sk89q) or on our forums @ http://forum.enginehub.org"); - player.printRaw(ColorCodeBuilder.asColorCodes(fragment)); - beenToldVersion = true; - } - } } /** - * Dispatch a CUI event but only if the player has CUI support. + * Dispatch a CUI event but only if the actor has CUI support. * - * @param player - * @param event + * @param actor the actor + * @param event the event */ - public void dispatchCUIEvent(Actor player, CUIEvent event) { + public void dispatchCUIEvent(Actor actor, CUIEvent event) { + checkNotNull(actor); + checkNotNull(event); + if (hasCUISupport) { - player.dispatchCUIEvent(event); + actor.dispatchCUIEvent(event); } } /** * Dispatch the initial setup CUI messages. * - * @param player + * @param actor the actor */ - public void dispatchCUISetup(Player player) { + public void dispatchCUISetup(Actor actor) { if (selector != null) { - dispatchCUISelection(player); + dispatchCUISelection(actor); } } /** * Send the selection information. * - * @param player + * @param actor the actor */ - public void dispatchCUISelection(Player player) { + public void dispatchCUISelection(Actor actor) { + checkNotNull(actor); + if (!hasCUISupport) { return; } @@ -636,17 +704,24 @@ public class LocalSession { CUIRegion tempSel = (CUIRegion) selector; if (tempSel.getProtocolVersion() > cuiVersion) { - player.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getLegacyTypeID())); - tempSel.describeLegacyCUI(this, player); + actor.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getLegacyTypeID())); + tempSel.describeLegacyCUI(this, actor); } else { - player.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getTypeID())); - tempSel.describeCUI(this, player); + actor.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getTypeID())); + tempSel.describeCUI(this, actor); } } } - public void describeCUI(Actor player) { + /** + * Describe the selection to the CUI actor. + * + * @param actor the actor + */ + public void describeCUI(Actor actor) { + checkNotNull(actor); + if (!hasCUISupport) { return; } @@ -655,15 +730,22 @@ public class LocalSession { CUIRegion tempSel = (CUIRegion) selector; if (tempSel.getProtocolVersion() > cuiVersion) { - tempSel.describeLegacyCUI(this, player); + tempSel.describeLegacyCUI(this, actor); } else { - tempSel.describeCUI(this, player); + tempSel.describeCUI(this, actor); } } } + /** + * Handle a CUI initialization message. + * + * @param text the message + */ public void handleCUIInitializationMessage(String text) { + checkNotNull(text); + String[] split = text.split("\\|"); if (split.length > 1 && split[0].equalsIgnoreCase("v")) { // enough fields and right message setCUISupport(true); @@ -678,7 +760,7 @@ public class LocalSession { /** * Gets the status of CUI support. * - * @return + * @return true if CUI is enabled */ public boolean hasCUISupport() { return hasCUISupport; @@ -687,7 +769,7 @@ public class LocalSession { /** * Sets the status of CUI support. * - * @param support + * @param support true if CUI is enabled */ public void setCUISupport(boolean support) { hasCUISupport = support; @@ -696,7 +778,7 @@ public class LocalSession { /** * Gets the client's CUI protocol version * - * @return + * @return the CUI version */ public int getCUIVersion() { return cuiVersion; @@ -705,7 +787,7 @@ public class LocalSession { /** * Sets the client's CUI protocol version * - * @param cuiVersion + * @param cuiVersion the CUI version */ public void setCUIVersion(int cuiVersion) { this.cuiVersion = cuiVersion; @@ -714,10 +796,13 @@ public class LocalSession { /** * Detect date from a user's input. * - * @param input - * @return + * @param input the input to parse + * @return a date */ + @Nullable public Calendar detectDate(String input) { + checkNotNull(input); + Time.setTimeZone(getTimeZone()); Options opt = new com.sk89q.jchronic.Options(); opt.setNow(Calendar.getInstance(getTimeZone())); @@ -730,27 +815,9 @@ public class LocalSession { } /** - * Update the last update time for calculating expiration. - */ - public void update() { - expirationTime = System.currentTimeMillis(); - } - - /** - * Returns whether this session has expired. - * - * @return - */ - public boolean hasExpired() { - return System.currentTimeMillis() - expirationTime > EXPIRATION_GRACE; - } - - /** - * Construct a new edit session. - * - * @param player - * @return + * @deprecated use {@link #createEditSession(Player)} */ + @Deprecated public EditSession createEditSession(LocalPlayer player) { return createEditSession((Player) player); } @@ -758,11 +825,13 @@ public class LocalSession { /** * Construct a new edit session. * - * @param player - * @return + * @param player the player + * @return an edit session */ @SuppressWarnings("deprecation") public EditSession createEditSession(Player player) { + checkNotNull(player); + BlockBag blockBag = getBlockBag(player); // Create an edit session @@ -779,7 +848,7 @@ public class LocalSession { /** * Checks if the session has fast mode enabled. * - * @return + * @return true if fast mode is enabled */ public boolean hasFastMode() { return fastMode; @@ -788,7 +857,7 @@ public class LocalSession { /** * Set fast mode. * - * @param fastMode + * @param fastMode true if fast mode is enabled */ public void setFastMode(boolean fastMode) { this.fastMode = fastMode; diff --git a/src/main/java/com/sk89q/worldedit/WorldEdit.java b/src/main/java/com/sk89q/worldedit/WorldEdit.java index 2e3a4d2df..92623795a 100644 --- a/src/main/java/com/sk89q/worldedit/WorldEdit.java +++ b/src/main/java/com/sk89q/worldedit/WorldEdit.java @@ -660,41 +660,6 @@ public class WorldEdit { } } - /** - * Handle a disconnection. - * - * @param player the player - */ - @Deprecated - public void handleDisconnect(Player player) { - forgetPlayer(player); - } - - /** - * Mark for expiration of the session. - * - * @param player the player - */ - public void markExpire(Player player) { - sessions.markforExpiration(player); - } - - /** - * Forget a player. - * - * @param player the player - */ - public void forgetPlayer(Player player) { - sessions.remove(player); - } - - /* - * Flush expired sessions. - */ - public void flushExpiredSessions(SessionCheck checker) { - sessions.removeExpired(checker); - } - /** * Called on arm swing. * diff --git a/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java b/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java index 33070a6b3..1c40eb8f6 100644 --- a/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java +++ b/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java @@ -24,6 +24,7 @@ import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.worldedit.*; import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; @@ -78,6 +79,7 @@ public class WorldEditCommands { @CommandPermissions("worldedit.reload") public void reload(Actor actor) throws WorldEditException { we.getServer().reload(); + we.getEventBus().post(new ConfigurationLoadEvent(we.getPlatformManager().queryCapability(Capability.CONFIGURATION).getConfiguration())); actor.print("Configuration reloaded!"); } diff --git a/src/main/java/com/sk89q/worldedit/event/platform/ConfigurationLoadEvent.java b/src/main/java/com/sk89q/worldedit/event/platform/ConfigurationLoadEvent.java new file mode 100644 index 000000000..73d23131c --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/event/platform/ConfigurationLoadEvent.java @@ -0,0 +1,53 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.event.platform; + +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.event.Event; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Raised when the configuration has been loaded or re-loaded. + */ +public class ConfigurationLoadEvent extends Event { + + private final LocalConfiguration configuration; + + /** + * Create a new instance. + * + * @param configuration the new configuration + */ + public ConfigurationLoadEvent(LocalConfiguration configuration) { + checkNotNull(configuration); + this.configuration = configuration; + } + + /** + * Get the configuration. + * + * @return the configuration + */ + public LocalConfiguration getConfiguration() { + return configuration; + } + +} diff --git a/src/main/java/com/sk89q/worldedit/WorldEditPermissionException.java b/src/main/java/com/sk89q/worldedit/event/platform/PlatformInitializeEvent.java similarity index 75% rename from src/main/java/com/sk89q/worldedit/WorldEditPermissionException.java rename to src/main/java/com/sk89q/worldedit/event/platform/PlatformInitializeEvent.java index 8862590bd..49aeecd2b 100644 --- a/src/main/java/com/sk89q/worldedit/WorldEditPermissionException.java +++ b/src/main/java/com/sk89q/worldedit/event/platform/PlatformInitializeEvent.java @@ -17,15 +17,15 @@ * along with this program. If not, see . */ -package com.sk89q.worldedit; +package com.sk89q.worldedit.event.platform; + +import com.sk89q.worldedit.event.Event; /** - * @author zml2008 + * Fired when configuration has been loaded and the platform is in the + * intialization stage. + * + *

This event is fired once.

*/ -public class WorldEditPermissionException extends WorldEditException { - private static final long serialVersionUID = 1L; - - public WorldEditPermissionException() { - super("You don't have permission to do this."); - } +public class PlatformInitializeEvent extends Event { } diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java b/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java index 6cc034891..883930915 100644 --- a/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java +++ b/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java @@ -19,13 +19,13 @@ package com.sk89q.worldedit.extension.platform; +import com.sk89q.worldedit.util.auth.AuthorizationException; import com.sk89q.worldedit.BlockWorldVector; import com.sk89q.worldedit.LocalPlayer; import com.sk89q.worldedit.NotABlockException; import com.sk89q.worldedit.PlayerDirection; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.WorldEditPermissionException; import com.sk89q.worldedit.WorldVector; import com.sk89q.worldedit.WorldVectorFace; import com.sk89q.worldedit.blocks.BaseBlock; @@ -464,9 +464,9 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable { } @Override - public void checkPermission(String permission) throws WorldEditPermissionException { + public void checkPermission(String permission) throws AuthorizationException { if (!hasPermission(permission)) { - throw new WorldEditPermissionException(); + throw new AuthorizationException(); } } diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java b/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java index f9912de1a..43f665423 100644 --- a/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java +++ b/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java @@ -19,16 +19,17 @@ package com.sk89q.worldedit.extension.platform; -import com.sk89q.worldedit.WorldEditPermissionException; import com.sk89q.worldedit.internal.cui.CUIEvent; -import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.session.SessionOwner; +import com.sk89q.worldedit.util.Identifiable; +import com.sk89q.worldedit.util.auth.Subject; import java.io.File; /** * An object that can perform actions in WorldEdit. */ -public interface Actor { +public interface Actor extends Identifiable, SessionOwner, Subject { /** * Get the name of the actor. @@ -72,30 +73,6 @@ public interface Actor { */ boolean canDestroyBedrock(); - /** - * Get a actor's list of groups. - * - * @return an array containing a group name per entry - */ - String[] getGroups(); - - /** - * Checks if a player has permission. - * - * @param perm The permission to check - * @return true if the player has that permission - */ - boolean hasPermission(String perm); - - /** - * Check whether this actor has the given permission, and throw an - * exception if not. - * - * @param permission the permission - * @throws WorldEditPermissionException thrown if permission is not availabe - */ - void checkPermission(String permission) throws WorldEditPermissionException; - /** * Return whether this actor is a player. * diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java b/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java index e8eb0b227..5c524c7ee 100644 --- a/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java +++ b/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java @@ -19,12 +19,22 @@ package com.sk89q.worldedit.extension.platform; -import com.sk89q.worldedit.*; +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.ServerInterface; import com.sk89q.worldedit.Vector; -import com.sk89q.worldedit.command.tool.*; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldVector; +import com.sk89q.worldedit.command.tool.BlockTool; +import com.sk89q.worldedit.command.tool.DoubleActionBlockTool; +import com.sk89q.worldedit.command.tool.DoubleActionTraceTool; +import com.sk89q.worldedit.command.tool.Tool; +import com.sk89q.worldedit.command.tool.TraceTool; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.event.platform.BlockInteractEvent; +import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent; import com.sk89q.worldedit.event.platform.Interaction; +import com.sk89q.worldedit.event.platform.PlatformInitializeEvent; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; import com.sk89q.worldedit.event.platform.PlayerInputEvent; import com.sk89q.worldedit.internal.ServerInterfaceAdapter; @@ -34,8 +44,13 @@ import com.sk89q.worldedit.util.eventbus.Subscribe; import com.sk89q.worldedit.world.World; import javax.annotation.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -56,6 +71,8 @@ public class PlatformManager { private final List platforms = new ArrayList(); private final Map preferences = new EnumMap(Capability.class); private @Nullable String firstSeenVersion; + private final AtomicBoolean initialized = new AtomicBoolean(); + private final AtomicBoolean configured = new AtomicBoolean(); /** * Create a new platform manager. @@ -163,6 +180,11 @@ public class PlatformManager { capability.initialize(this, preferred); } } + + // Fire configuration event + if (preferences.containsKey(Capability.CONFIGURATION) && configured.compareAndSet(false, true)) { + worldEdit.getEventBus().post(new ConfigurationLoadEvent(queryCapability(Capability.CONFIGURATION).getConfiguration())); + } } /** @@ -276,6 +298,9 @@ public class PlatformManager { @Subscribe public void handlePlatformReady(PlatformReadyEvent event) { choosePreferred(); + if (initialized.compareAndSet(false, true)) { + worldEdit.getEventBus().post(new PlatformInitializeEvent()); + } } @SuppressWarnings("deprecation") diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java b/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java index b1d8979aa..7ed6d20d1 100644 --- a/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java +++ b/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java @@ -25,11 +25,14 @@ import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.internal.cui.CUIEvent; +import com.sk89q.worldedit.session.SessionKey; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.World; import javax.annotation.Nullable; +import java.util.UUID; + import static com.google.common.base.Preconditions.checkNotNull; class PlayerProxy extends AbstractPlayerActor { @@ -50,6 +53,11 @@ class PlayerProxy extends AbstractPlayerActor { this.world = world; } + @Override + public UUID getUniqueId() { + return basePlayer.getUniqueId(); + } + @Override public int getItemInHand() { return basePlayer.getItemInHand(); @@ -145,4 +153,9 @@ class PlayerProxy extends AbstractPlayerActor { public T getFacet(Class cls) { return basePlayer.getFacet(cls); } + + @Override + public SessionKey getSessionKey() { + return basePlayer.getSessionKey(); + } } diff --git a/src/main/java/com/sk89q/worldedit/session/MissingSessionException.java b/src/main/java/com/sk89q/worldedit/session/MissingSessionException.java new file mode 100644 index 000000000..46043b32e --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/session/MissingSessionException.java @@ -0,0 +1,42 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.session; + +/** + * Raised when the session is missing. + */ +public class MissingSessionException extends Exception { + + public MissingSessionException() { + } + + public MissingSessionException(String message) { + super(message); + } + + public MissingSessionException(String message, Throwable cause) { + super(message, cause); + } + + public MissingSessionException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/com/sk89q/worldedit/session/SessionKey.java b/src/main/java/com/sk89q/worldedit/session/SessionKey.java new file mode 100644 index 000000000..bb3f1696c --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/session/SessionKey.java @@ -0,0 +1,62 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.session; + +import com.sk89q.worldedit.util.Identifiable; + +import javax.annotation.Nullable; + +/** + * Provides information about a session. + * + *

A reference for this object may be kept around for a long time.

+ */ +public interface SessionKey extends Identifiable { + + /** + * Get the name for this session, if one is available, so that it can + * be referred to by others. + * + * @return a name or {@code null} + */ + @Nullable + String getName(); + + /** + * Return whether the session is still active. Sessions that are inactive + * for a prolonged amount of time may be removed. If this method + * always returns {@code false}, the the related session may never + * be stored. + * + *

This method may be called from any thread, so this call + * must be thread safe.

+ * + * @return true if active + */ + boolean isActive(); + + /** + * Return whether this session should be persisted. + * + * @return true if persistent + */ + boolean isPersistent(); + +} diff --git a/src/main/java/com/sk89q/worldedit/session/SessionManager.java b/src/main/java/com/sk89q/worldedit/session/SessionManager.java index 562bdc757..3a3260aa1 100644 --- a/src/main/java/com/sk89q/worldedit/session/SessionManager.java +++ b/src/main/java/com/sk89q/worldedit/session/SessionManager.java @@ -19,28 +19,53 @@ package com.sk89q.worldedit.session; -import com.sk89q.worldedit.*; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.Player; -import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent; +import com.sk89q.worldedit.session.storage.JsonFileSessionStore; +import com.sk89q.worldedit.session.storage.SessionStore; +import com.sk89q.worldedit.session.storage.VoidStore; +import com.sk89q.worldedit.util.concurrency.EvenMoreExecutors; +import com.sk89q.worldedit.util.eventbus.Subscribe; import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; import static com.google.common.base.Preconditions.checkNotNull; /** * Session manager for WorldEdit. - *

- * Get a reference to one from {@link WorldEdit}. - *

- * While this class is thread-safe, the returned session may not be. + * + *

Get a reference to one from {@link WorldEdit}.

+ * + *

While this class is thread-safe, the returned session may not be.

*/ public class SessionManager { + public static int EXPIRATION_GRACE = 600000; + private static final int FLUSH_PERIOD = 1000 * 30; + private static final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(EvenMoreExecutors.newBoundedCachedThreadPool(0, 1, 5)); + private static final Logger log = Logger.getLogger(SessionManager.class.getCanonicalName()); + private final Timer timer = new Timer(); private final WorldEdit worldEdit; - private final HashMap sessions = new HashMap(); + private final Map sessions = new HashMap(); + private SessionStore store = new VoidStore(); /** * Create a new session manager. @@ -48,73 +73,98 @@ public class SessionManager { * @param worldEdit a WorldEdit instance */ public SessionManager(WorldEdit worldEdit) { + checkNotNull(worldEdit); this.worldEdit = worldEdit; + + worldEdit.getEventBus().register(this); + timer.schedule(new SessionTracker(), FLUSH_PERIOD, FLUSH_PERIOD); } /** - * Get whether a session exists for the given actor. + * Get whether a session exists for the given owner. * - * @param actor the actor + * @param owner the owner * @return true if a session exists */ - public synchronized boolean contains(Actor actor) { - checkNotNull(actor); - return sessions.containsKey(getKey(actor)); + public synchronized boolean contains(SessionOwner owner) { + checkNotNull(owner); + return sessions.containsKey(getKey(owner)); } /** - * Gets the session for an actor and return it if it exists, otherwise - * return null. + * Find a session by its name specified by {@link SessionKey#getName()}. * - * @param actor the actor - * @return the session for the actor, if it exists + * @param name the name + * @return the session, if found, otherwise {@code null} */ - public synchronized @Nullable LocalSession find(Actor actor) { - checkNotNull(actor); - return sessions.get(getKey(actor)); - } - - /** - * Gets the session for someone named by the given name and return it if - * it exists, otherwise return null. - * - * @param name the actor's name - * @return the session for the actor, if it exists - */ - public synchronized @Nullable LocalSession findByName(String name) { + @Nullable + public synchronized LocalSession findByName(String name) { checkNotNull(name); - return sessions.get(name); + for (SessionHolder holder : sessions.values()) { + String test = holder.key.getName(); + if (test != null && name.equals(test)) { + return holder.session; + } + } + + return null; } /** - * Get the session for an actor and create one if one doesn't exist. + * Gets the session for an owner and return it if it exists, otherwise + * return {@code null}. * - * @param actor the actor + * @param owner the owner + * @return the session for the owner, if it exists + */ + @Nullable + public synchronized LocalSession getIfPresent(SessionOwner owner) { + checkNotNull(owner); + SessionHolder stored = sessions.get(getKey(owner)); + if (stored != null) { + return stored.session; + } else { + return null; + } + } + + /** + * Get the session for an owner and create one if one doesn't exist. + * + * @param owner the owner * @return a session */ - public synchronized LocalSession get(Actor actor) { - checkNotNull(actor); + public synchronized LocalSession get(SessionOwner owner) { + checkNotNull(owner); - LocalSession session; + LocalSession session = getIfPresent(owner); LocalConfiguration config = worldEdit.getConfiguration(); + SessionKey sessionKey = owner.getSessionKey(); - if (sessions.containsKey(actor.getName())) { - session = sessions.get(actor.getName()); - } else { - session = new LocalSession(config); + // No session exists yet -- create one + if (session == null) { + try { + session = store.load(getKey(sessionKey)); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to load saved session", e); + session = new LocalSession(); + } + + session.setConfiguration(config); session.setBlockChangeLimit(config.defaultChangeLimit); - // Remember the session - sessions.put(actor.getName(), session); + + // Remember the session if the session is still active + if (sessionKey.isActive()) { + sessions.put(getKey(owner), new SessionHolder(sessionKey, session)); + } } // Set the limit on the number of blocks that an operation can - // change at once, or don't if the actor has an override or there + // change at once, or don't if the owner has an override or there // is no limit. There is also a default limit int currentChangeLimit = session.getBlockChangeLimit(); - if (!actor.hasPermission("worldedit.limit.unrestricted") - && config.maxChangeLimit > -1) { - + if (!owner.hasPermission("worldedit.limit.unrestricted") && config.maxChangeLimit > -1) { // If the default limit is infinite but there is a maximum // limit, make sure to not have it be overridden if (config.defaultChangeLimit < 0) { @@ -130,47 +180,90 @@ public class SessionManager { } } - // Have the session use inventory if it's enabled and the actor + // Have the session use inventory if it's enabled and the owner // doesn't have an override session.setUseInventory(config.useInventory && !(config.useInventoryOverride - && (actor.hasPermission("worldedit.inventory.unrestricted") - || (config.useInventoryCreativeOverride && (!(actor instanceof Player) || ((Player) actor).hasCreativeMode()))))); + && (owner.hasPermission("worldedit.inventory.unrestricted") + || (config.useInventoryCreativeOverride && (!(owner instanceof Player) || ((Player) owner).hasCreativeMode()))))); return session; } /** - * Get the key to use in the map for an actor. + * Save a map of sessions to disk. * - * @param actor the actor - * @return the key object + * @param sessions a map of sessions to save + * @return a future that completes on save or error */ - protected String getKey(Actor actor) { - return actor.getName(); + private ListenableFuture commit(final Map sessions) { + checkNotNull(sessions); + + if (sessions.isEmpty()) { + return Futures.immediateFuture(sessions); + } + + return executorService.submit(new Callable() { + @Override + public Object call() throws Exception { + Exception exception = null; + + for (Map.Entry entry : sessions.entrySet()) { + SessionKey key = entry.getKey(); + + if (key.isPersistent()) { + try { + store.save(getKey(key), entry.getValue()); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to write session for UUID " + getKey(key), e); + exception = e; + } + } + } + + if (exception != null) { + throw exception; + } + + return sessions; + } + }); } /** - * Mark for expiration. + * Get the key to use in the map for an owner. * - * @param actor the actor + * @param owner the owner + * @return the key object */ - public synchronized void markforExpiration(Actor actor) { - checkNotNull(actor); - LocalSession session = find(actor); - if (session != null) { - session.update(); + protected UUID getKey(SessionOwner owner) { + return getKey(owner.getSessionKey()); + } + + + /** + * Get the key to use in the map for a {@code SessionKey}. + * + * @param key the session key object + * @return the key object + */ + protected UUID getKey(SessionKey key) { + String forcedKey = System.getProperty("worldedit.session.uuidOverride"); + if (forcedKey != null) { + return UUID.fromString(forcedKey); + } else { + return key.getUniqueId(); } } /** - * Remove the session for the given actor if one exists. + * Remove the session for the given owner if one exists. * - * @param actor the actor + * @param owner the owner */ - public synchronized void remove(Actor actor) { - checkNotNull(actor); - sessions.remove(actor.getName()); + public synchronized void remove(SessionOwner owner) { + checkNotNull(owner); + sessions.remove(getKey(owner)); } /** @@ -180,18 +273,61 @@ public class SessionManager { sessions.clear(); } - /** - * Remove expired sessions with the given session checker. - * - * @param checker the session checker - */ - public synchronized void removeExpired(SessionCheck checker) { - Iterator> it = sessions.entrySet().iterator(); + @Subscribe + public void onConfigurationLoad(ConfigurationLoadEvent event) { + LocalConfiguration config = event.getConfiguration(); + File dir = new File(config.getWorkingDirectory(), "sessions"); + store = new JsonFileSessionStore(dir); + } - while (it.hasNext()) { - Map.Entry entry = it.next(); - if (entry.getValue().hasExpired() && !checker.isOnlinePlayer(entry.getKey())) { - it.remove(); + /** + * Stores the owner of a session, the session, and the last active time. + */ + private static class SessionHolder { + private final SessionKey key; + private final LocalSession session; + private long lastActive = System.currentTimeMillis(); + + private SessionHolder(SessionKey key, LocalSession session) { + this.key = key; + this.session = session; + } + } + + /** + * Removes inactive sessions after they have been inactive for a period + * of time. Commits them as well. + */ + private class SessionTracker extends TimerTask { + @Override + public void run() { + synchronized (SessionManager.this) { + long now = System.currentTimeMillis(); + Iterator it = sessions.values().iterator(); + Map saveQueue = new HashMap(); + + while (it.hasNext()) { + SessionHolder stored = it.next(); + if (stored.key.isActive()) { + stored.lastActive = now; + + if (stored.session.compareAndResetDirty()) { + saveQueue.put(stored.key, stored.session); + } + } else { + if (now - stored.lastActive > EXPIRATION_GRACE) { + if (stored.session.compareAndResetDirty()) { + saveQueue.put(stored.key, stored.session); + } + + it.remove(); + } + } + } + + if (!saveQueue.isEmpty()) { + commit(saveQueue); + } } } } diff --git a/src/main/java/com/sk89q/worldedit/session/SessionOwner.java b/src/main/java/com/sk89q/worldedit/session/SessionOwner.java new file mode 100644 index 000000000..be288dccb --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/session/SessionOwner.java @@ -0,0 +1,37 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.session; + +import com.sk89q.worldedit.util.Identifiable; +import com.sk89q.worldedit.util.auth.Subject; + +/** + * An object that owns a session. + */ +public interface SessionOwner extends Subject { + + /** + * Get an object describing this session. + * + * @return the status object + */ + SessionKey getSessionKey(); + +} diff --git a/src/main/java/com/sk89q/worldedit/session/TransientSessionException.java b/src/main/java/com/sk89q/worldedit/session/TransientSessionException.java new file mode 100644 index 000000000..fe72de0dd --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/session/TransientSessionException.java @@ -0,0 +1,42 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.session; + +/** + * Thrown if the session cannot be persisted + * (because {@link SessionKey#isPersistent()} returns false). + */ +public class TransientSessionException extends Exception { + + public TransientSessionException() { + } + + public TransientSessionException(String message) { + super(message); + } + + public TransientSessionException(String message, Throwable cause) { + super(message, cause); + } + + public TransientSessionException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/sk89q/worldedit/session/storage/JsonFileSessionStore.java b/src/main/java/com/sk89q/worldedit/session/storage/JsonFileSessionStore.java new file mode 100644 index 000000000..7247a52cd --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/session/storage/JsonFileSessionStore.java @@ -0,0 +1,135 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.session.storage; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonParseException; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.util.gson.GsonUtil; +import com.sk89q.worldedit.util.io.Closer; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Stores sessions as JSON files in a directory. + * + *

Currently, this implementation doesn't handle thread safety very well.

+ */ +public class JsonFileSessionStore implements SessionStore { + + private static final Logger log = Logger.getLogger(JsonFileSessionStore.class.getCanonicalName()); + private final Gson gson; + private final File dir; + + /** + * Create a new session store. + * + * @param dir the directory + */ + public JsonFileSessionStore(File dir) { + checkNotNull(dir); + + if (!dir.isDirectory()) { + if (!dir.mkdirs()) { + log.log(Level.WARNING, "Failed to create directory '" + dir.getPath() + "' for sessions"); + } + } + + this.dir = dir; + + GsonBuilder builder = GsonUtil.createBuilder(); + gson = builder.create(); + } + + /** + * Get the path for the given UUID. + * + * @param id the ID + * @return the file + */ + private File getPath(UUID id) { + checkNotNull(id); + return new File(dir, id + ".json"); + } + + @Override + public LocalSession load(UUID id) throws IOException { + File file = getPath(id); + Closer closer = Closer.create(); + try { + FileReader fr = closer.register(new FileReader(file)); + BufferedReader br = closer.register(new BufferedReader(fr)); + return gson.fromJson(br, LocalSession.class); + } catch (JsonParseException e) { + throw new IOException(e); + } catch (FileNotFoundException e) { + return new LocalSession(); + } finally { + try { + closer.close(); + } catch (IOException ignored) { + } + } + } + + @Override + public void save(UUID id, LocalSession session) throws IOException { + File finalFile = getPath(id); + File tempFile = new File(finalFile.getParentFile(), finalFile.getName() + ".tmp"); + Closer closer = Closer.create(); + + try { + FileWriter fr = closer.register(new FileWriter(tempFile)); + BufferedWriter bw = closer.register(new BufferedWriter(fr)); + gson.toJson(session, bw); + } catch (JsonIOException e) { + throw new IOException(e); + } finally { + try { + closer.close(); + } catch (IOException ignored) { + } + } + + if (finalFile.exists()) { + if (!finalFile.delete()) { + log.log(Level.WARNING, "Failed to delete " + finalFile.getPath() + " so the .tmp file can replace it"); + } + } + + if (!tempFile.renameTo(finalFile)) { + log.log(Level.WARNING, "Failed to rename temporary session file to " + finalFile.getPath()); + } + } + +} diff --git a/src/main/java/com/sk89q/worldedit/session/storage/SessionStore.java b/src/main/java/com/sk89q/worldedit/session/storage/SessionStore.java new file mode 100644 index 000000000..aa08e3d0f --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/session/storage/SessionStore.java @@ -0,0 +1,57 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.session.storage; + +import com.sk89q.worldedit.LocalSession; + +import java.io.IOException; +import java.util.UUID; + +/** + * Commits sessions to disk. + * + *

Both {@link #load(UUID)} and {@link #save(UUID, LocalSession)} may be + * called at the same in different threads, so implementations should + * be aware of this issue.

+ */ +public interface SessionStore { + + /** + * Load a session identified by the given UUID. + * + *

If the session does not exist (has never been saved), then + * a new {@link LocalSession} must be returned.

+ * + * @param id the UUID + * @return a session + * @throws IOException thrown on read error + */ + LocalSession load(UUID id) throws IOException; + + /** + * Save the given session identified by the given UUID. + * + * @param id the UUID + * @param session a session + * @throws IOException thrown on read error + */ + void save(UUID id, LocalSession session) throws IOException; + +} diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/SessionTimer.java b/src/main/java/com/sk89q/worldedit/session/storage/VoidStore.java similarity index 53% rename from src/bukkit/java/com/sk89q/worldedit/bukkit/SessionTimer.java rename to src/main/java/com/sk89q/worldedit/session/storage/VoidStore.java index bf0e76da6..0089ded7e 100644 --- a/src/bukkit/java/com/sk89q/worldedit/bukkit/SessionTimer.java +++ b/src/main/java/com/sk89q/worldedit/session/storage/VoidStore.java @@ -17,34 +17,25 @@ * along with this program. If not, see . */ -package com.sk89q.worldedit.bukkit; +package com.sk89q.worldedit.session.storage; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import com.sk89q.worldedit.SessionCheck; -import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.LocalSession; + +import java.io.IOException; +import java.util.UUID; /** - * Used to remove expired sessions in Bukkit. + * A session store that doesn't know how to store sessions. */ -class SessionTimer implements Runnable { +public class VoidStore implements SessionStore { - private WorldEdit worldEdit; - private SessionCheck checker; - - SessionTimer(WorldEdit worldEdit, final Server server) { - this.worldEdit = worldEdit; - this.checker = new SessionCheck() { - public boolean isOnlinePlayer(String name) { - Player player = server.getPlayer(name); - return player != null && player.isOnline(); - } - }; + @Override + public LocalSession load(UUID id) throws IOException { + return new LocalSession(); } @Override - public void run() { - worldEdit.flushExpiredSessions(checker); + public void save(UUID id, LocalSession session) throws IOException { } } diff --git a/src/main/java/com/sk89q/worldedit/SessionCheck.java b/src/main/java/com/sk89q/worldedit/util/Identifiable.java similarity index 75% rename from src/main/java/com/sk89q/worldedit/SessionCheck.java rename to src/main/java/com/sk89q/worldedit/util/Identifiable.java index 79a3c425a..cc0b485c0 100644 --- a/src/main/java/com/sk89q/worldedit/SessionCheck.java +++ b/src/main/java/com/sk89q/worldedit/util/Identifiable.java @@ -1,37 +1,36 @@ -/* - * WorldEdit, a Minecraft world manipulation toolkit - * Copyright (C) sk89q - * Copyright (C) WorldEdit team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldedit; - -/** - * Used to discard old sessions. - * - * @author sk89q - */ -public interface SessionCheck { - - /** - * Checks if a player is online. - * - * @param name - * @return - */ - public boolean isOnlinePlayer(String name); - -} +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.util; + +import java.util.UUID; + +/** + * Represents an object that can be identified by a UUID. + */ +public interface Identifiable { + + /** + * Get the UUID for this object. + * + * @return the UUID + */ + UUID getUniqueId(); + +} diff --git a/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java b/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java index bd8e11534..c800591c5 100644 --- a/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java +++ b/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java @@ -22,6 +22,7 @@ package com.sk89q.worldedit.util; import com.sk89q.util.yaml.YAMLProcessor; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.session.SessionManager; import com.sk89q.worldedit.world.snapshot.SnapshotRepository; import java.io.IOException; @@ -106,7 +107,7 @@ public class YAMLConfiguration extends LocalConfiguration { allowSymlinks = config.getBoolean("files.allow-symbolic-links", false); LocalSession.MAX_HISTORY_SIZE = Math.max(0, config.getInt("history.size", 15)); - LocalSession.EXPIRATION_GRACE = config.getInt("history.expiration", 10) * 60 * 1000; + SessionManager.EXPIRATION_GRACE = config.getInt("history.expiration", 10) * 60 * 1000; showHelpInfo = config.getBoolean("show-help-on-first-use", true); diff --git a/src/main/java/com/sk89q/worldedit/util/auth/AuthorizationException.java b/src/main/java/com/sk89q/worldedit/util/auth/AuthorizationException.java new file mode 100644 index 000000000..497bc2240 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/auth/AuthorizationException.java @@ -0,0 +1,43 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.util.auth; + +import com.sk89q.worldedit.WorldEditException; + +/** + * Raised when authorization is not granted. + */ +public class AuthorizationException extends WorldEditException { + + public AuthorizationException() { + } + + public AuthorizationException(String message) { + super(message); + } + + public AuthorizationException(String message, Throwable cause) { + super(message, cause); + } + + public AuthorizationException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/sk89q/worldedit/util/auth/Subject.java b/src/main/java/com/sk89q/worldedit/util/auth/Subject.java new file mode 100644 index 000000000..166ad22b5 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/auth/Subject.java @@ -0,0 +1,51 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.util.auth; + +/** + * A subject has authorization attached to it. + */ +public interface Subject { + + /** + * Get a list of groups that this subject is a part of. + * + * @return an array containing a group name per entry + */ + String[] getGroups(); + + /** + * Check whether this subject has been granted the given permission + * and throw an exception on error. + * + * @param permission the permission + * @throws AuthorizationException thrown if not permitted + */ + void checkPermission(String permission) throws AuthorizationException; + + /** + * Return whether this subject has the given permission. + * + * @param permission the permission + * @return true if permission is granted + */ + boolean hasPermission(String permission); + +} diff --git a/src/main/java/com/sk89q/worldedit/util/concurrency/EvenMoreExecutors.java b/src/main/java/com/sk89q/worldedit/util/concurrency/EvenMoreExecutors.java new file mode 100644 index 000000000..6a5ee3430 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/concurrency/EvenMoreExecutors.java @@ -0,0 +1,54 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.util.concurrency; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Even more {@code ExecutorService} factory methods. + */ +public final class EvenMoreExecutors { + + private EvenMoreExecutors() { + } + + /** + * Creates a thread pool that creates new threads as needed up to + * a maximum number of threads, but will reuse previously constructed + * threads when they are available. + * + * @param minThreads the minimum number of threads to have at a given time + * @param maxThreads the maximum number of threads to have at a given time + * @param queueSize the size of the queue before new submissions are rejected + * @return the newly created thread pool + */ + public static ExecutorService newBoundedCachedThreadPool(int minThreads, int maxThreads, int queueSize) { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + minThreads, maxThreads, + 60L, TimeUnit.SECONDS, + new ArrayBlockingQueue(queueSize)); + threadPoolExecutor.allowCoreThreadTimeOut(true); + return threadPoolExecutor; + } + +} diff --git a/src/main/java/com/sk89q/worldedit/util/gson/GsonUtil.java b/src/main/java/com/sk89q/worldedit/util/gson/GsonUtil.java new file mode 100644 index 000000000..8d14f03b5 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/gson/GsonUtil.java @@ -0,0 +1,44 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.util.gson; + +import com.google.gson.GsonBuilder; +import com.sk89q.worldedit.Vector; + +/** + * Utility methods for Google's GSON library. + */ +public final class GsonUtil { + + private GsonUtil() { + } + + /** + * Create a standard {@link GsonBuilder} for WorldEdit. + * + * @return a builder + */ + public static GsonBuilder createBuilder() { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(Vector.class, new VectorAdapter()); + return gsonBuilder; + } + +}