Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for #34 and #39 #40

Merged
merged 7 commits into from Jan 31, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Expand Up @@ -14,3 +14,10 @@

#Configuration
bot.properties

#javadocs
/javadocs/biweekly-0.4.0.zip
/javadocs/ez-vcard-0.9.6.zip
/javadocs/guava-18.0.zip
/javadocs/java8.zip
/javadocs/javafx.zip
93 changes: 63 additions & 30 deletions src/main/java/com/gmail/inverseconduit/Main.java
Expand Up @@ -7,55 +7,88 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Policy;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.logging.Filter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import com.gmail.inverseconduit.bot.Program;
import com.gmail.inverseconduit.chat.StackExchangeChat;
import com.gmail.inverseconduit.commands.sets.CoreBotCommands;
import com.gmail.inverseconduit.security.ScriptSecurityManager;
import com.gmail.inverseconduit.security.ScriptSecurityPolicy;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

public class Main {

private static final Logger LOGGER = Logger.getLogger(Main.class.getName());

public static void main(String[] args) throws Exception {
setupLogging();
setupLogging();

//sandbox this ...
Policy.setPolicy(ScriptSecurityPolicy.getInstance());
System.setSecurityManager(ScriptSecurityManager.getInstance());

BotConfig config = loadConfig();
AppContext.INSTANCE.add(config);

Program p = new Program();
p.startup();
try (StackExchangeChat seInterface = new StackExchangeChat()) {
if ( !seInterface.login(SESite.STACK_OVERFLOW, config)) {
LOGGER.severe("Login failed!");
throw new RuntimeException("Login failure");
}

Program p = new Program(seInterface);

// Binds all core commands to the Bot (move to Bot instantiation??)
new CoreBotCommands(seInterface, p.getBot()).allCommands().forEach(p.getBot()::subscribe);

p.startup();
ThreadFactory factory = new ThreadFactoryBuilder().setDaemon(true).setNameFormat("message-query-thread-%d").build();
Executors.newSingleThreadScheduledExecutor(factory).scheduleAtFixedRate(() -> queryMessagesFor(seInterface), 5, 3, TimeUnit.SECONDS);
}
}

private static void setupLogging(){
Filter filter = new Filter() {
private final String packageName = Main.class.getPackage().getName();

@Override
public boolean isLoggable(LogRecord record) {
//only log messages from this app
String name = record.getLoggerName();
return (name == null) ? false : name.startsWith(packageName);
}
};

Logger global = Logger.getLogger("");
for (Handler handler : global.getHandlers()){
handler.setFilter(filter);

private static void queryMessagesFor(StackExchangeChat seInterface) {
try {
seInterface.queryMessages();
} catch(RuntimeException | Error e) {
LOGGER.log(Level.SEVERE, "Runtime Exception or Error occurred:", e);
throw e;
} catch(Exception e) {
LOGGER.log(Level.WARNING, "Exception occured:", e);
}
}

private static BotConfig loadConfig() throws IOException{
Path file = Paths.get("bot.properties");
Properties properties = new Properties();
try (Reader reader = Files.newBufferedReader(file, Charset.forName("UTF-8"))){
properties.load(reader);
}
return new BotConfig(properties);

private static void setupLogging() {
Filter filter = new Filter() {

private final String packageName = Main.class.getPackage().getName();

@Override
public boolean isLoggable(LogRecord record) {
//only log messages from this app
String name = record.getLoggerName();
return name != null && name.startsWith(packageName);
}
};

Logger global = Logger.getLogger("");
Arrays.stream(global.getHandlers()).forEach(h -> h.setFilter(filter));
}

private static BotConfig loadConfig() throws IOException {
Path file = Paths.get("bot.properties");
Properties properties = new Properties();
try (Reader reader = Files.newBufferedReader(file, Charset.forName("UTF-8"))) {
properties.load(reader);
}
return new BotConfig(properties);
}
}
18 changes: 13 additions & 5 deletions src/main/java/com/gmail/inverseconduit/bot/AbstractBot.java
Expand Up @@ -20,15 +20,23 @@ public abstract class AbstractBot implements ChatWorker {

@Override
public final synchronized boolean enqueueMessage(ChatMessage chatMessage) throws InterruptedException {
if (chatMessage == Program.POISON_PILL) {
executor.shutdown();
processingThread.shutdown();
this.shutdown();
return true;
}
return messageQueue.offer(chatMessage, 200, TimeUnit.MILLISECONDS);
}

@Override
public abstract void start();

@Override
protected void finalize() {
executor.shutdownNow();
}

/**
* Intended for shutting down any other Threads or executors declared in
* extending classes.
* This method will be called when the ChatWorker recieves a Shutdown
* request via {@link Program.POISON_PILL}
*/
protected abstract void shutdown();
}
11 changes: 6 additions & 5 deletions src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java
Expand Up @@ -29,7 +29,7 @@
*/
public class DefaultBot extends AbstractBot implements Subscribable<CommandHandle> {

private final Logger LOGGER = Logger.getLogger(DefaultBot.class.getName());
private static final Logger LOGGER = Logger.getLogger(DefaultBot.class.getName());

protected final ChatInterface chatInterface;

Expand Down Expand Up @@ -85,15 +85,16 @@ public void unSubscribe(CommandHandle subscriber) {
commands.remove(subscriber);
}

public void shutdown() {
executor.shutdown();
}

@Override
public Collection<CommandHandle> getSubscriptions() {
Set<CommandHandle> set = new HashSet<CommandHandle>(commands);
set.addAll(listeners);
return set;
}

@Override
protected void shutdown() {
// nothing to do here!
}

}
Expand Up @@ -54,4 +54,9 @@ public void unSubscribe(Interaction subscriber) {
public Collection<Interaction> getSubscriptions() {
return Collections.unmodifiableCollection(interactions);
}

@Override
protected void shutdown() {
// nothing to do here
}
}
97 changes: 35 additions & 62 deletions src/main/java/com/gmail/inverseconduit/bot/Program.java
@@ -1,9 +1,6 @@
package com.gmail.inverseconduit.bot;

import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
Expand All @@ -13,9 +10,8 @@
import com.gmail.inverseconduit.BotConfig;
import com.gmail.inverseconduit.SESite;
import com.gmail.inverseconduit.chat.ChatInterface;
import com.gmail.inverseconduit.chat.StackExchangeChat;
import com.gmail.inverseconduit.commands.CommandHandle;
import com.gmail.inverseconduit.commands.sets.CoreBotCommands;
import com.gmail.inverseconduit.datatype.ChatMessage;
import com.gmail.inverseconduit.datatype.SeChatDescriptor;
import com.gmail.inverseconduit.javadoc.JavaDocAccessor;

Expand All @@ -27,36 +23,47 @@
*/
public class Program {

private static final Logger LOGGER = Logger.getLogger(Program.class.getName());
private static final Logger LOGGER = Logger.getLogger(Program.class.getName());

private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private static final BotConfig config = AppContext.INSTANCE.get(BotConfig.class);

private static final BotConfig config = AppContext.INSTANCE.get(BotConfig.class);
private final DefaultBot bot;

private final DefaultBot bot;
private final InteractionBot interactionBot;

private final InteractionBot interactionBot;
private final ChatInterface chatInterface;

private final ChatInterface chatInterface = new StackExchangeChat();
private final JavaDocAccessor javaDocAccessor;

private final JavaDocAccessor javaDocAccessor;
private static final Pattern javadocPattern = Pattern.compile("^" + Pattern.quote(config.getTrigger()) + "javadoc:(.*)", Pattern.DOTALL);

private static final Pattern javadocPattern = Pattern.compile("^" + Pattern.quote(config.getTrigger()) + "javadoc:(.*)", Pattern.DOTALL);
public static final ChatMessage POISON_PILL = new ChatMessage(null, -1, "", "", -1, "", -1);

/**
* @param chatInterface
* The ChatInterface to use as main interface to wire bots to. It is
* assumed that the ChatInterface's
* {@link ChatInterface#login(com.gmail.inverseconduit.datatype.ProviderDescriptor, com.gmail.inverseconduit.datatype.CredentialsProvider)
* login()} has been called already
* @throws IOException
* if there's a problem loading the Javadocs
*/
// TODO: get the chatInterface solved via Dependency Injection instead.
// This would greatly improve testability and ease of switching
// implementations
public Program() throws IOException {
public Program(ChatInterface chatInterface) throws IOException {
LOGGER.finest("Instantiating Program");
bot = new DefaultBot(chatInterface);
interactionBot = new InteractionBot(chatInterface);
this.chatInterface = chatInterface;
this.bot = new DefaultBot(chatInterface);
this.interactionBot = new InteractionBot(chatInterface);

JavaDocAccessor tmp;
//better not get ExceptionInInitializerError
javaDocAccessor = new JavaDocAccessor(config.getJavadocsDir());
try {
tmp = new JavaDocAccessor(config.getJavadocsDir());
} catch(IOException ex) {
LOGGER.log(Level.WARNING, "Couldn't initialize Javadoc accessor.", ex);
tmp = null;
}
this.javaDocAccessor = tmp;

chatInterface.subscribe(bot);
chatInterface.subscribe(interactionBot);
LOGGER.info("Basic component setup complete");
Expand All @@ -68,53 +75,26 @@ public Program() throws IOException {
public void startup() {
LOGGER.info("Beginning startup process");
bindDefaultCommands();
login();
for (Integer room : config.getRooms()) {
// FIXME: isn't always Stackoverflow
chatInterface.joinChat(new SeChatDescriptor.DescriptorBuilder(SESite.STACK_OVERFLOW).setRoom(() -> room).build());
}
scheduleQueryingThread();
bot.start();
interactionBot.start();
LOGGER.info("Startup completed.");
}

private void scheduleQueryingThread() {
executor.scheduleAtFixedRate(() -> {
try {
chatInterface.queryMessages();
} catch(RuntimeException | Error e) {
Logger.getAnonymousLogger().log(Level.SEVERE, "Runtime Exception or Error occurred in querying thread", e);
throw e;
} catch(Exception e) {
Logger.getAnonymousLogger().log(Level.WARNING, "Exception occured in querying thread:", e);
}
}, 5, 3, TimeUnit.SECONDS);
Logger.getAnonymousLogger().info("querying thread started");
}

private void login() {
boolean loggedIn = chatInterface.login(SESite.STACK_OVERFLOW, config);
if ( !loggedIn) {
Logger.getAnonymousLogger().severe("Login failed!");
System.exit(2);
}
}

private void bindDefaultCommands() {
bindShutdownCommand();
bindNumberCommand();
bindJavaDocCommand();
new CoreBotCommands(chatInterface, bot).allCommands().forEach(bot::subscribe);
}

private void bindNumberCommand() {
final Pattern p = Pattern.compile("^\\d+$");
CommandHandle javaDoc = new CommandHandle.Builder(null, message -> {
final Pattern p = Pattern.compile("^\\d+$");
CommandHandle javaDoc = new CommandHandle.Builder(null, message -> {
Matcher matcher = p.matcher(message.getMessage());
if (!matcher.find()){
return null;
}

if ( !matcher.find()) { return null; }

int choice = Integer.parseInt(matcher.group(0));
return javaDocAccessor.showChoice(message, choice);
}).build();
Expand All @@ -130,14 +110,7 @@ private void bindJavaDocCommand() {
bot.subscribe(javaDoc);
}

private void bindShutdownCommand() {
CommandHandle shutdown = new CommandHandle.Builder("shutdown", message -> {
// FIXME: Require permissions for this
chatInterface.broadcast("*~going down*");
executor.shutdownNow();
System.exit(0);
return "";
}).build();
bot.subscribe(shutdown);
public DefaultBot getBot() {
return bot;
}
}