/* * SpoolFiles.c * * Routines for handling spool files and folders. * * Written by: Ken McLeod, 2026-01-14 * Last update: 2026-01-31 * * This software is provided as-is, with no warranties expressed or implied, * under the terms of the BSD 2-Clause License. See separate "LICENSE" file. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Logging.h" #include "SpoolFiles.h" /* set if we should save each incoming data packet length */ #define USING_CHUNK_LENGTHS 0 char *prefFileName = "\pSimple Spooler Prefs"; char *spoolFolderName = "\pSimple Spooler"; char spoolFileName[256]; short spoolFileRefNum; short spoolVRefNum; long spoolDirID; char *spoolFilePrefixes[] = { "??", /* 0 */ "LW", /* 1 */ "LQ", /* 2 */ "IW", /* 3 */ }; int NumToolboxTraps(void) { if (NGetTrapAddress(0xA86E /* _InitGraf */, ToolTrap) == NGetTrapAddress(0xAA6E, ToolTrap)) { return 0x200; } return 0x400; } TrapType GetTrapType(int theTrap) { if ((theTrap & 0x0800) > 0) { return ToolTrap; } return OSTrap; } Boolean TrapAvailable(int theTrap) { int trap = theTrap; TrapType tType = GetTrapType(theTrap); if (tType == ToolTrap) { trap = theTrap & 0x07FF; if (trap >= NumToolboxTraps) { trap = 0xA89F; /* _Unimplemented */ } } return (NGetTrapAddress(trap, tType) != NGetTrapAddress(0xA89F, ToolTrap)); /* _Unimplemented */ } Boolean GestaltAvailable(void) { return TrapAvailable(0xA1AD); /* _Gestalt */ } /* MyFindFolder is an adaptation of the FindSysFolder function * from DTS Utilities sample code on Dev CD Mar '94. It should * work even if Gestalt is not available by returning the * system folder for any folders normally in the system folder. */ OSErr MyFindFolder(OSType folderType,short *foundVRefNum,long *foundDirID) { long gesResponse; SysEnvRec envRec; WDPBRec myWDPB; unsigned char volName[34]; OSErr err; *foundVRefNum = 0; *foundDirID = 0; if (GestaltAvailable() && !Gestalt(gestaltFindFolderAttr, &gesResponse) && (gesResponse & (1<-, e.g. "IW-6778001" */ char tickString[256]; long curTicks = TickCount(); int len = (int)(strlen(spoolFilePrefixes[printerType]) & 0xFF); BlockMove(&spoolFilePrefixes[printerType][0], &nameBuf[nameBuf[0]+1], len); nameBuf[0] = len; BlockMove("-",&nameBuf[nameBuf[0]+1],1); nameBuf[0] += 1; NumToString(curTicks,tickString); len = 31 - (1 + nameBuf[0] + 1); if ((short)tickString[0] > len) { tickString[0] = len & 0xFF; } BlockMove(&tickString[1],&nameBuf[nameBuf[0]+1],len); nameBuf[0] += tickString[0]; } #else void NewSpoolFilename(int printerType, char *nameBuf) { /* creates a new filename in the provided 34-byte buffer, * in the form -YYYYMMDD-HHMMSS, e.g. "IW-20260121-092756" */ char tmpStr[34]; DateTimeRec dtRec; int len; unsigned long seconds; GetDateTime(&seconds); Secs2Date(seconds,&dtRec); len = (int)(strlen(spoolFilePrefixes[printerType]) & 0xFF); BlockMove(&spoolFilePrefixes[printerType][0], &nameBuf[nameBuf[0]+1], len); nameBuf[0] = len; sprintf(tmpStr,"-%d%02d%02d-%02d%02d%02d", dtRec.year,dtRec.month,dtRec.day, dtRec.hour,dtRec.minute,dtRec.second); len = strlen(tmpStr); BlockMove(tmpStr,&nameBuf[nameBuf[0]+1],len); nameBuf[0] += len; } #endif OSErr OpenSpoolFile(int printerType) { /* creates a new spool file in the spool directory, * using printer type as the prefix for the filename, * and opens it for writing. * Note: InitSpoolFolder must have been called first. * The file's fRefNum is stored in spoolFileRefNum. */ HParamBlockRec pb; short vRefNum = spoolVRefNum; /* set up by InitSpoolFolder */ long dirID = spoolDirID; OSErr err; NewSpoolFilename(printerType,spoolFileName); MessageWithQuotedString(LOG_ERR,"\pSpooling to file",spoolFileName); pb.fileParam.ioCompletion = nil; pb.fileParam.ioFDirIndex = 0; pb.fileParam.ioNamePtr = spoolFileName; pb.fileParam.ioVRefNum = vRefNum; pb.fileParam.ioDirID = dirID; err = PBHCreate(&pb,false); if (err != noErr && err != dupFNErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not create spool file:",err); return err; } pb.fileParam.ioNamePtr = spoolFileName; pb.fileParam.ioFDirIndex = 0; pb.fileParam.ioVRefNum = vRefNum; pb.fileParam.ioDirID = dirID; pb.ioParam.ioPermssn = fsRdWrPerm; /* want exclusive read-write access */ pb.ioParam.ioMisc = nil; err = PBHOpen(&pb,false); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not open spool file:",err); return err; } spoolFileRefNum = pb.ioParam.ioRefNum; return err; } OSErr WriteDataToSpoolFile(long count, void *data) { OSErr err = noErr; if (!spoolFileRefNum) { err = fnOpnErr; /* no spool file open! */ } #if USING_CHUNK_LENGTHS if (err == noErr) { /* the length of each data chunk comes from atpActCount, * which is defined as a short integer (16 bits). * ergo, we only have to store lengths as 2 bytes. * Note this is in network byte order on 68k/PPC. */ short chunkLength = (short)(count & 0x0000FFFF); long chunkLengthCount = 2; err = FSWrite(spoolFileRefNum,&chunkLengthCount,&chunkLength); } #endif if (err == noErr) { long chunkCount = count; err = FSWrite(spoolFileRefNum,&chunkCount,data); } if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not write data:", err); } return err; } OSErr SetFileInfo(char *fNameBuf, short vRefNum, long dirID, long fType, long fCreator) { OSErr err; HParamBlockRec pb; pb.fileParam.ioCompletion = nil; pb.fileParam.ioNamePtr = fNameBuf; pb.fileParam.ioVRefNum = vRefNum; pb.fileParam.ioDirID = dirID; pb.fileParam.ioFDirIndex = 0; err = PBHGetFInfo(&pb,false); if (err != noErr) { MessageWithIntValue(LOG_WARN_X,"\pCould not get file's FInfo:",err); return err; } pb.fileParam.ioCompletion = nil; pb.fileParam.ioNamePtr = fNameBuf; pb.fileParam.ioVRefNum = vRefNum; pb.fileParam.ioDirID = dirID; pb.fileParam.ioFDirIndex = 0; pb.fileParam.ioFlFndrInfo.fdType = fType; pb.fileParam.ioFlFndrInfo.fdCreator = fCreator; pb.fileParam.ioFlFndrInfo.fdFlags &= ~(1<<8); /* clear "inited" bit */ err = PBHSetFInfo(&pb,false); if (err != noErr) { MessageWithIntValue(LOG_WARN_X,"\pCould not set file's FInfo:",err); } return err; } OSErr MakeIWEmPostscriptFile(void) { OSErr err; HParamBlockRec pb; char psFileName[34]; char IWEmFileName[34]; short IWEmFileRefNum; short psFileRefNum; short vRefNum = spoolVRefNum; /* set up by InitSpoolFolder */ long dirID = spoolDirID; long count; char buf[1024]; err = noErr; if (!(spoolFileName[0] > 2 && spoolFileName[3] == '-' && ((spoolFileName[1] == 'I' && spoolFileName[2] == 'W') || (spoolFileName[1] == 'L' && spoolFileName[2] == 'Q')))) { return err; /* not a file starting with 'LQ-' or 'IW-' so ignore it */ } BlockMove("\pIWEm",IWEmFileName,5); BlockMove(spoolFileName,psFileName,29); /* copy 28 bytes max, plus length byte */ BlockMove(".ps",&psFileName[psFileName[0]+1],3); /* add .ps extension */ psFileName[0] += 3; /* try to open a file named IWEm in the spool directory */ pb.fileParam.ioNamePtr = IWEmFileName; pb.fileParam.ioFDirIndex = 0; pb.fileParam.ioVRefNum = vRefNum; pb.fileParam.ioDirID = dirID; pb.ioParam.ioPermssn = fsRdPerm; /* read access */ pb.ioParam.ioMisc = nil; err = PBHOpen(&pb,false); if (err != noErr) { return err; /* file doesn't exist or otherwise can't be used */ } IWEmFileRefNum = pb.ioParam.ioRefNum; /* create and open the output PS file */ pb.fileParam.ioCompletion = nil; pb.fileParam.ioFDirIndex = 0; pb.fileParam.ioNamePtr = psFileName; pb.fileParam.ioVRefNum = vRefNum; pb.fileParam.ioDirID = dirID; err = PBHCreate(&pb,false); if (err != noErr && err != dupFNErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not create PostScript file:",err); return err; } pb.fileParam.ioNamePtr = psFileName; pb.fileParam.ioFDirIndex = 0; pb.fileParam.ioVRefNum = vRefNum; pb.fileParam.ioDirID = dirID; pb.ioParam.ioPermssn = fsRdWrPerm; /* want exclusive read-write access */ pb.ioParam.ioMisc = nil; err = PBHOpen(&pb,false); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not open PostScript file:",err); return err; } psFileRefNum = pb.ioParam.ioRefNum; MessageWithQuotedString(LOG_WARN,"\pWriting PostScript file",psFileName); /* write IWEm file to the output file */ err = SetFPos(IWEmFileRefNum,fsFromStart,0L); while (!err) { count = sizeof(buf); err = FSRead(IWEmFileRefNum,&count,buf); if (err == noErr || (err == eofErr && count > 0)) { err = FSWrite(psFileRefNum,&count,buf); } } err = FSClose(IWEmFileRefNum); /* read data chunks from the spool file and write to output file */ err = SetFPos(spoolFileRefNum,fsFromStart,0L); while (!err) { #if USING_CHUNK_LENGTHS short int chunkLengthData; count = 2; err = FSRead(spoolFileRefNum,&count,&chunkLengthData); if (err == noErr) { count = chunkLengthData; err = FSRead(spoolFileRefNum,&count,buf); if (err == noErr || (err == eofErr && count > 0)) { err = FSWrite(psFileRefNum,&count,buf); } } #else count = sizeof(buf); err = FSRead(spoolFileRefNum,&count,buf); if (err == noErr || (err == eofErr && count > 0)) { err = FSWrite(psFileRefNum,&count,buf); } #endif } err = FSClose(psFileRefNum); (void) SetFileInfo(psFileName, vRefNum, dirID, 'TEXT', 'KAHL'); return err; } OSErr FinalizeSpoolFile(void) { /* Once all the data that's going to be written has been written, * we call this to set the file's FInfo. This is one way to tell * later whether the spool file is "good" or incomplete. */ OSErr err; ParamBlockRec pb; if (!spoolFileRefNum) { return noErr; /* nothing to do */ } pb.fileParam.ioCompletion = nil; pb.fileParam.ioFRefNum = spoolFileRefNum; err = PBFlushFile(&pb,false); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not flush spool file to disk:",err); return err; } (void) SetFileInfo(spoolFileName, spoolVRefNum, spoolDirID, 'pjob', 'spoo'); return err; } OSErr CloseSpoolFile(long int numBytesWritten) { OSErr err; if (!spoolFileRefNum) { return noErr; /* nothing to do */ } if (numBytesWritten > 0) { (void) FinalizeSpoolFile(); (void) MakeIWEmPostscriptFile(); } err = FSClose(spoolFileRefNum); spoolFileRefNum = 0; spoolFileName[0] = 0; return err; } OSErr SaveDataToPrefsFile(long count, void *data) { OSErr err; HParamBlockRec pb; short vRefNum = spoolVRefNum; /* set up by InitSpoolFolder */ short prefFileRefNum; long dirID = spoolDirID; long actCount; pb.fileParam.ioCompletion = nil; pb.fileParam.ioFDirIndex = 0; pb.fileParam.ioNamePtr = prefFileName; pb.fileParam.ioVRefNum = vRefNum; pb.fileParam.ioDirID = dirID; err = PBHCreate(&pb,false); if (err != noErr && err != dupFNErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not create prefs file:",err); return err; } pb.fileParam.ioNamePtr = prefFileName; pb.fileParam.ioFDirIndex = 0; pb.fileParam.ioVRefNum = vRefNum; pb.fileParam.ioDirID = dirID; pb.ioParam.ioPermssn = fsRdWrPerm; /* want exclusive read-write access */ pb.ioParam.ioMisc = nil; err = PBHOpen(&pb,false); if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not open prefs file:",err); return err; } prefFileRefNum = pb.ioParam.ioRefNum; /* write provided data to the prefs file */ err = SetFPos(prefFileRefNum,fsFromStart,0L); actCount = count; if (err == noErr) { err = FSWrite(prefFileRefNum,&actCount,data); } if (err != noErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not write prefs file:",err); return err; } err = FSClose(prefFileRefNum); (void) SetFileInfo(prefFileName, vRefNum, dirID, 'pref', 'spoo'); return err; } OSErr CopyDataFromPrefsFile(long *count, void **data) { OSErr err; HParamBlockRec pb; short vRefNum = spoolVRefNum; /* set up by InitSpoolFolder */ short prefFileRefNum; long dirID = spoolDirID; long actCount; Ptr prefsData; pb.fileParam.ioCompletion = nil; pb.fileParam.ioNamePtr = prefFileName; pb.fileParam.ioFDirIndex = 0; pb.fileParam.ioVRefNum = vRefNum; pb.fileParam.ioDirID = dirID; pb.ioParam.ioPermssn = fsRdWrPerm; /* want exclusive read-write access */ pb.ioParam.ioMisc = nil; err = PBHOpen(&pb,false); if (err != noErr) { /* note: it's expected that the prefs file won't exist the first time we run */ if (err != fnfErr) { MessageWithIntValue(LOG_ERR_X,"\pCould not open prefs file:",err); } return err; } prefFileRefNum = pb.ioParam.ioRefNum; actCount = *count; prefsData = NewPtrClear(actCount); if (!prefsData) { err = mFulErr; } if (err == noErr) { err = SetFPos(prefFileRefNum,fsFromStart,0L); } if (err == noErr) { err = FSRead(prefFileRefNum,&actCount,prefsData); } err = FSClose(prefFileRefNum); *count = actCount; *data = prefsData; return err; }