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