From b7882444f671908e4d09626c6357922d52f66c79 Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Sat, 20 Jun 2020 11:50:57 -0500 Subject: [PATCH] Threw in TACNET and config stuff, added gson dependency --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 5 + .../backend/Backend.java | 111 ++++++++ .../backend/Config.java | 163 ++++++++++++ .../backend/Settings.java | 12 + .../backend/TAC.java | 13 + .../backend/TACNET.java | 86 +++++++ .../tacnet/Client.java | 240 ++++++++++++++++++ .../tacnet/Connection.java | 126 +++++++++ .../tacnet/Packet.java | 113 +++++++++ .../tacnet/PacketHandler.java | 105 ++++++++ .../tacnet/Server.java | 198 +++++++++++++++ .../main/res/drawable/navigation_button.xml | 8 +- app/src/main/res/layout/fragment_search.xml | 13 +- 14 files changed, 1185 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Backend.java create mode 100644 app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Config.java create mode 100644 app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Settings.java create mode 100644 app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/TAC.java create mode 100644 app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/TACNET.java create mode 100755 app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Client.java create mode 100755 app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Connection.java create mode 100755 app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Packet.java create mode 100755 app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/PacketHandler.java create mode 100755 app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Server.java diff --git a/app/build.gradle b/app/build.gradle index ff39bd5..49f9537 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,6 +24,7 @@ android { dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation 'com.google.code.gson:gson:2.8.6' implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'com.google.android.material:material:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8607f4d..ba8499e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,11 @@ + + + + + groups; + private ArrayList presets; + + public Config(String name) { + // Do things + parse(name); + } + + public Config(Configuration configuration, ArrayList groups, ArrayList presets) { + this.configuration = configuration; + this.groups = groups; + this.presets = presets; + } + + public Configuration getConfiguration() { + return configuration; + } + + public ArrayList getPresets() { + return presets; + } + + public ArrayList getGroups() { + return groups; + } + + private void parse(String name) { + Gson gson = new Gson(); + gson.fromJson("", Config.class); + } + + public class Configuration { + public Date createdAt, updatedAt; + private int specVersion; + public int revision; + + public Configuration(Date createdAt, Date updatedAt, int specVersion, int revision) { + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.specVersion = specVersion; + this.revision = revision; + } + + public int getSpecVersion() { return specVersion; } + } + + public class Preset { + private ArrayList groups; + private ArrayList actions; + + public Preset(ArrayList groups, ArrayList actions) { + this.groups = groups; + this.actions = actions; + } + } + + public class Group { + public String name; + private ArrayList actions; + + public Group(String name, ArrayList actions) { + this.name = name; + this.actions = actions; + } + + public ArrayList getActions() { + return actions; + } + } + + public class Action { + public String name, comment; + public boolean enabled; + private ArrayList variables; + + public Action(String name, String comment, boolean enabled, ArrayList variables) { + this.name = name; + this.comment = comment; + this.enabled = enabled; + this.variables = variables; + } + } + + public class Variable { + public String name; + private String value; + + public Variable(String name, String value) { + this.name = name; + this.value = value; + } + + public T value() { + return valueOf(); + } + + @SuppressWarnings("unchecked") + public T valueOf() { + String[] split = value.split("x"); + + switch (split[0]) { + case "B": { + return (T) Boolean.valueOf(split[(split.length-1)]); + } + case "D": { + return (T) Double.valueOf(split[(split.length-1)]); + } + case "F": { + return (T) Float.valueOf(split[(split.length-1)]); + } + case "I": { + return (T) Integer.valueOf(split[(split.length-1)]); + } + case "L": { + return (T) Long.valueOf(split[(split.length-1)]); + } + case "S": { + return (T) String.valueOf(split[(split.length-1)]); + } + default: { + return null; + } + } + } + + public String typeOf(String value) { + String[] split = value.split("x"); + + switch (split[0]) { + case "B": { + return "Boolean"; + } + case "D": { + return "Double"; + } + case "F": { + return "Float"; + } + case "I": { + return "Integer"; + } + case "L": { + return "Long"; + } + case "S": { + return "String"; + } + default: { + return "=!UNKNOWN!="; + } + } + } + } +} diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Settings.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Settings.java new file mode 100644 index 0000000..b093e73 --- /dev/null +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Settings.java @@ -0,0 +1,12 @@ +package org.timecrafters.TimeCraftersConfigurationTool.backend; + +public class Settings { + public String hostname, config; + public int port; + + public Settings(String hostname, int port, String config) { + this.hostname = hostname; + this.port = port; + this.config = config; + } +} diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/TAC.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/TAC.java new file mode 100644 index 0000000..c338ba8 --- /dev/null +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/TAC.java @@ -0,0 +1,13 @@ +package org.timecrafters.TimeCraftersConfigurationTool.backend; + +import android.os.Environment; + +import java.io.File; + +public class TAC { + public static final String ROOT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "TimeCrafters_Configuration_Tool", + CONFIGS_PATH = ROOT_PATH + File.separator + "/configs", + SETTINGS_PATH = ROOT_PATH + File.separator + "settings.json"; + + public static final int CONFIG_SPEC_VERSION = 2; +} diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/TACNET.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/TACNET.java new file mode 100644 index 0000000..b01323b --- /dev/null +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/TACNET.java @@ -0,0 +1,86 @@ +package org.timecrafters.TimeCraftersConfigurationTool.backend; + +import android.os.SystemClock; + +import org.timecrafters.TimeCraftersConfigurationTool.tacnet.Client; +import org.timecrafters.TimeCraftersConfigurationTool.tacnet.Connection; + +import java.io.IOException; + +public class TACNET { + public static final String DEFAULT_HOSTNAME = "192.168.49.1"; + public static final int DEFAULT_PORT = 8962; + + public static final int SYNC_INTERVAL = 250; // ms + public static final int HEARTBEAT_INTERVAL = 1_500; // ms + + public enum Status { + CONNECTED, + CONNECTING, + CONNECTION_ERROR, + NOT_CONNECTED, + } + + private Connection connection; + + public void connect(String hostname, int port) { + if (connection != null && connection.isConnected()) { + return; + } + + connection = new Connection(hostname, port); + connection.connect(null); + } + + public Status status() { + if (isConnected()) { + return Status.CONNECTED; + } else if (connection != null && !connection.socketError()) { + return Status.CONNECTING; + } else if (connection != null && connection.socketError()) { + return Status.CONNECTION_ERROR; + } else { + return Status.NOT_CONNECTED; + } + } + + public boolean isConnected() { + return connection != null && connection.isConnected(); + } + + public void close() { + if (connection != null) { + try { + connection.close(); + } catch (IOException e) {} + + connection = null; + } + } + + public Client getClient() { + if (isConnected()) { + return connection.getClient(); + } else { + return null; + } + } + + public void puts(String message) { + if (isConnected()) { + connection.puts(message); + } + } + + public String gets() { + if (isConnected()) { + return connection.gets(); + } else { + return null; + } + } + + public static long milliseconds() { + return SystemClock.elapsedRealtime(); + } +} diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Client.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Client.java new file mode 100755 index 0000000..bc8e538 --- /dev/null +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Client.java @@ -0,0 +1,240 @@ +package org.timecrafters.TimeCraftersConfigurationTool.tacnet; + +import android.util.Log; + +import org.timecrafters.TimeCraftersConfigurationTool.backend.Backend; +import org.timecrafters.TimeCraftersConfigurationTool.backend.TACNET; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class Client { + private Socket socket; + private BufferedReader bufferedReader; + private BufferedWriter bufferedWriter; + private String uuid; + + private ArrayList readQueue; + private ArrayList writeQueue; + + final private Object readQueueLock = new Object(); + final private Object writeQueueLock = new Object(); + + private long syncInterval = TACNET.SYNC_INTERVAL; + + private int packetsSent, packetsReceived = 0; + private long dataSent, dataReceived = 0; + + private String TAG = "TACNET|Client"; + + public Client() { + this.uuid = (UUID.randomUUID()).toString(); + + this.readQueue = new ArrayList<>(); + this.writeQueue = new ArrayList<>(); + } + + public void setSyncInterval(long milliseconds) { + syncInterval = milliseconds; + } + + public void setSocket(Socket socket) throws IOException { + this.socket = socket; + + // This socket is for a "Connection" thus set a connect timeout + if (!this.socket.isBound()) { + this.socket.connect(new InetSocketAddress(Backend.instance().getSettings().hostname, Backend.instance().getSettings().port), 1500); + } + + this.bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + this.bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + + startReader(); + startWriter(); + } + + public ArrayList readQueue() { + return readQueue; + } + + public ArrayList writeQueue() { + return writeQueue; + } + + private void startReader() { + new Thread(new Runnable() { + @Override + public void run() { + while(!socket.isClosed()) { + // READER + try { + String message = read(); + if (!message.equals("")) { + Log.i(TAG, "Read: " + message); + + synchronized (readQueueLock) { + readQueue.add(message); + + packetsReceived++; + dataReceived += message.length(); + } + } + + } catch (IOException e) { + Log.e(TAG, "Read error: " + e.getMessage()); + } + + try { + TimeUnit.MILLISECONDS.sleep(syncInterval); + } catch (InterruptedException e) {} + } + } + }).start(); + } + + private void startWriter() { + new Thread(new Runnable() { + @Override + public void run() { + while(!socket.isClosed()) { + // WRITER + String message; + + synchronized (writeQueueLock) { + for (Iterator itr = writeQueue.iterator(); itr.hasNext(); ) { + try { + message = (String) itr.next(); + + write(message); + + packetsSent++; + dataSent += message.length(); + + Log.i(TAG, "Write: " + message); + itr.remove(); + + } catch (IOException e) { + Log.e(TAG, "Write error: " + e.getMessage()); + try { + socket.close(); + } catch (IOException k) { + Log.e(TAG, "Failed to close socket: " + e.getMessage()); + } + } + } + } + + try { + TimeUnit.MILLISECONDS.sleep(syncInterval); + } catch (InterruptedException e) {} + } + } + }).start(); + } + + public void sync(Runnable runner) { + runner.run(); + } + + public void handleReadQueue() { + String message = this.gets(); + + while (message != null) { + Log.i(TAG, "Writing to Queue: " + message); + this.puts(message); + + message = this.gets(); + + } + } + + public String uuid() { + return this.uuid; + } + + public boolean isConnected() { + return this.socket != null && !this.socket.isClosed(); + } + + public boolean isBound() { + return this.socket == null || this.socket.isBound(); + } + + public boolean isClosed() { + return this.socket == null || this.socket.isClosed(); + } + + public void write(String message) throws IOException { + bufferedWriter.write(message + "\r\n\n"); + bufferedWriter.flush(); + } + + public String read() throws IOException { + String message = ""; + String readLine; + + while((readLine = bufferedReader.readLine()) != null) { + message+=readLine; + if (readLine.isEmpty()) { break; } + } + + return message; + } + + public void puts(String message) { + synchronized (writeQueueLock) { + writeQueue.add(message); + } + } + + public String gets() { + String message = null; + + synchronized (readQueueLock) { + if (readQueue.size() > 0) { + message = readQueue.get(0); + + readQueue.remove(0); + } + } + + return message; + } + + public String encode(String message) { + return message; + } + + public String decode(String blob) { + return blob; + } + + public int getPacketsSent() { return packetsSent; } + public int getPacketsReceived() { return packetsReceived; } + public long getDataSent() { return dataSent; } + public long getDataReceived() { return dataReceived; } + + public void flush() throws IOException { + this.bufferedWriter.flush(); + } + + public void close(String reason) throws IOException { + write(reason); + this.socket.close(); + } + + public void close() throws IOException { + if (this.socket != null) { + this.socket.close(); + } + } +} diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Connection.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Connection.java new file mode 100755 index 0000000..256bce3 --- /dev/null +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Connection.java @@ -0,0 +1,126 @@ +package org.timecrafters.TimeCraftersConfigurationTool.tacnet; + +import android.util.Log; + +import org.timecrafters.TimeCraftersConfigurationTool.backend.TACNET; + +import java.io.IOException; +import java.net.Socket; + +public class Connection { + private PacketHandler packetHandler; + private Client client; + private String hostname; + private int port; + private String lastSocketError = null; + private boolean socketError = false; + + private long lastSyncTime = 0; + private long syncInterval = TACNET.SYNC_INTERVAL; + + private Runnable connectionHandlingRunner; + private long lastHeartBeatSent = 0; + private long heartBeatInterval = TACNET.HEARTBEAT_INTERVAL; + + private String TAG = "TACNET|Connection"; + + public Connection(String hostname, int port) { + this.hostname = hostname; + this.port = port; + this.packetHandler = new PacketHandler(true); + + this.connectionHandlingRunner = new Runnable() { + @Override + public void run() { + handleConnection(); + } + }; + } + + public void connect(final Runnable callback) { + if (client != null) { + return; + } + + client = new Client(); + + new Thread(new Runnable() { + @Override + public void run() { + try { + client.setSocket(new Socket()); + Log.i(TAG, "Connected to: " + hostname + ":" + port); + + while(client != null && !client.isClosed()) { + if (System.currentTimeMillis() > lastSyncTime + syncInterval) { + lastSyncTime = System.currentTimeMillis(); + + client.sync(connectionHandlingRunner); + } + } + } catch (IOException e) { + socketError = true; + lastSocketError = e.getMessage(); + + callback.run(); + + Log.e(TAG, e.toString()); + } + } + }).start(); + } + + private void handleConnection() { + if (client != null && !client.isClosed()) { + String message = client.gets(); + + if (message != null) { + packetHandler.handle(message); + } + + if (System.currentTimeMillis() > lastHeartBeatSent + heartBeatInterval) { + lastHeartBeatSent = System.currentTimeMillis(); + + client.puts(PacketHandler.packetHeartBeat().toString()); + } + + try { + Thread.sleep(syncInterval); + } catch (InterruptedException e) { + // Failed to sleep I suppose. + } + + } else { + client = null; + } + } + + public void puts(String message) { + this.client.puts(message); + } + + public String gets() { + return this.client.gets(); + } + + public Client getClient() { + return client; + } + + public boolean isClosed() { + return this.client == null || this.client.isClosed(); + } + public boolean isConnected() { + return this.client != null && this.client.isConnected(); + } + public boolean socketError() { + return socketError; + } + public String lastError() { + return lastSocketError; + } + + public void close() throws IOException { + this.client.close(); + } +} diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Packet.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Packet.java new file mode 100755 index 0000000..413d021 --- /dev/null +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Packet.java @@ -0,0 +1,113 @@ +package org.timecrafters.TimeCraftersConfigurationTool.tacnet; + +import android.util.Log; + +import java.util.Arrays; + +public class Packet { + final static public String PROTOCOL_VERSION = "0"; + final static public String PROTOCOL_HEADER_SEPERATOR = "|"; + final static public String PROTOCOL_HEARTBEAT = "heartbeat"; + private static final String TAG = "TACNET|Packet"; + + + // NOTE: PacketType is cast to a char, no more than 255 packet types can exist unless + // header is updated. + public enum PacketType { + HANDSHAKE, + HEARTBEAT, + DUMP_CONFIG, + CHANGE_ACTION, + CHANGE_VARIABLE, + } + + private String protocolVersion; + private PacketType packetType; + private int contentLength; + private String content; + + String rawMessage; + + Packet(String protocolVersion, PacketType packetType, int contentLength, String content) { + this.protocolVersion = protocolVersion; + this.packetType = packetType; + this.contentLength = contentLength; + this.content = content; + } + + static public Packet fromStream(String message) { + String version; + PacketType type; + int length; + String body; + + String[] slice = message.split("\\|", 4); + + if (slice.length < 4) { + Log.i(TAG, "Failed to split packet along first 4 " + PROTOCOL_HEADER_SEPERATOR + ". Raw return: " + Arrays.toString(slice)); + return null; + } + + if (!slice[0].equals(PROTOCOL_VERSION)) { + Log.i(TAG, "Incompatible protocol version received, expected: " + PROTOCOL_VERSION + " got: " + slice[0]); + return null; + } + + version = slice[0]; + type = PacketType.values()[Integer.parseInt(slice[1])]; + length = Integer.parseInt(slice[2]); + body = slice[slice.length - 1]; + + return new Packet(version, type, length, body); + } + + static public Packet create(PacketType packetType, String message) { + return new Packet(PROTOCOL_VERSION, packetType, message.length(), message); + } + + public boolean isValid() { + if (rawMessage == null) { + return true; + } + + String[] parts = rawMessage.split(PROTOCOL_HEADER_SEPERATOR); + + return parts[0].equals(PROTOCOL_VERSION) && + isPacketTypeValid( Integer.parseInt(parts[1])); + } + + public boolean isPacketTypeValid(int rawPacketType) { + return PacketType.values().length >= rawPacketType && PacketType.values()[rawPacketType] != null; + } + + public String encodeHeader() { + String string = ""; + string += PROTOCOL_VERSION; + string += PROTOCOL_HEADER_SEPERATOR; + string += packetType.ordinal(); + string += PROTOCOL_HEADER_SEPERATOR; + string += contentLength; + string += PROTOCOL_HEADER_SEPERATOR; + return string; + } + + public String toString() { + return ("" + encodeHeader() + content); + } + + public String getProtocolVersion() { + return protocolVersion; + } + + public PacketType getPacketType() { + return packetType; + } + + public int getContentLength() { + return contentLength; + } + + public String getContent() { + return content; + } +} diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/PacketHandler.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/PacketHandler.java new file mode 100755 index 0000000..bd331f6 --- /dev/null +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/PacketHandler.java @@ -0,0 +1,105 @@ +package org.timecrafters.TimeCraftersConfigurationTool.tacnet; + +import android.util.Log; + +import org.timecrafters.TimeCraftersConfigurationTool.backend.Backend; +import java.lang.reflect.Array; +import java.util.Arrays; + +public class PacketHandler { + private static final String TAG = "TACNET|PacketHandler"; + private boolean hostIsAConnection = false; + + public PacketHandler(boolean isHostAConnection) { + this.hostIsAConnection = isHostAConnection; + } + + public void handle(String message) { + Packet packet = Packet.fromStream(message); + + if (packet != null && packet.isValid()) { + Log.i(TAG, "Received packet of type: " + packet.getPacketType()); + handOff(packet); + } else { + if (packet == null) { + Log.i(TAG, "Rejected raw packet: " + message); + } else { + Log.i(TAG, "Rejected packet: " + packet.toString()); + } + } + } + + public void handOff(Packet packet) { + switch(packet.getPacketType()) { + case HANDSHAKE: { + handleHandShake(packet); + return; + } + + case HEARTBEAT: { + handleHeartBeat(packet); + return; + } + + case DUMP_CONFIG: { + handleDumpConfig(packet); + return; + } + + case CHANGE_ACTION: { + handleChangeAction(packet); + return; + } + + default: { + return; + } + } + } + + // NO-OP + private void handleHandShake(Packet packet) {} + // NO-OP + private void handleHeartBeat(Packet packet) {} + + private void handleDumpConfig(Packet packet) { + if ( + packet.getContent().length() > 4 && packet.getContent().charAt(0) == "[".toCharArray()[0] && + packet.getContent().charAt(packet.getContent().length() - 1) == "]".toCharArray()[0] + ) { /* "unless" keyword anyone? */ } else { return; } + + Log.i(TAG, "Got valid json: " + packet.getContent()); + + if (hostIsAConnection) { + // save and reload menu +// Writer.overwriteConfigFile(packet.getContent()); + +// Backend.instance().loadConfig(); + } else { + // save +// Writer.overwriteConfigFile(packet.getContent()); + } + } + + private void handleChangeAction(Packet packet) { + // TODO: Handle renaming, deleting, and adding. + } + + private void handleChangeVariable(Packet packet) { + // TODO: Handle renaming, deleting, and adding. + } + + static public Packet packetHandShake(String clientUUID) { + return Packet.create(Packet.PacketType.HANDSHAKE, clientUUID); + } + + static public Packet packetHeartBeat() { + return Packet.create(Packet.PacketType.HEARTBEAT, Packet.PROTOCOL_HEARTBEAT); + } + + static public Packet packetDumpConfig(String string) { + string = string.replace("\n", " "); + + return Packet.create(Packet.PacketType.DUMP_CONFIG, string); + } +} diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Server.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Server.java new file mode 100755 index 0000000..66d05f3 --- /dev/null +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Server.java @@ -0,0 +1,198 @@ +package org.timecrafters.TimeCraftersConfigurationTool.tacnet; + +import android.util.Log; + +import org.timecrafters.TimeCraftersConfigurationTool.backend.TACNET; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; + +public class Server { + private ServerSocket server; + private int port; + private Client activeClient; + private long lastSyncTime = 0; + private long syncInterval = TACNET.SYNC_INTERVAL; + + private String TAG = "TACNET|Server"; + + private int packetsSent, packetsReceived, clientLastPacketsSent, clientLastPacketsReceived = 0; + private long dataSent, dataReceived, clientLastDataSent, clientLastDataReceived = 0; + + private Runnable handleClientRunner; + private PacketHandler packetHandler; + + private long lastHeartBeatSent = 0; + private long heartBeatInterval = TACNET.HEARTBEAT_INTERVAL; + + public Server(int port) throws IOException { + this.server = new ServerSocket(); + this.port = port; + this.packetHandler = new PacketHandler(false); + this.handleClientRunner = new Runnable() { + @Override + public void run() { + handleClient(); + } + }; + } + + public void start() throws IOException { + new Thread(new Runnable() { + @Override + public void run() { + int connectionAttempts = 0; + + while(!server.isBound() && connectionAttempts < 10) { + try { + server.bind(new InetSocketAddress(port)); + Log.i(TAG, "Server bound and ready!"); + } catch (IOException e) { + connectionAttempts++; + Log.e(TAG, "Server failed to bind: " + e.getMessage()); + } + } + + while (!server.isClosed()) { + try { + runServer(); + } catch (IOException e) { + Log.e(TAG, "Error running server: " + e.getMessage()); + } + + } + } + }).start(); + } + + private void runServer() throws IOException { + while (!isClosed()) { + + final Client client = new Client(); + client.setSyncInterval(syncInterval); + client.setSocket(this.server.accept()); + + if (activeClient != null && !activeClient.isClosed()) { + Log.i(TAG, "Too many clients, already have one connected!"); + client.close("Too many clients!"); + + } else { +// Writer.writeJSON(Writer.getBackupConfigFilePath(), AppSync.getDataStructs()); + + this.activeClient = client; +// AppSync.getMainActivity().clientConnected(); + + activeClient.puts(PacketHandler.packetHandShake( activeClient.uuid() ).toString()); +// activeClient.puts(PacketHandler.packetDumpConfig( Reader.rawConfigFile() ).toString()); + + Log.i(TAG, "Client connected!"); + + new Thread(new Runnable() { + @Override + public void run() { + while(activeClient != null && !activeClient.isClosed()) { + if (System.currentTimeMillis() > lastSyncTime + syncInterval) { + lastSyncTime = System.currentTimeMillis(); + + activeClient.sync(handleClientRunner); + updateNetStats(); + } + + try { + Thread.sleep(syncInterval); + } catch (InterruptedException e) { + // Failed to sleep, i guess. + } + } + + updateNetStats(); + activeClient = null; + + clientLastPacketsSent = 0; + clientLastPacketsReceived = 0; + clientLastDataSent = 0; + clientLastDataReceived = 0; + +// AppSync.getMainActivity().clientDisconnected(); + } + }).start(); + + } + } + } + + private void handleClient() { + if (activeClient != null && !activeClient.isClosed()) { + String message = activeClient.gets(); + + if (message != null) { + packetHandler.handle(message); + } + + if (System.currentTimeMillis() > lastHeartBeatSent + heartBeatInterval) { + lastHeartBeatSent = System.currentTimeMillis(); + + activeClient.puts(PacketHandler.packetHeartBeat().toString()); + } + } + } + + public void stop() throws IOException { + if (this.activeClient != null) { + this.activeClient.close(); + this.activeClient = null; + } + + this.server.close(); + } + + public boolean hasActiveClient() { + return activeClient != null; + } + + public Client getActiveClient() { + return activeClient; + } + + public int getPacketsSent() { + return packetsSent; + } + + public int getPacketsReceived() { + return packetsReceived; + } + + public long getDataSent() { + return dataSent; + } + + public long getDataReceived() { + return dataReceived; + } + + private void updateNetStats() { + if (activeClient != null) { + // NOTE: In and Out are reversed for Server stats + + packetsSent += activeClient.getPacketsReceived() - clientLastPacketsReceived; + packetsReceived += activeClient.getPacketsSent() - clientLastPacketsSent; + + dataSent += activeClient.getDataReceived() - clientLastDataReceived; + dataReceived += activeClient.getDataSent() - clientLastDataSent; + + clientLastPacketsSent = activeClient.getPacketsSent(); + clientLastPacketsReceived = activeClient.getPacketsReceived(); + clientLastDataSent = activeClient.getDataSent(); + clientLastDataReceived = activeClient.getDataReceived(); + } + } + + public boolean isBound() { + return this.server.isBound(); + } + + public boolean isClosed() { + return this.server.isClosed(); + } +} diff --git a/app/src/main/res/drawable/navigation_button.xml b/app/src/main/res/drawable/navigation_button.xml index 7fc3cdd..148a996 100644 --- a/app/src/main/res/drawable/navigation_button.xml +++ b/app/src/main/res/drawable/navigation_button.xml @@ -1,22 +1,22 @@ - + - + - + - + diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index ea9791f..412ee77 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -31,11 +31,14 @@ android:src="@drawable/search" /> - + android:layout_height="match_parent"> - + + \ No newline at end of file