AmendHub

Download

thecloud

/

SimpleSpooler

/

SpoolFiles.c

 

(View History)

thecloud   Simple Spooler 0.1 release Latest amendment: 1 on 2026-07-29

1 /*
2 * SpoolFiles.c
3 *
4 * Routines for handling spool files and folders.
5 *
6 * Written by: Ken McLeod, 2026-01-14
7 * Last update: 2026-01-31
8 *
9 * This software is provided as-is, with no warranties expressed or implied,
10 * under the terms of the BSD 2-Clause License. See separate "LICENSE" file.
11 */
12
13 #include <Types.h>
14 #include <Errors.h>
15 #include <Events.h>
16 #include <Files.h>
17 #include <Folders.h>
18 #include <SysEqu.h>
19 #include <GestaltEqu.h>
20 #include <Memory.h>
21 #include <OSUtils.h>
22 #include <Packages.h>
23 #include <Resources.h>
24 #include <StdIO.h>
25 #include <String.h>
26 #include "Logging.h"
27 #include "SpoolFiles.h"
28
29 /* set if we should save each incoming data packet length */
30 #define USING_CHUNK_LENGTHS 0
31
32 char *prefFileName = "\pSimple Spooler Prefs";
33 char *spoolFolderName = "\pSimple Spooler";
34
35 char spoolFileName[256];
36 short spoolFileRefNum;
37 short spoolVRefNum;
38 long spoolDirID;
39
40 char *spoolFilePrefixes[] = {
41 "??", /* 0 */
42 "LW", /* 1 */
43 "LQ", /* 2 */
44 "IW", /* 3 */
45 };
46
47 int NumToolboxTraps(void)
48 {
49 if (NGetTrapAddress(0xA86E /* _InitGraf */, ToolTrap) ==
50 NGetTrapAddress(0xAA6E, ToolTrap)) {
51 return 0x200;
52 }
53 return 0x400;
54 }
55
56 TrapType GetTrapType(int theTrap)
57 {
58 if ((theTrap & 0x0800) > 0) {
59 return ToolTrap;
60 }
61 return OSTrap;
62 }
63
64 Boolean TrapAvailable(int theTrap)
65 {
66 int trap = theTrap;
67 TrapType tType = GetTrapType(theTrap);
68 if (tType == ToolTrap) {
69 trap = theTrap & 0x07FF;
70 if (trap >= NumToolboxTraps) {
71 trap = 0xA89F; /* _Unimplemented */
72 }
73 }
74 return (NGetTrapAddress(trap, tType) !=
75 NGetTrapAddress(0xA89F, ToolTrap)); /* _Unimplemented */
76 }
77
78 Boolean GestaltAvailable(void)
79 {
80 return TrapAvailable(0xA1AD); /* _Gestalt */
81 }
82
83 /* MyFindFolder is an adaptation of the FindSysFolder function
84 * from DTS Utilities sample code on Dev CD Mar '94. It should
85 * work even if Gestalt is not available by returning the
86 * system folder for any folders normally in the system folder.
87 */
88 OSErr MyFindFolder(OSType folderType,short *foundVRefNum,long *foundDirID)
89 {
90 long gesResponse;
91 SysEnvRec envRec;
92 WDPBRec myWDPB;
93 unsigned char volName[34];
94 OSErr err;
95
96 *foundVRefNum = 0;
97 *foundDirID = 0;
98 if (GestaltAvailable() &&
99 !Gestalt(gestaltFindFolderAttr, &gesResponse) &&
100 (gesResponse & (1<<gestaltFindFolderPresent))) { /* Does Folder Manager exist? */
101 err = FindFolder(kOnSystemDisk, folderType, kDontCreateFolder,
102 foundVRefNum, foundDirID);
103 } else {
104 /* Gestalt can't give us the answer, so we resort to SysEnvirons */
105 if (!(err = SysEnvirons(curSysEnvVers, &envRec))) {
106 myWDPB.ioVRefNum = envRec.sysVRefNum;
107 volName[0] = '\000'; /* Zero volume name */
108 myWDPB.ioNamePtr = volName;
109 myWDPB.ioWDIndex = 0;
110 myWDPB.ioWDProcID = 0;
111 if (!(err = PBGetWDInfo(&myWDPB, 0))) {
112 *foundVRefNum = myWDPB.ioWDVRefNum;
113 *foundDirID = myWDPB.ioWDDirID;
114 }
115 }
116 }
117 return err;
118 }
119
120 /* GetDirID is an adaptation of the GetDirID function from the
121 * MoreFilesExtras sample code on Dev CD Mar '94. (Thanks, Jim!)
122 */
123 OSErr GetDirID(short vRefNum,long dirID,char *name,long *theDirID,Boolean *isDir)
124 {
125 OSErr err;
126 CInfoPBRec pb;
127 pb.hFileInfo.ioNamePtr = name;
128 pb.hFileInfo.ioVRefNum = vRefNum;
129 pb.hFileInfo.ioDirID = dirID;
130 pb.hFileInfo.ioFDirIndex = 0; /* use ioNamePtr and ioDirID */
131 err = PBGetCatInfoSync(&pb);
132 *theDirID = pb.hFileInfo.ioDirID;
133 *isDir = (pb.hFileInfo.ioFlAttrib & 0x10) != 0;
134 return err;
135 }
136
137 OSErr InitSpoolFolder(void)
138 {
139 /* create our spool directory inside Preferences if we have it,
140 * otherwise in the System Folder.
141 */
142 short myVRefNum;
143 long myDirID;
144 OSErr err = MyFindFolder(kPreferencesFolderType,&myVRefNum,&myDirID);
145 if (err == noErr) {
146 HParamBlockRec pb;
147 char nameBuf[34];
148 BlockMove(spoolFolderName,nameBuf,15);
149 pb.fileParam.ioCompletion = nil;
150 pb.fileParam.ioNamePtr = nameBuf;
151 pb.fileParam.ioVRefNum = myVRefNum;
152 pb.fileParam.ioDirID = myDirID;
153 err = PBDirCreate(&pb,false);
154 if (err != noErr && err != dupFNErr) {
155 MessageWithIntValue(LOG_ERR_X,"\pCould not create spool folder:",err);
156 return err;
157 } else {
158 Boolean isDir;
159 long theDirID;
160 BlockMove(spoolFolderName,nameBuf,15);
161 err = GetDirID(myVRefNum,myDirID,nameBuf,&theDirID,&isDir);
162 if (err != noErr || !isDir) {
163 MessageWithIntValue(LOG_ERR_X,"\pCould not get spool folder dirID:",err);
164 return false;
165 }
166 spoolVRefNum = myVRefNum;
167 spoolDirID = theDirID;
168 }
169 } else {
170 MessageWithIntValue(LOG_ERR_X,"\pCould not locate preference folder:",err);
171 }
172 return err;
173 }
174
175 OSErr GetSpoolFolder(short *vRefNum, long *dirID)
176 {
177 if (vRefNum) { *vRefNum = spoolVRefNum; }
178 if (dirID) { *dirID = spoolDirID; }
179 return noErr;
180 }
181
182 #if FILENAME_USES_TICK_COUNT
183 void NewSpoolFilename(int printerType, char *nameBuf)
184 {
185 /* creates a new filename in the provided 34-byte buffer,
186 * in the form <typePrefix>-<curTicks>, e.g. "IW-6778001"
187 */
188 char tickString[256];
189 long curTicks = TickCount();
190 int len = (int)(strlen(spoolFilePrefixes[printerType]) & 0xFF);
191 BlockMove(&spoolFilePrefixes[printerType][0], &nameBuf[nameBuf[0]+1], len);
192 nameBuf[0] = len;
193 BlockMove("-",&nameBuf[nameBuf[0]+1],1);
194 nameBuf[0] += 1;
195 NumToString(curTicks,tickString);
196 len = 31 - (1 + nameBuf[0] + 1);
197 if ((short)tickString[0] > len) {
198 tickString[0] = len & 0xFF;
199 }
200 BlockMove(&tickString[1],&nameBuf[nameBuf[0]+1],len);
201 nameBuf[0] += tickString[0];
202 }
203 #else
204 void NewSpoolFilename(int printerType, char *nameBuf)
205 {
206 /* creates a new filename in the provided 34-byte buffer,
207 * in the form <typePrefix>-YYYYMMDD-HHMMSS, e.g. "IW-20260121-092756"
208 */
209 char tmpStr[34];
210 DateTimeRec dtRec;
211 int len;
212 unsigned long seconds;
213 GetDateTime(&seconds);
214 Secs2Date(seconds,&dtRec);
215
216 len = (int)(strlen(spoolFilePrefixes[printerType]) & 0xFF);
217 BlockMove(&spoolFilePrefixes[printerType][0], &nameBuf[nameBuf[0]+1], len);
218 nameBuf[0] = len;
219 sprintf(tmpStr,"-%d%02d%02d-%02d%02d%02d",
220 dtRec.year,dtRec.month,dtRec.day,
221 dtRec.hour,dtRec.minute,dtRec.second);
222 len = strlen(tmpStr);
223 BlockMove(tmpStr,&nameBuf[nameBuf[0]+1],len);
224 nameBuf[0] += len;
225 }
226 #endif
227
228 OSErr OpenSpoolFile(int printerType)
229 {
230 /* creates a new spool file in the spool directory,
231 * using printer type as the prefix for the filename,
232 * and opens it for writing.
233 * Note: InitSpoolFolder must have been called first.
234 * The file's fRefNum is stored in spoolFileRefNum.
235 */
236 HParamBlockRec pb;
237 short vRefNum = spoolVRefNum; /* set up by InitSpoolFolder */
238 long dirID = spoolDirID;
239 OSErr err;
240
241 NewSpoolFilename(printerType,spoolFileName);
242 MessageWithQuotedString(LOG_ERR,"\pSpooling to file",spoolFileName);
243
244 pb.fileParam.ioCompletion = nil;
245 pb.fileParam.ioFDirIndex = 0;
246 pb.fileParam.ioNamePtr = spoolFileName;
247 pb.fileParam.ioVRefNum = vRefNum;
248 pb.fileParam.ioDirID = dirID;
249 err = PBHCreate(&pb,false);
250 if (err != noErr && err != dupFNErr) {
251 MessageWithIntValue(LOG_ERR_X,"\pCould not create spool file:",err);
252 return err;
253 }
254 pb.fileParam.ioNamePtr = spoolFileName;
255 pb.fileParam.ioFDirIndex = 0;
256 pb.fileParam.ioVRefNum = vRefNum;
257 pb.fileParam.ioDirID = dirID;
258 pb.ioParam.ioPermssn = fsRdWrPerm; /* want exclusive read-write access */
259 pb.ioParam.ioMisc = nil;
260 err = PBHOpen(&pb,false);
261 if (err != noErr) {
262 MessageWithIntValue(LOG_ERR_X,"\pCould not open spool file:",err);
263 return err;
264 }
265 spoolFileRefNum = pb.ioParam.ioRefNum;
266 return err;
267 }
268
269 OSErr WriteDataToSpoolFile(long count, void *data)
270 {
271 OSErr err = noErr;
272 if (!spoolFileRefNum) {
273 err = fnOpnErr; /* no spool file open! */
274 }
275 #if USING_CHUNK_LENGTHS
276 if (err == noErr) {
277 /* the length of each data chunk comes from atpActCount,
278 * which is defined as a short integer (16 bits).
279 * ergo, we only have to store lengths as 2 bytes.
280 * Note this is in network byte order on 68k/PPC.
281 */
282 short chunkLength = (short)(count & 0x0000FFFF);
283 long chunkLengthCount = 2;
284 err = FSWrite(spoolFileRefNum,&chunkLengthCount,&chunkLength);
285 }
286 #endif
287 if (err == noErr) {
288 long chunkCount = count;
289 err = FSWrite(spoolFileRefNum,&chunkCount,data);
290 }
291 if (err != noErr) {
292 MessageWithIntValue(LOG_ERR_X,"\pCould not write data:", err);
293 }
294 return err;
295 }
296
297 OSErr SetFileInfo(char *fNameBuf, short vRefNum, long dirID,
298 long fType, long fCreator)
299 {
300 OSErr err;
301 HParamBlockRec pb;
302 pb.fileParam.ioCompletion = nil;
303 pb.fileParam.ioNamePtr = fNameBuf;
304 pb.fileParam.ioVRefNum = vRefNum;
305 pb.fileParam.ioDirID = dirID;
306 pb.fileParam.ioFDirIndex = 0;
307 err = PBHGetFInfo(&pb,false);
308 if (err != noErr) {
309 MessageWithIntValue(LOG_WARN_X,"\pCould not get file's FInfo:",err);
310 return err;
311 }
312 pb.fileParam.ioCompletion = nil;
313 pb.fileParam.ioNamePtr = fNameBuf;
314 pb.fileParam.ioVRefNum = vRefNum;
315 pb.fileParam.ioDirID = dirID;
316 pb.fileParam.ioFDirIndex = 0;
317 pb.fileParam.ioFlFndrInfo.fdType = fType;
318 pb.fileParam.ioFlFndrInfo.fdCreator = fCreator;
319 pb.fileParam.ioFlFndrInfo.fdFlags &= ~(1<<8); /* clear "inited" bit */
320 err = PBHSetFInfo(&pb,false);
321 if (err != noErr) {
322 MessageWithIntValue(LOG_WARN_X,"\pCould not set file's FInfo:",err);
323 }
324 return err;
325 }
326
327 OSErr MakeIWEmPostscriptFile(void)
328 {
329 OSErr err;
330 HParamBlockRec pb;
331 char psFileName[34];
332 char IWEmFileName[34];
333 short IWEmFileRefNum;
334 short psFileRefNum;
335 short vRefNum = spoolVRefNum; /* set up by InitSpoolFolder */
336 long dirID = spoolDirID;
337 long count;
338 char buf[1024];
339 err = noErr;
340 if (!(spoolFileName[0] > 2 && spoolFileName[3] == '-' &&
341 ((spoolFileName[1] == 'I' && spoolFileName[2] == 'W') ||
342 (spoolFileName[1] == 'L' && spoolFileName[2] == 'Q')))) {
343 return err; /* not a file starting with 'LQ-' or 'IW-' so ignore it */
344 }
345 BlockMove("\pIWEm",IWEmFileName,5);
346 BlockMove(spoolFileName,psFileName,29); /* copy 28 bytes max, plus length byte */
347 BlockMove(".ps",&psFileName[psFileName[0]+1],3); /* add .ps extension */
348 psFileName[0] += 3;
349
350 /* try to open a file named IWEm in the spool directory */
351 pb.fileParam.ioNamePtr = IWEmFileName;
352 pb.fileParam.ioFDirIndex = 0;
353 pb.fileParam.ioVRefNum = vRefNum;
354 pb.fileParam.ioDirID = dirID;
355 pb.ioParam.ioPermssn = fsRdPerm; /* read access */
356 pb.ioParam.ioMisc = nil;
357 err = PBHOpen(&pb,false);
358 if (err != noErr) {
359 return err; /* file doesn't exist or otherwise can't be used */
360 }
361 IWEmFileRefNum = pb.ioParam.ioRefNum;
362
363 /* create and open the output PS file */
364 pb.fileParam.ioCompletion = nil;
365 pb.fileParam.ioFDirIndex = 0;
366 pb.fileParam.ioNamePtr = psFileName;
367 pb.fileParam.ioVRefNum = vRefNum;
368 pb.fileParam.ioDirID = dirID;
369 err = PBHCreate(&pb,false);
370 if (err != noErr && err != dupFNErr) {
371 MessageWithIntValue(LOG_ERR_X,"\pCould not create PostScript file:",err);
372 return err;
373 }
374 pb.fileParam.ioNamePtr = psFileName;
375 pb.fileParam.ioFDirIndex = 0;
376 pb.fileParam.ioVRefNum = vRefNum;
377 pb.fileParam.ioDirID = dirID;
378 pb.ioParam.ioPermssn = fsRdWrPerm; /* want exclusive read-write access */
379 pb.ioParam.ioMisc = nil;
380 err = PBHOpen(&pb,false);
381 if (err != noErr) {
382 MessageWithIntValue(LOG_ERR_X,"\pCould not open PostScript file:",err);
383 return err;
384 }
385 psFileRefNum = pb.ioParam.ioRefNum;
386
387 MessageWithQuotedString(LOG_WARN,"\pWriting PostScript file",psFileName);
388
389 /* write IWEm file to the output file */
390 err = SetFPos(IWEmFileRefNum,fsFromStart,0L);
391 while (!err) {
392 count = sizeof(buf);
393 err = FSRead(IWEmFileRefNum,&count,buf);
394 if (err == noErr || (err == eofErr && count > 0)) {
395 err = FSWrite(psFileRefNum,&count,buf);
396 }
397 }
398 err = FSClose(IWEmFileRefNum);
399
400 /* read data chunks from the spool file and write to output file */
401 err = SetFPos(spoolFileRefNum,fsFromStart,0L);
402 while (!err) {
403 #if USING_CHUNK_LENGTHS
404 short int chunkLengthData;
405 count = 2;
406 err = FSRead(spoolFileRefNum,&count,&chunkLengthData);
407 if (err == noErr) {
408 count = chunkLengthData;
409 err = FSRead(spoolFileRefNum,&count,buf);
410 if (err == noErr || (err == eofErr && count > 0)) {
411 err = FSWrite(psFileRefNum,&count,buf);
412 }
413 }
414 #else
415 count = sizeof(buf);
416 err = FSRead(spoolFileRefNum,&count,buf);
417 if (err == noErr || (err == eofErr && count > 0)) {
418 err = FSWrite(psFileRefNum,&count,buf);
419 }
420 #endif
421 }
422 err = FSClose(psFileRefNum);
423 (void) SetFileInfo(psFileName, vRefNum, dirID, 'TEXT', 'KAHL');
424 return err;
425 }
426
427 OSErr FinalizeSpoolFile(void)
428 {
429 /* Once all the data that's going to be written has been written,
430 * we call this to set the file's FInfo. This is one way to tell
431 * later whether the spool file is "good" or incomplete.
432 */
433 OSErr err;
434 ParamBlockRec pb;
435 if (!spoolFileRefNum) {
436 return noErr; /* nothing to do */
437 }
438 pb.fileParam.ioCompletion = nil;
439 pb.fileParam.ioFRefNum = spoolFileRefNum;
440 err = PBFlushFile(&pb,false);
441 if (err != noErr) {
442 MessageWithIntValue(LOG_ERR_X,"\pCould not flush spool file to disk:",err);
443 return err;
444 }
445 (void) SetFileInfo(spoolFileName, spoolVRefNum, spoolDirID, 'pjob', 'spoo');
446 return err;
447 }
448
449 OSErr CloseSpoolFile(long int numBytesWritten)
450 {
451 OSErr err;
452 if (!spoolFileRefNum) {
453 return noErr; /* nothing to do */
454 }
455 if (numBytesWritten > 0) {
456 (void) FinalizeSpoolFile();
457 (void) MakeIWEmPostscriptFile();
458 }
459 err = FSClose(spoolFileRefNum);
460 spoolFileRefNum = 0;
461 spoolFileName[0] = 0;
462 return err;
463 }
464
465 OSErr SaveDataToPrefsFile(long count, void *data)
466 {
467 OSErr err;
468 HParamBlockRec pb;
469 short vRefNum = spoolVRefNum; /* set up by InitSpoolFolder */
470 short prefFileRefNum;
471 long dirID = spoolDirID;
472 long actCount;
473
474 pb.fileParam.ioCompletion = nil;
475 pb.fileParam.ioFDirIndex = 0;
476 pb.fileParam.ioNamePtr = prefFileName;
477 pb.fileParam.ioVRefNum = vRefNum;
478 pb.fileParam.ioDirID = dirID;
479 err = PBHCreate(&pb,false);
480 if (err != noErr && err != dupFNErr) {
481 MessageWithIntValue(LOG_ERR_X,"\pCould not create prefs file:",err);
482 return err;
483 }
484 pb.fileParam.ioNamePtr = prefFileName;
485 pb.fileParam.ioFDirIndex = 0;
486 pb.fileParam.ioVRefNum = vRefNum;
487 pb.fileParam.ioDirID = dirID;
488 pb.ioParam.ioPermssn = fsRdWrPerm; /* want exclusive read-write access */
489 pb.ioParam.ioMisc = nil;
490 err = PBHOpen(&pb,false);
491 if (err != noErr) {
492 MessageWithIntValue(LOG_ERR_X,"\pCould not open prefs file:",err);
493 return err;
494 }
495 prefFileRefNum = pb.ioParam.ioRefNum;
496
497 /* write provided data to the prefs file */
498 err = SetFPos(prefFileRefNum,fsFromStart,0L);
499 actCount = count;
500 if (err == noErr) {
501 err = FSWrite(prefFileRefNum,&actCount,data);
502 }
503 if (err != noErr) {
504 MessageWithIntValue(LOG_ERR_X,"\pCould not write prefs file:",err);
505 return err;
506 }
507 err = FSClose(prefFileRefNum);
508 (void) SetFileInfo(prefFileName, vRefNum, dirID, 'pref', 'spoo');
509 return err;
510 }
511
512 OSErr CopyDataFromPrefsFile(long *count, void **data)
513 {
514 OSErr err;
515 HParamBlockRec pb;
516 short vRefNum = spoolVRefNum; /* set up by InitSpoolFolder */
517 short prefFileRefNum;
518 long dirID = spoolDirID;
519 long actCount;
520 Ptr prefsData;
521
522 pb.fileParam.ioCompletion = nil;
523 pb.fileParam.ioNamePtr = prefFileName;
524 pb.fileParam.ioFDirIndex = 0;
525 pb.fileParam.ioVRefNum = vRefNum;
526 pb.fileParam.ioDirID = dirID;
527 pb.ioParam.ioPermssn = fsRdWrPerm; /* want exclusive read-write access */
528 pb.ioParam.ioMisc = nil;
529 err = PBHOpen(&pb,false);
530 if (err != noErr) {
531 /* note: it's expected that the prefs file won't exist the first time we run */
532 if (err != fnfErr) {
533 MessageWithIntValue(LOG_ERR_X,"\pCould not open prefs file:",err);
534 }
535 return err;
536 }
537 prefFileRefNum = pb.ioParam.ioRefNum;
538
539 actCount = *count;
540 prefsData = NewPtrClear(actCount);
541 if (!prefsData) {
542 err = mFulErr;
543 }
544 if (err == noErr) {
545 err = SetFPos(prefFileRefNum,fsFromStart,0L);
546 }
547 if (err == noErr) {
548 err = FSRead(prefFileRefNum,&actCount,prefsData);
549 }
550 err = FSClose(prefFileRefNum);
551 *count = actCount;
552 *data = prefsData;
553
554 return err;
555 }