164638597cc57db1b37babd3ae337079604ade94
[azimuth] / src / position-publisher.c
1 /*
2  * position-publisher.c - Source for PositionPublisher
3  * Copyright (C) 2010 Guillaume Desmottes
4  * @author Guillaume Desmottes <gdesmott@gnome.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <telepathy-glib/dbus.h>
26 #include <telepathy-glib/interfaces.h>
27
28 #include <location/location-gps-device.h>
29
30 #include "connection-watcher.h"
31 #include "position-publisher.h"
32
33 G_DEFINE_TYPE(PositionPublisher, position_publisher, G_TYPE_OBJECT)
34
35 /* properties */
36 enum
37 {
38   PROP_BLUR = 1,
39   LAST_PROPERTY
40 };
41
42 enum
43 {
44   SIG_HAS_CONNECTIONS_CHANGED,
45   LAST_SIGNAL
46 };
47
48 static guint signals[LAST_SIGNAL] = { 0 };
49
50 /* Minimum time before 2 publishing (in seconds) */
51 #define PUBLISH_THROTTLE 10
52
53 /* private structure */
54 typedef struct _PositionPublisherPrivate PositionPublisherPrivate;
55
56 struct _PositionPublisherPrivate
57 {
58   ConnectionWatcher *watcher;
59   LocationGPSDevice *gps_device;
60   /* List of (TpConnection *) supporting location publishing */
61   GSList *connections;
62   GHashTable *location;
63   /* If not 0, we are waiting before publishing again */
64   guint throttle_timeout;
65   /* TRUE if location has been modified while we were waiting */
66   gboolean modified;
67   gboolean blur;
68
69   gboolean dispose_has_run;
70 };
71
72 #define POSITION_PUBLISHER_GET_PRIVATE(o)     (G_TYPE_INSTANCE_GET_PRIVATE ((o), POSITION_PUBLISHER_TYPE, PositionPublisherPrivate))
73
74 static void conn_invalidated_cb (TpProxy *conn,
75     guint domain,
76     gint code,
77     gchar *message,
78     PositionPublisher *self);
79
80 static void publish_to_all (PositionPublisher *self);
81
82 static void
83 remove_connection (PositionPublisher *self,
84     TpConnection *conn)
85 {
86   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
87
88   g_signal_handlers_disconnect_by_func (conn, G_CALLBACK (conn_invalidated_cb),
89       self);
90   priv->connections = g_slist_remove (priv->connections, conn);
91   g_object_unref (conn);
92
93   if (g_slist_length (priv->connections) == 0)
94     {
95       g_print ("We don't have location connection any more\n");
96       g_signal_emit (self, signals[SIG_HAS_CONNECTIONS_CHANGED], 0, FALSE);
97     }
98 }
99
100 static void
101 set_location_cb (TpConnection *conn,
102     const GError *error,
103     gpointer user_data,
104     GObject *weak_object)
105 {
106   PositionPublisher *self = POSITION_PUBLISHER (weak_object);
107
108   if (error != NULL)
109     {
110       g_print ("SetLocation failed (%s): %s\n", tp_proxy_get_object_path (conn),
111           error->message);
112
113       if (error->code == TP_ERROR_NOT_IMPLEMENTED)
114         {
115           g_print ("remove connection\n");
116           remove_connection (self, conn);
117         }
118
119       return;
120     }
121
122   g_print ("SetLocation succeed (%s)\n", tp_proxy_get_object_path (conn));
123 }
124
125 static gboolean
126 publish_throttle_timeout_cb (gpointer data)
127 {
128   PositionPublisher *self = data;
129   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
130
131   priv->throttle_timeout = 0;
132
133   if (priv->modified)
134     {
135       publish_to_all (self);
136       priv->modified = FALSE;
137     }
138
139   return FALSE;
140 }
141
142 static void
143 publish_to_conn (PositionPublisher *self,
144     TpConnection *conn)
145 {
146   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
147
148   if (priv->location == NULL)
149     return;
150
151   tp_cli_connection_interface_location_call_set_location (conn, -1,
152       priv->location, set_location_cb, NULL, NULL, G_OBJECT (self));
153 }
154
155 static void
156 publish_to_all (PositionPublisher *self)
157 {
158   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
159   GSList *l;
160
161   if (priv->throttle_timeout != 0)
162     /* We are waiting */
163     return;
164
165   for (l = priv->connections; l != NULL; l = g_slist_next (l))
166     {
167       TpConnection *conn = l->data;
168
169       publish_to_conn (self, conn);
170     }
171
172   /* We won't publish during the next PUBLISH_THROTTLE seconds */
173   priv->throttle_timeout = g_timeout_add_seconds (PUBLISH_THROTTLE,
174       publish_throttle_timeout_cb, self);
175 }
176
177 static void
178 update_position (PositionPublisher *self,
179     gdouble lat,
180     gdouble lon,
181     gdouble alt,
182     gdouble accuracy)
183 {
184   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
185
186   if (priv->blur)
187     {
188       /* Truncate at 1 decimal place */
189       lon = ((int) (lon * 10)) / 10.0;
190       lat = ((int) (lat * 10)) / 10.0;
191
192       /* FIXME: change accuracy (not that easy as accuracy is in meters) */
193     }
194
195   g_print ("update position: lat: %f  lon:  %f  alt: %f accuracy: %f\n",
196       lat, lon, alt, accuracy);
197
198   if (priv->location != NULL)
199     g_hash_table_unref (priv->location);
200
201   priv->location = tp_asv_new (
202       "timestamp", G_TYPE_INT64, (gint64) time (NULL),
203       "lat", G_TYPE_DOUBLE, lat,
204       "lon", G_TYPE_DOUBLE, lon,
205       "alt", G_TYPE_DOUBLE, alt,
206       "accuracy", G_TYPE_DOUBLE, accuracy,
207       NULL);
208
209   priv->modified = TRUE;
210
211   publish_to_all (self);
212 }
213
214 static void
215 location_changed_cb (LocationGPSDevice *device,
216     PositionPublisher *self)
217 {
218   if (device == NULL)
219     return;
220
221   if (device->fix == NULL)
222     return;
223
224   if (!(device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET))
225     return;
226
227   update_position (self, device->fix->latitude, device->fix->longitude,
228       device->fix->altitude, device->fix->eph / 100.0);
229 }
230
231 static void
232 conn_invalidated_cb (TpProxy *conn,
233     guint domain,
234     gint code,
235     gchar *message,
236     PositionPublisher *self)
237 {
238   g_print ("connection %s invalidated; removing\n", tp_proxy_get_object_path (
239         conn));
240
241   remove_connection (self, TP_CONNECTION (conn));
242 }
243
244 static void
245 connection_added_cb (ConnectionWatcher *watcher,
246     TpConnection *conn,
247     PositionPublisher *self)
248 {
249   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
250
251   if (g_slist_find (priv->connections, conn) != NULL)
252     return;
253
254   if (!tp_proxy_has_interface_by_id (conn,
255         TP_IFACE_QUARK_CONNECTION_INTERFACE_LOCATION))
256     return;
257
258   priv->connections = g_slist_prepend (priv->connections, g_object_ref (conn));
259
260   if (g_slist_length (priv->connections) == 1)
261     {
262       /* We just added the first connection */
263       g_print ("We have location connection\n");
264       g_signal_emit (self, signals[SIG_HAS_CONNECTIONS_CHANGED], 0, TRUE);
265     }
266
267   g_signal_connect (conn, "invalidated",
268       G_CALLBACK (conn_invalidated_cb), self);
269
270   publish_to_conn (self, conn);
271 }
272
273 static void
274 position_publisher_init (PositionPublisher *obj)
275 {
276   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (obj);
277
278   priv->watcher = connection_watcher_new ();
279
280   g_signal_connect (priv->watcher, "connection-added",
281       G_CALLBACK (connection_added_cb), obj);
282
283   priv->gps_device = g_object_new (LOCATION_TYPE_GPS_DEVICE, NULL);
284
285   g_signal_connect (priv->gps_device, "changed",
286       G_CALLBACK (location_changed_cb), obj);
287
288   priv->connections = NULL;
289 }
290
291 static void
292 position_publisher_constructed (GObject *object)
293 {
294   PositionPublisher *self = POSITION_PUBLISHER (object);
295   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
296
297   connection_watcher_start (priv->watcher);
298
299   if (G_OBJECT_CLASS (position_publisher_parent_class)->constructed)
300     G_OBJECT_CLASS (position_publisher_parent_class)->constructed (object);
301 }
302
303 static void position_publisher_dispose (GObject *object);
304 static void position_publisher_finalize (GObject *object);
305 static void position_publisher_get_property (GObject *object,
306     guint property_id, GValue *value, GParamSpec *pspec);
307 static void position_publisher_set_property (GObject *object,
308     guint property_id, const GValue *value, GParamSpec *pspec);
309
310 static void
311 position_publisher_class_init (PositionPublisherClass *position_publisher_class)
312 {
313   GObjectClass *object_class = G_OBJECT_CLASS (position_publisher_class);
314   GParamSpec *param_spec;
315
316   g_type_class_add_private (position_publisher_class, sizeof (PositionPublisherPrivate));
317
318   object_class->dispose = position_publisher_dispose;
319   object_class->finalize = position_publisher_finalize;
320   object_class->get_property = position_publisher_get_property;
321   object_class->set_property = position_publisher_set_property;
322
323   object_class->constructed = position_publisher_constructed;
324
325   param_spec = g_param_spec_boolean ("blur", "Blur?",
326       "Whether the real GPS position is truncated for privacy concerns.", TRUE,
327       G_PARAM_CONSTRUCT | G_PARAM_WRITABLE | G_PARAM_READABLE
328       | G_PARAM_STATIC_STRINGS);
329   g_object_class_install_property (object_class, PROP_BLUR, param_spec);
330
331   signals[SIG_HAS_CONNECTIONS_CHANGED] = g_signal_new (
332       "has-connections-changed",
333       G_OBJECT_CLASS_TYPE (object_class),
334       G_SIGNAL_RUN_LAST,
335       0,
336       NULL, NULL,
337       g_cclosure_marshal_VOID__BOOLEAN,
338       G_TYPE_NONE,
339       1, G_TYPE_BOOLEAN);
340 }
341
342 void
343 position_publisher_dispose (GObject *object)
344 {
345   PositionPublisher *self = POSITION_PUBLISHER (object);
346   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
347   GSList *l;
348
349   if (priv->dispose_has_run)
350     return;
351
352   priv->dispose_has_run = TRUE;
353
354   g_object_unref (priv->watcher);
355   g_object_unref (priv->gps_device);
356
357   for (l = priv->connections; l != NULL; l = g_slist_next (l))
358     {
359       g_object_unref (l->data);
360     }
361
362   g_hash_table_unref (priv->location);
363
364   if (priv->throttle_timeout != 0)
365     g_source_remove (priv->throttle_timeout);
366
367   if (G_OBJECT_CLASS (position_publisher_parent_class)->dispose)
368     G_OBJECT_CLASS (position_publisher_parent_class)->dispose (object);
369 }
370
371 void
372 position_publisher_finalize (GObject *object)
373 {
374   PositionPublisher *self = POSITION_PUBLISHER (object);
375   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
376
377   g_slist_free (priv->connections);
378
379   G_OBJECT_CLASS (position_publisher_parent_class)->finalize (object);
380 }
381
382 static void
383 position_publisher_get_property (GObject *object,
384                                  guint property_id,
385                                  GValue *value,
386                                  GParamSpec *pspec)
387 {
388   PositionPublisher *self = POSITION_PUBLISHER (object);
389   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
390
391   switch (property_id) {
392     case PROP_BLUR:
393       g_value_set_boolean (value, priv->blur);
394       break;
395     default:
396       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
397       break;
398   }
399 }
400
401 static void
402 position_publisher_set_property (GObject *object,
403                                  guint property_id,
404                                  const GValue *value,
405                                  GParamSpec *pspec)
406 {
407   PositionPublisher *self = POSITION_PUBLISHER (object);
408   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
409
410   switch (property_id) {
411     case PROP_BLUR:
412       priv->blur = g_value_get_boolean (value);
413       break;
414
415       break;
416     default:
417       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
418       break;
419   }
420 }
421
422 PositionPublisher *
423 position_publisher_new (void)
424 {
425   return g_object_new (POSITION_PUBLISHER_TYPE,
426       NULL);
427 }
428
429 void
430 position_publisher_set_blur (PositionPublisher *self,
431     gboolean blur)
432 {
433   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
434
435   g_print ("%s blurring location\n", blur ? "Start": "Stop");
436   priv->blur = blur;
437   g_object_notify (G_OBJECT (self), "blur");
438 }
439
440 gboolean
441 position_publisher_has_connections (PositionPublisher *self)
442 {
443   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
444
445   return g_slist_length (priv->connections) > 0;
446 }