gt: 			if(sentAnnouncements >= WANT_ANNOUNCEMENTS) {
context: /* This code is part of Freenet. It is distributed under the GNU General
   * Public License, version 2 (or at your option any later version). See
   * http://www.gnu.org/ for further details of the GPL. */
  package freenet.node;
  
  import java.io.BufferedInputStream;
  import java.io.BufferedReader;
  import java.io.EOFException;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStreamReader;
  import java.net.InetAddress;
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.List;
  
  import freenet.io.comm.PeerParseException;
  import freenet.io.comm.ReferenceSignatureVerificationException;
  import freenet.l10n.NodeL10n;
  import freenet.node.useralerts.AbstractUserEvent;
  import freenet.node.useralerts.SimpleUserAlert;
  import freenet.node.useralerts.UserAlert;
  import freenet.node.useralerts.UserEvent;
  import freenet.support.ByteArrayWrapper;
  import freenet.support.HTMLNode;
  import freenet.support.ListUtils;
  import freenet.support.Logger;
  import freenet.support.SimpleFieldSet;
  import freenet.support.TimeUtil;
  import freenet.support.Logger.LogLevel;
  import freenet.support.io.Closer;
  import freenet.support.transport.ip.IPUtil;
  import java.util.Arrays;
  
  /**
   * Decide whether to announce, and announce if necessary to a node in the
   * routing table, or to a seednode.
   * @author toad
   */
  public class Announcer {
  	private static boolean logMINOR;
  	private final Node node;
  	private final OpennetManager om;
  	private static final int STATUS_LOADING = 0;
  	private static final int STATUS_CONNECTING_SEEDNODES = 1;
  	private static final int STATUS_NO_SEEDNODES = -1;
  	private int runningAnnouncements;
  	/** We want to announce to 5 different seednodes. */
  	private static final int WANT_ANNOUNCEMENTS = 5;
  	private int sentAnnouncements;
  	private long startTime;
  	private long timeAddedSeeds;
  	private static final long MIN_ADDED_SEEDS_INTERVAL = 60*1000;
  	/** After we have sent 3 announcements, wait for 30 seconds before sending 3 more if we still have no connections. */
  	static final int COOLING_OFF_PERIOD = 30*1000;
  	/** Pubkey hashes of nodes we have announced to */
  	private final HashSet<ByteArrayWrapper> announcedToIdentities;
  	/** IPs of nodes we have announced to. Maybe this should be first-two-bytes, but I'm not sure how to do that with IPv6. */
  	private final HashSet<InetAddress> announcedToIPs;
  	/** How many nodes to connect to at once? */
  	private static final int CONNECT_AT_ONCE = 15;
  	/** Do not announce if there are more than this many opennet peers connected */
  	private static final int MIN_OPENNET_CONNECTED_PEERS = 10;
  	private static final long NOT_ALL_CONNECTED_DELAY = 60*1000;
  	public static final String SEEDNODES_FILENAME = "seednodes.fref";
  	/** Total nodes added by announcement so far */
  	private int announcementAddedNodes;
  	/** Total nodes that didn't want us so far */
  	private int announcementNotWantedNodes;
  
  	Announcer(OpennetManager om) {
  		this.om = om;
  		this.node = om.node;
  		announcedToIdentities = new HashSet<ByteArrayWrapper>();
  		announcedToIPs = new HashSet<InetAddress>();
  		logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
  	}
  
  	protected void start() {
  		if(!node.isOpennetEnabled()) return;
  		int darkPeers = node.peers.getDarknetPeers().length;
  		int openPeers = node.peers.getOpennetPeers().length;
  		int oldOpenPeers = om.countOldOpennetPeers();
  		if(darkPeers + openPeers + oldOpenPeers == 0) {
  			// We know opennet is enabled.
  			// We have no peers AT ALL.
  			// So lets connect to a few seednodes, and attempt an announcement.
  			System.err.println("Attempting announcement to seednodes...");
  			synchronized(this) {
  				registerEvent(STATUS_LOADING);
  				started = true;
  			}
  			connectSomeSeednodes();
  		} else {
  			System.out.println("Not attempting immediate announcement: dark peers="+darkPeers+" open peers="+openPeers+" old open peers="+oldOpenPeers+" - will wait 1 minute...");
  			// Wait a minute, then check whether we need to seed.
  			node.getTicker().queueTimedJob(new Runnable() {
  				@Override
  				public void run() {
  					synchronized(Announcer.this) {
  						started = true;
  					}
  					try {
  						maybeSendAnnouncement();
  					} catch (Throwable t) {
  						Logger.error(this, "Caught "+t+" trying to send announcements", t);
  					}
  				}
  			}, MIN_ADDED_SEEDS_INTERVAL);
  		}
  	}
  
  	private void registerEvent(int eventStatus) {
  		node.clientCore.alerts.register(new AnnouncementUserEvent(eventStatus));
  	}
  
  	private void connectSomeSeednodes() {
  		if(!node.isOpennetEnabled()) return;
  		boolean announceNow = false;
  		if(logMINOR)
  			Logger.minor(this, "Connecting some seednodes...");
  		List<SimpleFieldSet> seeds = Announcer.readSeednodes(node.nodeDir().file(SEEDNODES_FILENAME));
  		System.out.println("Trying to connect to "+seeds.size()+" seednodes...");
  		long now = System.currentTimeMillis();
  		synchronized(this) {
  			if(now - timeAddedSeeds < MIN_ADDED_SEEDS_INTERVAL) return;
  			timeAddedSeeds = now;
  			if(seeds.size() == 0) {
  				registerEvent(STATUS_NO_SEEDNODES);
  				return;
  			} else {
  				registerEvent(STATUS_CONNECTING_SEEDNODES);
  			}
  		}
  		// Try to connect to some seednodes.
  		// Once they are connected they will report back and we can attempt an announcement.
  
  		int count = connectSomeNodesInner(seeds);
  		boolean stillConnecting = false;
  		List<SeedServerPeerNode> tryingSeeds = node.peers.getSeedServerPeersVector();
  		synchronized(this) {
  			for(SeedServerPeerNode seed : tryingSeeds) {
  				if(!announcedToIdentities.contains(new ByteArrayWrapper(seed.pubKeyHash))) {
  					// Either:
  					// a) we are still trying to connect to this node,
  					// b) there is a race condition and we haven't sent the announcement yet despite connecting, or
  					// c) something is severely broken and we didn't send an announcement.
  					// In any of these cases, we want to delay for 1 minute before resetting the connection process and connecting to everyone.
  					stillConnecting = true;
  					break;
  				}
  			}
  			if(logMINOR)
  				Logger.minor(this, "count = "+count+
  						" announced = "+announcedToIdentities.size()+" running = "+runningAnnouncements+" still connecting "+stillConnecting);
  			if(count == 0 && runningAnnouncements == 0) {
  				// No more peers to connect to, and no announcements running.
  				// Are there any peers which we are still trying to connect to?
  				if(stillConnecting) {
  					// Give them another minute.
  					if(logMINOR)
  						Logger.minor(this, "Will clear announced-to in 1 minute...");
  					node.getTicker().queueTimedJob(new Runnable() {
  						@Override
  						public void run() {
  							if(logMINOR)
  								Logger.minor(this, "Clearing old announced-to list");
  							synchronized(Announcer.this) {
  								if(runningAnnouncements != 0) return;
  								announcedToIdentities.clear();
  								announcedToIPs.clear();
  							}
  							maybeSendAnnouncement();
  						}
  					}, NOT_ALL_CONNECTED_DELAY);
  				} else {
  					// We connected to all the seeds.
  					// No point waiting!
  					announcedToIdentities.clear();
  					announcedToIPs.clear();
  					announceNow = true;
  				}
  			}
  		}
  		node.dnsr.forceRun();
  		// If none connect in a minute, try some more.
  		node.getTicker().queueTimedJob(new Runnable() {
  			@Override
  			public void run() {
  				try {
  					maybeSendAnnouncement();
  				} catch (Throwable t) {
  					Logger.error(this, "Caught "+t+" trying to send announcements", t);
  				}
  			}
  		}, announceNow ? 0 : MIN_ADDED_SEEDS_INTERVAL);
  	}
  
  	// Synchronize to protect announcedToIdentities and prevent running in parallel.
  	private synchronized int connectSomeNodesInner(List<SimpleFieldSet> seeds) {
  		if(logMINOR)
  			Logger.minor(this, "Connecting some seednodes from "+seeds.size());
  		int count = 0;
  		while(count < CONNECT_AT_ONCE) {
  			if(seeds.isEmpty()) break;
  			SimpleFieldSet fs = ListUtils.removeRandomBySwapLastSimple(node.random, seeds);
  			try {
  				SeedServerPeerNode seed =
  					new SeedServerPeerNode(fs, node, om.crypto, node.peers, false, om.crypto.packetMangler);
  				if(node.wantAnonAuth(true) && Arrays.equals(node.getOpennetPubKeyHash(), seed.pubKeyHash)) {
                                      if(logMINOR)
                                          Logger.minor("Not adding: I am a seednode attempting to connect to myself!", seed.userToString());
                                      continue;
                                  }
                                  if(announcedToIdentities.contains(new ByteArrayWrapper(seed.pubKeyHash))) {
  					if(logMINOR)
  						Logger.minor(this, "Not adding: already announced-to: "+seed.userToString());
  					continue;
  				}
  				if(logMINOR)
  					Logger.minor(this, "Trying to connect to seednode "+seed);
  				if(node.peers.addPeer(seed)) {
  					count++;
  					if(logMINOR)
  						Logger.minor(this, "Connecting to seednode "+seed);
  				} else {
  					if(logMINOR)
  						Logger.minor(this, "Not connecting to seednode "+seed);
  				}
  			} catch (FSParseException e) {
  				Logger.error(this, "Invalid seed in file: "+e+" for\n"+fs, e);
  				continue;
  			} catch (PeerParseException e) {
  				Logger.error(this, "Invalid seed in file: "+e+" for\n"+fs, e);
  				continue;
  			} catch (ReferenceSignatureVerificationException e) {
  				Logger.error(this, "Invalid seed in file: "+e+" for\n"+fs, e);
  				continue;
  			}
  		}
  		if(logMINOR) Logger.minor(this, "connectSomeNodesInner() returning "+count);
  		return count;
  	}
  
  	public static List<SimpleFieldSet> readSeednodes(File file) {
  		List<SimpleFieldSet> list = new ArrayList<SimpleFieldSet>();
  		FileInputStream fis = null;
  		try {
  			fis = new FileInputStream(file);
  			BufferedInputStream bis = new BufferedInputStream(fis);
  			InputStreamReader isr = new InputStreamReader(bis, "UTF-8");
  			BufferedReader br = new BufferedReader(isr);
  			while(true) {
  				try {
  					SimpleFieldSet fs = new SimpleFieldSet(br, false, false);
  					if(!fs.isEmpty())
  						list.add(fs);
  				} catch (EOFException e) {
  					return list;
  				}
  			}
  		} catch (IOException e) {
  			return list;
  		} finally {
  			Closer.close(fis);
  		}
  	}
  
  	protected void stop() {
  		// Do nothing at present
  	}
  
  	private long timeGotEnoughPeers = -1;
  	private final Object timeGotEnoughPeersLock = new Object();
  	private boolean killedAnnouncementTooOld;
  
  	public int getAnnouncementThreshold() {
  		// First, do we actually need to announce?
  		int target = Math.min(MIN_OPENNET_CONNECTED_PEERS, om.getNumberOfConnectedPeersToAimIncludingDarknet() / 2);
  		return target;
  	}
  
  	private SimpleUserAlert announcementDisabledAlert = 
  		new SimpleUserAlert(false, l10n("announceDisabledTooOldTitle"), l10n("announceDisabledTooOld"), l10n("announceDisabledTooOldShort"), UserAlert.CRITICAL_ERROR) {
  		
  		@Override
  		public HTMLNode getHTMLText() {
  			HTMLNode div = new HTMLNode("div");
  			div.addChild("#", l10n("announceDisabledTooOld"));
  			if(!node.nodeUpdater.isEnabled()) {
  				div.addChild("#", " ");
  				NodeL10n.getBase().addL10nSubstitution(div, "Announcer.announceDisabledTooOldUpdateDisabled", new String[] { "config" }, new HTMLNode[] { HTMLNode.link("/config/node.updater") });
  			}
  			// No point with !armed() or blown() because they have their own messages.
  			return div;
  		}
  		
  		@Override
  		public String getText() {
  			StringBuilder sb = new StringBuilder();
  			sb.append(l10n("announceDisabledTooOld"));
  			sb.append(" ");
  			if(!node.nodeUpdater.isEnabled()) {
  				sb.append(l10n("announceDisabledTooOldUpdateDisabled", new String[] { "config", "/config" }, new String[] { "", "" }));
  			}
  			return sb.toString();
  		}
  		
  		@Override
  		public boolean isValid() {
  			if(node.nodeUpdater.isEnabled()) return false;
  			// If it is enabled but not armed there will be a message from the updater.
  			synchronized(Announcer.this) {
  				return killedAnnouncementTooOld;
  			}
  		}
  		
  	};
  
  	
  	/** @return True if we have enough peers that we don't need to announce. */
  	boolean enoughPeers() {
  		if(om.stopping()) return true;
  		// Do we want to send an announcement to the node?
  		int opennetCount = node.peers.countConnectedPeers();
  		int target = getAnnouncementThreshold();
  		if(opennetCount >= target) {
  			if(logMINOR)
  				Logger.minor(this, "We have enough opennet peers: "+opennetCount+" > "+target+" since "+(System.currentTimeMillis()-timeGotEnoughPeers)+" ms");
  			synchronized(timeGotEnoughPeersLock) {
  				if(timeGotEnoughPeers <= 0)
  					timeGotEnoughPeers = System.currentTimeMillis();
  			}
  			return true;
  		}
  		boolean killAnnouncement = false;
  		if((!node.nodeUpdater.isEnabled()) ||
  				(node.nodeUpdater.canUpdateNow() && !node.nodeUpdater.isArmed())) {
  			// If we also have 10 TOO_NEW peers, we should shut down the announcement,
  			// because we're obviously broken and would only be spamming the seednodes
  			synchronized(this) {
  				// Once we have shut down announcement, this persists until the auto-updater
  				// is enabled.
  				if(killedAnnouncementTooOld) return true;
  			}
  			if(node.peers.getPeerNodeStatusSize(PeerManager.PEER_NODE_STATUS_TOO_NEW, false) > 10) {
  				synchronized(this) {
  					if(killedAnnouncementTooOld) return true;
  					killedAnnouncementTooOld = true;
  					killAnnouncement = true;
  				}
  				Logger.error(this, "Shutting down announcement as we are older than the current mandatory build and auto-update is disabled or waiting for user input.");
  				System.err.println("Shutting down announcement as we are older than the current mandatory build and auto-update is disabled or waiting for user input.");
  				if(node.clientCore != null)
  					node.clientCore.alerts.register(announcementDisabledAlert);
  			}
  
  		}
  		
  		if(killAnnouncement) {
  			node.executor.execute(new Runnable() {
  
  				@Override
  				public void run() {
  					for(OpennetPeerNode pn : node.peers.getOpennetPeers()) {
  						node.peers.disconnectAndRemove(pn, true, true, true);
  					}
  					for(SeedServerPeerNode pn : node.peers.getSeedServerPeersVector()) {
  						node.peers.disconnectAndRemove(pn, true, true, true);
  					}
  				}
  				
  			});
  			return true;
  		} else {
  			synchronized(this) {
  				killedAnnouncementTooOld = false;
  			}
  			if(node.clientCore != null)
  				node.clientCore.alerts.unregister(announcementDisabledAlert);
  			if(node.nodeUpdater.isEnabled() && node.nodeUpdater.isArmed() &&
  					node.nodeUpdater.uom.fetchingFromTwo() &&
  					node.peers.getPeerNodeStatusSize(PeerManager.PEER_NODE_STATUS_TOO_NEW, false) > 5) {
  				// No point announcing at the moment, but we might need to if a transfer falls through.
  				return true;
  			}
  		}
  		
  		synchronized(timeGotEnoughPeersLock) {
  			timeGotEnoughPeers = -1;
  		}
  		return false;
  	}
  
  	/**
  	 * Get the earliest time at which we had enough opennet peers. This is reset when we drop
  	 * below the threshold.
  	 */
  	long timeGotEnoughPeers() {
  		synchronized(timeGotEnoughPeersLock) {
  			return timeGotEnoughPeers;
  		}
  	}
  
  	/** 1 minute after we have enough peers, remove all seednodes left (presumably disconnected ones) */
  	private static final int FINAL_DELAY = 60*1000;
  	/** But if we don't have enough peers at that point, wait another minute and if the situation has not improved, reannounce. */
  	static final int RETRY_DELAY = 60*1000;
  	private boolean started = false;
  
  	private final Runnable checker = new Runnable() {
  
  		@Override
  		public void run() {
  			int running;
  			synchronized(Announcer.this) {
  				running = runningAnnouncements;
  			}
  			if(enoughPeers()) {
  				for(SeedServerPeerNode pn : node.peers.getConnectedSeedServerPeersVector(null)) {
  					node.peers.disconnectAndRemove(pn, true, true, false);
  				}
  				// Re-check every minute. Something bad might happen (e.g. cpu starvation), causing us to have to reseed.
  				node.getTicker().queueTimedJob(new Runnable() {
  					@Override
  					public void run() {
  						maybeSendAnnouncement();
  					}
  				}, "Check whether we need to announce", RETRY_DELAY, false, true);
  			} else {
  				node.getTicker().queueTimedJob(new Runnable() {
  					@Override
  					public void run() {
  						maybeSendAnnouncement();
  					}
  				}, "Check whether we need to announce", RETRY_DELAY, false, true);
  				if(running != 0)
  					maybeSendAnnouncement();
  			}
  		}
  
  	};
  
  	public void maybeSendAnnouncementOffThread() {
  		if(enoughPeers()) return;
  		node.getTicker().queueTimedJob(new Runnable() {
  
  			@Override
  			public void run() {
  				maybeSendAnnouncement();
  			}
  
  		}, 0);
  	}
  
  	protected void maybeSendAnnouncement() {
  		synchronized(this) {
  			if(!started) return;
  		}
  		logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
  		if(logMINOR)
  			Logger.minor(this, "maybeSendAnnouncement()");
  		long now = System.currentTimeMillis();
  		if(!node.isOpennetEnabled()) return;
  		if(enoughPeers()) {
  			// Check again in 60 seconds.
  			node.getTicker().queueTimedJob(checker, "Announcement checker", FINAL_DELAY, false, true);
  			return;
  		}
  		synchronized(this) {
  			// Double check after taking the lock.
  			if(enoughPeers()) {
  				// Check again in 60 seconds.
  				node.getTicker().queueTimedJob(checker, "Announcement checker", FINAL_DELAY, false, true);
  				return;
  			}
  			// Second, do we have many announcements running?
  			if(runningAnnouncements > WANT_ANNOUNCEMENTS) {
  				if(logMINOR)
  					Logger.minor(this, "Running announcements already");
  				return;
  			}
  			// In cooling-off period?
  			if(System.currentTimeMillis() < startTime) {
  				if(logMINOR)
  					Logger.minor(this, "In cooling-off period for next "+TimeUtil.formatTime(startTime - System.currentTimeMillis()));
  				return;
  			}
  