/* SPOOLER.c - asynchronous file print program */

/*
**	Date written: 04/23/86
**	Author: Tim Holloway
**		Compuserve: 73026,2026
**		Bix: tholloway
**		Fido node: 112/1 (Casa Mi Amiga).
**
**	As featured in Ami Project Magazine.
**
**	Version: 1.1
**
**	Copyright (C) 1986, by Tim Holloway.  This program may be
**	freely distributed for non-commercial use only.  Use for commercial
**	purposes without the express permission of the author is a violation
**	of copyright law.
**
**	Description:
**	   This program is the intermediary between user requests submitted
**	via the SPOOL program, and the printers driven by copies of the
**	PRTSPOOL program.  Three queues are maintained: the logged-in user
**	queue has an entry for each user (SPOOL or PRTSPOOL) logged into
**	SPOOLER.  The filename queue is where filenames passed from SPOOL
**	are stored until they can be passed on to PRTSPOOLs.  Finally, the
**	waiting-printer queue is where requests for work from PRTSPOOL
**	are held.
**
**	This program illustrates message passing and waiting for messages.
**	Each message is the name of a file.  The named file will be printed,
**	unless the name is SHUTDOWN, which causes SPOOLER to cease
**	accepting login requests, and to respond to further messages with
**	forced logouts.  SPOOL shuts down completely when the login queue
**	has at last been emptied.
**
**	Change History:
**
**		11/19/86, TFH - added logic to FreeStruct SPOOL queue
**	entries when they were removed from the SPOOL queue.  How
**	embarassing!  Also, removed all printfs - does all talking via
**	Intuition now.
**
**	Usage: RUN SPOOLER
*/

#include <exec/types.h>
#include <exec/exec.h>
#include <exec/alerts.h>
#include <libraries/dosextens.h>

#include "stdio.h"
#include "spool.h"

#define NOT !			/* A la RJ Mical */
#define USER_NAME_LENGTH 33	/* maximum length of a logged user name */

/*
**	The following is an information hider - it insulates the workings
**	of this program against changes in the DOS and from compiler
**	dependencies.  Also makes program more readable.
*/

#define on_port(mport) (1L << mport->mp_SigBit)

/*	A convenient allocator for structures: */

#define AllocStruct(structype, pool) (structype *) \
	AllocMem (sizeof(structype), pool)

/* And its converse... */

#define FreeStruct(area, structype) FreeMem(area, sizeof(structype))

/* Hide the details of test for an empty list */

#define EmptyList(list) (list.lh_Head->ln_Succ == NULL)

#define streq(a,b) (strcmp(a,b) == 0)	/* an old favorite */

/*
**	Logged-in user queue element definition:
*/

typedef struct {
	struct Node lu_node;
	char lu_name[USER_NAME_LENGTH];
} logged_user;

/* Prototypes. Lie slightly about function return types. */

extern logged_user *Find_Name(struct List *, TEXT *);

extern SPOOLmsg *RemHead (struct List *),
		*GetMsg (struct MsgPort *);



/* Global variables. */

BOOL
	spooler_running;
	spooler_closing_down;


static void
make_id (packet, str)	/* create a unique login ID */
SPOOLmsg *packet;	/* based on the sender's reply port address */
TEXT *str;
{
	sprintf (str, "SPOOL User %lx", &packet->minfo.mn_ReplyPort);
}

static void
error (msg)		/* report error */
TEXT *msg;
{
	Gripe ("SPOOLER:", msg, "");
}

/*************************************************************************
**									**
**	Log users in and out.  Users are logged to ensure that nobody	**
**	tries to send spool requests once the spooler starts closing	**
**	down.								**
**									**
*************************************************************************/

struct List login_list;	/* anchor the list of logged-in users */

struct List file_queue;	/* ditto the list of filenames to spool */

struct List waiting_printers;	/* unemployed printer list */

static void
login(packet)
register SPOOLmsg *packet;
{
	register logged_user *user;

	packet->log_status = LOG_OUT;	/* assume the worst */
	if (NOT spooler_closing_down)
	{
/*		printf ("Log in: "); */
		user = AllocStruct (logged_user, MEMF_PUBLIC | MEMF_CLEAR);
		if (user == NULL)
			error ("Couldn't get memory to log in user");
		else
		{
			user->lu_node.ln_Name = user->lu_name;
			user->lu_node.ln_Type = NT_UNKNOWN;
			user->lu_node.ln_Pri = 0;
			make_id (packet, user->lu_name);
			AddTail (&login_list, user);
/*	printf ("%s completed\n", user->lu_name); */
			packet->log_status = LOGGED;
		}
	}
}

static void
logout(packet)
register SPOOLmsg *packet;
{
	char user_id[USER_NAME_LENGTH];
	register logged_user *user;

	make_id (packet, user_id);
	user = (logged_user *) FindName (&login_list, user_id);
	if (user == NULL)
	{
		error ("Logout failed - could not find user ID:");
		error (user_id);
	}
	else
	{
/*	printf ("%s logged out.\n", user_id); */
		Remove(user);	/* ... from the logged-user list */
		FreeStruct(user, logged_user);
		packet->log_status = LOG_OUT;
		if (EmptyList(login_list) && spooler_closing_down)
			spooler_running = FALSE;
	}
}


/*************************************************************************
**									**
**	Spooler shutdown routines.					** 
**									**
*************************************************************************/

static void
purge_queues()		/* throw away any outstanding SPOOL requests */
{			/* modified v1.1 */
	SPOOLmsg *qp;

	while ( (qp = (SPOOLmsg *) RemHead(&file_queue)) != NULL)
		FreeStruct(qp, SPOOLmsg);
}

static void
terminate_printers()		/* tell the printers to get lost! */
{
	register SPOOLmsg *bail_out;

	while (
		 (bail_out = (SPOOLmsg *)RemHead(&waiting_printers))
	          !=
		 NULL
	      )
	{

/* printf ("Force off PRT %lx\n", bail_out); */

		logout(bail_out);	/* remove from log */
		bail_out->log_status = LOG_OUT;
		ReplyMsg(bail_out);
	}
}

/*************************************************************************
**									**
**	Process inbound SPOOL requests.					**
**	If somebody wants the request, pass it on, else make a copy	**
**	of it, and queue the copy.					**
**									**
*************************************************************************/

static void
pass_request(packet)
register SPOOLmsg *packet;
{
	register SPOOLmsg *msg_requested;

	if ( (msg_requested= RemHead(&waiting_printers)) == NULL)
	{
		msg_requested =
			AllocStruct (SPOOLmsg, MEMF_PUBLIC);
		movmem (packet, msg_requested, sizeof(SPOOLmsg));

/* printf ("Queuing request for %s\n", packet->filename); */

		AddTail (&file_queue, msg_requested);
	}
	else	/* send the name to the PRTSPOOL's query */
	{
		strcpy (msg_requested->filename, packet->filename);
		ReplyMsg (msg_requested);
	}
}

/*************************************************************************
**									**
**	Process inbound SPOOL requests.					**
**									**
*************************************************************************/

static void
handle_input(packet)
register SPOOLmsg *packet;
{
/* printf ("Inbound message from %lx\n", packet); */

	switch (packet->log_status)
	{
	case LOG_IN:
		login (packet);
		break;
	case LOG_OUT:
		logout (packet);
		break;
	case LOGGED:
		if (spooler_closing_down)
			logout(packet);		/* sorry! */
		else
		if (streq(packet->filename, SHUTDOWN))
		{
			spooler_closing_down = TRUE;
			purge_queues();
			terminate_printers();
		}
		else
		    pass_request (packet);
		break;
	default: error ("SPL: Invalid log request");
	}
	ReplyMsg (packet);	/* return to owner! */
}

/*************************************************************************
**									**
**	Process outbound SPOOL requests.				**
**									**
**	Notice that since the PRTSPOOL messages contain List nodes,	**
**	they can be queued directly!					**
**									**
*************************************************************************/

static void
prt_request(packet)
register SPOOLmsg *packet;
{
	register SPOOLmsg *msg_requested;

	if ( (msg_requested= RemHead(&file_queue)) == NULL)
	{
/* printf ("Queuing PRT request from %lx.\n", packet); */

		AddTail (&waiting_printers, packet);
	}
	else
	{

/* printf ("dequeueing and outputting %s\n", msg_requested->filename); */

		strcpy (packet->filename, msg_requested->filename);
		FreeStruct (msg_requested, SPOOLmsg);	/* V1.1 */
		ReplyMsg (packet);
	}
}

/*************************************************************************
**									**
**	Process PRTSPOOL requests.					**
**									**
*************************************************************************/

static void
handle_output(packet)
register SPOOLmsg *packet;
{
/* printf ("PRTSPOOL message from %lx\n", packet); */

	switch (packet->log_status)
	{
	case LOG_IN:
		login (packet);
		break;
	case LOG_OUT:
		logout (packet);
		break;
	case LOGGED:
		if (spooler_closing_down)
			logout(packet);		/* sorry! */
		else
		{
		    prt_request (packet);
		    return;	/* reply done in prt_request */
		}
		break;
	default: error ("PRT: Invalid log request");
	}
	ReplyMsg (packet);	/* return to owner! */
}

/*************************************************************************
**									**
**	Gentlemen, start your SPOOLers.					**
**									**
*************************************************************************/

struct Library *IntuitionBase, *OpenLibrary();

void
main (argc, argv)
int argc;
char *argv[];
{
   struct MsgPort *FindPort(), *CreatePort(), *inport, *outport;
   SPOOLmsg *packet;
   struct Process *myprocess, *FindTask();

   if ( (IntuitionBase = OpenLibrary("intuition.library", 0)) == NULL)
   {
	Alert (AT_Recovery + AG_OpenLib + AO_Intuition, NULL);
	exit(20);
   }

	/* it doesn't hurt to run multiple SPOOLERS at once -
	   but why bother?  Only one will get the messages! */

   if(FindPort(SPOOL_ME) != NULL)
   {
	error ("SPOOLER is already active!");
	goto abort;
   }

   if ((inport = CreatePort(SPOOL_ME,0)) == NULL)
   {
      error ("Unable to create spool message port\n");
      goto abort;
   }

   if ((outport = CreatePort(GIMME_A_FILE,0)) == NULL)
   {
      error ("Unable to create spool message port\n");
      DeletePort (inport);
      goto abort;
   }

   NewList (&file_queue);
   NewList (&login_list);
   NewList (&waiting_printers);


     /* raise priority - this program is low-overhead (mostly Waiting) */

    myprocess = FindTask(NULL);
    (void) SetTaskPri (myprocess, SPOOLER_PRIORITY);

/* printf ("input message port for %s is at %lx\n", SPOOL_ME, inport); */

   for (spooler_running = TRUE; spooler_running ; )
   {

/* printf ("Wait for port...\n"); */
      Wait (on_port(inport) | on_port(outport)); /* '|', NOT '||' ! */
      while ( (packet = GetMsg (inport)) != NULL) handle_input(packet);

      while ( (packet = GetMsg (outport)) != NULL) handle_output(packet);
   }
/*   printf ("Spooling has been terminated\n"); */
   DeletePort (inport);
   DeletePort (outport);

abort:
   CloseLibrary(IntuitionBase);
}
