<commit_before>package org.lantern.proxy.pt;

import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.fluent.Form;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.fluent.Response;
import org.lantern.ConnectivityChangedEvent;
import org.lantern.LanternUtils;
import org.lantern.Messages;
import org.lantern.Shutdownable;
import org.lantern.Tr;
import org.lantern.event.Events;
import org.lantern.event.ModeChangedEvent;
import org.lantern.state.Mode;
import org.lantern.state.Model;
import org.lantern.state.Notification.MessageType;
import org.lantern.util.GatewayUtil;
import org.lantern.util.Threads;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class FlashlightServerManager implements Shutdownable {

    private static final Logger LOGGER = LoggerFactory
            .getLogger(FlashlightServerManager.class);

    /**
     * Use internal port 443 plus myleshorton's lucky number 77.
     */
    private static final int PREFERRED_FLASHLIGHT_INTERNAL_PORT = 44377;
    private static final int FLASHLIGHT_EXTERNAL_PORT = 443;
    private static final long HEARTBEAT_PERIOD_MINUTES = 2;
    
    private final Model model;
    private final Messages msgs;
    private volatile Flashlight flashlight;
    private volatile ScheduledExecutorService heartbeat;
    
    @Inject
    public FlashlightServerManager(
            Model model,
            Messages messages) {
        LOGGER.info("Starting up...");
        this.model = model;
        this.msgs = messages;
        Events.register(this);
    }

    @Subscribe
    public void onModeChanged(ModeChangedEvent event) {
        boolean inGiveMode = event.getNewMode() == Mode.give;
        boolean isConnected = model.getConnectivity().isInternet();
        update(inGiveMode, isConnected);
    }

    @Subscribe
    public void onConnectivityChanged(
            final ConnectivityChangedEvent event) {
        boolean inGiveMode = LanternUtils.isGive();
        boolean isConnected = event.isConnected();
        update(inGiveMode, isConnected);
    }

    @Override
    synchronized public void stop() {
        LOGGER.debug("Flashlight manager closing.");
        stopFlashlight(model.getConnectivity().isInternet());
    }

    synchronized private void update(boolean inGiveMode, boolean isConnected) {
        boolean eligibleToRun = inGiveMode && isConnected;
        boolean running = flashlight != null;
        if (eligibleToRun && !running) {
            startFlashlight();
        } else if (!eligibleToRun && running) {
            stopFlashlight(isConnected);
        }
    }

    private void startFlashlight() {
        LOGGER.debug("Starting flashlight");
        try {
            runFlashlight(true);
        } catch (RuntimeException re) {
            final String msg = re.getMessage();
            if (msg != null && msg.contains("Exit value: 50")) {
                LOGGER.warn("Unable to start flashlight with automatically mapped external port, try without mapping");
                runFlashlight(false);
            } else {
                LOGGER.error("Unexpected runtime exception", re);
                throw re;
            }
        }

        heartbeat = Threads
                .newSingleThreadScheduledExecutor("FlashlightServerManager-Heartbeat");
        heartbeat.scheduleAtFixedRate(peerRegistrar,
                0,
                HEARTBEAT_PERIOD_MINUTES,
                TimeUnit.MINUTES);
    }

    private void stopFlashlight(boolean unregister) {
        LOGGER.debug("Stopping flashlight");
        if (unregister) {
            unregisterPeer();
        }
        if (heartbeat != null) {
            heartbeat.shutdownNow();
        }
        if (flashlight != null) {
            flashlight.stopServer();
        }
        heartbeat = null;
        flashlight = null;
    }

    private void runFlashlight(boolean mapExternalPort) {
        Properties props = new Properties();
        String instanceId = model.getInstanceId();
        props.setProperty(
                Flashlight.SERVER_KEY,
                instanceId + ".getiantem.org");
        if (mapExternalPort) {
            props.setProperty(
                    Flashlight.PORTMAP_KEY,
                    Integer.toString(FLASHLIGHT_EXTERNAL_PORT));
        }

        LOGGER.debug("Props: {}", props);
        flashlight = new Flashlight(props);
        int localPort = LanternUtils
                .findFreePort(PREFERRED_FLASHLIGHT_INTERNAL_PORT);
        flashlight.startServer(localPort, null);
    }

    private Runnable peerRegistrar = new Runnable() {
        @Override
        public void run() {
            boolean externallyAccessible = isFlashlightExternallyAccessible();
            if (externallyAccessible) {
                LOGGER.debug("Confirmed able to proxy for external clients!");
                hidePortMappingError();
                registerPeer();
            } else {
                LOGGER.warn("Unable to proxy for external clients!");
                showPortMappingError();
                unregisterPeer();
            }
        }
    };

    private void registerPeer() {
        Response response = null;
        try {
            response = Request
                    .Post(
                            "https://" + model.getS3Config().getDnsRegUrl()+"/register")
                    .bodyForm(
                            Form.form()
                                    .add("name", model.getInstanceId())
                                    .add("port",
                                            "" + FLASHLIGHT_EXTERNAL_PORT)
                                    .add("ip", model.getConnectivity().getIp())
                                    .build())
                    .execute();
            if (response.returnResponse().getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                LOGGER.error("Unable to register peer: {}", response<commit_msg>Added note about use of model.getConnectivity().getIp() in FlashlightServerManager<commit_after>package org.lantern.proxy.pt;

import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.fluent.Form;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.fluent.Response;
import org.lantern.ConnectivityChangedEvent;
import org.lantern.LanternUtils;
import org.lantern.Messages;
import org.lantern.Shutdownable;
import org.lantern.Tr;
import org.lantern.event.Events;
import org.lantern.event.ModeChangedEvent;
import org.lantern.state.Mode;
import org.lantern.state.Model;
import org.lantern.state.Notification.MessageType;
import org.lantern.util.GatewayUtil;
import org.lantern.util.Threads;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class FlashlightServerManager implements Shutdownable {

    private static final Logger LOGGER = LoggerFactory
            .getLogger(FlashlightServerManager.class);

    /**
     * Use internal port 443 plus myleshorton's lucky number 77.
     */
    private static final int PREFERRED_FLASHLIGHT_INTERNAL_PORT = 44377;
    private static final int FLASHLIGHT_EXTERNAL_PORT = 443;
    private static final long HEARTBEAT_PERIOD_MINUTES = 2;
    
    private final Model model;
    private final Messages msgs;
    private volatile Flashlight flashlight;
    private volatile ScheduledExecutorService heartbeat;
    
    @Inject
    public FlashlightServerManager(
            Model model,
            Messages messages) {
        LOGGER.info("Starting up...");
        this.model = model;
        this.msgs = messages;
        Events.register(this);
    }

    @Subscribe
    public void onModeChanged(ModeChangedEvent event) {
        boolean inGiveMode = event.getNewMode() == Mode.give;
        boolean isConnected = model.getConnectivity().isInternet();
        update(inGiveMode, isConnected);
    }

    @Subscribe
    public void onConnectivityChanged(
            final ConnectivityChangedEvent event) {
        boolean inGiveMode = LanternUtils.isGive();
        boolean isConnected = event.isConnected();
        update(inGiveMode, isConnected);
    }

    @Override
    synchronized public void stop() {
        LOGGER.debug("Flashlight manager closing.");
        stopFlashlight(model.getConnectivity().isInternet());
    }

    synchronized private void update(boolean inGiveMode, boolean isConnected) {
        boolean eligibleToRun = inGiveMode && isConnected;
        boolean running = flashlight != null;
        if (eligibleToRun && !running) {
            startFlashlight();
        } else if (!eligibleToRun && running) {
            stopFlashlight(isConnected);
        }
    }

    private void startFlashlight() {
        LOGGER.debug("Starting flashlight");
        try {
            runFlashlight(true);
        } catch (RuntimeException re) {
            final String msg = re.getMessage();
            if (msg != null && msg.contains("Exit value: 50")) {
                LOGGER.warn("Unable to start flashlight with automatically mapped external port, try without mapping");
                runFlashlight(false);
            } else {
                LOGGER.error("Unexpected runtime exception", re);
                throw re;
            }
        }

        heartbeat = Threads
                .newSingleThreadScheduledExecutor("FlashlightServerManager-Heartbeat");
        heartbeat.scheduleAtFixedRate(peerRegistrar,
                0,
                HEARTBEAT_PERIOD_MINUTES,
                TimeUnit.MINUTES);
    }

    private void stopFlashlight(boolean unregister) {
        LOGGER.debug("Stopping flashlight");
        if (unregister) {
            unregisterPeer();
        }
        if (heartbeat != null) {
            heartbeat.shutdownNow();
        }
        if (flashlight != null) {
            flashlight.stopServer();
        }
        heartbeat = null;
        flashlight = null;
    }

    private void runFlashlight(boolean mapExternalPort) {
        Properties props = new Properties();
        String instanceId = model.getInstanceId();
        props.setProperty(
                Flashlight.SERVER_KEY,
                instanceId + ".getiantem.org");
        if (mapExternalPort) {
            props.setProperty(
                    Flashlight.PORTMAP_KEY,
                    Integer.toString(FLASHLIGHT_EXTERNAL_PORT));
        }

        LOGGER.debug("Props: {}", props);
        flashlight = new Flashlight(props);
        int localPort = LanternUtils
                .findFreePort(PREFERRED_FLASHLIGHT_INTERNAL_PORT);
        flashlight.startServer(localPort, null);
    }

    private Runnable peerRegistrar = new Runnable() {
        @Override
        public void run() {
            boolean externallyAccessible = isFlashlightExternallyAccessible();
            if (externallyAccessible) {
                LOGGER.debug("Confirmed able to proxy for external clients!");
                hidePortMappingError();
                registerPeer();
            } else {
                LOGGER.warn("Unable to proxy for external clients!");
                showPortMappingError();
                unregisterPeer();
            }
        }
    };

    private void registerPeer() {
        Response response = null;
        try {
            response = Request
                    .Post(
                            "https://" + model.getS3Config().getDnsRegUrl()+"/register")
                    .bodyForm(
                            Form.form()
                                    .add("name", model.getInstanceId())
                                    .add("port",
                                            "" + FLASHLIGHT_EXTERNAL_PORT)
                                            // Note - the below is only used for testing locally
                                            // The production dns registration service determines
                                            // the IP based on the network client/X-Forwarded-For
                                            // header.
                                            // model.getConnectivity().getIp() may actually return
                                            // null here since we may or may not have obtained a
                                            // public IP at this point.
                                    .add("ip", model.getConnectivity().getIp())
                                    .build())
                    .execute();
            if (response.returnResponse().getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                LOGGER.error("Unable to register peer: {}", response
{'fraction_non_alphanumeric': 0.08950034016176582, 'fraction_numerical': 0.0031748431476302063, 'mean_word_length': 2.007501704932939, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6743034', 'n_tokens_mistral': 3447, 'n_tokens_neox': 3316, 'n_words': 743}