Rewrite session code and add support for persistent sessions.

This commit is contained in:
sk89q 2014-07-27 20:44:05 -07:00
parent e95eeefa2b
commit aaeaf19fc8
30 changed files with 1418 additions and 382 deletions

View File

@ -19,19 +19,27 @@
package com.sk89q.worldedit.bukkit; 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.extension.platform.Actor;
import com.sk89q.worldedit.internal.cui.CUIEvent; import com.sk89q.worldedit.internal.cui.CUIEvent;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
public class BukkitCommandSender implements Actor { 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 CommandSender sender;
private WorldEditPlugin plugin; private WorldEditPlugin plugin;
@ -44,6 +52,11 @@ public class BukkitCommandSender implements Actor {
this.sender = sender; this.sender = sender;
} }
@Override
public UUID getUniqueId() {
return DEFAULT_ID;
}
@Override @Override
public String getName() { public String getName() {
return sender.getName(); return sender.getName();
@ -93,7 +106,7 @@ public class BukkitCommandSender implements Actor {
} }
@Override @Override
public void checkPermission(String permission) throws WorldEditPermissionException { public void checkPermission(String permission) throws AuthorizationException {
} }
@Override @Override
@ -115,4 +128,29 @@ public class BukkitCommandSender implements Actor {
public void dispatchCUIEvent(CUIEvent event) { 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;
}
};
}
} }

View File

@ -30,14 +30,18 @@ import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.internal.cui.CUIEvent; import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.session.SessionKey;
import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.UUID;
public class BukkitPlayer extends LocalPlayer { public class BukkitPlayer extends LocalPlayer {
private Player player; private Player player;
private WorldEditPlugin plugin; private WorldEditPlugin plugin;
@ -46,6 +50,11 @@ public class BukkitPlayer extends LocalPlayer {
this.player = player; this.player = player;
} }
@Override
public UUID getUniqueId() {
return player.getUniqueId();
}
@Override @Override
public int getItemInHand() { public int getItemInHand() {
ItemStack itemStack = player.getItemInHand(); ItemStack itemStack = player.getItemInHand();
@ -192,4 +201,48 @@ public class BukkitPlayer extends LocalPlayer {
public <T> T getFacet(Class<? extends T> cls) { public <T> T getFacet(Class<? extends T> cls) {
return null; 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;
}
}
} }

View File

@ -37,7 +37,6 @@ import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerGameModeChangeEvent; import org.bukkit.event.player.PlayerGameModeChangeEvent;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
/** /**
* Handles all events thrown in relation to a Player * Handles all events thrown in relation to a Player
@ -62,20 +61,6 @@ public class WorldEditListener implements Listener {
this.plugin = plugin; 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) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onGamemode(PlayerGameModeChangeEvent event) { public void onGamemode(PlayerGameModeChangeEvent event) {
if (!plugin.getInternalPlatform().isHookingEvents()) { if (!plugin.getInternalPlatform().isHookingEvents()) {

View File

@ -110,9 +110,6 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter {
// Now we can register events // Now we can register events
getServer().getPluginManager().registerEvents(new WorldEditListener(this), this); 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 // If we are on MCPC+/Cauldron, then Forge will have already loaded
// Forge WorldEdit and there's (probably) not going to be any other // Forge WorldEdit and there's (probably) not going to be any other
// platforms to be worried about... at the current time of writing // platforms to be worried about... at the current time of writing

View File

@ -27,6 +27,7 @@ import com.sk89q.worldedit.extension.platform.AbstractPlayerActor;
import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.internal.LocalWorldAdapter; import com.sk89q.worldedit.internal.LocalWorldAdapter;
import com.sk89q.worldedit.internal.cui.CUIEvent; import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.session.SessionKey;
import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.Location;
import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
@ -34,6 +35,7 @@ import net.minecraft.network.packet.Packet250CustomPayload;
import net.minecraft.util.ChatMessageComponent; import net.minecraft.util.ChatMessageComponent;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.UUID;
public class ForgePlayer extends AbstractPlayerActor { public class ForgePlayer extends AbstractPlayerActor {
@ -41,13 +43,21 @@ public class ForgePlayer extends AbstractPlayerActor {
protected ForgePlayer(EntityPlayerMP player) { protected ForgePlayer(EntityPlayerMP player) {
this.player = player; this.player = player;
ThreadSafeCache.getInstance().getOnlineIds().add(getUniqueId());
} }
@Override
public UUID getUniqueId() {
return player.getUniqueID();
}
@Override
public int getItemInHand() { public int getItemInHand() {
ItemStack is = this.player.getCurrentEquippedItem(); ItemStack is = this.player.getCurrentEquippedItem();
return is == null ? 0 : is.itemID; return is == null ? 0 : is.itemID;
} }
@Override
public String getName() { public String getName() {
return this.player.username; return this.player.username;
} }
@ -67,26 +77,32 @@ public class ForgePlayer extends AbstractPlayerActor {
this.player.cameraPitch); this.player.cameraPitch);
} }
@Override
public WorldVector getPosition() { public WorldVector getPosition() {
return new WorldVector(LocalWorldAdapter.adapt(ForgeWorldEdit.inst.getWorld(this.player.worldObj)), this.player.posX, this.player.posY, this.player.posZ); 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() { public com.sk89q.worldedit.world.World getWorld() {
return ForgeWorldEdit.inst.getWorld(this.player.worldObj); return ForgeWorldEdit.inst.getWorld(this.player.worldObj);
} }
@Override
public double getPitch() { public double getPitch() {
return this.player.rotationPitch; return this.player.rotationPitch;
} }
@Override
public double getYaw() { public double getYaw() {
return this.player.rotationYaw; return this.player.rotationYaw;
} }
@Override
public void giveItem(int type, int amt) { public void giveItem(int type, int amt) {
this.player.inventory.addItemStackToInventory(new ItemStack(type, amt, 0)); this.player.inventory.addItemStackToInventory(new ItemStack(type, amt, 0));
} }
@Override
public void dispatchCUIEvent(CUIEvent event) { public void dispatchCUIEvent(CUIEvent event) {
String[] params = event.getParameters(); String[] params = event.getParameters();
String send = event.getTypeId(); String send = event.getTypeId();
@ -97,42 +113,50 @@ public class ForgePlayer extends AbstractPlayerActor {
this.player.playerNetServerHandler.sendPacketToPlayer(packet); this.player.playerNetServerHandler.sendPacketToPlayer(packet);
} }
@Override
public void printRaw(String msg) { public void printRaw(String msg) {
for (String part : msg.split("\n")) { for (String part : msg.split("\n")) {
this.player.sendChatToPlayer(ChatMessageComponent.createFromText(part)); this.player.sendChatToPlayer(ChatMessageComponent.createFromText(part));
} }
} }
@Override
public void printDebug(String msg) { public void printDebug(String msg) {
for (String part : msg.split("\n")) { for (String part : msg.split("\n")) {
this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a77" + part)); this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a77" + part));
} }
} }
@Override
public void print(String msg) { public void print(String msg) {
for (String part : msg.split("\n")) { for (String part : msg.split("\n")) {
this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a7d" + part)); this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a7d" + part));
} }
} }
@Override
public void printError(String msg) { public void printError(String msg) {
for (String part : msg.split("\n")) { for (String part : msg.split("\n")) {
this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a7c" + part)); this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a7c" + part));
} }
} }
@Override
public void setPosition(Vector pos, float pitch, float yaw) { public void setPosition(Vector pos, float pitch, float yaw) {
this.player.playerNetServerHandler.setPlayerLocation(pos.getX(), pos.getY(), pos.getZ(), pitch, yaw); this.player.playerNetServerHandler.setPlayerLocation(pos.getX(), pos.getY(), pos.getZ(), pitch, yaw);
} }
@Override
public String[] getGroups() { public String[] getGroups() {
return new String[]{}; // WorldEditMod.inst.getPermissionsResolver().getGroups(this.player.username); return new String[]{}; // WorldEditMod.inst.getPermissionsResolver().getGroups(this.player.username);
} }
@Override
public BlockBag getInventoryBlockBag() { public BlockBag getInventoryBlockBag() {
return null; return null;
} }
@Override
public boolean hasPermission(String perm) { public boolean hasPermission(String perm) {
return ForgeUtil.hasPermission(this.player, perm); return ForgeUtil.hasPermission(this.player, perm);
} }
@ -143,4 +167,45 @@ public class ForgePlayer extends AbstractPlayerActor {
return null; 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;
}
}
} }

View File

@ -32,8 +32,15 @@ import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler; import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.Mod.Instance; 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.network.NetworkMod;
import cpw.mods.fml.common.registry.TickRegistry;
import cpw.mods.fml.relauncher.Side;
import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
@ -84,6 +91,8 @@ public class ForgeWorldEdit {
config = new ForgeConfiguration(this); config = new ForgeConfiguration(this);
config.load(); config.load();
TickRegistry.registerTickHandler(ThreadSafeCache.getInstance(), Side.SERVER);
} }
@EventHandler @EventHandler

View File

@ -0,0 +1,91 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<UUID> onlineIds = Collections.emptySet();
private long lastRefresh = 0;
/**
* Get an concurrent-safe set of UUIDs of online players.
*
* @return a set of UUIDs
*/
public Set<UUID> getOnlineIds() {
return onlineIds;
}
@Override
public void tickStart(EnumSet<TickType> type, Object... tickData) {
long now = System.currentTimeMillis();
if (now - lastRefresh > REFRESH_DELAY) {
Set<UUID> onlineIds = new HashSet<UUID>();
for (Object object : FMLCommonHandler.instance().getMinecraftServerInstance().getConfigurationManager().playerEntityList) {
if (object != null) {
EntityPlayerMP player = (EntityPlayerMP) object;
onlineIds.add(player.getUniqueID());
}
}
this.onlineIds = new CopyOnWriteArraySet<UUID>(onlineIds);
lastRefresh = now;
}
}
@Override
public void tickEnd(EnumSet<TickType> type, Object... tickData) {
}
@Override
public EnumSet<TickType> ticks() {
return EnumSet.of(TickType.SERVER);
}
@Override
public String getLabel() {
return "WorldEdit Cache";
}
public static ThreadSafeCache getInstance() {
return INSTANCE;
}
}

View File

@ -40,9 +40,6 @@ import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.session.request.Request; 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.World;
import com.sk89q.worldedit.world.snapshot.Snapshot; import com.sk89q.worldedit.world.snapshot.Snapshot;
@ -52,57 +49,102 @@ import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; 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 session information.
* stores history and settings. Sessions are not tied particularly to any
* player and can be shuffled between players, saved, and loaded.
*
* @author sk89q
*/ */
public class LocalSession { 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; // Non-session related fields
public static int EXPIRATION_GRACE = 600000; 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<EditSession> history = new LinkedList<EditSession>();
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<Integer, Tool> tools = new HashMap<Integer, Tool>();
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; // Saved properties
private RegionSelector selector = new CuboidRegionSelector();
private boolean placeAtPos1 = false;
private LinkedList<EditSession> history = new LinkedList<EditSession>();
private int historyPointer = 0;
private ClipboardHolder clipboard;
private boolean toolControl = true;
private boolean superPickaxe = false;
private BlockTool pickaxeMode = new SinglePickaxe();
private Map<Integer, Tool> tools = new HashMap<Integer, Tool>();
private int maxBlocksChanged = -1;
private boolean useInventory;
private Snapshot snapshot;
private String lastScript; 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. * Construct the object.
* *
* @param config * <p>{@link #setConfiguration(LocalConfiguration)} should be called
* later with configuration.</p>
*/ */
public LocalSession(LocalConfiguration config) { public LocalSession() {
}
/**
* Construct the object.
*
* @param config the configuration
*/
public LocalSession(@Nullable LocalConfiguration config) {
this.config = 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. * Get the session's timezone.
* *
* @return * @return the timezone
*/ */
public TimeZone getTimeZone() { public TimeZone getTimeZone() {
return timezone; return timezone;
@ -111,9 +153,10 @@ public class LocalSession {
/** /**
* Set the session's timezone. * Set the session's timezone.
* *
* @param timezone * @param timezone the user's timezone
*/ */
public void setTimezone(TimeZone timezone) { public void setTimezone(TimeZone timezone) {
checkNotNull(timezone);
this.timezone = timezone; this.timezone = timezone;
} }
@ -129,9 +172,11 @@ public class LocalSession {
* Remember an edit session for the undo history. If the history maximum * Remember an edit session for the undo history. If the history maximum
* size is reached, old edit sessions will be discarded. * size is reached, old edit sessions will be discarded.
* *
* @param editSession * @param editSession the edit session
*/ */
public void remember(EditSession editSession) { public void remember(EditSession editSession) {
checkNotNull(editSession);
// Don't store anything if no changes were made // Don't store anything if no changes were made
if (editSession.size() == 0) return; if (editSession.size() == 0) return;
@ -149,8 +194,8 @@ public class LocalSession {
/** /**
* Performs an undo. * Performs an undo.
* *
* @param newBlockBag * @param newBlockBag a new block bag
* @param player * @param player the player
* @return whether anything was undone * @return whether anything was undone
*/ */
public EditSession undo(BlockBag newBlockBag, LocalPlayer player) { public EditSession undo(BlockBag newBlockBag, LocalPlayer player) {
@ -160,11 +205,13 @@ public class LocalSession {
/** /**
* Performs an undo. * Performs an undo.
* *
* @param newBlockBag * @param newBlockBag a new block bag
* @param player * @param player the player
* @return whether anything was undone * @return whether anything was undone
*/ */
public EditSession undo(BlockBag newBlockBag, Player player) { public EditSession undo(BlockBag newBlockBag, Player player) {
checkNotNull(newBlockBag);
checkNotNull(player);
--historyPointer; --historyPointer;
if (historyPointer >= 0) { if (historyPointer >= 0) {
EditSession editSession = history.get(historyPointer); EditSession editSession = history.get(historyPointer);
@ -183,8 +230,8 @@ public class LocalSession {
/** /**
* Performs a redo * Performs a redo
* *
* @param newBlockBag * @param newBlockBag a new block bag
* @param player * @param player the player
* @return whether anything was redone * @return whether anything was redone
*/ */
public EditSession redo(BlockBag newBlockBag, LocalPlayer player) { public EditSession redo(BlockBag newBlockBag, LocalPlayer player) {
@ -194,11 +241,13 @@ public class LocalSession {
/** /**
* Performs a redo * Performs a redo
* *
* @param newBlockBag * @param newBlockBag a new block bag
* @param player * @param player the player
* @return whether anything was redone * @return whether anything was redone
*/ */
public EditSession redo(BlockBag newBlockBag, Player player) { public EditSession redo(BlockBag newBlockBag, Player player) {
checkNotNull(newBlockBag);
checkNotNull(player);
if (historyPointer < history.size()) { if (historyPointer < history.size()) {
EditSession editSession = history.get(historyPointer); EditSession editSession = history.get(historyPointer);
EditSession newEditSession = WorldEdit.getInstance().getEditSessionFactory() EditSession newEditSession = WorldEdit.getInstance().getEditSessionFactory()
@ -213,6 +262,9 @@ public class LocalSession {
return null; return null;
} }
/**
* @deprecated Use {@link #getRegionSelector(World)}
*/
@Deprecated @Deprecated
public RegionSelector getRegionSelector(LocalWorld world) { public RegionSelector getRegionSelector(LocalWorld world) {
return getRegionSelector((World) world); return getRegionSelector((World) world);
@ -222,10 +274,11 @@ public class LocalSession {
* Get the region selector for defining the selection. If the selection * Get the region selector for defining the selection. If the selection
* was defined for a different world, the old selection will be discarded. * was defined for a different world, the old selection will be discarded.
* *
* @param world * @param world the world
* @return position * @return position the position
*/ */
public RegionSelector getRegionSelector(World world) { public RegionSelector getRegionSelector(World world) {
checkNotNull(world);
if (selector.getIncompleteRegion().getWorld() == null) { if (selector.getIncompleteRegion().getWorld() == null) {
selector = new CuboidRegionSelector(world); selector = new CuboidRegionSelector(world);
} else if (!selector.getIncompleteRegion().getWorld().equals(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 * @deprecated use {@link #getRegionSelector(World)}
* this region selector isn't used blindly.
*
* @return position
*/ */
@Deprecated @Deprecated
public RegionSelector getRegionSelector() { public RegionSelector getRegionSelector() {
return selector; return selector;
} }
/**
* @deprecated use {@link #setRegionSelector(World, RegionSelector)}
*/
@Deprecated @Deprecated
public void setRegionSelector(LocalWorld world, RegionSelector selector) { public void setRegionSelector(LocalWorld world, RegionSelector selector) {
setRegionSelector((World) world, selector); setRegionSelector((World) world, selector);
@ -254,10 +307,12 @@ public class LocalSession {
/** /**
* Set the region selector. * Set the region selector.
* *
* @param world * @param world the world
* @param selector * @param selector the selector
*/ */
public void setRegionSelector(World world, RegionSelector selector) { public void setRegionSelector(World world, RegionSelector selector) {
checkNotNull(world);
checkNotNull(selector);
selector.getIncompleteRegion().setWorld(world); selector.getIncompleteRegion().setWorld(world);
this.selector = selector; this.selector = selector;
} }
@ -265,13 +320,16 @@ public class LocalSession {
/** /**
* Returns true if the region is fully defined. * Returns true if the region is fully defined.
* *
* @return * @return true if a region selection is defined
*/ */
@Deprecated @Deprecated
public boolean isRegionDefined() { public boolean isRegionDefined() {
return selector.isDefined(); return selector.isDefined();
} }
/**
* @deprecated use {@link #isSelectionDefined(World)}
*/
@Deprecated @Deprecated
public boolean isSelectionDefined(LocalWorld world) { public boolean isSelectionDefined(LocalWorld world) {
return isSelectionDefined((World) world); return isSelectionDefined((World) world);
@ -280,10 +338,11 @@ public class LocalSession {
/** /**
* Returns true if the region is fully defined for the specified world. * Returns true if the region is fully defined for the specified world.
* *
* @param world * @param world the world
* @return * @return true if a region selection is defined
*/ */
public boolean isSelectionDefined(World world) { public boolean isSelectionDefined(World world) {
checkNotNull(world);
if (selector.getIncompleteRegion().getWorld() == null || !selector.getIncompleteRegion().getWorld().equals(world)) { if (selector.getIncompleteRegion().getWorld() == null || !selector.getIncompleteRegion().getWorld().equals(world)) {
return false; return false;
} }
@ -291,16 +350,16 @@ public class LocalSession {
} }
/** /**
* Use <code>getSelection()</code>. * @deprecated use {@link #getSelection(World)}
*
* @return region
* @throws IncompleteRegionException
*/ */
@Deprecated @Deprecated
public Region getRegion() throws IncompleteRegionException { public Region getRegion() throws IncompleteRegionException {
return selector.getRegion(); return selector.getRegion();
} }
/**
* @deprecated use {@link #getSelection(World)}
*/
@Deprecated @Deprecated
public Region getSelection(LocalWorld world) throws IncompleteRegionException { public Region getSelection(LocalWorld world) throws IncompleteRegionException {
return getSelection((World) world); return getSelection((World) world);
@ -309,14 +368,15 @@ public class LocalSession {
/** /**
* Get the selection region. If you change the region, you should * Get the selection region. If you change the region, you should
* call learnRegionChanges(). If the selection is defined in * call learnRegionChanges(). If the selection is defined in
* a different world, the <code>IncompleteRegionException</code> * a different world, the {@code IncompleteRegionException}
* exception will be thrown. * exception will be thrown.
* *
* @param world * @param world the world
* @return region * @return a region
* @throws IncompleteRegionException * @throws IncompleteRegionException if no region is selected
*/ */
public Region getSelection(World world) throws IncompleteRegionException { public Region getSelection(World world) throws IncompleteRegionException {
checkNotNull(world);
if (selector.getIncompleteRegion().getWorld() == null || !selector.getIncompleteRegion().getWorld().equals(world)) { if (selector.getIncompleteRegion().getWorld() == null || !selector.getIncompleteRegion().getWorld().equals(world)) {
throw new IncompleteRegionException(); throw new IncompleteRegionException();
} }
@ -326,7 +386,7 @@ public class LocalSession {
/** /**
* Get the selection world. * Get the selection world.
* *
* @return * @return the the world of the selection
*/ */
public World getSelectionWorld() { public World getSelectionWorld() {
return selector.getIncompleteRegion().getWorld(); return selector.getIncompleteRegion().getWorld();
@ -335,7 +395,7 @@ public class LocalSession {
/** /**
* Gets the clipboard. * Gets the clipboard.
* *
* @return clipboard, may be null * @return clipboard
* @throws EmptyClipboardException thrown if no clipboard is set * @throws EmptyClipboardException thrown if no clipboard is set
*/ */
public ClipboardHolder getClipboard() throws EmptyClipboardException { public ClipboardHolder getClipboard() throws EmptyClipboardException {
@ -368,7 +428,7 @@ public class LocalSession {
/** /**
* Change tool control setting. * Change tool control setting.
* *
* @param toolControl * @param toolControl true to enable tool control
*/ */
public void setToolControl(boolean toolControl) { public void setToolControl(boolean toolControl) {
this.toolControl = toolControl; this.toolControl = toolControl;
@ -386,7 +446,7 @@ public class LocalSession {
/** /**
* Set the maximum number of blocks that can be changed. * 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) { public void setBlockChangeLimit(int maxBlocksChanged) {
this.maxBlocksChanged = maxBlocksChanged; this.maxBlocksChanged = maxBlocksChanged;
@ -418,7 +478,7 @@ public class LocalSession {
/** /**
* Toggle the super pick axe. * Toggle the super pick axe.
* *
* @return status * @return whether the super pick axe is now enabled
*/ */
public boolean toggleSuperPickAxe() { public boolean toggleSuperPickAxe() {
superPickaxe = !superPickaxe; 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 * @param player the player
* @return position * @return the position to use
* @throws IncompleteRegionException * @throws IncompleteRegionException thrown if a region is not fully selected
*/ */
public Vector getPlacementPosition(Player player) throws IncompleteRegionException { public Vector getPlacementPosition(Player player) throws IncompleteRegionException {
checkNotNull(player);
if (!placeAtPos1) { if (!placeAtPos1) {
return player.getBlockIn(); return player.getBlockIn();
} }
@ -443,7 +505,7 @@ public class LocalSession {
/** /**
* Toggle placement position. * Toggle placement position.
* *
* @return * @return whether "place at position 1" is now enabled
*/ */
public boolean togglePlacementPosition() { public boolean togglePlacementPosition() {
placeAtPos1 = !placeAtPos1; placeAtPos1 = !placeAtPos1;
@ -453,10 +515,12 @@ public class LocalSession {
/** /**
* Get a block bag for a player. * Get a block bag for a player.
* *
* @param player * @param player the player to get the block bag for
* @return * @return a block bag
*/ */
@Nullable
public BlockBag getBlockBag(Player player) { public BlockBag getBlockBag(Player player) {
checkNotNull(player);
if (!useInventory) { if (!useInventory) {
return null; return null;
} }
@ -468,6 +532,7 @@ public class LocalSession {
* *
* @return the snapshot * @return the snapshot
*/ */
@Nullable
public Snapshot getSnapshot() { public Snapshot getSnapshot() {
return snapshot; return snapshot;
} }
@ -475,34 +540,38 @@ public class LocalSession {
/** /**
* Select a snapshot. * Select a snapshot.
* *
* @param snapshot * @param snapshot a snapshot
*/ */
public void setSnapshot(Snapshot snapshot) { public void setSnapshot(@Nullable Snapshot snapshot) {
this.snapshot = snapshot; this.snapshot = snapshot;
} }
/** /**
* @return the superPickaxeMode * Get the assigned block tool.
*
* @return the super pickaxe tool mode
*/ */
public BlockTool getSuperPickaxe() { public BlockTool getSuperPickaxe() {
return pickaxeMode; 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) { public void setSuperPickaxe(BlockTool tool) {
checkNotNull(tool);
this.pickaxeMode = tool; this.pickaxeMode = tool;
} }
/** /**
* Get the tool assigned to the item. * Get the tool assigned to the item.
* *
* @param item * @param item the item type ID
* @return the tool * @return the tool, which may be {@link null}
*/ */
@Nullable
public Tool getTool(int item) { public Tool getTool(int item) {
return tools.get(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 * or the tool is not assigned, the slot will be replaced with the
* brush tool. * brush tool.
* *
* @param item * @param item the item type ID
* @return the tool * @return the tool, or {@code null}
* @throws InvalidToolBindException * @throws InvalidToolBindException if the item can't be bound to that item
*/ */
@Nullable
public BrushTool getBrushTool(int item) throws InvalidToolBindException { public BrushTool getBrushTool(int item) throws InvalidToolBindException {
Tool tool = getTool(item); Tool tool = getTool(item);
@ -530,11 +600,11 @@ public class LocalSession {
/** /**
* Set the tool. * Set the tool.
* *
* @param item * @param item the item type ID
* @param tool the tool to set * @param tool the tool to set, which can be {@code null}
* @throws InvalidToolBindException * @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) { if (item > 0 && item < 255) {
throw new InvalidToolBindException(item, "Blocks can't be used"); throw new InvalidToolBindException(item, "Blocks can't be used");
} else if (item == config.wandItem) { } else if (item == config.wandItem) {
@ -549,7 +619,7 @@ public class LocalSession {
/** /**
* Returns whether inventory usage is enabled for this session. * Returns whether inventory usage is enabled for this session.
* *
* @return the useInventory * @return if inventory is being used
*/ */
public boolean isUsingInventory() { public boolean isUsingInventory() {
return useInventory; return useInventory;
@ -558,7 +628,7 @@ public class LocalSession {
/** /**
* Set the state of inventory usage. * 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) { public void setUseInventory(boolean useInventory) {
this.useInventory = useInventory; this.useInventory = useInventory;
@ -567,8 +637,9 @@ public class LocalSession {
/** /**
* Get the last script used. * Get the last script used.
* *
* @return the lastScript * @return the last script's name
*/ */
@Nullable
public String getLastScript() { public String getLastScript() {
return lastScript; return lastScript;
} }
@ -576,58 +647,55 @@ public class LocalSession {
/** /**
* Set the last script used. * 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; this.lastScript = lastScript;
setDirty();
} }
/** /**
* Tell the player the WorldEdit version. * Tell the player the WorldEdit version.
* *
* @param player * @param player the player
*/ */
@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"})
public void tellVersion(Actor player) { 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 actor the actor
* @param event * @param event the event
*/ */
public void dispatchCUIEvent(Actor player, CUIEvent event) { public void dispatchCUIEvent(Actor actor, CUIEvent event) {
checkNotNull(actor);
checkNotNull(event);
if (hasCUISupport) { if (hasCUISupport) {
player.dispatchCUIEvent(event); actor.dispatchCUIEvent(event);
} }
} }
/** /**
* Dispatch the initial setup CUI messages. * 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) { if (selector != null) {
dispatchCUISelection(player); dispatchCUISelection(actor);
} }
} }
/** /**
* Send the selection information. * Send the selection information.
* *
* @param player * @param actor the actor
*/ */
public void dispatchCUISelection(Player player) { public void dispatchCUISelection(Actor actor) {
checkNotNull(actor);
if (!hasCUISupport) { if (!hasCUISupport) {
return; return;
} }
@ -636,17 +704,24 @@ public class LocalSession {
CUIRegion tempSel = (CUIRegion) selector; CUIRegion tempSel = (CUIRegion) selector;
if (tempSel.getProtocolVersion() > cuiVersion) { if (tempSel.getProtocolVersion() > cuiVersion) {
player.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getLegacyTypeID())); actor.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getLegacyTypeID()));
tempSel.describeLegacyCUI(this, player); tempSel.describeLegacyCUI(this, actor);
} else { } else {
player.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getTypeID())); actor.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getTypeID()));
tempSel.describeCUI(this, player); 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) { if (!hasCUISupport) {
return; return;
} }
@ -655,15 +730,22 @@ public class LocalSession {
CUIRegion tempSel = (CUIRegion) selector; CUIRegion tempSel = (CUIRegion) selector;
if (tempSel.getProtocolVersion() > cuiVersion) { if (tempSel.getProtocolVersion() > cuiVersion) {
tempSel.describeLegacyCUI(this, player); tempSel.describeLegacyCUI(this, actor);
} else { } else {
tempSel.describeCUI(this, player); tempSel.describeCUI(this, actor);
} }
} }
} }
/**
* Handle a CUI initialization message.
*
* @param text the message
*/
public void handleCUIInitializationMessage(String text) { public void handleCUIInitializationMessage(String text) {
checkNotNull(text);
String[] split = text.split("\\|"); String[] split = text.split("\\|");
if (split.length > 1 && split[0].equalsIgnoreCase("v")) { // enough fields and right message if (split.length > 1 && split[0].equalsIgnoreCase("v")) { // enough fields and right message
setCUISupport(true); setCUISupport(true);
@ -678,7 +760,7 @@ public class LocalSession {
/** /**
* Gets the status of CUI support. * Gets the status of CUI support.
* *
* @return * @return true if CUI is enabled
*/ */
public boolean hasCUISupport() { public boolean hasCUISupport() {
return hasCUISupport; return hasCUISupport;
@ -687,7 +769,7 @@ public class LocalSession {
/** /**
* Sets the status of CUI support. * Sets the status of CUI support.
* *
* @param support * @param support true if CUI is enabled
*/ */
public void setCUISupport(boolean support) { public void setCUISupport(boolean support) {
hasCUISupport = support; hasCUISupport = support;
@ -696,7 +778,7 @@ public class LocalSession {
/** /**
* Gets the client's CUI protocol version * Gets the client's CUI protocol version
* *
* @return * @return the CUI version
*/ */
public int getCUIVersion() { public int getCUIVersion() {
return cuiVersion; return cuiVersion;
@ -705,7 +787,7 @@ public class LocalSession {
/** /**
* Sets the client's CUI protocol version * Sets the client's CUI protocol version
* *
* @param cuiVersion * @param cuiVersion the CUI version
*/ */
public void setCUIVersion(int cuiVersion) { public void setCUIVersion(int cuiVersion) {
this.cuiVersion = cuiVersion; this.cuiVersion = cuiVersion;
@ -714,10 +796,13 @@ public class LocalSession {
/** /**
* Detect date from a user's input. * Detect date from a user's input.
* *
* @param input * @param input the input to parse
* @return * @return a date
*/ */
@Nullable
public Calendar detectDate(String input) { public Calendar detectDate(String input) {
checkNotNull(input);
Time.setTimeZone(getTimeZone()); Time.setTimeZone(getTimeZone());
Options opt = new com.sk89q.jchronic.Options(); Options opt = new com.sk89q.jchronic.Options();
opt.setNow(Calendar.getInstance(getTimeZone())); opt.setNow(Calendar.getInstance(getTimeZone()));
@ -730,27 +815,9 @@ public class LocalSession {
} }
/** /**
* Update the last update time for calculating expiration. * @deprecated use {@link #createEditSession(Player)}
*/
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
public EditSession createEditSession(LocalPlayer player) { public EditSession createEditSession(LocalPlayer player) {
return createEditSession((Player) player); return createEditSession((Player) player);
} }
@ -758,11 +825,13 @@ public class LocalSession {
/** /**
* Construct a new edit session. * Construct a new edit session.
* *
* @param player * @param player the player
* @return * @return an edit session
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public EditSession createEditSession(Player player) { public EditSession createEditSession(Player player) {
checkNotNull(player);
BlockBag blockBag = getBlockBag(player); BlockBag blockBag = getBlockBag(player);
// Create an edit session // Create an edit session
@ -779,7 +848,7 @@ public class LocalSession {
/** /**
* Checks if the session has fast mode enabled. * Checks if the session has fast mode enabled.
* *
* @return * @return true if fast mode is enabled
*/ */
public boolean hasFastMode() { public boolean hasFastMode() {
return fastMode; return fastMode;
@ -788,7 +857,7 @@ public class LocalSession {
/** /**
* Set fast mode. * Set fast mode.
* *
* @param fastMode * @param fastMode true if fast mode is enabled
*/ */
public void setFastMode(boolean fastMode) { public void setFastMode(boolean fastMode) {
this.fastMode = fastMode; this.fastMode = fastMode;

View File

@ -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. * Called on arm swing.
* *

View File

@ -24,6 +24,7 @@ import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.worldedit.*; import com.sk89q.worldedit.*;
import com.sk89q.worldedit.entity.Player; 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.Actor;
import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extension.platform.Platform; import com.sk89q.worldedit.extension.platform.Platform;
@ -78,6 +79,7 @@ public class WorldEditCommands {
@CommandPermissions("worldedit.reload") @CommandPermissions("worldedit.reload")
public void reload(Actor actor) throws WorldEditException { public void reload(Actor actor) throws WorldEditException {
we.getServer().reload(); we.getServer().reload();
we.getEventBus().post(new ConfigurationLoadEvent(we.getPlatformManager().queryCapability(Capability.CONFIGURATION).getConfiguration()));
actor.print("Configuration reloaded!"); actor.print("Configuration reloaded!");
} }

View File

@ -0,0 +1,53 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -17,15 +17,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
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.
*
* <p>This event is fired once.</p>
*/ */
public class WorldEditPermissionException extends WorldEditException { public class PlatformInitializeEvent extends Event {
private static final long serialVersionUID = 1L;
public WorldEditPermissionException() {
super("You don't have permission to do this.");
}
} }

View File

@ -19,13 +19,13 @@
package com.sk89q.worldedit.extension.platform; package com.sk89q.worldedit.extension.platform;
import com.sk89q.worldedit.util.auth.AuthorizationException;
import com.sk89q.worldedit.BlockWorldVector; import com.sk89q.worldedit.BlockWorldVector;
import com.sk89q.worldedit.LocalPlayer; import com.sk89q.worldedit.LocalPlayer;
import com.sk89q.worldedit.NotABlockException; import com.sk89q.worldedit.NotABlockException;
import com.sk89q.worldedit.PlayerDirection; import com.sk89q.worldedit.PlayerDirection;
import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.WorldEditPermissionException;
import com.sk89q.worldedit.WorldVector; import com.sk89q.worldedit.WorldVector;
import com.sk89q.worldedit.WorldVectorFace; import com.sk89q.worldedit.WorldVectorFace;
import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BaseBlock;
@ -464,9 +464,9 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
} }
@Override @Override
public void checkPermission(String permission) throws WorldEditPermissionException { public void checkPermission(String permission) throws AuthorizationException {
if (!hasPermission(permission)) { if (!hasPermission(permission)) {
throw new WorldEditPermissionException(); throw new AuthorizationException();
} }
} }

View File

@ -19,16 +19,17 @@
package com.sk89q.worldedit.extension.platform; package com.sk89q.worldedit.extension.platform;
import com.sk89q.worldedit.WorldEditPermissionException;
import com.sk89q.worldedit.internal.cui.CUIEvent; 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; import java.io.File;
/** /**
* An object that can perform actions in WorldEdit. * An object that can perform actions in WorldEdit.
*/ */
public interface Actor { public interface Actor extends Identifiable, SessionOwner, Subject {
/** /**
* Get the name of the actor. * Get the name of the actor.
@ -72,30 +73,6 @@ public interface Actor {
*/ */
boolean canDestroyBedrock(); 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. * Return whether this actor is a player.
* *

View File

@ -19,12 +19,22 @@
package com.sk89q.worldedit.extension.platform; 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.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.entity.Player;
import com.sk89q.worldedit.event.platform.BlockInteractEvent; 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.Interaction;
import com.sk89q.worldedit.event.platform.PlatformInitializeEvent;
import com.sk89q.worldedit.event.platform.PlatformReadyEvent; import com.sk89q.worldedit.event.platform.PlatformReadyEvent;
import com.sk89q.worldedit.event.platform.PlayerInputEvent; import com.sk89q.worldedit.event.platform.PlayerInputEvent;
import com.sk89q.worldedit.internal.ServerInterfaceAdapter; import com.sk89q.worldedit.internal.ServerInterfaceAdapter;
@ -34,8 +44,13 @@ import com.sk89q.worldedit.util.eventbus.Subscribe;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import javax.annotation.Nullable; 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.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -56,6 +71,8 @@ public class PlatformManager {
private final List<Platform> platforms = new ArrayList<Platform>(); private final List<Platform> platforms = new ArrayList<Platform>();
private final Map<Capability, Platform> preferences = new EnumMap<Capability, Platform>(Capability.class); private final Map<Capability, Platform> preferences = new EnumMap<Capability, Platform>(Capability.class);
private @Nullable String firstSeenVersion; private @Nullable String firstSeenVersion;
private final AtomicBoolean initialized = new AtomicBoolean();
private final AtomicBoolean configured = new AtomicBoolean();
/** /**
* Create a new platform manager. * Create a new platform manager.
@ -163,6 +180,11 @@ public class PlatformManager {
capability.initialize(this, preferred); 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 @Subscribe
public void handlePlatformReady(PlatformReadyEvent event) { public void handlePlatformReady(PlatformReadyEvent event) {
choosePreferred(); choosePreferred();
if (initialized.compareAndSet(false, true)) {
worldEdit.getEventBus().post(new PlatformInitializeEvent());
}
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")

View File

@ -25,11 +25,14 @@ import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.internal.cui.CUIEvent; import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.session.SessionKey;
import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
class PlayerProxy extends AbstractPlayerActor { class PlayerProxy extends AbstractPlayerActor {
@ -50,6 +53,11 @@ class PlayerProxy extends AbstractPlayerActor {
this.world = world; this.world = world;
} }
@Override
public UUID getUniqueId() {
return basePlayer.getUniqueId();
}
@Override @Override
public int getItemInHand() { public int getItemInHand() {
return basePlayer.getItemInHand(); return basePlayer.getItemInHand();
@ -145,4 +153,9 @@ class PlayerProxy extends AbstractPlayerActor {
public <T> T getFacet(Class<? extends T> cls) { public <T> T getFacet(Class<? extends T> cls) {
return basePlayer.getFacet(cls); return basePlayer.getFacet(cls);
} }
@Override
public SessionKey getSessionKey() {
return basePlayer.getSessionKey();
}
} }

View File

@ -0,0 +1,42 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,62 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.session;
import com.sk89q.worldedit.util.Identifiable;
import javax.annotation.Nullable;
/**
* Provides information about a session.
*
* <p>A reference for this object may be kept around for a long time.</p>
*/
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.
*
* <p>This method may be called from any thread, so this call
* must be thread safe.</p>
*
* @return true if active
*/
boolean isActive();
/**
* Return whether this session should be persisted.
*
* @return true if persistent
*/
boolean isPersistent();
}

View File

@ -19,28 +19,53 @@
package com.sk89q.worldedit.session; 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.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 javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; 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; import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* Session manager for WorldEdit. * Session manager for WorldEdit.
* </p> *
* Get a reference to one from {@link WorldEdit}. * <p>Get a reference to one from {@link WorldEdit}.</p>
* </p> *
* While this class is thread-safe, the returned session may not be. * <p>While this class is thread-safe, the returned session may not be.</p>
*/ */
public class SessionManager { 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 WorldEdit worldEdit;
private final HashMap<String, LocalSession> sessions = new HashMap<String, LocalSession>(); private final Map<UUID, SessionHolder> sessions = new HashMap<UUID, SessionHolder>();
private SessionStore store = new VoidStore();
/** /**
* Create a new session manager. * Create a new session manager.
@ -48,73 +73,98 @@ public class SessionManager {
* @param worldEdit a WorldEdit instance * @param worldEdit a WorldEdit instance
*/ */
public SessionManager(WorldEdit worldEdit) { public SessionManager(WorldEdit worldEdit) {
checkNotNull(worldEdit);
this.worldEdit = 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 * @return true if a session exists
*/ */
public synchronized boolean contains(Actor actor) { public synchronized boolean contains(SessionOwner owner) {
checkNotNull(actor); checkNotNull(owner);
return sessions.containsKey(getKey(actor)); return sessions.containsKey(getKey(owner));
} }
/** /**
* Gets the session for an actor and return it if it exists, otherwise * Find a session by its name specified by {@link SessionKey#getName()}.
* return <code>null</code>.
* *
* @param actor the actor * @param name the name
* @return the session for the actor, if it exists * @return the session, if found, otherwise {@code null}
*/ */
public synchronized @Nullable LocalSession find(Actor actor) { @Nullable
checkNotNull(actor); public synchronized LocalSession findByName(String name) {
return sessions.get(getKey(actor));
}
/**
* Gets the session for someone named by the given name and return it if
* it exists, otherwise return <code>null</code>.
*
* @param name the actor's name
* @return the session for the actor, if it exists
*/
public synchronized @Nullable LocalSession findByName(String name) {
checkNotNull(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 * @return a session
*/ */
public synchronized LocalSession get(Actor actor) { public synchronized LocalSession get(SessionOwner owner) {
checkNotNull(actor); checkNotNull(owner);
LocalSession session; LocalSession session = getIfPresent(owner);
LocalConfiguration config = worldEdit.getConfiguration(); LocalConfiguration config = worldEdit.getConfiguration();
SessionKey sessionKey = owner.getSessionKey();
if (sessions.containsKey(actor.getName())) { // No session exists yet -- create one
session = sessions.get(actor.getName()); if (session == null) {
} else { try {
session = new LocalSession(config); 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); 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 // 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 // is no limit. There is also a default limit
int currentChangeLimit = session.getBlockChangeLimit(); int currentChangeLimit = session.getBlockChangeLimit();
if (!actor.hasPermission("worldedit.limit.unrestricted") if (!owner.hasPermission("worldedit.limit.unrestricted") && config.maxChangeLimit > -1) {
&& config.maxChangeLimit > -1) {
// If the default limit is infinite but there is a maximum // If the default limit is infinite but there is a maximum
// limit, make sure to not have it be overridden // limit, make sure to not have it be overridden
if (config.defaultChangeLimit < 0) { 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 // doesn't have an override
session.setUseInventory(config.useInventory session.setUseInventory(config.useInventory
&& !(config.useInventoryOverride && !(config.useInventoryOverride
&& (actor.hasPermission("worldedit.inventory.unrestricted") && (owner.hasPermission("worldedit.inventory.unrestricted")
|| (config.useInventoryCreativeOverride && (!(actor instanceof Player) || ((Player) actor).hasCreativeMode()))))); || (config.useInventoryCreativeOverride && (!(owner instanceof Player) || ((Player) owner).hasCreativeMode())))));
return session; return session;
} }
/** /**
* Get the key to use in the map for an actor. * Save a map of sessions to disk.
* *
* @param actor the actor * @param sessions a map of sessions to save
* @return the key object * @return a future that completes on save or error
*/ */
protected String getKey(Actor actor) { private ListenableFuture<?> commit(final Map<SessionKey, LocalSession> sessions) {
return actor.getName(); checkNotNull(sessions);
if (sessions.isEmpty()) {
return Futures.immediateFuture(sessions);
}
return executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
Exception exception = null;
for (Map.Entry<SessionKey, LocalSession> 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) { protected UUID getKey(SessionOwner owner) {
checkNotNull(actor); return getKey(owner.getSessionKey());
LocalSession session = find(actor); }
if (session != null) {
session.update();
/**
* 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) { public synchronized void remove(SessionOwner owner) {
checkNotNull(actor); checkNotNull(owner);
sessions.remove(actor.getName()); sessions.remove(getKey(owner));
} }
/** /**
@ -180,18 +273,61 @@ public class SessionManager {
sessions.clear(); sessions.clear();
} }
/** @Subscribe
* Remove expired sessions with the given session checker. public void onConfigurationLoad(ConfigurationLoadEvent event) {
* LocalConfiguration config = event.getConfiguration();
* @param checker the session checker File dir = new File(config.getWorkingDirectory(), "sessions");
*/ store = new JsonFileSessionStore(dir);
public synchronized void removeExpired(SessionCheck checker) { }
Iterator<Map.Entry<String, LocalSession>> it = sessions.entrySet().iterator();
while (it.hasNext()) { /**
Map.Entry<String, LocalSession> entry = it.next(); * Stores the owner of a session, the session, and the last active time.
if (entry.getValue().hasExpired() && !checker.isOnlinePlayer(entry.getKey())) { */
it.remove(); 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<SessionHolder> it = sessions.values().iterator();
Map<SessionKey, LocalSession> saveQueue = new HashMap<SessionKey, LocalSession>();
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);
}
} }
} }
} }

View File

@ -0,0 +1,37 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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();
}

View File

@ -0,0 +1,42 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,135 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>Currently, this implementation doesn't handle thread safety very well.</p>
*/
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());
}
}
}

View File

@ -0,0 +1,57 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.session.storage;
import com.sk89q.worldedit.LocalSession;
import java.io.IOException;
import java.util.UUID;
/**
* Commits sessions to disk.
*
* <p>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.</p>
*/
public interface SessionStore {
/**
* Load a session identified by the given UUID.
*
* <p>If the session does not exist (has never been saved), then
* a new {@link LocalSession} must be returned.</p>
*
* @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;
}

View File

@ -17,34 +17,25 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.sk89q.worldedit.bukkit; package com.sk89q.worldedit.session.storage;
import org.bukkit.Server; import com.sk89q.worldedit.LocalSession;
import org.bukkit.entity.Player;
import com.sk89q.worldedit.SessionCheck; import java.io.IOException;
import com.sk89q.worldedit.WorldEdit; 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; @Override
private SessionCheck checker; public LocalSession load(UUID id) throws IOException {
return new LocalSession();
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 @Override
public void run() { public void save(UUID id, LocalSession session) throws IOException {
worldEdit.flushExpiredSessions(checker);
} }
} }

View File

@ -17,21 +17,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.sk89q.worldedit; package com.sk89q.worldedit.util;
import java.util.UUID;
/** /**
* Used to discard old sessions. * Represents an object that can be identified by a UUID.
*
* @author sk89q
*/ */
public interface SessionCheck { public interface Identifiable {
/** /**
* Checks if a player is online. * Get the UUID for this object.
* *
* @param name * @return the UUID
* @return
*/ */
public boolean isOnlinePlayer(String name); UUID getUniqueId();
} }

View File

@ -22,6 +22,7 @@ package com.sk89q.worldedit.util;
import com.sk89q.util.yaml.YAMLProcessor; import com.sk89q.util.yaml.YAMLProcessor;
import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.session.SessionManager;
import com.sk89q.worldedit.world.snapshot.SnapshotRepository; import com.sk89q.worldedit.world.snapshot.SnapshotRepository;
import java.io.IOException; import java.io.IOException;
@ -106,7 +107,7 @@ public class YAMLConfiguration extends LocalConfiguration {
allowSymlinks = config.getBoolean("files.allow-symbolic-links", false); allowSymlinks = config.getBoolean("files.allow-symbolic-links", false);
LocalSession.MAX_HISTORY_SIZE = Math.max(0, config.getInt("history.size", 15)); 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); showHelpInfo = config.getBoolean("show-help-on-first-use", true);

View File

@ -0,0 +1,43 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,51 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}

View File

@ -0,0 +1,54 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<Runnable>(queueSize));
threadPoolExecutor.allowCoreThreadTimeOut(true);
return threadPoolExecutor;
}
}

View File

@ -0,0 +1,44 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}