innaircbot/src/main/java/InnaIrcBot/ProvidersConsumers/DataProvider.java

234 lines
10 KiB
Java

package InnaIrcBot.ProvidersConsumers;
import InnaIrcBot.Config.StorageFile;
import InnaIrcBot.LogDriver.BotDriver;
import InnaIrcBot.ReconnectControl;
import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class DataProvider implements Runnable {
private StorageFile configFile;
private String serverName;
private String userNick;
private BufferedReader rawStreamReader;
private boolean ableToRun = true;
/**
* Initiate connection and prepare input/output streams for run()
* */
public DataProvider(StorageFile storageFile){
this.configFile = storageFile;
this.serverName = storageFile.getServerName();
int port = storageFile.getServerPort();
try {
InetAddress inetAddress = InetAddress.getByName(serverName);
Socket socket = new Socket(); // TODO: set timeout?
for (int i=0; i<5; i++) {
socket.connect(new InetSocketAddress(inetAddress, port), 5000); // 10sec = 10000
if (socket.isConnected())
break;
}
if (socket.isConnected())
System.out.println("Socket connected");
else {
System.out.println("Unable to connect to remote server after 5 retry.");
this.ableToRun = false;
return;
}
if (!StreamProvider.setStream(serverName, socket)) {
this.ableToRun = false;
return;
}
InputStream inStream = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(inStream, StandardCharsets.UTF_8); //TODO set charset in options;
this.rawStreamReader = new BufferedReader(isr);
} catch(UnknownHostException e){
this.ableToRun = false;
System.out.println("Internal issue: DataProvider->constructor caused unknown host exception:\n\t"+e); // caused by InetAddress
} catch (IOException e){
this.ableToRun = false;
System.out.println("Internal issue: DataProvider->constructor caused I/O exception\n\t"+e); // caused by Socket
}
}
//HANDLE ALWAYS in case thread decided to die
private void close(){
StreamProvider.delStream(serverName);
}
public void run(){
if (!ableToRun) {
this.close();
return;
}
if(!this.initConnection(rawStreamReader)) {
this.close();
return;
}
//Prepare logDriver for using in threads.
if(!BotDriver.setFileDriver(serverName, configFile.getLogDriver(), configFile.getLogDriverParameters())) {
this.close();
return;
}
/* Used for sending data into consumers objects*/
Map<String, PrintWriter> channelsMap = Collections.synchronizedMap(new HashMap<String, PrintWriter>());
try {
PipedOutputStream streamOut = new PipedOutputStream(); // K-K-K-KOMBO \m/
BufferedReader streamBufferedReader = new BufferedReader(
new InputStreamReader(
new PipedInputStream(streamOut), StandardCharsets.UTF_8)
);
Runnable consumer = new SystemConsumer(streamBufferedReader, userNick, channelsMap, this.configFile);
new Thread(consumer).start();
channelsMap.put("", new PrintWriter(streamOut)); // Not sure that PrintWriter is thread-safe..
} catch (IOException e){
System.out.println("Internal issue: DataProvider->run() I/O exception while initialized child objects.\n\t"+e); // caused by Socket
this.close();
return;
}
////////////////////////////////////// Start loop //////////////////////////////////////////////////////////////
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.
String chan;
// Say 'yes, we need reconnect if connection somehow died'
ReconnectControl.register(serverName);
while ((rawMessage = rawStreamReader.readLine()) != null) {
//System.out.println(rawMessage);
if (rawMessage.startsWith(":")) {
rawStrings = rawMessage
.substring(1)
.split(" :?", 3); // Removing ':'
chan = rawStrings[2].replaceAll("(\\s.?$)|(\\s.+?$)", "");
//System.out.println("\tChannel: "+chan+"\n\tAction: "+rawStrings[1]+"\n\tSender: "+rawStrings[0]+"\n\tMessage: "+rawStrings[2]+"\n");
if (rawStrings[1].equals("QUIT") || rawStrings[1].equals("NICK")) { // replace regex
for (PrintWriter value : channelsMap.values()) {
value.println(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
value.flush();
}
} else if (channelsMap.containsKey(chan)) {
channelsMap.get(chan).println(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
channelsMap.get(chan).flush();
} else {
channelsMap.get("").println(rawStrings[1] + " " + rawStrings[0] + " " + rawStrings[2]);
channelsMap.get("").flush();
}
} else if (rawMessage.startsWith("PING :")) {
pingSrvResponse(rawMessage);
} else {
System.out.println("Not a valid response=" + rawMessage);
}
}
} catch (IOException e){
System.out.println("Socket issue: I/O exception"); //Connection closed. TODO: MAYBE try reconnect
}
finally {
for (PrintWriter p :channelsMap.values()) {
p.close();
}
this.close();
}
}
private void pingSrvResponse(String rawData){
StreamProvider.writeToStream(serverName,"PONG :" + rawData.replace("PING :", ""));
}
/**
* Initiate connection before starting main routine.
* */
private boolean initConnection(BufferedReader genericStreamReader){
int nickTail = 0; // handle appendix to nickname
this.userNick = configFile.getUserNick();
if (this.userNick == null || this.userNick.isEmpty()) {
System.out.println("Configuration issue: no nickname specified.");
return false;
}
String rawMessage;
StreamProvider.writeToStream(serverName,"NICK "+this.userNick);
StreamProvider.writeToStream(serverName,"USER "+configFile.getUserIdent()+" 8 * :"+configFile.getUserRealName()); // TODO: Add usermode 4 rusnet
if (!configFile.getUserNickPass().isEmpty() && (!configFile.getUserNickAuthStyle().isEmpty() && configFile.getUserNickAuthStyle().toLowerCase().equals("freenode")))
StreamProvider.writeToStream(serverName,"PRIVMSG NickServ :IDENTIFY "+configFile.getUserNickPass());
else if (!configFile.getUserNickPass().isEmpty() && (!configFile.getUserNickAuthStyle().isEmpty() && configFile.getUserNickAuthStyle().toLowerCase().equals("rusnet")))
StreamProvider.writeToStream(serverName,"NickServ IDENTIFY "+configFile.getUserNickPass());
else if (!configFile.getUserNickPass().isEmpty())
System.out.println("Configuration issue: Unable to determinate method of user nick identification (by password): could be \"freenode\" or \"rusnet\"\nSee \"userNickAuthStyle\".");
try {
// 431 ERR_NONICKNAMEGIVEN how can we get this?
// 432 ERR_ERRONEUSNICKNAME covered
// 433 ERR_NICKNAMEINUSE covered
// 436 ERR_NICKCOLLISION
while ((rawMessage = genericStreamReader.readLine()) != null){
System.out.println(rawMessage);
if (rawMessage.startsWith("PING :")) {
pingSrvResponse(rawMessage);
}
if (rawMessage.contains(" 001 ")) {
StringBuilder message = new StringBuilder();
if (!configFile.getUserMode().trim().isEmpty()){
String modes = configFile.getUserMode();
modes = modes.replaceAll("[\t\\s]", "");
for(char c :modes.toCharArray()) {
message.append("MODE ");
message.append(userNick);
message.append(" +");
message.append(c);
message.append("\n");
}
}
StreamProvider.writeToStream(serverName,message.toString());
return true;
}
else if (rawMessage.contains(" 433 ")) {
if (this.userNick.substring(userNick.length()-1, userNick.length()-1).equals("|"))
this.userNick = this.userNick.substring(0,userNick.length()-1)+Integer.toString(nickTail++);
else
this.userNick = this.userNick+"|"+Integer.toString(nickTail++);
StreamProvider.writeToStream(serverName,"NICK "+this.userNick);
}
else if (rawMessage.contains(" 432 ")) {
System.out.println("Configuration issue: Nickname contains unacceptable characters (432 ERR_ERRONEUSNICKNAME).");
return false;
}
}
} catch (IOException e){
System.out.println("Internal issue: DataProvider->initConnection() caused I/O exception.");
return false;
}
return false;
}
}