+ protected void run() throws Exception {
+ while (isRunning()) {
+
+ Set<Channel> missingChannels = new HashSet<>();
+ for (Channel channel : channels) {
+ if (joinedChannels.contains(channel) || channelsBeingJoined.contains(channel)) {
+ continue;
+ }
+ if (channelBanManager.isBanned(channel)) {
+ continue;
+ }
+ if (!networkConnections.containsKey(channel.network()) || networkConnections.get(channel.network()).established()) {
+ missingChannels.add(channel);
+ }
+ }
+ Set<Network> missingNetworks = missingChannels.stream()
+ .map(Channel::network)
+ .distinct()
+ .filter((network) -> !networkConnections.containsKey(network))
+ .collect(Collectors.toSet());
+
+ if (missingNetworks.isEmpty()) {
+ if (!missingChannels.isEmpty()) {
+ for (Channel missingChannel : missingChannels) {
+ Network network = missingChannel.network();
+ eventBus.post(new GenericMessage(String.format("Trying to join %s on %s...", missingChannel.name(), network)));
+ try {
+ channelsBeingJoined.add(missingChannel);
+ networkConnections.get(network).joinChannel(missingChannel.name());
+ } catch (IOException ioe1) {
+ logger.warn(String.format("Could not join %s on %s!", missingChannel.name(), network.name()), ioe1);
+ }
+ }
+ } else {
+ synchronized (syncObject) {
+ try {
+ syncObject.wait(TimeUnit.MINUTES.toMillis(1));
+ } catch (InterruptedException ie1) {
+ /* ignore. */
+ }
+ }
+ }
+ continue;
+ }
+
+ Map<Long, Network> timesForNextConnects = new TreeMap<>(missingNetworks.stream()
+ .collect(Collectors.toMap(connectionBackoff::getConnectionTime, Function.identity(), (network, ignore) -> network)));
+
+ Optional<Entry<Long, Network>> firstNetwork = Optional.fromNullable(timesForNextConnects.entrySet().stream().findFirst().orElse(null));
+ if (!firstNetwork.isPresent()) {
+ continue;
+ }
+ if (firstNetwork.get().getKey() > System.currentTimeMillis()) {
+ eventBus.post(new GenericMessage(String.format("Will connect to %2$s at %1$tH:%1$tM...", firstNetwork.get().getKey(), firstNetwork.get().getValue().name())));
+ synchronized (syncObject) {
+ try {
+ syncObject.wait(firstNetwork.get().getKey() - System.currentTimeMillis());
+ } catch (InterruptedException ie1) {
+ /* ignore. */
+ }
+ }
+ if (!isRunning()) {
+ break;
+ }
+ if (firstNetwork.get().getKey() > System.currentTimeMillis()) {
+ continue;
+ }
+ }
+
+ connectNetwork(firstNetwork.get().getValue());
+ }
+ }
+
+ @Override
+ protected void triggerShutdown() {
+ synchronized (syncObject) {
+ syncObject.notifyAll();
+ }
+ }
+
+ //
+ // PRIVATE METHODS
+ //
+
+ /**
+ * Starts a new connection for the given network if no such connection exists
+ * already.
+ *
+ * @param network
+ * The network to connect to
+ */
+ private void connectNetwork(Network network) {
+ if (!networkConnections.containsKey(network)) {
+ /* select a random server. */
+ List<Server> servers = Lists.newArrayList(network.servers());
+ if (servers.isEmpty()) {
+ eventBus.post(new GenericError(String.format("Network %s does not have any servers.", network.name())));
+ return;
+ }
+ Server server = servers.get((int) (Math.random() * servers.size()));
+ eventBus.post(new GenericMessage(String.format("Connecting to %s on %s...", network.name(), server.hostname())));
+ Connection connection = connectionFactory.createConnection(server.hostname(),
+ server.unencryptedPorts().iterator().next());
+ connection.username(RandomNickname.get()).realName(RandomNickname.get());
+ networkConnections.put(network, connection);
+ connection.open();
+ }
+ }
+
+ /**
+ * Removes the given connection and all its channels and bots.
+ *
+ * @param connection
+ * The connection to remove
+ */
+ private void removeConnection(Connection connection) {
+ logger.debug(String.format("Removing Connection %s...", connection));
+ Optional<Network> network = getNetwork(connection);
+ if (!network.isPresent()) {
+ logger.debug(String.format("Connection %s did not belong to any network.", connection));
+ return;
+ }
+ logger.debug(String.format("Connection %s belongs to network %s.", connection, network.get()));
+ networkConnections.remove(network.get());
+
+ /* find all channels that need to be removed. */
+ for (Collection<Channel> channels : ImmutableList.of(joinedChannels, extraChannels)) {
+ for (Iterator<Channel> channelIterator = channels.iterator(); channelIterator.hasNext(); ) {
+ Channel joinedChannel = channelIterator.next();
+ if (!joinedChannel.network().equals(network.get())) {
+ continue;
+ }
+ logger.debug(String.format("Channel %s will be removed.", joinedChannel));
+ channelIterator.remove();
+ }
+ }
+
+ /* now remove all bots for that network. */
+ Map<String, Bot> bots = networkBots.row(network.get());
+ int botCount = bots.size();
+ int packCount = 0;
+ for (Bot bot : bots.values()) {
+ packCount += bot.packs().size();
+ }
+ bots.clear();
+ eventBus.post(new GenericMessage(String.format("Network %s disconnected, %d bots removed, %d packs removed.", network.get().name(), botCount, packCount)));