/* File support routines to complement ARP. * Author: Mark R. Rinfret * Usenet: mrr@amanpt1.Newport.RI.US * BIX: markr * CIS: 72017, 136 (good luck!) * * 348 Indian Avenue * Portsmouth, RI 02871 * 401-846-7639 (home) * 401-849-9390 (work) * * This package was written primarily because of _one_ missing element * in ARP: FGets. ARGH! ARPFFFT! It extends ARP by adding buffering to * the basic file type (FileHandle) and defining a new type, named * ARPFileHandle (hope this is OK with the ARP guys). Also, I've used the * convention of embedding ARP (vs. Arp in MicroSmith's stuff) in all type * and function names to (hopefully) avoid naming collisions. * * This package (as far as I am concerned) is public domain. Use it any * way you like. Some comments on the "MR" prefix: it isn't short for * "Mister", it comprises the initials of my first and last names; * I use it primarily to avoid name collisions with stuff by other * authors. If any other authors whose initials are "MR" start doing * this, we'll probably have to appeal to Electronic Arts to dole out * "author codes" along the lines of those issued for IFF :-). I hereby * stake a claim to 'MRR_'. * * A word of warning: * * DO NOT INTERMIX STANDARD AMIGADOS FILE SUPPORT CALLS WITH CALLS TO * THIS PACKAGE ON FILES USING THIS FILE METHOD! * * Obviously, the system doesn't know about the buffering added here * and will cause unpredictable results (unless, of course, you * take care to maintain the ARPFileHandle information). */ /* Note: define DEBUG if you want the test program at the end. */ /* Embedded documentation template (cut & paste): */ /* FUNCTION * * SYNOPSIS * * DESCRIPTION * */ #include "MRARPFile.h" #include #include #ifndef min #define min(a,b) ((a) <= (b) ? (a) : (b)) #define max(a,b) ((a) >= (b) ? (a) : (b)) #endif /* StoreTracker is my attempt to fix an apparent bug in ARP 1.3. I have * found that the LastTracker kludge (via IoErr()) doesn't work reliably. * StoreTracker simply stuffs A1 into D0 and returns . It *MUST* be * called immediately after any ARP call which allocates a tracker to * assure that the value is correct. */ struct DefaultTracker *StoreTracker(); /* asm routine */ static LONG FillARPFileBuffer(); /* FUNCTION * FGetsARP - get string from a buffered ARP file. * * SYNOPSIS * char *FGetsARP(s, length, file) * UBYTE *s; * LONG length; * ARPFileHandle *file; * * DESCRIPTION * FGetsARP models the behavior of the "standard" fgets, except that * it is used with a buffered ARP file. The data is read from * and transfered to string . Up to length-1 characters will be * read. Reading is also terminated upon receipt of a newline * or detection of end-of-file. * * If the was opened without a buffer, one of MaxInputBuf * bytes will be allocated. * * If end of file or an error is detected, the return value will be * NULL. Otherwise, the return value will be . will be * terminated with a null byte. */ char * FGetsARP(s, length, file) char *s; LONG length; ARPFileHandle *file; { LONG bytesNeeded = length - 1; LONG bytesRead = 0; char c; char *s1 = s; struct DefaultTracker *tracker; /* Set string initially empty to protect user from failure to check * return value. */ *s1 = '\0'; if (file->mode != MODE_OLDFILE) file->lastError = ERROR_READ_PROTECTED; if (file->lastError) { dangit: return NULL; } if (! file->buf ) { /* Ohmigosh! No buffer? */ file->buf = ArpAllocMem(MaxInputBuf, MEMF_CLEAR|MEMF_PUBLIC); tracker = StoreTracker(); if (! file->buf ) { file->lastError = ERROR_NO_FREE_STORE; goto dangit; } file->bufSize = MaxInputBuf; /* bufLength, bufPos are zero. */ file->bufTracker = tracker; } while (bytesNeeded) { if (file->bufPos >= file->bufLength) { if (FillARPFileBuffer(file) < 0) goto dangit; if (file->bufLength == 0) break; } c = file->buf[file->bufPos++]; ++bytesRead; if (c == '\n') break; *s1++ = c; --bytesNeeded; } *s1 = '\0'; return (bytesRead ? s : NULL); } /* FUNCTION * FillARPFileBuffer - read data into ARPFile buffer. * * SYNOPSIS * static LONG FillARPFileBuffer(file) * ARPFileHandle *file; * * DESCRIPTION * Attempt to fill the buffer associated with by reading * data from the associated external file. The return value will * be one of the following: * >0 => number of bytes read * 0 => end of file * -1 => a Bad Thing happened (error code in file->lastError) * * Note: this is a local routine and is thus declared as "static". * Please inform the author if this is a hardship. */ static LONG FillARPFileBuffer(file) ARPFileHandle *file; { /* Remember where we were. The user might want to try error * recovery. Of course, this probably isn't enough info, but * it's a start. */ file->lastPosition = Seek(file->fh, 0L, OFFSET_CURRENT); file->bufPos = 0; file->bufLength = Read(file->fh, file->buf, file->bufSize); if (file->bufLength == -1) { /* We got trubble! */ file->lastError = IoErr(); } else if (file->bufLength == 0) { file->endOfFile = TRUE; } return file->bufLength; } /* FUNCTION * FlushARPFileBuffer - write file buffer contents to disk. * * SYNOPSIS * static LONG FlushARPFileBuffer(file) * ARPFileHandle *file; * DESCRIPTION * FlushARPFileBuffer writes the contents of 's buffer * (if any) to disk and resets the buffer information. The * return value may be any of: * * >0 => number of bytes written * 0 => nothing in buffer * <0 => negated error code * * Note: it is assumed that this function will only be used locally * and therefore need not be public. If you disagree, please contact * the author. */ static LONG FlushARPFileBuffer(file) ARPFileHandle *file; { LONG bytesWritten = 0; /* This operation is only allowed for output files. */ if (file->mode != MODE_NEWFILE) { file->lastError = ERROR_WRITE_PROTECTED; badstuff: return -file->lastError; } if (file->lastError) goto badstuff; /* Residual error? */ if (file->bufLength) { file->lastPosition = Seek(file->fh, 0L, OFFSET_CURRENT); bytesWritten = Write(file->fh, file->buf, file->bufLength); if (bytesWritten != file->bufLength) { file->lastError = IoErr(); goto badstuff; } else { file->bufLength = 0; file->bufPos = 0; } } return bytesWritten; } /* FUNCTION * FPutsARP - write a string to a buffered ARP file. * * SYNOPSIS * LONG FPutsARP(s, file) * char *s; * ARPFileHandle *file; * * DESCRIPTION * FPutsARP writes the contents of string to the specified . * If successful, it returns 0. On failure, it returns the negated * system error code. */ LONG FPutsARP(s, file) char *s; ARPFileHandle *file; { LONG bytesLeft, bytesUsed; char *s1 = s; if (file->mode != MODE_NEWFILE) file->lastError = ERROR_WRITE_PROTECTED; if (file->lastError) { shucks: return -file->lastError; } bytesLeft = strlen(s); /* Attempt to be smart about this transfer. Copy the string to the * buffer in chunks. There is a possibility that the string is bigger * than the size of the buffer. */ while (bytesLeft) { if (file->bufPos >= file->bufSize) { if (FlushARPFileBuffer(file) <= 0) goto shucks; } bytesUsed = min(file->bufSize - file->bufPos, bytesLeft); CopyMem(s1, &file->buf[file->bufPos], bytesUsed); s1 += bytesUsed; file->bufLength = (file->bufPos += bytesUsed); bytesLeft -= bytesUsed; } return 0; } /* FUNCTION * ReadARPFile - read from a buffered ARP file. * * SYNOPSIS * LONG ReadARPFile(file, buffer, length) * ARPFile *file; * UBYTE *buffer; * LONG length; * * DESCRIPTION * ReadARPFile attempts to read bytes from , transferring * the data to . * * The return value may be any of the following: * * >0 number of bytes transferred * 0 end of file * <0 negated error code * * Note: if the lastError field of the descriptor contains a * non-zero value, its negated value will be returned and no * attempt will be made to read the file. If you attempt error * recovery, you must clear this field to zero. */ LONG ReadARPFile(file, buffer, length) ARPFileHandle *file; UBYTE *buffer; LONG length; { LONG bytesLeft; LONG bytesRead = 0; LONG needBytes = length; LONG pos = 0; LONG usedBytes = 0; /* Prevent read if this file opened for writing. */ if (file->mode != MODE_OLDFILE) file->lastError = ERROR_READ_PROTECTED; if (file->lastError) { /* Have residual error? */ boofar: return -file->lastError; } if ( ! file->buf ) { /* No buffer? */ bytesRead = Read(file->fh, buffer, length); if (bytesRead == -1) { file->lastError = IoErr(); goto boofar; } } else while (needBytes) { if (file->bufLength - file->bufPos <= 0) { if (FillARPFileBuffer(file) == -1) goto boofar; if (file->bufLength == 0) break; } bytesLeft = file->bufLength - file->bufPos; usedBytes = min(bytesLeft, length); CopyMem(&file->buf[file->bufPos], &buffer[pos], usedBytes); file->bufPos += usedBytes; pos += usedBytes; bytesRead += usedBytes; needBytes -= usedBytes; } return bytesRead; } /* FUNCTION * CloseARPFile - close buffered ARP file. * * SYNOPSIS * LONG CloseARPFile(file) * ARPFileHandle *file; * * DESCRIPTION * CloseARPFile closes the file described by which MUST have * been opened by OpenARPFile. It releases all tracked items * associated with , as well. * * The return value is only meaningful for output files. If, upon * flushing the buffer, a write error is detected, a system error * code (ERROR_DISK_FULL, etc.) will be returned. */ LONG CloseARPFile(file) ARPFileHandle *file; { LONG result = 0; if (file) { /* Just in case... */ if (file->fileTracker) { /* Any left-over stuff in the buffer? If so, we must flush * it to disk. However, if an error was detected in the * previous operation, punt. */ if ( ( file->mode == MODE_NEWFILE) && ! file->lastError && file->bufLength) { if (Write(file->fh, file->buf, file->bufLength) != file->bufLength) result = IoErr(); } FreeTrackedItem(file->fileTracker); } if (file->bufTracker) FreeTrackedItem(file->bufTracker); FreeTrackedItem(file->myTracker); } return result; } /* FUNCTION * OpenARPFile - open a buffered ARP file * * SYNOPSIS * struct MRARPFile *OpenARPFile(name, accessMode, bytes) * char *name; * LONG accessMode, bytes; * * DESCRIPTION * OpenARPFile opens the file , with the given * (MODE_OLDFILE, MODE_NEWFILE only!) for buffered access. The * size of the local buffer is specified by . * * A zero value for is OK and is sometimes appropriate, as * when performing file copy operations, etc. However, if a file * opened with a zero length buffer is then passed to the * FGetsARP function, "spontaneous buffer allocation" will occur. * No biggy, really, just something you should know. The ARP constant, * MaxInputBuf will be used for the buffer size, in this case. * * If successful, a pointer to the file tracking structure is * returned. Otherwise, the return value will be NULL. * * OpenARPFile uses full resource tracking for all information * associated with the file. * */ ARPFileHandle * OpenARPFile(name, accessMode, bytes) char *name; LONG accessMode, bytes; { BPTR fh; ARPFileHandle *theFile = NULL; struct DefaultTracker *lastTracker; /* This package does not support READ/WRITE access! */ if ( (accessMode != MODE_OLDFILE) && (accessMode != MODE_NEWFILE)) return NULL; /* * Note: I'm using ArpAllocMem vs. ArpAlloc here because it appears * that ArpAlloc gives me a bad tracker pointer (?). */ theFile = ArpAllocMem((LONG) sizeof(ARPFileHandle), MEMF_PUBLIC|MEMF_CLEAR); lastTracker = StoreTracker(); if (theFile) { theFile->myTracker = lastTracker; theFile->mode = accessMode; fh = ArpOpen(name, accessMode); lastTracker = StoreTracker(); if (!fh) { fungu: CloseARPFile(theFile); /* Don't worry - it's "smart". */ theFile = NULL; } theFile->fileTracker = lastTracker; theFile->fh = fh; if ( bytes) { /* Does user want a buffer? */ theFile->buf = ArpAllocMem(bytes, MEMF_PUBLIC|MEMF_CLEAR); lastTracker = StoreTracker(); if (!theFile->buf) goto fungu; theFile->bufSize = bytes; theFile->bufTracker = lastTracker; } } return theFile; } /* FUNCTION * SeekARPFile - move to new logical position in file. * * SYNOPSIS * LONG SeekARPFile(file, position, mode) * ARPFileHandle *file; * LONG position; * LONG mode; * * DESCRIPTION * SeekARPFile attempts to position the to a new logical * position or report it's current position. The * parameter represets a signed offset. The parameter may * be one of: * * OFFSET_BEGINNING (-1) => from beginning of file * OFFSET_CURRENT (0) => from current position * OFFSET_END (-1) => from end of file * * On output files, the current buffer contents, if any, are * written to disk. * * The return value will either be a positive displacement >=0) or * a negated system error code. */ LONG SeekARPFile(file, position, mode) ARPFileHandle *file; LONG position; LONG mode; { LONG newPosition; if (file->mode == MODE_NEWFILE && file->bufLength) { if (FlushARPFileBuffer(file) < 0) { farboo: return -file->lastError; } } /* Remember our last position. */ file->lastPosition = Seek(file, 0L, OFFSET_CURRENT); if ((newPosition = Seek(file->fh, position, mode)) == -1) { file->lastError = IoErr(); goto farboo; } return newPosition; } /* FUNCTION * WriteARPFile - write data to a buffered ARP file. * * SYNOPSIS * LONG WriteARPFile(file, buffer, length) * ARPFileHandle *file; * UBYTE *buffer; * LONG length; * * DESCRIPTION * WriteARPFile attempts to write bytes from to * the buffered ARP file, . The file MUST have been opened * with OpenARPFile for MODE_NEWFILE access. * * WriteARPFile will not write to a file if the lastError field is * non-zero, or if the file was opened for input. * * If successful, WriteARPFile will return the parameter. * Otherwise, it will return a negated system error code. */ LONG WriteARPFile(file, buffer, length) ARPFileHandle *file; UBYTE *buffer; LONG length; { LONG bufferBytes; LONG bytesLeft = length; LONG pos = 0; LONG usedBytes = 0; if (file->mode != MODE_NEWFILE) file->lastError = ERROR_WRITE_PROTECTED; if (file->lastError) { /* Catches mode and residual errors. */ sumbidge: return -file->lastError; } if ( ! file->buf ) { /* No buffer? */ if (Write(file->fh, buffer, length) != length) { file->lastError = IoErr(); goto sumbidge; } } else while (bytesLeft) { /* Need to flush the file's buffer? */ if (file->bufPos >= file->bufSize) { if (FlushARPFileBuffer(file) < 0) { goto sumbidge; } } bufferBytes = file->bufSize - file->bufPos; usedBytes = min(bufferBytes, bytesLeft); CopyMem(&buffer[pos], &file->buf[file->bufPos], usedBytes); file->bufLength = (file->bufPos += usedBytes); pos += usedBytes; bytesLeft -= usedBytes; } return length; } #ifdef DEBUG /* Short test program. */ struct ArpBase *ArpBase; struct IntuitionBase *IntuitionBase; struct GfxBase *GfxBase; ARPFileHandle * GetFile(prompt, mode, bufferSize) char *prompt; LONG mode, bufferSize; { BOOL keepTrying; ARPFileHandle *theFile = NULL; char fileName[MaxInputBuf+1]; for (keepTrying = TRUE; keepTrying; ) { Puts("\nA null response aborts this test."); Puts(prompt); ReadLine(fileName); if ( ! *fileName ) break; /* Null response? */ theFile = OpenARPFile(fileName, mode, bufferSize); if (theFile == NULL) { Puts("That file failed to open. Try again.\n"); } else keepTrying = FALSE; } return theFile; } main() { #define BUFFER_SIZE 16384L static char line[81]; UBYTE *buffer; LONG count; ARPFileHandle *file1, *file2; int keepTesting; LONG totalBytes; ArpBase = (struct ArpBase *) OpenLibrary(ArpName, ArpVersion); if (ArpBase == NULL) { exit(1); } GfxBase = (struct GfxBase *) ArpBase->GfxBase; IntuitionBase = (struct IntuitionBase *) ArpBase->IntuiBase; Puts("MRARPFile package test.\n\n"); FGetsARPTest: Puts("FGetsARP test - echo file to console."); file1 = GetFile("Enter the name of a text file:", MODE_OLDFILE, 4192L); if ( file1) { while (FGetsARP(line, (LONG) sizeof(line), file1)) Puts(line); CloseARPFile(file1); } CopyFileTest: buffer = ArpAllocMem(BUFFER_SIZE, MEMF_PUBLIC); if ( ! buffer ) { Printf("I couldn't allocate a %ld byte buffer!\n", BUFFER_SIZE); goto die; } for (keepTesting = TRUE; keepTesting; ) { Puts("ReadARPFile/WriteARPFile test."); Puts("For this test, I will copy a file."); file1 = GetFile("Enter input file name:", MODE_OLDFILE, 512L); if (!file1) break; file2 = GetFile("Enter output file name:", MODE_NEWFILE, 8192L); if (!file2) { CloseARPFile(file1); break; } keepTesting = FALSE; /* This is it... */ totalBytes = 0; while (count = ReadARPFile(file1, buffer, BUFFER_SIZE)) { totalBytes += count; if (WriteARPFile(file2, buffer, count) != count) { Printf("Error on output file: %ld\n", file2->lastError); break; } if (count < BUFFER_SIZE) break; } if (file1->lastError) Printf("Error on input file: %ld\n", file1->lastError); CloseARPFile(file1); CloseARPFile(file2); Printf("Bytes transferred: %ld\n", totalBytes); /* Note: leave the buffer for ARP to free up. You should check * available memory before/after running this test to make sure * that ARP does proper housecleaning. */ } die: CloseLibrary(ArpBase); exit(0); } #endif