]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-location-manager.c
Queue location publish requests
[empathy.git] / libempathy-gtk / empathy-location-manager.c
1 /*
2  * Copyright (C) 2008 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., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Authors: Pierre-Luc Beaudoin <pierre-luc@pierlux.com>
20  */
21
22 #include "config.h"
23
24 #include <string.h>
25 #include <time.h>
26
27 #include <glib/gi18n.h>
28
29 #include <telepathy-glib/util.h>
30
31 #if HAVE_GEOCLUE
32 #include <geoclue/geoclue-master.h>
33
34 #include <extensions/extensions.h>
35
36 #include "empathy-location-manager.h"
37 #include "empathy-conf.h"
38
39 #include "libempathy/empathy-account-manager.h"
40 #include "libempathy/empathy-enum-types.h"
41 #include "libempathy/empathy-location.h"
42 #include "libempathy/empathy-tp-contact-factory.h"
43 #include "libempathy/empathy-utils.h"
44
45 #define DEBUG_FLAG EMPATHY_DEBUG_LOCATION
46 #include "libempathy/empathy-debug.h"
47
48 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyLocationManager)
49 typedef struct {
50     gboolean is_setup;
51     MissionControl *mc;
52     GHashTable *location;
53     gpointer token;
54
55     GeoclueResourceFlags resources;
56     GeoclueMasterClient *gc_client;
57     GeocluePosition *gc_position;
58     GeoclueAddress *gc_address;
59
60     gboolean reduce_accuracy;
61     gdouble reduce_value;
62     EmpathyAccountManager *account_manager;
63
64     /* The idle id for publish_on_idle func */
65     guint idle_id;
66 } EmpathyLocationManagerPriv;
67
68 static void location_manager_finalize (GObject *object);
69 static void location_manager_get_property (GObject *object, guint param_id,
70     GValue *value, GParamSpec *pspec);
71 static void location_manager_set_property (GObject *object, guint param_id,
72     const GValue *value, GParamSpec *pspec);
73 static void position_changed_cb (GeocluePosition *position,
74     GeocluePositionFields fields, int timestamp, double latitude,
75     double longitude, double altitude, GeoclueAccuracy *accuracy,
76     gpointer user_data);
77 static void address_changed_cb (GeoclueAddress *address, int timestamp,
78     GHashTable *details, GeoclueAccuracy *accuracy, gpointer user_data);
79 static void setup_geoclue (EmpathyLocationManager *location_manager);
80 static void publish_cb (EmpathyConf  *conf, const gchar *key,
81     gpointer user_data);
82 static void update_resources (EmpathyLocationManager *location_manager);
83 static void resource_cb (EmpathyConf  *conf, const gchar *key,
84     gpointer user_data);
85 static void accuracy_cb (EmpathyConf  *conf, const gchar *key,
86     gpointer user_data);
87 static void account_connection_changed_cb (EmpathyAccountManager *manager,
88     McAccount *account, TpConnectionStatusReason reason,
89     TpConnectionStatus current, TpConnectionStatus previous,
90     gpointer *location_manager);
91 static void publish_to_all_accounts (EmpathyLocationManager *location_manager,
92     gboolean force_publication);
93 static gboolean publish_on_idle (gpointer user_data);
94
95 G_DEFINE_TYPE (EmpathyLocationManager, empathy_location_manager, G_TYPE_OBJECT);
96
97 enum
98 {
99   PROP_0,
100 };
101
102 static void
103 empathy_location_manager_class_init (EmpathyLocationManagerClass *class)
104 {
105   GObjectClass *object_class;
106
107   object_class = G_OBJECT_CLASS (class);
108
109   object_class->finalize = location_manager_finalize;
110   object_class->get_property = location_manager_get_property;
111   object_class->set_property = location_manager_set_property;
112
113   g_type_class_add_private (object_class, sizeof (EmpathyLocationManagerPriv));
114 }
115
116 static gboolean
117 publish_on_idle (gpointer user_data)
118 {
119   EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
120   EmpathyLocationManagerPriv *priv = GET_PRIV (manager);
121
122   priv->idle_id = 0;
123   publish_to_all_accounts (manager, TRUE);
124   return FALSE;
125 }
126
127 static void
128 publish_location (EmpathyLocationManager *location_manager,
129                   McAccount *account,
130                   gboolean force_publication)
131 {
132   EmpathyLocationManagerPriv *priv;
133   guint connection_status = -1;
134   gboolean can_publish;
135   EmpathyConf *conf = empathy_conf_get ();
136   TpConnection *conn;
137   EmpathyTpContactFactory *factory;
138
139   priv = GET_PRIV (location_manager);
140
141   conn = mission_control_get_tpconnection (priv->mc, account, NULL);
142   if (!conn)
143     return;
144
145   factory = empathy_tp_contact_factory_dup_singleton (conn);
146
147   if (force_publication == FALSE)
148     {
149       if (!empathy_conf_get_bool (conf, EMPATHY_PREFS_LOCATION_PUBLISH,
150             &can_publish))
151         return;
152
153       if (can_publish == FALSE)
154         return;
155     }
156
157   connection_status = mission_control_get_connection_status (priv->mc,
158       account, NULL);
159
160   if (connection_status != TP_CONNECTION_STATUS_CONNECTED)
161     return;
162
163   DEBUG ("Publishing %s location to account %s", 
164       (g_hash_table_size (priv->location) == 0 ? "empty" : ""),
165       mc_account_get_display_name (account));
166
167   empathy_tp_contact_factory_set_location (factory, priv->location);
168   g_object_unref (factory);
169 }
170
171 static void
172 publish_to_all_accounts (EmpathyLocationManager *location_manager,
173                                   gboolean force_publication)
174 {
175   GList *accounts = NULL, *l;
176
177   accounts = mc_accounts_list_by_enabled (TRUE);
178   for (l = accounts; l; l = l->next)
179     {
180       publish_location (location_manager, l->data, force_publication);
181     }
182
183   mc_accounts_list_free (accounts);
184 }
185
186 static void
187 account_connection_changed_cb (EmpathyAccountManager *manager,
188                                McAccount *account,
189                                TpConnectionStatusReason reason,
190                                TpConnectionStatus current,
191                                TpConnectionStatus previous,
192                                gpointer *location_manager)
193 {
194   DEBUG ("Account %s changed status from %d to %d", mc_account_get_display_name (account),
195       previous, current);
196
197   if (account && current == TP_CONNECTION_STATUS_CONNECTED)
198     publish_location (EMPATHY_LOCATION_MANAGER (location_manager), account,
199         FALSE);
200 }
201
202 static void
203 empathy_location_manager_init (EmpathyLocationManager *location_manager)
204 {
205   EmpathyConf               *conf;
206   EmpathyLocationManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (location_manager,
207       EMPATHY_TYPE_LOCATION_MANAGER, EmpathyLocationManagerPriv);
208
209   location_manager->priv = priv;
210   priv->is_setup = FALSE;
211   priv->mc = empathy_mission_control_dup_singleton ();
212   priv->location = g_hash_table_new_full (g_direct_hash, g_direct_equal,
213       g_free, (GDestroyNotify) tp_g_value_slice_free);
214
215   // Setup settings status callbacks
216   conf = empathy_conf_get ();
217   empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_PUBLISH, publish_cb,
218       location_manager);
219   empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK,
220       resource_cb, location_manager);
221   empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_RESOURCE_CELL,
222       resource_cb, location_manager);
223   empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_RESOURCE_GPS,
224       resource_cb, location_manager);
225   empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_REDUCE_ACCURACY,
226       accuracy_cb, location_manager);
227
228   resource_cb (conf, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK, location_manager);
229   resource_cb (conf, EMPATHY_PREFS_LOCATION_RESOURCE_CELL, location_manager);
230   resource_cb (conf, EMPATHY_PREFS_LOCATION_RESOURCE_GPS, location_manager);
231   accuracy_cb (conf, EMPATHY_PREFS_LOCATION_REDUCE_ACCURACY, location_manager);
232   publish_cb (conf, EMPATHY_PREFS_LOCATION_PUBLISH, location_manager);
233
234   // Setup account status callbacks
235   priv->account_manager = empathy_account_manager_dup_singleton ();
236   g_signal_connect (priv->account_manager,
237     "account-connection-changed",
238     G_CALLBACK (account_connection_changed_cb), location_manager);
239 }
240
241
242 static void
243 location_manager_finalize (GObject *object)
244 {
245   EmpathyLocationManagerPriv *priv;
246
247   priv = GET_PRIV (object);
248
249   DEBUG ("finalize: %p", object);
250   g_object_unref (priv->account_manager);
251
252   G_OBJECT_CLASS (empathy_location_manager_parent_class)->finalize (object);
253 }
254
255
256 static void
257 location_manager_get_property (GObject *object,
258                       guint param_id,
259                       GValue *value,
260                       GParamSpec *pspec)
261 {
262   EmpathyLocationManagerPriv *priv;
263
264   priv = GET_PRIV (object);
265
266   switch (param_id)
267     {
268       default:
269         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
270         break;
271     };
272 }
273
274
275 static void
276 location_manager_set_property (GObject *object,
277                       guint param_id,
278                       const GValue *value,
279                       GParamSpec *pspec)
280 {
281   EmpathyLocationManagerPriv *priv;
282
283   priv = GET_PRIV (object);
284
285   switch (param_id)
286     {
287       default:
288         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
289         break;
290     };
291 }
292
293
294 EmpathyLocationManager *
295 empathy_location_manager_dup_default (void)
296 {
297   static EmpathyLocationManager *singleton = NULL;
298   if (singleton == NULL)
299     {
300       singleton = g_object_new (EMPATHY_TYPE_LOCATION_MANAGER, NULL);
301       g_object_add_weak_pointer (G_OBJECT (singleton), (gpointer *)&singleton);
302     }
303
304   return g_object_ref (singleton);
305 }
306
307 static void
308 update_timestamp (EmpathyLocationManager *location_manager)
309 {
310   EmpathyLocationManagerPriv *priv;
311   priv = GET_PRIV (location_manager);
312   GValue *new_value;
313   gint64 stamp64;
314   time_t timestamp;
315
316   timestamp = time (NULL);
317   stamp64 = (gint64) timestamp;
318   new_value = tp_g_value_slice_new (G_TYPE_INT64);
319   g_value_set_int64 (new_value, stamp64);
320   g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_TIMESTAMP),
321       new_value);
322   DEBUG ("\t - Timestamp: %" G_GINT64_FORMAT, stamp64);
323 }
324
325 static void
326 initial_position_cb (GeocluePosition *position,
327                      GeocluePositionFields fields,
328                      int timestamp,
329                      double latitude,
330                      double longitude,
331                      double altitude,
332                      GeoclueAccuracy *accuracy,
333                      GError *error,
334                      gpointer location_manager)
335 {
336   if (error)
337     {
338       DEBUG ("Error: %s", error->message);
339       g_error_free (error);
340     }
341   else
342     position_changed_cb (position, fields, timestamp, latitude, longitude,
343       altitude, accuracy, location_manager);
344 }
345
346 static void
347 position_changed_cb (GeocluePosition *position,
348                      GeocluePositionFields fields,
349                      int timestamp,
350                      double latitude,
351                      double longitude,
352                      double altitude,
353                      GeoclueAccuracy *accuracy,
354                      gpointer location_manager)
355 {
356   EmpathyLocationManagerPriv *priv;
357   priv = GET_PRIV (location_manager);
358   GeoclueAccuracyLevel level;
359   gdouble mean, horizontal, vertical;
360   GValue *new_value;
361
362   geoclue_accuracy_get_details (accuracy, &level, &horizontal, &vertical);
363   DEBUG ("New position (accuracy level %d)", level);
364   if (level == GEOCLUE_ACCURACY_LEVEL_NONE)
365     return;
366
367   if (fields & GEOCLUE_POSITION_FIELDS_LONGITUDE)
368     {
369       longitude += priv->reduce_value;
370       new_value = tp_g_value_slice_new (G_TYPE_DOUBLE);
371       g_value_set_double (new_value, longitude);
372       g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_LON),
373           new_value);
374       DEBUG ("\t - Longitude: %f", longitude);
375     }
376   if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE)
377     {
378       latitude += priv->reduce_value;
379       new_value = tp_g_value_slice_new (G_TYPE_DOUBLE);
380       g_value_set_double (new_value, latitude);
381       g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_LAT),
382           new_value);
383       DEBUG ("\t - Latitude: %f", latitude);
384     }
385   if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE)
386     {
387       new_value = tp_g_value_slice_new (G_TYPE_DOUBLE);
388       g_value_set_double (new_value, altitude);
389       g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_ALT),
390           new_value);
391       DEBUG ("\t - Altitude: %f", altitude);
392     }
393
394   if (level == GEOCLUE_ACCURACY_LEVEL_DETAILED)
395     {
396       mean = (horizontal + vertical) / 2.0;
397       new_value = tp_g_value_slice_new (G_TYPE_DOUBLE);
398       g_value_set_double (new_value, mean);
399       g_hash_table_insert (priv->location,
400           g_strdup (EMPATHY_LOCATION_ACCURACY), new_value);
401       DEBUG ("\t - Accuracy: %f", mean);
402     }
403
404   update_timestamp (location_manager);
405   if (priv->idle_id == 0)
406     g_idle_add (publish_on_idle, location_manager);
407 }
408
409
410 static void
411 address_foreach_cb (gpointer key,
412                     gpointer value,
413                     gpointer location_manager)
414 {
415   if (location_manager == NULL)
416     return;
417
418   EmpathyLocationManagerPriv *priv;
419   priv = GET_PRIV (location_manager);
420
421   // Discard street information if reduced accuracy is on
422   if (priv->reduce_accuracy && strcmp (key, EMPATHY_LOCATION_STREET) == 0)
423     return;
424
425   GValue *new_value = tp_g_value_slice_new (G_TYPE_STRING);
426   g_value_set_string (new_value, value);
427
428   g_hash_table_insert (priv->location, g_strdup (key), new_value);
429   DEBUG ("\t - %s: %s", (char*) key, (char*) value);
430 }
431
432 static void
433 initial_address_cb (GeoclueAddress *address,
434                     int timestamp,
435                     GHashTable *details,
436                     GeoclueAccuracy *accuracy,
437                     GError *error,
438                     gpointer location_manager)
439 {
440   if (error)
441     {
442       DEBUG ("Error: %s", error->message);
443       g_error_free (error);
444     }
445   else
446     address_changed_cb (address, timestamp, details, accuracy, location_manager);
447 }
448
449 static void
450 address_changed_cb (GeoclueAddress *address,
451                     int timestamp,
452                     GHashTable *details,
453                     GeoclueAccuracy *accuracy,
454                     gpointer location_manager)
455 {
456   GeoclueAccuracyLevel level;
457   geoclue_accuracy_get_details (accuracy, &level, NULL, NULL);
458   EmpathyLocationManagerPriv *priv;
459
460   DEBUG ("New address (accuracy level %d):", level);
461
462   priv = GET_PRIV (location_manager);
463   g_hash_table_remove_all (priv->location);
464
465   if (g_hash_table_size (details) == 0)
466     return;
467
468   g_hash_table_foreach (details, address_foreach_cb, (gpointer)location_manager);
469
470   update_timestamp (location_manager);
471   if (priv->idle_id == 0)
472     g_idle_add (publish_on_idle, location_manager);
473 }
474
475
476 static void
477 update_resources (EmpathyLocationManager *location_manager)
478 {
479   EmpathyLocationManagerPriv *priv;
480
481   priv = GET_PRIV (location_manager);
482
483   DEBUG ("Updating resources %d", priv->resources);
484
485   if (!geoclue_master_client_set_requirements (priv->gc_client,
486           GEOCLUE_ACCURACY_LEVEL_NONE, 0, TRUE, priv->resources,
487           NULL))
488     {
489       g_printerr ("set_requirements failed");
490       return;
491     }
492
493   if (!priv->is_setup)
494     return;
495
496   geoclue_address_get_address_async (priv->gc_address,
497       initial_address_cb, location_manager);
498   geoclue_position_get_position_async (priv->gc_position,
499       initial_position_cb, location_manager);
500
501 }
502
503 static void
504 setup_geoclue (EmpathyLocationManager *location_manager)
505 {
506   EmpathyLocationManagerPriv *priv;
507
508   priv = GET_PRIV (location_manager);
509
510   GeoclueMaster *master;
511   GError *error = NULL;
512
513   DEBUG ("Setting up Geoclue");
514   master = geoclue_master_get_default ();
515   priv->gc_client = geoclue_master_create_client (master, NULL, NULL);
516   g_object_unref (master);
517
518   update_resources (location_manager);
519
520   /* Get updated when the position is changes */
521   priv->gc_position = geoclue_master_client_create_position (
522       priv->gc_client, &error);
523   if (priv->gc_position == NULL)
524     {
525       g_printerr ("Failed to create GeocluePosition: %s", error->message);
526       g_error_free (error);
527       return;
528     }
529
530   g_signal_connect (G_OBJECT (priv->gc_position), "position-changed",
531       G_CALLBACK (position_changed_cb), location_manager);
532
533   /* Get updated when the address changes */
534   priv->gc_address = geoclue_master_client_create_address (
535       priv->gc_client, &error);
536   if (priv->gc_address == NULL)
537     {
538       g_printerr ("Failed to create GeoclueAddress: %s", error->message);
539       g_error_free (error);
540       return;
541     }
542
543   g_signal_connect (G_OBJECT (priv->gc_address), "address-changed",
544       G_CALLBACK (address_changed_cb), location_manager);
545
546   priv->is_setup = TRUE;
547 }
548
549 static void
550 publish_cb (EmpathyConf *conf,
551             const gchar *key,
552             gpointer user_data)
553 {
554   EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
555   EmpathyLocationManagerPriv *priv;
556   gboolean can_publish;
557
558   DEBUG ("Publish Conf changed");
559   priv = GET_PRIV (manager);
560
561
562   if (empathy_conf_get_bool (conf, key, &can_publish) == FALSE)
563     return;
564
565   if (can_publish == TRUE)
566     {
567       if (priv->is_setup == FALSE)
568         setup_geoclue (manager);
569       /* if still not setup than the init failed */
570       if (priv->is_setup == FALSE)
571         return;
572
573       geoclue_address_get_address_async (priv->gc_address,
574           initial_address_cb, manager);
575       geoclue_position_get_position_async (priv->gc_position,
576           initial_position_cb, manager);
577     }
578   else
579     {
580       /* As per XEP-0080: send an empty location to have remove current
581        * location from the servers
582        */
583       g_hash_table_remove_all (priv->location);
584       publish_to_all_accounts (manager, TRUE);
585     }
586
587 }
588
589
590 static void
591 accuracy_cb (EmpathyConf  *conf,
592              const gchar *key,
593              gpointer user_data)
594 {
595   EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
596   EmpathyLocationManagerPriv *priv;
597
598   gboolean enabled;
599
600   priv = GET_PRIV (manager);
601   DEBUG ("%s changed", key);
602
603   if (!empathy_conf_get_bool (conf, key, &enabled))
604     return;
605   priv->reduce_accuracy = enabled;
606
607   if (enabled)
608     {
609       GRand *rand = g_rand_new_with_seed (time (NULL));
610       priv->reduce_value = g_rand_double_range (rand, -0.25, 0.25);
611       g_rand_free (rand);
612     }
613   else
614     priv->reduce_value = 0.0;
615
616   if (!priv->is_setup)
617     return;
618
619   geoclue_address_get_address_async (priv->gc_address,
620       initial_address_cb, manager);
621   geoclue_position_get_position_async (priv->gc_position,
622       initial_position_cb, manager);
623 }
624
625 static void
626 resource_cb (EmpathyConf  *conf,
627              const gchar *key,
628              gpointer user_data)
629 {
630   EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
631   EmpathyLocationManagerPriv *priv;
632   GeoclueResourceFlags resource = 0;
633   gboolean resource_enabled;
634
635   priv = GET_PRIV (manager);
636   DEBUG ("%s changed", key);
637
638   if (!empathy_conf_get_bool (conf, key, &resource_enabled))
639     return;
640
641   if (strcmp (key, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK) == 0)
642     resource = GEOCLUE_RESOURCE_NETWORK;
643   if (strcmp (key, EMPATHY_PREFS_LOCATION_RESOURCE_CELL) == 0)
644     resource = GEOCLUE_RESOURCE_CELL;
645   if (strcmp (key, EMPATHY_PREFS_LOCATION_RESOURCE_GPS) == 0)
646     resource = GEOCLUE_RESOURCE_GPS;
647
648   if (resource_enabled)
649     priv->resources |= resource;
650   else
651     priv->resources &= ~resource;
652
653   if (priv->is_setup)
654     update_resources (manager);
655 }
656
657 #endif