c0c57fd8b973c5c6bc90a66ff6fb1ba928dbe93a
[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 /* Minimum time before 2 publishing (in seconds) */
43 #define PUBLISH_THROTTLE 10
44
45 /* private structure */
46 typedef struct _PositionPublisherPrivate PositionPublisherPrivate;
47
48 struct _PositionPublisherPrivate
49 {
50   ConnectionWatcher *watcher;
51   LocationGPSDevice *gps_device;
52   GSList *connections;
53   GHashTable *location;
54   /* If not 0, we are waiting before publishing again */
55   guint throttle_timeout;
56   /* TRUE if location has been modified while we were waiting */
57   gboolean modified;
58   gboolean blur;
59
60   gboolean dispose_has_run;
61 };
62
63 #define POSITION_PUBLISHER_GET_PRIVATE(o)     (G_TYPE_INSTANCE_GET_PRIVATE ((o), POSITION_PUBLISHER_TYPE, PositionPublisherPrivate))
64
65 static void conn_invalidated_cb (TpProxy *conn,
66     guint domain,
67     gint code,
68     gchar *message,
69     PositionPublisher *self);
70
71 static void publish_to_all (PositionPublisher *self);
72
73 static void
74 remove_connection (PositionPublisher *self,
75     TpConnection *conn)
76 {
77   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
78
79   g_signal_handlers_disconnect_by_func (conn, G_CALLBACK (conn_invalidated_cb),
80       self);
81   priv->connections = g_slist_remove (priv->connections, conn);
82   g_object_unref (conn);
83 }
84
85 static void
86 set_location_cb (TpConnection *conn,
87     const GError *error,
88     gpointer user_data,
89     GObject *weak_object)
90 {
91   PositionPublisher *self = POSITION_PUBLISHER (weak_object);
92
93   if (error != NULL)
94     {
95       g_print ("SetLocation failed (%s): %s\n", tp_proxy_get_object_path (conn),
96           error->message);
97
98       if (error->code == TP_ERROR_NOT_IMPLEMENTED)
99         {
100           g_print ("remove connection\n");
101           remove_connection (self, conn);
102         }
103
104       return;
105     }
106
107   g_print ("SetLocation succeed (%s)\n", tp_proxy_get_object_path (conn));
108 }
109
110 static gboolean
111 publish_throttle_timeout_cb (gpointer data)
112 {
113   PositionPublisher *self = data;
114   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
115
116   priv->throttle_timeout = 0;
117
118   if (priv->modified)
119     {
120       publish_to_all (self);
121       priv->modified = FALSE;
122     }
123
124   return FALSE;
125 }
126
127 static void
128 publish_to_conn (PositionPublisher *self,
129     TpConnection *conn)
130 {
131   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
132
133   if (priv->location == NULL)
134     return;
135
136   if (priv->throttle_timeout != 0)
137     /* We are waiting */
138     return;
139
140   tp_cli_connection_interface_location_call_set_location (conn, -1,
141       priv->location, set_location_cb, NULL, NULL, G_OBJECT (self));
142
143   /* We won't publish during the next PUBLISH_THROTTLE seconds */
144   priv->throttle_timeout = g_timeout_add_seconds (PUBLISH_THROTTLE,
145       publish_throttle_timeout_cb, self);
146 }
147
148 static void
149 publish_to_all (PositionPublisher *self)
150 {
151   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
152   GSList *l;
153
154   for (l = priv->connections; l != NULL; l = g_slist_next (l))
155     {
156       TpConnection *conn = l->data;
157
158       publish_to_conn (self, conn);
159     }
160 }
161
162 static void
163 update_position (PositionPublisher *self,
164     gdouble lat,
165     gdouble lon,
166     gdouble alt,
167     gdouble accuracy)
168 {
169   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
170
171   if (priv->blur)
172     {
173       /* Truncate at 1 decimal place */
174       lon = ((int) (lon * 10)) / 10.0;
175       lat = ((int) (lat * 10)) / 10.0;
176
177       /* FIXME: change accuracy (not that easy as accuracy is in meters) */
178     }
179
180   g_print ("update position: lat: %f  lon:  %f  alt: %f accuracy: %f\n",
181       lat, lon, alt, accuracy);
182
183   if (priv->location != NULL)
184     g_hash_table_unref (priv->location);
185
186   priv->location = tp_asv_new (
187       "timestamp", G_TYPE_INT64, (gint64) time (NULL),
188       "lat", G_TYPE_DOUBLE, lat,
189       "lon", G_TYPE_DOUBLE, lon,
190       "alt", G_TYPE_DOUBLE, alt,
191       "accuracy", G_TYPE_DOUBLE, accuracy,
192       NULL);
193
194   priv->modified = TRUE;
195
196   publish_to_all (self);
197 }
198
199 static void
200 location_changed_cb (LocationGPSDevice *device,
201     PositionPublisher *self)
202 {
203   if (device == NULL)
204     return;
205
206   if (device->fix == NULL)
207     return;
208
209   if (!(device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET))
210     return;
211
212   update_position (self, device->fix->latitude, device->fix->longitude,
213       device->fix->altitude, device->fix->eph / 100.0);
214 }
215
216 static void
217 conn_invalidated_cb (TpProxy *conn,
218     guint domain,
219     gint code,
220     gchar *message,
221     PositionPublisher *self)
222 {
223   g_print ("connection %s invalidated; removing\n", tp_proxy_get_object_path (
224         conn));
225
226   remove_connection (self, TP_CONNECTION (conn));
227 }
228
229 static void
230 connection_added_cb (ConnectionWatcher *watcher,
231     TpConnection *conn,
232     PositionPublisher *self)
233 {
234   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
235
236   if (g_slist_find (priv->connections, conn) != NULL)
237     return;
238
239   if (!tp_proxy_has_interface_by_id (conn,
240         TP_IFACE_QUARK_CONNECTION_INTERFACE_LOCATION))
241     return;
242
243   priv->connections = g_slist_prepend (priv->connections, g_object_ref (conn));
244
245   g_signal_connect (conn, "invalidated",
246       G_CALLBACK (conn_invalidated_cb), self);
247
248   publish_to_conn (self, conn);
249 }
250
251 static void
252 position_publisher_init (PositionPublisher *obj)
253 {
254   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (obj);
255
256   priv->watcher = connection_watcher_new ();
257
258   g_signal_connect (priv->watcher, "connection-added",
259       G_CALLBACK (connection_added_cb), obj);
260
261   priv->gps_device = g_object_new (LOCATION_TYPE_GPS_DEVICE, NULL);
262
263   g_signal_connect (priv->gps_device, "changed",
264       G_CALLBACK (location_changed_cb), obj);
265
266   priv->connections = NULL;
267 }
268
269 static void
270 position_publisher_constructed (GObject *object)
271 {
272   PositionPublisher *self = POSITION_PUBLISHER (object);
273   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
274
275   connection_watcher_start (priv->watcher);
276
277   if (G_OBJECT_CLASS (position_publisher_parent_class)->constructed)
278     G_OBJECT_CLASS (position_publisher_parent_class)->constructed (object);
279 }
280
281 static void position_publisher_dispose (GObject *object);
282 static void position_publisher_finalize (GObject *object);
283 static void position_publisher_get_property (GObject *object,
284     guint property_id, GValue *value, GParamSpec *pspec);
285 static void position_publisher_set_property (GObject *object,
286     guint property_id, const GValue *value, GParamSpec *pspec);
287
288 static void
289 position_publisher_class_init (PositionPublisherClass *position_publisher_class)
290 {
291   GObjectClass *object_class = G_OBJECT_CLASS (position_publisher_class);
292   GParamSpec *param_spec;
293
294   g_type_class_add_private (position_publisher_class, sizeof (PositionPublisherPrivate));
295
296   object_class->dispose = position_publisher_dispose;
297   object_class->finalize = position_publisher_finalize;
298   object_class->get_property = position_publisher_get_property;
299   object_class->set_property = position_publisher_set_property;
300
301   object_class->constructed = position_publisher_constructed;
302
303   param_spec = g_param_spec_boolean ("blur", "Blur?",
304       "Whether the real GPS position is truncated for privacy concerns.", TRUE,
305       G_PARAM_CONSTRUCT | G_PARAM_WRITABLE | G_PARAM_READABLE
306       | G_PARAM_STATIC_STRINGS);
307   g_object_class_install_property (object_class, PROP_BLUR, param_spec);
308 }
309
310 void
311 position_publisher_dispose (GObject *object)
312 {
313   PositionPublisher *self = POSITION_PUBLISHER (object);
314   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
315   GSList *l;
316
317   if (priv->dispose_has_run)
318     return;
319
320   priv->dispose_has_run = TRUE;
321
322   g_object_unref (priv->watcher);
323   g_object_unref (priv->gps_device);
324
325   for (l = priv->connections; l != NULL; l = g_slist_next (l))
326     {
327       g_object_unref (l->data);
328     }
329
330   g_hash_table_unref (priv->location);
331
332   if (priv->throttle_timeout != 0)
333     g_source_remove (priv->throttle_timeout);
334
335   if (G_OBJECT_CLASS (position_publisher_parent_class)->dispose)
336     G_OBJECT_CLASS (position_publisher_parent_class)->dispose (object);
337 }
338
339 void
340 position_publisher_finalize (GObject *object)
341 {
342   PositionPublisher *self = POSITION_PUBLISHER (object);
343   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
344
345   g_slist_free (priv->connections);
346
347   G_OBJECT_CLASS (position_publisher_parent_class)->finalize (object);
348 }
349
350 static void
351 position_publisher_get_property (GObject *object,
352                                  guint property_id,
353                                  GValue *value,
354                                  GParamSpec *pspec)
355 {
356   PositionPublisher *self = POSITION_PUBLISHER (object);
357   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
358
359   switch (property_id) {
360     case PROP_BLUR:
361       g_value_set_boolean (value, priv->blur);
362       break;
363     default:
364       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
365       break;
366   }
367 }
368
369 static void
370 position_publisher_set_property (GObject *object,
371                                  guint property_id,
372                                  const GValue *value,
373                                  GParamSpec *pspec)
374 {
375   PositionPublisher *self = POSITION_PUBLISHER (object);
376   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
377
378   switch (property_id) {
379     case PROP_BLUR:
380       priv->blur = g_value_get_boolean (value);
381       break;
382
383       break;
384     default:
385       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
386       break;
387   }
388 }
389
390 PositionPublisher *
391 position_publisher_new (void)
392 {
393   return g_object_new (POSITION_PUBLISHER_TYPE,
394       NULL);
395 }
396
397 void
398 position_publisher_set_blur (PositionPublisher *self,
399     gboolean blur)
400 {
401   PositionPublisherPrivate *priv = POSITION_PUBLISHER_GET_PRIVATE (self);
402
403   g_print ("%s blurring location\n", blur ? "Start": "Stop");
404   priv->blur = blur;
405   g_object_notify (G_OBJECT (self), "blur");
406 }