Prolog:
XNSPrint is a veneer over the XNS Printing protocol XSIS 118404. An attempt has been made here to totally isolate clients from machine generated code, although you'll probably have to have the BCD around because of the compiler's symbol copying mechanisms.
It may seem a little bizarre at first that things are so complex, such as the status callbacks, but the attempt here is to keep the vulnerabilities of the printers (such as limited calls/sec) isolated from clients while still providing rich and responsive operations. It may take a little extra code, but by using the registration mechanism, you'll be a good network citizen.
Things that can go bump in the nite!
The general idea here is that everything but serviceRetry is pretty disgusting, and should be relayed to your "customer". If you get a serviceRetry, then you probably want to register with the status watcher, and try again when the printer feels better...
Problem:
TYPE ~ {
connection, -- network transport error
file, -- some FS error, such as the named file does not exist
name, -- address database does not contain this host name
protocol, -- fatal, somebody got their spec confused!
service, -- printer (totally) unable to complete request
serviceRetry, -- printer (temporarily) unable to complete request
stream, -- some stream error encountered when shipping data
unknown -- unexpected transport error, and catchall
};
Error: ERROR [problem: Problem, explanation: ROPE];
FIREWALL: ERROR; -- nobody should see and/or catch this one!
An abstract representation of printer "Media"
Media is a little sticky. The print protocol deals with real messy data types, so the idea here is to make things simple for clients. To this end, we traffic here in "AvailableKind"s which are represented by ropes that are registered in an internal symbol table along with the *real* representation for the print protocol, and with the paper sizes (taken from the standards document). Clients should not create kinds of their own, but rather should use values which have validly been returned by some printer, or are well known. A preferred approach is to ask a printer what it can do before creating a master to be printed there...
Media: TYPE ~ LIST OF AvailableKind;
AvailableKind: TYPE ~ ROPE; -- considered opaque, the rope is a convenience
GetPaperDimensions:
PROC [s: AvailableKind]
RETURNS [size: PaperDimensions ¬ [0, 0]];
Return the size of this medium in millimeters.
A length of 0 means roll paper (indefinite length).
A width of 0 will be returned if the medium is not (currently) known.
ListAvailableKinds:
PROC []
RETURNS [list: Media ¬
NIL];
Returns the current list (alphabetized of course). Well know types get registered initially, so before we ever check with a printer, the standard ones get listed. As we acquire more knowledge, the list grows, and grows...
Discovering and monitoring the printer "state"
Properties:
TYPE ~
RECORD [
media: Media,
staple: BOOL,
twoSided: BOOL
];
GetPrinterProperties:
PROC [printer:
ROPE]
RETURNS [service:
ROPE, answer: Properties];
REPORTS [ServiceUnavailable, SystemError, Undefined]
PrinterStatus:
TYPE ~
RECORD [
spooler: SpoolerStatus,
formatter: FormatterStatus,
printer: MarkingEngineStatus,
media: Media
];
GetPrinterStatus:
PROC [printer:
ROPE]
RETURNS [service:
ROPE, answer: PrinterStatus];
REPORTS [ServiceUnavailable, SystemError, Undefined]
StatusWatcherProc:
TYPE ~
PROC [newStatus: PrinterStatus, clientData:
REF, watcher: StatusWatcher];
The watcher here is you, so that you can unregister during the callback
RegisterStatusWatcher:
PROC [printer:
ROPE, update: StatusWatcherProc, clientData:
REF ¬
NIL]
RETURNS [watcher: StatusWatcher];
Adds update to the data base of individuals interested in learning the state of the printer when it next changes. Since there's a race condition here, the initial condition is that a callback will occur at registration time (since the status is initially unknown). It is required that RegisterStatusWatcher persist until the request is unregistered. Update may do the unregister (I promise not to monitor lock this way...).
UnRegisterStatusWatcher:
PROC [watcher: StatusWatcher];
Remove watcher from the data base.
Contextual information for request interpretation:
In order to simply the basic printing operations, and to simplify keeping track of user defined quantities (thru the user profile) requests are serviced with a "Context", which is used to satisfy secondary request options. The basic idea here is that you're probably going to use a context over and over again, so you might just as well hang on to it. If you're not watching for user profile changes you might decide to pay the price to always refresh this information rather than "caching" a copy.
Proper semantics are to always get contexts from GetDefaults[NIL], or refreshing them with GetDefaults[context]. ContextObjects belong to the creator (the person invoking GetDefaults), so the object is yours and you can modify it to your hearts content. An interesting sequence might look like:
context ← GetDefaults[NIL];
context.printerName ← "Quoth"; -- a request to use a special printer
context ← GetDefaults[context]; -- return to the printerName in the user's profile.
Context: TYPE ~ REF ContextObject;
ContextObject:
TYPE ~
RECORD [
copyCount: CARD16 ¬ 1,
mediumHint: AvailableKind ¬ NIL, -- "usLetter"
message: ROPE ¬ NIL,
pageFirst: CARD16 ¬ 0,
pageLast: CARD16 ¬ LAST [CARD16],
printerName: ROPE ¬ NIL,
printObjectCreateDate: BasicTime.GMT ¬ BasicTime.nullGMT,
printObjectName: ROPE ¬ NIL,
printObjectSize: INT ¬ 0,
priorityHint: ROPE ¬ NIL,
recipientName: ROPE ¬ NIL,
releaseKey: CARD16 ¬ 0,
senderName: ROPE ¬ NIL, -- UserCredentials.Get[].name
stapled: BOOL ¬ TRUE,
telephone: ROPE ¬ NIL,
twoSided: BOOL ¬ TRUE
];
GetDefaults:
PROC [context: Context ¬
NIL]
RETURNS [newContext: Context];
decorates ContextObject from current user's defaults. Supplying NIL causes a context to be allocated, otherwise, its content is altered.
Making and monitoring print requests
StatusChangedProc:
TYPE ~
PROC [request: PrintRequest, clientData:
REF];
The request may be used to unregister at callback time.
PrintFromFile:
PROC [file:
ROPE, context: Context, update: StatusChangedProc ¬
NIL, clientData:
REF ¬
NIL]
RETURNS [request: PrintRequest];
REPORTS [ServiceUnavailable, SystemError, Undefined]
PrintFromStream:
PROC [s:
IO.
STREAM, context: Context, update: StatusChangedProc ¬
NIL, clientData:
REF ¬
NIL]
RETURNS [request: PrintRequest];
REPORTS [ServiceUnavailable, SystemError, Undefined]
If update is non-NIL, then RegisterPrintRequest will be invoked. All of the commentary below on RegisterPrintRequest applies.
RequestStatus:
TYPE ~
RECORD [
status: InterpressMasterStatus,
statusMessage: ROPE
];
GetPrintRequestStatus:
PROC [request: PrintRequest]
RETURNS [status: RequestStatus];
REPORTS [ServiceUnavailable, SystemError, Undefined]
RegisterPrintRequest:
PROC [request: PrintRequest, update: StatusChangedProc, clientData:
REF ¬
NIL];
Add request to the data base of pending requests for possible use by other applications. If update is non-NIL, then status updates will be performed, and the StatusChangedProc will be invoked (asynchronously) when lastStatus changes; lastStatus is initialized in such a way as to have the first status probe generate a callback. This requires that StatusChangedProc persist until the request is unregistered. Update may do the unregister (I promise not to monitor lock this way...).
UnRegisterPrintRequest:
PROC [request: PrintRequest];
Remove request from the data base of pending requests.
Private Stuff, those not faint of heart can read on...
StatusWatcher Objects belong to the implementation. It's considered stretching the rules to be poking around inside.
StatusWatcherList: TYPE ~ LIST OF StatusWatcher;
StatusWatcher: TYPE ~ REF StatusWatcherObject;
StatusWatcherObject:
TYPE ~
RECORD [
clientData: REF, -- must be NIL'ed out during UnRegisterStatusWatcher[]
lastStatus: PrinterStatus,
update: StatusWatcherProc
];
WatcherListChanged:
CONDITION;
Notified whenever someone either registers or unregisters a request
GetWatcherList:
PROC
RETURNS [list: PrintRequestList];
Atomically return the data base of pending requests. The intent is that it will be (almost) immediately traversed. Updates will happen behind the scenes, so this represents some state from the past. Safe Storage provides adequate guarantees for this.
PrintRequestObjects have some internal constraints depending upon how this interface is implemented. Considering this, you should probably consider them immutable, unless of course you're sure that you own them. The intent is to capture all of the state required external to the implementation so that clients can examine the values, and to make debugging and/or exception handing easier along with overloading to avoid another totally private data type.
PrintRequestList: TYPE ~ LIST OF PrintRequest;
PrintRequest: TYPE ~ REF PrintRequestObject;
PrintRequestObject:
TYPE ~
RECORD [
clientData: REF, -- must be NIL'ed out during UnRegisterPrintRequest[]
context: Context, -- a copy of the ContextObject used to create this PrintRequestObject
update: StatusChangedProc,
distinguishedName: ROPE,
requestID: Printing.RequestID,
lastStatus: RequestStatus,
attributes: Printing.PrintAttributes,
options: Printing.PrintOptions,
fileName: ROPE,
ipMasterStream: IO.STREAM
];
RequestListChanged:
CONDITION;
Notified whenever someone either registers or unregisters a request
GetPrintRequestList:
PROC
RETURNS [list: PrintRequestList];
Atomically return the data base of pending requests. The intent is that it will be (almost) immediately traversed. Updates will happen behind the scenes, so this represents some state from the past. Safe Storage provides adequate guarantees for this.
CreateAvailableKind: PROC [s: Printing.KnownPaperSize]
RETURNS [kind: AvailableKind];
CreateAvailableKindOther: PROC [size: PaperDimensions]
RETURNS [kind: AvailableKind];
CreateMedium: PROC [s: AvailableKind]
RETURNS [medium: Printing.Medium];
MungeMedia: PROC [s: Printing.Media]
RETURNS [media: Media];
Insert: PROC [key: ROPE, medium: Printing.Medium, size: PaperDimensions];
CreateAttributes: PROC [context: Context]
RETURNS [attributes: Printing.PrintAttributes];
CreateOptions: PROC [context: Context]
RETURNS [options: Printing.PrintOptions];
CreatePrintRequest: PROC [context: Context, copyContext: BOOL ¬ TRUE]
RETURNS [request: PrintRequest];
MungeProperties: PROC [s: Printing.PrinterProperties]
RETURNS [properties: Properties];
MungePrinterStatus: PROC [s: Printing.PrinterStatus]
RETURNS [printerstatus: PrinterStatus];
MungeRequestStatus: PROC [s: Printing.RequestStatus]
RETURNS [requeststatus: RequestStatus];
}...