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