diff -r 28c358053693 -r 8aa32fa19a8f branches/gmyth-0.1b/src/gmyth_socket.c --- a/branches/gmyth-0.1b/src/gmyth_socket.c Wed Feb 14 23:06:17 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1082 +0,0 @@ -/** - * 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); -}