/* Jungle Monkey
* Copyright (C) 1999-2001 The Regents of the University of Michigan
*
* 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; 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 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
*/
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include "mtp_client.h"
#include "mtp_server.h"
#include "mtp_debug.h"
#include "util/elf.h"
#include "util/util.h"
#define TIMEOUT 30000
#define MAX_LINE_LEN 64000
#define BUF_LEN 2048
/**
Call graph:
mtp_client_get_mirrors
mtp_client_get_info
mtp_client_get_info_conn
mtp_client_get_mirror
mtp_client_get_mirrors
mtp_client_get_info_conn
mtp_client_get_file_mirror
mtp_client_get_file_rendezvous
*/
static MtpClient* mtp_client_new (const GURL* url, GConn* conn,
gpointer func, gpointer user_data);
static void mtp_client_connect (MtpClient* client, gchar* message,
GConnFunc func);
static MtpClient* mtp_client_get_info_conn (const GURL* url, GConn* conn,
MtpClientInfoFunc func, gpointer user_data);
static MtpClient* mtp_client_get_file_mirror_conn (const GURL* url, GConn* conn,
MtpClientRecvFunc func, gpointer user_data);
/* **************************************** */
static MtpClient*
mtp_client_new (const GURL* url, GConn* conn, gpointer func, gpointer user_data)
{
MtpClient* client = NULL;
client = g_new0 (MtpClient, 1);
if (url)
client->url = gnet_url_clone (url);
if (conn)
client->conn = conn;
else if (url)
client->conn = gnet_conn_new (url->hostname,
url->port ? url->port : MTP_SERVER_PORT,
NULL, NULL);
client->func = func;
client->user_data = user_data;
return client;
}
static void
mtp_client_connect (MtpClient* client, gchar* message, GConnFunc func)
{
ElfNode* jmpa;
gchar* buffer;
gint length;
g_return_if_fail (client);
g_return_if_fail (message);
g_return_if_fail (func);
g_return_if_fail (client->conn);
g_return_if_fail (client->url);
g_return_if_fail (client->func);
client->conn->func = func;
client->conn->user_data = client;
/* Connect if not connected */
if (!gnet_conn_is_connected (client->conn))
gnet_conn_connect (client->conn, MTP_SERVER_TIMEOUT);
/* Create the announcement */
jmpa = elf_new (message);
elf_set_attribute (jmpa, "name", client->url->resource);
elf_write (jmpa, &buffer, &length);
elf_delete (jmpa);
/* Send the announcement */
gnet_conn_write (client->conn, buffer, length, MTP_SERVER_TIMEOUT);
}
void
mtp_client_delete (MtpClient* client)
{
if (client)
{
GSList* i;
gnet_url_delete (client->url);
gnet_conn_unref (client->conn, TRUE);
g_free (client->buffer);
if (client->file)
{
fclose (client->file);
unlink (client->path);
}
g_free (client->path);
gnet_sha_delete (client->sha);
for (i = client->children; i != NULL; i = i->next)
{
MtpClient* c = (MtpClient*) i->data;
mtp_client_delete (c);
}
g_free (client);
}
}
/* ************************************************************ */
static gboolean mirror_gconn_func (GConn* conn, GConnStatus status,
gchar* buffer, gint length, gpointer user_data);
MtpClient*
mtp_client_get_mirrors (const GURL* url, MtpClientMirrorsFunc func, gpointer user_data)
{
MtpClient* client = NULL;
g_return_val_if_fail (url, NULL);
g_return_val_if_fail (func, NULL);
client = mtp_client_new (url, NULL, func, user_data);
g_return_val_if_fail (client, NULL);
mtp_client_connect (client, "GET_MIRRORS", mirror_gconn_func);
return client;
}
static gboolean
mirror_gconn_func (GConn* conn, GConnStatus status,
gchar* buffer, gint length, gpointer user_data)
{
MtpClient* client = (MtpClient*) user_data;
g_return_val_if_fail (client, FALSE);
switch (status)
{
case GNET_CONN_STATUS_CONNECT:
{
/* Nothing */
break;
}
case GNET_CONN_STATUS_READ:
{
GList* as = NULL;
ElfNode* a;
gchar* mirrors;
gchar** mirrors_split = NULL;
gint i;
GSList* conns = NULL;
gchar* cname = NULL;
g_return_val_if_fail (conn->inetaddr, FALSE);
/* Read, hopefully, MIRRORS mirrors="blah..." */
as = elf_read_list (buffer, length);
if (!as)
goto done;
a = (ElfNode*) as->data;
g_return_val_if_fail (a && a->name, FALSE);
/* name should be MIRRORS or ERROR */
if (strcmp (a->name, "MIRRORS"))
goto done;
mirrors = elf_get_attribute(a, "mirrors");
if (!mirrors)
goto done;
/* Parse the mirrors */
mirrors_split = g_strsplit (mirrors, ";", 16000);
if (!mirrors_split)
goto done;
cname = gnet_inetaddr_get_canonical_name (conn->inetaddr);
/* Examine each mirror */
for (i = 0; mirrors_split[i]; ++i)
{
gchar** pair_split;
/* Parse the mirror. There may be additional arguments in
the future - we allow up to 10. For now, we expect a
just a hostname-port pair. */
pair_split = g_strsplit (mirrors_split[i], ",", 10);
if (pair_split)
{
if (pair_split[0] && *pair_split[0] &&
pair_split[1] && *pair_split[1])
{
gint port;
port = atoi (pair_split[1]);
/* If the mirror is the same as the rendezvous,
reference the connection - we may reuse it. */
if (port == conn->port &&
!strcmp (pair_split[0], cname))
{
gnet_conn_ref (conn);
conns = g_slist_prepend (conns, conn);
}
/* Otherwise, create a new conn. */
else
{
GConn* new_conn;
new_conn = gnet_conn_new (pair_split[0], port, NULL, NULL);
conns = g_slist_prepend (conns, new_conn);
}
}
g_strfreev (pair_split);
}
}
/* Reverse the list. Now they are in the order they were
sent. */
conns = g_slist_reverse (conns);
done:
if (as)
elf_delete_list(as);
/* Delete the mirrors */
if (mirrors_split)
{
for (i = 0; mirrors_split[i]; ++i)
g_free (mirrors_split[i]);
g_free (mirrors_split);
}
if (cname)
g_free (cname);
if (conns)
((MtpClientMirrorsFunc) client->func)(client, MTP_CLIENT_STATUS_OK,
conns, client->user_data);
else
((MtpClientMirrorsFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
NULL, client->user_data);
return FALSE;
break;
}
case GNET_CONN_STATUS_WRITE:
{
g_free (buffer);
/* Read response */
gnet_conn_readline (conn, NULL, MAX_LINE_LEN, MTP_SERVER_TIMEOUT);
break;
}
case GNET_CONN_STATUS_CLOSE:
case GNET_CONN_STATUS_TIMEOUT:
case GNET_CONN_STATUS_ERROR:
{
gnet_conn_disconnect (client->conn, TRUE);
((MtpClientMirrorsFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
NULL, client->user_data);
break;
}
}
return FALSE;
}
/* ************************************************************ */
static gboolean info_gconn_func (GConn* conn, GConnStatus status,
gchar* buffer, gint length, gpointer user_data);
MtpClient*
mtp_client_get_info (const GURL* url, MtpClientInfoFunc func, gpointer user_data)
{
return mtp_client_get_info_conn (url, NULL, func, user_data);
}
static MtpClient*
mtp_client_get_info_conn (const GURL* url, GConn* conn,
MtpClientInfoFunc func, gpointer user_data)
{
MtpClient* client = NULL;
g_return_val_if_fail (url, NULL);
g_return_val_if_fail (func, NULL);
client = mtp_client_new (url, conn, func, user_data);
g_return_val_if_fail (client, NULL);
mtp_client_connect (client, "GET_INFO", info_gconn_func);
return client;
}
static gboolean
info_gconn_func (GConn* conn, GConnStatus status,
gchar* buffer, gint length, gpointer user_data)
{
MtpClient* client = (MtpClient*) user_data;
g_return_val_if_fail (client, FALSE);
switch (status)
{
case GNET_CONN_STATUS_CONNECT:
{
/* Nothing */
break;
}
case GNET_CONN_STATUS_READ:
{
GList* as = NULL;
ElfNode* a;
gboolean ok = FALSE;
gchar* length_str;
gint len = -1;
/* Read, hopefully, INFO length="#" */
as = elf_read_list (buffer, length);
if (!as)
goto done;
a = (ElfNode*) as->data;
g_return_val_if_fail (a && a->name, FALSE);
/* name should be INFO or ERROR */
if (strcmp (a->name, "INFO"))
goto done;
length_str = elf_get_attribute(a, "length");
if (length_str)
len = atoi (length_str);
if (len < 0)
goto done;
ok = TRUE;
done:
if (as)
elf_delete_list (as);
if (ok)
((MtpClientInfoFunc) client->func)(client, MTP_CLIENT_STATUS_OK,
len, client->user_data);
else
((MtpClientInfoFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
-1, client->user_data);
break;
}
case GNET_CONN_STATUS_WRITE:
{
g_free (buffer);
/* Read reponse */
gnet_conn_readline (conn, NULL, MAX_LINE_LEN, MTP_SERVER_TIMEOUT);
break;
}
case GNET_CONN_STATUS_CLOSE:
case GNET_CONN_STATUS_TIMEOUT:
case GNET_CONN_STATUS_ERROR:
{
gnet_conn_disconnect (client->conn, TRUE);
((MtpClientInfoFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
-1, client->user_data);
break;
}
}
return FALSE;
}
/* ************************************************************ */
static void mirror_mirrors_func (MtpClient* client, MtpClientStatus status,
GSList* conns, gpointer user_data);
static void mirror_info_func (MtpClient* iclient, MtpClientStatus status,
gint length, gpointer user_data);
MtpClient*
mtp_client_get_mirror (const GURL* url, MtpClientMirrorFunc func, gpointer user_data)
{
MtpClient* client;
MtpClient* mclient;
g_return_val_if_fail (url, NULL);
g_return_val_if_fail (func, NULL);
client = mtp_client_new (url, NULL, func, user_data);
mclient = mtp_client_get_mirrors (url, mirror_mirrors_func, client);
if (!mclient)
{
g_free (client);
return NULL;
}
client->children = g_slist_prepend (client->children, mclient);
return client;
}
static void
mirror_mirrors_func (MtpClient* mclient, MtpClientStatus status,
GSList* conns, gpointer user_data)
{
MtpClient* client = (MtpClient*) user_data;
/* Done with client */
client->children = g_slist_remove (client->children, mclient);
mtp_client_delete (mclient);
if (status == MTP_CLIENT_STATUS_OK && conns)
{
/* If there's only one mirror, use just that mirror */
if (!conns->next)
{
GConn* conn = (GConn*) conns->data;
/* conn is refed already */
((MtpClientMirrorFunc) client->func)(client, MTP_CLIENT_STATUS_OK,
conn, client->user_data);
}
/* Otherwise, ping each mirror to find the best. We have a ref
to each connection already. */
else
{
GSList* i;
for (i = conns; i != NULL; i = i->next)
{
GConn* conn;
MtpClient* iclient;
conn = (GConn*) i->data;
iclient = mtp_client_get_info_conn (client->url, conn,
mirror_info_func, client);
g_return_if_fail (iclient);
client->children = g_slist_prepend (client->children, iclient);
}
}
g_slist_free (conns);
}
else
{
((MtpClientMirrorFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
NULL, client->user_data);
}
}
static void
mirror_info_func (MtpClient* iclient, MtpClientStatus status, gint length, gpointer user_data)
{
MtpClient* client = (MtpClient*) user_data;
/* Done with client */
client->children = g_slist_remove (client->children, iclient);
if (status == MTP_CLIENT_STATUS_OK)
{
GSList* i;
/* Remove all other info request children */
for (i = client->children; i != NULL; i = i->next)
{
MtpClient* c = (MtpClient*) i->data;
mtp_client_delete (c);
}
g_slist_free (client->children);
client->children = NULL;
gnet_conn_ref (iclient->conn);
((MtpClientMirrorFunc) client->func)(client, MTP_CLIENT_STATUS_OK,
iclient->conn, client->user_data);
}
else
{
/* If not children left, that's an error */
if (!client->children)
{
((MtpClientMirrorFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
NULL, client->user_data);
}
/* Otherwise, hopefully another client/child will work. */
}
mtp_client_delete (iclient);
}
/* ************************************************************ */
static gboolean mirror_file_gconn_func (GConn* conn, GConnStatus status,
gchar* buffer, gint length, gpointer user_data);
MtpClient*
mtp_client_get_file_mirror (const GURL* url, MtpClientRecvFunc func, gpointer user_data)
{
return mtp_client_get_file_mirror_conn (url, NULL, func, user_data);
}
static MtpClient*
mtp_client_get_file_mirror_conn (const GURL* url, GConn* conn,
MtpClientRecvFunc func, gpointer user_data)
{
MtpClient* client = NULL;
g_return_val_if_fail (url, NULL);
g_return_val_if_fail (func, NULL);
client = mtp_client_new (url, conn, func, user_data);
g_return_val_if_fail (client, NULL);
client->buffer = g_new0 (gchar, BUF_LEN);
mtp_client_connect (client, "GET", mirror_file_gconn_func);
return client;
}
static gboolean
mirror_file_gconn_func (GConn* conn, GConnStatus status,
gchar* buffer, gint length, gpointer user_data)
{
MtpClient* client = (MtpClient*) user_data;
g_return_val_if_fail (client, FALSE);
switch (status)
{
case GNET_CONN_STATUS_CONNECT:
{
/* Nothing */
break;
}
case GNET_CONN_STATUS_READ:
{
/* Read header */
if (!client->length)
{
GList* as = NULL;
ElfNode* a;
gboolean ok = FALSE;
gchar* length_str;
gint len;
as = elf_read_list (buffer, length);
if (!as)
goto done;
a = (ElfNode*) as->data;
g_return_val_if_fail (a && a->name, FALSE);
/* Name shoudl be FILE or ERROR */
if (strcmp (a->name, "FILE"))
goto done;
length_str = elf_get_attribute(a, "length");
if (!length_str)
goto done;
len = atoi (length_str);
if (len < 0)
goto done;
client->length = len;
ok = TRUE;
done:
if (as)
elf_delete_list (as);
if (ok)
{
/* Read file if there is a file */
if (client->length)
{
gnet_conn_readany (client->conn, client->buffer,
MIN(BUF_LEN, client->length),
TIMEOUT);
}
/* Otherwise, file is 0-length - we are done */
else
{
gnet_conn_disconnect (client->conn, TRUE);
}
/* Buffer == NULL signals the start of the file */
((MtpClientRecvFunc) client->func)(client, MTP_CLIENT_STATUS_OK,
NULL, 0, client->user_data);
}
else
{
((MtpClientRecvFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
NULL, 0, client->user_data);
}
}
else
{
guint rv;
guint len;
gboolean done;
/* Don't allow sender to send more than the length of the
file */
len = MIN (client->length - client->offset, length);
done = (client->offset + len) == client->length;
/* Pass file up to user */
rv = ((MtpClientRecvFunc) client->func)(client, MTP_CLIENT_STATUS_OK,
buffer, len,
client->user_data);
/* Upper level deletes me when done or on error */
if (rv != len || done)
return FALSE;
client->offset += len;
/* Close connection if done */
if (client->offset == client->length)
{
gnet_conn_disconnect (client->conn, TRUE);
}
/* Otherwise, read more */
else
{
gint len;
len = MIN(client->length - client->offset, BUF_LEN);
gnet_conn_readany (client->conn, client->buffer,
len, TIMEOUT);
}
}
break;
}
case GNET_CONN_STATUS_WRITE:
{
g_free (buffer);
/* Read reponse */
gnet_conn_readline (conn, NULL, MAX_LINE_LEN, MTP_SERVER_TIMEOUT);
break;
}
case GNET_CONN_STATUS_CLOSE:
case GNET_CONN_STATUS_TIMEOUT:
case GNET_CONN_STATUS_ERROR:
{
gnet_conn_disconnect (client->conn, TRUE);
((MtpClientRecvFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
NULL, 0, client->user_data);
break;
}
}
return FALSE;
}
/* ************************************************************ */
static void file_mirror_func (MtpClient* mclient, MtpClientStatus status,
GConn* conn, gpointer user_data);
static guint file_file_func (MtpClient* fclient, MtpClientStatus status,
const void* buffer, guint length,
gpointer user_data);
/**
Get a file from via a rendezvous.
We connect to the rendezvous and get the best mirror (using
mtp_client_get_mirror). Then we connect to the mirror and get the
file (using mtp_client_get_file_mirror).
*/
MtpClient*
mtp_client_get_file_rendezvous (const GURL* url, MtpClientRecvFunc func, gpointer user_data)
{
MtpClient* client;
MtpClient* mclient;
g_return_val_if_fail (url, NULL);
g_return_val_if_fail (func, NULL);
client = mtp_client_new (url, NULL, func, user_data);
g_return_val_if_fail (client, NULL);
/* Get the best mirror */
mclient = mtp_client_get_mirror (url, file_mirror_func, client);
if (!mclient)
{
g_free (client);
return NULL;
}
client->children = g_slist_prepend (client->children, mclient);
return client;
}
/*
Called when we have a mirror. We now connect to the mirror and get
the file.
*/
static void
file_mirror_func (MtpClient* mclient, MtpClientStatus status,
GConn* conn /* have ref */, gpointer user_data)
{
MtpClient* client = (MtpClient*) user_data;
/* Done with client */
client->children = g_slist_remove (client->children, mclient);
mtp_client_delete (mclient);
/* Make sure we found a mirror */
if (status == MTP_CLIENT_STATUS_OK)
{
MtpClient* fclient;
/* Copy the conn of the mirror - we will reuse it */
gnet_conn_ref (conn);
gnet_conn_unref (client->conn, TRUE);
client->conn = conn;
/* Get the file from the mirror */
fclient = mtp_client_get_file_mirror_conn (client->url, conn,
file_file_func, client);
g_return_if_fail (fclient);
client->children = g_slist_prepend (client->children, fclient);
}
else
{
((MtpClientRecvFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
NULL, 0, client->user_data);
}
}
/**
Called when we get a file from the mirror.
*/
static guint
file_file_func (MtpClient* fclient, MtpClientStatus status,
const void* buffer, guint length, gpointer user_data)
{
MtpClient* client = (MtpClient*) user_data;
/* Fake we are the file client (we copied the conn above) */
client->length = fclient->length;
client->offset = fclient->offset;
return ((MtpClientRecvFunc) client->func)(client, status, buffer,
length, client->user_data);
}
/* ************************************************************ */
static guint recv_func (MtpClient* rclient, MtpClientStatus status,
const void* buf, guint len,
gpointer user_data);
MtpClient*
mtp_client_get_file (const GURL* url, const gchar* path,
MtpClientFileFunc func, gpointer user_data)
{
MtpClient* client;
MtpClient* mclient;
g_return_val_if_fail (url, NULL);
g_return_val_if_fail (path, NULL);
g_return_val_if_fail (func, NULL);
client = mtp_client_new (url, NULL, func, user_data);
g_return_val_if_fail (client, NULL);
/* Get the best mirror */
mclient = mtp_client_get_file_rendezvous (url, recv_func, client);
if (!mclient)
{
g_free (client);
return NULL;
}
client->children = g_slist_prepend (client->children, mclient);
client->path = g_strdup (path);
return client;
}
/* TODO. Support random access recvs using an offset. */
static guint
recv_func (MtpClient* rclient, MtpClientStatus status,
const void* buf, guint len,
gpointer user_data)
{
MtpClient* client = (MtpClient*) user_data;
/* If there was an error, reset myself */
if (status != MTP_CLIENT_STATUS_OK) /* Error */
{
MTPP (2, "recv_func error\n");
/* Close the file */
if (client->file)
{
fclose (client->file);
client->file = NULL;
}
unlink (client->path);
/* Error upcall */
((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
client->user_data);
return 0;
}
/* If the is the first time, then there will be no buffer */
else if (!buf) /* Create */
{
/* Open the file */
client->file = fopen (client->path, "w");
if (!client->file)
{
g_warning ("Could not open file %s: %s\n",
client->path, strerror(errno));
/* Error upcall */
((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_ERROR,
client->user_data);
return 0;
}
client->offset = 0;
client->length = rclient->length;
client->sha = gnet_sha_new_incremental ();
/* Check if zero length file */
if (client->length == 0)
{
/* Close the file */
fclose (client->file);
client->file = NULL;
/* Finish the SHA */
gnet_sha_final (client->sha);
/* Success upcall */
((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_OK,
client->user_data);
return 0;
}
/* Update upcall */
((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_UPDATE,
client->user_data);
}
/* Otherwise, this is data - append it */
else /* Write */
{
guint rv;
MTPP (2, "recv_func %s (%d bytes at %d, need %d)\n",
client->path, len, client->offset, client->length);
g_return_val_if_fail (len + client->offset <= client->length, 0);
/* Append the data */
rv = fwrite (buf, len, 1, client->file);
if (rv != 1)
{
g_warning ("fwrite failed: %s\n", strerror(errno));
return 0;
}
/* Update SHA */
gnet_sha_update (client->sha, buf, len);
/* Update offset */
client->offset += len;
/* Check if done */
if (client->offset == client->length)
{
MTPP (2, "recv_func %s done\n", client->path);
/* Close the file */
fclose (client->file);
client->file = NULL;
/* Finish the SHA */
gnet_sha_final (client->sha);
/* Success upcall */
((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_OK,
client->user_data);
return 0;
}
/* Update upcall */
((MtpClientFileFunc) client->func)(client, MTP_CLIENT_STATUS_UPDATE,
client->user_data);
}
return len;
}
syntax highlighted by Code2HTML, v. 0.9.1