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=17d238916470f7661a1a4e44d2f3655247dd574b;hpb=36b0afc82542273a238c1464abe8790aa85aed93;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 17d2389..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,17 +17,36 @@ package net.pterodactylus.xdcc.ui.stdin; +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.Collections; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import net.pterodactylus.irc.util.MessageCleaner; import net.pterodactylus.xdcc.core.Core; -import net.pterodactylus.xdcc.data.Bot; -import net.pterodactylus.xdcc.data.Pack; +import net.pterodactylus.xdcc.core.event.DownloadFailed; +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.Download; +import net.pterodactylus.xdcc.util.io.DuplicateLineSuppressingWriter; -import com.google.common.collect.Lists; +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.util.concurrent.AbstractExecutionThreadService; /** @@ -37,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. @@ -56,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(); } // @@ -70,98 +104,193 @@ public class CommandReader extends AbstractExecutionThreadService { protected void run() throws Exception { String lastLine = ""; String line; - final List lastResult = 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 : core.bots()) { - for (Pack pack : 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) { - writer.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())); - } - writer.write("End of Search.\n"); + 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; - writer.flush(); } } - /** Container for result information. */ - private static class Result implements Comparable { - - /** 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; + // + // EVENT HANDLERS + // + + /** + * Called when a download was started. + * + * @param downloadStarted + * The download started event + */ + @Subscribe + public void downloadStarted(DownloadStarted downloadStarted) { + Download download = downloadStarted.download(); + try { + 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. */ } + } - // - // ACCESSORS - // - - /** - * Returns the bot carrying the pack. - * - * @return The bot carrying the pack - */ - public Bot bot() { - return bot; + /** + * Called when a download is finished. + * + * @param downloadFinished + * The download finished event + */ + @Subscribe + public void downloadFinished(DownloadFinished downloadFinished) { + Download download = downloadFinished.download(); + removeFailedDownloads(download.pack().name()); + try { + 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. */ } + } - /** - * Returns the pack. - * - * @return The pack - */ - public Pack pack() { - return pack; + 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); + } - // - // COMPARABLE METHODS - // + /** + * Called when a download fails. + * + * @param downloadFailed + * The download failed event + */ + @Subscribe + public void downloadFailed(DownloadFailed downloadFailed) { + Download download = downloadFailed.download(); + failedDownloads.add(download); + try { + 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. */ + } + } - @Override - public int compareTo(Result result) { - return pack().name().compareToIgnoreCase(result.pack().name()); + /** + * Displays the received message on the console. + * + * @param messageReceived + * The message received event + */ + @Subscribe + public void messageReceived(MessageReceived messageReceived) { + try { + writeLine(String.format("Message from %s: %s", messageReceived.source(), MessageCleaner.getDefaultInstance().clean(messageReceived.message()))); + } catch (IOException e) { + /* ignore. */ } + } + /** + * Writes a generic message to the console. + * + * @param genericMessage + * The generic message event + */ + @Subscribe + public void genericMessage(GenericMessage genericMessage) { + try { + writeLine(genericMessage.message()); + } catch (IOException ioe1) { + /* ignore. */ + } + } + + // + // 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}. + * + * @param line + * The line to write + * @throws IOException + * if an I/O error occurs + */ + private void writeLine(String line) throws IOException { + writer.write(line + "\n"); + writer.flush(); + } + + /** + * Converts large numbers into a human-friendly format, by showing SI prefixes + * for ×1024 (K), ×1048576 (M), and ×1073741824 (G). + * + * @param number + * The number to convert + * @return The converted number + */ + static String f(long number) { + if (number >= (1 << 30)) { + return String.format("%.1fG", number / (double) (1 << 30)); + } + if (number >= (1 << 20)) { + return String.format("%.1fM", number / (double) (1 << 20)); + } + if (number >= (1 << 10)) { + return String.format("%.1fK", number / (double) (1 << 10)); + } + return String.format("%dB", number); + } + + /** + * 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"; + } + 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); } }