It was and it is so bad that I refuse to describe changes made. Just one more refactoring iteration ><

This commit is contained in:
Dmitry Isaenko 2020-10-25 23:39:05 +03:00
parent b3da839bcd
commit f3cdfc532c
18 changed files with 806 additions and 700 deletions

View file

@ -30,8 +30,8 @@
<groupId>org.ini4j</groupId> <groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId> <artifactId>ini4j</artifactId>
<version>0.5.4</version> <version>0.5.4</version>
<scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-cli</groupId> <groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId> <artifactId>commons-cli</artifactId>

View file

@ -20,10 +20,10 @@ public class ChanelCommander implements Runnable {
private HashMap<String, String[]> msgMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw private HashMap<String, String[]> msgMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw
private HashMap<String, String[]> nickMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw private HashMap<String, String[]> nickMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw
private boolean joinFloodTrackNeed = false; private boolean joinFloodTrackNeed;
private JoinFloodHandler jfh; private JoinFloodHandler jfh;
private boolean joinCloneTrackNeed = false; // todo:fix private boolean joinCloneTrackNeed;
private JoinCloneHandler jch; private JoinCloneHandler jch;
public ChanelCommander(BlockingQueue<String> stream, String serverName, String channel) throws Exception{ public ChanelCommander(BlockingQueue<String> stream, String serverName, String channel) throws Exception{
@ -40,21 +40,21 @@ public class ChanelCommander implements Runnable {
try { try {
while (true) { while (true) {
String data = streamQueue.take(); String data = streamQueue.take();
String[] dataStrings = data.split(" ",3); String[] dataStrings = data.split(" :?",3);
switch (dataStrings[0]) { switch (dataStrings[1]) {
case "NICK": case "NICK":
nickCame(dataStrings[2]+dataStrings[1].replaceAll("^.+?!","!")); nickCame(dataStrings[2]+dataStrings[0].replaceAll("^.+?!","!"));
break; // todo: need to track join flood break; // todo: need to track join flood
case "JOIN": case "JOIN":
if (joinFloodTrackNeed) if (joinFloodTrackNeed)
jfh.track(simplifyNick(dataStrings[1])); jfh.track(simplifyNick(dataStrings[0]));
if (joinCloneTrackNeed) if (joinCloneTrackNeed)
jch.track(dataStrings[1]); jch.track(dataStrings[0]);
joinCame(dataStrings[1]); joinCame(dataStrings[0]);
break; break;
case "PRIVMSG": case "PRIVMSG":
privmsgCame(dataStrings[1], dataStrings[2]); privmsgCame(dataStrings[0], dataStrings[2]);
break; break;
/* /*
case "PART": // todo: need to track join flood? Fuck that. Track using JOIN case "PART": // todo: need to track join flood? Fuck that. Track using JOIN
@ -68,9 +68,10 @@ public class ChanelCommander implements Runnable {
} }
} }
catch (InterruptedException ie){ catch (InterruptedException ie){
System.out.println("Internal issue: thread ChanelCommander->run() interrupted.\n\t"); // TODO: reconnect System.out.println("ChanelCommander interrupted.");
} }
System.out.println("Thread for ChanelCommander ended"); // TODO:REMOVE DEBUG System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] ChanelCommander thread "
+server+":"+this.channel +" ended");// TODO:REMOVE DEBUG
} }
// Do we need old nick? // Do we need old nick?
@ -94,7 +95,7 @@ public class ChanelCommander implements Runnable {
StringBuilder whatToSendStringBuilder; StringBuilder whatToSendStringBuilder;
ArrayList<String> whatToSendList; ArrayList<String> whatToSendList;
for (int i = 0; i<cmdOrMsg.length;) { for (int i = 0; i < cmdOrMsg.length;) {
switch (cmdOrMsg[i]) { switch (cmdOrMsg[i]) {
case "\\chanmsg": case "\\chanmsg":
whatToSendList = new ArrayList<>(); whatToSendList = new ArrayList<>();
@ -163,15 +164,19 @@ public class ChanelCommander implements Runnable {
else else
whatToSendStringBuilder.append(cmdOrMsg[i]); whatToSendStringBuilder.append(cmdOrMsg[i]);
} }
if (objectRegexp != null) {
if (objectRegexp == null)
break;
String objectToCtcp = arg1.trim().replaceAll(objectRegexp, ""); // note: trim() ? String objectToCtcp = arg1.trim().replaceAll(objectRegexp, ""); // note: trim() ?
if (!objectToCtcp.isEmpty()){
if (objectToCtcp.isEmpty())
break;
if (CTCPType.startsWith("\\c")) if (CTCPType.startsWith("\\c"))
CTCPHelper.getInstance().registerRequest(server, channel, CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString()); registerCTCPforChannel(CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString());
else else
CTCPHelper.getInstance().registerRequest(server, simplifyNick(arg2), CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString()); registerCTCPforUser(simplifyNick(arg2), CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString());
}
}
break; break;
default: default:
@ -180,15 +185,22 @@ public class ChanelCommander implements Runnable {
} }
} }
} }
///////// ///////// ///////// /////////
private void registerCTCPforChannel(String CTCPType, String object, String message){
CTCPHelper.getInstance().registerRequest(server, channel, CTCPType, object, message);
}
private void registerCTCPforUser(String user, String CTCPType, String object, String message){
CTCPHelper.getInstance().registerRequest(server, user, CTCPType, object, message);
}
private void whoisAction(String who){ // TODO: maybe we have to extend functionality to reuse received information. private void whoisAction(String who){ // TODO: maybe we have to extend functionality to reuse received information.
StreamProvider.writeToStream(server, "WHOIS "+simplifyNick(who)); StreamProvider.writeToStream(server, "WHOIS "+simplifyNick(who));
} }
private void msgAction(String[] messages, String who, boolean sendToPrivate){ private void msgAction(String[] messages, String who, boolean isToUser){
StringBuilder executiveStr = new StringBuilder(); StringBuilder executiveStr = new StringBuilder();
executiveStr.append("PRIVMSG "); executiveStr.append("PRIVMSG ");
if(sendToPrivate) { if(isToUser) {
executiveStr.append(simplifyNick(who)); executiveStr.append(simplifyNick(who));
executiveStr.append(" :"); executiveStr.append(" :");
} }
@ -236,6 +248,13 @@ public class ChanelCommander implements Runnable {
private void readConfing() throws Exception{ private void readConfing() throws Exception{
ConfigurationChannel configChannel = ConfigurationManager.getConfiguration(server).getChannelConfig(channel); ConfigurationChannel configChannel = ConfigurationManager.getConfiguration(server).getChannelConfig(channel);
if (configChannel == null){
joinMap = new HashMap<>();
msgMap = new HashMap<>();
nickMap = new HashMap<>();
return;
}
joinMap = configChannel.getJoinMap(); joinMap = configChannel.getJoinMap();
msgMap = configChannel.getMsgMap(); msgMap = configChannel.getMsgMap();
nickMap = configChannel.getNickMap(); nickMap = configChannel.getNickMap();

View file

@ -18,8 +18,16 @@ public class PrivateMsgCommander { // TODO: add black list: add users afte
} }
public void receiver(String sender, String message){ public void receiver(String sender, String message){
if (!password.isEmpty()) { if (password.isEmpty() || message.isEmpty())
if (administrators.contains(sender) && !message.isEmpty()) { return;
if (isNotAuthorized(sender)){
if (message.startsWith("login ")) {
login(sender, message.substring("login ".length()).trim());
}
return;
}
String[] cmd = message.split("(\\s)|(\t)+?", 2); String[] cmd = message.split("(\\s)|(\t)+?", 2);
cmd[0] = cmd[0].toLowerCase(); cmd[0] = cmd[0].toLowerCase();
if (cmd.length > 1) if (cmd.length > 1)
@ -217,13 +225,10 @@ public class PrivateMsgCommander { // TODO: add black list: add users afte
break; break;
default: default:
tell(simplifyNick(sender), "Unknown command. Could be: join, part, quit, tell, nick, ctcp, notice, umode, cmode, raw, kick[k], ban[b], unban[-b], kickban[kb], voice[v], unvoice[-v], hop[h], unhop[-h], op[o], unop[-o], topic, invite and (login)"); tell(simplifyNick(sender), "Unknown command. Could be: join, part, quit, tell, nick, ctcp, notice, umode, cmode, raw, kick[k], ban[b], unban[-b], kickban[kb], voice[v], unvoice[-v], hop[h], unhop[-h], op[o], unop[-o], topic, invite and (login)");
} // TODO: chanel limits set/remove } // TODO: add options for setting channel limits (MODEs)
} else {
if (!message.isEmpty() && message.startsWith("login ")) {
login(sender, message.replaceAll("^([\t\\s]+)?login([\t\\s]+)|([\t\\s]+$)", ""));
}
}
} }
private boolean isNotAuthorized(String sender){
return ! administrators.contains(sender);
} }
private void join(String channel){ private void join(String channel){
@ -233,14 +238,12 @@ public class PrivateMsgCommander { // TODO: add black list: add users afte
raw("PART "+channel); raw("PART "+channel);
} }
private void quit(String message){ private void quit(String message){
ReconnectControl.update(server, false);
if (message.isEmpty()){ if (message.isEmpty()){
raw("QUIT :"+ GlobalData.getAppVersion()); raw("QUIT :"+ GlobalData.getAppVersion());
} }
else else
raw("QUIT :"+message); raw("QUIT :"+message);
ReconnectControl.update(server, false);
//ReconnectControl.update(serverName, true);
//System.exit(0); // TODO: change to normal exit
} }
private void tell(String channelUser, String message){ private void tell(String channelUser, String message){
message = message.trim(); message = message.trim();

View file

@ -8,5 +8,6 @@ public class GlobalData {
System.getProperty("os.version"), System.getProperty("os.version"),
System.getProperty("os.arch")); System.getProperty("os.arch"));
} }
public static final String applicationHomePage = "https://github.com/developersu/InnaIrcBot";
public static final int CHANNEL_QUEUE_CAPACITY = 500; public static final int CHANNEL_QUEUE_CAPACITY = 500;
} }

View file

@ -21,10 +21,10 @@ public class ChanConsumer implements Runnable {
private final BlockingQueue<String> chanConsumerQueue; private final BlockingQueue<String> chanConsumerQueue;
private final String serverName; private final String serverName;
private final String channelName; private final String channelName;
private Worker writerWorker; private final ArrayList<String> users;
private ArrayList<String> userList; private Worker logWorker;
private String nick; private String nick;
private final boolean rejoin; private final boolean autoRejoin;
private final Map<String, IrcChannel> channels; private final Map<String, IrcChannel> channels;
private Thread channelCommanderThread; private Thread channelCommanderThread;
@ -32,17 +32,20 @@ public class ChanConsumer implements Runnable {
private boolean endThread = false; private boolean endThread = false;
private boolean hasBeenKicked;
ChanConsumer(String serverName, ChanConsumer(String serverName,
IrcChannel thisIrcChannel, IrcChannel thisIrcChannel,
String ownNick, String ownNick,
Map<String, IrcChannel> channels) throws Exception{ Map<String, IrcChannel> channels) throws Exception
{
this.chanConsumerQueue = thisIrcChannel.getChannelQueue(); this.chanConsumerQueue = thisIrcChannel.getChannelQueue();
this.serverName = serverName; this.serverName = serverName;
this.channelName = thisIrcChannel.toString(); this.channelName = thisIrcChannel.toString();
this.writerWorker = LogDriver.getWorker(serverName, channelName); this.logWorker = LogDriver.getWorker(serverName, channelName);
this.userList = new ArrayList<>(); this.users = new ArrayList<>();
this.nick = ownNick; this.nick = ownNick;
this.rejoin = ConfigurationManager.getConfiguration(serverName).getRejoinOnKick(); this.autoRejoin = ConfigurationManager.getConfiguration(serverName).getRejoinOnKick();
this.channels = channels; this.channels = channels;
getChanelCommander(); getChanelCommander();
} }
@ -55,97 +58,115 @@ public class ChanConsumer implements Runnable {
} }
public void run(){ public void run(){
String data;
String[] dataStrings;
System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] ChanConsumer thread "+serverName+":"+this.channelName +" started"); // TODO:REMOVE DEBUG System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] ChanConsumer thread "+serverName+":"+this.channelName +" started"); // TODO:REMOVE DEBUG
try { try {
while (! endThread) { while (! endThread) {
data = chanConsumerQueue.take(); String data = chanConsumerQueue.take();
dataStrings = data.split(" ",3); String[] dataStrings = data.split(" :?",3);
if (! trackUsers(dataStrings[0], dataStrings[1], dataStrings[2])) if (trackUsers(dataStrings[1], dataStrings[0], dataStrings[2]))
continue; continue;
// Send to chanel commander thread // Send to channel commander thread
queue.add(data); // TODO: Check and add consistency validation // TODO: Check and add consistency validation
queue.add(data);
if (!writerWorker.logAdd(dataStrings[0], dataStrings[1], dataStrings[2])){ // Write logs, check if LogDriver consistent. If not: if (! logWorker.logAdd(dataStrings[1], dataStrings[0], dataStrings[2])){ // Write logs checks if LogDriver consistent.
this.fixLogDriverIssues(dataStrings[0], dataStrings[1], dataStrings[2]); this.fixLogDriverIssues(dataStrings[1], dataStrings[0], dataStrings[2]);
} }
} }
channels.remove(channelName);
} catch (InterruptedException e){ } catch (InterruptedException e){
System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->run(): Interrupted\n\t"+e); // TODO: reconnect? System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->run(): Interrupted\n\t"+e.getMessage()); // TODO: reconnect?
} }
writerWorker.close();
//Kill sub-thread close();
channelCommanderThread.interrupt();
System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":"+this.channelName +" ended"); // TODO:REMOVE DEBUG System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":"+this.channelName +" ended"); // TODO:REMOVE DEBUG
} }
private boolean trackUsers(String event, String initiatorArg, String subjectArg){ private boolean trackUsers(String event, String initiator, String subject){
initiator = simplifyNick(initiator);
switch (event) { switch (event) {
case "PRIVMSG": // most common, we don't have to handle anything else case "PRIVMSG": // most common, we don't have to handle anything else
return true; return false;
case "JOIN": case "JOIN":
addUser(simplifyNick(initiatorArg)); addUser(initiator);
return true; return false;
case "PART": case "PART":
deleteUser(simplifyNick(initiatorArg)); // nick non-simple deleteUser(initiator);
return true; return false;
case "QUIT": case "QUIT":
if (userList.contains(simplifyNick(initiatorArg))) { if (users.contains(initiator)) {
deleteUser(simplifyNick(initiatorArg)); // nick non-simple deleteUser(initiator);
return true; return false;
} }
else return true; // user quit, but he/she is not in this channel
return false; // user quit, but he/she is not in this channel
case "KICK": case "KICK":
if (rejoin && nick.equals(subjectArg.replaceAll("(^.+?\\s)|(\\s.+$)", ""))) // if it's me and I have rejoin policy 'Auto-Rejoin on kick'. String kickedUser = subject.replaceAll("(^.+?\\s)|(\\s.+$)", "");
StreamProvider.writeToStream(serverName, "JOIN " + channelName); if (nick.equals(kickedUser) && autoRejoin) { // TODO: FIX
deleteUser(subjectArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")); // nick already simplified hasBeenKicked = true;
deleteUser(kickedUser);
return true; return true;
}
deleteUser(kickedUser);
return false;
case "NICK": case "NICK":
if (userList.contains(simplifyNick(initiatorArg))) { if (users.contains(initiator)) {
swapUsers(simplifyNick(initiatorArg), subjectArg); swapUsers(initiator, subject);
return true; return false;
}
else {
return false; // user changed nick, but he/she is not in this channel
} }
return true; // user changed nick, but he/she is not in this channel
case "353": case "353":
String userOnChanStr = subjectArg.substring(subjectArg.indexOf(":") + 1); String userOnChanStr = subject.substring(subject.indexOf(":") + 1);
userOnChanStr = userOnChanStr.replaceAll("[%@+]", "").trim(); userOnChanStr = userOnChanStr.replaceAll("[%@+]", "").trim();
String[] usersOnChanArr = userOnChanStr.split(" "); String[] usersOnChanArr = userOnChanStr.split(" ");
userList.addAll(Arrays.asList(usersOnChanArr)); users.addAll(Arrays.asList(usersOnChanArr));
return true; return true;
default: default:
return true; return false;
} }
} }
private void addUser(String user){ private void addUser(String user){
if (!userList.contains(user)) if (! users.contains(user))
userList.add(user); users.add(user);
} }
private void deleteUser(String user){ private void deleteUser(String user){
if (user.equals(nick)) { if (user.equals(nick)) {
endThread = true; endThread = true;
} }
userList.remove(user); users.remove(user);
} }
private void swapUsers(String userNickOld, String userNickNew){ private void swapUsers(String userNickOld, String userNickNew){
userList.remove(userNickOld); users.remove(userNickOld);
userList.add(userNickNew); users.add(userNickNew);
if (userNickOld.equals(nick)) if (userNickOld.equals(nick))
this.nick = userNickNew; nick = userNickNew;
} }
private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); } private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); }
private void close(){
try{
channels.remove(channelName);
logWorker.close();
channelCommanderThread.interrupt(); //kill sub-thread
channelCommanderThread.join();
handleAutoRejoin();
}
catch (InterruptedException e){
e.printStackTrace();
}
}
private void handleAutoRejoin(){
if (hasBeenKicked && autoRejoin) {
StreamProvider.writeToStream(serverName, "JOIN " + channelName);
}
}
private void fixLogDriverIssues(String a, String b, String c){ private void fixLogDriverIssues(String a, String b, String c){
System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->fixLogDriverIssues(): Some issues detected. Trying to fix..."); System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->fixLogDriverIssues(): Some issues detected. Trying to fix...");
this.writerWorker = LogDriver.getWorker(serverName, channelName); // Reset logDriver and try using the same one logWorker = LogDriver.getWorker(serverName, channelName); // Reset logDriver and try using the same one
if (! writerWorker.logAdd(a, b, c)){ // Write to it what was not written (most likely) and if it's still not consistent: if (! logWorker.logAdd(a, b, c)){ // Write to it what was not written (most likely) and if it's still not consistent:
this.writerWorker = new WorkerZero(); logWorker = new WorkerZero();
System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->fixLogDriverIssues(): failed to use defined LogDriver. Using ZeroWorker instead."); System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->fixLogDriverIssues(): failed to use defined LogDriver. Using ZeroWorker instead.");
} }
} }

View file

@ -11,103 +11,53 @@ import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.concurrent.BlockingQueue;
public class DataProvider implements Runnable { public class DataProvider implements Runnable {
private final ConfigurationFile configurationFile; private final ConfigurationFile configuration;
private final String server; private final String server;
private final String nickName; private final String nick;
private BufferedReader mainReader; private BufferedReader mainReader;
private Thread systemConsumerThread;
private Map<String, IrcChannel> ircChannels;
private IrcChannel systemConsumerChannel;
/** /**
* Initiate connection and prepare input/output streams for run() * Initiate connection and prepare input/output streams for run()
* */ * */
public DataProvider(ConfigurationFile configurationFile){ public DataProvider(ConfigurationFile configuration){
this.configurationFile = configurationFile; this.configuration = configuration;
this.server = configurationFile.getServerName(); this.server = configuration.getServerName();
this.nickName = configurationFile.getUserNick(); this.nick = configuration.getUserNick();
} }
public void run(){ public void run(){
try { try {
connect(); connectSocket();
} catch (Exception e){
System.out.println("Internal issue: DataProvider->run() caused exception:\n\t"+e.getMessage());
e.printStackTrace();
close();
return;
}
ReconnectControl.register(server); ReconnectControl.register(server);
LogDriver.setLogDriver(server); LogDriver.setLogDriver(server);
/* Used for sending data into consumers objects*/ ircChannels = Collections.synchronizedMap(new HashMap<>());
Map<String, IrcChannel> ircChannels = Collections.synchronizedMap(new HashMap<>()); systemConsumerChannel = new IrcChannel("");
ircChannels.put(systemConsumerChannel.toString(), systemConsumerChannel);
IrcChannel systemConsumerChannel = new IrcChannel(""); startSystemConsumer();
BlockingQueue<String> systemConsumerQueue = systemConsumerChannel.getChannelQueue(); sendUserNickAndIdent();
Thread SystemConsumerThread = new Thread( startLoop();
new SystemConsumer(systemConsumerQueue, nickName, ircChannels, this.configurationFile));
SystemConsumerThread.start();
StreamProvider.setSysConsumer(server, systemConsumerQueue); // Register system consumer at StreamProvider } catch (Exception e){
System.out.println("DataProvider exception: "+e.getMessage());
ircChannels.put(systemConsumerChannel.toString(), systemConsumerChannel); // Not sure that PrintWriter is thread-safe..
////////////////////////////////////// Start loop //////////////////////////////////////////////////////////////
StreamProvider.writeToStream(server,"NICK "+this.nickName);
StreamProvider.writeToStream(server,"USER "+ configurationFile.getUserIdent()+" 8 * :"+ configurationFile.getUserRealName()); // TODO: Add usermode 4 rusnet
try {
String rawMessage;
String[] rawStrings; // prefix[0] command[1] command-parameters\r\n[2]
//if there is no prefix, you should assume the message came from your client.
while ((rawMessage = mainReader.readLine()) != null) {
System.out.println(rawMessage);
if (rawMessage.startsWith(":")) {
rawStrings = rawMessage
.substring(1)
.split(" :?", 3); // Removing ':'
String chan = rawStrings[2].replaceAll("(\\s.?$)|(\\s.+?$)", "");
if (rawStrings[1].equals("QUIT") || rawStrings[1].equals("NICK")) { // replace regex
for (IrcChannel ircChannel : ircChannels.values()) {
ircChannel.getChannelQueue().add(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
} }
}
else if (ircChannels.containsKey(chan)) {
IrcChannel chnl = ircChannels.get(chan);
chnl.getChannelQueue().add(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
}
else {
systemConsumerQueue.add(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
}
}
else if (rawMessage.startsWith("PING :")) {
sendPingReply(rawMessage);
}
else {
System.out.println("Not a valid response=" + rawMessage);
}
}
} catch (IOException e){
System.out.println("Socket issue: I/O exception: "+e.getMessage()); //Connection closed. TODO: MAYBE try reconnect
}
finally {
SystemConsumerThread.interrupt();
close(); close();
} }
}
private void connect() throws Exception{ private void connectSocket() throws Exception{
final int port = configurationFile.getServerPort(); final int port = configuration.getServerPort();
InetAddress inetAddress = InetAddress.getByName(server); InetAddress inetAddress = InetAddress.getByName(server);
Socket socket = new Socket(); // TODO: set timeout? Socket socket = new Socket();
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
socket.connect(new InetSocketAddress(inetAddress, port), 5000); // 5sec socket.connect(new InetSocketAddress(inetAddress, port), 5000); // 5sec
if (socket.isConnected()) if (socket.isConnected())
@ -117,18 +67,63 @@ public class DataProvider implements Runnable {
throw new Exception("Unable to connect server."); throw new Exception("Unable to connect server.");
StreamProvider.setStream(server, socket); StreamProvider.setStream(server, socket);
//TODO set charset in options;
InputStream inStream = socket.getInputStream(); mainReader = new BufferedReader(
InputStreamReader isr = new InputStreamReader(inStream, StandardCharsets.UTF_8); //TODO set charset in options; new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)
mainReader = new BufferedReader(isr); );
}
private void sendUserNickAndIdent(){
StreamProvider.writeToStream(server,"NICK " + nick);
StreamProvider.writeToStream(server,"USER " + configuration.getUserIdent()+" 8 * :"+ configuration.getUserRealName()); // TODO: Add usermode 4 rusnet
}
private void startSystemConsumer(){
systemConsumerThread = new Thread(
new SystemConsumer(nick, ircChannels, configuration));
systemConsumerThread.start();
} }
private void sendPingReply(String rawData){ private void startLoop() throws Exception{
StreamProvider.writeToStream(server,"PONG :" + rawData.replace("PING :", "")); String rawMessage;
while ((rawMessage = mainReader.readLine()) != null) {
if (rawMessage.startsWith(":")) {
handleRegular(rawMessage.substring(1));
}
else if (rawMessage.startsWith("PING :")) {
sendPingReply(rawMessage);
}
else {
System.out.println(rawMessage);
}
}
}
private void handleRegular(String rawMessage){
//System.out.println(rawMessage);
String[] rawStrings = rawMessage.split(" :?", 3);
if (rawStrings[1].equals("QUIT") || rawStrings[1].equals("NICK")) {
for (IrcChannel ircChannel : ircChannels.values()) {
ircChannel.getChannelQueue().add(rawMessage);
}
return;
}
String channel = rawStrings[2].replaceAll("(\\s.?$)|(\\s.+?$)", "");
IrcChannel ircChannel = ircChannels.getOrDefault(channel, systemConsumerChannel);
ircChannel.getChannelQueue().add(rawMessage);
}
private void sendPingReply(String message){
StreamProvider.writeToStream(server, message.replaceFirst("PING", "PONG"));
} }
private void close(){ private void close(){
try {
systemConsumerThread.interrupt();
systemConsumerThread.join();
StreamProvider.delStream(server); StreamProvider.delStream(server);
ReconnectControl.notify(server); ReconnectControl.notify(server);
} }
catch (InterruptedException ignored){}
}
} }

View file

@ -5,20 +5,17 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.net.Socket; import java.net.Socket;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
public class StreamProvider { public class StreamProvider {
private static final HashMap<String, OutputStreamWriter> srvStreamMap = new HashMap<>(); private static final HashMap<String, OutputStreamWriter> srvStreamMap = new HashMap<>();
private static final HashMap<String, BlockingQueue<String>> srvSysConsumersMap = new HashMap<>();
public static synchronized void writeToStream(String server, String message){ public static synchronized void writeToStream(String server, String message){
try { try {
srvStreamMap.get(server).write(message+"\n"); srvStreamMap.get(server).write(message+"\n");
srvStreamMap.get(server).flush(); srvStreamMap.get(server).flush();
// If this application says something, then pass it into system consumer thread to handle
if (message.startsWith("PRIVMSG ")) { if (message.startsWith("PRIVMSG ")) {
srvSysConsumersMap.get(server).add("INNA "+message); SystemConsumer.getSystemConsumer(server).add("INNA "+message);
} }
} catch (IOException e){ } catch (IOException e){
System.out.println("Internal issue: StreamProvider->writeToStream() caused I/O exception:\n\t"+e.getMessage()); System.out.println("Internal issue: StreamProvider->writeToStream() caused I/O exception:\n\t"+e.getMessage());
@ -30,10 +27,5 @@ public class StreamProvider {
} }
public static synchronized void delStream(String server){ public static synchronized void delStream(String server){
srvStreamMap.remove(server); srvStreamMap.remove(server);
srvSysConsumersMap.remove(server);
}
public static synchronized void setSysConsumer(String server, BlockingQueue<String> queue){
srvSysConsumersMap.put(server, queue);
} }
} }

View file

@ -0,0 +1,86 @@
package InnaIrcBot.ProvidersConsumers;
import InnaIrcBot.GlobalData;
import InnaIrcBot.logging.WorkerSystem;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class SystemCTCP {
private final String server;
private LocalDateTime lastReplyTime;
private final int cooldownTime;
private final WorkerSystem writerWorker;
SystemCTCP(String server, int cooldownTime, WorkerSystem writerWorker){
this.server = server;
this.lastReplyTime = LocalDateTime.now();
this.cooldownTime = cooldownTime;
this.writerWorker = writerWorker;
}
void replyCTCP(String sender, String message) {
if (isTooManyRequests())
return;
lastReplyTime = LocalDateTime.now();
switch (message) {
case "\u0001VERSION\u0001":
replyVersion(sender);
log("CTCP VERSION from", sender);
return;
case "\u0001CLIENTINFO\u0001":
replyClientInfo(sender);
log("CTCP CLIENTINFO from", sender);
return;
case "\u0001TIME\u0001":
replyTime(sender);
log( "CTCP TIME from", sender);
return;
case "\u0001SOURCE\u0001":
replySource(sender);
log( "CTCP TIME from", sender);
return;
}
if (message.startsWith("\u0001PING ") && message.endsWith("\u0001")) {
replyPing(sender, message);
log( "CTCP PING from", sender);
return;
}
log( "CTCP not supported: \"" + message + "\" from ", sender);
}
private boolean isTooManyRequests(){
return lastReplyTime.isAfter(LocalDateTime.now().minusSeconds(cooldownTime));
}
private void replyVersion(String sender){
reply("NOTICE " + sender + " :\u0001VERSION " + GlobalData.getAppVersion() + "\u0001");
}
private void replyClientInfo(String sender){
reply("NOTICE " + sender + " :\u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO SOURCE\u0001");
}
private void replyTime(String sender){
reply("NOTICE " + sender + " :\u0001TIME " + timeStamp() + "\u0001");
}
private void replySource(String sender){
reply("NOTICE " + sender + " :\u0001SOURCE " + GlobalData.applicationHomePage + "\u0001");
}
private void replyPing(String sender, String message){
reply("NOTICE " + sender + " :" + message);
}
private void reply(String message){
StreamProvider.writeToStream(server, message);
}
private void log(String event, String sender){
writerWorker.log(event, sender);
}
private String timeStamp(){
return ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME);
}
}

View file

@ -4,76 +4,78 @@ import InnaIrcBot.Commanders.CTCPHelper;
import InnaIrcBot.Commanders.PrivateMsgCommander; import InnaIrcBot.Commanders.PrivateMsgCommander;
import InnaIrcBot.ReconnectControl; import InnaIrcBot.ReconnectControl;
import InnaIrcBot.config.ConfigurationFile; import InnaIrcBot.config.ConfigurationFile;
import InnaIrcBot.GlobalData;
import InnaIrcBot.IrcChannel; import InnaIrcBot.IrcChannel;
import InnaIrcBot.logging.LogDriver; import InnaIrcBot.logging.LogDriver;
import InnaIrcBot.logging.WorkerSystem; import InnaIrcBot.logging.WorkerSystem;
import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
public class SystemConsumer implements Runnable{ public class SystemConsumer implements Runnable{
private final BlockingQueue<String> systemQueue; private final BlockingQueue<String> systemQueue;
private WorkerSystem writerWorker; private final WorkerSystem writerWorker;
private String nick; private String nick;
private String serverName; private final String server;
private final Map<String, IrcChannel> channels; private final Map<String, IrcChannel> channels;
private ConfigurationFile configurationFile; private final ConfigurationFile configurationFile;
private PrivateMsgCommander commander; private final PrivateMsgCommander commander;
private LocalDateTime lastCTCPReplyTime; private final ArrayList<Thread> channelThreads;
private ArrayList<Thread> channelThreads;
private int nickTail = 0; private int nickTail = 0;
private final SystemCTCP systemCTCP;
SystemConsumer(BlockingQueue<String> systemQueue, String userNick, Map<String, IrcChannel> channels, ConfigurationFile configurationFile) { private static final HashMap<String, BlockingQueue<String>> systemConsumers = new HashMap<>();
this.systemQueue = systemQueue; public static synchronized BlockingQueue<String> getSystemConsumer(String server){
this.writerWorker = LogDriver.getSystemWorker(configurationFile.getServerName()); return systemConsumers.get(server);
}
SystemConsumer(String userNick, Map<String, IrcChannel> channels, ConfigurationFile configurationFile) {
this.systemQueue = channels.get("").getChannelQueue();
this.nick = userNick; this.nick = userNick;
this.serverName = configurationFile.getServerName(); this.server = configurationFile.getServerName();
this.writerWorker = LogDriver.getSystemWorker(server);
this.channels = channels; this.channels = channels;
this.channelThreads = new ArrayList<>(); this.channelThreads = new ArrayList<>();
this.configurationFile = configurationFile; this.configurationFile = configurationFile;
this.commander = new PrivateMsgCommander(serverName, this.configurationFile.getBotAdministratorPassword()); this.commander = new PrivateMsgCommander(server, this.configurationFile.getBotAdministratorPassword());
this.systemCTCP = new SystemCTCP(server, configurationFile.getCooldownTime(), writerWorker);
lastCTCPReplyTime = LocalDateTime.now(); systemConsumers.put(server, systemQueue);
} }
@Override @Override
public void run() { public void run() {
System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":[system] started"); // TODO:REMOVE DEBUG System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
+"] Thread SystemConsumer \""+ server +"\": started"); // TODO:REMOVE
setMainRoutine(); startMainRoutine();
close();
for (Thread channel : channelThreads) { //TODO: check, code duplication. see Data provider constructor System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))
channel.interrupt(); +"] Thread SystemConsumer \""+ server +"\": ended"); // TODO:REMOVE
} }
writerWorker.close(); private void startMainRoutine(){
System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":[system] ended"); // TODO:REMOVE DEBUG
}
private void setMainRoutine(){
try { try {
while (true) { while (true) {
String data = systemQueue.take(); String data = systemQueue.take();
String[] dataStrings = data.split(" ",3); String[] dataStrings = data.split(" :?",3);
//TODO: handle mode change //TODO: handle mode change
switch (dataStrings[0]){ switch (dataStrings[1]){
case "PRIVMSG": case "PRIVMSG":
if (dataStrings[2].indexOf("\u0001") < dataStrings[2].lastIndexOf("\u0001")) { if (dataStrings[2].indexOf("\u0001") < dataStrings[2].lastIndexOf("\u0001")) {
replyCTCP(simplifyNick(dataStrings[1]), dataStrings[2].substring(dataStrings[2].indexOf(":") + 1)); String sender = simplifyNick(dataStrings[0]);
String message = dataStrings[2].substring(dataStrings[2].indexOf(":") + 1);
systemCTCP.replyCTCP(sender, message);
} }
else { else {
commander.receiver(dataStrings[1], dataStrings[2].replaceAll("^.+?:", "").trim()); commander.receiver(dataStrings[0], dataStrings[2].replaceAll("^.+?:", "").trim());
writerWorker.logAdd("[system]", "PRIVMSG from "+dataStrings[1]+" received: ", writerWorker.log(dataStrings[1]+" "+dataStrings[0]+" :", dataStrings[2].replaceAll("^.+?:", "").trim());
dataStrings[2].replaceAll("^.+?:", "").trim());
} }
break; break;
case "INNA": case "INNA":
@ -81,7 +83,7 @@ public class SystemConsumer implements Runnable{
if (dataStrings.length > 2){ // Don't touch 'cuz it's important if (dataStrings.length > 2){ // Don't touch 'cuz it's important
splitter = dataStrings[2].split(" ", 2); splitter = dataStrings[2].split(" ", 2);
if (splitter.length == 2){ if (splitter.length == 2){
handleSpecial(dataStrings[1], splitter[0], splitter[1]); handleSpecial(dataStrings[0], splitter[0], splitter[1]);
} }
} }
break; break;
@ -91,46 +93,14 @@ public class SystemConsumer implements Runnable{
} }
} }
catch (InterruptedException ie){ catch (InterruptedException ie){
System.out.println("Thread SystemConsumer->run() interrupted."); // TODO: reconnect OR AT LEAST DIE System.out.println("Thread SystemConsumer interrupted."); // TODO: reconnect OR AT LEAST DIE
} }
catch (Exception e){ catch (Exception e){
System.out.println("Internal issue: thread SystemConsumer->run(): "+e.getMessage()); // TODO: DO.. some thing System.out.println("Internal issue: SystemConsumer: "+e.getMessage()); // TODO: DO.. some thing
e.printStackTrace();
} }
} }
private void replyCTCP(String sender, String message) { // got simplified nick
// TODO: Consider moving to config file. Now set to 3 sec
if (lastCTCPReplyTime.isAfter(LocalDateTime.now().minusSeconds(3)))
return;
lastCTCPReplyTime = LocalDateTime.now();
switch (message) {
case "\u0001VERSION\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001VERSION " + GlobalData.getAppVersion() + "\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP VERSION from", sender);
return;
case "\u0001CLIENTINFO\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001CLIENTINFO ACTION PING VERSION TIME CLIENTINFO SOURCE\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP CLIENTINFO from", sender);
return;
case "\u0001TIME\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001TIME " + ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME) + "\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP TIME from", sender);
return;
case "\u0001SOURCE\u0001":
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :\u0001SOURCE https://github.com/developersu/InnaIrcBot\u0001");
writerWorker.logAdd("[system]", "catch/handled CTCP TIME from", sender);
return;
}
if (message.startsWith("\u0001PING ") && message.endsWith("\u0001")) {
StreamProvider.writeToStream(serverName, "NOTICE " + sender + " :" + message);
writerWorker.logAdd("[system]", "catch/handled CTCP PING from", sender);
return;
}
writerWorker.logAdd("[system]", "catch unknown CTCP request \"" + message + "\" from ", sender);
}
private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); } private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); }
private void handleSpecial(String event, String channelName, String message){ private void handleSpecial(String event, String channelName, String message){
@ -138,22 +108,22 @@ public class SystemConsumer implements Runnable{
if (ircChannel == null) if (ircChannel == null)
return; return;
String ircFormatterMessage = event+" "+nick+" "+channelName+" "+message; String ircFormatterMessage = event+" "+nick+" "+channelName+" "+message;
//System.out.println("Formatted: |"+event+"|"+nick+"|"+channelName+" "+message+"|");
ircChannel.getChannelQueue().add(ircFormatterMessage); ircChannel.getChannelQueue().add(ircFormatterMessage);
} }
//todo: handle nickserv messages somehow //todo: handle nickserv messages somehow
private void handleNumeric(String eventNum, String sender, String message) throws Exception{ private void handleNumeric(String sender, String eventNum, String message) throws Exception{
switch (eventNum){ switch (eventNum){
case "501": // Notify user about incorrect setup case "501": // Notify user about incorrect setup
writerWorker.logAdd("[system]", "catch/handled:", eventNum writerWorker.log("catch/handled:", eventNum
+ " [MODE message was sent with a nickname parameter and that the a mode flag sent was not recognized.]"); + " [MODE message was sent with a nickname parameter and that the a mode flag sent was not recognized.]");
break; break;
case "433": // TODO: try to use alternative nickname case "433": // TODO: try to use alternative nickname
writerWorker.logAdd("[system]", "catch/handled:", eventNum writerWorker.log("catch/handled:", eventNum
+ " [nickname already in use and will be changed]"); + " [nickname already in use and will be changed]");
break; break;
case "353": case "353":
writerWorker.logAdd("[system]", "catch/handled:", eventNum+" [RPL_NAMREPLY]"); writerWorker.log("catch/handled:", eventNum+" [RPL_NAMREPLY]");
String channelName = message.substring(nick.length()+3).replaceAll("\\s.*$", ""); String channelName = message.substring(nick.length()+3).replaceAll("\\s.*$", "");
IrcChannel ircChannel = channels.get(channelName); IrcChannel ircChannel = channels.get(channelName);
@ -164,7 +134,7 @@ public class SystemConsumer implements Runnable{
case "NICK": case "NICK":
if (sender.startsWith(nick+"!")) { if (sender.startsWith(nick+"!")) {
nick = message.trim(); nick = message.trim();
writerWorker.logAdd("[system]", "catch own NICK change:", sender+" to: "+message); writerWorker.log("catch own NICK change:", sender+" to: "+message);
} }
break; break;
case "JOIN": case "JOIN":
@ -182,43 +152,53 @@ public class SystemConsumer implements Runnable{
newIrcChannelThread.start(); newIrcChannelThread.start();
channelThreads.add(newIrcChannelThread); channelThreads.add(newIrcChannelThread);
//proxyAList.get(message).add(eventNum+" "+sender+" "+message); // Add message to array linked //proxyAList.get(message).add(eventNum+" "+sender+" "+message); // Add message to array linked
writerWorker.logAdd("[system]", "joined to channel ", message); writerWorker.log("joined channel ", message);
} }
break; break;
case "401": // No such nick/channel case "401": // No such nick/channel
//System.out.println("|"+message.replaceAll("^(\\s)?.+?(\\s)|((\\s)?:No such nick/channel)","")+"|"); //System.out.println("|"+message.replaceAll("^(\\s)?.+?(\\s)|((\\s)?:No such nick/channel)","")+"|");
CTCPHelper.getInstance().handleErrorReply(serverName, message.replaceAll("^(\\s)?.+?(\\s)|((\\s)?:No such nick/channel)","")); CTCPHelper.getInstance().handleErrorReply(server, message.replaceAll("^(\\s)?.+?(\\s)|((\\s)?:No such nick/channel)",""));
writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message+" [ok]"); writerWorker.log("catch: "+eventNum+" from: "+sender+" :",message+" [ok]");
break; break;
case "NOTICE": case "NOTICE":
CTCPHelper.getInstance().handleCtcpReply(serverName, simplifyNick(sender), message.replaceAll("^.+?:", "").trim()); CTCPHelper.getInstance().handleCtcpReply(server, simplifyNick(sender), message.replaceAll("^.+?:", "").trim());
writerWorker.logAdd("[system]", "NOTICE from "+sender+" received: ", message.replaceAll("^.+?:", "").trim()); writerWorker.log("NOTICE from "+sender+" received: ", message.replaceAll("^.+?:", "").trim());
break; break;
case "001": case "001":
sendUserModes(); sendUserModes();
sendNickPassword(); sendNickPassword();
joinChannels(); joinChannels();
writerWorker.log(eventNum, message);
break; break;
case "443": case "443":
String newNick = nick+"|"+nickTail++; String newNick = nick+"|"+nickTail++;
StreamProvider.writeToStream(serverName,"NICK "+newNick); StreamProvider.writeToStream(server,"NICK "+newNick);
break; break;
case "464": // password for server/znc/bnc case "464": // password for server/znc/bnc
StreamProvider.writeToStream(serverName,"PASS "+configurationFile.getServerPass()); StreamProvider.writeToStream(server,"PASS "+configurationFile.getServerPass());
writerWorker.log(eventNum, message);
break; break;
case "432": case "432":
writerWorker.log(eventNum, message);
System.out.println("Configuration issue: Nickname contains unacceptable characters (432 ERR_ERRONEUSNICKNAME)."); System.out.println("Configuration issue: Nickname contains unacceptable characters (432 ERR_ERRONEUSNICKNAME).");
ReconnectControl.update(serverName, false);
break;
case "465": case "465":
ReconnectControl.update(serverName, false); ReconnectControl.update(server, false);
writerWorker.log(eventNum, message);
break; break;
case "QUIT": // TODO: Do something? case "QUIT": // TODO: Do something?
writerWorker.log(eventNum, message);
break;
case "375":
writerWorker.log("MOTD Start:", message.replaceAll("^.+?:", ""));
break;
case "372":
writerWorker.log("MOTD:", message.replaceAll("^.+?:", ""));
break;
case "376":
writerWorker.log("MOTD End:", message.replaceAll("^.+?:", ""));
break; break;
default: default:
writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message); writerWorker.log("catch: "+eventNum+" from: "+sender+" :", message);
break; break;
// 431 ERR_NONICKNAMEGIVEN how can we get this? // 431 ERR_NONICKNAMEGIVEN how can we get this?
// 436 ERR_NICKCOLLISION // 436 ERR_NICKCOLLISION
@ -240,7 +220,7 @@ public class SystemConsumer implements Runnable{
message.append("\n"); message.append("\n");
} }
StreamProvider.writeToStream(serverName, message.toString()); StreamProvider.writeToStream(server, message.toString());
} }
private void sendNickPassword(){ private void sendNickPassword(){
@ -249,11 +229,11 @@ public class SystemConsumer implements Runnable{
switch (configurationFile.getUserNickAuthStyle()){ switch (configurationFile.getUserNickAuthStyle()){
case "freenode": case "freenode":
StreamProvider.writeToStream(serverName,"PRIVMSG NickServ :IDENTIFY " StreamProvider.writeToStream(server,"PRIVMSG NickServ :IDENTIFY "
+ configurationFile.getUserNickPass()); + configurationFile.getUserNickPass());
break; break;
case "rusnet": case "rusnet":
StreamProvider.writeToStream(serverName,"NickServ IDENTIFY " StreamProvider.writeToStream(server,"NickServ IDENTIFY "
+ configurationFile.getUserNickPass()); + configurationFile.getUserNickPass());
} }
} }
@ -261,12 +241,21 @@ public class SystemConsumer implements Runnable{
private void joinChannels(){ private void joinChannels(){
StringBuilder joinMessage = new StringBuilder(); StringBuilder joinMessage = new StringBuilder();
for (String cnl : configurationFile.getChannels()) { // TODO: add validation of channels. for (String channel : configurationFile.getChannels()) { // TODO: add validation of channels.
joinMessage.append("JOIN "); joinMessage.append("JOIN ");
joinMessage.append(cnl); joinMessage.append(channel);
joinMessage.append("\n"); joinMessage.append("\n");
} }
StreamProvider.writeToStream(serverName, joinMessage.toString()); StreamProvider.writeToStream(server, joinMessage.toString());
}
private void close(){
for (Thread channel : channelThreads) { //TODO: check, code duplication. see Data provider constructor
channel.interrupt();
}
writerWorker.close();
systemConsumers.remove(server);
} }
} }

View file

@ -1,23 +1,35 @@
package InnaIrcBot; package InnaIrcBot;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
public class ReconnectControl { public class ReconnectControl {
private static final HashMap<String, Boolean> serversList = new HashMap<>(); private static final Map<String, Boolean> serversList = Collections.synchronizedMap(new HashMap<>());
public static synchronized void register(String serverName){ private static final Map<String, Integer> serversReconnects = Collections.synchronizedMap(new HashMap<>());
serversList.put(serverName, true);
public static void register(String server){
serversList.putIfAbsent(server, true);
serversReconnects.putIfAbsent(server, 0);
} }
public static synchronized void update(String serverName, boolean needReconnect){ public static void update(String server, boolean needReconnect){
serversList.replace(serverName, needReconnect); serversList.replace(server, needReconnect);
} }
public static synchronized void notify(String serverName) { public static synchronized void notify(String server){
if (serversList.get(serverName) == null || ! serversList.get(serverName)){ if (! serversList.getOrDefault(server, false))
serversList.remove(serverName); return;
int count = serversReconnects.get(server);
if (count > 5) {
serversList.replace(server, false);
return; return;
} }
count++;
serversReconnects.put(server, count);
System.out.println("DEBUG: Thread "+serverName+" removed from observable list after unexpected finish.\n\t"); System.out.println("Main thread \"" + server + "\" removed from observable list after unexpected finish.\n");
ConnectionsBuilder.getConnections().startNewConnection(serverName); ConnectionsBuilder.getConnections().startNewConnection(server);
} }
} }

View file

@ -23,6 +23,7 @@ public class ConfigurationFile {
private boolean rejoinOnKick; private boolean rejoinOnKick;
private String botAdministratorPassword; private String botAdministratorPassword;
private String applicationLogDir; private String applicationLogDir;
private int cooldownTime;
private LogDriverConfiguration logDriverConfiguration; private LogDriverConfiguration logDriverConfiguration;
private List<String> channels; private List<String> channels;
private HashMap<String, ConfigurationChannel> channelConfigs; private HashMap<String, ConfigurationChannel> channelConfigs;
@ -39,6 +40,8 @@ public class ConfigurationFile {
public boolean getRejoinOnKick() { return rejoinOnKick; } public boolean getRejoinOnKick() { return rejoinOnKick; }
public String getBotAdministratorPassword() { return botAdministratorPassword; } public String getBotAdministratorPassword() { return botAdministratorPassword; }
public String getApplicationLogDir() { return applicationLogDir; } public String getApplicationLogDir() { return applicationLogDir; }
public int getCooldownTime() { return cooldownTime; }
public LogDriverConfiguration getLogDriverConfiguration(){ return logDriverConfiguration; } public LogDriverConfiguration getLogDriverConfiguration(){ return logDriverConfiguration; }
public List<String> getChannels() { return channels; } public List<String> getChannels() { return channels; }
public ConfigurationChannel getChannelConfig(String channel) { return channelConfigs.get(channel); } public ConfigurationChannel getChannelConfig(String channel) { return channelConfigs.get(channel); }
@ -76,6 +79,7 @@ public class ConfigurationFile {
this.rejoinOnKick = mainSection.get("auto rejoin", boolean.class); this.rejoinOnKick = mainSection.get("auto rejoin", boolean.class);
this.botAdministratorPassword = mainSection.getOrDefault("bot administrator password", ""); this.botAdministratorPassword = mainSection.getOrDefault("bot administrator password", "");
this.applicationLogDir = mainSection.getOrDefault("application logs", ""); this.applicationLogDir = mainSection.getOrDefault("application logs", "");
this.cooldownTime = mainSection.get("cooldown (sec)", int.class);
} }
private void parseChannels(Wini ini){ private void parseChannels(Wini ini){

View file

@ -62,36 +62,6 @@ public class ConfigurationFileGenerator {
if (! Files.exists(folderPath)) if (! Files.exists(folderPath))
Files.createDirectories(folderPath); Files.createDirectories(folderPath);
} }
/*
private void createConfigurationFileOld() throws IOException{
File configurationFile = new File(this.fileLocation);
Writer writerFile = new OutputStreamWriter(new FileOutputStream(configurationFile.getAbsolutePath()), StandardCharsets.UTF_8);
ConfigurationFile configurationFileObject = new ConfigurationFile("srv",
6667,
"",
new String[] {"#lpr",
"#main"},
"user_nick",
"ident",
"bot",
"",
"freenode",
"ix",
true,
"files",
new String[] {System.getProperty("user.home")},
"pswd",
System.getProperty("user.home"),
"/var/logs/"
);
Gson writingStorageObject = new GsonBuilder().setPrettyPrinting().create();
writingStorageObject.toJson(configurationFileObject, writerFile);
writerFile.close();
}
*/
private void createConfigurationFile() throws IOException{ private void createConfigurationFile() throws IOException{
final String mainSectionName = "main"; final String mainSectionName = "main";
final String channelSectionName = "channels"; final String channelSectionName = "channels";
@ -127,6 +97,7 @@ public class ConfigurationFileGenerator {
mainSection.put( "auto rejoin", true); mainSection.put( "auto rejoin", true);
mainSection.put( "bot administrator password", "i_pswd"); mainSection.put( "bot administrator password", "i_pswd");
mainSection.put( "application logs", "/tmp"); mainSection.put( "application logs", "/tmp");
mainSection.put("cooldown (sec)", 3);
Ini.Section loggingSection = ini.add("logging"); Ini.Section loggingSection = ini.add("logging");
loggingSection.put( "driver", "files"); loggingSection.put( "driver", "files");

View file

@ -5,7 +5,7 @@ import InnaIrcBot.logging.SupportedLogDrivers;
public class LogDriverConfiguration { public class LogDriverConfiguration {
private String name; private String name;
private final String path; private String path;
private final String mongoURI; private final String mongoURI;
private final String mongoTable; private final String mongoTable;
@ -48,12 +48,8 @@ public class LogDriverConfiguration {
} }
private void validatePath(){ private void validatePath(){
try { if (path == null)
checkFieldNotEmpty(path); path = ".";
}
catch (Exception e){
name = SupportedLogDrivers.zero;
}
} }
private void validateMongo(){ private void validateMongo(){

View file

@ -29,11 +29,14 @@ public class WorkerFiles implements Worker {
createFileWriter(); createFileWriter();
consistent = true; consistent = true;
} catch (Exception e){ } catch (Exception e){
System.out.println("BotFilesWorker (@"+server+")->constructor(): Failure:\n" + e.getMessage()); System.out.println("WorkerFiles (@"+server+")->constructor(): Failure:\n" + e.getMessage());
} }
} }
private void formatFilePath(String server, String dirLocation){ private void formatFilePath(String server, String dirLocation){
if (dirLocation.isEmpty())
dirLocation = ".";
if (! dirLocation.endsWith(File.separator)) if (! dirLocation.endsWith(File.separator))
dirLocation += File.separator; dirLocation += File.separator;
@ -47,13 +50,13 @@ public class WorkerFiles implements Worker {
if (file.isDirectory()) if (file.isDirectory())
return; return;
else else
throw new Exception("BotFilesWorker->createServerFolder() "+filePath+" is file while directory expected."); throw new Exception("WorkerFiles->createServerFolder() "+filePath+" is file while directory expected.");
} }
if (file.mkdirs()) if (file.mkdirs())
return; return;
throw new Exception("BotFilesWorker->createServerFolder() Can't create directory: "+filePath); throw new Exception("WorkerFiles->createServerFolder() Can't create directory: "+filePath);
} }
private void resetFileWriter() throws IOException{ private void resetFileWriter() throws IOException{
@ -116,7 +119,7 @@ public class WorkerFiles implements Worker {
fileWriter.write(string); fileWriter.write(string);
fileWriter.flush(); fileWriter.flush();
} catch (Exception e){ } catch (Exception e){
System.out.println("BotFilesWorker->prettyPrint() failed\n" + System.out.println("WorkerFiles->prettyPrint() failed\n" +
"\tUnable to write logs to " + this.filePath + " "+e.getMessage()); "\tUnable to write logs to " + this.filePath + " "+e.getMessage());
close(); close();
consistent = false; consistent = false;
@ -180,7 +183,7 @@ public class WorkerFiles implements Worker {
} }
catch (NullPointerException ignore) {} catch (NullPointerException ignore) {}
catch (IOException e){ catch (IOException e){
System.out.println("BotFilesWorker->close() failed\n" + System.out.println("WorkerFiles->close() failed\n" +
"\tUnable to properly close file: "+this.filePath); // Live with it. "\tUnable to properly close file: "+this.filePath); // Live with it.
} }
this.consistent = false; this.consistent = false;

View file

@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit;
public class WorkerMongoDB implements Worker { //TODO consider skipping checks if server already added. public class WorkerMongoDB implements Worker { //TODO consider skipping checks if server already added.
private final static Map<String, MongoClient> serversMap = Collections.synchronizedMap(new HashMap<>()); private final static Map<String, MongoClient> serversMap = Collections.synchronizedMap(new HashMap<>());
private final String server; private final String server;
private MongoCollection<Document> collection; private final MongoCollection<Document> collection;
private boolean consistent; private boolean consistent;
public WorkerMongoDB(String server, LogDriverConfiguration logDriverConfiguration, String channel) throws Exception{ public WorkerMongoDB(String server, LogDriverConfiguration logDriverConfiguration, String channel) throws Exception{
@ -59,26 +59,7 @@ public class WorkerMongoDB implements Worker { //TODO consider ski
} }
}; };
*/ */
ServerListener mongoServerListener = new ServerListener() { ServerListener mongoServerListener = getServerListener();
@Override
public void serverOpening(ServerOpeningEvent serverOpeningEvent) {
System.out.println("BotMongoWorker (@"+server+"): ServerListener: Server opened successfully: "+serverOpeningEvent.getServerId());
}
@Override
public void serverClosed(ServerClosedEvent serverClosedEvent) {
System.out.println("BotMongoWorker (@"+server+"): ServerListener: Server has been closed");
}
@Override
public void serverDescriptionChanged(ServerDescriptionChangedEvent serverDescriptionChangedEvent) {
if (!serverDescriptionChangedEvent.getNewDescription().isOk()) {
close(server); // server recieved by constructor, not this.server
System.out.println("BotMongoWorker (@"+server+"): ServerListener: Server description changed (exception occurs): "
+ serverDescriptionChangedEvent.getNewDescription().getException());
}
}
};
MongoClientSettings MCS = MongoClientSettings.builder() MongoClientSettings MCS = MongoClientSettings.builder()
// .addCommandListener(mongoCommandListener) // .addCommandListener(mongoCommandListener)
@ -106,6 +87,30 @@ public class WorkerMongoDB implements Worker { //TODO consider ski
setClosable(); setClosable();
} }
private ServerListener getServerListener(){
return new ServerListener() {
@Override
public void serverOpening(ServerOpeningEvent serverOpeningEvent) {
System.out.println("BotMongoWorker (@"+server+"): ServerListener: Server opened successfully: "+serverOpeningEvent.getServerId());
}
@Override
public void serverClosed(ServerClosedEvent serverClosedEvent) {
System.out.println("BotMongoWorker (@"+server+"): ServerListener: Server has been closed");
}
@Override
public void serverDescriptionChanged(ServerDescriptionChangedEvent serverDescriptionChangedEvent) {
if (serverDescriptionChangedEvent.getNewDescription().isOk())
return;
System.out.println("BotMongoWorker (@"+server+"): ServerListener: Server description changed (exception occurs): "
+ serverDescriptionChangedEvent.getNewDescription().getException());
close(server); // server recieved by constructor, not this.server
}
};
}
private void setClosable(){ private void setClosable(){
if (! consistent) if (! consistent)
return; return;
@ -148,23 +153,22 @@ public class WorkerMongoDB implements Worker { //TODO consider ski
document.append("message1", message); document.append("message1", message);
break; break;
} }
insert(document);
return consistent;
}
private void insert(Document document){
try { try {
collection.insertOne(document); // TODO: call finalize? collection.insertOne(document); // TODO: call finalize?
consistent = true; // if no exceptions, then true consistent = true; // if no exceptions, then true
} catch (MongoCommandException mce){
System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): Command exception. Check if username/password set correctly.");
this.close();
} catch (MongoTimeoutException mte) {
System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): Timeout exception.");
this.close();
}catch (MongoException me){ }catch (MongoException me){
System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): MongoDB Exception."); System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): MongoDB Exception: "+me.getMessage());
this.close(); this.close();
} catch (IllegalStateException ise){ } catch (IllegalStateException ise){
System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): Illegal state exception: MongoDB server already closed (not an issue)."); System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): Illegal state exception: MongoDB server already closed (not an issue).");
this.close(); this.close();
} }
return consistent;
} }
private long getDate(){ return System.currentTimeMillis() / 1000L; } // UNIX time private long getDate(){ return System.currentTimeMillis() / 1000L; } // UNIX time

View file

@ -9,8 +9,8 @@ import java.sql.*;
public class WorkerSQLite implements Worker { public class WorkerSQLite implements Worker {
private Connection connection; private final Connection connection;
private boolean consistent = false; private boolean consistent;
private PreparedStatement preparedStatement; private PreparedStatement preparedStatement;
private final String server; private final String server;
@ -21,10 +21,11 @@ public class WorkerSQLite implements Worker {
this.server = server; this.server = server;
String dbFileLocation = logDriverConfiguration.getPath(); String dbFileLocation = logDriverConfiguration.getPath();
File dir = new File(dbFileLocation); File dir = new File(dbFileLocation);
dir.mkdirs(); // ignore result, because if it's already exists we're good. Otherwise, it will be created. Only issue that can occur is SecurityException thrown, so let's catch it. dir.mkdirs(); // ignore result, because if it's already exists we're good. Otherwise, it will be created. Only issue that can occur is SecurityException thrown, so let's catch it.
if (!dir.exists()) { if (! dir.exists()) {
throw new Exception("BotSQLiteWorker (@"+server+")->constructor(): Failure:\n\tUnable to create directory to store DB file: " + dbFileLocation); throw new Exception("WorkerSQLite for"+server+": Unable to create directory to store DB file: " + dbFileLocation);
} }
String connectionURL; String connectionURL;
if (dbFileLocation.endsWith(File.separator)) if (dbFileLocation.endsWith(File.separator))
@ -38,7 +39,21 @@ public class WorkerSQLite implements Worker {
sqlConfig.setOpenMode(SQLiteOpenMode.NOMUTEX); //SQLITE_OPEN_NOMUTEX : multithreaded mode sqlConfig.setOpenMode(SQLiteOpenMode.NOMUTEX); //SQLITE_OPEN_NOMUTEX : multithreaded mode
this.connection = DriverManager.getConnection(connectionURL, sqlConfig.toProperties()); this.connection = DriverManager.getConnection(connectionURL, sqlConfig.toProperties());
if (connection != null){ if (connection == null){
System.out.println("WorkerSQLite for"+server+": Connection to SQLite is not established.");
return;
}
createSQLiteDatabaseTables(channel);
this.consistent = true;
this.preparedStatement = connection.prepareStatement(
"INSERT INTO \""+channel
+"\" (unixtime, event, subject, message, object) "
+"VALUES (?, ?, ?, ?, ?);");
}
private void createSQLiteDatabaseTables(String channel) throws Exception{
// Create table if not created // Create table if not created
Statement statement = connection.createStatement(); Statement statement = connection.createStatement();
String query = "CREATE TABLE IF NOT EXISTS \""+channel+"\" (" String query = "CREATE TABLE IF NOT EXISTS \""+channel+"\" ("
@ -119,26 +134,15 @@ public class WorkerSQLite implements Worker {
} }
// Validating result: it table in DB have expected schema. If not, removing and recreating table. // Validating result: it table in DB have expected schema. If not, removing and recreating table.
for (boolean element: schemaResultCheck) { for (boolean element: schemaResultCheck) {
if (!element) { if (! element) {
System.out.println("BotSQLiteWorker (@"+server+")->Constructor(): Notice:\n\tFound already existing table for channel with incorrect syntax: removing table and re-creating."); System.out.println("WorkerSQLite for "+server+": Found already existing table for channel with incorrect syntax: removing and re-creating.");
statement.executeUpdate("DROP TABLE \"" + channel + "\";"); statement.executeUpdate("DROP TABLE \"" + channel + "\";");
statement.executeUpdate(query); statement.executeUpdate(query);
break; break;
} }
} }
this.consistent = true;
this.preparedStatement = connection.prepareStatement(
"INSERT INTO \""+channel
+"\" (unixtime, event, subject, message, object) "
+"VALUES (?, ?, ?, ?, ?);");
}
else {
System.out.println("BotSQLiteWorker (@"+server+")->constructor() failed:\n\t Connection to SQLite not established.");
this.consistent = false;
} }
}
private long getDate(){ private long getDate(){
return System.currentTimeMillis() / 1000L; // UNIX time return System.currentTimeMillis() / 1000L; // UNIX time
} }

View file

@ -13,60 +13,68 @@ public class WorkerSystem{
private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss"); private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
private Closeable thingToCloseOnDie; // call .close() method of this classes when this (system log class) dies. private Closeable thingToCloseOnDie; // call .close() method of this classes when this (system log class) dies.
private final String server; private boolean consistent;
private boolean consistent = false; private String filePath;
public WorkerSystem(String server, String appLogDir){ public WorkerSystem(String server, String appLogDir){
this.server = server;
if (appLogDir.isEmpty()) {
appLogDir = System.getProperty("java.io.tmpdir")+File.separator+"innaircbot"+File.separator;
}
else if (! appLogDir.endsWith(File.separator)) {
appLogDir += File.separator;
}
appLogDir += server;
File logFile = new File(appLogDir);
try { try {
if (! logFile.getParentFile().exists()) { formatFilePath(server, appLogDir);
if (! logFile.getParentFile().mkdirs()){
System.out.println("BotSystemWorker (@"+server+")->constructor() failed:\n" + fileWriter = new FileWriter(createServerLogsFile(), true);
"\tUnable to create sub-directory(-ies) to store log file: " + appLogDir);
return;
}
}
fileWriter = new FileWriter(logFile, true);
consistent = true; consistent = true;
} catch (SecurityException e){ } catch (Exception e){
System.out.println("BotSystemWorker (@"+server+")->constructor() failed.\n" + System.out.println("BotSystemWorker for "+server+" failed: " + e.getMessage());
"\tUnable to create sub-directory(-ies) to store logs file ("+appLogDir+"):\n\t"+e.getMessage());
} catch (IOException oie){
System.out.println("BotSystemWorker (@"+server+")->constructor() failed:\n" +
"\tUnable to open file to store logs: " + appLogDir + " "+ oie.getMessage());
} }
} }
private void formatFilePath(String server, String dirLocation){
if (dirLocation.isEmpty())
dirLocation = "./";
if (dirLocation.endsWith(File.separator))
this.filePath = dirLocation+server+".log";
else
this.filePath = dirLocation;
}
private File createServerLogsFile() throws Exception{
final File file = new File(filePath);
if (file.exists()){
if (file.isFile())
return file;
else
throw new Exception("WorkerSystem: \""+filePath+"\" is directory while file expected.");
}
if (file.createNewFile())
return file;
throw new Exception("WorkerSystem: Can't create file: "+filePath);
}
private String genDate(){ private String genDate(){
return "["+ LocalTime.now().format(dateFormat)+"] "; return "["+ LocalTime.now().format(dateFormat)+"]";
} }
public void logAdd(String event, String initiatorArg, String messageArg) { public void log(String initiatorArg, String messageArg) {
if (consistent) { String message = String.format("%s %s %s\n", genDate(), initiatorArg, messageArg);
if (consistent)
logToFile(message);
else
System.out.println(message);
}
private void logToFile(String message){
try { try {
fileWriter.write(genDate() + event + " " + initiatorArg + " " + messageArg + "\n"); fileWriter.write(message);
fileWriter.flush(); fileWriter.flush();
} catch (Exception e) { // ??? No ideas. Just in case. Consider removing. } catch (Exception e) {
System.out.println("BotSystemWorker (@" + server + ")->logAdd() failed\n\tUnable to write logs of because of exception:\n\t" + e.getMessage()); System.out.println("BotSystemWorker: unable to write application logs: " + e.getMessage());
//this.close();
consistent = false; consistent = false;
//this.close();
} }
return;
}
System.out.println(genDate() + event + " " + initiatorArg + " " + messageArg + "\n");
} }
public void registerInSystemWorker(Closeable thing){ public void registerInSystemWorker(Closeable thing){

View file

@ -1,7 +1,6 @@
package InnaIrcBot.Commanders; package InnaIrcBot.Commanders;
import InnaIrcBot.ProvidersConsumers.StreamProvider; import InnaIrcBot.ProvidersConsumers.StreamProvider;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -37,7 +36,6 @@ class JoinFloodHandlerTest {
testSocket.connect(new InetSocketAddress(60000)); testSocket.connect(new InetSocketAddress(60000));
StreamProvider.setStream(serverName, testSocket); StreamProvider.setStream(serverName, testSocket);
StreamProvider.setSysConsumer(serverName, new ArrayBlockingQueue<>(100));
} }
catch (IOException e){ catch (IOException e){
e.printStackTrace(); e.printStackTrace();