From a1cb4d7802e6e024c842f2a4fa41b91b67162698 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 17 Dec 2013 22:00:51 -0600 Subject: add timezone-geoclue + tests --- include/datetime/timezone-geoclue.h | 65 ++++++++++ src/timezone-geoclue.cpp | 248 ++++++++++++++++++++++++++++++++++++ tests/test-timezone-geoclue.cc | 97 ++++++++++++++ 3 files changed, 410 insertions(+) create mode 100644 include/datetime/timezone-geoclue.h create mode 100644 src/timezone-geoclue.cpp create mode 100644 tests/test-timezone-geoclue.cc diff --git a/include/datetime/timezone-geoclue.h b/include/datetime/timezone-geoclue.h new file mode 100644 index 0000000..382b3cc --- /dev/null +++ b/include/datetime/timezone-geoclue.h @@ -0,0 +1,65 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_GEOCLUE_TIMEZONE_H +#define INDICATOR_DATETIME_GEOCLUE_TIMEZONE_H + +#include // base class + +#include + +#include +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A #Timezone that gets its information from asking GeoClue + */ +class GeoclueTimezone: public Timezone +{ +public: + GeoclueTimezone(); + ~GeoclueTimezone(); + +private: + static void on_bus_got (GObject*, GAsyncResult*, gpointer); + static void on_client_created (GObject*, GAsyncResult*, gpointer); + static void on_address_changed (GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant*, gpointer); + static void on_requirements_set (GObject*, GAsyncResult*, gpointer); + static void on_address_started (GObject*, GAsyncResult*, gpointer); + static void on_address_got (GObject*, GAsyncResult*, gpointer); + void setTimezoneFromAddressVariant (GVariant*); + static GVariant * call_finish (GObject*, GAsyncResult*); + + GCancellable * cancellable_ = nullptr; + GDBusConnection * connection_ = nullptr; + std::string client_object_path_; + guint signal_subscription_ = 0; +}; + + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_GEOCLUE_TIMEZONE_H + diff --git a/src/timezone-geoclue.cpp b/src/timezone-geoclue.cpp new file mode 100644 index 0000000..f6d1f5e --- /dev/null +++ b/src/timezone-geoclue.cpp @@ -0,0 +1,248 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 + +#define GEOCLUE_BUS_NAME "org.freedesktop.Geoclue.Master" + +namespace unity { +namespace indicator { +namespace datetime { + + +GeoclueTimezone::GeoclueTimezone(): + cancellable_(g_cancellable_new()) +{ + g_bus_get(G_BUS_TYPE_SESSION, cancellable_, on_bus_got, this); +} + +GeoclueTimezone::~GeoclueTimezone() +{ + g_cancellable_cancel(cancellable_); + g_object_unref(cancellable_); + + if (signal_subscription_) + g_dbus_connection_signal_unsubscribe(connection_, signal_subscription_); + + g_object_unref(connection_); +} + +/*** +**** +***/ + +void +GeoclueTimezone::on_bus_got(GObject * source G_GNUC_UNUSED, GAsyncResult * res, gpointer gself) +{ + GError * error; + GDBusConnection * connection; + + error = nullptr; + connection = g_bus_get_finish(res, &error); + if (error) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("Couldn't get bus: %s", error->message); + + g_error_free(error); + } + else + { + auto self = static_cast(gself); + + self->connection_ = connection; + + g_dbus_connection_call(self->connection_, + GEOCLUE_BUS_NAME, + "/org/freedesktop/Geoclue/Master", + "org.freedesktop.Geoclue.Master", + "Create", + nullptr, // parameters + G_VARIANT_TYPE("(o)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + self->cancellable_, + on_client_created, + self); + } +} + +void +GeoclueTimezone::on_client_created(GObject * source, GAsyncResult * res, gpointer gself) +{ + GVariant * result; + + if ((result = call_finish(source, res))) + { + auto self = static_cast(gself); + + GVariant * child = g_variant_get_child_value(result, 0); + self->client_object_path_ = g_variant_get_string(child, nullptr); + g_variant_unref(child); + g_variant_unref(result); + + self->signal_subscription_ = g_dbus_connection_signal_subscribe( + self->connection_, + GEOCLUE_BUS_NAME, + "org.freedesktop.Geoclue.Address", // inteface + "AddressChanged", // signal name + self->client_object_path_.c_str(), // object path + nullptr, // arg0 + G_DBUS_SIGNAL_FLAGS_NONE, + on_address_changed, + self, + nullptr); + + g_dbus_connection_call(self->connection_, + GEOCLUE_BUS_NAME, + self->client_object_path_.c_str(), + "org.freedesktop.Geoclue.MasterClient", + "SetRequirements", + g_variant_new("(iibi)", 2, 0, FALSE, 1023), + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, + self->cancellable_, + on_requirements_set, + self); + } +} + +void +GeoclueTimezone::on_address_changed(GDBusConnection * connection G_GNUC_UNUSED, + const gchar * sender_name G_GNUC_UNUSED, + const gchar * object_path G_GNUC_UNUSED, + const gchar * interface_name G_GNUC_UNUSED, + const gchar * signal_name G_GNUC_UNUSED, + GVariant * parameters, + gpointer gself) +{ + static_cast(gself)->setTimezoneFromAddressVariant(parameters); +} + +void +GeoclueTimezone::on_requirements_set(GObject * source, GAsyncResult * res, gpointer gself) +{ + GVariant * result; + + if ((result = call_finish(source, res))) + { + auto self = static_cast(gself); + + g_dbus_connection_call(self->connection_, + GEOCLUE_BUS_NAME, + self->client_object_path_.c_str(), + "org.freedesktop.Geoclue.MasterClient", + "AddressStart", + nullptr, + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, + self->cancellable_, + on_address_started, + self); + + g_variant_unref(result); + } +} + +void +GeoclueTimezone::on_address_started(GObject * source, GAsyncResult * res, gpointer gself) +{ + GVariant * result; + + if ((result = call_finish(source, res))) + { + auto self = static_cast(gself); + + g_dbus_connection_call(self->connection_, + GEOCLUE_BUS_NAME, + self->client_object_path_.c_str(), + "org.freedesktop.Geoclue.Address", + "GetAddress", + nullptr, + G_VARIANT_TYPE("(ia{ss}(idd))"), + G_DBUS_CALL_FLAGS_NONE, + -1, + self->cancellable_, + on_address_got, + self); + + g_variant_unref(result); + } +} + +void +GeoclueTimezone::on_address_got(GObject * source, GAsyncResult * res, gpointer gself) +{ + GVariant * result; + + if ((result = call_finish(source, res))) + { + static_cast(gself)->setTimezoneFromAddressVariant(result); + g_variant_unref(result); + } +} + +void +GeoclueTimezone::setTimezoneFromAddressVariant(GVariant * variant) +{ + g_return_if_fail(g_variant_is_of_type(variant, G_VARIANT_TYPE("(ia{ss}(idd))"))); + + const gchar * timezone_string = nullptr; + GVariant * dict = g_variant_get_child_value(variant, 1); + if (dict) + { + if (g_variant_lookup(dict, "timezone", "&s", &timezone_string)) + timezone.set(timezone_string); + + g_variant_unref(dict); + } +} + +GVariant* +GeoclueTimezone::call_finish(GObject * source, GAsyncResult * res) +{ + GError * error; + GVariant * result; + + error = nullptr; + result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + + if (error) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("AddressStart() failed: %s", error->message); + + g_error_free(error); + + g_clear_pointer(&result, g_variant_unref); + } + + return result; +} + +/**** +***** +****/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + diff --git a/tests/test-timezone-geoclue.cc b/tests/test-timezone-geoclue.cc new file mode 100644 index 0000000..a577fbd --- /dev/null +++ b/tests/test-timezone-geoclue.cc @@ -0,0 +1,97 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 "geoclue-fixture.h" + +#include + +//#include + +using unity::indicator::datetime::GeoclueTimezone; + +/*** +**** +***/ + +class TimezoneGeoclueFixture : public GeoclueFixture +{ +}; + +#if 0 +namespace +{ + struct EmitAddressChangedData + { + DbusTestDbusMock * mock = nullptr; + DbusTestDbusMockObject * obj_client = nullptr; + std::string timezone; + EmitAddressChangedData(DbusTestDbusMock * mock_, + DbusTestDbusMockObject * obj_client_, + const std::string& timezone_): mock(mock_), obj_client(obj_client_), timezone(timezone_) {} + }; + + gboolean emit_address_changed_idle (gpointer gdata) + { + auto data = static_cast(gdata); + + GError * error = nullptr; + dbus_test_dbus_mock_object_emit_signal (data->mock, data->obj_client, + "org.freedesktop.Geoclue.Address", + "AddressChanged", + G_VARIANT_TYPE("(ia{ss}(idd))"), + g_variant_new_parsed ("(1385238033, {'timezone': 'America/Chicago'}, (3, 0.0, 0.0))"), + &error); + if (error) + { + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } + + delete data; + return G_SOURCE_REMOVE; + } +} +#endif + +TEST_F (TimezoneGeoclueFixture, ChangeDetected) +{ +// const std::string timezone_1 = "America/Denver"; + const std::string timezone_2 = "America/Chicago"; + + GeoclueTimezone tz; + wait_msec (500); // wait for the bus to get set up + EXPECT_EQ (timezone_1, tz.timezone.get()); + + // start listening for a timezone change, then change the timezone + + bool changed = false; + auto connection = tz.timezone.changed().connect( + [&changed, this](const std::string& s){ + g_debug ("timezone changed to %s", s.c_str()); + changed = true; + g_main_loop_quit (loop); + }); + + setGeoclueTimezoneOnIdle (timezone_2); + //g_timeout_add (50, emit_address_changed_idle, new EmitAddressChangedData(mock, obj_client, timezone_2.c_str())); + g_main_loop_run (loop); + EXPECT_EQ (timezone_2, tz.timezone.get()); +} + + -- cgit v1.2.3