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: BOOLEAN ← FALSE;
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];
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: BOOLEAN ← FALSE;
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;
Property stuff
MyDesiredProperties: TYPE = PACKED ARRAY STP.ValidProperties OF BoolDefaultFalse;
BoolDefaultFalse: TYPE = BOOLEAN ← FALSE;
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;
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: BOOLEAN ← FALSE;
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;