DIRECTORY
BasicTime USING [GMT, Now, nullGMT, Pack, Unpack, Unpacked],
FS USING [Error, ErrorGroup, InfoProc, NameProc],
FSBackdoor USING [ErrorCode, ProduceError, Version],
FSName USING [BangStarFile, BangVersionFile, VersionFromRope],
FSPseudoServers USING [AvoidRemoteCheck, PseudoServerList, TranslateForRead, TranslateForWrite],
FSRemoteFile USING [ConfirmProc, Lookup, LookupResult],
FSReport USING [UnknownFile],
GVBasics USING [Connect],
GVNames USING [ConnectInfo, GetConnect],
IO USING [STREAM],
Process USING [CheckForAbort, Detach, Seconds, SecondsToTicks],
Rope USING [Cat, Concat, Equal, Fetch, Find, Flatten, Length, Match, ROPE],
RuntimeError USING [UNCAUGHT],
STP USING [Close, ConfirmProcType, Continue, 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 BasicTime, FS, FSBackdoor, FSName, FSPseudoServers, FSRemoteFile, FSReport, GVNames, Process, Rope, RuntimeError, STP, UserCredentials
EXPORTS FSRemoteFile
= {

GMT: TYPE = BasicTime.GMT;
Handle: TYPE = STP.Handle;
ROPE: TYPE = Rope.ROPE;
PseudoServerList: TYPE = FSPseudoServers.PseudoServerList;
STREAM: TYPE = IO.STREAM;
Version: TYPE = FSBackdoor.Version;

Delete: PUBLIC PROC [server, file: ROPE, wantedCreatedTime: GMT, proc: FSRemoteFile.ConfirmProc] = {
Confirm: STP.ConfirmProcType = {
info: STP.FileInfo = STP.GetFileInfo[h];
created: GMT = FTPTimeToGMT[info.create];
SELECT TRUE FROM
matchFound => answer _ abort;
NOT timeSearch OR wantedCreatedTime = created => {
answer _ IF proc [FSName.VersionFromRope[info.version]] THEN do ELSE abort;
matchFound _ TRUE;
};
ENDCASE => answer _ skip;
};
matchFound: BOOLEAN _ FALSE;
timeSearch: BOOLEAN = (wantedCreatedTime # BasicTime.nullGMT);
stpCode: STP.ErrorCode _ noSuchFile;
h: Handle _ NIL;
innerDelete: PROC RETURNS [ok: BOOL _ TRUE] = {
tServer: ROPE _ FSPseudoServers.TranslateForWrite[UnbracketServer[server]];
h _ NIL;
{
ENABLE UNWIND => ReturnConnection[tServer, h];
h _ GetConnection[tServer, file, FALSE];
STP.Delete[h, file, Confirm
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF NOT matchFound THEN {
IF timeSearch THEN {
file _ FSName.BangStarFile[file];
STP.Delete[h, file, Confirm];
};
IF NOT matchFound THEN {stpCode _ noSuchFile; RETURN [FALSE]};
};
};
ReturnConnection[tServer, h];
};
IF innerDelete[ ! STP.Error => {stpCode _ code; CONTINUE}] THEN RETURN;
ReportSTPError[stpCode, server, file, wantedCreatedTime];
};
EnumerateForInfo: PUBLIC PROC [server, pattern: ROPE, proc: FS.InfoProc] = {
NoteInfo: LocalEnumProc = {
info: STP.FileInfo = STP.GetFileInfo[h];
created: BasicTime.GMT = FTPTimeToGMT[info.create];
continue _ IF proc[Rope.Concat[serverB, file], NIL, created, info.size, 0]
THEN yes ELSE no;
};
serverB: ROPE = BracketServer[server];
InnerEnumerate[server, pattern, NoteInfo, FALSE];
};
EnumerateForNames: PUBLIC PROC [server, pattern: ROPE, proc: FS.NameProc] = {
NoteName: LocalEnumProc = {
continue _ IF proc[Rope.Concat[serverB, file]] THEN yes ELSE no;
};
serverB: ROPE = BracketServer[server];
InnerEnumerate[server, pattern, NoteName, TRUE];
};
LocalEnumProc: TYPE = PROC [h: Handle, file: ROPE] RETURNS [continue: STP.Continue];
InnerEnumerate: PUBLIC PROC [server, pattern: ROPE, Note: LocalEnumProc, namesOnly: BOOL] = {
stpCode: STP.ErrorCode _ noSuchFile;
h: Handle _ NIL;
notedOne: BOOL _ FALSE;
innerNote: STP.NoteFileProcType = {
Process.CheckForAbort[];
notedOne _ TRUE;
IF NOT Rope.Match["<*", file] THEN file _ Rope.Concat["<>", file];
continue _ Note[h, file];
};
innerBlock: PROC [eachName: ROPE] = {
h _ NIL;
{
ENABLE UNWIND => ReturnConnection[eachName, h];
h _ GetConnection[eachName, pattern, namesOnly];
STP.Enumerate[h, pattern, innerNote];
};
ReturnConnection[eachName, h];
};
serverList: LIST OF ROPE = FSPseudoServers.TranslateForRead[server];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
FOR each: LIST OF ROPE _ serverList, each.rest WHILE each # NIL DO
innerBlock[each.first
! STP.Error =>
SELECT TRUE FROM
code = noSuchFile =>
SELECT TRUE FROM
replicated, notedOne => GO TO NotFound;
ENDCASE => LOOP;
notedOne => {
stpCode _ code; EXIT};
each = serverList => {
stpCode _ code; LOOP};
ENDCASE =>
LOOP;
];
RETURN;
ENDLOOP;
IF stpCode # noSuchFile THEN ReportSTPError[stpCode, server, pattern, BasicTime.nullGMT];
EXITS NotFound => {};
};
Info: PUBLIC PROC [server, file: ROPE, wantedCreatedTime: GMT] RETURNS [version: Version, bytes: INT, created: GMT] = {
ReportError: PROC [stpCode: STP.ErrorCode] = {
ReportSTPError[stpCode, server, file, wantedCreatedTime];
};
result: FSRemoteFile.LookupResult;
serverList: LIST OF ROPE = FSPseudoServers.TranslateForRead[server];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
allDown: BOOL _ TRUE;
FOR each: LIST OF ROPE _ serverList, each.rest WHILE each # NIL DO
eachServer: ROPE _ each.first;
last: BOOL _ each.rest = NIL;
[result, version, created, bytes] _ FSRemoteFile.Lookup[eachServer, file];
IF result#noResponse THEN allDown _ FALSE;
SELECT result FROM
noSuchFile => {
IF NOT replicated THEN LOOP;
IF wantedCreatedTime = BasicTime.nullGMT THEN ReportError[noSuchFile];
EXIT;
};
ok => {
IF wantedCreatedTime = BasicTime.nullGMT THEN RETURN;
IF wantedCreatedTime = created THEN RETURN;
IF NOT replicated THEN LOOP;
EXIT};
ENDCASE;
ENDLOOP;
[version, bytes, created] _ STPInfo[server, file, wantedCreatedTime];
};
Rename: PUBLIC PROC [server, fromFile: ROPE, fromCreated: GMT, toFile: ROPE, proc: FSRemoteFile.ConfirmProc] = {
stpCode: STP.ErrorCode _ noSuchFile;
version: Version = Info[server, fromFile, fromCreated].version;
innerRename: PROC RETURNS [ok: BOOL _ TRUE] = {
h: Handle _ NIL;
tServer: ROPE _ FSPseudoServers.TranslateForWrite[server];
{
ENABLE UNWIND => ReturnConnection[tServer, h];
h _ GetConnection[tServer, fromFile, TRUE];
fromFile _ FSName.BangVersionFile[fromFile, version];
STP.Rename[h, fromFile, toFile];
};
ReturnConnection[tServer, h];
};
IF proc[version] THEN {
IF innerRename[! STP.Error => {stpCode _ code; CONTINUE}] THEN RETURN;
ReportSTPError[stpCode, server, toFile, BasicTime.nullGMT];
};
};
Retrieve: PUBLIC PROC [server, file: ROPE, wantedCreatedTime: GMT, proc: PROC[fullGName: ROPE, bytes: INT, created: GMT] RETURNS [STREAM]] = {
matchFound: BOOL _ FALSE;
timeSearch: BOOL = (wantedCreatedTime # BasicTime.nullGMT);
h: Handle _ NIL;
stpCode: STP.ErrorCode _ noSuchFile;
serverB: ROPE = BracketServer[server];
Confirm: STP.ConfirmProcType = {
info: STP.FileInfo = STP.GetFileInfo[h];
created: GMT = FTPTimeToGMT[info.create];
SELECT TRUE FROM
matchFound => answer _ abort;
NOT timeSearch OR wantedCreatedTime = created => {
localStream _ proc[Rope.Concat[serverB, file], info.size, created];
answer _ IF localStream = NIL THEN abort ELSE do;
matchFound _ TRUE;
};
ENDCASE => answer _ skip;
};
innerRetrieve: PROC [eachServer: ROPE] RETURNS [success: BOOL _ TRUE] = {
h _ NIL;
{
ENABLE UNWIND => ReturnConnection[eachServer, h];
h _ GetConnection[eachServer, file, FALSE];
STP.Retrieve[h, file, Confirm
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF NOT matchFound THEN {
IF timeSearch THEN {
file _ FSName.BangStarFile[file];
STP.Retrieve[h, file, Confirm];
};
IF NOT matchFound THEN {stpCode _ noSuchFile; success _ FALSE};
};
};
ReturnConnection[eachServer, h];
};
serverList: LIST OF ROPE = FSPseudoServers.TranslateForRead[server];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
FOR each: LIST OF ROPE _ serverList, each.rest WHILE each # NIL DO
IF innerRetrieve[each.first
! STP.Error => {
IF each = serverList THEN stpCode _ code;
IF each.rest = NIL THEN EXIT;
IF stpCode = noSuchFile AND replicated THEN EXIT;
LOOP}
] THEN RETURN ELSE IF replicated THEN EXIT;
ENDLOOP;
ReportSTPError[stpCode, server, file, wantedCreatedTime];
};
Store: PUBLIC PROC [server, file: ROPE, str: STREAM, created: GMT, proc: FSRemoteFile.ConfirmProc] = {
h: Handle _ NIL;
Note: STP.NoteFileProcType = {
doIt: BOOL = proc [ FSName.VersionFromRope[ STP.GetFileInfo[h].version ] ];
continue _ IF doIt THEN yes ELSE no;
};
innerStore: PROC RETURNS [ok: BOOL _ TRUE] = {
tServer: ROPE _ FSPseudoServers.TranslateForWrite[server];
{
ENABLE UNWIND => ReturnConnection[tServer, h];
h _ GetConnection[tServer, file, TRUE];
STP.Store[h, file, str, Note, unknown, created];
};
ReturnConnection[tServer, h];
};
stpCode: STP.ErrorCode _ noSuchFile;
IF innerStore[ ! STP.Error => {stpCode _ code; CONTINUE}] THEN RETURN;
ReportSTPError[stpCode, server, file, BasicTime.nullGMT];
};
FTPTimeToGMT: PUBLIC PROC [t: Rope.ROPE] RETURNS [time: BasicTime.GMT _ BasicTime.nullGMT] = {
adjust: NAT _ 0;
Digit: PROC [index: INT] RETURNS [CARDINAL] = {
c: CHAR = Rope.Fetch[t, index-adjust];
SELECT c FROM
'  => RETURN [0];
IN ['0..'9] => RETURN [c-'0];
ENDCASE => ERROR BadTime;
};
DoubleDigit: PROC [index: INT] RETURNS [CARDINAL] = {
RETURN [Digit[index]*10 + Digit[index+1]];
};
Char: PROC [index: INT] RETURNS [CHAR] = {
c: CHAR = Rope.Fetch[t, index];
SELECT c FROM
'-, '+, ' , IN ['a..'z], IN ['0..'9] => RETURN [c];
IN ['A..'Z] => RETURN [c + ('a-'A)];
ENDCASE => ERROR BadTime;
};
Inner: PROC = {
uT: BasicTime.Unpacked;
yr: CARDINAL = DoubleDigit[7];
SELECT yr FROM
< 50 => uT.year _ yr + 2000;
ENDCASE => uT.year _ yr + 1900;
IF Char[1] IN ['0..'9]
THEN uT.day _ DoubleDigit[0]
ELSE {
uT.day _ Digit[0];
adjust _ 1};
uT.day _ DoubleDigit[0];
uT.hour _ DoubleDigit[10];
uT.minute _ DoubleDigit[13];
uT.second _ DoubleDigit[16];
uT.month _ SELECT Char[3] FROM
'j => IF Char[4] = 'a THEN January ELSE IF Char[5] = 'n THEN June ELSE July,
'f => February,
'm => IF Char[5] = 'r THEN March ELSE May,
'a => IF Char[4] = 'p THEN April ELSE August,
's => September,
'o => October,
'n => November,
'd => December,
ENDCASE => ERROR BadTime;
IF t.Length[] < 19
THEN {
temp: BasicTime.Unpacked _ BasicTime.Unpack[BasicTime.Now[]];
uT.zone _ temp.zone;
uT.dst _ temp.dst;
}
ELSE {
uT.zone _ 60 * ( SELECT Char[19] FROM
'g => 0,
'e =>  5,
'c => 6,
'm => 7,
'p => 8, 
'+ => Digit[20],
'- => - Digit[20],
ENDCASE => ERROR BadTime) ;
uT.dst _ IF (Char[20] = 'd) THEN yes ELSE no;
};
time _ BasicTime.Pack[uT];
};
Inner[ ! RuntimeError.UNCAUGHT => CONTINUE];
};
BadTime: ERROR = CODE;
STPInfo: PROC [server, file: ROPE, wantedCreatedTime: GMT] RETURNS [version: Version, bytes: INT, created: GMT] = {
h: Handle _ NIL;
Note: STP.NoteFileProcType = {
info: STP.FileInfo = STP.GetFileInfo[h];
created _ FTPTimeToGMT[info.create];
IF NOT timeSearch OR wantedCreatedTime = created
THEN {
version _ FSName.VersionFromRope[info.version];
bytes _ info.size;
matchFound _ TRUE;
continue _ IF timeSearch THEN no ELSE yes;
}
ELSE continue _ yes;
};
matchFound: BOOL _ FALSE;
timeSearch: BOOL = (wantedCreatedTime # BasicTime.nullGMT);
stpCode: STP.ErrorCode _ noSuchFile;
serverList: LIST OF ROPE = FSPseudoServers.TranslateForRead[server];
replicated: BOOL = FSPseudoServers.AvoidRemoteCheck[server];
innerSTPInfo: PROC [eachServer: ROPE] RETURNS [found: BOOL _ TRUE] = {
ENABLE UNWIND => ReturnConnection[eachServer, h];
h _ NIL;
h _ GetConnection[eachServer, file, FALSE];
STP.Enumerate[h, file, Note
! STP.Error => IF code = noSuchFile THEN CONTINUE ];
IF NOT matchFound THEN {
IF timeSearch THEN {
file _ FSName.BangStarFile[file];
STP.Enumerate[h, file, Note];
};
IF NOT matchFound THEN {stpCode _ noSuchFile; found _ FALSE}; 
};
ReturnConnection[eachServer, h];
};
FOR each: LIST OF ROPE _ serverList, each.rest WHILE each # NIL DO
IF innerSTPInfo[each.first
! STP.Error => {
IF each = serverList THEN stpCode _ code;
IF each.rest = NIL THEN EXIT;
IF stpCode = noSuchFile AND replicated THEN EXIT;
LOOP};
] THEN RETURN ELSE IF replicated THEN EXIT;
ENDLOOP;
ReportSTPError[stpCode, server, file, wantedCreatedTime];
};
MyDesiredProperties: TYPE = PACKED ARRAY STP.ValidProperties OF BoolDefaultFalse;
BoolDefaultFalse: TYPE = BOOL _ 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: Handle, nameBody: ROPE, justNames: BOOL] = {
IF NOT Rope.Match["<*", nameBody]
THEN {
STP.SetDirectory[h, " "];
STP.SetDesiredProperties[h, all];
}
ELSE {
STP.SetDirectory[h, NIL];
STP.SetDesiredProperties[h, IF justNames THEN namesOnly ELSE nameSizeCreated];
};
};
BracketServer: PROC[server: ROPE] RETURNS [ROPE] = {
IF Rope.Match["[*", server]
THEN RETURN [server]
ELSE RETURN [ Rope.Cat[ "[", server, "]" ] ];
};
UnbracketServer: PROC[server: ROPE] RETURNS [ROPE] = {
IF Rope.Match["[*", server]
THEN RETURN [ Rope.Flatten[server, 1, Rope.Length[server]-2]]
ELSE RETURN [ server ];
};
ReportSTPError: PUBLIC PROC [stpCode: STP.ErrorCode, server, file: ROPE, time: GMT] = {
gName: ROPE = Rope.Concat[BracketServer[server], file];
e1: ROPE _ "Server for \"";
e2: ROPE _ "\"";
code: FSBackdoor.ErrorCode;
NewError: PROC [group: FS.ErrorGroup, code: ATOM, explanation: ROPE] = {
ERROR FS.Error[[group, code, Rope.Cat[e1, gName, "\"", explanation]]];
};
IF stpCode = noSuchFile THEN {
FSReport.UnknownFile[gName, time];  -- raises FS.Error
};
SELECT stpCode FROM
noRouteToNetwork, noNameLookupResponse => {
code _ serverInaccessible;
e2 _ "\" is inaccessible";
};
connectionClosed => {
code _ wentOffline;
e2 _ "\" connection closed unexpectedly (wentOffline)";
};
connectionRejected => {
code _ connectionRejected;
e2 _ "\" rejected the connection attempt";
};
connectionTimedOut =>  {
code _ connectionTimedOut;
e2 _ "\" timed-out the connection";
};
accessDenied => {
code _ accessDenied;
e2 _ "\" denied file access permission";
};
requestRefused => {
code _ quotaExceeded;
e1 _ "Request refused (possibily no quota for storing) for \"";
};
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 \"";
};
alreadyAConnection =>
NewError[bug, $alreadyAConnection, " already had a connection"];
noConnection =>
NewError[bug, $noConnection, " gave a noConnection error"];
illegalUserPassword =>
NewError[environment, $illegalUserPassword, " had an illegal user password"];
illegalUserAccount =>
NewError[environment, $illegalUserAccount, " had an illegal user account"];
illegalConnectName =>
NewError[environment, $illegalConnectName, " had an illegal connect name"];
illegalConnectPassword =>
NewError[environment, $illegalConnectPassword, " had an illegal connect password"];
credentailsMissing =>
NewError[environment, $credentailsMissing, " had missing credentails"];
protocolError =>
NewError[bug, $protocolError, " gave a protocol error to STP"];
noSuchFile =>
NewError[bug, $noSuchFile, " reported no such file"];
undefinedError =>
NewError[bug, $undefinedError, " gave STP an undefinedError"];
ENDCASE => ERROR;
FSBackdoor.ProduceError[code, Rope.Cat[e1, gName, e2]];
};
maxSlots: CARDINAL = 9; -- only need enough for all used in last TimeOut seconds
TimeOut: Process.Seconds _ 7; -- keep connection around for this long after use
SlotArray: TYPE = ARRAY [0..maxSlots) OF Slot;
Slot: TYPE = RECORD [
server: ROPE,
h: Handle,
used: BOOL
];
emptySlot: Slot = [NIL, NIL, FALSE];
new, reused, flushed: INT _ 0; -- statistics
slot: REF SlotArray _ NEW[SlotArray _ ALL [emptySlot]];
haveSlotTimer: BOOL _ FALSE;
ForTimeout: CONDITION _ [Process.SecondsToTicks[TimeOut]];
GrapevineCacheArray: TYPE = ARRAY [0..maxGVineCache) OF GrapevineCacheEntry;
GrapevineCacheEntry: TYPE = RECORD[
name: ROPE _ NIL,
connect: GVBasics.Connect
];
maxGVineCache: CARDINAL = 9; -- its quite expensive to find these out
grapevineCachePut: CARDINAL _ 0;  -- next victim
grapevineCache: REF GrapevineCacheArray _ NEW[GrapevineCacheArray];
GetConnection: PROC [server, pattern: ROPE, justNames: BOOL] RETURNS [h: Handle] = {
h _ LookForConnection[server];
IF h = NIL
THEN {
user, password: ROPE;
h _ STP.Create[];
[user, password] _ UserCredentials.Get[];
STP.Login[h, user, password];
[] _ STP.Open[h, GetServerPupName[server]];
}
ELSE {
IF NOT STP.IsOpen[h] THEN {
[] _ STP.Open[h, GetServerPupName[server]];
};
};
ConditionConnection[h, pattern, justNames];
};
GetServerPupName: PUBLIC PROC [server: ROPE] RETURNS [pupServer: ROPE] = {
IF server.Find[".", 0, FALSE] > 0 THEN {
info: GVNames.ConnectInfo;
connect: GVBasics.Connect;
foundInCache: BOOL;
[found: foundInCache, connect: connect] _  FindInGrapevineCache[name: server];
IF foundInCache THEN RETURN[connect];
[info: info, connect: connect ] _ GVNames.GetConnect[server];
IF info = group OR info = individual THEN {
AddToGrapevineCache[name: server, connect: connect];
RETURN[connect];
};
};
RETURN[server];
};
FindInGrapevineCache: ENTRY PROC [name: ROPE] RETURNS [found: BOOL _ FALSE, connect: GVBasics.Connect] = {
FOR i: CARDINAL IN [0..maxGVineCache) DO
IF Rope.Equal[name, grapevineCache[i].name, FALSE] THEN RETURN[TRUE, grapevineCache[i].connect];
ENDLOOP;
};
AddToGrapevineCache: ENTRY PROC [name: ROPE, connect: GVBasics.Connect] = {
grapevineCache[grapevineCachePut].name _ name;
grapevineCache[grapevineCachePut].connect _ connect;
IF (grapevineCachePut _ grapevineCachePut + 1) >= maxGVineCache THEN grapevineCachePut _ 0;
};
LookForConnection: ENTRY PROC[server: ROPE] RETURNS [h: Handle] = {
ENABLE UNWIND => NULL;
FOR i: CARDINAL IN [0..maxSlots) DO
IF slot[i] # emptySlot AND Rope.Equal[slot[i].server, server, FALSE] THEN  {
reused _ reused + 1;
h _ slot[i].h;
slot[i] _ emptySlot;
RETURN;
};
ENDLOOP;
h _ NIL;
new _ new + 1;
};
ReturnConnection: PROC [server: ROPE, h: Handle] = {
IF server = NIL OR h = NIL THEN RETURN;
IF STP.IsOpen[h] AND NOT SaveConnection[server, h] THEN
STP.Close[h ! STP.Error => CONTINUE ];
};
SaveConnection: ENTRY PROC [server: ROPE, h: Handle] RETURNS [BOOL] = {
ENABLE UNWIND => NULL;
FOR i: CARDINAL IN [0..maxSlots) DO
IF slot[i] = emptySlot THEN {
slot[i] _ [server, h, TRUE];
IF NOT haveSlotTimer THEN TRUSTED {
haveSlotTimer _ TRUE;
Process.Detach[FORK SlotTimer[]];
};
RETURN [TRUE];
};
ENDLOOP;
RETURN [FALSE];
};
SlotTimer: PROC = {
i: CARDINAL _ 0;
h: Handle;
DO
[h, i] _ NextInterestingSlot[i];
IF h = NIL THEN RETURN;
STP.Close[h ! STP.Error => LOOP ];
flushed _ flushed + 1;
ENDLOOP;
};
NextInterestingSlot: ENTRY PROC [start: CARDINAL] RETURNS [h: Handle, index: CARDINAL]  = {
ENABLE UNWIND => NULL;
DO
IF start = 0 THEN WAIT ForTimeout;
FOR index IN [start..maxSlots) DO
IF slot[index] # emptySlot THEN {
IF slot[index].used
THEN slot[index].used _ FALSE
ELSE { -- here's one that needs to be closed
h _ slot[index].h;
slot[index] _ emptySlot;
RETURN;
};
};
ENDLOOP;
FOR index IN [0 .. maxSlots) DO
IF slot[index] # emptySlot THEN EXIT; -- need to stick around
REPEAT FINISHED => { -- nothing more to do
h _ NIL;
index _ maxSlots;
haveSlotTimer _ FALSE;
RETURN;
};
ENDLOOP;
start _ 0;
ENDLOOP;
};
}.
���ô��FSRemoteFileImpl.mesa
Copyright c 1984, 1985, 1986 by Xerox Corporation.  All rights reserved.
HGM, February 8, 1984 11:55:19 pm PST
Bob Hagmann, August 2, 1985 4:38:26 pm PDT
Doug Wyatt, November 27, 1984 3:53:09 pm PST -- added allDown test to Info
Russ Atkinson (RRA) April 8, 1986 12:59:54 pm PST
Tim Diebert: February 11, 1986 9:39:22 am PST
Exported to FSRemoteFile
[file: ROPE] RETURNS [answer: {do, skip, abort}, localStream: STREAM]
[file: ROPE] RETURNS [continue: BOOL]
[file: ROPE] RETURNS [continue: BOOL]
This helpful procedure allows enumeration for either names or information.
RRA: this allows us to abort during an enumeration, which is non-trivial
RRA: certain STP servers, like Nebula, do not prepend "<>" if they do not support directories.  Since FS demands this syntax, we should make sure that it is here.
If we do not get a match then we yield nothing.  If replicated, then there is also no reason to try other servers, since replication should not affect presence.  Otherwise, we go on to the next server, provided that we have not yielded something from this server.
If we have seen at least one file then we do NOT try other servers, which would royally screw up the enumeration.
If the auxilliary servers do not work, we report the error from the first server only.  So we remember the error code here.
Any other errors during auxiliiary severs searches just go on to try the next server in the list (if any).
If we continue here, we have enumerated the pattern all right, and have returned the connection.
If we continue here, we have an error of some kind during the enumeration, so we report it.
No such file was the response.  This is a positive response that the named file was not present on the named server.
If we are NOT assuming replicated files, then we go look for another server.
If we wanted any date, and we are assuming replicated files, then we should just give up right now, since we will not find a match.
If we wanted a specific date, then we may just have a bad version number hint, so we exit to do the search the expensive way.
We got a response that the file was present.
We were requested to find any date, so the one we got was OK.
We were asked for a specific date, and we got it, so its all OK.
We were asked for a specific date, and the file was there, but the date was wrong, which means that the version # hint was bogus.  If we assume the files are replicated, we should exit, since the version hint is wrong on the replicated servers as well.  Otherwise, we loop, hoping that another server will have the correct hint and date as well.
The line of code following fails if there is no response to a single packet info check.  Tim D.
IF allDown THEN ReportError[noNameLookupResponse];
This call is for the general case.  It is more expensive, of course, since we have to grab a connection for it.  However, STPInfo can handle bogus version # hints.
[file: ROPE] RETURNS [answer: {do, skip, abort}, localStream: STREAM] 
[file: ROPE] RETURNS [continue: BOOL]
In this case we have a single-character day of month with no leading blank.  We accept this number, as well as adjusting the indexing. 
Yetch, FTPServer on AltoGateway does this. Use local zone info.
Internal procedures
[file: ROPE] RETURNS [continue: BOOL]
If version part was missing from client's file name, then Enumerate will produce all versions, but we only want the !H version
Property stuff
no directory, so turn off directory defaulting and Desired-Property properties
Internal procedures
STP connection cacheing
need a new connection
already had a connection
Names with "." are GVNames (Grapevine names), so ask Grapevine to look them up
If successful, use the connect as the server name for STP.Open
Bob Hagmann April 29, 1985 9:12:49 am PDT
changes to: DIRECTORY, GetConnection
Bob Hagmann April 29, 1985 10:03:03 am PDT
changes to: DIRECTORY, GetConnection, GetServerPupName, LookForConnection, FSRemoteFileImpl

Bob Hagmann May 6, 1985 4:13:27 pm PDT
changes to: InnerEnumerate
Bob Hagmann August 2, 1985 3:25:38 pm PDT
changes to: maxSlots, TimeOut, GetServerPupName, FindInGrapevineCache, AddToGrapevineCache, LookForConnection, ForTimeout, GrapevineCacheEntry, maxGVineCache, grapevineCachePut, grapevineCache, GetConnection

Bob Hagmann August 2, 1985 4:37:37 pm PDT
changes to: grapevineCachePut

Êä��–
"cedar" style˜�code2šœ™Jšœ
Ïmœ=™HIcode1™%L™*L™JIcode™1M™-—šÏk	˜	Lšœ
žœžœžœ!˜<Lšžœžœ)˜1Lšœžœ$˜4Lšœžœ2˜>LšœžœK˜`Lšœ
žœ%˜7Lšœ	žœ˜Lšœ	žœ˜Lšœžœ˜(Lšžœžœžœ˜Lšœžœ2˜?Lšœžœ;žœ˜KLšœ
žœžœž˜Lšžœžœø˜Lšœžœ˜—šœžœž˜Lšžœžœežœ˜ŽLšžœ
˜Lšœ˜L˜�Lšžœžœ
žœ˜Lšœžœžœ˜Lšžœžœžœ˜Lšœžœ$˜:Lšžœžœžœžœ˜Lšœ	žœ˜#L˜�—šœ™š
Ïnœžœžœžœžœ%˜dšœ	žœ˜ Kšœžœžœ*žœ™EKšœžœžœ˜(Kšœ	žœ˜)šžœžœž˜Kšœ˜šžœžœ!˜2Kšœ	žœ-žœžœ˜KKšœ
žœ˜Kšœ˜—Kšžœ˜—Kšœ˜—Kšœžœžœ˜Kšœžœ+˜>Kšœ	žœ˜$Kšœžœ˜š	œ
žœžœžœžœ˜/Kšœ	žœ>˜KKšœžœ˜˜Kšžœžœ!˜.Kšœ!žœ˜(šžœ˜Kš	œžœ
žœžœžœ˜4—šžœžœžœ˜šžœžœ˜Kšœ!˜!Kšžœ˜Kšœ˜—Kš
žœžœžœžœžœ˜>Kšœ˜—K˜—Kšœ˜Kšœ˜—Kš
žœžœžœžœžœ˜GKšœ9˜9Kšœ˜—šŸœžœžœžœ˜Lšœ˜Kšœžœžœžœ™%Kšœžœžœ˜(Kšœžœ˜3šœžœ"žœ˜JKšžœžœ˜—Kšœ˜—Kšœ	žœ˜&Kšœ*žœ˜1Kšœ˜—šŸœžœžœžœ˜Mšœ˜Kšœžœžœžœ™%Kšœžœ"žœžœ˜@Kšœ˜—Kšœ	žœ˜&Kšœ*žœ˜0Kšœž˜—KšŸ
œžœžœžœžœžœ˜Tš
Ÿœžœžœžœ"žœ˜]KšœJ™JKšœ	žœ˜$Kšœžœ˜Kšœ
žœžœ˜šœžœ˜#šœ˜KšœH™H—Kšœžœ˜šžœžœžœ ˜BKšœ¢™¢—Kšœ˜K˜—šœžœžœ˜%Kšœžœ˜˜Kšžœžœ"˜/Kšœ0˜0Kšžœ"˜%K˜—Kšœ˜K˜—Kšœžœžœžœ,˜DKšœžœ,˜<š
žœžœžœžœžœžœž˜Bšœ˜šœžœ	˜šžœžœž˜šœ˜Kšœ‡™‡šžœžœž˜Kšœžœžœ
˜'Kšžœžœ˜——šœ
˜
Kšœq™qKšœžœ˜—šœ˜Kšœ{™{Kšœžœ˜—šžœ˜
Kšœj™jKšžœ˜———Kšœ˜—Kšœ`™`Kšžœ˜Kšžœ˜—Kšœ[™[Kšžœžœ=˜YKšžœ˜Kšœ˜—šŸœžœžœžœžœžœžœžœ˜wšŸœžœžœ˜.Kšœ9˜9Kšœ˜—Kšœ"˜"Kšœžœžœžœ,˜DKšœžœ,˜<Kšœ	žœžœ˜š
žœžœžœžœžœžœž˜BKšœžœ˜Kšœžœžœ˜KšœJ˜JKšžœžœžœ˜*šžœž˜šœ˜Kšœt™tšžœžœžœžœ˜KšœL™L—šžœ'žœ˜FKšœƒ™ƒ—šžœ˜K™}—K˜—šœ˜Kšœ,™,šžœ'žœžœ˜5Kšœ=™=—šžœžœžœ˜+KšÏc@™@—šžœžœžœžœ˜KšœÙ™Ù—Kšžœ˜—Kšžœ˜—Kšžœ˜—K™_Kšžœ	žœ#™2Kšœ£™£KšœE˜EKšœ˜—šŸœžœžœžœžœ
žœ%˜pKšœ	žœ˜$Kšœ?˜?š	œ
žœžœžœžœ˜/Kšœžœ˜Kšœ	žœ-˜:˜Kšžœžœ!˜.Kšœ%žœ˜+Kšœ5˜5Kšžœ˜ K˜—Kšœ˜Kšœ˜—šžœžœ˜Kš
žœžœžœžœžœ˜FKšœ;˜;K˜—Kšœ˜—šŸœžœžœžœžœžœžœ	žœžœžœžœ˜ŽKšœžœžœ˜Kšœžœ+˜;Kšœžœ˜Kšœ	žœ˜$Kšœ	žœ˜&šœ	žœ˜ Kšœžœžœ*žœ™FKšœžœžœ˜(Kšœ	žœ˜)šžœžœž˜Kšœ˜šžœžœ!˜2KšœC˜CKš	œ	žœžœžœžœ˜1Kšœ
žœ˜Kšœ˜—Kšžœ˜—Kšœ˜—šœžœžœžœžœžœ˜IKšœžœ˜˜Kšžœžœ$˜1Kšœ$žœ˜+šžœ˜Kš	œžœ
žœžœžœ˜4—šžœžœžœ˜šžœžœ˜Kšœ!˜!Kšžœ˜Kšœ˜—Kšžœžœžœ"žœ˜?Kšœ˜—K˜—Kšœ ˜ K˜—Kšœžœžœžœ,˜DKšœžœ,˜<š
žœžœžœžœžœžœž˜Bšžœ˜šœžœ˜Kšžœžœ˜)Kšžœ
žœžœžœ˜Kšžœžœžœžœ˜1Kšžœ˜—Kš
œžœžœžœžœžœžœ˜+—Kšžœ˜—Kšœ9˜9Kšœ˜—šŸœžœžœžœžœžœ%˜fKšœžœ˜šœžœ˜Kšœžœžœžœ™%Kšœžœ"žœ˜KKšœžœžœžœ˜$Kšœ˜—š	œžœžœžœžœ˜.Kšœ	žœ-˜:˜Kšžœžœ!˜.Kšœ!žœ˜'Kšžœ-˜0K˜—Kšœ˜Kšœ˜—Kšœ	žœ˜$Kš
žœžœžœžœžœ˜FKšœ9˜9Kšœ˜—šŸœžœžœ
žœžœžœ˜^Kšœžœ˜š
Ÿœžœ	žœžœžœ˜/Kšœžœ˜&šžœž˜
Kšœžœ˜Kšžœ
žœ˜Kšžœžœ	˜—K˜—š
Ÿœžœ	žœžœžœ˜5Kšžœ$˜*K˜—š
Ÿœžœ	žœžœžœ˜*Kšœžœ˜šžœž˜
Kšœžœžœ
žœ˜3Kšžœ
žœ˜$Kšžœžœ	˜—Kšœ˜—šŸœžœ˜Kšœ˜Kšœžœ˜šžœž˜Kšœ˜Kšžœ˜—šžœ	žœ	˜Kšžœ˜šžœ˜Kšœ‡™‡Kšœ˜Kšœ˜——Kšœ˜Kšœ˜Kšœ˜Kšœ˜šœžœ	ž˜Kš
œžœžœžœžœžœžœ˜LK˜Kšœžœžœžœ˜*Kšœžœžœžœ˜-K˜K˜K˜K˜Kšžœžœ	˜—šžœ˜šžœ˜Kšœ?™?Kšœ=˜=Kšœ˜Kšœ˜Kšœ˜—šžœ˜šœžœ
ž˜%K˜Kšœ	˜	Kšœ˜K˜Kšœ	˜	K˜K˜Kšžœžœ˜—Kšœ	žœžœžœ˜-Kšœ˜——Kšœ˜Kšœ˜—Kšœžœžœ˜,Kšœ˜—Kšœ	žœžœ˜—™šŸœžœžœžœžœžœžœ˜sKšœžœ˜šœžœ˜Kšœžœžœžœ™%Kšœžœžœ˜(Kšœ$˜$šžœžœžœ˜0šžœ˜Kšœ/˜/Kšœ˜Kšœ
žœ˜K™~Kšœžœžœžœ˜*Kšœ˜—Kšžœ˜—Kšœ˜—Kšœžœžœ˜Kšœžœ+˜;Kšœ	žœ˜$Kšœžœžœžœ,˜DKšœžœ,˜<šœžœžœžœ	žœžœ˜FKšžœžœ$˜1Kšœžœ˜Kšœ$žœ˜+šžœ˜Kš	œžœ
žœžœžœ˜4—šžœžœžœ˜šžœžœ˜Kšœ!˜!Kšžœ˜Kšœ˜—Kšžœžœžœ žœ˜>Kšœ˜—Kšœ ˜ K˜—š
žœžœžœžœžœžœž˜Bšžœ˜šœžœ˜Kšžœžœ˜)Kšžœ
žœžœžœ˜Kšžœžœžœžœ˜1Kšžœ˜—Kš
œžœžœžœžœžœžœ˜+—Kšžœ˜—Kšœ9˜9Kšœ˜——™Kšœžœžœžœžœžœ˜QKšœžœžœžœ˜&Kšœ-žœžœžœ˜Ršœ(˜(Kšœžœžœžœžœžœ˜N—Kšœžœžœžœ /˜WšŸœžœžœ
žœ˜Išžœžœ˜!šžœ˜KšœN™NKšžœ˜Kšžœ˜!Kšœ˜—šžœ˜Kšžœžœ˜Kšžœžœžœžœ˜NKšœ˜——Kšœ˜——™š
Ÿ
œžœ	žœžœžœ˜4šžœ˜Kšžœžœ	˜Kšžœžœ"˜-—Kšœ˜—š
Ÿœžœ	žœžœžœ˜6šžœ˜Kšžœžœ2˜=Kšžœžœ˜—Kšœ˜—šŸœžœžœžœžœžœ˜WKšœžœ,˜7Kšœžœ˜Kšœžœ˜Kšœ˜š
Ÿœžœ	žœžœžœ˜HKšžœžœ>˜FK˜—šžœžœ˜Kšœ$ ˜6Kšœ˜—šžœ	ž˜šœ+˜+Kšœ˜Kšœ˜Kšœ˜—šœ˜Kšœ˜Kšœ7˜7Kšœ˜—šœ˜Kšœ˜Kšœ*˜*Kšœ˜—šœ˜Kšœ˜Kšœ#˜#Kšœ˜—šœ˜Kšœ˜Kšœ(˜(Kšœ˜—šœ˜Kšœ˜Kšœ?˜?Kšœ˜—šœ˜Kšœ˜Kšœ
˜
Kšœ"˜"Kšœ˜—šœ˜Kšœ˜Kšœ.˜.Kšœ˜—šœ˜Kšœ˜Kšœ-˜-Kšœ˜—šœ˜Kšœ˜Kšœ'˜'Kšœ˜—šœ˜Kšœ@˜@—šœ˜Kšœ;˜;—šœ˜KšœM˜M—šœ˜KšœK˜K—šœ˜KšœK˜K—šœ˜KšœS˜S—šœ˜KšœG˜G—šœ˜Kšœ?˜?—šœ
˜
Kšœ5˜5—šœ˜Kšœ>˜>—Kšžœžœ˜—Kšœ7˜7Kšœ˜——™Kšœ
žœ 8˜PKšœ 1˜OKšœžœžœžœ˜.šœžœžœ˜Kšœžœ˜
Kšœ
˜
Kšœž˜
Kšœ˜—Kšœžœžœžœ˜$Kšœžœ 
˜,Kšœžœ
žœ
žœ˜7Kšœžœžœ˜Kšœž	œ%˜:Kšœžœžœžœ˜Lšœžœžœ˜#Kšœžœžœ˜Kšœ˜K˜—Kšœžœ (˜EKšœžœ ˜0Kšœžœžœ˜Cš
Ÿ
œžœžœ
žœžœ˜TKšœ˜šžœž˜
šžœ˜Kšœ™Kšœžœ˜Kšœžœ
˜Kšœ)˜)Kšžœ˜Kšœžœ#˜+Kšœ˜—šžœ˜Kšœ™šžœžœžœž˜Kšœ+˜+K˜—Kšœ˜——Kšœ+˜+Kšœ˜—š
Ðbnœžœ
žœžœ
žœ˜Jšžœžœžœ˜(K™NKšœ˜Kšœ˜Kšœžœ˜KšœN˜NKšžœžœžœ
˜%šœ=˜=Kšœ6žœ™>—šžœžœžœ˜+Kšœ4˜4Kšžœ
˜K˜—K˜—Kšžœ	˜K˜—šŸœžœžœžœžœ	žœžœ ˜jšžœžœžœž˜(Kš
žœ*žœžœžœžœ˜`Kšžœ˜—K˜—šŸœžœžœžœ ˜KKšœ.˜.Kšœ4˜4Kšžœ>žœ˜[K˜—š
Ÿœžœžœ	žœžœ˜CKšžœžœžœ˜šžœžœžœž˜#šžœžœ$žœžœ˜LKšœ˜Kšœ˜Kšœ˜Kšžœ˜Kšœ˜—Kšžœ˜—Kšœžœ˜Kšœ˜Kšœ˜—šŸœžœ
žœ˜4Kšžœ
žœžœžœžœžœ˜'š	žœžœžœžœž˜7Kšžœžœ
žœ˜&—Kšœ˜—šŸœžœžœ
žœ
žœžœ˜GKšžœžœžœ˜šžœžœžœž˜#šžœžœ˜Kšœžœ˜šžœžœžœžœ˜#Kšœžœ˜Kšœžœ˜!Kšœ˜—Kšž
œ˜Kšœ˜—Kšžœ˜—Kšžœ˜Kšœ˜—šŸ	œžœ˜Kšœžœ˜Kšœ
˜
šž˜Kšœ ˜ Kšžœžœžœžœ˜Kšžœžœ
žœ˜"Kšœ˜Kšžœ˜—Kšœ˜—šŸœžœžœ	žœžœžœ˜[Kšžœžœžœ˜šž˜Kšžœžœžœ˜"šžœžœž˜!šžœžœ˜!šžœ˜Kšžœž˜šžœ %˜,Kšœ˜Kšœ˜Kšžœ˜Kšœ˜——Kšœ˜—Kšžœ˜—šžœžœž˜Kšžœžœžœ ˜=šžœžœ ˜*Kšœžœ˜K˜Kšœžœ˜Kšžœ˜Kšœ˜—Kšžœ˜—K˜
Kšžœ˜—Kšœ˜——Kšœ˜™)MšœÏr™$—™*Mšœ¢O™[—M™�™&Mšœ¢™—™)Mšœ¢Ã™Ï—M™�™)Mšœ¢™—M™�—�…—����Jè��xÀ��