package de.bwlehrpool.bwlp_guac; import java.io.IOException; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicLong; import org.apache.guacamole.protocol.GuacamoleConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.bwlehrpool.bwlp_guac.JsonClient.State; public class AvailableClient implements Cloneable { private static final Logger LOGGER = LoggerFactory.getLogger(AvailableClient.class); private static final AtomicLong CON_ID = new AtomicLong(); public ArrayList locationList = new ArrayList(); private final String clientip; private String password; private int locationid; private State state; private String inUseBy; private WrappedConnection connection; private long deadline; private long lastConnectionCheck; private boolean connectionOk; private final Object connCheckLock = new Object(); public AvailableClient(JsonClient source) { this.clientip = source.clientip; this.locationid = source.locationid; update(source); } private AvailableClient(String clientip) { this.clientip = clientip; } /** * Update this client's state, resetting "in use" if appropriate. Ie, the * password changed, which would mean the remote VNC server was restarted, so it * can't possibly be in use by the old user anymore. */ public synchronized void update(JsonClient source) { if (this.password == null || !this.password.equals(source.password)) { if (source.state != State.OCCUPIED) { if (this.inUseBy != null) { LOGGER.info("Client " + this + " is available again"); this.inUseBy = null; this.connection = null; } } this.lastConnectionCheck = 0; this.password = source.password; } this.state = source.state; this.deadline = 0; } /** * Try to reserve this client for the given user. */ public synchronized boolean claim(String user) { if (this.inUseBy != null || this.password == null || this.state != State.IDLE || user == null) return false; this.inUseBy = user; this.connection = new WrappedConnection(this.clientip + "/" + CON_ID.incrementAndGet(), this); this.state = State.OCCUPIED; return true; } public synchronized boolean isInUseBy(String user) { return (this.inUseBy != null && this.inUseBy.equals(user)); } public synchronized WrappedConnection getConnection(String expectedOwner) { if (isInUseBy(expectedOwner)) return this.connection; return null; } public synchronized void releaseConnection(String expectedOwner) { if (isInUseBy(expectedOwner)) { LOGGER.info("Prematurely releasing client " + this); this.inUseBy = null; this.connection = null; } } /** * If this connection is not returned by the sat server anymore, we keep it * around for another 5 minutes just in case. */ public boolean isTimeout(long NOW) { if (deadline == 0) { deadline = NOW + 300000; return false; } return deadline < NOW; } @Override public String toString() { return clientip + "/" + state + "/" + inUseBy; } public State getState() { return state; } public int getLocationid() { return locationid; } public GuacamoleConfiguration toGuacConfig() { GuacamoleConfiguration cfg = new GuacamoleConfiguration(); cfg.setProtocol("vnc"); cfg.setParameter("hostname", this.clientip); cfg.setParameter("port", Integer.toString(5900)); // TODO cfg.setParameter("password", password); return cfg; } /** * Check if the given VNC credentials actually work, so we avoid assigning a * bogus entry to a user. */ public boolean checkConnection(int retries) { long now = System.currentTimeMillis(); synchronized (connCheckLock) { if (now < this.lastConnectionCheck) { this.lastConnectionCheck = 0; } if (now - this.lastConnectionCheck < 5000) return this.connectionOk; for (;;) { try (VncConnection vnc = new VncConnection(this.clientip, 5900)) { LOGGER.debug("VNC Version for " + this.clientip + " is " + vnc.handshake()); if (vnc.tryLogin(this.password)) { LOGGER.info("Connection to " + this + " is OK"); this.lastConnectionCheck = now; return this.connectionOk = true; } } catch (IOException e) { LOGGER.info("Connection error VNC @ " + this, e); if (retries-- > 0) { try { Thread.sleep(1000); } catch (InterruptedException e1) { Thread.currentThread().interrupt(); break; } continue; } } break; } this.lastConnectionCheck = now; this.password = null; // Render invalid, so ConnectionManager::getForUser() doesn't turn into an infinite loop return this.connectionOk = false; } } @Override public AvailableClient clone() { AvailableClient c = new AvailableClient(this.clientip); c.state = this.state; c.inUseBy = this.inUseBy; c.password = this.password; return c; } }