diff --git a/assets/ArkPetsConfigDefault.json b/assets/ArkPetsConfigDefault.json index cf4491f5..a376aefc 100644 --- a/assets/ArkPetsConfigDefault.json +++ b/assets/ArkPetsConfigDefault.json @@ -18,7 +18,5 @@ "physic_gravity_acc":800.0, "physic_speed_limit_x":1000.0, "physic_speed_limit_y":1000.0, - "physic_static_friction_acc":500.0, - "server_port": 8080, - "separate_arkpet_from_launcher": false + "physic_static_friction_acc":500.0 } \ No newline at end of file diff --git a/core/src/cn/harryh/arkpets/ArkConfig.java b/core/src/cn/harryh/arkpets/ArkConfig.java index 890063fe..e94bc746 100644 --- a/core/src/cn/harryh/arkpets/ArkConfig.java +++ b/core/src/cn/harryh/arkpets/ArkConfig.java @@ -63,8 +63,6 @@ public class ArkConfig { public float physic_static_friction_acc; public float physic_speed_limit_x; public float physic_speed_limit_y; - public int server_port = 8080; - public boolean separate_arkpet_from_launcher; private ArkConfig() { } diff --git a/core/src/cn/harryh/arkpets/ArkPets.java b/core/src/cn/harryh/arkpets/ArkPets.java index cc06b5cb..f0af399e 100644 --- a/core/src/cn/harryh/arkpets/ArkPets.java +++ b/core/src/cn/harryh/arkpets/ArkPets.java @@ -88,7 +88,7 @@ public void create() { ArkConfig.Monitor primaryMonitor = ArkConfig.Monitor.getMonitors()[0]; initWindow((int)(primaryMonitor.size[0] * 0.1f), (int)(primaryMonitor.size[0] * 0.1f)); // 5.Tray icon setup - tray = new ArkTray(this, new SocketClient(config.server_port), UUID.randomUUID()); + tray = new ArkTray(this, new SocketClient(), UUID.randomUUID()); // Setup complete Logger.info("App", "Render"); } @@ -142,7 +142,6 @@ public void resize(int x, int y) { @Override public void dispose() { Logger.info("App", "Dispose"); - tray.removeTray(); } public boolean canChangeStage() { diff --git a/core/src/cn/harryh/arkpets/ArkTray.java b/core/src/cn/harryh/arkpets/ArkTray.java index ab820675..59555d1a 100644 --- a/core/src/cn/harryh/arkpets/ArkTray.java +++ b/core/src/cn/harryh/arkpets/ArkTray.java @@ -12,14 +12,17 @@ import javax.swing.*; import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.io.IOException; import java.io.InputStream; import java.util.Objects; +import java.util.Timer; +import java.util.TimerTask; import java.util.UUID; -import static cn.harryh.arkpets.Const.fontFileRegular; -import static cn.harryh.arkpets.Const.linearEasingDuration; +import static cn.harryh.arkpets.Const.*; public class ArkTray extends Tray { @@ -29,6 +32,7 @@ public class ArkTray extends Tray { public static Font font; private final JDialog popWindow; private final JPopupMenu popMenu; + private final boolean[] button = {false, false}; static { try { @@ -65,7 +69,31 @@ public void firePopupMenuWillBecomeInvisible() { }; name = (arkPets.config.character_label == null || arkPets.config.character_label.isEmpty()) ? "Unknown" : arkPets.config.character_label; socketClient.connect(socketData -> { - if (socketData == null || socketData.uuid.compareTo(uuid) != 0) + if (socketData == null) { + Image image = Toolkit.getDefaultToolkit().createImage(getClass().getResource(iconFilePng)); + TrayIcon icon = getTrayIcon(image); + + // Add the icon to the system tray. + try { + SystemTray.getSystemTray().add(icon); + Logger.info("Tray", "Tray icon applied"); + } catch (AWTException e) { + Logger.error("Tray", "Unable to apply tray icon, details see below", e); + } + socketClient.disconnect(); + socketClient.reconnect(() -> { + SystemTray.getSystemTray().remove(icon); + socketClient.sendRequest(new SocketData(this.uuid, SocketData.OperateType.LOGIN, name, arkPets.canChangeStage())); + if (button[0]) { + socketClient.sendRequest(new SocketData(this.uuid, SocketData.OperateType.KEEP_ACTION)); + } + if (button[1]) { + socketClient.sendRequest(new SocketData(this.uuid, SocketData.OperateType.TRANSPARENT_MODE)); + } + }); + return; + } + if (socketData.uuid.compareTo(uuid) != 0) return; switch (socketData.operateType) { case LOGOUT -> optExitHandler(); @@ -80,6 +108,22 @@ public void firePopupMenuWillBecomeInvisible() { addComponent(); } + private TrayIcon getTrayIcon(Image image) { + TrayIcon icon = new TrayIcon(image, name); + icon.setImageAutoSize(true); + icon.addMouseListener(new MouseAdapter() { + @Override + public void mouseReleased(MouseEvent e) { + if (e.getButton() == 3 && e.isPopupTrigger()) { + // After right-click on the tray icon. + int x = e.getX(); + int y = e.getY(); + showDialog(x + 5, y); + } + } + }); + return icon; + } @Override protected void addComponent() { @@ -109,11 +153,12 @@ protected void optExitHandler() { Logger.info("Tray", "Request to exit"); arkPets.windowAlpha.reset(0f); removeTray(); - try { - Thread.sleep((long) (linearEasingDuration * 1000)); - Gdx.app.exit(); - } catch (InterruptedException ignored) { - } + new Timer().schedule(new TimerTask() { + @Override + public void run() { + Gdx.app.exit(); + } + }, (int) (linearEasingDuration * 1000)); } @Override @@ -130,6 +175,7 @@ protected void optChangeStageHandler() { @Override protected void optTransparentDisHandler() { Logger.info("Tray", "Transparent disabled"); + button[1] = false; arkPets.windowAlpha.reset(1f); arkPets.hWndMine.setWindowTransparent(false); popMenu.remove(optTransparentDis); @@ -139,6 +185,7 @@ protected void optTransparentDisHandler() { @Override protected void optTransparentEnHandler() { Logger.info("Tray", "Transparent enabled"); + button[1] = true; arkPets.windowAlpha.reset(0.75f); arkPets.hWndMine.setWindowTransparent(true); popMenu.remove(optTransparentEn); @@ -148,6 +195,7 @@ protected void optTransparentEnHandler() { @Override protected void optKeepAnimDisHandler() { Logger.info("Tray", "Keep-Anim disabled"); + button[0] = false; keepAnim = null; popMenu.remove(optKeepAnimDis); popMenu.add(optKeepAnimEn, 1); @@ -156,6 +204,7 @@ protected void optKeepAnimDisHandler() { @Override protected void optKeepAnimEnHandler() { Logger.info("Tray", "Keep-Anim enabled"); + button[0] = true; keepAnim = arkPets.cha.getPlaying(); popMenu.remove(optKeepAnimEn); popMenu.add(optKeepAnimDis, 1); diff --git a/core/src/cn/harryh/arkpets/Const.java b/core/src/cn/harryh/arkpets/Const.java index 34d46ddb..937c3bf4 100644 --- a/core/src/cn/harryh/arkpets/Const.java +++ b/core/src/cn/harryh/arkpets/Const.java @@ -103,4 +103,8 @@ public static class LogConfig { public static final String debugArg = "--debug"; } + // SocketServer Ports + + public static final int[] serverPorts = {8686, 8866, 8989, 8899, 8800}; + } diff --git a/core/src/cn/harryh/arkpets/exception/NoPortAvailableException.java b/core/src/cn/harryh/arkpets/exception/NoPortAvailableException.java new file mode 100644 index 00000000..814b8c20 --- /dev/null +++ b/core/src/cn/harryh/arkpets/exception/NoPortAvailableException.java @@ -0,0 +1,7 @@ +package cn.harryh.arkpets.exception; + +public class NoPortAvailableException extends Exception { + public NoPortAvailableException() { + super("No port available"); + } +} diff --git a/core/src/cn/harryh/arkpets/exception/NoServerRunningException.java b/core/src/cn/harryh/arkpets/exception/NoServerRunningException.java new file mode 100644 index 00000000..0817fda6 --- /dev/null +++ b/core/src/cn/harryh/arkpets/exception/NoServerRunningException.java @@ -0,0 +1,7 @@ +package cn.harryh.arkpets.exception; + +public class NoServerRunningException extends Exception { + public NoServerRunningException() { + super("Can not find a running server"); + } +} diff --git a/core/src/cn/harryh/arkpets/exception/ServerRunningException.java b/core/src/cn/harryh/arkpets/exception/ServerRunningException.java new file mode 100644 index 00000000..ec8ba0f0 --- /dev/null +++ b/core/src/cn/harryh/arkpets/exception/ServerRunningException.java @@ -0,0 +1,7 @@ +package cn.harryh.arkpets.exception; + +public class ServerRunningException extends Exception { + public ServerRunningException() { + super("Server is already running"); + } +} diff --git a/core/src/cn/harryh/arkpets/process_pool/ProcessPool.java b/core/src/cn/harryh/arkpets/process_pool/ProcessPool.java index 8b008e11..68bc102e 100644 --- a/core/src/cn/harryh/arkpets/process_pool/ProcessPool.java +++ b/core/src/cn/harryh/arkpets/process_pool/ProcessPool.java @@ -1,20 +1,30 @@ package cn.harryh.arkpets.process_pool; +import cn.harryh.arkpets.socket.InteriorSocketServer; + import java.io.File; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; + public class ProcessPool { private final Set processHolderHashSet = new HashSet<>(); - private final java.util.concurrent.ExecutorService executorService; + private final java.util.concurrent.ExecutorService executorService = InteriorSocketServer.getThreadPool(); + private static ProcessPool instance = null; + + public static ProcessPool getInstance() { + if (instance == null) + instance = new ProcessPool(); + return instance; + } - public ProcessPool() { - this.executorService = Executors.newFixedThreadPool(10); + private ProcessPool() { } public void shutdown() { processHolderHashSet.forEach(processHolder -> processHolder.getProcess().destroy()); - executorService.shutdown(); } public Future submit(Runnable task) { diff --git a/core/src/cn/harryh/arkpets/process_pool/Status.java b/core/src/cn/harryh/arkpets/process_pool/Status.java deleted file mode 100644 index 96e068c5..00000000 --- a/core/src/cn/harryh/arkpets/process_pool/Status.java +++ /dev/null @@ -1,6 +0,0 @@ -package cn.harryh.arkpets.process_pool; - -public enum Status { - SUCCESS, - FAILURE -} diff --git a/core/src/cn/harryh/arkpets/process_pool/TaskStatus.java b/core/src/cn/harryh/arkpets/process_pool/TaskStatus.java index 7c20587d..e44e2619 100644 --- a/core/src/cn/harryh/arkpets/process_pool/TaskStatus.java +++ b/core/src/cn/harryh/arkpets/process_pool/TaskStatus.java @@ -1,6 +1,12 @@ package cn.harryh.arkpets.process_pool; public class TaskStatus { + + public enum Status { + SUCCESS, + FAILURE + } + private final Status status; private final Throwable exception; private final Long processId; diff --git a/core/src/cn/harryh/arkpets/socket/InteriorSocketServer.java b/core/src/cn/harryh/arkpets/socket/InteriorSocketServer.java index a4b2f166..1d508f0b 100644 --- a/core/src/cn/harryh/arkpets/socket/InteriorSocketServer.java +++ b/core/src/cn/harryh/arkpets/socket/InteriorSocketServer.java @@ -1,6 +1,7 @@ package cn.harryh.arkpets.socket; -import cn.harryh.arkpets.ArkConfig; +import cn.harryh.arkpets.exception.NoPortAvailableException; +import cn.harryh.arkpets.exception.ServerRunningException; import cn.harryh.arkpets.tray.ClientTrayHandler; import cn.harryh.arkpets.utils.Logger; @@ -9,11 +10,14 @@ import java.net.Socket; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.concurrent.*; +import static cn.harryh.arkpets.utils.IOUtils.NetUtils.getAvailablePort; + + public class InteriorSocketServer { - private final int port; + private int port; + private boolean checked = false; private static final List clientSockets = new ArrayList<>(); private static final List clientHandlers = new ArrayList<>(); private static final ExecutorService executorService = @@ -24,57 +28,78 @@ public class InteriorSocketServer { thread.setDaemon(true); return thread; }); - private ServerSocket serverSocket; - private static volatile boolean mainThreadExitFlag = false; + private ServerSocket serverSocket = null; + private static Thread mainThread; private static InteriorSocketServer instance = null; + + public static ExecutorService getThreadPool() { + return executorService; + } + public static InteriorSocketServer getInstance() { - if (instance == null) + if (instance == null) { instance = new InteriorSocketServer(); + } return instance; } + public int checkServerAvailable() { + try { + this.port = getAvailablePort(); + checked = true; + return 1; + } catch (NoPortAvailableException e) { + return 0; + } catch (ServerRunningException e) { + return -1; + } + } + private InteriorSocketServer() { - this.port = Objects.requireNonNull(ArkConfig.getConfig()).server_port; } public synchronized void startServer() { - executorService.execute(() -> { + if (!checked) + return; + mainThread = new Thread(() -> { try { serverSocket = new ServerSocket(port); Logger.info("Socket", "Server is running on port " + port); - while (!mainThreadExitFlag) { + while (true) { Socket clientSocket = serverSocket.accept(); clientSockets.add(clientSocket); Logger.info("Socket", "New client connection from %s:%d".formatted(clientSocket.getInetAddress().getHostAddress(), clientSocket.getPort())); ClientTrayHandler clientTrayHandler = new ClientTrayHandler(clientSocket); clientHandlers.add(clientTrayHandler); + if (executorService.isShutdown()) + break; executorService.execute(clientTrayHandler); } + Logger.info("Socket", "Server stop"); } catch (IOException e) { throw new RuntimeException(e); } }); + executorService.execute(mainThread); } public synchronized void stopServer() { - mainThreadExitFlag = true; - clientSockets.forEach(socket -> { - try { - socket.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - if (!(Objects.requireNonNull(ArkConfig.getConfig()).separate_arkpet_from_launcher)) - clientHandlers.forEach(ClientTrayHandler::stopThread); + if (!checked) + return; + mainThread.interrupt(); + clientHandlers.forEach(ClientTrayHandler::stopThread); executorService.shutdown(); } public void removeClientSocket(Socket socket) { + if (!checked) + return; clientSockets.remove(socket); } public void removeClientHandler(ClientTrayHandler handler) { + if (!checked) + return; clientHandlers.remove(handler); } diff --git a/core/src/cn/harryh/arkpets/socket/SocketClient.java b/core/src/cn/harryh/arkpets/socket/SocketClient.java index a612bdef..af06a394 100644 --- a/core/src/cn/harryh/arkpets/socket/SocketClient.java +++ b/core/src/cn/harryh/arkpets/socket/SocketClient.java @@ -1,5 +1,6 @@ package cn.harryh.arkpets.socket; +import cn.harryh.arkpets.exception.NoServerRunningException; import cn.harryh.arkpets.utils.Logger; import com.alibaba.fastjson2.JSONObject; @@ -8,27 +9,63 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; +import java.net.SocketException; +import java.util.Timer; +import java.util.TimerTask; import java.util.function.Consumer; +import static cn.harryh.arkpets.utils.IOUtils.NetUtils.getServerPort; + + public class SocketClient { - private final String host; - private final int port; + private class Task extends TimerTask { + + private final Runnable callback; + + public Task(Runnable callback) { + this.callback = callback; + } + + @Override + public void run() { + try { + Logger.info("Socket", "Searching server"); + port = getServerPort(); + Logger.info("Socket", "Server found, connecting"); + timer.cancel(); + receiveThreadBreakFlag = false; + connect(consumer); + callback.run(); + } catch (NoServerRunningException ignored) { + } + } + } + + private final static String host = "localhost"; + private int port; private boolean connected = false; private Socket socket; private PrintWriter socketOut; private BufferedReader socketIn; + private Thread thread = null; + private Timer timer; + private Consumer consumer; private volatile boolean receiveThreadBreakFlag = false; - public SocketClient(String host, int port) { - this.host = host; - this.port = port; + public SocketClient() { + try { + port = getServerPort(); + } catch (NoServerRunningException e) { + throw new RuntimeException(e); + } } - - public SocketClient(int port) { - this("localhost", port); + + public void reconnect(Runnable callback) { + timer = new Timer(); + timer.schedule(new Task(callback), 0, 5000); } - public void connect(Consumer consumer) { + public void connect() { if (connected) { return; } @@ -36,17 +73,6 @@ public void connect(Consumer consumer) { socket = new Socket(host, port); socketOut = new PrintWriter(socket.getOutputStream(), true); socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); - Thread thread = new Thread(() -> { - while (!receiveThreadBreakFlag) { - try { - consumer.accept(JSONObject.parseObject(socketIn.readLine(), SocketData.class)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }); - thread.setDaemon(true); - thread.start(); connected = true; } catch (IOException e) { Logger.error("Socket", "Error connecting to %s:%d".formatted(host, port)); @@ -54,12 +80,34 @@ public void connect(Consumer consumer) { } } + public void connect(Consumer consumer) { + connect(); + this.consumer = consumer; + thread = new Thread(() -> { + while (!receiveThreadBreakFlag) { + try { + String receive = socketIn.readLine(); + Logger.debug("Socket", receive); + consumer.accept(JSONObject.parseObject(receive, SocketData.class)); + } catch (SocketException e) { + consumer.accept(null); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + thread.setDaemon(true); + thread.start(); + } + public void disconnect() { if (!connected) { return; } receiveThreadBreakFlag = true; try { + if (thread != null) + thread.interrupt(); socket.close(); socketOut.close(); socketIn.close(); diff --git a/core/src/cn/harryh/arkpets/socket/SocketData.java b/core/src/cn/harryh/arkpets/socket/SocketData.java index ce1b487d..7b511bbc 100644 --- a/core/src/cn/harryh/arkpets/socket/SocketData.java +++ b/core/src/cn/harryh/arkpets/socket/SocketData.java @@ -2,6 +2,7 @@ import java.util.UUID; + public class SocketData { public enum OperateType { LOGIN, @@ -10,7 +11,10 @@ public enum OperateType { NO_KEEP_ACTION, TRANSPARENT_MODE, NO_TRANSPARENT_MODE, - CHANGE_STAGE + CHANGE_STAGE, + VERIFY, + SERVER_ONLINE, + ACTIVATE_LAUNCHER } public UUID uuid; diff --git a/core/src/cn/harryh/arkpets/tray/ClientTrayHandler.java b/core/src/cn/harryh/arkpets/tray/ClientTrayHandler.java index 5c0e01b6..70e3b9f2 100644 --- a/core/src/cn/harryh/arkpets/tray/ClientTrayHandler.java +++ b/core/src/cn/harryh/arkpets/tray/ClientTrayHandler.java @@ -8,9 +8,11 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.io.PrintWriter; import java.net.Socket; import java.util.UUID; + public class ClientTrayHandler implements Runnable { private final Socket clientSocket; private Tray tray; @@ -25,6 +27,11 @@ public ClientTrayHandler(Socket clientSocket) { public synchronized void stopThread() { threadExitFlag = true; + try { + clientSocket.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } } @Override @@ -40,6 +47,18 @@ public void run() { if (uuid == null) uuid = socketData.uuid; switch (socketData.operateType) { + case VERIFY -> { + PrintWriter socketOut = new PrintWriter(clientSocket.getOutputStream(), true); + socketOut.println(JSONObject.toJSONString(new SocketData(uuid, SocketData.OperateType.SERVER_ONLINE))); + socketOut.close(); + clientSocket.close(); + return; + } + case ACTIVATE_LAUNCHER -> { + SystemTrayManager.getInstance().showLauncher(); + clientSocket.close(); + return; + } case LOGIN -> { tray = new TrayInstance(socketData.uuid, clientSocket, new String(socketData.name, "GBK"), socketData.canChangeStage); systemTrayManager.addTray(socketData.uuid, tray); @@ -68,7 +87,6 @@ public void run() { clientSocket.close(); } catch (IOException e) { Logger.error("Socket", e.getMessage()); - tray.optExitHandler(); InteriorSocketServer.getInstance().removeClientSocket(clientSocket); InteriorSocketServer.getInstance().removeClientHandler(this); } diff --git a/core/src/cn/harryh/arkpets/tray/SystemTrayManager.java b/core/src/cn/harryh/arkpets/tray/SystemTrayManager.java index 55b46702..b0f14bbe 100644 --- a/core/src/cn/harryh/arkpets/tray/SystemTrayManager.java +++ b/core/src/cn/harryh/arkpets/tray/SystemTrayManager.java @@ -14,11 +14,13 @@ import static cn.harryh.arkpets.Const.iconFilePng; + public class SystemTrayManager extends TrayManager { private static SystemTrayManager instance = null; private volatile JPopupMenu popupMenu; private volatile JDialog popWindow; private volatile JMenu playerMenu; + private static Stage stage; private static double x = 0; private static double y = 0; @@ -96,13 +98,14 @@ public void showDialog(int x, int y) { public void listen(Stage stage) { if (!initialized) return; + SystemTrayManager.stage = stage; trayIcon.removeMouseListener(new MouseAdapter() { }); MouseListener mouseListener = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { - showStage(stage); + showStage(); } } }; @@ -112,7 +115,7 @@ public void mouseClicked(MouseEvent e) { } @Override - public void hide(Stage stage) { + public void hide() { if (!initialized) return; Platform.runLater(() -> { @@ -126,7 +129,7 @@ public void hide(Stage stage) { }); } - private void showStage(Stage stage) { + private void showStage() { if (!initialized) return; Platform.runLater(() -> { @@ -142,9 +145,14 @@ private void showStage(Stage stage) { }); } + public void showLauncher() { + showStage(); + } + @Override public void addTray(UUID uuid, Tray tray) { arkPetTrays.put(uuid, tray); +// sendInfoMessage("新桌宠连接", "%s", tray.getName()); } @Override diff --git a/core/src/cn/harryh/arkpets/tray/Tray.java b/core/src/cn/harryh/arkpets/tray/Tray.java index 5f99ea29..766e1f40 100644 --- a/core/src/cn/harryh/arkpets/tray/Tray.java +++ b/core/src/cn/harryh/arkpets/tray/Tray.java @@ -3,6 +3,7 @@ import javax.swing.*; import java.util.UUID; + public abstract class Tray { protected JMenuItem optKeepAnimEn = new JMenuItem("保持动作"); protected JMenuItem optKeepAnimDis = new JMenuItem("取消保持"); @@ -24,6 +25,10 @@ public Tray(UUID uuid) { optExit.addActionListener(e -> optExitHandler()); } + public String getName() { + return name; + } + protected abstract void addComponent(); protected abstract void optExitHandler(); @@ -37,5 +42,6 @@ public Tray(UUID uuid) { protected abstract void optKeepAnimDisHandler(); protected abstract void optKeepAnimEnHandler(); + public abstract void removeTray(); } diff --git a/core/src/cn/harryh/arkpets/tray/TrayManager.java b/core/src/cn/harryh/arkpets/tray/TrayManager.java index b1bbe767..e172def5 100644 --- a/core/src/cn/harryh/arkpets/tray/TrayManager.java +++ b/core/src/cn/harryh/arkpets/tray/TrayManager.java @@ -1,8 +1,6 @@ package cn.harryh.arkpets.tray; import cn.harryh.arkpets.ArkTray; -import cn.harryh.arkpets.process_pool.ProcessPool; -import cn.harryh.arkpets.process_pool.TaskStatus; import cn.harryh.arkpets.utils.Logger; import javafx.stage.Stage; @@ -10,17 +8,16 @@ import java.awt.*; import java.io.IOException; import java.io.InputStream; -import java.util.List; -import java.util.*; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; import static cn.harryh.arkpets.Const.fontFileRegular; public abstract class TrayManager { protected volatile SystemTray tray; protected volatile TrayIcon trayIcon; - protected final static ProcessPool processPool = new ProcessPool(); protected boolean initialized = false; protected Map arkPetTrays = new HashMap<>(); public static Font font; @@ -43,7 +40,7 @@ public abstract class TrayManager { public abstract void listen(Stage stage); - public abstract void hide(Stage stage); + public abstract void hide(); public abstract void addTray(UUID uuid, Tray tray); @@ -51,14 +48,18 @@ public abstract class TrayManager { public abstract Tray getTray(UUID uuid); - public void shutdown() { - processPool.shutdown(); - } public SystemTray getTray() { return tray; } + /** + * Send system tray information + * @param messageType info type + * @param title title + * @param content content + * @param args content args + */ private void sendMessage(TrayIcon.MessageType messageType, String title, String content, Object... args) { if (!initialized) return; @@ -80,12 +81,4 @@ public void sendWarnMessage(String title, String content, Object... args) { public void sendMessage(String title, String content, Object... args) { sendMessage(TrayIcon.MessageType.NONE, title, content, args); } - - public FutureTask submit(Class clazz, java.util.List jvmArgs, List args) { - return processPool.submit(clazz, jvmArgs, args); - } - - public Future submit(Runnable task) { - return processPool.submit(task); - } } diff --git a/core/src/cn/harryh/arkpets/utils/IOUtils.java b/core/src/cn/harryh/arkpets/utils/IOUtils.java index e5d2ba0a..92aa0802 100644 --- a/core/src/cn/harryh/arkpets/utils/IOUtils.java +++ b/core/src/cn/harryh/arkpets/utils/IOUtils.java @@ -3,7 +3,16 @@ */ package cn.harryh.arkpets.utils; +import cn.harryh.arkpets.exception.NoPortAvailableException; +import cn.harryh.arkpets.exception.NoServerRunningException; +import cn.harryh.arkpets.exception.ServerRunningException; +import cn.harryh.arkpets.socket.SocketData; +import com.alibaba.fastjson2.JSONObject; + import java.io.*; +import java.net.DatagramSocket; +import java.net.Socket; +import java.net.SocketException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -12,14 +21,64 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Enumeration; +import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import static cn.harryh.arkpets.Const.*; +import static cn.harryh.arkpets.Const.serverPorts; +import static cn.harryh.arkpets.Const.zipBufferSizeDefault; public class IOUtils { + public static class NetUtils { + /** + * Get available server port for client to connect + * @return int + * @throws NoServerRunningException if server is not running + */ + public static int getServerPort() throws NoServerRunningException { + for (int serverPort : serverPorts) { + try (Socket socket = new Socket("localhost", serverPort)) { + socket.setSoTimeout(100); + PrintWriter out = new PrintWriter(socket.getOutputStream(), true); + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + out.println(JSONObject.toJSONString(new SocketData(UUID.randomUUID(), SocketData.OperateType.VERIFY))); + SocketData socketData = JSONObject.parseObject(in.readLine(), SocketData.class); + out.close(); + in.close(); + if (socketData.operateType == SocketData.OperateType.SERVER_ONLINE) { + return serverPort; + } + } catch (IOException ignored) { + } + } + throw new NoServerRunningException(); + } + + /** + * Get available server port for server to bind + * @return int + * @throws NoPortAvailableException if every port was bound + * @throws ServerRunningException if the server is running + */ + public static int getAvailablePort() throws NoPortAvailableException, ServerRunningException { + try { + getServerPort(); + throw new ServerRunningException(); + } catch (NoServerRunningException ignore) { + } + for (int serverPort : serverPorts) { + try (DatagramSocket ignored = new DatagramSocket(serverPort)) { + return serverPort; + } catch (SocketException ignored) { + } + } + throw new NoPortAvailableException(); + } + + } + public static class FileUtil { /** Reads the entire file into a byte array. * @param file The file to be read. @@ -28,7 +87,7 @@ public static class FileUtil { */ public static byte[] readByte(File file) throws IOException { - byte[] content = new byte[(int)file.length()]; + byte[] content = new byte[(int) file.length()]; FileInputStream stream = new FileInputStream(file); //noinspection ResultOfMethodCallIgnored stream.read(content); diff --git a/desktop/src/cn/harryh/arkpets/ArkHomeFX.java b/desktop/src/cn/harryh/arkpets/ArkHomeFX.java index 67b5a286..c13a1a41 100644 --- a/desktop/src/cn/harryh/arkpets/ArkHomeFX.java +++ b/desktop/src/cn/harryh/arkpets/ArkHomeFX.java @@ -9,11 +9,14 @@ import cn.harryh.arkpets.controllers.RootModule; import cn.harryh.arkpets.controllers.SettingsModule; import cn.harryh.arkpets.socket.InteriorSocketServer; +import cn.harryh.arkpets.socket.SocketClient; +import cn.harryh.arkpets.socket.SocketData; import cn.harryh.arkpets.tray.SystemTrayManager; import cn.harryh.arkpets.utils.FXMLHelper; import cn.harryh.arkpets.utils.FXMLHelper.LoadFXMLResult; import cn.harryh.arkpets.utils.Logger; import javafx.application.Application; +import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Scene; @@ -25,6 +28,7 @@ import javafx.util.Duration; import java.util.Objects; +import java.util.UUID; import static cn.harryh.arkpets.Const.*; @@ -47,13 +51,39 @@ public void start(Stage stage) throws Exception { Logger.info("Launcher", "Starting"); this.stage = stage; + // Socket server initialize + Logger.info("Socket", "Check server status"); + int status = InteriorSocketServer.getInstance().checkServerAvailable(); + switch (status) { + case 1: { + // No server started, start server + Logger.info("Socket", "Server starting"); + InteriorSocketServer.getInstance().startServer(); + break; + } + case 0: { + // No available port + Logger.error("Socket", "No available port"); + // TODO + // What if there are no port available + Platform.exit(); + } + case -1: { + // Server is running + Logger.error("Socket", "Server is running"); + SocketClient socketClient = new SocketClient(); + socketClient.connect(); + socketClient.sendRequest(new SocketData(UUID.randomUUID(), SocketData.OperateType.ACTIVATE_LAUNCHER)); + socketClient.disconnect(); + Logger.info("Launcher", "ArkPets Launcher has started."); + System.exit(0); + } + } + // Load fonts. Font.loadFont(getClass().getResourceAsStream(fontFileRegular), Font.getDefault().getSize()); Font.loadFont(getClass().getResourceAsStream(fontFileBold), Font.getDefault().getSize()); - // Start Socket Server - InteriorSocketServer.getInstance().startServer(); - // Load FXML for root node. LoadFXMLResult fxml0 = FXMLHelper.loadFXML(getClass().getResource("/UI/RootModule.fxml")); fxml0.initializeWith(this); @@ -71,15 +101,18 @@ public void start(Stage stage) throws Exception { stage.setTitle(desktopTitle); rootModule.titleText.setText(desktopTitle); + // Add system tray and listen stage SystemTrayManager.getInstance().listen(stage); + // Listen for tray click event stage.iconifiedProperty().addListener(((observable, oldValue, newValue) -> { if (newValue) { - SystemTrayManager.getInstance().hide(stage); + SystemTrayManager.getInstance().hide(); } })); - stage.setOnCloseRequest(e -> SystemTrayManager.getInstance().hide(stage)); + // Listen window close event + stage.setOnCloseRequest(e -> SystemTrayManager.getInstance().hide()); // After the stage is shown, do initialize modules. stage.show(); @@ -110,8 +143,8 @@ public void start(Stage stage) throws Exception { public void stop() throws Exception { super.stop(); InteriorSocketServer.getInstance().stopServer(); - if (!(Objects.requireNonNull(ArkConfig.getConfig()).separate_arkpet_from_launcher)) - SystemTrayManager.getInstance().shutdown(); + // function shutdown will kill all arkpets started by this launcher +// ProcessPool.getInstance().shutdown(); } public boolean initModelsDataset(boolean popNotice) { diff --git a/desktop/src/cn/harryh/arkpets/DesktopLauncher.java b/desktop/src/cn/harryh/arkpets/DesktopLauncher.java index 620483aa..be97e21f 100644 --- a/desktop/src/cn/harryh/arkpets/DesktopLauncher.java +++ b/desktop/src/cn/harryh/arkpets/DesktopLauncher.java @@ -3,7 +3,7 @@ */ package cn.harryh.arkpets; -import cn.harryh.arkpets.tray.SystemTrayManager; +import cn.harryh.arkpets.process_pool.ProcessPool; import cn.harryh.arkpets.utils.ArgPending; import cn.harryh.arkpets.utils.Logger; import javafx.application.Application; @@ -62,7 +62,7 @@ protected void process(String command, String addition) { }; // Java FX bootstrap - Future future = SystemTrayManager.getInstance().submit(() -> Application.launch(ArkHomeFX.class, args)); + Future future = ProcessPool.getInstance().submit(() -> Application.launch(ArkHomeFX.class, args)); try { future.get(); } catch (InterruptedException | ExecutionException e) { diff --git a/desktop/src/cn/harryh/arkpets/controllers/RootModule.java b/desktop/src/cn/harryh/arkpets/controllers/RootModule.java index a3be23ed..a8caa13c 100644 --- a/desktop/src/cn/harryh/arkpets/controllers/RootModule.java +++ b/desktop/src/cn/harryh/arkpets/controllers/RootModule.java @@ -8,9 +8,8 @@ import cn.harryh.arkpets.EmbeddedLauncher; import cn.harryh.arkpets.guitasks.CheckAppUpdateTask; import cn.harryh.arkpets.guitasks.GuiTask; -import cn.harryh.arkpets.process_pool.Status; +import cn.harryh.arkpets.process_pool.ProcessPool; import cn.harryh.arkpets.process_pool.TaskStatus; -import cn.harryh.arkpets.tray.SystemTrayManager; import cn.harryh.arkpets.utils.ArgPending; import cn.harryh.arkpets.utils.GuiPrefabs; import cn.harryh.arkpets.utils.JavaProcess; @@ -147,9 +146,9 @@ protected Boolean call() throws InterruptedException, ExecutionException { // Start ArkPets core. Logger.info("Launcher", "Launching " + app.config.character_asset); Logger.debug("Launcher", "With args " + args); - FutureTask future = SystemTrayManager.getInstance().submit(EmbeddedLauncher.class, List.of(), args); + FutureTask future = ProcessPool.getInstance().submit(EmbeddedLauncher.class, List.of(), args); // ArkPets core finalized. - if (Objects.equals(future.get().getStatus(), Status.FAILURE)) { + if (Objects.equals(future.get().getStatus(), TaskStatus.Status.FAILURE)) { Logger.warn("Launcher", "Detected an abnormal finalization of an ArkPets thread (exit code -1). Please check the log file for details."); lastLaunchFailed = new JavaProcess.UnexpectedExitCodeException(-1); return false;