From 83bec27ad579c0e67adff010312d30d74365aa05 Mon Sep 17 00:00:00 2001 From: Florian Brosch Date: Thu, 12 Oct 2017 23:51:57 +0200 Subject: [PATCH] Initial weather service. This commit introduces two new classes: 1. GCalWeatherService 2. GCalWeatherInfo GcalWeatherservice picks-up the current location and periodically fetches weather reports represented by GcalWeatherInfo. The weather part is not complete yet. --- data/meson.build | 1 + meson.build | 6 +- src/gcal-weather-info.c | 336 ++++++++++++++++ src/gcal-weather-info.h | 52 +++ src/gcal-weather-service.c | 779 +++++++++++++++++++++++++++++++++++++ src/gcal-weather-service.h | 47 +++ src/meson.build | 5 + 7 files changed, 1225 insertions(+), 1 deletion(-) create mode 100644 src/gcal-weather-info.c create mode 100644 src/gcal-weather-info.h create mode 100644 src/gcal-weather-service.c create mode 100644 src/gcal-weather-service.h diff --git a/data/meson.build b/data/meson.build index 34be45e5..4c5e0a7c 100644 --- a/data/meson.build +++ b/data/meson.build @@ -2,6 +2,7 @@ subdir('icons') subdir('appdata') # Desktop files +# Note: This name is registered in gcal-weather-service.c. desktop = 'org.gnome.Calendar.desktop' i18n.merge_file( diff --git a/meson.build b/meson.build index 52bb0f15..b3ed0d7e 100644 --- a/meson.build +++ b/meson.build @@ -130,7 +130,8 @@ common_flags = [ '-DPACKAGE_DATA_DIR="@0@"'.format(calendar_pkgdatadir), '-DUI_DATA_DIR="@0@"'.format(join_paths(calendar_datadir), 'style'), '-DEDS_DISABLE_DEPRECATED', - '-DGOA_API_IS_SUBJECT_TO_CHANGE' + '-DGOA_API_IS_SUBJECT_TO_CHANGE', + '-DGWEATHER_I_KNOW_THIS_IS_UNSTABLE' ] test_cflags = ['-Wno-sign-compare'] @@ -175,6 +176,9 @@ glib_dep = dependency('glib-2.0', version: '>= 2.43.4') gtk_dep = dependency('gtk+-3.0', version: '>= 3.22.20') gio_dep = dependency('gio-2.0', version: '>= 2.43.4') goa_dep = dependency('goa-1.0', version: '>= 3.2.0') +gweather_dep = dependency('gweather-3.0', version: '>= 3.24.0') +geoclue_dep = dependency('libgeoclue-2.0', version: '>=2.4') +geocode_dep = dependency('geocode-glib-1.0', version: '>=3.23') m_dep = cc.find_library('m') configure_file( diff --git a/src/gcal-weather-info.c b/src/gcal-weather-info.c new file mode 100644 index 00000000..08efa372 --- /dev/null +++ b/src/gcal-weather-info.c @@ -0,0 +1,336 @@ +/* gcal-weather-info.c + * + * Copyright (C) 2017 Florian Brosch + * + * 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 . + */ + +#include +#include "gcal-weather-info.h" + +static void gcal_weather_info_set_date (GcalWeatherInfo *self, + GDate *date); + +static void gcal_weather_info_set_temperature (GcalWeatherInfo *self, + const gchar *temperature); + +static void gcal_weather_info_set_icon_name (GcalWeatherInfo *self, + const gchar *icon_name); + + +/* _GcalWeatherInfo: + * @date: (not nullable): The day this information belongs to. + * @name: (not nullable): Icon name if weather informations are available or NULL. + * @temperature: (not nullable): Temperature string including right units. + * + * Data type holding weather information. + * + * Geometric information and @image should only be set + * inside widgets. + */ +struct _GcalWeatherInfo { + GObject parent_instance; + + /* */ + GDate date; /* owned, non-null */ + gchar *icon_name; /* owned, non-null */ + gchar *temperature; /* owned, non-null */ +}; + + +G_DEFINE_TYPE (GcalWeatherInfo, gcal_weather_info, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_DATE, + PROP_ICON_NAME, + PROP_TEMPERATURE, + PROP_NUM, +}; + + + +/* < gobject setup > */ + +static void +gcal_weather_info_finalize (GObject *object) +{ + GcalWeatherInfo *self; /* unowned */ + + self = (GcalWeatherInfo *) object; + + g_date_clear (&self->date, 1); + g_free (self->icon_name); + g_free (self->temperature); + + G_OBJECT_CLASS (gcal_weather_info_parent_class)->finalize (object); +} + + + +static void +gcal_weather_info_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GcalWeatherInfo *self; /* unowned */ + + self = GCAL_WEATHER_INFO (object); + switch (property_id) + { + case PROP_DATE: + g_value_set_boxed (value, &self->date); + break; + case PROP_ICON_NAME: + g_value_set_string (value, gcal_weather_info_get_icon_name (self)); + break; + case PROP_TEMPERATURE: + g_value_set_string (value, gcal_weather_info_get_temperature (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + + +static void +gcal_weather_info_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalWeatherInfo *self; /* unowned */ + + self = GCAL_WEATHER_INFO (object); + switch (property_id) + { + case PROP_DATE: + gcal_weather_info_set_date (self, g_value_get_boxed (value)); + break; + + case PROP_ICON_NAME: + gcal_weather_info_set_icon_name (self, g_value_get_string (value)); + break; + + case PROP_TEMPERATURE: + gcal_weather_info_set_temperature (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + + +static void +gcal_weather_info_class_init (GcalWeatherInfoClass *klass) +{ + GObjectClass *object_class; /* unowned */ + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = gcal_weather_info_finalize; + object_class->get_property = gcal_weather_info_get_property; + object_class->set_property = gcal_weather_info_set_property; + + const gint pflags_rwc = G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | + G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT; + + /** + * GcalWeatherInfo:date: + * + * The non-nullable date weather information belongs to. + */ + g_object_class_install_property (object_class, + PROP_DATE, + g_param_spec_boxed ("date", "date", "date", G_TYPE_DATE, pflags_rwc)); + /** + * GcalWeatherInfo:icon-name: + * + * Non-nullable Icon name representing the weather. + */ + g_object_class_install_property (object_class, + PROP_ICON_NAME, + g_param_spec_string ("icon-name", "icon-name", "icon-name", NULL, pflags_rwc)); + /** + * GcalWeatherInfo:temperature: + * + * The temperature as string or %NULL. + */ + g_object_class_install_property (object_class, + PROP_TEMPERATURE, + g_param_spec_string ("temperature", "temperature", "temperature", NULL, pflags_rwc)); +} + + + +static void +gcal_weather_info_init (GcalWeatherInfo *self) +{ + g_date_clear (&self->date, 1); + self->icon_name = NULL; + self->temperature = NULL; +} + + + +/* < private > */ + +/* gcal_weather_info_set_date: + * @self: A #GcalWeatherInfo instance. + * @date: The date to set. + * + * Setter for :date. + */ +static void +gcal_weather_info_set_date (GcalWeatherInfo *self, + GDate *date) +{ + g_return_if_fail (GCAL_IS_WEATHER_INFO (self)); + g_return_if_fail (date != NULL); + + self->date = *date; +} + + + +/* gcal_weather_info_set_temperature: + * @self: A #GcalWeatherInfo instance. + * @temperature: A weather string. + * + * Setter for #GcalWeatherInfo:temperature. + */ +static void +gcal_weather_info_set_temperature (GcalWeatherInfo *self, + const gchar *temperature) +{ + g_return_if_fail (GCAL_IS_WEATHER_INFO (self)); + g_return_if_fail (temperature != NULL); + + if (g_strcmp0 (self->temperature, temperature) != 0) + { + g_free (self->temperature); + self->temperature = g_strdup (temperature); + g_object_notify ((GObject*) self, "temperature"); + } +} + + + +/* gcal_weather_info_set_icon_name: + * @self: A #GcalWeatherInfo instance. + * @icon_name: The name of the icon to display. + * + * Setter for #GcalWeatherInfo:icon_name. + */ +static void +gcal_weather_info_set_icon_name (GcalWeatherInfo *self, + const gchar *icon_name) +{ + g_return_if_fail (GCAL_IS_WEATHER_INFO (self)); + g_return_if_fail (icon_name != NULL); + + if (g_strcmp0 (self->icon_name, icon_name) != 0) + { + g_free (self->icon_name); + self->icon_name = g_strdup (icon_name); + g_object_notify ((GObject*) self, "icon-name"); + } +} + + +/* < public > */ + +/** + * gcal_weather_info_new: + * @date: (not nullable): The date weather infos belong to. + * @icon_name: (not nullable): The icon name indicating the weather. + * @temperature: (nullable): The temperature to display. + * + * Creates a new #GcalWeatherInfo. + * + * @Returns: (transfer full): A newly allocated #GcalWeatherInfo. + */ +GcalWeatherInfo* +gcal_weather_info_new (GDate *date, + const gchar *icon_name, + const gchar *temperature) +{ + GcalWeatherInfo *info; /* owned */ + + g_return_val_if_fail (date != NULL, NULL); + g_return_val_if_fail (icon_name != NULL, NULL); + g_return_val_if_fail (temperature != NULL, NULL); + + info = (GcalWeatherInfo*) g_object_new (GCAL_TYPE_WEATHER_INFO, + "date", date, + "icon-name", icon_name, + "temperature", temperature, + NULL); + return info; +} + + + +/** + * gcal_weather_info_get_date: + * @self: A #GcalWeatherInfo instance. + * @date: (out) (not nullable): The associated day. + * + * Getter for #GcalWeatherInfo:date. + */ +void +gcal_weather_info_get_date (GcalWeatherInfo *self, + GDate *date) +{ + g_return_if_fail (GCAL_IS_WEATHER_INFO (self)); + + *date = self->date; +} + + + +/** + * gcal_weather_info_get_icon_name: + * @self: A #GcalWeatherInfo instance. + * + * Getter for #GcalWeatherInfo:icon_name. + */ +const gchar* +gcal_weather_info_get_icon_name (GcalWeatherInfo *self) +{ + g_return_val_if_fail (GCAL_IS_WEATHER_INFO (self), NULL); + + return self->icon_name; +} + + + +/** + * gcal_weather_info_get_temperature: + * @self: A #GcalWeatherInfo instance. + * + * Getter for #GcalWeatherInfo:temperature. + */ +const gchar* +gcal_weather_info_get_temperature (GcalWeatherInfo *self) +{ + g_return_val_if_fail (GCAL_IS_WEATHER_INFO (self), NULL); + + return self->temperature; +} diff --git a/src/gcal-weather-info.h b/src/gcal-weather-info.h new file mode 100644 index 00000000..d2e73447 --- /dev/null +++ b/src/gcal-weather-info.h @@ -0,0 +1,52 @@ +/* gcal-weather-info.h + * + * Copyright (C) 2017 Florian Brosch + * + * 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 . + */ + +#ifndef __GCAL_WEATHER_INFO_H__ +#define __GCAL_WEATHER_INFO_H__ + +#include +#include + + +G_BEGIN_DECLS + +#define GCAL_TYPE_WEATHER_INFO (gcal_weather_info_get_type()) + +G_DECLARE_FINAL_TYPE (GcalWeatherInfo, gcal_weather_info, GCAL, WEATHER_INFO, GObject) + + +GcalWeatherInfo* gcal_weather_info_new (GDate *date, + const gchar *icon_name, + const gchar *temperature); + +void gcal_weather_info_get_date (GcalWeatherInfo *self, + GDate *date); + +void gcal_weather_info_get_date (GcalWeatherInfo *self, + GDate *date); + +const gchar* gcal_weather_info_get_icon_name (GcalWeatherInfo *self); + +const gchar* gcal_weather_info_get_temperature (GcalWeatherInfo *self); + + +G_END_DECLS + +#endif /* __GCAL_WEATHER_INFO_H__ */ + + diff --git a/src/gcal-weather-service.c b/src/gcal-weather-service.c new file mode 100644 index 00000000..6346e0c4 --- /dev/null +++ b/src/gcal-weather-service.c @@ -0,0 +1,779 @@ +/* gcal-weather-service.c + * + * Copyright (C) 2017 - Florian Brosch + * + * 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 . + */ + +#define DESKTOP_FILE_NAME "org.gnome.Calendar" + +#include +#include +#include + +#include "gcal-weather-service.h" + + +G_BEGIN_DECLS + +/* GcalWeatherService: + * + * @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. + * @weather_info: The weather info to query. + * + * 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; + + /* */ + + /* */ + /* timer: */ + guint check_interval; + guint timeout_id; + + /* locations: */ + GClueSimple *location_service; /* owned, nullable */ + GCancellable *location_cancellable; /* owned, non-null */ + gboolean location_service_running; + + /* weather: */ + GWeatherInfo *weather_info; /* owned, nullable */ + guint max_days; +}; + + + +enum +{ + PROP_0, + PROP_MAX_DAYS, + PROP_CHECK_INTERVAL, + PROP_NUM, +}; + +enum +{ + SIG_WEATHER_CHANGED, + SIG_NUM, +}; + +static guint gcal_weather_service_signals[SIG_NUM] = { 0 }; + + +G_DEFINE_TYPE (GcalWeatherService, gcal_weather_service, G_TYPE_OBJECT) + + +/* Internal location API and callbacks: */ +static void gcal_weather_service_update_location (GcalWeatherService *self, + GClueLocation *location); + +static char* gcal_weather_service_get_location_name (GClueLocation *location); + +static void on_gclue_simple_creation (GClueSimple *source, + GAsyncResult *result, + GcalWeatherService *data); + +static void on_gclue_location_changed (GClueLocation *location, + GcalWeatherService *self); + +static void on_gclue_client_activity_changed (GClueClient *client, + GcalWeatherService *self); + +static void on_gclue_client_stop (GClueClient *client, + GAsyncResult *res, + GClueSimple *simple); + +/* Internal Wweather API */ +static void gcal_weather_service_set_max_days (GcalWeatherService *self, + guint days); + +static void gcal_weather_service_update_weather (GWeatherInfo *info, + GcalWeatherService *self); + +/* Internal weather update timer API and callbacks */ +static void gcal_weather_service_set_check_interval (GcalWeatherService *self, + guint check_interval); + +static void gcal_weather_service_timer_stop (GcalWeatherService *self); + +static void gcal_weather_service_timer_start (GcalWeatherService *self); + +static gboolean on_timer_timeout (GcalWeatherService *self); + + +G_END_DECLS + + +/******************** + * < gobject setup > + *******************/ + +static void +gcal_weather_service_finalize (GObject *object) +{ + GcalWeatherService *self; /* unowned */ + + self = (GcalWeatherService *) object; + + if (self->location_service != NULL) + g_clear_object (&self->location_service); + + g_cancellable_cancel (self->location_cancellable); + g_clear_object (&self->location_cancellable); + + if (self->weather_info != NULL) + g_clear_object (&self->weather_info); + + 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; /* unowned */ + + self = GCAL_WEATHER_SERVICE (object); + switch (prop_id) + { + case PROP_MAX_DAYS: + g_value_set_uint (value, gcal_weather_service_get_max_days (self)); + break; + case PROP_CHECK_INTERVAL: + g_value_set_uint (value, gcal_weather_service_get_check_interval (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; /* unowned */ + + self = GCAL_WEATHER_SERVICE (object); + switch (prop_id) + { + case PROP_MAX_DAYS: + gcal_weather_service_set_max_days (self, g_value_get_uint (value)); + break; + case PROP_CHECK_INTERVAL: + gcal_weather_service_set_check_interval (self, g_value_get_uint (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; /* unowned */ + + 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; + + const gint prop_flags = G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB | G_PARAM_READABLE | + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT; + + /** + * GcalWeatherServiceClass: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", + 0, G_MAXUINT, 0, prop_flags)); + /** + * GcalWeatherServiceClass:check-interval: + * + * Amount of seconds to wait before re-fetching weather infos. + * Use %0 to disable timers. + */ + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_CHECK_INTERVAL, + g_param_spec_uint ("check-interval", "check-interval", "check-interval", + 0, G_MAXUINT, 0, prop_flags)); + + /** + * GcalWeatherService::weather-changed: + * @sender: The #GcalWeatherService + * @gwi: A SList containing updated #GcalWeatherInfos. + * @self: data pointer. + * + * Triggered on weather changes. + */ + gcal_weather_service_signals[SIG_WEATHER_CHANGED] + = g_signal_new ("weather-changed", + GCAL_TYPE_WEATHER_SERVICE, + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); +} + + + +static void +gcal_weather_service_init (GcalWeatherService *self) +{ + self->check_interval = 0; + self->timeout_id = 0; + self->location_cancellable = g_cancellable_new (); + self->location_service_running = FALSE; + self->location_service = NULL; + self->weather_info = NULL; + self->max_days = 0; +} + + + +/************** + * < private > + **************/ + +/** + * gcal_weather_service_update_location: + * @self: The #GcalWeatherService instance. + * @location: (nullable) (element-type Gcal.WeatherInfo): The location we want weather information for. + * + * Registers the location to retrieve weather information from. + */ +static void +gcal_weather_service_update_location (GcalWeatherService *self, + GClueLocation *location) +{ + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + g_return_if_fail (location == NULL || GCLUE_IS_LOCATION (location)); + + if (self->weather_info != NULL) + { + gcal_weather_service_timer_stop (self); + g_clear_object (&self->weather_info); + } + + if (location == NULL) + { + g_info ("Could not retrieve current location."); + gcal_weather_service_update_weather (NULL, self); + } + else + { + GWeatherLocation *wlocation; /* owned */ + g_autofree gchar *loc_name = NULL; + gdouble latitude; + gdouble longitude; + + loc_name = gcal_weather_service_get_location_name (location); + latitude = gclue_location_get_latitude (location); + longitude = gclue_location_get_longitude (location); + wlocation = gweather_location_new_detached (loc_name, NULL, latitude, longitude); + + self->weather_info = gweather_info_new (wlocation, GWEATHER_FORECAST_ZONE); + + gweather_info_set_enabled_providers (self->weather_info, GWEATHER_PROVIDER_ALL); + g_signal_connect (self->weather_info, "updated", (GCallback) gcal_weather_service_update_weather, self); + gweather_info_update (self->weather_info); + + gcal_weather_service_timer_start (self); + gweather_location_unref (wlocation); + } +} + + + +/* gcal_weather_service_get_location_name: + * @location: A #GClueLocation to get the city name from. + * + * Queries the city name for the given location or %NULL. + * + * @Returns: (transfer full): City name or %NULL. + */ +static char* +gcal_weather_service_get_location_name (GClueLocation *location) +{ + g_autoptr (GeocodeLocation) glocation = NULL; + g_autoptr (GeocodeReverse) greverse = NULL; + g_autoptr (GeocodePlace) gplace = NULL; + + g_return_val_if_fail (GCLUE_IS_LOCATION (location), NULL); + + glocation = geocode_location_new (gclue_location_get_latitude (location), + gclue_location_get_longitude (location), + gclue_location_get_accuracy (location)); + + greverse = geocode_reverse_new_for_location (glocation); + + gplace = geocode_reverse_resolve (greverse, NULL); + if (gplace == NULL) + return NULL; + + return g_strdup (geocode_place_get_town (gplace)); +} + + + +/* on_gclue_simple_creation: + * @source: + * @result: Result of gclue_simple_new(). + * @self: (transfer full): A GcalWeatherService reference. + * + * Callback used in gcal_weather_service_run(). + */ +static void +on_gclue_simple_creation (GClueSimple *_source, + GAsyncResult *result, + GcalWeatherService *self) +{ + GClueSimple *location_service; /* owned */ + GClueLocation *location; /* unowned */ + GClueClient *client; /* unowned */ + g_autoptr (GError) error = NULL; + + g_return_if_fail (G_IS_ASYNC_RESULT (result)); + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + g_return_if_fail (self->location_service_running); + + /* 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 != NULL) + { + g_assert (location_service == NULL); + + if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED) + /* Cancelled during creation. Silently fail. */; + else + g_warning ("Could not create GCLueSimple: %s", error->message); + + g_object_unref (self); + return; + } + + g_assert (self->location_service == NULL); + g_assert (location_service != NULL); + + 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 != NULL) + { + gcal_weather_service_update_location (self, location); + + + g_signal_connect_object (location, + "notify::location", + G_CALLBACK (on_gclue_location_changed), + self, + 0); + } + + g_signal_connect_object (client, + "notify::active", + G_CALLBACK (on_gclue_client_activity_changed), + self, + 0); + + g_object_unref (self); +} + + + +/* on_gclue_location_changed: + * @location: #GClueLocation owned by @self + * @self: The #GcalWeatherService + * + * Handles location changes. + */ +static void +on_gclue_location_changed (GClueLocation *location, + GcalWeatherService *self) +{ + g_return_if_fail (GCLUE_IS_LOCATION (location)); + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + g_return_if_fail (self->location_service_running); + + gcal_weather_service_update_location (self, location); +} + + + +/* on_gclue_client_activity_changed: + * @client: The #GClueclient ownd by @self + * @self: The #GcalWeatherService + * + * Handles location client activity changes. + */ +static void +on_gclue_client_activity_changed (GClueClient *client, + GcalWeatherService *self) +{ + g_return_if_fail (GCLUE_IS_CLIENT (client)); + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + g_return_if_fail (self->location_service_running); + + /* Notify listeners about unknown locations: */ + gcal_weather_service_update_location (self, NULL); +} + + + +/* 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; + + g_return_if_fail (GCLUE_IS_CLIENT (client)); + g_return_if_fail (G_IS_ASYNC_RESULT (res)); + g_return_if_fail (GCLUE_IS_SIMPLE (simple)); + + 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); +} + + + +/* gcal_weather_service_set_max_days: + * @self: The #GcalWeatherService instance. + * @days: Number of days. + * + * Setter for #GcalWeatherInfos:max-days. + */ +static void +gcal_weather_service_set_max_days (GcalWeatherService *self, + guint days) +{ + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + + self->max_days = days; + + g_object_notify ((GObject*) self, "max-days"); +} + + + +/* gcal_weather_service_update_weather: + * @info: (nullable): Newly received weather information or %NULL. + * @self: A #GcalWeatherService instance. + * + * Retrieves weather information for @location and triggers + * #GcalWeatherService::weather-changed. + */ +static void +gcal_weather_service_update_weather (GWeatherInfo *info, + GcalWeatherService *self) +{ + GSList* infos = NULL; + + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + + if (info == NULL) + { + g_info ("Could not retrieve valid weather"); + } + else if (gweather_info_is_valid (info)) + { + // TODO + } + else + { + g_autofree gchar* location_name = gweather_info_get_location_name (info); + g_info ("Could not retrieve valid weather for location '%s'", location_name); + } + + g_signal_emit (self, gcal_weather_service_signals[SIG_WEATHER_CHANGED], 0, infos); +} + + + +/* gcal_weather_service_set_max_days: + * @self: The #GcalWeatherService instance. + * @days: Number of days. + * + * Setter for GcalWeatherInfos:max-days. + */ +static void +gcal_weather_service_set_check_interval (GcalWeatherService *self, + guint interval) +{ + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + + self->check_interval = interval; + + g_object_notify ((GObject*) self, "check-interval"); +} + + + +/* gcal_weather_service_timer_stop: + * @self: The #GcalWeatherService instance. + * + * Stops the internal timer. Does nothing if + * timer is not running. + * + * This timer is used to update weather information + * every :check-interval seconds + */ +static void +gcal_weather_service_timer_stop (GcalWeatherService *self) +{ + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + + if (self->timeout_id > 0) + { + g_source_remove (self->timeout_id); + self->timeout_id = 0; + } +} + + + +/* gcal_weather_service_timer_start + * @self: The #GcalWeatherService instance. + * + * Starts the internal timer. Does nothing if + * timer is already running. + * + * Timer is used to update weather information + * every :check-interval seconds. + */ +static void +gcal_weather_service_timer_start (GcalWeatherService *self) +{ + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + + if (self->check_interval == 0) + /* timer is disabled */ + return; + + if (self->timeout_id > 0) + /* timer is already running */ + return; + + self->timeout_id = g_timeout_add_seconds (self->check_interval, + (GSourceFunc) on_timer_timeout, + self); +} + + + +/* on_timer_timeout + * @self: A #GcalWeatherService. + * + * Handles scheduled weather report updates. + * + * Returns: %G_SOURCE_CONTINUE + */ +static gboolean +on_timer_timeout (GcalWeatherService *self) +{ + g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), G_SOURCE_REMOVE); + + if (self->weather_info != NULL) + gweather_info_update (self->weather_info); + + return G_SOURCE_CONTINUE; +} + + + +/************* + * < public > + *************/ + +/** + * gcal_weather_service_new: + * @max_days: mumber of days to fetch forecasts for. + * @check_interval: seconds between checks for new weather information or %0 to disable checks. + * + * 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 (guint max_days, + guint check_interval) +{ + return g_object_new (GCAL_TYPE_WEATHER_SERVICE, + "max-days", + max_days, + "check-interval", + check_interval, + NULL); +} + + + +/** + * gcal_weather_service_run: + * @self: The #GcalWeatherService instance. + * + * Starts to monitor location and weather changes. + * Use ::weather-changed to catch responses. + */ +void +gcal_weather_service_run (GcalWeatherService *self) +{ + g_return_if_fail (GCAL_IS_WEATHER_SERVICE (self)); + + if (self->location_service_running) + return ; + + self->location_service_running = TRUE; + + g_assert (self->location_service == NULL); + + if (self->max_days > 0) + { + 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, + g_object_ref (self)); + } +} + + + +/** + * 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)); + + if (!self->location_service_running) + return ; + + self->location_service_running = FALSE; + + + gcal_weather_service_timer_stop (self); + + /* Notify all listeners about unknown location: */ + gcal_weather_service_update_location (self, NULL); + + if (self->location_service == NULL) + { + /* location service is under construction. Cancel creation. */ + g_cancellable_cancel (self->location_cancellable); + } + else + { + GClueClient *client; /* owned */ + + client = gclue_simple_get_client (self->location_service); + self->location_service = NULL; + + gclue_client_call_stop (client, + NULL, + (GAsyncReadyCallback) on_gclue_client_stop, + client); /* transfres ownership */ + } +} + + + +/** + * 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_max_days: + * @self: The #GcalWeatherService instance. + * + * Getter for #GcalWeatherService:max-days. + */ +guint +gcal_weather_service_get_check_interval (GcalWeatherService *self) +{ + g_return_val_if_fail (GCAL_IS_WEATHER_SERVICE (self), 0); + + return self->check_interval; +} diff --git a/src/gcal-weather-service.h b/src/gcal-weather-service.h new file mode 100644 index 00000000..22100f6e --- /dev/null +++ b/src/gcal-weather-service.h @@ -0,0 +1,47 @@ +/* gcal-weather-service.h + * + * Copyright (C) 2017 - Florian Brosch + * + * 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 . + */ + +#ifndef GCAL_WEATHER_SERVICE_H +#define GCAL_WEATHER_SERVICE_H + +#include +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_WEATHER_SERVICE (gcal_weather_service_get_type()) + +G_DECLARE_FINAL_TYPE (GcalWeatherService, gcal_weather_service, GCAL, WEATHER_SERVICE, GObject) + + +GcalWeatherService* gcal_weather_service_new (guint max_days, + guint check_interval); + +void gcal_weather_service_run (GcalWeatherService *self); + +void gcal_weather_service_stop (GcalWeatherService *self); + +guint gcal_weather_service_get_max_days (GcalWeatherService *self); + +guint gcal_weather_service_get_check_interval (GcalWeatherService *self); + + +G_END_DECLS + +#endif /* GCAL_WEATHER_SERVICE_H */ + diff --git a/src/meson.build b/src/meson.build index 3b30e80b..24b1da43 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,6 +15,9 @@ gcal_deps = [ gtk_dep, gio_dep, goa_dep, + gweather_dep, + geoclue_dep, + geocode_dep, m_dep, ] @@ -47,6 +50,8 @@ sources = files( 'gcal-time-selector.c', 'gcal-utils.c', 'gcal-window.c', + 'gcal-weather-service.c', + 'gcal-weather-info.c', ) gnome.mkenums(