From e413318fb7fafd22e075a37fa06cd033be358956 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Sat, 16 Apr 2016 22:31:20 +1000 Subject: [PATCH] Fix regen for non cuboid / vp2 / clear sessions on logout --- .../com/boydti/fawe/object/FawePlayer.java | 1 + .../boydti/fawe/wrappers/WorldWrapper.java | 1 - .../extension/platform/CommandManager.java | 27 +- .../worldedit/session/SessionManager.java | 336 ++++++++++++++++++ 4 files changed, 354 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/com/sk89q/worldedit/session/SessionManager.java diff --git a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java index 2cab2b6d..6fa901d4 100644 --- a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java +++ b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java @@ -176,6 +176,7 @@ public abstract class FawePlayer { } public void unregister() { + WorldEdit.getInstance().removeSession(getPlayer()); Fawe.get().unregister(getName()); } } diff --git a/core/src/main/java/com/boydti/fawe/wrappers/WorldWrapper.java b/core/src/main/java/com/boydti/fawe/wrappers/WorldWrapper.java index 46214165..40791b42 100644 --- a/core/src/main/java/com/boydti/fawe/wrappers/WorldWrapper.java +++ b/core/src/main/java/com/boydti/fawe/wrappers/WorldWrapper.java @@ -183,7 +183,6 @@ public class WorldWrapper extends AbstractWorld { final FaweChangeSet fcs = (FaweChangeSet) session.getChangeSet(); final FaweExtent fe = session.getFaweExtent(); session.setChangeSet(fcs); - final CuboidRegion cb = (CuboidRegion) region; final boolean cuboid = region instanceof CuboidRegion; Set chunks = region.getChunks(); TaskManager.IMP.objectTask(chunks, new RunnableVal() { diff --git a/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java b/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java index 4327fa4d..5f8f10ca 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java @@ -85,6 +85,7 @@ import com.sk89q.worldedit.util.logging.DynamicStreamHandler; import com.sk89q.worldedit.util.logging.LogFormat; import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; import java.util.logging.FileHandler; import java.util.logging.Level; import java.util.logging.Logger; @@ -236,25 +237,31 @@ public final class CommandManager { LocalConfiguration config = worldEdit.getConfiguration(); CommandLocals locals = new CommandLocals(); - if (actor != null && actor.isPlayer()) { - locals.put(Actor.class, new PlayerWrapper((Player) actor)); - } else { - locals.put(Actor.class, actor); - } - locals.put("arguments", event.getArguments()); - - final long start = System.currentTimeMillis(); FawePlayer fp; - if (actor.isPlayer()) { - fp = Fawe.imp().wrap(actor.getName()); + if (actor != null && actor.isPlayer()) { + try { + Field fieldBasePlayer = actor.getClass().getDeclaredField("basePlayer"); + fieldBasePlayer.setAccessible(true); + Player player = (Player) fieldBasePlayer.get(actor); + Field fieldPlayer = player.getClass().getDeclaredField("player"); + fieldPlayer.setAccessible(true); + fp = Fawe.imp().wrap(fieldPlayer.get(player)); + } catch (Throwable e) { + e.printStackTrace(); + fp = Fawe.imp().wrap(actor.getName()); + } if (fp.getMeta("fawe_action") != null) { BBC.WORLDEDIT_COMMAND_LIMIT.send(fp); return; } fp.setMeta("fawe_action", true); + locals.put(Actor.class, new PlayerWrapper((Player) actor)); } else { + locals.put(Actor.class, actor); fp = null; } + locals.put("arguments", event.getArguments()); + final long start = System.currentTimeMillis(); try { dispatcher.call(Joiner.on(" ").join(split), locals, new String[0]); } catch (CommandPermissionsException e) { diff --git a/core/src/main/java/com/sk89q/worldedit/session/SessionManager.java b/core/src/main/java/com/sk89q/worldedit/session/SessionManager.java new file mode 100644 index 00000000..c43b0d6f --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/session/SessionManager.java @@ -0,0 +1,336 @@ +/* + * 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.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.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.

+ */ +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 Map sessions = new HashMap(); + private SessionStore store = new VoidStore(); + + /** + * Create a new session manager. + * + * @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 owner. + * + * @param owner the owner + * @return true if a session exists + */ + public synchronized boolean contains(SessionOwner owner) { + checkNotNull(owner); + return sessions.containsKey(getKey(owner)); + } + + /** + * Find a session by its name specified by {@link SessionKey#getName()}. + * + * @param name the name + * @return the session, if found, otherwise {@code null} + */ + @Nullable + public synchronized LocalSession findByName(String name) { + checkNotNull(name); + for (SessionHolder holder : sessions.values()) { + String test = holder.key.getName(); + if (test != null && name.equals(test)) { + return holder.session; + } + } + + return null; + } + + /** + * Gets the session for an owner and return it if it exists, otherwise + * return {@code null}. + * + * @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(SessionOwner owner) { + checkNotNull(owner); + + LocalSession session = getIfPresent(owner); + LocalConfiguration config = worldEdit.getConfiguration(); + SessionKey sessionKey = owner.getSessionKey(); + + // No session exists yet -- create one + if (session == null) { + try { + session = store.load(getKey(sessionKey)); + session.postLoad(); + } 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 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 owner has an override or there + // is no limit. There is also a default limit + int currentChangeLimit = session.getBlockChangeLimit(); + + 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) { + if (currentChangeLimit < 0 || currentChangeLimit > config.maxChangeLimit) { + session.setBlockChangeLimit(config.maxChangeLimit); + } + } else { + // Bound the change limit + int maxChangeLimit = config.maxChangeLimit; + if (currentChangeLimit == -1 || currentChangeLimit > maxChangeLimit) { + session.setBlockChangeLimit(maxChangeLimit); + } + } + } + + // Have the session use inventory if it's enabled and the owner + // doesn't have an override + session.setUseInventory(config.useInventory + && !(config.useInventoryOverride + && (owner.hasPermission("worldedit.inventory.unrestricted") + || (config.useInventoryCreativeOverride && (!(owner instanceof Player) || ((Player) owner).hasCreativeMode()))))); + + return session; + } + + /** + * Save a map of sessions to disk. + * + * @param sessions a map of sessions to save + * @return a future that completes on save or error + */ + 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; + } + }); + } + + /** + * Get the key to use in the map for an owner. + * + * @param owner the owner + * @return the key object + */ + 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 owner if one exists. + * + * @param owner the owner + */ + public synchronized void remove(SessionOwner owner) { + checkNotNull(owner); + sessions.remove(getKey(owner)); + } + + /** + * Remove all sessions. + */ + public synchronized void clear() { + sessions.clear(); + } + + @Subscribe + public void onConfigurationLoad(ConfigurationLoadEvent event) { + LocalConfiguration config = event.getConfiguration(); + File dir = new File(config.getWorkingDirectory(), "sessions"); + store = new JsonFileSessionStore(dir); + } + + /** + * 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); + } + } + } + } + +} \ No newline at end of file