package ca.szc.keratin.bot; import java.net.UnknownHostException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import net.engio.mbassy.listener.Handler; import org.pmw.tinylog.Logger; import ca.szc.keratin.bot.handlers.ConnectionPreamble; import ca.szc.keratin.core.event.connection.IrcConnect; import ca.szc.keratin.core.net.IrcConnection; import ca.szc.keratin.core.net.IrcConnection.SslMode; import ca.szc.keratin.core.net.mbassador.MBassadorWrapper; import ca.szc.keratin.core.net.util.InvalidPortException; /** * A connection to be delagated short-term tasks. This is in the public API, but users should probably use * {@link KeratinBot} instead. */ public class DelegateConnection { /** * Basically a Runnable, but called with an IrcConnection instead of nothing. */ public static interface ConnectionRunnable { public void run( IrcConnection conn ); } private IrcConnection connection; private final BlockingQueue<DelegateConnection.ConnectionRunnable> taskQueue; private final String address; private final int port; private final String user; private final String nick; private final String realName; private final SslMode sslMode; /** * Create with given information. Connection will not be made until the first task is recieved. * * @param address Server address * @param port Server port * @param sslMode * @param user User name * @param nick Initial nick * @param realName Real name */ public DelegateConnection( String address, int port, SslMode sslMode, String user, String nick, String realName ) { taskQueue = new LinkedBlockingQueue<DelegateConnection.ConnectionRunnable>(); this.address = address; this.port = port; this.sslMode = sslMode; this.user = user; this.nick = nick; this.realName = realName; new Thread() { @Override public void run() { Thread.currentThread().setName( "DelegateConnection" ); Logger.trace( "Delegate connection thread running" ); while ( !Thread.interrupted() ) { try { ConnectionRunnable task = taskQueue.take(); Logger.trace( "Got delegate connection task" ); try { task.run( getConnection() ); } catch ( Exception e ) { Logger.error( e, "Delegate task threw an exception" ); } } catch ( InterruptedException e ) { break; } } Logger.trace( "Run loop ends, exiting" ); if ( connection != null ) { Logger.trace( "Disconnecting delegate connection" ); connection.disconnect(); } } }.start(); } private IrcConnection getConnection() { if ( connection == null ) { Logger.info( "Starting delegate connection" ); try { IrcConnection conn = new IrcConnection( address, port, sslMode ); MBassadorWrapper bus = conn.getEventBus(); bus.subscribe( new ConnectionPreamble( user, nick, realName ) ); // So up so we can wait for the connection to be established final Semaphore connectionEstablished = new Semaphore( 0 ); bus.subscribe( new Object() { @Handler private void awaitConnection( IrcConnect event ) { connectionEstablished.release(); } } ); connection = conn; conn.connect(); Logger.trace( "Waiting for the connection to be established" ); try { connectionEstablished.acquire(); } catch ( InterruptedException e ) { Logger.trace( e, "Interrupted while waiting for the connection to be established" ); } Logger.trace( "Connection established" ); } catch ( UnknownHostException | InvalidPortException e ) { Logger.error( e, "Could not make IrcConnection" ); } } return connection; } /** * @see {@link LinkedBlockingQueue#offer(Object)} */ public boolean offer( DelegateConnection.ConnectionRunnable task ) { return taskQueue.offer( task ); } /** * @see {@link LinkedBlockingQueue#offer(Object, long, TimeUnit)} */ public void offer( DelegateConnection.ConnectionRunnable task, long timeout, TimeUnit unit ) throws InterruptedException { taskQueue.offer( task, timeout, unit ); } }