diff --git a/.classpath b/.classpath index 07ca123..2595219 100644 --- a/.classpath +++ b/.classpath @@ -2,5 +2,7 @@ + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a77abc7 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +StatsXmlQuery +============= + +This plugin for the bukkit minecraft-server offers the possibility to access some playerstats via xml files. The provided data is basically generated by [nidefawl/Stats](https://github.com/nidefawl/Stats). \ No newline at end of file diff --git a/lib/Stats.jar b/lib/Stats.jar new file mode 100644 index 0000000..c13b053 Binary files /dev/null and b/lib/Stats.jar differ diff --git a/lib/craftbukkit-0.0.1-SNAPSHOT.jar b/lib/craftbukkit-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000..83565a6 Binary files /dev/null and b/lib/craftbukkit-0.0.1-SNAPSHOT.jar differ diff --git a/src/de/sockenklaus/XmlStats/Datasource/Datasource.java b/src/de/sockenklaus/XmlStats/Datasource/Datasource.java new file mode 100644 index 0000000..4b084bf --- /dev/null +++ b/src/de/sockenklaus/XmlStats/Datasource/Datasource.java @@ -0,0 +1,26 @@ +package de.sockenklaus.XmlStats.Datasource; + +import java.io.File; +import java.util.ArrayList; + +public abstract class Datasource { + + + + protected ArrayList fetchAllPlayers(){ + File[] files = new File("world/players").listFiles(); + ArrayList result = new ArrayList(); + + for (int i = 0; i < files.length; i++){ + int whereDot = files[i].getName().lastIndexOf('.'); + + if (0 < whereDot && whereDot <= files[i].getName().length() - 2){ + String playerName = files[i].getName().substring(0, whereDot); + + result.add(playerName); + } + } + + return result; + } +} diff --git a/src/de/sockenklaus/XmlStats/Datasource/MoneyDS.java b/src/de/sockenklaus/XmlStats/Datasource/MoneyDS.java new file mode 100644 index 0000000..a3a7319 --- /dev/null +++ b/src/de/sockenklaus/XmlStats/Datasource/MoneyDS.java @@ -0,0 +1,5 @@ +package de.sockenklaus.XmlStats.Datasource; + +public class MoneyDS extends Datasource { + +} diff --git a/src/de/sockenklaus/XmlStats/Datasource/StatsDS.java b/src/de/sockenklaus/XmlStats/Datasource/StatsDS.java new file mode 100644 index 0000000..3952932 --- /dev/null +++ b/src/de/sockenklaus/XmlStats/Datasource/StatsDS.java @@ -0,0 +1,50 @@ +package de.sockenklaus.XmlStats.Datasource; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; + +import com.nidefawl.Stats.Stats; +import com.nidefawl.Stats.datasource.PlayerStat; +import com.nidefawl.Stats.datasource.PlayerStatSQL; + +import de.sockenklaus.XmlStats.XmlStats; + +public class StatsDS extends Datasource { + private Stats statsPlugin; + //private Server serverRef; + private ArrayList allPlayerNames; + private HashMap stats = new HashMap(); + + public StatsDS() { + this.statsPlugin = XmlStats.getStatsPlugin(); + //this.serverRef = XmlStats.getServerRef(); + this.allPlayerNames = fetchAllPlayers(); + this.stats = fetchAllPlayerStats(allPlayerNames); + } + + public Stats getPlugin() { + return this.statsPlugin; + } + + public File getDataFolder(){ + return this.statsPlugin.getDataFolder(); + } + + private HashMap fetchAllPlayerStats(ArrayList pPlayerNames){ + HashMap result = new HashMap(); + + for (String playerName : pPlayerNames){ + + PlayerStat ps = new PlayerStatSQL(playerName, statsPlugin); + ps.load(); + result.put(playerName, ps); + } + + return result; + } + + public HashMap getStats(){ + return this.stats; + } +} diff --git a/src/de/sockenklaus/XmlStats/Datasource/UsersDS.java b/src/de/sockenklaus/XmlStats/Datasource/UsersDS.java new file mode 100644 index 0000000..2a4cbe2 --- /dev/null +++ b/src/de/sockenklaus/XmlStats/Datasource/UsersDS.java @@ -0,0 +1,9 @@ +package de.sockenklaus.XmlStats.Datasource; + +import java.util.ArrayList; + +public class UsersDS extends Datasource { + public ArrayList getAllPlayers(){ + return fetchAllPlayers(); + } +} diff --git a/src/de/sockenklaus/XmlStats/Settings/Settings.java b/src/de/sockenklaus/XmlStats/Settings/Settings.java new file mode 100644 index 0000000..b76477f --- /dev/null +++ b/src/de/sockenklaus/XmlStats/Settings/Settings.java @@ -0,0 +1,24 @@ +package de.sockenklaus.XmlStats.Settings; + +import java.io.File; + +import de.sockenklaus.XmlStats.XmlStats; + +public class Settings { + + public static final String settingsFilename = "XmlStats.conf"; + + public static int xmlStatsPort; + public static boolean xmlStatsEnabled; + + public static void load(XmlStats plugin) { + + SettingsFile properties = new SettingsFile(new File(plugin.getDataFolder(), settingsFilename)); + + xmlStatsPort = properties.getInt("xmlstats-port", 8080, "port of the webserver for xml-access"); + xmlStatsEnabled = properties.getBoolean("xmlstats-enabled", false, "disabled per default to avoid unwanted port-mappings"); + + properties.save(); + } + +} diff --git a/src/de/sockenklaus/XmlStats/Settings/SettingsFile.java b/src/de/sockenklaus/XmlStats/Settings/SettingsFile.java new file mode 100644 index 0000000..84d21a6 --- /dev/null +++ b/src/de/sockenklaus/XmlStats/Settings/SettingsFile.java @@ -0,0 +1,174 @@ +package de.sockenklaus.XmlStats.Settings; + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; + +import de.sockenklaus.XmlStats.XmlStats; + +public class SettingsFile { + private HashMap map; + private File file; + private boolean modified; + + public SettingsFile(File file) { + this.file = file; + map = new HashMap(); + Scanner scan; + try { + if (!file.exists()) + file.createNewFile(); + scan = new Scanner(file); + while (scan.hasNextLine()) { + String line = scan.nextLine(); + if (!line.contains("=")) + continue; + if (line.length() == 0) + continue; + if (line.trim().charAt(0) == '#') + continue; + int equals = line.indexOf("="); + int commentIndex = line.length(); + if (line.contains("#")) { + commentIndex = line.indexOf("#"); + } + + String key = line.substring(0, equals).trim(); + if (key.equals("")) + continue; + String value = line.substring(equals + 1, commentIndex).trim(); + String comment = ""; + if (commentIndex < line.length() - 1) { + comment = line.substring(commentIndex + 1, line.length()).trim(); + } + map.put(key, new PropertiesEntry(value, comment)); + } + } catch (FileNotFoundException e) { + XmlStats.LogError("Cannot read file " + file.getName()); + } catch (IOException e) { + XmlStats.LogError("Cannot create file " + file.getName()); + } + } + + public boolean getBoolean(String key, Boolean defaultValue, String defaultComment) { + if (map.containsKey(key)) { + return Boolean.parseBoolean(map.get(key).value); + } else { + map.put(key, new PropertiesEntry(defaultValue.toString(), defaultComment)); + modified = true; + return defaultValue; + } + } + + public boolean getBoolean(String key) { + if (map.containsKey(key)) { + return Boolean.parseBoolean(map.get(key).value); + } + return false; + } + + public void remove(String key) { + map.remove(key); + } + + public String getString(String key, String defaultValue, String defaultComment) { + if (map.containsKey(key)) { + return map.get(key).value; + } else { + map.put(key, new PropertiesEntry(defaultValue.toString(), defaultComment)); + modified = true; + return defaultValue; + } + } + + public int getInt(String key, Integer defaultValue, String defaultComment) { + if (map.containsKey(key)) { + try { + return Integer.parseInt(map.get(key).value); + } catch (Exception e) { + XmlStats.LogWarn("Trying to get Integer from " + key + ": " + map.get(key).value); + return 0; + } + } else { + map.put(key, new PropertiesEntry(defaultValue.toString(), defaultComment)); + modified = true; + return defaultValue; + } + } + + public double getDouble(String key, Double defaultValue, String defaultComment) { + if (map.containsKey(key)) { + try { + return Double.parseDouble(map.get(key).value); + } catch (Exception e) { + XmlStats.LogWarn("Trying to get Double from " + key + ": " + map.get(key).value); + return 0; + } + } else { + map.put(key, new PropertiesEntry(defaultValue.toString(), defaultComment)); + modified = true; + return defaultValue; + } + } + + public void save() { + if (!modified) + return; + BufferedWriter bwriter = null; + FileWriter fwriter = null; + try { + if (!file.exists()) + file.createNewFile(); + fwriter = new FileWriter(file); + bwriter = new BufferedWriter(fwriter); + SortedSet> results = new TreeSet>(new Comparator>() { + @Override + public int compare(Map.Entry a, Map.Entry b) { + // int d = a.getValue().compareTo(b.getValue()); + int d = a.getKey().compareTo(b.getKey()); + return d; + } + + }); + results.addAll(map.entrySet()); + for (Entry entry : results) { + StringBuilder builder = new StringBuilder(); + builder.append(entry.getKey()); + builder.append(" = "); + builder.append(entry.getValue().value); + if (!entry.getValue().comment.equals("")) { + builder.append(" #"); + builder.append(entry.getValue().comment); + } + bwriter.write(builder.toString()); + bwriter.newLine(); + } + bwriter.flush(); + } catch (IOException e) { + XmlStats.LogError("IO Exception with file " + file.getName()); + } finally { + try { + if (bwriter != null) { + bwriter.flush(); + bwriter.close(); + } + if (fwriter != null) { + fwriter.close(); + } + } catch (IOException e) { + XmlStats.LogError("IO Exception with file " + file.getName() + " (on close)"); + } + } + + } + + private class PropertiesEntry { + public String value; + public String comment; + + public PropertiesEntry(String value, String comment) { + this.value = value; + this.comment = comment; + } + } +} \ No newline at end of file diff --git a/src/de/sockenklaus/XmlStats/WebServer.java b/src/de/sockenklaus/XmlStats/WebServer.java new file mode 100644 index 0000000..ea14414 --- /dev/null +++ b/src/de/sockenklaus/XmlStats/WebServer.java @@ -0,0 +1,41 @@ +package de.sockenklaus.XmlStats; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import com.sun.net.httpserver.HttpServer; + +import de.sockenklaus.XmlStats.XmlWorkers.*; + +public class WebServer { + + private InetSocketAddress address; + private HttpServer server = null; + + + /* + * Konstruktoren, um die Address zu initialisieren + */ + public WebServer(int port) throws IOException { + this.address = new InetSocketAddress(port); + + server = HttpServer.create(address, 0); + + server.createContext("/users.xml", new XmlWorkerUsers()); + server.createContext("/userstats.xml", new XmlWorkerUserstats()); + server.createContext("/money.xml", new XmlWorkerMoney()); + + this.server.start(); + } + + /* + * Beende den Server + */ + public void stopServer() { + server.stop(0); + } + + public boolean serverRunning(){ + return (this.server == null) ? false : true; + } +} diff --git a/src/de/sockenklaus/XmlStats/XmlStats.java b/src/de/sockenklaus/XmlStats/XmlStats.java new file mode 100644 index 0000000..61927af --- /dev/null +++ b/src/de/sockenklaus/XmlStats/XmlStats.java @@ -0,0 +1,91 @@ +package de.sockenklaus.XmlStats; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Server; +import org.bukkit.plugin.java.JavaPlugin; + +import com.nidefawl.Stats.Stats; + +import de.sockenklaus.XmlStats.Settings.Settings; + +public class XmlStats extends JavaPlugin { + + public final static Logger log = Logger.getLogger("Minecraft"); + public final static double version = 0.01; + public final static String logprefix = "[XmlStats-" + version + "]"; + public boolean enabled = false; + private static Stats statsPlugin; + private static Server serverRef; + public WebServer xmlQueryServer; + + + @Override + public void onDisable() { + if(enabled && xmlQueryServer.serverRunning()){ + enabled = false; + + xmlQueryServer.stopServer(); + + getServer().getScheduler().cancelTasks(this); + + } + LogInfo("Plugin Disabled"); + } + + @Override + public void onEnable() { + + getDataFolder().mkdirs(); + statsPlugin = (Stats)getServer().getPluginManager().getPlugin("Stats"); + serverRef = getServer(); + + Settings.load(this); + + if (Settings.xmlStatsEnabled){ + if (getServer().getPluginManager().isPluginEnabled("Stats")){ + try { + xmlQueryServer = new WebServer(Settings.xmlStatsPort); + + enabled = true; + LogInfo("Plugin Enabled"); + } + catch (Exception ex){ + LogError("Fehler beim Erstellen des Webservers:"); + LogError(ex.getMessage()); + } + + } + else { + LogError("Stats-Plugin laeuft nicht... Breche ab..."); + } + } + else { + LogError("Plugin ist derzeit in der "+getDataFolder().getPath()+"/"+Settings.settingsFilename+" deaktiviert."); + } + + + } + + public static void LogError(String Message) { + log.log(Level.SEVERE, logprefix + " " + Message); + } + + public static void LogInfo(String Message) { + log.info(logprefix + " " + Message); + } + + public static void LogWarn(String Message){ + log.log(Level.WARNING, logprefix + " "+ Message); + } + + public static Stats getStatsPlugin(){ + return statsPlugin; + } + + public static Server getServerRef(){ + return serverRef; + } + +} diff --git a/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorker.java b/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorker.java new file mode 100644 index 0000000..dea0ee9 --- /dev/null +++ b/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorker.java @@ -0,0 +1,95 @@ +package de.sockenklaus.XmlStats.XmlWorkers; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpExchange; + +import de.sockenklaus.XmlStats.XmlStats; + + +public abstract class XmlWorker implements HttpHandler { + + public void handle(HttpExchange exchange) { + Map parameters = new HashMap(); + + if("get".equalsIgnoreCase(exchange.getRequestMethod())){ + String queryString = exchange.getRequestURI().getRawQuery(); + String xmlResponse = ""; + byte[] byteResponse; + + try { + + parseQuery(queryString, parameters); + + } catch(UnsupportedEncodingException ex){ + XmlStats.LogError("Fehler beim Parsen des HTTP-Query-Strings."); + XmlStats.LogError(ex.getMessage()); + } + + xmlResponse = processQuery(parameters); + + byteResponse = xmlResponse.getBytes(); + + try { + exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, byteResponse.length); + exchange.getResponseBody().write(byteResponse); + } + catch(IOException ex){ + XmlStats.LogError("Fehler beim Senden HTTP-Antwort."); + XmlStats.LogError(ex.getMessage()); + } + + exchange.close(); + } + } + + @SuppressWarnings("unchecked") + public void parseQuery(String queryString, Map parameters) throws UnsupportedEncodingException { + if (queryString != null){ + String pairs[] = queryString.split("[&]"); + + for (String pair : pairs){ + String param[] = pair.split("[=]"); + + String key = null; + String value = null; + + if(param.length > 0){ + key = URLDecoder.decode(param[0], System.getProperty("file.encoding")); + } + + if(param.length > 1){ + value = URLDecoder.decode(param[1], System.getProperty("file.encoding")); + } + + if (parameters.containsKey(key)){ + Object obj = parameters.get(key); + + if(obj instanceof List){ + List values = (List)obj; + values.add(value); + } + else if (obj instanceof String){ + List values = new ArrayList(); + values.add((String)obj); + values.add(value); + parameters.put(key, values); + } + } + else { + parameters.put(key, value); + } + } + } + } + + abstract String processQuery(Map parameters); +} diff --git a/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorkerMoney.java b/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorkerMoney.java new file mode 100644 index 0000000..e0eba7d --- /dev/null +++ b/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorkerMoney.java @@ -0,0 +1,13 @@ +package de.sockenklaus.XmlStats.XmlWorkers; + +import java.util.Map; + +public class XmlWorkerMoney extends XmlWorker { + + @Override + public String processQuery(Map parameters) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorkerUsers.java b/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorkerUsers.java new file mode 100644 index 0000000..0ef6c02 --- /dev/null +++ b/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorkerUsers.java @@ -0,0 +1,70 @@ +package de.sockenklaus.XmlStats.XmlWorkers; + +import java.io.StringWriter; +import java.util.Map; +import java.util.logging.Level; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import com.nidefawl.Stats.Stats; + +import de.sockenklaus.XmlStats.Datasource.UsersDS; + +public class XmlWorkerUsers extends XmlWorker { + + @Override + public String processQuery(Map parameters) { + UsersDS users = new UsersDS(); + + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.newDocument(); + DOMSource source = new DOMSource(doc); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + + Element root = doc.createElement("players"); + root.setAttribute("count", String.valueOf(users.getAllPlayers().size())); + doc.appendChild(root); + + + /* + * Hier wird das XML aufgebaut + */ + + for(String playerName : users.getAllPlayers()){ + + Element elem_player = doc.createElement("player"); + elem_player.setAttribute("name", playerName); + + root.appendChild(elem_player); + } + /* + * Hier endet der XML-Aufbau + */ + + transformer.transform(source, result); + return writer.toString(); + } + + catch (Exception e) + { + Stats.log.log(Level.SEVERE, "Something went terribly wrong!"); + Stats.log.log(Level.SEVERE, e.getMessage()); + } + + return ""; + } + +} diff --git a/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorkerUserstats.java b/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorkerUserstats.java new file mode 100644 index 0000000..5c391ce --- /dev/null +++ b/src/de/sockenklaus/XmlStats/XmlWorkers/XmlWorkerUserstats.java @@ -0,0 +1,99 @@ +package de.sockenklaus.XmlStats.XmlWorkers; + +import java.io.File; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Map; +import java.util.logging.Level; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import com.nidefawl.Stats.Stats; +import com.nidefawl.Stats.ItemResolver.hModItemResolver; +import com.nidefawl.Stats.datasource.Category; +import com.nidefawl.Stats.datasource.PlayerStat; + +import de.sockenklaus.XmlStats.Datasource.StatsDS; + +public class XmlWorkerUserstats extends XmlWorker { + + @Override + public String processQuery(Map parameters) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.newDocument(); + DOMSource source = new DOMSource(doc); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + StatsDS statsDS = new StatsDS(); + + hModItemResolver itemResolver = new hModItemResolver(new File(statsDS.getDataFolder(),"items.txt")); + + String[] resolveCats = {"blockdestroy", "blockcreate", "itemdrop", "itempickup"}; + + Element root = doc.createElement("stats"); + doc.appendChild(root); + + /* + * Hier wird das XML aufgebaut + */ + + for(String playerName : statsDS.getStats().keySet()){ + PlayerStat player_stats = statsDS.getStats().get(playerName); + + Element elem_player = doc.createElement("player"); + elem_player.setAttribute("name", playerName); + + for(String catName : player_stats.getCats()){ + Category cat = player_stats.get(catName); + Element elem_cat = doc.createElement("category"); + elem_cat.setAttribute("name", catName); + + for(String valName : cat.stats.keySet()){ + int value = cat.get(valName); + Element elem_value = doc.createElement("stat"); + + elem_value.setAttribute("name", valName); + + if (Arrays.asList(resolveCats).contains(catName)){ + elem_value.setAttribute("id", String.valueOf(itemResolver.getItem(valName))); + } + elem_value.setAttribute("value", String.valueOf(value)); + + elem_cat.appendChild(elem_value); + } + + + elem_player.appendChild(elem_cat); + } + + root.appendChild(elem_player); + } + /* + * Hier endet der XML-Aufbau + */ + + transformer.transform(source, result); + return writer.toString(); + } + + catch (Exception e){ + Stats.log.log(Level.SEVERE, "Something went terribly wrong!"); + Stats.log.log(Level.SEVERE, e.getMessage()); + } + + return ""; + } + +} diff --git a/src/plugin.yml b/src/plugin.yml new file mode 100644 index 0000000..8729130 --- /dev/null +++ b/src/plugin.yml @@ -0,0 +1,4 @@ +name: XmlStats +main: de.sockenklaus.XmlStats.XmlStats +version: 0.01 +author: sockenklaus \ No newline at end of file