Java tutorial
/* * Morphy Open Source Chess Server * Copyright (c) 2008-2011, 2016-2017 http://code.google.com/p/morphy-chess-server/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package morphy.user; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.Timer; import java.util.TreeMap; import morphy.Morphy; import morphy.channel.Channel; import morphy.game.Game; import morphy.game.GameInterface; import morphy.game.request.MatchRequest; import morphy.game.request.PartnershipRequest; import morphy.game.request.Request; import morphy.service.*; import morphy.service.ScreenService.Screen; import morphy.utils.BufferUtils; import morphy.utils.john.TimeZoneUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class SocketChannelUserSession implements UserSession, Comparable<UserSession> { public enum UserSessionState { LOGIN_NEED_USERNAME, LOGIN_NEED_PASSWORD, LOGGED_IN; } protected static Log LOG = LogFactory.getLog(SocketChannelUserSession.class); protected User user; protected SocketChannel channel; protected StringBuilder inputBuffer; protected long lastReceivedTime; protected long loginTime; protected Map<UserSessionKey, Object> objectMap; protected Timer idleLogoutTimer; protected Channel lastChannelToldTo = null; protected UserSession lastPersonToldTo = null; protected boolean isPlaying = false; protected boolean isExamining = false; protected List<Integer> gamesObserving; protected UserSessionState currentState; public boolean usingTimeseal = false; protected List<SocketChannelUserSession> multipleLogins; protected SocketChannelUserSession multipleLoginsParent; public SocketChannelUserSession(User user, SocketChannel channel) { this.user = user; this.channel = channel; inputBuffer = new StringBuilder(400); loginTime = System.currentTimeMillis(); objectMap = new TreeMap<UserSessionKey, Object>(); if (!UserService.getInstance().isAdmin(user.getUserName())) idleLogoutTimer = new Timer(); gamesObserving = new ArrayList<Integer>(); if (LOG.isInfoEnabled()) { LOG.info("Created SocketChannelUserSession user " + user.getUserName() + " " + channel.socket().getInetAddress()); } multipleLogins = new ArrayList<SocketChannelUserSession>(0); } public void addParentOnMultipleLogins(SocketChannelUserSession e) { multipleLoginsParent = e; } public void addUserOnMultipleLogins(SocketChannelUserSession e) { multipleLogins.add(e); } public void scheduleIdleTimeout() { if (idleLogoutTimer != null) idleLogoutTimer.cancel(); // admins don't idle out if (UserService.getInstance().isAdmin(getUser().getUserName())) { return; } /*LOG.info("scheduling " + getUser().getUserName() + " for idle logout (60 minutes)");*/ final int millis = 60 * 60 * 1000; if (idleLogoutTimer != null) idleLogoutTimer.cancel(); final UserSession sess = this; idleLogoutTimer = new Timer(); idleLogoutTimer.schedule(new java.util.TimerTask() { public void run() { if (getIdleTimeMillis() >= millis - 1) { send("\n\n**** Auto-logout because you were idle for 60 minutes ****\n"); if (isExamining()) { GameService gs = GameService.getInstance(); GameInterface gi = gs.map.get(sess); if (gi != null) { gs.unexamineGame(sess); } } disconnect(); } else { idleLogoutTimer.purge(); scheduleIdleTimeout(); // recursion } } }, 60 * 60 * 1000); } public void disconnect() { if (isConnected()) { String v = getNotifyNames(); if (!v.equals("")) send("Your departure was noted by the following: " + v); send(ScreenService.getInstance().getScreen(Screen.Logout)); if (getUser().isRegistered()) { // save user variables getUser().getUserVars().dumpToDB(); } if (user.getUserName() != null) { if (LOG.isInfoEnabled()) { LOG.info("Disconnected user " + user.getUserName()); } UserService.getInstance().removeLoggedInUser(this); SocketConnectionService.getInstance().removeUserSession(this); SeekService.getInstance().removeSeeksByUsername(user.getUserName()); if (idleLogoutTimer != null) { idleLogoutTimer.cancel(); } RequestService rs = RequestService.getInstance(); List<Request> list = rs.getRequestsTo(this); if (list != null) { for (Request r : list) { String toUsername = r.getTo().getUser().getUserName(); if (r.getClass() == MatchRequest.class) { r.getFrom().send(String.format( "%s whom you were challenging, has departed.\nChallenge to %s withdrawn.", toUsername, toUsername)); } else if (r.getClass() == PartnershipRequest.class) { r.getFrom() .send(String.format( "%s, whom you were offering a partnership with, has departed.\n" + "Partnership offer to %s withdrawn.", toUsername, toUsername)); } rs.removeRequest(r); } } GameService gs = GameService.getInstance(); morphy.game.GameInterface g = gs.map.get(this); if (g != null) { if (g instanceof Game) { Game gg = (Game) g; gg.setReason(String.format("%s forfeits by disconnection", user.getUserName())); gg.setResult(this == gg.getWhite() ? "0-1" : "1-0"); gs.endGame(gg); final String line = "\n{Game " + g.getGameNumber() + " (" + gg.getWhite().getUser().getUserName() + " vs. " + gg.getBlack().getUser().getUserName() + ") " + gg.getReason() + "} " + gg.getResult() + ""; if (this != gg.getWhite()) gg.getWhite().send(line); if (this != gg.getBlack()) gg.getBlack().send(line); } } gs.map.remove(this); sendDisconnectPinNotifications(); sendDisconnectNotifications(); } try { channel.close(); } catch (Throwable t) { if (LOG.isErrorEnabled()) { LOG.error("Error disconnecting socket channel", t); } } } } private void sendDisconnectPinNotifications() { UserSession[] sessions = UserService.getInstance().fetchAllUsersWithVariable("pin", "1"); for (UserSession s : sessions) { s.send(String.format("[%s has disconnected.]", getUser().getUserName())); } } private void sendDisconnectNotifications() { // Notifications are only sent if this user is registered. if (getUser().isRegistered()) { String query = "SELECT u.username FROM personallist pl INNER JOIN personallist_entry ple ON (pl.id = ple.personallist_id) INNER JOIN users u ON (u.id = pl.user_id) WHERE pl.`name` = 'notify' && ple.`value` LIKE '" + getUser().getUserName() + "';"; DatabaseConnectionService dbcs = DatabaseConnectionService.getInstance(); ResultSet resultSet = dbcs.getDBConnection().executeQueryWithRS(query); try { UserService us = UserService.getInstance(); while (resultSet.next()) { String username = resultSet.getString(1); UserSession sess = us.getUserSession(username); if (sess != null && sess.isConnected()) { boolean highlight = sess.getUser().getUserVars().getVariables().get("highlight") .equals("1"); sess.send( "Notification: " + (highlight ? ((char) 27) + "[7m" : "") + getUser().getUserName() + (highlight ? ((char) 27) + "[0m" : "") + " has departed."); } } } catch (SQLException e) { Morphy.getInstance().onError(e); } } } public Object get(UserSessionKey key) { return objectMap.get(key); } public Boolean getBoolean(UserSessionKey key) { return (Boolean) get(key); } public SocketChannel getChannel() { return channel; } public long getIdleTimeMillis() { return lastReceivedTime == 0 ? 0 : System.currentTimeMillis() - lastReceivedTime; } public StringBuilder getInputBuffer() { return inputBuffer; } public Integer getInt(UserSessionKey key) { return (Integer) get(key); } public long getLoginTime() { return loginTime; } public String getString(UserSessionKey key) { return (String) get(key); } public User getUser() { return user; } public boolean hasLoggedIn() { return currentState == UserSessionState.LOGGED_IN; } public boolean isConnected() { return channel.isOpen(); } public void put(UserSessionKey key, Object object) { objectMap.put(key, object); } public void send(String message) { try { /* this logic block should be commented out to get rid of multiple-login implementation. */ if (multipleLoginsParent != null) { List<SocketChannelUserSession> list = multipleLoginsParent.multipleLogins; multipleLoginsParent.multipleLogins = null; multipleLoginsParent.send(message); multipleLoginsParent.multipleLogins = list; } else { if (multipleLogins != null) { for (SocketChannelUserSession sess : multipleLogins) { if (sess != null) sess.send(message); } } } if (isConnected()) { String prompt = "fics% "; HashMap<String, String> map = getUser().getUserVars().getVariables(); if (map.containsKey("prompt") && map.containsKey("ptime") && map.containsKey("tzone")) { prompt = map.get("prompt"); boolean useptime = map.get("ptime").equals("1"); if (useptime) { Date d = new Date(); java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("HH:mm"); String tzone = getUser().getUserVars().getVariables().get("tzone").toUpperCase(); TimeZone tz = TimeZone.getDefault(); if (tzone.equals("SERVER")) tzone = tz.getDisplayName(tz.inDaylightTime(d), TimeZone.SHORT); sdf.setTimeZone(TimeZoneUtils.getTimeZone(tzone)); prompt = sdf.format(d) + "_" + prompt; } } ByteBuffer buffer = BufferUtils.createBuffer( SocketConnectionService.getInstance().formatMessage(this, message + "\n" + prompt + " ")); System.out.println((message + "\n\r" + prompt + " ").replace("\n", "\\n").replace("\r", "\\r")); try { channel.write(buffer); } catch (java.io.IOException e) { Morphy.getInstance().onError("IOException while trying to write to channel.", e); } } else { if (LOG.isInfoEnabled()) { LOG.info("Tried to send message to a logged off user " + user.getUserName() + " " + message); } disconnect(); } } catch (Throwable t) { if (LOG.isErrorEnabled()) LOG.error("Error sending message to user " + user.getUserName() + " " + message, t); disconnect(); } } public void setChannel(SocketChannel channel) { this.channel = channel; } public void setInputBuffer(StringBuilder inputBuffer) { this.inputBuffer = inputBuffer; } public void setUser(User user) { this.user = user; } public void touchLastReceivedTime() { lastReceivedTime = System.currentTimeMillis(); scheduleIdleTimeout(); } public int compareTo(UserSession o) { return getUser().getUserName().compareToIgnoreCase(o.getUser().getUserName()); } /** Gets the player names who have this user on their notify list.<br /> * Note that this method has poor performance, O(N), where N = number of logged in players.<br /> * Returns an empty string if no names. */ private String getNotifyNames() { StringBuilder b = new StringBuilder(); final UserSession[] arr = UserService.getInstance().getLoggedInUsers(); java.util.Arrays.sort(arr); for (int i = 0; i < arr.length; i++) { UserSession s = arr[i]; List<String> l = s.getUser().getLists().get(PersonalList.notify); if (l.contains(getUser().getUserName())) { b.append(s.getUser().getUserName()); if (i != l.size() - 1) b.append(" "); } } return b.toString(); } public Channel getLastChannelToldTo() { return lastChannelToldTo; } public void setLastChannelToldTo(Channel lastChannelToldTo) { this.lastChannelToldTo = lastChannelToldTo; } public UserSession getLastPersonToldTo() { return lastPersonToldTo; } public void setLastPersonToldTo(UserSession lastPersonToldTo) { this.lastPersonToldTo = lastPersonToldTo; } public boolean isPlaying() { return isPlaying; } public void setPlaying(boolean isPlaying) { this.isPlaying = isPlaying; } public boolean isExamining() { return isExamining; } public void setExamining(boolean isExamining) { this.isExamining = isExamining; } public List<Integer> getGamesObserving() { return gamesObserving; } public void setGamesObserving(List<Integer> gamesObserving) { this.gamesObserving = gamesObserving; } public void setCurrentState(UserSessionState currentState) { this.currentState = currentState; } public UserSessionState getCurrentState() { return currentState; } }