PageRenderTime 48ms CodeModel.GetById 33ms app.highlight 12ms RepoModel.GetById 0ms app.codeStats 1ms

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