001package jmri.jmrix.bidib.tcpserver; 002 003import java.io.FileInputStream; 004import java.io.FileNotFoundException; 005import java.io.FileOutputStream; 006import java.io.IOException; 007import java.io.OutputStream; 008import java.io.PrintStream; 009import java.net.ServerSocket; 010import java.util.Properties; 011 012import jmri.InstanceManager; 013import jmri.jmrix.bidib.BiDiBSystemConnectionMemo; 014import jmri.util.FileUtil; 015import jmri.util.zeroconf.ZeroConfService; 016 017import org.bidib.jbidibc.net.serialovertcp.NetBidib; 018import org.slf4j.Logger; 019import org.slf4j.LoggerFactory; 020 021/** 022 * JMRI Implementation of the BiDiBOverTcp Server Protocol. 023 * Starting and Stopping of the server is delegated to the 024 * NetPlainTcpBidib class. 025 * 026 * There is one server for each BiDiB connection and they must have different port numbers, 027 * so the client is connected to a specific BiDiB connection. 028 * 029 * @author Alex Shepherd Copyright (C) 2006 030 * @author Mark Underwood Copyright (C) 2015 031 * @author Eckart Meyer Copyright (C) 2023 032 */ 033public class TcpServer { 034 035 final java.util.ResourceBundle rb = java.util.ResourceBundle.getBundle("jmri.jmrix.bidib.swing.BiDiBSwingBundle"); // NOI18N 036 037 //private final LinkedList<ClientRxHandler> clients = new LinkedList<>(); 038 private final BiDiBSystemConnectionMemo memo; 039 NetPlainTcpBidib netPlainTcpBidib; 040 //Thread socketListener; 041 ServerSocket serverSocket; 042 boolean settingsLoaded = false; 043 //ServerListner stateListner; 044 boolean settingsChanged = false; 045 Runnable shutDownTask; 046 ZeroConfService service = null; 047 //private boolean autoStart; 048 private boolean autoStart = false; 049 050 static final String AUTO_START_KEY = "AutoStart"; 051 static final String PORT_NUMBER_KEY = "PortNumber"; 052 static final String SETTINGS_FILE_NAME = "BiDiBOverTcpSettings.ini"; 053 054 // private TcpServer() { 055 // log.debug("BiDiB TcpServer started!"); 056 // memo = null; 057 // } 058 059 public TcpServer(BiDiBSystemConnectionMemo memo) { 060 this.memo = memo; 061 log.debug("BiDiB TcpServer created for {}", memo.getUserName()); 062 } 063 064// public void setStateListner(ServerListner l) { 065// stateListner = l; 066// } 067 068 private void loadSettings() { 069 if (!settingsLoaded) { 070 settingsLoaded = true; 071 Properties settings = new Properties(); 072 073 String settingsFileName = FileUtil.getUserFilesPath() + SETTINGS_FILE_NAME; 074 075 try { 076 log.debug("TcpServer: opening settings file {}", settingsFileName); 077 java.io.InputStream settingsStream = new FileInputStream(settingsFileName); 078 try { 079 settings.load(settingsStream); 080 } finally { 081 settingsStream.close(); 082 } 083 084 String val = settings.getProperty(AUTO_START_KEY, "0"); 085 autoStart = (val.equals("1")); 086 val = settings.getProperty(PORT_NUMBER_KEY, Integer.toString(NetBidib.BIDIB_UDP_PORT_NUMBER)); 087 portNumber = Integer.parseInt(val, 10); 088 } catch (FileNotFoundException ex) { 089 log.debug("TcpServer: loadSettings file not found"); 090 } catch (IOException ex) { 091 log.debug("TcpServer: loadSettings exception: ", ex); 092 } 093 updateServerStateListener(); 094 } 095 } 096 097 public void saveSettings() { 098 // we can't use the store capabilities of java.util.Properties, as 099 // they are not present in Java 1.1.8 100 // TODO: Use preferences like some other code do. But more important: Provide a GUI ! 101 String settingsFileName = FileUtil.getUserFilesPath() + SETTINGS_FILE_NAME; 102 log.debug("TcpServer: saving settings file {}", settingsFileName); 103 104 try ( OutputStream outStream = new FileOutputStream(settingsFileName); 105 PrintStream settingsStream = new PrintStream(outStream); ) { 106 107 settingsStream.println("# BiDiBOverTcp Configuration Settings"); 108 settingsStream.println(AUTO_START_KEY + " = " + (autoStart ? "1" : "0")); 109 settingsStream.println(PORT_NUMBER_KEY + " = " + portNumber); 110 111 settingsStream.flush(); 112 settingsStream.close(); 113 settingsChanged = false; 114 } catch ( IOException ex) { 115 log.warn("TcpServer: saveSettings exception: ", ex); 116 } 117 updateServerStateListener(); 118 } 119 120 public boolean getAutoStart() { 121 loadSettings(); 122 return autoStart; 123 } 124 125 public void setAutoStart(boolean start) { 126 loadSettings(); 127 autoStart = start; 128 settingsChanged = true; 129 updateServerStateListener(); 130 } 131 private int portNumber = NetBidib.BIDIB_UDP_PORT_NUMBER; 132 133 public int getPortNumber() { 134 loadSettings(); 135 return portNumber; 136 } 137 138 public void setPortNumber(int port) { 139 loadSettings(); 140 if ((port >= 1024) && (port <= 65535)) { 141 portNumber = port; 142 settingsChanged = true; 143 updateServerStateListener(); 144 } 145 } 146 147 public boolean isEnabled() { 148 //return (socketListener != null) && (socketListener.isAlive()); 149 if (netPlainTcpBidib != null && netPlainTcpBidib.isStarted()) { 150 return true; 151 } 152 return false; 153 } 154 155 public boolean isSettingChanged() { 156 return settingsChanged; 157 } 158 159 public void enable() { 160 if (netPlainTcpBidib == null || !netPlainTcpBidib.isStarted()) { 161 162 log.info("Starting new BiDiBOverTcpServer listener on port {}", portNumber); 163 164 if (netPlainTcpBidib == null) { 165 netPlainTcpBidib = new NetPlainTcpBidib(memo.getBiDiBTrafficController()); 166 } 167 netPlainTcpBidib.start(portNumber); 168 169 if (netPlainTcpBidib.isStarted()) { 170 171 updateServerStateListener(); 172 173 if (this.shutDownTask == null) { 174 this.shutDownTask = this::disable; 175 } 176 if (this.shutDownTask != null) { 177 InstanceManager.getDefault(jmri.ShutDownManager.class).register(this.shutDownTask); 178 } 179 } 180 else { 181 jmri.util.swing.JmriJOptionPane.showMessageDialog(null, rb.getString("BiDiBOverTCPServerConnectError"), "TCP over BiDiB Server", jmri.util.swing.JmriJOptionPane.ERROR_MESSAGE); 182 } 183 184// // advertise over Zeroconf/Bonjour 185// if (this.service == null) { 186// this.service = ZeroConfService.create("_bidibovertcpserver._tcp.local.", portNumber); 187// } 188// log.info("Starting ZeroConfService _bidibovertcpserver._tcp.local for BiDiBOverTCP Server"); 189// this.service.publish(); 190 191 } 192 } 193 194 public void disable() { 195 if (netPlainTcpBidib != null) { 196 log.info("Stopping BiDiBOverTcpServer listener."); 197 198 netPlainTcpBidib.stop(); 199 netPlainTcpBidib = null; 200 201 updateServerStateListener(); 202 203 // Now close all the client connections 204// Object[] clientsArray; 205 206// synchronized (clients) { 207// clientsArray = clients.toArray(); 208// } 209// for (int i = 0; i < clientsArray.length; i++) { 210// ((ClientRxHandler) clientsArray[i]).close(); 211// } 212 } 213 214// this.service.stop(); 215 216 if (this.shutDownTask != null) { 217 InstanceManager.getDefault(jmri.ShutDownManager.class).deregister(this.shutDownTask); 218 } 219 } 220 221 public void updateServerStateListener() { 222// if (stateListner != null) { 223// stateListner.notifyServerStateChanged(this); 224// } 225 } 226 227 public void updateClientStateListener() { 228// if (stateListner != null) { 229// stateListner.notifyClientStateChanged(this); 230// } 231 } 232 233 /** 234 * Get access to the system connection memo associated with this traffic 235 * controller. 236 * 237 * @return associated systemConnectionMemo object 238 */ 239 public BiDiBSystemConnectionMemo getSystemConnectionMemo() { 240 return (memo); 241 } 242 243 244 245// class ClientListener implements Runnable { 246// 247// @Override 248// public void run() { 249// Socket newClientConnection; 250// String remoteAddress; 251// try { 252// serverSocket = new ServerSocket(getPortNumber()); 253// serverSocket.setReuseAddress(true); 254// while (!socketListener.isInterrupted()) { 255// newClientConnection = serverSocket.accept(); 256// remoteAddress = newClientConnection.getRemoteSocketAddress().toString(); 257// log.info("TcpServer: Connection from: {}", remoteAddress); 258// //addClient(new ClientRxHandler(remoteAddress, newClientConnection)); 259// } 260// serverSocket.close(); 261// } catch (IOException ex) { 262// if (ex.toString().indexOf("socket closed") == -1) { 263// log.error("TcpServer: IO Exception: ", ex); 264// } 265// } 266// serverSocket = null; 267// } 268// } 269 270// protected void addClient(ClientRxHandler handler) { 271// synchronized (clients) { 272// clients.add(handler); 273// } 274// updateClientStateListener(); 275// } 276 277// protected void removeClient(ClientRxHandler handler) { 278// synchronized (clients) { 279// clients.remove(handler); 280// } 281// updateClientStateListener(); 282// } 283 284// public int getClientCount() { 285// synchronized (clients) { 286// return clients.size(); 287// } 288// } 289 290 291// @ServiceProvider(service = InstanceInitializer.class) 292// public static class Initializer extends AbstractInstanceInitializer { 293// 294// @Override 295// public <T> Object getDefault(Class<T> type) { 296// if (type.equals(TcpServer.class)) { 297// TcpServer instance = new TcpServer(); 298// if (instance.getAutoStart()) { 299// instance.enable(); 300// } 301// return instance; 302// } 303// return super.getDefault(type); 304// } 305// 306// @Override 307// public Set<Class<?>> getInitalizes() { 308// Set<Class<?>> set = super.getInitalizes(); 309// set.add(TcpServer.class); 310// return set; 311// } 312// } 313 314 private final static Logger log = LoggerFactory.getLogger(TcpServer.class); 315 316}