From 3ced22bbddbcc6685b57f37679cb59ae20b9592a Mon Sep 17 00:00:00 2001 From: Mars Ultor Date: Sun, 23 Mar 2025 19:13:07 -0500 Subject: [PATCH] screameth --- .../net/client/ClientConnect.java | 14 ++ .../net/client/Dashboard.form | 53 +++++++ .../net/client/Dashboard.java | 141 ++++++++++++++++++ .../ayyalasomayajula/net/server/EHRUtils.java | 105 +++++++++++++ .../net/server/ServerDaemon.java | 85 ++++++++++- .../net/ayyalasomayajula/net/shared/EHR.java | 2 + .../net/shared/SerializationUtils.java | 41 +++++ target/classes/EHR.class | Bin 2916 -> 0 bytes .../net/client/ClientConnect$1.class | Bin 991 -> 1838 bytes .../net/client/ClientConnect.class | Bin 3298 -> 3298 bytes .../net/client/Dashboard.class | Bin 0 -> 7143 bytes .../net/server/ServerDaemon$1.class | Bin 1089 -> 1160 bytes .../net/server/ServerDaemon.class | Bin 5815 -> 9243 bytes 13 files changed, 435 insertions(+), 6 deletions(-) create mode 100644 src/main/java/net/ayyalasomayajula/net/client/Dashboard.form create mode 100644 src/main/java/net/ayyalasomayajula/net/client/Dashboard.java create mode 100644 src/main/java/net/ayyalasomayajula/net/server/EHRUtils.java create mode 100644 src/main/java/net/ayyalasomayajula/net/shared/SerializationUtils.java delete mode 100644 target/classes/EHR.class create mode 100644 target/classes/net/ayyalasomayajula/net/client/Dashboard.class diff --git a/src/main/java/net/ayyalasomayajula/net/client/ClientConnect.java b/src/main/java/net/ayyalasomayajula/net/client/ClientConnect.java index b4f9aec..77539c0 100644 --- a/src/main/java/net/ayyalasomayajula/net/client/ClientConnect.java +++ b/src/main/java/net/ayyalasomayajula/net/client/ClientConnect.java @@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.IOException; import java.net.Socket; /** @@ -42,6 +43,19 @@ public class ClientConnect extends JFrame { public void actionPerformed(ActionEvent actionEvent) { logger.info("Triggered a Connection request."); // Connection logic would go here (e.g., retrieve values and initiate connection) + try { + Socket socket = new Socket(getSanitizedText(hostname), Integer.valueOf(getSanitizedText(portNumber))); + logger.info("socket connection has been successfully established by the client"); + dispose(); + if(socket.isConnected()){ + Dashboard dashboard = new Dashboard(socket); + } + //pass a socket with a worker thread to a new variable so that the dashboard can get access to a consumer of messages + //make sure those messages are in turn written to the ServerDaemon socket. The Dashboard will make the messages + + } catch (IOException e) { + throw new RuntimeException(e); + } } }); diff --git a/src/main/java/net/ayyalasomayajula/net/client/Dashboard.form b/src/main/java/net/ayyalasomayajula/net/client/Dashboard.form new file mode 100644 index 0000000..79d9075 --- /dev/null +++ b/src/main/java/net/ayyalasomayajula/net/client/Dashboard.form @@ -0,0 +1,53 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/net/ayyalasomayajula/net/client/Dashboard.java b/src/main/java/net/ayyalasomayajula/net/client/Dashboard.java new file mode 100644 index 0000000..425e1f0 --- /dev/null +++ b/src/main/java/net/ayyalasomayajula/net/client/Dashboard.java @@ -0,0 +1,141 @@ +package net.ayyalasomayajula.net.client; + +import net.ayyalasomayajula.net.shared.Message; +import net.ayyalasomayajula.net.shared.MessageVariant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.Socket; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * The Dashboard class represents the user interface for interacting with the client after a successful connection. + * It provides buttons to trigger different actions (e.g., searching for Electronic Health Records (EHRs), finding X-ray files), + * and sends relevant messages to the server for processing. It also listens for incoming responses from the server. + */ +public class Dashboard extends JFrame { + private static final Logger logger = LoggerFactory.getLogger(Dashboard.class); + + private JTextField textField1; + private JPanel main; + private JButton findEHRButton; + private JButton findXrayButton; + private JButton generateEHRButton; + private JTextArea messageDisplayArea; + private Socket connection; + private ObjectOutputStream outputStream; + private ObjectInputStream inputStream; + private List receivedMessages; + + /** + * Constructs a new {@code Dashboard} frame, initializing the GUI components and setting up + * the button actions to send requests to the server. It also starts a background thread to listen + * for incoming messages from the server. + * + * @param connection The active socket connection to the server. + */ + public Dashboard(Socket connection) { + this.connection = connection; + this.receivedMessages = new CopyOnWriteArrayList<>(); // Store messages + + try { + this.outputStream = new ObjectOutputStream(connection.getOutputStream()); + this.inputStream = new ObjectInputStream(connection.getInputStream()); + } catch (IOException e) { + logger.error("Failed to create output/input streams: {}", e.getMessage()); + } + + // Setup GUI components (buttons, text fields, etc.) + setTitle("Client Dashboard"); + setContentPane(main); + setSize(800, 600); + setVisible(true); + + // Action listener for "Find EHR" button + findEHRButton.addActionListener(e -> sendMessage("EHR", textField1.getText())); + + // Action listener for "Find X-ray" button + findXrayButton.addActionListener(e -> sendMessage("XRAY", textField1.getText())); + + // Action listener for "Generate EHR" button + generateEHRButton.addActionListener(e -> generateEHR()); + + // Start the thread to listen for incoming messages + new Thread(this::listenForMessages).start(); + } + + /** + * Sends a message to the server with the specified asset type and asset ID. + * The message is wrapped in a {@link Message} object and sent to the server via the output stream. + * + * @param assetType The type of asset (either "EHR" or "XRAY"). + * @param assetId The ID of the asset, typically the UUID or path. + */ + private void sendMessage(String assetType, String assetId) { + try { + String query = assetType + " " + assetId; + logger.info("Query: {}", query); + Message message = new Message(MessageVariant.GET, query, 5, new byte[]{}); + + // Ensure the message is written to the output stream + outputStream.writeObject(message); + outputStream.flush(); // Flush to ensure the message is sent + logger.info("Sent {} request for asset ID: {}", assetType, assetId); + + // Wait for a response from the server + Message response = (Message) inputStream.readObject(); + updateMessageDisplay(response); // Update the display with the server's response + } catch (IOException | ClassNotFoundException e) { + logger.error("Failed to send message: {}", e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Sends a request to the server to generate an EHR. This simulates the generation of a new EHR. + */ + private void generateEHR() { + try { + String query = "EHR generate"; + Message message = new Message(MessageVariant.GET, query, 5, new byte[]{}); + outputStream.writeObject(message); + logger.info("Sent request to generate new EHR."); + } catch (IOException e) { + logger.error("Failed to send EHR generation request: {}", e.getMessage()); + } + } + + /** + * Listens for incoming messages from the server and stores them in the list of received messages. + * It also updates the message display area in the GUI to show new messages. + */ + private void listenForMessages() { + try { + while (true) { + // Read the next message from the server + Message receivedMessage = (Message) inputStream.readObject(); + receivedMessages.add(receivedMessage); + updateMessageDisplay(receivedMessage); // Update the GUI with the new message + } + } catch (IOException | ClassNotFoundException e) { + logger.error("Error while listening for messages: {}", e.getMessage()); + } + } + + /** + * Updates the message display area in the GUI with the received message. + * This method is called whenever a new message is received from the server. + * + * @param receivedMessage The message received from the server. + */ + private void updateMessageDisplay(Message receivedMessage) { + String displayText = "Received: " + receivedMessage.messageQuery() + "\n"; + messageDisplayArea.append(displayText); // Append the message to the display area + messageDisplayArea.setCaretPosition(messageDisplayArea.getDocument().getLength()); // Auto-scroll to the bottom + } +} diff --git a/src/main/java/net/ayyalasomayajula/net/server/EHRUtils.java b/src/main/java/net/ayyalasomayajula/net/server/EHRUtils.java new file mode 100644 index 0000000..f28f95c --- /dev/null +++ b/src/main/java/net/ayyalasomayajula/net/server/EHRUtils.java @@ -0,0 +1,105 @@ +package net.ayyalasomayajula.net.server; + +import net.ayyalasomayajula.net.shared.EHR; + +import java.io.*; +import java.nio.file.*; +import java.util.UUID; +import java.util.Comparator; +import java.util.List; + +/** + * Utility class for handling EHR operations: serialization, deserialization, and searching. + */ +public class EHRUtils { + + /** + * Constructs the file path for a given EHR based on its UUID. + * + * @param dirPath The base directory for storing EHRs. + * @param ehrUUID The UUID of the EHR. + * @return The full path to the EHR file. + */ + public static Path getEHRFilePath(Path dirPath, UUID ehrUUID) { + return dirPath.resolve(ehrUUID.toString() + ".ehr"); + } + + /** + * Writes an EHR object to a file. + * + * @param ehr The EHR object to write. + * @param dirPath The directory where the file will be stored. + * @throws IOException If an I/O error occurs. + */ + public static void writeEHRToFile(EHR ehr, Path dirPath) throws IOException { + if (!Files.exists(dirPath)) { + Files.createDirectories(dirPath); + } + + Path filePath = getEHRFilePath(dirPath, ehr.getUuid()); + + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath.toFile()))) { + oos.writeObject(ehr); + } + } + + /** + * Reads and deserializes an EHR object from a file. + * + * @param dirPath The directory containing the EHR files. + * @param ehrUUID The UUID of the EHR to retrieve. + * @return The deserialized EHR object, or null if not found. + */ + public static EHR readEHRFromFile(Path dirPath, UUID ehrUUID) { + Path filePath = getEHRFilePath(dirPath, ehrUUID); + if (!Files.exists(filePath)) return null; + + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath.toFile()))) { + return (EHR) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + return null; + } + } + + /** + * Searches for the closest matching EHR based on an asset ID (UUID or patient name). + * + * @param assetId The UUID string or patient name to search for. + * @param dirPath The directory containing serialized EHR files. + * @return The closest matching EHR, or null if none are found. + */ + public static EHR searchClosestEHR(String assetId, Path dirPath) { + File[] ehrFiles = dirPath.toFile().listFiles((dir, name) -> name.endsWith(".ehr")); + if (ehrFiles == null || ehrFiles.length == 0) return null; + + return List.of(ehrFiles).stream() + .map(file -> readEHRFromFile(dirPath, UUID.fromString(file.getName().replace(".ehr", "")))) + .filter(ehr -> ehr != null) + .min(Comparator.comparingInt(ehr -> Math.min( + levenshteinDistance(ehr.getUuid().toString(), assetId), + levenshteinDistance(ehr.getPatientName(), assetId) + ))) + .orElse(null); + } + + /** + * Computes the Levenshtein distance between two strings. + * + * @param s1 First string. + * @param s2 Second string. + * @return The Levenshtein distance between s1 and s2. + */ + private static int levenshteinDistance(String s1, String s2) { + int[][] dp = new int[s1.length() + 1][s2.length() + 1]; + + for (int i = 0; i <= s1.length(); i++) + for (int j = 0; j <= s2.length(); j++) + if (i == 0) dp[i][j] = j; + else if (j == 0) dp[i][j] = i; + else dp[i][j] = Math.min(dp[i - 1][j - 1] + (s1.charAt(i - 1) == s2.charAt(j - 1) ? 0 : 1), + Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)); + + return dp[s1.length()][s2.length()]; + } +} + diff --git a/src/main/java/net/ayyalasomayajula/net/server/ServerDaemon.java b/src/main/java/net/ayyalasomayajula/net/server/ServerDaemon.java index 18a1120..d757e05 100644 --- a/src/main/java/net/ayyalasomayajula/net/server/ServerDaemon.java +++ b/src/main/java/net/ayyalasomayajula/net/server/ServerDaemon.java @@ -1,5 +1,9 @@ package net.ayyalasomayajula.net.server; +import net.ayyalasomayajula.net.shared.EHR; +import net.ayyalasomayajula.net.shared.Message; +import net.ayyalasomayajula.net.shared.MessageVariant; +import net.ayyalasomayajula.net.shared.SerializationUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,6 +16,8 @@ import java.util.Scanner; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * The ServerDaemon class implements a server daemon that listens for incoming client connections. @@ -19,8 +25,9 @@ import java.util.concurrent.TimeUnit; * and handles incoming socket connections using a thread pool for concurrency. */ public class ServerDaemon implements Runnable { + private static final String patientEhrs = "patients/"; private static final Logger logger = LoggerFactory.getLogger(ServerDaemon.class); - + private String basePath = ""; /** * The entry point of the server daemon that sets up the server, initializes root paths, * and listens for client connections. @@ -40,7 +47,7 @@ public class ServerDaemon implements Runnable { boolean success = initRootPath(path); if (!success) throw new RuntimeException("Your Path is dysfunctional"); } - + basePath=path.toString(); try { ServerSocket serverSocket = new ServerSocket(8475); logger.info("Server to handle connections has been made at: {} on 0.0.0.0", serverSocket.getLocalPort()); @@ -55,7 +62,7 @@ public class ServerDaemon implements Runnable { public void run() { try { handleConnections(socketToPass); - } catch (IOException e) { + } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } } @@ -109,7 +116,7 @@ public class ServerDaemon implements Runnable { } // Create the patient directory - String patientEhrs = "patients/"; + Path patientPath = path.resolve(patientEhrs); File dir = patientPath.toFile(); @@ -147,10 +154,76 @@ public class ServerDaemon implements Runnable { * @param socket The socket for the incoming connection. * @throws IOException If an I/O error occurs while setting up the streams. */ - private void handleConnections(Socket socket) throws IOException { + private void handleConnections(Socket socket) throws IOException, ClassNotFoundException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); + objectOutputStream.flush(); // Important to flush the stream to avoid issues on the first write ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); - // Further processing can be added here + logger.info("Got a handle running on the socket. Will await new message objects."); + + Object readBuffer = objectInputStream.readObject(); + + while (socket.isConnected()) { + if (readBuffer instanceof Message intercepted) { + // Handle the received Message + Message toPush = handleMessage(intercepted); + objectOutputStream.writeObject(toPush); + objectOutputStream.flush(); // Ensure the response is sent + } + // Keep reading from the input stream + readBuffer = objectInputStream.readObject(); + } } + + private Message handleMessage(Message in) throws IOException { + switch (in.messageVariant()) { + case GET: { + // Format of GET query: + // | asset types: EHR, XRAY | asset id: uuid field or the path + + // Define regex pattern with capture groups + Pattern pattern = Pattern.compile("^(EHR|XRAY) (.+)$"); + + Matcher matcher = pattern.matcher(in.messageQuery()); + if (matcher.matches()) { + String assetType = matcher.group(1); // Captures EHR or XRAY + String assetId = matcher.group(2); // Captures UUID or path + logger.debug("Asset Type: {}", assetType); + logger.debug("Asset ID: {}", assetId); + + switch (assetType) { + case "EHR": + logger.info("EHR caught"); + EHR found = EHRUtils.searchClosestEHR(assetId, Path.of(basePath)); + return new Message(MessageVariant.SET, "", 5, SerializationUtils.toBytes(found)); + case "XRAY": + logger.info("XRAY caught"); + // Construct full file path for XRAY asset + Path xrayFilePath = Path.of(basePath, assetId); + File xrayFile = xrayFilePath.toFile(); + + // Check if the file exists and is a valid file + if (xrayFile.exists() && xrayFile.isFile()) { + // Read the file into a byte array and return it + try (FileInputStream fis = new FileInputStream(xrayFile)) { + byte[] fileBytes = fis.readAllBytes(); + return new Message(MessageVariant.SET, "", 5, fileBytes); + } catch (IOException e) { + logger.error("Error reading XRAY file: {}", e.getMessage()); + return new Message(MessageVariant.SET, "ERROR: Failed to read XRAY file.", 5, new byte[]{}); + } + } else { + logger.error("XRAY file not found or invalid path: {}", xrayFilePath); + return new Message(MessageVariant.SET, "ERROR: XRAY file not found.", 5, new byte[]{}); + } + } + } else { + logger.debug("No match found for: {}", in.messageQuery()); + } + break; + } + } + return new Message(MessageVariant.SET, "THROW;", 5, new byte[]{}); + } + } diff --git a/src/main/java/net/ayyalasomayajula/net/shared/EHR.java b/src/main/java/net/ayyalasomayajula/net/shared/EHR.java index 3c766be..6de2e5d 100644 --- a/src/main/java/net/ayyalasomayajula/net/shared/EHR.java +++ b/src/main/java/net/ayyalasomayajula/net/shared/EHR.java @@ -1,3 +1,5 @@ +package net.ayyalasomayajula.net.shared; + import net.ayyalasomayajula.net.shared.Appointment; import net.ayyalasomayajula.net.shared.Message; diff --git a/src/main/java/net/ayyalasomayajula/net/shared/SerializationUtils.java b/src/main/java/net/ayyalasomayajula/net/shared/SerializationUtils.java new file mode 100644 index 0000000..a1f6789 --- /dev/null +++ b/src/main/java/net/ayyalasomayajula/net/shared/SerializationUtils.java @@ -0,0 +1,41 @@ +package net.ayyalasomayajula.net.shared; + +import java.io.*; + +/** + * Utility class for serializing and deserializing objects to and from byte arrays. + * This class provides methods for converting an object into a byte array and vice versa. + */ +public class SerializationUtils { + + /** + * Serializes an object to a byte array. + * + * @param obj the object to serialize + * @return a byte array representing the serialized object + * @throws IOException if an I/O error occurs during serialization + */ + public static byte[] toBytes(Object obj) throws IOException { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeObject(obj); + oos.flush(); + return bos.toByteArray(); + } + } + + /** + * Deserializes a byte array to an object. + * + * @param bytes the byte array to deserialize + * @return the deserialized object + * @throws IOException if an I/O error occurs during deserialization + * @throws ClassNotFoundException if the class of a serialized object cannot be found + */ + public static Object fromBytes(byte[] bytes) throws IOException, ClassNotFoundException { + try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bis)) { + return ois.readObject(); + } + } +} diff --git a/target/classes/EHR.class b/target/classes/EHR.class deleted file mode 100644 index f94808440f82004173b62c50694df7a6498b81af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2916 zcmX^0Z`VEs1_m33a4rTW24;2!79Ivx1~x_pfvm)`ME#t^ymWp4q^#8B5=I6#o6Nk- z5<5l)W)00SP6iGJPId+^9tLg(9!3UcSC1e@2JV8ylFZb+62HXUR7M6NAA}jfB}JKe z>DC}!d^`;N3<8V{ETyHHDU1vP5LKlmnK}BQp`I?*>Fa#Qn4eDc#lE|ue9kY`X}WMEFoPhw<{gxFY;nVYKblb@WJ z3NAIrA4WX3@Q+(fP)95*9O7|Yk`Ke zoi!taJwzBuSzc<1eqv>1VoqXler{rAVpeHRqCQBlI3uwrHAUYM5eC*E6VxzF06RJ_ zGhaU~GbdF)Ah9F^tgs!~)lerO=>Pq|~C2#H1W>as)+MSYlBohz}KHDaptzW@KRXarFqYW@HcpM=V@TWkD)b z-2>A7hc!|lUV*#V#s?Jm#K#CkJI;{6DS>1m)(8Xz9AA2BNdR*G717Xy*oU017#aAA z(bS=&SZLgX3}jDFEeQqZaUqyVV0VBM10w@_F-%$*8n_5qkaqs`)DmBm{EKP~I1Mo} zsG*n*62uztj0}8QQWF*4|qq@R(2Ej_iw1ypoN!@PqffiN;~r>B;9K}#)029EU9k_b?7#mJxm z(*}+hq@cyj|Lln=DIi^p4C2tpL{$Uw3=60fWMmM5Bq~&SMh33n{L-T2RJTk}UgdE0 z2+{+kR0c%`4hAL$1_pKpCQwboz`(!+sy@K91cM|4BZDrOt;fIwD#zh`eFh;=;myFn zAOtFy!D`fy)EhAHGB7f*G8i&2F&Hs0FnniVVc=k3U~tsh&cLXpy_JDoOJ^$suhwn` z!ARZh48mI(#K3Gx7BELjOOkafgRGV$+g1iehy*)WLS-WZ0|PUIF#`jG8UrT-1A{sP z3xfs&Cxa#fAA=Ty6oWQ{I)e^_E`u(EF@qk11A{(;2?GN|5X5o@QwB2z5O4&0mJ#G2 zuy;X0;0UVJ7?{CgG7ORoEMS&Ag8~C9n5E31z`zD(fjr92V9vn6pa4}b!Jr3a$uQ_c zb*eEKFt9^|&w_!G!IHs>fe{=S`V5Q=AiG($wlJ`70EZpOLu_C{bFhPXz-(5igBcmD z8El}M?HHKAF~$ZCMNn`sfrEn!Eb73(%HRk#K@7}igPOp=zy|d)BZDo29n>sjhw!31 z#1m``xm=57*5CDgOBZCt(c_2GP4Ba8oU}I1n0!fw5 z42%pe@Q`tVh76C^76vixoeb(s4B_a`N@3t;NCle*aTX6ee0UgK89;V0GPq%NmK3_P za>2%cv@kHhA`0RxcLqiV4+c+YI3YVr3Wu}G7`PeA!RBE)%ZtGq99ljMzEBg9LrWIj zS@mFJP@Tod;D=SSBD&@dteX9?28ar}=80G|Ljoj#fsrAQAqeVxgT1F=m+N~XQ!5MGVn3T@i53UC@?Z` zWaJl@KP0e3>g_XGV@BpKqfFUFl%Ufaxq9V81pc2GngwEgURwSnu zfZWT@;Ks-x1(pCuL{Mp7NoH;;T%L=;mB9mKvL_>h1k_}(!jOz2p8WE}q?}Z+@!p$D z8QB@l*&qSJ$RG%gU8Hnn&B(x*%E%xJ5l3}|_2eoheLmjclEma}-^79tkg1cGGlkcC zF)#`UFt9SPF)%PNF)%Q2Gx#ttG59htFxW9Lfkn8qw6`)aYHep=-w0OY$H2hA#=ywH zz@W+C&%nSS22sTjz!1m)0$gCz7#V^X7#LU?85kM_7#JCX8A2Es!NxvkU}a!oU|>kz z#K0>gT)&e+l!;+CgLI_U76wKT-ohZejX@E_0^uzTs@oV8cQ9ydW6;~qVB`zZWD3>; zlHJB&uCtB7Y9<5EZU(zZo$U+`e#;qneRngsf;jH`H!?6Xa5023L@+orFf)K0uEoH` zz`&rxz{Q}?V8UR)P;bXz$l%Ig#NfkV%n-_8#Sp<@!4Sz{$&kcg1NNO30}F!*gCavH zLl^@SLnH$yLpVbO*vH9mA15 delta 242 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 literal 0 HcmV?d00001 diff --git a/target/classes/net/ayyalasomayajula/net/server/ServerDaemon$1.class b/target/classes/net/ayyalasomayajula/net/server/ServerDaemon$1.class index e5546d62f0d63074f123716ef0556e592abdac69..066e26f0a777c680626e5892a2484ebe641e04e1 100644 GIT binary patch delta 426 zcmX@e(ZR`e>ff$?3=9l53}zd-Vi{EwvJ%S@^>Y&Q()FEl5{rxd@=M(EOY>4(E0R+S zN;32F*coIdr!vZONHfUsFz_?TPwruqWmKHJit&pDSQlKOH6sIWa7kivwr^rVNMce> zDm#NdBZDx)e7MTVdznlbwI+XNQk7R=U=k2u;AG%pU=R>sU}a!qU|?WkU|`^9P-0+a zP@ZhgtWdAQz`(%9zzBAd1}6py1{VgHllU1p8MGO6zz&sUfH+Qff$?3=9l53`QHdVi_m*GRmq+GRW{S@H5CVGDv9nWF?j*>gOcprR#@e z6y=vECgr4BYlg8i$WOk&_(eW5U*FT;wIVsSpd>Rt&zg~eH@GA*Iomg}AOxgx@*gHs zMvciv%&PKo3`_z744e#H3=9GS46F=n3=9lR3=9nX3m=)@k7#JAX7#JBC z7)%+I85kJEpej@tR2e{kAFPj&L5+cdfs=`Wp+Nv_m^#=f0R~0}4F(XY$iTo*#J~*J zEUC4Hfl+G<1N$}x!Q~9RKD!wtBel0PNblbWwuhU6k3pP)nE_<8ST+MI0|SF412=;W z10RDG13!Z`g9L*u+$sDFoD7-_T40Tm3=l_YGcYi)GcftFGl+9CurTO=oubR2$H2hA b!XU(;&tL!+5n?c8U|`^4U<5mgfq?-4X(uwa diff --git a/target/classes/net/ayyalasomayajula/net/server/ServerDaemon.class b/target/classes/net/ayyalasomayajula/net/server/ServerDaemon.class index cb3931a06880d1aafdcc782f05f53daa048104f0..2d98ce3dd392a53c7bf8f451fc0be120f6dc2d2b 100644 GIT binary patch literal 9243 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 A3IG5A