/** * Copyright (C) 2010-2014 Leon Blakey <lord.quackstar at gmail.com> * * This file is part of PircBotX. * * PircBotX 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. * * PircBotX 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 * PircBotX. If not, see <http://www.gnu.org/licenses/>. */ package org.pircbotx.output; import static com.google.common.base.Preconditions.*; import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.text.WordUtils; import org.pircbotx.PircBotX; import org.pircbotx.Utils; import org.slf4j.Marker; import org.slf4j.MarkerFactory; /** * Send raw lines to the server with locking and message delay support. * <p> * @author Leon Blakey */ @RequiredArgsConstructor @Slf4j public class OutputRaw { public static final Marker OUTPUT_MARKER = MarkerFactory.getMarker("pircbotx.output"); @NonNull protected final PircBotX bot; protected final ReentrantLock writeLock = new ReentrantLock(true); protected final Condition writeNowCondition = writeLock.newCondition(); protected final long delayNanos; protected long lastSentLine = 0; public OutputRaw(PircBotX bot) { this.bot = bot; this.delayNanos = bot.getConfiguration().getMessageDelay() * 1000000; } /** * Sends a raw line through the outgoing message queue. * * @param line The raw line to send to the IRC server. */ public void rawLine(String line) { checkArgument(StringUtils.isNotBlank(line), "Cannot send empty line to server: '%s'", line); checkArgument(bot.isConnected(), "Not connected to server"); writeLock.lock(); try { //Block until we can send, taking into account a changing lastSentLine long curNanos = System.nanoTime(); while (lastSentLine + delayNanos > curNanos) { writeNowCondition.await(lastSentLine + delayNanos - curNanos, TimeUnit.NANOSECONDS); curNanos = System.nanoTime(); } log.info(OUTPUT_MARKER, line); Utils.sendRawLineToServer(bot, line); lastSentLine = System.nanoTime(); } catch (IOException e) { throw new RuntimeException("IO exception when sending line to server, is the network still up? " + exceptionDebug(), e); } catch (InterruptedException e) { throw new RuntimeException("Couldn't pause thread for message delay. " + exceptionDebug(), e); } catch (Exception e) { throw new RuntimeException("Could not send line to server. " + exceptionDebug(), e); } finally { writeLock.unlock(); } } /** * Sends a raw line to the IRC server as soon as possible without resetting * the message delay for messages waiting to send * * @param line The raw line to send to the IRC server. * @see #rawLineNow(java.lang.String, boolean) */ public void rawLineNow(String line) { rawLineNow(line, false); } /** * Sends a raw line to the IRC server as soon as possible * <p> * @param line The raw line to send to the IRC server * @param resetDelay If true, pending messages will reset their delay. */ public void rawLineNow(String line, boolean resetDelay) { checkNotNull(line, "Line cannot be null"); checkArgument(bot.isConnected(), "Not connected to server"); writeLock.lock(); try { log.info(OUTPUT_MARKER, line); Utils.sendRawLineToServer(bot, line); lastSentLine = System.nanoTime(); if (resetDelay) //Reset the writeNowCondition.signalAll(); } catch (IOException e) { throw new RuntimeException("IO exception when sending line to server, is the network still up? " + exceptionDebug(), e); } catch (Exception e) { throw new RuntimeException("Could not send line to server. " + exceptionDebug(), e); } finally { writeLock.unlock(); } } public void rawLineSplit(String prefix, String message) { rawLineSplit(prefix, message, ""); } public void rawLineSplit(String prefix, String message, String suffix) { checkNotNull(prefix, "Prefix cannot be null"); checkNotNull(message, "Message cannot be null"); checkNotNull(suffix, "Suffix cannot be null"); //Find if final line is going to be shorter than the max line length String finalMessage = prefix + message + suffix; int realMaxLineLength = bot.getConfiguration().getMaxLineLength() - 2; if (!bot.getConfiguration().isAutoSplitMessage() || finalMessage.length() < realMaxLineLength) { //Length is good (or auto split message is false), just go ahead and send it rawLine(finalMessage); return; } //Too long, split it up int maxMessageLength = realMaxLineLength - (prefix + suffix).length(); //v3 word split, just use Apache commons lang for (String curPart : StringUtils.split(WordUtils.wrap(message, maxMessageLength, "\r\n", true), "\r\n")) { rawLine(prefix + curPart + suffix); } } /** * Gets the number of lines currently waiting in the outgoing message Queue. * If this returns 0, then the Queue is empty and any new message is likely * to be sent to the IRC server immediately. * * @since PircBot 0.9.9 * * @return The number of lines in the outgoing message Queue. */ public int getOutgoingQueueSize() { return writeLock.getHoldCount(); } protected String exceptionDebug() { return "Connected: " + bot.isConnected() + " | Bot State: " + bot.getState(); } }