package me.rkfg.ns2gather.server;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.TimerTask;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import me.rkfg.ns2gather.client.AnonymousAuthException;
import me.rkfg.ns2gather.client.ClientSettings;
import me.rkfg.ns2gather.client.NS2GService;
import me.rkfg.ns2gather.domain.Gather;
import me.rkfg.ns2gather.domain.Map;
import me.rkfg.ns2gather.domain.Player;
import me.rkfg.ns2gather.domain.PlayerVote;
import me.rkfg.ns2gather.domain.Remembered;
import me.rkfg.ns2gather.domain.Server;
import me.rkfg.ns2gather.domain.Streamer;
import me.rkfg.ns2gather.domain.Vote;
import me.rkfg.ns2gather.domain.VoteResult;
import me.rkfg.ns2gather.dto.GatherState;
import me.rkfg.ns2gather.dto.HiveStatsDTO;
import me.rkfg.ns2gather.dto.InitStateDTO;
import me.rkfg.ns2gather.dto.MapDTO;
import me.rkfg.ns2gather.dto.MessageDTO;
import me.rkfg.ns2gather.dto.MessageType;
import me.rkfg.ns2gather.dto.MessageVisibility;
import me.rkfg.ns2gather.dto.PlayerDTO;
import me.rkfg.ns2gather.dto.ServerDTO;
import me.rkfg.ns2gather.dto.Side;
import me.rkfg.ns2gather.dto.VoteResultDTO;
import me.rkfg.ns2gather.dto.VoteType;
import me.rkfg.ns2gather.server.GatherPlayersManager.CleanupCallback;
import me.rkfg.ns2gather.server.GatherPlayersManager.GatherPlayers;
import me.rkfg.ns2gather.server.GatherPlayersManager.TeamStatType;
import me.rkfg.ns2gather.server.ServerManager.ServersChangeCallback;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.hibernate.NonUniqueResultException;
import org.hibernate.Session;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.discovery.DiscoveryException;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.MessageException;
import org.pegdown.PegDownProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.ppsrk.gwt.client.ClientAuthException;
import ru.ppsrk.gwt.client.ClientAuthenticationException;
import ru.ppsrk.gwt.client.LogicException;
import ru.ppsrk.gwt.server.HibernateCallback;
import ru.ppsrk.gwt.server.HibernateUtil;
import ru.ppsrk.gwt.server.LogicExceptionFormatted;
import ru.ppsrk.gwt.server.LongPollingServer;
import ru.ppsrk.gwt.server.ServerUtils;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
/**
* The server side implementation of the RPC service.
*/
@SuppressWarnings("serial")
public class NS2GServiceImpl extends RemoteServiceServlet implements NS2GService {
private enum ResetType {
ALL, VOTES, RESULTS
}
private static final boolean forceDebug = false;
private static final boolean forceRelease = false;
Logger logger = LoggerFactory.getLogger(getClass());
CleanupManager cleanupManager = new CleanupManager();
static ConsumerManager consumerManager = new ConsumerManager();
ServerManager serverManager;
GatherPlayersManager connectedPlayers = new GatherPlayersManager(new CleanupCallback() {
@Override
public void playerRemoved(Long gatherId, Long steamId) throws LogicException, ClientAuthException {
removePlayer(gatherId, steamId, false);
}
});
Long playerId = 1L;
MessageManager messageManager = new MessageManager();
GatherCountdownManager gatherCountdownManager = new GatherCountdownManager();
private boolean debug = false;
PegDownProcessor pegDownProcessor = new NS2GPegDownProcessor();
private class MessagePollingServer extends LongPollingServer<List<MessageDTO>> {
private long since;
public MessagePollingServer(long period, long execDelay, long since) {
super(period, execDelay);
this.since = since;
}
@Override
public List<MessageDTO> exec() throws LogicException, ClientAuthException, ClientAuthException {
List<MessageDTO> result = new LinkedList<>();
Long gatherId = null;
Long steamId = null;
try {
gatherId = getCurrentGatherId();
steamId = getSteamId();
} catch (ClientAuthException e) {
}
result = messageManager.getNewMessages(gatherId, steamId, since);
if (result.isEmpty()) {
return null;
}
return result;
}
@Override
public void close() throws Exception {
logger.info("Stopping the long polling " + Thread.currentThread());
super.close();
}
}
public NS2GServiceImpl() {
ServerUtils.setMappingFile("dozerMapping.xml");
debug = HibernateUtil.initSessionFactoryDebugRelease(forceDebug, forceRelease, "hibernate_dev.cfg.xml", "hibernate.cfg.xml");
try {
resetVotes(null, ResetType.ALL);
} catch (LogicException | ClientAuthException e) {
e.printStackTrace();
}
serverManager = new ServerManager(new ServersChangeCallback() {
@Override
public void onChange() {
try {
messageManager.postMessage(MessageType.SERVER_UPDATE, "", null);
} catch (LogicException | ClientAuthException e) {
e.printStackTrace();
}
}
});
cleanupManager.add(connectedPlayers);
cleanupManager.add(messageManager);
cleanupManager.add(serverManager);
}
protected void removeVotesForPlayer(final Long gatherId, final Long playerLeftId) throws LogicException, ClientAuthException {
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
List<Long> votes = HibernateUtil.exec(new HibernateCallback<List<Long>>() {
@SuppressWarnings("unchecked")
@Override
public List<Long> run(Session session) throws LogicException, ClientAuthException {
session.enableFilter("gatherId").setParameter("gid", gatherId);
return session
.createQuery(
"select pv.steamId from PlayerVote pv, Vote v, Gather g2 where v in elements (pv.votes) and v.gather = g2 and v.type = :comm and v.targetId = :tid")
.setParameter("comm", VoteType.COMM).setLong("tid", playerLeftId).list();
}
});
for (Long steamId : votes) {
MessageDTO messageDTO = new MessageDTO(MessageType.VOTE_ENDED,
"Игрок, за которого вы голосовали, ушёл. Пожалуйста, переголосуйте.", gatherId);
messageDTO.setVisibility(MessageVisibility.PERSONAL);
messageDTO.setToSteamId(steamId);
messageManager.postMessage(messageDTO);
unvote(gatherId, steamId);
}
}
}
private void resetVotes(final Long gatherId, final ResetType type) throws LogicException, ClientAuthException {
HibernateUtil.exec(new HibernateCallback<Void>() {
@Override
public Void run(Session session) throws LogicException, ClientAuthException {
if (gatherId != null) {
session.enableFilter("gatherId").setParameter("gid", gatherId);
}
if (type == ResetType.ALL || type == ResetType.VOTES) {
@SuppressWarnings("unchecked")
List<PlayerVote> playerVotes = session.createQuery(
"select pv from PlayerVote pv left join pv.votes v left join v.gather g, Gather g2 where g = g2").list();
for (PlayerVote playerVote : playerVotes) {
session.delete(playerVote);
}
messageManager.postMessage(MessageType.RESET_HIGHLIGHT, "", gatherId);
}
if (type == ResetType.ALL || type == ResetType.RESULTS) {
@SuppressWarnings("unchecked")
List<VoteResult> voteResults = session.createQuery(
"select vr from VoteResult vr left join vr.gather g, Gather g2 where g = g2").list();
for (VoteResult voteResult : voteResults) {
session.delete(voteResult);
}
}
return null;
}
});
}
@Override
public String login() throws LogicException {
try {
// perform discovery on the user-supplied identifier
List<?> discoveries = consumerManager.discover("http://steamcommunity.com/openid");
// attempt to associate with the OpenID provider
// and retrieve one service endpoint for authentication
DiscoveryInformation discovered = consumerManager.associate(discoveries);
// store the discovery information in the user's session for later use
// leave out for stateless operation / if there is no session
getSession().setAttribute("discovered", discovered);
// obtain a AuthRequest message to be sent to the OpenID provider
AuthRequest authReq = consumerManager.authenticate(discovered, getServletContext().getInitParameter("callback.url"));
String result = authReq.getDestinationUrl(true);
return result;
} catch (MessageException | ConsumerException | DiscoveryException e) {
e.printStackTrace();
return null;
}
}
@Override
public Long getSteamId() throws ClientAuthException, LogicException {
Boolean isAnonymous = (Boolean) getSession().getAttribute(Settings.ANONYMOUS_SESSION);
if (isAnonymous != null) {
throw new AnonymousAuthException("anonymous");
}
Long result = (Long) getSession().getAttribute(Settings.STEAMID_SESSION);
if (result == null) {
result = rememberMe();
if (result == null) {
throw new ClientAuthenticationException("not logged in");
}
}
return result;
}
private Long rememberMe() throws LogicException, ClientAuthException {
return HibernateUtil.exec(new HibernateCallback<Long>() {
@Override
public Long run(Session session) throws LogicException, ClientAuthException {
Long rid = null;
for (Cookie cookie : perThreadRequest.get().getCookies()) {
if (cookie.getName().equals("rememberSteamId")) {
try {
rid = Long.valueOf(cookie.getValue());
break;
} catch (NumberFormatException e) {
throw new LogicException("Invalid remember cookie value.");
}
}
}
Long steamId = null;
if (rid != null) {
try {
steamId = (Long) session.createQuery("select r.steamId from Remembered r where r.rememberId = :rid")
.setLong("rid", rid).uniqueResult();
getSession().setAttribute(Settings.STEAMID_SESSION, steamId);
AuthCallbackServlet.updateRememberCookie(getThreadLocalResponse(), rid.toString());
} catch (NonUniqueResultException e) {
throw new LogicException("Дублирующийся id в БД, автовход отклонён.");
}
}
return steamId;
}
});
}
@Override
public PlayerDTO getPlayer() throws LogicException, ClientAuthException {
return getPlayer(getSteamId());
}
public PlayerDTO getPlayer(Long steamId) throws LogicException, ClientAuthException {
PlayerDTO player = connectedPlayers.getPlayerBySteamId(steamId);
if (player != null) {
return player;
}
if (debug) {
player = new PlayerDTO(steamId, "fake" + steamId.toString(), "http://steamcommunity.com", System.currentTimeMillis());
connectedPlayers.addPlayerBySteamId(player);
} else {
player = connectedPlayers.lookupPlayerBySteamId(steamId);
}
return player;
}
@Override
public List<ServerDTO> getServers() throws LogicException, ClientAuthException {
return serverManager.getServers();
}
@Override
public List<MapDTO> getMaps() throws LogicException, ClientAuthException {
return HibernateUtil.queryList("from Map", new String[] {}, new Object[] {}, MapDTO.class);
}
@Override
public void ping() throws ClientAuthException, LogicException {
synchronized (connectedPlayers.getPlayersByGather(getCurrentGatherId())) {
Long steamId = getSteamId();
// this should be inside synchronized block to prevent racing with kick
final Long gatherId = getCurrentGatherId();
PlayerDTO existing = connectedPlayers.getPlayerByGatherSteamId(gatherId, steamId);
if (existing == null) {
existing = connectedPlayers.getPlayerBySteamId(steamId);
if (existing == null) {
return;
}
existing.setLastPing(System.currentTimeMillis());
connectedPlayers.addPlayerToGather(gatherId, existing);
if (System.currentTimeMillis() - existing.getLastHiveUpdate() > Settings.HIVE_UPDATE_INTERVAL) {
final PlayerDTO hivePlayer = existing;
connectedPlayers.getPlayersByGather(gatherId).getHiveStats(steamId, new AsyncCallback<HiveStatsDTO>() {
@Override
public void onSuccess(HiveStatsDTO result) {
try {
logger.info("Got hive info for {}", hivePlayer.getId());
hivePlayer.setLastHiveUpdate(System.currentTimeMillis());
messageManager.postMessage(MessageType.PLAYERS_UPDATE, "", gatherId);
} catch (LogicException | ClientAuthException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Throwable caught) {
logger.warn("Can't get hive info for {}", hivePlayer.getId());
hivePlayer.setLastHiveUpdate(System.currentTimeMillis() + Settings.HIVE_UPDATE_FAILURE_WAIT
- Settings.HIVE_UPDATE_INTERVAL);
}
});
}
messageManager.postMessage(MessageType.USER_ENTERS, existing.getEffectiveName(), gatherId);
postVoteChangeMessage(gatherId);
updateGatherStateByPlayerNumber(gatherId);
} else {
existing.setLastPing(System.currentTimeMillis());
}
}
}
private void updateGatherStateByPlayerNumber(final Long gatherId) throws LogicException, ClientAuthException {
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
Gather gather = getGatherById(gatherId);
if (isGatherClosed(gatherId)) {
// nothing to do here
return;
}
int connectedPlayersCount = connectedPlayers.getPlayersByGather(gatherId).playerCount();
if (connectedPlayersCount == Settings.GATHER_PLAYER_MAX) {
if (gather.getState() == GatherState.OPEN) {
// close gather if player limit reached
updateGatherState(gatherId, GatherState.CLOSED);
}
} else {
if (gather.getState() == GatherState.CLOSED) {
// reopen gather if players have left
updateGatherState(gatherId, GatherState.OPEN);
}
}
if (connectedPlayersCount >= Settings.GATHER_PLAYER_MIN) {
// minimum players count reached
if (getVotedPlayersCount(gatherId) < connectedPlayersCount) {
// not everyone voted yet, check for evenness
if (connectedPlayersCount % 2 == 0) {
// even number, run the timer
gatherCountdownManager.scheduleGatherCountdownTask(gatherId, new TimerTask() {
@Override
public void run() {
try {
resolveGather(gatherId);
} catch (LogicException | ClientAuthException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, Settings.GATHER_RESOLVE_DELAY);
updateGatherState(gatherId, GatherState.ONTIMER);
messageManager.postMessage(MessageType.RUN_TIMER, String.valueOf(Settings.GATHER_RESOLVE_DELAY / 1000), gatherId);
} else {
// odd number, stop the timer and wait
stopGatherTimer(gatherId);
messageManager.postMessage(MessageType.MORE_PLAYERS, "", gatherId);
}
} else {
// gather is fully ready, let's count votes
countResults(gatherId);
}
} else {
stopGatherTimer(gatherId);
}
}
}
private void stopGatherTimer(final Long gatherId) throws LogicException, ClientAuthException {
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
messageManager.postMessage(MessageType.STOP_TIMER, "", gatherId);
gatherCountdownManager.cancelGatherCountdownTasks(gatherId);
if (getGatherById(gatherId).getState() == GatherState.ONTIMER) {
updateGatherState(gatherId, GatherState.OPEN);
}
}
}
protected void resolveGather(final Long gatherId) throws LogicException, ClientAuthException {
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
HibernateUtil.exec(new HibernateCallback<Void>() {
@Override
public Void run(Session session) throws LogicException, ClientAuthException {
session.enableFilter("gatherId").setParameter("gid", gatherId);
@SuppressWarnings("unchecked")
Set<Long> votedSteamIds = new HashSet<>(session.createQuery(
"select distinct(v.player.steamId) from Vote v left join v.gather").list());
GatherPlayers gatherPlayers = connectedPlayers.getPlayersByGather(gatherId);
for (PlayerDTO player : gatherPlayers.getPlayers()) {
if (!votedSteamIds.contains(player.getId())) {
removePlayer(gatherId, player.getId(), true);
}
}
return null;
}
});
updateGatherStateByPlayerNumber(gatherId);
}
}
@Override
public void unvote() throws LogicException, ClientAuthException {
requiresOngoingGather();
unvote(getCurrentGatherId(), getSteamId());
}
private void unvote(Long gatherId, Long steamId) throws LogicException, ClientAuthException {
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
if (removeVotes(steamId)) {
sendReadiness(getPlayer(steamId).getId(), gatherId, false);
}
}
}
private void updateGatherState(Long gatherId, GatherState newState) throws LogicException, ClientAuthException {
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
Gather gather = getGatherById(gatherId);
gather.setState(newState);
messageManager.postMessage(MessageType.GATHER_STATUS, String.valueOf(newState.ordinal()), gather.getId());
HibernateUtil.saveObject(gather);
}
}
@Override
public List<MessageDTO> getNewMessages(Long since) {
MessagePollingServer server = null;
try {
if (since < 0) {
since += System.currentTimeMillis();
}
server = new MessagePollingServer(30000, 100, since);
cleanupManager.add(server);
return server.start();
} catch (InterruptedException | LogicException | ClientAuthException e) {
return null;
} finally {
cleanupManager.remove(server);
}
}
@Override
public void sendChatMessage(String text) throws ClientAuthException, LogicException {
if (text.length() > ClientSettings.CHAT_MAX_LENGTH) {
text = text.substring(0, ClientSettings.CHAT_MAX_LENGTH);
}
synchronized (pegDownProcessor) {
text = pegDownProcessor.markdownToHtml(text);
}
messageManager.postMessage(MessageType.CHAT_MESSAGE, SafeHtmlUtils.htmlEscape(getPlayer().getEffectiveName()) + ": " + text,
getCurrentGatherId());
}
@Override
public List<PlayerDTO> getConnectedPlayers() throws LogicException, ClientAuthException {
List<PlayerDTO> result = new LinkedList<>();
result.addAll(connectedPlayers.getPlayersByGather(getCurrentGatherId()).getPlayers());
Collections.sort(result, new Comparator<PlayerDTO>() {
@Override
public int compare(PlayerDTO o1, PlayerDTO o2) {
return o1.getEffectiveName().compareTo(o2.getEffectiveName());
}
});
return result;
}
@Override
public void vote(final Long[][] votes) throws LogicException, ClientAuthException {
Long gatherId = getCurrentGatherId();
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
requiresOngoingGather();
if (votes.length != 3) {
throw LogicExceptionFormatted.format("invalid vote size, expected %d, got %d", 3, votes.length);
}
validateVoteNumbers(votes);
boolean showVote = HibernateUtil.exec(new HibernateCallback<Boolean>() {
@Override
public Boolean run(Session session) throws LogicException, ClientAuthException {
Long steamId = getSteamId();
// delete all previous votes
boolean showVote = !removeVotes(steamId);
PlayerVote playerVote = new PlayerVote(steamId);
for (int i = 0; i < 3; i++) {
Long[] votesCat = votes[i];
for (Long targetId : votesCat) {
Vote vote = new Vote(playerVote, targetId, VoteType.values()[i], getCurrentGather());
playerVote.getVotes().add(vote);
}
}
session.merge(playerVote);
return showVote;
}
});
if (showVote) {
sendReadiness(getPlayer().getId(), gatherId, true);
}
int connectedPlayersCount = connectedPlayers.getPlayersByGather(gatherId).playerCount();
if (connectedPlayersCount >= Settings.GATHER_PLAYER_MIN && getVotedPlayersCount(gatherId) == connectedPlayersCount) {
countResults(gatherId);
}
}
}
private void requiresOngoingGather() throws LogicException, ClientAuthException {
if (isGatherClosed(getCurrentGatherId())) {
throw new LogicException("Голосование уже завершено.");
}
}
private boolean isGatherClosed(Long gatherId) throws LogicException, ClientAuthException {
return Arrays.asList(GatherState.COMPLETED, GatherState.SIDEPICK, GatherState.PLAYERS).contains(getGatherById(gatherId).getState());
}
protected Gather getCurrentGather() throws LogicException, ClientAuthException {
return getGatherById(getCurrentGatherId());
}
@Override
public List<PlayerDTO> getGatherParticipantsList() throws LogicException, ClientAuthException {
List<PlayerDTO> participantList = new ArrayList<>(connectedPlayers.getPlayersByGather(getCurrentGatherId()).getParticipants());
Collections.sort(participantList, new Comparator<PlayerDTO>() {
@Override
public int compare(PlayerDTO o1, PlayerDTO o2) {
return o1.getLastPing().compareTo(o2.getLastPing());
}
});
return participantList;
}
protected Gather getGatherById(Long gatherId) throws LogicException, ClientAuthException {
return HibernateUtil.tryGetObject(gatherId, Gather.class, "Не удалось найти gather id=" + gatherId
+ " в БД. Операция не выполнена.");
}
private void countResults(final Long gatherId) throws LogicException, ClientAuthException {
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
stopGatherTimer(gatherId);
try {
HibernateUtil.exec(new HibernateCallback<Void>() {
@Override
public Void run(Session session) throws LogicException, ClientAuthException {
session.enableFilter("gatherId").setParameter("gid", gatherId);
resetVotes(gatherId, ResetType.RESULTS);
Gather gather = getGatherById(gatherId);
for (VoteType voteType : VoteType.values()) {
@SuppressWarnings("unchecked")
List<Vote> votesForType = session
.createQuery("select v from Vote v left join v.gather g, Gather g2 where v.type = :vt and g = g2")
.setParameter("vt", voteType).list();
for (Vote vote : votesForType) {
Long targetId = vote.getTargetId();
try {
VoteResult existingVoteResult = (VoteResult) session
.createQuery(
"select vr from VoteResult vr left join vr.gather g, Gather g2 where vr.type = :vt and vr.targetId = :tid and g = g2")
.setParameter("vt", voteType).setLong("tid", targetId).uniqueResult();
if (existingVoteResult == null) {
existingVoteResult = (VoteResult) session.merge(new VoteResult(gather, voteType, targetId, 0L));
}
existingVoteResult.inc();
} catch (NonUniqueResultException e) {
throw LogicExceptionFormatted.format("Обнаружен дубликат результата голосования: %d, %d, %d",
voteType.ordinal(), gatherId, vote.getTargetId());
}
}
List<VoteResult> results = getVoteResultsByType(gatherId, session, voteType);
long i = 0;
for (VoteResult voteResult : results) {
voteResult.setPlace(i++);
}
@SuppressWarnings("unchecked")
List<VoteResult> toCrop = session
.createQuery(
"select vr from VoteResult vr left join vr.gather g, Gather g2 where vr.type = :vt and vr not in (:vrl) and g = g2")
.setParameter("vt", voteType).setParameterList("vrl", results).list();
for (VoteResult voteResult : toCrop) {
session.delete(voteResult);
}
}
List<Long> commsId = new ArrayList<Long>();
for (VoteResult voteResult : getVoteResultsByType(gatherId, session, VoteType.COMM)) {
commsId.add(voteResult.getTargetId());
}
GatherPlayers players = connectedPlayers.getPlayersByGather(gatherId);
players.setComms(commsId);
players.playersToParticipants();
setupServer(gatherId, session);
return null;
}
});
} catch (LogicException e) {
resetVotes(gatherId, ResetType.ALL);
messageManager.postMessage(MessageType.VOTE_ENDED, e.getMessage(), gatherId);
postVoteChangeMessage(gatherId);
updateGatherStateByPlayerNumber(gatherId);
return;
}
resetVotes(gatherId, ResetType.VOTES);
updateGatherState(gatherId, GatherState.SIDEPICK);
messageManager.postMessage(MessageType.VOTE_ENDED, "ok", gatherId);
}
}
private void setupServer(Long gatherId, Session session) throws LogicException, ClientAuthException {
List<VoteResult> servers = getVoteResultsByType(gatherId, session, VoteType.SERVER);
if (servers.size() == 0) {
throw new LogicException("Серверы не выбраны, невозможно сменить пароль и карту.");
}
List<VoteResult> maps = getVoteResultsByType(gatherId, session, VoteType.MAP);
if (maps.size() == 0) {
throw new LogicException("Карты не выбраны, невозможно сменить пароль и карту.");
}
Long serverId = servers.get(0).getTargetId();
Server server = HibernateUtil.tryGetObject(serverId, Server.class, session, "Выбранный сервер с id " + serverId
+ " не найден в БД.");
if (server.getWebLogin() == null || server.getWebPassword() == null || server.getWebPort() == null) {
return;
}
Long mapId = maps.get(0).getTargetId();
Map map = HibernateUtil.tryGetObject(mapId, Map.class, session, "Выбранная карта с id " + mapId + " не найдена в БД.");
server.setPassword(genPassword(Settings.SERVER_PASSWORD_LENGTH));
HttpClient webClient = NetworkUtils.getHTTPClient(server.getIp(), server.getWebPort(), server.getWebLogin(),
server.getWebPassword());
try {
HttpUriRequest changePasswordRequest = buildWebAdminGetRequest(server, "sv_password+" + server.getPassword());
webClient.execute(changePasswordRequest);
} catch (IOException e) {
throw new LogicException("Не удалось сменить пароль на сервере " + server.getName());
}
try {
HttpUriRequest changeMapRequest = buildWebAdminGetRequest(server, "sv_changemap+" + map.getName());
webClient.execute(changeMapRequest);
} catch (IOException e) {
throw new LogicException("Не удалось сменить карту на сервере " + server.getName());
}
}
private HttpUriRequest buildWebAdminGetRequest(Server server, String command) {
return new HttpGet("http://" + server.getIp() + ":" + server.getWebPort() + "/?command=Send&rcon=" + command);
}
private String genPassword(int length) {
Random random = new Random();
String chars = "123456789qwertyuiopasdfghkzxcvbnm";
StringBuilder result = new StringBuilder(length);
for (; length > 0; length--) {
result.append(chars.charAt(random.nextInt(chars.length())));
}
return result.toString();
}
@Override
public List<VoteResultDTO> getVoteResults() throws LogicException, ClientAuthException {
return HibernateUtil.exec(new HibernateCallback<List<VoteResultDTO>>() {
@Override
public List<VoteResultDTO> run(Session session) throws LogicException, ClientAuthException {
Long gatherId = getCurrentGatherId();
List<VoteResultDTO> result = new LinkedList<>();
for (VoteType voteType : VoteType.values()) {
List<VoteResult> voteResults = getVoteResultsByType(gatherId, session, voteType);
for (VoteResult voteResult : voteResults) {
result.add(voteResult.toDTO(session, connectedPlayers.getPlayersByGather(gatherId)));
}
}
return result;
}
});
}
protected Long getCurrentGatherId() throws LogicException, ClientAuthException {
try {
String kickReason = connectedPlayers.getKickedReason(getSteamId());
if (kickReason != null) {
throw new ClientAuthenticationException(kickReason);
}
} catch (AnonymousAuthException e) {
// ok, let the anon pass in
}
Long gatherId = (Long) getSession().getAttribute(Settings.GATHER_ID);
if (gatherId == null) {
gatherId = findOpenGatherId();
getSession().setAttribute(Settings.GATHER_ID, gatherId);
}
return gatherId;
}
private HttpSession getSession() throws LogicException {
if (perThreadRequest == null || perThreadRequest.get() == null) {
throw new LogicException("session lost");
}
return perThreadRequest.get().getSession();
}
private Long findOpenGatherId() throws LogicException, ClientAuthException {
synchronized (connectedPlayers) {
return HibernateUtil.exec(new HibernateCallback<Long>() {
@Override
public Long run(Session session) throws LogicException, ClientAuthException {
Gather gather = (Gather) session.createQuery("from Gather g where g.state in (:states)")
.setParameterList("states", Arrays.asList(GatherState.OPEN, GatherState.ONTIMER)).setMaxResults(1)
.uniqueResult();
if (gather == null) {
// create new gather
gather = (Gather) session.merge(new Gather());
}
return gather.getId();
}
});
}
}
private void postVoteChangeMessage(Long gatherId) throws LogicException, ClientAuthException {
messageManager.postMessage(MessageType.VOTE_CHANGE, getVoteStat(gatherId), gatherId);
}
@Override
public String getVoteStat() throws LogicException, ClientAuthException {
return getVoteStat(getCurrentGatherId());
}
private String getVoteStat(Long gatherId) throws LogicException, ClientAuthException {
return String.format("%d/%d", getVotedPlayersCount(gatherId), connectedPlayers.getPlayersByGather(gatherId).playerCount());
}
private Long getVotedPlayersCount(final Long gatherId) throws LogicException, ClientAuthException {
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
return HibernateUtil.exec(new HibernateCallback<Long>() {
@Override
public Long run(Session session) throws LogicException, ClientAuthException {
return (Long) session
.createQuery(
"select count(*) from PlayerVote pv where pv in (select v.player from Vote v where v.gather.id = :gid)")
.setLong("gid", gatherId).uniqueResult();
}
});
}
}
private void validateVoteNumbers(final Long[][] votes) throws LogicException, ClientAuthException {
try {
HibernateUtil.exec(new HibernateCallback<Void>() {
@Override
public Void run(Session session) throws LogicException, ClientAuthException {
int idx = 0;
Long gatherId = getCurrentGatherId();
for (Long[] vote : votes) {
if (vote.length < ClientSettings.voteRules[idx].getVotesRequired()
|| vote.length > ClientSettings.voteRules[idx].getVotesLimit()) {
throw LogicExceptionFormatted.format("Ожидается %s голосов за %s, получено %d. Пожалуйста, переголосуйте.",
ClientSettings.voteRules[idx].voteRange(), ClientSettings.voteRules[idx].getName(), vote.length);
}
for (Long targetId : vote) {
VoteResult.getTarget(session, connectedPlayers.getPlayersByGather(gatherId), VoteType.values()[idx], targetId);
}
idx++;
}
return null;
}
});
} catch (LogicException e) {
if (removeVotes(getSteamId())) {
sendReadiness(getPlayer().getId(), getCurrentGatherId(), false);
}
throw e;
}
}
private void sendReadiness(Long steamId, Long gatherId, boolean ready) throws LogicException, ClientAuthException {
postVoteChangeMessage(gatherId);
messageManager.postMessage(ready ? MessageType.USER_READY : MessageType.USER_UNREADY, steamId.toString(), gatherId);
}
private Boolean removeVotes(final Long steamId) throws LogicException, ClientAuthException {
synchronized (connectedPlayers) {
return HibernateUtil.exec(new HibernateCallback<Boolean>() {
@Override
public Boolean run(Session session) throws LogicException, ClientAuthException {
@SuppressWarnings("unchecked")
List<PlayerVote> playerVotes = session.createQuery("from PlayerVote pv where pv.steamId = :sid")
.setLong("sid", steamId).list();
for (PlayerVote playerVote : playerVotes) {
session.delete(playerVote);
}
return playerVotes.size() > 0;
}
});
}
}
private List<VoteResult> getVoteResultsByType(final Long gatherId, Session session, VoteType voteType) throws LogicException,
ClientAuthException {
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
int idx = voteType.ordinal();
session.enableFilter("gatherId").setParameter("gid", gatherId);
@SuppressWarnings("unchecked")
List<VoteResult> voteResults = session
.createQuery(
"select vr from VoteResult vr left join vr.gather g, Gather g2 where vr.type = :vt and g = g2 order by vr.voteCount desc, vr.place, rand()")
.setParameter("vt", voteType).setMaxResults(ClientSettings.voteRules[idx].getWinnerCount()).list();
if (voteResults.size() < ClientSettings.voteRules[idx].getWinnerCount()) {
throw LogicExceptionFormatted.format("Невозможно определить %s, переголосовка.", ClientSettings.voteRules[idx].getName());
}
return voteResults;
}
}
@Override
public void fakeLogin() throws ClientAuthException, LogicException {
requiresDebug();
getSession().removeAttribute(Settings.GATHER_ID);
getSession().setAttribute(Settings.STEAMID_SESSION, new Random().nextLong());
}
private void requiresDebug() throws LogicException {
if (!debug) {
throw new LogicException("debug mode required");
}
}
@Override
public void logout() throws LogicException, ClientAuthException {
HibernateUtil.exec(new HibernateCallback<Void>() {
@Override
public Void run(Session session) throws LogicException, ClientAuthException {
try {
Long steamId = getSteamId();
@SuppressWarnings("unchecked")
List<Remembered> remembereds = session.createQuery("from Remembered r where r.steamId = :sid").setLong("sid", steamId)
.list();
for (Remembered remembered : remembereds) {
session.delete(remembered);
}
removePlayer(getCurrentGatherId(), steamId, false);
} catch (AnonymousAuthException e) {
}
getSession().invalidate();
return null;
}
});
}
@Override
public void resetGatherPresence() throws LogicException, ClientAuthException {
connectedPlayers.removeKickedId(getSteamId());
getSession().removeAttribute(Settings.GATHER_ID);
}
@Override
public Set<Long> getVotedPlayerIds() throws LogicException, ClientAuthException {
return HibernateUtil.exec(new HibernateCallback<Set<Long>>() {
@Override
public Set<Long> run(Session session) throws LogicException, ClientAuthException {
Set<Long> result = new HashSet<>();
Long gatherId = getCurrentGatherId();
session.enableFilter("gatherId").setParameter("gid", gatherId);
@SuppressWarnings("unchecked")
List<Long> votedSteamIds = session.createQuery(
"select distinct(v.player.steamId) from Vote v left join v.gather g, Gather g2 where g = g2").list();
GatherPlayers gatherPlayers = connectedPlayers.getPlayersByGather(gatherId);
for (Long voteSteamId : votedSteamIds) {
PlayerDTO playerDTO = gatherPlayers.getPlayer(voteSteamId);
if (playerDTO != null) {
result.add(playerDTO.getId());
}
}
return result;
}
});
}
@Override
public GatherState getGatherState() throws LogicException, ClientAuthException {
return getCurrentGather().getState();
}
@Override
public InitStateDTO getInitState() throws LogicException, ClientAuthException {
try {
getCurrentGatherId();
} catch (ClientAuthException e) {
throw new LogicException(e.getMessage());
}
InitStateDTO result = new InitStateDTO();
result.setGatherState(getGatherState());
result.setMaps(getMaps());
result.setPlayers(getConnectedPlayers());
result.setServers(getServers());
result.setVotedIds(getVotedPlayerIds());
result.setVoteStat(getVoteStat());
result.setVersion(getVersion());
try {
result.setPasswords(getPasswordsForStreamer());
} catch (AnonymousAuthException e) {
}
if (isGatherClosed(getCurrentGatherId())) {
result.setVoteResults(getVoteResults());
}
return result;
}
private String getPasswordsForStreamer() throws LogicException, ClientAuthException {
return HibernateUtil.exec(new HibernateCallback<String>() {
@Override
public String run(Session session) throws LogicException, ClientAuthException {
@SuppressWarnings("unchecked")
List<Streamer> streamers = session.createQuery("from Streamer s where s.steamId = :sid").setLong("sid", getSteamId())
.list();
if (streamers.size() == 0) {
return null;
}
StringBuilder result = new StringBuilder();
result.append("Вы являетесь стримером [").append(streamers.get(0).getName())
.append("]. Данные для подключения к серверам:<br/>");
@SuppressWarnings("unchecked")
List<Server> servers = session.createQuery("from Server s").list();
for (Server server : servers) {
result.append("Сервер: ").append(server.getName()).append("; ");
String password = server.getPassword();
if (password != null && !password.isEmpty()) {
result.append("пароль: ").append(server.getPassword());
} else {
result.append("пароля нет");
}
result.append("<br/>");
}
return result.toString();
}
});
}
private String getVersion() {
try {
Properties properties = new Properties();
properties.load(getClass().getResourceAsStream("/buildNumber.properties"));
return "build " + properties.getProperty("version", "no version");
} catch (IOException e) {
return "no version";
}
}
private void removePlayer(Long gatherId, Long steamId, boolean isKicked) throws LogicException, ClientAuthException {
synchronized (connectedPlayers.getPlayersByGather(gatherId)) {
removeVotes(steamId);
removeVotesForPlayer(gatherId, steamId);
connectedPlayers.removePlayerFromGather(gatherId, steamId);
messageManager.postMessage(MessageType.USER_LEAVES, steamId.toString(), gatherId);
if (isKicked) {
connectedPlayers.addKicked(steamId,
"Вы были кикнуты из Gather по неактивности. Нажмите «Зайти в новый сбор», чтобы продолжить участие.");
messageManager.postMessage(MessageDTO.privateMessage(steamId, MessageType.USER_KICKED, "", null));
}
postVoteChangeMessage(gatherId);
updateGatherStateByPlayerNumber(gatherId);
}
}
@Override
public void pickSide(final Side side) throws LogicException, ClientAuthException {
Long gatherId = getCurrentGatherId();
requiresGatherState(gatherId, GatherState.SIDEPICK, "Сейчас нельзя выбирать строну.");
connectedPlayers.getPlayersByGather(gatherId).pickSide(getSteamId(), side);
messageManager.postMessage(MessageType.PICKED, "", gatherId);
updateGatherState(gatherId, GatherState.PLAYERS);
}
private void requiresGatherState(Long gatherId, GatherState state, String errorMessage) throws LogicException, ClientAuthException {
if (getGatherById(gatherId).getState() != state) {
throw new LogicException(errorMessage);
}
}
@Override
public void pickPlayer(Long playerSteamId) throws LogicException, ClientAuthException {
Long gatherId = getCurrentGatherId();
requiresGatherState(gatherId, GatherState.PLAYERS, "Сейчас нельзя выбирать игроков.");
GatherPlayers gatherPlayers = connectedPlayers.getPlayersByGather(gatherId);
gatherPlayers.pickPlayer(getSteamId(), playerSteamId);
messageManager.postMessage(MessageType.PICKED, "", gatherId);
if (gatherPlayers.getTeamsStat(TeamStatType.NOFREE)) {
updateGatherState(gatherId, GatherState.COMPLETED);
}
}
@Override
public void destroy() {
try {
cleanupManager.doCleanup();
} catch (Exception e) {
e.printStackTrace();
}
HibernateUtil.cleanup();
HibernateUtil.mysqlCleanup();
logger.info("Cleanup complete. Good bye");
}
@Override
public void loginAnonymously() throws LogicException, ClientAuthException {
getSession().setAttribute(Settings.ANONYMOUS_SESSION, true);
getCurrentGatherId();
}
@Override
public void changeNick(String newNick) throws LogicException, ClientAuthException {
Long gatherId = getCurrentGatherId();
changeNick(connectedPlayers.getPlayerByGatherSteamId(gatherId, getSteamId()), newNick);
messageManager.postMessage(MessageType.PLAYERS_UPDATE, "", gatherId);
}
private void changeNick(final PlayerDTO playerDTO, final String newNick) throws LogicException, ClientAuthException {
HibernateUtil.exec(new HibernateCallback<Void>() {
@Override
public Void run(Session session) throws LogicException, ClientAuthException {
Player playerEnt = (Player) session.createQuery("from Player p where p.steamId = :sid").setLong("sid", playerDTO.getId())
.uniqueResult();
if (playerEnt == null) {
session.merge(new Player(newNick, playerDTO.getId()));
} else {
playerEnt.setNick(newNick);
}
playerDTO.setNick(newNick);
return null;
}
});
}
}