PageRenderTime 43ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java

https://gitlab.com/Skull3x/WorldEdit
Java | 336 lines | 196 code | 42 blank | 98 comment | 39 complexity | e2134d24679ea493ba2d99060d07c7f8 MD5 | raw file
  1. /*
  2. * WorldEdit, a Minecraft world manipulation toolkit
  3. * Copyright (C) sk89q <http://www.sk89q.com>
  4. * Copyright (C) WorldEdit team and contributors
  5. *
  6. * This program is free software: you can redistribute it and/or modify it
  7. * under the terms of the GNU Lesser General Public License as published by the
  8. * Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but WITHOUT
  12. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
  14. * for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.sk89q.worldedit.session;
  20. import com.google.common.util.concurrent.Futures;
  21. import com.google.common.util.concurrent.ListenableFuture;
  22. import com.google.common.util.concurrent.ListeningExecutorService;
  23. import com.google.common.util.concurrent.MoreExecutors;
  24. import com.sk89q.worldedit.LocalConfiguration;
  25. import com.sk89q.worldedit.LocalSession;
  26. import com.sk89q.worldedit.WorldEdit;
  27. import com.sk89q.worldedit.entity.Player;
  28. import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent;
  29. import com.sk89q.worldedit.session.storage.JsonFileSessionStore;
  30. import com.sk89q.worldedit.session.storage.SessionStore;
  31. import com.sk89q.worldedit.session.storage.VoidStore;
  32. import com.sk89q.worldedit.util.concurrency.EvenMoreExecutors;
  33. import com.sk89q.worldedit.util.eventbus.Subscribe;
  34. import javax.annotation.Nullable;
  35. import java.io.File;
  36. import java.io.IOException;
  37. import java.util.HashMap;
  38. import java.util.Iterator;
  39. import java.util.Map;
  40. import java.util.Timer;
  41. import java.util.TimerTask;
  42. import java.util.UUID;
  43. import java.util.concurrent.Callable;
  44. import java.util.logging.Level;
  45. import java.util.logging.Logger;
  46. import static com.google.common.base.Preconditions.checkNotNull;
  47. /**
  48. * Session manager for WorldEdit.
  49. *
  50. * <p>Get a reference to one from {@link WorldEdit}.</p>
  51. *
  52. * <p>While this class is thread-safe, the returned session may not be.</p>
  53. */
  54. public class SessionManager {
  55. public static int EXPIRATION_GRACE = 600000;
  56. private static final int FLUSH_PERIOD = 1000 * 30;
  57. private static final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(EvenMoreExecutors.newBoundedCachedThreadPool(0, 1, 5));
  58. private static final Logger log = Logger.getLogger(SessionManager.class.getCanonicalName());
  59. private final Timer timer = new Timer();
  60. private final WorldEdit worldEdit;
  61. private final Map<UUID, SessionHolder> sessions = new HashMap<UUID, SessionHolder>();
  62. private SessionStore store = new VoidStore();
  63. /**
  64. * Create a new session manager.
  65. *
  66. * @param worldEdit a WorldEdit instance
  67. */
  68. public SessionManager(WorldEdit worldEdit) {
  69. checkNotNull(worldEdit);
  70. this.worldEdit = worldEdit;
  71. worldEdit.getEventBus().register(this);
  72. timer.schedule(new SessionTracker(), FLUSH_PERIOD, FLUSH_PERIOD);
  73. }
  74. /**
  75. * Get whether a session exists for the given owner.
  76. *
  77. * @param owner the owner
  78. * @return true if a session exists
  79. */
  80. public synchronized boolean contains(SessionOwner owner) {
  81. checkNotNull(owner);
  82. return sessions.containsKey(getKey(owner));
  83. }
  84. /**
  85. * Find a session by its name specified by {@link SessionKey#getName()}.
  86. *
  87. * @param name the name
  88. * @return the session, if found, otherwise {@code null}
  89. */
  90. @Nullable
  91. public synchronized LocalSession findByName(String name) {
  92. checkNotNull(name);
  93. for (SessionHolder holder : sessions.values()) {
  94. String test = holder.key.getName();
  95. if (test != null && name.equals(test)) {
  96. return holder.session;
  97. }
  98. }
  99. return null;
  100. }
  101. /**
  102. * Gets the session for an owner and return it if it exists, otherwise
  103. * return {@code null}.
  104. *
  105. * @param owner the owner
  106. * @return the session for the owner, if it exists
  107. */
  108. @Nullable
  109. public synchronized LocalSession getIfPresent(SessionOwner owner) {
  110. checkNotNull(owner);
  111. SessionHolder stored = sessions.get(getKey(owner));
  112. if (stored != null) {
  113. return stored.session;
  114. } else {
  115. return null;
  116. }
  117. }
  118. /**
  119. * Get the session for an owner and create one if one doesn't exist.
  120. *
  121. * @param owner the owner
  122. * @return a session
  123. */
  124. public synchronized LocalSession get(SessionOwner owner) {
  125. checkNotNull(owner);
  126. LocalSession session = getIfPresent(owner);
  127. LocalConfiguration config = worldEdit.getConfiguration();
  128. SessionKey sessionKey = owner.getSessionKey();
  129. // No session exists yet -- create one
  130. if (session == null) {
  131. try {
  132. session = store.load(getKey(sessionKey));
  133. session.postLoad();
  134. } catch (IOException e) {
  135. log.log(Level.WARNING, "Failed to load saved session", e);
  136. session = new LocalSession();
  137. }
  138. session.setConfiguration(config);
  139. session.setBlockChangeLimit(config.defaultChangeLimit);
  140. // Remember the session if the session is still active
  141. if (sessionKey.isActive()) {
  142. sessions.put(getKey(owner), new SessionHolder(sessionKey, session));
  143. }
  144. }
  145. // Set the limit on the number of blocks that an operation can
  146. // change at once, or don't if the owner has an override or there
  147. // is no limit. There is also a default limit
  148. int currentChangeLimit = session.getBlockChangeLimit();
  149. if (!owner.hasPermission("worldedit.limit.unrestricted") && config.maxChangeLimit > -1) {
  150. // If the default limit is infinite but there is a maximum
  151. // limit, make sure to not have it be overridden
  152. if (config.defaultChangeLimit < 0) {
  153. if (currentChangeLimit < 0 || currentChangeLimit > config.maxChangeLimit) {
  154. session.setBlockChangeLimit(config.maxChangeLimit);
  155. }
  156. } else {
  157. // Bound the change limit
  158. int maxChangeLimit = config.maxChangeLimit;
  159. if (currentChangeLimit == -1 || currentChangeLimit > maxChangeLimit) {
  160. session.setBlockChangeLimit(maxChangeLimit);
  161. }
  162. }
  163. }
  164. // Have the session use inventory if it's enabled and the owner
  165. // doesn't have an override
  166. session.setUseInventory(config.useInventory
  167. && !(config.useInventoryOverride
  168. && (owner.hasPermission("worldedit.inventory.unrestricted")
  169. || (config.useInventoryCreativeOverride && (!(owner instanceof Player) || ((Player) owner).hasCreativeMode())))));
  170. return session;
  171. }
  172. /**
  173. * Save a map of sessions to disk.
  174. *
  175. * @param sessions a map of sessions to save
  176. * @return a future that completes on save or error
  177. */
  178. private ListenableFuture<?> commit(final Map<SessionKey, LocalSession> sessions) {
  179. checkNotNull(sessions);
  180. if (sessions.isEmpty()) {
  181. return Futures.immediateFuture(sessions);
  182. }
  183. return executorService.submit(new Callable<Object>() {
  184. @Override
  185. public Object call() throws Exception {
  186. Exception exception = null;
  187. for (Map.Entry<SessionKey, LocalSession> entry : sessions.entrySet()) {
  188. SessionKey key = entry.getKey();
  189. if (key.isPersistent()) {
  190. try {
  191. store.save(getKey(key), entry.getValue());
  192. } catch (IOException e) {
  193. log.log(Level.WARNING, "Failed to write session for UUID " + getKey(key), e);
  194. exception = e;
  195. }
  196. }
  197. }
  198. if (exception != null) {
  199. throw exception;
  200. }
  201. return sessions;
  202. }
  203. });
  204. }
  205. /**
  206. * Get the key to use in the map for an owner.
  207. *
  208. * @param owner the owner
  209. * @return the key object
  210. */
  211. protected UUID getKey(SessionOwner owner) {
  212. return getKey(owner.getSessionKey());
  213. }
  214. /**
  215. * Get the key to use in the map for a {@code SessionKey}.
  216. *
  217. * @param key the session key object
  218. * @return the key object
  219. */
  220. protected UUID getKey(SessionKey key) {
  221. String forcedKey = System.getProperty("worldedit.session.uuidOverride");
  222. if (forcedKey != null) {
  223. return UUID.fromString(forcedKey);
  224. } else {
  225. return key.getUniqueId();
  226. }
  227. }
  228. /**
  229. * Remove the session for the given owner if one exists.
  230. *
  231. * @param owner the owner
  232. */
  233. public synchronized void remove(SessionOwner owner) {
  234. checkNotNull(owner);
  235. sessions.remove(getKey(owner));
  236. }
  237. /**
  238. * Remove all sessions.
  239. */
  240. public synchronized void clear() {
  241. sessions.clear();
  242. }
  243. @Subscribe
  244. public void onConfigurationLoad(ConfigurationLoadEvent event) {
  245. LocalConfiguration config = event.getConfiguration();
  246. File dir = new File(config.getWorkingDirectory(), "sessions");
  247. store = new JsonFileSessionStore(dir);
  248. }
  249. /**
  250. * Stores the owner of a session, the session, and the last active time.
  251. */
  252. private static class SessionHolder {
  253. private final SessionKey key;
  254. private final LocalSession session;
  255. private long lastActive = System.currentTimeMillis();
  256. private SessionHolder(SessionKey key, LocalSession session) {
  257. this.key = key;
  258. this.session = session;
  259. }
  260. }
  261. /**
  262. * Removes inactive sessions after they have been inactive for a period
  263. * of time. Commits them as well.
  264. */
  265. private class SessionTracker extends TimerTask {
  266. @Override
  267. public void run() {
  268. synchronized (SessionManager.this) {
  269. long now = System.currentTimeMillis();
  270. Iterator<SessionHolder> it = sessions.values().iterator();
  271. Map<SessionKey, LocalSession> saveQueue = new HashMap<SessionKey, LocalSession>();
  272. while (it.hasNext()) {
  273. SessionHolder stored = it.next();
  274. if (stored.key.isActive()) {
  275. stored.lastActive = now;
  276. if (stored.session.compareAndResetDirty()) {
  277. saveQueue.put(stored.key, stored.session);
  278. }
  279. } else {
  280. if (now - stored.lastActive > EXPIRATION_GRACE) {
  281. if (stored.session.compareAndResetDirty()) {
  282. saveQueue.put(stored.key, stored.session);
  283. }
  284. it.remove();
  285. }
  286. }
  287. }
  288. if (!saveQueue.isEmpty()) {
  289. commit(saveQueue);
  290. }
  291. }
  292. }
  293. }
  294. }