diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Backend.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Backend.java index 100ba58..7de3ed1 100644 --- a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Backend.java +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Backend.java @@ -11,7 +11,9 @@ import android.util.Log; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; +import org.json.JSONException; import org.timecrafters.TimeCraftersConfigurationTool.R; import org.timecrafters.TimeCraftersConfigurationTool.backend.config.Action; import org.timecrafters.TimeCraftersConfigurationTool.backend.config.Configuration; @@ -32,6 +34,7 @@ import org.timecrafters.TimeCraftersConfigurationTool.serializers.SettingsDeseri import org.timecrafters.TimeCraftersConfigurationTool.serializers.SettingsSerializer; import org.timecrafters.TimeCraftersConfigurationTool.serializers.VariableDeserializer; import org.timecrafters.TimeCraftersConfigurationTool.serializers.VariableSerializer; +import org.timecrafters.TimeCraftersConfigurationTool.tacnet.PacketHandler; import org.timecrafters.TimeCraftersConfigurationTool.tacnet.Server; import java.io.BufferedReader; @@ -135,6 +138,14 @@ public class Backend { configChanged = true; saveConfig(); + + /* Automatically upload whole config to server + * TODO: Implement a more atomic remote config updating + * */ + if (config != null && tacnet.isConnected()) { + String json = gsonForConfig().toJson(config); + tacnet.puts(PacketHandler.packetUploadConfig(config.getName(), json).toString()); + } } public boolean hasConfigChanged() { return configChanged; } @@ -158,6 +169,34 @@ public class Backend { } } + public Config loadConfigWithoutMutatingBackend(String name) { + if (name.equals("")) { + return null; + } + + String path = configPath(name); + File file = new File(path); + + if (file.exists() && file.isFile()) { + Config config = gsonForConfig().fromJson(readFromFile(path), Config.class); + config.setName(name); + + return config; + } + + return null; + } + + public boolean isConfigValid(String json) { + try { + gsonForConfig().fromJson(json, Config.class); + + return true; + } catch (JsonSyntaxException ignored) { + return false; + } + } + public boolean saveConfig() { if (config == null) { return false; } @@ -193,19 +232,6 @@ public class Backend { return file.delete(); } - public void uploadConfig() { - if (config != null && tacnet.isConnected()) { - String json = ""; -// tacnet.puts(PacketHandler.packetUploadConfig(json)); - } - } - - public void downloadConfig() { - if (config != null && tacnet.isConnected()) { -// tacnet.puts(PacketHandler.packetDownloadConfig()); - } - } - public void writeNewConfig(String name) { String path = configPath(name); File file = new File(path); diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/config/Group.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/config/Group.java index a06ada5..40702ba 100644 --- a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/config/Group.java +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/config/Group.java @@ -11,6 +11,16 @@ public class Group { this.actions = actions; } + public static boolean nameIsUnique(ArrayList groups, String name) { + for (Group group: groups) { + if (group.name.equals(name)) { + return false; + } + } + + return true; + } + public ArrayList getActions() { return actions; } diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Packet.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Packet.java index c07c2e9..ef6fe2a 100755 --- a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Packet.java +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Packet.java @@ -6,7 +6,7 @@ import java.util.Arrays; public class Packet { final static public String PROTOCOL_VERSION = "1"; - final static public String PROTOCOL_HEADER_SEPERATOR = "|"; + final static public String PROTOCOL_SEPERATOR = "|"; final static public String PROTOCOL_HEARTBEAT = "heartbeat"; private static final String TAG = "TACNET|Packet"; @@ -21,9 +21,10 @@ public class Packet { DOWNLOAD_CONFIG(10), UPLOAD_CONFIG(11), LIST_CONFIGS(12), - ADD_CONFIG(13), - UPDATE_CONFIG(14), - DELETE_CONFIG(15), + SELECT_CONFIG(13), + ADD_CONFIG(14), + UPDATE_CONFIG(15), + DELETE_CONFIG(16), ADD_GROUP(20), UPDATE_GROUP(21), @@ -70,7 +71,7 @@ public class Packet { 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)); + Log.i(TAG, "Failed to split packet along first 4 " + PROTOCOL_SEPERATOR + ". Raw return: " + Arrays.toString(slice)); return null; } @@ -108,7 +109,7 @@ public class Packet { return true; } - String[] parts = rawMessage.split(PROTOCOL_HEADER_SEPERATOR); + String[] parts = rawMessage.split(PROTOCOL_SEPERATOR); return parts[0].equals(PROTOCOL_VERSION) && isPacketTypeValid( Integer.parseInt(parts[1])); @@ -121,11 +122,11 @@ public class Packet { public String encodeHeader() { String string = ""; string += PROTOCOL_VERSION; - string += PROTOCOL_HEADER_SEPERATOR; + string += PROTOCOL_SEPERATOR; string += packetType.getId(); - string += PROTOCOL_HEADER_SEPERATOR; + string += PROTOCOL_SEPERATOR; string += contentLength; - string += PROTOCOL_HEADER_SEPERATOR; + string += PROTOCOL_SEPERATOR; return string; } diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/PacketHandler.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/PacketHandler.java index 163b210..bc857c7 100755 --- a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/PacketHandler.java +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/PacketHandler.java @@ -3,10 +3,13 @@ package org.timecrafters.TimeCraftersConfigurationTool.tacnet; import android.util.Log; import org.timecrafters.TimeCraftersConfigurationTool.backend.Backend; +import org.timecrafters.TimeCraftersConfigurationTool.backend.Config; import org.timecrafters.TimeCraftersConfigurationTool.backend.TAC; +import org.timecrafters.TimeCraftersConfigurationTool.backend.config.Action; +import org.timecrafters.TimeCraftersConfigurationTool.backend.config.Group; import java.io.File; -import java.lang.reflect.Array; +import java.util.ArrayList; import java.util.Arrays; public class PacketHandler { @@ -45,8 +48,8 @@ public class PacketHandler { } case ERROR: { -// handleHeartBeat(packet); -// return; + handleError(packet); + return; } case DOWNLOAD_CONFIG: { @@ -59,13 +62,32 @@ public class PacketHandler { return; } + case LIST_CONFIGS: { + handleListConfigs(packet); + return; + } + + case ADD_CONFIG: { + handleAddConfig(packet); + return; + } + + case UPDATE_CONFIG: { + handleUpdateConfig(packet); + return; + } + + case DELETE_CONFIG: { + handleDeleteConfig(packet); + return; + } + // case CHANGE_ACTION: { // handleChangeAction(packet); // return; // } default: { - return; } } } @@ -74,20 +96,21 @@ public class PacketHandler { private void handleHandShake(Packet packet) {} // NO-OP private void handleHeartBeat(Packet packet) {} + // NO-OP + private void handleError(Packet packet) {} private void handleUploadConfig(Packet packet) { - String[] split = packet.getContent().split("\\" + Packet.PROTOCOL_HEADER_SEPERATOR, 2); + String[] split = packet.getContent().split("\\" + Packet.PROTOCOL_SEPERATOR, 2); final String configName = split[0]; final String json = split[1]; - if (configName.length() == 0 && false) { //!Backend.instance().configIsValid(json)) { - return; - } - if (configName.length() == 0) { + + if (configName.length() == 0 && !Backend.instance().isConfigValid(json)) { return; } + final String path = TAC.CONFIGS_PATH + File.separator + configName + ".json"; - Log.i(TAG, "Got valid json: " + packet.getContent()); + Log.i(TAG, "Got valid json: " + json); Backend.instance().writeToFile(path, json); } @@ -101,27 +124,152 @@ public class PacketHandler { final String path = TAC.CONFIGS_PATH + File.separator + configName + ".json"; String content = Backend.instance().readFromFile(path); - Packet.create(Packet.PacketType.UPLOAD_CONFIG, content); - } else { // Errored - final String content = "ERROR"; - Packet.create(Packet.PacketType.ERROR, content); + pkt = packetUploadConfig(configName, content); + } else { // Error + pkt = packetError("Remote config not found", "The requested config " + configName + " does not exist over here."); } if (hostIsAConnection) { - Backend.instance().tacnet().puts(packet.toString()); + Backend.instance().tacnet().puts(pkt.toString()); } else { - Backend.instance().getServer().getActiveClient().puts(packet.toString()); + Backend.instance().getServer().getActiveClient().puts(pkt.toString()); } } - private void handleChangeAction(Packet packet) { - // TODO: Handle renaming, deleting, and adding. + // TODO: reply with config_name,456|other_config,10 (config name,revision) + private void handleListConfigs(Packet packet) { + if (hostIsAConnection) { + final String[] remoteConfigs = packet.getContent().split("\\" + Packet.PROTOCOL_SEPERATOR); + ArrayList diff = Backend.instance().configsList(); + + for (String part : remoteConfigs) { + final String[] configInfo = part.split(",", 2); + final String name = configInfo[0]; + final int revision = Integer.parseInt(configInfo[1]); + + diff.remove(name); + + File file = new File(Backend.instance().configPath(name)); + + if (file.exists()) { + final Config config = Backend.instance().loadConfigWithoutMutatingBackend(name); + + if (config.getConfiguration().revision < revision) { + Log.i(TAG, "handleListConfigs: requesting config: " + name + " since local " + config.getName() + " is @ " + config.getConfiguration().revision); + Backend.instance().tacnet().puts(PacketHandler.packetDownloadConfig(name).toString()); + } else if (config.getConfiguration().revision > revision) { + Log.i(TAG, "handleListConfigs: sending config: " + name + " since local " + config.getName() + " is @ " + config.getConfiguration().revision); + Backend.instance().tacnet().puts(PacketHandler.packetUploadConfig(name, Backend.instance().gsonForConfig().toJson(config)).toString()); + } + } else { + Log.i(TAG, "handleListConfigs: requesting config: " + name + " since there is no local file with that name"); + + Backend.instance().tacnet().puts( PacketHandler.packetDownloadConfig(name).toString() ); + } + } + + for (String name : diff) { + final Config config = Backend.instance().loadConfigWithoutMutatingBackend(name); + + Backend.instance().tacnet().puts(PacketHandler.packetUploadConfig(name, Backend.instance().gsonForConfig().toJson(config)).toString()); + } + + } else { + Backend.instance().getServer().getActiveClient().puts(PacketHandler.packetListConfigs().toString()); + } } - private void handleChangeVariable(Packet packet) { - // TODO: Handle renaming, deleting, and adding. + private void handleSelectConfig(Packet packet) { + final String configName = packet.getContent(); + + Backend.instance().getSettings().config = configName; + Backend.instance().saveSettings(); + Backend.instance().loadConfig(configName); } + private void handleAddConfig(Packet packet) { + final String configName = packet.getContent(); + + if (Backend.instance().configsList().contains(configName)) { + if (!hostIsAConnection) { + Backend.instance().getServer().getActiveClient().puts( + packetError("Config already exists!", "A config with the name " + + configName + " already exists here.").toString() + ); + } + } else { + Backend.instance().writeNewConfig(configName); + } + } + + private void handleUpdateConfig(Packet packet) { + final String[] split = packet.getContent().split("\\" + Packet.PROTOCOL_SEPERATOR, 2); + final String oldConfigName = split[0]; + final String newConfigName = split[1]; + + if (Backend.instance().configsList().contains(newConfigName)) { + if (!hostIsAConnection) { + Backend.instance().getServer().getActiveClient().puts( + packetError("Config already exists!", "A config with the name " + + newConfigName + " already exists here.").toString() + ); + } + } else { + Backend.instance().moveConfig(oldConfigName, newConfigName); + } + } + + private void handleDeleteConfig(Packet packet) { + final String configName = packet.getContent(); + + Backend.instance().deleteConfig(configName); + } + + private void handleAddGroup(Packet packet) { + final String[] split = packet.getContent().split("\\" + Packet.PROTOCOL_SEPERATOR, 2); + final String configName = packet.getContent(); + final String groupName = packet.getContent(); + + if (Backend.instance().getConfig().getName().equals(configName)) { + if (Group.nameIsUnique(Backend.instance().getConfig().getGroups(), groupName)) { + Group group = new Group(groupName, new ArrayList()); + Backend.instance().getConfig().getGroups().add(group); + } else { + Backend.instance().getServer().getActiveClient().puts( + packetError("Group name collision", "A group with the name " + groupName + " already exists").toString() + ); + } + } else { + Backend.instance().getServer().getActiveClient().puts( + packetError("Active config mismatch", "Active config is not " + configName).toString() + ); + } + } + + private void handleUpdateGroup(Packet packet) {} + + private void handleDeleteGroup(Packet packet) {} + + private void handleAddAction(Packet packet) {} + + private void handleChangeAction(Packet packet) { + // TODO: Handle renaming action and updating comment. + } + + private void handleDeleteAction(Packet packet) {} + + private void handleAddVariable(Packet packet) {} + + private void handleUpdateVariable(Packet packet) {} + + private void handleChangeVariable(Packet packet) {} + + private void handleDeleteVariable(Packet packet) {} + + /************************************** + PACKET HELPER FUNCTIONS + **************************************/ + static public Packet packetHandShake(String clientUUID) { return Packet.create(Packet.PacketType.HANDSHAKE, clientUUID); } @@ -130,9 +278,98 @@ public class PacketHandler { 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); -// } + static private Packet packetError(String errorTitle, String errorMessage) { + return Packet.create(Packet.PacketType.ERROR, errorTitle + Packet.PROTOCOL_SEPERATOR + errorMessage); + } + + static public Packet packetUploadConfig(String configName, String json) { + return Packet.create(Packet.PacketType.UPLOAD_CONFIG, configName + Packet.PROTOCOL_SEPERATOR + json); + } + + static public Packet packetDownloadConfig(String configName) { + return Packet.create(Packet.PacketType.DOWNLOAD_CONFIG, configName); + } + + static public Packet packetListConfigs() { + String configsList = ""; + final ArrayList configs = Backend.instance().configsList(); + + int i = 0; + for (final String configName : configs) { + final String path = Backend.instance().configPath(configName); + Config config = Backend.instance().gsonForConfig().fromJson(Backend.instance().readFromFile(path), Config.class); + + configsList += configName + "," + config.getConfiguration().revision; + + if (i != configs.size() - 1) { + configsList += Packet.PROTOCOL_SEPERATOR; + } + + i++; + } + + return Packet.create(Packet.PacketType.LIST_CONFIGS, configsList); + } + + static public Packet packetSelectConfig(String configName) { + return Packet.create(Packet.PacketType.SELECT_CONFIG, configName); + } + + static public Packet packetAddConfig(String configName) { + return Packet.create(Packet.PacketType.ADD_CONFIG, configName); + } + + static public Packet packetUpdateConfig(String oldConfigName, String newConfigName) { + return Packet.create(Packet.PacketType.UPDATE_CONFIG, oldConfigName + Packet.PROTOCOL_SEPERATOR + newConfigName); + } + + static public Packet packetDeleteConfig(String configName) { + return Packet.create(Packet.PacketType.DELETE_CONFIG, configName); + } + + static public Packet packetAddGroup(String configName, String groupName) { + return Packet.create(Packet.PacketType.ADD_GROUP, configName + Packet.PROTOCOL_SEPERATOR + groupName); + } + + static public Packet packetUpdateGroup(String configName, String oldGroupName, String newGroupName) { + return Packet.create(Packet.PacketType.UPDATE_GROUP, configName + Packet.PROTOCOL_SEPERATOR + + oldGroupName + Packet.PROTOCOL_SEPERATOR + newGroupName); + } + + static public Packet packetDeleteGroup(String configName, String groupName) { + return Packet.create(Packet.PacketType.DELETE_GROUP, configName + Packet.PROTOCOL_SEPERATOR + groupName); + } + + // TODO + static public Packet packetAddAction(String configName, String groupName, String actionName) { + return Packet.create(Packet.PacketType.ADD_ACTION, configName + Packet.PROTOCOL_SEPERATOR + groupName); + } + + // TODO + static public Packet packetUpdateAction(String configName, String oldGroupName, String newGroupName) { + return Packet.create(Packet.PacketType.UPDATE_ACTION, configName + Packet.PROTOCOL_SEPERATOR + + oldGroupName + Packet.PROTOCOL_SEPERATOR + newGroupName); + } + + // TODO + static public Packet packetDeleteAction(String configName, String groupName) { + return Packet.create(Packet.PacketType.DELETE_ACTION, configName + Packet.PROTOCOL_SEPERATOR + groupName); + } + + // TODO + static public Packet packetAddVariable(String configName, String groupName) { + return Packet.create(Packet.PacketType.ADD_VARIABLE, configName + Packet.PROTOCOL_SEPERATOR + groupName); + } + + // TODO + static public Packet packetUpdateVariable(String configName, String oldGroupName, String newGroupName) { + return Packet.create(Packet.PacketType.UPDATE_VARIABLE, configName + Packet.PROTOCOL_SEPERATOR + + oldGroupName + Packet.PROTOCOL_SEPERATOR + newGroupName); + } + + static public Packet packetDeleteVariable(String configName, String groupName, String actionName, String variableName) { + return Packet.create(Packet.PacketType.DELETE_VARIABLE, configName + Packet.PROTOCOL_SEPERATOR + groupName + + Packet.PROTOCOL_SEPERATOR + actionName + + Packet.PROTOCOL_SEPERATOR + variableName); + } } diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Server.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Server.java index 66d05f3..0ea1590 100755 --- a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Server.java +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/tacnet/Server.java @@ -78,13 +78,10 @@ public class Server { 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()); + activeClient.puts(PacketHandler.packetListConfigs().toString()); Log.i(TAG, "Client connected!");