FSRemoteFileImpl.mesa
Last Edited by: Schroeder, December 11, 1983 3:01 pm
Last Edited by: Levin, September 22, 1983 1:04 pm
DIRECTORY
BasicTime USING [GMT, nullGMT],
FS USING [InfoProc, NameProc],
FSBackdoor USING [ErrorCode, ProduceError, Version],
FSName USING [BangStarFile, BangVersionFile, VersionFromRope],
FSRemoteFile USING [ConfirmProc, FTPTimeToGMT, Lookup, LookupResult],
FSReport USING [UnknownFile],
IO USING [STREAM],
Process USING [Detach, Seconds, SecondsToTicks],
Rope USING [Cat, Equal, Fetch, ROPE],
STP USING [Close, ConfirmProcType, Create, Delete, DesiredProperties, Enumerate, Error, ErrorCode, FileInfo, GetFileInfo, Handle, IsOpen, Login, NoteFileProcType, Open, Rename, Retrieve, SetDesiredProperties, SetDirectory, Store, ValidProperties],
UserCredentials USING [Get];
FSRemoteFileImpl: CEDAR MONITOR
IMPORTS FSBackdoor, FSName, FSRemoteFile, FSReport, Process, Rope, STP , UserCredentials
EXPORTS FSRemoteFile
= BEGIN
Exported to FSRemoteFile
Delete: PUBLIC PROC [server, file: Rope.ROPE, wantedCreatedTime: BasicTime.GMT, proc: FSRemoteFile.ConfirmProc] =
BEGIN
Confirm: STP.ConfirmProcType -- [file: Rope.ROPE] RETURNS [answer: {do, skip, abort}, localStream: IO.STREAM] -- =
BEGIN
info: STP.FileInfo = STP.GetFileInfo[h];
created: BasicTime.GMT = FSRemoteFile.FTPTimeToGMT[info.create];
SELECT TRUE FROM
matchFound => answer ← abort;
NOT timeSearch OR wantedCreatedTime = created =>
BEGIN
answer ← IF proc [FSName.VersionFromRope[info.version]] THEN do ELSE abort;
matchFound ← TRUE;
END;
ENDCASE => answer ← skip;
END;
matchFound: BOOLEANFALSE;
timeSearch: BOOLEAN = (wantedCreatedTime # BasicTime.nullGMT);
h: STP.Handle ← NIL;
stpCode: STP.ErrorCode;
BEGIN
ENABLE STP.Error => {stpCode ← code; GOTO ReportError};
h ← GetConnection[server];
ConditionConnection[h, file, FALSE];
STP.Delete[h, file, Confirm
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF NOT matchFound
THEN BEGIN
IF timeSearch
THEN BEGIN
file ← FSName.BangStarFile[file];
STP.Delete[h, file, Confirm];
END;
IF NOT matchFound
THEN {stpCode ← noSuchFile; GOTO ReportError};
END;
EXITS ReportError =>
ReportSTPError[h, stpCode, server, file, wantedCreatedTime];
END;
ReturnConnection[server, h];
END;
EnumerateForInfo: PUBLIC PROC [server, pattern: Rope.ROPE, proc: FS.InfoProc] =
BEGIN
Note: STP.NoteFileProcType -- [file: Rope.ROPE] RETURNS [continue: BOOLEAN] -- =
BEGIN
info: STP.FileInfo = STP.GetFileInfo[h];
created: BasicTime.GMT = FSRemoteFile.FTPTimeToGMT[info.create];
continue ← IF proc[MakeFullGName[server, file], NIL, created, info.size, 0] THEN yes ELSE no;
END;
h: STP.Handle ← NIL;
stpCode: STP.ErrorCode;
BEGIN
ENABLE STP.Error =>
IF code = noSuchFile
THEN CONTINUE -- ignore no match
ELSE {stpCode ← code; GOTO ReportError};
h ← GetConnection[server];
ConditionConnection[h, pattern, FALSE];
STP.Enumerate[h, pattern, Note];
EXITS ReportError =>
ReportSTPError[h, stpCode, server, pattern, BasicTime.nullGMT];
END;
ReturnConnection[server, h];
END;
EnumerateForNames: PUBLIC PROC [server, pattern: Rope.ROPE, proc: FS.NameProc] =
BEGIN
Note: STP.NoteFileProcType -- [file: Rope.ROPE] RETURNS [continue: BOOLEAN] -- =
BEGIN
continue ← IF proc[MakeFullGName[server, file]] THEN yes ELSE no;
END;
h: STP.Handle ← NIL;
stpCode: STP.ErrorCode;
BEGIN
ENABLE STP.Error =>
IF code = noSuchFile
THEN CONTINUE -- ignore no match
ELSE {stpCode ← code; GOTO ReportError};
h ← GetConnection[server];
ConditionConnection[h, pattern, TRUE];
STP.Enumerate[h, pattern, Note];
EXITS ReportError =>
ReportSTPError[h, stpCode, server, pattern, BasicTime.nullGMT];
END;
ReturnConnection[server, h];
END;
Info: PUBLIC PROC [server, file: Rope.ROPE, wantedCreatedTime: BasicTime.GMT] RETURNS [version: FSBackdoor.Version, bytes: INT, created: BasicTime.GMT] =
BEGIN
ReportError: PROC [stpCode: STP.ErrorCode] =
{ ReportSTPError[NIL, stpCode, server, file, wantedCreatedTime] };
result: FSRemoteFile.LookupResult;
[result, version, created, bytes] ← FSRemoteFile.Lookup[server, file];
SELECT result FROM
noResponse =>
ReportError[noNameLookupResponse];
noSuchPort =>
NULL;
noSuchFile =>
IF wantedCreatedTime = BasicTime.nullGMT
THEN ReportError[noSuchFile];
noSuchServer =>
ReportError[noSuchHost];
ok =>
IF wantedCreatedTime = BasicTime.nullGMT OR wantedCreatedTime = created
THEN RETURN;
ENDCASE;
[version, bytes, created] ← STPInfo[server, file, wantedCreatedTime];
END;
Rename: PUBLIC PROC [server, fromFile: Rope.ROPE, fromCreated: BasicTime.GMT, toFile: Rope.ROPE, proc: FSRemoteFile.ConfirmProc] =
BEGIN
h: STP.Handle ← NIL;
stpCode: STP.ErrorCode;
version: FSBackdoor.Version = Info[server, fromFile, fromCreated].version;
IF proc[version]
THEN BEGIN
ENABLE STP.Error => {stpCode ← code; GOTO ReportError};
h ← GetConnection[server];
ConditionConnection[h, fromFile, TRUE];
fromFile ← FSName.BangVersionFile[fromFile, version];
STP.Rename[h, fromFile, toFile];
EXITS ReportError =>
ReportSTPError[h, stpCode, server, toFile, BasicTime.nullGMT];
END;
ReturnConnection[server, h];
END;
Retrieve: PUBLIC PROC [server, file: Rope.ROPE, wantedCreatedTime: BasicTime.GMT, proc: PROC[fullGName: Rope.ROPE, bytes: INT, created: BasicTime.GMT] RETURNS [IO.STREAM]] =
BEGIN
Confirm: STP.ConfirmProcType -- [file: Rope.ROPE] RETURNS [answer: {do, skip, abort}, localStream: IO.STREAM] -- =
BEGIN
info: STP.FileInfo = STP.GetFileInfo[h];
created: BasicTime.GMT = FSRemoteFile.FTPTimeToGMT[info.create];
SELECT TRUE FROM
matchFound => answer ← abort;
NOT timeSearch OR wantedCreatedTime = created =>
BEGIN
localStream ← proc[MakeFullGName[server, file], info.size, created];
answer ← IF localStream = NIL THEN abort ELSE do;
matchFound ← TRUE;
END;
ENDCASE => answer ← skip;
END;
matchFound: BOOLEANFALSE;
timeSearch: BOOLEAN = (wantedCreatedTime # BasicTime.nullGMT);
h: STP.Handle ← NIL;
stpCode: STP.ErrorCode;
BEGIN
ENABLE STP.Error => {stpCode ← code; GOTO ReportError};
h ← GetConnection[server];
ConditionConnection[h, file, FALSE];
STP.Retrieve[h, file, Confirm
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF NOT matchFound
THEN BEGIN
IF timeSearch
THEN BEGIN
file ← FSName.BangStarFile[file];
STP.Retrieve[h, file, Confirm];
END;
IF NOT matchFound
THEN {stpCode ← noSuchFile; GOTO ReportError};
END;
EXITS ReportError =>
ReportSTPError[h, stpCode, server, file, wantedCreatedTime];
END;
ReturnConnection[server, h];
END;
Store: PUBLIC PROC [server, file: Rope.ROPE, str: IO.STREAM, created: BasicTime.GMT, proc: FSRemoteFile.ConfirmProc] =
BEGIN
Note: STP.NoteFileProcType -- [file: Rope.ROPE] RETURNS [continue: BOOLEAN] -- =
BEGIN
doIt: BOOLEAN = proc [ FSName.VersionFromRope[ STP.GetFileInfo[h].version ] ];
continue ← IF doIt THEN yes ELSE no;
END;
h: STP.Handle ← NIL;
stpCode: STP.ErrorCode;
BEGIN
ENABLE STP.Error => {stpCode ← code; GOTO ReportError};
h ← GetConnection[server];
ConditionConnection[h, file, TRUE];
STP.Store[h, file, str, Note, unknown, created];
EXITS ReportError =>
ReportSTPError[h, stpCode, server, file, BasicTime.nullGMT];
END;
ReturnConnection[server, h];
END;
Internal procedures
STPInfo: PROC[server, file: Rope.ROPE, wantedCreatedTime: BasicTime.GMT] RETURNS [version: FSBackdoor.Version, bytes: INT, created: BasicTime.GMT] =
BEGIN
Note: STP.NoteFileProcType -- [file: Rope.ROPE] RETURNS [continue: BOOLEAN] -- =
BEGIN
info: STP.FileInfo = STP.GetFileInfo[h];
created ← FSRemoteFile.FTPTimeToGMT[info.create];
IF NOT timeSearch OR wantedCreatedTime = created
THEN BEGIN
version ← FSName.VersionFromRope[info.version];
bytes ← info.size;
matchFound ← TRUE;
If version part was missing from client's file name, then Enumerate will produce all versions, but we only want the !H version
continue ← IF timeSearch THEN no ELSE yes;
END
ELSE continue ← yes;
END;
matchFound: BOOLEANFALSE;
timeSearch: BOOLEAN = (wantedCreatedTime # BasicTime.nullGMT);
h: STP.Handle ← NIL;
stpCode: STP.ErrorCode;
BEGIN
ENABLE STP.Error => {stpCode ← code; GOTO ReportError};
h ← GetConnection[server];
ConditionConnection[h, file, FALSE];
STP.Enumerate[h, file, Note
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF NOT matchFound
THEN BEGIN
IF timeSearch
THEN BEGIN
file ← FSName.BangStarFile[file];
STP.Enumerate[h, file, Note];
END;
IF NOT matchFound
THEN {stpCode ← noSuchFile; GOTO ReportError};
END;
EXITS ReportError =>
ReportSTPError[h, stpCode, server, file, wantedCreatedTime];
END;
ReturnConnection[server, h];
END;
Property stuff
MyDesiredProperties: TYPE = PACKED ARRAY STP.ValidProperties OF BoolDefaultFalse;
BoolDefaultFalse: TYPE = BOOLEANFALSE;
namesOnly: MyDesiredProperties = [directory: TRUE, nameBody: TRUE, version: TRUE];
nameSizeCreated: MyDesiredProperties = [directory: TRUE, nameBody: TRUE, version: TRUE, createDate: TRUE, size: TRUE];
all: STP.DesiredProperties = ALL[TRUE]; -- stops sending of Desired-Property properties
ConditionConnection: PROC[h: STP.Handle, nameBody: Rope.ROPE, justNames: BOOLEAN] =
BEGIN
IF Rope.Fetch[nameBody, 0] # '<
THEN BEGIN -- no directory, so turn off directory defaulting and Desired-Property properties
STP.SetDirectory[h, " "];
STP.SetDesiredProperties[h, all];
END
ELSE BEGIN
STP.SetDirectory[h, NIL];
STP.SetDesiredProperties[h, IF justNames THEN namesOnly ELSE nameSizeCreated];
END;
END;
Internal procedures
MakeFullGName: PROC[server, file: Rope.ROPE] RETURNS [Rope.ROPE] =
{ RETURN [ Rope.Cat[ "[", server, "]", file ] ] };
ReportSTPError: PUBLIC PROC [h: STP.Handle, stpCode: STP.ErrorCode, server, file: Rope.ROPE, time: BasicTime.GMT] =
BEGIN
gName: Rope.ROPE = MakeFullGName[server, file];
e1: Rope.ROPE ← "Server for \"";
e2: Rope.ROPE ← "\".";
code: FSBackdoor.ErrorCode;
IF stpCode = noSuchFile
THEN BEGIN
IF h # NIL THEN ReturnConnection[server, h];
FSReport.UnknownFile[gName, time];
END
ELSE BEGIN
IF h # NIL THEN STP.Close[h ! STP.Error => CONTINUE];
SELECT stpCode FROM
noRouteToNetwork, noNameLookupResponse =>
{ code ← serverInaccessible;
e2 ← "\" is inaccessible." };
connectionRejected =>
{ code ← connectionRejected;
e2 ← "\" rejected the connection attempt." };
connectionTimedOut =>
{ code ← connectionTimedOut;
e2 ← "\" timed-out the connection." };
accessDenied =>
{ code ← accessDenied;
e2 ← "\" denied permission to access the file." };
requestRefused =>
{ code ← quotaExceeded;
e1 ← "Request refused (possibily no quota for storing) when accessing \"" };
accessError =>
{ code ← fileBusy;
e1 ← "\""; e2 ← "\" is locked on the server." };
illegalUserName =>
{ code ← badCredentials;
e1 ← "Credentials rejected when accessing \"" };
illegalFileName =>
{ code ← illegalName;
e2 ← "\" says that the file name is illegal." };
noSuchHost =>
{ code ← unknownServer;
e1 ← "Couldn't find the server for \"" };
ENDCASE => ERROR;
FSBackdoor.ProduceError[code, Rope.Cat[e1, gName, e2]];
END;
END;
STP connection cacheing
maxSlots: CARDINAL = 4; -- only need enough for all used in last TimeOut seconds
TimeOut: Process.Seconds ← 10; -- keep connection around for this long after use
Slot: TYPE = RECORD [
server: Rope.ROPE,
h: STP.Handle,
used: BOOLEAN
];
emptySlot: Slot = [NIL, NIL, FALSE];
new, reused: INT ← 0; -- statistics
slot: ARRAY [0..maxSlots) OF Slot ← ALL [emptySlot];
haveSlotTimer: BOOLEANFALSE;
ForTimeout: CONDITION ← [Process.SecondsToTicks[TimeOut]];
GetConnection: PROC[server: Rope.ROPE] RETURNS [h: STP.Handle] =
BEGIN
h ← LookForConnection[server];
IF h = NIL
THEN BEGIN -- need a new connection
user, password: Rope.ROPE;
h ← STP.Create[];
[user, password] ← UserCredentials.Get[];
STP.Login[h, user, password];
[] ← STP.Open[h, server];
END
ELSE BEGIN -- already had a connection
IF NOT STP.IsOpen[h]
THEN [] ← STP.Open[h, server];
END;
END;
LookForConnection: ENTRY PROC[server: Rope.ROPE] RETURNS [h: STP.Handle] =
BEGIN
FOR i: CARDINAL IN [0..maxSlots) DO
IF slot[i] # emptySlot AND Rope.Equal[slot[i].server, server, FALSE]
THEN BEGIN
reused ← reused + 1;
h ← slot[i].h;
slot[i] ← emptySlot;
RETURN;
END;
ENDLOOP;
h ← NIL;
new ← new + 1;
END;
ReturnConnection: PROC [server: Rope.ROPE, h: STP.Handle] =
BEGIN
IF STP.IsOpen[h] AND NOT SaveConnection[server, h]
THEN STP.Close[h ! STP.Error => CONTINUE ];
END;
SaveConnection: ENTRY PROC [server: Rope.ROPE, h: STP.Handle] RETURNS [BOOLEAN] =
BEGIN
FOR i: CARDINAL IN [0..maxSlots) DO
IF slot[i] = emptySlot
THEN BEGIN
slot[i] ← [server, h, TRUE];
IF NOT haveSlotTimer
THEN TRUSTED BEGIN
haveSlotTimer ← TRUE;
Process.Detach[FORK SlotTimer[]];
END;
RETURN [TRUE];
END;
ENDLOOP;
RETURN [FALSE];
END;
SlotTimer: PROC =
BEGIN
i: CARDINAL ← 0;
h: STP.Handle;
DO
[h, i] ← NextInterestingSlot[i];
IF h = NIL THEN RETURN;
STP.Close[h ! STP.Error => CONTINUE ];
ENDLOOP;
END;
NextInterestingSlot: ENTRY PROC [start: CARDINAL] RETURNS [h: STP.Handle, index: CARDINAL] =
BEGIN
DO
IF start = 0 THEN WAIT ForTimeout;
FOR index IN [start..maxSlots) DO
IF slot[index] # emptySlot
THEN BEGIN
IF slot[index].used
THEN slot[index].used ← FALSE
ELSE BEGIN -- here's one that needs to be closed
h ← slot[index].h;
slot[index] ← emptySlot;
RETURN;
END;
END;
ENDLOOP;
FOR index IN [0 .. maxSlots) DO
IF slot[index] # emptySlot THEN EXIT; -- need to stick around
REPEAT FINISHED => BEGIN -- nothing more to do
h ← NIL;
index ← maxSlots;
haveSlotTimer ← FALSE;
RETURN;
END;
ENDLOOP;
start ← 0;
ENDLOOP;
END;
END.