package com.limegroup.gnutella.daap; import java.io.IOException; import java.net.BindException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.nio.channels.Channel; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.io.IOUtils; import org.limewire.nio.NIODispatcher; import org.limewire.nio.SocketFactory; import org.limewire.nio.channel.ChannelReadObserver; import org.limewire.nio.channel.ChannelWriter; import org.limewire.nio.channel.InterestReadableByteChannel; import org.limewire.nio.channel.InterestWritableByteChannel; import org.limewire.nio.channel.NIOMultiplexor; import org.limewire.nio.observer.AcceptObserver; import com.google.inject.name.Named; import de.kapsi.net.daap.DaapConfig; import de.kapsi.net.daap.Library; import de.kapsi.net.daap.nio.DaapConnectionNIO; import de.kapsi.net.daap.nio.DaapServerNIO; /** * A DAAP Server that uses LimeWire's I/O libraries for NIO. */ public class LimeDaapServerNIO extends DaapServerNIO { private static final Log LOG = LogFactory.getLog(LimeDaapServerNIO.class); private final Map<DaapConnectionNIO, DaapController> allConnections = new HashMap<DaapConnectionNIO, DaapController>(); private ServerSocket serverSocket; private final ScheduledExecutorService backgroundExecutor; public LimeDaapServerNIO(Library library, DaapConfig config, @Named("backgroundExecutor") ScheduledExecutorService backgroundExecutor) { super(library, config); this.backgroundExecutor = backgroundExecutor; scheduleServices(); } /** * Schedules a repeated service that will process any connections * that should be timed out. */ private void scheduleServices() { backgroundExecutor.scheduleWithFixedDelay(new Runnable() { public void run() { NIODispatcher.instance().getScheduledExecutorService().execute(new Runnable() { public void run() { synchronized (LimeDaapServerNIO.this) { if (!running) return; } processTimeout(); } }); } }, 30000, 30000, TimeUnit.MILLISECONDS); } /** * Binds this server to the SocketAddress supplied by DaapConfig. * * @throws IOException */ @Override public void bind() throws IOException { SocketAddress bindAddr = config.getInetSocketAddress(); int backlog = config.getBacklog(); serverSocket = SocketFactory.newServerSocket(new DaapDispatcher()); // BugID: 4546610 // On Win2k, Mac OS X, XYZ it is possible to bind // the same address without rising a SocketException // (the Documentation lies) serverSocket.setReuseAddress(false); try { serverSocket.bind(bindAddr, backlog); } catch (SocketException err) { throw new BindException(err.getMessage()); } } /** * Starts this server. */ @Override public synchronized void run() { running = true; } /** * Closes the channel this connection is based on. */ @Override protected void cancelConnection(final DaapConnectionNIO connection) { NIODispatcher.instance().getScheduledExecutorService().execute(new Runnable() { public void run() { Channel channel = connection.getChannel(); try { channel.close(); } catch(IOException ignored) {} } }); } /** * Disconnects all connections from this server. */ @Override public void disconnectAll() { NIODispatcher.instance().getScheduledExecutorService().execute(new Runnable() { public void run() { for(DaapConnectionNIO next : allConnections.keySet()) { Channel channel = next.getChannel(); try { channel.close(); } catch(IOException ignored) {} synchronized(LimeDaapServerNIO.this) { libraryQueue.clear(); } } } }); } /** * Stops this server. */ @Override public synchronized void stop() { try { serverSocket.close(); } catch(IOException iox) {} disconnectAll(); synchronized(this) { running = false; } } /** * Forces all connections to process an update. */ @Override protected void update() { NIODispatcher.instance().getScheduledExecutorService().execute(new Runnable() { public void run() { synchronized (LimeDaapServerNIO.this) { for(DaapConnectionNIO connection : getDaapConnections()) { for (Library aLibraryQueue : libraryQueue) connection.enqueueLibrary(aLibraryQueue); SelectableChannel channel = connection.getChannel(); try { connection.update(); DaapController controller = allConnections.get(connection); controller.setOps(); } catch (IOException ignored) { try { channel.close(); } catch (IOException ignoredToo) { } } } libraryQueue.clear(); } } }); } /** * An observer for incoming connections. * This will only dispatch the connection if the server is * running. */ private class DaapDispatcher implements AcceptObserver { public void handleIOException(IOException iox) {} public void shutdown() {} public void handleAccept(Socket socket) throws IOException { synchronized(this) { if(!running) { IOUtils.close(socket); return; } } DaapConnectionNIO connection = new DaapConnectionNIO(LimeDaapServerNIO.this, socket.getChannel()); DaapController cont = new DaapController(connection); socket.setSoTimeout(0); allConnections.put(connection, cont); addPendingConnection(connection); ((NIOMultiplexor)socket).setReadObserver(cont); ((NIOMultiplexor)socket).setWriteObserver(cont); } } /** * An observer for DAAP connections, to process reading & writing. */ private class DaapController implements ChannelReadObserver, ChannelWriter { private DaapConnectionNIO conn; private InterestReadableByteChannel readChannel; private InterestWritableByteChannel writeChannel; private boolean shutdown; DaapController(DaapConnectionNIO dcn) { this.conn = dcn; } public void handleRead() throws IOException { if (!conn.read()) throw new IOException("finished"); else setOps(); } public void handleIOException(IOException iox) { } public InterestReadableByteChannel getReadChannel() { return readChannel; } public void setReadChannel(InterestReadableByteChannel newChannel) { this.readChannel = newChannel; conn.setReadChannel(newChannel); setOps(); } public void shutdown() { synchronized (this) { if (shutdown) return; shutdown = true; } conn.close(); allConnections.remove(conn); try { removeConnection(conn); } catch(IllegalStateException ise) { // Not a huge deal, just a bug in DAAP. LOG.error("ISE", ise); } } public InterestWritableByteChannel getWriteChannel() { return writeChannel; } public void setWriteChannel(InterestWritableByteChannel newChannel) { conn.setWriteChannel(newChannel); this.writeChannel = newChannel; setOps(); } public boolean handleWrite() throws IOException { if (!conn.write()) throw new IOException("finished"); else return setOps(); } /** Interests the right sink channels depending on what we can do. */ private boolean setOps() { int ops = conn.interrestOps(); boolean moreToWrite = false; if (writeChannel != null) { boolean write = (ops & SelectionKey.OP_WRITE) != 0; moreToWrite = write; writeChannel.interestWrite(this, write); } if (readChannel != null) { boolean read = (ops & SelectionKey.OP_READ) != 0; readChannel.interestRead(read); } return moreToWrite; } } @Override public synchronized boolean isRunning() { return running; } }