The deprecated GweatherForecastType enumeration was dropped from the API, and gweather_info_new() lost the forecast type parameter. This requires bumping up the minimum version of libgweather we require to build Calendar. Ideally, we could do a version check and an ifdef dance to keep Calendar building with the previous versions of libgweather, but since we were requiring version 3.24 I don't think it's worth it.
1422 lines
41 KiB
C
1422 lines
41 KiB
C
/* gcal-weather-service.c
|
|
*
|
|
* Copyright (C) 2017 - Florian Brosch <flo.brosch@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "GcalWeatherService"
|
|
|
|
#define DESKTOP_FILE_NAME "org.gnome.Calendar"
|
|
|
|
#include <geocode-glib/geocode-glib.h>
|
|
#include <geoclue.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "gcal-weather-service.h"
|
|
#include "gcal-timer.h"
|
|
|
|
|
|
#define DAY_SECONDS (24 * 60 * 60)
|
|
|
|
/**
|
|
* Internal structure used to manage known
|
|
* weather icons.
|
|
*/
|
|
typedef struct
|
|
{
|
|
gchar *name;
|
|
gboolean night_support;
|
|
} GcalWeatherIconInfo;
|
|
|
|
|
|
/* GcalWeatherService:
|
|
*
|
|
* @time_zone: The current time zone
|
|
* @check_interval_new: Amount of seconds to wait before fetching weather infos.
|
|
* @check_interval_renew: Amount of seconds to wait before re-fetching weather information.
|
|
* @duration_timer Timer used to request weather report updates.
|
|
* @midnight_timer Timer used to update weather reports at midnight.
|
|
* @network_changed_sid "network-changed" signal ID.
|
|
* @location_service: Used to monitor location changes.
|
|
* Initialized by gcal_weather_service_run(),
|
|
* freed by gcal_weather_service_stop().
|
|
* @location_cancellable: Used to deal with async location service construction.
|
|
* @locaton_running: Whether location service is active.
|
|
* @weather_infos: List of #GcalWeatherInfo objects.
|
|
* @weather_infos_upated: The monotonic time @weather_info was set at.
|
|
* @valid_timespan: Amount of seconds weather information are considered valid.
|
|
* @gweather_info: The weather info to query.
|
|
* @max_days: Number of days we want weather information for.
|
|
* @weather_service_running: True if weather service is active.
|
|
*
|
|
* This service listens to location and weather changes and reports them.
|
|
*
|
|
* * Create a new instance with gcal_weather_service_new().
|
|
* * Connect to ::changed to catch weather information.
|
|
* * Use gcal_weather_service_start() to start the service.
|
|
* * Use gcal_weather_service_stop() when you are done.
|
|
*
|
|
* Make sure to stop this service before destroying it.
|
|
*/
|
|
struct _GcalWeatherService
|
|
{
|
|
GObjectClass parent;
|
|
|
|
GTimeZone *timezone; /* owned, nullable */
|
|
|
|
/* timer: */
|
|
guint check_interval_new;
|
|
guint check_interval_renew;
|
|
GcalTimer *duration_timer;
|
|
GcalTimer *midnight_timer;
|
|
|
|
/* network monitoring */
|
|
gulong network_changed_sid;
|
|
|
|
/* locations: */
|
|
GClueSimple *location_service; /* owned, nullable */
|
|
GCancellable *location_cancellable; /* owned, non-null */
|
|
gboolean location_service_running;
|
|
|
|
/* weather: */
|
|
GSList *weather_infos; /* owned[owned] */
|
|
gint64 weather_infos_upated;
|
|
gint64 valid_timespan;
|
|
GWeatherInfo *gweather_info; /* owned, nullable */
|
|
guint max_days;
|
|
gboolean weather_service_running;
|
|
};
|
|
|
|
static void on_gweather_update_cb (GWeatherInfo *info,
|
|
GcalWeatherService *self);
|
|
|
|
static void schedule_midnight (GcalWeatherService *self);
|
|
|
|
static void start_timer (GcalWeatherService *self);
|
|
|
|
static void stop_timer (GcalWeatherService *self);
|
|
|
|
static gboolean has_valid_weather_infos (GcalWeatherService *self);
|
|
|
|
static void update_weather (GcalWeatherService *self,
|
|
GWeatherInfo *info,
|
|
gboolean reuse_old_on_error);
|
|
|
|
static gssize get_normalized_icon_name_len (const gchar *str);
|
|
|
|
G_DEFINE_TYPE (GcalWeatherService, gcal_weather_service, G_TYPE_OBJECT)
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_MAX_DAYS,
|
|
PROP_TIME_ZONE,
|
|
PROP_CHECK_INTERVAL_NEW,
|
|
PROP_CHECK_INTERVAL_RENEW,
|
|
PROP_VALID_TIMESPAN,
|
|
PROP_NUM,
|
|
};
|
|
|
|
enum
|
|
{
|
|
SIG_WEATHER_CHANGED,
|
|
N_SIGNALS,
|
|
};
|
|
|
|
static guint signals[N_SIGNALS] = { 0 };
|
|
|
|
|
|
/*
|
|
* Auxiliary methods
|
|
*/
|
|
|
|
static gchar*
|
|
get_normalized_icon_name (GWeatherInfo* wi,
|
|
gboolean is_night_icon)
|
|
{
|
|
const gchar night_pfx[] = "-night";
|
|
const gsize night_pfx_size = G_N_ELEMENTS (night_pfx) - 1;
|
|
|
|
const gchar sym_pfx[] = "-symbolic";
|
|
const gsize sym_pfx_size = G_N_ELEMENTS (sym_pfx) - 1;
|
|
|
|
const gchar *str; /* unowned */
|
|
gssize normalized_size;
|
|
gchar *buffer = NULL; /* owned */
|
|
gchar *bufpos = NULL; /* unowned */
|
|
gsize buffer_size;
|
|
|
|
g_return_val_if_fail (wi != NULL, NULL);
|
|
|
|
str = gweather_info_get_icon_name (wi);
|
|
if (!str)
|
|
return NULL;
|
|
|
|
normalized_size = get_normalized_icon_name_len (str);
|
|
g_return_val_if_fail (normalized_size >= 0, NULL);
|
|
|
|
if (is_night_icon)
|
|
buffer_size = normalized_size + night_pfx_size + sym_pfx_size + 1;
|
|
else
|
|
buffer_size = normalized_size + sym_pfx_size + 1;
|
|
|
|
buffer = g_malloc (buffer_size);
|
|
bufpos = buffer;
|
|
|
|
memcpy (bufpos, str, normalized_size);
|
|
bufpos = bufpos + normalized_size;
|
|
|
|
if (is_night_icon)
|
|
{
|
|
memcpy (bufpos, night_pfx, night_pfx_size);
|
|
bufpos = bufpos + night_pfx_size;
|
|
}
|
|
|
|
memcpy (bufpos, sym_pfx, sym_pfx_size);
|
|
buffer[buffer_size - 1] = '\0';
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static void
|
|
update_timeout_interval (GcalWeatherService *self)
|
|
{
|
|
guint interval;
|
|
|
|
if (has_valid_weather_infos (self))
|
|
interval = self->check_interval_renew;
|
|
else
|
|
interval = self->check_interval_new;
|
|
|
|
gcal_timer_set_default_duration (self->duration_timer, interval);
|
|
}
|
|
|
|
static void
|
|
schedule_midnight (GcalWeatherService *self)
|
|
{
|
|
g_autoptr (GTimeZone) zone = NULL;
|
|
g_autoptr (GDateTime) now = NULL;
|
|
g_autoptr (GDateTime) tom = NULL;
|
|
g_autoptr (GDateTime) mid = NULL;
|
|
gint64 real_now;
|
|
gint64 real_mid;
|
|
|
|
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
|
|
|
|
zone = !self->timezone ? g_time_zone_new_local () : g_time_zone_ref (self->timezone);
|
|
|
|
now = g_date_time_new_now (zone);
|
|
tom = g_date_time_add_days (now, 1);
|
|
mid = g_date_time_new (zone,
|
|
g_date_time_get_year (tom),
|
|
g_date_time_get_month (tom),
|
|
g_date_time_get_day_of_month (tom),
|
|
0, 0, 0);
|
|
|
|
real_mid = g_date_time_to_unix (mid);
|
|
real_now = g_date_time_to_unix (now);
|
|
|
|
gcal_timer_set_default_duration (self->midnight_timer,
|
|
real_mid - real_now);
|
|
}
|
|
|
|
static void
|
|
start_timer (GcalWeatherService *self)
|
|
{
|
|
GNetworkMonitor *monitor; /* unowned */
|
|
|
|
monitor = g_network_monitor_get_default ();
|
|
if (g_network_monitor_get_network_available (monitor))
|
|
{
|
|
update_timeout_interval (self);
|
|
gcal_timer_start (self->duration_timer);
|
|
|
|
schedule_midnight (self);
|
|
gcal_timer_start (self->midnight_timer);
|
|
}
|
|
}
|
|
|
|
static void
|
|
stop_timer (GcalWeatherService *self)
|
|
{
|
|
gcal_timer_stop (self->duration_timer);
|
|
gcal_timer_stop (self->midnight_timer);
|
|
}
|
|
|
|
static void
|
|
set_check_interval_new (GcalWeatherService *self,
|
|
guint interval)
|
|
{
|
|
self->check_interval_new = interval;
|
|
update_timeout_interval (self);
|
|
|
|
g_object_notify ((GObject*) self, "check-interval-new");
|
|
}
|
|
|
|
static void
|
|
set_check_interval_renew (GcalWeatherService *self,
|
|
guint interval)
|
|
{
|
|
self->check_interval_renew = interval;
|
|
update_timeout_interval (self);
|
|
|
|
g_object_notify ((GObject*) self, "check-interval-renew");
|
|
}
|
|
|
|
static gssize
|
|
get_normalized_icon_name_len (const gchar *str)
|
|
{
|
|
const gchar *suffix1 = "-symbolic";
|
|
const gssize suffix1_len = strlen (suffix1);
|
|
|
|
const gchar *suffix2 = "-night";
|
|
const gssize suffix2_len = strlen (suffix2);
|
|
|
|
gssize clean_len;
|
|
gssize str_len;
|
|
|
|
str_len = strlen (str);
|
|
|
|
clean_len = str_len - suffix1_len;
|
|
if (clean_len >= 0 && memcmp (suffix1, str + clean_len, suffix1_len) == 0)
|
|
str_len = clean_len;
|
|
|
|
clean_len = str_len - suffix2_len;
|
|
if (clean_len >= 0 && memcmp (suffix2, str + clean_len, suffix2_len) == 0)
|
|
str_len = clean_len;
|
|
|
|
return str_len;
|
|
}
|
|
|
|
static gint
|
|
get_icon_name_sortkey (const gchar *icon_name,
|
|
gboolean *supports_night_icon)
|
|
{
|
|
/* Note that we can't use gweathers condition
|
|
* field as it is not necessarily holds valid values.
|
|
* libgweather uses its own heuristic to determine
|
|
* the icon to use. String matching is still better
|
|
* than copying their algorithm, I guess.
|
|
*/
|
|
|
|
gssize normalized_name_len;
|
|
|
|
const GcalWeatherIconInfo icons[] =
|
|
{ {"weather-clear", TRUE},
|
|
{"weather-few-clouds", TRUE},
|
|
{"weather-overcast", FALSE},
|
|
{"weather-fog", FALSE},
|
|
{"weather-showers-scattered", FALSE},
|
|
{"weather-showers", FALSE},
|
|
{"weather-snow", FALSE},
|
|
{"weather-storm", FALSE},
|
|
{"weather-severe-alert", FALSE}
|
|
};
|
|
|
|
g_return_val_if_fail (icon_name != NULL, -1);
|
|
g_return_val_if_fail (supports_night_icon != NULL, -1);
|
|
|
|
*supports_night_icon = FALSE;
|
|
|
|
normalized_name_len = get_normalized_icon_name_len (icon_name);
|
|
g_return_val_if_fail (normalized_name_len >= 0, -1);
|
|
|
|
for (int i = 0; i < G_N_ELEMENTS (icons); i++)
|
|
{
|
|
if (icons[i].name[normalized_name_len] == '\0' && strncmp (icon_name, icons[i].name, normalized_name_len) == 0)
|
|
{
|
|
*supports_night_icon = icons[i].night_support;
|
|
return i;
|
|
}
|
|
}
|
|
|
|
g_warning ("Unknown weather icon '%s'", icon_name);
|
|
|
|
return -1;
|
|
}
|
|
|
|
static gboolean
|
|
get_time_day_start (GcalWeatherService *self,
|
|
GDate *ret_date,
|
|
gint64 *ret_unix,
|
|
gint64 *ret_unix_exact)
|
|
{
|
|
g_autoptr (GTimeZone) zone = NULL;
|
|
g_autoptr (GDateTime) now = NULL;
|
|
g_autoptr (GDateTime) day = NULL;
|
|
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
g_return_val_if_fail (ret_date != NULL, FALSE);
|
|
g_return_val_if_fail (ret_unix != NULL, FALSE);
|
|
g_return_val_if_fail (ret_unix_exact != NULL, FALSE);
|
|
|
|
zone = !self->timezone ? g_time_zone_new_local () : g_time_zone_ref (self->timezone);
|
|
|
|
now = g_date_time_new_now (zone);
|
|
day = g_date_time_new (zone,
|
|
g_date_time_get_year (now),
|
|
g_date_time_get_month (now),
|
|
g_date_time_get_day_of_month (now),
|
|
0, 0, 0);
|
|
|
|
g_date_set_dmy (ret_date,
|
|
g_date_time_get_day_of_month (day),
|
|
g_date_time_get_month (day),
|
|
g_date_time_get_year (day));
|
|
|
|
*ret_unix = g_date_time_to_unix (day);
|
|
*ret_unix_exact = g_date_time_to_unix (now);
|
|
return TRUE;
|
|
}
|
|
|
|
static inline gboolean
|
|
get_gweather_temperature (GWeatherInfo *gwi,
|
|
gdouble *temp)
|
|
{
|
|
gboolean valid;
|
|
gdouble value;
|
|
|
|
*temp = NAN;
|
|
|
|
g_return_val_if_fail (gwi != NULL, FALSE);
|
|
g_return_val_if_fail (temp != NULL, FALSE);
|
|
|
|
valid = gweather_info_get_value_temp (gwi,
|
|
GWEATHER_TEMP_UNIT_DEFAULT,
|
|
&value);
|
|
|
|
/* TODO: Extract temperatures in Celsius and catch implausible cases */
|
|
if (valid)
|
|
{
|
|
*temp = value;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
*temp = NAN;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
compute_weather_info_data (GSList *samples,
|
|
gboolean is_today,
|
|
gchar **icon_name,
|
|
gchar **temperature)
|
|
{
|
|
GWeatherInfo *phenomenon_gwi = NULL;
|
|
GWeatherInfo *temp_gwi = NULL;
|
|
GSList *iter;
|
|
gboolean phenomenon_supports_night_icon;
|
|
gboolean has_daytime;
|
|
gdouble temp_val;
|
|
gint phenomenon_val;
|
|
|
|
temp_val = NAN;
|
|
has_daytime = FALSE;
|
|
phenomenon_val = -1;
|
|
phenomenon_supports_night_icon = FALSE;
|
|
|
|
/* Note: I checked three different gweather consumers
|
|
* and they all pick different values. So here is my
|
|
* take: I pick up the worst weather for icons and
|
|
* the highest temperature. I basically want to know
|
|
* whether I need my umbrella for my appointment.
|
|
* Not sure about the right temperature. It is probably
|
|
* better to pick-up the median of all predictions
|
|
* during daytime.
|
|
*/
|
|
|
|
for (iter = samples; iter; iter = iter->next)
|
|
{
|
|
GWeatherInfo *gwi; /* unowned */
|
|
const gchar *icon_name; /* unowned */
|
|
gboolean supports_night_icon;
|
|
gboolean valid_temp;
|
|
gdouble temp;
|
|
gint phenomenon;
|
|
|
|
gwi = GWEATHER_INFO (iter->data);
|
|
phenomenon = -1;
|
|
|
|
icon_name = gweather_info_get_icon_name (gwi);
|
|
if (icon_name)
|
|
phenomenon = get_icon_name_sortkey (icon_name, &supports_night_icon);
|
|
|
|
valid_temp = get_gweather_temperature (gwi, &temp);
|
|
|
|
if (phenomenon >= 0 && (phenomenon_gwi == NULL || phenomenon > phenomenon_val))
|
|
{
|
|
phenomenon_supports_night_icon = supports_night_icon;
|
|
phenomenon_val = phenomenon;
|
|
phenomenon_gwi = gwi;
|
|
}
|
|
|
|
if (valid_temp && (!temp_gwi || temp > temp_val))
|
|
{
|
|
temp_val = temp;
|
|
temp_gwi = gwi;
|
|
}
|
|
|
|
if (gweather_info_is_daytime (gwi))
|
|
has_daytime = TRUE;
|
|
}
|
|
|
|
if (phenomenon_gwi && temp_gwi)
|
|
{
|
|
*icon_name = get_normalized_icon_name (phenomenon_gwi, is_today && !has_daytime && phenomenon_supports_night_icon);
|
|
*temperature = gweather_info_get_temp (temp_gwi);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* empty list */
|
|
*icon_name = NULL;
|
|
*temperature = NULL;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
update_location (GcalWeatherService *self,
|
|
GWeatherLocation *location)
|
|
{
|
|
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
|
|
|
|
if (gcal_timer_is_running (self->duration_timer))
|
|
stop_timer (self);
|
|
|
|
if (self->gweather_info != NULL)
|
|
g_clear_object (&self->gweather_info);
|
|
|
|
if (location == NULL)
|
|
{
|
|
g_debug ("Could not retrieve current location");
|
|
update_weather (self, NULL, FALSE);
|
|
}
|
|
else
|
|
{
|
|
g_debug ("Got new weather service location: '%s'",
|
|
(location == NULL)? "<null>" : gweather_location_get_name (location));
|
|
|
|
self->gweather_info = gweather_info_new (location);
|
|
|
|
/* NOTE: We do not get detailed infos for GWEATHER_PROVIDER_ALL.
|
|
* This combination works fine, though. We should open a bug / investigate
|
|
* what is going on.
|
|
*/
|
|
gweather_info_set_enabled_providers (self->gweather_info, GWEATHER_PROVIDER_METAR | GWEATHER_PROVIDER_OWM | GWEATHER_PROVIDER_YR_NO);
|
|
g_signal_connect (self->gweather_info, "updated", (GCallback) on_gweather_update_cb, self);
|
|
|
|
/* gweather_info_update might or might not trigger a
|
|
* GWeatherInfo::update() signal. Therefore, we have to
|
|
* remove weather information before querying new one.
|
|
* This might result in icon flickering on screen.
|
|
* We probably want to introduce a "unknown" or "loading"
|
|
* state in gweather-info to soften the effect.
|
|
*/
|
|
update_weather (self, NULL, FALSE);
|
|
gweather_info_update (self->gweather_info);
|
|
|
|
start_timer (self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_gclue_location (GcalWeatherService *self,
|
|
GClueLocation *location)
|
|
{
|
|
GWeatherLocation *wlocation = NULL; /* owned */
|
|
|
|
if (location)
|
|
{
|
|
GWeatherLocation *wworld; /* unowned */
|
|
gdouble latitude;
|
|
gdouble longitude;
|
|
|
|
latitude = gclue_location_get_latitude (location);
|
|
longitude = gclue_location_get_longitude (location);
|
|
|
|
/* nearest-city works more closely to gnome weather. */
|
|
wworld = gweather_location_get_world ();
|
|
wlocation = gweather_location_find_nearest_city (wworld, latitude, longitude);
|
|
}
|
|
|
|
update_location (self, wlocation);
|
|
|
|
g_clear_pointer (&wlocation, gweather_location_unref);
|
|
}
|
|
|
|
static GSList*
|
|
preprocess_gweather_reports (GcalWeatherService *self,
|
|
GSList *samples)
|
|
{
|
|
GWeatherInfo *first_tomorrow = NULL; /* unowned */
|
|
GSList **days = NULL; /* owned[owned[unowned]] */
|
|
GSList *result = NULL; /* owned[owned] */
|
|
GSList *iter = NULL; /* unowned */
|
|
GDate cur_gdate;
|
|
glong first_tomorrow_dtime = -1;
|
|
glong today_unix;
|
|
glong unix_now;
|
|
|
|
/* This function basically separates samples by date and calls compute_weather_info_data
|
|
* for every bucket to build weather infos. Each bucket represents a single day.
|
|
*
|
|
* All gweather consumers I reviewed presume sorted samples. However, there is no documented
|
|
* order. Lets assume the worst.
|
|
*/
|
|
|
|
if (self->max_days <= 0)
|
|
return NULL;
|
|
|
|
if (!get_time_day_start (self, &cur_gdate, &today_unix, &unix_now))
|
|
return NULL;
|
|
|
|
days = g_malloc0 (sizeof (GSList*) * self->max_days);
|
|
|
|
/* Split samples to max_days buckets: */
|
|
for (iter = samples; iter != NULL; iter = iter->next)
|
|
{
|
|
GWeatherInfo *gwi; /* unowned */
|
|
gboolean valid_date;
|
|
glong gwi_dtime;
|
|
gsize bucket;
|
|
|
|
gwi = GWEATHER_INFO (iter->data);
|
|
valid_date = gweather_info_get_value_update (gwi, &gwi_dtime);
|
|
if (!valid_date)
|
|
continue;
|
|
|
|
#if PRINT_WEATHER_DATA
|
|
{
|
|
g_autofree gchar* dbg_str = gwc2str (gwi);
|
|
g_message ("WEATHER READING POINT: %s", dbg_str);
|
|
}
|
|
#endif
|
|
|
|
if (gwi_dtime >= 0 && gwi_dtime >= today_unix)
|
|
{
|
|
bucket = (gwi_dtime - today_unix) / DAY_SECONDS;
|
|
if (bucket < self->max_days)
|
|
days[bucket] = g_slist_prepend (days[bucket], gwi);
|
|
|
|
if (bucket == 1 && (first_tomorrow == NULL || first_tomorrow_dtime > gwi_dtime))
|
|
{
|
|
first_tomorrow_dtime = gwi_dtime;
|
|
first_tomorrow = gwi;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_debug ("Encountered historic weather information");
|
|
}
|
|
}
|
|
|
|
if (!days[0] && first_tomorrow)
|
|
{
|
|
|
|
glong secs_left_today;
|
|
glong secs_between;
|
|
|
|
/* There is no data point left for today. Lets borrow one. */
|
|
secs_left_today = DAY_SECONDS - (unix_now - today_unix);
|
|
secs_between = first_tomorrow_dtime - unix_now;
|
|
|
|
if (secs_left_today < 90*60 && secs_between <= 180*60)
|
|
days[0] = g_slist_prepend (days[0], first_tomorrow);
|
|
}
|
|
|
|
/* Produce GcalWeatherInfo for each bucket: */
|
|
for (int i = 0; i < self->max_days; i++)
|
|
{
|
|
g_autofree gchar *icon_name;
|
|
g_autofree gchar *temperature;
|
|
|
|
if (compute_weather_info_data (days[i], i == 0, &icon_name, &temperature))
|
|
{
|
|
GcalWeatherInfo* gcwi;
|
|
|
|
gcwi = gcal_weather_info_new (&cur_gdate, icon_name, temperature);
|
|
result = g_slist_prepend (result, g_steal_pointer (&gcwi));
|
|
}
|
|
|
|
g_date_add_days (&cur_gdate, 1);
|
|
}
|
|
|
|
/* Cleanup */
|
|
for (int i = 0; i < self->max_days; i++)
|
|
g_slist_free (days[i]);
|
|
g_free (days);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* Callbacks
|
|
*/
|
|
|
|
static void
|
|
on_network_changed_cb (GNetworkMonitor *monitor,
|
|
gboolean available,
|
|
GcalWeatherService *self)
|
|
{
|
|
gboolean is_running;
|
|
|
|
is_running = gcal_timer_is_running (self->duration_timer);
|
|
|
|
if (available && !is_running)
|
|
{
|
|
if (self->gweather_info != NULL)
|
|
gweather_info_update (self->gweather_info);
|
|
|
|
start_timer (self);
|
|
}
|
|
else if (!available && is_running)
|
|
{
|
|
stop_timer (self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_gclue_location_changed_cb (GClueLocation *location,
|
|
GcalWeatherService *self)
|
|
{
|
|
update_gclue_location (self, location);
|
|
}
|
|
|
|
static void
|
|
on_gclue_client_activity_changed_cb (GClueClient *client,
|
|
GcalWeatherService *self)
|
|
{
|
|
/* Notify listeners about unknown locations: */
|
|
update_location (self, NULL);
|
|
}
|
|
|
|
static void
|
|
on_gclue_simple_creation_cb (GClueSimple *_source,
|
|
GAsyncResult *result,
|
|
GcalWeatherService *self)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
GClueLocation *location; /* unowned */
|
|
GClueSimple *location_service; /* owned */
|
|
GClueClient *client; /* unowned */
|
|
|
|
/* make sure we do not touch self->location_service
|
|
* if the current operation was cancelled.
|
|
*/
|
|
location_service = gclue_simple_new_finish (result, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_assert_null (location_service);
|
|
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
/* Cancelled during creation. Silently fail. */;
|
|
else
|
|
g_warning ("Could not create GCLueSimple: %s", error->message);
|
|
|
|
g_object_unref (self);
|
|
return;
|
|
}
|
|
|
|
self->location_service = g_steal_pointer (&location_service);
|
|
|
|
location = gclue_simple_get_location (self->location_service);
|
|
client = gclue_simple_get_client (self->location_service);
|
|
|
|
if (location)
|
|
{
|
|
update_gclue_location (self, location);
|
|
|
|
g_signal_connect_object (location,
|
|
"notify::location",
|
|
G_CALLBACK (on_gclue_location_changed_cb),
|
|
self,
|
|
0);
|
|
}
|
|
|
|
g_signal_connect_object (client,
|
|
"notify::active",
|
|
G_CALLBACK (on_gclue_client_activity_changed_cb),
|
|
self,
|
|
0);
|
|
|
|
g_object_unref (self);
|
|
}
|
|
|
|
/* on_gclue_client_stop:
|
|
* @source_object: A #GClueClient.
|
|
* @res: Result of gclue_client_call_stop().
|
|
* @simple: (transfer full): A #GClueSimple.
|
|
*
|
|
* Helper-callback used in gcal_weather_service_stop().
|
|
*/
|
|
static void
|
|
on_gclue_client_stop (GClueClient *client,
|
|
GAsyncResult *res,
|
|
GClueSimple *simple)
|
|
{
|
|
g_autoptr(GError) error = NULL; /* owned */
|
|
gboolean stopped;
|
|
|
|
stopped = gclue_client_call_stop_finish (client,
|
|
res,
|
|
&error);
|
|
if (error != NULL)
|
|
g_warning ("Could not stop location service: %s", error->message);
|
|
else if (!stopped)
|
|
g_warning ("Could not stop location service");
|
|
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
on_gweather_update_cb (GWeatherInfo *info,
|
|
GcalWeatherService *self)
|
|
{
|
|
update_weather (self, info, TRUE);
|
|
}
|
|
|
|
/* on_duration_timer_timeout
|
|
* @self: A #GcalWeatherService.
|
|
*
|
|
* Handles scheduled weather report updates.
|
|
*/
|
|
static void
|
|
on_duration_timer_timeout (GcalTimer *timer,
|
|
GcalWeatherService *self)
|
|
{
|
|
if (self->gweather_info)
|
|
gweather_info_update (self->gweather_info);
|
|
}
|
|
|
|
/* on_midnight_timer_timeout
|
|
* @self: A #GcalWeatherService.
|
|
*
|
|
* Handles scheduled weather report updates.
|
|
*/
|
|
static void
|
|
on_midnight_timer_timeout (GcalTimer *timer,
|
|
GcalWeatherService *self)
|
|
{
|
|
if (self->gweather_info)
|
|
gweather_info_update (self->gweather_info);
|
|
|
|
if (gcal_timer_is_running (self->duration_timer))
|
|
gcal_timer_reset (self->duration_timer);
|
|
|
|
schedule_midnight (self);
|
|
}
|
|
|
|
#if PRINT_WEATHER_DATA
|
|
static gchar*
|
|
gwc2str (GWeatherInfo *gwi)
|
|
{
|
|
g_autoptr (GDateTime) date = NULL;
|
|
g_autofree gchar *date_str = NULL;
|
|
glong update;
|
|
|
|
gchar *icon_name; /* unowned */
|
|
gdouble temp;
|
|
|
|
if (!gweather_info_get_value_update (gwi, &update))
|
|
return g_strdup ("<null>");
|
|
|
|
date = g_date_time_new_from_unix_local (update);
|
|
date_str = g_date_time_format (date, "%F %T"),
|
|
|
|
get_gweather_temperature (gwi, &temp);
|
|
icon_name = gweather_info_get_symbolic_icon_name (gwi);
|
|
|
|
return g_strdup_printf ("(%s: t:%f, w:%s)",
|
|
date_str,
|
|
temp,
|
|
icon_name);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
set_max_days (GcalWeatherService *self,
|
|
guint days)
|
|
{
|
|
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
|
|
g_return_if_fail (days >= 1);
|
|
|
|
self->max_days = days;
|
|
|
|
g_object_notify ((GObject*) self, "max-days");
|
|
}
|
|
|
|
static void
|
|
set_valid_timespan (GcalWeatherService *self,
|
|
gint64 timespan)
|
|
{
|
|
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
|
|
g_return_if_fail (timespan >= 0);
|
|
|
|
self->valid_timespan = timespan;
|
|
|
|
g_object_notify ((GObject*) self, "valid-timespan");
|
|
}
|
|
|
|
static gboolean
|
|
has_valid_weather_infos (GcalWeatherService *self)
|
|
{
|
|
gint64 now;
|
|
|
|
g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), FALSE);
|
|
|
|
if (self->gweather_info == NULL || self->weather_infos_upated < 0)
|
|
return FALSE;
|
|
|
|
now = g_get_monotonic_time ();
|
|
return (now - self->weather_infos_upated) / 1000000 <= self->valid_timespan;
|
|
}
|
|
|
|
/* update_weather:
|
|
* @self: A #GcalWeatherService instance.
|
|
* @info: (nullable): Newly received weather information or %NULL.
|
|
* @reuse_old_on_error: Whether to re-use old but not outdated weather
|
|
* information in case we could not fetch new data.
|
|
*
|
|
* Retrieves weather information for @location and triggers
|
|
* #GcalWeatherService::weather-changed.
|
|
*/
|
|
static void
|
|
update_weather (GcalWeatherService *self,
|
|
GWeatherInfo *info,
|
|
gboolean reuse_old_on_error)
|
|
{
|
|
GSList *gwforecast = NULL; /* unowned */
|
|
|
|
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
|
|
g_return_if_fail (info == NULL || GWEATHER_IS_INFO (info));
|
|
|
|
|
|
/* Compute a list of newly received weather infos. */
|
|
if (!info)
|
|
{
|
|
g_debug ("Could not retrieve valid weather");
|
|
}
|
|
else if (gweather_info_is_valid (info))
|
|
{
|
|
g_debug ("Received valid weather information");
|
|
gwforecast = gweather_info_get_forecast_list (info);
|
|
}
|
|
else
|
|
{
|
|
g_autofree gchar* location_name = gweather_info_get_location_name (info);
|
|
g_debug ("Could not retrieve valid weather for location '%s'", location_name);
|
|
}
|
|
|
|
if (!gwforecast && self->weather_infos_upated >= 0)
|
|
{
|
|
if (!reuse_old_on_error || !has_valid_weather_infos (self))
|
|
{
|
|
g_slist_free_full (self->weather_infos, g_object_unref);
|
|
self->weather_infos = NULL;
|
|
self->weather_infos_upated = -1;
|
|
|
|
g_signal_emit (self, signals[SIG_WEATHER_CHANGED], 0);
|
|
}
|
|
}
|
|
else if (gwforecast)
|
|
{
|
|
g_slist_free_full (self->weather_infos, g_object_unref);
|
|
self->weather_infos = preprocess_gweather_reports (self, gwforecast);
|
|
self->weather_infos_upated = g_get_monotonic_time ();
|
|
|
|
g_signal_emit (self, signals[SIG_WEATHER_CHANGED], 0);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* GObject overrides
|
|
*/
|
|
|
|
static void
|
|
gcal_weather_service_finalize (GObject *object)
|
|
{
|
|
GcalWeatherService *self = (GcalWeatherService *) object;
|
|
|
|
g_cancellable_cancel (self->location_cancellable);
|
|
|
|
g_clear_pointer (&self->duration_timer, gcal_timer_free);
|
|
g_clear_pointer (&self->midnight_timer, gcal_timer_free);
|
|
g_clear_pointer (&self->timezone, g_time_zone_unref);
|
|
|
|
g_clear_object (&self->gweather_info);
|
|
g_clear_object (&self->location_service);
|
|
g_clear_object (&self->location_cancellable);
|
|
|
|
g_slist_free_full (self->weather_infos, g_object_unref);
|
|
|
|
if (self->network_changed_sid > 0)
|
|
g_signal_handler_disconnect (g_network_monitor_get_default (), self->network_changed_sid);
|
|
|
|
G_OBJECT_CLASS (gcal_weather_service_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gcal_weather_service_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GcalWeatherService *self = (GcalWeatherService *) object;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_MAX_DAYS:
|
|
g_value_set_uint (value, gcal_weather_service_get_max_days (self));
|
|
break;
|
|
|
|
case PROP_TIME_ZONE:
|
|
g_value_set_pointer (value, gcal_weather_service_get_time_zone (self));
|
|
break;
|
|
|
|
case PROP_CHECK_INTERVAL_NEW:
|
|
g_value_set_uint (value, gcal_weather_service_get_check_interval_new (self));
|
|
break;
|
|
|
|
case PROP_CHECK_INTERVAL_RENEW:
|
|
g_value_set_uint (value, gcal_weather_service_get_check_interval_renew (self));
|
|
break;
|
|
|
|
case PROP_VALID_TIMESPAN:
|
|
g_value_set_int64 (value, gcal_weather_service_get_valid_timespan (self));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gcal_weather_service_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GcalWeatherService *self = (GcalWeatherService *) object;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_MAX_DAYS:
|
|
set_max_days (self, g_value_get_uint (value));
|
|
break;
|
|
|
|
case PROP_TIME_ZONE:
|
|
gcal_weather_service_set_time_zone (self, g_value_get_pointer (value));
|
|
break;
|
|
|
|
case PROP_CHECK_INTERVAL_NEW:
|
|
set_check_interval_new (self, g_value_get_uint (value));
|
|
break;
|
|
|
|
case PROP_CHECK_INTERVAL_RENEW:
|
|
set_check_interval_renew (self, g_value_get_uint (value));
|
|
break;
|
|
|
|
case PROP_VALID_TIMESPAN:
|
|
set_valid_timespan (self, g_value_get_int64 (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gcal_weather_service_class_init (GcalWeatherServiceClass *klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
|
|
object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = gcal_weather_service_finalize;
|
|
object_class->get_property = gcal_weather_service_get_property;
|
|
object_class->set_property = gcal_weather_service_set_property;
|
|
|
|
/**
|
|
* GcalWeatherService:max-days:
|
|
*
|
|
* Maximal number of days to fetch forecasts for.
|
|
*/
|
|
g_object_class_install_property
|
|
(G_OBJECT_CLASS (klass),
|
|
PROP_MAX_DAYS,
|
|
g_param_spec_uint ("max-days", "max-days", "max-days",
|
|
1, G_MAXUINT, 3,
|
|
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
/**
|
|
* GcalWeatherService:time-zone:
|
|
*
|
|
* The time zone to use.
|
|
*/
|
|
g_object_class_install_property
|
|
(G_OBJECT_CLASS (klass),
|
|
PROP_TIME_ZONE,
|
|
g_param_spec_pointer ("time-zone", "time-zone", "time-zone",
|
|
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
|
|
|
|
/**
|
|
* GcalWeatherService:check-interval-new:
|
|
*
|
|
* Amount of seconds to wait before re-fetching weather infos.
|
|
* This interval is used when no valid weather reports are available.
|
|
*/
|
|
g_object_class_install_property
|
|
(G_OBJECT_CLASS (klass),
|
|
PROP_CHECK_INTERVAL_NEW,
|
|
g_param_spec_uint ("check-interval-new", "check-interval-new", "check-interval-new",
|
|
0, G_MAXUINT, GCAL_WEATHER_CHECK_INTERVAL_NEW_DFLT,
|
|
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
/**
|
|
* GcalWeatherService:check-interval-renew:
|
|
*
|
|
* Amount of seconds to wait before re-fetching weather information.
|
|
* This interval is used when valid weather reports are available.
|
|
*/
|
|
g_object_class_install_property
|
|
(G_OBJECT_CLASS (klass),
|
|
PROP_CHECK_INTERVAL_RENEW,
|
|
g_param_spec_uint ("check-interval-renew", "check-interval-renew", "check-interval-renew",
|
|
0, G_MAXUINT, GCAL_WEATHER_CHECK_INTERVAL_RENEW_DFLT,
|
|
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
/**
|
|
* GcalWeatherService:valid_timespan:
|
|
*
|
|
* Amount of seconds fetched weather information are considered as valid.
|
|
*/
|
|
g_object_class_install_property
|
|
(G_OBJECT_CLASS (klass),
|
|
PROP_VALID_TIMESPAN,
|
|
g_param_spec_int64 ("valid-timespan", "valid-timespan", "valid-timespan",
|
|
0, G_MAXINT64, GCAL_WEATHER_VALID_TIMESPAN_DFLT,
|
|
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
|
|
/**
|
|
* GcalWeatherService::weather-changed:
|
|
* @sender: The #GcalWeatherService
|
|
* @self: data pointer.
|
|
*
|
|
* Triggered on weather changes. Call
|
|
* gcal_weather_service_get_weather_infos() to
|
|
* retrieve predictions.
|
|
*/
|
|
signals[SIG_WEATHER_CHANGED] = g_signal_new ("weather-changed",
|
|
GCAL_TYPE_WEATHER_SERVICE,
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
}
|
|
|
|
static void
|
|
gcal_weather_service_init (GcalWeatherService *self)
|
|
{
|
|
self->check_interval_new = GCAL_WEATHER_CHECK_INTERVAL_NEW_DFLT;
|
|
self->check_interval_renew = GCAL_WEATHER_CHECK_INTERVAL_RENEW_DFLT;
|
|
self->location_cancellable = g_cancellable_new ();
|
|
self->weather_infos_upated = -1;
|
|
self->valid_timespan = -1;
|
|
|
|
self->duration_timer = gcal_timer_new (GCAL_WEATHER_CHECK_INTERVAL_NEW_DFLT);
|
|
gcal_timer_set_callback (self->duration_timer, (GCalTimerFunc) on_duration_timer_timeout, self, NULL);
|
|
|
|
self->midnight_timer = gcal_timer_new (24 * 60 * 60);
|
|
gcal_timer_set_callback (self->midnight_timer, (GCalTimerFunc) on_midnight_timer_timeout, self, NULL);
|
|
|
|
self->network_changed_sid = g_signal_connect (g_network_monitor_get_default (),
|
|
"network-changed",
|
|
G_CALLBACK (on_network_changed_cb),
|
|
self);
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_new:
|
|
* @max_days: mumber of days to fetch forecasts for.
|
|
* @check_interval_new: timeout used when fetching new weather reports.
|
|
* @check_interval_renew: timeout used to update valid weather reports.
|
|
*
|
|
* Creates a new #GcalWeatherService. This service listens
|
|
* to location and weather changes and reports them.
|
|
*
|
|
* Returns: (transfer full): A newly created #GcalWeatherService.
|
|
*/
|
|
GcalWeatherService *
|
|
gcal_weather_service_new (GTimeZone *time_zone,
|
|
guint max_days,
|
|
guint check_interval_new,
|
|
guint check_interval_renew,
|
|
gint64 valid_timespan)
|
|
{
|
|
return g_object_new (GCAL_TYPE_WEATHER_SERVICE,
|
|
"time-zone", time_zone,
|
|
"max-days", max_days,
|
|
"check-interval-new", check_interval_new,
|
|
"check-interval-renew", check_interval_renew,
|
|
"valid-timespan", valid_timespan,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_get_time_zone:
|
|
* @self: The #GcalWeatherService instance.
|
|
*
|
|
* Getter for #GcalWeatherService:time-zone.
|
|
*/
|
|
GTimeZone*
|
|
gcal_weather_service_get_time_zone (GcalWeatherService *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return self->timezone;
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_set_time_zone:
|
|
* @self: The #GcalWeatherService instance.
|
|
* @days: Number of days.
|
|
*
|
|
* Setter for #GcalWeatherInfos:time-zone.
|
|
*/
|
|
void
|
|
gcal_weather_service_set_time_zone (GcalWeatherService *self,
|
|
GTimeZone *value)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
if (self->timezone == value)
|
|
return;
|
|
|
|
g_clear_pointer (&self->timezone, g_time_zone_unref);
|
|
self->timezone = value ? g_time_zone_ref (value) : NULL;
|
|
|
|
/* make sure we provide correct weather infos */
|
|
gweather_info_update (self->gweather_info);
|
|
|
|
/* make sure midnight is timed correctly: */
|
|
schedule_midnight (self);
|
|
|
|
g_object_notify ((GObject*) self, "time-zone");
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_get_max_days:
|
|
* @self: The #GcalWeatherService instance.
|
|
*
|
|
* Getter for #GcalWeatherService:max-days.
|
|
*/
|
|
guint
|
|
gcal_weather_service_get_max_days (GcalWeatherService *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), 0);
|
|
|
|
return self->max_days;
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_get_valid_timespan:
|
|
* @self: The #GcalWeatherService instance.
|
|
*
|
|
* Getter for #GcalWeatherService:valid-interval.
|
|
*/
|
|
gint64
|
|
gcal_weather_service_get_valid_timespan (GcalWeatherService *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), 0);
|
|
return self->valid_timespan;
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_get_check_interval_new:
|
|
* @self: The #GcalWeatherService instance.
|
|
*
|
|
* Getter for #GcalWeatherService:check-interval-new.
|
|
*/
|
|
guint
|
|
gcal_weather_service_get_check_interval_new (GcalWeatherService *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), 0);
|
|
|
|
return self->check_interval_new;
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_get_check_interval_renew:
|
|
* @self: The #GcalWeatherService instance.
|
|
*
|
|
* Getter for #GcalWeatherService:check-interval-renew.
|
|
*/
|
|
guint
|
|
gcal_weather_service_get_check_interval_renew (GcalWeatherService *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), 0);
|
|
|
|
return self->check_interval_renew;
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_get_weather_infos:
|
|
* @self: The #GcalWeatherService instance.
|
|
*
|
|
* Returns: (transfer none): list of known weather reports.
|
|
*/
|
|
GSList*
|
|
gcal_weather_service_get_weather_infos (GcalWeatherService *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), NULL);
|
|
|
|
return self->weather_infos;
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_get_attribution:
|
|
* @self: The #GcalWeatherService instance.
|
|
*
|
|
* Returns weather service attribution.
|
|
*
|
|
* Returns: (nullable) (transfer none): Text to display.
|
|
*/
|
|
const gchar*
|
|
gcal_weather_service_get_attribution (GcalWeatherService *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), NULL);
|
|
|
|
if (self->gweather_info != NULL)
|
|
return gweather_info_get_attribution (self->gweather_info);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_update:
|
|
* @self: The #GcalWeatherService instance.
|
|
*
|
|
* Tries to update weather reports.
|
|
*/
|
|
void
|
|
gcal_weather_service_update (GcalWeatherService *self)
|
|
{
|
|
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
|
|
|
|
if (self->gweather_info != NULL)
|
|
{
|
|
gweather_info_update (self->gweather_info);
|
|
update_timeout_interval (self);
|
|
|
|
if (gcal_timer_is_running (self->duration_timer))
|
|
gcal_timer_reset (self->duration_timer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_run:
|
|
* @self: The #GcalWeatherService instance.
|
|
* @location: (nullable): A fixed location or %NULL to use Gclue.
|
|
*
|
|
* Starts to monitor location and weather changes.
|
|
* Use ::weather-changed to catch responses.
|
|
*/
|
|
void
|
|
gcal_weather_service_run (GcalWeatherService *self,
|
|
GWeatherLocation *location)
|
|
{
|
|
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
|
|
|
|
g_debug ("Starting weather service");
|
|
|
|
if (self->location_service_running || self->weather_service_running)
|
|
gcal_weather_service_stop (self);
|
|
|
|
if (!location)
|
|
{
|
|
/* Start location and weather service: */
|
|
self->location_service_running = TRUE;
|
|
self->weather_service_running = TRUE;
|
|
|
|
g_cancellable_cancel (self->location_cancellable);
|
|
g_cancellable_reset (self->location_cancellable);
|
|
|
|
gclue_simple_new (DESKTOP_FILE_NAME,
|
|
GCLUE_ACCURACY_LEVEL_EXACT,
|
|
self->location_cancellable,
|
|
(GAsyncReadyCallback) on_gclue_simple_creation_cb,
|
|
g_object_ref (self));
|
|
}
|
|
else
|
|
{
|
|
/* Use the given location to retrieve weather information: */
|
|
self->location_service_running = FALSE;
|
|
self->weather_service_running = TRUE;
|
|
|
|
/*_update_location starts timer if necessary */
|
|
update_location (self, location);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gcal_weather_service_stop:
|
|
* @self: The #GcalWeatherService instance.
|
|
*
|
|
* Stops the service. Returns gracefully if service is
|
|
* not running.
|
|
*/
|
|
void
|
|
gcal_weather_service_stop (GcalWeatherService *self)
|
|
{
|
|
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self));
|
|
|
|
g_debug ("Stop weather service");
|
|
|
|
if (!self->location_service_running && !self->weather_service_running)
|
|
return ;
|
|
|
|
self->location_service_running = FALSE;
|
|
self->weather_service_running = FALSE;
|
|
|
|
/* Notify all listeners about unknown location */
|
|
update_location (self, NULL);
|
|
|
|
if (!self->location_service)
|
|
{
|
|
/* location service is under construction. Cancel creation. */
|
|
g_cancellable_cancel (self->location_cancellable);
|
|
}
|
|
else
|
|
{
|
|
gclue_client_call_stop (gclue_simple_get_client (self->location_service),
|
|
NULL,
|
|
(GAsyncReadyCallback) on_gclue_client_stop,
|
|
g_steal_pointer (&self->location_service));
|
|
}
|
|
}
|