]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-location-manager.c
location-manager: include telepathy-glib-dbus.h
[empathy.git] / libempathy-gtk / empathy-location-manager.c
1 /*
2  * Copyright (C) 2009 Collabora Ltd.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA  02110-1301  USA
18  *
19  * Authors: Pierre-Luc Beaudoin <pierre-luc.beaudoin@collabora.co.uk>
20  */
21
22 #include "config.h"
23 #include "empathy-location-manager.h"
24
25 #include <telepathy-glib/telepathy-glib-dbus.h>
26
27 #include <tp-account-widgets/tpaw-time.h>
28
29 #include "empathy-gsettings.h"
30 #include "empathy-location.h"
31 #include "empathy-geoclue-helper.h"
32
33 #define DEBUG_FLAG EMPATHY_DEBUG_LOCATION
34 #include "empathy-debug.h"
35
36 /* Seconds before updating the location */
37 #define TIMEOUT 10
38 static EmpathyLocationManager *location_manager = NULL;
39
40 typedef enum
41 {
42   GEOCLUE_NONE = 0,
43   GEOCLUE_STARTING,
44   GEOCLUE_STARTED,
45   GEOCLUE_FAILED,
46 } GeoclueStatus;
47
48 struct _EmpathyLocationManagerPrivate {
49     GeoclueStatus geoclue_status;
50     /* Contains the location to be sent to accounts.  Geoclue is used
51      * to populate it.  This HashTable uses Telepathy's style (string,
52      * GValue). Keys are defined in empathy-location.h
53      */
54     GHashTable *location;
55
56     GSettings *gsettings_loc;
57
58     gboolean reduce_accuracy;
59     TpAccountManager *account_manager;
60     EmpathyGeoclueHelper *geoclue;
61
62     /* The idle id for publish_on_idle func */
63     guint timeout_id;
64 };
65
66 G_DEFINE_TYPE (EmpathyLocationManager, empathy_location_manager, G_TYPE_OBJECT);
67
68 static GObject *
69 location_manager_constructor (GType type,
70     guint n_construct_params,
71     GObjectConstructParam *construct_params)
72 {
73   GObject *retval;
74
75   if (location_manager == NULL)
76     {
77       retval = G_OBJECT_CLASS (empathy_location_manager_parent_class)->constructor
78           (type, n_construct_params, construct_params);
79
80       location_manager = EMPATHY_LOCATION_MANAGER (retval);
81       g_object_add_weak_pointer (retval, (gpointer) &location_manager);
82     }
83   else
84     {
85       retval = g_object_ref (location_manager);
86     }
87
88   return retval;
89 }
90
91 static void
92 location_manager_dispose (GObject *object)
93 {
94   EmpathyLocationManager *self = (EmpathyLocationManager *) object;
95   void (*dispose) (GObject *) =
96     G_OBJECT_CLASS (empathy_location_manager_parent_class)->dispose;
97
98   tp_clear_object (&self->priv->account_manager);
99   tp_clear_object (&self->priv->gsettings_loc);
100   tp_clear_pointer (&self->priv->location, g_hash_table_unref);
101
102   if (dispose != NULL)
103     dispose (object);
104 }
105
106 static void
107 empathy_location_manager_class_init (EmpathyLocationManagerClass *class)
108 {
109   GObjectClass *object_class;
110
111   object_class = G_OBJECT_CLASS (class);
112
113   object_class->constructor = location_manager_constructor;
114   object_class->dispose = location_manager_dispose;
115
116   g_type_class_add_private (object_class, sizeof (EmpathyLocationManagerPrivate));
117 }
118
119 static void
120 publish_location_cb (TpConnection *connection,
121                      const GError *error,
122                      gpointer user_data,
123                      GObject *weak_object)
124 {
125   if (error != NULL)
126       DEBUG ("Error setting location: %s", error->message);
127 }
128
129 static void
130 publish_location (EmpathyLocationManager *self,
131     TpConnection *conn,
132     gboolean force_publication)
133 {
134   guint connection_status = -1;
135
136   if (!conn)
137     return;
138
139   if (!force_publication)
140     {
141       if (!g_settings_get_boolean (self->priv->gsettings_loc,
142             EMPATHY_PREFS_LOCATION_PUBLISH))
143         return;
144     }
145
146   connection_status = tp_connection_get_status (conn, NULL);
147
148   if (connection_status != TP_CONNECTION_STATUS_CONNECTED)
149     return;
150
151   DEBUG ("Publishing %s location to connection %p",
152       (g_hash_table_size (self->priv->location) == 0 ? "empty" : ""),
153       conn);
154
155   tp_cli_connection_interface_location_call_set_location (conn, -1,
156       self->priv->location, publish_location_cb, NULL, NULL, G_OBJECT (self));
157 }
158
159 typedef struct
160 {
161   EmpathyLocationManager *self;
162   gboolean force_publication;
163 } PublishToAllData;
164
165 static void
166 publish_to_all_am_prepared_cb (GObject *source_object,
167     GAsyncResult *result,
168     gpointer user_data)
169 {
170   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
171   PublishToAllData *data = user_data;
172   GList *accounts, *l;
173   GError *error = NULL;
174
175   if (!tp_proxy_prepare_finish (manager, result, &error))
176     {
177       DEBUG ("Failed to prepare account manager: %s", error->message);
178       g_error_free (error);
179       goto out;
180     }
181
182   accounts = tp_account_manager_dup_valid_accounts (manager);
183   for (l = accounts; l; l = l->next)
184     {
185       TpConnection *conn = tp_account_get_connection (TP_ACCOUNT (l->data));
186
187       if (conn != NULL)
188         publish_location (data->self, conn, data->force_publication);
189     }
190   g_list_free_full (accounts, g_object_unref);
191
192 out:
193   g_object_unref (data->self);
194   g_slice_free (PublishToAllData, data);
195 }
196
197 static void
198 publish_to_all_connections (EmpathyLocationManager *self,
199     gboolean force_publication)
200 {
201   PublishToAllData *data;
202
203   data = g_slice_new0 (PublishToAllData);
204   data->self = g_object_ref (self);
205   data->force_publication = force_publication;
206
207   tp_proxy_prepare_async (self->priv->account_manager, NULL,
208       publish_to_all_am_prepared_cb, data);
209 }
210
211 static gboolean
212 publish_on_idle (gpointer user_data)
213 {
214   EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
215
216   manager->priv->timeout_id = 0;
217   publish_to_all_connections (manager, TRUE);
218   return FALSE;
219 }
220
221 static void
222 new_connection_cb (TpAccount *account,
223     guint old_status,
224     guint new_status,
225     guint reason,
226     gchar *dbus_error_name,
227     GHashTable *details,
228     gpointer user_data)
229 {
230   EmpathyLocationManager *self = user_data;
231   TpConnection *conn;
232
233   conn = tp_account_get_connection (account);
234
235   DEBUG ("New connection %p", conn);
236
237   /* Don't publish if it is already planned (ie startup) */
238   if (self->priv->timeout_id == 0)
239     {
240       publish_location (EMPATHY_LOCATION_MANAGER (self), conn,
241           FALSE);
242     }
243 }
244
245 static void
246 update_location (EmpathyLocationManager *self,
247     GClueLocation *proxy)
248 {
249   gdouble latitude, longitude, accuracy;
250   const gchar *desc;
251   gint64 timestamp;
252
253   latitude = gclue_location_get_latitude (proxy);
254   longitude = gclue_location_get_longitude (proxy);
255   accuracy = gclue_location_get_accuracy (proxy);
256   desc = gclue_location_get_description (proxy);
257
258   DEBUG ("Location updated: (%f %f) accuracy: %f (%s)",
259       latitude, longitude, accuracy, desc);
260
261   if (self->priv->reduce_accuracy)
262     {
263       /* Truncate at 1 decimal place */
264       latitude = ((int) (latitude * 10)) / 10.0;
265       longitude = ((int) (longitude * 10)) / 10.0;
266     }
267   else
268     {
269       /* Include the description only if we are not asked to reduce the
270        * accuracy as it can contains a pretty specific description of the
271        * location. */
272       tp_asv_set_string (self->priv->location, EMPATHY_LOCATION_DESCRIPTION,
273           desc);
274     }
275
276   tp_asv_set_double (self->priv->location, EMPATHY_LOCATION_LAT, latitude);
277   tp_asv_set_double (self->priv->location, EMPATHY_LOCATION_LON, longitude);
278   tp_asv_set_double (self->priv->location, EMPATHY_LOCATION_ACCURACY, accuracy);
279
280   timestamp = tpaw_time_get_current ();
281   tp_asv_set_int64 (self->priv->location, EMPATHY_LOCATION_TIMESTAMP,
282       timestamp);
283
284   if (self->priv->timeout_id == 0)
285     self->priv->timeout_id = g_timeout_add_seconds (TIMEOUT, publish_on_idle,
286         self);
287 }
288
289 static void
290 location_changed_cb (EmpathyGeoclueHelper *geoclue,
291     GClueLocation *location,
292     EmpathyLocationManager *self)
293 {
294   update_location (self, location);
295 }
296
297 static void
298 geoclue_new_cb (GObject *source,
299     GAsyncResult *result,
300     gpointer user_data)
301 {
302   EmpathyLocationManager *self = EMPATHY_LOCATION_MANAGER (user_data);
303   GError *error = NULL;
304   GClueLocation *location;
305
306   self->priv->geoclue = empathy_geoclue_helper_new_started_finish (result,
307       &error);
308
309   if (self->priv->geoclue == NULL)
310     {
311       DEBUG ("Failed to create Geoclue client: %s", error->message);
312       g_error_free (error);
313       self->priv->geoclue_status = GEOCLUE_FAILED;
314       return;
315     }
316
317   self->priv->geoclue_status = GEOCLUE_STARTED;
318
319   g_signal_connect_object (self->priv->geoclue, "location-changed",
320       G_CALLBACK (location_changed_cb), self, 0);
321
322   location = empathy_geoclue_helper_get_location (self->priv->geoclue);
323   if (location != NULL)
324     update_location (self, location);
325 }
326
327 static void
328 setup_geoclue (EmpathyLocationManager *self)
329 {
330   switch (self->priv->geoclue_status)
331     {
332       case GEOCLUE_NONE:
333         g_assert (self->priv->geoclue == NULL);
334         self->priv->geoclue_status = GEOCLUE_STARTING;
335         empathy_geoclue_helper_new_started_async (0, geoclue_new_cb, self);
336         break;
337       case GEOCLUE_STARTED:
338       case GEOCLUE_STARTING:
339       case GEOCLUE_FAILED:
340       return;
341     }
342 }
343
344 static void
345 publish_cb (GSettings *gsettings_loc,
346             const gchar *key,
347             gpointer user_data)
348 {
349   EmpathyLocationManager *self = EMPATHY_LOCATION_MANAGER (user_data);
350
351   DEBUG ("Publish Conf changed");
352
353   if (g_settings_get_boolean (gsettings_loc, key))
354     {
355       setup_geoclue (self);
356     }
357   else
358     {
359       /* As per XEP-0080: send an empty location to have remove current
360        * location from the servers
361        */
362       g_hash_table_remove_all (self->priv->location);
363       publish_to_all_connections (self, TRUE);
364
365       g_clear_object (&self->priv->geoclue);
366       self->priv->geoclue_status = GEOCLUE_NONE;
367     }
368 }
369
370 static void
371 account_manager_prepared_cb (GObject *source_object,
372     GAsyncResult *result,
373     gpointer user_data)
374 {
375   GList *accounts, *l;
376   TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
377   EmpathyLocationManager *self = user_data;
378   GError *error = NULL;
379
380   if (!tp_proxy_prepare_finish (account_manager, result, &error))
381     {
382       DEBUG ("Failed to prepare account manager: %s", error->message);
383       g_error_free (error);
384       return;
385     }
386
387   accounts = tp_account_manager_dup_valid_accounts (account_manager);
388   for (l = accounts; l != NULL; l = l->next)
389     {
390       TpAccount *account = TP_ACCOUNT (l->data);
391
392       tp_g_signal_connect_object (account, "status-changed",
393           G_CALLBACK (new_connection_cb), self, 0);
394     }
395   g_list_free_full (accounts, g_object_unref);
396 }
397
398 static void
399 empathy_location_manager_init (EmpathyLocationManager *self)
400 {
401   EmpathyLocationManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
402       EMPATHY_TYPE_LOCATION_MANAGER, EmpathyLocationManagerPrivate);
403
404   self->priv = priv;
405   priv->location = tp_asv_new (NULL, NULL);
406   priv->gsettings_loc = g_settings_new (EMPATHY_PREFS_LOCATION_SCHEMA);
407
408   /* Setup account status callbacks */
409   priv->account_manager = tp_account_manager_dup ();
410
411   tp_proxy_prepare_async (priv->account_manager, NULL,
412       account_manager_prepared_cb, self);
413
414   /* Setup settings status callbacks */
415   g_signal_connect (priv->gsettings_loc,
416       "changed::" EMPATHY_PREFS_LOCATION_PUBLISH,
417       G_CALLBACK (publish_cb), self);
418
419   publish_cb (priv->gsettings_loc, EMPATHY_PREFS_LOCATION_PUBLISH, self);
420 }
421
422 EmpathyLocationManager *
423 empathy_location_manager_dup_singleton (void)
424 {
425   return EMPATHY_LOCATION_MANAGER (g_object_new (EMPATHY_TYPE_LOCATION_MANAGER,
426       NULL));
427 }