<> <> <> 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 <> 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]; 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: 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; <> 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; <> continue _ IF timeSearch THEN no ELSE yes; END ELSE continue _ yes; 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.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; <> 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; <> 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 _ "No quota for storing \"" }; 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; <> 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]; opened, reused, reopened: INT _ 0; -- statistics slot: ARRAY [0..maxSlots) OF Slot _ ALL [emptySlot]; haveSlotTimer: BOOLEAN _ FALSE; GetConnection: ENTRY PROC[server: Rope.ROPE] RETURNS [h: STP.Handle] = BEGIN ENABLE UNWIND => NULL; 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; IF NOT STP.IsOpen[h] THEN { [] _ STP.Open[h, slot[i].server]; reopened _ reopened + 1 }; slot[i] _ emptySlot; RETURN; END; ENDLOOP; BEGIN user, password: Rope.ROPE; h _ STP.Create[]; [user, password] _ UserCredentials.Get[]; STP.Login[h, user, password]; [] _ STP.Open[h, server]; opened _ opened + 1; END; END; ReturnConnection: ENTRY PROC [server: Rope.ROPE, h: STP.Handle] = BEGIN IF STP.IsOpen[h] THEN 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; END; ENDLOOP; END; SlotTimer: ENTRY PROC = BEGIN ForTimeout: CONDITION _ [Process.SecondsToTicks[TimeOut]]; DO noFullSlot: BOOLEAN _ TRUE; FOR i: CARDINAL IN [0..maxSlots) DO IF slot[i] # emptySlot THEN BEGIN IF slot[i].used THEN BEGIN slot[i].used _ FALSE; noFullSlot _ FALSE; END ELSE BEGIN STP.Close[slot[i].h ! STP.Error => CONTINUE ]; slot[i] _ emptySlot; END; END; ENDLOOP; IF noFullSlot THEN EXIT; WAIT ForTimeout; ENDLOOP; haveSlotTimer _ FALSE; END; END.