Logo Search packages:      
Sourcecode: pan version File versions  Download package

sockets.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Pan - A Newsreader for Gtk+
 * Copyright (C) 2002  Charles Kerr <charles@rebelbase.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * 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 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
 */

/*********************
**********************  Includes
*********************/

#include <config.h>

#include <errno.h>
#include <string.h>
#include <signal.h> /* for signal (SIGPIPE, SIG_IGN) */
#include <unistd.h>
#include <time.h> /* for time_t */

#include <glib.h>

#if defined(HAVE_GNET_1)
#include <gnet/gnet.h>
#define gnet_tcp_socket_get_io_channel gnet_tcp_socket_get_iochannel
#elif defined(HAVE_GNET_2)
#include <gnet.h>
#else
#error Must have GNet 1 or GNet t
#endif

#include <pan/base/debug.h>
#include <pan/base/log.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/pan-i18n.h>

#include <pan/sockets.h>

/*********************
**********************  Structs
*********************/

struct _PanSocket
{
      /* parent class */
      PanObject parent;

      /* public fields */
      time_t last_action_time;

      /* public fields */
      gboolean error;

      /* private fields */  
      gboolean need_auth;
      PString nntp_group_name;
      PString nntp_username;
      PString nntp_password;
      GString * line_buffer;
      GTcpSocket * gnet_socket;
      GIOChannel * gio_channel;

      /* statistics */
      PString host_address;
      PString host_name;
      guint bytes_read;
      guint bytes_written;
      time_t byte_count_start_time;

      int consecutive_failed_operations;
};


#define MAX_CONSECUTIVE_FAILED_OPERATIONS 600

/*********************
**********************  Variables
*********************/

static gulong total_bytes = 0;

/*********************
**********************  BEGINNING OF SOURCE
*********************/

PanSocket*
pan_socket_new (const PString     * server_name,
                const PString     * server_address,
                int                 port)
{
      PanSocket * sock;

      /* sanity clause */
      g_return_val_if_fail (pstring_is_set (server_name), NULL);
      g_return_val_if_fail (pstring_is_set (server_address), NULL);
      g_return_val_if_fail (port>=0, NULL);

      /* normally SIGPIPE, from writes on closed sockets, kill the app */
#     ifndef G_OS_WIN32
      signal (SIGPIPE, SIG_IGN);
#     endif

      /* create the socket */
      sock = g_new0 (PanSocket, 1); 
      debug1 (DEBUG_PAN_OBJECT, "pan_socket_new: %p", sock);
      pan_socket_constructor (sock, pan_socket_destructor, server_name, server_address, port);
      return sock;
}


void
pan_socket_constructor (PanSocket             * sock,
                  PanObjectDestructor     destructor,
                  const PString         * server_name,
                  const PString         * server_address,
                  gint                    port)
{
      /* sanity clause */
      g_return_if_fail (sock!=NULL);
      g_return_if_fail (pstring_is_set (server_name));
      g_return_if_fail (pstring_is_set (server_address));
      g_return_if_fail (port>=0);

      /* constructor - zero things out */
      pan_object_constructor (PAN_OBJECT(sock), destructor);
      sock->host_address = PSTRING_INIT;
      sock->host_name = PSTRING_INIT;
      sock->bytes_read = 0u;
      sock->bytes_written = 0u;
      sock->byte_count_start_time = time(NULL);
      sock->consecutive_failed_operations = 0;
      sock->line_buffer = g_string_sized_new (512);
      sock->error = FALSE;
      sock->need_auth = FALSE;
      sock->nntp_group_name = PSTRING_INIT;
      sock->nntp_username = PSTRING_INIT;
      sock->nntp_password = PSTRING_INIT;

      /* set the host information */
      pstring_copy (&sock->host_address, server_address);
      pstring_copy (&sock->host_name, server_name);

      /* set up the serv_addr struct */
      errno = 0;
      sock->gnet_socket = gnet_tcp_socket_connect (server_address->str, port);
      if (sock->gnet_socket==NULL)
      {
            pan_socket_set_error_flag (sock, TRUE);

            log_add_va (LOG_ERROR, _("Connection to %*.*s, port %d failed: %s"),
                        server_address->len, server_address->len, server_address->str,
                        port, g_strerror(errno));
      }
      else
      {
            sock->gio_channel = gnet_tcp_socket_get_io_channel (sock->gnet_socket);
            if (g_io_channel_get_encoding (sock->gio_channel) != NULL)
                  g_io_channel_set_encoding (sock->gio_channel, NULL, NULL);
#ifndef G_OS_WIN32
            /* commented out on win32 to avoid annoying glib "not implemented" */
            g_io_channel_set_flags (sock->gio_channel, G_IO_FLAG_NONBLOCK, NULL);
#endif
            g_io_channel_set_buffered (sock->gio_channel, TRUE);
            g_io_channel_set_line_term (sock->gio_channel, "\n", 1);
            log_add_va (LOG_INFO, _("New connection %p for %*.*s, port %d"), sock,
                        server_address->len, server_address->len, server_address->str,
                        port);
      }

        debug1 (DEBUG_PAN_OBJECT, "pan_socket_constructor: %p", sock);
}

#define DEAD_POINTER ((void*)(0xDEADBEEF))

void
pan_socket_destructor (PanObject *obj)
{
      PanSocket * sock;

      g_return_if_fail (obj != NULL);
      sock = PAN_SOCKET(obj);
      debug1 (DEBUG_SOCKET, "socket closed: %p", sock);
        debug1 (DEBUG_PAN_OBJECT, "pan_socket_destructor: %p", sock);

#ifdef HAVE_GNET_1
      if (sock->gio_channel != NULL)
            g_io_channel_unref (sock->gio_channel);
#endif
      gnet_tcp_socket_delete (sock->gnet_socket);
      g_string_free (sock->line_buffer, TRUE);

      sock->gio_channel = DEAD_POINTER;
      sock->gnet_socket = DEAD_POINTER;
      sock->line_buffer = DEAD_POINTER;

      pstring_clear (&sock->nntp_group_name);
      pstring_clear (&sock->nntp_username);
      pstring_clear (&sock->nntp_password);
      pstring_clear (&sock->host_address);
      pstring_clear (&sock->host_name);

      /* pass along to superclass */
      pan_object_destructor (obj);
}

 
void
pan_socket_set_nntp_auth (PanSocket            * socket,
                          gboolean               need_auth,
                          const PString        * username,
                          const PString        * password)
{
      g_return_if_fail (socket!=NULL);

      socket->need_auth = need_auth;
      pstring_copy (&socket->nntp_username, username);
      pstring_copy (&socket->nntp_password, password);
}


/*
 * get_server - read from a socket until \n
 */
TaskStateEnum
pan_socket_getline (PanSocket    * sock,
                    const char  ** setme)
{
      gboolean done = FALSE;
      GError * err = NULL;
      GIOStatus status;
      debug_enter ("pan_socket_getline");

      /* sanity clause */
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      g_return_val_if_fail (setme!=NULL, TASK_FAIL);
      if (sock->error || sock->gio_channel==NULL)
            return TASK_FAIL_NETWORK;

      /* read the next line */
      g_string_truncate (sock->line_buffer, 0);
      while (!done) {
            sock->last_action_time = time(0);
            status = g_io_channel_read_line_string (sock->gio_channel, sock->line_buffer, NULL,  &err);
            switch (status) {
                  case G_IO_STATUS_AGAIN:
                        if (++sock->consecutive_failed_operations <= MAX_CONSECUTIVE_FAILED_OPERATIONS ) {
                              g_usleep (G_USEC_PER_SEC/10);
                              continue;
                        }
                        /* fall through */
                  case G_IO_STATUS_ERROR:
                        /* fall through */
                  case G_IO_STATUS_EOF:
                        if (err != NULL) {
                              log_add_va (LOG_ERROR, _("Error reading from socket: %s"), err->message);
                              g_error_free (err);
                        }
                        pan_socket_set_error_flag (sock, TRUE);
                        return TASK_FAIL_NETWORK;
                  case G_IO_STATUS_NORMAL:
                        sock->consecutive_failed_operations = 0;
                        done = TRUE;
                        break;
            }
      }

      sock->bytes_read += sock->line_buffer->len;
      total_bytes += sock->line_buffer->len;

      debug2 (DEBUG_SOCKET_INPUT,
             "socket [%p] received [%s]", sock,
             (char*)sock->line_buffer->str);

      *setme = (const char*) sock->line_buffer->str;
      debug_exit ("pan_socket_getline");
      return TASK_OK;
}


TaskStateEnum 
pan_socket_putline (PanSocket     * sock,
                    const char    * line)
{
      gsize old_buffer_size;
      gsize nbytes;
      gsize nleft;

      /* sanity clause */
      g_return_val_if_fail (is_nonempty_string(line), TASK_FAIL);
      g_return_val_if_fail (sock!=NULL, TASK_FAIL);
      if (sock->error || sock->gio_channel==NULL)
            return TASK_FAIL_NETWORK;

      debug3 (DEBUG_SOCKET_OUTPUT,
              "[%ld][socket %p putline][%s]", time(0), (void*)sock, line);

      /* force unbuffered writes */
      /* (workaround for http://bugzilla.gnome.org/show_bug.cgi?id=119285) */
      old_buffer_size = g_io_channel_get_buffer_size (sock->gio_channel);
      g_io_channel_set_buffered (sock->gio_channel, FALSE);

      /* try to write the string */
      nleft = nbytes = strlen (line);
      while (nleft>0)
      {
            gsize bytes_written = 0;
            GIOStatus status;
            GError * err = NULL;

            sock->last_action_time = time (0);
            status = g_io_channel_write_chars (sock->gio_channel, line, nleft, &bytes_written, &err);
            switch (status)
            {
                  case G_IO_STATUS_AGAIN:
                        if (++sock->consecutive_failed_operations <= MAX_CONSECUTIVE_FAILED_OPERATIONS) {
                              nleft -= bytes_written;
                              line += bytes_written;
                              g_usleep (G_USEC_PER_SEC/10);
                              continue;
                        }
                        /* fall through */
                  case G_IO_STATUS_EOF:
                        /* fall through */
                  case G_IO_STATUS_ERROR:
                        if (err != NULL) {
                              log_add_va (LOG_ERROR, _("Error writing to socket: %s"), err->message);
                              g_error_free (err);
                        }
                        pan_socket_set_error_flag (sock, TRUE);
                        return TASK_FAIL_NETWORK;

                  case G_IO_STATUS_NORMAL:
                        nleft -= bytes_written;
                        line += bytes_written;
                        sock->consecutive_failed_operations = 0;
                        continue;
            }
      }

      sock->bytes_written += nbytes;
      total_bytes += nbytes;
      g_io_channel_flush (sock->gio_channel, NULL);
      g_io_channel_set_buffered (sock->gio_channel, TRUE);
      g_io_channel_set_buffer_size (sock->gio_channel, old_buffer_size);

      return nleft ? TASK_FAIL : TASK_OK;
}

TaskStateEnum 
pan_socket_putline_va (PanSocket    *sock,
                       const gchar  *format,
                       ...)
{
      va_list args;
      char* line;
      TaskStateEnum retval;

      g_return_val_if_fail (format!=NULL, -1);

      va_start(args, format);
      line = g_strdup_vprintf(format, args);
      va_end(args);

      retval = pan_socket_putline (sock, line);

      g_free (line);
      return retval;
}


/* Flush a socket; wait for a closing socket */
TaskStateEnum
pan_socket_flush (PanSocket * ps)
{
      char buf[512];
      gsize bytes_read = 0;
      GError * err = NULL;
      GIOStatus status;

      for (;;)
      {
            status = g_io_channel_read_chars (ps->gio_channel, buf, sizeof(buf), &bytes_read, &err);
            switch (status)
            {
                  case G_IO_STATUS_EOF:
                        return TASK_FAIL_NETWORK;
                  case G_IO_STATUS_AGAIN:
                        continue;
                  case G_IO_STATUS_NORMAL:
                        if (bytes_read == 0)
                              return TASK_OK;
                        continue;
                  case G_IO_STATUS_ERROR:
                        log_add_va (LOG_ERROR, _("Error reading from socket: %s"), err->message);
                        g_error_free (err);
                        return TASK_FAIL_NETWORK;
            }
      }
}

/**
***
**/

gboolean
pan_socket_needs_auth (const PanSocket * sock)
{
      return sock->need_auth;
}

const PString*
pan_socket_get_username (const PanSocket * sock)
{
      return &sock->nntp_username;
}

const PString*
pan_socket_get_password (const PanSocket * sock)
{
      return &sock->nntp_password;
}

const PString*
pan_socket_get_current_group (const PanSocket * sock)
{
      return &sock->nntp_group_name;
}

void
pan_socket_set_current_group (PanSocket * sock, const PString * group_name)
{
      pstring_copy (&sock->nntp_group_name, group_name);
}

const PString*
pan_socket_get_server_name (const PanSocket * sock)
{
      return &sock->host_name;
}

void
pan_socket_set_error_flag (PanSocket   * sock,
                           gboolean      err_state)
{
      sock->error = err_state;
}

gboolean
pan_socket_get_error_flag (const PanSocket * sock)
{
      return sock->error;
}

time_t
pan_socket_get_last_action_time  (const PanSocket * sock)
{
      return sock->last_action_time;
}

void pan_socket_set_last_action_time  (PanSocket * sock, time_t action_time)
{
      sock->last_action_time = action_time;
}

time_t
pan_socket_get_statistics_start_time (const PanSocket  * sock)
{
      return sock->byte_count_start_time;
}


/**
***
**/

void
pan_socket_reset_statistics (PanSocket* sock)
{
      sock->bytes_read = 0;
      sock->bytes_written = 0;
      sock->byte_count_start_time = time(0);
}

double
pan_socket_get_xfer_rate_KBps (const PanSocket* sock)
{
      const double bytes = sock ? sock->bytes_read + sock->bytes_written : 0;
      const double K = bytes / 1024.0;
      const time_t time_elapsed = sock ? time(0) - sock->byte_count_start_time: 0;
      const double KBps = time_elapsed ? (K / time_elapsed) : 0;
      return KBps;
}

gulong
pan_socket_get_total_xfer_K (void)
{
      return total_bytes / (gulong)1024;
}

Generated by  Doxygen 1.6.0   Back to index