Rename main package

This commit is contained in:
Lorenzo Dellacà
2022-05-31 20:09:36 +02:00
parent ff8f52720d
commit 96f8ef153f
32 changed files with 112 additions and 113 deletions

View File

@@ -0,0 +1,41 @@
package wtf.beatrice.hubthat.utils;
public enum ConfigEntries
{
HUB_DELAY("hub.delay"),
SPAWN_DELAY("spawn.delay"),
WORLD_RELATED_CHAT("chat.limit-to-worlds-sharing-spawns"),
UPDATE_CHECKER_ENABLED("update-checker.enable"),
MOVEMENT_DETECTION_ENABLED("movement-detection.enable"),
GAMEMODE_SET_ON_JOIN("gamemode.set-on-join"),
GAMEMODE("gamemode.mode"),
TELEPORTATION_RESPAWN_HANDLER("teleportation.respawn-handler"),
TELEPORTATION_TP_HUB_ON_JOIN("teleportation.teleport-to-hub-on-join"),
TELEPORTATION_TP_MESSAGE_ON_JOIN("teleportation.send-tp-message-on-join"),
TELEPORTATION_TP_HUB_ON_RESPAWN("teleportation.teleport-to-hub-on-respawn"),
MULTIVERSE_BYPASS("settings.multiverse-bypass"),
INVISIBILITY_FIX("settings.fix-invisible-after-tp"),
//UPDATE_CHECKER_ENABLE("updates.enable-update-checker"),
;
public String path;
ConfigEntries(String path)
{
this.path = path;
}
}

View File

@@ -0,0 +1,87 @@
package wtf.beatrice.hubthat.utils;
import wtf.beatrice.hubthat.HubThat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.UUID;
import java.util.logging.Level;
public class Debugger
{
// Enable or disable debugging messages (aka verbosity).
private final boolean DEBUGGING = false;
// Initialize needed variables. We will need those variables to be able to precisely debug the plugin.
private String className;
private String packageName;
// Save my UUID and current Username somewhere so I can get debug messages too.
public UUID authorUUID = UUID.fromString("297a1dc8-c0a3-485a-ad21-8956c749f927");
public String authorName = "mind_overflow";
// Make a constructor requiring to be given a class so we exactly know which class has made an instance of it and all of its properties.
public Debugger(String instanceClassName)
{
// Only run this code and actually make a whole instance of the class only if debugging is active.
if(DEBUGGING)
{
// Initializing the class variable and set it to this one: in case something bad happens, we still have the log without the class info.
Class instanceClass = getClass();
try
{
/*
* Try finding the instancing class. This is normally bad for performance as we have to search for the class since we only have its name
* but the only other way would have been to always instantiate a whole class instead of a single String, making the plugin resource
* hungry even if the Debugger was disabled.
*/
instanceClass = Class.forName(instanceClassName);
}
catch (ClassNotFoundException e)
{
// In case it throws an error, go mad and report it in console.
HubThat.logger.log(Level.INFO, "WTF? A class made an instance of the Debugger but it somehow can't define which class was it. Very weird. Setting it to the Debugger class.");
HubThat.logger.log(Level.INFO, "Please send the following error log to me (" + authorName + "):");
e.printStackTrace();
}
// Give the instance's variables their respective values.
className = instanceClass.getSimpleName();
packageName = instanceClass.getPackage().getName();
}
}
/*
* Check if debugging is enabled and eventually send debug logs. No need to worry about some of this data being null as there already are
* checks and fixed for that in the constructor. Also, the debugger must be instanced for this method to be called (it's not static), so
* we already have the info we need thanks to that.
*/
public void sendDebugMessage(Level lvl, String str)
{
// Check if debugging is enabled.
if(DEBUGGING)
{
// Put together all the info we have in a single String.
String msg = className + ": " + str;
// Send the info to the server log.
HubThat.logger.log(lvl, msg);
// Check if I'm online and if I am, send me the same info.
Player author = Bukkit.getPlayer(authorUUID);
if(author == null) {
author = Bukkit.getPlayer(authorName);
}
if(author != null)
{
if(Bukkit.getServer().getOnlinePlayers().contains(author))
{
author.sendMessage(msg);
}
}
}
}
}

View File

@@ -0,0 +1,57 @@
package wtf.beatrice.hubthat.utils;
public enum LocalizedMessages
{
NO_PERMISSION("error.no-permission"),
WARNING_TELEPORTATION_CANCELLED("warning.teleportation-cancelled"),
ERROR_ALREADY_TELEPORTING("error.already-teleporting"),
ERROR_HUB_NOT_SET("error.hub-not-set"),
ERROR_SPAWN_NOT_SET("error.spawn-not-set"),
ERROR_WORLD_NOT_EXISTING("error.unknown-world"),
ERROR_WRONG_USAGE("error.wrong-usage"),
ERROR_CONSOLE_ACCESS_BLOCKED("error.console-access-blocked"),
ERROR_PLAYER_OFFLINE("error.player-offline"),
INFO_HUB_TELEPORTED("info.hub.teleported"),
INFO_HUB_TELEPORTED_OTHER("info.hub.teleported-other"),
INFO_SPAWN_TELEPORTED("info.spawn.teleported"),
INFO_SPAWN_TELEPORTED_OTHER("info.spawn.teleported-other"),
INFO_WORLDTP_TELEPORTED("info.worldtp.teleported"),
INFO_TELEPORT_DELAY("info.global.teleport-delay"),
INFO_HUB_SET("info.hub.set"),
INFO_SPAWN_SET("info.spawn.set"),
INFO_WORLDS_LIST("info.worlds-list"),
INFO_PREFIX("info.global.prefix"),
;
public String path;
LocalizedMessages(String path)
{
this.path = path;
}
}

View File

@@ -0,0 +1,81 @@
package wtf.beatrice.hubthat.utils;
import wtf.beatrice.hubthat.utils.files.FileUtils;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import java.util.logging.Level;
public class MessageUtils
{
// Initialize the Debugger instance.
private static final Debugger debugger = new Debugger(MessageUtils.class.getName());
// Method to automatically load and send a localized message to the CommandSender.
public static void sendLocalizedMessage(CommandSender sender, LocalizedMessages messageEnum)
{
// If we actually have a sender, send it the message and color it!
if(sender != null) sender.sendMessage(getLocalizedMessage(messageEnum, true));
// If the sender is null, report it to the debugger.
else debugger.sendDebugMessage(Level.SEVERE, "Sender is null!");
}
// Method to send a colorized message to the CommandSender.
public static void sendColorizedMessage(CommandSender sender, String message)
{
// If we actually have a sender, send it the message!
if(sender != null) sender.sendMessage(colorize(message));
// If the sender is null, report it to the debugger.
else debugger.sendDebugMessage(Level.SEVERE, "Sender is null!");
}
public static String getLocalizedMessage(LocalizedMessages messageEnum, boolean applyColor)
{
/*
Load the string from the enum.
We are doing this because we don't want random strings to be passed to this method: we want it done
this way and this way only, so we don't get any error as every entry added to the enum is manually
checked before actually adding it.
*/
String path = messageEnum.path;
// Initialize the Lang file.
YamlConfiguration langFile = FileUtils.FileType.LANG_YAML.yaml;
// Initialize the message string and store the String from the lang file to it.
String localizedMessage = langFile.getString(LocalizedMessages.INFO_PREFIX.path) + langFile.getString(path);
// Check if the message is null and if we have to color the message or leave the symbols inside for further elaboration.
if (localizedMessage != null)
{
if(applyColor)
{
// Replace the famous '&' and '§' symbols with a ChatColor so we can color the messages!
localizedMessage = colorize(localizedMessage);
}
} else
{
// Report if the message is null.
debugger.sendDebugMessage(Level.SEVERE, "String " + path + " is null!");
}
// Return the message, whether it's null or not (if it's null, it's the same as writing "return null").
return localizedMessage;
}
// Colorize Strings!
public static String colorize(String str)
{
/*
Since the translateAlternateColors method works with only one char and overwrites the previous one,
we are going to replace '&' with '§' and then run that method with all the '§'s.
*/
str = str.replace('&', '§');
ChatColor.translateAlternateColorCodes('§', str);
return str;
}
}

View File

@@ -0,0 +1,18 @@
package wtf.beatrice.hubthat.utils;
import org.bukkit.command.CommandSender;
public class PermissionUtils
{
// Method to get the permission string from the Permissions enum.
public static boolean playerHasPermission(CommandSender user, Permissions permission)
{
if (user != null && user.hasPermission(permission.permission))
{
return true;
}
return false;
}
}

View File

@@ -0,0 +1,37 @@
package wtf.beatrice.hubthat.utils;
public enum Permissions
{
RELOAD_CONFIG("hubthat.reloadconfig"),
NO_HUB_DELAY("hubthat.nohubdelay"),
HUB_SET("hubthat.sethub"),
HUB_TELEPORT("hubthat.hub"),
HUB_TELEPORT_OTHERS("hubthat.hub.others"),
NO_SPAWN_DELAY("hubthat.nospawndelay"),
SPAWN_SET("hubthat.setspawn"),
SPAWN_TELEPORT("hubthat.spawn"),
SPAWN_TELEPORT_OTHERS("hubthat.spawn.others"),
SPAWN_TELEPORT_ANOTHER_WORLD("hubthat.spawn.anotherworld"),
TELEPORT_TO_WORLD("hubthat.gotoworld"),
WORLD_LIST("hubthat.listworlds"),
GET_UPDATES_NOTIFICATIONS("hubthat.updates"),
HELP_MESSAGE("hubthat.help");
public String permission;
Permissions(String permission) { this.permission = permission; }
}

View File

@@ -0,0 +1,22 @@
package wtf.beatrice.hubthat.utils;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
public class PluginCache
{
public static ArrayList<String> teleporting = new ArrayList<>();
public static ArrayList<String>cancelRunnable = new ArrayList<>();
public static Boolean updateChecker = true;
public static boolean invisibilityFix = false; // sometimes, in buggy versions of Spigot, players become invisible after getting teleported. this fixes the problem.
public static boolean sendJoinTpMessage = true;
public static final ItemStack AIR = new ItemStack(Material.AIR, 1);
public static final double minSupportedVersion = 1.7;
public static final double maxSupportedVersion = 1.16;
}

View File

@@ -0,0 +1,195 @@
package wtf.beatrice.hubthat.utils;
import wtf.beatrice.hubthat.HubThat;
import wtf.beatrice.hubthat.utils.files.FileUtils;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.logging.Level;
public class TeleportUtils
{
private static HubThat plugin;
public TeleportUtils(HubThat givenPlugin)
{
plugin = givenPlugin;
}
// Initialize the debugger so I can debug the plugin.
private static final Debugger debugger = new Debugger(TeleportUtils.class.getName());
// Method to teleport a player, given the location coordinates, the world name and the player's name.
public static void teleportPlayer(double x, double y, double z, double yaw, double pitch, String worldName, String playerName)
{
Location location = new Location(plugin.getServer().getWorld(worldName), x, y, z, (float)yaw, (float)pitch);
Player player = plugin.getServer().getPlayer(playerName);
if(player == null) return;
fixInvisibilityBefore(location);
plugin.getServer().getScheduler().runTaskLater(plugin, () ->
{
player.teleport(location);
fixInvisibilityAfter(player);
}, 1);
}
// Method to teleport a player, given its username and defined if it's a hub or a spawn.
public static void teleportPlayer(CommandSender sender, Player player, FileUtils.FileType type, String currentWorldName, boolean sendMessage)
{
// Get the Player object from his playername.
//Player player = plugin.getServer().getPlayer(playerName);
// If the player is null, give a warning and stop the method.
if(player == null)
{
debugger.sendDebugMessage(Level.SEVERE, "Error: player who tried to teleport is NULL!");
return;
}
// Initialize various needed variables.
String worldName;
double x, y, z, yaw, pitch;
// Check if the given file type is a hub file.
if(type == FileUtils.FileType.HUB_YAML)
{
// Load hub location.
x = type.yaml.getDouble("hub.x");
y = type.yaml.getDouble("hub.y");
z = type.yaml.getDouble("hub.z");
yaw = type.yaml.getDouble("hub.yaw");
pitch = type.yaml.getDouble("hub.pitch");
worldName = type.yaml.getString("hub.world");
}
// Check if the given file type is a spawn file.
else if(type == FileUtils.FileType.SPAWN_YAML)
{
// Load the spawn location.
x = type.yaml.getDouble("spawn.x." + currentWorldName);
y = type.yaml.getDouble("spawn.y." + currentWorldName);
z = type.yaml.getDouble("spawn.z." + currentWorldName);
yaw = type.yaml.getDouble("spawn.yaw." + currentWorldName);
pitch = type.yaml.getDouble("spawn.pitch." + currentWorldName);
worldName = type.yaml.getString("spawn.world." + currentWorldName);
}
// Else, set the world to null because there was a problem.
else
{
worldName = null;
x = 0; y = 0; z = 0; yaw = 0; pitch = 0;
}
// Check if the world name is null.
if(worldName == null)
{
// Send a debug message about it.
debugger.sendDebugMessage(Level.SEVERE, "Error: could not find world!");
if(type == FileUtils.FileType.HUB_YAML)
{
// send a message about the hub being not set
if(sendMessage) MessageUtils.sendLocalizedMessage(sender, LocalizedMessages.ERROR_HUB_NOT_SET);
}
else if(type == FileUtils.FileType.SPAWN_YAML)
{
// send a message about the spawn being not set.
if(sendMessage) MessageUtils.sendLocalizedMessage(sender, LocalizedMessages.ERROR_SPAWN_NOT_SET);
}
else
{
if(sendMessage) MessageUtils.sendColorizedMessage(sender, "&cError in code. Contact the developer!");
}
// Stop.
return;
} else
{
debugger.sendDebugMessage(Level.INFO, "Found world name: " + worldName);
// Check if the hub is not set.
if(worldName.equals("__UNSET__") && type == FileUtils.FileType.HUB_YAML)
{
// Warn the player about the hub not being set.
if(sendMessage) MessageUtils.sendLocalizedMessage(sender, LocalizedMessages.ERROR_HUB_NOT_SET);
// Stop.
return;
}
}
// Check if the world actually exists.
World destinationWorld = plugin.getServer().getWorld(worldName);
if(destinationWorld == null)
{
// Tell the player that the world does not exist.
if(sendMessage)
{
String errorWorldNotExistingMessage = MessageUtils.getLocalizedMessage(LocalizedMessages.ERROR_WORLD_NOT_EXISTING, false);
errorWorldNotExistingMessage = errorWorldNotExistingMessage.replace("%w%", worldName);
MessageUtils.sendColorizedMessage(player, errorWorldNotExistingMessage);
}
return;
}
// Store the location in a variable and teleport the player to it.
final Location finalLocation = new Location(destinationWorld, x, y, z, (float)yaw, (float)pitch);
fixInvisibilityBefore(finalLocation);
plugin.getServer().getScheduler().runTaskLater(plugin, () ->
{
player.teleport(finalLocation);
fixInvisibilityAfter(player);
}, 1);
// Check if the player is teleporting to the hub.
if(type == FileUtils.FileType.HUB_YAML)
{
// Send a message to the player about him being successfully teleported.
if(sendMessage) MessageUtils.sendLocalizedMessage(player, LocalizedMessages.INFO_HUB_TELEPORTED);
if((sender != player) && (sendMessage))
{
String message = MessageUtils.getLocalizedMessage(LocalizedMessages.INFO_HUB_TELEPORTED_OTHER, true).replace("%player%", player.getName());
sender.sendMessage(message);
}
}
else //if(type == FileUtils.FileType.SPAWN_YAML) // left here but commented, for easy understanding
{
if(sendMessage) MessageUtils.sendLocalizedMessage(player, LocalizedMessages.INFO_SPAWN_TELEPORTED);
if((sender != player) && (sendMessage))
{
String message = MessageUtils.getLocalizedMessage(LocalizedMessages.INFO_SPAWN_TELEPORTED_OTHER, true).replace("%player%", player.getName()).replace("%world%", worldName);
sender.sendMessage(message);
}
}
}
public static void teleportPlayer(CommandSender sender, Player player, FileUtils.FileType type, String currentWorldName)
{
teleportPlayer(sender, player, type, currentWorldName, true);
}
public static void teleportPlayer(CommandSender sender, Player player, FileUtils.FileType type, boolean sendMessage)
{
teleportPlayer(sender, player, type, null, sendMessage);
}
public static void teleportPlayer(CommandSender sender, Player player, FileUtils.FileType type)
{
teleportPlayer(sender, player, type, null);
}
public static void fixInvisibilityAfter(Player player)
{
if(PluginCache.invisibilityFix)
{
debugger.sendDebugMessage(Level.INFO, "Invisibility fix enabled!");
player.getInventory().addItem(PluginCache.AIR);
}
}
public static void fixInvisibilityBefore(Location destination)
{
destination.getChunk().load();
}
}

View File

@@ -0,0 +1,229 @@
package wtf.beatrice.hubthat.utils.files;
import wtf.beatrice.hubthat.HubThat;
import wtf.beatrice.hubthat.utils.PluginCache;
import wtf.beatrice.hubthat.utils.ConfigEntries;
import wtf.beatrice.hubthat.utils.Debugger;
import wtf.beatrice.hubthat.utils.metrics.UpdateChecker;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.logging.Level;
public class FileUtils
{
// Instantiate a Debugger for this class.
private static final Debugger debugger = new Debugger(FileUtils.class.getName());
// Necessary variables.
private static HubThat plugin;
public FileUtils(HubThat plugin) {
FileUtils.plugin = plugin;
}
public static void copyFileFromSrc(FileType givenFileType)
{
// Check if files already exists and if it doesn't, then create it.
if(!givenFileType.file.exists())
{
// Load the InputStream of the file in the source folder.
InputStream is = FileUtils.class.getResourceAsStream("/" + givenFileType.file.getName());
try
{
// Try copying the file to the directory where it's supposed to be, and log it.
Files.copy(is, Paths.get(givenFileType.file.getAbsolutePath()));
is.close();
debugger.sendDebugMessage(Level.INFO, "File " + givenFileType.file.getName() + " successfully created.");
}
catch (IOException e)
{
// Throw exception if something went wrong (lol, I expect this to happen since we're working with files in different systems)
HubThat.logger.log(Level.SEVERE, "There were some unexpected errors from " + givenFileType.file.getName() + " file creation. Please contact the developer and send him this log:");
e.printStackTrace();
}
}
}
// As method says, reload YamlConfigurations by overwriting their previous value.
public static void reloadYamls()
{
for(FileType fileType : FileType.values())
{
fileType.yaml = YamlConfiguration.loadConfiguration(fileType.file);
}
YamlConfiguration config = FileType.CONFIG_YAML.yaml;
if(config.getBoolean(ConfigEntries.UPDATE_CHECKER_ENABLED.path))
{
PluginCache.updateChecker = true;
if(UpdateChecker.task != null)
{
plugin.getServer().getScheduler().cancelTask(UpdateChecker.task.getTaskId());
}
UpdateChecker.task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, plugin.updateChecker, 1, 20 * 60 * 60 * 12); // 12 hours
}
PluginCache.invisibilityFix = config.getBoolean(ConfigEntries.INVISIBILITY_FIX.path);
PluginCache.sendJoinTpMessage = config.getBoolean(ConfigEntries.TELEPORTATION_TP_MESSAGE_ON_JOIN.path);
}
// Only reload the needed File.
public static void reloadYaml(FileType yamlFile)
{
yamlFile.yaml = YamlConfiguration.loadConfiguration(yamlFile.file);
debugger.sendDebugMessage(Level.INFO, "File " + yamlFile.file.getName() + " YAML loaded.");
}
// Save a Yaml file from the list of the plugin's YamlFiles enum.
public static void saveExistingYaml(FileType yamlFile)
{
// Get the actual File and its location.
File configFile = yamlFile.file;
try {
// Try saving the value in FileType.NAME.yaml into the file itself we just got. Else, it would only be saved in RAM and then be lost after unloading the plugin.
yamlFile.yaml.save(configFile);
debugger.sendDebugMessage(Level.INFO, "Successfully saved " + configFile.getName() +" (YAML)!");
} catch (IOException e) {
debugger.sendDebugMessage(Level.SEVERE, "Error in saving " + configFile.getName() + "(YAML)!");
e.printStackTrace();
}
// Reload the Yaml configuration from the file, just in case.
reloadYaml(yamlFile);
}
// Check if all needed files exist and work correctly.
public static void checkFiles() {
// Check if the different needed files and folders exist and if not, try creating them.
// Check if plugin folder exists and eventually make it. Easy enough.
if(!plugin.getDataFolder().exists())
{
if(plugin.getDataFolder().mkdir())
{
debugger.sendDebugMessage(Level.INFO, "Plugin dir successfully created.");
}
}
for(FileType file : FileType.values())
{
// Check and eventually create config file.
copyFileFromSrc(file);
// Reload file YAML data into FileType.NAME.yaml.
reloadYaml(file);
// Check if there is any missing entry.
checkYamlMissingEntries(file);
}
HubThat.logger.log(Level.INFO, "All files are working correctly.");
}
private static void checkYamlMissingEntries(FileType givenFile)
{
/*
Load the file from source so we can check if the file in the plugin directory is missing any entries.
Since our file is not an actual file on the filesystem but rather a compressed file in the jar archive,
we can't directly access it via a "File file = new File();" method. To do it, we'd need to extract
the file from the archive to a temporary file, read it and then delete it.
The choice of making an InputStream instead is better because we don't risk leaving junk files
in the filesystem and we can achieve the same objective without so many resource-consuming passages.
*/
// First of all, we're gonna get the InputStream of the file from the jar archive.
InputStream is = FileUtils.class.getResourceAsStream("/" + givenFile.file.getName());
// Then, we're gonna make a Reader because we don't want to save it as a file but only load it in memory.
// Bukkit's YamlConfiguration accepts Readers so this is perfect!
Reader targetReader = new InputStreamReader(is);
// Load its YamlConfiguration.
YamlConfiguration srcYaml = YamlConfiguration.loadConfiguration(targetReader);
// Iterate each entry in the YamlConfiguration.
debugger.sendDebugMessage(Level.INFO, "Iterating src config entries for file " + givenFile.file.getName() + ".");
/* For each String which we'll name 'key' in the Set of entries of the yaml file, do...
getKeys(true) returns all the entries and all the sub-entries, which is what we need because
we want to check the whole file for missing entries.
If we wanted to only load an entry with the highest level sub-entries, we would just pass 'false'
as an argument.
Example
---- FILE ----------------
hello: 'this is a string'
myname: 4
islorenzo: 8
who: true
areu: '?'
john: false
--------------------------
Set<String> keys = srcYaml.getConfigurationSection("path").getKeys(true);
By saving our set with 'false' as an argument, and "" as the path (which means the highest level of the file),
we'd only get the 'hello' String and the 'john' boolean's value in the set.
By saving our set with 'false' as an argument, and "hello" as the path (which means the highest level of the
'hello' entry), we'd only get the 'hello' String's value and the 'hello.myname' and 'hello.islorenzo' booleans' values in the set.
By saving our set with 'true' as an argument, and "" as the path (which means the highest level of the file
with all its sub-entries), we'd get the value of all entries in the whole file ('hello', 'hello.myname', 'hello.islorenzo',
'hello.islorenzo.who', 'hello.islorenzo.areu', 'john') in the set.
By saving our set with 'true' as an argument, and "hello" as the path (which means the highest level of the
'hello' entry with all its sub-entries), we'd get the value of all entries in the 'hello' entry ('hello', 'hello.myname',
'hello.islorenzo', 'hello.islorenzo.who', 'hello.islorenzo.areu') in the set.
*/
for (String key : srcYaml.getConfigurationSection("").getKeys(true))
{
debugger.sendDebugMessage(Level.INFO, "Analyzing key '" + key + "' with default value '" + srcYaml.get(key) + "'.");
// Check if file is missing every entry.
if(!givenFile.yaml.contains(key))
{
debugger.sendDebugMessage(Level.WARNING, "Config file is missing '" + key + "' key! Proceeding to add it...");
// Add the entry to the file.
givenFile.yaml.set(key, srcYaml.get(key));
debugger.sendDebugMessage(Level.WARNING, "Added key '" + key + "' with value '" + srcYaml.get(key) + "'.");
// Save the file!
saveExistingYaml(givenFile);
}
}
debugger.sendDebugMessage(Level.INFO, " Done iterating src config entries for file " + givenFile.file.getName() + "!");
}
// Save all the info about our files location.
/*
Also initialize all files and their config, so we know where are the files when we need to save or reload them.
this is better than loading the files in the single classes that use them as if we had to reload them, we'd
need to set them again in each of the classes. Doing this instead allows us to have them all in one place.
*/
public enum FileType
{
//PLUGIN_FOLDER(plugin.getDataFolder(), null),
CONFIG_YAML(new File(plugin.getDataFolder()+File.separator + "config.yml"), new YamlConfiguration()),
LANG_YAML(new File(plugin.getDataFolder()+File.separator + "lang.yml"), new YamlConfiguration()),
SPAWN_YAML(new File(plugin.getDataFolder()+File.separator + "spawn.yml"), new YamlConfiguration()),
HUB_YAML(new File(plugin.getDataFolder()+File.separator + "hub.yml"), new YamlConfiguration());
// Constructor, so we can assign the value we set here ^ to our File.
public File file;
public YamlConfiguration yaml;
FileType(File givenFile, YamlConfiguration yamlConfig)
{
file = givenFile;
yaml = yamlConfig;
}
}
}

View File

@@ -0,0 +1,90 @@
package wtf.beatrice.hubthat.utils.files;
import wtf.beatrice.hubthat.HubThat;
import wtf.beatrice.hubthat.utils.ConfigEntries;
import wtf.beatrice.hubthat.utils.LocalizedMessages;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.util.HashMap;
import java.util.logging.Logger;
public class OldConfigConversion
{
public static void checkOldConfig(HubThat plugin, Logger logger)
{
logger.warning(plugin.getName() + ": Checking if config exists...");
File oldConfigFile = FileUtils.FileType.CONFIG_YAML.file;
if(!oldConfigFile.exists()) return;
logger.warning("Loading config...");
YamlConfiguration oldConfig = YamlConfiguration.loadConfiguration(oldConfigFile);
if(!oldConfig.getKeys(true).contains("global.VERSION")) return;
logger.warning("WARNING! Old Configuration Detected!");
logger.warning("Starting conversion now!");
String configDir = plugin.getDataFolder().getAbsolutePath();
logger.warning("Renaming old config...");
oldConfigFile.renameTo(new File( configDir + File.separator + "config.old"));
logger.warning("generating new files...");
FileUtils.checkFiles();
// newPath, oldPath
HashMap<ConfigEntries, String>newAndOldConfigEntries = new HashMap<>();
logger.warning("Loading config entries...");
newAndOldConfigEntries.put(ConfigEntries.HUB_DELAY, "hub.delay");
newAndOldConfigEntries.put(ConfigEntries.SPAWN_DELAY, "spawn.delay");
newAndOldConfigEntries.put(ConfigEntries.WORLD_RELATED_CHAT, "global.world-related-chat");
newAndOldConfigEntries.put(ConfigEntries.UPDATE_CHECKER_ENABLED, "updates.update-notify");
newAndOldConfigEntries.put(ConfigEntries.MOVEMENT_DETECTION_ENABLED, "global.move-detect");
newAndOldConfigEntries.put(ConfigEntries.GAMEMODE_SET_ON_JOIN, "global.set-gamemode-on-join");
newAndOldConfigEntries.put(ConfigEntries.GAMEMODE, "global.gamemode");
newAndOldConfigEntries.put(ConfigEntries.TELEPORTATION_TP_HUB_ON_JOIN, "global.tp-hub-on-join");
newAndOldConfigEntries.put(ConfigEntries.TELEPORTATION_TP_HUB_ON_RESPAWN, "global.tp-hub-on-respawn");
newAndOldConfigEntries.put(ConfigEntries.TELEPORTATION_RESPAWN_HANDLER, "global.respawn-handler");
logger.warning("Converting config entries...");
for(ConfigEntries entry : newAndOldConfigEntries.keySet())
{
logger.warning("Entry: " + entry.path);
FileUtils.FileType.CONFIG_YAML.yaml.set(entry.path, oldConfig.get(newAndOldConfigEntries.get(entry)));
}
logger.warning("Saving file...");
FileUtils.saveExistingYaml(FileUtils.FileType.CONFIG_YAML);
logger.warning("Done with config.yml!");
logger.warning("Loading lang entries...");
HashMap<LocalizedMessages, String>newAndOldLangEntries = new HashMap<>();
newAndOldLangEntries.put(LocalizedMessages.ERROR_ALREADY_TELEPORTING, "global.ALREADY-TELEPORTING");
newAndOldLangEntries.put(LocalizedMessages.ERROR_HUB_NOT_SET, "hub.HUB_NOT_SET");
newAndOldLangEntries.put(LocalizedMessages.ERROR_SPAWN_NOT_SET, "spawn.SPAWN_NOT_SET");
newAndOldLangEntries.put(LocalizedMessages.ERROR_WORLD_NOT_EXISTING, "worldtp.UNKNOWN_WORLD");
newAndOldLangEntries.put(LocalizedMessages.WARNING_TELEPORTATION_CANCELLED, "global.MOVED");
newAndOldLangEntries.put(LocalizedMessages.INFO_HUB_TELEPORTED, "hub.TELEPORTED");
newAndOldLangEntries.put(LocalizedMessages.INFO_SPAWN_TELEPORTED, "spawn.TELEPORTED");
newAndOldLangEntries.put(LocalizedMessages.INFO_WORLDTP_TELEPORTED, "worldtp.TELEPORTED");
logger.warning("Converting lang entries...");
for(LocalizedMessages message : newAndOldLangEntries.keySet())
{
logger.warning("Entry: " + message.path);
FileUtils.FileType.LANG_YAML.yaml.set(message.path, oldConfig.get(newAndOldLangEntries.get(message)));
}
logger.warning("Saving file...");
FileUtils.saveExistingYaml(FileUtils.FileType.LANG_YAML);
logger.warning("Done with lang.yml!");
}
}

View File

@@ -0,0 +1,717 @@
package wtf.beatrice.hubthat.utils.metrics;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicePriority;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
/**
* bStats collects some data for plugin authors.
* <p>
* Check out https://bStats.org/ to learn more about bStats!
*/
public class Metrics {
static {
if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) {
final String defaultPackage = new String(
new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'});
final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) {
throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
}
}
}
// The version of this bStats class
public static final int B_STATS_VERSION = 1;
// The url to which the data is sent
private static final String URL = "https://bStats.org/submitData/bukkit";
// Is bStats enabled on this server?
private boolean enabled;
// Should failed requests be logged?
private static boolean logFailedRequests = false;
// Should the sent data be logged?
private static boolean logSentData = false;
// Should the response text be logged?
private static boolean logResponseStatusText = false;
// The uuid of the server
private static String serverUUID;
// The plugin
private final Plugin plugin;
// A list with all custom charts
private final List<CustomChart> charts = new ArrayList<>();
/**
* Class constructor.
*
* @param plugin The plugin which stats should be submitted.
*/
public Metrics(Plugin plugin) {
if (plugin == null) {
throw new IllegalArgumentException("Plugin cannot be null!");
}
this.plugin = plugin;
// Get the config file
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
File configFile = new File(bStatsFolder, "config.yml");
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
// Check if the config file exists
if (!config.isSet("serverUuid")) {
// Add default values
config.addDefault("enabled", true);
// Every server gets its unique random id.
config.addDefault("serverUuid", UUID.randomUUID().toString());
// Should failed request be logged?
//config.addDefault("logFailedRequests", false);
// Should the sent data be logged?
//config.addDefault("logSentData", false);
// Should the response text be logged?
//config.addDefault("logResponseStatusText", false);
// Inform the server owners about bStats
config.options().header(
"bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
"To honor their work, you should not disable it.\n" +
"All data is anonymous and this has practically no effect on the server performance!\n" +
"Check out https://bStats.org/ to learn more :)"
).copyDefaults(true);
try {
config.save(configFile);
} catch (IOException ignored) { }
}
// Load the data
enabled = config.getBoolean("enabled", true);
serverUUID = config.getString("serverUuid");
//logFailedRequests = config.getBoolean("logFailedRequests", false);
//logSentData = config.getBoolean("logSentData", false);
//logResponseStatusText = config.getBoolean("logResponseStatusText", false);
logFailedRequests = false;
logSentData = false;
logResponseStatusText = false;
if (enabled) {
boolean found = false;
// Search for all other bStats Metrics classes to see if we are the first one
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
try {
service.getField("B_STATS_VERSION"); // Our identifier :)
found = true; // We aren't the first
break;
} catch (NoSuchFieldException ignored) { }
}
// Register our service
Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal);
if (!found) {
// We are the first!
startSubmitting();
}
}
}
/**
* Checks if bStats is enabled.
*
* @return Whether bStats is enabled or not.
*/
public boolean isEnabled() {
return enabled;
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
if (chart == null) {
throw new IllegalArgumentException("Chart cannot be null!");
}
charts.add(chart);
}
/**
* Starts the Scheduler which submits our data every 30 minutes.
*/
private void startSubmitting() {
final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (!plugin.isEnabled()) { // Plugin was disabled
timer.cancel();
return;
}
// Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler
// Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;)
Bukkit.getScheduler().runTask(plugin, () -> submitData());
}
}, 1000 * 60 * 5, 1000 * 60 * 30);
// Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start
// WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
// WARNING: Just don't do it!
}
/**
* Gets the plugin specific data.
* This method is called using Reflection.
*
* @return The plugin specific data.
*/
public JsonObject getPluginData() {
JsonObject data = new JsonObject();
String pluginName = plugin.getDescription().getName();
String pluginVersion = plugin.getDescription().getVersion();
data.addProperty("pluginName", pluginName); // Append the name of the plugin
data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin
JsonArray customCharts = new JsonArray();
for (CustomChart customChart : charts) {
// Add the data of the custom charts
JsonObject chart = customChart.getRequestJsonObject();
if (chart == null) { // If the chart is null, we skip it
continue;
}
customCharts.add(chart);
}
data.add("customCharts", customCharts);
return data;
}
/**
* Gets the server specific data.
*
* @return The server specific data.
*/
private JsonObject getServerData() {
// Minecraft specific data
int playerAmount;
try {
// Around MC 1.8 the return type was changed to a collection from an array,
// This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers");
playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class)
? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
: ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
} catch (Exception e) {
playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed
}
int onlineMode = Bukkit.getOnlineMode() ? 1 : 0;
String bukkitVersion = Bukkit.getVersion();
String bukkitName = Bukkit.getName();
// OS/Java specific data
String javaVersion = System.getProperty("java.version");
String osName = System.getProperty("os.name");
String osArch = System.getProperty("os.arch");
String osVersion = System.getProperty("os.version");
int coreCount = Runtime.getRuntime().availableProcessors();
JsonObject data = new JsonObject();
data.addProperty("serverUUID", serverUUID);
data.addProperty("playerAmount", playerAmount);
data.addProperty("onlineMode", onlineMode);
data.addProperty("bukkitVersion", bukkitVersion);
data.addProperty("bukkitName", bukkitName);
data.addProperty("javaVersion", javaVersion);
data.addProperty("osName", osName);
data.addProperty("osArch", osArch);
data.addProperty("osVersion", osVersion);
data.addProperty("coreCount", coreCount);
return data;
}
/**
* Collects the data and sends it afterwards.
*/
private void submitData() {
final JsonObject data = getServerData();
JsonArray pluginData = new JsonArray();
// Search for all other bStats Metrics classes to get their plugin data
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
try {
service.getField("B_STATS_VERSION"); // Our identifier :)
for (RegisteredServiceProvider<?> provider : Bukkit.getServicesManager().getRegistrations(service)) {
try {
Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider());
if (plugin instanceof JsonObject) {
pluginData.add((JsonObject) plugin);
} else { // old bstats version compatibility
try {
Class<?> jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject");
if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) {
Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString");
jsonStringGetter.setAccessible(true);
String jsonString = (String) jsonStringGetter.invoke(plugin);
JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject();
pluginData.add(object);
}
} catch (ClassNotFoundException e) {
// minecraft version 1.14+
if (logFailedRequests) {
this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e);
}
continue; // continue looping since we cannot do any other thing.
}
}
} catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { }
}
} catch (NoSuchFieldException ignored) { }
}
data.add("plugins", pluginData);
// Create a new thread for the connection to the bStats server
new Thread(new Runnable() {
@Override
public void run() {
try {
// Send the data
sendData(plugin, data);
} catch (Exception e) {
// Something went wrong! :(
if (logFailedRequests) {
plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e);
}
}
}
}).start();
}
/**
* Sends the data to the bStats server.
*
* @param plugin Any plugin. It's just used to get a logger instance.
* @param data The data to send.
* @throws Exception If the request failed.
*/
private static void sendData(Plugin plugin, JsonObject data) throws Exception {
if (data == null) {
throw new IllegalArgumentException("Data cannot be null!");
}
if (Bukkit.isPrimaryThread()) {
throw new IllegalAccessException("This method must not be called from the main thread!");
}
if (logSentData) {
plugin.getLogger().info("Sending data to bStats: " + data.toString());
}
HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
// Compress the data to save bandwidth
byte[] compressedData = compress(data.toString());
// Add headers
connection.setRequestMethod("POST");
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format
connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);
// Send data
connection.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.write(compressedData);
outputStream.flush();
outputStream.close();
InputStream inputStream = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder builder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
bufferedReader.close();
if (logResponseStatusText) {
plugin.getLogger().info("Sent data to bStats and received response: " + builder.toString());
}
}
/**
* Gzips the given String.
*
* @param str The string to gzip.
* @return The gzipped String.
* @throws IOException If the compression failed.
*/
private static byte[] compress(final String str) throws IOException {
if (str == null) {
return null;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
gzip.write(str.getBytes(StandardCharsets.UTF_8));
gzip.close();
return outputStream.toByteArray();
}
/**
* Represents a custom chart.
*/
public static abstract class CustomChart {
// The id of the chart
final String chartId;
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
CustomChart(String chartId) {
if (chartId == null || chartId.isEmpty()) {
throw new IllegalArgumentException("ChartId cannot be null or empty!");
}
this.chartId = chartId;
}
private JsonObject getRequestJsonObject() {
JsonObject chart = new JsonObject();
chart.addProperty("chartId", chartId);
try {
JsonObject data = getChartData();
if (data == null) {
// If the data is null we don't send the chart.
return null;
}
chart.add("data", data);
} catch (Throwable t) {
if (logFailedRequests) {
Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t);
}
return null;
}
return chart;
}
protected abstract JsonObject getChartData() throws Exception;
}
/**
* Represents a custom simple pie.
*/
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimplePie(String chartId, Callable<String> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
JsonObject data = new JsonObject();
String value = callable.call();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
data.addProperty("value", value);
return data;
}
}
/**
* Represents a custom advanced pie.
*/
public static class AdvancedPie extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
JsonObject data = new JsonObject();
JsonObject values = new JsonObject();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.addProperty(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom drilldown pie.
*/
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObject getChartData() throws Exception {
JsonObject data = new JsonObject();
JsonObject values = new JsonObject();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObject value = new JsonObject();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
value.addProperty(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
values.add(entryValues.getKey(), value);
}
}
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom single line chart.
*/
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
JsonObject data = new JsonObject();
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
data.addProperty("value", value);
return data;
}
}
/**
* Represents a custom multi line chart.
*/
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
JsonObject data = new JsonObject();
JsonObject values = new JsonObject();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.addProperty(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom simple bar chart.
*/
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
JsonObject data = new JsonObject();
JsonObject values = new JsonObject();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
JsonArray categoryValues = new JsonArray();
categoryValues.add(entry.getValue());
values.add(entry.getKey(), categoryValues);
}
data.add("values", values);
return data;
}
}
/**
* Represents a custom advanced bar chart.
*/
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObject getChartData() throws Exception {
JsonObject data = new JsonObject();
JsonObject values = new JsonObject();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
continue; // Skip this invalid
}
allSkipped = false;
JsonArray categoryValues = new JsonArray();
for (int categoryValue : entry.getValue()) {
categoryValues.add(categoryValue);
}
values.add(entry.getKey(), categoryValues);
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.add("values", values);
return data;
}
}
}

View File

@@ -0,0 +1,265 @@
package wtf.beatrice.hubthat.utils.metrics;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import wtf.beatrice.hubthat.HubThat;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;
import wtf.beatrice.hubthat.utils.*;
import javax.net.ssl.HttpsURLConnection;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.logging.Level;
public class UpdateChecker implements Runnable
{
public static BukkitTask task;
private final Debugger debugger = new Debugger(getClass().getName());
public String newVersion, updateLink;
public ArrayList<String> updateText = new ArrayList<>(), warningMessage = new ArrayList<>();
public Boolean updateWarningBoolean, isNewVersionOut = false;
public Boolean isServerUnreachable = true;
public String errorCode;
private final HubThat plugin;
public UpdateChecker(HubThat givenPlugin)
{
plugin = givenPlugin;
}
private String servicesUrl = "https://services.mind-overflow.net/";
private String hubthatUrl = "java/plugins/hubthat/updates/";
@Override
public void run()
{
CommandSender console = plugin.getServer().getConsoleSender();
updateText.clear();
warningMessage.clear();
try
{
// Open an HTTPS Connection to the MOWS (Mind Overflow Web Services)
HttpsURLConnection connection = (HttpsURLConnection) new URL(servicesUrl).openConnection();
connection.setRequestMethod("HEAD");
// Read the response code.
int responseCode = connection.getResponseCode();
// If it's not "OK"... Stop
if (responseCode != 200)
{
setAndSendErrorCode(responseCode + "", console);
return;
}
errorCode = null;
} // Exception means the server was unreachable. Stop
catch (IOException e)
{
sendUnreachableCode(console);
isServerUnreachable = true;
return;
}
isServerUnreachable = false;
// Now, try loading the JSON file...
InputStream jsonIS;
try
{
jsonIS = new URL( servicesUrl + hubthatUrl + "update.json").openStream();
} catch (IOException e)
{
setAndSendErrorCode("null json", console);
return;
}
// And parsing it...
JsonParser jsonParser = new JsonParser();
JsonObject json = (JsonObject)jsonParser.parse(new InputStreamReader(jsonIS, StandardCharsets.UTF_8));
// Close the input stream...
/*try {
jsonIS.close();
} catch (IOException ignored)
{
}*/
// Check if the "version" String is present...
newVersion = null;
if(json.get("version") == null)
{
setAndSendErrorCode("null version", console);
return;
}
// Store it.
newVersion = json.get("version").getAsString();
// Check if versions match... And stop
if(newVersion.equalsIgnoreCase(plugin.getDescription().getVersion()))
{
isNewVersionOut = false;
return;
}
// If we are here, it means a new version is out.
isNewVersionOut = true;
// Load the update link...
updateLink = null;
if(json.get("link") == null)
{
setAndSendErrorCode("null link", console);
return;
}
updateLink = json.get("link").getAsString();
// Load all the lines from the String Array...
if(json.get("text") == null)
{
setAndSendErrorCode("null text", console);
return;
}
JsonArray updateTextArray = json.get("text").getAsJsonArray();
for(JsonElement obj : updateTextArray)
{
String relatedString = obj.getAsString();
updateText.add(relatedString);
}
// Load the warning object...
if(json.get("warning") == null)
{
setAndSendErrorCode("null warning", console);
return;
}
JsonObject warning = json.get("warning").getAsJsonObject();
// See if the warning is enabled...
if(warning.get("enabled") == null)
{
setAndSendErrorCode("null warning boolean", console);
return;
}
updateWarningBoolean = warning.get("enabled").getAsBoolean();
// Load all the lines from the String Array...
if(warning.get("text") == null)
{
setAndSendErrorCode("null warning text", console);
return;
}
JsonArray warningTextArray = warning.get("text").getAsJsonArray();
for(JsonElement obj : warningTextArray)
{
String relatedString = obj.getAsString();
warningMessage.add(relatedString);
}
// And finally send the message!
sendUpdateMessages(console);
}
public void sendUnreachableCode(CommandSender sender)
{
String pluginName = plugin.getName();
MessageUtils.sendColorizedMessage(sender, "&7-----[&3 " + pluginName + " Updater &7]-----");
MessageUtils.sendColorizedMessage(sender, "&cWarning! Updates Server is unreachable.");
MessageUtils.sendColorizedMessage(sender, "&cTry fixing connectivity problems and reload " + pluginName + " with &3/" + pluginName.toLowerCase() + " reload&c!");
}
public void setAndSendErrorCode(String code, CommandSender sender)
{
isNewVersionOut = false;
errorCode = code;
sendErrorCode(sender);
}
public void sendErrorCode(CommandSender sender)
{
String pluginName = plugin.getName();
MessageUtils.sendColorizedMessage(sender, "&7-----[&3 " + pluginName + " Updater &7]-----");
MessageUtils.sendColorizedMessage(sender, "&cWarning! Updates Server returned error code: &4" + errorCode);
MessageUtils.sendColorizedMessage(sender, "&cPlease contact the developer (" + debugger.authorName + ") immediately.");
}
public void sendUpdateMessages(CommandSender sender)
{
String pluginName = plugin.getName();
MessageUtils.sendColorizedMessage(sender, "&7-----[&3 " + pluginName + " Updater &7]-----");
MessageUtils.sendColorizedMessage(sender, "&7A new version is out: &6" + newVersion);
MessageUtils.sendColorizedMessage(sender, "&7Download: &6" + updateLink);
for(String line : updateText)
{
MessageUtils.sendColorizedMessage(sender, line);
}
if(updateWarningBoolean)
{
for(String line : warningMessage)
{
MessageUtils.sendColorizedMessage(sender, line);
}
}
}
public void playerMessage(CommandSender player)
{
plugin.getServer().getScheduler().runTaskLater(plugin, () ->
{
// Stop this in case the sender is the console: it already receives intended messages.
if(!(player instanceof Player)) return;
// Check if the updater is enabled.
if(PluginCache.updateChecker)
{
debugger.sendDebugMessage(Level.INFO, "Update Checker is enabled!");
// Check if the player has permissions to get notifications about updates.
if(PermissionUtils.playerHasPermission(player, Permissions.GET_UPDATES_NOTIFICATIONS))
{
debugger.sendDebugMessage(Level.INFO, "Player has permissions to check updates.");
// Instantiate the update checker so we can access it.
if(isNewVersionOut)
{
sendUpdateMessages(player);
}
if(isServerUnreachable)
{sendUnreachableCode(player);
}
if(errorCode != null)
{
sendErrorCode(player);
}
}
}
}, 40);
}
}