/** * 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); }