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