package org.lantern;

import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;

import org.lastbamboo.common.portmapping.PortMapListener;
import org.lastbamboo.common.portmapping.PortMappingProtocol;
import org.littleshoot.util.NetworkUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teleal.cling.UpnpService;
import org.teleal.cling.UpnpServiceImpl;
import org.teleal.cling.support.model.PortMapping;
import org.teleal.cling.transport.spi.InitializationException;

public class Upnp implements org.lastbamboo.common.portmapping.UpnpService {
    
    private final Logger log = LoggerFactory.getLogger(getClass());
    
    private final static Collection<UpnpService> allServices =
            new ArrayList<UpnpService>();
    
    public Upnp() {
        final String HACK_STREAM_HANDLER_SYSTEM_PROPERTY = 
            "hackStreamHandlerProperty";
        System.setProperty(HACK_STREAM_HANDLER_SYSTEM_PROPERTY, 
            "alreadyWorkedAroundTheEvilJDK");
    }

    @Override
    public void removeUpnpMapping(final int mappingIndex) {
        // The underlying implementation just removes mappings on shutdown,
        // so we don't need to do this here.
    }
    
    @Override
    public int addUpnpMapping(final PortMappingProtocol prot, 
        final int localPort, final int externalPortRequested,
        final PortMapListener portMapListener) {
        if (NetworkUtils.isPublicAddress()) {
            return 1;
        }
        final String lh;
        try {
            lh = NetworkUtils.getLocalHost().getHostAddress();
        } catch (final UnknownHostException e) {
            log.error("Could not find host?", e);
            return -1;
        }
        
        // This call will block unless we thread it here.
        final Runnable upnpRunner = new Runnable() {
            @Override
            public void run() {
                addMapping(prot, externalPortRequested, 
                    portMapListener, lh);
            }
        };
        final Thread mapper = new Thread(upnpRunner, "UPnP-Mapping-Thread");
        mapper.setDaemon(true);
        mapper.start();
        
        // The mapping index isn't relevant in this case because the underlying
        // UPnP implementation handles removing mappings automatically. We
        // return a positive number to indicate the mapping hasn't failed at
        // this point.
        return 1;
    }

    protected void addMapping(final PortMappingProtocol prot, 
        final int externalPortRequested, 
        final PortMapListener portMapListener, final String lh) {

        final PortMapping desiredMapping = new PortMapping(
            externalPortRequested,
            lh,
            prot == PortMappingProtocol.TCP ? PortMapping.Protocol.TCP : PortMapping.Protocol.UDP,
            "Lantern Port Mapping"
        );

        final UpnpService upnpService;
        try {
            upnpService = new UpnpServiceImpl(
                new UpnpPortMappingListener(portMapListener, desiredMapping)
            );
            allServices.add(upnpService);
        } catch (final InitializationException e) {
            final String msg = e.getMessage();
            if (msg.contains("no Inet4Address associated")) {
                log.info("Could not map -- no internet access?", e);
            } else {
                log.error("Could not map port", e);
            }
            return;
        }

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                upnpService.shutdown();
            }
        });
        
        upnpService.getControlPoint().search();
    }

    @Override
    public void shutdown() {
        final Runnable shutdown = new Runnable() {
            
            @Override
            public void run() {
                for (final UpnpService service : allServices) {
                    log.info("Shutting down UPNP service: {}", service);
                    service.shutdown();
                }
            }
        };
        final Thread t = new Thread(shutdown, "Lantern-UPNP-Shutdown-Thread");
        t.setDaemon(true);
        t.start();
    }
}

@@ -109,16 +109,136 @@
 lection;
+%0Aimport java.util.concurrent.Executor;%0Aimport java.util.concurrent.Executors;%0Aimport java.util.concurrent.ThreadFactory;
 %0A%0Aimport
@@ -450,16 +450,73 @@
 actory;%0A
+import org.teleal.cling.DefaultUpnpServiceConfiguration;%0A
 import o
@@ -2624,24 +2624,701 @@
 urn 1;%0A    %7D
+%0A    %0A    private static class LanternUpnpServiceConfiguration extends DefaultUpnpServiceConfiguration %7B%0A        %0A        @Override%0A        protected Executor createDefaultExecutor() %7B%0A            return Executors.newCachedThreadPool(new ThreadFactory() %7B%0A%0A                private int count = 0;%0A                %0A                @Override%0A                public Thread newThread(Runnable r) %7B%0A                    final Thread t = new Thread(r, %0A                        %22Lantern-UPnP-Default-Thread-Pool-%22+count);%0A                    t.setDaemon(true);%0A                    count++;%0A                    return t;%0A                %7D%0A                %0A            %7D);%0A        %7D%0A    %7D
 %0A%0A    protec
@@ -3474,25 +3474,24 @@
 tring lh) %7B%0A
-%0A
         fina
@@ -3734,17 +3734,16 @@
      );%0A
-%0A
         
@@ -3779,32 +3779,32 @@
 ;%0A        try %7B%0A
-
             upnp
@@ -3833,16 +3833,55 @@
 iceImpl(
+new LanternUpnpServiceConfiguration(), 
 %0A       

Change thread pool for UPnP closes #481