diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index a3fdfb00f62..79ef6e09dae 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -26,6 +26,7 @@ import mage.client.plugins.impl.Plugins; import mage.client.preference.MagePreferences; import mage.client.remote.CallbackClientImpl; import mage.client.table.TablesPane; +import mage.client.table.TablesPanel; import mage.client.tournament.TournamentPane; import mage.client.util.*; import mage.client.util.audio.MusicPlayer; @@ -261,7 +262,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { desktopPane.add(errorDialog, JLayeredPane.MODAL_LAYER); UI.addComponent(MageComponents.DESKTOP_PANE, desktopPane); - PING_TASK_EXECUTOR.scheduleAtFixedRate(() -> SessionHandler.ping(), 60, 60, TimeUnit.SECONDS); + PING_TASK_EXECUTOR.scheduleAtFixedRate(() -> SessionHandler.ping(), TablesPanel.PING_SERVER_SECS, TablesPanel.PING_SERVER_SECS, TimeUnit.SECONDS); updateMemUsageTask = new UpdateMemUsageTask(jMemUsageLabel); diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 62326c77a84..b0533fd92ee 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -64,6 +64,9 @@ public class TablesPanel extends javax.swing.JPanel { private static final Logger LOGGER = Logger.getLogger(TablesPanel.class); private static final int[] DEFAULT_COLUMNS_WIDTH = {35, 150, 100, 50, 120, 180, 80, 120, 80, 60, 40, 40, 60}; + // ping timeout (warning, must be less than SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS) + public static final int PING_SERVER_SECS = 20; + // refresh timeouts for data downloads from server public static final int REFRESH_ACTIVE_TABLES_SECS = 5; public static final int REFRESH_FINISHED_TABLES_SECS = 30; diff --git a/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java b/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java index 1ee9285c390..df46fb279ee 100644 --- a/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java +++ b/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java @@ -67,7 +67,7 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient { logger.fatal("", ex); } - pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 60, 60, TimeUnit.SECONDS); + pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 20, 20, TimeUnit.SECONDS); } public boolean connect(Connection connection) { diff --git a/Mage.Server/src/main/java/mage/server/ChatManager.java b/Mage.Server/src/main/java/mage/server/ChatManager.java index 755571becf4..be02de0ed5d 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManager.java +++ b/Mage.Server/src/main/java/mage/server/ChatManager.java @@ -5,6 +5,7 @@ import mage.cards.repository.CardRepository; import mage.server.exceptions.UserNotFoundException; import mage.server.game.GameController; import mage.server.game.GameManager; +import mage.server.game.GamesRoomManager; import mage.server.util.SystemUtil; import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageType; @@ -316,12 +317,16 @@ public enum ChatManager { } public void sendLostConnectionMessage(UUID userId, DisconnectReason reason) { + UserManager.instance.getUser(userId).ifPresent(user -> sendMessageToUserChats(userId, user.getName() + " " + reason.getMessage())); + } + + public void sendMessageToUserChats(UUID userId, String message) { UserManager.instance.getUser(userId).ifPresent(user -> getChatSessions() .stream() + .filter(chat -> !chat.getChatId().equals(GamesRoomManager.instance.getMainChatId())) // ignore main lobby .filter(chat -> chat.hasUser(userId)) - .forEach(chatSession -> chatSession.broadcast(null, user.getName() + reason.getMessage(), MessageColor.BLUE, true, MessageType.STATUS, null))); - + .forEach(chatSession -> chatSession.broadcast(null, message, MessageColor.BLUE, true, MessageType.STATUS, null))); } public void removeUser(UUID userId, DisconnectReason reason) { diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 21ca15d278f..2c7cdea698c 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -22,6 +22,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; public enum UserManager { instance; + private static final int SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS = 30; // send to chat info about disconnection troubles, must be more than ping timeout private static final int SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS = 3 * 60; // removes from all games and chats too (can be seen in users list with disconnected status) private static final int SERVER_TIMEOUTS_USER_REMOVE_FROM_SERVER_AFTER_SECS = 8 * 60; // removes from users list @@ -145,6 +146,22 @@ public enum UserManager { } } + public void informUserOpponents(final UUID userId, final String message) { + if (userId != null) { + getUser(userId).ifPresent(user + -> USER_EXECUTOR.execute( + () -> { + try { + logger.info("INFORM OPPONENTS by " + user.getName() + ": " + message); + ChatManager.instance.sendMessageToUserChats(user.getId(), message); + } catch (Exception ex) { + handleException(ex); + } + } + )); + } + } + public boolean extendUserSession(UUID userId, String pingInfo) { if (userId != null) { User user = users.get(userId); @@ -163,6 +180,8 @@ public enum UserManager { */ private void checkExpired() { try { + Calendar calendarInform = Calendar.getInstance(); + calendarInform.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS); Calendar calendarExp = Calendar.getInstance(); calendarExp.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS); Calendar calendarRemove = Calendar.getInstance(); @@ -179,6 +198,12 @@ public enum UserManager { } for (User user : userList) { try { + if (user.getUserState() != UserState.Offline + && user.isExpired(calendarInform.getTime())) { + long secsInfo = (Calendar.getInstance().getTimeInMillis() - user.getLastActivity().getTime()) / 1000; + informUserOpponents(user.getId(), user.getName() + " got connection problem for " + secsInfo + " secs"); + } + if (user.getUserState() == UserState.Offline) { if (user.isExpired(calendarRemove.getTime())) { // removes from users list diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 356b57ac331..7374ddb6a86 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -50,7 +50,7 @@ import java.util.zip.GZIPOutputStream; public class GameController implements GameCallback { private static final int GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS = 15; // checks and inform players about joining status - private static final int GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS = 4 * 60; // leave player from game if it don't join and inactive on server + private static final int GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS = 2 * 60; // leave player from game if it don't join and inactive on server private static final ExecutorService gameExecutor = ThreadExecutor.instance.getGameExecutor(); private static final Logger logger = Logger.getLogger(GameController.class); @@ -336,7 +336,10 @@ public class GameController implements GameCallback { GameManager.instance.joinGame(game.getId(), user.getId()); logger.debug("Player " + player.getName() + " (disconnected) has joined gameId: " + game.getId()); } - ChatManager.instance.broadcast(chatId, player.getName(), user.getPingInfo() + " is pending to join the game", MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null); + ChatManager.instance.broadcast(chatId, player.getName(), user.getPingInfo() + + " is pending to join the game (waiting " + user.getSecondsDisconnected() + " of " + + GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS + " secs)", + MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null); if (user.getSecondsDisconnected() > GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS) { // TODO: 2019.04.22 - if user playing another game on server but not joining (that's the reason?), then that's check will never trigger // Cancel player join possibility lately after 4 minutes @@ -1201,6 +1204,7 @@ public class GameController implements GameCallback { if (activePlayer != null && activePlayer.hasLeft()) { sb.append("
Found disconnected player! Concede..."); activePlayer.concede(game); + activePlayer.leave(); // abort any wait response actions Phase currentPhase = game.getPhase(); if (currentPhase != null) { @@ -1221,6 +1225,7 @@ public class GameController implements GameCallback { Player p = game.getPlayer(state.getChoosingPlayerId()); if (p != null) { p.concede(game); + p.leave(); // abort any wait response actions } Phase currentPhase = game.getPhase(); if (currentPhase != null && !fixedAlready) { @@ -1235,14 +1240,14 @@ public class GameController implements GameCallback { } // fix lost priority + Player p = game.getPlayer(state.getPriorityPlayerId()); sb.append("
Checking priority player: " + getName(game.getPlayer(state.getPriorityPlayerId()))); - if (state.getPriorityPlayerId() != null) { - if (game.getPlayer(state.getPriorityPlayerId()).hasLeft()) { + if (p != null) { + if (p.hasLeft()) { sb.append("
Found disconnected player! Concede..."); - Player p = game.getPlayer(state.getPriorityPlayerId()); - if (p != null) { - p.concede(game); - } + p.concede(game); + p.leave(); // abort any wait response actions + Phase currentPhase = game.getPhase(); if (currentPhase != null && !fixedAlready) { currentPhase.getStep().skipStep(game, state.getActivePlayerId()); @@ -1250,7 +1255,6 @@ public class GameController implements GameCallback { sb.append("
Forcibly passing the phase!"); } } - sb.append(game.getPlayer(state.getPriorityPlayerId()).getName()); sb.append(""); } @@ -1272,6 +1276,8 @@ public class GameController implements GameCallback { sb.append("Not using future Timeout!"); } sb.append(""); + + return sb.toString(); } } diff --git a/Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java b/Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java index 83e78ed57e6..73eaeba89bf 100644 --- a/Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java +++ b/Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java @@ -1,5 +1,3 @@ - - package mage.server.game; import org.apache.log4j.Logger; @@ -16,12 +14,14 @@ public enum GamesRoomManager { private final ConcurrentHashMap rooms = new ConcurrentHashMap<>(); private final UUID mainRoomId; + private final UUID mainChatId; private static final Logger logger = Logger.getLogger(GamesRoomManager.class); GamesRoomManager() { GamesRoom mainRoom = new GamesRoomImpl(); mainRoomId = mainRoom.getRoomId(); + mainChatId = mainRoom.getChatId(); rooms.put(mainRoomId, mainRoom); } @@ -35,8 +35,12 @@ public enum GamesRoomManager { return mainRoomId; } + public UUID getMainChatId() { + return mainChatId; + } + public Optional getRoom(UUID roomId) { - if(rooms.containsKey(roomId)) { + if (rooms.containsKey(roomId)) { return Optional.of(rooms.get(roomId)); } logger.error("room not found : " + roomId);