package dev.wiing.gossip.client; import dev.wiing.gossip.lib.PacketHandler; import dev.wiing.gossip.lib.PacketManager; import dev.wiing.gossip.lib.models.SecretUser; import dev.wiing.gossip.lib.models.User; import dev.wiing.gossip.lib.packets.*; import java.io.IOException; import java.net.Socket; import java.net.SocketException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class Connection { private static final Connection instance = new Connection(); private final PacketManager packetManager = new PacketManager(); private final PacketHandler packetHandler = new PacketHandler(); private Thread handlerThread; private Socket socket; private SecretUser self; private final List queuedPackets = Collections.synchronizedList(new LinkedList<>()); private Connection() { packetManager.registerPackets(); } public static Connection getInstance() { return instance; } public PacketManager getPacketManager() { return packetManager; } public PacketHandler getPacketHandler() { return packetHandler; } public static class PacketHandlerRunnable implements Runnable { private final Connection connection; public PacketHandlerRunnable(Connection connection) { this.connection = connection; } @Override public void run() { while (true) { try { Packet packet; if (connection.getSocket().getInputStream().available() > 0 || connection.queuedPackets.isEmpty()) { packet = connection.nextPacket(false); } else { packet = connection.findPacketOfTypes(connection.packetHandler.getListeningTypes()); } if (packet == null) continue; if (!connection.getPacketHandler().runPacket(packet)) { connection.queuedPackets.add(packet); } } catch (SocketException e) { break; } catch (IOException e) { e.printStackTrace(); } } } } public void beginHandlingPackets() { handlerThread = new Thread(new PacketHandlerRunnable(this)); handlerThread.start(); } public Socket getSocket() { return socket; } public void setSocket(Socket socket) { this.socket = socket; } public void sendPacket(Packet packet) { try { getPacketManager().writePacket(getSocket().getOutputStream(), packet); } catch (IOException e) { e.printStackTrace(); } } public void sendPacketAuthenticated(AuthRequiredPacket packet) { packet.setAuth(getSelf().getUserSecret()); sendPacket(packet); } public Packet nextPacket(boolean useQueue) throws SocketException { if (useQueue && !queuedPackets.isEmpty()) { return queuedPackets.remove(0); } try { return getPacketManager().readPacket(getSocket().getInputStream()); } catch (SocketException e) { throw e; } catch (IOException e) { e.printStackTrace(); return null; } } public Packet nextPacket() throws SocketException { return nextPacket(true); } public Packet nextPacket(short packetType) throws SocketException { while (true) { Packet packet = nextPacket(false); if (packet.getType() != packetType) { queuedPackets.add(packet); continue; } return packet; } } public Packet findPacketOfTypes(List types) { Packet packet = null; int i; for (i = 0; i < queuedPackets.size(); i++) { Packet queuedPacket = queuedPackets.get(i); if (types.contains(queuedPacket.getType())) { packet = queuedPacket; break; } } if (packet != null) { queuedPackets.remove(i); return packet; } return null; } public boolean findAck(short acknowledgement) throws IOException { while (true) { while (socket.getInputStream().available() > 0); AckPacket ack = null; int i; for (i = 0; i < queuedPackets.size(); i++) { Packet queuedPacket = queuedPackets.get(i); if (queuedPacket.getType() == AckPacket.TYPE) { ack = (AckPacket) queuedPacket; if (ack.getAcknowledgement() == acknowledgement) { break; } ack = null; } } if (ack != null) { queuedPackets.remove(i); return true; } } } public SecretUser getSelf() { return self; } public void setSelf(SecretUser self) { this.self = self; userCache.put(self.getUserID(), new User( self.getUsername(), self.getAvatarID(), self.getUserID() )); } private final Map userCache = new ConcurrentHashMap<>(); public User getUser(long userID) { User result; if ((result = userCache.getOrDefault(userID, null)) != null) { return result; } UserFetchPacket fetch = new UserFetchPacket(); fetch.setUserID(userID); Connection.getInstance().sendPacket(fetch); Packet resp; try { resp = Connection.getInstance().nextPacket(UserDataPacket.TYPE); } catch (SocketException e) { throw new RuntimeException(e); } if (resp.getType() == UserDataPacket.TYPE) { UserDataPacket userData = (UserDataPacket)resp; result = new User( userData.getUsername(), userData.getAvatarID(), userData.getUserID() ); userCache.put(result.getUserID(), result); } return result; } }