Copyright Ó 1984, 1985, 1991, 1992 by Xerox Corporation. All rights reserved.
File: ARAccessImpl.mesa - last edit:
BJD  15-Jul-85 12:58:26
RSF  25-Sep-85 14:19:36
Pam  10-Jan-85 17:10:44
JCS   2-Aug-85 15:52:29
rlc  24-Jul-87 17:31:11
Philip James, March 20, 1991 5:04 pm PST
DIRECTORY
AdobeIO USING [LocalAndDomain],
AdobeOps USING [ARSystemHandle],
ARAccess USING [ARStorage, DataTable, ErrorCode],
Auth USING [CopyIdentity, FreeIdentity, IdentityHandle],
Convert USING [RopeFromCard],
Courier USING [Error, ErrorCode],
Environment USING [Block, bytesPerPage],
IO USING [Close, EndOfStream, Error, GetChar, STREAM],
MSegment USING [Address, Create, Handle, Kill],
NSAssignedTypes USING [tText],
NSDataStream USING [
Abort, Aborted, Handle, SinkStream, Source, SourceStream],
NSFile USING [
Attribute, AttributesRecord, ChangeControls, ClearAttributes,
Close, Error, ErrorRecord, fullAccess, GetAttributes, Handle, ID,
Logoff, LogonDirect, nullHandle, nullID, nullSession, nullString,
Open, OpenByName, Probe, readAccess, Reference, Retrieve,
Selections, ServiceRecord, Session, Source, Store, String],
NSName USING [ClearName, CopyNameFields, String],
NSString USING [
CopyString, FreeString, String, StringFromMesaString, SubString],
PFS USING [AbsoluteName, Close, Error, ErrorDesc, Open, OpenFile, PATH, PathFromRope, StreamFromOpenFile],
Process USING [Abort, Detach, Pause, Seconds, SecondsToTicks],
RefText USING [AppendChar, ObtainScratch, ReleaseScratch],
Rope USING [Cat, Concat, FromRefText, Length, ROPE, Text];
Stream USING [
Block, CompletionCode, defaultInputOptions, Delete, EndOfStream,
Handle, SubSequenceType],
String USING [AppendChar, AppendLongNumber, AppendString];
ARAccessImpl: CEDAR MONITOR --LOCKS session USING session: Session
IMPORTS AdobeIO, Convert, IO, RefText, Rope, PFS
--Auth, Courier, MSegment, NSDataStream, NSFile, NSName, NSString,
--Process, Stream, String
EXPORTS ARAccess =
BEGIN OPEN ARAccess;
-- -- -- -- -- -- --
TYPES & CONSTANTS :
-- -- -- -- -- -- --
Session: TYPE = AdobeOps.ARSystemHandle;
Session: TYPE = REF SessionObject;
SessionObject: PUBLIC TYPE = MONITORED RECORD [
sessionPassword: CARDINAL ← SessionPassword,
z: UNCOUNTED ZONENIL,
nsFileSession: NSFile.Session ← NSFile.nullSession,
referenceToARs: NSFile.Reference ← [
fileID: NSFile.nullID, service: NIL],
dirH: NSFile.Handle ← NSFile.nullHandle,
userId: Auth.IdentityHandle ← NIL,
buffer: MSegment.Handle ← NIL,
probingProcess: PROCESSNIL,
sessionError: BOOLEANFALSE];
ARHandle: TYPE = REF ARObject;
ARObject: PUBLIC TYPE = RECORD [
password: CARDINAL ¬ ARPassword,
parentID: NSFile.ID ← NSFile.nullID,
fH: PFS.OpenFile ¬ NIL, --NSFile.Handle ¬ NSFile.nullHandle,
sH: IO.STREAM ¬ NIL,
name: Rope.ROPE ¬ NIL, --NSFile.String ¬ NSFile.nullString,
number: CARDINAL ¬ LAST[CARDINAL],
checkedOut: BOOLEAN ¬ FALSE];
CommandType: TYPE = {get, examine, checkout, checkin, submit, null};
SessionPassword: CARDINAL = 567B;
ARPassword: CARDINAL = 234B;
maxFileName: CARDINAL = 100;
maxDigits: CARDINAL = 5; -- constant length of an AR filename
secondsToWaitForCheckedOutAR: CARD --Process.Seconds-- = 0;
-- -- -- -- -- -- --
PUBLIC PROCEDURES :
-- -- -- -- -- -- --
Error: PUBLIC ERROR [session: Session, why: ErrorCode] = CODE;
Create: PUBLIC PROCEDURE [
zone: UNCOUNTED ZONE, arLocation: NSFile.Reference]
RETURNS [session: Session] =
BEGIN
session ← NEW[
SessionObject ← [
z: zone,
referenceToARs: [
fileID: arLocation.fileID,
service: NEW[
NSFile.ServiceRecord ← [
systemElement: arLocation.service.systemElement]]]]];
NSName.CopyNameFields[
z: zone, source: @arLocation.service.name,
destination: @session.referenceToARs.service.name];
END;
Destroy: PUBLIC PROCEDURE [session: Session] =
BEGIN
z: UNCOUNTED ZONE;
Logoff[session];
z ← session.z; --wait to extract z til here in case session=NIL
IF session.userId # NIL THEN
Auth.FreeIdentity[identityPtr: @session.userId, z: session.z];
IF session.referenceToARs.service # NIL THEN {
NSName.ClearName[z, @session.referenceToARs.service.name];
z.FREE[@session.referenceToARs.service]};
z.FREE[@session]
END;
Logon: PUBLIC PROCEDURE [
session: Session, user: Auth.IdentityHandle] =
BEGIN
ValidateSession[session];
IF session.userId # NIL THEN
Auth.FreeIdentity[identityPtr: @session.userId, z: session.z];
session.userId ← Auth.CopyIdentity[identity: user, z: session.z];
END;
Logoff: PUBLIC PROCEDURE [session: Session] =
BEGIN
ValidateSession[session];
IF session.nsFileSession # NSFile.nullSession THEN
IF session.probingProcess # NIL THEN
ERROR Error[session, currentlyCheckedOut]
ELSE CloseFileSession[session];
END;
GetAR: PUBLIC PROCEDURE [session: Session, arNumber: CARDINAL]
RETURNS [arH: ARHandle] =
Holds open file handle within open file session;
BEGIN
getAR: PROC = {
arH ¬ NEW[ARObject ¬ [
fH: OpenFileHandleOnAR[session, arNumber], number: arNumber]];
};
CatchErrors[session, getAR, get];
RETURN[arH]
END;
OpenFileHandleOnAR: PROCEDURE [
session: Session, arNumber: CARDINAL]
RETURNS [fH: PFS.OpenFile --NSFile.Handle--] = {
arFileName: Rope.ROPE ¬ NIL; --[maxFileName];
dH: PFS.PATH ¬ OpenSubDirectory[session, arNumber];
fH ¬ PFS.Open[
name: PFS.AbsoluteName[ARNumberToFileName[arNumber], dH]];
fH ← NSFile.OpenByName[
directory: dH, path: ARNumberToFileName[arNumber, arFileName],
session: session.nsFileSession,
controls: [
lock: share, access: NSFile.readAccess,
timeout: secondsToWaitForCheckedOutAR]];
** with current impl of OpenSubDirectory, for now don't close;
-- CloseFile[@dH, session] -- };
FreeAR: PUBLIC PROCEDURE [session: Session, arH: ARHandle]
RETURNS [nil: ARHandle] = {
ValidateSession[session];
ValidateAR[arH, session];
IF arH.checkedOut THEN ERROR Error[session, youMustFirstCheckIn];
CloseFile[arH--@arH.fH--, session];
session.z.FREE[@arH];
RETURN[NIL];
};
ExamineAR: PUBLIC PROCEDURE [session: Session, arH: ARHandle] RETURNS [to: ARAccess.ARStorage] = {
Sink: PROC [source: ARHandle] = {
ENABLE
UNWIND => IO.Close[source.sH];
to ¬ CopyAR[session: session, from: source ! UNWIND => IO.Close[source.sH]];
};
examineAR: PROC = {
Sink[arH];
CloseFile[arH, session];
};
CatchErrors[session, examineAR, examine]
};
ExamineAR: PUBLIC PROCEDURE [session: Session, arH: ARHandle, to: ARAccess.ARStorage] =
BEGIN
Sink: PROC [source: NSDataStream.SourceStream] =
BEGIN
BEGIN
ENABLE {
NSDataStream.Aborted => CONTINUE;
UNWIND => Stream.Delete[source]};
[] ← CopyAR[
session: session, from: source, to: to !
UNWIND =>
NSDataStream.Abort[
source ! NSDataStream.Aborted => CONTINUE]];
END;
Stream.Delete[source ! NSDataStream.Aborted => CONTINUE]
END;
examineAR: PROC = {
NSFile.Retrieve[arH.fH, [proc[Sink]], session.nsFileSession];
CloseFile[arH --@arH.fH--, session];
};
ValidateAR[arH, session];
CatchErrors[session, examineAR, examine]
END;
CheckOutAR: PUBLIC PROCEDURE [
session: Session, arH: ARHandle, to: ARAccess.ARStorage --Stream.Handle--] = {
Sink: PROC [source: NSDataStream.SourceStream] =
BEGIN
BEGIN
ENABLE {
NSDataStream.Aborted => CONTINUE;
UNWIND => Stream.Delete[source]};
[] ← CopyAR[
session: session, from: source, to: to !
UNWIND =>
NSDataStream.Abort[
source ! NSDataStream.Aborted => CONTINUE]];
END;
Stream.Delete[source ! NSDataStream.Aborted => CONTINUE]
END;
checkOutAR: PROC = {
attributesRecord: NSFile.AttributesRecord;
selections: NSFile.Selections ← NSFile.Selections[
interpreted: [parentID: TRUE, name: TRUE]];
NSFile.ChangeControls[
file: arH.fH, session: session.nsFileSession,
controlSelections: [lock: TRUE, access: TRUE],
controls: [lock: exclusive, access: NSFile.fullAccess]];
NSFile.Retrieve[arH.fH, [proc[Sink]], session.nsFileSession];
NSFile.GetAttributes[
arH.fH, selections, @attributesRecord, session.nsFileSession];
arH.parentID ← attributesRecord.parentID;
arH.name ← NSString.CopyString[
session.z, attributesRecord.name];
NSFile.ClearAttributes[@attributesRecord];
};
ValidateAR[arH, session];
CatchErrors[session, checkOutAR, checkout];
arH.checkedOut ← TRUE;
};
CheckInAR: PUBLIC PROCEDURE [
session: Session, arH: ARHandle, from: Stream.Handle] =
BEGIN
Source: PROC [sink: NSDataStream.SinkStream] =
BEGIN
BEGIN
ENABLE {
UNWIND => Stream.Delete[sink];
NSDataStream.Aborted => CONTINUE};
[] ← CopyAR[
session: session, from: from, to: sink !
UNWIND => NSDataStream.Abort[sink! NSDataStream.Aborted => CONTINUE]]
END;
Stream.Delete[sink ! NSDataStream.Aborted => CONTINUE]
END;
checkInAR: PROC = {
arAttr: ARRAY [0..2) OF NSFile.Attribute ← [
[name[arH.name]], [type[NSAssignedTypes.tText]]];
idAttr: ARRAY [0..1) OF NSFile.Attribute ← [
[fileID[arH.parentID]]];
fH, dH: NSFile.Handle ← NSFile.nullHandle;
dH ← NSFile.Open[
attributes: DESCRIPTOR[idAttr],
session: session.nsFileSession];
fH ← NSFile.Store[ -- stores new version
directory: dH, source: [proc[Source]],
attributes: DESCRIPTOR[arAttr],
session: session.nsFileSession];
CloseFile[@dH, session];
CloseFile[@arH.fH, session];
CloseFile[@fH, session];
NSString.FreeString[session.z, arH.name];
};
ValidateAR[arH, session];
IF ~arH.checkedOut THEN ERROR Error[session, notCheckedOut];
CatchErrors[session, checkInAR, checkin];
arH.checkedOut ← FALSE;
END;
AbortCheckOut: PUBLIC PROCEDURE [session: Session, arH: ARHandle] =
BEGIN
ValidateSession[session];
ValidateAR[arH, session];
IF ~arH.checkedOut THEN ERROR Error[session, notCheckedOut];
CloseFile[@arH.fH, session];
KillProbingProcess[session];
NSString.FreeString[session.z, arH.name];
arH.name ← NSFile.nullString;
arH.checkedOut ← FALSE;
END;
SubmitAR: PUBLIC PROCEDURE [
session: Session, arNumber: LONG CARDINAL, from: Stream.Handle] =
BEGIN
Source: PROC [sink: NSDataStream.SinkStream] =
BEGIN
BEGIN
ENABLE {
UNWIND => Stream.Delete[sink];
NSDataStream.Aborted => CONTINUE};
[] ← CopyAR[
session: session, from: from, to: sink !
UNWIND => NSDataStream.Abort[sink ! NSDataStream.Aborted => CONTINUE]]
END;
Stream.Delete[sink ! NSDataStream.Aborted => CONTINUE]
END;
submitAR: PROC = {
fileName: Rope.ROPE ← [maxFileName];
fH, dH: NSFile.Handle ← NSFile.nullHandle;
a: ARRAY [0..2) OF NSFile.Attribute ← [
[name[ARNumberToFileName[arNumber, fileName]]], [
type[NSAssignedTypes.tText]]];
dH ← OpenSubDirectory[session, arNumber];
fH ← NSFile.Store[ -- stores new version
directory: dH, source: [proc[Source]],
attributes: DESCRIPTOR[a], session: session.nsFileSession];
** with current impl of OpenSubDirectory, for now don't close;
CloseFile[@dH, session];
CloseFile[@fH, session]};
CatchErrors[session, submitAR, submit];
END;
-- -- -- -- -- -- --
UTILITIES :
-- -- -- -- -- -- --
CatchErrors: PROCEDURE [session: Session, proc: PROC, type: CommandType] =
BEGIN
ENABLE {
Let the session stay open; If it times out, fine. Force client to close it explicitly;
PFS.Error => PFSError[session, error];
IO.Error => ERROR Error[session, communicationError]
};
proc[];
SELECT type FROM -- For checkouts, must keep file, hence session, open;
checkout =>
Process.Detach[
session.probingProcess ← FORK KeepFileSessionOpen[session]];
checkin => KillProbingProcess[session];
ENDCASE;
END;
CatchErrors: PROCEDURE [session: Session, proc: PROC, type: CommandType] =
BEGIN
ENABLE {
Let the session stay open; If it times out, fine. Force client to close it explicitly;
NSFile.Error => NSError[session, error];
Courier.Error => ERROR Error[session, communicationError]};
ValidateSession[session];
IF CheckoutLost[session] THEN {
SetSessionError[session, FALSE];
ERROR Error[session, crashDuringCheckOut]};
IF NOT FileSessionIsOpen[session] THEN OpenFileSession[session];
proc[];
SELECT type FROM -- For checkouts, must keep file, hence session, open;
checkout =>
Process.Detach[
session.probingProcess ← FORK KeepFileSessionOpen[session]];
checkin => KillProbingProcess[session];
ENDCASE;
END;
CourierError: PROCEDURE [session: Session--, errorCode: Courier.ErrorCode--] =
BEGIN
SELECT errorCode FROM
noCourierAtRemoteSite, remoteSystemElementNotResponding,
noSuchProcedureNumber =>
ERROR Error[session, serverNotResponding];
ENDCASE => ERROR Error[session, communicationError];
END;
NSError: PROC [session: Session, error: NSFile.ErrorRecord] =
BEGIN
WITH error SELECT FROM
access =>
SELECT problem FROM
accessRightsInsufficient =>
ERROR Error[session, accessDenied];
accessRightsIndeterminate =>
ERROR Error[session, accessUndetermined];
fileInUse => ERROR Error[session, currentlyCheckedOut];
fileNotFound => ERROR Error[session, arNotFound];
ENDCASE => ERROR Error[session, communicationError];
authentication =>
SELECT problem FROM
credentialsInvalid, credentialsTooWeak, keysUnavailable,
simpleKeyDoesNotExist, strongKeyDoesNotExist =>
ERROR Error[session, invalidLogin];
ENDCASE => ERROR Error[session, authError];
space =>
IF problem = allocationExceeded THEN
ERROR Error[session, directoryFull]
ELSE
IF problem = mediumFull THEN
ERROR Error[session, fileServerFull]
ENDCASE => ERROR Error[session, communicationError];
END;
PFSError: PROC [session: Session, error: PFS.ErrorDesc] =
BEGIN
SELECT error.group FROM
bug =>
SELECT error.code FROM
$inconsistent => ERROR Error[session, unknown];
ENDCASE => ERROR Error[session, unknown];
environment =>
SELECT error.code FROM
$accessDenied => ERROR Error[session, accessDenied];
$quotaExceeded => ERROR Error[session, directoryFull];
$volumeFull => Error[session, fileServerFull];
ENDCASE => ERROR Error[session, authError];
client =>
SELECT error.code FROM
$unkownFile =>ERROR Error[session, arNotFound];
ENDCASE => ERROR Error[session, unknown];
user => 
SELECT error.code FROM
$versionSpecified => Error[session, invalidARNumber];
ENDCASE => ERROR Error[session, communicationError];
ENDCASE => ERROR Error[session, communicationError];
END;
Note: The NSFile session should never timeout during checkouts; it is kept open by the probing process;
OpenFileSession: PROCEDURE [session: Session] = {
attr: ARRAY [0..1) OF NSFile.Attribute ← [
[fileID[session.referenceToARs.fileID]]];
session.nsFileSession ← NSFile.LogonDirect[
identity: session.userId,
service: session.referenceToARs.service];
session.dirH ← NSFile.Open[
attributes: DESCRIPTOR[attr], session: session.nsFileSession]};
CloseFileSession: PROCEDURE [session: Session] = {
KillProbingProcess[session];
Process.Detach[FORK EndSession[session.nsFileSession]];
session.nsFileSession ← NSFile.nullSession};
EndSession: PROCEDURE [session: NSFile.Session] = {
NSFile.Logoff[session ! NSFile.Error, Courier.Error => CONTINUE]};
closes session.dirH
KillProbingProcess: PROCEDURE [session: Session] = {
IF session.probingProcess # NIL THEN
Process.Abort[session.probingProcess]};
KeepFileSessionOpen: PROCEDURE [session: Session] =
BEGIN
DO
ENABLE ABORTED => EXIT;
waitSeconds: CARDINAL
NSFile.Probe[
session.nsFileSession !
NSFile.Error =>
WITH error SELECT FROM session => GOTO error ENDCASE;
Courier.Error => GOTO error] / 2;
Process.Pause[Process.SecondsToTicks[waitSeconds]]
ENDLOOP;
session.probingProcess ← NIL;
EXITS error => SetSessionError[session, TRUE];
END;
ARNumberToFileName: PROCEDURE [number: CARDINAL--, name: Rope.ROPE--]
RETURNS [PFS.PATH] = {
temp: Rope.ROPE ¬ NIL; --[maxFileName];
temp ¬ temp.Concat[Convert.RopeFromCard[number, 10, FALSE]];
String.AppendLongNumber[temp, number];
FOR i: CARDINAL IN [0..maxDigits - temp.Length) DO
temp ¬ Rope.Concat["0", temp];
String.AppendChar[name, '0]
ENDLOOP;
String.AppendString[name, temp];
temp ¬ temp.Concat[".AR"];
String.AppendString[name, ".AR"L];
RETURN[PFS.PathFromRope[temp]]
RETURN[NSString.StringFromMesaString[name]]
};
ValidateSession: PROCEDURE [session: Session] = {
IF session = NIL OR session.sessionPassword # SessionPassword THEN
ERROR Error[session, invalidSession]};
ValidateAR: PROCEDURE [arH: ARHandle, session: Session] = {
IF arH = NIL OR arH.password # ARPassword THEN
ERROR Error[session, invalidARHandle]};
bufferPages: CARDINAL = 4;
bufferBytes: CARDINAL = bufferPages * Environment.bytesPerPage;
AddPairToTable: PROCEDURE [arS: ARAccess.ARStorage, field, value: Rope.ROPE]
RETURNS [newARS: ARAccess.ARStorage ¬ NIL] = {
oldLen: CARD ¬ IF arS # NIL THEN arS.index ELSE 0;
newARS ¬ NEW[ARAccess.DataTable[oldLen + 1]];
FOR i: CARD IN [0..oldLen) DO
newARS[i] ¬ arS[i];
ENDLOOP;
newARS[oldLen] ¬ [field, value];
newARS.index ¬ oldLen + 1;
};
AppendChar: PUBLIC PROCEDURE [r: Rope.ROPE, c: CHAR] RETURNS [Rope.ROPE] = {
scratch: REF TEXT = RefText.ObtainScratch[3];
result: Rope.Text = Rope.FromRefText[RefText.AppendChar[scratch, c]];
RefText.ReleaseScratch[scratch];
RETURN[r.Concat[result]];
};
CopyAR: PROC [session: Session, from: ARHandle]
RETURNS [to: ARAccess.ARStorage] = {
field, value: Rope.ROPE ¬ NIL;
c: CHAR;
{
ENABLE IO.EndOfStream => {
IO.Close[from.sH];
IF (field # NIL) THEN
to ¬ AddPairToTable[to, field, value];
GOTO done;
};
from.sH ¬ PFS.StreamFromOpenFile[from.fH];
DO
c ¬ IO.GetChar[from.sH ! IO.EndOfStream => {IO.Close[from.sH]; GOTO done}];
WHILE c # ': DO
IF c = '' THEN
c ¬ IO.GetChar[from.sH];
field ¬ AppendChar[field, c];
c ¬ IO.GetChar[from.sH];
ENDLOOP;
c ¬ IO.GetChar[from.sH];
c ¬ IO.GetChar[from.sH];
WHILE c # ('J - 75B) DO
IF c = '' THEN
c ¬ IO.GetChar[from.sH];
value ¬ AppendChar[value, c];
c ¬ IO.GetChar[from.sH];
ENDLOOP;
c ¬ IO.GetChar[from.sH];
to ¬ AddPairToTable[to, field, value];
field ¬ value ¬ NIL;
ENDLOOP;
EXITS
done => {};
}
};
CopyAR: PROC [session: Session, from: ARHandle, to: ARAccess.ARStorage--Stream.Handle--]
RETURNS [bytes: LONG CARDINAL] = {
buffer: LONG POINTER = AssertBuffer[session];
bytes ← 0;
DO
bytesTransferred: CARDINAL;
why: Stream.CompletionCode;
savedSST: Stream.SubSequenceType;
block: Stream.Block ← [buffer, 0, bufferBytes];
[bytesTransferred, why, savedSST] ← from.get[
from, block, Stream.defaultInputOptions !
Stream.EndOfStream => {
why ← endOfStream; bytesTransferred ← nextIndex; CONTINUE}];
block.stopIndexPlusOne ← bytesTransferred;
bytes ← bytes + bytesTransferred;
to.put[to, block, FALSE];
IF why = endOfStream THEN EXIT;
ENDLOOP;
MSegment.Kill[session.buffer]};
AssertBuffer: PROC [session: Session] RETURNS [LONG POINTER] = {
IF session.buffer = NIL THEN
session.buffer ← MSegment.Create[
file: NIL, release: [], pages: bufferPages];
RETURN[MSegment.Address[session.buffer]]};
CloseFile: PROC [arH: ARHandle --fH: REF NSFile.Handle--, session: Session] = {
IF arH.fH # NIL THEN {
PFS.Close[arH.fH ! PFS.Error => CONTINUE];
arH.fH ¬ NIL
}
IF fH^ # NSFile.nullHandle THEN {
NSFile.Close[
fH^, session.nsFileSession !
NSFile.Error, Courier.Error => CONTINUE];
fH^ ← NSFile.nullHandle}
};
FileSessionIsOpen: PROCEDURE [session: Session] RETURNS [BOOLEAN] =
{
IF session.nsFileSession # NSFile.nullSession THEN
[] ← NSFile.Probe[
session.nsFileSession !
NSFile.Error =>
BEGIN
WITH error SELECT FROM
session =>
IF problem = sessionInvalid THEN
session.nsFileSession ← NSFile.nullSession
ENDCASE;
CONTINUE
END; Courier.Error => CONTINUE];
RETURN[session.nsFileSession # NSFile.nullSession]};
SetSessionError: ENTRY PROCEDURE [
session: Session, value: BOOLEAN] = {
session.sessionError ← value; session.probingProcess ← NIL};
CheckoutLost: ENTRY PROCEDURE [session: Session] RETURNS [BOOLEAN] =
{RETURN[session.sessionError]};
EmptySubString: PROC [s: NSString.SubString] RETURNS [BOOLEAN] =
INLINE {RETURN[s.length = 0]};
OpenSubDirectory: PROCEDURE [session: Session, arNumber: CARDINAL]
RETURNS [dH: PFS.PATH --NSFile.Handle--] = {
pathRope: Rope.ROPE ¬ NIL;
FindSubDirectory[xxx]; ** i.e. Map name to subdirectory **
pathRope ¬ Rope.Cat[
AdobeIO.LocalAndDomain[session.host],
"-XNS:/",
session.directory,
"/"];
RETURN[PFS.PathFromRope[pathRope]] -- ** for now ** -- };
END...
24-Jul-87 17:31:11 - rlc - fix NSDataStream catch phrases