summaryrefslogblamecommitdiff
path: root/src/lib/elocation/elocation.c
blob: dc00a00aa1f4b4d4b2697e313ad9df708a0f7ded (plain) (tree)



































                                                                           
                                                      




                                                                









                                            
 
                     

                            
 
           
                                                                     
























                                                                                   
                                                                                                           
























                                                                                
                                                                                                            




































































































                                                                                         

                                                                         
































                                              


                                                                                      









                                                                           
                                                                                                     






















                                                                                    
                                                                                                




















                                                                                        
                                                                                                  


                                                                                           


                                                                  











                                                                                                             
                                                                                                    





































                                                                              


                                                                                      







                                                                            
                                                                                                    



















                                                                           
                                                                      

























































                                                                              
                                                                                                     



















                                                                           
                                                                       




























                                                                               
                                                                                                 



















                                                                           
                                                                   



































                                                                                        


                                                                                                      






                                                                           
                                                                                                      



















                                                                           
                                                                                                           



















                                                                           
                                                                        

























































                                                                                      
                                                                                                     



















                                                                           
                                                                       











                                                                           
                                                                                       





                                                 
                                                                                      





                              
                                                                                                           












                                                      
                                                                                                                       




                                                                          
                                                                                                   








                                                      
                                                          




                                                  
                                                                 
                                                                           
                                                                       


           
                                                                     
 
                                                          




                                                  
                                                                 
                                                                           
                                                                       


           
                                                                                                             










                                                                                
                                                                                                   


















































































































































































                                                                                                    
                                                                                                          




















                                                                                    
                                                                                                                       




























                                                                      
                                                                                                                 
























                                                                                        
                                                                                                                 





















































                                                                              
                                                                                                                     













                                                                                                                      
                                                   
 
                              







                                                           
                                                     
 
                                







                                               
                                                  
 
                           















































































































                                                                               













                                                                

















































































                                                                                                 












                                                         



















































                                                                                              
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>

#include <Eina.h>
#include <Ecore.h>
#include <Eldbus.h>
#include <Elocation.h>
#include <elocation_private.h>

/* FIXME: These globals really need to get reduced before leaving the PROTO
 * area.
 */
static char *unique_name = NULL;
static Eldbus_Connection *conn = NULL;
static Elocation_Provider *address_provider = NULL;
static Elocation_Provider *position_provider = NULL;
static Eldbus_Object *obj_meta = NULL;
static Eldbus_Proxy *manager_master = NULL;
static Eldbus_Proxy *meta_geoclue = NULL;
static Eldbus_Proxy *meta_address = NULL;
static Eldbus_Proxy *meta_position = NULL;
static Eldbus_Proxy *meta_masterclient = NULL;
static Eldbus_Proxy *meta_velocity = NULL;
static Eldbus_Proxy *meta_nmea = NULL;
static Eldbus_Proxy *meta_satellite = NULL;
static Eldbus_Proxy *geonames_geocode = NULL;
static Eldbus_Proxy *geonames_rgeocode = NULL;
static Eldbus_Proxy *master_poi = NULL;
static Elocation_Address *address = NULL;
static Elocation_Position *position = NULL;
static Elocation_Address *addr_geocode = NULL;
static Elocation_Position *pos_geocode = NULL;
static Elocation_Velocity *velocity = NULL;
static int status = -1; /* 0 is a valid status code */
static char nmea_sentence[256];

int _elocation_log_dom = -1;

/* Elocation ecore event types we provide to the application. */
EAPI int ELOCATION_EVENT_STATUS = 0;
EAPI int ELOCATION_EVENT_POSITION = 0;
EAPI int ELOCATION_EVENT_ADDRESS = 0;
EAPI int ELOCATION_EVENT_VELOCITY = 0;
EAPI int ELOCATION_EVENT_GEOCODE = 0;
EAPI int ELOCATION_EVENT_REVERSEGEOCODE = 0;
EAPI int ELOCATION_EVENT_NMEA = 0;
EAPI int ELOCATION_EVENT_SATELLITE = 0;
EAPI int ELOCATION_EVENT_POI = 0;
EAPI int ELOCATION_EVENT_META_READY = 0;

/* Internal events */
int ELOCATION_EVENT_IN = 0;
int ELOCATION_EVENT_OUT = 0;

static void
_dummy_free(void *user_data EINA_UNUSED, void *func_data EINA_UNUSED)
{
   /* Don't free the event data after dispatching the event. We keep track of
    * it on our own
    */
}

/* Generic provider message unmarshaller. Used from all different provider
 * calbacks that receive such a message
 */
static Eina_Bool
unmarshall_provider(const Eldbus_Message *reply, Elocation_Provider *provider)
{
   char *name = NULL, *desc = NULL, *service = NULL, *path = NULL;

   if (!eldbus_message_arguments_get(reply, "ssss", &name, &desc, &service, &path))
     return EINA_FALSE;

   provider->name = strdup(name);
   provider->description = strdup(desc);
   provider->service = strdup(service);
   provider->path = strdup(path);
   return EINA_TRUE;
}

static void
meta_address_provider_info_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;
   Elocation_Provider *addr_provider;

   addr_provider = data;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   if (!unmarshall_provider(reply, addr_provider))
     {
        ERR("Error: Unable to unmarshall address provider");
        return;
     }

   DBG("Meta address provider name: %s, %s, %s, %s", addr_provider->name,
                                                     addr_provider->description,
                                                     addr_provider->service,
                                                     addr_provider->path);
}

static void
meta_position_provider_info_cb(void *data, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;
   Elocation_Provider *pos_provider;

   pos_provider = data;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   if (!unmarshall_provider(reply, pos_provider))
     {
        ERR("Error: Unable to unmarshall position provider");
        return;
     }

   DBG("Meta position provider name: %s, %s, %s, %s", pos_provider->name,
                                                      pos_provider->description,
                                                      pos_provider->service,
                                                      pos_provider->path);
}

static void
meta_address_provider_info_signal_cb(void *data, const Eldbus_Message *reply)
{
   Elocation_Provider *addr_provider;
   addr_provider = data;

   if (!unmarshall_provider(reply, addr_provider))
     {
        ERR("Error: Unable to unmarshall address provider");
        return;
     }

   DBG("Meta address provider name changed: %s, %s, %s, %s", addr_provider->name,
                                                             addr_provider->description,
                                                             addr_provider->service,
                                                             addr_provider->path);
}

static void
meta_position_provider_info_signal_cb(void *data, const Eldbus_Message *reply)
{
   Elocation_Provider *pos_provider;
   pos_provider = data;

   if (!unmarshall_provider(reply, pos_provider))
     {
        ERR("Error: Unable to unmarshall position provider");
        return;
     }

   DBG("Meta position provider name changed: %s, %s, %s, %s", pos_provider->name,
                                                              pos_provider->description,
                                                              pos_provider->service,
                                                              pos_provider->path);
}

/* A address is quite flexible what kind of key value pairs it contains in the
 * dict. Similar to a reverse GeoCode message as both return an address object.
 */
static Eina_Bool
unmarshall_address(const Eldbus_Message *reply, Elocation_Address *addr)
{
   int32_t level, timestamp;
   Eldbus_Message_Iter *sub, *dict, *entry;
   double horizontal;
   double vertical;
   const char *key, *signature;
   char *value;

   signature = eldbus_message_signature_get(reply);

   if (!strcmp(signature, "ia{ss}(idd)"))
     {
        if (!eldbus_message_arguments_get(reply, "ia{ss}(idd)", &timestamp, &dict, &sub))
          return EINA_FALSE;
        addr->timestamp = timestamp;
     }
   else if (!strcmp(signature, "a{ss}(idd)"))
     {
        if (!eldbus_message_arguments_get(reply, "a{ss}(idd)", &dict, &sub))
          return EINA_FALSE;
        addr->timestamp = 0;
      }
   else
     return EINA_FALSE;


   /* Cleanup potential old entries before re-using */
   addr->country = NULL;
   addr->countrycode = NULL;
   addr->locality = NULL;
   addr->postalcode = NULL;
   addr->region = NULL;
   addr->timezone = NULL;

   while (eldbus_message_iter_get_and_next(dict, 'e', &entry))
    {
       if (!eldbus_message_iter_arguments_get(entry, "ss", &key, &value))
         continue;

       if (!strcmp(key, "country"))
         {
            free(addr->country);
            addr->country = strdup(value);
         }
       else if (!strcmp(key, "countrycode"))
         {
            free(addr->countrycode);
            addr->countrycode = strdup(value);
         }
       else if (!strcmp(key, "locality"))
         {
            free(addr->locality);
            addr->locality = strdup(value);
         }
       else if (!strcmp(key, "postalcode"))
         {
            free(addr->postalcode);
            addr->postalcode = strdup(value);
         }
       else if (!strcmp(key, "region"))
         {
            free(addr->region);
            addr->region = strdup(value);
         }
       else if (!strcmp(key, "timezone"))
         {
            free(addr->timezone);
            addr->timezone = strdup(value);
         }
    }

   if (!eldbus_message_iter_arguments_get(sub, "idd", &level, &horizontal, &vertical))
     return EINA_FALSE;

   addr->accur->level = level;
   addr->accur->horizontal = horizontal;
   addr->accur->vertical = vertical;
   return EINA_TRUE;
}

/* Receive and unmarshall a reverse GeoCode message. The dict can contain a
 * variable set of key value pairs so we need to handle this with care
 */
static void
rgeocode_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   if (!unmarshall_address(reply, addr_geocode))
     {
        ERR("Error: Unable to unmarshall address");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_REVERSEGEOCODE, addr_geocode, _dummy_free, NULL);
}

/* Point of Interest (POI) aka landmark message unmarshalling. Thsi interface is
 * not in standard GeoClue but currently a Tizen extension.
 */
static void
poi_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   int32_t count, id, rank;
   double lat, lon, bound_left, bound_top, bound_right, bound_bottom;
   const char *name, *icon, *house, *road, *village, *suburb, *postcode;
   const char *city, *county, *country, *country_code;
   Eldbus_Message_Iter *array, *struct_landmark;
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   /* Yeah, its quite a horrible message. The POI interface could use a better design */
   if (!eldbus_message_arguments_get(reply, "ia(iiddddddsssssssssss", &count ,&array))
     return;

   /* TODO re-check that the parameter ordering is what we expect */
   while (eldbus_message_iter_get_and_next(array, 'r', &struct_landmark))
     {
        if (!eldbus_message_iter_arguments_get(struct_landmark, "iiddddddsssssssssss", &id, &rank,
                                         &lat, &lon, &bound_left, &bound_top, &bound_right,
                                         &bound_bottom, &name, &icon, &house, &road,
                                         &village, &suburb, &postcode, &city, &county,
                                         &country, &country_code))
          return;

        DBG("Landmark received: %i, %i, %f, %f, %f, %f, %f, %f, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,",
                                         id, rank, lat, lon, bound_left, bound_top, bound_right,
                                         bound_bottom, name, icon, house, road, village,
                                         suburb, postcode, city, county, country, country_code);
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_POI, NULL, _dummy_free, NULL);
}

/* Unmarshall a GeoCode message */
static void
geocode_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   GeocluePositionFields fields;
   int32_t level;
   double horizontal = 0.0;
   double vertical = 0.0;
   double latitude = 0.0;
   double longitude = 0.0;
   double altitude = 0.0;
   Eldbus_Message_Iter *sub;
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   if (!eldbus_message_arguments_get(reply, "iddd(idd)", &fields,&latitude,
                                    &longitude, &altitude, &sub))
     return;

   /* GeoClue uses some flags to mark position fields as valid. We set invalid
    * fields to 0.0 */
   if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE)
      pos_geocode->latitude = latitude;
   else
      pos_geocode->latitude = 0.0;

   if (fields & GEOCLUE_POSITION_FIELDS_LONGITUDE)
      pos_geocode->longitude = longitude;
   else
      pos_geocode->longitude = 0.0;

   if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE)
      pos_geocode->altitude = altitude;
   else
      pos_geocode->altitude = 0.0;

   if (!eldbus_message_iter_arguments_get(sub, "idd", &level, &horizontal, &vertical))
     return;

   pos_geocode->accur->level = level;
   pos_geocode->accur->horizontal = horizontal;
   pos_geocode->accur->vertical = vertical;
   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_GEOCODE, pos_geocode, _dummy_free, NULL);
}

static void
address_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   if (!unmarshall_address(reply, address))
     {
        ERR("Error: Unable to unmarshall address");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_ADDRESS, address, _dummy_free, NULL);
}

static void
address_signal_cb(void *data EINA_UNUSED, const Eldbus_Message *reply)
{
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   if (!unmarshall_address(reply, address))
     {
        ERR("Error: Unable to unmarshall address");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_ADDRESS, address, _dummy_free, NULL);
}

/* Unmarshall a velocity message. This is only available if we use a GPS
 * provider from GeoClue. None of the other providers offer this currently.
 */
static Eina_Bool
unmarshall_velocity(const Eldbus_Message *reply)
{
   GeoclueVelocityFields fields;
   int32_t timestamp = 0;
   double speed = 0.0;
   double direction = 0.0;
   double climb = 0.0;

   if (!eldbus_message_arguments_get(reply, "iiddd", &fields, &timestamp,
                                    &speed, &direction, &climb))
     return EINA_FALSE;

   velocity->timestamp = timestamp;

   /* GeoClue uses some flags to mark velocity fields as valid. We set invalid
    * fields to 0.0 */
   if (fields & GEOCLUE_VELOCITY_FIELDS_SPEED)
      velocity->speed = speed;
   else
      velocity->speed = 0.0;

   if (fields & GEOCLUE_VELOCITY_FIELDS_DIRECTION)
      velocity->direction = direction;
   else
      velocity->direction = 0.0;

   if (fields & GEOCLUE_VELOCITY_FIELDS_CLIMB)
      velocity->climb = climb;
   else
      velocity->climb = 0.0;

   return EINA_TRUE;
}

static void
velocity_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        WARN("Warning: %s %s", err, errmsg);
        return;
     }

   if (!unmarshall_velocity(reply))
     {
        ERR("Error: Unable to unmarshall velocity");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_VELOCITY, velocity, _dummy_free, NULL);
}

static void
velocity_signal_cb(void *data EINA_UNUSED, const Eldbus_Message *reply)
{
   if (!unmarshall_velocity(reply))
     {
        ERR("Error: Unable to unmarshall velocity");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_VELOCITY, velocity, _dummy_free, NULL);
}

/* Unmarshall an raw NMEA message. It conatins a raw NMEA sentence which we can
 * pass on to applications that want to use their own NMEA parser. This is not
 * reommended.  Better use the other interfaces to access the needed data.
 *
 * This is currently a Tizen only interface and not in GeoClue upstream.
 */
static Eina_Bool
unmarshall_nmea(const Eldbus_Message *reply)
{
   int32_t timestamp = 0;

   if (!eldbus_message_arguments_get(reply, "is", &timestamp, &nmea_sentence))
     return EINA_FALSE;

   return EINA_TRUE;
}

static void
nmea_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        WARN("Warning: %s %s", err, errmsg);
        return;
     }

   if (!unmarshall_nmea(reply))
     {
        ERR("Error: Unable to unmarshall nmea");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_NMEA, nmea_sentence, _dummy_free, NULL);
}

static void
nmea_signal_cb(void *data EINA_UNUSED, const Eldbus_Message *reply)
{
   if (!unmarshall_nmea(reply))
     {
        ERR("Error: Unable to unmarshall nmea");
        return;
     }

   ecore_event_add(ELOCATION_EVENT_NMEA, nmea_sentence, _dummy_free, NULL);
}

/* Unmarshall a satellite information message. This offers GPS specific
 * information about the used satellites and its properties. It can be used for
 * applications that rely on GPS and want to show more information like a 3D fix
 * or used satellites.
 *
 * This is currently a Tizen only interface and not available in GeoClue upstream.
 */
static Eina_Bool
unmarshall_satellite(const Eldbus_Message *reply)
{
   int32_t timestamp = 0, satellite_used = 0, satellite_visible = 0;
   int32_t snr = 0, elevation = 0, azimuth = 0, prn = 0, used_prn = 0;
   Eldbus_Message_Iter *sub_prn, *sub_info, *struct_info;

   if (!eldbus_message_arguments_get(reply, "iiiaia(iiii)", &timestamp, &satellite_used,
                                    &satellite_visible, &sub_prn, &sub_info))
     return EINA_FALSE;

   while (eldbus_message_iter_get_and_next(sub_prn, 'i', &used_prn))
     {
       DBG("Satellite used PRN %i", used_prn);
     }

   /* TODO re-check that the parameter ordering is what we expect */
   while (eldbus_message_iter_get_and_next(sub_info, 'r', &struct_info))
     {
        if (!eldbus_message_iter_arguments_get(struct_info, "iiii", &prn, &elevation, &azimuth, &snr))
          return EINA_FALSE;

        DBG("Satellite info %i, %i, %i, %i", prn, elevation, azimuth, snr);
     }

   return EINA_TRUE;
}

static void
satellite_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        WARN("Warning: %s %s", err, errmsg);
        return;
     }

   if (!unmarshall_satellite(reply))
     {
        ERR("Error: Unable to unmarshall satellite");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_SATELLITE, NULL, _dummy_free, NULL);
}

static void
last_satellite_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        WARN("Warning: %s %s", err, errmsg);
        return;
     }

   if (!unmarshall_satellite(reply))
     {
        ERR("Error: Unable to unmarshall last satellite");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_SATELLITE, NULL, _dummy_free, NULL);
}

static void
satellite_signal_cb(void *data EINA_UNUSED, const Eldbus_Message *reply)
{
   if (!unmarshall_satellite(reply))
     {
        ERR("Error: Unable to unmarshall satellite");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_SATELLITE, NULL, _dummy_free, NULL);
}

/* Unmarshall position coordination message */
static Eina_Bool
unmarshall_position(const Eldbus_Message *reply)
{
   GeocluePositionFields fields;
   int32_t level, timestamp;
   double horizontal = 0.0;
   double vertical = 0.0;
   double latitude = 0.0;
   double longitude = 0.0;
   double altitude = 0.0;
   Eldbus_Message_Iter *sub;

   if (!eldbus_message_arguments_get(reply, "iiddd(idd)", &fields, &timestamp,
                                    &latitude, &longitude, &altitude, &sub))
     return EINA_FALSE;

   if (!eldbus_message_iter_arguments_get(sub, "idd", &level, &horizontal, &vertical))
     return EINA_FALSE;

   position->timestamp = timestamp;

   /* GeoClue uses some flags to mark position fields as valid. We set invalid
    * fields to 0.0 */
   if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE)
      position->latitude = latitude;
   else
      position->latitude = 0.0;

   if (fields & GEOCLUE_POSITION_FIELDS_LONGITUDE)
      position->longitude = longitude;
   else
      position->longitude = 0.0;

   if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE)
      position->altitude = altitude;
   else
      position->altitude = 0.0;

   position->accur->level = level;
   position->accur->horizontal = horizontal;
   position->accur->vertical = vertical;

   return EINA_TRUE;
}

static void
position_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   if (!unmarshall_position(reply))
     {
        ERR("Error: Unable to unmarshall position");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_POSITION, position, _dummy_free, NULL);
}

static void
position_signal_cb(void *data EINA_UNUSED, const Eldbus_Message *reply)
{
   if (!unmarshall_position(reply))
     {
        ERR("Error: Unable to unmarshall position");
        return;
     }

   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_POSITION, position, _dummy_free, NULL);
}

static Eina_Bool
geoclue_start(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *event EINA_UNUSED)
{
   DBG("GeoClue start event at %s", unique_name);
   return ECORE_CALLBACK_DONE;
}

static Eina_Bool
geoclue_stop(void *data EINA_UNUSED, int ev_type EINA_UNUSED, void *event EINA_UNUSED)
{
   DBG("GeoClue stop event");
   return ECORE_CALLBACK_DONE;
}

static void
_reference_add_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   DBG("Reference added");
}

static void
_reference_del_cb(void *data EINA_UNUSED, const Eldbus_Message *reply EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
   /* Dummy callback. We are not waiting for any reply here on shutdown */
}

static void
status_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   if (!eldbus_message_arguments_get(reply,"i",  &status))
     {
        ERR("Error: Unable to unmarshall status");
        return;
     }

   address_provider->status = position_provider->status = status;
   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_STATUS, &status, _dummy_free, NULL);
}

static void
status_signal_cb(void *data EINA_UNUSED, const Eldbus_Message *reply)
{
   if (!eldbus_message_arguments_get(reply,"i",  &status))
     {
        ERR("Error: Unable to unmarshall status");
        return;
     }

   address_provider->status = position_provider->status = status;
   /* Send out an event to all interested parties that we have an update */
   ecore_event_add(ELOCATION_EVENT_STATUS, &status, _dummy_free, NULL);
}

static void
_dummy_cb(void *data EINA_UNUSED, const Eldbus_Message *msg EINA_UNUSED, Eldbus_Pending *pending EINA_UNUSED)
{
}

/* We got notified from GeoClue that the meta-provider we asked for is now
 * ready. That means we can finish up our initialization and set up all
 * callbacks and handling for the interfaces we use on the meta-provider.
 *
 * We also call the interfaces to get an initial set of data that we can provide
 * to eager aplications.
 */
static void
create_cb(void *data EINA_UNUSED, const Eldbus_Message *reply, Eldbus_Pending *pending EINA_UNUSED)
{
   const char *object_path;
   Eina_Bool updates;
   int accur_level, min_time, resources;
   const char *err, *errmsg;

   if (eldbus_message_error_get(reply, &err, &errmsg))
     {
        ERR("Error: %s %s", err, errmsg);
        return;
     }

   if (!eldbus_message_arguments_get(reply, "o", &object_path)) return;

   DBG("Object path for client: %s", object_path);

   /* With the created object path we now have a meta provider we can operate on.
    * Geoclue handles the selection of the best provider internally for the meta
    * provider */
   obj_meta = eldbus_object_get(conn, GEOCLUE_DBUS_NAME, object_path);
   if (!obj_meta)
     {
        ERR("Error: could not get object for client");
        return;
     }

   meta_geoclue = eldbus_proxy_get(obj_meta, GEOCLUE_GEOCLUE_IFACE);
   if (!meta_geoclue)
     {
        ERR("Error: could not get proxy for geoclue");
        return;
     }

   meta_address = eldbus_proxy_get(obj_meta, GEOCLUE_ADDRESS_IFACE);
   if (!meta_address)
     {
        ERR("Error: could not get proxy address");
        return;
     }

   meta_position = eldbus_proxy_get(obj_meta, GEOCLUE_POSITION_IFACE);
   if (!meta_position)
     {
        ERR("Error: could not get proxy for position");
        return;
     }

   meta_masterclient = eldbus_proxy_get(obj_meta, GEOCLUE_MASTERCLIENT_IFACE);
   if (!meta_masterclient)
     {
        ERR("Error: could not get proxy for master client");
        return;
     }

   meta_velocity = eldbus_proxy_get(obj_meta, GEOCLUE_VELOCITY_IFACE);
   if (!meta_velocity)
     {
        ERR("Error: could not get proxy for velocity");
        return;
     }

   meta_nmea = eldbus_proxy_get(obj_meta, GEOCLUE_NMEA_IFACE);
   if (!meta_nmea)
     {
        ERR("Error: could not get proxy for nmea");
        return;
     }

   meta_satellite = eldbus_proxy_get(obj_meta, GEOCLUE_SATELLITE_IFACE);
   if (!meta_satellite)
     {
        ERR("Error: could not get proxy for satellite");
        return;
     }

   /* Send Geoclue a set of requirements we have for the provider and start the address and position
    * meta provider afterwards. After this we should be ready for operation. */
   updates = EINA_FALSE; /* Especially the web providers do not offer updates */
   accur_level = ELOCATION_ACCURACY_LEVEL_COUNTRY;
   min_time = 0; /* Minimal times between updates (no implemented yet) */
   resources = ELOCATION_RESOURCE_ALL;

   eldbus_proxy_signal_handler_add(meta_masterclient, "AddressProviderChanged",
                                  meta_address_provider_info_signal_cb, address_provider);
   eldbus_proxy_signal_handler_add(meta_masterclient, "PositionProviderChanged",
                                  meta_position_provider_info_signal_cb, position_provider);

   if (!eldbus_proxy_call(meta_masterclient, "SetRequirements", _dummy_cb, NULL, -1, "iibi",
                         accur_level, min_time, updates, resources))
     {
        ERR("Error: could not call SetRequirements");
        return;
     }

   if (!eldbus_proxy_call(meta_masterclient, "AddressStart", _dummy_cb, NULL, -1, ""))
     {
        ERR("Error: could not call AddressStart");
        return;
     }

   if (!eldbus_proxy_call(meta_masterclient, "PositionStart", _dummy_cb, NULL, -1, ""))
     {
        ERR("Error: could not call PositionStart");
        return;
     }

   if (!eldbus_proxy_call(meta_geoclue, "AddReference", _reference_add_cb, NULL, -1, ""))
     {
        ERR("Error: could not call AddReference");
        return;
     }

   if (!eldbus_proxy_call(meta_address, "GetAddress", address_cb, NULL, -1, ""))
     {
        ERR("Error: could not call GetAddress");
        return;
     }

   if (!eldbus_proxy_call(meta_position, "GetPosition", position_cb, NULL, -1, ""))
     {
        ERR("Error: could not call GetPosition");
        return;
     }

   if (!eldbus_proxy_call(meta_geoclue, "GetStatus", status_cb, NULL, -1, ""))
     {
        ERR("Error: could not call GetStatus");
        return;
     }

   if (!eldbus_proxy_call(meta_velocity, "GetVelocity", velocity_cb, NULL, -1, ""))
     {
        ERR("Error: could not call GetVelocity");
        return;
     }

   if (!eldbus_proxy_call(meta_nmea, "GetNmea", nmea_cb, NULL, -1, ""))
     {
        ERR("Error: could not call GetNmea");
        return;
     }

   if (!eldbus_proxy_call(meta_satellite, "GetSatellite", satellite_cb, NULL, -1, ""))
     {
        ERR("Error: could not call GetSatellite");
        return;
     }

   if (!eldbus_proxy_call(meta_satellite, "GetLastSatellite", last_satellite_cb, NULL, -1, ""))
     {
        ERR("Error: could not call GetLastSatellite");
        return;
     }

   if (!eldbus_proxy_call(meta_masterclient, "GetAddressProvider", meta_address_provider_info_cb,
                         address_provider, -1, ""))
     {
        ERR("Error: could not call GetAddressProvider");
        return;
     }

   if (!eldbus_proxy_call(meta_masterclient, "GetPositionProvider", meta_position_provider_info_cb,
                         position_provider, -1, ""))
     {
        ERR("Error: could not call GetPositionProvider");
        return;
     }

   eldbus_proxy_signal_handler_add(meta_address, "AddressChanged", address_signal_cb, NULL);
   eldbus_proxy_signal_handler_add(meta_position, "PositionChanged", position_signal_cb, NULL);
   eldbus_proxy_signal_handler_add(meta_geoclue, "StatusChanged", status_signal_cb, NULL);
   eldbus_proxy_signal_handler_add(meta_velocity, "VelocityChanged", velocity_signal_cb, NULL);
   eldbus_proxy_signal_handler_add(meta_nmea, "NmeaChanged", nmea_signal_cb, NULL);
   eldbus_proxy_signal_handler_add(meta_satellite, "SatelliteChanged", satellite_signal_cb, NULL);

   ecore_event_add(ELOCATION_EVENT_META_READY, NULL, NULL, NULL);
}

static void
_name_owner_changed(void *data EINA_UNUSED, const char *bus EINA_UNUSED, const char *old, const char *new)
{
   if (old[0] == '\0' && new[0] != '\0')
     {
        ecore_event_add(ELOCATION_EVENT_IN, NULL, NULL, NULL);
        unique_name = strdup(new);
     }
   else if (old[0] != '\0' && new[0] == '\0')
     {
        if (strcmp(unique_name, old) != 0)
           WARN("%s was not the known name %s, ignored.", old, unique_name);
        else
           ecore_event_add(ELOCATION_EVENT_OUT, NULL, NULL, NULL);
     }
   else
     {
        DBG("unknow change from %s to %s", old, new);
     }
}

/* Public API function to request a landmarks position based on an address object */
EAPI Eina_Bool
elocation_landmarks_get(Elocation_Position *position_shadow EINA_UNUSED, Elocation_Address *address_shadow EINA_UNUSED)
{
   Eldbus_Message *msg;
   Eldbus_Message_Iter *iter;
   const char *keyword = NULL, *lang = NULL, *country_code = NULL;
   int limit = 0;
   double left= 0.0, top = 0.0, right = 0.0, bottom = 0.0;

   msg = eldbus_proxy_method_call_new(master_poi, "SearchByPosition");
   iter = eldbus_message_iter_get(msg);
   eldbus_message_iter_basic_append(iter, 's', keyword);
   eldbus_message_iter_basic_append(iter, 's', lang);
   eldbus_message_iter_basic_append(iter, 's', country_code);
   eldbus_message_iter_basic_append(iter, 'i', limit);
   eldbus_message_iter_basic_append(iter, 'd', left);
   eldbus_message_iter_basic_append(iter, 'd', top);
   eldbus_message_iter_basic_append(iter, 'd', right);
   eldbus_message_iter_basic_append(iter, 'd', bottom);
   if (!eldbus_proxy_send(master_poi, msg, poi_cb, NULL, -1))
     {
        ERR("Error: could not call SearchByPosition");
        eldbus_message_unref(msg);
        return EINA_FALSE;
     }

   return EINA_TRUE;
}

/* Public API function to get an address from a position */
EAPI Eina_Bool
elocation_position_to_address(Elocation_Position *position_shadow, Elocation_Address *address_shadow EINA_UNUSED)
{
   Eldbus_Message *msg;
   Eldbus_Message_Iter *iter, *structure;

   msg = eldbus_proxy_method_call_new(geonames_rgeocode, "PositionToAddress");
   iter = eldbus_message_iter_get(msg);
   eldbus_message_iter_basic_append(iter, 'd', position_shadow->latitude);
   eldbus_message_iter_basic_append(iter, 'd', position_shadow->longitude);
   structure = eldbus_message_iter_container_new(iter, 'r', NULL);
   eldbus_message_iter_basic_append(structure, 'i', position_shadow->accur->level);
   eldbus_message_iter_basic_append(structure, 'd', position_shadow->accur->horizontal);
   eldbus_message_iter_basic_append(structure, 'd', position_shadow->accur->vertical);
   eldbus_message_iter_container_close(iter, structure);
   if (!eldbus_proxy_send(geonames_rgeocode, msg, rgeocode_cb, NULL, -1))
     {
        ERR("Error: could not call PositionToAddress");
        eldbus_message_unref(msg);
        return EINA_FALSE;
     }

   return EINA_TRUE;
}

/* Public API function to get a position from and address */
EAPI Eina_Bool
elocation_address_to_position(Elocation_Address *address_shadow, Elocation_Position *position_shadow EINA_UNUSED)
{
   Eldbus_Message *msg;
   Eldbus_Message_Iter *iter, *array;

   /* In function macro to generate a key value pair structure for the dict */
   #define ENTRY(key) { #key, address_shadow->key }
   struct keyval {
      const char *key;
      const char *val;
   } keyval[] = {
      ENTRY(country),
      ENTRY(countrycode),
      ENTRY(locality),
      ENTRY(postalcode),
      ENTRY(region),
      ENTRY(timezone),
      { NULL, NULL }
   };
   #undef ENTRY

   struct keyval *k;

   msg = eldbus_proxy_method_call_new(geonames_geocode, "AddressToPosition");
   iter = eldbus_message_iter_get(msg);

   array = eldbus_message_iter_container_new(iter, 'a', "{ss}");

   for (k = keyval; k && k->key; k++)
     {
        Eldbus_Message_Iter *entry;

        if (!k->val) continue;

        entry = eldbus_message_iter_container_new(array, 'e', NULL);
        eldbus_message_iter_arguments_append(entry, "ss", k->key, k->val);
        eldbus_message_iter_container_close(array, entry);
     }

   eldbus_message_iter_container_close(iter, array);

   if (!eldbus_proxy_send(geonames_geocode, msg, geocode_cb, NULL, -1))
     {
        ERR("Error: could not call AddressToPosition");
        eldbus_message_unref(msg);
        return EINA_FALSE;
     }

   return EINA_TRUE;
}

/* Public API function to get the position from a freeform text input style
 * address
 */
EAPI Eina_Bool
elocation_freeform_address_to_position(const char *freeform_address, Elocation_Position *position_shadow EINA_UNUSED)
{
   if (!eldbus_proxy_call(geonames_geocode, "FreeformAddressToPosition", geocode_cb, NULL, -1, "s", freeform_address))
     {
        ERR("Error: could not call FreeformAddressToPosition");
        return EINA_FALSE;
     }
   return EINA_TRUE;
}

/* Public API function to request the current address */
EAPI Eina_Bool
elocation_address_get(Elocation_Address *address_shadow)
{
   if (!address) return EINA_FALSE;
   if (address == address_shadow) return EINA_TRUE;

   *address_shadow = *address;
   return EINA_TRUE;
}

/* Public API function to request the current position */
EAPI Eina_Bool
elocation_position_get(Elocation_Position *position_shadow)
{
   if (!position) return EINA_FALSE;
   if (position == position_shadow) return EINA_TRUE;

   *position_shadow = *position;
   return EINA_TRUE;
}

/* Public API function to request the status */
EAPI Eina_Bool
elocation_status_get(int *status_shadow)
{
   if (status < 0) return EINA_FALSE;
   if (&status == status_shadow) return EINA_TRUE;

   *status_shadow = status;
   return EINA_TRUE;
}

/* Public API function to create a new position object */
EAPI Elocation_Position *
elocation_position_new(void)
{
   /* Malloc the global struct we operate on here in this lib. This shadows the
    * updated data we are giving to the application */
   position = calloc(1, sizeof(Elocation_Position));
   if (!position) return NULL;

   position->accur = calloc(1, sizeof(Elocation_Accuracy));
   if (!position->accur) return NULL;

   return position;
}

/* Public API function to create an new address object */
EAPI Elocation_Address *
elocation_address_new(void)
{
   /* Malloc the global struct we operate on here in this lib. This shadows the
    * updated data we are giving to the application */
   address = calloc(1, sizeof(Elocation_Address));
   if (!address) return NULL;

   address->accur = calloc(1, sizeof(Elocation_Accuracy));
   if (!address->accur) return NULL;

   return address;
}

/* Public API function to free an position object */
EAPI void
elocation_position_free(Elocation_Position *position_shadow)
{
   if (position != position_shadow)
     {
        ERR("Corrupted position object");
        return;
     }

   free(position->accur);
   free(position);
}

/* Public API function to free an address object */
EAPI void
elocation_address_free(Elocation_Address *address_shadow)
{
   if (address != address_shadow)
     {
        ERR("Corrupted address object");
        return;
     }

   if (address)
     {
        free(address->country);
        free(address->countrycode);
        free(address->locality);
        free(address->postalcode);
        free(address->region);
        free(address->timezone);
        free(address->accur);
        free(address);
     }
}

/* Public API funtion to initialize the elocation library */
EAPI Eina_Bool
elocation_init(void)
{
   Eldbus_Object *obj_master = NULL;
   Eldbus_Object *obj_geonames = NULL;

   if (!eina_init()) return EINA_FALSE;
   if (!ecore_init()) return EINA_FALSE;
   if (!eldbus_init()) return EINA_FALSE;

   _elocation_log_dom = eina_log_domain_register("elocation", EINA_COLOR_BLUE);
   if (_elocation_log_dom < 0)
     {
        EINA_LOG_ERR("Could not register 'elocation' log domain.");
     }

   /* Create objects, one for each kind, we operate on internally */
   address_provider = calloc(1, sizeof(Elocation_Provider));
   position_provider = calloc(1, sizeof(Elocation_Provider));

   addr_geocode = calloc(1, sizeof(Elocation_Address));
   if (!addr_geocode) return EINA_FALSE;

   addr_geocode->accur = calloc(1, sizeof(Elocation_Accuracy));
   if (!addr_geocode->accur) return EINA_FALSE;

   pos_geocode = calloc(1, sizeof(Elocation_Position));
   if (!pos_geocode) return EINA_FALSE;

   pos_geocode->accur = calloc(1, sizeof(Elocation_Accuracy));
   if (!pos_geocode->accur) return EINA_FALSE;

   conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION);
   if (!conn)
     {
      ERR("Error: could not connect to session bus.");
      return EXIT_FAILURE;
     }

   /* Create all ecore event types we send out to interested applications */
   if (ELOCATION_EVENT_IN == 0)
     {
        ELOCATION_EVENT_IN = ecore_event_type_new();
        ELOCATION_EVENT_OUT = ecore_event_type_new();
        ELOCATION_EVENT_STATUS = ecore_event_type_new();
        ELOCATION_EVENT_POSITION = ecore_event_type_new();
        ELOCATION_EVENT_ADDRESS = ecore_event_type_new();
        ELOCATION_EVENT_VELOCITY = ecore_event_type_new();
        ELOCATION_EVENT_GEOCODE = ecore_event_type_new();
        ELOCATION_EVENT_REVERSEGEOCODE = ecore_event_type_new();
        ELOCATION_EVENT_NMEA = ecore_event_type_new();
        ELOCATION_EVENT_SATELLITE = ecore_event_type_new();
        ELOCATION_EVENT_POI = ecore_event_type_new();
        ELOCATION_EVENT_META_READY = ecore_event_type_new();
     }

   obj_master= eldbus_object_get(conn, GEOCLUE_DBUS_NAME, GEOCLUE_OBJECT_PATH);
   if (!obj_master)
     {
        ERR("Error: could not get object");
        return EXIT_FAILURE;
     }

   manager_master = eldbus_proxy_get(obj_master, GEOCLUE_MASTER_IFACE);
   if (!manager_master)
     {
        ERR("Error: could not get proxy");
        return EXIT_FAILURE;
     }

   /* Create a meta provider for all normal use cases. This will allow GeoClue
    * to decide which provider is the best for us internally.
    * Right now we don't have the functionality in place to specifically request
    * a provider but we maybe need this in the future. We will try without it
    * for now.
    */
   if (!eldbus_proxy_call(manager_master, "Create", create_cb, NULL, -1, ""))
     {
        ERR("Error: could not call Create");
        return EXIT_FAILURE;
     }

   master_poi = eldbus_proxy_get(obj_master, GEOCLUE_POI_IFACE);
   if (!master_poi)
     {
        ERR("Error: could not get proxy");
        return EXIT_FAILURE;
     }

   /* Geocode and reverse geocode never show up as meta provider. Still we want
    * to be able to convert so we keep them around directly here. */
   obj_geonames= eldbus_object_get(conn, GEONAMES_DBUS_NAME, GEONAMES_OBJECT_PATH);
   if (!obj_geonames)
     {
        ERR("Error: could not get object for geonames");
        return EXIT_FAILURE;
     }

   geonames_geocode = eldbus_proxy_get(obj_geonames, GEOCLUE_GEOCODE_IFACE);
   if (!geonames_geocode)
     {
        ERR("Error: could not get proxy");
        return EXIT_FAILURE;
     }

   geonames_rgeocode = eldbus_proxy_get(obj_geonames, GEOCLUE_REVERSEGEOCODE_IFACE);
   if (!geonames_rgeocode)
     {
        ERR("Error: could not get proxy");
        return EXIT_FAILURE;
     }

   eldbus_name_owner_changed_callback_add(conn, GEOCLUE_DBUS_NAME, _name_owner_changed,
                                         NULL, EINA_TRUE);

   ecore_event_handler_add(ELOCATION_EVENT_IN, geoclue_start, NULL);
   ecore_event_handler_add(ELOCATION_EVENT_OUT, geoclue_stop, NULL);

   return EINA_TRUE;
}

/* Public API function to shutdown the elocation library form the application */
EAPI void
elocation_shutdown(void)
{
   /* Depending on if the create_cb was successfully received meta_geoclue is
    * setup or not. So we  * need to check here if this is not the case
    */
   if (meta_geoclue)
     {
        /* To allow geoclue freeing unused providers we free our reference on it here */
        if (!eldbus_proxy_call(meta_geoclue, "RemoveReference", _reference_del_cb, NULL, -1, ""))
          {
            ERR("Error: could not call RemoveReference");
          }
     }

   ecore_event_type_flush(ELOCATION_EVENT_IN,
                          ELOCATION_EVENT_OUT,
                          ELOCATION_EVENT_STATUS,
                          ELOCATION_EVENT_POSITION,
                          ELOCATION_EVENT_ADDRESS,
                          ELOCATION_EVENT_VELOCITY,
                          ELOCATION_EVENT_GEOCODE,
                          ELOCATION_EVENT_REVERSEGEOCODE,
                          ELOCATION_EVENT_NMEA,
                          ELOCATION_EVENT_SATELLITE,
                          ELOCATION_EVENT_POI,
                          ELOCATION_EVENT_META_READY);

   /* Quite a bit of allocated string and generic memory cleanup. This should be
    *less when we went away from all this global var business.
    */
   if (address_provider)
     {
        free(address_provider->name);
        free(address_provider->description);
        free(address_provider->service);
        free(address_provider->path);
        free(address_provider);
     }

   if (position_provider)
     {
        free(position_provider->name);
        free(position_provider->description);
        free(position_provider->service);
        free(position_provider->path);
        free(position_provider);
     }

   if (pos_geocode)
     {
        free(pos_geocode->accur);
        free(pos_geocode);
     }

   if (addr_geocode)
     {
        free(addr_geocode->country);
        free(addr_geocode->countrycode);
        free(addr_geocode->locality);
        free(addr_geocode->postalcode);
        free(addr_geocode->region);
        free(addr_geocode->timezone);
        free(addr_geocode->accur);
        free(addr_geocode);
     }

   /* Unreference some eldbus strcutures we now longer use. To allow eldbus to
    * free them internally.
    */
   if (manager_master)
      eldbus_proxy_unref(manager_master);

   eldbus_name_owner_changed_callback_del(conn, GEOCLUE_DBUS_NAME, _name_owner_changed, NULL);
   eldbus_connection_unref(conn);
   eldbus_shutdown();
   ecore_shutdown();
   eina_log_domain_unregister(_elocation_log_dom);
   eina_shutdown();
}