Commit 2e9ef8c8 authored by Harald Judt's avatar Harald Judt

Fetch and cache astronomical data for multiple days.

met.no sunrise API provides astronomical data for up to 30 days in the
future (requesting more than that will result in an error page).
We can make use of that and cache it, similar as we do for the forecast
data. current_astro will point to the astronomical data for the current
day.

Download will now happen in intervals of 24 hours, not at a fixed time
at midnight as previously. The current conditions updates will take
care that astronomical data gets updated for the present day, because
they happen exactly at midnight and thus save us from adding additional
logic having to deal with that.
parent fe6baf20
......@@ -759,6 +759,33 @@ make_combined_timeslice(xml_weather *wd,
}
void
merge_astro(GArray *astrodata,
const xml_astro *astro)
{
xml_astro *old_astro, *new_astro;
guint index;
g_assert(astrodata != NULL);
if (G_UNLIKELY(astrodata == NULL))
return;
/* copy astro, as it may be deleted by the calling function */
new_astro = xml_astro_copy(astro);
/* check for and replace existing astrodata of the same date */
if ((old_astro = get_astro(astrodata, astro->day, &index))) {
xml_astro_free(old_astro);
g_array_remove_index(astrodata, index);
g_array_insert_val(astrodata, index, new_astro);
weather_debug("Replaced existing astrodata at %d.", index);
} else {
g_array_append_val(astrodata, new_astro);
weather_debug("Appended new astrodata to the existing data.");
}
}
void
merge_timeslice(xml_weather *wd,
const xml_time *timeslice)
......@@ -850,6 +877,52 @@ time_calc_day(const struct tm time_tm,
}
/*
* Compare two xml_astro structs using their date (days) field.
*/
gint
xml_astro_compare(gconstpointer a,
gconstpointer b)
{
xml_astro *a1 = *(xml_astro **) a;
xml_astro *a2 = *(xml_astro **) b;
if (G_UNLIKELY(a1 == NULL && a2 == NULL))
return 0;
if (G_UNLIKELY(a1 == NULL))
return 1;
if (G_UNLIKELY(a2 == NULL))
return -1;
return (gint) difftime(a2->day, a1->day) * -1;
}
void
astrodata_clean(GArray *astrodata)
{
xml_astro *astro;
time_t now_t = time(NULL);
gint i;
if (G_UNLIKELY(astrodata == NULL))
return;
for (i = 0; i < astrodata->len; i++) {
astro = g_array_index(astrodata, xml_astro *, i);
if (G_UNLIKELY(astro == NULL))
continue;
if (difftime(now_t, astro->day) >= 24 * 3600) {
weather_debug("Removing expired astrodata:");
weather_dump(weather_dump_astrodata, astro);
xml_astro_free(astro);
g_array_remove_index(astrodata, i--);
weather_debug("Remaining astrodata entries: %d", astrodata->len);
}
}
}
/*
* Compare two xml_time structs using their start and end times,
* returning the result as a qsort()-style comparison function (less
......@@ -1071,26 +1144,63 @@ make_current_conditions(xml_weather *wd,
}
/*
* Add days to time_t and set the calculated day to midnight.
*/
time_t
day_at_midnight(time_t day_t,
const gint add_days)
{
struct tm day_tm;
day_tm = *localtime(&day_t);
day_tm.tm_mday += add_days;
day_tm.tm_hour = day_tm.tm_min = day_tm.tm_sec = 0;
day_tm.tm_isdst = -1;
day_t = mktime(&day_tm);
return day_t;
}
/*
* Returns astro data for a given day.
*/
xml_astro *
get_astro_data_for_day(const GArray *astrodata,
const gint day)
{
xml_astro *astro;
time_t day_t = time(NULL);
gint i;
if (G_UNLIKELY(astrodata == NULL))
return NULL;
day_t = day_at_midnight(day_t, day);
for (i = 0; i < astrodata->len; i++) {
astro = g_array_index(astrodata, xml_astro *, i);
if (astro && (difftime(astro->day, day_t) == 0))
return astro;
}
return NULL;
}
/*
* Get all point data relevant for a given day.
*/
GArray *
get_point_data_for_day(xml_weather *wd,
int day)
gint day)
{
GArray *found;
xml_time *timeslice;
struct tm day_tm;
time_t day_t;
time_t day_t = time(NULL);
gint i;
/* calculate 00:00 for the requested day */
time(&day_t);
day_tm = *localtime(&day_t);
day_tm.tm_mday += day;
day_tm.tm_hour = day_tm.tm_min = day_tm.tm_sec = 0;
day_tm.tm_isdst = -1;
day_t = mktime(&day_tm);
day_t = day_at_midnight(day_t, day);
/* loop over weather data and pick relevant point data */
found = g_array_new(FALSE, TRUE, sizeof(xml_time *));
......
......@@ -134,9 +134,15 @@ time_t time_calc_hour(struct tm time_tm,
time_t time_calc_day(struct tm time_tm,
gint days);
gint xml_astro_compare(gconstpointer a,
gconstpointer b);
gint xml_time_compare(gconstpointer a,
gconstpointer b);
void merge_astro(GArray *astrodata,
const xml_astro *astro);
void merge_timeslice(xml_weather *wd,
const xml_time *timeslice);
......@@ -145,8 +151,14 @@ xml_time *get_current_conditions(const xml_weather *wd);
xml_time *make_current_conditions(xml_weather *wd,
time_t now_t);
time_t day_at_midnight(time_t day_t,
const gint add_days);
xml_astro *get_astro_data_for_day(const GArray *astrodata,
const gint day_t);
GArray *get_point_data_for_day(xml_weather *wd,
int day);
const gint day);
xml_time *make_forecast_data(xml_weather *wd,
GArray *daydata,
......
......@@ -204,17 +204,20 @@ weather_dump_icon_theme(const icon_theme *theme)
gchar *
weather_dump_astrodata(const xml_astro *astro)
{
gchar *out, *sunrise, *sunset, *moonrise, *moonset;
gchar *out, *day, *sunrise, *sunset, *moonrise, *moonset;
if (!astro)
return g_strdup("No astronomical data.");
day = format_date(astro->day, "%Y-%m-%d", TRUE);
sunrise = format_date(astro->sunrise, "%c", TRUE);
sunset = format_date(astro->sunset, "%c", TRUE);
moonrise = format_date(astro->moonrise, "%c", TRUE);
moonset = format_date(astro->moonset, "%c", TRUE);
out = g_strdup_printf("Astronomical data:\n"
" --------------------------------------------\n"
" day: %s\n"
" --------------------------------------------\n"
" sunrise: %s\n"
" sunset: %s\n"
......@@ -227,6 +230,7 @@ weather_dump_astrodata(const xml_astro *astro)
" moon never sets: %s\n"
" moon phase: %s\n"
" --------------------------------------------",
day,
sunrise,
sunset,
YESNO(astro->sun_never_rises),
......@@ -236,6 +240,7 @@ weather_dump_astrodata(const xml_astro *astro)
YESNO(astro->moon_never_rises),
YESNO(astro->moon_never_sets),
astro->moon_phase);
g_free(day);
g_free(sunrise);
g_free(sunset);
g_free(moonrise);
......
......@@ -94,6 +94,30 @@ get_timeslice(xml_weather *wd,
}
xml_astro *
get_astro(const GArray *astrodata,
const time_t day_t,
guint *index)
{
xml_astro *astro;
gint i;
g_assert(astrodata != NULL);
if (G_UNLIKELY(astrodata == NULL))
return NULL;
for (i = 0; i < astrodata->len; i++) {
astro = g_array_index(astrodata, xml_astro *, i);
if (astro && astro->day == day_t) {
if (index != NULL)
*index = i;
return astro;
}
}
return NULL;
}
time_t
parse_timestring(const gchar *ts,
gchar *format) {
......@@ -404,37 +428,58 @@ parse_astro_location(xmlNode *cur_node,
}
static xml_astro *
parse_astro_time(xmlNode *cur_node)
{
xmlNode *child_node;
xml_astro *astro;
gchar *date;
astro = g_slice_new0(xml_astro);
if (G_UNLIKELY(astro == NULL))
return NULL;
date = PROP(cur_node, "date");
astro->day = parse_timestring(date, "%Y-%m-%d");
astro->day = day_at_midnight(astro->day, 0);
xmlFree(date);
for (child_node = cur_node->children; child_node;
child_node = child_node->next)
if (NODE_IS_TYPE(child_node, "location"))
parse_astro_location(child_node, astro);
return astro;
}
/*
* Look at http://api.yr.no/weatherapi/sunrise/1.0/schema for information
* of elements and attributes to expect.
*/
xml_astro *
parse_astro(xmlNode *cur_node)
gboolean
parse_astrodata(xmlNode *cur_node,
GArray *astrodata)
{
xmlNode *child_node, *time_node = NULL;
xmlNode *child_node;
xml_astro *astro;
g_assert(astrodata != NULL);
if (G_UNLIKELY(astrodata == NULL))
return FALSE;
g_assert(cur_node != NULL);
if (G_UNLIKELY(cur_node == NULL || !NODE_IS_TYPE(cur_node, "astrodata")))
return NULL;
astro = g_slice_new0(xml_astro);
if (G_UNLIKELY(astro == NULL))
return NULL;
return FALSE;
for (child_node = cur_node->children; child_node;
child_node = child_node->next)
if (NODE_IS_TYPE(child_node, "time")) {
time_node = child_node;
break;
if ((astro = parse_astro_time(child_node))) {
merge_astro(astrodata, astro);
xml_astro_free(astro);
}
}
if (G_LIKELY(time_node))
for (child_node = time_node->children; child_node;
child_node = child_node->next)
if (NODE_IS_TYPE(child_node, "location"))
parse_astro_location(child_node, astro);
return astro;
return TRUE;
}
......@@ -613,6 +658,36 @@ xml_location_free(xml_location *loc)
}
/*
* Deep copy xml_astro struct.
*/
xml_astro *
xml_astro_copy(const xml_astro *src)
{
xml_astro *dst;
if (G_UNLIKELY(src == NULL))
return NULL;
dst = g_slice_new0(xml_astro);
g_assert(dst != NULL);
if (G_UNLIKELY(dst == NULL))
return NULL;
dst->day = src->day;
dst->sunrise = src->sunrise;
dst->sunset = src->sunset;
dst->sun_never_rises = src->sun_never_rises;
dst->sun_never_sets = src->sun_never_sets;
dst->moonrise = src->moonrise;
dst->moonset = src->moonset;
dst->moon_never_rises = src->moon_never_rises;
dst->moon_never_sets = src->moon_never_sets;
dst->moon_phase = g_strdup(src->moon_phase);
return dst;
}
/*
* Deep copy xml_time struct.
*/
......@@ -748,6 +823,23 @@ xml_astro_free(xml_astro *astro)
}
void
astrodata_free(GArray *astrodata)
{
xml_astro *astro;
gint i;
if (G_UNLIKELY(astrodata == NULL))
return;
for (i = 0; i < astrodata->len; i++) {
astro = g_array_index(astrodata, xml_astro *, i);
if (astro)
xml_astro_free(astro);
}
g_array_free(astrodata, FALSE);
}
void
xml_geolocation_free(xml_geolocation *geo)
{
......
......@@ -80,6 +80,8 @@ typedef struct {
} xml_weather;
typedef struct {
time_t day;
time_t sunrise;
time_t sunset;
gboolean sun_never_rises;
......@@ -143,11 +145,17 @@ xml_time *get_timeslice(xml_weather *wd,
const time_t end_t,
guint *index);
xml_astro *get_astro(const GArray *astrodata,
const time_t day_t,
guint *index);
xmlDoc *get_xml_document(SoupMessage *msg);
gpointer parse_xml_document(SoupMessage *msg,
XmlParseFunc parse_func);
xml_astro *xml_astro_copy(const xml_astro *src);
xml_time *xml_time_copy(const xml_time *src);
void xml_time_free(xml_time *timeslice);
......@@ -158,6 +166,8 @@ void xml_weather_clean(xml_weather *wd);
void xml_astro_free(xml_astro *astro);
void astrodata_free(GArray *astrodata);
void xml_geolocation_free(xml_geolocation *geo);
void xml_place_free(xml_place *place);
......
......@@ -416,48 +416,48 @@ create_summary_tab(plugin_data *data)
/* sun and moon */
APPEND_BTEXT(_("\nAstronomical Data\n"));
if (data->astrodata) {
if (data->astrodata->sun_never_rises) {
if (data->current_astro) {
if (data->current_astro->sun_never_rises) {
value = g_strdup(_("\tSunrise:\t\tThe sun never rises today.\n"));
APPEND_TEXT_ITEM_REAL(value);
} else if (data->astrodata->sun_never_sets) {
} else if (data->current_astro->sun_never_sets) {
value = g_strdup(_("\tSunset:\t\tThe sun never sets today.\n"));
APPEND_TEXT_ITEM_REAL(value);
} else {
sunrise = format_date(data->astrodata->sunrise, NULL, TRUE);
sunrise = format_date(data->current_astro->sunrise, NULL, TRUE);
value = g_strdup_printf(_("\tSunrise:\t\t%s\n"), sunrise);
g_free(sunrise);
APPEND_TEXT_ITEM_REAL(value);
sunset = format_date(data->astrodata->sunset, NULL, TRUE);
sunset = format_date(data->current_astro->sunset, NULL, TRUE);
value = g_strdup_printf(_("\tSunset:\t\t%s\n\n"), sunset);
g_free(sunset);
APPEND_TEXT_ITEM_REAL(value);
}
if (data->astrodata->moon_phase)
if (data->current_astro->moon_phase)
value = g_strdup_printf(_("\tMoon phase:\t%s\n"),
translate_moon_phase
(data->astrodata->moon_phase));
(data->current_astro->moon_phase));
else
value = g_strdup(_("\tMoon phase:\tUnknown\n"));
APPEND_TEXT_ITEM_REAL(value);
if (data->astrodata->moon_never_rises) {
if (data->current_astro->moon_never_rises) {
value =
g_strdup(_("\tMoonrise:\tThe moon never rises today.\n"));
APPEND_TEXT_ITEM_REAL(value);
} else if (data->astrodata->moon_never_sets) {
} else if (data->current_astro->moon_never_sets) {
value =
g_strdup(_("\tMoonset:\tThe moon never sets today.\n"));
APPEND_TEXT_ITEM_REAL(value);
} else {
moonrise = format_date(data->astrodata->moonrise, NULL, TRUE);
moonrise = format_date(data->current_astro->moonrise, NULL, TRUE);
value = g_strdup_printf(_("\tMoonrise:\t%s\n"), moonrise);
g_free(moonrise);
APPEND_TEXT_ITEM_REAL(value);
moonset = format_date(data->astrodata->moonset, NULL, TRUE);
moonset = format_date(data->current_astro->moonset, NULL, TRUE);
value = g_strdup_printf(_("\tMoonset:\t%s\n"), moonset);
g_free(moonset);
APPEND_TEXT_ITEM_REAL(value);
......
......@@ -45,6 +45,12 @@
#define CONN_RETRY_INTERVAL_SMALL (10)
#define CONN_RETRY_INTERVAL_LARGE (10 * 60)
/* met.no sunrise API returns data for up to 30 days in the future and
will return an error page if too many days are requested. Let's
play it safe and request fewer than that, since we can only get a
10 days forecast too. */
#define ASTRODATA_MAX_DAYS 25
/* power saving update interval in seconds used as a precaution to
deal with suspend/resume events etc., when nothing needs to be
updated earlier: */
......@@ -370,6 +376,22 @@ update_scrollbox(plugin_data *data,
}
/* get the present day's astrodata */
static void
update_current_astrodata(plugin_data *data)
{
time_t now_t = time(NULL);
if ((data->current_astro == NULL && data->astrodata) ||
(data->current_astro &&
difftime(data->current_astro->day, now_t) >= 24 * 3600)) {
data->current_astro = get_astro_data_for_day(data->astrodata, 0);
weather_debug("Updated astrodata for the present day.");
weather_dump(weather_dump_astrodata, data->current_astro);
}
}
static void
update_current_conditions(plugin_data *data,
gboolean immediately)
......@@ -400,10 +422,13 @@ update_current_conditions(plugin_data *data,
make_current_conditions(data->weatherdata,
data->conditions_update->last);
/* update astrodata for the present day */
update_current_astrodata(data);
/* schedule next update */
now_tm.tm_min += 5;
data->conditions_update->next = mktime(&now_tm);
data->night_time = is_night_time(data->astrodata);
data->night_time = is_night_time(data->current_astro);
schedule_next_wakeup(data);
/* update widgets */
......@@ -452,12 +477,13 @@ cb_astro_update(SoupSession *session,
gpointer user_data)
{
plugin_data *data = user_data;
xml_astro *astro = NULL;
xmlDoc *doc;
xmlNode *root_node;
time_t now_t;
struct tm now_tm;
gboolean parsing_error = TRUE;
time(&now_t);
now_tm = *localtime(&now_t);
data->astro_update->attempt++;
if ((msg->status_code == 200 || msg->status_code == 203)) {
if (msg->status_code == 203)
g_warning
......@@ -467,47 +493,38 @@ cb_astro_update(SoupSession *session,
"within a few month. Please file a bug on "
"https://bugzilla.xfce.org if no one else has done so "
"yet."));
if ((astro = (xml_astro *)
parse_xml_document(msg, (XmlParseFunc) parse_astro))) {
if (data->astrodata)
xml_astro_free(data->astrodata);
data->astrodata = astro;
/* schedule next update at 00:00 of the next day */
data->astro_update->last = now_t;
data->astro_update->next =
time_calc(now_tm, 0, 0, 1, 0 - now_tm.tm_hour,
0 - now_tm.tm_min, 0 - now_tm.tm_sec);
data->astro_update->attempt = 0;
} else
doc = get_xml_document(msg);
if (G_LIKELY(doc)) {
root_node = xmlDocGetRootElement(doc);
if (G_LIKELY(root_node))
if (parse_astrodata(root_node, data->astrodata)) {
/* schedule next update */
data->astro_update->attempt = 0;
data->astro_update->last = now_t;
parsing_error = FALSE;
}
xmlFreeDoc(doc);
}
if (parsing_error)
g_warning(_("Error parsing astronomical data!"));
} else
g_warning(_("Download of astronomical data failed with "
"HTTP Status Code %d, Reason phrase: %s"),
msg->status_code, msg->reason_phrase);
data->astro_update->next = calc_next_download_time(data->astro_update,
now_t);
if (G_UNLIKELY(astro == NULL)) {
/* download or parsing failed, schedule retry */
data->astro_update->attempt++;
data->astro_update->next = calc_next_download_time(data->astro_update,
now_t);
/* invalidate obsolete sunrise/sunset data */
if (data->astrodata != NULL &&
difftime(data->astrodata->sunset, now_t) < 0 &&
difftime(data->astrodata->sunrise, now_t) < 0) {
xml_astro_free(data->astrodata);
data->astrodata = NULL;
weather_debug("Obsolete astronomical data has been invalidated.");
}
}
astrodata_clean(data->astrodata);
g_array_sort(data->astrodata, (GCompareFunc) xml_astro_compare);
update_current_astrodata(data);
/* update icon */
data->night_time = is_night_time(data->astrodata);
data->night_time = is_night_time(data->current_astro);
update_icon(data);
schedule_next_wakeup(data);
weather_dump(weather_dump_astrodata, data->astrodata);
weather_dump(weather_dump_astrodata, data->current_astro);
}
......@@ -573,8 +590,8 @@ update_handler(plugin_data *data)
{
gchar *url;
gboolean night_time;
time_t now_t;
struct tm now_tm;
time_t now_t, end_t;
struct tm now_tm, end_tm;
g_assert(data != NULL);
if (G_UNLIKELY(data == NULL))
......@@ -597,13 +614,22 @@ update_handler(plugin_data *data)
this is to prevent spawning multiple updates in a row */