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}