--- /dev/null
+/*
+ * XdccDownloader - AbortDownloadCommand.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.ui.stdin;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import net.pterodactylus.xdcc.core.Core;
+
+import com.google.common.primitives.Ints;
+
+/**
+ * Command that will abort a running download.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ * @see State#getLastDownloads()
+ */
+public class AbortDownloadCommand implements Command {
+
+ /** The core to operate on. */
+ private final Core core;
+
+ /**
+ * Creates a new abort download command.
+ *
+ * @param core
+ * The core to operate on
+ */
+ public AbortDownloadCommand(Core core) {
+ this.core = core;
+ }
+
+ //
+ // COMMAND METHODS
+ //
+
+ @Override
+ public String getName() {
+ return "abort";
+ }
+
+ @Override
+ public Collection<String> getAliases() {
+ return Arrays.asList("cancel");
+ }
+
+ @Override
+ public State execute(State state, List<String> parameters, Writer outputWriter) throws IOException {
+ Integer index = Ints.tryParse(parameters.get(0));
+ if ((index != null) && (index < state.getLastDownloads().size())) {
+ core.cancelDownload(state.getLastDownloads().get(index).bot(), state.getLastDownloads().get(index).pack());
+ }
+ return state;
+ }
+
+}
--- /dev/null
+/*
+ * XdccDownloader - Command.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.ui.stdin;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.List;
+
+import com.google.common.base.Function;
+
+/**
+ * A command is executed by the {@link CommandReader}. It receives the current
+ * state of the command reader and can return a changed state, depending on the
+ * given parameters.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface Command {
+
+ /** Converts a command into its name. */
+ public static final Function<Command, String> TO_NAME = new Function<Command, String>() {
+ @Override
+ public String apply(Command command) {
+ return command.getName();
+ }
+ };
+
+ /** Converts a command into its aliases. */
+ public static final Function<Command, Collection<String>> TO_ALIASES = new Function<Command, Collection<String>>() {
+ @Override
+ public Collection<String> apply(Command command) {
+ return command.getAliases();
+ }
+ };
+
+ /**
+ * Returns the name of this command.
+ *
+ * @return The name of this command
+ */
+ public String getName();
+
+ /**
+ * Returns possible aliases for this command.
+ *
+ * @return Possible aliases for this command
+ */
+ public Collection<String> getAliases();
+
+ /**
+ * Executes this command.
+ *
+ * @param state
+ * The current state of the command reader
+ * @param parameters
+ * The parameters given on the command line
+ * @param outputWriter
+ * The output writer to write the output of the command to
+ * @return The new state of the command reader (which may be the given state if
+ * the command does not need to modify the state)
+ * @throws IOException
+ * if an I/O error occurs
+ */
+ public State execute(State state, List<String> parameters, Writer outputWriter) throws IOException;
+
+}
package net.pterodactylus.xdcc.ui.stdin;
-import static com.google.common.collect.Lists.newArrayList;
-import static net.pterodactylus.xdcc.data.Download.BY_NAME;
-import static net.pterodactylus.xdcc.data.Download.BY_RUNNING;
+import static com.google.common.collect.FluentIterable.from;
+import static java.util.Arrays.asList;
+import static net.pterodactylus.xdcc.ui.stdin.Command.TO_NAME;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
-import java.util.Set;
-import java.util.regex.Pattern;
-import net.pterodactylus.irc.Connection;
-import net.pterodactylus.irc.DccReceiver;
import net.pterodactylus.irc.util.MessageCleaner;
import net.pterodactylus.xdcc.core.Core;
import net.pterodactylus.xdcc.core.event.DownloadFailed;
import net.pterodactylus.xdcc.core.event.DownloadStarted;
import net.pterodactylus.xdcc.core.event.GenericMessage;
import net.pterodactylus.xdcc.core.event.MessageReceived;
-import net.pterodactylus.xdcc.data.Bot;
import net.pterodactylus.xdcc.data.Download;
-import net.pterodactylus.xdcc.data.Pack;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ComparisonChain;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
-import com.google.common.collect.Sets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.Subscribe;
-import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.AbstractExecutionThreadService;
/**
*/
public class CommandReader extends AbstractExecutionThreadService {
- /** The core being controlled. */
- private final Core core;
+ /** The commands to process. */
+ private final Collection<Command> commands;
/** The reader to read commands from. */
private final BufferedReader reader;
* The write to write results to
*/
public CommandReader(Core core, Reader reader, Writer writer) {
- this.core = core;
this.reader = new BufferedReader(reader);
this.writer = writer;
+
+ /* initialize commands. */
+ ImmutableList.Builder<Command> commandBuilder = ImmutableList.builder();
+ commandBuilder.add(new ListDownloadsCommand(core));
+ commandBuilder.add(new SearchCommand(core));
+ commandBuilder.add(new DownloadCommand(core));
+ commandBuilder.add(new StatsCommand(core));
+ commandBuilder.add(new ListConnectionsCommand(core));
+ commandBuilder.add(new AbortDownloadCommand(core));
+ commandBuilder.add(new DisconnectCommand(core));
+ commands = commandBuilder.build();
}
//
protected void run() throws Exception {
String lastLine = "";
String line;
- State state = new State();
+ net.pterodactylus.xdcc.ui.stdin.State state = new net.pterodactylus.xdcc.ui.stdin.State();
while ((line = reader.readLine()) != null) {
+ line = line.trim();
if (line.equals("")) {
line = lastLine;
}
String[] words = line.split(" +");
- if (words[0].equalsIgnoreCase("search")) {
- List<Result> lastResult = newArrayList();
- for (Bot bot : newArrayList(core.bots())) {
- for (Pack pack : newArrayList(bot)) {
- boolean found = true;
- for (int wordIndex = 1; wordIndex < words.length; ++wordIndex) {
- if (words[wordIndex].startsWith("-") && pack.name().toLowerCase().contains(words[wordIndex].toLowerCase().substring(1))) {
- found = false;
- break;
- }
- if (!words[wordIndex].startsWith("-") && !pack.name().toLowerCase().contains(words[wordIndex].toLowerCase())) {
- found = false;
- break;
- }
- }
- if (found) {
- lastResult.add(new Result(bot, pack));
- }
- }
- }
- Collections.sort(lastResult);
- int counter = 0;
- for (Result result : lastResult) {
- writeLine(String.format("[%d] %s (%s) from %s (#%s) on %s", counter++, result.pack().name(), result.pack().size(), result.bot().name(), result.pack().id(), result.bot().network().name()));
- }
- writeLine("End of Search.");
- state = state.setLastResults(lastResult);
- } else if (words[0].equalsIgnoreCase("dcc")) {
- int counter = 0;
- List<Download> downloads = newArrayList(FluentIterable.from(core.downloads()).toSortedList(Ordering.from(BY_NAME).compound(BY_RUNNING)));
- for (Download download : downloads) {
- DccReceiver dccReceiver = download.dccReceiver();
- if (dccReceiver == null) {
- /* download has not even started. */
- writer.write(String.format("[%d] %s requested from %s (not started yet)\n", counter++, download.pack().name(), download.bot().name()));
- continue;
- }
- writer.write(String.format("[%d] %s from %s (%s, ", counter++, dccReceiver.filename(), download.bot().name(), f(dccReceiver.size())));
- if (dccReceiver.isRunning()) {
- writer.write(String.format("%.1f%%, %s/s, %s", dccReceiver.progress() * 100.0 / dccReceiver.size(), f(dccReceiver.currentRate()), getTimeLeft(dccReceiver)));
- } else {
- if (dccReceiver.progress() >= dccReceiver.size()) {
- writer.write(String.format("complete, %s/s", f(dccReceiver.overallRate())));
- } else {
- writer.write(String.format("aborted at %.1f%%, %s/s", dccReceiver.progress() * 100.0 / dccReceiver.size(), f(dccReceiver.currentRate())));
- }
- }
- writer.write(")\n");
- }
- writeLine("End of DCCs.");
- state = state.setLastDownloads(downloads);
- } else if (words[0].equalsIgnoreCase("get")) {
- Integer index = Ints.tryParse(words[1]);
- if ((index != null) && (index < state.getLastResults().size())) {
- core.fetch(state.getLastResults().get(index).bot(), state.getLastResults().get(index).pack());
- }
- } else if (words[0].equalsIgnoreCase("cancel")) {
- Integer index = Ints.tryParse(words[1]);
- if ((index != null) && (index < state.getLastDownloads().size())) {
- core.cancelDownload(state.getLastDownloads().get(index).bot(), state.getLastDownloads().get(index).pack());
- }
- } else if (words[0].equalsIgnoreCase("stats")) {
- int configuredChannelsCount = core.channels().size();
- int joinedChannelsCount = core.joinedChannels().size();
- int extraChannelsCount = core.extraChannels().size();
- Collection<Bot> bots = core.bots();
- Set<String> packNames = Sets.newHashSet();
- int packsCount = 0;
- for (Bot bot : bots) {
- packsCount += bot.packs().size();
- for (Pack pack : bot) {
- packNames.add(pack.name());
- }
- }
-
- writeLine(String.format("%d channels (%d joined, %d extra), %d bots offering %d packs (%d unique).", configuredChannelsCount, joinedChannelsCount, extraChannelsCount, bots.size(), packsCount, packNames.size()));
- } else if (words[0].equalsIgnoreCase("connections")) {
- List<Connection> lastConnections = newArrayList();
- int counter = 0;
- for (Connection connection : core.connections()) {
- lastConnections.add(connection);
- writer.write(String.format("[%d] %s:%d, %s/s\n", counter++, connection.hostname(), connection.port(), f(connection.getInputRate())));
- }
- writeLine("End of connections.");
- state = state.setLastConnections(lastConnections);
- } else if (words[0].equalsIgnoreCase("disconnect")) {
- if ((words.length == 1) || ("all".equals(words[1]))) {
- for (Connection connection : state.getLastConnections()) {
- core.closeConnection(connection);
- }
- } else {
- Integer index = Ints.tryParse(words[1]);
- if ((index != null) && (index < state.getLastConnections().size())) {
- core.closeConnection(state.getLastConnections().get(index));
- }
- }
+ String commandName = words[0];
+ Collection<Command> eligibleCommands = findEligibleCommands(commandName);
+ if (eligibleCommands.isEmpty()) {
+ writeLine(String.format("Invalid command: %s, valid: %s.", commandName, Joiner.on(' ').join(from(commands).transform(TO_NAME))));
+ } else if (eligibleCommands.size() > 1) {
+ writeLine(String.format("Commands: %s.", Joiner.on(' ').join(from(eligibleCommands).transform(TO_NAME))));
+ } else {
+ Command command = eligibleCommands.iterator().next();
+ List<String> parameters = from(asList(words)).skip(1).toList();
+ state = command.execute(state, parameters, writer);
}
lastLine = line;
// PRIVATE METHODS
//
+ private Collection<Command> findEligibleCommands(String name) {
+ ImmutableSet.Builder<Command> eligibleCommands = ImmutableSet.builder();
+ for (Command command : commands) {
+ if (command.getName().toLowerCase().startsWith(name.toLowerCase())) {
+ eligibleCommands.add(command);
+ }
+ for (String alias : command.getAliases()) {
+ if (alias.toLowerCase().startsWith(name.toLowerCase())) {
+ eligibleCommands.add(command);
+ }
+ }
+ }
+ return eligibleCommands.build();
+ }
+
/**
* Writes the given line followed by an LF to the {@link #writer}.
*
* The number to convert
* @return The converted number
*/
- private static String f(long number) {
+ static String f(long number) {
if (number >= (1 << 30)) {
return String.format("%.1fG", number / (double) (1 << 30));
}
return String.format("%dB", number);
}
- /**
- * Returns the estimated time left for the given transfer.
- *
- * @param dccReceiver
- * The DCC receiver to get the time left for
- * @return The time left for the transfer, or “unknown” if the time can not be
- * estimated
- */
- private static String getTimeLeft(DccReceiver dccReceiver) {
- if ((dccReceiver.size() == -1) || (dccReceiver.currentRate() == 0)) {
- return "unknown";
- }
- long secondsLeft = (dccReceiver.size() - dccReceiver.progress()) / dccReceiver.currentRate();
- if (secondsLeft > 3600) {
- return String.format("%02d:%02d:%02d", secondsLeft / 3600, (secondsLeft / 60) % 60, secondsLeft % 60);
- }
- return String.format("%02d:%02d", (secondsLeft / 60) % 60, secondsLeft % 60);
- }
-
- /** Container for result information. */
- private static class Result implements Comparable<Result> {
-
- /** {@link Predicate} that matches {@link Result}s that contain an archive. */
- private static final Predicate<Result> isArchive = new Predicate<Result>() {
-
- /** All suffixes that are recognized as archives. */
- private final List<String> archiveSuffixes = Arrays.asList("rar", "tar", "zip", "tar.gz", "tar.bz2", "tar.lzma", "7z");
-
- @Override
- public boolean apply(Result result) {
- for (String suffix : archiveSuffixes) {
- if (result.pack().name().toLowerCase().endsWith(suffix)) {
- return true;
- }
- }
- return false;
- }
- };
-
- /**
- * {@link Comparator} for {@link Result}s that sorts archives (as per {@link
- * #isArchive} to the back of the list.
- */
- private static final Comparator<Result> packArchiveComparator = new Comparator<Result>() {
- @Override
- public int compare(Result leftResult, Result rightResult) {
- if (isArchive.apply(leftResult) && !isArchive.apply(rightResult)) {
- return 1;
- }
- if (!isArchive.apply(leftResult) && isArchive.apply(rightResult)) {
- return -1;
- }
- return 0;
- }
- };
-
- /**
- * {@link Comparator} for bot nicknames. It comprises different strategies:
- * one name pattern is preferred (and thus listed first), one pattern is
- * disliked (and thus listed last), the rest is sorted alphabetically.
- */
- private static final Comparator<Result> botNameComparator = new Comparator<Result>() {
-
- /** Regular expression pattern for preferred names. */
- private final Pattern preferredNames = Pattern.compile("(?i)[^\\w]EUR?[^\\w]");
-
- /** Regular expression pattern for disliked names. */
- private final Pattern dislikedNames = Pattern.compile("(?i)[^\\w]USA?[^\\w]");
-
- @Override
- public int compare(Result leftResult, Result rightResult) {
- String leftBotName = leftResult.bot().name();
- String rightBotName = rightResult.bot().name();
- /* preferred names to the front! */
- if (preferredNames.matcher(leftBotName).find() && !preferredNames.matcher(rightBotName).find()) {
- return -1;
- }
- if (preferredNames.matcher(rightBotName).find() && !preferredNames.matcher(leftBotName).find()) {
- return 1;
- }
- /* disliked names to the back. */
- if (dislikedNames.matcher(leftBotName).find() && !dislikedNames.matcher(rightBotName).find()) {
- return 1;
- }
- if (dislikedNames.matcher(rightBotName).find() && !dislikedNames.matcher(leftBotName).find()) {
- return -1;
- }
- return 0;
- }
- };
-
- /**
- * {@link Comparator} for {@link Result}s that sorts them by the name of the
- * {@link Pack}.
- */
- private static final Comparator<Result> packNameComparator = new Comparator<Result>() {
- @Override
- public int compare(Result leftResult, Result rightResult) {
- return leftResult.pack().name().compareToIgnoreCase(rightResult.pack().name());
- }
- };
-
- /** The bot carrying the pack. */
- private final Bot bot;
-
- /** The pack. */
- private final Pack pack;
-
- /**
- * Creates a new result.
- *
- * @param bot
- * The bot carrying the pack
- * @param pack
- * The pack
- */
- private Result(Bot bot, Pack pack) {
- this.bot = bot;
- this.pack = pack;
- }
-
- //
- // ACCESSORS
- //
-
- /**
- * Returns the bot carrying the pack.
- *
- * @return The bot carrying the pack
- */
- public Bot bot() {
- return bot;
- }
-
- /**
- * Returns the pack.
- *
- * @return The pack
- */
- public Pack pack() {
- return pack;
- }
-
- //
- // COMPARABLE METHODS
- //
-
- @Override
- public int compareTo(Result result) {
- return ComparisonChain.start()
- .compare(this, result, packArchiveComparator)
- .compare(this, result, botNameComparator)
- .compare(this, result, packNameComparator).result();
- }
-
- }
-
- /**
- * Container for the current state of the command reader.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
- private static class State {
-
- /** The last connections displayed. */
- private final List<Connection> lastConnections;
-
- /** The last results displayed. */
- private final List<Result> lastResults;
-
- /** The last downloads displayed. */
- private final List<Download> lastDownloads;
-
- /** Creates a new empty state. */
- public State() {
- this(Lists.<Connection>newArrayList(), Lists.<Result>newArrayList(), Lists.<Download>newArrayList());
- }
-
- /**
- * Creates a new state.
- *
- * @param lastConnections
- * The last connections
- * @param lastResults
- * The last results
- * @param lastDownloads
- * The last downloads
- */
- private State(List<Connection> lastConnections, List<Result> lastResults, List<Download> lastDownloads) {
- this.lastConnections = lastConnections;
- this.lastResults = lastResults;
- this.lastDownloads = lastDownloads;
- }
-
- //
- // ACCESSORS
- //
-
- /**
- * Returns the last connections displayed.
- *
- * @return The last connections displayed
- */
- public List<Connection> getLastConnections() {
- return lastConnections;
- }
-
- /**
- * Returns the last results displayed.
- *
- * @return The last results displayed
- */
- public List<Result> getLastResults() {
- return lastResults;
- }
-
- /**
- * Returns the last downloads displayed.
- *
- * @return The last downloads displayed
- */
- public List<Download> getLastDownloads() {
- return lastDownloads;
- }
-
- //
- // MUTATORS
- //
-
- /**
- * Returns a new state with the given last connections and the last downloads
- * and results of this state.
- *
- * @param lastConnections
- * The new last connections displayed
- * @return The new state
- */
- public State setLastConnections(List<Connection> lastConnections) {
- return new State(lastConnections, lastResults, lastDownloads);
- }
-
- /**
- * Returns a new state with the given last results and the last downloads and
- * connections of this state.
- *
- * @param lastResults
- * The new last results displayed
- * @return The new state
- */
- public State setLastResults(List<Result> lastResults) {
- return new State(lastConnections, lastResults, lastDownloads);
- }
-
- /**
- * Returns a new state with the given last downloads and the last connections
- * and results of this state.
- *
- * @param lastDownloads
- * The new last downloads displayed
- * @return The new state
- */
- public State setLastDownloads(List<Download> lastDownloads) {
- return new State(lastConnections, lastResults, lastDownloads);
- }
-
- }
-
}
--- /dev/null
+/*
+ * XdccDownloader - DisconnectCommand.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.ui.stdin;
+
+import static java.util.Arrays.asList;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.List;
+
+import net.pterodactylus.irc.Connection;
+import net.pterodactylus.xdcc.core.Core;
+
+import com.google.common.primitives.Ints;
+
+/**
+ * Command that will disconnect a {@link Connection}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ * @see State#getLastConnections()
+ */
+public class DisconnectCommand implements Command {
+
+ /** The core to operate on. */
+ private final Core core;
+
+ /**
+ * Creates a new disconnect command.
+ *
+ * @param core
+ * The core to operate on
+ */
+ public DisconnectCommand(Core core) {
+ this.core = core;
+ }
+
+ //
+ // COMMAND METHODS
+ //
+
+ @Override
+ public String getName() {
+ return "disconnect";
+ }
+
+ @Override
+ public Collection<String> getAliases() {
+ return asList("close");
+ }
+
+ @Override
+ public State execute(State state, List<String> parameters, Writer outputWriter) throws IOException {
+ if ((parameters.isEmpty() || ("all".equals(parameters.get(0))))) {
+ for (Connection connection : state.getLastConnections()) {
+ core.closeConnection(connection);
+ }
+ } else {
+ Integer index = Ints.tryParse(parameters.get(0));
+ if ((index != null) && (index < state.getLastConnections().size())) {
+ core.closeConnection(state.getLastConnections().get(index));
+ }
+ }
+ return state;
+ }
+
+}
--- /dev/null
+/*
+ * XdccDownloader - DownloadCommand.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.ui.stdin;
+
+import static java.util.Arrays.asList;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.List;
+
+import net.pterodactylus.xdcc.core.Core;
+
+import com.google.common.primitives.Ints;
+
+/**
+ * Command that requests a download from a bot.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ * @see State#getLastResults()
+ */
+public class DownloadCommand implements Command {
+
+ /** The core to operate on. */
+ private final Core core;
+
+ /**
+ * Creates a new download command.
+ *
+ * @param core
+ * The core to operate on
+ */
+ public DownloadCommand(Core core) {
+ this.core = core;
+ }
+
+ //
+ // COMMAND METHODS
+ //
+
+ @Override
+ public String getName() {
+ return "download";
+ }
+
+ @Override
+ public Collection<String> getAliases() {
+ return asList("get");
+ }
+
+ @Override
+ public State execute(State state, List<String> parameters, Writer outputWriter) throws IOException {
+ Integer index = Ints.tryParse(parameters.get(0));
+ if ((index != null) && (index < state.getLastResults().size())) {
+ core.fetch(state.getLastResults().get(index).bot(), state.getLastResults().get(index).pack());
+ }
+ return state;
+ }
+
+}
--- /dev/null
+/*
+ * XdccDownloader - ListConnectionsCommand.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.ui.stdin;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Collections.emptyList;
+import static net.pterodactylus.xdcc.ui.stdin.CommandReader.f;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.List;
+
+import net.pterodactylus.irc.Connection;
+import net.pterodactylus.xdcc.core.Core;
+
+/**
+ * Command that will list all current connectios.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ListConnectionsCommand implements Command {
+
+ /** The core to operate on. */
+ private final Core core;
+
+ /**
+ * Creates a new list connections command.
+ *
+ * @param core
+ * The core to operate on
+ */
+ public ListConnectionsCommand(Core core) {
+ this.core = core;
+ }
+
+ //
+ // COMMAND METHODS
+ //
+
+ @Override
+ public String getName() {
+ return "connections";
+ }
+
+ @Override
+ public Collection<String> getAliases() {
+ return emptyList();
+ }
+
+ @Override
+ public State execute(State state, List<String> parameters, Writer outputWriter) throws IOException {
+ List<Connection> lastConnections = newArrayList();
+ int counter = 0;
+ for (Connection connection : core.connections()) {
+ lastConnections.add(connection);
+ outputWriter.write(String.format("[%d] %s:%d, %s/s\n", counter++, connection.hostname(), connection.port(), f(connection.getInputRate())));
+ }
+ outputWriter.write("End of connections.\n");
+ outputWriter.flush();
+ return state.setLastConnections(lastConnections);
+ }
+
+}
--- /dev/null
+/*
+ * XdccDownloader - ListDownloadsCommand.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.ui.stdin;
+
+import static com.google.common.collect.FluentIterable.from;
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Arrays.asList;
+import static net.pterodactylus.xdcc.data.Download.BY_NAME;
+import static net.pterodactylus.xdcc.data.Download.BY_RUNNING;
+import static net.pterodactylus.xdcc.ui.stdin.CommandReader.f;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.List;
+
+import net.pterodactylus.irc.DccReceiver;
+import net.pterodactylus.xdcc.core.Core;
+import net.pterodactylus.xdcc.data.Download;
+
+import com.google.common.collect.Ordering;
+
+/**
+ * Command that will list all current downloads.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ListDownloadsCommand implements Command {
+
+ /** The core to operate on. */
+ private final Core core;
+
+ /**
+ * Creates a new list downloads command.
+ *
+ * @param core
+ * The core to operate on
+ */
+ public ListDownloadsCommand(Core core) {
+ this.core = core;
+ }
+
+ //
+ // COMMAND METHODS
+ //
+
+ @Override
+ public String getName() {
+ return "list";
+ }
+
+ @Override
+ public Collection<String> getAliases() {
+ return asList("dcc");
+ }
+
+ @Override
+ public State execute(State state, List<String> parameters, Writer outputWriter) throws IOException {
+ int counter = 0;
+ List<Download> downloads = newArrayList(from(core.downloads()).toSortedList(Ordering.from(BY_NAME).compound(BY_RUNNING)));
+ for (Download download : downloads) {
+ DccReceiver dccReceiver = download.dccReceiver();
+ if (dccReceiver == null) {
+ /* download has not even started. */
+ outputWriter.write(String.format("[%d] %s requested from %s (not started yet)\n", counter++, download.pack().name(), download.bot().name()));
+ continue;
+ }
+ outputWriter.write(String.format("[%d] %s from %s (%s, ", counter++, dccReceiver.filename(), download.bot().name(), f(dccReceiver.size())));
+ if (dccReceiver.isRunning()) {
+ outputWriter.write(String.format("%.1f%%, %s/s, %s", dccReceiver.progress() * 100.0 / dccReceiver.size(), f(dccReceiver.currentRate()), getTimeLeft(dccReceiver)));
+ } else {
+ if (dccReceiver.progress() >= dccReceiver.size()) {
+ outputWriter.write(String.format("complete, %s/s", f(dccReceiver.overallRate())));
+ } else {
+ outputWriter.write(String.format("aborted at %.1f%%, %s/s", dccReceiver.progress() * 100.0 / dccReceiver.size(), f(dccReceiver.currentRate())));
+ }
+ }
+ outputWriter.write(")\n");
+ }
+ outputWriter.write("End of DCCs.\n");
+ outputWriter.flush();
+ return state.setLastDownloads(downloads);
+ }
+
+ //
+ // PRIVATE METHODS
+ //
+
+ /**
+ * Returns the estimated time left for the given transfer.
+ *
+ * @param dccReceiver
+ * The DCC receiver to get the time left for
+ * @return The time left for the transfer, or “unknown” if the time can not be
+ * estimated
+ */
+ private static String getTimeLeft(DccReceiver dccReceiver) {
+ if ((dccReceiver.size() == -1) || (dccReceiver.currentRate() == 0)) {
+ return "unknown";
+ }
+ long secondsLeft = (dccReceiver.size() - dccReceiver.progress()) / dccReceiver.currentRate();
+ if (secondsLeft > 3600) {
+ return String.format("%02d:%02d:%02d", secondsLeft / 3600, (secondsLeft / 60) % 60, secondsLeft % 60);
+ }
+ return String.format("%02d:%02d", (secondsLeft / 60) % 60, secondsLeft % 60);
+ }
+
+}
--- /dev/null
+/*
+ * XdccDownloader - Result.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.ui.stdin;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import net.pterodactylus.xdcc.data.Bot;
+import net.pterodactylus.xdcc.data.Pack;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ComparisonChain;
+
+/**
+ * Container for result information.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class Result implements Comparable<Result> {
+
+ /** {@link Predicate} that matches {@link Result}s that contain an archive. */
+ private static final Predicate<Result> isArchive = new Predicate<Result>() {
+
+ /** All suffixes that are recognized as archives. */
+ private final List<String> archiveSuffixes = Arrays.asList("rar", "tar", "zip", "tar.gz", "tar.bz2", "tar.lzma", "7z");
+
+ @Override
+ public boolean apply(Result result) {
+ for (String suffix : archiveSuffixes) {
+ if (result.pack().name().toLowerCase().endsWith(suffix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ /**
+ * {@link Comparator} for {@link Result}s that sorts archives (as per {@link
+ * #isArchive} to the back of the list.
+ */
+ private static final Comparator<Result> packArchiveComparator = new Comparator<Result>() {
+ @Override
+ public int compare(Result leftResult, Result rightResult) {
+ if (isArchive.apply(leftResult) && !isArchive.apply(rightResult)) {
+ return 1;
+ }
+ if (!isArchive.apply(leftResult) && isArchive.apply(rightResult)) {
+ return -1;
+ }
+ return 0;
+ }
+ };
+
+ /**
+ * {@link Comparator} for bot nicknames. It comprises different strategies: one
+ * name pattern is preferred (and thus listed first), one pattern is disliked
+ * (and thus listed last), the rest is sorted alphabetically.
+ */
+ private static final Comparator<Result> botNameComparator = new Comparator<Result>() {
+
+ /** Regular expression pattern for preferred names. */
+ private final Pattern preferredNames = Pattern.compile("(?i)[^\\w]EUR?[^\\w]");
+
+ /** Regular expression pattern for disliked names. */
+ private final Pattern dislikedNames = Pattern.compile("(?i)[^\\w]USA?[^\\w]");
+
+ @Override
+ public int compare(Result leftResult, Result rightResult) {
+ String leftBotName = leftResult.bot().name();
+ String rightBotName = rightResult.bot().name();
+ /* preferred names to the front! */
+ if (preferredNames.matcher(leftBotName).find() && !preferredNames.matcher(rightBotName).find()) {
+ return -1;
+ }
+ if (preferredNames.matcher(rightBotName).find() && !preferredNames.matcher(leftBotName).find()) {
+ return 1;
+ }
+ /* disliked names to the back. */
+ if (dislikedNames.matcher(leftBotName).find() && !dislikedNames.matcher(rightBotName).find()) {
+ return 1;
+ }
+ if (dislikedNames.matcher(rightBotName).find() && !dislikedNames.matcher(leftBotName).find()) {
+ return -1;
+ }
+ return 0;
+ }
+ };
+
+ /**
+ * {@link Comparator} for {@link Result}s that sorts them by the name of the
+ * {@link Pack}.
+ */
+ private static final Comparator<Result> packNameComparator = new Comparator<Result>() {
+ @Override
+ public int compare(Result leftResult, Result rightResult) {
+ return leftResult.pack().name().compareToIgnoreCase(rightResult.pack().name());
+ }
+ };
+
+ /** The bot carrying the pack. */
+ private final Bot bot;
+
+ /** The pack. */
+ private final Pack pack;
+
+ /**
+ * Creates a new result.
+ *
+ * @param bot
+ * The bot carrying the pack
+ * @param pack
+ * The pack
+ */
+ Result(Bot bot, Pack pack) {
+ this.bot = bot;
+ this.pack = pack;
+ }
+
+ //
+ // ACCESSORS
+ //
+
+ /**
+ * Returns the bot carrying the pack.
+ *
+ * @return The bot carrying the pack
+ */
+ public Bot bot() {
+ return bot;
+ }
+
+ /**
+ * Returns the pack.
+ *
+ * @return The pack
+ */
+ public Pack pack() {
+ return pack;
+ }
+
+ //
+ // COMPARABLE METHODS
+ //
+
+ @Override
+ public int compareTo(Result result) {
+ return ComparisonChain.start()
+ .compare(this, result, packArchiveComparator)
+ .compare(this, result, botNameComparator)
+ .compare(this, result, packNameComparator).result();
+ }
+
+}
--- /dev/null
+/*
+ * XdccDownloader - SearchCommand.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.ui.stdin;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Arrays.asList;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import net.pterodactylus.xdcc.core.Core;
+import net.pterodactylus.xdcc.data.Bot;
+import net.pterodactylus.xdcc.data.Pack;
+
+/**
+ * Command that searches all {@link Pack}s of all {@link Bot}s for files
+ * matching the search parameters.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SearchCommand implements Command {
+
+ /** The core to operate on. */
+ private final Core core;
+
+ /**
+ * Creates a new search command.
+ *
+ * @param core
+ * The core to operate on
+ */
+ public SearchCommand(Core core) {
+ this.core = core;
+ }
+
+ //
+ // COMMAND METHODS
+ //
+
+ @Override
+ public String getName() {
+ return "search";
+ }
+
+ @Override
+ public Collection<String> getAliases() {
+ return asList("find", "locate");
+ }
+
+ @Override
+ public State execute(State state, List<String> parameters, Writer outputWriter) throws IOException {
+ List<Result> lastResult = newArrayList();
+ for (Bot bot : newArrayList(core.bots())) {
+ for (Pack pack : newArrayList(bot)) {
+ boolean found = true;
+ for (String parameter : parameters) {
+ if (parameter.startsWith("-") && pack.name().toLowerCase().contains(parameter.toLowerCase().substring(1))) {
+ found = false;
+ break;
+ }
+ if (!parameter.startsWith("-") && !pack.name().toLowerCase().contains(parameter.toLowerCase())) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ lastResult.add(new Result(bot, pack));
+ }
+ }
+ }
+ Collections.sort(lastResult);
+ int counter = 0;
+ for (Result result : lastResult) {
+ outputWriter.write(String.format("[%d] %s (%s) from %s (#%s) on %s\n", counter++, result.pack().name(), result.pack().size(), result.bot().name(), result.pack().id(), result.bot().network().name()));
+ }
+ outputWriter.write("End of Search.\n");
+ outputWriter.flush();
+ return state.setLastResults(lastResult);
+ }
+
+}
--- /dev/null
+/*
+ * XdccDownloader - State.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.ui.stdin;
+
+import java.util.List;
+
+import net.pterodactylus.irc.Connection;
+import net.pterodactylus.xdcc.data.Download;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Container for the current state of the command reader.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class State {
+
+ /** The last connections displayed. */
+ private final List<Connection> lastConnections;
+
+ /** The last results displayed. */
+ private final List<Result> lastResults;
+
+ /** The last downloads displayed. */
+ private final List<Download> lastDownloads;
+
+ /** Creates a new empty state. */
+ public State() {
+ this(Lists.<Connection>newArrayList(), Lists.<Result>newArrayList(), Lists.<Download>newArrayList());
+ }
+
+ /**
+ * Creates a new state.
+ *
+ * @param lastConnections
+ * The last connections
+ * @param lastResults
+ * The last results
+ * @param lastDownloads
+ * The last downloads
+ */
+ State(List<Connection> lastConnections, List<Result> lastResults, List<Download> lastDownloads) {
+ this.lastConnections = lastConnections;
+ this.lastResults = lastResults;
+ this.lastDownloads = lastDownloads;
+ }
+
+ //
+ // ACCESSORS
+ //
+
+ /**
+ * Returns the last connections displayed.
+ *
+ * @return The last connections displayed
+ */
+ public List<Connection> getLastConnections() {
+ return lastConnections;
+ }
+
+ /**
+ * Returns the last results displayed.
+ *
+ * @return The last results displayed
+ */
+ public List<Result> getLastResults() {
+ return lastResults;
+ }
+
+ /**
+ * Returns the last downloads displayed.
+ *
+ * @return The last downloads displayed
+ */
+ public List<Download> getLastDownloads() {
+ return lastDownloads;
+ }
+
+ //
+ // MUTATORS
+ //
+
+ /**
+ * Returns a new state with the given last connections and the last downloads
+ * and results of this state.
+ *
+ * @param lastConnections
+ * The new last connections displayed
+ * @return The new state
+ */
+ public State setLastConnections(List<Connection> lastConnections) {
+ return new State(lastConnections, lastResults, lastDownloads);
+ }
+
+ /**
+ * Returns a new state with the given last results and the last downloads and
+ * connections of this state.
+ *
+ * @param lastResults
+ * The new last results displayed
+ * @return The new state
+ */
+ public State setLastResults(List<Result> lastResults) {
+ return new State(lastConnections, lastResults, lastDownloads);
+ }
+
+ /**
+ * Returns a new state with the given last downloads and the last connections
+ * and results of this state.
+ *
+ * @param lastDownloads
+ * The new last downloads displayed
+ * @return The new state
+ */
+ public State setLastDownloads(List<Download> lastDownloads) {
+ return new State(lastConnections, lastResults, lastDownloads);
+ }
+
+}
--- /dev/null
+/*
+ * XdccDownloader - StatsCommand.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.xdcc.ui.stdin;
+
+import static java.util.Collections.emptyList;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import net.pterodactylus.xdcc.core.Core;
+import net.pterodactylus.xdcc.data.Bot;
+import net.pterodactylus.xdcc.data.Pack;
+
+import com.google.common.collect.Sets;
+
+/**
+ * Command that outputs a short statistic of what is going on.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class StatsCommand implements Command {
+
+ /** The core to operate on. */
+ private final Core core;
+
+ /**
+ * Creates a new stats command.
+ *
+ * @param core
+ * The core to operate on
+ */
+ public StatsCommand(Core core) {
+ this.core = core;
+ }
+
+ //
+ // COMMAND METHODS
+ //
+
+ @Override
+ public String getName() {
+ return "stats";
+ }
+
+ @Override
+ public Collection<String> getAliases() {
+ return emptyList();
+ }
+
+ @Override
+ public State execute(State state, List<String> parameters, Writer outputWriter) throws IOException {
+ int configuredChannelsCount = core.channels().size();
+ int joinedChannelsCount = core.joinedChannels().size();
+ int extraChannelsCount = core.extraChannels().size();
+ Collection<Bot> bots = core.bots();
+ Set<String> packNames = Sets.newHashSet();
+ int packsCount = 0;
+ for (Bot bot : bots) {
+ packsCount += bot.packs().size();
+ for (Pack pack : bot) {
+ packNames.add(pack.name());
+ }
+ }
+
+ outputWriter.write(String.format("%d channels (%d joined, %d extra), %d bots offering %d packs (%d unique).\n", configuredChannelsCount, joinedChannelsCount, extraChannelsCount, bots.size(), packsCount, packNames.size()));
+ outputWriter.flush();
+ return state;
+ }
+
+}