[gnome-calendar/wip/flb/weather-forecast: 1/50] Initial weather service.



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]