Add methods to close the connection.
[xudocci.git] / src / main / java / net / pterodactylus / xdcc / core / Core.java
index 9d5dcb7..5b883db 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.xdcc.core;
 
+import static net.pterodactylus.xdcc.data.Download.FILTER_RUNNING;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -46,8 +48,8 @@ import net.pterodactylus.irc.event.DccDownloadFailed;
 import net.pterodactylus.irc.event.DccDownloadFinished;
 import net.pterodactylus.irc.event.DccSendReceived;
 import net.pterodactylus.irc.event.NicknameChanged;
-import net.pterodactylus.irc.event.PrivateNoticeReceived;
 import net.pterodactylus.irc.event.PrivateMessageReceived;
+import net.pterodactylus.irc.event.PrivateNoticeReceived;
 import net.pterodactylus.irc.util.MessageCleaner;
 import net.pterodactylus.irc.util.RandomNickname;
 import net.pterodactylus.xdcc.core.event.BotAdded;
@@ -66,11 +68,15 @@ import net.pterodactylus.xdcc.data.Pack;
 import net.pterodactylus.xdcc.data.Server;
 
 import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Table;
 import com.google.common.eventbus.EventBus;
@@ -114,7 +120,7 @@ public class Core extends AbstractExecutionThreadService {
        private final Table<Network, String, Bot> networkBots = HashBasedTable.create();
 
        /** The current downloads. */
-       private final Map<String, Download> downloads = Maps.newHashMap();
+       private final Multimap<String, Download> downloads = HashMultimap.create();
 
        /** The current DCC receivers. */
        private final Collection<DccReceiver> dccReceivers = Lists.newArrayList();
@@ -178,12 +184,12 @@ public class Core extends AbstractExecutionThreadService {
        }
 
        /**
-        * Returns the currently active DCC receivers.
+        * Returns all currently running downloads.
         *
-        * @return The currently active DCC receivers
+        * @return All currently running downloads
         */
-       public Collection<DccReceiver> dccReceivers() {
-               return dccReceivers;
+       public Collection<Download> downloads() {
+               return downloads.values();
        }
 
        //
@@ -214,6 +220,25 @@ public class Core extends AbstractExecutionThreadService {
                        return;
                }
 
+               /* check if we are already downloading the file? */
+               if (downloads.containsKey(pack.name())) {
+                       Collection<Download> packDownloads = downloads.get(pack.name());
+                       Collection<Download> runningDownloads = FluentIterable.from(packDownloads).filter(FILTER_RUNNING).toSet();
+                       if (!runningDownloads.isEmpty()) {
+                               Download download = runningDownloads.iterator().next();
+                               eventBus.post(new GenericMessage(String.format("File %s is already downloading from %s (%s).", pack.name(), download.bot().name(), download.bot().network().name())));
+                               return;
+                       }
+                       StringBuilder bots = new StringBuilder();
+                       for (Download download : packDownloads) {
+                               if (bots.length() > 0) {
+                                       bots.append(", ");
+                               }
+                               bots.append(download.bot().name()).append(" (").append(download.bot().network().name()).append(')');
+                       }
+                       eventBus.post(new GenericMessage(String.format("File %s is already requested from %d bots (%s).", pack.name(), packDownloads.size(), bots.toString())));
+               }
+
                Download download = new Download(bot, pack);
                downloads.put(pack.name(), download);
 
@@ -224,6 +249,20 @@ public class Core extends AbstractExecutionThreadService {
                }
        }
 
+       /**
+        * Closes the given connection.
+        *
+        * @param connection
+        *              The connection to close
+        */
+       public void closeConnection(Connection connection) {
+               try {
+                       connection.close();
+               } catch (IOException ioe1) {
+                       /* TODO */
+               }
+       }
+
        //
        // ABSTRACTIDLESERVICE METHODS
        //
@@ -509,7 +548,7 @@ public class Core extends AbstractExecutionThreadService {
                Bot bot;
                synchronized (networkBots) {
                        if (!networkBots.contains(network.get(), channelMessageReceived.source().nick().get())) {
-                               bot = new Bot(network.get()).name(channelMessageReceived.source().nick().get());
+                               bot = new Bot(network.get()channelMessageReceived.source().nick().get());
                                networkBots.put(network.get(), channelMessageReceived.source().nick().get(), bot);
                                eventBus.post(new BotAdded(bot));
                        } else {
@@ -546,7 +585,7 @@ public class Core extends AbstractExecutionThreadService {
                        return;
                }
 
-               eventBus.post(new GenericMessage(String.format("Notice from %s (%s): %s", privateNoticeReceived.reply().source(), network.get(), privateNoticeReceived.text())));
+               eventBus.post(new GenericMessage(String.format("Notice from %s (%s): %s", privateNoticeReceived.reply().source().get(), network.get(), privateNoticeReceived.text())));
        }
 
        /**
@@ -556,18 +595,41 @@ public class Core extends AbstractExecutionThreadService {
         *              The DCC SEND event
         */
        @Subscribe
-       public void dccSendReceived(DccSendReceived dccSendReceived) {
-               Optional<Network> network = getNetwork(dccSendReceived.connection());
+       public void dccSendReceived(final DccSendReceived dccSendReceived) {
+               final Optional<Network> network = getNetwork(dccSendReceived.connection());
                if (!network.isPresent()) {
                        return;
                }
 
-               Download download = downloads.get(dccSendReceived.filename());
-               if (download == null) {
+               Collection<Download> packDownloads = downloads.get(dccSendReceived.filename());
+               if (packDownloads.isEmpty()) {
                        /* unknown download, ignore. */
                        return;
                }
 
+               /* check if it’s already downloading. */
+               Collection<Download> runningDownloads = FluentIterable.from(packDownloads).filter(FILTER_RUNNING).toSet();
+               if (!runningDownloads.isEmpty()) {
+                       eventBus.post(new GenericMessage(String.format("Ignoring offer for %s, it’s already being downloaded.", dccSendReceived.filename())));
+                       return;
+               }
+
+               /* locate the correct download. */
+               Collection<Download> requestedDownload = FluentIterable.from(packDownloads).filter(new Predicate<Download>() {
+
+                       @Override
+                       public boolean apply(Download download) {
+                               return download.bot().network().equals(network.get()) && download.bot().name().equalsIgnoreCase(dccSendReceived.source().nick().get());
+                       }
+               }).toSet();
+
+               /* we did not request this download. */
+               if (requestedDownload.isEmpty()) {
+                       return;
+               }
+
+               Download download = requestedDownload.iterator().next();
+
                /* check if the file already exists. */
                File outputFile = new File(temporaryDirectory, dccSendReceived.filename());
                if (outputFile.exists()) {
@@ -583,7 +645,7 @@ public class Core extends AbstractExecutionThreadService {
                                }
 
                                /* remove download. */
-                               downloads.remove(download.pack().name());
+                               downloads.removeAll(download.pack().name());
                                return;
                        }
 
@@ -612,25 +674,48 @@ public class Core extends AbstractExecutionThreadService {
        }
 
        @Subscribe
-       public void dccAcceptReceived(DccAcceptReceived dccAcceptReceived) {
-               Optional<Network> network = getNetwork(dccAcceptReceived.connection());
+       public void dccAcceptReceived(final DccAcceptReceived dccAcceptReceived) {
+               final Optional<Network> network = getNetwork(dccAcceptReceived.connection());
                if (!network.isPresent()) {
                        return;
                }
 
-               Download download = downloads.get(dccAcceptReceived.filename());
-               if (download == null) {
+               Collection<Download> packDownloads = downloads.get(dccAcceptReceived.filename());
+               if (packDownloads.isEmpty()) {
                        /* unknown download, ignore. */
                        return;
                }
 
+               /* check if it’s already downloading. */
+               Collection<Download> runningDownloads = FluentIterable.from(packDownloads).filter(FILTER_RUNNING).toSet();
+               if (!runningDownloads.isEmpty()) {
+                       eventBus.post(new GenericMessage(String.format("Ignoring offer for %s, it’s already being downloaded.", dccAcceptReceived.filename())));
+                       return;
+               }
+
+               /* locate the correct download. */
+               Collection<Download> requestedDownload = FluentIterable.from(packDownloads).filter(new Predicate<Download>() {
+
+                       @Override
+                       public boolean apply(Download download) {
+                               return download.bot().network().equals(network.get()) && download.bot().name().equalsIgnoreCase(dccAcceptReceived.source().nick().get());
+                       }
+               }).toSet();
+
+               /* we did not request this download. */
+               if (requestedDownload.isEmpty()) {
+                       return;
+               }
+
+               Download download = requestedDownload.iterator().next();
+
                try {
                        File outputFile = new File(temporaryDirectory, dccAcceptReceived.filename());
                        if (outputFile.length() != dccAcceptReceived.position()) {
                                eventBus.post(new GenericError(String.format("Download %s from %s does not start at the right position!")));
                                logger.log(Level.WARNING, String.format("Download %s from %s: have %d bytes but wants to resume from %d!", dccAcceptReceived.filename(), dccAcceptReceived.source(), outputFile.length(), dccAcceptReceived.position()));
 
-                               downloads.remove(download.pack().name());
+                               downloads.removeAll(download.pack().name());
                                return;
                        }
                        OutputStream outputStream = new FileOutputStream(outputFile, true);
@@ -652,11 +737,15 @@ public class Core extends AbstractExecutionThreadService {
         */
        @Subscribe
        public void dccDownloadFinished(DccDownloadFinished dccDownloadFinished) {
-               Download download = downloads.get(dccDownloadFinished.dccReceiver().filename());
-               if (download == null) {
-                       /* probably shouldn’t happen. */
+
+               /* locate the correct download. */
+               Collection<Download> requestedDownload = FluentIterable.from(downloads.get(dccDownloadFinished.dccReceiver().filename())).filter(FILTER_RUNNING).toSet();
+               if (requestedDownload.isEmpty()) {
+                       /* this seems wrong. */
+                       logger.warning("Download finished but could not be located.");
                        return;
                }
+               Download download = requestedDownload.iterator().next();
 
                try {
                        download.outputStream().close();
@@ -664,7 +753,7 @@ public class Core extends AbstractExecutionThreadService {
                        file.renameTo(new File(finalDirectory, download.pack().name()));
                        eventBus.post(new DownloadFinished(download));
                        dccReceivers.remove(dccDownloadFinished.dccReceiver());
-                       downloads.remove(download.pack().name());
+                       downloads.removeAll(download.pack().name());
                } catch (IOException ioe1) {
                        /* TODO - handle all the errors. */
                        logger.log(Level.WARNING, String.format("Could not move file %s to directory %s.", download.filename(), finalDirectory), ioe1);
@@ -679,17 +768,21 @@ public class Core extends AbstractExecutionThreadService {
         */
        @Subscribe
        public void dccDownloadFailed(DccDownloadFailed dccDownloadFailed) {
-               Download download = downloads.get(dccDownloadFailed.dccReceiver().filename());
-               if (download == null) {
-                       /* probably shouldn’t happen. */
+
+               /* locate the correct download. */
+               Collection<Download> requestedDownload = FluentIterable.from(downloads.get(dccDownloadFailed.dccReceiver().filename())).filter(FILTER_RUNNING).toSet();
+               if (requestedDownload.isEmpty()) {
+                       /* this seems wrong. */
+                       logger.warning("Download finished but could not be located.");
                        return;
                }
+               Download download = requestedDownload.iterator().next();
 
                try {
                        Closeables.close(download.outputStream(), true);
                        eventBus.post(new DownloadFailed(download));
                        dccReceivers.remove(dccDownloadFailed.dccReceiver());
-                       downloads.remove(download.pack().name());
+                       downloads.removeAll(download.pack().name());
                } catch (IOException ioe1) {
                        /* swallow silently. */
                }