Proper file handling on your STBy PATRICK BASS, Antic ST Program Editor
Atari 8-bit owners who use DOS 2.0/2.5 don't need 'em. SpartaDOS, and other "big-disk" DOS's, have 'em. The Atari ST series computer couldn't live without 'em. What are they? Pathways. What are pathways? How could they be so important if they aren't used in computers like the Atari 8-bits?
The standard Atari 8-bit DOS 2.0/2.5 allows up to 64 files on a disk at one time. On a disk which stores roughly 88K this is a practical solution. But hook up a disk drive which allows greater storage, and you need to move up to a DOS which supports the extra room, like SpartaDOS. The Atari ST floppy disk drives may store up to 800K, and hard drives store 20,000K or more. A little math shows 64 files would get spread pretty thin over 20 million bytes.
In practice, the Atari ST allows hundreds of different files to be stored on the disk at one time. If you so desire, they may be all on the first directory which opens up. If there are more files than can show onscreen at one time, you may scroll the screen around to view the other files.
BIRDS OF A FEATHER
Eventually, you'll find that a number of files seem to always belong together. Picture files always go with DEGAS, text files always go with FirstWord, and so forth. It would be nice to be able to group these different files together. The basic Atari ST operating system, TOS, allows you to do just that. TOS lets you create a "folder," which is nothing more than a file which holds another disk directory. Storing a file inside a folder is just like storing it to a special small "disk-on-a-disk" TOS has set up for you. This folder, or series of folders, along with the disk drive specifier is known as the path.
This means we may create a folder called BASIC, and store all our BASIC programs, and ST BASIC itself, apart from picture or text files. When we open the BASIC folder by double-clicking on it, we only see BASIC and its programs, and no other files. We may also safely store two files which share the same name on the same disk, as long as they're in different folders.
Let's examine the filename structure for the Atari 8-bit, and then compare it to the pathway\filename structure for the Atari ST in detail.
PATHWAY TO HEAVEN
Take, for example, the Atari 8-bit file specification "D2:AFILE.BAS". It is made up of three parts. First, the "D2:" is the drive specifier. It describes which logical device the information will be transferred to or from. The next part, "AFILE", is the name of the file you typed in when the file was created. The last part, ".BAS" is the file extender. It should describe the type of file stored. Without it, mixing different types of files, like text and data, becomes remarkably easy. The extender means more to the human than to the Atari 8-bit, but the extender takes on great meaning in the world of the Atari ST series computer.
In the Atari ST, the same file could be stored as "D:\BASIC\AFILE.BAS". Compare it to the filename for the Atari 8-bit. The file specification is now broken into four parts, with a significant new character introduced. The first part is nearly the same. "D:"is typical of a RAM-disk or hard disk, with "A:" or B:" describing the floppy drives. The new part is the folder name, or pathway, "\BASIC", which describes which directory (folder) TOS should go to find the file "\AFILE." ST BASIC itself will only show (without coercion) files which have a "BAS" extender.
The new character is the backslash, or "\". This is a special character to TOS, which reserves it for file handling functions like separating pathways and filenames.
DEEPER AND DEEPER
You may place folders inside folders, up to eight deep. This means we could have a legal file specification like "B:\GRAPHICS\DEGAS\LORES\PICTURE.PI1" which breaks down to having a low resolution DEGAS picture called PICTURE.PIl inside the LORES folder, which is inside the DEGAS folder, which itself is inside the GRAPHICS folder, which is on floppy disk drive B:. Whew. Programs published in The ST Resource up to now have had somewhat simplistic file handling abilities. For example, all our C programs up to San Francisco Fogger (ST Resource, Feb. 1987) have assumed the program ran from the first directory which opens up on the disk, and all the files they accessed were on the same directory. This, of course, can't last. At one point or another, you'll need to read or write a file from another directory, or another disk drive.
This month we'll explore how to instruct the Atari ST exactly where to go and find the file you want. We'll do this in a program which performs a somewhat curious function. Recently, our Antic ONLINE editor, Charlie Jackson, completely revamped our online magazine on CompuServe. (Type GO ANTIC next time you're online.) He had a problem. He needed a program which would take a text file and allow him to reformat the text to different line widths. For example, take a text file in 80 column format and change it to 35 column format...without breaking words in the middle. I wrote it, and since it's a good vehicle for demonstrating pathways, I present it to you this month. Those of you out there in magazine land who publish newsletters might find it interesting also. Want 12 column format? How about 37,254 columns? Be my guest.
In a nutshell, the program allows you to select the line width, from one to one million characters per line, read in a file from any logical source disk into an internal 100K buffer, reformat it to the desired line width, and then write the file to any logical destination disk.
TYPING IT IN
Those of you who get the Antic Disk may skip down to the next section, and for help in transferring the program LIMIT.PRG to your ST, see the Help file on side B of the Antic Monthly Disk. Otherwise, type in Listing 1, LIMIT.C, using your favorite word processor and save it to disk as an ASCII file. (This means making sure the WP mode in FirstWord isn't check-marked.) We'll create the finished program using the developers' Alcyon C, but most any C should be able to duplicate the functions. MegaMax C owners will need to Malloc() the 100K buffers. Create the proper batch file for your flavor of C, and boil your source code down to candy.
USING THE LIMITER
First, select a text file to reformat. To activate the program, double-click on LIMIT.PRG. Click through the Title box, and select the desired number of characters per line in the next Alert Box by clicking on the [<] or [>] buttons. When you're happy, click on [SELECT]. Next comes a file selector box, where you select the text file to reformat. When you click on [OK] the file is found, read in, and reformatted. Next, another file selector box is presented for you to enter the path\filename for the newly reformatted file to be written to. Enter the new path\name, and click on [OK]. The new file will be written to disk, and a new Alert Box will pop up, asking if you desire to reformat another file. Answer either [YES] or [NO], with [NO] exiting you from the program.
Examine Listing 1, LIMIT.C . Notice we have room for two 100K buffers. You may change this up or down as desired,
a "folder," which is
nothing more than
a file which holds
another disk directory.
Storing a file inside
a folder is just like
storing it to a special
TOS has set up for you.
to fit your amount of memory. The first function of any interest is get_line__length(), where we dynamically build and display an Alert Box which presents a user-changeable number. The number displayed is the number of characters per line in the finished text file.
Below, in convert(), the actual work of conversion is performed. When the function is entered, it is assumed the file to work on has been loaded into text_buffer, and the size of the file is in (long)bytes_read. Starting at the beginning, and accounting for each character in the original file, first copy the current character into temp_buffer. If the character transferred was a carriage return, and the next character in line is a line feed, then reset the current count of characters in the newly formatted line, thus starting a new line.
Otherwise, advance the count of characters in the new line. Next, check if the current character count is greater than the maximum linelength requested. If it is, then keep copying characters from text_buffer into temp_ buffer until the current word is finished, and then insert a new CR, LF combination. When the entire file has been processed, remember the new file length in (long)bytes_read.
The next two functions are really why we visited here this month. The first is read_the_file(), and the other is write__the__file(). These two functions will search out any file from any device. In them we also have bonus string-handling functions for copying strings hither and yon, breaking them apart, and sewing them back together. These functions, and the function long2ascii() were provided by Tom Hudson, and I thank him for letting me use them here.
a text file and reformats
the text to different
line widths-you can
change an 80-column
file into 35 columns-
without breaking words
in the middle. Want 12
columns? How about
37,254? Be my guest.
Before we get to the routines, let's discuss what GEM wants and when it wants it. When you first get your feet wet, squishily stomping through the GEM interface (and folks, there's a lot of squishy stuff in there) it's hard to keep track of which part of GEM wants what at what time. For example, the fsel_input() call, which presents the familiar file selector box, wants separate path and filename buffers. But the read and write functions have no apparent space for the path. Do they automatically know where to go to find the current path?
In a word, no. Yes, it is important to keep the path and filename separate, since there are special calls to get and set each of them, but right before we actually make the call to read or write, we build a new string out of the filename tacked onto the end of the pathstring, and we present this new string to the file routines. They love it!
KEEP AN OPEN FILE
To open a file, get the last known accessed device number from Dgetdrv(). This function returns a number typically in the range of zero to ten. Add this number to the value of the letter "A", and the result is the device letter. For example, if drive is 1, then the device is 1 + 65, or 66, which is ASCII for "B". The very first character in the path must be the device letter, so add the value of drive to "A", and place the result in position zero of the path-string.
The second character must be a colon. Pick up the previously known path, starting with the second character, with the call Dgetpath(), and place it into the new path we're building. In this example, we want to access any file on the disk, so we strcat(), or string concatenate, the string onto the end of our new path string. These are wildcards, just like in the Atari 8-bits. Next, we make sure the new filename is cleared out.
At this point we've created a new path string for TOS to use, and apparantly erased our filename. Before we can call fsel_input(), we need to make sure we have made space for a button value which fsel_input() will return. I cleverly did this long ago at the top of the program. We may now call fsel_input() safely.
When the box appears onscreen notice the path you built is now displayed along the top line. The filename line is empty. Scroll through the file list and pick out any one, or type one in yourself. When you click on either the [OK] or the [CANCEL] buttons the call is finished and control returns to your program. The exit button pressed is returned as a number, either one or two, in the variable button. Whatever you type into the path string or the filename string, while the file selector box is up, may be found in the path and filename buffers you supply the routine in the beginning.
We may check the returned button number to see which button was pressed. Which, interestingly enough, we do in the very next line. If the button we pressed was not the [CANCEL] button, (Hmmm. . . must be the [OK] button, eh?) it's time for us to build the entire path/filename string to give to the file handling routines.
Above, at the top of the program, I saved space for a string called workname. The call strcpy() will copy our path string into the workname buffer. We then use the next call to strip off the "*. *" we placed on the path string above, as we're about to add our filename string to it. And finally, strcat() the new filename string onto the end of the path string.
We now may present this new workname string to the file handling functions. Next, attempt to Fopen( ) the desired file. If the number returned from this call is negative, an error occurred, and the file probably isn't open. But if the value returned is positive it's should be safe to access the file.
Which we do below, where we Fread() up to max_ bytes into text_buffer. In either case of good or bad access, we close the file to be sure, and perform a quick check to see if the buffer overflowed. This means you tried to read a file bigger than the buffer.
The routine to write a file is simply the inverse of the read. The string and file handling methods are identical.
I hope you enjoy these routines. They're short, and they cure a problem in inadequate file access. Use them as you see fit.
Listing 1 LIMIT.C