X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Fxdcc%2Fui%2Fstdin%2FCommandReader.java;h=2d0a35292a0f525fc70aba6b274dcf366b063586;hb=57fa2048f94ac1975ed4a417fbc0150dd9277f00;hp=5feabd7045342b8c7c42cdba401c4a0547af06b9;hpb=051375293ec12d97d9615c739cf92ef9725cc7dd;p=xudocci.git diff --git a/src/main/java/net/pterodactylus/xdcc/ui/stdin/CommandReader.java b/src/main/java/net/pterodactylus/xdcc/ui/stdin/CommandReader.java index 5feabd7..2d0a352 100644 --- a/src/main/java/net/pterodactylus/xdcc/ui/stdin/CommandReader.java +++ b/src/main/java/net/pterodactylus/xdcc/ui/stdin/CommandReader.java @@ -17,23 +17,21 @@ package net.pterodactylus.xdcc.ui.stdin; -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.Ansi.bold; +import static net.pterodactylus.xdcc.ui.stdin.Ansi.green; +import static net.pterodactylus.xdcc.ui.stdin.Ansi.red; +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.ArrayList; 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; @@ -41,18 +39,14 @@ import net.pterodactylus.xdcc.core.event.DownloadFinished; 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 net.pterodactylus.xdcc.util.io.DuplicateLineSuppressingWriter; -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.base.Optional; +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; /** @@ -62,14 +56,15 @@ 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 commands; /** The reader to read commands from. */ private final BufferedReader reader; /** The writer to write the results to. */ - private final Writer writer; + private final DuplicateLineSuppressingWriter writer; + private final Collection failedDownloads; /** * Creates a new command reader. @@ -81,10 +76,24 @@ public class CommandReader extends AbstractExecutionThreadService { * @param writer * The write to write results to */ - public CommandReader(Core core, Reader reader, Writer writer) { - this.core = core; + public CommandReader(Core core, Reader reader, Writer writer, Collection failedDownloads) { this.reader = new BufferedReader(reader); - this.writer = writer; + this.writer = new DuplicateLineSuppressingWriter(writer); + this.failedDownloads = failedDownloads; + + /* initialize commands. */ + ImmutableList.Builder 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)); + commandBuilder.add(new FailedDownloadsCommand(failedDownloads)); + commandBuilder.add(new RestartCommand(core, failedDownloads)); + commandBuilder.add(new ResearchCommand(core)); + commands = commandBuilder.build(); } // @@ -95,100 +104,25 @@ public class CommandReader extends AbstractExecutionThreadService { protected void run() throws Exception { String lastLine = ""; String line; - final List lastResult = Lists.newArrayList(); - final List lastConnections = Lists.newArrayList(); + 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; } + writer.reset(); String[] words = line.split(" +"); - if (words[0].equalsIgnoreCase("search")) { - lastResult.clear(); - for (Bot bot : Lists.newArrayList(core.bots())) { - for (Pack pack : Lists.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."); - } else if (words[0].equalsIgnoreCase("dcc")) { - int counter = 0; - for (Download download : FluentIterable.from(core.downloads()).toSortedList(Ordering.from(BY_NAME).compound(BY_RUNNING))) { - 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", dccReceiver.progress() * 100.0 / dccReceiver.size(), f(dccReceiver.currentRate()))); - } else { - if (dccReceiver.progress() >= dccReceiver.size()) { - writer.write(String.format("complete, %s", f(dccReceiver.overallRate()))); - } else { - writer.write(String.format("aborted at %.1f%%, %s", dccReceiver.progress() * 100.0 / dccReceiver.size(), f(dccReceiver.currentRate()))); - } - } - writer.write("/s)\n"); - } - writeLine("End of DCCs."); - } else if (words[0].equalsIgnoreCase("get")) { - Integer index = Ints.tryParse(words[1]); - if ((index != null) && (index < lastResult.size())) { - core.fetch(lastResult.get(index).bot(), lastResult.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 bots = core.bots(); - Set 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")) { - lastConnections.clear(); - 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."); - } else if (words[0].equalsIgnoreCase("disconnect")) { - if ((words.length == 1) || ("all".equals(words[1]))) { - for (Connection connection : lastConnections) { - core.closeConnection(connection); - } - } else { - Integer index = Ints.tryParse(words[1]); - if ((index != null) && (index < lastConnections.size())) { - core.closeConnection(lastConnections.get(index)); - } - } + String commandName = words[0]; + Collection 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 parameters = from(asList(words)).skip(1).toList(); + state = command.execute(state, parameters, writer); + writer.flush(); } lastLine = line; @@ -209,7 +143,7 @@ public class CommandReader extends AbstractExecutionThreadService { public void downloadStarted(DownloadStarted downloadStarted) { Download download = downloadStarted.download(); try { - writeLine(String.format("Download of %s (from %s, %s) has started.", download.pack().name(), download.bot().name(), download.bot().network().name())); + writeLine(String.format("Download of %s (from %s, %s) has started.", bold(download.pack().name()), download.bot().name(), download.bot().network().name())); } catch (IOException ioe1) { /* ignore. */ } @@ -224,13 +158,24 @@ public class CommandReader extends AbstractExecutionThreadService { @Subscribe public void downloadFinished(DownloadFinished downloadFinished) { Download download = downloadFinished.download(); + removeFailedDownloads(download.pack().name()); try { - writeLine(String.format("Download of %s (from %s, %s) has finished, at %s/s.", download.pack().name(), download.bot().name(), download.bot().network().name(), f(download.dccReceiver().overallRate()))); + writeLine(green(String.format("Download of %s (from %s, %s) has finished, at %s/s.", download.pack().name(), download.bot().name(), download.bot().network().name(), f(download.dccReceiver().overallRate())))); } catch (IOException ioe1) { /* ignore. */ } } + private void removeFailedDownloads(String name) { + List failedDownloadsToRemove = new ArrayList<>(); + for (Download failedDownload : failedDownloads) { + if (failedDownload.pack().name().equals(name)) { + failedDownloadsToRemove.add(failedDownload); + } + } + failedDownloads.removeAll(failedDownloadsToRemove); + } + /** * Called when a download fails. * @@ -240,8 +185,9 @@ public class CommandReader extends AbstractExecutionThreadService { @Subscribe public void downloadFailed(DownloadFailed downloadFailed) { Download download = downloadFailed.download(); + failedDownloads.add(download); try { - writeLine(String.format("Download of %s (from %s, %s) has failed at %.1f%% and %s/s.", download.filename(), download.bot().name(), download.bot().network().name(), download.dccReceiver().progress() * 100.0 / download.dccReceiver().size(), f(download.dccReceiver().overallRate()))); + writeLine(red(String.format("Download of %s (from %s, %s) has failed at %.1f%% and %s/s.", download.filename(), download.bot().name(), download.bot().network().name(), download.dccReceiver().progress() * 100.0 / download.dccReceiver().size(), f(download.dccReceiver().overallRate())))); } catch (IOException ioe1) { /* ignore. */ } @@ -281,6 +227,21 @@ public class CommandReader extends AbstractExecutionThreadService { // PRIVATE METHODS // + private Collection findEligibleCommands(String name) { + ImmutableSet.Builder 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}. * @@ -302,7 +263,7 @@ public class CommandReader extends AbstractExecutionThreadService { * 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)); } @@ -315,142 +276,21 @@ public class CommandReader extends AbstractExecutionThreadService { return String.format("%dB", number); } - /** Container for result information. */ - private static class Result implements Comparable { - - /** {@link Predicate} that matches {@link Result}s that contain an archive. */ - private static final Predicate isArchive = new Predicate() { - - /** All suffixes that are recognized as archives. */ - private final List 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 packArchiveComparator = new Comparator() { - @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 botNameComparator = new Comparator() { - - /** 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 packNameComparator = new Comparator() { - @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; + /** + * Formats the given number of seconds into a more easily readable string. + * + * @param seconds + * The number of seconds + * @return The formatted time, or “unknown” if the time is unknown + */ + static String t(Optional seconds) { + if (!seconds.isPresent()) { + return "unknown"; } - - // - // COMPARABLE METHODS - // - - @Override - public int compareTo(Result result) { - return ComparisonChain.start() - .compare(this, result, packArchiveComparator) - .compare(this, result, botNameComparator) - .compare(this, result, packNameComparator).result(); + if (seconds.get() > 3600) { + return String.format("%02d:%02d:%02d", seconds.get() / 3600, (seconds.get() / 60) % 60, seconds.get() % 60); } - + return String.format("%02d:%02d", (seconds.get() / 60) % 60, seconds.get() % 60); } }