summaryrefslogblamecommitdiffstats
path: root/src/main/java/de/bwlehrpool/bwlp_guac/AvailableClient.java
blob: c886397e56fcc4f0a7514ee5a6512bb63bc5ae87 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11










                                                            
                                                   


























                                                                                            



                                                  









                                                                                        
                                                                                              


















































































                                                                                                                    
                                                                                                





















                                                                                                                                      








                                                                       

 
package de.bwlehrpool.bwlp_guac;

import java.io.IOException;
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();

	private final String clientip;

	private String password;

	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;
		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 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;
	}

}