diff --git a/NEWS.md b/NEWS.md index abd1fad..50feb8d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added +- Dump/Restore most of model in application state + [#57](https://github.com/orontee/taranis/issues/57) + - Configuration to preserve device being offline [#57](https://github.com/orontee/taranis/issues/57) diff --git a/src/convert.cc b/src/convert.cc new file mode 100644 index 0000000..bdce0cf --- /dev/null +++ b/src/convert.cc @@ -0,0 +1,250 @@ +#include "convert.h" +#include "experimental/optional" +#include "model.h" + +namespace taranis { + +// NAN is written as null by jsoncpp, this will fix deserialization + +inline double deserialize_possible_null(const Json::Value &value) { + if (value.isNull()) { + return NAN; + } + return value.asDouble(); +} + +Json::Value to_json(const Location &location) { + Json::Value value; + value["longitude"] = location.longitude; + value["latitude"] = location.latitude; + value["name"] = location.name; + value["country"] = location.country; + value["state"] = location.state; + + return value; +} + +Json::Value to_json(const Condition &condition) { + Json::Value value; + value["date"] = static_cast(condition.date); + value["sunrise"] = static_cast(condition.sunrise); + value["sunset"] = static_cast(condition.sunset); + value["temperature"] = condition.temperature; + value["felt_temperature"] = condition.felt_temperature; + value["pressure"] = condition.pressure; + value["humidity"] = condition.humidity; + value["uv_index"] = condition.uv_index; + value["clouds"] = condition.clouds; + value["visibility"] = condition.visibility; + value["probability_of_precipitation"] = condition.probability_of_precipitation; + value["wind_speed"] = condition.wind_speed; + value["wind_degree"] = condition.wind_degree; + value["wind_gust"] = condition.wind_gust; + value["weather"] = condition.weather; + value["weather_description"] = condition.weather_description; + value["weather_icon_name"] = condition.weather_icon_name; + value["rain"] = condition.rain; + value["snow"] = condition.snow; + + return value; +} + +Json::Value to_json(const DailyCondition &condition) { + Json::Value value; + value["date"] = static_cast(condition.date); + value["sunrise"] = static_cast(condition.sunrise); + value["sunset"] = static_cast(condition.sunset); + value["moonrise"] = static_cast(condition.moonrise); + value["moonset"] = static_cast(condition.moonset); + value["moon_phase"] = condition.moon_phase; + value["pressure"] = condition.pressure; + value["humidity"] = condition.humidity; + value["dew_point"] = condition.dew_point; + value["wind_speed"] = condition.wind_speed; + value["wind_degree"] = condition.wind_degree; + value["wind_gust"] = condition.wind_gust; + value["clouds"] = condition.clouds; + value["probability_of_precipitation"] = condition.probability_of_precipitation; + value["uv_index"] = condition.uv_index; + value["rain"] = condition.rain; + value["snow"] = condition.snow; + value["weather"] = condition.weather; + value["weather_description"] = condition.weather_description; + value["weather_icon_name"] = condition.weather_icon_name; + value["temperature_day"] = condition.temperature_day; + value["temperature_min"] = condition.temperature_min; + value["temperature_max"] = condition.temperature_max; + value["temperature_night"] = condition.temperature_night; + value["temperature_evening"] = condition.temperature_evening; + value["temperature_morning"] = condition.temperature_morning; + value["felt_temperature_day"] = condition.felt_temperature_day; + value["felt_temperature_night"] = condition.felt_temperature_night; + value["felt_temperature_evening"] = condition.felt_temperature_evening; + value["felt_temperature_morning"] = condition.felt_temperature_morning; + + return value; +} + +Json::Value to_json(const Alert &alert) { + Json::Value value; + value["sender"] = alert.sender; + value["event"] = alert.event; + value["start_date"] = static_cast(alert.start_date); + value["end_date"] = static_cast(alert.end_date); + value["description"] = alert.description; + return value; +} + +Json::Value to_json(const HistoryItem &item) { + Json::Value value; + value["location"] = to_json(item.location); + value["favorite"] = item.favorite; + + return value; +} + +Json::Value to_json(const Model &model) { + Json::Value value; + value["source"] = model.source; + value["unit_system"] = model.unit_system; + value["refresh_date"] = static_cast(model.refresh_date); + value["location"] = to_json(model.location); + + if (model.current_condition) { + value["current_condition"] = to_json(*model.current_condition); + } else { + value["current_condition"] = Json::Value::null; + } + for (const auto &forecast : model.hourly_forecast) { + value["hourly_forecast"].append(to_json(forecast)); + } + for (const auto &forecast : model.daily_forecast) { + value["daily_forecast"].append(to_json(forecast)); + } + for (const auto &alert : model.alerts) { + value["alerts"].append(to_json(alert)); + } + for (const auto &item : model.location_history) { + value["location_history"].append(to_json(item)); + } + return value; +} + +void update_from_json(Location& location, const Json::Value &value) { + location.longitude = deserialize_possible_null(value["longitude"]); + location.latitude = deserialize_possible_null(value["latitude"]); + location.name = value.get("name", "").asString(); + location.country = value.get("country", "").asString(); + location.state = value.get("state", "").asString(); +} + +void update_from_json(Condition& condition, const Json::Value &value) { + condition.date = static_cast(value.get("date", 0).asLargestInt()); + condition.sunrise = static_cast(value.get("sunrise", 0).asLargestInt()); + condition.sunset = static_cast(value.get("sunset", 0).asLargestInt()); + condition.temperature = deserialize_possible_null(value["temperature"]); + condition.felt_temperature = deserialize_possible_null(value["felt_temperature"]); + condition.pressure = value.get("pressure", 0).asInt(); + condition.humidity = value.get("humidity", 0).asInt(); + condition.uv_index = deserialize_possible_null(value["uv_index"]); + condition.clouds = value.get("clouds", 0).asInt(); + condition.visibility = value.get("visibility", 0).asInt(); + condition.probability_of_precipitation = deserialize_possible_null(value["probability_of_precipitation"]); + condition.wind_speed = deserialize_possible_null(value["wind_speed"]); + condition.wind_degree = value.get("wind_degree", 0).asInt(); + condition.wind_gust = deserialize_possible_null(value["wind_gust"]); + condition.weather = static_cast(value.get("weather", CLEAR_SKY).asInt()); + condition.weather_description = value.get("weather_description", "").asString(); + condition.weather_icon_name = value.get("weather_icon_name", "").asString(); + condition.rain = deserialize_possible_null(value["rain"]); + condition.snow = deserialize_possible_null(value["snow"]); +} + +void update_from_json(DailyCondition& condition, const Json::Value &value) { + condition.date = static_cast(value.get("date", 0).asLargestInt()); + condition.sunrise = static_cast(value.get("sunrise", 0).asLargestInt()); + condition.sunset = static_cast(value.get("sunset", 0).asLargestInt()); + condition.moonrise = static_cast(value.get("moonrise", 0).asLargestInt()); + condition.moonset = static_cast(value.get("moonset", 0).asLargestInt()); + condition.moon_phase = deserialize_possible_null(value["moon_phase"]); + condition.pressure = value.get("pressure", 0).asInt(); + condition.humidity = value.get("humidity", 0).asInt(); + condition.dew_point = deserialize_possible_null(value["dew_point"]); + condition.wind_speed = deserialize_possible_null(value["wind_speed"]); + condition.wind_degree = value.get("wind_degree", 0).asInt(); + condition.wind_gust = deserialize_possible_null(value["wind_gust"]); + condition.clouds = value.get("clouds", 0).asInt(); + condition.probability_of_precipitation = deserialize_possible_null(value["probability_of_precipitation"]); + condition.uv_index = deserialize_possible_null(value["uv_index"]); + condition.rain = deserialize_possible_null(value["rain"]); + condition.snow = deserialize_possible_null(value["snow"]); + condition.weather = static_cast(value.get("weather", CLEAR_SKY).asInt()); + condition.weather_description = value.get("weather_description", "").asString(); + condition.weather_icon_name = value.get("weather_icon_name", "").asString(); + condition.temperature_day = deserialize_possible_null(value["temperature_day"]); + condition.temperature_min = deserialize_possible_null(value["temperature_min"]); + condition.temperature_max = deserialize_possible_null(value["temperature_max"]); + condition.temperature_night = deserialize_possible_null(value["temperature_night"]); + condition.temperature_evening = deserialize_possible_null(value["temperature_evening"]); + condition.temperature_morning = deserialize_possible_null(value["temperature_morning"]); + condition.felt_temperature_day = deserialize_possible_null(value["felt_temperature_day"]); + condition.felt_temperature_night = deserialize_possible_null(value["felt_temperature_night"]); + condition.felt_temperature_evening = deserialize_possible_null(value["felt_temperature_evening"]); + condition.felt_temperature_morning = deserialize_possible_null(value["felt_temperature_morning"]); +} + +void update_from_json(Alert& alert, const Json::Value &value) { + alert.sender = value.get("sender", "").asString(); + alert.event = value.get("event", "").asString(); + alert.start_date = static_cast(value.get("start_date", 0).asLargestInt()); + alert.end_date = static_cast(value.get("end_date", 0).asLargestInt()); + alert.description = value.get("description", "").asString(); +} + +void update_from_json(HistoryItem &item, const Json::Value &value) { + auto &location = item.location; + const auto &location_value = value["location"]; + update_from_json(location, location_value); + + item.favorite = value.get("favorite", false).asBool(); +} + +void update_from_json(Model &model, const Json::Value &value) { + model.source = value.get("source", "OpenWeather").asString(); + model.unit_system = static_cast(value.get("unit_system", standard).asInt()); + model.refresh_date = static_cast(value.get("refresh_date", 0).asLargestInt()); + + auto &location = model.location; + const auto &location_value = value["location"]; + update_from_json(location, location_value); + + if (value["current_condition"].isNull()) { + model.current_condition = std::experimental::nullopt; + } else { + Condition current_condition{}; + const auto ¤t_condition_value = value["current_condition"]; + update_from_json(current_condition, current_condition_value); + model.current_condition = current_condition; + } + for (const auto &condition_value : value["hourly_forecast"]) { + Condition condition{}; + update_from_json(condition, condition_value); + model.hourly_forecast.push_back(condition); + } + for (const auto &condition_value : value["daily_forecast"]) { + DailyCondition condition{}; + update_from_json(condition, condition_value); + model.daily_forecast.push_back(condition); + } + for (const auto &alert_value : value["alerts"]) { + Alert alert{}; + update_from_json(alert, alert_value); + model.alerts.push_back(alert); + } + for (const auto &item_value : value["location_history"]) { + HistoryItem item{}; + update_from_json(item, item_value); + model.location_history.push_back(item); + } +} +} diff --git a/src/convert.h b/src/convert.h new file mode 100644 index 0000000..881f3f9 --- /dev/null +++ b/src/convert.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "model.h" + +namespace taranis { + Json::Value to_json(const Location &location); + Json::Value to_json(const Condition &condition); + Json::Value to_json(const DailyCondition &condition); + Json::Value to_json(const Alert &alert); + Json::Value to_json(const HistoryItem &item); + Json::Value to_json(const Model &model); + + void update_from_json(Location& location, const Json::Value &value); + void update_from_json(Condition& condition, const Json::Value &value); + void update_from_json(DailyCondition& condition, const Json::Value &value); + void update_from_json(Alert& alert, const Json::Value &value); + void update_from_json(HistoryItem &item, const Json::Value &value); + void update_from_json(Model &model, const Json::Value &value); +} diff --git a/src/meson.build b/src/meson.build index d9589ea..faaacdf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,6 +22,7 @@ sources = [about_cc] + files( 'alerts.cc', 'app.cc', 'config.cc', + 'convert.cc', 'currentconditionbox.cc', 'events.cc', 'http.cc', diff --git a/src/model.h b/src/model.h index d640af1..5398597 100644 --- a/src/model.h +++ b/src/model.h @@ -180,11 +180,8 @@ struct Alert { }; struct HistoryItem { - const Location location; + Location location; bool favorite; - - HistoryItem(const Location &location, bool favorite) - : location{location}, favorite{favorite} {} }; struct Model { diff --git a/src/state.cc b/src/state.cc index 58e68d9..fd52a05 100644 --- a/src/state.cc +++ b/src/state.cc @@ -6,10 +6,12 @@ #include #include -constexpr char LOCATION_HISTORY_KEY[]{"location_history"}; +#include "convert.h" namespace taranis { +constexpr char MODEL_KEY[]{"model"}; + void ApplicationState::restore() { BOOST_LOG_TRIVIAL(debug) << "Restoring application state"; @@ -27,7 +29,7 @@ void ApplicationState::restore() { BOOST_LOG_TRIVIAL(error) << "Unexpected application state"; return; } - this->restore_location_history(root); + this->restore_model(root); } void ApplicationState::dump() { @@ -41,7 +43,7 @@ void ApplicationState::dump() { return; } Json::Value root; - this->dump_location_history(root); + this->dump_model(root); output << root << std::endl; if (not output) { @@ -63,50 +65,14 @@ std::string ApplicationState::get_application_state_path() { return application_state_path; } -void ApplicationState::restore_location_history(const Json::Value &root) { - this->model->location_history.clear(); - - for (const auto &value : root[LOCATION_HISTORY_KEY]) { - const auto location_value = value["location"]; - if (!location_value.isObject()) { - continue; - } - - const auto longitude{location_value.get("longitude", NAN).asDouble()}; - const auto latitude{location_value.get("latitude", NAN).asDouble()}; - const auto name{location_value.get("name", "").asString()}; - const auto country{location_value.get("country", "").asString()}; - const auto state{location_value.get("state", "").asString()}; - if (std::isnan(longitude) or std::isnan(latitude) or name.empty() or - country.empty()) { - continue; - } - const Location location{longitude, latitude, name, country, state}; - const HistoryItem item{location, value.get("favorite", false).asBool()}; - this->model->location_history.push_back(item); - } - if (this->model->location_history.size() > 0) { - this->model->location = this->model->location_history.begin()->location; - } +void ApplicationState::restore_model(const Json::Value &root) { + const auto &model_value = root[MODEL_KEY]; + update_from_json(*this->model, model_value); } -void ApplicationState::dump_location_history(Json::Value &root) { - auto &history_value = root[LOCATION_HISTORY_KEY]; - const auto &history = this->model->location_history; - int location_index = 0; - for (const auto &item : history) { - history_value[location_index]["location"]["longitude"] = - Json::Value{item.location.longitude}; - history_value[location_index]["location"]["latitude"] = - Json::Value{item.location.latitude}; - history_value[location_index]["location"]["name"] = item.location.name; - history_value[location_index]["location"]["country"] = - item.location.country; - history_value[location_index]["location"]["state"] = item.location.state; - history_value[location_index]["favorite"] = item.favorite; - - ++location_index; - } +void ApplicationState::dump_model(Json::Value &root) { + auto &model_value = root[MODEL_KEY]; + model_value = to_json(*this->model); } } // namespace taranis diff --git a/src/state.h b/src/state.h index f5e5aba..4dc9b02 100644 --- a/src/state.h +++ b/src/state.h @@ -25,8 +25,8 @@ class ApplicationState { static std::string get_application_state_path(); - void restore_location_history(const Json::Value &root); + void restore_model(const Json::Value &root); - void dump_location_history(Json::Value &root); + void dump_model(Json::Value &root); }; } // namespace taranis