/* * 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")); }