
/*
 * Blackhole.c -- File deletion tool. When run, it puts an appicon on the WB
 * screen. Any icons that are dropped on the appicon are deleted.
 * $Revision: 1.32
 */

#include "header.h"
#include "prog-protos.h"

/* Prototypes */
Prototype BOOL is_mine (char *);
Prototype void save_prefs (void);
Prototype LONG get_limit (void);
Prototype void snapshot (LONG , LONG );
Prototype void read_prefs (void);
Prototype void allow_request (BOOL );
Prototype char *safecat (char *, char *, int );
Prototype LONG set_current (struct WBArg *, int );
Prototype LONG error_request (char *, char *);
Prototype BOOL user_request (LONG );
Prototype void fail_request (char *);
Prototype void get_tooltypes (struct DiskObject *);

Local void wbmain (struct WBStartup *);
Local void copy_and_add (struct List *, int , struct WBArg *);
Local void delete_next (struct List *);
Local LONG add_WBArg_to_List (struct WBArg *, struct List *);
Local BOOL confirm_deletion (struct List *);
Local void scrub (struct DiskObject *);
Local struct HoleArg *create_arg (void);
Local void free_arg (struct HoleArg *);
Local void free_list (struct List *);
Local void handle_messages (struct List *);
Local int main (int , char **);

/* Shared Variables */
Prototype struct IntuitionBase *IntuitionBase;

/* Globals */
static const struct HolePrefs defaultprefs =
{
    FALSE,	/* Ignore protection == FALSE */
    TRUE,	/* confirm == TRUE */
    15, 	/* DeleteLimit */
    NO_ICON_POSITION,
    NO_ICON_POSITION
};
static const char *memfail = "Out of memory.";

struct HolePrefs currprefs; /* current preferences, copied into HoleArgs for use */

struct IntuitionBase *IntuitionBase = NULL;
struct WorkbenchBase *WorkbenchBase = NULL;
struct IconBase      *IconBase	    = NULL;
struct CxBase	     *CxBase	    = NULL;
struct GadToolsBase  *GadToolsBase  = NULL;
struct WBArg	     *start_icon    = NULL; /* name of icon we started from */
struct MsgPort	     *appmsgport    = NULL; /* AppIcon Message port */
struct DiskObject    *myicon	    = NULL; /* The icon we have been started from */

APTR old_window_ptr = NULL;	/* For allow_request */
struct EasyStruct error_req =
{
    sizeof (struct EasyStruct),
    0,
    "Black Hole",
    NULL,
    NULL,
};

BYTE *version = "$VER: BlackHole v1.1 " __DATE__;
#define ABOUT_STRING "BlackHole v1.1\n" __DATE__ "\nby Alan Singfield"
#define NUMBER_OF_MY_TOOLTYPES 5

#define CURR_SIZE 255
char current [CURR_SIZE+1];	/* current file we are working on */

#ifdef DEBUG
#define D(x) x
#define bug printf
#else
#define D(x) ;
#endif

/* Functions */
void wbmain (struct WBStartup *wbs)
{
    struct AppIcon *appicon = NULL;	/* The AppIcon itself */
    struct List *list = NULL; /* list of files to delete */
    BPTR olddir = NULL;

    unless (IntuitionBase = (struct IntuitionBase *)
    OpenLibrary("intuition.library", 37))
    {
	fail_request ("Black Hole requires\nIntuition library v37\n or greater.");
	goto fail;
    }

    unless (WorkbenchBase = (struct WorkbenchBase *)
    OpenLibrary("workbench.library", 37))
    {
	fail_request ("Black Hole requires\nWorkbench library v37\n or greater.");
	goto fail;
    }

    unless (IconBase = (struct IconBase *) OpenLibrary ("icon.library", 37))
    {
	fail_request ("Black Hole requires\nIcon library v37\n or greater.");
	goto fail;
    }

    unless (CxBase = (struct CxBase *) OpenLibrary ("commodities.library", 37))
    {
	fail_request ("Black Hole requires\nCommodities library v37\n or greater.");
	goto fail;
    }

    unless (GadToolsBase = (struct GadToolsBase *)
    OpenLibrary ("gadtools.library", 37))
    {
	fail_request ("Black Hole requires\nGadTools library v37\n or greater.");
	goto fail;
    }

    start_icon = &(wbs->sm_ArgList[0]);    /* put name of icon into global */
    olddir = CurrentDir (start_icon->wa_Lock);

    unless (myicon = GetDiskObjectNew (start_icon->wa_Name))
    {
	fail_request ("Cannot open file\nBlackHole.info");
	goto fail;
    }
    get_tooltypes (myicon); /* get user preferences from tooltypes */
    scrub (myicon);   /* Remove all the extraneous data from the DiskObject */

    unless (appmsgport = CreateMsgPort())
    {
	fail_request ("MsgPort not opened!");
	goto fail;
    }

    unless (appicon = AddAppIcon (NULL, NULL, wbs->sm_ArgList[0].wa_Name,
    appmsgport, NULL, myicon, NULL))
    {
	fail_request ("Could not put AppIcon\non Workbench screen!");
	goto fail;
    }

    unless (list = malloc (sizeof (struct List)))
    {
	fail_request (memfail);
	goto fail;
    }
    NewList (list); /* Intialise the list */

    handle_messages (list); /* The main bit!        */

    fail:				/* This is the tidy up section	    */

    if (list)
    {
	free_list (list);
	free (list);
    }
    if (appicon)
    {
	struct Message *mess;

	Forbid();                       /* strip messages under Forbid()    */
	while (mess = GetMsg (appmsgport)) ReplyMsg (mess);
	RemoveAppIcon (appicon);
	Permit();
    }
    if (appmsgport)     DeleteMsgPort   (appmsgport);
    if (myicon)         FreeDiskObject  (myicon);
    if (olddir)         CurrentDir      (olddir);
    if (GadToolsBase)   CloseLibrary    (GadToolsBase);
    if (CxBase)         CloseLibrary    (CxBase);
    if (IconBase)       CloseLibrary    (IconBase);
    if (WorkbenchBase)  CloseLibrary    (WorkbenchBase);
    if (IntuitionBase)  CloseLibrary    (IntuitionBase);
}

/* In case anyone tries to run us from the CLI */
int main (int argc, char **argv)
{
    printf ("Run me from my icon, please!\n");
    return (10);
}

void fail_request (char *text)
{
    error_request (text, "OK");
}

LONG error_request (char *text, char *gadget)
{
    struct Screen *psc;
    struct Window *window = NULL;
    int rc;

    if (psc = LockPubScreen ("Workbench")) window = psc->FirstWindow;

    error_req.es_GadgetFormat = gadget;
    error_req.es_TextFormat = text;

    rc = EasyRequest (window, &error_req, 0, "");

    if (psc) UnlockPubScreen (NULL, psc);

    return (rc);
}

/* set_current -- update a global buffer current[] with the name of the file */
/* that is currently being worked on. (Used for error messages)              */

LONG set_current (struct WBArg *wa, int mode)
{
    char buf[CURR_SIZE+1];  /* temporary buffer for filenames */
    char *end;		    /* pointer to a character found by FilePart */

    switch (mode)
    {
	case SET_START:
	strcpy (current, "");   /* reset current */
	break;

	case SET_NEWFILE:
	unless (wa) return (ERROR_OBJECT_WRONG_TYPE);
	if ((wa->wa_Name[0] == 0) || !wa->wa_Name)  /* is it a dir? */
	{
	    if (!NameFromLock (wa->wa_Lock, buf, CURR_SIZE)) return (IoErr());
	    if (buf [strlen(buf)-1] == ':') /* is it a disk? */
	    {
		sprintf (current, "%s", buf);
		return (ERROR_OBJECT_WRONG_TYPE);
	    }
	    AddPart (current, FilePart (buf), CURR_SIZE);   /* put name of dir into current */
	    strcat (current, "/");
	}
	else	    /* must be a file */
	{
	    end = FilePart (current);
	    *end = NULL;	/* remove filename */
	    AddPart (current, wa->wa_Name, CURR_SIZE);
	}
	break;

	case SET_EXIT_DIR:	/* exit one directory level */
	end = PathPart (current);
	*end = NULL;
	break;
    }
    return (0);
}
void get_tooltypes (struct DiskObject *dob)
{
    char **tt = dob->do_ToolTypes;

    currprefs = defaultprefs;	 /* Aren't structure assigns excellent? */

    if (FindToolType (tt, "IGNOREPROTECTION")) currprefs.ignore_prot = TRUE;
    if (FindToolType (tt, "DONOTCONFIRM")) currprefs.confirm = FALSE;

    currprefs.CurrentX = ArgInt (tt, "ICONX", currprefs.CurrentX);
    currprefs.CurrentY = ArgInt (tt, "ICONY", currprefs.CurrentY);
    currprefs.deletelimit = ArgInt (tt, "DELETELIMIT", currprefs.deletelimit);
}

/* Return TRUE if tooltype is one that Black Hole reads (e.g. ICONY) */
BOOL is_mine (char *tooltype)
{
    if (!(stricmp (tooltype, "IGNOREPROTECTION"))) return (TRUE);
    if (!(stricmp (tooltype, "(IGNOREPROTECTION)"))) return (TRUE);
    if (!(stricmp (tooltype, "DONOTCONFIRM"))) return (TRUE);
    if (!(stricmp (tooltype, "(DONOTCONFIRM)"))) return (TRUE);
    if (!(strnicmp (tooltype, "ICONX", 5))) return (TRUE);
    if (!(strnicmp (tooltype, "(ICONX", 6))) return (TRUE);
    if (!(strnicmp (tooltype, "ICONY", 5))) return (TRUE);
    if (!(strnicmp (tooltype, "(ICONY", 6))) return (TRUE);
    if (!(strnicmp (tooltype, "DELETELIMIT", 11))) return (TRUE);
    if (!(strnicmp (tooltype, "(DELETELIMIT", 12))) return (TRUE);

    return (FALSE);
}

#define BUF_SIZE 80
void save_prefs (void)
{
    read_prefs();

    struct DiskObject *dob = NULL;
    BPTR olddir = NULL;
    char **newtt = NULL;    /* new tooltypes created by me. */
    int othercount = 0; /* Count of other tooltypes in icon (like DONOTWAIT, etc) */
    int i;  /* general count variable */
    BOOL success = FALSE;

    olddir = CurrentDir (start_icon->wa_Lock);
    unless (dob = GetDiskObjectNew (start_icon->wa_Name)) goto fail;

    /* Count the tooltypes, excluding those we are going to remove */
    for (i = 0; dob->do_ToolTypes[i]; i++)
    {
	unless (is_mine (dob->do_ToolTypes[i])) othercount++;
    }

    /* Reserve the memory for the new tooltypes */
    unless (newtt = calloc (sizeof (char *), NUMBER_OF_MY_TOOLTYPES + othercount+ 1)) goto fail;

    /* Copy old tooltypes to the new by duplicating, excluding any of mine */
    int newcount = 0;
    for (i=0; dob->do_ToolTypes[i]; i++)
    {
	unless (is_mine (dob->do_ToolTypes[i]))
	{
	    unless (newtt [newcount++] = strdup (dob->do_ToolTypes[i])) goto fail;
	}
    }

    /* Now write in my tooltypes. */
    char buf[BUF_SIZE];

    if (panel->prefs.CurrentX != NO_ICON_POSITION)
    {
	sprintf (buf, "ICONX=%d", panel->prefs.CurrentX);
    }
    else sprintf (buf, "(ICONX=0)");
    unless (newtt [newcount++] = strdup (buf)) goto fail;

    if (panel->prefs.CurrentY != NO_ICON_POSITION)
    {
	sprintf (buf, "ICONY=%d", panel->prefs.CurrentY);
    }
    else sprintf (buf, "(ICONY=0)");
    unless (newtt [newcount++] = strdup (buf)) goto fail;

    sprintf (buf, "DELETELIMIT=%d", panel->prefs.deletelimit);
    unless (newtt [newcount++] = strdup (buf)) goto fail;

    if (panel->prefs.confirm) sprintf (buf, "(DONOTCONFIRM)");
    else sprintf (buf, "DONOTCONFIRM");
    unless (newtt [newcount++] = strdup (buf)) goto fail;

    if (panel->prefs.ignore_prot) sprintf (buf, "IGNOREPROTECTION");
    else sprintf (buf, "(IGNOREPROTECTION)");
    unless (newtt [newcount++] = strdup (buf)) goto fail;

    /* Now put new tooltypes into icon, and save it. */
    dob->do_ToolTypes = newtt;

    unless (PutDiskObject (start_icon->wa_Name, dob)) goto fail;

    success = TRUE; /* We did it! */

    fail:   /* Now clean up */

    unless (success) fail_request ("Could not\nsave preferences");
    if (newtt)
    {
	int i = 0;
	while (newtt[i]) free (newtt[i++]);
	free (newtt);
    }
    if (dob) FreeDiskObject (dob);
    if (olddir) CurrentDir (olddir);

}

#undef BUF_SIZE

/* Read in the user preferences from the gadgets in the control panel */
/* assumes panel is non-NULL */
void read_prefs (void)
{
    panel->prefs.confirm = ((panel->gad_confirm->Flags & GFLG_SELECTED) != 0);
    panel->prefs.ignore_prot = ((panel->gad_ignore_prot->Flags & GFLG_SELECTED) != 0);
    panel->prefs.deletelimit = get_limit();
    /* If value of panel's deletelimit is invalid, use the old one. */
    if (panel->prefs.deletelimit < 1) panel->prefs.deletelimit = currprefs.deletelimit;
}

/* Get the delete limit */
LONG get_limit (void)
{
    struct StringInfo *si = (struct StringInfo *)panel->gad_limit->SpecialInfo;
    return (si->LongInt);
}

/* Snapshot the position of the appicon */
void snapshot (LONG xpos, LONG ypos)
{
    panel->prefs.CurrentX = xpos;
    panel->prefs.CurrentY = ypos;
    if (xpos == NO_ICON_POSITION) fail_request ("Icon position\nforgotten.");
    else fail_request ("Icon position\nrecorded.");
}

void handle_messages (struct List *list)
{
    BOOL quitnow = FALSE;
    LONG error;
    int argnum;

    ULONG winmask;
    ULONG appmask = 1<< appmsgport->mp_SigBit;

    repeat  /* until (quitnow) */
    {
	winmask = panel ? (1<< panel->window->UserPort->mp_SigBit) : 0;

	/* if there are no files to delete at the moment then Wait() */
	if (IsListEmpty (list)) Wait (winmask | appmask);
	else delete_next (list);

	/* Handle appmessages */
	{
	    struct AppMessage *appmsg;
	    while (appmsg = (struct AppMessage *)GetMsg (appmsgport))
	    {
		if (argnum = appmsg->am_NumArgs)
		{
		    copy_and_add (list, argnum, appmsg->am_ArgList);
		}
		ReplyMsg ((struct Message *)appmsg);

		if (argnum == 0)    /* did the user double click on our appicon? */
		{
		    /* Open control panel, or if already open, activate it */
		    unless (open_control_panel(&currprefs))
			fail_request (memfail);
		}
	    }
	}

	struct IntuiMessage *intuimsg;
	while (panel && (intuimsg = (struct IntuiMessage *)
	GT_GetIMsg (panel->window->UserPort)))
	{
	    /* It's always a gadget, and always an IDCMP_GADGETUP */
	    struct Gadget *gad = (struct Gadget *)intuimsg->IAddress;
	    GT_ReplyIMsg (intuimsg);

	    switch (gad->UserData)
	    {
		case GAD_SAVE:
		save_prefs();
				    /* No break! */
		case GAD_USE:
		read_prefs();
		currprefs = panel->prefs;
				    /* No break! */
		case GAD_CANCEL:
		close_control_panel();
		break;

		case GAD_QUIT:
		/* Assumes nothing else has caused quitnow to be true */
		quitnow = error_request ("OK to quit?", "Yes|No");
		break;

		case GAD_CONFIRM:
		case GAD_IGNORE:
		/* These need no action */
		break;

		case GAD_LIMIT:
		/* Check that limit value is valid */
		if (get_limit() < 1)
		{
		    DisplayBeep (NULL); /* flash the display */
		}
		break;

		case GAD_UNSNAP:
		snapshot (NO_ICON_POSITION, NO_ICON_POSITION);
		break;

		case GAD_SNAP:
		/* This doesn't work!!! I hope Commodore patch AddAppIcon so it */
		/* updates these fields in the near future. */
		snapshot (myicon->do_CurrentX, myicon->do_CurrentY);
		break;

		case GAD_ABOUT:
		fail_request (ABOUT_STRING);
		break;
	    }
	}
    } until (quitnow);

    close_control_panel();  /* Safe to call this even if panel is closed */
}

/* Delete one file from list, which must not be empty. Tries to minimise disk */
/* swapping by deleting those files that are available first.		      */
void delete_next (struct List *list)
{
    LONG error = 0;
    BOOL done = FALSE;
    struct HoleArg *arg;

    /* Try to delete anything that we can get without prompting the user */
    allow_request (FALSE);
    for (arg = list->lh_Head; arg->node.ln_Succ; arg = arg->node.ln_Succ)
    {
	if (!delete (arg))  /* Returns 0 for success */
	{
	    done = TRUE; break;
	}
    }
    allow_request (TRUE);

    /* If that didn't work, just try first on the list */
    arg = (struct HoleArg *)list->lh_Head;
    until (done)
    {
	if (error = delete ((struct HoleArg *) arg))
	{
	    if (user_request (error)) continue; /* user says Retry */
	    else
	    {				/* User says Cancel */
		done = TRUE; break;
	    }
	}
	else done = TRUE;
    }

    /* Now remove and free the arg */
    Remove ((struct Node *)arg);
    free_arg (arg);
}

/* Add filenames from WBArg to the list, copying prefs etc... */
/* Tries to minimise disk swapping */
void copy_and_add (struct List *list, int argnum, struct WBArg *arglist)
{
    if (argnum > currprefs.deletelimit)
    {
	fail_request ("Warning:\nYou are trying to\ndelete too much");
	return;
    }

    struct List *tlist; /* temporary list before we have confirmed */
    unless (tlist = malloc (sizeof (struct List))) goto fail;
    NewList (tlist);
    int lastarg = argnum;
    LONG error = 0;

    BOOL firsttimeround = TRUE;
    BOOL requesters;		    /* flag saying whether requesters are on */

    int i;
    for (i=0; lastarg; )
    {
	if (!i) requesters = TRUE;  /* If we have started the second round, */
				    /* allow requesters to be put up.	    */
	if (i || firsttimeround)
	{			    /* otherwise requesters are not allowed */
	    requesters = FALSE;
	    firsttimeround = FALSE;
	}
	allow_request (requesters);

	/* We always add the first arg on the list, because we swap around */
	unless (error = add_WBArg_to_List (&arglist[0], tlist))
	{
	    /* everything worked, so swap the used arg to the end */
	    {	/* and reduce lastarg so that we don't see it again */
		lastarg--;
		struct WBArg targ = arglist [lastarg];
		arglist [lastarg] = arglist[0];
		arglist [0] = targ;
	    }
	}
	else
	{
	    if (!requesters)
	    {	/* Some error has occurred but we haven't told the user about it */
		/* Put off sorting it out till later by moving arg to the end. */
		struct WBArg targ = arglist [lastarg-1];
		arglist [lastarg-1] = arglist[0];
		arglist [0] = targ;
	    }
	    else /* An error occurred so put up a requester for it */
	    {
		/* If user presses retry then try again */
		if (user_request(error)) continue;
		else goto fail;
	    }
	}

	i++;
	if ((i == argnum) && lastarg)
	{   /* We've tried all the args, but some have failed */
	    i = 0; /* back to the start again, this time requesters will be ok */
	}
    }

    unless (confirm_deletion (tlist)) goto fail;

    until (IsListEmpty (tlist))
    {
	/* Move contents of tlist to list */
	AddTail (list, RemHead (tlist));
    }

    fail:

    allow_request (TRUE);
    free_list (tlist);      /* Only does anything if something failed */
    if (tlist) free (tlist);   /* free the memory */
}

LONG add_WBArg_to_List (struct WBArg *arg, struct List *list)
{
    struct HoleArg *holearg;
    LONG error = 0;

    unless (holearg = create_arg()) return (ERROR_NO_FREE_STORE);
    unless (holearg->wbarg.wa_Name = strdup (arg->wa_Name)) goto fail;

    unless (holearg->wbarg.wa_Lock = DupLock (arg->wa_Lock))
    {
	error = IoErr();
	goto fail;
    }

    if (currprefs.confirm)  /* put name of file into truncname */
    {
	set_current (NULL, SET_START);
	if (error = set_current (&(holearg->wbarg), SET_NEWFILE)) goto fail;
	unless (holearg->truncname = strdup (current))
	{
	    error = ERROR_NO_FREE_STORE;
	    goto fail;
	}
    }

    holearg->prefs = currprefs;  /* put current prefs into arg */

    AddTail (list, (struct Node *)holearg);
    return (0);

    fail:

    free_arg (holearg);
    return (error);
}

void free_list (struct List *list)
{
    unless (list) return;
    until (IsListEmpty (list)) free_arg ((struct HoleArg *)RemTail (list));
}

void free_arg (struct HoleArg *arg)
{
    unless (arg) return;

    if (arg->truncname) free (arg->truncname);
    if (arg->wbarg.wa_Name) free (arg->wbarg.wa_Name);
    UnLock (arg->wbarg.wa_Lock);
    free (arg);
}

/* reserve memory for a HoleArg, returning a pointer to it or NULL if failed */
struct HoleArg *create_arg (void)
{
    struct HoleArg *arg;
    unless (arg = calloc (1, sizeof (struct HoleArg))) return (NULL);
    return (arg);
}

void scrub (struct DiskObject *dob) /* RKM recommends you do this */
{
    dob->do_Magic = NULL;
    dob->do_Version = NULL;
    dob->do_Gadget.NextGadget = NULL;
    dob->do_Gadget.LeftEdge = NULL;
    dob->do_Gadget.TopEdge = NULL;
    dob->do_Gadget.Activation = NULL;
    dob->do_Gadget.GadgetType = NULL;
    dob->do_Gadget.GadgetText = NULL;
    dob->do_Gadget.MutualExclude = NULL;
    dob->do_Gadget.SpecialInfo = NULL;
    dob->do_Gadget.GadgetID = NULL;
    dob->do_Gadget.UserData = NULL;
    dob->do_Type = NULL;
    dob->do_DefaultTool = NULL;
    dob->do_CurrentX = currprefs.CurrentX;  /* set up position of icon */
    dob->do_CurrentY = currprefs.CurrentY;
    dob->do_ToolTypes = NULL;
    dob->do_DrawerData = NULL;
    dob->do_ToolWindow = NULL;
    dob->do_StackSize = NULL;
}			       /* Yaaaaaaawn! */

/* safe version of strncat that will not write over edge of dest buffer */
char *safecat (char *dest, char *source, int destlen)
{
    int num = destlen - strlen(dest);
    if (num <= 0) return (0);

    return (strncat (dest, source, num));
}

/* Get rid of Please Insert Volume ... requester */
void allow_request (BOOL allow)
{
    struct Process *proc;
    proc = (struct Process *)FindTask (NULL);

    if (!allow && !old_window_ptr)
    {
	old_window_ptr = proc->pr_WindowPtr;
	proc->pr_WindowPtr = (APTR)-1;
    }
    if (allow && (proc->pr_WindowPtr == (APTR)-1))
    {
	proc->pr_WindowPtr = old_window_ptr;
	old_window_ptr = NULL;
    }
}

#define TEXT_LEN 768

/* returns TRUE if user says it's ok to delete */
/* Relies on arg->truncname being filled in previously */
BOOL confirm_deletion (struct List *tlist)
{
    unless (currprefs.confirm) return (TRUE);

    char text[TEXT_LEN+1];
    char buf[40];
    char *end;
    struct HoleArg *arg;
    BOOL isdir = FALSE;
    int dirs = 0, files = 0;

    text[0] = (char)0;
    safecat (text, "Delete the following?\n", TEXT_LEN);

    for (arg = tlist->lh_Head; arg->node.ln_Succ; )
    {
	end = arg->truncname + strlen (arg->truncname) -1;
	if (*end == '/')
	{
	    dirs++;
	    isdir = TRUE;
	    *end = 0;	    /* remove trailing slash */
	}
	else
	{
	    files++;
	    isdir = FALSE;
	}

	safecat (text, arg->truncname, TEXT_LEN);
	if (isdir)
	{
	    safecat (text, " (drawer)", TEXT_LEN);
	    *end = '/';     /* restore what we altered */
	}
	safecat (text, "\n", TEXT_LEN);

	/* this must go here since there is a continue statement */
	arg=arg->node.ln_Succ;	/* goto next arg */
    }

    if (files)
    {
	if (dirs)
	{
	    sprintf (buf, "(%d file%s and %d drawer%s)",
		files,	(files == 1) ? "" : "s",
		dirs,	(dirs == 1) ? "" : "s");
	}
	else sprintf (buf, "(%d file%s)", files, (files == 1) ? "" : "s");
    }
    else sprintf (buf, "(%d drawer%s)", dirs, (dirs == 1) ? "" : "s");
    safecat (text, buf, TEXT_LEN);

    return (error_request (text, "Ok|Cancel"));
}

#define BUF_LEN 255

BOOL user_request (LONG error)
{
    char buf[BUF_LEN+1], fault[FAULT_MAX+1];
    char *end;

    strcpy (buf, "Error while deleting\n");
    safecat (buf, current, BUF_LEN);    /* put in name of file being deleted */

    end = buf + strlen(buf) -1;
    if (*end == '/') *end = 0;  /* if buf has trailing slash remove it */

    if (strcmp (buf, "")) safecat (buf, " :\n", BUF_LEN);  /* punctuation */

    Fault (error, "", fault, FAULT_MAX);   /* get error message from dos. */
    fault[2] = toupper ((unsigned char)fault[2]);   /* capitalise 1st letter */

    safecat (buf, fault + 2, BUF_LEN);  /* copy removing ": " at head of message */
    safecat (buf, ".", BUF_LEN);        /* finish the sentence */

    return (error_request (buf, "Retry|Cancel"));
}

