2014-04-04 02:12:30 +00:00
|
|
|
/*
|
|
|
|
* WorldEdit, a Minecraft world manipulation toolkit
|
|
|
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
|
|
|
* Copyright (C) WorldEdit team and contributors
|
|
|
|
*
|
2014-04-04 22:03:18 +00:00
|
|
|
* 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
|
2014-04-04 02:12:30 +00:00
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
2014-04-04 22:03:18 +00:00
|
|
|
* 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.
|
2014-04-04 02:12:30 +00:00
|
|
|
*
|
2014-04-04 22:03:18 +00:00
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
2014-04-04 02:12:30 +00:00
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.sk89q.worldedit.session;
|
|
|
|
|
2014-07-28 03:44:05 +00:00
|
|
|
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;
|
2014-04-05 00:54:14 +00:00
|
|
|
import com.sk89q.worldedit.entity.Player;
|
2014-07-28 03:44:05 +00:00
|
|
|
import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent;
|
2019-03-16 23:04:24 +00:00
|
|
|
import com.sk89q.worldedit.session.request.Request;
|
2014-07-28 03:44:05 +00:00
|
|
|
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;
|
2018-07-19 12:41:26 +00:00
|
|
|
import com.sk89q.worldedit.world.gamemode.GameModes;
|
2019-03-14 02:51:48 +00:00
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2014-04-04 02:12:30 +00:00
|
|
|
|
2019-03-14 02:51:48 +00:00
|
|
|
import javax.annotation.Nullable;
|
2014-07-28 03:44:05 +00:00
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
2014-04-04 02:12:30 +00:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.Map;
|
2014-07-28 03:44:05 +00:00
|
|
|
import java.util.Timer;
|
|
|
|
import java.util.TimerTask;
|
|
|
|
import java.util.UUID;
|
2019-04-05 06:51:42 +00:00
|
|
|
import java.util.concurrent.Callable;
|
2018-08-12 14:03:07 +00:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
2014-04-04 02:12:30 +00:00
|
|
|
|
2018-08-12 14:03:07 +00:00
|
|
|
|
|
|
|
import static com.google.common.base.Preconditions.checkNotNull;
|
|
|
|
|
2014-04-04 02:12:30 +00:00
|
|
|
/**
|
|
|
|
* Session manager for WorldEdit.
|
2014-07-28 03:44:05 +00:00
|
|
|
*
|
|
|
|
* <p>Get a reference to one from {@link WorldEdit}.</p>
|
|
|
|
*
|
|
|
|
* <p>While this class is thread-safe, the returned session may not be.</p>
|
2014-04-04 02:12:30 +00:00
|
|
|
*/
|
|
|
|
public class SessionManager {
|
|
|
|
|
2019-04-03 05:53:34 +00:00
|
|
|
public static int EXPIRATION_GRACE = 0;
|
|
|
|
private static final int FLUSH_PERIOD = 1000 * 60;
|
2014-07-28 03:44:05 +00:00
|
|
|
private static final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(EvenMoreExecutors.newBoundedCachedThreadPool(0, 1, 5));
|
2019-03-14 02:51:48 +00:00
|
|
|
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
|
2014-07-28 03:44:05 +00:00
|
|
|
private final Timer timer = new Timer();
|
2014-04-04 02:12:30 +00:00
|
|
|
private final WorldEdit worldEdit;
|
2018-06-16 06:36:55 +00:00
|
|
|
private final Map<UUID, SessionHolder> sessions = new HashMap<>();
|
2014-07-28 03:44:05 +00:00
|
|
|
private SessionStore store = new VoidStore();
|
2014-04-04 02:12:30 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new session manager.
|
|
|
|
*
|
|
|
|
* @param worldEdit a WorldEdit instance
|
|
|
|
*/
|
|
|
|
public SessionManager(WorldEdit worldEdit) {
|
2014-07-28 03:44:05 +00:00
|
|
|
checkNotNull(worldEdit);
|
2014-04-04 02:12:30 +00:00
|
|
|
this.worldEdit = worldEdit;
|
2014-07-28 03:44:05 +00:00
|
|
|
|
|
|
|
worldEdit.getEventBus().register(this);
|
|
|
|
timer.schedule(new SessionTracker(), FLUSH_PERIOD, FLUSH_PERIOD);
|
2014-04-04 02:12:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-28 03:44:05 +00:00
|
|
|
* Get whether a session exists for the given owner.
|
2014-04-04 02:12:30 +00:00
|
|
|
*
|
2014-07-28 03:44:05 +00:00
|
|
|
* @param owner the owner
|
2014-04-04 02:12:30 +00:00
|
|
|
* @return true if a session exists
|
|
|
|
*/
|
2014-07-28 03:44:05 +00:00
|
|
|
public synchronized boolean contains(SessionOwner owner) {
|
|
|
|
checkNotNull(owner);
|
|
|
|
return sessions.containsKey(getKey(owner));
|
2014-04-04 02:12:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-28 03:44:05 +00:00
|
|
|
* Find a session by its name specified by {@link SessionKey#getName()}.
|
2014-04-04 02:12:30 +00:00
|
|
|
*
|
2014-07-28 03:44:05 +00:00
|
|
|
* @param name the name
|
|
|
|
* @return the session, if found, otherwise {@code null}
|
2014-04-04 02:12:30 +00:00
|
|
|
*/
|
2014-07-28 03:44:05 +00:00
|
|
|
@Nullable
|
|
|
|
public synchronized LocalSession findByName(String name) {
|
|
|
|
checkNotNull(name);
|
|
|
|
for (SessionHolder holder : sessions.values()) {
|
|
|
|
String test = holder.key.getName();
|
2018-08-27 07:24:18 +00:00
|
|
|
if (name.equals(test)) {
|
2014-07-28 03:44:05 +00:00
|
|
|
return holder.session;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2014-04-04 02:12:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-28 03:44:05 +00:00
|
|
|
* Gets the session for an owner and return it if it exists, otherwise
|
|
|
|
* return {@code null}.
|
2014-04-04 02:12:30 +00:00
|
|
|
*
|
2014-07-28 03:44:05 +00:00
|
|
|
* @param owner the owner
|
|
|
|
* @return the session for the owner, if it exists
|
2014-04-04 02:12:30 +00:00
|
|
|
*/
|
2014-07-28 03:44:05 +00:00
|
|
|
@Nullable
|
|
|
|
public synchronized LocalSession getIfPresent(SessionOwner owner) {
|
|
|
|
checkNotNull(owner);
|
|
|
|
SessionHolder stored = sessions.get(getKey(owner));
|
|
|
|
if (stored != null) {
|
|
|
|
return stored.session;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2014-04-04 02:12:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-28 03:44:05 +00:00
|
|
|
* Get the session for an owner and create one if one doesn't exist.
|
2014-04-04 02:12:30 +00:00
|
|
|
*
|
2014-07-28 03:44:05 +00:00
|
|
|
* @param owner the owner
|
2014-04-04 02:12:30 +00:00
|
|
|
* @return a session
|
|
|
|
*/
|
2014-07-28 03:44:05 +00:00
|
|
|
public synchronized LocalSession get(SessionOwner owner) {
|
|
|
|
checkNotNull(owner);
|
2014-04-04 02:12:30 +00:00
|
|
|
|
2014-07-28 03:44:05 +00:00
|
|
|
LocalSession session = getIfPresent(owner);
|
2014-04-04 02:12:30 +00:00
|
|
|
LocalConfiguration config = worldEdit.getConfiguration();
|
2014-07-28 03:44:05 +00:00
|
|
|
SessionKey sessionKey = owner.getSessionKey();
|
2014-04-04 02:12:30 +00:00
|
|
|
|
2014-07-28 03:44:05 +00:00
|
|
|
// No session exists yet -- create one
|
|
|
|
if (session == null) {
|
|
|
|
try {
|
|
|
|
session = store.load(getKey(sessionKey));
|
2014-07-29 03:53:17 +00:00
|
|
|
session.postLoad();
|
2014-07-28 03:44:05 +00:00
|
|
|
} catch (IOException e) {
|
2019-03-14 02:51:48 +00:00
|
|
|
log.warn("Failed to load saved session", e);
|
2014-07-28 03:44:05 +00:00
|
|
|
session = new LocalSession();
|
|
|
|
}
|
2019-03-16 23:04:24 +00:00
|
|
|
Request.request().setSession(session);
|
2014-07-28 03:44:05 +00:00
|
|
|
|
|
|
|
session.setConfiguration(config);
|
2014-04-04 02:12:30 +00:00
|
|
|
session.setBlockChangeLimit(config.defaultChangeLimit);
|
2019-03-07 00:58:32 +00:00
|
|
|
session.setTimeout(config.calculationTimeout);
|
2014-07-28 03:44:05 +00:00
|
|
|
|
2015-06-26 05:09:09 +00:00
|
|
|
// Remember the session regardless of if it's currently active or not.
|
|
|
|
// And have the SessionTracker FLUSH inactive sessions.
|
2018-08-12 14:03:07 +00:00
|
|
|
sessions.put(getKey(owner), new SessionHolder(sessionKey, session));
|
2014-04-04 02:12:30 +00:00
|
|
|
}
|
|
|
|
|
2019-03-12 00:37:35 +00:00
|
|
|
if (shouldBoundLimit(owner, "worldedit.limit.unrestricted", session.getBlockChangeLimit(), config.maxChangeLimit)) {
|
2019-03-07 00:58:32 +00:00
|
|
|
session.setBlockChangeLimit(config.maxChangeLimit);
|
|
|
|
}
|
2019-03-12 00:37:35 +00:00
|
|
|
if (shouldBoundLimit(owner, "worldedit.timeout.unrestricted", session.getTimeout(), config.maxCalculationTimeout)) {
|
2019-03-07 00:58:32 +00:00
|
|
|
session.setTimeout(config.maxCalculationTimeout);
|
2014-04-04 02:12:30 +00:00
|
|
|
}
|
|
|
|
|
2014-07-28 03:44:05 +00:00
|
|
|
// Have the session use inventory if it's enabled and the owner
|
2014-04-04 02:12:30 +00:00
|
|
|
// doesn't have an override
|
|
|
|
session.setUseInventory(config.useInventory
|
|
|
|
&& !(config.useInventoryOverride
|
2014-07-28 03:44:05 +00:00
|
|
|
&& (owner.hasPermission("worldedit.inventory.unrestricted")
|
2018-07-19 12:41:26 +00:00
|
|
|
|| (config.useInventoryCreativeOverride && (!(owner instanceof Player) || ((Player) owner).getGameMode() == GameModes.CREATIVE)))));
|
2014-04-04 02:12:30 +00:00
|
|
|
|
|
|
|
return session;
|
|
|
|
}
|
|
|
|
|
2019-03-12 00:37:35 +00:00
|
|
|
private boolean shouldBoundLimit(SessionOwner owner, String permission, int currentLimit, int maxLimit) {
|
|
|
|
if (maxLimit > -1) { // if max is finite
|
|
|
|
return (currentLimit < 0 || currentLimit > maxLimit) // make sure current is finite and less than max
|
|
|
|
&& !owner.hasPermission(permission); // unless user has unlimited permission
|
2019-03-07 00:58:32 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-04-04 02:12:30 +00:00
|
|
|
/**
|
2014-07-28 03:44:05 +00:00
|
|
|
* 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<SessionKey, LocalSession> sessions) {
|
|
|
|
checkNotNull(sessions);
|
|
|
|
|
|
|
|
if (sessions.isEmpty()) {
|
|
|
|
return Futures.immediateFuture(sessions);
|
|
|
|
}
|
|
|
|
|
2018-06-16 06:36:55 +00:00
|
|
|
return executorService.submit((Callable<Object>) () -> {
|
|
|
|
Exception exception = null;
|
2014-07-28 03:44:05 +00:00
|
|
|
|
2018-06-16 06:36:55 +00:00
|
|
|
for (Map.Entry<SessionKey, LocalSession> entry : sessions.entrySet()) {
|
|
|
|
SessionKey key = entry.getKey();
|
2014-07-28 03:44:05 +00:00
|
|
|
|
2018-06-16 06:36:55 +00:00
|
|
|
if (key.isPersistent()) {
|
|
|
|
try {
|
|
|
|
store.save(getKey(key), entry.getValue());
|
|
|
|
} catch (IOException e) {
|
2019-03-14 02:51:48 +00:00
|
|
|
log.warn("Failed to write session for UUID " + getKey(key), e);
|
2018-06-16 06:36:55 +00:00
|
|
|
exception = e;
|
2014-07-28 03:44:05 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-16 06:36:55 +00:00
|
|
|
}
|
2014-07-28 03:44:05 +00:00
|
|
|
|
2018-06-16 06:36:55 +00:00
|
|
|
if (exception != null) {
|
|
|
|
throw exception;
|
2014-07-28 03:44:05 +00:00
|
|
|
}
|
2018-06-16 06:36:55 +00:00
|
|
|
|
|
|
|
return sessions;
|
2014-07-28 03:44:05 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the key to use in the map for an owner.
|
2014-04-04 02:12:30 +00:00
|
|
|
*
|
2014-07-28 03:44:05 +00:00
|
|
|
* @param owner the owner
|
2014-04-04 02:12:30 +00:00
|
|
|
* @return the key object
|
|
|
|
*/
|
2014-07-28 03:44:05 +00:00
|
|
|
protected UUID getKey(SessionOwner owner) {
|
|
|
|
return getKey(owner.getSessionKey());
|
2014-04-04 02:12:30 +00:00
|
|
|
}
|
|
|
|
|
2014-07-28 03:44:05 +00:00
|
|
|
|
2014-04-04 02:12:30 +00:00
|
|
|
/**
|
2014-07-28 03:44:05 +00:00
|
|
|
* Get the key to use in the map for a {@code SessionKey}.
|
2014-04-04 02:12:30 +00:00
|
|
|
*
|
2014-07-28 03:44:05 +00:00
|
|
|
* @param key the session key object
|
|
|
|
* @return the key object
|
2014-04-04 02:12:30 +00:00
|
|
|
*/
|
2014-07-28 03:44:05 +00:00
|
|
|
protected UUID getKey(SessionKey key) {
|
|
|
|
String forcedKey = System.getProperty("worldedit.session.uuidOverride");
|
|
|
|
if (forcedKey != null) {
|
|
|
|
return UUID.fromString(forcedKey);
|
|
|
|
} else {
|
|
|
|
return key.getUniqueId();
|
2014-04-04 02:12:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2014-07-28 03:44:05 +00:00
|
|
|
* Remove the session for the given owner if one exists.
|
2014-04-04 02:12:30 +00:00
|
|
|
*
|
2014-07-28 03:44:05 +00:00
|
|
|
* @param owner the owner
|
2014-04-04 02:12:30 +00:00
|
|
|
*/
|
2014-07-28 03:44:05 +00:00
|
|
|
public synchronized void remove(SessionOwner owner) {
|
|
|
|
checkNotNull(owner);
|
|
|
|
sessions.remove(getKey(owner));
|
2018-08-12 14:03:07 +00:00
|
|
|
}
|
|
|
|
|
2018-08-19 03:01:08 +00:00
|
|
|
/**
|
|
|
|
* Called to unload this session manager.
|
|
|
|
*/
|
|
|
|
public synchronized void unload() {
|
|
|
|
clear();
|
2014-04-04 02:12:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove all sessions.
|
|
|
|
*/
|
|
|
|
public synchronized void clear() {
|
2018-08-19 03:01:08 +00:00
|
|
|
saveChangedSessions();
|
2014-04-04 02:12:30 +00:00
|
|
|
sessions.clear();
|
|
|
|
}
|
|
|
|
|
2018-08-19 03:01:08 +00:00
|
|
|
private synchronized void saveChangedSessions() {
|
|
|
|
long now = System.currentTimeMillis();
|
|
|
|
Iterator<SessionHolder> it = sessions.values().iterator();
|
|
|
|
Map<SessionKey, LocalSession> saveQueue = new HashMap<>();
|
|
|
|
|
|
|
|
while (it.hasNext()) {
|
|
|
|
SessionHolder stored = it.next();
|
|
|
|
if (stored.key.isActive()) {
|
|
|
|
stored.lastActive = now;
|
|
|
|
|
|
|
|
if (stored.session.compareAndResetDirty()) {
|
2019-04-03 05:53:34 +00:00
|
|
|
// Don't save unless player disconnects
|
|
|
|
// saveQueue.put(stored.key, stored.session);
|
2018-08-19 03:01:08 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (now - stored.lastActive > EXPIRATION_GRACE) {
|
|
|
|
if (stored.session.compareAndResetDirty()) {
|
|
|
|
saveQueue.put(stored.key, stored.session);
|
|
|
|
}
|
|
|
|
|
|
|
|
it.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!saveQueue.isEmpty()) {
|
|
|
|
commit(saveQueue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-28 03:44:05 +00:00
|
|
|
@Subscribe
|
|
|
|
public void onConfigurationLoad(ConfigurationLoadEvent event) {
|
|
|
|
LocalConfiguration config = event.getConfiguration();
|
|
|
|
File dir = new File(config.getWorkingDirectory(), "sessions");
|
|
|
|
store = new JsonFileSessionStore(dir);
|
|
|
|
}
|
|
|
|
|
2014-04-04 02:12:30 +00:00
|
|
|
/**
|
2014-07-28 03:44:05 +00:00
|
|
|
* Stores the owner of a session, the session, and the last active time.
|
|
|
|
*/
|
2019-03-16 23:04:24 +00:00
|
|
|
private static final class SessionHolder {
|
2014-07-28 03:44:05 +00:00
|
|
|
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.
|
2014-04-04 02:12:30 +00:00
|
|
|
*/
|
2014-07-28 03:44:05 +00:00
|
|
|
private class SessionTracker extends TimerTask {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
synchronized (SessionManager.this) {
|
2018-08-19 03:01:08 +00:00
|
|
|
saveChangedSessions();
|
2014-04-04 02:12:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-12 14:03:07 +00:00
|
|
|
|
2019-04-02 22:21:02 +00:00
|
|
|
}
|