Source Code

Source Code — The code used in the tutorial.

Header File

#ifndef __FLP_CLIENT_H__
#define __FLP_CLIENT_H__

#include <libgtcpsocket/gtcp-connection.h>

/* Used by the "flp-status" property */
typedef enum /* < prefix=FLP_STATUS > */
{
    FLP_STATUS_CLOSING = -3,
    FLP_STATUS_CLOSED = -2,
    FLP_STATUS_OPENING = -1,
    FLP_STATUS_READY = 0,
    FLP_STATUS_RECIEVING = 1
}
FLPStatusType;

/* Used by the "error" signal */
typedef enum /* < prefix=FLP_ERROR > */
{
    FLP_ERROR_UNKNOWN,
    FLP_ERROR_BAD_USERPASS,
    FLP_ERROR_NOT_FOUND,
    FLP_ERROR_BAD_PERMISSIONS,
    FLP_ERROR_NOTRANSFER
}
FLPErrorType;


typedef struct _FLPClient FLPClient;
typedef struct _FLPClientClass FLPClientClass;

struct _FLPClient
{
    GTcpConnection parent;

    /* Read-Write Object Properties */
    FLPStatusType flp_status;
    gchar *username;
    gchar *passwd;

    /* Read-only Object Properties */
    gchar *filename;
    gulong filesize;
    gulong offset;

    /* Other Data */
    gulong filesize_recieved;
    gint fd;
};

struct _FLPClientClass
{
    GTcpConnectionClass parent_class;

    /* Protocol Signals */
    void (*connect_complete) (FLPClient *client,
                              gboolean connected);
    void (*quit)             (FLPClient *client,
                              gboolean requested);

    /* Incoming Data Signals */
    void (*error)            (FLPClient *client,
                              FLPErrorType error);

    void (*download_start)   (FLPClient *client,
                              const gchar *filename,
                              gsize filesize,
                              gsize offset);
    void (*download_done)    (FLPClient *client,
                              const gchar *filename,
                              gboolean completed);
};

GType flp_client_get_type (void);

#endif /* __FLP_CLIENT_H__ */

Source File

#include "flp-client.h"

/* For open() */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* For close() */
#include <unistd.h>


/* Property IDs */
enum
{
    PROP_0,
    FLP_STATUS,
    USERNAME,
    PASSWD,
    FILENAME,
    FILESIZE,
    OFFSET
};

/* Signal ID Location */
enum
{
    CONNECT_COMPLETE,
    QUIT,
    ERROR,
    DOWNLOAD_START,
    DOWNLOAD_DONE,
    LAST_SIGNAL
};


/* Cached Signal IDs */
static gint flp_signals[LAST_SIGNAL] = {0};

/* Parent Class (GTcpConnectionClass) */
static gpointer parent_class = NULL;


/* FLPClient Object Callbacks */

static void
flp_client_quit (FLPClient *client,
                 gboolean requested)
{
    if (client->fd != -1)
    {
        g_signal_emit (client, flp_signals[DOWNLOAD_DONE], 0,
                       client->filename, FALSE);
    }
}

static void
flp_client_error (FLPClient *client,
                  FLPErrorType error)
{
    if (client->fd != -1)
    {
        g_signal_emit (client, flp_signals[DOWNLOAD_DONE], 0,
                       client->filename, FALSE);
    }
}


static void
flp_client_download_done (FLPClient *client,
                          const gchar *filename,
                          gboolean completed)
{
    /* The destination file should always be open at this point. */
    g_assert (client->fd != -1);
    
    close (client->fd);

    /* Reset the object members */
    client->fd = -1;
    client->filesize_recieved = 0;

    /* Reset the object properties */
    /* Freeze the "notify" signal until we're done. */
    g_object_freeze_notify (G_OBJECT (client));

    client->offset = 0;
    g_object_notify (G_OBJECT (client), "offset");

    client->filesize = 0;
    g_object_notify (G_OBJECT (client), "filesize");
   
    g_free (client->filename);
    client->filename = NULL;
    g_object_notify (G_OBJECT (client), "filename");

    /* We're done, so thaw the "notify" signal. */
    g_object_thaw_notify (G_OBJECT (client));

}

static void
flp_client_download_start (FLPClient *client,
                           const gchar *filename,
                           gsize filesize,
                           gsize offset)
{
    gchar *path;

    /* The client should not have an open file already */
    g_assert (client->fd == -1);

    /* Put the file in ~/Downloads/<filename> */
    path = g_build_filename (g_get_home_dir (), "Downloads", filename, NULL);

    /* Actually open the file */
    client->fd = open (path, (O_CREAT | O_NONBLOCK | O_APPEND));

    /* Free the path */
    g_free (path);
}


/* GTcpConnection Callbacks */

static void
flp_client_connect_done (GTcpConnection *conn,
                         GTcpConnectionStatus status)
{
    if (GTCP_CONNECTION_CLASS (parent_class)->connect_done != NULL)
        (*GTCP_CONNECTION_CLASS (parent_class)->connect_done) (conn, status);

    /* If we're connected... */
    if (status == GTCP_CONNECTION_CONNECTED)
    {
        gchar *data = NULL;
        FLPClient *client = FLP_CLIENT (conn);

        /* Set the "flp-status" property */
        client->status = FLP_CLIENT_LOGGING_IN;
        g_object_notify (G_OBJECT (conn), "flp-status");

        /* Construct the login string */
        if (client->username != NULL)
        {
            if (client->password != NULL)
            {
                data = g_strdup_printf ("LOGIN %s %s\n",
                                        client->username,
                                        client->password);
            }
            else
            {
                data = g_strdup_printf ("LOGIN %s %s\n",
                                        client->username);
            }
        }
        else
        {
            data = g_strdup ("LOGIN\n");
        }

        /* Send the login string */
        gtcp_connection_send (conn, data, -1);

        /* Free the login string */
        g_free (data);
    }
}

static void
flp_client_closed (GTcpConnection *conn,
                   gboolean requested)
{
    if (GTCP_CONNECTION_CLASS (parent_class)->closed != NULL)
        (*GTCP_CONNECTION_CLASS (parent_class)->closed) (conn, requested);

    /* If the user requested the quit, then the quit function should
       have set the status appropriately. */
    g_signal_emit (conn, signals[QUIT], 0,
                   (FLP_CLIENT (conn)->status == FLP_CLIENT_QUITTING));
}

static void
flp_client_recv (GTcpConnection *conn,
                 gconstpointer data,
                 gsize length)
{
    FLPClient *client = FLP_CLIENT (conn);
    gint line_end_pos = -1;

    if (GTCP_CONNECTION_CLASS (parent_class)->recv != NULL)
        (*GTCP_CONNECTION_CLASS (parent_class)->recv) (conn, data, length);

    /* First, split the raw incoming data into lines */
    lines = g_strsplit (data, "\n", -1);

    if (lines == NULL)
        return;

    for (i = 0; lines[i] != NULL; i++)
    {
        gchar *msg = NULL;

        /* verify that we've got a message, and not data for a file.
           We can do this by checking to see if the following sscanf parse
           works. If it does, msg will be filled with the message type
           string (and any other characters up to the newline). */
        if (sscanf (lines[i], "FLP %a[^\n]", &msg) == 1)
        {
            FLPErrorType error_code;
            gboolean error_msg = FALSE;

            /* Find the proper message type */
            /* Login successful */
            if (g_ascii_strcasecmp (msg, "LOGGED_IN") == 0)
            {
                /* Then, emit the "connect-complete" signal */
                g_signal_emit (client, signals[CONNECT_COMPLETE], 0, TRUE);
            }
            /* Starting to recieve a file */
            else if (g_ascii_strcasencmp (msg, "STARTGET", 8) == 0)
            {
                gchar *filename = NULL;
                gulong size = 0;
                gint sscanf_retval;

                /* First, get the filename and size */
                sscanf_retval = sscanf (msg + 9, "%as %ul",
                                        &filename, &size);

                /* Set the "filename" property */
                g_free (client->filename);
                client->filename = g_strdup (filename);
                g_free (filename);
                g_object_notify (G_OBJECT (client), "filename");

                /* Set the "filesize" property */
                client->size = size;
                g_object_notify (G_OBJECT (client), "filesize");

                /* Reset the bytes recieved for this file */
                client->bytes_recieved = 0;
                client->offset = 0;
                g_object_notify (G_OBJECT (client), "offset");

                /* Emit the "download-start" signal */
                g_signal_emit (client, flp_signals[DOWNLOAD_START], 0,
                               client->filename,
                               client->filesize,
                               client->offset);
            }
            /* Done recieving a file */
            else if (g_ascii_strcasecmp (msg, "DONEGET") == 0)
            {
                gboolean complete;
                
                complete = (client->bytes_recieved == client->filesize);

                g_signal_emit (client, flp_signals[DOWNLOAD_DONE], 0,
                               client->filename,
                               client->filesize,
                               complete);
            }
            /* Error Types */
            else if (g_ascii_strcasecmp (msg, "BADUSERPASS") == 0)
            {
                error_msg = TRUE;
                error_code = FLP_ERROR_BAD_USERPASS;
            }
            else if (g_ascii_strcasecmp (msg, "NOTFOUND") == 0)
            {
                error_msg = TRUE;
                error_code = FLP_ERROR_NOT_FOUND;
            }
            else if (g_ascii_strcasecmp (msg, "BADPERMS") == 0)
            {
                error_msg = TRUE;
                error_code = FLP_ERROR_BAD_PERMISSIONS;
            }
            else if (g_ascii_strcasecmp (msg, "NOTRANSFER") == 0)
            {
                error_msg = TRUE;
                error_code = FLP_ERROR_NOTRANSFER;
            }
            /* *msg == "UNKNOWN" or some random bad data. */
            else
            {
                error_msg = TRUE;
                error_code = FLP_ERROR_UNKNOWN;
            }

            if (error_msg)
                g_signal_emit (client, signals[ERROR], 0, error_code);
        }
        /* We don't have a message, this is data for an incoming file */
        else
        {
            gsize line_size = strlen (lines[i]);

            client->filesize_recieved += line_size;

            write (client->fd, lines[i], line_size);
        }

     g_free (msg);
}


/* GObject Callbacks */

static void
flp_client_finalize (GObject *object)
{
    FlpClient *client = FLP_CLIENT (object);

    g_free (client->filename);

    if (client->fd != -1)
        close (client->fd);

    if (G_OBJECT_CLASS (parent_class)->finalize != NULL)
        (*G_OBJECT_CLASS (parent_class)->finalize) (object);
}


static void
flp_client_set_property (GObject * object,
                         guint property,
                         const GValue * value,
                         GParamSpec * param_spec)
{
    FlpClient *client = FLP_CLIENT (object);

    switch (property)
    {
        case USERNAME:
            g_return_if_fail (client->flp_status <= GIRC_CLIENT_CLOSED);

            g_free (client->username);

            client->username = g_value_dup_string (value);

            g_object_notify (object, "username");
            break;

        case PASSWORD:
            g_return_if_fail (client->flp_status <= GIRC_CLIENT_CLOSED);

            g_free (client->password);

            client->password = g_value_dup_string (value);

            g_object_notify (object, "password");
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property,
                                               param_spec);
            break;
    }
}

static void
flp_client_get_property (GObject * object,
                         guint property,
                         GValue * value,
                         GParamSpec * param_spec)
{
    FLPClient *client = FLP_CLIENT (object);

    switch (property)
    {
        case FLP_STATUS:
            g_value_set_enum (value, client->flp_status);
            break;

        case USERNAME:
            g_value_set_string (value, client->username);
            break;

        case PASSWORD:
            g_value_set_string (value, client->password);
            break;

        case FILENAME:
            g_value_set_string (value, client->filename);
            break;

        case FILESIZE:
            g_value_set_ulong (value, client->filesize);
            break;

        case OFFSET:
            g_value_set_ulong (value, client->offset);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property,
                                               param_spec);
            break;
    }
}


/* GType Functions */

static void
flp_client_class_init (FLPClientClass *class)
{
    GObjectClass *object_class = G_OBJECT_CLASS (class);
    GTcpConnectionClass *tcp_class = GTCP_CONNECTION_CLASS (class);

    parent_class = g_type_class_peek_parent (class);

    object_class->finalize = flp_client_finalize;
    object_class->set_property = flp_client_set_property;
    object_class->get_property = flp_client_get_property;

    tcp_class->closed = flp_client_closed;
    tcp_class->connect_done = flp_client_connect_done;
    tcp_class->recv = flp_client_recv;

    class->quit = flp_client_quit;
    class->error = flp_client_error;
    class->download_start = flp_client_download_start;
    class->download_done = flp_client_download_done;

    /* Signals */
    flp_signals[CONNECT_COMPLETE] =
        g_signal_new ("connect-complete",
                      G_TYPE_FROM_CLASS (object_class),
                      G_SIGNAL_RUN_FIRST,
                      G_STRUCT_OFFSET (FLPClientClass, connect_complete),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__BOOLEAN,
                      G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
    flp_signals[QUIT] =
        g_signal_new ("quit",
                      G_TYPE_FROM_CLASS (object_class),
                      G_SIGNAL_RUN_FIRST,
                      G_STRUCT_OFFSET (FLPClientClass, quit),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__BOOLEAN,
                      G_TYPE_NONE, 1, G_TYPE_BOOLEAN);

    flp_signals[ERROR] =
        g_signal_new ("error",
                      G_TYPE_FROM_CLASS (object_class),
                      G_SIGNAL_RUN_FIRST,
                      G_STRUCT_OFFSET (FLPClientClass, error),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__ENUM,
                      G_TYPE_NONE, 1, FLP_TYPE_ERROR_TYPE);

    flp_signals[DOWNLOAD_START] =
        g_signal_new ("download-start",
                      G_TYPE_FROM_CLASS (object_class),
                      G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (FLPClientClass, download_start),
                      NULL, NULL,
                      _flp_marshal_VOID__STRING_ULONG_ULONG,
                      G_TYPE_NONE, 3,
                      G_TYPE_STRING, G_TYPE_ULONG, G_TYPE_ULONG);
    flp_signals[DOWNLOAD_DONE] =
        g_signal_new ("download-done",
                      G_TYPE_FROM_CLASS (object_class),
                      G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (FLPClientClass, download_done),
                      NULL, NULL,
                      _flp_marshal_VOID__STRING_BOOLEAN,
                      G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN);

    /* Properties */
    g_object_class_install_property (object_class,
        FLP_STATUS,                             // Property ID
        g_param_spec_enum ("flp-status",        // Property Name
            _("Protocol Status"),               // Short Description
            _("The status of the connection."), // Long Description
            FLP_TYPE_STATUS_TYPE,               // Enumerated GType
            FLP_STATUS_CLOSED,                  // Default Value
            G_PARAM_READABLE));                 // Property Flags

    g_object_class_install_property (object_class, USERNAME,
        g_param_spec_string ("username",
                             _("User name"),
                             _("The username used to authenticate "
                               "connections."),
                             NULL,              // Default Value
                             (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)));

    g_object_class_install_property (object_class, PASSWORD,
        g_param_spec_string ("password",
                             _("Password"),
                             _("The password used to authenticate "
                               "connections."),
                             NULL,
                             (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)));

    g_object_class_install_property (object_class, FILENAME,
        g_param_spec_string ("filename",
                             _("Transfer Filename"),
                             _("The filename currently being transferred."),
                             NULL,
                             G_PARAM_READABLE));

    g_object_class_install_property (object_class, FILESIZE,
        g_param_spec_ulong ("filesize",
                            _("Transfer Filename"),
                            _("The size of the file currently being "
                              "transferred."),
                             0,                 // Minimum Value
                             G_MAXULONG,        // Maximum Value
                             0,                 // Default Value
                             G_PARAM_READABLE));

    g_object_class_install_property (object_class, OFFSET,
        g_param_spec_ulong ("offset",
                            _("Transfer Offset"),
                            _("The byte position of the current download."),
                             0, G_MAXULONG, 0,
                             G_PARAM_READABLE));
}

static void
flp_client_instance_init (FlpClient *client)
{
    /* Initialize the file descriptor to invalid */
    client->fd = -1;
}


/* PUBLIC API */

GType
flp_client_get_type (void)
{
    static GType type = 0;

    if (type == 0)
    {
        static const GTypeInfo info = {
            sizeof (FLPClientClass),
            (GBaseInitFunc) NULL,
            (GBaseFinalizeFunc) NULL,
            (GClassInitFunc) flp_client_class_init,
            (GClassFinalizeFunc) NULL,
            NULL,
            sizeof (FLPClient),
            0,
            (GInstanceInitFunc) flp_client_instance_init,
            NULL
        };

        type = g_type_register_static (GTCP_TYPE_CONNECTION,
                                       "FLPClient", &info, 0);
    }

    return type;
}