[gnome-calendar/wip/flb/weather-forecast: 1/50] Initial weather service.
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-calendar/wip/flb/weather-forecast: 1/50] Initial weather service.
- Date: Tue, 31 Oct 2017 08:17:54 +0000 (UTC)
commit fda1f9f04b1dafef9c1fce007931e14764b1f317
Author: Florian Brosch <flo brosch gmail com>
Date: Thu Oct 12 23:51:57 2017 +0200
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 deletions(-)
---
diff --git a/data/meson.build b/data/meson.build
index baa50cb..0a29b91 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 054ae29..15862d6 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']
@@ -174,6 +175,9 @@ glib_dep = dependency('glib-2.0', version: '>= 2.43.4')
gtk_dep = dependency('gtk+-3.0', version: '>= 3.21.6')
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 0000000..08efa37
--- /dev/null
+++ b/src/gcal-weather-info.c
@@ -0,0 +1,336 @@
+/* gcal-weather-info.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/>.
+ */
+
+#include <string.h>
+#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;
+
+ /* <private> */
+ 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 0000000..d2e7344
--- /dev/null
+++ b/src/gcal-weather-info.h
@@ -0,0 +1,52 @@
+/* gcal-weather-info.h
+ *
+ * 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/>.
+ */
+
+#ifndef __GCAL_WEATHER_INFO_H__
+#define __GCAL_WEATHER_INFO_H__
+
+#include <glib-object.h>
+#include <glib.h>
+
+
+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 0000000..6346e0c
--- /dev/null
+++ b/src/gcal-weather-service.c
@@ -0,0 +1,779 @@
+/* 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 DESKTOP_FILE_NAME "org.gnome.Calendar"
+
+#include <geocode-glib/geocode-glib.h>
+#include <libgweather/gweather.h>
+#include <geoclue.h>
+
+#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;
+
+ /* <public> */
+
+ /* <private> */
+ /* 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 0000000..22100f6
--- /dev/null
+++ b/src/gcal-weather-service.h
@@ -0,0 +1,47 @@
+/* gcal-weather-service.h
+ *
+ * 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/>.
+ */
+
+#ifndef GCAL_WEATHER_SERVICE_H
+#define GCAL_WEATHER_SERVICE_H
+
+#include <glib-object.h>
+#include <glib.h>
+
+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 b5a29e2..085856e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -14,6 +14,9 @@ gcal_deps = [
gtk_dep,
gio_dep,
goa_dep,
+ gweather_dep,
+ geoclue_dep,
+ geocode_dep,
m_dep,
]
@@ -45,6 +48,8 @@ sources = files(
'gcal-time-selector.c',
'gcal-utils.c',
'gcal-window.c',
+ 'gcal-weather-service.c',
+ 'gcal-weather-info.c',
)
gnome.mkenums(
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]