1/*
2 * Copyright (C) 2008 Holger Hans Peter Freyther
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library 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 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "GeolocationServiceGtk.h"
22#if ENABLE(GEOLOCATION)
23
24#include "GOwnPtr.h"
25#include "NotImplemented.h"
26#include "PositionOptions.h"
27#include <wtf/text/CString.h>
28
29namespace WTF {
30    template<> void freeOwnedGPtr<GeoclueAccuracy>(GeoclueAccuracy* accuracy)
31    {
32        if (!accuracy)
33            return;
34
35        geoclue_accuracy_free(accuracy);
36    }
37}
38
39namespace WebCore {
40
41GeolocationService* GeolocationServiceGtk::create(GeolocationServiceClient* client)
42{
43    return new GeolocationServiceGtk(client);
44}
45
46GeolocationService::FactoryFunction* GeolocationService::s_factoryFunction = &GeolocationServiceGtk::create;
47
48GeolocationServiceGtk::GeolocationServiceGtk(GeolocationServiceClient* client)
49    : GeolocationService(client)
50    , m_geoclueClient(0)
51    , m_geocluePosition(0)
52    , m_latitude(0.0)
53    , m_longitude(0.0)
54    , m_altitude(0.0)
55    , m_altitudeAccuracy(0.0)
56    , m_timestamp(0)
57{
58}
59
60GeolocationServiceGtk::~GeolocationServiceGtk()
61{
62    if (m_geoclueClient)
63        g_object_unref(m_geoclueClient);
64
65    if (m_geocluePosition)
66        g_object_unref(m_geocluePosition);
67}
68
69//
70// 1.) Initialize Geoclue with our requirements
71// 2.) Try to get a GeocluePosition
72// 3.) Update the Information and get the current position
73//
74// TODO: Also get GeoclueVelocity but there is no master client
75//       API for that.
76//
77bool GeolocationServiceGtk::startUpdating(PositionOptions* options)
78{
79    ASSERT(!m_geoclueClient);
80
81    m_lastPosition = 0;
82    m_lastError = 0;
83
84    GOwnPtr<GError> error;
85    GeoclueMaster* master = geoclue_master_get_default();
86    GeoclueMasterClient* client = geoclue_master_create_client(master, 0, 0);
87    g_object_unref(master);
88
89    if (!client) {
90        setError(PositionError::POSITION_UNAVAILABLE, "Could not connect to location provider.");
91        return false;
92    }
93
94    GeoclueAccuracyLevel accuracyLevel = GEOCLUE_ACCURACY_LEVEL_LOCALITY;
95    int timeout = 0;
96    if (options) {
97        accuracyLevel = options->enableHighAccuracy() ? GEOCLUE_ACCURACY_LEVEL_DETAILED : GEOCLUE_ACCURACY_LEVEL_LOCALITY;
98        if (options->hasTimeout())
99            timeout = options->timeout();
100    }
101
102    gboolean result = geoclue_master_client_set_requirements(client, accuracyLevel, timeout,
103                                                             false, GEOCLUE_RESOURCE_ALL, &error.outPtr());
104
105    if (!result) {
106        setError(PositionError::POSITION_UNAVAILABLE, error->message);
107        g_object_unref(client);
108        return false;
109    }
110
111    m_geocluePosition = geoclue_master_client_create_position(client, &error.outPtr());
112    if (!m_geocluePosition) {
113        setError(PositionError::POSITION_UNAVAILABLE, error->message);
114        g_object_unref(client);
115        return false;
116    }
117
118    m_geoclueClient = client;
119
120    geoclue_position_get_position_async(m_geocluePosition, (GeocluePositionCallback)getPositionCallback, this);
121
122    g_signal_connect(G_OBJECT(m_geocluePosition), "position-changed",
123                     G_CALLBACK(position_changed), this);
124
125    return true;
126}
127
128void GeolocationServiceGtk::stopUpdating()
129{
130    if (!m_geoclueClient)
131        return;
132
133    g_object_unref(m_geocluePosition);
134    g_object_unref(m_geoclueClient);
135
136    m_geocluePosition = 0;
137    m_geoclueClient = 0;
138}
139
140void GeolocationServiceGtk::suspend()
141{
142    // not available with geoclue
143    notImplemented();
144}
145
146void GeolocationServiceGtk::resume()
147{
148    // not available with geoclue
149    notImplemented();
150}
151
152Geoposition* GeolocationServiceGtk::lastPosition() const
153{
154    return m_lastPosition.get();
155}
156
157PositionError* GeolocationServiceGtk::lastError() const
158{
159    return m_lastError.get();
160}
161
162void GeolocationServiceGtk::updatePosition()
163{
164    m_lastError = 0;
165
166    RefPtr<Coordinates> coordinates = Coordinates::create(m_latitude, m_longitude,
167                                                          true, m_altitude, m_accuracy,
168                                                          true, m_altitudeAccuracy, false, 0.0, false, 0.0);
169    m_lastPosition = Geoposition::create(coordinates.release(), m_timestamp * 1000.0);
170    positionChanged();
171}
172
173void GeolocationServiceGtk::getPositionCallback(GeocluePosition *position,
174                                                GeocluePositionFields fields,
175                                                int timestamp,
176                                                double latitude,
177                                                double longitude,
178                                                double altitude,
179                                                GeoclueAccuracy* accuracy,
180                                                GError* error,
181                                                GeolocationServiceGtk* that)
182{
183    if (error) {
184        that->setError(PositionError::POSITION_UNAVAILABLE, error->message);
185        g_error_free(error);
186        return;
187    }
188    position_changed(position, fields, timestamp, latitude, longitude, altitude, accuracy, that);
189}
190
191void GeolocationServiceGtk::position_changed(GeocluePosition*, GeocluePositionFields fields, int timestamp, double latitude, double longitude, double altitude, GeoclueAccuracy* accuracy, GeolocationServiceGtk* that)
192{
193    if (!(fields & GEOCLUE_POSITION_FIELDS_LATITUDE && fields & GEOCLUE_POSITION_FIELDS_LONGITUDE)) {
194        that->setError(PositionError::POSITION_UNAVAILABLE, "Position could not be determined.");
195        return;
196    }
197
198    that->m_timestamp = timestamp;
199    that->m_latitude = latitude;
200    that->m_longitude = longitude;
201    that->m_altitude = altitude;
202
203    GeoclueAccuracyLevel level;
204    geoclue_accuracy_get_details(accuracy, &level, &that->m_accuracy, &that->m_altitudeAccuracy);
205    that->updatePosition();
206}
207
208void GeolocationServiceGtk::setError(PositionError::ErrorCode errorCode, const char* message)
209{
210    m_lastPosition = 0;
211    m_lastError = PositionError::create(errorCode, String::fromUTF8(message));
212}
213
214}
215#endif // ENABLE(GEOLOCATION)
216