diff -r 000000000000 -r 28c358053693 branches/gmyth-0.1b/src/gmyth_socket.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/branches/gmyth-0.1b/src/gmyth_socket.c Wed Feb 14 23:06:17 2007 +0000 @@ -0,0 +1,1082 @@ +/** + * GMyth Library + * + * @file gmyth/gmyth_socket.c + * + * @brief

MythTV socket implementation, according to the MythTV Project + * (www.mythtv.org). + * + * This component provides basic socket functionalities to interact with + * the Mythtv backend. + *

+ * + * Copyright (C) 2006 INdT - Instituto Nokia de Tecnologia. + * @author Rosfran Lins Borges + * + *//* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gmyth_socket.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "gmyth_stringlist.h" +#include "gmyth_uri.h" +#include "gmyth_debug.h" + +#define BUFLEN 512 +#define MYTH_SEPARATOR "[]:[]" +#define MYTH_PROTOCOL_FIELD_SIZE 8 + +/* max number of iterations */ +#define MYTHTV_MAX_VERSION_CHECKS 40 + +// FIXME: put this in the right place +#define MYTHTV_VERSION_DEFAULT 30 + +static GStaticMutex mutex = G_STATIC_MUTEX_INIT; + +static gchar* local_hostname = NULL; + +static void gmyth_socket_class_init (GMythSocketClass *klass); +static void gmyth_socket_init (GMythSocket *object); + +static void gmyth_socket_dispose (GObject *object); +static void gmyth_socket_finalize (GObject *object); + +G_DEFINE_TYPE(GMythSocket, gmyth_socket, G_TYPE_OBJECT) + +static void +gmyth_socket_class_init (GMythSocketClass *klass) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) klass; + + gobject_class->dispose = gmyth_socket_dispose; + gobject_class->finalize = gmyth_socket_finalize; +} + +static void +gmyth_socket_init (GMythSocket *gmyth_socket) +{ + + /* gmyth_socket->local_hostname = NULL; */ + +} + +/** Gets the some important address translation info, from the client socket + * that will open a connection. + * + * @return gint that represents the error number from getaddrinfo(). + */ +static gint +gmyth_socket_toaddrinfo (const gchar *addr, gint port, struct addrinfo **addrInfo ) +{ + struct addrinfo hints; + gchar *portStr = NULL; + gint errorn = EADDRNOTAVAIL; + + g_return_val_if_fail ( addr != NULL, -1 ); + g_debug ("Calling %s\n", __FUNCTION__); + + /* hints = g_malloc0 ( sizeof(struct addrinfo) ); */ + memset ( &hints, 0, sizeof(struct addrinfo) ); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + /* hints.ai_flags = AI_NUMERICHOST; */ + + if ( port != -1 ) + portStr = g_strdup_printf ( "%d", port ); + else + portStr = NULL; + + gmyth_debug ("Getting name resolution for: %s, %d\n", addr, port); + + if ( ( errorn = getaddrinfo(addr, portStr, &hints, addrInfo) ) != 0 ) { + g_printerr( "[%s] Socket ERROR: %s\n", __FUNCTION__, gai_strerror(errorn) ); + } + g_free (portStr); + /* g_free (hints); */ + return errorn; +} + +static gint +gmyth_socket_find_match_address_uri( GMythURI* uri, gchar *address ) { + + if ( g_ascii_strcasecmp( gmyth_uri_get_host( uri ), address ) == 0 ) { + //g_printerr( "Found URI: %s !!!\n", rui_uri_getvalue(uri) ); + return 0; + } else { + return -1; + } + +} + +static const gchar *PATH_PROC_NET_DEV = "/proc/net/dev"; + +/** Gets the list of all local network interfaces (using the /proc/net/dev directory). + * + * @param current_connections A list with all the network interfaces are valid, + * to be applied just like a filter. + * @return List with all the local net interfaces. + */ +static GList * +gmyth_socket_get_local_addrs( GList *current_connections ) +{ + + GList *local_addrs = NULL; + FILE *fd; + gint s; + gchar buffer[256+1]; + gchar ifaddr[20+1]; + gchar *ifname; + gchar *sep; + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + return 0; + fd = fopen(PATH_PROC_NET_DEV, "r"); + fgets(buffer, sizeof(buffer)-1, fd); + fgets(buffer, sizeof(buffer)-1, fd); + while (!feof(fd)) { + ifname = buffer; + sep; + if (fgets(buffer, sizeof(buffer)-1, fd) == NULL) + break; + sep = strrchr(buffer, ':'); + if (sep) + *sep = 0; + while (*ifname == ' ') + ifname++; + struct ifreq req; + strcpy(req.ifr_name, ifname); + if (ioctl(s, SIOCGIFFLAGS, &req) < 0) + continue; + if (!(req.ifr_flags & IFF_UP)) + continue; + if (req.ifr_flags & IFF_LOOPBACK) + continue; + if (ioctl(s, SIOCGIFADDR, &req) < 0) + continue; + g_strlcpy( ifaddr, inet_ntoa(((struct sockaddr_in*)&req.ifr_addr)->sin_addr), sizeof(struct ifaddr)-1 ); + local_addrs = g_list_append( local_addrs, g_strdup( ifaddr ) ); + + gmyth_debug( "( from the /proc/net/dev) Interface name: %s, address: %s\n", + ifname, ifaddr ); + } + fclose(fd); + close(s); + return local_addrs; + +} + + +/** + * Get only the local addresses from the primary interface + */ +static gchar * +gmyth_socket_get_primary_addr() +{ + + gchar *if_eth0 = g_new0( gchar, sizeof(struct ifaddr)-1 ); + GList *if_tmp = NULL; + + GList *interfs = gmyth_socket_get_local_addrs( NULL ); + + if ( interfs != NULL && ( g_list_length( interfs ) > 0 ) ) + { + /* get the first occurrence (primary interface) */ + if_tmp = g_list_first( interfs ); + + if ( if_tmp != NULL ) + g_strlcpy (if_eth0, (gchar *)if_tmp->data, sizeof(struct ifaddr)-1 ); + + } + + if ( interfs != NULL ) + g_list_free( interfs ); + + return if_eth0; +} + +/** This function retrieves the local hostname of the + * client machine. + * + * @return GString* get local hostname. + */ +GString * +gmyth_socket_get_local_hostname () +{ + + char hname[50]; + gint res = gethostname (hname, 50); + + if (res == -1) { + g_debug ("Error while getting hostname"); + return NULL; + } + + return g_string_new (hname); + +#if 0 + GString *str = NULL; + + if ( local_hostname != NULL && strlen(local_hostname) > 0 ) + return g_string_new( local_hostname ); + + gchar *localaddr = NULL; + gboolean found_addr = FALSE; + struct addrinfo* addr_info_data = NULL, *addr_info0 = NULL; + struct sockaddr_in* sa = NULL; + gchar localhostname[MAXHOSTNAMELEN]; + + + if (gethostname (localhostname, MAXHOSTNAMELEN) != 0 ) { + gmyth_debug ( "Error on gethostname" ); + } + localhostname[MAXHOSTNAMELEN-1] = 0; + + gint err = gmyth_socket_toaddrinfo (localhostname, -1, &addr_info_data ); + + if ( err == EADDRNOTAVAIL ) + { + g_warning( "[%s] Address (%s) not available. (reason = %d)\n", __FUNCTION__, localhostname, err ); + return str; + } + + g_static_mutex_lock( &mutex ); + + addr_info0 = addr_info_data; + + while( addr_info0 != NULL && addr_info0->ai_addr != NULL && + ( sa = (struct sockaddr_in*)addr_info0->ai_addr ) != NULL && !found_addr ) { + localaddr = inet_ntoa( sa->sin_addr ); + + if ( localaddr != NULL && ( g_strrstr( localaddr, "127" ) == NULL ) ) { + str = g_string_new (localaddr); + found_addr = TRUE; + g_free (localaddr); + break; + } +/* + if (localaddr != NULL) { + g_free (localaddr); + localaddr = NULL; + } + */ + + addr_info0 = addr_info0->ai_next; + }; + + freeaddrinfo (addr_info_data); + addr_info_data = NULL; + + if ( found_addr == FALSE ) { + gchar *prim_addr = gmyth_socket_get_primary_addr(); + + if ( prim_addr != NULL ) { + g_warning("[%s] Could not determine the local alphanumerical hostname. Setting to %s\n", + __FUNCTION__, prim_addr ); + + str = g_string_new (prim_addr); + g_free (prim_addr); + } else { + str = g_string_new (localhostname); + } + } + + g_static_mutex_unlock (&mutex); + + if ( str != NULL && str->str != NULL ) + local_hostname = g_strdup( str->str ); + + return str; +#endif +} + +static void +gmyth_socket_dispose (GObject *object) +{ + GMythSocket *gmyth_socket = GMYTH_SOCKET(object); + + /* disconnect socket */ + gmyth_socket_close_connection (gmyth_socket); + + g_free (gmyth_socket->hostname); + gmyth_socket->hostname = NULL; + + g_free (local_hostname); + + local_hostname = NULL; + + G_OBJECT_CLASS (gmyth_socket_parent_class)->dispose (object); +} + +static void +gmyth_socket_finalize (GObject *object) +{ + g_signal_handlers_destroy (object); + + G_OBJECT_CLASS (gmyth_socket_parent_class)->finalize (object); +} + +/** Creates a new instance of GMythSocket. + * + * @return a new instance of GMythSocket. + */ +GMythSocket* +gmyth_socket_new () +{ + GMythSocket *gmyth_socket = GMYTH_SOCKET (g_object_new(GMYTH_SOCKET_TYPE, NULL)); + + gmyth_socket->mythtv_version = MYTHTV_VERSION_DEFAULT; + + return gmyth_socket; +} + +/** Try to open an asynchronous connection to the MythTV backend. + * + * @param fd Socket descriptor. + * @param remote Remote address. + * @param len Newly created socket length field. + * @param timeout Timeval argument with the time interval to timeout before closing. + * @param err Error message number. + * @return Any numerical value below 0, if an error had been found. + */ +static gint +gmyth_socket_try_connect ( gint fd, struct sockaddr *remote, gint len, + struct timeval *timeout, gint *err) +{ + /*g_return_val_if_fail( timeout != NULL, 0 );*/ + gint saveflags, ret, back_err; + + fd_set fd_w; + + saveflags = fcntl( fd, F_GETFL, 0 ); + if( saveflags < 0 ) { + g_warning( "[%s] Problems when getting socket flags on fcntl.\n", __FUNCTION__ ); + *err=errno; + return -1; + } + + /* Set non blocking */ + if( fcntl( fd, F_SETFL, saveflags | O_NONBLOCK ) < 0) { + g_warning( "[%s] Problems when setting non-blocking using fcntl.\n", __FUNCTION__ ); + *err=errno; + return -1; + } + + /* This will return immediately */ + *err= connect ( fd, remote, len ); + back_err=errno; + + /* restore flags */ + if( fcntl( fd, F_SETFL, saveflags ) < 0) { + g_warning( "[%s] Problems when trying to restore flags with fcntl.\n", __FUNCTION__ ); + *err=errno; + return -1; + } + + /* return unless the connection was successful or the connect is + still in progress. */ + if( *err < 0 && back_err != EINPROGRESS) { + g_warning( "[%s] Connection unsucessfully (it is not in progress).\n", __FUNCTION__ ); + *err = errno; + return -1; + } + + FD_ZERO( &fd_w ); + FD_SET( fd, &fd_w ); + + *err = select( FD_SETSIZE, NULL, &fd_w, NULL, timeout); + if ( *err < 0 ) { + g_warning( "[%s] Connection unsucessfull (timed out).\n", __FUNCTION__ ); + *err=errno; + return -1; + } + + /* 0 means it timeout out & no fds changed */ + if(*err==0) { + close(fd); + *err=ETIMEDOUT; + return -1; + } + + /* Get the return code from the connect */ + len = sizeof( ret ); + *err=getsockopt( fd, SOL_SOCKET, SO_ERROR, &ret, (socklen_t *) &len); + + if( *err < 0 ) { + g_warning( "[%s] Connection usnsucessfull.\n", __FUNCTION__ ); + *err=errno; + return -1; + } + + /* ret=0 means success, otherwise it contains the errno */ + if (ret) { + *err=ret; + return -1; + } + + *err=0; + return 0; +} + +/** Connects to the backend. + * + * @param gmyth_socket The GMythSocket instance. + * @param hostname The backend hostname or IP address. + * @param port The backend port. + * @return TRUE if success, FALSE if error. + */ + + +gboolean +gmyth_socket_connect (GMythSocket *gmyth_socket, + const gchar *hostname, gint port) +{ + return gmyth_socket_connect_with_timeout (gmyth_socket, + hostname, port, 0); +} + +gboolean +gmyth_socket_connect_with_timeout (GMythSocket *gmyth_socket, + const gchar *hostname, gint port, guint timeout) +{ + struct addrinfo *addr_info_data = NULL, *addr_info0 = NULL; + gint ret_code = -1; + gint errno; + gboolean ret = TRUE; + + gmyth_debug ("CONNECTING %s:%d", hostname, port); + + if ( hostname == NULL ) + gmyth_debug ( "Invalid hostname parameter!\n"); + + /* store hostname and port number */ + if (gmyth_socket->hostname != NULL) { + //g_free (gmyth_socket->hostname); + gmyth_socket->hostname = NULL; + } + + errno = gmyth_socket_toaddrinfo ( hostname, port, &addr_info_data ); + + g_return_val_if_fail( addr_info_data != NULL && hostname != NULL, FALSE ); + + gmyth_socket->hostname = g_strdup( hostname ); + gmyth_socket->port = port; + + for ( addr_info0 = addr_info_data; addr_info0; addr_info0 = addr_info_data->ai_next ) { + /* init socket descriptor */ + gmyth_socket->sd = socket( addr_info0->ai_family, addr_info0->ai_socktype, + addr_info0->ai_protocol ); + + if ( gmyth_socket->sd < 0 ) + continue; + + struct timeval *timeout_val = g_new0 (struct timeval, 1); + if (timeout != 0) { + /*timeout_val = g_new0 (struct timeval, 1);*/ + + timeout_val->tv_sec = timeout; + timeout_val->tv_usec = 0; + } else { + timeout_val->tv_sec = 5; + timeout_val->tv_usec = 100; + } + + if (gmyth_socket_try_connect (gmyth_socket->sd, (struct sockaddr *)addr_info0->ai_addr, + addr_info0->ai_addrlen, timeout_val, &ret_code ) < 0 ) + { + g_printerr( "[%s] Error connecting to backend!\n", __FUNCTION__ ); + if (ret_code == ETIMEDOUT) + g_printerr( "[%s]\tBackend host unreachable!\n", __FUNCTION__ ); + + close (gmyth_socket->sd); + gmyth_socket->sd = -1; + g_printerr ("ERROR: %s\n", gai_strerror(ret_code)); + g_free (timeout_val); + continue; + } + + g_free (timeout_val); + + /* only will be reached if none of the error above occurred */ + break; + } + + freeaddrinfo (addr_info_data); + addr_info_data = NULL; + + if (gmyth_socket->sd_io_ch != NULL) { + g_io_channel_unref (gmyth_socket->sd_io_ch); + gmyth_socket->sd_io_ch = NULL; + } + + gmyth_socket->sd_io_ch = g_io_channel_unix_new (gmyth_socket->sd); + + //GIOFlags flags = g_io_channel_get_flags (gmyth_socket->sd_io_ch); + /* unset the nonblock flag */ + //flags &= ~G_IO_FLAG_NONBLOCK; + /* unset the nonblocking stuff for some time, because GNUTLS doesn't like + * that */ + //g_io_channel_set_flags (gmyth_socket->sd_io_ch, flags, NULL); + + ret = ( ret_code == 0 ) ? TRUE : FALSE ; + return ret; +} + +/** Gets the GIOChannel associated to the given GMythSocket. + * + * @param gmyth_socket The GMythSocket instance. + */ +GIOChannel * +gmyth_socket_get_io_channel( GMythSocket *gmyth_socket ) +{ + g_return_val_if_fail( gmyth_socket != NULL, NULL ); + + return gmyth_socket->sd_io_ch; +} + +/** Verifies if the socket is able to read. + * + * @param gmyth_socket The GMythSocket instance. + * @return TRUE if the socket is able to read, FALSE if not. + */ +gboolean +gmyth_socket_is_able_to_read( GMythSocket *gmyth_socket ) +{ + gboolean ret = TRUE; + + /* verify if the input (read) buffer is ready to receive data */ + GIOCondition io_cond = g_io_channel_get_buffer_condition( gmyth_socket->sd_io_ch ); + + if ( ( io_cond & G_IO_IN ) == 0 ) { + g_warning ("[%s] IO channel is not able to send data!\n", __FUNCTION__); + ret = FALSE; + } + + return ret; + +} + +/** Verifies if the socket is able to write. + * + * @param gmyth_socket The GMythSocket instance. + * @return TRUE if the socket is able to write, FALSE if not. + */ +gboolean +gmyth_socket_is_able_to_write( GMythSocket *gmyth_socket ) +{ + gboolean ret = TRUE; + + /* verify if the input (read) buffer is ready to receive data */ + GIOCondition io_cond = g_io_channel_get_buffer_condition( gmyth_socket->sd_io_ch ); + + if ( ( ( io_cond & G_IO_OUT ) == 0 ) || ( ( io_cond & G_IO_HUP ) == 0 ) ) { + g_warning ("[%s] IO channel is not able to send data!\n", __FUNCTION__); + ret = FALSE; + } + + return ret; + +} + +/** Sends a command to the backend. + * + * @param gmyth_socket the GMythSocket instance. + * @param command The string command to be sent. + */ +gboolean +gmyth_socket_send_command(GMythSocket *gmyth_socket, GString *command) +{ + gboolean ret = TRUE; + + GIOStatus io_status = G_IO_STATUS_NORMAL; + //GIOCondition io_cond; + GError* error = NULL; + + + gchar *buffer = NULL; + + gsize bytes_written = 0; + + if( command == NULL || ( command->len <= 0 ) || command->str == NULL ) { + g_warning ("[%s] Invalid NULL command parameter!\n", __FUNCTION__); + ret = FALSE; + goto done; + } + + //g_static_mutex_lock( &mutex ); + gmyth_debug ("Sending command to backend: %s\n", command->str); + + buffer = g_strnfill( BUFLEN, ' ' ); + g_snprintf( buffer, MYTH_PROTOCOL_FIELD_SIZE+1, "%-8d", command->len); + + command = g_string_prepend(command, buffer); + + /* write bytes to socket */ + io_status = g_io_channel_write_chars( gmyth_socket->sd_io_ch, command->str, + command->len, &bytes_written, &error ); + + + if( (io_status == G_IO_STATUS_ERROR) || ( bytes_written <= 0 ) ) { + g_warning ("[%s] Error while writing to socket", __FUNCTION__); + ret = FALSE; + } else if ( bytes_written < command->len ) { + g_warning ("[%s] Not all data was written socket", __FUNCTION__); + ret = FALSE; + } + + io_status = g_io_channel_flush( gmyth_socket->sd_io_ch, &error ); + + if ( ( bytes_written != command->len ) || ( io_status == G_IO_STATUS_ERROR ) ) + { + g_warning ("[%s] Some problem occurred when sending data to the socket\n", __FUNCTION__); + + ret = TRUE; + } + + //g_static_mutex_unlock( &mutex ); +done: + if ( error != NULL ) { + g_printerr( "[%s] Error found reading data from IO channel: (%d, %s)\n", __FUNCTION__, error->code, error->message ); + ret = FALSE; + g_error_free( error ); + } + + if ( buffer!= NULL ) + g_free( buffer ); + + return ret; +} + +/** Starts Mythtv protocol level connection. Checks Mythtv protocol version + * supported by the backend and send the "ANN" command. + * + * @param gmyth_socket the GMythSocket instance. + * @param hostname_backend The backend hostname or IP address. + * @param port The backend port to connect. + * @param blocking_client A flag to choose between blocking and non-blocking + * @param with_events Sets the connection flag to receive events. + * backend connection. + */ +static gboolean +gmyth_socket_connect_to_backend_and_events (GMythSocket *gmyth_socket, + const gchar *hostname_backend, gint port, gboolean blocking_client, + gboolean with_events) +{ + if (!gmyth_socket_connect (gmyth_socket, hostname_backend, port)) { + g_warning ("[%s] Could not open socket to backend machine [%s]\n", __FUNCTION__, + hostname_backend ); + return FALSE; + } + + if ( gmyth_socket_check_protocol_version (gmyth_socket) ) { + + GString *result; + GString *base_str = g_string_new(""); + GString *hostname = NULL; + + hostname = gmyth_socket_get_local_hostname(); + if (hostname == NULL) { + g_debug ("Hostname not available, setting to n800frontend\n"); + hostname = g_strdup ("n800frontend"); + } + + g_string_printf(base_str, "ANN %s %s %u", + (blocking_client ? "Playback" : "Monitor"), + hostname->str, with_events); + + gmyth_socket_send_command (gmyth_socket, base_str); + result = gmyth_socket_receive_response (gmyth_socket); + + if (result != NULL) { + gmyth_debug ("Response received from backend: %s", result->str); + g_string_free (result, TRUE); + } + + g_string_free (hostname, TRUE); + g_string_free (base_str, TRUE); + + return TRUE; + } else { + g_warning ("[%s] GMythSocket could not connect to the backend", __FUNCTION__); + return FALSE; + } +} + +/** Starts Mythtv protocol level connection. Checks Mythtv protocol version + * supported by the backend and send the "ANN" command. + * + * @param gmyth_socket the GMythSocket instance. + * @param hostname_backend The backend hostname or IP address. + * @param port The backend port to connect. + * @param blocking_client A flag to choose between blocking and non-blocking + */ +gboolean +gmyth_socket_connect_to_backend (GMythSocket *gmyth_socket, + const gchar *hostname_backend, gint port, gboolean blocking_client) +{ + if (!gmyth_socket_connect_to_backend_and_events ( gmyth_socket, hostname_backend, port, + blocking_client, FALSE) ) { + gmyth_debug ("Could not open socket to backend machine [%s]\n", + hostname_backend ); + return FALSE; + } + + return TRUE; + +} + +/** Starts Mythtv protocol level connection. Checks Mythtv protocol version + * supported by the backend and send the "ANN" command. + * + * @param gmyth_socket the GMythSocket instance. + * @param hostname_backend The backend hostname or IP address. + * @param port The backend port to connect. + * @param blocking_client A flag to choose between blocking and non-blocking + */ +gboolean +gmyth_socket_connect_to_backend_events (GMythSocket *gmyth_socket, + const gchar *hostname_backend, gint port, gboolean blocking_client) +{ + if (!gmyth_socket_connect_to_backend_and_events ( gmyth_socket, hostname_backend, port, + blocking_client, TRUE) ) { + gmyth_debug ("Could not open socket to backend machine in order to receive events [%s]\n", + hostname_backend ); + return FALSE; + } + + return TRUE; +} + +/** Closes the socket connection to the backend. + * + * @param gmyth_socket The GMythSocket instance. + */ +void +gmyth_socket_close_connection (GMythSocket *gmyth_socket) +{ + close (gmyth_socket->sd); + gmyth_socket->sd = -1; + + if (gmyth_socket->sd_io_ch != NULL) { + g_io_channel_unref (gmyth_socket->sd_io_ch); + gmyth_socket->sd_io_ch = NULL; + } +} + + +/** Try the MythTV version numbers, and get the version returned by + * the possible REJECT message, in order to contruct a new + * MythTV version request. + * + * @param gmyth_socket The GMythSocket instance. + * @param mythtv_version The Mythtv protocol version to be tested + * + * @return The actual MythTV the client is connected to. + */ +gint +gmyth_socket_check_protocol_version_number (GMythSocket *gmyth_socket, gint mythtv_version) +{ + GString *response = NULL; + GString *payload = NULL; + gboolean res = TRUE; + gint mythtv_new_version = MYTHTV_CANNOT_NEGOTIATE_VERSION; + guint max_iterations = MYTHTV_MAX_VERSION_CHECKS; + +try_new_version: + payload = g_string_new ("MYTH_PROTO_VERSION"); + g_string_append_printf( payload, " %d", mythtv_version ); + + gmyth_socket_send_command(gmyth_socket, payload); + response = gmyth_socket_receive_response(gmyth_socket); + + if (response == NULL) { + g_warning ("[%s] Check protocol version error! Not answered!", __FUNCTION__); + res = FALSE; + goto done; + } + + res = g_str_has_prefix (response->str, "ACCEPT"); + if (!res) { + g_warning ("[%s] Protocol version request error: %s", __FUNCTION__, response->str); + /* get the version number returned by the REJECT message */ + if ( ( res = g_str_has_prefix (response->str, "REJECT") ) == TRUE ) { + gchar *new_version = NULL; + new_version = g_strrstr( response->str, "]" ); + if (new_version!=NULL) { + ++new_version; /* skip ']' character */ + if ( new_version != NULL ) { + gmyth_debug ( "[%s] got MythTV version = %s.\n", __FUNCTION__, new_version ); + mythtv_version = (gint)g_ascii_strtoull (new_version, NULL, 10 ); + /* do reconnection to the socket (socket is closed if the MythTV version was wrong) */ + gmyth_socket_connect( gmyth_socket, gmyth_socket->hostname, gmyth_socket->port ); + new_version =NULL; + if ( --max_iterations > 0 ) + goto try_new_version; + else + goto done; + } + } + } + } + + /* change the return value to a valid one */ + if ( res ) { + mythtv_new_version = mythtv_version; + gmyth_socket->mythtv_version = mythtv_new_version; + } + +done: + if ( payload != NULL ) + g_string_free (payload, TRUE); + if ( response != NULL ) + g_string_free (response, TRUE); + + return mythtv_new_version; +} + +/** Verifies if the Mythtv backend supported the GMyth supported version. + * + * @param gmyth_socket The GMythSocket instance. + * @return TRUE if supports, FALSE if not. + */ +gboolean +gmyth_socket_check_protocol_version (GMythSocket *gmyth_socket) +{ + return ( ( gmyth_socket->mythtv_version = + gmyth_socket_check_protocol_version_number ( gmyth_socket, + MYTHTV_VERSION_DEFAULT ) ) != MYTHTV_CANNOT_NEGOTIATE_VERSION ); +} + +/** Returns the Mythtv backend supported version. + * + * @param gmyth_socket The GMythSocket instance. + * @return The actual MythTV version number. + */ +gint +gmyth_socket_get_protocol_version (GMythSocket *gmyth_socket) +{ + return gmyth_socket->mythtv_version; +} + +/** Receives a backend answer after a gmyth_socket_send_command_call (). + * + * @param gmyth_socket The GMythSocket instance. + * @return The response received, or NULL if error or nothing was received. + */ +GString* +gmyth_socket_receive_response(GMythSocket *gmyth_socket) +{ + GIOStatus io_status = G_IO_STATUS_NORMAL; + GError* error = NULL; + gchar *buffer; + + GString *str = NULL; + + gsize bytes_read = 0; + gint len = 0; + GIOCondition io_cond = g_io_channel_get_buffer_condition (gmyth_socket->sd_io_ch); + + g_return_val_if_fail( gmyth_socket != NULL, NULL ); + + /* verify if the input (read) buffer is ready to receive data */ + + //g_static_mutex_lock( &mutex ); + + //buffer = g_new0 (gchar, MYTH_PROTOCOL_FIELD_SIZE); + buffer = g_strnfill (MYTH_PROTOCOL_FIELD_SIZE, ' '); + io_status = g_io_channel_read_chars (gmyth_socket->sd_io_ch, buffer, MYTH_PROTOCOL_FIELD_SIZE, &bytes_read, &error); + + /* verify if the input (read) buffer is ready to receive data */ + io_cond = g_io_channel_get_buffer_condition (gmyth_socket->sd_io_ch); + + gmyth_debug ( "[%s] Bytes read = %d\n", __FUNCTION__, bytes_read ); + + if( (io_status == G_IO_STATUS_ERROR) || (bytes_read <= 0) ) { + g_warning ("[%s] Error in mythprotocol response from backend\n", __FUNCTION__); + str = NULL; + //return NULL; + } else { + + io_status = g_io_channel_flush( gmyth_socket->sd_io_ch, &error ); + /* verify if the input (read) buffer is ready to receive data */ + io_cond = g_io_channel_get_buffer_condition( gmyth_socket->sd_io_ch ); + + //if ( ( io_cond & G_IO_IN ) != 0 ) { + //gchar *buffer_aux = NULL; + + /* removes trailing whitespace */ + //buffer_aux = g_strstrip (buffer); + len = (gint)g_ascii_strtoull ( g_strstrip (buffer), NULL, 10 ); + + if (buffer != NULL) { + g_free (buffer); + buffer = NULL; + } + + /* + if (buffer_aux != NULL) { + g_free (buffer_aux); + buffer_aux = NULL; + } + */ + + buffer = g_new0 (gchar, len+1); + + bytes_read = 0; + io_status = g_io_channel_read_chars( gmyth_socket->sd_io_ch, buffer, len, &bytes_read, &error); + buffer[bytes_read] = '\0'; + //} + } + + //g_static_mutex_unlock( &mutex ); + + gmyth_debug ("Response received from backend: {%s}\n", buffer); + + if ( ( bytes_read != len ) || ( io_status == G_IO_STATUS_ERROR ) ) + str = NULL; + else + str = g_string_new (buffer); + + if ( error != NULL ) { + g_printerr( "[%s] Error found receiving response from the IO channel: (%d, %s)\n", __FUNCTION__, error->code, error->message ); + str = NULL; + g_error_free (error); + } + + g_free (buffer); + return str; +} + +/** Format a Mythtv command from the str_list entries and send it to backend. + * + * @param gmyth_socket The GMythSocket instance. + * @param str_list The string list to form the command + * @return TRUE if command was sent, FALSE if any error happens. + */ +gboolean +gmyth_socket_write_stringlist(GMythSocket *gmyth_socket, GMythStringList* str_list) +{ + + GList *tmp_list = NULL; + GPtrArray *ptr_array = NULL; + gchar *str_array = NULL; + + g_static_mutex_lock( &mutex ); + + ptr_array = g_ptr_array_sized_new (g_list_length(str_list->glist)); + + // FIXME: change this implementation! + tmp_list = str_list->glist; + for(; tmp_list; tmp_list = tmp_list->next) { + if ( tmp_list->data != NULL ) { + g_ptr_array_add(ptr_array, ((GString*)tmp_list->data)->str); + } else { + g_ptr_array_add (ptr_array, ""); + } + } + g_ptr_array_add(ptr_array, NULL); // g_str_joinv() needs a NULL terminated string + + str_array = g_strjoinv (MYTH_SEPARATOR, (gchar **) (ptr_array->pdata)); + + g_static_mutex_unlock( &mutex ); + + gmyth_debug ( "[%s] Sending socket request: %s\n", __FUNCTION__, str_array ); + + // Sends message to backend + // TODO: implement looping to send remaining data, and add timeout testing! + GString *command = g_string_new(str_array); + gmyth_socket_send_command(gmyth_socket, command); + g_string_free (command, TRUE); + + g_free (str_array); + g_ptr_array_free (ptr_array, TRUE); + + return TRUE; +} + +/* Receives a backend command response and split it into the given string list. + * + * @param gmyth_socket The GMythSocket instance. + * @param str_list the string list to be filled. + * @return The number of received strings. + */ +gint +gmyth_socket_read_stringlist (GMythSocket *gmyth_socket, GMythStringList* str_list) +{ + GString *response; + gchar **str_array; + gint i; + + response = gmyth_socket_receive_response(gmyth_socket); + g_static_mutex_lock( &mutex ); + + gmyth_string_list_clear_all (str_list); + str_array = g_strsplit (response->str, MYTH_SEPARATOR, -1); + + for (i=0; i< g_strv_length (str_array); i++) { + gmyth_string_list_append_char_array (str_list, str_array[i] ); + } + g_static_mutex_unlock( &mutex ); + + g_string_free (response, TRUE); + g_strfreev (str_array); + + return gmyth_string_list_length (str_list); +} + +/** Formats a Mythtv protocol command based on str_list and sends it to + * the connected backend. The backend response is overwritten into str_list. + * + * @param gmyth_socket The GMythSocket instance. + * @param str_list The string list to be sent, and on which the answer + * will be written. + * @return TRUE if command was sent and an answer was received, FALSE if any + * error happens. + */ +gint +gmyth_socket_sendreceive_stringlist (GMythSocket *gmyth_socket, GMythStringList *str_list) +{ + gmyth_socket_write_stringlist (gmyth_socket, str_list); + + return gmyth_socket_read_stringlist (gmyth_socket, str_list); +}