/* Jungle Monkey
 * Copyright (C) 1999, 2000  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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "sha_async.h"


typedef struct _GSHAAsyncState
{
  gboolean 	waiting;

  gchar* 	pathname;
  int 		fd;
  GIOChannel* 	iochannel;
  guint 	watch;

  GSHA* 	sha;

  GSHAAsyncFunc func;
  gpointer 	user_data;

} GSHAAsyncState;

#define 	MAX_ASYNC_SHA			8
static guint 	num_async_sha =			0;
static GSList*	waiting = 			NULL;
static GSList*	waiting_last = 			NULL;


static gboolean dispatch_sha (GSHAAsyncState* state);
static gboolean sha_cb (gpointer data);


GSHAAsyncID*
gnet_sha_new_file_async (const gchar* path, GSHAAsyncFunc func,
			 gpointer user_data)
{
  GSHAAsyncState* state;

  g_return_val_if_fail (path, NULL);
  g_return_val_if_fail (func, NULL);

  state = g_new0 (GSHAAsyncState, 1);
  state->pathname = g_strdup (path);
  state->sha = gnet_sha_new_incremental();
  state->func = func;
  state->user_data = user_data;

  /* Dispatch now if no one is waiting, otherwise queue */
  if (num_async_sha < MAX_ASYNC_SHA)
    {
      if (dispatch_sha (state))
	state = NULL;
    }
  else
    {
      GSList* new_list;

      state->waiting = TRUE;

      /* Append waiting */
      new_list = g_slist_alloc ();
      new_list->data = state;
      if (waiting_last)
	waiting_last->next = new_list;
      else
	waiting = new_list;
      waiting_last = new_list;
    }

  return (GSHAAsyncID) state;
}


/* Return FALSE if ok, TRUE otherwise */
static gboolean
dispatch_sha (GSHAAsyncState* state)
{
  g_return_val_if_fail (state, TRUE);
  g_return_val_if_fail (!state->fd, TRUE);
  g_return_val_if_fail (state->pathname, TRUE);

  state->waiting = FALSE;
  ++num_async_sha;

  /* Fail if file does not exist */
  state->fd = open (state->pathname, O_RDONLY);
  /* TODO: A newer GLib may include an a way to open files
     non-blocking. */
  if (state->fd == -1)
    {
      (state->func)(NULL, state->user_data);
      gnet_sha_new_file_async_cancel ((GSHAAsyncID) state);
      return TRUE;
    }

  state->iochannel = g_io_channel_unix_new (state->fd);
  state->watch = g_idle_add (sha_cb, state);

  return FALSE;
}


static gboolean 
sha_cb (gpointer data)
{
  GSHAAsyncState* state =  (GSHAAsyncState*) data;
  gchar buffer [4096];
  guint bytes_read;

  g_return_val_if_fail (state, FALSE);

  bytes_read = read (state->fd, buffer, sizeof(buffer));
  if (bytes_read > 0)
    {
      gnet_sha_update (state->sha, buffer, bytes_read);
      return TRUE;
    }
  else if (bytes_read == 0)
    {
      gnet_sha_final (state->sha);
      (state->func)(state->sha, state->user_data);

      state->sha = NULL;
      gnet_sha_new_file_async_cancel ((GSHAAsyncID) state);
    }
  else
    {
      (state->func)(NULL, state->user_data);
      gnet_sha_new_file_async_cancel ((GSHAAsyncID) state);
    }

  return FALSE;
}


/* Cancel or delete a SHA async state.  This is used internally to
   delete states. */
void
gnet_sha_new_file_async_cancel (GSHAAsyncID* id)
{
  GSHAAsyncState* state = (GSHAAsyncState*) id;

  g_return_if_fail (id);

  g_free (state->pathname);
  gnet_sha_delete (state->sha);

  if (state->fd)
    close (state->fd);
  if (state->iochannel)
    g_io_channel_unref (state->iochannel);
  if (state->watch)
    g_source_remove (state->watch);

  if (state->waiting)
    {
      g_return_if_fail (waiting);
      g_return_if_fail (waiting_last);

      if (waiting_last->data == state)
	waiting_last = NULL;
      waiting = g_slist_remove (waiting, state);
      if (waiting && !waiting_last)
	waiting_last = g_slist_last (waiting);
    }
  else
    {
      --num_async_sha;
      if (waiting && num_async_sha < MAX_ASYNC_SHA)
	{
	  GSHAAsyncState* next_state;

	  /* Remove from front of list */
	  next_state = (GSHAAsyncState*) waiting->data;
	  if (waiting == waiting_last)
	    waiting_last = NULL;
	  waiting = g_slist_remove (waiting, next_state);

	  dispatch_sha (next_state);
	}
    }

  g_free (state);
}


syntax highlighted by Code2HTML, v. 0.9.1