diff --git a/pom.xml b/pom.xml index 1ba5fea..fa46077 100644 --- a/pom.xml +++ b/pom.xml @@ -30,8 +30,8 @@ org.ini4j ini4j 0.5.4 + compile - commons-cli commons-cli diff --git a/src/main/java/InnaIrcBot/Commanders/ChanelCommander.java b/src/main/java/InnaIrcBot/Commanders/ChanelCommander.java index f32c387..ad1c7d9 100644 --- a/src/main/java/InnaIrcBot/Commanders/ChanelCommander.java +++ b/src/main/java/InnaIrcBot/Commanders/ChanelCommander.java @@ -20,10 +20,10 @@ public class ChanelCommander implements Runnable { private HashMap msgMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw private HashMap nickMap; // Mask(Pattern) ->, Action | Where Action[0] could be: raw - private boolean joinFloodTrackNeed = false; + private boolean joinFloodTrackNeed; private JoinFloodHandler jfh; - private boolean joinCloneTrackNeed = false; // todo:fix + private boolean joinCloneTrackNeed; private JoinCloneHandler jch; public ChanelCommander(BlockingQueue stream, String serverName, String channel) throws Exception{ @@ -40,21 +40,21 @@ public class ChanelCommander implements Runnable { try { while (true) { String data = streamQueue.take(); - String[] dataStrings = data.split(" ",3); + String[] dataStrings = data.split(" :?",3); - switch (dataStrings[0]) { + switch (dataStrings[1]) { case "NICK": - nickCame(dataStrings[2]+dataStrings[1].replaceAll("^.+?!","!")); + nickCame(dataStrings[2]+dataStrings[0].replaceAll("^.+?!","!")); break; // todo: need to track join flood case "JOIN": if (joinFloodTrackNeed) - jfh.track(simplifyNick(dataStrings[1])); + jfh.track(simplifyNick(dataStrings[0])); if (joinCloneTrackNeed) - jch.track(dataStrings[1]); - joinCame(dataStrings[1]); + jch.track(dataStrings[0]); + joinCame(dataStrings[0]); break; case "PRIVMSG": - privmsgCame(dataStrings[1], dataStrings[2]); + privmsgCame(dataStrings[0], dataStrings[2]); break; /* 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){ - 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? @@ -94,7 +95,7 @@ public class ChanelCommander implements Runnable { StringBuilder whatToSendStringBuilder; ArrayList whatToSendList; - for (int i = 0; i(); @@ -163,15 +164,19 @@ public class ChanelCommander implements Runnable { else whatToSendStringBuilder.append(cmdOrMsg[i]); } - if (objectRegexp != null) { - String objectToCtcp = arg1.trim().replaceAll(objectRegexp, ""); // note: trim() ? - if (!objectToCtcp.isEmpty()){ - if (CTCPType.startsWith("\\c")) - CTCPHelper.getInstance().registerRequest(server, channel, CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString()); - else - CTCPHelper.getInstance().registerRequest(server, simplifyNick(arg2), CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString()); - } - } + + if (objectRegexp == null) + break; + + String objectToCtcp = arg1.trim().replaceAll(objectRegexp, ""); // note: trim() ? + + if (objectToCtcp.isEmpty()) + break; + + if (CTCPType.startsWith("\\c")) + registerCTCPforChannel(CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString()); + else + registerCTCPforUser(simplifyNick(arg2), CTCPType.substring(2).toUpperCase(), objectToCtcp, whatToSendStringBuilder.toString()); break; 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. 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(); executiveStr.append("PRIVMSG "); - if(sendToPrivate) { + if(isToUser) { executiveStr.append(simplifyNick(who)); executiveStr.append(" :"); } @@ -236,6 +248,13 @@ public class ChanelCommander implements Runnable { private void readConfing() throws Exception{ ConfigurationChannel configChannel = ConfigurationManager.getConfiguration(server).getChannelConfig(channel); + if (configChannel == null){ + joinMap = new HashMap<>(); + msgMap = new HashMap<>(); + nickMap = new HashMap<>(); + return; + } + joinMap = configChannel.getJoinMap(); msgMap = configChannel.getMsgMap(); nickMap = configChannel.getNickMap(); diff --git a/src/main/java/InnaIrcBot/Commanders/PrivateMsgCommander.java b/src/main/java/InnaIrcBot/Commanders/PrivateMsgCommander.java index 7ee629c..3456735 100644 --- a/src/main/java/InnaIrcBot/Commanders/PrivateMsgCommander.java +++ b/src/main/java/InnaIrcBot/Commanders/PrivateMsgCommander.java @@ -18,212 +18,217 @@ public class PrivateMsgCommander { // TODO: add black list: add users afte } public void receiver(String sender, String message){ - if (!password.isEmpty()) { - if (administrators.contains(sender) && !message.isEmpty()) { - String[] cmd = message.split("(\\s)|(\t)+?", 2); - cmd[0] = cmd[0].toLowerCase(); - if (cmd.length > 1) - cmd[1] = cmd[1].trim(); + if (password.isEmpty() || message.isEmpty()) + return; - switch (cmd[0]){ - case "tell": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] tellArgs = cmd[1].split("(\\s)|(\t)+?", 2); - tell(tellArgs[0], tellArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: tell "); - break; - case "join": - if (cmd.length == 2) - join(cmd[1]); - else - tell(simplifyNick(sender), "Pattern: join "); - break; - case "quit": - if (cmd.length == 2) - quit(cmd[1]); - else - quit(""); - break; - case "nick": - if (cmd.length == 2) - nick(cmd[1]); - else - tell(simplifyNick(sender), "Pattern: nick "); - break; - case "part": //TODO: update - if (cmd.length == 2) - part(cmd[1]); - else - tell(simplifyNick(sender), "Pattern: part [reason]"); - break; - case "ctcp": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] ctcpArgs = cmd[1].split("(\\s)|(\t)+?", 2); - ctcp(ctcpArgs[0], ctcpArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: ctcp "); - break; - case "notice": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] noticeArgs = cmd[1].split("(\\s)|(\t)+?", 2); - notice(noticeArgs[0], noticeArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: notice "); - break; - case "umode": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] modeArgs = cmd[1].split("(\\s)|(\t)+?", 2); - umode(modeArgs[0], modeArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: umode <[+|-]mode_single_char>"); - break; - case "raw": - if (cmd.length == 2) - raw(cmd[1]); - else - tell(simplifyNick(sender), "Pattern: raw "); - break; - case "cmode": - if ((cmd.length >= 2) && (cmd[1].split("(\\s)|(\t)+?",3).length >= 2)) { - String[] args = cmd[1].split("(\\s)|(\t)+?", 3); - if (args.length == 2) - cmode(args[0], args[1], null); - else if(args.length == 3) - cmode(args[0], args[1], args[2]); - } - else - tell(simplifyNick(sender), "Pattern: cmode [| ]"); - - break; - case "k": - case "kick": - if ((cmd.length >= 2) && (cmd[1].split("(\\s)|(\t)+?",3).length >= 2)) { - String[] args = cmd[1].split("(\\s)|(\t)+?", 3); - if (args.length == 2) - kick(args[0], args[1], null); - else if(args.length == 3) - kick(args[0], args[1], args[2]); - } - else - tell(simplifyNick(sender), "Pattern: [k|kick] [reason]"); - break; - case "b": - case "ban": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] banArgs = cmd[1].split("(\\s)|(\t)+?", 2); - ban(banArgs[0], banArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: [b|ban] "); - break; - case "-b": - case "unban": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] banArgs = cmd[1].split("(\\s)|(\t)+?", 2); - unban(banArgs[0], banArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: [-b|unban] "); - break; - case "kb": - case "kickban": - if ((cmd.length >= 2) && (cmd[1].split("(\\s)|(\t)+?",3).length >= 2)) { - String[] args = cmd[1].split("(\\s)|(\t)+?", 3); - if (args.length == 2) - kickban(args[0], args[1], null); - else if(args.length == 3) - kickban(args[0], args[1], args[2]); - } - else - tell(simplifyNick(sender), "Pattern: [kb|kickban] "); - break; - case "v": - case "voice": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] voiceArgs = cmd[1].split("(\\s)|(\t)+?", 2); - voice(voiceArgs[0], voiceArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: [v|voice] "); - break; - case "-v": - case "unvoice": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] voiceArgs = cmd[1].split("(\\s)|(\t)+?", 2); - devoice(voiceArgs[0], voiceArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: [-v|unvoice] "); - break; - case "h": - case "hop": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] hopArgs = cmd[1].split("(\\s)|(\t)+?", 2); - hop(hopArgs[0], hopArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: [h|hop] "); - break; - case "-h": - case "unhop": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] hopArgs = cmd[1].split("(\\s)|(\t)+?", 2); - dehop(hopArgs[0], hopArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: [-h|unhop] "); - break; - case "o": - case "op": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] operatorArgs = cmd[1].split("(\\s)|(\t)+?", 2); - op(operatorArgs[0], operatorArgs[1]); - } - else - tell(simplifyNick(sender), "Pattern: [o|operator] "); - break; - case "-o": - case "unop": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] args = cmd[1].split("(\\s)|(\t)+?", 2); - deop(args[0], args[1]); - } - else - tell(simplifyNick(sender), "Pattern: [-o|unoperator] "); - break; - case "topic": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] args = cmd[1].split("(\\s)|(\t)+?", 2); - topic(args[0], args[1]); - } - else - tell(simplifyNick(sender), "Pattern: topic "); - break; - case "invite": - if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { - String[] args = cmd[1].split("(\\s)|(\t)+?", 2); - invite(args[0], args[1]); - } - else - tell(simplifyNick(sender), "Pattern: invite "); - break; - case "login": - tell(simplifyNick(sender), "Already logged in."); - break; - 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)"); - } // TODO: chanel limits set/remove - } else { - if (!message.isEmpty() && message.startsWith("login ")) { - login(sender, message.replaceAll("^([\t\\s]+)?login([\t\\s]+)|([\t\\s]+$)", "")); - } + if (isNotAuthorized(sender)){ + if (message.startsWith("login ")) { + login(sender, message.substring("login ".length()).trim()); } + return; } + + String[] cmd = message.split("(\\s)|(\t)+?", 2); + cmd[0] = cmd[0].toLowerCase(); + if (cmd.length > 1) + cmd[1] = cmd[1].trim(); + + switch (cmd[0]){ + case "tell": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] tellArgs = cmd[1].split("(\\s)|(\t)+?", 2); + tell(tellArgs[0], tellArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: tell "); + break; + case "join": + if (cmd.length == 2) + join(cmd[1]); + else + tell(simplifyNick(sender), "Pattern: join "); + break; + case "quit": + if (cmd.length == 2) + quit(cmd[1]); + else + quit(""); + break; + case "nick": + if (cmd.length == 2) + nick(cmd[1]); + else + tell(simplifyNick(sender), "Pattern: nick "); + break; + case "part": //TODO: update + if (cmd.length == 2) + part(cmd[1]); + else + tell(simplifyNick(sender), "Pattern: part [reason]"); + break; + case "ctcp": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] ctcpArgs = cmd[1].split("(\\s)|(\t)+?", 2); + ctcp(ctcpArgs[0], ctcpArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: ctcp "); + break; + case "notice": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] noticeArgs = cmd[1].split("(\\s)|(\t)+?", 2); + notice(noticeArgs[0], noticeArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: notice "); + break; + case "umode": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] modeArgs = cmd[1].split("(\\s)|(\t)+?", 2); + umode(modeArgs[0], modeArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: umode <[+|-]mode_single_char>"); + break; + case "raw": + if (cmd.length == 2) + raw(cmd[1]); + else + tell(simplifyNick(sender), "Pattern: raw "); + break; + case "cmode": + if ((cmd.length >= 2) && (cmd[1].split("(\\s)|(\t)+?",3).length >= 2)) { + String[] args = cmd[1].split("(\\s)|(\t)+?", 3); + if (args.length == 2) + cmode(args[0], args[1], null); + else if(args.length == 3) + cmode(args[0], args[1], args[2]); + } + else + tell(simplifyNick(sender), "Pattern: cmode [| ]"); + + break; + case "k": + case "kick": + if ((cmd.length >= 2) && (cmd[1].split("(\\s)|(\t)+?",3).length >= 2)) { + String[] args = cmd[1].split("(\\s)|(\t)+?", 3); + if (args.length == 2) + kick(args[0], args[1], null); + else if(args.length == 3) + kick(args[0], args[1], args[2]); + } + else + tell(simplifyNick(sender), "Pattern: [k|kick] [reason]"); + break; + case "b": + case "ban": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] banArgs = cmd[1].split("(\\s)|(\t)+?", 2); + ban(banArgs[0], banArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: [b|ban] "); + break; + case "-b": + case "unban": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] banArgs = cmd[1].split("(\\s)|(\t)+?", 2); + unban(banArgs[0], banArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: [-b|unban] "); + break; + case "kb": + case "kickban": + if ((cmd.length >= 2) && (cmd[1].split("(\\s)|(\t)+?",3).length >= 2)) { + String[] args = cmd[1].split("(\\s)|(\t)+?", 3); + if (args.length == 2) + kickban(args[0], args[1], null); + else if(args.length == 3) + kickban(args[0], args[1], args[2]); + } + else + tell(simplifyNick(sender), "Pattern: [kb|kickban] "); + break; + case "v": + case "voice": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] voiceArgs = cmd[1].split("(\\s)|(\t)+?", 2); + voice(voiceArgs[0], voiceArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: [v|voice] "); + break; + case "-v": + case "unvoice": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] voiceArgs = cmd[1].split("(\\s)|(\t)+?", 2); + devoice(voiceArgs[0], voiceArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: [-v|unvoice] "); + break; + case "h": + case "hop": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] hopArgs = cmd[1].split("(\\s)|(\t)+?", 2); + hop(hopArgs[0], hopArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: [h|hop] "); + break; + case "-h": + case "unhop": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] hopArgs = cmd[1].split("(\\s)|(\t)+?", 2); + dehop(hopArgs[0], hopArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: [-h|unhop] "); + break; + case "o": + case "op": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] operatorArgs = cmd[1].split("(\\s)|(\t)+?", 2); + op(operatorArgs[0], operatorArgs[1]); + } + else + tell(simplifyNick(sender), "Pattern: [o|operator] "); + break; + case "-o": + case "unop": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] args = cmd[1].split("(\\s)|(\t)+?", 2); + deop(args[0], args[1]); + } + else + tell(simplifyNick(sender), "Pattern: [-o|unoperator] "); + break; + case "topic": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] args = cmd[1].split("(\\s)|(\t)+?", 2); + topic(args[0], args[1]); + } + else + tell(simplifyNick(sender), "Pattern: topic "); + break; + case "invite": + if ((cmd.length == 2) && (cmd[1].split("(\\s)|(\t)+?",2).length == 2)) { + String[] args = cmd[1].split("(\\s)|(\t)+?", 2); + invite(args[0], args[1]); + } + else + tell(simplifyNick(sender), "Pattern: invite "); + break; + case "login": + tell(simplifyNick(sender), "Already logged in."); + break; + 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)"); + } // TODO: add options for setting channel limits (MODEs) + } + private boolean isNotAuthorized(String sender){ + return ! administrators.contains(sender); } private void join(String channel){ @@ -233,14 +238,12 @@ public class PrivateMsgCommander { // TODO: add black list: add users afte raw("PART "+channel); } private void quit(String message){ + ReconnectControl.update(server, false); if (message.isEmpty()){ raw("QUIT :"+ GlobalData.getAppVersion()); } else 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){ message = message.trim(); diff --git a/src/main/java/InnaIrcBot/GlobalData.java b/src/main/java/InnaIrcBot/GlobalData.java index 682a36d..1bac000 100644 --- a/src/main/java/InnaIrcBot/GlobalData.java +++ b/src/main/java/InnaIrcBot/GlobalData.java @@ -8,5 +8,6 @@ public class GlobalData { System.getProperty("os.version"), System.getProperty("os.arch")); } + public static final String applicationHomePage = "https://github.com/developersu/InnaIrcBot"; public static final int CHANNEL_QUEUE_CAPACITY = 500; } diff --git a/src/main/java/InnaIrcBot/ProvidersConsumers/ChanConsumer.java b/src/main/java/InnaIrcBot/ProvidersConsumers/ChanConsumer.java index da713ba..7a632a2 100644 --- a/src/main/java/InnaIrcBot/ProvidersConsumers/ChanConsumer.java +++ b/src/main/java/InnaIrcBot/ProvidersConsumers/ChanConsumer.java @@ -21,10 +21,10 @@ public class ChanConsumer implements Runnable { private final BlockingQueue chanConsumerQueue; private final String serverName; private final String channelName; - private Worker writerWorker; - private ArrayList userList; + private final ArrayList users; + private Worker logWorker; private String nick; - private final boolean rejoin; + private final boolean autoRejoin; private final Map channels; private Thread channelCommanderThread; @@ -32,17 +32,20 @@ public class ChanConsumer implements Runnable { private boolean endThread = false; + private boolean hasBeenKicked; + ChanConsumer(String serverName, IrcChannel thisIrcChannel, String ownNick, - Map channels) throws Exception{ + Map channels) throws Exception + { this.chanConsumerQueue = thisIrcChannel.getChannelQueue(); this.serverName = serverName; this.channelName = thisIrcChannel.toString(); - this.writerWorker = LogDriver.getWorker(serverName, channelName); - this.userList = new ArrayList<>(); + this.logWorker = LogDriver.getWorker(serverName, channelName); + this.users = new ArrayList<>(); this.nick = ownNick; - this.rejoin = ConfigurationManager.getConfiguration(serverName).getRejoinOnKick(); + this.autoRejoin = ConfigurationManager.getConfiguration(serverName).getRejoinOnKick(); this.channels = channels; getChanelCommander(); } @@ -55,97 +58,115 @@ public class ChanConsumer implements Runnable { } 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 try { while (! endThread) { - data = chanConsumerQueue.take(); - dataStrings = data.split(" ",3); + String data = chanConsumerQueue.take(); + String[] dataStrings = data.split(" :?",3); - if (! trackUsers(dataStrings[0], dataStrings[1], dataStrings[2])) + if (trackUsers(dataStrings[1], dataStrings[0], dataStrings[2])) continue; - // Send to chanel commander thread - queue.add(data); // TODO: Check and add consistency validation + // Send to channel commander thread + // 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: - this.fixLogDriverIssues(dataStrings[0], dataStrings[1], dataStrings[2]); + if (! logWorker.logAdd(dataStrings[1], dataStrings[0], dataStrings[2])){ // Write logs checks if LogDriver consistent. + this.fixLogDriverIssues(dataStrings[1], dataStrings[0], dataStrings[2]); } } - channels.remove(channelName); } 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 - channelCommanderThread.interrupt(); - System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":"+this.channelName +" ended"); // TODO:REMOVE DEBUG + + close(); + 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) { case "PRIVMSG": // most common, we don't have to handle anything else - return true; + return false; case "JOIN": - addUser(simplifyNick(initiatorArg)); - return true; + addUser(initiator); + return false; case "PART": - deleteUser(simplifyNick(initiatorArg)); // nick non-simple - return true; + deleteUser(initiator); + return false; case "QUIT": - if (userList.contains(simplifyNick(initiatorArg))) { - deleteUser(simplifyNick(initiatorArg)); // nick non-simple - return true; + if (users.contains(initiator)) { + deleteUser(initiator); + return false; } - else - return false; // user quit, but he/she is not in this channel + return true; // user quit, but he/she is not in this channel case "KICK": - if (rejoin && nick.equals(subjectArg.replaceAll("(^.+?\\s)|(\\s.+$)", ""))) // if it's me and I have rejoin policy 'Auto-Rejoin on kick'. - StreamProvider.writeToStream(serverName, "JOIN " + channelName); - deleteUser(subjectArg.replaceAll("(^.+?\\s)|(\\s.+$)", "")); // nick already simplified - return true; - case "NICK": - if (userList.contains(simplifyNick(initiatorArg))) { - swapUsers(simplifyNick(initiatorArg), subjectArg); + String kickedUser = subject.replaceAll("(^.+?\\s)|(\\s.+$)", ""); + if (nick.equals(kickedUser) && autoRejoin) { // TODO: FIX + hasBeenKicked = true; + deleteUser(kickedUser); return true; } - else { - return false; // user changed nick, but he/she is not in this channel + deleteUser(kickedUser); + return false; + case "NICK": + if (users.contains(initiator)) { + swapUsers(initiator, subject); + return false; } + return true; // user changed nick, but he/she is not in this channel case "353": - String userOnChanStr = subjectArg.substring(subjectArg.indexOf(":") + 1); + String userOnChanStr = subject.substring(subject.indexOf(":") + 1); userOnChanStr = userOnChanStr.replaceAll("[%@+]", "").trim(); String[] usersOnChanArr = userOnChanStr.split(" "); - userList.addAll(Arrays.asList(usersOnChanArr)); + users.addAll(Arrays.asList(usersOnChanArr)); return true; default: - return true; + return false; } } private void addUser(String user){ - if (!userList.contains(user)) - userList.add(user); + if (! users.contains(user)) + users.add(user); } private void deleteUser(String user){ if (user.equals(nick)) { endThread = true; } - userList.remove(user); + users.remove(user); } private void swapUsers(String userNickOld, String userNickNew){ - userList.remove(userNickOld); - userList.add(userNickNew); + users.remove(userNickOld); + users.add(userNickNew); if (userNickOld.equals(nick)) - this.nick = userNickNew; + nick = userNickNew; } 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){ 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 - if (! writerWorker.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 = LogDriver.getWorker(serverName, channelName); // Reset logDriver and try using the same one + if (! logWorker.logAdd(a, b, c)){ // Write to it what was not written (most likely) and if it's still not consistent: + logWorker = new WorkerZero(); System.out.println("ChanConsumer (@"+serverName+"/"+channelName+")->fixLogDriverIssues(): failed to use defined LogDriver. Using ZeroWorker instead."); } } diff --git a/src/main/java/InnaIrcBot/ProvidersConsumers/DataProvider.java b/src/main/java/InnaIrcBot/ProvidersConsumers/DataProvider.java index 130cf96..fd5b095 100644 --- a/src/main/java/InnaIrcBot/ProvidersConsumers/DataProvider.java +++ b/src/main/java/InnaIrcBot/ProvidersConsumers/DataProvider.java @@ -11,103 +11,53 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.concurrent.BlockingQueue; public class DataProvider implements Runnable { - private final ConfigurationFile configurationFile; + private final ConfigurationFile configuration; private final String server; - private final String nickName; + private final String nick; private BufferedReader mainReader; + private Thread systemConsumerThread; + private Map ircChannels; + private IrcChannel systemConsumerChannel; + /** * Initiate connection and prepare input/output streams for run() * */ - public DataProvider(ConfigurationFile configurationFile){ - this.configurationFile = configurationFile; - this.server = configurationFile.getServerName(); - this.nickName = configurationFile.getUserNick(); + public DataProvider(ConfigurationFile configuration){ + this.configuration = configuration; + this.server = configuration.getServerName(); + this.nick = configuration.getUserNick(); } public void run(){ try { - connect(); + connectSocket(); + + ReconnectControl.register(server); + LogDriver.setLogDriver(server); + + ircChannels = Collections.synchronizedMap(new HashMap<>()); + systemConsumerChannel = new IrcChannel(""); + ircChannels.put(systemConsumerChannel.toString(), systemConsumerChannel); + + startSystemConsumer(); + sendUserNickAndIdent(); + + startLoop(); + } catch (Exception e){ - System.out.println("Internal issue: DataProvider->run() caused exception:\n\t"+e.getMessage()); - e.printStackTrace(); - - close(); - return; - } - - ReconnectControl.register(server); - - LogDriver.setLogDriver(server); - - /* Used for sending data into consumers objects*/ - Map ircChannels = Collections.synchronizedMap(new HashMap<>()); - - IrcChannel systemConsumerChannel = new IrcChannel(""); - BlockingQueue systemConsumerQueue = systemConsumerChannel.getChannelQueue(); - - Thread SystemConsumerThread = new Thread( - new SystemConsumer(systemConsumerQueue, nickName, ircChannels, this.configurationFile)); - SystemConsumerThread.start(); - - StreamProvider.setSysConsumer(server, systemConsumerQueue); // Register system consumer at StreamProvider - - 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(); + System.out.println("DataProvider exception: "+e.getMessage()); } + close(); } - private void connect() throws Exception{ - final int port = configurationFile.getServerPort(); + private void connectSocket() throws Exception{ + final int port = configuration.getServerPort(); InetAddress inetAddress = InetAddress.getByName(server); - Socket socket = new Socket(); // TODO: set timeout? + Socket socket = new Socket(); for (int i = 0; i < 5; i++) { socket.connect(new InetSocketAddress(inetAddress, port), 5000); // 5sec if (socket.isConnected()) @@ -117,18 +67,63 @@ public class DataProvider implements Runnable { throw new Exception("Unable to connect server."); StreamProvider.setStream(server, socket); - - InputStream inStream = socket.getInputStream(); - InputStreamReader isr = new InputStreamReader(inStream, StandardCharsets.UTF_8); //TODO set charset in options; - mainReader = new BufferedReader(isr); + //TODO set charset in options; + mainReader = new BufferedReader( + new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8) + ); + } + 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){ - StreamProvider.writeToStream(server,"PONG :" + rawData.replace("PING :", "")); + private void startLoop() throws Exception{ + 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(){ - StreamProvider.delStream(server); - ReconnectControl.notify(server); + try { + systemConsumerThread.interrupt(); + systemConsumerThread.join(); + StreamProvider.delStream(server); + ReconnectControl.notify(server); + } + catch (InterruptedException ignored){} } } diff --git a/src/main/java/InnaIrcBot/ProvidersConsumers/StreamProvider.java b/src/main/java/InnaIrcBot/ProvidersConsumers/StreamProvider.java index aa22911..590461a 100644 --- a/src/main/java/InnaIrcBot/ProvidersConsumers/StreamProvider.java +++ b/src/main/java/InnaIrcBot/ProvidersConsumers/StreamProvider.java @@ -5,20 +5,17 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.HashMap; -import java.util.concurrent.BlockingQueue; public class StreamProvider { private static final HashMap srvStreamMap = new HashMap<>(); - private static final HashMap> srvSysConsumersMap = new HashMap<>(); public static synchronized void writeToStream(String server, String message){ try { srvStreamMap.get(server).write(message+"\n"); srvStreamMap.get(server).flush(); - // If this application says something, then pass it into system consumer thread to handle if (message.startsWith("PRIVMSG ")) { - srvSysConsumersMap.get(server).add("INNA "+message); + SystemConsumer.getSystemConsumer(server).add("INNA "+message); } } catch (IOException e){ 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){ srvStreamMap.remove(server); - srvSysConsumersMap.remove(server); - } - - public static synchronized void setSysConsumer(String server, BlockingQueue queue){ - srvSysConsumersMap.put(server, queue); } } diff --git a/src/main/java/InnaIrcBot/ProvidersConsumers/SystemCTCP.java b/src/main/java/InnaIrcBot/ProvidersConsumers/SystemCTCP.java new file mode 100644 index 0000000..ab13312 --- /dev/null +++ b/src/main/java/InnaIrcBot/ProvidersConsumers/SystemCTCP.java @@ -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); + } +} diff --git a/src/main/java/InnaIrcBot/ProvidersConsumers/SystemConsumer.java b/src/main/java/InnaIrcBot/ProvidersConsumers/SystemConsumer.java index e7d72d9..f1f173c 100644 --- a/src/main/java/InnaIrcBot/ProvidersConsumers/SystemConsumer.java +++ b/src/main/java/InnaIrcBot/ProvidersConsumers/SystemConsumer.java @@ -4,84 +4,86 @@ import InnaIrcBot.Commanders.CTCPHelper; import InnaIrcBot.Commanders.PrivateMsgCommander; import InnaIrcBot.ReconnectControl; import InnaIrcBot.config.ConfigurationFile; -import InnaIrcBot.GlobalData; import InnaIrcBot.IrcChannel; import InnaIrcBot.logging.LogDriver; import InnaIrcBot.logging.WorkerSystem; -import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.BlockingQueue; public class SystemConsumer implements Runnable{ private final BlockingQueue systemQueue; - private WorkerSystem writerWorker; + private final WorkerSystem writerWorker; private String nick; - private String serverName; + private final String server; private final Map channels; - private ConfigurationFile configurationFile; + private final ConfigurationFile configurationFile; - private PrivateMsgCommander commander; + private final PrivateMsgCommander commander; - private LocalDateTime lastCTCPReplyTime; - - private ArrayList channelThreads; + private final ArrayList channelThreads; private int nickTail = 0; + private final SystemCTCP systemCTCP; - SystemConsumer(BlockingQueue systemQueue, String userNick, Map channels, ConfigurationFile configurationFile) { - this.systemQueue = systemQueue; - this.writerWorker = LogDriver.getSystemWorker(configurationFile.getServerName()); + private static final HashMap> systemConsumers = new HashMap<>(); + public static synchronized BlockingQueue getSystemConsumer(String server){ + return systemConsumers.get(server); + } + + SystemConsumer(String userNick, Map channels, ConfigurationFile configurationFile) { + this.systemQueue = channels.get("").getChannelQueue(); this.nick = userNick; - this.serverName = configurationFile.getServerName(); + this.server = configurationFile.getServerName(); + this.writerWorker = LogDriver.getSystemWorker(server); this.channels = channels; this.channelThreads = new ArrayList<>(); 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 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 - channel.interrupt(); - } - - writerWorker.close(); - System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))+"] THREAD "+serverName+":[system] ended"); // TODO:REMOVE DEBUG + System.out.println("["+LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + +"] Thread SystemConsumer \""+ server +"\": ended"); // TODO:REMOVE } - private void setMainRoutine(){ + private void startMainRoutine(){ try { while (true) { String data = systemQueue.take(); - String[] dataStrings = data.split(" ",3); + String[] dataStrings = data.split(" :?",3); //TODO: handle mode change - switch (dataStrings[0]){ + switch (dataStrings[1]){ case "PRIVMSG": 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 { - commander.receiver(dataStrings[1], dataStrings[2].replaceAll("^.+?:", "").trim()); - writerWorker.logAdd("[system]", "PRIVMSG from "+dataStrings[1]+" received: ", - dataStrings[2].replaceAll("^.+?:", "").trim()); + commander.receiver(dataStrings[0], dataStrings[2].replaceAll("^.+?:", "").trim()); + writerWorker.log(dataStrings[1]+" "+dataStrings[0]+" :", dataStrings[2].replaceAll("^.+?:", "").trim()); } break; case "INNA": String[] splitter; - 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); if (splitter.length == 2){ - handleSpecial(dataStrings[1], splitter[0], splitter[1]); + handleSpecial(dataStrings[0], splitter[0], splitter[1]); } } break; @@ -91,46 +93,14 @@ public class SystemConsumer implements Runnable{ } } 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){ - 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 void handleSpecial(String event, String channelName, String message){ @@ -138,22 +108,22 @@ public class SystemConsumer implements Runnable{ if (ircChannel == null) return; String ircFormatterMessage = event+" "+nick+" "+channelName+" "+message; - //System.out.println("Formatted: |"+event+"|"+nick+"|"+channelName+" "+message+"|"); + ircChannel.getChannelQueue().add(ircFormatterMessage); } //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){ 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.]"); break; 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]"); break; 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.*$", ""); IrcChannel ircChannel = channels.get(channelName); @@ -164,7 +134,7 @@ public class SystemConsumer implements Runnable{ case "NICK": if (sender.startsWith(nick+"!")) { nick = message.trim(); - writerWorker.logAdd("[system]", "catch own NICK change:", sender+" to: "+message); + writerWorker.log("catch own NICK change:", sender+" to: "+message); } break; case "JOIN": @@ -182,43 +152,53 @@ public class SystemConsumer implements Runnable{ newIrcChannelThread.start(); channelThreads.add(newIrcChannelThread); //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; case "401": // 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)","")); - writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message+" [ok]"); + CTCPHelper.getInstance().handleErrorReply(server, message.replaceAll("^(\\s)?.+?(\\s)|((\\s)?:No such nick/channel)","")); + writerWorker.log("catch: "+eventNum+" from: "+sender+" :",message+" [ok]"); break; case "NOTICE": - CTCPHelper.getInstance().handleCtcpReply(serverName, simplifyNick(sender), message.replaceAll("^.+?:", "").trim()); - writerWorker.logAdd("[system]", "NOTICE from "+sender+" received: ", message.replaceAll("^.+?:", "").trim()); + CTCPHelper.getInstance().handleCtcpReply(server, simplifyNick(sender), message.replaceAll("^.+?:", "").trim()); + writerWorker.log("NOTICE from "+sender+" received: ", message.replaceAll("^.+?:", "").trim()); break; case "001": sendUserModes(); sendNickPassword(); joinChannels(); + writerWorker.log(eventNum, message); break; case "443": String newNick = nick+"|"+nickTail++; - StreamProvider.writeToStream(serverName,"NICK "+newNick); + StreamProvider.writeToStream(server,"NICK "+newNick); break; case "464": // password for server/znc/bnc - StreamProvider.writeToStream(serverName,"PASS "+configurationFile.getServerPass()); + StreamProvider.writeToStream(server,"PASS "+configurationFile.getServerPass()); + writerWorker.log(eventNum, message); break; case "432": + writerWorker.log(eventNum, message); System.out.println("Configuration issue: Nickname contains unacceptable characters (432 ERR_ERRONEUSNICKNAME)."); - ReconnectControl.update(serverName, false); - - break; case "465": - ReconnectControl.update(serverName, false); - + ReconnectControl.update(server, false); + writerWorker.log(eventNum, message); break; 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; default: - writerWorker.logAdd("[system]", "catch: "+eventNum+" from: "+sender+" :",message); + writerWorker.log("catch: "+eventNum+" from: "+sender+" :", message); break; // 431 ERR_NONICKNAMEGIVEN how can we get this? // 436 ERR_NICKCOLLISION @@ -240,7 +220,7 @@ public class SystemConsumer implements Runnable{ message.append("\n"); } - StreamProvider.writeToStream(serverName, message.toString()); + StreamProvider.writeToStream(server, message.toString()); } private void sendNickPassword(){ @@ -249,11 +229,11 @@ public class SystemConsumer implements Runnable{ switch (configurationFile.getUserNickAuthStyle()){ case "freenode": - StreamProvider.writeToStream(serverName,"PRIVMSG NickServ :IDENTIFY " + StreamProvider.writeToStream(server,"PRIVMSG NickServ :IDENTIFY " + configurationFile.getUserNickPass()); break; case "rusnet": - StreamProvider.writeToStream(serverName,"NickServ IDENTIFY " + StreamProvider.writeToStream(server,"NickServ IDENTIFY " + configurationFile.getUserNickPass()); } } @@ -261,12 +241,21 @@ public class SystemConsumer implements Runnable{ private void joinChannels(){ 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(cnl); + joinMessage.append(channel); 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); } } \ No newline at end of file diff --git a/src/main/java/InnaIrcBot/ReconnectControl.java b/src/main/java/InnaIrcBot/ReconnectControl.java index 0f87b81..8a67c37 100644 --- a/src/main/java/InnaIrcBot/ReconnectControl.java +++ b/src/main/java/InnaIrcBot/ReconnectControl.java @@ -1,23 +1,35 @@ package InnaIrcBot; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; public class ReconnectControl { - private static final HashMap serversList = new HashMap<>(); - public static synchronized void register(String serverName){ - serversList.put(serverName, true); + private static final Map serversList = Collections.synchronizedMap(new HashMap<>()); + private static final Map serversReconnects = Collections.synchronizedMap(new HashMap<>()); + + public static void register(String server){ + serversList.putIfAbsent(server, true); + serversReconnects.putIfAbsent(server, 0); } - public static synchronized void update(String serverName, boolean needReconnect){ - serversList.replace(serverName, needReconnect); + public static void update(String server, boolean needReconnect){ + serversList.replace(server, needReconnect); } - public static synchronized void notify(String serverName) { - if (serversList.get(serverName) == null || ! serversList.get(serverName)){ - serversList.remove(serverName); + public static synchronized void notify(String server){ + if (! serversList.getOrDefault(server, false)) + return; + + int count = serversReconnects.get(server); + + if (count > 5) { + serversList.replace(server, false); return; } + count++; + serversReconnects.put(server, count); - System.out.println("DEBUG: Thread "+serverName+" removed from observable list after unexpected finish.\n\t"); - ConnectionsBuilder.getConnections().startNewConnection(serverName); + System.out.println("Main thread \"" + server + "\" removed from observable list after unexpected finish.\n"); + ConnectionsBuilder.getConnections().startNewConnection(server); } } diff --git a/src/main/java/InnaIrcBot/config/ConfigurationFile.java b/src/main/java/InnaIrcBot/config/ConfigurationFile.java index 667321f..07814e0 100644 --- a/src/main/java/InnaIrcBot/config/ConfigurationFile.java +++ b/src/main/java/InnaIrcBot/config/ConfigurationFile.java @@ -23,6 +23,7 @@ public class ConfigurationFile { private boolean rejoinOnKick; private String botAdministratorPassword; private String applicationLogDir; + private int cooldownTime; private LogDriverConfiguration logDriverConfiguration; private List channels; private HashMap channelConfigs; @@ -39,6 +40,8 @@ public class ConfigurationFile { public boolean getRejoinOnKick() { return rejoinOnKick; } public String getBotAdministratorPassword() { return botAdministratorPassword; } public String getApplicationLogDir() { return applicationLogDir; } + public int getCooldownTime() { return cooldownTime; } + public LogDriverConfiguration getLogDriverConfiguration(){ return logDriverConfiguration; } public List getChannels() { return channels; } 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.botAdministratorPassword = mainSection.getOrDefault("bot administrator password", ""); this.applicationLogDir = mainSection.getOrDefault("application logs", ""); + this.cooldownTime = mainSection.get("cooldown (sec)", int.class); } private void parseChannels(Wini ini){ diff --git a/src/main/java/InnaIrcBot/config/ConfigurationFileGenerator.java b/src/main/java/InnaIrcBot/config/ConfigurationFileGenerator.java index 2f24c41..3d0f5e9 100644 --- a/src/main/java/InnaIrcBot/config/ConfigurationFileGenerator.java +++ b/src/main/java/InnaIrcBot/config/ConfigurationFileGenerator.java @@ -62,36 +62,6 @@ public class ConfigurationFileGenerator { if (! Files.exists(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{ final String mainSectionName = "main"; final String channelSectionName = "channels"; @@ -127,6 +97,7 @@ public class ConfigurationFileGenerator { mainSection.put( "auto rejoin", true); mainSection.put( "bot administrator password", "i_pswd"); mainSection.put( "application logs", "/tmp"); + mainSection.put("cooldown (sec)", 3); Ini.Section loggingSection = ini.add("logging"); loggingSection.put( "driver", "files"); diff --git a/src/main/java/InnaIrcBot/config/LogDriverConfiguration.java b/src/main/java/InnaIrcBot/config/LogDriverConfiguration.java index 0997799..de22492 100644 --- a/src/main/java/InnaIrcBot/config/LogDriverConfiguration.java +++ b/src/main/java/InnaIrcBot/config/LogDriverConfiguration.java @@ -5,7 +5,7 @@ import InnaIrcBot.logging.SupportedLogDrivers; public class LogDriverConfiguration { private String name; - private final String path; + private String path; private final String mongoURI; private final String mongoTable; @@ -48,12 +48,8 @@ public class LogDriverConfiguration { } private void validatePath(){ - try { - checkFieldNotEmpty(path); - } - catch (Exception e){ - name = SupportedLogDrivers.zero; - } + if (path == null) + path = "."; } private void validateMongo(){ diff --git a/src/main/java/InnaIrcBot/logging/WorkerFiles.java b/src/main/java/InnaIrcBot/logging/WorkerFiles.java index be86f1c..f9098af 100644 --- a/src/main/java/InnaIrcBot/logging/WorkerFiles.java +++ b/src/main/java/InnaIrcBot/logging/WorkerFiles.java @@ -29,11 +29,14 @@ public class WorkerFiles implements Worker { createFileWriter(); consistent = true; } 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){ + if (dirLocation.isEmpty()) + dirLocation = "."; + if (! dirLocation.endsWith(File.separator)) dirLocation += File.separator; @@ -47,13 +50,13 @@ public class WorkerFiles implements Worker { if (file.isDirectory()) return; 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()) 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{ @@ -116,7 +119,7 @@ public class WorkerFiles implements Worker { fileWriter.write(string); fileWriter.flush(); } 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()); close(); consistent = false; @@ -180,7 +183,7 @@ public class WorkerFiles implements Worker { } catch (NullPointerException ignore) {} 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. } this.consistent = false; diff --git a/src/main/java/InnaIrcBot/logging/WorkerMongoDB.java b/src/main/java/InnaIrcBot/logging/WorkerMongoDB.java index 63a3c8b..85ef9cf 100644 --- a/src/main/java/InnaIrcBot/logging/WorkerMongoDB.java +++ b/src/main/java/InnaIrcBot/logging/WorkerMongoDB.java @@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit; public class WorkerMongoDB implements Worker { //TODO consider skipping checks if server already added. private final static Map serversMap = Collections.synchronizedMap(new HashMap<>()); private final String server; - private MongoCollection collection; + private final MongoCollection collection; private boolean consistent; 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() { - @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()); - } - } - }; + ServerListener mongoServerListener = getServerListener(); MongoClientSettings MCS = MongoClientSettings.builder() // .addCommandListener(mongoCommandListener) @@ -106,6 +87,30 @@ public class WorkerMongoDB implements Worker { //TODO consider ski 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(){ if (! consistent) return; @@ -148,23 +153,22 @@ public class WorkerMongoDB implements Worker { //TODO consider ski document.append("message1", message); break; } + + insert(document); + + return consistent; + } + private void insert(Document document){ try { collection.insertOne(document); // TODO: call finalize? 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){ - System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): MongoDB Exception."); + System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): MongoDB Exception: "+me.getMessage()); this.close(); } catch (IllegalStateException ise){ System.out.println("BotMongoWorker (@"+this.server +")->logAdd(): Illegal state exception: MongoDB server already closed (not an issue)."); this.close(); } - return consistent; } private long getDate(){ return System.currentTimeMillis() / 1000L; } // UNIX time diff --git a/src/main/java/InnaIrcBot/logging/WorkerSQLite.java b/src/main/java/InnaIrcBot/logging/WorkerSQLite.java index dd173be..e34085e 100644 --- a/src/main/java/InnaIrcBot/logging/WorkerSQLite.java +++ b/src/main/java/InnaIrcBot/logging/WorkerSQLite.java @@ -9,8 +9,8 @@ import java.sql.*; public class WorkerSQLite implements Worker { - private Connection connection; - private boolean consistent = false; + private final Connection connection; + private boolean consistent; private PreparedStatement preparedStatement; private final String server; @@ -21,10 +21,11 @@ public class WorkerSQLite implements Worker { this.server = server; String dbFileLocation = logDriverConfiguration.getPath(); 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. - if (!dir.exists()) { - throw new Exception("BotSQLiteWorker (@"+server+")->constructor(): Failure:\n\tUnable to create directory to store DB file: " + dbFileLocation); + if (! dir.exists()) { + throw new Exception("WorkerSQLite for"+server+": Unable to create directory to store DB file: " + dbFileLocation); } String connectionURL; if (dbFileLocation.endsWith(File.separator)) @@ -38,107 +39,110 @@ public class WorkerSQLite implements Worker { sqlConfig.setOpenMode(SQLiteOpenMode.NOMUTEX); //SQLITE_OPEN_NOMUTEX : multithreaded mode this.connection = DriverManager.getConnection(connectionURL, sqlConfig.toProperties()); - if (connection != null){ - // Create table if not created - Statement statement = connection.createStatement(); - String query = "CREATE TABLE IF NOT EXISTS \""+channel+"\" (" - + " id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," - + " unixtime INTEGER," - + " event TEXT," - + " subject TEXT," - + " message TEXT," - + " object TEXT" - +");"; - statement.executeUpdate(query); - - // Check table representation - ResultSet rs = statement.executeQuery("PRAGMA table_info(\""+channel+"\");"); // executeQuery never null - boolean[] schemaResultCheck = {false, false, false, false, false, false}; - - while (rs.next()) { - switch (rs.getInt("cid")) { - case 0: - if (rs.getString("name").equals("id") - && rs.getString("type").equals("INTEGER") - && (rs.getInt("notnull") == 1) - && (rs.getString("dflt_value") == null) - && (rs.getInt("pk") == 1)) - schemaResultCheck[0] = true; - //System.out.println("Got 0"); - break; - case 1: - if (rs.getString("name").equals("unixtime") - && rs.getString("type").equals("INTEGER") - && (rs.getInt("notnull") == 0) - && (rs.getString("dflt_value") == null) - && (rs.getInt("pk") == 0)) - schemaResultCheck[1] = true; - //System.out.println("Got 1"); - break; - case 2: - if (rs.getString("name").equals("event") - && rs.getString("type").equals("TEXT") - && (rs.getInt("notnull") == 0) - && (rs.getString("dflt_value") == null) - && (rs.getInt("pk") == 0)) - schemaResultCheck[2] = true; - //System.out.println("Got 2"); - break; - case 3: - if (rs.getString("name").equals("subject") - && rs.getString("type").equals("TEXT") - && (rs.getInt("notnull") == 0) - && (rs.getString("dflt_value") == null) - && (rs.getInt("pk") == 0)) - schemaResultCheck[3] = true; - //System.out.println("Got 3"); - break; - case 4: - if (rs.getString("name").equals("message") - && rs.getString("type").equals("TEXT") - && (rs.getInt("notnull") == 0) - && (rs.getString("dflt_value") == null) - && (rs.getInt("pk") == 0)) - schemaResultCheck[4] = true; - //System.out.println("Got 4"); - break; - case 5: - if (rs.getString("name").equals("object") - && rs.getString("type").equals("TEXT") - && (rs.getInt("notnull") == 0) - && (rs.getString("dflt_value") == null) - && (rs.getInt("pk") == 0)) - schemaResultCheck[5] = true; - //System.out.println("Got 5"); - break; - default: - for (int i = 0; i <= 5; i++) { - schemaResultCheck[i] = false; // If more then 5 elements, ruin results - } - } - } - // Validating result: it table in DB have expected schema. If not, removing and recreating table. - for (boolean element: schemaResultCheck) { - if (!element) { - System.out.println("BotSQLiteWorker (@"+server+")->Constructor(): Notice:\n\tFound already existing table for channel with incorrect syntax: removing table and re-creating."); - statement.executeUpdate("DROP TABLE \"" + channel + "\";"); - statement.executeUpdate(query); - 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; + 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 + Statement statement = connection.createStatement(); + String query = "CREATE TABLE IF NOT EXISTS \""+channel+"\" (" + + " id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + + " unixtime INTEGER," + + " event TEXT," + + " subject TEXT," + + " message TEXT," + + " object TEXT" + +");"; + statement.executeUpdate(query); + + // Check table representation + ResultSet rs = statement.executeQuery("PRAGMA table_info(\""+channel+"\");"); // executeQuery never null + boolean[] schemaResultCheck = {false, false, false, false, false, false}; + + while (rs.next()) { + switch (rs.getInt("cid")) { + case 0: + if (rs.getString("name").equals("id") + && rs.getString("type").equals("INTEGER") + && (rs.getInt("notnull") == 1) + && (rs.getString("dflt_value") == null) + && (rs.getInt("pk") == 1)) + schemaResultCheck[0] = true; + //System.out.println("Got 0"); + break; + case 1: + if (rs.getString("name").equals("unixtime") + && rs.getString("type").equals("INTEGER") + && (rs.getInt("notnull") == 0) + && (rs.getString("dflt_value") == null) + && (rs.getInt("pk") == 0)) + schemaResultCheck[1] = true; + //System.out.println("Got 1"); + break; + case 2: + if (rs.getString("name").equals("event") + && rs.getString("type").equals("TEXT") + && (rs.getInt("notnull") == 0) + && (rs.getString("dflt_value") == null) + && (rs.getInt("pk") == 0)) + schemaResultCheck[2] = true; + //System.out.println("Got 2"); + break; + case 3: + if (rs.getString("name").equals("subject") + && rs.getString("type").equals("TEXT") + && (rs.getInt("notnull") == 0) + && (rs.getString("dflt_value") == null) + && (rs.getInt("pk") == 0)) + schemaResultCheck[3] = true; + //System.out.println("Got 3"); + break; + case 4: + if (rs.getString("name").equals("message") + && rs.getString("type").equals("TEXT") + && (rs.getInt("notnull") == 0) + && (rs.getString("dflt_value") == null) + && (rs.getInt("pk") == 0)) + schemaResultCheck[4] = true; + //System.out.println("Got 4"); + break; + case 5: + if (rs.getString("name").equals("object") + && rs.getString("type").equals("TEXT") + && (rs.getInt("notnull") == 0) + && (rs.getString("dflt_value") == null) + && (rs.getInt("pk") == 0)) + schemaResultCheck[5] = true; + //System.out.println("Got 5"); + break; + default: + for (int i = 0; i <= 5; i++) { + schemaResultCheck[i] = false; // If more then 5 elements, ruin results + } + } + } + // Validating result: it table in DB have expected schema. If not, removing and recreating table. + for (boolean element: schemaResultCheck) { + if (! element) { + 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(query); + break; + } + } + } + private long getDate(){ return System.currentTimeMillis() / 1000L; // UNIX time } diff --git a/src/main/java/InnaIrcBot/logging/WorkerSystem.java b/src/main/java/InnaIrcBot/logging/WorkerSystem.java index 0bf7a2e..65bc376 100644 --- a/src/main/java/InnaIrcBot/logging/WorkerSystem.java +++ b/src/main/java/InnaIrcBot/logging/WorkerSystem.java @@ -13,60 +13,68 @@ public class WorkerSystem{ 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 final String server; + private boolean consistent; - private boolean consistent = false; + private String filePath; 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 { - if (! logFile.getParentFile().exists()) { - if (! logFile.getParentFile().mkdirs()){ - System.out.println("BotSystemWorker (@"+server+")->constructor() failed:\n" + - "\tUnable to create sub-directory(-ies) to store log file: " + appLogDir); - return; - } - } - fileWriter = new FileWriter(logFile, true); + formatFilePath(server, appLogDir); + + fileWriter = new FileWriter(createServerLogsFile(), true); consistent = true; - } catch (SecurityException e){ - System.out.println("BotSystemWorker (@"+server+")->constructor() failed.\n" + - "\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()); + } catch (Exception e){ + System.out.println("BotSystemWorker for "+server+" failed: " + e.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(){ - return "["+ LocalTime.now().format(dateFormat)+"] "; + return "["+ LocalTime.now().format(dateFormat)+"]"; } - public void logAdd(String event, String initiatorArg, String messageArg) { - if (consistent) { - try { - fileWriter.write(genDate() + event + " " + initiatorArg + " " + messageArg + "\n"); - fileWriter.flush(); - } catch (Exception e) { // ??? No ideas. Just in case. Consider removing. - System.out.println("BotSystemWorker (@" + server + ")->logAdd() failed\n\tUnable to write logs of because of exception:\n\t" + e.getMessage()); - //this.close(); - consistent = false; - } - return; + public void log(String initiatorArg, String messageArg) { + 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 { + fileWriter.write(message); + fileWriter.flush(); + } catch (Exception e) { + System.out.println("BotSystemWorker: unable to write application logs: " + e.getMessage()); + consistent = false; + //this.close(); } - System.out.println(genDate() + event + " " + initiatorArg + " " + messageArg + "\n"); } public void registerInSystemWorker(Closeable thing){ diff --git a/src/test/java/InnaIrcBot/Commanders/JoinFloodHandlerTest.java b/src/test/java/InnaIrcBot/Commanders/JoinFloodHandlerTest.java index bd9a1f8..2ecc1de 100644 --- a/src/test/java/InnaIrcBot/Commanders/JoinFloodHandlerTest.java +++ b/src/test/java/InnaIrcBot/Commanders/JoinFloodHandlerTest.java @@ -1,7 +1,6 @@ package InnaIrcBot.Commanders; import InnaIrcBot.ProvidersConsumers.StreamProvider; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -37,7 +36,6 @@ class JoinFloodHandlerTest { testSocket.connect(new InetSocketAddress(60000)); StreamProvider.setStream(serverName, testSocket); - StreamProvider.setSysConsumer(serverName, new ArrayBlockingQueue<>(100)); } catch (IOException e){ e.printStackTrace();