/* 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