001package jmri.jmrit.withrottle; 002 003import java.io.IOException; 004import java.net.ServerSocket; 005import java.util.ArrayList; 006import jmri.InstanceManager; 007import jmri.UserPreferencesManager; 008import jmri.util.zeroconf.ZeroConfService; 009import jmri.util.zeroconf.ZeroConfServiceEvent; 010import jmri.util.zeroconf.ZeroConfServiceListener; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014/** 015 * Copied from UserInterface, but with the UI stuff removed. Sets up to 016 * advertise service, and creates a thread for it to run in. 017 * <p> 018 * listen() has to run in a separate thread. 019 * 020 * @author Brett Hoffman Copyright (C) 2009, 2010 021 * @author Paul Bender Copyright (C) 2018 022 */ 023public class FacelessServer implements DeviceListener, DeviceManager, ZeroConfServiceListener { 024 025 private final static Logger log = LoggerFactory.getLogger(FacelessServer.class); 026 027 UserPreferencesManager userPreferences = InstanceManager.getNullableDefault(UserPreferencesManager.class); 028 029// Server iVars 030 int port; 031 ZeroConfService service; 032 boolean isListen = true; 033 ServerSocket socket = null; 034 final private ArrayList<DeviceServer> deviceList = new ArrayList<>(); 035 final private ArrayList<DeviceListener> deviceListenerList = new ArrayList<>(); 036 private int threadNumber = 1; 037 038 FacelessServer() { 039 createServerThread(); 040 setShutDownTask(); 041 } // End of constructor 042 043 @Override 044 public void listen() { 045 int socketPort = InstanceManager.getDefault(WiThrottlePreferences.class).getPort(); 046 047 try { //Create socket on available port 048 socket = new ServerSocket(socketPort); 049 } catch (IOException e1) { 050 log.error("New ServerSocket({}) Failed during listen()", socketPort); 051 return; 052 } 053 054 port = socket.getLocalPort(); 055 log.debug("WiThrottle listening on TCP port: {}", port); 056 057 service = ZeroConfService.create("_withrottle._tcp.local.", port); 058 service.addEventListener(this); 059 service.publish(); 060 061 addDeviceListener(this); 062 063 while (isListen) { //Create DeviceServer threads 064 DeviceServer device; 065 try { 066 log.info("Creating new WiThrottle DeviceServer(socket) on port {}, waiting for incoming connection...", port); 067 device = new DeviceServer(socket.accept(), this); //blocks here until a connection is made 068 069 String threadName = "DeviceServer-" + threadNumber++; // NOI18N 070 Thread t = jmri.util.ThreadingUtil.newThread(device, threadName); 071 for (DeviceListener dl : deviceListenerList) { 072 device.addDeviceListener(dl); 073 } 074 log.debug("Starting thread '{}'", threadName); // NOI18N 075 t.start(); 076 } catch (IOException e3) { 077 if (isListen) { 078 log.error("Listen Failed on port {}", port); 079 } 080 return; 081 } 082 083 } 084 085 } 086 087 // package protected getters 088 ZeroConfService getZeroConfService() { 089 return service; 090 } 091 092 int getPort() { 093 return port; 094 } 095 096 /** 097 * Add a device listener that will be added for each new device connection 098 * 099 * @param dl the device listener to add 100 */ 101 @Override 102 public void addDeviceListener(DeviceListener dl) { 103 if (!deviceListenerList.contains(dl)) { 104 deviceListenerList.add(dl); 105 } 106 } 107 108 /** 109 * Remove a device listener from the list that will be added for each new 110 * device connection 111 * 112 * @param dl the device listener to remove 113 */ 114 @Override 115 public void removeDeviceListener(DeviceListener dl) { 116 if (deviceListenerList.contains(dl)) { 117 deviceListenerList.remove(dl); 118 } 119 } 120 121 @Override 122 public void notifyDeviceConnected(DeviceServer device) { 123 124 deviceList.add(device); 125 } 126 127 @Override 128 public void notifyDeviceDisconnected(DeviceServer device) { 129 if (deviceList.size() < 1) { 130 return; 131 } 132 if (!deviceList.remove(device)) { 133 return; 134 } 135 136 device.removeDeviceListener(this); 137 } 138 139// public void notifyDeviceAddressChanged(DeviceServer device){ 140// } 141 /** 142 * Received an UDID, filter out any duplicate. 143 * 144 * @param device the device to filter for duplicates 145 */ 146 @Override 147 public void notifyDeviceInfoChanged(DeviceServer device) { 148 149 // Filter duplicate connections 150 if ((device.getUDID() != null)) { 151 for (DeviceServer listDevice : deviceList) { 152 if (device != listDevice && device.getUDID().equals(listDevice.getUDID())) { 153 // If in here, array contains duplicate of a device 154 log.debug("Has duplicate of device '{}', clearing old one.", listDevice.getUDID()); 155 listDevice.closeThrottles(); 156 break; 157 } 158 } 159 } 160 } 161 162 public ArrayList<DeviceServer> getDeviceList() { 163 return deviceList; 164 } 165 166 @Override 167 public void notifyDeviceAddressChanged(DeviceServer device) { 168 // TODO Auto-generated method stub 169 170 } 171 172 private String rosterGroup = null; 173 174 @Override 175 public void setSelectedRosterGroup(String group) { 176 rosterGroup = group; 177 } 178 179 @Override 180 public String getSelectedRosterGroup() { 181 return rosterGroup; 182 } 183 184 @Override 185 public void serviceQueued(ZeroConfServiceEvent se) { 186 } 187 188 @Override 189 public void servicePublished(ZeroConfServiceEvent se) { 190 try { 191 log.info("Published ZeroConf service for '{}' on {}:{}", se.getService().getKey(), se.getAddress().getHostAddress(), port); // NOI18N 192 } catch (NullPointerException ex) { 193 log.error("NPE in FacelessServer.servicePublished(): {}", ex.getLocalizedMessage()); 194 } 195 } 196 197 // package protected method to disable the server. 198 void disableServer() { 199 isListen = false; 200 stopDevices(); 201 try { 202 socket.close(); 203 log.debug("closed socket in ServerThread"); 204 service.stop(); 205 } catch (NullPointerException ex) { 206 log.debug("NPE while attempting to close socket, ignored"); 207 } catch (IOException ex) { 208 log.error("socket in ServerThread won't close"); 209 } 210 } 211 212 // Clear out the deviceList array and close each device thread 213 private void stopDevices() { 214 DeviceServer device; 215 int cnt = 0; 216 if (deviceList.size() > 0) { 217 do { 218 device = deviceList.get(0); 219 if (device != null) { 220 device.closeThrottles(); //Tell device to stop its throttles, 221 device.closeSocket(); //close its sockets 222 //close() will throw read error and it will be caught 223 //and drop the thread. 224 cnt++; 225 if (cnt > 200) { 226 break; 227 } 228 } 229 } while (!deviceList.isEmpty()); 230 } 231 deviceList.clear(); 232 } 233 234 private void setShutDownTask() { 235 jmri.InstanceManager.getDefault(jmri.ShutDownManager.class).register(this::disableServer); 236 } 237 238 @Override 239 public void serviceUnpublished(ZeroConfServiceEvent se) { 240 } 241 242}