diff --git a/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java b/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java index 81ee956b31d..67dfd47f7d4 100644 --- a/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java +++ b/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java @@ -5,6 +5,7 @@ import mage.client.SessionHandler; import mage.client.cards.BigCard; import mage.client.dialog.PreferencesDialog; import mage.client.util.GUISizeHelper; +import mage.constants.Constants; import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageType; import org.mage.card.arcane.ManaSymbols; @@ -103,7 +104,6 @@ public class ChatPanelBasic extends javax.swing.JPanel { jScrollPaneTxt.getViewport().setBackground(new Color(0, 0, 0, CHAT_ALPHA)); jScrollPaneTxt.setViewportBorder(null); } - } public void cleanUp() { @@ -398,6 +398,12 @@ public class ChatPanelBasic extends javax.swing.JPanel { public void handleKeyTyped(java.awt.event.KeyEvent evt) { if (evt.getKeyChar() == KeyEvent.VK_ENTER) { + + if (this.txtMessage.getText().length() > Constants.MAX_CHAT_MESSAGE_SIZE) { + JOptionPane.showMessageDialog(null, "Can't send too long message", "Chat", JOptionPane.WARNING_MESSAGE); + return; + } + if (parentChatRef != null) { SessionHandler.sendChatMessage(parentChatRef.chatId, this.txtMessage.getText()); } else { diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index f1506ff52ca..e278cd7fb64 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -1497,13 +1497,13 @@ public class DeckEditorPanel extends javax.swing.JPanel { timeToSubmit = 60; this.btnSubmitTimer.setEnabled(false); - ScheduledFuture scheduledFuture = scheduledExecutorService.schedule((Callable) () -> { + scheduledExecutorService.schedule(() -> { if (updateDeckTask != null) { updateDeckTask.cancel(true); } if (SessionHandler.submitDeck(mode, tableId, deck.getDeckCardLists())) { - removeDeckEditor(); + SwingUtilities.invokeLater(this::removeDeckEditor); } return null; }, 60, TimeUnit.SECONDS); diff --git a/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java b/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java index 21872969086..d78a361b226 100644 --- a/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java @@ -11,6 +11,7 @@ import mage.constants.PlayerAction; import mage.constants.TurnPhase; import org.apache.log4j.Logger; +import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.io.Serializable; @@ -40,6 +41,7 @@ public class FeedbackPanel extends javax.swing.JPanel { private ChatPanelBasic connectedChatPanel; private Map lastOptions = new HashMap<>(); + private static final int AUTO_CLOSE_END_DIALOG_TIMEOUT_SECS = 8; private static final ScheduledExecutorService WORKER = Executors.newSingleThreadScheduledExecutor(); /** @@ -153,16 +155,18 @@ public class FeedbackPanel extends javax.swing.JPanel { */ private void endWithTimeout() { Runnable task = () -> { - LOGGER.info("Ending game..."); - Component c = MageFrame.getGame(gameId); - while (c != null && !(c instanceof GamePane)) { - c = c.getParent(); - } - if (c != null && c.isVisible()) { // check if GamePanel still visible - FeedbackPanel.this.btnRight.doClick(); - } + SwingUtilities.invokeLater(() -> { + LOGGER.info("Ending game..."); + Component c = MageFrame.getGame(gameId); + while (c != null && !(c instanceof GamePane)) { + c = c.getParent(); + } + if (c != null && c.isVisible()) { // check if GamePanel still visible + FeedbackPanel.this.btnRight.doClick(); + } + }); }; - WORKER.schedule(task, 8, TimeUnit.SECONDS); + WORKER.schedule(task, AUTO_CLOSE_END_DIALOG_TIMEOUT_SECS, TimeUnit.SECONDS); } public void updateOptions(Map options) { diff --git a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java index 259a686922e..766c1a9d38f 100644 --- a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java +++ b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java @@ -87,12 +87,12 @@ public class MageActionCallback implements ActionCallback { private Date enlargeredViewOpened; private volatile EnlargedWindowState enlargedWindowState = EnlargedWindowState.CLOSED; - //private volatile boolean enlargedImageWindowOpen = false; // shows the alternative card the normal card or the alternative card (copy source, other flip side, other transformed side) private volatile EnlargeMode enlargeMode; - private static final ScheduledExecutorService timeoutExecutor = Executors.newScheduledThreadPool(1); - private ScheduledFuture hideTimeout; + private static final ScheduledExecutorService hideEnlargedCardWorker = Executors.newScheduledThreadPool(1); + private ScheduledFuture hideEnlagedCardTask; + private static final int HIDE_ENLARGED_CARD_TIMEOUT_MS = 700; private MageCard prevCardPanel; private boolean startedDragging; @@ -472,7 +472,7 @@ public class MageActionCallback implements ActionCallback { } hideTooltipPopup(); - cancelTimeout(); + cancelHidingEnlagedCard(); Component parentComponent = SwingUtilities.getRoot(cardPanel); if (parentComponent == null) { // virtual card (example: show card popup in non cards panel like PickChoiceDialog) @@ -514,7 +514,7 @@ public class MageActionCallback implements ActionCallback { popupTextWindowOpen = true; } if (enlargedWindowState != EnlargedWindowState.CLOSED) { - cancelTimeout(); + cancelHidingEnlagedCard(); displayEnlargedCard(cardPanel.getOriginal(), data); } } @@ -552,7 +552,7 @@ public class MageActionCallback implements ActionCallback { public void hideAll(UUID gameId) { hideTooltipPopup(); - startHideTimeout(); + startHidingEnlagedCard(); this.popupTextWindowOpen = false; if (gameId != null) { ArrowBuilder.getBuilder().removeArrowsByType(gameId, ArrowBuilder.Type.TARGET); @@ -740,14 +740,16 @@ public class MageActionCallback implements ActionCallback { return PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_RENDERING_ABILITY_TEXT_OVERLAY, "true").equals("true"); } - private synchronized void startHideTimeout() { - cancelTimeout(); - hideTimeout = timeoutExecutor.schedule(this::hideEnlargedCard, 700, TimeUnit.MILLISECONDS); + private synchronized void startHidingEnlagedCard() { + cancelHidingEnlagedCard(); + hideEnlagedCardTask = hideEnlargedCardWorker.schedule( + () -> SwingUtilities.invokeLater(this::hideEnlargedCard), HIDE_ENLARGED_CARD_TIMEOUT_MS, TimeUnit.MILLISECONDS + ); } - private synchronized void cancelTimeout() { - if (hideTimeout != null) { - hideTimeout.cancel(false); + private synchronized void cancelHidingEnlagedCard() { + if (hideEnlagedCardTask != null) { + hideEnlagedCardTask.cancel(false); } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java index 4ef12a83305..2ee09f658cf 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java @@ -647,7 +647,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements while (!executor.isTerminated()) { try { TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException ie) { + } catch (InterruptedException ignore) { } } } diff --git a/Mage.Common/src/main/java/mage/constants/Constants.java b/Mage.Common/src/main/java/mage/constants/Constants.java index 400b9df9eff..0b603870732 100644 --- a/Mage.Common/src/main/java/mage/constants/Constants.java +++ b/Mage.Common/src/main/java/mage/constants/Constants.java @@ -43,6 +43,8 @@ public final class Constants { public static final int MAX_AVATAR_ID = 32; public static final int DEFAULT_AVATAR_ID = 10; + public static final int MAX_CHAT_MESSAGE_SIZE = 500; // ignore too big messages + /** * Time each player has during the game to play using his\her priority. */ diff --git a/Mage.Common/src/main/java/mage/interfaces/MageServer.java b/Mage.Common/src/main/java/mage/interfaces/MageServer.java index 8be6214173a..be020467844 100644 --- a/Mage.Common/src/main/java/mage/interfaces/MageServer.java +++ b/Mage.Common/src/main/java/mage/interfaces/MageServer.java @@ -45,16 +45,20 @@ public interface MageServer { Object serverGetPromotionMessages(String sessionId) throws MageException; // sync cards send sets db - // TODO: outdated, no more client/server sync, can be removed? + @Deprecated // TODO: outdated, no more client/server sync, can be removed? List syncGetMissingExpansionData(List codes); + @Deprecated // TODO: outdated, no more client/server sync, can be removed? List syncGetMissingCardsData(List classNames); ServerState getServerState() throws MageException; // TODO: need stable update process, so rename it after few releases + // TODO: miss session UUID serverGetMainRoomId() throws MageException; + // TODO: miss session List roomGetUsers(UUID roomId) throws MageException; + // TODO: miss session List roomGetFinishedMatches(UUID roomId) throws MageException; TableView roomCreateTable(String sessionId, UUID roomId, MatchOptions matchOptions) throws MageException; @@ -81,22 +85,29 @@ public interface MageServer { boolean tableIsOwner(String sessionId, UUID roomId, UUID tableId) throws MageException; + // TODO: miss session TableView roomGetTableById(UUID roomId, UUID tableId) throws MageException; + // TODO: miss session List roomGetAllTables(UUID roomId) throws MageException; + // TODO: miss session void chatSendMessage(UUID chatId, String userName, String message) throws MageException; void chatJoin(UUID chatId, String sessionId, String userName) throws MageException; void chatLeave(UUID chatId, String sessionId) throws MageException; + // TODO: miss session UUID chatFindByGame(UUID gameId) throws MageException; + // TODO: miss session UUID chatFindByTable(UUID tableId) throws MageException; + // TODO: miss session UUID chatFindByTournament(UUID tournamentId) throws MageException; + // TODO: miss session UUID chatFindByRoom(UUID roomId) throws MageException; boolean matchStart(String sessionId, UUID roomId, UUID tableId) throws MageException; @@ -135,6 +146,7 @@ public interface MageServer { void tournamentQuit(UUID tournamentId, String sessionId) throws MageException; + // TODO: miss session TournamentView tournamentFindById(UUID tournamentId) throws MageException; void draftJoin(UUID draftId, String sessionId) throws MageException; diff --git a/Mage.Common/src/main/java/mage/remote/Connection.java b/Mage.Common/src/main/java/mage/remote/Connection.java index ddc30666f72..28b60f18611 100644 --- a/Mage.Common/src/main/java/mage/remote/Connection.java +++ b/Mage.Common/src/main/java/mage/remote/Connection.java @@ -261,6 +261,7 @@ public class Connection { return userData; } + @Deprecated // TODO: server side cards do not supports now, so remove outdated code (db sync with server) public boolean isForceDBComparison() { return forceDBComparison; } diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index 96c5e674771..2cd8b722744 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -424,12 +424,8 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { * @return */ protected Integer addActionsTimed() { - FutureTask task = new FutureTask<>(new Callable() { - @Override - public Integer call() throws Exception { - return addActions(root, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE); - } - }); + // run new game simulation in parallel thread + FutureTask task = new FutureTask<>(() -> addActions(root, maxDepth, Integer.MIN_VALUE, Integer.MAX_VALUE)); pool.execute(task); try { int maxSeconds = maxThink; @@ -437,18 +433,19 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { maxSeconds = 3600; } logger.debug("maxThink: " + maxSeconds + " seconds "); - if (task.get(maxSeconds, TimeUnit.SECONDS) != null) { - return task.get(maxSeconds, TimeUnit.SECONDS); + Integer res = task.get(maxSeconds, TimeUnit.SECONDS); + if (res != null) { + return res; } } catch (TimeoutException e) { logger.info("simulating - timed out"); task.cancel(true); } catch (ExecutionException e) { // exception error in simulated game - e.printStackTrace(); task.cancel(true); - // real games: must catch + // real games: must catch and log // unit tests: must raise again for test fail + logger.error("AI simulation game catch error: " + e.getCause(), e); if (this.isTestsMode()) { throw new IllegalStateException("One of the simulated games raise the error: " + e.getCause()); } diff --git a/Mage.Server/src/main/java/mage/server/MageServerImpl.java b/Mage.Server/src/main/java/mage/server/MageServerImpl.java index 5240865fb29..bb39a2c1bcb 100644 --- a/Mage.Server/src/main/java/mage/server/MageServerImpl.java +++ b/Mage.Server/src/main/java/mage/server/MageServerImpl.java @@ -7,6 +7,7 @@ import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; import mage.cards.repository.ExpansionInfo; import mage.cards.repository.ExpansionRepository; +import mage.constants.Constants; import mage.constants.ManaType; import mage.constants.PlayerAction; import mage.constants.TableState; @@ -488,7 +489,13 @@ public class MageServerImpl implements MageServer { @Override //FIXME: why no sessionId here??? public void chatSendMessage(final UUID chatId, final String userName, final String message) throws MageException { + if (message.length() > Constants.MAX_CHAT_MESSAGE_SIZE) { + logger.error("Chat message too big: " + message.length() + ", from user " + userName); + return; + } + try { + // TODO: check and replace all usage of callExecutor.execute() by execute("actionName") callExecutor.execute( () -> managerFactory.chatManager().broadcast(chatId, userName, HtmlEscape.escapeHtml4(message), MessageColor.BLUE, true, null, ChatMessage.MessageType.TALK, null) ); @@ -953,8 +960,14 @@ public class MageServerImpl implements MageServer { } @Override - //TODO: check how often it is used public ServerState getServerState() throws MageException { + // called one time per login, must work without auth and with diff versions + try { + // some ddos protection + Thread.sleep(1000); + } catch (InterruptedException ignore) { + } + try { return new ServerState( GameFactory.instance.getGameTypes(), diff --git a/Mage.Server/src/main/java/mage/server/TableManagerImpl.java b/Mage.Server/src/main/java/mage/server/TableManagerImpl.java index 8826acb2d44..c71ea8f7970 100644 --- a/Mage.Server/src/main/java/mage/server/TableManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/TableManagerImpl.java @@ -36,7 +36,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; public class TableManagerImpl implements TableManager { protected final ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor(); - // protected static ScheduledExecutorService expireExecutor = ThreadExecutorImpl.getInstance().getExpireExecutor(); private final ManagerFactory managerFactory; private final Logger logger = Logger.getLogger(TableManagerImpl.class); private final DateFormat formatter = new SimpleDateFormat("HH:mm:ss"); diff --git a/Mage.Server/src/main/java/mage/server/UserManagerImpl.java b/Mage.Server/src/main/java/mage/server/UserManagerImpl.java index 8b1fa6ff34b..8f10aadd7d1 100644 --- a/Mage.Server/src/main/java/mage/server/UserManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/UserManagerImpl.java @@ -26,12 +26,15 @@ public class UserManagerImpl implements UserManager { 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 + private static final int SERVER_TIMEOUTS_USER_EXPIRE_CHECK_SECS = 60; + private static final int SERVER_TIMEOUTS_USERS_LIST_UPDATE_SECS = 4; // server side updates (client use own timeouts to request users list) + private static final Logger logger = Logger.getLogger(UserManagerImpl.class); protected final ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor(); protected final ScheduledExecutorService userListExecutor = Executors.newSingleThreadScheduledExecutor(); - private List userInfoList = new ArrayList<>(); + private List userInfoList = new ArrayList<>(); // all users list for main room/chat private final ManagerFactory managerFactory; @@ -46,9 +49,8 @@ public class UserManagerImpl implements UserManager { public void init() { USER_EXECUTOR = managerFactory.threadExecutor().getCallExecutor(); - expireExecutor.scheduleAtFixedRate(this::checkExpired, 60, 60, TimeUnit.SECONDS); - - userListExecutor.scheduleAtFixedRate(this::updateUserInfoList, 4, 4, TimeUnit.SECONDS); + expireExecutor.scheduleAtFixedRate(this::checkExpired, SERVER_TIMEOUTS_USER_EXPIRE_CHECK_SECS, SERVER_TIMEOUTS_USER_EXPIRE_CHECK_SECS, TimeUnit.SECONDS); + userListExecutor.scheduleAtFixedRate(this::updateUserInfoList, SERVER_TIMEOUTS_USERS_LIST_UPDATE_SECS, SERVER_TIMEOUTS_USERS_LIST_UPDATE_SECS, TimeUnit.SECONDS); } @Override @@ -254,7 +256,7 @@ public class UserManagerImpl implements UserManager { } /** - * This method recreated the user list that will be send to all clients + * This method recreated the user list that will be sent to all clients */ private void updateUserInfoList() { try { 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 b03261b390e..c61bfc85942 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -335,7 +335,7 @@ public class GameController implements GameCallback { gameFuture = gameExecutor.submit(worker); try { Thread.sleep(1000); - } catch (InterruptedException ex) { + } catch (InterruptedException ignore) { } if (game.getState().getChoosingPlayerId() != null) { // start timer to force player to choose starting player otherwise loosing by being idle diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java index 65a7f4653a9..d5672e411cc 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java @@ -37,7 +37,7 @@ public class GameSessionPlayer extends GameSessionWatcher { public GameSessionPlayer(ManagerFactory managerFactory, Game game, UUID userId, UUID playerId) { super(managerFactory.userManager(), userId, game, true); this.userManager = managerFactory.userManager(); - callExecutor = managerFactory.threadExecutor().getCallExecutor(); + this.callExecutor = managerFactory.threadExecutor().getCallExecutor(); this.playerId = playerId; } diff --git a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java index c69f29f69d7..e4e72d9fe05 100644 --- a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java +++ b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java @@ -45,11 +45,11 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { UPDATE_EXECUTOR.scheduleAtFixedRate(() -> { try { update(); - } catch (Exception ex) { - LOGGER.fatal("Games room update exception! " + ex.toString(), ex); + } catch (Exception e) { + LOGGER.fatal("Games room update error: " + e.getMessage(), e); } - }, 2, 2, TimeUnit.SECONDS); + }, 2, 2, TimeUnit.SECONDS); // TODO: is it ok for performance? } @Override