From 549a37039a45bc85002d631bfd7f502e2211cf76 Mon Sep 17 00:00:00 2001 From: Nemo D'ACREMONT Date: Sat, 14 Dec 2024 14:57:39 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20finish=20cache=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 13 ++ src/main/java/eirb/pg203/Main.java | 28 +++-- src/main/java/eirb/pg203/OpenMeteo.java | 11 +- src/main/java/eirb/pg203/OpenWeatherMap.java | 11 +- src/main/java/eirb/pg203/WeatherAPI.java | 11 +- .../java/eirb/pg203/WeatherCachedAPI.java | 76 +++++++++++- src/main/java/eirb/pg203/WeatherData.java | 14 ++- .../java/eirb/pg203/WeatherDataCache.java | 115 ++++++++++++------ .../java/eirb/pg203/utils/JSONFetcher.java | 1 + 9 files changed, 201 insertions(+), 79 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..88c599c --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ + +all: run + +run: + ./gradlew run --args="-l Bordeaux" + +test: + ./gradlew test + +clean: + $(RM) *.cache.json + +.PHONY: all run clean diff --git a/src/main/java/eirb/pg203/Main.java b/src/main/java/eirb/pg203/Main.java index 1c0574c..dfd4a0b 100644 --- a/src/main/java/eirb/pg203/Main.java +++ b/src/main/java/eirb/pg203/Main.java @@ -1,10 +1,13 @@ package eirb.pg203; +import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Scanner; public class Main { private static class Option { @@ -27,7 +30,8 @@ public class Main { return value; } } - public static void main(String[] args) throws IOException, IllegalArgumentException{ + + public static void main(String[] args) throws IOException, IllegalArgumentException{ HashMap options = new HashMap<>(); List argsList = new ArrayList<>(); @@ -60,21 +64,25 @@ public class Main { WeatherAPI weatherAPI = new WeatherAPI(WeatherAPIKey); OpenMeteo openMeteo = new OpenMeteo(); OpenWeatherMap openWeatherMap = new OpenWeatherMap(OpenWMapKey); - WeatherDisplay display = new WeatherDisplayBasic(); - WeatherDisplay display2 = new WeatherDisplayBasic(); - WeatherDisplay display3 = new WeatherDisplayBasic(); - display.addAPI(weatherAPI); + weatherAPI.loadCacheFromFile("wapi.cache.json"); + openWeatherMap.loadCacheFromFile("owm.cache.json"); + openMeteo.loadCacheFromFile("om.cache.json"); + + System.out.println(weatherAPI.toJSON().toString()); + + WeatherDisplay display = new WeatherDisplayBasic(); + display.addAPI(weatherAPI); display.addAPI(openMeteo); display.addAPI(openWeatherMap); - display2.addAPI(weatherAPI); - display3.addAPI(weatherAPI); // weatherAPI can't fetch for more than 3 days with free plan display.display(days, city); - display2.display(days, city); - display3.display(days, city); - } + + weatherAPI.saveCacheToFile("wapi.cache.json"); + openWeatherMap.saveCacheToFile("owm.cache.json"); + openMeteo.saveCacheToFile("om.cache.json"); + } } diff --git a/src/main/java/eirb/pg203/OpenMeteo.java b/src/main/java/eirb/pg203/OpenMeteo.java index 6d45992..f764e12 100644 --- a/src/main/java/eirb/pg203/OpenMeteo.java +++ b/src/main/java/eirb/pg203/OpenMeteo.java @@ -14,10 +14,9 @@ import eirb.pg203.WeatherData.Condition; // https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m -public class OpenMeteo implements WeatherDataAPI { +public class OpenMeteo extends WeatherCachedAPI { private static final String forecastBaseURL = "https://api.open-meteo.com/v1/forecast"; private static final String dailyQuery = "weather_code,temperature_2m_max,temperature_2m_min,wind_speed_10m_max,wind_direction_10m_dominant"; - private final WeatherDataCache cache = new WeatherDataCache(); // https://www.nodc.noaa.gov/archive/arc0021/0002199/1.1/data/0-data/HTML/WMO-CODE/WMO4677.HTM private JSONObject fetchWeather(int days, City city) throws IOException { @@ -79,11 +78,7 @@ public class OpenMeteo implements WeatherDataAPI { return getTemperature(day, cityName); } - @Override - public ArrayList getTemperatures(int days, String cityName) throws IOException { - if (!this.cache.needsUpdate(cityName, days)) - return this.cache.get(cityName, days); - + public ArrayList fetchTemperatures(int days, String cityName) throws IOException { JSONObject result = fetchWeather(days, new City(cityName)); ArrayList weatherDatas = new ArrayList<>(); @@ -93,8 +88,6 @@ public class OpenMeteo implements WeatherDataAPI { ); } - this.cache.set(cityName, days, weatherDatas, Instant.now()); - return weatherDatas; } diff --git a/src/main/java/eirb/pg203/OpenWeatherMap.java b/src/main/java/eirb/pg203/OpenWeatherMap.java index fa4f520..f6faa95 100644 --- a/src/main/java/eirb/pg203/OpenWeatherMap.java +++ b/src/main/java/eirb/pg203/OpenWeatherMap.java @@ -16,10 +16,9 @@ import eirb.pg203.WeatherData.Condition; // https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m -public class OpenWeatherMap implements WeatherDataAPI { +public class OpenWeatherMap extends WeatherCachedAPI { private static final String forecastBaseURL = "https://api.openweathermap.org/data/2.5/forecast"; private String APIKey; - private final WeatherDataCache cache = new WeatherDataCache(); OpenWeatherMap(String APIKey) { this.APIKey = APIKey; @@ -110,11 +109,7 @@ public class OpenWeatherMap implements WeatherDataAPI { return getTemperature(day, cityname); } - @Override - public ArrayList getTemperatures(int days, String cityName) throws IOException { - if (!this.cache.needsUpdate(cityName, days)) - return this.cache.get(cityName, days); - + public ArrayList fetchTemperatures(int days, String cityName) throws IOException { JSONObject result = fetchWeather(days, new City(cityName)); ArrayList weatherDatas = new ArrayList<>(); @@ -124,8 +119,6 @@ public class OpenWeatherMap implements WeatherDataAPI { ); } - this.cache.set(cityName, days, weatherDatas, Instant.now()); - return weatherDatas; } diff --git a/src/main/java/eirb/pg203/WeatherAPI.java b/src/main/java/eirb/pg203/WeatherAPI.java index 8ea17a4..0bd0ed9 100644 --- a/src/main/java/eirb/pg203/WeatherAPI.java +++ b/src/main/java/eirb/pg203/WeatherAPI.java @@ -15,10 +15,9 @@ import java.util.ArrayList; /** * WeatherAPI implementation */ -public class WeatherAPI implements WeatherDataAPI{ +public class WeatherAPI extends WeatherCachedAPI { private final String weatherAPIKey; private static final String forecastBaseURL = "https://api.weatherapi.com/v1/forecast.json"; - private final WeatherDataCache cache = new WeatherDataCache(); WeatherAPI(String weatherAPIKey) { this.weatherAPIKey = weatherAPIKey; @@ -91,11 +90,7 @@ public class WeatherAPI implements WeatherDataAPI{ return getTemperature(day, cityName); } - @Override - public ArrayList getTemperatures(int days, String cityName) throws IOException { - if (!this.cache.needsUpdate(cityName, days)) - return this.cache.get(cityName, days); - + public ArrayList fetchTemperatures(int days, String cityName) throws IOException { JSONObject result = fetchWeather(days, cityName); ArrayList weatherDatas = new ArrayList<>(); @@ -105,8 +100,6 @@ public class WeatherAPI implements WeatherDataAPI{ ); } - this.cache.set(cityName, days, weatherDatas, Instant.now()); - return weatherDatas; } diff --git a/src/main/java/eirb/pg203/WeatherCachedAPI.java b/src/main/java/eirb/pg203/WeatherCachedAPI.java index 627308e..ce85d79 100644 --- a/src/main/java/eirb/pg203/WeatherCachedAPI.java +++ b/src/main/java/eirb/pg203/WeatherCachedAPI.java @@ -1,10 +1,82 @@ package eirb.pg203; -import org.json.JSONObject; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Scanner; + +import org.json.JSONArray; abstract class WeatherCachedAPI implements WeatherDataAPI { private final WeatherDataCache cache = new WeatherDataCache(); - public void loadCache(JSONObject cache) + abstract ArrayList fetchTemperatures(int days, String cityName) throws IOException; + public void loadCache(JSONArray data) { + this.cache.fromJSON(data, this.getAPIName()); + } + + private void updateCache(int days, String cityName) throws IOException { + ArrayList data = fetchTemperatures(days, cityName); + Instant timestamp = Instant.now(); + + for (int i = 0 ; i < days ; ++i) + this.cache.set(cityName, i, data.get(i), timestamp); + } + + public ArrayList getTemperatures(int days, String cityName) throws IOException + { + ArrayList result = new ArrayList<>(); + + for (int i = 0 ; i < days ; ++i) + { + if (this.cache.needsUpdate(cityName, i)) + { + updateCache(days, cityName); + } + } + + for (int i = 0 ; i < days ; ++i) + result.add(this.cache.get(cityName, i)); + + return result; + } + + public JSONArray toJSON() { + return this.cache.toJSON(this.getAPIName()); + } + + public void loadCacheFromFile(String path) + { + try { + File file = new File(path); + Scanner scanner = new Scanner(file); + StringBuilder data = new StringBuilder(); + + while (scanner.hasNextLine()) { + data.append(scanner.nextLine()); + } + scanner.close(); + + this.loadCache(new JSONArray(data.toString())); + } catch (FileNotFoundException e) { + System.err.println("An error occurred."); + } + + } + + public void saveCacheToFile(String path) + { + try { + FileWriter myWriter = new FileWriter(path); + myWriter.write(this.toJSON().toString()); + myWriter.close(); + } catch (IOException e) { + System.out.println("An error occurred while saving cache to " + path); + } + } } diff --git a/src/main/java/eirb/pg203/WeatherData.java b/src/main/java/eirb/pg203/WeatherData.java index 29b806c..03762f7 100644 --- a/src/main/java/eirb/pg203/WeatherData.java +++ b/src/main/java/eirb/pg203/WeatherData.java @@ -190,18 +190,24 @@ class WeatherData { JSONObject jsonObject = new JSONObject(); jsonObject.put("city", city.getCityName()); - jsonObject.put("date", date.toString()); + jsonObject.put("date", date.toEpochMilli()); jsonObject.put("temp", temp); jsonObject.put("condition", condition.toString()); jsonObject.put("windSpeed", windSpeed); jsonObject.put("windDirectionAngle", windDirectionAngle); jsonObject.put("windDirection", windDirection.toString()); - return null; + return jsonObject; } public static WeatherData fromJSON(JSONObject data) { - return null; - } + City city = new City(data.getString("city")); + Instant date = Instant.ofEpochMilli(data.getLong("date")); + float temp = data.getFloat("temp"); + float windSpeed = data.getFloat("windSpeed"); + float windDirectionAngle = data.getFloat("windDirectionAngle"); + Condition condition = Condition.fromString(data.getString("windDirection")); + return new WeatherData(city, date, temp, windSpeed, windDirectionAngle, condition); + } } diff --git a/src/main/java/eirb/pg203/WeatherDataCache.java b/src/main/java/eirb/pg203/WeatherDataCache.java index e61fb8d..89d306c 100644 --- a/src/main/java/eirb/pg203/WeatherDataCache.java +++ b/src/main/java/eirb/pg203/WeatherDataCache.java @@ -3,21 +3,24 @@ package eirb.pg203; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; +import java.util.Locale; import org.json.JSONArray; import org.json.JSONObject; public class WeatherDataCache { - static class CacheValue { - private ArrayList value; + private static class CacheValue { + private WeatherData value; + private int day; private Instant timestamp; - CacheValue(ArrayList value, Instant timestamp) { + CacheValue(WeatherData value, int day, Instant timestamp) { this.value = value; - this.timestamp = timestamp; + this.timestamp = timestamp; + this.day = day; } - public ArrayList getWeatherData() { + public WeatherData getWeatherData() { return value; } @@ -28,67 +31,107 @@ public class WeatherDataCache { /* * Will parse CacheValues with { "api": apiName } from JSON */ - JSONObject toJSON(String apiName, String cityName) { + JSONObject toJSON(String apiName, String key) { JSONObject jsonObject = new JSONObject(); - JSONArray values = new JSONArray(); - jsonObject.put("city", cityName); - jsonObject.put("api", apiName); + jsonObject.put("key", key); + jsonObject.put("apiName", apiName); + jsonObject.put("day", day); jsonObject.put("timestamp", this.timestamp.getEpochSecond()); - for (int i = 0 ; i < this.value.size() ; ++i) - values.put(this.value.get(i)); - - jsonObject.put("value", values); + jsonObject.put("value", this.value.toJSON().toString()); return jsonObject; } public static CacheValue fromJSON(JSONObject data) { - ArrayList value = new ArrayList<>(); + Instant timestamp = Instant.ofEpochSecond(data.getLong("timestamp")); + int day = data.getInt("day"); + String valueString = data.getString("value"); + JSONObject value = new JSONObject(valueString); - JSONArray values = data.getJSONArray("value"); - for (int i = 0 ; i < values.length() ; ++i) - value.add(WeatherData.fromJSON(values.getJSONObject(i))); - - Instant timestamp = Instant.ofEpochSecond(data.getLong("timestamp")); - - return new CacheValue(value, timestamp); + return new CacheValue( + WeatherData.fromJSON(value), + day, + timestamp + ); } } private HashMap cache = new HashMap<>(); - private long cacheTTL = 3600; // Cache data Time To Live + private long cacheTTL = 3600; // Cache data Time To Live in sec - public boolean has(String cityName, int days) { - CacheValue cacheValue = this.cache.get(cityName); + private String makeKey(String cityName, int day) { + return String.format(Locale.ENGLISH, "%s%d", cityName, day); + } + + /* + * Returns true the cache has the key (cityName, days) + */ + public boolean has(String cityName, int day) { + CacheValue cacheValue = this.cache.get(makeKey(cityName, day)); return cacheValue != null; } - public boolean needsUpdate(String cityName, int days) { - if (!has(cityName, days)) // if (cityName, days) isn't cached, needs udpate + /* + * Returns true if (cityName, day) needs to be udpated in the cache + */ + public boolean needsUpdate(String cityName, int day) { + if (!has(cityName, day)) // if (cityName, day) isn't cached, needs udpate return true; - CacheKey cacheKey = new CacheKey(cityName, days); - CacheValue cacheValue = this.cache.get(cacheKey); + CacheValue cacheValue = this.cache.get(makeKey(cityName, day)); long dt = Instant.now().getEpochSecond() - cacheValue.getTimestamp().getEpochSecond(); return dt > this.cacheTTL; // if older than TTL, needs update } - public ArrayList get(String cityName, int days) { - CacheKey cacheKey = new CacheKey(cityName, days); - ArrayList weatherData = this.cache.get(cacheKey).getWeatherData(); + /* + * Get values from the cache + */ + public WeatherData get(String cityName, int day) { + String cacheKey = makeKey(cityName, day); + CacheValue weatherDatas = this.cache.get(cacheKey); - return weatherData; + if (weatherDatas == null) + return null; + + return weatherDatas.getWeatherData(); } - public void set(String cityName, int days, ArrayList value, Instant timestamp) { - CacheKey cacheKey = new CacheKey(cityName, days); - CacheValue cacheValue = new CacheValue(value, timestamp); + /* + * Set a value in the cache + */ + public void set(String cityName, int day, WeatherData value, Instant timestamp) { + String cacheKey = makeKey(cityName, day); + CacheValue cacheValue = new CacheValue(value, day, timestamp); - this.cache.put(cacheKey, cacheValue); + this.cache.put(cacheKey, cacheValue); + } + + public JSONArray toJSON(String apiName) { + JSONArray out = new JSONArray(); + + for (String key: this.cache.keySet()) { + CacheValue cacheValue = this.cache.get(key); + out.put(cacheValue.toJSON(apiName, key)); + } + + return out; + } + + public void fromJSON(JSONArray data, String apiName) { + for (int i = 0 ; i < data.length() ; ++i) { + JSONObject entry = data.getJSONObject(i); + + if (entry.getString("apiName").equals(apiName)) { + String key = entry.getString("key"); + + CacheValue cacheValue = CacheValue.fromJSON(entry); + this.cache.put(key, cacheValue); + } + } } } diff --git a/src/main/java/eirb/pg203/utils/JSONFetcher.java b/src/main/java/eirb/pg203/utils/JSONFetcher.java index 54e108e..b0a2c15 100644 --- a/src/main/java/eirb/pg203/utils/JSONFetcher.java +++ b/src/main/java/eirb/pg203/utils/JSONFetcher.java @@ -12,6 +12,7 @@ import org.json.JSONObject; public class JSONFetcher { private static String fetchString(URL url) throws IOException{ + System.err.println("Requesting " + url); StringBuilder result = new StringBuilder(); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET");