/*
Copyright (C) 2011 monte
This file is part of PSP NetParty.
PSP NetParty 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 pspnetparty.lib.engine;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import pspnetparty.lib.FileContentCache;
import pspnetparty.lib.ILogger;
import pspnetparty.lib.LobbyUser;
import pspnetparty.lib.LobbyUserState;
import pspnetparty.lib.Utility;
import pspnetparty.lib.constants.IServerRegistry;
import pspnetparty.lib.constants.ProtocolConstants;
import pspnetparty.lib.socket.IProtocol;
import pspnetparty.lib.socket.IProtocolDriver;
import pspnetparty.lib.socket.IProtocolMessageHandler;
import pspnetparty.lib.socket.IServer;
import pspnetparty.lib.socket.IServerListener;
import pspnetparty.lib.socket.ISocketConnection;
import pspnetparty.lib.socket.TextProtocolDriver;
public class LobbyEngine {
private ILogger logger;
private FileContentCache loginMessageFile = new FileContentCache();
private int maxUsers = Integer.MAX_VALUE;
private ConcurrentHashMap<String, LobbyProtocolDriver> loginUsers;
private ConcurrentHashMap<String, ConcurrentHashMap<String, LobbyProtocolDriver>> circleMap;
private IServerRegistry serverNetWork;
private ConcurrentHashMap<LobbyStatusProtocolDriver, Object> portalConnections;
private boolean isAcceptingPortal = true;
public LobbyEngine(IServer server, ILogger logger, IServerRegistry net) throws IOException {
this.logger = logger;
serverNetWork = net;
portalConnections = new ConcurrentHashMap<LobbyStatusProtocolDriver, Object>();
loginUsers = new ConcurrentHashMap<String, LobbyProtocolDriver>();
circleMap = new ConcurrentHashMap<String, ConcurrentHashMap<String, LobbyProtocolDriver>>();
server.addServerListener(new IServerListener() {
@Override
public void log(String message) {
LobbyEngine.this.logger.log(message);
}
@Override
public void serverStartupFinished() {
}
@Override
public void serverShutdownFinished() {
}
});
server.addProtocol(new LobbyProtocol());
server.addProtocol(new LobbyStatusProtocol());
}
public void setLoginMessageFile(String loginMessageFile) {
this.loginMessageFile.setFile(loginMessageFile);
}
private void appendLoginMessage(StringBuilder sb) {
String loginMessage = loginMessageFile.getContent();
if (!Utility.isEmpty(loginMessage)) {
sb.append(TextProtocolDriver.MESSAGE_SEPARATOR);
sb.append(ProtocolConstants.Lobby.NOTIFY_FROM_ADMIN);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(loginMessage);
}
}
public Set<InetSocketAddress> getPortalAddresses() {
HashSet<InetSocketAddress> addresses = new HashSet<InetSocketAddress>();
for (LobbyStatusProtocolDriver portal : portalConnections.keySet()) {
addresses.add(portal.getConnection().getRemoteAddress());
}
return addresses;
}
public boolean isAcceptingPortal() {
return isAcceptingPortal;
}
public void setAcceptingPortal(boolean isAcceptingPortal) {
if (!isAcceptingPortal) {
for (LobbyStatusProtocolDriver portal : portalConnections.keySet()) {
portal.getConnection().disconnect();
}
portalConnections.clear();
}
this.isAcceptingPortal = isAcceptingPortal;
}
public int getCurrentPlayers() {
return loginUsers.size();
}
public void notifyAllUsers(String message) {
ByteBuffer buffer = Utility.encode(ProtocolConstants.Lobby.NOTIFY_FROM_ADMIN + TextProtocolDriver.ARGUMENT_SEPARATOR + message);
for (Entry<String, LobbyProtocolDriver> entry : loginUsers.entrySet()) {
LobbyProtocolDriver user = entry.getValue();
buffer.position(0);
user.getConnection().send(buffer);
}
}
private class LobbyProtocol implements IProtocol {
@Override
public void log(String message) {
}
@Override
public String getProtocol() {
return ProtocolConstants.PROTOCOL_LOBBY;
}
@Override
public IProtocolDriver createDriver(ISocketConnection connection) {
LobbyProtocolDriver driver = new LobbyProtocolDriver(connection);
return driver;
}
}
private class LobbyProtocolDriver extends TextProtocolDriver {
private LobbyUser bean;
public LobbyProtocolDriver(ISocketConnection connection) {
super(connection, lobbyLoginHandlers);
}
@Override
public void log(String message) {
logger.log(message);
}
@Override
public void connectionDisconnected() {
if (bean == null)
return;
String name = bean.getName();
loginUsers.remove(name);
for (ConcurrentHashMap<String, LobbyProtocolDriver> members : circleMap.values()) {
members.remove(name);
}
ByteBuffer buffer = Utility.encode(ProtocolConstants.Lobby.NOTIFY_LOGOUT + TextProtocolDriver.ARGUMENT_SEPARATOR + name);
for (Entry<String, LobbyProtocolDriver> e : loginUsers.entrySet()) {
LobbyProtocolDriver user = e.getValue();
buffer.position(0);
user.getConnection().send(buffer);
}
}
@Override
public void errorProtocolNumber(String number) {
}
private void appendUserInitializer(StringBuilder sb) {
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getName());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getState().getAbbreviation());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getUrl());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getIconUrl());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getProfile());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
for (String circle : bean.getCircles()) {
sb.append(circle).append("\n");
}
}
private void appendUserProfile(StringBuilder sb) {
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getName());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getUrl());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getIconUrl());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getProfile());
}
private void appendUserState(StringBuilder sb) {
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getName());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(bean.getState().getAbbreviation());
}
}
private final HashMap<String, IProtocolMessageHandler> lobbyLoginHandlers = new HashMap<String, IProtocolMessageHandler>();
private final HashMap<String, IProtocolMessageHandler> lobbyHandlers = new HashMap<String, IProtocolMessageHandler>();
{
lobbyLoginHandlers.put(ProtocolConstants.Lobby.COMMAND_LOGIN, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR, -1);
if (tokens.length != 4)
return false;
String name = tokens[0];
if (!Utility.isValidNameString(name))
return false;
if (loginUsers.size() > maxUsers) {
driver.getConnection().send(Utility.encode(ProtocolConstants.Lobby.ERROR_LOGIN_USER_BEYOND_CAPACITY));
return false;
}
String url = tokens[1].trim();
String iconUrl = tokens[2].trim();
String profile = tokens[3].trim();
LobbyProtocolDriver me = (LobbyProtocolDriver) driver;
if (loginUsers.putIfAbsent(name, me) != null) {
driver.getConnection().send(Utility.encode(ProtocolConstants.Lobby.ERROR_LOGIN_USER_DUPLICATED_NAME));
return false;
}
me.bean = new LobbyUser(name, LobbyUserState.LOGIN);
me.bean.setUrl(url);
me.bean.setIconUrl(iconUrl);
me.bean.setProfile(profile);
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.Lobby.NOTIFY_LOGIN);
me.appendUserProfile(sb);
ByteBuffer buffer = Utility.encode(sb);
for (Entry<String, LobbyProtocolDriver> e : loginUsers.entrySet()) {
LobbyProtocolDriver user = e.getValue();
if (user == me)
continue;
buffer.position(0);
user.getConnection().send(buffer);
sb.delete(0, sb.length());
sb.append(ProtocolConstants.Lobby.COMMAND_LOGIN);
user.appendUserInitializer(sb);
me.getConnection().send(Utility.encode(sb));
}
sb.delete(0, sb.length());
sb.append(ProtocolConstants.Lobby.COMMAND_LOGIN);
appendLoginMessage(sb);
me.getConnection().send(Utility.encode(sb));
me.setMessageHandlers(lobbyHandlers);
return true;
}
});
lobbyHandlers.put(ProtocolConstants.Lobby.COMMAND_CHAT, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
LobbyProtocolDriver user = (LobbyProtocolDriver) driver;
String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR, -1);
if (tokens.length != 2)
return true;
String circle = tokens[0];
String message = tokens[1];
ConcurrentHashMap<String, LobbyProtocolDriver> list;
if (Utility.isEmpty(circle)) {
list = loginUsers;
} else {
list = circleMap.get(circle);
if (list == null)
return true;
}
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.Lobby.COMMAND_CHAT);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(user.bean.getName());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(circle);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(message);
ByteBuffer buffer = Utility.encode(sb);
for (LobbyProtocolDriver u : list.values()) {
buffer.position(0);
u.getConnection().send(buffer);
}
return true;
}
});
lobbyHandlers.put(ProtocolConstants.Lobby.COMMAND_PRIVATE_MESSAGE, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
LobbyProtocolDriver user = (LobbyProtocolDriver) driver;
String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR);
if (tokens.length != 2)
return true;
LobbyProtocolDriver sendTo = loginUsers.get(tokens[0]);
if (sendTo == null || user == sendTo)
return true;
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.Lobby.COMMAND_PRIVATE_MESSAGE);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(user.bean.getName());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(tokens[1]);
sendTo.getConnection().send(Utility.encode(sb));
return true;
}
});
lobbyHandlers.put(ProtocolConstants.Lobby.COMMAND_CHANGE_STATE, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
LobbyProtocolDriver user = (LobbyProtocolDriver) driver;
LobbyUserState state = LobbyUserState.findState(argument);
if (state == null)
return false;
switch (state) {
case OFFLINE:
return false;
}
user.bean.setState(state);
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.Lobby.NOTIFY_STATE_CHANGE);
user.appendUserState(sb);
ByteBuffer buffer = Utility.encode(sb);
for (Entry<String, LobbyProtocolDriver> e : loginUsers.entrySet()) {
LobbyProtocolDriver u = e.getValue();
buffer.position(0);
u.getConnection().send(buffer);
}
return true;
}
});
lobbyHandlers.put(ProtocolConstants.Lobby.COMMAND_UPDATE_PROFILE, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
LobbyProtocolDriver user = (LobbyProtocolDriver) driver;
String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR, -1);
if (tokens.length != 3)
return true;
user.bean.setUrl(tokens[0].trim());
user.bean.setIconUrl(tokens[1].trim());
user.bean.setProfile(tokens[2].trim());
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.Lobby.NOTIFY_PROFILE_UPDATE);
user.appendUserProfile(sb);
ByteBuffer buffer = Utility.encode(sb);
for (Entry<String, LobbyProtocolDriver> e : loginUsers.entrySet()) {
LobbyProtocolDriver u = e.getValue();
buffer.position(0);
u.getConnection().send(buffer);
}
return true;
}
});
lobbyHandlers.put(ProtocolConstants.Lobby.COMMAND_CIRCLE_JOIN, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR, -1);
if (tokens.length != 1)
return true;
String circleName = tokens[0];
if (!Utility.isValidNameString(circleName))
return true;
LobbyProtocolDriver user = (LobbyProtocolDriver) driver;
ConcurrentHashMap<String, LobbyProtocolDriver> members = circleMap.get(circleName);
if (members == null) {
members = new ConcurrentHashMap<String, LobbyProtocolDriver>();
circleMap.put(circleName, members);
}
String name = user.bean.getName();
if (members.putIfAbsent(name, user) != null)
return true;
user.bean.addCircle(circleName);
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.Lobby.NOTIFY_CIRCLE_JOIN);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(name);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(circleName);
ByteBuffer buffer = Utility.encode(sb);
for (Entry<String, LobbyProtocolDriver> e : loginUsers.entrySet()) {
LobbyProtocolDriver u = e.getValue();
buffer.position(0);
u.getConnection().send(buffer);
}
return true;
}
});
lobbyHandlers.put(ProtocolConstants.Lobby.COMMAND_CIRCLE_LEAVE, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR, -1);
if (tokens.length != 1)
return true;
String circleName = tokens[0];
if (!Utility.isValidNameString(circleName))
return true;
LobbyProtocolDriver user = (LobbyProtocolDriver) driver;
ConcurrentHashMap<String, LobbyProtocolDriver> members = circleMap.get(circleName);
if (members == null) {
return true;
}
String name = user.bean.getName();
if (members.remove(name) == null)
return true;
user.bean.removeCircle(circleName);
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.Lobby.NOTIFY_CIRCLE_LEAVE);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(name);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(circleName);
ByteBuffer buffer = Utility.encode(sb);
for (Entry<String, LobbyProtocolDriver> e : loginUsers.entrySet()) {
LobbyProtocolDriver u = e.getValue();
buffer.position(0);
u.getConnection().send(buffer);
}
return true;
}
});
}
private class LobbyStatusProtocol implements IProtocol {
@Override
public void log(String message) {
logger.log(message);
}
@Override
public String getProtocol() {
return ProtocolConstants.PROTOCOL_LOBBY_STATUS;
}
@Override
public IProtocolDriver createDriver(ISocketConnection connection) {
if (!isAcceptingPortal)
return null;
serverNetWork.reload();
if (!serverNetWork.isValidPortalServer(connection.getRemoteAddress().getAddress()))
return null;
LobbyStatusProtocolDriver driver = new LobbyStatusProtocolDriver(connection);
logger.log("ポータルから接続されました: " + driver.address);
portalConnections.put(driver, this);
return driver;
}
}
private class LobbyStatusProtocolDriver extends TextProtocolDriver {
private String address;
public LobbyStatusProtocolDriver(ISocketConnection connection) {
super(connection, portalHandlers);
address = Utility.socketAddressToStringByIP(getConnection().getRemoteAddress());
}
@Override
public void connectionDisconnected() {
portalConnections.remove(this);
logger.log("ポータルから切断されました: " + address);
}
@Override
public void errorProtocolNumber(String number) {
}
@Override
public void log(String message) {
logger.log(message);
}
}
private final HashMap<String, IProtocolMessageHandler> portalHandlers;
{
portalHandlers = new HashMap<String, IProtocolMessageHandler>();
}
}