Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

Wizardcopy for Fast Backups


SP 89: WIZARDCOPY FOR FAST BACKUPS

WIZARDCOPY FOR FAST BACKUPS

Fast disk backups save time and money

Don Gaspar

Don is a physicist and a senior software engineer for DIALOG Information Services, Inc. in Palo Alto. He can be reached at 3460 Hillview Ave., Palo Alto, CA 94304.


It's 8 a.m., and you need ten copies of a program you've been writing to hand out to beta testers. The Finder is just too slow when copying and formatting entire disks: A Kobayashi Maru scenario won't work here. Besides, you need the disk now, not next year. So you try your disk copier, only to find it doesn't allow you to make multiple copies. Oh no!

You decide that you'll make single copies by reading the disk into memory, writing the copy to another disk, and then repeating both steps again, and again. You know this process will be extremely time consuming, but the only alternative is using the Finder, which will require more disk swaps and mouse clicks than you can count. You launch your copier and find that it doesn't support the new Macintosh 1.44-Mbyte disks. Nuts!

So you start the single-copy process, knowing full-well it's going to require ten steps of swapping and clicking and almost as many minutes for each of ten disks. Holy Bazookas! What are you going to do?

How about writing your own disk copier, over which you'll have complete control? Too hard? No way.

This article explains how to write a sector-copying disk utility for your Macintosh. A sector copier is a program that reads entire sectors on a disk into a designated area in RAM, and then later writes them from RAM to a destination drive. The data of all your programs, files, and utilities is stored on your media in this way. The sector copier will read a disk into memory, sector by sector (which is 512 bytes at a time), and then write this data to the destination disk. Reading a sector at a time has the advantage of speed since you're reading/writing sequences of 512 bytes from each read/write process. The C source code for WizardCopy, a disk copier I wrote, is shown in Listing One, page 80. (The Rez file that implements the WizardCopy application is available on CompuServe, the DDJ on-line service, and on disk.) Programs like WizardCopy are timesavers when it comes to multiple copies; I just made 15 copies of a beta version of a program I'm writing to send to beta testers, and the whole copy process took only a few minutes! I would have aged a year if I'd had to make the copies by other means.

The programming techniques presented in this article illustrate Macintosh fundamentals for using the Device Manager and the Disk Driver. You'll quickly understand these important fundamentals by looking through the source code in Listing One. No more clicking the mouse again and again just to format a disk. No more dragging disks' icons to other icons and waiting. How about auto-formatting with minimal input? How about copying the disk in 17 to 45 seconds (depending on whether the disk is 400K, 800K, or 1.44-Mbyte)? These speed advantages are another reason to make a sector copier; the more copies you have to make, the better the performance gain will be.

The WizardCopy Program

WizardCopy is a sector-copying utility that will copy 400K, 800K, and 1.44-Mbyte disks for your Macintosh. It requires minimal input, is extremely fast, and has only four controls (see the main window in Figure 1).

The check boxes let you tell WizardCopy to warn you before copying over an existing disk, and whether or not to format every disk that you insert. These items are simple to implement and are extremely useful. The check boxes are represented in WizardCopy as Boolean flags that are either on or off; simple semantic logic dictates how the program behaves after checking these flags. This alert was written as a separate dialog box because it requires a response of Yes or No, and the other dialog box offers only an acknowledgement.

For example, if you click 'Format All,' all the destination disks are automatically formatted. If 'Warn Before' is active and you insert a destination disk that already has data on it, WizardCopy will warn you, and ask if you wish to proceed.

The 'New Master' and 'Quit' buttons are the other controls, and are also available as commands in the File Menu. The 'New Master' button (or command) tells WizardCopy that you would like to copy a new disk. WizardCopy will, of course, free as much space as possible in the heap, and prompt you to insert the disk. When you want to copy another disk, select this option again. The Quit button (or command) does what its name implies: It frees all memory that was used by WizardCopy and disposes of all windows, menus, and so on. It then returns you to the Finder. The WizardCopy window also contains four status items: A progress meter at the bottom of the window, and boxes labeled Status, Copies Made, and In Memory. The progress meter (perhaps more decorative than functional, some might argue) tells you how far along you are when reading and writing sectors. The light gray rectangle (see Figure 1) fills to solid black when the copying is done. This meter is simple; it uses a fundamental algebraic relation between the number of sectors on the disk and the size of the rectangle.

The progress meter is advanced each time through a read loop or a write loop (which read and write ten 512-byte sectors, respectively). The right side of the rectangle is equal to the left side plus the quantity of the sector just read (or written) times the length of the rectangle divided by the number of sectors.

The meter is implemented with Quick-Draw. To draw the meter, WizardCopy gets the dialog user item (ID #11, in this case), fills it with a light gray pattern, and then frames it. Another rectangle is created, equal to the first one, but its right side is calculated by the equation just described -- it's filled with black. Every time a sector (or ten sectors, in this case) is read, the counter is incremented and the rectangle is drawn -- an accurate copy progress meter.

It may be easier for some of you to write this meter as a control definition procedure, which will have many advantages over the technique outlined here. No matter which technique you use, the progress meter will have the same look and will perform virtually the same way.

The 'Status' item tells the user what's going on. It displays the messages "Writing, Reading, Formatting, Please insert a master disk," and so on. It doesn't serve as an alert, but more as a message center for the basic tasks our program executes. The strings for these operations are stored as a STR# resource, and each is individually indexed.

The 'Copies Made' item tells the user exactly how many copies of a disk they have made. This feature is useful when you need several copies and aren't counting each one. A global integer, nCopies, is incremented each time a successful copy is made; it's cleared when 'New Master' is selected, as the counter must then be reset.

The 'In Memory' item displays the name of the disk being copied. If there is no disk in RAM, WizardCopy displays the string 'Nothing' in this box. This item is extremely helpful when you are making copies of multiple disks and you're not sure what you're copying: just look at the In Memory item, and you'll see the disk in action.

The Wizard icon in Figure 1 has been added to the program for aesthetic purposes only, and can be removed from the resource fork if you don't like it. I think it makes the program look great. (It was drawn by artist extraordinaire Danny Green.) The alert boxes are as simple as possible.

The text for the alerts and the main window is stored as an STR# resource, and accessed via the procedure Get-IndString. Remember that we're in the Macintosh world, and all the strings we're working with are Pascal strings. To use this alert for other problems that may occur, I simply get the appropriate string, and then do a GetDItem followed by a SetIText to change the text to the desired result. This dual operation is in a routine called SetDText, which is short for Set Dialog Text. Having the strings in this format also simplifies translating WizardCopy to foreign languages -- just access WizardCopy's resource fork!

Event Loop

The main window is actually a modeless dialog box, which simplifies our coding slightly. The event loop is structured as normal, and a function called IsDialogEvent has been added. If Get-NextEvent returns TRUE, we then test IsDialogEvent. IsDialogEvent will tell us if an event has occurred in our dialog box; we can then take appropriate action. If IsDialogEvent is TRUE, we then call DialogSelect.

DialogSelecttakes the appropriate action and tells us which item has been selected. If DialogSelect is TRUE, we then call DoDialog. The events for the dialog box are intercepted before going to DoDialog, which then processes the required response -- a simple filtering operation. Two events are intercepted: updateEvt and diskEvt.

The updateEvt is intercepted only because the dialog box has some nice boxes around some of the status items. Intercepting the event here, we call our own update procedure, which both updates the dialog box and draws our text.

For the copy process, the disk events are also intercepted. The two possible levels are reading or writing disks, and are identified by two flags. A function called DoDisk executes the appropriate action based on the level. For example, if DoDisk is in masterLevel, it will know it is to copy a master disk; after copying the disk, it moves into destLevel (destination level), where it writes the copied disk to a destination volume. If New Master was not selected, WizardCopy will then eject all inserted disks.

The event loop for other events, like menus or desk accessories, is then processed as usual Listing One).

Formatting Disks

Formatting disks is a simple operation. Make a control call with csCode equal to 6, and send the reference number of the disk driver (-5). We also tell the driver what type of disk to format. For a single-sided or a double-sided disk, we make csParam equal to either 1 or 2; for the 1.44-Mbyte disks, I had no idea what to do, so I ran The Debugger (by Jasik Designs) while in the Finder, and set a trap intercept at Control and Status. The calls for formatting 1.44-Mbyte floppies are identical to those for formatting 400K disks. The Macintosh apparently realizes we have an HD floppy, and formats accordingly.

When formatting a 400K or 800K disk, I always format it as 80OK; I have found no physical difference between the two types of disk except for the price. Save some money and buy 400K disks, and use them as 800K disks -- you should have no problems. You can also save by purchasing bulk SS disks and formatting them as DS disks. The reason manufacturers differentiate between the two is that it's cheaper to produce one kind of disk and label the disks either SS or DS, rather than producing two different kinds of disks in the same factory. Try formatting disks this way for yourself, and see if you ever have an abnormally high number of failures.

The formatting routine is called DoFormat, and it works in conjunction with a routine called CheckDisk. CheckDisk merely checks the format of the disk being copied, so that we can allocate RAM and do other tasks.

Reading and Writing Disks

The calls for reading and writing disks are similar to the formatting calls, except that we use some low-level read and write calls in the File Manager. Essentially, we set up an ioParamBlock: We specify where to start the read/write process, how many bytes to read/write, a buffer pointer for the data, and the disk driver reference number (-5, again). When we receive a disk event, DoDisk checks the appropriate level (masterLevel or destLevel) and executes a read or write call.

WizardCopy executes a loop and reads/writes ten 512-byte sectors at a time. The progress meter can thus be implemented accurately. We could have made one read/write call and done the entire process in one swing, but it wouldn't have been as exciting or as user friendly.

If you would like to modify the progress meter for greater accuracy, you can have WizardCopy read a sector at a time (this will cause it to run a bit more slowly). The routines ReadFloppy and WriteFloppy accomplish this task; see Listing One.

Possible Enhancements

WizardCopy could use more error trapping. Error detection is provided in every instance where it would be important, but it has not been implemented and intercepted in each case, because of some laziness on my part. You can easily see where to add your own dialog boxes. Or you can simply add strings in the STR# resource and call the biker dialog box with strings added in it.

You could make WizardCopy check to see if the latest version of the disk driver is available on the machine you're using. It would also be nice to see if you're in System 6.0.3 or a later version. You can put this routine in the procedure CheckThings.

WizardCopy reads and writes an entire disk in RAM in one pass; and will tell users of smaller machines that more RAM is needed. You can easily make WizardCopy a multiple-pass copier by putting some of the routines in a loop, and repeating the loop n times until n passes are accomplished. The next version of WizardCopy will include this feature.

References

Macintosh Developer Tech. Note #70 Venus Flytrap preliminary notes (available from DTS) Inside Macintosh, Volumes I-III.

Acknowledgments

The products used to make this program were The Debugger, by Jasik Designs, and THINK C. Thanks to my associate with Autosoft, artist extraordinaire, Danny Green. The author is also heavily indebted to Macintosh Developer Technical Support at Apple Computer, Inc. Not only are they technical wizards that help, but they've also put up with stupid questions I've asked time and time again. In particular, the author is indebted with soul to wizard Dennis Hescox.

Modifying WizardCopy for Hard Disks

Kenneth Turner

While making multiple copies of a floppy diskette using the approach Don presents here is certainly a handy utility, it does require that your Mac has at least 2 Mbytes of RAM or that you modify the WizardCopy program for multiple-passes (for single floppy drive systems). Besides being more complex, such a modification requires more disk swapping (an undesirable side effect). One alternative is to change the WizardCopy code so that you use a hard drive instead of using valuable RAM to store the data from the master disk. Modifying the existing programming scheme to do this involves little more than using the Macintosh's File Manager in place of many Memory Manager routines.

Actual code modifications are simple and straightforward. Allocate space on the hard drive volume instead of in memory. When the master floppy is read, transfer the data to the newly created hard drive file instead of to memory. Finally, when the duplicate diskettes are to be written, read the data from the hard drive instead of from RAM.

The first change you can make to WizardCopy is to the MakeRAM routine. The new code should create a work file on the hard drive with the File Manager function Create. This replaces the call to the Memory Manager function NewPtr. Remove the calls to DisposPtr, FreeMem, and CompactMem. These functions managed the allocation of memory and reduced heap fragmentation. Instead, open the file with FSOpen and use the function Allocate to ensure there is enough free space on the hard drive volume. (Though hard drive fragmentation is also an important issue, it is not crucial in this application and will be ignored.)

In the existing ReadFloppy and WriteFloppy routines, a FORloop controls the transfer of the master data to and from a 5K-byte buffer. A hard drive version will still maintain this smaller, temporary data area. Another FORloop, however, controls the transfer of data between the large, master buffer and this intermediate buffer. Replace this loop with a single call to either FSRead or FSWrite, depending on the situation. These, obviously, interface with the master data file instead of the old master RAM buffer.

The Memory Manager requires careful maintenance of pointers and handles. Though there is not a direct correspondence, the File Manager demands its own bookkeeping. The disk copier needs to keep track of the access path number for the work file it creates and the volume reference number of the hard drive volume. These enable the program to precisely specify to the operating system the file containing the master floppy data.

The volume reference number can be obtained with the GetVolfunction. The path reference number is returned when the file is opened.

Also, WizardCopy must always know the position of the file mark. This is the logical location of the last read or write. Whenever a new master diskette is to be read or a new duplicate diskette is to be written, the mark must be set to the beginning of the file with SetFPos. Finally, when the program is finished, the file should be closed with FSClose and deleted from the volume with FSDelete.

To put the finishing touches on the hard drive version, small changes should be made to the user interface. Replace strings such as "In Memory" with "On Disk" and change warnings such as "You need memory" to "The hard drive is full."

With this modification, the door is now open to other attractive features. For instance, the program could keep track of several master diskettes at once, instead of having to reread a floppy that was duplicated previously. A library of virtual master diskettes could be maintained on the hard drive and recalled as needed. Or, the program might be modified to work in the background under MultiFinderwithout requiring the heap space used by other concurrent applications. This simple hard drive option is only the first step toward a comprehensive disk copier.

Ken is a design engineer for Rodime Systems, Boca Raton, Fla., makers of hard disk drives for the Mac. He can be contacted through DDJ's office.

--K.T.

_WIZARDCOPY FOR FAST BACKUPS_ by Don Gaspar

[LISTING ONE]

<a name="0294_000e">

/*
 *   WizardCopy v1.0.0 by Don Gaspar
 *      A disk copier for the Apple Macintosh
 */

 #include <ControlMgr.h>
 #include <DeviceMgr.h>
 #include <DialogMgr.h>
 #include <DiskDvr.h>
 #include <EventMgr.h>
 #include <FileMgr.h>
 #include <FontMgr.h>
 #include <ListMgr.h>
 #include <MacTypes.h>
 #include <MemoryMgr.h>
 #include <MenuMgr.h>
 #include <OSUtil.h>
 #include <pascal.h>
 #include <Quickdraw.h>
 #include <ToolboxUtil.h>
 #include <WindowMgr.h>
 #include <math.h>

 #define beginMenu      880
 #define endMenu      882
 #define mainDialog      880
 #define ourStrings      880
 #define aboutDialog           881
 #define badDisk      882
 #define dataDialog      883
 #define useVol       8000
 #define nil         0L
 #define false         0
 #define true         1
 #define watchCursor           4

 #define newMaster      1
 #define aboutMe      1
 #define quit         2
 #define mQuit         3
 #define inMemory      3
 #define copiesMade      4
 #define status       5
 #define nothing      6
 #define none         7
 #define waiting      8
 #define pictItem      9
 #define warnBefore      10
 #define statMeter      11
 #define progress      12
 #define alwaysFormat           13

 #define SSDD         1
 #define DSDD         2
 #define MFM         3

 /* globals */
 Handle       myMenus[3];       /* array of our menus */
 Boolean      done = false, format = false, warn = true,
            masterLevel = false,destLevel = false;
 DialogPtr      wizDialog;
 Ptr         data = nil;     /* here's the disk data */
 EventRecord   theEvent;                 /* main event record */
 Point         dummyPt;
 Str255       defaultName,volumeName;
 short         temp,diskKind,nCopies = 0;
 long         sectors;         /* #of sectors of copying disk */
 int         errno;           /* because I don't want stdio */

 /* sets the text of our main dialog to the specified string */
void SetDText(item, text)
   short   item;
   Str255   *text;
{
   short   type;
   Handle   h;
   Rect   r;
   GetDItem(wizDialog,item,&type,&h,&r);
   SetIText(h,text);
}/* SetDText */

/* changes the status text in the main dialog box */
void SetAllDText(item1,item2,item3)
   short   item1,item2,item3;
{
   Str255   myStr;/* a pascal string */
   if (item1 != useVol) {
      GetIndString(&myStr,ourStrings,item1);
      SetDText(nothing,&myStr);
   }
   else
      SetDText(nothing,&volumeName);
   NumToString((long)nCopies,&myStr);
   SetDText(none,&myStr);
   GetIndString(&myStr,ourStrings,item3);
   SetDText(waiting,&myStr);
}/* SetAllDText */

 /* simple routine to center the window; will make
   it visible if showIt is true */
 void CenterWindow(theWindow,showIt)
   WindowPtr   theWindow;
   Boolean    showIt;
{
   Point   centerScreen,centerWind;
   Rect   toRect;
   centerScreen.h = screenBits.bounds.right/2;
   centerScreen.v = screenBits.bounds.bottom/2;
   centerWind.h = (theWindow->portRect.right -
      theWindow->portRect.left)/2;
   centerWind.v = (theWindow->portRect.bottom -
      theWindow->portRect.top)/2;
   MoveWindow(theWindow,centerScreen.h - centerWind.h,
      centerScreen.v - centerWind.v,false);
   if (showIt)
      ShowWindow(theWindow);
}/* CenterWindow */

/* use this for updating the wizard dialog when the
   conditions are that an update event is not posted */
void DrawWizDialog()
{
   GrafPtr oldPort;
   SetAllDText(2,3,1);
   GetPort(&oldPort);
   SetPort(wizDialog);
   InvalRect(&(wizDialog->portRect));
   SetPort(oldPort);
}/* DrawWizDialog */

/* ejects the disk and unmounts the volume */
OSErr MyEject(drive)
   short   drive;
{
   OSErr   err;
   short   vRef;
   Str255   name;
   long   dummy;
   err = GetVInfo(drive,&name,&vRef,&dummy);
   err = UnmountVol(&name,vRef);
   err = DiskEject(drive);
   return(err);
}/* myEject */

/* tells you that you inserted a bad disk */
void BadDisk(index)
   short   index; /* this is the string index # */
{
   DialogPtr   badBox;
   short      item,type;
   GrafPtr    oldPort;
   Handle      h;
   Rect      r;
   Str255      myStr;
   GetPort(&oldPort);         /* get the current port */
   badBox = GetNewDialog(badDisk,nil,((WindowPtr)-1));
   GetDItem(badBox,3,&type,&h,&r);
   GetIndString(&myStr,ourStrings,index);
   SetIText(h,&myStr);
   CenterWindow(badBox,true);
   SysBeep(3);
   SetPort(badBox);          /* set current port */
   do
      ModalDialog(nil,&item);
   while (item != 2);

   DisposDialog(badBox);    /* trash it since we're done with it */
   SetPort(oldPort);        /* set it back */
   DrawWizDialog();
}/* BadDisk */

/* allocate sufficient space for copy */
OSErr MakeRAM(format)
   short   format;
{
   long   free,sizo;
   OSErr   err;
   if (data != nil)
      DisposPtr(data);
   sizo = (format == MFM) ? 1474560:819200;
   sectors = sizo/512;      /* globally keep track of this */

   if((long)FreeMem() < sizo)
         free = (long)CompactMem((long)1474560);/* ask for large block */

   data = NewPtr(sizo);
   err = MemError();
   if (err != noErr) {
      BadDisk(20);
      (void)EjectAllDisks();
      masterLevel = true;
      destLevel = false;
      DisposPtr(data);
   }/* if */
   return(err);
}/* MakeRAM */

/* disk has data on it. Proceed? */
Boolean DataAlert()
{
   DialogPtr   box;
   short      item;
   GrafPtr    oldPort;

   GetPort(&oldPort);        /* get the current port */
   box = GetNewDialog(dataDialog,nil,((WindowPtr)-1));
   CenterWindow(box,true);
   SetPort(box);            /* set current port */
   do
      ModalDialog(nil,&item);
   while (item!=1 && item !=2);
   DisposDialog(box);      /* trash it since we're done with it */
   SetPort(oldPort);       /* set it back */
   return((item == 1) ? true : false);
}/* DataAlert */

/* formats the disk in the desired format */
OSErr CheckDisk(drive,format)
   short   drive,*format;
{
   OSErr      err;
   cntrlParam   db;
   DrvSts      sts;
   db.ioVRefNum = drive;
   db.ioCompletion = nil;
   db.csCode = 10;         /* we want to inspect the disk and the drive */
   db.ioRefNum = -5;       /* the disk driver */
   err = PBStatus(&db,false);
   *format = (db.csParam[0] == -1 && db.csParam[1] == -1)
      ? MFM : nil;
   if (*format != MFM) {   /*call was invalid, get disk format */
      err = DriveStatus(drive,&sts);
      *format = (sts.twoSideFmt == -1) ? DSDD : SSDD;
   }/* if ... */
   return(err);
}/*DoFormat */

/* formats the disk in the desired format */
OSErr DoFormat(drive,format)
   short   drive,format;
{
   OSErr      err;
   cntrlParam   db;
   int         *dummy;
   db.ioVRefNum = drive;
   db.ioCompletion = nil;
   db.csCode = 6; /* we want to format the disk */
   db.ioRefNum = -5;
   dummy = &db.csParam;
   *dummy = (format == MFM) ? 1:format;/* gotta format it right */
   err = PBControl(&db,false);
   return(err);
}/*DoFormat */

/* Get the default disk back */
void GetDefaultVol()
{
   done = true;
   DisposDialog(wizDialog);
   if (data != nil)
      DisposPtr(data);
}/* GetDefaultVol */

/* toggle between off and on depending what iut is */
void ToggleItem(aWindPtr,item,new)
   WindowPtr   aWindPtr;
   short      item;
   Boolean    new;
{
   ControlHandle   h;
   Rect         aRect;
   short         type,val;
   GetDItem(aWindPtr,item,&type,&h,&aRect);
   SetCtlValue((ControlHandle)h,(GetCtlValue(
      (ControlHandle)h) ? false : true));   /* on-off or off-on */
   if (new)
      SetCtlValue((ControlHandle)h,true);
}

/* disable appropriate menu items */
void SetMenus()
{
   short   index;
   DisableItem(myMenus[0],2);
   DisableItem(myMenus[1],2);
   for (index=0;index<8;index++)
      DisableItem(myMenus[2],index);
}/* SetMenus */

/* read an entire floppy into RAM */
OSErr ReadFloppy(drive)
   short   drive;
{
   OSErr      err;
   ioParam    ioStuff;
   short      index,i,delta;
   Rect      r,x;
   Handle      h;
   short      kind;
   GrafPtr    oldPort;
   GetDItem(wizDialog,statMeter,&kind,&h,&r);
   delta = r.right - r.left;
   x = r;                            /* this is our status rect. */
   x.right = r.left;
   GetPort(&oldPort);
   SetPort(wizDialog);

   ioStuff.ioVRefNum = drive;       /* the drive # */
   ioStuff.ioReqCount = 5120;       /* read 10 sectors at a time */
   ioStuff.ioMisc = nil;
   ioStuff.ioRefNum = -5;           /* the disk driver */
   ioStuff.ioBuffer = NewPtr(5120); /* this is a temp. ptr*/
   ioStuff.ioPosOffset = 0;         /* start at beg. of disk */
   ioStuff.ioPosMode = fsFromStart;

   for (index=0;index<sectors/10;index++) {  /* read the disk */
         err = PBRead(&ioStuff,false);       /* read it */
         x.right = floor((double)delta*(double)index/(double)(sectors/10))
         +r.left;
      FillRect(&x,&black);
      for(i=0;i<5120;i++)
         data[ioStuff.ioPosOffset+i] = ioStuff.ioBuffer[i];
      ioStuff.ioPosOffset += 5120;     /* advance 512 bytes */
   }/* for... */
   DisposPtr(ioStuff.ioBuffer);
   FillRect(&r,<Gray);
   FrameRect(&r);
   SetPort(oldPort);
   return(err);
}/* ReadFloppy */

/* write an entire floppy from RAM to a dest. disk */
OSErr WriteFloppy(drive)
   short   drive;
{
   OSErr      err;
   ioParam    ioStuff;
   short      index,i,delta;
   Rect      r,x;
   Handle      h;
   short      kind;
   GrafPtr    oldPort;
   GetDItem(wizDialog,statMeter,&kind,&h,&r);
   delta = r.right - r.left;    /* diff. between left and right sides */
   x = r;                       /* this is our status rect. */
   x.right = r.left;
   GetPort(&oldPort);
   SetPort(wizDialog);

   ioStuff.ioVRefNum = drive;   /* the drive # */
   ioStuff.ioReqCount = 5120;   /* write 10 sectors at a time */
   ioStuff.ioMisc = nil;
   ioStuff.ioRefNum = -5;       /* the disk driver */
   ioStuff.ioBuffer = NewPtr(5120);    /* this is a temp. ptr*/
   ioStuff.ioPosOffset = 0;            /* start at beg. of disk */
   ioStuff.ioPosMode = fsFromStart;

   for (index=0;index<sectors/10;index++) {   /* write the disk */
      for(i=0;i<5120;i++)       /* accuarate control meter */
         ioStuff.ioBuffer[i] = data[ioStuff.ioPosOffset+i];
         err = PBWrite(&ioStuff,false);    /* write it */
         x.right = floor((double)delta*(double)index/(double)(sectors/10))
         +r.left;
      FillRect(&x,&black);
      ioStuff.ioPosOffset += 5120;   /* advance 512 bytes */
   }/* for... */
   DisposPtr(ioStuff.ioBuffer);
   FillRect(&r,<Gray);
   FrameRect(&r);
   SetPort(oldPort);
   return(err);
}/* ReadFloppy */

/* pop out all disks in all drives */
OSErr EjectAllDisks()
{
   OSErr   err;
   DrvSts   sts;
   short   drive;
   for(drive=1;drive<3;drive++) {
      err = DriveStatus(drive,&sts);
      if (err == noErr)
         if (sts.diskInPlace > 0)      /* is it there? */
            (void)MyEject(drive); /* pop it out */
   }/* for */
   return(err);
}/* EjectAllDisks */

/* which drive was the disk inserted into? */
short WhichDrive()
{
   DrvSts   sts;
   OSErr   err;
   short   drive,index;
   do {
      err = DriveStatus(index,&sts);
      if (sts.diskInPlace>0)          /* is it there? */
         drive = sts.dQDrive;    /* this drive has the disk */
   } while(drive<0);
   return(drive);
}/* whichDrive */

/* returns the name of the inserted disk */
OSErr GetDiskName(drive,name)
   short   drive;
   Str255   *name;
{
   OSErr      err;
   long      free;
   short      vRef;
   err = GetVInfo(drive,name,&vRef,&free);
   return(err);
}/* GetDiskName */

/* we have to handle activate events too! */
void DoActivate(myEvent)
   EventRecord   myEvent;
{
   WindowPtr   targetWP;
   targetWP = (WindowPtr)myEvent.message;
   if (targetWP != FrontWindow())
      SelectWindow(targetWP);
   SetPort(targetWP);
}/* DoActivate */

/* set the text to tell you what's up */
void SetTheText(item,strInd)
   short   item,strInd;
{
   Rect   r;
   Handle   h;
   short   type;
   Str255   theStr;
   GetDItem(wizDialog,item,&type,&h,&r);
   GetIndString(&theStr,ourStrings,strInd);
   SetIText(h,&theStr);
}/* SetTheText */

/* here's the simple line drawings */
void DrawPseudoBoxes(r,x)
   Rect   r;
   short   x;
{
   MoveTo(r.left-3,r.top+7);
   LineTo(r.left-3,r.bottom+20);
   LineTo(r.right,r.bottom+20);
   LineTo(r.right,r.top+7);
   LineTo(r.left+x,r.top+7);
   MoveTo(r.left-3,r.top+7);
   LineTo(r.left,r.top+7);
}/* DrawPseudoBoxes */

/* is the disk write protected?*/
Boolean WriteProtected(drive)
   short   drive;
{
   DrvSts   sts;
   OSErr   err;                 /* for err handling to be added later */

   err = DriveStatus(drive,&sts);
   return(BitTst(&sts.writeProt,(long)7));      /* is it? */
}/* WriteProtected */

/* simple QuickDraw McGraw stuff to make main dialog look good */
void BoxDialogThings()
{
   short   type,item;
   Handle   h;
   Rect   r;

   GetDItem(wizDialog,inMemory,&type,&h,&r);
   DrawPseudoBoxes(r,72);
   GetDItem(wizDialog,copiesMade,&type,&h,&r);
   DrawPseudoBoxes(r,85);
   GetDItem(wizDialog,status,&type,&h,&r);
   DrawPseudoBoxes(r,50);
   GetDItem(wizDialog,statMeter,&type,&h,&r);
   FillRect(&r,<Gray);
   FrameRect(&r);
}/* BoxDialogThings */

/* handle disk event */
OSErr DoDisk(message)
   long   message;
{
   OSErr      err;
   Boolean    wp,flag = false;
   short      drive,kind;
   wp = WriteProtected(LoWord(message));
   drive = LoWord(message);
   if (masterLevel) {        /* are we reading a master disk? */
      if (HiWord(message) != noErr && HiWord(message) != volOnLinErr)
         BadDisk(19);
      else if (wp) {
         nCopies = 0;
         CheckDisk(drive,&diskKind);
         masterLevel = false;   /* don't copy unless it's wp! */
         destLevel = true;
         (void)GetDiskName(drive,&volumeName);
         SetAllDText(useVol,3,16);
         if (MakeRAM(diskKind,data) == noErr) {
            ReadFloppy(drive);
            SetAllDText(useVol,3,12);
         }/* if MakeRAM ... */
         else
            SetAllDText(2,3,11);
      }/* else if !... */
      else
         BadDisk(6);
      (void)MyEject(LoWord(message));
   }/* if */
   else if (destLevel) {             /* are we making copies??? */
      if ((HiWord(message)) == noErr && !wp ) {
         if (warn) {
            if (!DataAlert())
               flag = true;
         }
         if (!flag){
            CheckDisk(drive,&kind);
            if (kind != diskKind || format) {
               if (kind != MFM || format) {
                  SetAllDText(useVol,3,17);
                  (void)DoFormat(drive,diskKind);
               }
               else
                  BadDisk(4);
            }
         }/* else... */
      }/* if... */
      else if ((format || (HiWord(message) != noErr)) && !wp) {
            SetAllDText(useVol,3,17);
            (void)DoFormat(drive,diskKind);
      }/* if format */

      if (!wp && !flag) {/* write data to disk */
         SetAllDText(useVol,3,18);
         (void)WriteFloppy(drive);
         nCopies++;             /* let's keep track here */
      }
      else if (wp)
         BadDisk(7);
      (void)MyEject(drive);
      SetAllDText(useVol,3,12);
   }/* else if */
   return(err);
}/* DoDisk */

/* handle the new master disk inserted */
void DoNewMaster()
{
   (void)EjectAllDisks();
   SetAllDText(2,3,11);
   masterLevel = true;
}/* DoNewMaster */

/* simple dialog box */
void DoAbout()
{
   DialogPtr   aboutBox;
   short      item;
   GrafPtr    oldPort;
   GetPort(&oldPort);                    /* get the current port */
   aboutBox = GetNewDialog(aboutDialog,nil,((WindowPtr)-1));
   CenterWindow(aboutBox,true);
   SetPort(aboutBox);                   /* set current port to ours */
   ModalDialog(nil,&item);
   DisposDialog(aboutBox);        /* trash it since we're done with it */
   SetPort(oldPort);              /* set it back */
}/* DoAbout*/

/* process menu items that were selected */
void DoMenu(code)
   long   code;
{
   short   menuNum,itemNum;
   Str255   name;
   short   temp;
   menuNum = HiWord(code);
   itemNum = LoWord(code);
   if (itemNum > 0) {
      switch (menuNum) {
         case beginMenu:
            switch (itemNum) {
               case aboutMe :
                  DoAbout();
                  break;
               default :
                   GetItem(myMenus[0],itemNum,&name);
                   temp = OpenDeskAcc(&name);
            }/* switch itemNum */
            break;
         case beginMenu+1:
            switch (itemNum) {
               case newMaster :
                  DoNewMaster();
                  break;
               case mQuit :
                  GetDefaultVol();
            }/* switch itemNum */
            break;
         case endMenu:;
      }/* switch menuNum */
      HiliteMenu(false);
   }/* if */
}/* DoMenu */

/* update main window */
void DoUpdate(myEvent)
   EventRecord   myEvent;
{
   WindowPtr   tempPort,aWindPtr;
   aWindPtr = (WindowPtr)myEvent.message;       /* get window */
   if (aWindPtr == wizDialog) {
      SetCursor(*GetCursor(watchCursor));   /* hold on a sec */
      GetPort(&tempPort);
      SetPort(aWindPtr);
      ForeColor(blueColor);
      TextFont(geneva);                   /* easy on the eyes */
      ForeColor(blackColor);
      BeginUpdate(aWindPtr);
         EraseRect(&aWindPtr->portRect);    /* clean it out */
         DrawDialog(wizDialog);             /* and redraw */
         BoxDialogThings();
      EndUpdate(aWindPtr);
      SetPort(tempPort);
      TextFont(systemFont);         /* back to correct system font */
      InitCursor();                 /* back to the arrow */
   }/* if */
}/* DoUpdate */

/* handle things for our main dialog */
void DoDialog(item)
   short   item;
{
   switch (item) {
      case newMaster :    /* a master disk was inserted, do it! */
         DoNewMaster();
         break;
      case quit :         /* we're done */
         GetDefaultVol();
         break;
      case alwaysFormat :      /* automatically format all disks */
         ToggleItem(wizDialog,alwaysFormat,false);
         format = (format==true) ? false : true; /* toggle */
         break;
      case warnBefore : /* warn before formatting an existing disk */
         ToggleItem(wizDialog,warnBefore,false);
         warn = (warn==true) ? false : true;/* toggle */
   }/* switch */
}/* DoDialog */

/* handle the key events */
void DoKeyEvent(myEvent)
   EventRecord   myEvent;
{
   char   theKey;
   long   item;
   if (myEvent.modifiers & cmdKey) {
      theKey =
         myEvent.message & charCodeMask;  /* which key */
      if (item = MenuKey(theKey))
         DoMenu(item);               /* do it */
   }
}/* DoKeyEvent */

/* handle the mouse down event */
void DoMouseDown(myEvent)
   EventRecord   myEvent;
{
   short      windowLoc;
   Point      mousePos;
   WindowPtr   aWindPtr;
   Rect      r;
   mousePos = myEvent.where;
   windowLoc = FindWindow(mousePos,&aWindPtr);
   switch (windowLoc) {
      case inMenuBar :
         DoMenu((long)MenuSelect(mousePos));
         break;
      case inSysWindow :/* for those pesky DAs' */
         SystemClick(&myEvent,aWindPtr);
         break;
      case inDrag :
        SetRect(&r,screenBits.bounds.left+4,screenBits.bounds.top+24,
         screenBits.bounds.right-4,screenBits.bounds.bottom-4);
        DragWindow(aWindPtr,mousePos,&r);
        break;
      case inContent :
         if (FrontWindow() != aWindPtr)
            SelectWindow(aWindPtr);
         break;
      default :;
   }/* switch */
}/* DoMouseDown */

/* Init. mgrs., allocate space, etc. */
 void InitThings()
 {
   InitGraf(&thePort);/* init appropriate mgrs */
   InitFonts();
   InitWindows();
   InitMenus();
   TEInit();
   InitDialogs(nil);
   InitCursor();
   MoreMasters();                /* get master pointers */
   MoreMasters();
   MoreMasters();
   MaxApplZone();
   FlushEvents(everyEvent,0);   /* clear event queue */
   wizDialog = GetNewDialog(mainDialog,nil,((WindowPtr)-1));
   CenterWindow(wizDialog);

   DrawWizDialog();
 }/* InitThings */

 /* Handle any event that occurred */
 void HandleEvent(myEvent)
   EventRecord   *myEvent;
 {
   switch (myEvent->what) {
      case mouseDown :
         DoMouseDown(*myEvent);
         break;
      case activateEvt :
         DoActivate(*myEvent);
         break;
      case updateEvt :
         break;
      case keyDown :
      case autoKey :
         DoKeyEvent(*myEvent);
         break;
      case diskEvt :
         (void)DoDisk(myEvent->message);
   }/* switch */
 }/* HandleEvent */

 /* remember the default volume */
 void RememberDefault()
 {
   short   vRef;

   (void)GetVol(&defaultName,&vRef);
 }/* RememberDefault */

 /* check system, ram, etc. */
 Boolean CheckThings()
 {
   return(true);
 }/* CheckThings */

/* Setup menus, window, controls, etc. */
 void SetUpThings()
 {
   short   index;
   for (index=beginMenu;index<endMenu+1;index++)    /* get menus */
      myMenus[index-beginMenu] = GetMenu(index);
   AddResMenu(myMenus[0],'DRVR');        /* add desk accessories */
   for (index=beginMenu;index<endMenu+1;index++)
      InsertMenu(myMenus[index-beginMenu],0);
   DrawMenuBar();
   ToggleItem(wizDialog,warnBefore,true);
   SetMenus();
 }/* SetUpThings */

 void main()
 {
   InitThings();
   if (CheckThings()) {
      SetUpThings();
      RememberDefault();
      while (!done) {
         if (GetNextEvent(everyEvent,&theEvent))
            if (IsDialogEvent(&theEvent)) {
               if (theEvent.what == updateEvt)
                  DoUpdate(theEvent);
               else if (theEvent.what == diskEvt) {
                  if (masterLevel || destLevel)
                       DoDisk(theEvent.message);
                  else
                     (void)EjectAllDisks();
             }/* else if ... */
             else if (DialogSelect(&theEvent,&wizDialog,&temp))
                  DoDialog(temp);
            }/* if */
            else
               HandleEvent(&theEvent);
      }/* while */
   }/* if CheckThings */
   else {
   }/* else */
   if ( data != nil )
      DisposPtr(data);
 }/* main */











Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.