/************************************************************************
 *                                                                      *
 *                            Preliminary                               *
 *                        Amiga AppShell (tm)                           *
 *                                                                      *
 *  Copyright (c) 1990,1991 Commodore-Amiga, Inc. All Rights Reserved.  *
 *                                                                      *
 *   This software and information is proprietary, preliminary, and     *
 *   subject to change without notice.                                  *
 *                                                                      *
 *                            DISCLAIMER                                *
 *                                                                      *
 *   THIS SOFTWARE IS PROVIDED "AS IS".                                 *
 *   NO REPRESENTATIONS OR WARRANTIES ARE MADE WITH RESPECT TO THE      *
 *   ACCURACY, RELIABILITY, PERFORMANCE, CURRENTNESS, OR OPERATION      *
 *   OF THIS SOFTWARE, AND ALL USE IS AT YOUR OWN RISK.                 *
 *   NEITHER COMMODORE NOR THE AUTHORS ASSUME ANY RESPONSIBILITY OR     *
 *   LIABILITY WHATSOEVER WITH RESPECT TO YOUR USE OF THIS SOFTWARE.    *
 *                                                                      *
 *                          Non-Disclosure                              *
 *                                                                      *
 *   This information is not to be disclosed to any other company,      *
 *   individual or party.  Discussion is to be restricted to CBM        *
 *   approved discussion areas, such as the closed conferences on bix;  *
 *   amiga.cert, amiga.com, amiga.beta/appshell.                        *
 *                                                                      *
 ************************************************************************
 * multiproj.c
 * Copyright (C) 1991 Commodore-Amiga, Inc.
 * Minimum requirements for an AppShell application.
 * Written by David N. Junod
 *
 * Must be compiled with Lattice options: -b0 -cfist -ms -v
 *
 */

#include "multiproj.h"
#include "multiproj_rev.h"

#define	DI(x)	;

void kprintf(void *, ...);

/* Private functions */
BOOL start_project (struct AppInfo * ai, struct ProjNode * pn);
VOID signal_projects (struct AppInfo *, LONG, VOID (*func) (struct AppInfo *, struct ProjData *));
BOOL SafePutToPort (struct Message * message, STRPTR name);

/* Shell argument template */
#define TEMPLATE "Files/M,BG=Background/S,UnLoad/S"
#define	OPT_FILES	0
#define	OPT_BACKG	1
#define OPT_UNLOAD	2
#define	OPT_COUNT	3

/* All text used in the application must be defined in a text array.  Then
 * referred to by numeric ID. */
STRPTR MasterText[] =
{
 /* Padding */
    "",

    "Not enough memory",
    "Couldn't allocate project",
    "Couldn't start project process",
    "Master port went away",
    "Can't allocate images",

 /* NULL termination is required */
    NULL
};

#define	MPERR_NO_MEMORY		1
#define	MPERR_NO_PROJECT	2
#define	MPERR_NO_PROCESS	3
#define	MPERR_NO_PORT		4

/* The AppShell will convert this array into function table entries and will
 * add them to the function table list.  Using the APSHF_PRIVATE flag, we are
 * able to have functions that can't be triggered by the user.  This
 * also defines the commands that are available through ARexx.  Note that
 * the command parser is case-INSENSITIVE, but for readability, your
 * entries in this table should follow standard capitalization rules. */
struct Funcs MasterFuncTable[] =
{
 /* These are public master functions */
    {"Quit", QuitFunc, QuitID, "FORCE/S,UNLOAD/S", 2L, NULL,},
    {"New", NewFunc, NewID,},

 /* This function can not be accessed by the user */
    {"CInit", CInitFunc, CInitID, NULL, NULL, APSHF_PRIVATE},
    {"CExit", CExitFunc, CExitID, NULL, NULL, APSHF_PRIVATE},
    {"IsActive", IsActiveFunc, IsActiveID, NULL, NULL, APSHF_PRIVATE},
    {"Open", OpenFunc, OpenID, NULL, NULL, APSHF_PRIVATE,},
    {"Close", CloseFunc, CloseID, NULL, NULL, APSHF_PRIVATE,},
    {"ActiveTool", ActiveToolFunc, ActiveToolID, NULL, NULL, APSHF_PRIVATE,},
    {"SetAttrs", SetAttrsFunc, SetAttrsID, "BACKGROUND/S,UNLOAD/S", 2, NULL,},

 /* Marks the end of the array */
    {NULL, NO_FUNCTION,}
};

/* Shared system libraries */
struct Library *AslBase;
struct Library *GadToolsBase;
struct Library *GfxBase;
struct Library *IconBase;
struct Library *IntuitionBase;
struct Library *UtilityBase;
struct Library *AppObjectsBase;

/* Library Environment:
 * This tag array is used to open and close the shared system libraries
 * needed by our application.
 */
struct TagItem Our_Libs[] =
{
 /* Minimum library version */
    {APSH_LibVersion, 36L},

 /* All libraries are required */
    {APSH_LibStatus, APSH_REQUIRED},

 /* Libraries to open */
    {APSH_ASL, (ULONG) & AslBase},
    {APSH_GadTools, (ULONG) & GadToolsBase},
    {APSH_Gfx, (ULONG) & GfxBase},
    {APSH_Icon, (ULONG) & IconBase},
    {APSH_Intuition, (ULONG) & IntuitionBase},
    {APSH_Utility, (ULONG) & UtilityBase},

 /* The next library is optional */
    {APSH_LibStatus, APSH_OPTIONAL},
    {APSH_LibName, (ULONG) "appobjects.library"},
    {APSH_LibBase, (ULONG) & AppObjectsBase},
    {TAG_DONE,}
};

 /* ARexx user interface environment specification array */
static struct TagItem Handle_AREXX[] =
{
    {APSH_Extens, (ULONG) "skel"},
    {APSH_Status, APSHP_SINGLE},
    {APSH_Rating, APSH_OPTIONAL},
    {TAG_DONE,}
};

/* These tags describe the Simple IPC user interface. */
static struct TagItem Handle_SIPC[] =
{
    {APSH_Status, APSHP_SINGLE},
    {APSH_Rating, APSH_REQUIRED},
    {APSH_AlreadyRunning, IsActiveID},
    {TAG_DONE,}
};

/* These tags describe the Asynchronous Tool user interface */
struct TagItem Handle_Tool[] =
{
    {APSH_Rating, APSH_REQUIRED},
    {TAG_DONE,}
};

/* Application Environment:
 * Tell about the master server application */
struct TagItem Our_App[] =
{
 /* About the application */
    {APSH_AppName, (ULONG) APPNAME},
    {APSH_AppVersion, (ULONG) APPVERS},
    {APSH_AppCopyright, (ULONG) APPCOPY},
    {APSH_AppAuthor, (ULONG) APPAUTH},

 /* Trigger the library opening module */
    {APSH_OpenLibraries, (ULONG) Our_Libs},

 /* Specify the application function table */
    {APSH_FuncTable, (ULONG) MasterFuncTable},

 /* Specify the application text table */
    {APSH_DefText, (ULONG) MasterText},

 /* Tell how memory we need for our own data */
    {APSH_UserDataSize, sizeof (struct MasterData)},

 /* Give our Shell startup argument template */
    {APSH_Template, (ULONG) TEMPLATE},
    {APSH_NumOpts, (ULONG) OPT_COUNT},

 /* Must always specify the SIPC user interface */
    {APSH_AddSIPC_UI, (ULONG) Handle_SIPC},

 /* Add the Asynchronous tool user interface */
    {APSH_AddTool_UI, (ULONG) Handle_Tool},

 /* Add an ARexx user interface */
    {APSH_AddARexx_UI, (ULONG) Handle_AREXX},

 /* Specify a custom initialization routine */
    {APSH_AppInit, CInitID},
    {APSH_AppExit, CExitID},

    {TAG_DONE,}
};

/* handle messages between function handlers */
BOOL HandlerFunc (struct AppInfo * ai, ULONG tags,...)
{
    return (HandlerFuncA (ai, (struct TagItem *) & tags));
}

/* get handler data */
APTR HandlerData (struct AppInfo * ai, ULONG tags,...)
{
    return (HandlerDataA (ai, (struct TagItem *) & tags));
}

/* This is the initialization routine for the master server for the
 * application. */
VOID CInitFunc (struct Hook *h, struct AppInfo *ai, struct AppFunction *af)
{
    struct MasterData *md = (struct MasterData *) ai->ai_UserData;
    struct Project *p = &(ai->ai_Project);
    struct List *list = &(p->p_ProjList);
    struct ProjNode *pn = NULL;
    struct MsgHandler *mh;

    /* Check the options */
    DI (kprintf ("CInitFunc enter\n"));
    if (ai->ai_Options[OPT_BACKG])
    {
	md->md_Flags |= MADF_BACKG;
    }
    if (ai->ai_Options[OPT_UNLOAD])
    {
	md->md_Flags &= ~MADF_BACKG;
    }

    /* Get a handle on our SIPC port */
    if (mh = HandlerData (ai, APSH_Handler, "SIPC", TAG_DONE))
    {
	/* Cache the pointer */
	md->md_SIPC = mh->mh_Port;
    }

    /* Make sure there are entries in the project list */
    if (list->lh_TailPred != (struct Node *) list)
    {
	struct Node *node, *nxtnode;

	/* Let's start at the very beginning... */
	node = list->lh_Head;

	/* Continue while there are still nodes */
	while ((ai->ai_Pri_Ret == RETURN_OK) &&
	       ((nxtnode = node->ln_Succ)))
	{
	    /* Start the project */
	    start_project (ai, (struct ProjNode *) node);

	    /* Go on to the next node */
	    node = nxtnode;
	}
    }
    /*
     * No FILES/M parameters specified, see if they just want to load us in the
     * background.
     */
    else if (!(ai->ai_Options[OPT_BACKG]))
    {
	/* They want an new, blank project to work with. */
	if (pn = NewProject (ai, NULL, NULL))
	{
	    /* Start the project */
	    start_project (ai, pn);
	}
	/* Unable to allocate a new project */
	else
	{
	    ai->ai_Pri_Ret = RETURN_FAIL;
	    ai->ai_Sec_Ret = MPERR_NO_PROJECT;
	    ai->ai_TextRtn =
	      PrepText (ai, APSH_USER_ID, ai->ai_Sec_Ret, NULL);
	}
    }
    else
    {
    }
    DI (kprintf ("CInitFunc exit\n"));
}

VOID CExitFunc (struct Hook *h, struct AppInfo *ai, struct AppFunction *af)
{
    /* We don't really do anything in this example */
}

 /* Open existing project */
VOID OpenFunc (struct Hook *h, struct AppInfo *ai, struct AppFunction *af)
{
    struct Project *p = &(ai->ai_Project);
    struct List *list = &(p->p_ProjList);
    struct TagItem *attrs = af->af_Attrs;
    struct ProjNode *pn = NULL;
    LONG key;

    /* Lock the AppInfo structure */
    key = LockAppInfo (ai);

    /* Get a pointer to the project node */
    if (pn = (struct ProjNode *) GetTagData (APSH_ProjInfo, NULL, attrs))
    {
	/* Start the project */
	if (start_project (ai, pn))
	{
	    /* Add the project to our list, so that it gets freed properly */
	    AddTail (list, (struct Node *) pn);

	    /* Adjust our project count accordingly */
	    p->p_NumProjs++;
	}
    }

    /* Remove the lock */
    UnlockAppInfo (key);
}

 /* Open existing project */
VOID NewFunc (struct Hook *h, struct AppInfo *ai, struct AppFunction *af)
{
    struct ProjNode *pn;

    /* Create a new project */
    DI (kprintf ("NewFunc enter\n"));
    if (pn = NewProject (ai, NULL, NULL))
    {
	/* Start the project */
	start_project (ai, pn);
    }
    DI (kprintf ("NewFunc exit\n"));
}

 /* Close an existing project */
VOID CloseFunc (struct Hook *h, struct AppInfo *ai, struct AppFunction *af)
{
    struct MasterData *md = (struct MasterData *) ai->ai_UserData;
    struct Project *p = &(ai->ai_Project);
    struct TagItem *attrs = af->af_Attrs;
    struct ProjNode *pn;
    struct ProjData *pd;
    LONG key;

    /* Lock the master AppInfo */
    key = LockAppInfo (ai);

    /* Get a pointer to the project node */
    if (pn = (struct ProjNode *) GetTagData (APSH_ProjInfo, NULL, attrs))
    {
	/* Get the project data */
	if (pd = (struct ProjData *) pn->pn_UserData)
	{
	    /* Free the extra tags */
	    FreeTagItems (pd->pd_Clone);

	    /* Free the project data */
	    FreeVec (pd);

	    /* Clear the UserData field */
	    pn->pn_UserData = NULL;
	}

	/* Set the current project */
	p->p_CurProj = pn;

	/* Remove the project */
	RemoveProject (ai, NULL, NULL);
    }

    /* See if we should quit */
    if ((p->p_NumProjs <= 0L) && !(md->md_Flags & MADF_BACKG))
    {
	ai->ai_Done = TRUE;
    }

    /* Unlock the master AppInfo */
    UnlockAppInfo (key);
}

 /* One of our projects just went active */
VOID ActiveToolFunc (struct Hook *h, struct AppInfo *ai, struct AppFunction *af)
{
    struct TagItem *attrs = af->af_Attrs;
    struct ProjNode *pn;
    struct ProjData *pd;
    struct AppInfo *sai;
    struct MsgPort *smp;
    LONG key;

    /* Lock the AppInfo structure */
    key = LockAppInfo (ai);

    /* Get a pointer to the project node */
    if (pn = (struct ProjNode *) GetTagData (APSH_ProjInfo, NULL, attrs))
    {
	/* Cache the pointer to the Project data */
	pd = (struct ProjData *) pn->pn_UserData;

	/* Get the address of the projects AppInfo structure */
	if (sai = (struct AppInfo *) GetTagData (APSH_AppHandle, NULL, attrs))
	{
	    /* Remember the project's AppInfo structure */
	    pd->pd_AI = sai;
	}

	/* The address of the project's SIPC message port */
	if (smp = (struct MsgPort *) GetTagData (APSH_PortAddr, NULL, attrs))
	{
	    /* Remember the message port */
	    pd->pd_Port = smp->mp_Node.ln_Name;
	}
    }

    /* Remove the lock */
    UnlockAppInfo (key);
}

 /* We are already running */
VOID IsActiveFunc (struct Hook *h, struct AppInfo *ai, struct AppFunction *af)
{
    struct MasterData *md = (struct MasterData *) ai->ai_UserData;
    struct Project *p = &(ai->ai_Project);
    struct List *list = &(p->p_ProjList);
    struct TagItem *attrs = af->af_Attrs;
    struct SIPCMessage *msg;
    struct MsgPort *mp;
    BOOL going = TRUE;
    BOOL send = FALSE;
    STRPTR port_name;

    /* Get the name of our main port */
    DI (kprintf ("IsActiveFunc enter\n"));
    if (port_name = (STRPTR) GetTagData (APSH_NameTag, NULL, attrs))
    {
	DI (kprintf("PortName: %s\n", port_name));

	/* We need a reply port */
	if (mp = CreatePort (NULL, NULL))
	{
	    /* Allocate a message */
	    if (msg = (struct SIPCMessage *)
	     AllocVec (sizeof (struct SIPCMessage), MEMF_PUBLIC | MEMF_CLEAR))
	    {
		/* Fill out the Exec message */
		msg->sipc_Msg.mn_Node.ln_Type = NT_MESSAGE;
		msg->sipc_Msg.mn_Length = sizeof (struct SIPCMessage);
		msg->sipc_Msg.mn_ReplyPort = mp;

		/* Fill in the SIPC message portion */
		msg->sipc_Data = md->md_Tmp;
		msg->sipc_DType = APSH_SDT_Command;

		/* Initialize it */
		strcpy (md->md_Tmp, "SETATTRS ");

		/* See if they want us to unload */
		if (ai->ai_Options[OPT_UNLOAD])
		{
		    /* Send UNLOAD */
		    strcat (md->md_Tmp, "UNLOAD ");
		    send = TRUE;
		}

		/* See if they want us to become sticky */
		if (ai->ai_Options[OPT_BACKG])
		{
		    /* Send  */
		    strcat (md->md_Tmp, "BACKGROUND");
		    send = TRUE;
		}

		/* Trying to set an option? */
		if (send)
		{
		    /* Send the message */
		    if (SafePutToPort ((struct Message *) msg, port_name))
		    {
			/* wait for the reply */
			WaitPort (mp);

			/* Get the reply */
			GetMsg (mp);
		    }
		}
		/* Make sure there are entries in the list */
		else if (list->lh_TailPred != (struct Node *) list)
		{
		    struct Node *node, *nxtnode;
		    struct TagItem tg[2];

		    /* Prep the tag list */
		    tg[0].ti_Tag = APSH_ProjInfo;
		    tg[1].ti_Tag = TAG_DONE;

		    /* Calling the Open command */
		    msg->sipc_Type = OpenID;

		    /* Show that we're passing tags */
		    msg->sipc_DType = APSH_SDT_TagList;
		    msg->sipc_Data = tg;

		    /* Let's start at the very beginning... */
		    node = list->lh_Head;

		    /* Continue while there are still nodes */
		    while (going && (nxtnode = node->ln_Succ))
		    {
			/* Remove it from this list */
			Remove (node);

			/* Adjust our project count accordingly */
			p->p_NumProjs--;

			/* Point to it */
			tg[0].ti_Data = (ULONG) node;

			/* Send the message */
			if (SafePutToPort ((struct Message *) msg, port_name))
			{
			    /* Wait for the reply */
			    WaitPort (mp);

			    /* Pull the message */
			    GetMsg (mp);
			}
			else
			{
			    /* Don't continue if the port went away */
			    going = FALSE;

			    /* Set the error message */
			    ai->ai_Pri_Ret = RETURN_FAIL;
			    ai->ai_Sec_Ret = MPERR_NO_PORT;
			    ai->ai_TextRtn =
			      PrepText (ai, APSH_USER_ID, ai->ai_Sec_Ret, NULL);
			}

			/* Go on to the next node */
			node = nxtnode;
		    }
		}
		else
		{
		    /* Send NEW */
		    strcpy (md->md_Tmp, "NEW");

		    /* Send the message */
		    if (SafePutToPort ((struct Message *) msg, port_name))
		    {
			/* wait for the reply */
			WaitPort (mp);

			/* Get the reply */
			GetMsg (mp);
		    }
		}

		/* Free the message */
		FreeVec (msg);
	    }

	    /* Delete the reply port */
	    DeletePort (mp);
	}
    }

    DI (kprintf ("IsActiveFunc exit\n"));
}

VOID SetAttrsFunc (struct Hook *h, struct AppInfo *ai, struct AppFunction *af)
{
    struct MasterData *md = (struct MasterData *) ai->ai_UserData;
    struct Project *p = &(ai->ai_Project);
    struct Funcs *f;

    if (f = af->af_FE)
    {
	/* Set the background bit? */
	if (f->fe_Options[0])
	{
	    md->md_Flags |= MADF_BACKG;
	}

	/* Clear the background bit? */
	if (f->fe_Options[1])
	{
	    md->md_Flags &= ~MADF_BACKG;
	}
    }

    /* See if we should quit */
    if ((p->p_NumProjs <= 0L) && !(md->md_Flags & MADF_BACKG))
    {
	ai->ai_Done = TRUE;
    }
}

 /* Shutdown routine */
VOID QuitFunc (struct Hook *h, struct AppInfo *ai, struct AppFunction *af)
{
    struct MasterData *md = (struct MasterData *) ai->ai_UserData;
    struct Project *p = &(ai->ai_Project);
    extern VOID QuitForce ();
    BOOL unload = FALSE;
    BOOL force = FALSE;
    struct Funcs *f;

    /* See if we have a parsed command */
    if (f = af->af_FE)
    {
	/* Did they set the force switch? */
	force = (BOOL) f->fe_Options[0];

	/* Are they trying to unload us? */
	if (unload = (BOOL) f->fe_Options[1])
	{
	    md->md_Flags &= ~MADF_BACKG;
	}
    }

    if (force)
    {
	/* Tell all the projects to quit */
	signal_projects (ai, NULL, QuitForce);
    }
    else
    {
	/* Tell all the projects to quit */
	signal_projects (ai, SIGBREAKF_CTRL_C, NULL);
    }

    /* See if we should quit */
    if ((p->p_NumProjs <= 0L) && !(md->md_Flags & MADF_BACKG))
    {
	ai->ai_Done = TRUE;
    }
}

VOID QuitForce (struct AppInfo * ai, struct ProjData * pd)
{
    /* Tell our master to open a new application */
    HandlerFunc (ai,
		 APSH_Handler, "SIPC",
		 APSH_Command, AH_SENDCMD,
		 APSH_NameTag, pd->pd_Port,
		 APSH_CmdString, (ULONG) "quit force",
		 TAG_DONE);
}

 /* Signal all projects */
VOID signal_projects (struct AppInfo * ai, LONG sig, VOID (*func) (struct AppInfo *, struct ProjData *))
{
    struct Project *p = &(ai->ai_Project);
    struct List *list = &(p->p_ProjList);
    struct ProjNode *pn;
    struct ProjData *pd;
    LONG key;

    /* Lock the master AppInfo */
    key = LockAppInfo (ai);

    /* Make sure there are entries in the list */
    if (list->lh_TailPred != (struct Node *) list)
    {
	struct Node *node, *nxtnode;

	/* Let's start at the very beginning... */
	node = list->lh_Head;

	/* Continue while there are still nodes */
	while (nxtnode = node->ln_Succ)
	{
	    pn = (struct ProjNode *) node;
	    pd = (struct ProjData *) pn->pn_UserData;

	    if (sig)
	    {
		/* Send a break signal to the project process */
		APSHSignal (pd->pd_AI, sig);
	    }

	    if (func)
	    {
		(*(func)) (ai, pd);
	    }

	    /* Go on to the next node */
	    node = nxtnode;
	}
    }

    /* Unlock the master AppInfo */
    UnlockAppInfo (key);
}

BOOL start_project (struct AppInfo * ai, struct ProjNode * pn)
{
    struct MasterData *md = (struct MasterData *) ai->ai_UserData;
    struct Project *p = &(ai->ai_Project);
    extern struct TagItem Project_App[];
    struct ProjData *pd = NULL;
    struct TagItem tg[6];
    struct TagItem *clone;
    BOOL retval = FALSE;
    LONG key;

    /* Lock the AppInfo structure */
    key = LockAppInfo (ai);

    /* Set up our tags */
    tg[0].ti_Tag = APSH_AppHandle;
    tg[0].ti_Data = (ULONG) ai;
    tg[1].ti_Tag = APSH_ProjInfo;
    tg[2].ti_Tag = APSH_PortAddr;
    tg[2].ti_Data = (ULONG) md->md_SIPC;
    tg[3].ti_Tag = APSH_HookClass;
    tg[3].ti_Data = TRUE;
    tg[4].ti_Tag = TAG_MORE;
    tg[4].ti_Data = (ULONG) Project_App;
    tg[5].ti_Tag = TAG_DONE;

    /* Get a pointer to the project node */
    if (pn)
    {
	/* Set the current project */
	p->p_CurProj = pn;

	/* Pass the Project node */
	tg[1].ti_Data = (ULONG) pn;

	/* Clone the tag array */
	if (clone = CloneTagItems (tg))
	{
	    if (pd = (struct ProjData *) AllocVec (sizeof (struct ProjData), MEMF_CLEAR))
	    {
		/* Remember the clone, so that we can free it */
		pd->pd_Clone = clone;

		/* Attach the project data to the project node */
		pn->pn_UserData = pd;

		/* Make a nice unique process name */
		sprintf (md->md_Tmp, "%s.%ld", APPNAME, pn->pn_ID);

		/* Open a new project */
		if (HandlerFunc (ai,
				 APSH_Handler, "TOOL",
				 APSH_Command, APSH_MH_OPEN,
				 APSH_Tool, (ULONG) md->md_Tmp,
				 APSH_ToolData, (ULONG) clone,
				 TAG_DONE))
		{
		    /* Show success */
		    retval = TRUE;
		}
		else
		{
		    /* Unable to start the new process */
		    ai->ai_Pri_Ret = RETURN_FAIL;
		    ai->ai_Sec_Ret = MPERR_NO_PROCESS;
		    ai->ai_TextRtn =
		      PrepText (ai, APSH_USER_ID, ai->ai_Sec_Ret, NULL);
		}
	    }
	    else
	    {
		/* Free the cloned tags */
		FreeTagItems (clone);

		/* Unable to allocate the project data node */
		ai->ai_Pri_Ret = RETURN_FAIL;
		ai->ai_Sec_Ret = MPERR_NO_MEMORY;
		ai->ai_TextRtn =
		  PrepText (ai, APSH_USER_ID, ai->ai_Sec_Ret, NULL);
	    }
	}
	else
	{
	    /* Not enough memory to allocate tag list */
	    ai->ai_Pri_Ret = RETURN_FAIL;
	    ai->ai_Sec_Ret = MPERR_NO_MEMORY;
	    ai->ai_TextRtn =
	      PrepText (ai, APSH_USER_ID, ai->ai_Sec_Ret, NULL);
	}
    }

    /* Remove the lock */
    UnlockAppInfo (key);

    return (retval);
}

/* Send a message to the named message port */
BOOL SafePutToPort (struct Message * message, STRPTR name)
{
    struct MsgPort *port;

    Forbid ();

    if (port = FindPort (name))
    {
	PutMsg (port, message);
    }

    Permit ();

    return ((BOOL) port);
}
