diff --git a/TODO.md b/TODO.md index 74ccc5e..2682303 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,8 @@ +Current: +- weather forecast https://openweathermap.org/forecast5 +- multiple conditions +- on join event + Milestone: yesterday - fix bugs @@ -8,6 +13,7 @@ Milestone: 0.5.1 Milestone: 0.6.0 - account for real sun movement, not just time +- release / debug separate versions Milestone: future - weather simulator (weather is clientside rn and doesnt have things such as lightning or other effects) diff --git a/pom.xml b/pom.xml index 0a1bb26..3c2ca4e 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 pl.minecon724 realweather - 0.5.0.1 + 0.5.1-DEV 17 @@ -24,15 +24,9 @@ provided - org.json - json - 20231013 - provided - - - com.maxmind.geoip2 - geoip2 - 4.2.0 + com.google.code.gson + gson + 2.10.1 provided diff --git a/src/main/java/pl/minecon724/realweather/RW.java b/src/main/java/pl/minecon724/realweather/RealWeatherPlugin.java similarity index 51% rename from src/main/java/pl/minecon724/realweather/RW.java rename to src/main/java/pl/minecon724/realweather/RealWeatherPlugin.java index 0c19e40..d9efff9 100644 --- a/src/main/java/pl/minecon724/realweather/RW.java +++ b/src/main/java/pl/minecon724/realweather/RealWeatherPlugin.java @@ -1,35 +1,46 @@ package pl.minecon724.realweather; -import com.maxmind.geoip2.WebServiceClient; +import java.io.IOException; +import java.util.logging.Logger; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.java.JavaPlugin; import pl.minecon724.realweather.map.WorldMap; import pl.minecon724.realweather.realtime.RealTimeCommander; import pl.minecon724.realweather.weather.WeatherCommander; -import pl.minecon724.realweather.weather.exceptions.DisabledException; +import pl.minecon724.realweather.weather.exceptions.ModuleDisabledException; -public class RW extends JavaPlugin { - FileConfiguration config; +public class RealWeatherPlugin extends JavaPlugin { - WebServiceClient client = null; + private final Logger logger = getLogger(); + + private FileConfiguration config; @Override public void onEnable() { - long start = System.currentTimeMillis(); - + saveDefaultConfig(); config = getConfig(); SubLogger.init( - getLogger(), + logger, config.getBoolean("logging", false) ); - WorldMap.init( - config.getConfigurationSection("map") - ); + ConfigurationSection mapConfigurationSection = config.getConfigurationSection("map"); + + try { + WorldMap.init( + mapConfigurationSection, + getDataFolder() + ); + } catch (IOException e) { + logger.severe("Unable to initialize WorldMap:"); + e.printStackTrace(); + getServer().getPluginManager().disablePlugin(this); + } WeatherCommander weatherCommander = new WeatherCommander(this); try { @@ -37,9 +48,10 @@ public class RW extends JavaPlugin { config.getConfigurationSection("weather") ); weatherCommander.start(); - } catch (DisabledException e) { - getLogger().info("Weather module disabled"); + } catch (ModuleDisabledException e) { + logger.info("Weather is disabled by user"); } catch (IllegalArgumentException e) { + logger.severe("Couldn't initialize weather provider:"); e.printStackTrace(); getServer().getPluginManager().disablePlugin(this); } @@ -50,11 +62,9 @@ public class RW extends JavaPlugin { config.getConfigurationSection("time") ); realTimeCommander.start(); - } catch (DisabledException e) { - getLogger().info("Time module disabled"); + } catch (ModuleDisabledException e) { + logger.info("Time is disabled by user"); } - long end = System.currentTimeMillis(); - this.getLogger().info( String.format( this.getName() + " enabled! (%s ms)", Long.toString( end-start ) ) ); } } diff --git a/src/main/java/pl/minecon724/realweather/SubLogger.java b/src/main/java/pl/minecon724/realweather/SubLogger.java index 7c7887a..eca3833 100644 --- a/src/main/java/pl/minecon724/realweather/SubLogger.java +++ b/src/main/java/pl/minecon724/realweather/SubLogger.java @@ -4,30 +4,84 @@ import java.util.logging.Level; import java.util.logging.Logger; public class SubLogger { + // TODO TODO too many static private static Logger LOGGER; private static boolean ENABLED; private String name; + /** + * Initialize the SubLogger + * @param logger parent logger, usually JavaPlugin#getLogger() + * @param enabled is logging enabled + */ static void init(Logger logger, boolean enabled) { LOGGER = logger; ENABLED = enabled; } + /** + * Instantiate a SubLogger instance + * @param name name, it will be prefixing messages + */ public SubLogger(String name) { this.name = name; } + /** + * Log a message + * @param level + * @param format message, formatted like {@link String#format(String, Object...)} + * @param args args for formatting + */ public void log(Level level, String format, Object... args) { if (!ENABLED) return; Object[] combinedArgs = new Object[args.length + 1]; combinedArgs[0] = name; System.arraycopy(args, 0, combinedArgs, 1, args.length); - + LOGGER.log(level, String.format("[%s] " + format, combinedArgs)); } + /** + * Log an info message + * see {@link SubLogger#log(Level, String, Object...)} + * @param format message + * @param args args + */ public void info(String format, Object... args) { this.log(Level.INFO, format, args); } + + public void info(String message) { + this.log(Level.INFO, message, new Object[0]); + } + + /** + * Log a severe message + * see {@link SubLogger#log(Level, String, Object...)} + * @param format message + * @param args args + */ + public void severe(String format, Object... args) { + this.log(Level.SEVERE, format, args); + } + + public void severe(String message) { + this.log(Level.SEVERE, message, new Object[0]); + } + + /** + * Log a warning message + * see {@link SubLogger#log(Level, String, Object...)} + * @param format message + * @param args args + */ + public void warning(String format, Object... args) { + this.log(Level.WARNING, format, args); + } + + public void warning(String message) { + this.log(Level.WARNING, message, new Object[0]); + } } diff --git a/src/main/java/pl/minecon724/realweather/geoip/DatabaseDownloader.java b/src/main/java/pl/minecon724/realweather/geoip/DatabaseDownloader.java new file mode 100644 index 0000000..187b3a0 --- /dev/null +++ b/src/main/java/pl/minecon724/realweather/geoip/DatabaseDownloader.java @@ -0,0 +1,51 @@ +package pl.minecon724.realweather.geoip; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import pl.minecon724.realweather.SubLogger; + +public class DatabaseDownloader { + private SubLogger subLogger = new SubLogger("download"); + private URL downloadUrl; + + public DatabaseDownloader(URL downloadUrl) { + this.downloadUrl = downloadUrl; + } + + // TODO verify + public void download(File file, boolean ipv6) throws IOException { + URL url = new URL(downloadUrl, ipv6 ? "ipv6.geo.gz" : "ipv4.geo.gz"); + + URLConnection connection = url.openConnection(); + connection.connect(); + InputStream inputStream = connection.getInputStream(); + ReadableByteChannel readableByteChannel = Channels.newChannel(inputStream); + + file.getParentFile().mkdirs(); + file.createNewFile(); + + FileOutputStream fileOutputStream = new FileOutputStream(file); + FileChannel fileChannel = fileOutputStream.getChannel(); + + long size = connection.getHeaderFieldLong("Content-Length", 0); + long position = 0; + + while (position < size) { + position += fileChannel.transferFrom(readableByteChannel, position, 1048576); + subLogger.info("%d%%", (int)(1.0 * position / size * 100)); + } + + + fileChannel.close(); + fileOutputStream.close(); + readableByteChannel.close(); + inputStream.close(); // ok + } +} diff --git a/src/main/java/pl/minecon724/realweather/geoip/GeoIPDatabase.java b/src/main/java/pl/minecon724/realweather/geoip/GeoIPDatabase.java new file mode 100644 index 0000000..801018a --- /dev/null +++ b/src/main/java/pl/minecon724/realweather/geoip/GeoIPDatabase.java @@ -0,0 +1,96 @@ +package pl.minecon724.realweather.geoip; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.zip.GZIPInputStream; + +import com.google.common.primitives.Ints; +import com.google.common.primitives.Shorts; + +import pl.minecon724.realweather.map.Coordinates; + +public class GeoIPDatabase { + private byte formatVersion; + private long timestamp; + public HashMap entries = new HashMap<>(); + + public void read(File file) throws IOException, FileNotFoundException { + if (!file.exists()) { + throw new FileNotFoundException(); + } + + if (timestamp != 0) { + throw new IOException("GeoIPDatabase can only be read once"); + } + + FileInputStream fileInputStream = new FileInputStream(file); + GZIPInputStream gzipInputStream = new GZIPInputStream(fileInputStream); + + formatVersion = (byte) gzipInputStream.read(); + byte[] bytes = gzipInputStream.readNBytes(2); + timestamp = recoverTime(bytes); + + byte[] address; + Coordinates coordinates; + + while (true) { // TODO true? + System.out.println(gzipInputStream.available()); + address = gzipInputStream.readNBytes(4); + if (address.length == 0) + break; + + coordinates = recoverCoordinates(gzipInputStream.readNBytes(6)); + + entries.put(IPUtils.toInt(address), coordinates); + } + + gzipInputStream.close(); + fileInputStream.close(); + } + + public String getTimestamp() { + return new SimpleDateFormat("dd.MM.yyyy HH:mm:ss") + .format(new Date(timestamp * 1000)); + } + + public byte getFormatVersion() { + return formatVersion; + } + + /** + * 2 bytes to 4 bytes wow magic + * @param bytes 2 bytes + * @return + */ + @SuppressWarnings("null") // TODO better way of this? + private long recoverTime(byte[] bytes) { + long timestamp = 1704067200; // first second of 2024 + timestamp += Shorts.fromByteArray(bytes) * 60 * 10; + return timestamp; + } + + /** + * Encoded to Coordinates + * @param bytes 3 bytes + * @return decoded Coordinates + */ + private Coordinates recoverCoordinates(byte[] bytes) { + int skewedLatitude = Ints.fromBytes( + (byte)0, bytes[0], bytes[1], bytes[2] + ); + + int skewedLongitude = Ints.fromBytes( + (byte)0, bytes[3], bytes[4], bytes[5] + ); + + double latitude = (skewedLatitude - 900000) / 10000.0; + double longitude = (skewedLongitude - 1800000) / 10000.0; + + return new Coordinates(latitude, longitude); + } +} diff --git a/src/main/java/pl/minecon724/realweather/geoip/IPUtils.java b/src/main/java/pl/minecon724/realweather/geoip/IPUtils.java new file mode 100644 index 0000000..49d7ade --- /dev/null +++ b/src/main/java/pl/minecon724/realweather/geoip/IPUtils.java @@ -0,0 +1,32 @@ +package pl.minecon724.realweather.geoip; + +import com.google.common.primitives.Ints; + +public class IPUtils { + public static byte[] getSubnetStart(byte[] addressBytes, byte subnet) { + int address = toInt(addressBytes); + int mask = 0xFFFFFFFF << (32 - subnet); + + return fromInt(address & mask); + } + + @SuppressWarnings("null") + public static int toInt(byte[] address) { + return Ints.fromByteArray(address); + } + + public static byte[] fromInt(int value) { + return Ints.toByteArray(value); + } + + public static String toString(byte[] address) { + String s = ""; + + for (int i=0; i<4; i++) { + s += Integer.toString(address[i] & 0xFF); + if (i < 3) s += "."; + } + + return s; + } +} diff --git a/src/main/java/pl/minecon724/realweather/map/Coordinates.java b/src/main/java/pl/minecon724/realweather/map/Coordinates.java index 35cf22a..e73270a 100644 --- a/src/main/java/pl/minecon724/realweather/map/Coordinates.java +++ b/src/main/java/pl/minecon724/realweather/map/Coordinates.java @@ -1,7 +1,5 @@ package pl.minecon724.realweather.map; -import com.maxmind.geoip2.record.Location; - public class Coordinates { public double latitude, longitude; @@ -38,11 +36,4 @@ public class Coordinates { private static double wrapDouble(double min, double max, double val) { return min + (val - min) % (max - min); } - - public static Coordinates fromGeoIpLocation(Location location) { - return new Coordinates( - location.getLatitude(), - location.getLongitude() - ); - } } diff --git a/src/main/java/pl/minecon724/realweather/map/GeoLocator.java b/src/main/java/pl/minecon724/realweather/map/GeoLocator.java index 3b2736a..b6bd6e8 100644 --- a/src/main/java/pl/minecon724/realweather/map/GeoLocator.java +++ b/src/main/java/pl/minecon724/realweather/map/GeoLocator.java @@ -1,30 +1,48 @@ package pl.minecon724.realweather.map; +import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.InetAddress; +import java.net.URL; import java.util.HashMap; -import java.util.Map; - -import com.maxmind.geoip2.WebServiceClient; -import com.maxmind.geoip2.exception.GeoIp2Exception; +import pl.minecon724.realweather.SubLogger; +import pl.minecon724.realweather.geoip.DatabaseDownloader; +import pl.minecon724.realweather.geoip.GeoIPDatabase; +import pl.minecon724.realweather.geoip.IPUtils; import pl.minecon724.realweather.map.exceptions.GeoIPException; public class GeoLocator { private static GeoLocator INSTANCE = null; - private WebServiceClient client; - private Map cache; + private SubLogger subLogger = new SubLogger("geolocator"); + private GeoIPDatabase database; + private HashMap cache = new HashMap<>(); - public static void init(int accountId, String apiKey) { + public static void init(File databaseFile, String downloadUrl) throws IOException { INSTANCE = new GeoLocator( - new WebServiceClient.Builder(accountId, apiKey) - .host("geolite.info").build()); + new GeoIPDatabase()); + + INSTANCE.load(databaseFile, downloadUrl); } - public GeoLocator(WebServiceClient client) { - this.client = client; - this.cache = new HashMap<>(); + public GeoLocator(GeoIPDatabase database) { + this.database = database; + } + + public void load(File databaseFile, String downloadUrl) throws IOException { + subLogger.info("This product includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com"); + + try { + database.read(databaseFile); + } catch (FileNotFoundException e) { + new DatabaseDownloader(new URL(downloadUrl)) + .download(databaseFile, false); + database.read(databaseFile); + } + + subLogger.info("Database: %s", INSTANCE.database.getTimestamp()); } /** @@ -34,35 +52,37 @@ public class GeoLocator { * @throws GeoIp2Exception * @throws IOException */ - public static Coordinates getCoordinates(InetAddress address) + public static Coordinates getCoordinates(InetAddress inetAddress) throws GeoIPException { - GeoLocator instance = INSTANCE; - - Coordinates coordinates = null; - - coordinates = instance.lookup(address); + Coordinates coordinates = INSTANCE.cache.get(inetAddress); if (coordinates != null) return coordinates; - try { - coordinates = Coordinates.fromGeoIpLocation( - instance.client.city(address).getLocation() + byte[] address = inetAddress.getAddress(); + byte subnet = 32; + + while (coordinates == null) { + if (subnet == 0) { + INSTANCE.subLogger.info("Not found :("); + coordinates = new Coordinates(0, 0); + break; + } + + int query = IPUtils.toInt(address); + + coordinates = INSTANCE.database.entries.get( + query ); - } catch (IOException | GeoIp2Exception e) { - throw new GeoIPException(e.getMessage()); + + INSTANCE.subLogger.info("trying %s/%d = %d", IPUtils.toString(address), subnet, query); + + address = IPUtils.getSubnetStart(address, --subnet); } - instance.store(address, coordinates); + INSTANCE.subLogger.info("Done, caching"); + INSTANCE.cache.put(inetAddress, coordinates); return coordinates; } - - private Coordinates lookup(InetAddress address) { - return this.cache.get(address); - } - - private void store(InetAddress address, Coordinates coordinates) { - this.cache.put(address, coordinates); - } } diff --git a/src/main/java/pl/minecon724/realweather/map/WorldMap.java b/src/main/java/pl/minecon724/realweather/map/WorldMap.java index fd4411d..fb11c5f 100644 --- a/src/main/java/pl/minecon724/realweather/map/WorldMap.java +++ b/src/main/java/pl/minecon724/realweather/map/WorldMap.java @@ -1,5 +1,8 @@ package pl.minecon724.realweather.map; +import java.io.File; +import java.io.IOException; + import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; @@ -24,8 +27,8 @@ public class WorldMap { this.point = point; } - public static void init(ConfigurationSection config) - throws IllegalArgumentException { + public static void init(ConfigurationSection config, File dataFolder) + throws IOException { Type type; @@ -45,8 +48,8 @@ public class WorldMap { } else if (type == Type.PLAYER) { GeoLocator.init( - config.getInt("player.geolite2_accountId"), - config.getString("player.geolite2_api_key") + dataFolder.toPath().resolve("geoip/ipv4.geo.gz").toFile(), + config.getString("player.download_url", "https://inferior.network/geoip/") ); } else if (type == Type.GLOBE) { diff --git a/src/main/java/pl/minecon724/realweather/realtime/RealTimeCommander.java b/src/main/java/pl/minecon724/realweather/realtime/RealTimeCommander.java index 3cb4670..47d2786 100644 --- a/src/main/java/pl/minecon724/realweather/realtime/RealTimeCommander.java +++ b/src/main/java/pl/minecon724/realweather/realtime/RealTimeCommander.java @@ -14,11 +14,11 @@ import org.bukkit.event.Listener; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldUnloadEvent; -import pl.minecon724.realweather.RW; -import pl.minecon724.realweather.weather.exceptions.DisabledException; +import pl.minecon724.realweather.RealWeatherPlugin; +import pl.minecon724.realweather.weather.exceptions.ModuleDisabledException; public class RealTimeCommander implements Listener { - private RW plugin; + private RealWeatherPlugin plugin; private List worldNames; private double scale; @@ -31,15 +31,15 @@ public class RealTimeCommander implements Listener { private RealTimeTask task; private PlayerTimeSyncTask playerTimeSyncTask; - public RealTimeCommander(RW plugin) { + public RealTimeCommander(RealWeatherPlugin plugin) { this.plugin = plugin; } public void init(ConfigurationSection config) - throws DisabledException { + throws ModuleDisabledException { if (!config.getBoolean("enabled")) - throw new DisabledException(); + throw new ModuleDisabledException(); try { timezone = ZoneId.of(config.getString("timezone")); diff --git a/src/main/java/pl/minecon724/realweather/weather/GetStateTask.java b/src/main/java/pl/minecon724/realweather/weather/GetStateTask.java index 1cf3e3a..292cb4a 100644 --- a/src/main/java/pl/minecon724/realweather/weather/GetStateTask.java +++ b/src/main/java/pl/minecon724/realweather/weather/GetStateTask.java @@ -1,6 +1,5 @@ package pl.minecon724.realweather.weather; -import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -9,7 +8,7 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.scheduler.BukkitRunnable; -import pl.minecon724.realweather.RW; +import pl.minecon724.realweather.RealWeatherPlugin; import pl.minecon724.realweather.SubLogger; import pl.minecon724.realweather.map.Coordinates; import pl.minecon724.realweather.map.WorldMap; @@ -17,13 +16,14 @@ import pl.minecon724.realweather.map.WorldMap.Type; import pl.minecon724.realweather.map.exceptions.GeoIPException; import pl.minecon724.realweather.weather.WeatherState.State; import pl.minecon724.realweather.weather.events.WeatherSyncEvent; +import pl.minecon724.realweather.weather.exceptions.WeatherProviderException; import pl.minecon724.realweather.weather.provider.Provider; public class GetStateTask extends BukkitRunnable { private SubLogger subLogger = new SubLogger("weather updater"); - private RW plugin; + private RealWeatherPlugin plugin; private Provider provider; private WorldMap worldMap; @@ -32,7 +32,7 @@ public class GetStateTask extends BukkitRunnable { private PluginManager pluginManager = Bukkit.getPluginManager(); public GetStateTask( - RW plugin, + RealWeatherPlugin plugin, Provider provider, WorldMap worldMap ) { @@ -92,8 +92,9 @@ public class GetStateTask extends BukkitRunnable { } } } - } catch (IOException e) { - subLogger.info("Error updating: %s", e.getMessage()); + } catch (WeatherProviderException e) { + subLogger.info("Weather provider error"); + e.printStackTrace(); } } diff --git a/src/main/java/pl/minecon724/realweather/weather/WeatherChanger.java b/src/main/java/pl/minecon724/realweather/weather/WeatherChanger.java index d0f1a37..ec85818 100644 --- a/src/main/java/pl/minecon724/realweather/weather/WeatherChanger.java +++ b/src/main/java/pl/minecon724/realweather/weather/WeatherChanger.java @@ -26,7 +26,7 @@ public class WeatherChanger implements Listener { @EventHandler public void onWorldLoad(WorldLoadEvent event) { World world = event.getWorld(); - subLogger.info("World %s is loading", world.getName()); + subLogger.info("World %s has been loaded", world.getName()); if (worldNames.contains(world.getName())) { worlds.add(world); diff --git a/src/main/java/pl/minecon724/realweather/weather/WeatherCommander.java b/src/main/java/pl/minecon724/realweather/weather/WeatherCommander.java index f3a7e08..0004ff9 100644 --- a/src/main/java/pl/minecon724/realweather/weather/WeatherCommander.java +++ b/src/main/java/pl/minecon724/realweather/weather/WeatherCommander.java @@ -1,20 +1,20 @@ package pl.minecon724.realweather.weather; -import java.io.IOException; import java.util.List; import org.bukkit.configuration.ConfigurationSection; -import pl.minecon724.realweather.RW; +import pl.minecon724.realweather.RealWeatherPlugin; import pl.minecon724.realweather.SubLogger; import pl.minecon724.realweather.map.Coordinates; import pl.minecon724.realweather.map.WorldMap; -import pl.minecon724.realweather.weather.exceptions.DisabledException; +import pl.minecon724.realweather.weather.exceptions.ModuleDisabledException; +import pl.minecon724.realweather.weather.exceptions.WeatherProviderException; import pl.minecon724.realweather.weather.provider.Provider; public class WeatherCommander { private WorldMap worldMap = WorldMap.getInstance(); - private RW plugin; + private RealWeatherPlugin plugin; private boolean enabled; private List worldNames; @@ -26,23 +26,23 @@ public class WeatherCommander { private SubLogger subLogger = new SubLogger("weather"); - public WeatherCommander(RW plugin) { + public WeatherCommander(RealWeatherPlugin plugin) { this.plugin = plugin; } /** * Initialize weather commander * @param config "weather" ConfigurationSection - * @throws DisabledException if disabled in config + * @throws ModuleDisabledException if disabled in config * @throws ProviderException if invalid provider config */ public void init(ConfigurationSection config) - throws DisabledException, IllegalArgumentException { + throws ModuleDisabledException, IllegalArgumentException { enabled = config.getBoolean("enabled"); if (!enabled) - throw new DisabledException(); + throw new ModuleDisabledException(); worldNames = config.getStringList("worlds"); @@ -59,15 +59,15 @@ public class WeatherCommander { try { provider.request_state(new Coordinates(0, 0)); - } catch (IOException e) { - subLogger.info("Provider test failed, errors may occur", new Object[0]); + } catch (WeatherProviderException e) { + subLogger.severe("Provider test failed, errors may occur"); e.printStackTrace(); } plugin.getServer().getPluginManager().registerEvents( new WeatherChanger(worldNames), plugin); - subLogger.info("done", new Object[0]); + subLogger.info("done"); } public void start() { diff --git a/src/main/java/pl/minecon724/realweather/weather/exceptions/DisabledException.java b/src/main/java/pl/minecon724/realweather/weather/exceptions/ModuleDisabledException.java similarity index 52% rename from src/main/java/pl/minecon724/realweather/weather/exceptions/DisabledException.java rename to src/main/java/pl/minecon724/realweather/weather/exceptions/ModuleDisabledException.java index dde43be..ae7e475 100644 --- a/src/main/java/pl/minecon724/realweather/weather/exceptions/DisabledException.java +++ b/src/main/java/pl/minecon724/realweather/weather/exceptions/ModuleDisabledException.java @@ -1,5 +1,5 @@ package pl.minecon724.realweather.weather.exceptions; -public class DisabledException extends Exception { +public class ModuleDisabledException extends Exception { } diff --git a/src/main/java/pl/minecon724/realweather/weather/exceptions/WeatherProviderException.java b/src/main/java/pl/minecon724/realweather/weather/exceptions/WeatherProviderException.java new file mode 100644 index 0000000..9549eb6 --- /dev/null +++ b/src/main/java/pl/minecon724/realweather/weather/exceptions/WeatherProviderException.java @@ -0,0 +1,9 @@ +package pl.minecon724.realweather.weather.exceptions; + +public class WeatherProviderException extends Exception { + + public WeatherProviderException(String message) { + super(message); + } + +} diff --git a/src/main/java/pl/minecon724/realweather/weather/provider/OpenWeatherMapProvider.java b/src/main/java/pl/minecon724/realweather/weather/provider/OpenWeatherMapProvider.java index bcb3c1b..e476a77 100644 --- a/src/main/java/pl/minecon724/realweather/weather/provider/OpenWeatherMapProvider.java +++ b/src/main/java/pl/minecon724/realweather/weather/provider/OpenWeatherMapProvider.java @@ -1,18 +1,19 @@ package pl.minecon724.realweather.weather.provider; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; -import java.nio.charset.Charset; -import org.json.JSONException; -import org.json.JSONObject; +import com.google.common.base.Charsets; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonReader; import pl.minecon724.realweather.map.Coordinates; import pl.minecon724.realweather.weather.WeatherState.*; +import pl.minecon724.realweather.weather.exceptions.WeatherProviderException; public class OpenWeatherMapProvider implements Provider { @@ -23,6 +24,11 @@ public class OpenWeatherMapProvider implements Provider { public OpenWeatherMapProvider(String apiKey) { this.apiKey = apiKey; } + + @Override + public String getName() { + return "OpenWeatherMap"; + } public void init() { try { @@ -32,34 +38,40 @@ public class OpenWeatherMapProvider implements Provider { } } - public State request_state(Coordinates coordinates) throws IOException { - JSONObject json = new JSONObject(); + public State request_state(Coordinates coordinates) throws WeatherProviderException { + JsonObject jsonObject; try { URL url = new URL( - endpoint + String.format("/data/2.5/weather?lat=%s&lon=%s&appid=%s", - Double.toString(coordinates.latitude), Double.toString(coordinates.longitude), apiKey + String.format("%s/data/2.5/weather?lat=%f&lon=%f&appid=%s", + endpoint, coordinates.latitude, coordinates.longitude, apiKey )); InputStream is = url.openStream(); - BufferedReader rd = new BufferedReader( new InputStreamReader(is, Charset.forName("UTF-8")) ); - StringBuilder sb = new StringBuilder(); - int c; - while ((c = rd.read()) != -1) { - sb.append((char) c); - } - is.close(); - json = new JSONObject(sb.toString()); + BufferedReader rd = new BufferedReader( + new InputStreamReader(is, Charsets.UTF_8)); + + JsonReader jsonReader = new JsonReader(rd); + jsonObject = new Gson().fromJson(jsonReader, JsonObject.class); } catch (Exception e) { - throw new IOException("Couldn't contact openweathermap"); + e.printStackTrace(); + throw new WeatherProviderException("Couldn't contact openweathermap"); } int stateId; + try { - stateId = json.getJSONArray("weather") - .getJSONObject(0).getInt("id"); - } catch (JSONException e) { - throw new IOException("Invalid data from openweathermap"); + stateId = jsonObject.getAsJsonArray("weather") + .get(0).getAsJsonObject() + .get("id").getAsInt(); + /* + * org.json comparison: + * stateId = json.getJSONArray("weather").getJSONObject(0).getInt("id"); + * so is it truly worth it? yes see loading jsonobject from inputstream + */ + } catch (Exception e) { + e.printStackTrace(); + throw new WeatherProviderException("Invalid data from openweathermap"); } // Here comes the mess @@ -67,60 +79,44 @@ public class OpenWeatherMapProvider implements Provider { ConditionLevel level = ConditionLevel.LIGHT; if (stateId < 300) { condition = Condition.THUNDER; - switch (stateId) { - case 200: - case 210: - case 230: + switch (stateId % 10) { + case 0: // 200, 210, 230 level = ConditionLevel.LIGHT; break; - case 201: - case 211: - case 221: - case 231: + case 1: // 201, 211, 221, 231 level = ConditionLevel.MODERATE; break; - case 202: - case 212: - case 232: + case 2: // 202, 212, 232 level = ConditionLevel.HEAVY; } } else if (stateId < 400) { condition = Condition.DRIZZLE; - switch (stateId) { - case 300: - case 310: + switch (stateId % 10) { + case 0: // 300, 310 level = ConditionLevel.LIGHT; break; - case 301: - case 311: - case 313: - case 321: + case 1: // 301, 311, 321 + case 3: // 313 level = ConditionLevel.MODERATE; break; - case 302: - case 312: - case 314: + case 2: // 302, 312 + case 4: // 314 level = ConditionLevel.HEAVY; } } else if (stateId < 600) { condition = Condition.RAIN; - switch (stateId) { - case 500: - case 520: + switch (stateId % 10) { + case 0: // 500, 520 level = ConditionLevel.LIGHT; break; - case 501: - case 511: - case 521: - case 531: + case 1: // 501, 511, 521, 531 level = ConditionLevel.MODERATE; break; - case 502: - case 522: + case 2: // 502, 522 level = ConditionLevel.HEAVY; break; - case 503: - case 504: + case 3: // 503 + case 4: // 504 level = ConditionLevel.EXTREME; } } else if (stateId < 700) { @@ -165,7 +161,16 @@ public class OpenWeatherMapProvider implements Provider { } @Override - public String getName() { - return "OpenWeatherMap"; + public State[] request_state(Coordinates[] coordinates) throws WeatherProviderException { + // OpenWeatherMap doesnt support bulk requests + + int length = coordinates.length; + State[] states = new State[length]; + + for (int i=0; i