feat: finish cacheé

This commit is contained in:
Nemo D'ACREMONT 2024-12-14 14:57:39 +01:00
parent 932e272cc5
commit 549a37039a
9 changed files with 201 additions and 79 deletions

13
Makefile Normal file
View File

@ -0,0 +1,13 @@
all: run
run:
./gradlew run --args="-l Bordeaux"
test:
./gradlew test
clean:
$(RM) *.cache.json
.PHONY: all run clean

View File

@ -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,6 +30,7 @@ public class Main {
return value;
}
}
public static void main(String[] args) throws IOException, IllegalArgumentException{
HashMap<String, Option> options = new HashMap<>();
List<String> 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");
}
}

View File

@ -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<WeatherData> getTemperatures(int days, String cityName) throws IOException {
if (!this.cache.needsUpdate(cityName, days))
return this.cache.get(cityName, days);
public ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws IOException {
JSONObject result = fetchWeather(days, new City(cityName));
ArrayList<WeatherData> weatherDatas = new ArrayList<>();
@ -93,8 +88,6 @@ public class OpenMeteo implements WeatherDataAPI {
);
}
this.cache.set(cityName, days, weatherDatas, Instant.now());
return weatherDatas;
}

View File

@ -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<WeatherData> getTemperatures(int days, String cityName) throws IOException {
if (!this.cache.needsUpdate(cityName, days))
return this.cache.get(cityName, days);
public ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws IOException {
JSONObject result = fetchWeather(days, new City(cityName));
ArrayList<WeatherData> weatherDatas = new ArrayList<>();
@ -124,8 +119,6 @@ public class OpenWeatherMap implements WeatherDataAPI {
);
}
this.cache.set(cityName, days, weatherDatas, Instant.now());
return weatherDatas;
}

View File

@ -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<WeatherData> getTemperatures(int days, String cityName) throws IOException {
if (!this.cache.needsUpdate(cityName, days))
return this.cache.get(cityName, days);
public ArrayList<WeatherData> fetchTemperatures(int days, String cityName) throws IOException {
JSONObject result = fetchWeather(days, cityName);
ArrayList<WeatherData> weatherDatas = new ArrayList<>();
@ -105,8 +100,6 @@ public class WeatherAPI implements WeatherDataAPI{
);
}
this.cache.set(cityName, days, weatherDatas, Instant.now());
return weatherDatas;
}

View File

@ -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<WeatherData> 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<WeatherData> 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<WeatherData> getTemperatures(int days, String cityName) throws IOException
{
ArrayList<WeatherData> 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);
}
}
}

View File

@ -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);
}
}

View File

@ -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<WeatherData> value;
private static class CacheValue {
private WeatherData value;
private int day;
private Instant timestamp;
CacheValue(ArrayList<WeatherData> value, Instant timestamp) {
CacheValue(WeatherData value, int day, Instant timestamp) {
this.value = value;
this.timestamp = timestamp;
this.day = day;
}
public ArrayList<WeatherData> 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<WeatherData> value = new ArrayList<>();
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"));
int day = data.getInt("day");
String valueString = data.getString("value");
JSONObject value = new JSONObject(valueString);
return new CacheValue(value, timestamp);
return new CacheValue(
WeatherData.fromJSON(value),
day,
timestamp
);
}
}
private HashMap<String, CacheValue> 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<WeatherData> get(String cityName, int days) {
CacheKey cacheKey = new CacheKey(cityName, days);
ArrayList<WeatherData> 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<WeatherData> 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);
}
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);
}
}
}
}

View File

@ -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");