-- WFileSupport.mesa -- last edit: Stewart March 28, 1983 5:43 pm -- last edit: MBrown 29-Mar-82 15:12:47 DIRECTORY Ascii USING [CR, NUL, SP, TAB], ConvertUnsafe USING [AppendRope, ToRope], Date USING [PackedToString, StringToPacked], Directory USING [Error, RemoveFile, VolumeError], File USING [Capability, GetSize, Unknown], VFTOps USING [ ConfirmAbort, DestroyFile, Enumerate, FileExists, FreeBufferPages, FTPCommand, GetBufferPages, Handle, InvertIndicator, ListOptionsArray, NeedCommand, NeedConfirm, NoMoreRoom, PostComment, Task, WriteDiskForRetrieve], HeapString USING [AppendChar, AppendString], Inline USING [BITAND], IO USING [card, Handle, int, PutChar, PutF, string, time], KernelFile USING [MakeTemporary], PupStream USING [StreamClosing], Rope USING [Length, ROPE], RTFiles USING [IsFileInUse], Segments USING [ FHandle, FileNameProblem, FP, FPHandle, GetFileProperties, GetFileTimes, InsertFile, LockFile, ModifyFile, NullFP, Read, ReleasableFile, ReleaseFile, UnlockFile], Storage USING [ CopyString, EmptyString, FreeString, FreeStringNil, String, StringLength], STP USING [ CompletionProcType, CreateRemoteStream, Close, ConfirmProcType, Connect, ConnectionErrors, Create, Delete, Enumerate, Error, ErrorCode, FileInfo, FileInfoObject, GetFileInfo, Handle, Login, NextFileName, NoteFileProcType, Object, Open, Retrieve, SetDirectory, SetHost], Stream USING [ Block, Delete, EndOfStream, Handle, PutProcedure], Streams USING [ CreateStream, Destroy, End, GetBlock, GetByte, GetIndex, Handle, NewStream, SetIndex], String USING [AppendChar, AppendString, EquivalentString, StringBoundsFault], System USING [gmtEpoch], Time USING [Invalid, Packed], UserCredentials USING [GetUserCredentials]; WFileSupport: MONITOR LOCKS self.LOCK USING self: VFTOps.Handle IMPORTS ConvertUnsafe, Date, Directory, File, HeapString, Inline, IO, KernelFile, PupStream, Rope, RTFiles, Segments, Storage, Stream, Streams, STP, String, Time, UserCredentials, VFTOps EXPORTS VFTOps SHARES Segments = BEGIN -- global types and data -- Also in VFileSupportB PageSize: CARDINAL = 256; Byte: TYPE = [0..377B]; bufPages: CARDINAL = 6; -- also in VFileSUpportB wordsInBuffer: CARDINAL = bufPages*PageSize; bytesInBuffer: CARDINAL = wordsInBuffer*2; Buffer: TYPE = PACKED ARRAY [0..bytesInBuffer) OF Byte; WordBuffer: TYPE = PACKED ARRAY [0..wordsInBuffer) OF WORD; UserAbort: ERROR = CODE; FileTry: TYPE = {none, found, aborted}; FileTryProc: TYPE = PROC [STRING] RETURNS [FileTry]; -- cannot cause a bounds fault, but excercises Storage... RopeToString: PROC [from: Rope.ROPE, to: STRING] RETURNS [STRING] = { ropeLength: NAT _ from.Length[]; IF to#NIL THEN Storage.FreeString[to]; to _ Storage.String[ropeLength + 1]; -- wasn't there a bug in AppendSomething? to.length _ 0; ConvertUnsafe.AppendRope[to: to, from: from]; RETURN [to]; }; ReadDiskForStore: PROC [self: VFTOps.Handle, from: Streams.Handle, to: Stream.Handle] RETURNS [bytes: LONG CARDINAL] = { buffer: LONG POINTER TO Buffer = VFTOps.GetBufferPages[self, bufPages]; block: Stream.Block _ [buffer, 0, bytesInBuffer]; bytes _ 0; DO ENABLE UNWIND => VFTOps.FreeBufferPages[self, buffer]; block.stopIndexPlusOne _ Streams.GetBlock[from, buffer, wordsInBuffer]*2; IF Streams.GetIndex[from] MOD 2 # 0 THEN block.stopIndexPlusOne _ block.stopIndexPlusOne - 1; bytes _ bytes + block.stopIndexPlusOne; VFTOps.InvertIndicator[self]; to.put[to, block, FALSE]; IF block.stopIndexPlusOne # bytesInBuffer THEN EXIT; ENDLOOP; VFTOps.FreeBufferPages[self, buffer]; RETURN }; FileIsText: PROC [self: VFTOps.Handle, from: Streams.Handle] RETURNS [yes: BOOL] = { wordBuffer: LONG POINTER TO WordBuffer = VFTOps.GetBufferPages[self, bufPages]; wordsRead: CARDINAL; oddLength: BOOL _ FALSE; Streams.SetIndex[from, 0]; yes _ TRUE; DO ENABLE UNWIND => VFTOps.FreeBufferPages[self, wordBuffer]; wordsRead _ Streams.GetBlock[from, wordBuffer, wordsInBuffer]; IF wordsRead = 0 THEN EXIT; IF Streams.GetIndex[from] MOD 2 # 0 THEN { wordBuffer[wordsRead-1] _ Inline.BITAND[wordBuffer[wordsRead-1], 177400B]; oddLength _ FALSE}; FOR i: CARDINAL IN [0..wordsRead) DO IF Inline.BITAND[wordBuffer[i], 100200B] # 0 THEN GOTO binary ENDLOOP; IF wordsRead # wordsInBuffer OR oddLength THEN EXIT; REPEAT binary => yes _ FALSE; ENDLOOP; Streams.SetIndex[from, 0]; VFTOps.FreeBufferPages[self, wordBuffer]; RETURN; }; -- Enumerating remote files DeleteFiles: INTERNAL PROC [self: VFTOps.Handle, t: VFTOps.Task] = { remoteNames: STRING _ t.sourceFileName; fileTry: FileTry; DelConfirm: INTERNAL STP.ConfirmProcType = { self.log.PutF["%s ... ", IO.string[file]]; fileTry _ found; SELECT WaitForConfirm[self] FROM confirm => answer _ do; skip => { self.log.PutF["XXX\n"]; answer _ skip }; stop => {answer _ abort; fileTry _ aborted}; ENDCASE; RETURN[answer, NIL]}; DelComplete: INTERNAL STP.CompletionProcType = {self.log.PutF["%s\n", IO.string[fileOrError]];}; DeleteSome: INTERNAL PROC [rname: STRING] RETURNS [FileTry] = { fileTry _ none; STP.Delete[self.user, rname, DelConfirm, DelComplete ! STP.Error => IF code = noSuchFile THEN CONTINUE; UserAbort => self.opened _ FALSE]; RETURN[fileTry]}; IF Storage.EmptyString[remoteNames] THEN { self.log.PutF["Specify file names in 'Remote:' field or 'Path:' field\n"]; RETURN; }; self.log.PutF["Remote delete of %s\n", IO.string[remoteNames]]; ParseFiles[self, remoteNames, DeleteSome]; }; ListFiles: INTERNAL PROC [self: VFTOps.Handle, t: VFTOps.Task] = { remoteNames: STRING _ t.sourceFileName; fileTry: FileTry; currentDirectory: STRING _ Storage.String[2]; files: CARDINAL _ 0; names: STRING = IF Storage.EmptyString[remoteNames] THEN "*"L ELSE remoteNames; ListOne: INTERNAL STP.NoteFileProcType = { name: STRING = [100]; info: STP.FileInfo = STP.GetFileInfo[self.user]; IF --UserInput.userAbort OR-- self.pleaseStop THEN {fileTry _ aborted; RETURN[no]}; fileTry _ found; files _ files + 1; IF ~String.EquivalentString[currentDirectory, info.directory] THEN { PutNameInfo[self, info.directory, NIL, TRUE, -1]; Storage.FreeString[currentDirectory]; currentDirectory _ Storage.CopyString[info.directory]; }; String.AppendString[name, info.body]; IF ~Storage.EmptyString[info.version] THEN { String.AppendChar[name, file[file.length-info.version.length-1]]; String.AppendString[name, info.version]}; PutNameInfo[self, name, info, FALSE, -1]; RETURN[yes]}; ListSome: INTERNAL PROC [rname: STRING] RETURNS [FileTry] = { fileTry _ none; STP.Enumerate[self.user, rname, ListOne ! STP.Error => IF code = noSuchFile THEN CONTINUE]; SELECT fileTry FROM found => PutNameInfo[self, NIL, NIL, TRUE, -1]; aborted => self.opened _ FALSE; ENDCASE; RETURN[fileTry] }; IF Storage.EmptyString[remoteNames] THEN { self.log.PutF["Empty File Name\n"]; RETURN; }; self.log.PutF["Remote list of %s\n", IO.string[names]]; ParseFiles[self, names, ListSome ! UNWIND => Storage.FreeString[currentDirectory]]; Storage.FreeString[currentDirectory]; IF files > 1 THEN self.log.PutF[" Total of %d files\n", IO.int[files]]; }; RetrieveFiles: INTERNAL PROC [self: VFTOps.Handle, t: VFTOps.Task] = { remoteNames: STRING _ t.sourceFileName; localName: STRING _ t.destFileName; fileTry: FileTry; GetSome: INTERNAL PROC [remoteProbe: STRING] RETURNS [FileTry] = { info: STP.FileInfo; localFileName: STRING; remoteFileName: STRING _ NIL; create: Time.Packed; tc: LONG CARDINAL; fp: Segments.FP; oldFile: BOOL; fileSize: LONG CARDINAL; remoteStream: Stream.Handle _ NIL; Cleanup: PROC = { remoteFileName _ Storage.FreeStringNil[remoteFileName]; IF remoteStream # NIL THEN remoteStream.delete[remoteStream]; }; fileTry _ none; remoteStream _ STP.CreateRemoteStream[self.user, remoteProbe, read]; DO ENABLE UNWIND => { Cleanup[]; self.opened _ FALSE; }; remoteFileName _ Storage.FreeStringNil[remoteFileName]; -- free name from previous loop remoteFileName _ STP.NextFileName[remoteStream ! STP.Error => IF code = noSuchFile THEN CONTINUE]; IF remoteFileName = NIL THEN EXIT; fileTry _ found; info _ STP.GetFileInfo[self.user]; localFileName _ IF Storage.EmptyString[localName] THEN info.body ELSE localName; IF ~Segments.ModifyFile[localFileName] THEN { self.log.PutF["%s can't be modified\n", IO.string[localFileName]]; LOOP; }; fileSize _ info.size; create _ Date.StringToPacked[info.create ! Time.Invalid => { create _ System.gmtEpoch; CONTINUE}]; oldFile _ VFTOps.FileExists[self, localFileName, @fp]; self.log.PutF[" %s", IO.string[remoteFileName]]; IF self.update OR self.updateAlways THEN self.log.PutF[" [%t]", IO.time[create]]; self.log.PutF[" to local file %s", IO.string[localFileName]]; self.log.PutF[IF oldFile THEN " [Old File]" ELSE " [New File]"]; IF self.update OR (self.updateAlways AND oldFile) THEN { localTime: Time.Packed; fh: Segments.FHandle; IF ~oldFile THEN { self.log.PutF[" not retrieved\n"]; LOOP; }; fh _ Segments.InsertFile[@fp]; tc _ Segments.GetFileTimes[fh].create; localTime _ LOOPHOLE[tc]; self.log.PutF["[%t]", IO.time[localTime]]; IF Segments.ReleasableFile[fh] THEN Segments.ReleaseFile[fh]; IF localTime >= create THEN { self.log.PutF[" not retrieved\n"]; LOOP; }; }; SELECT WaitForConfirm[self] FROM confirm => { bytes: LONG CARDINAL; IF oldFile AND RTFiles.IsFileInUse[fp] THEN { -- Ask RTFiles if the file is in use self.log.PutF["[%s is in use. Removing from directory and making temporary.]", IO.string[localFileName]]; Directory.RemoveFile[fileName: localFileName, file: fp]; KernelFile.MakeTemporary[fp]; fp _ Segments.NullFP; oldFile _ FALSE; }; self.log.PutF["..."]; bytes _ VFTOps.WriteDiskForRetrieve[self, remoteStream, localFileName, @fp, create, remoteFileName, t ! STP.Error => IF code = accessDenied THEN { self.log.PutF["%s\n", IO.string[error]]; GOTO cantGetIt; }; VFTOps.NoMoreRoom[] => ERROR STP.Error[undefinedError, "No room on local disk"L]]; self.log.PutF["%d", IO.card[bytes]]; IF bytes # fileSize THEN { self.log.PutF[" bytes retrieved (file size hint = %d bytes!)\n", IO.card[fileSize]]; } ELSE self.log.PutF[" bytes\n"]; }; skip => self.log.PutF[" XXX\n"]; stop => {remoteStream.delete[remoteStream]; self.opened _ FALSE; RETURN[aborted]}; ENDCASE => ERROR; REPEAT cantGetIt => NULL; ENDLOOP; Cleanup[]; RETURN[fileTry]}; self.log.PutF["Retrieve of %s\n", IO.string[remoteNames]]; ParseFiles[self, remoteNames, GetSome]; }; StoreFiles: INTERNAL PROC [self: VFTOps.Handle, t: VFTOps.Task] = { remoteName: STRING _ t.destFileName; localNames: STRING _ t.sourceFileName; fileTry: FileTry; StoreIt: INTERNAL PROC [ remote: STRING, fh: Segments.FHandle, create: Time.Packed, size: LONG CARDINAL] = { local: Streams.Handle _ NIL; remoteStream: Stream.Handle _ NIL; bytesStored: LONG CARDINAL; self.log.PutF[" Storing to %s", IO.string[remote]]; SELECT WaitForConfirm[self] FROM confirm => {ENABLE UNWIND => { IF local # NIL THEN Streams.Destroy[local]; IF remoteStream # NIL THEN Stream.Delete[remoteStream]}; local _ Streams.CreateStream[fh]; remoteStream _ STP.CreateRemoteStream[ stp: self.user, file: remote, access: write, creation: create, fileType: IF FileIsText[self, local] THEN text ELSE binary]; self.log.PutF[" ..."]; bytesStored _ ReadDiskForStore[self: self, from: local, to: remoteStream]; Stream.Delete[remoteStream]; Streams.Destroy[local]; self.log.PutF["%d", IO.card[bytesStored]]; IF bytesStored # size THEN { self.log.PutF[" bytes stored (file size = %d bytes!)\n", IO.card[size]]; } ELSE self.log.PutF[" bytes\n"]; }; skip => self.log.PutF["XXX\n"]; stop => fileTry _ aborted; ENDCASE; }; StoreOne: INTERNAL PROC [fp: POINTER TO Segments.FP, file: STRING] RETURNS[done: BOOL] = { remote: STRING = IF Storage.StringLength[remoteName] = 0 THEN file ELSE remoteName; create: Time.Packed; tc: LONG CARDINAL; size: LONG CARDINAL; remoteExists: BOOL _ TRUE; fh: Segments.FHandle; fileTry _ found; IF --UserInput.userAbort OR-- self.pleaseStop THEN { fileTry _ aborted; RETURN[TRUE]}; fh _ Segments.InsertFile[fp]; Segments.LockFile[fh]; [create: tc, length: size, read:, write:] _ Segments.GetFileProperties[fh]; create _ LOOPHOLE[tc]; fileTry _ found; self.log.PutF[" %s", IO.string[file]]; IF ~self.update AND ~self.updateAlways THEN { self.log.PutChar[':]; StoreIt[remote, fh, create, size ! UNWIND => FreeLockedFile[fh]]; } ELSE { remoteCreate: Time.Packed; { remoteCreate _ GetRemoteTime[self, remote ! STP.Error => IF code = noSuchFile THEN GOTO NotThere]; EXITS NotThere => remoteExists _ FALSE; }; IF ~remoteExists AND ~self.updateAlways THEN { self.log.PutF[" No remote file.\n"]; GOTO cleanup; }; self.log.PutF[" [%t] (remote file [", IO.time[create]]; IF remoteExists THEN self.log.PutF["%t])", IO.time[remoteCreate]] ELSE self.log.PutF["New File])"]; IF (self.updateAlways AND ~remoteExists) OR create > remoteCreate THEN StoreIt[remote, fh, create, size ! UNWIND => FreeLockedFile[fh]] ELSE self.log.PutF[" not stored\n"]; EXITS cleanup => NULL}; FreeLockedFile[fh]; RETURN[fileTry = aborted]}; StoreSome: INTERNAL PROC [local: STRING] RETURNS [FileTry] = { fileTry _ none; VFTOps.Enumerate[self, local, StoreOne, FALSE]; RETURN[fileTry]}; self.log.PutF["Store of %s\n", IO.string[localNames]]; ParseFiles[self, localNames, StoreSome]; }; -- Enumerations on local files DeleteLocal: INTERNAL PROC [self: VFTOps.Handle, t: VFTOps.Task] = { localNames: STRING _ t.sourceFileName; count: CARDINAL _ 0; fileTry: FileTry; lastFP: Segments.FP _ Segments.NullFP; lastName: STRING _ NIL; DeleteLast: INTERNAL PROC = { IF lastFP = Segments.NullFP THEN RETURN; self.log.PutF["%s", IO.string[lastName]]; IF Segments.ModifyFile[lastName] THEN { self.log.PutF[" ... "]; SELECT WaitForConfirm[self] FROM confirm => { VFTOps.DestroyFile[self, @lastFP, lastName]; self.log.PutF["deleted.\n"]; count _ count + 1}; skip => self.log.PutF["XXX\n"]; stop => fileTry _ aborted; ENDCASE} ELSE self.log.PutF[" can't be modified\n"]; lastFP _ Segments.NullFP; lastName _ Storage.FreeStringNil[lastName]; }; DeleteOne: INTERNAL PROC [fp: Segments.FPHandle, file: STRING] RETURNS[done: BOOL] = { fileTry _ found; DeleteLast[]; lastFP _ fp^; lastName _ Storage.CopyString[file]; RETURN[fileTry = aborted]; }; DeleteSome: INTERNAL PROC [name: STRING] RETURNS [FileTry] = { fileTry _ none; VFTOps.Enumerate[self, name, DeleteOne, TRUE]; DeleteLast[]; RETURN[fileTry]}; IF Storage.EmptyString[localNames] THEN { self.log.PutF["Specify file names in 'Local:' field\n"]; RETURN; }; self.log.PutF["Local delete of %s\n", IO.string[localNames]]; ParseFiles[self, localNames, DeleteSome]; IF fileTry # aborted AND count > 1 THEN { self.log.PutF["%d files deleted.\n", IO.int[count]]; }; }; ListLocal: INTERNAL PROC [self: VFTOps.Handle, t: VFTOps.Task] = { localNames: STRING _ t.sourceFileName; count: CARDINAL _ 0; fileTry: FileTry; currentDirectory: STRING _ Storage.String[2]; thisDir: STRING _ Storage.String[50]; thisName: STRING _ Storage.String[50]; Cleanup: PROC = { currentDirectory _ Storage.FreeStringNil[currentDirectory]; thisDir _ Storage.FreeStringNil[thisDir]; thisName _ Storage.FreeStringNil[thisName]; }; ListOne: INTERNAL PROC [fp: POINTER TO Segments.FP, file: STRING] RETURNS[done: BOOL] = { info: STP.FileInfoObject; create, write, read: Time.Packed; tc, tw, tr: LONG CARDINAL; size: LONG CARDINAL; fh: Segments.FHandle; fileThere: BOOL _ TRUE; filePages: LONG INTEGER; fileTry _ found; IF --UserInput.userAbort OR-- self.pleaseStop THEN { fileTry _ aborted; RETURN[TRUE]}; count _ count + 1; fh _ Segments.InsertFile[fp]; [read: tr, write: tw, create: tc, length: size] _ Segments.GetFileProperties[fh ! File.Unknown => {fileThere _ FALSE; CONTINUE}]; filePages _ File.GetSize[fh.cap ! File.Unknown => {fileThere _ FALSE; CONTINUE}]; IF fileThere THEN { read _ LOOPHOLE[tr]; write _ LOOPHOLE[tw]; create _ LOOPHOLE[tc]; IF Segments.ReleasableFile[fh] THEN Segments.ReleaseFile[fh]; info _ [ body: file, size: size, create: Date.PackedToString[create], read: Date.PackedToString[read], write: Date.PackedToString[write]]; }; thisName.length _ thisDir.length _ 0; FOR i: CARDINAL IN [0..file.length) DO IF file[i] = '> THEN { HeapString.AppendString[@thisDir, thisName]; HeapString.AppendChar[@thisDir, '>]; thisName.length _ 0} ELSE HeapString.AppendChar[@thisName, file[i]]; ENDLOOP; IF ~String.EquivalentString[currentDirectory, thisDir] THEN { PutNameInfo[self, thisDir, NIL, TRUE, -1]; currentDirectory.length _ 0; HeapString.AppendString[@currentDirectory, thisDir]; }; IF fileThere THEN { PutNameInfo[self, thisName, @info, FALSE, filePages]; Storage.FreeString[info.create]; Storage.FreeString[info.read]; Storage.FreeString[info.write]; } ELSE self.log.PutF[" %s: directory entry but no file!\n", IO.string[thisName]]; RETURN[FALSE] }; ListSome: INTERNAL PROC [filter: STRING] RETURNS [FileTry] = { fileTry _ none; VFTOps.Enumerate[self, filter, ListOne, TRUE]; IF fileTry = found THEN PutNameInfo[self, NIL, NIL, TRUE, -1]; RETURN[fileTry]}; names: STRING = localNames; IF Storage.EmptyString[localNames] THEN { self.log.PutF["Empty file name.\n"]; Cleanup[]; RETURN; }; self.log.PutF["Local list of %s\n", IO.string[names]]; ParseFiles[self, names, ListSome ! UNWIND => Cleanup[]]; Cleanup[]; IF fileTry # aborted AND count > 1 THEN self.log.PutF["Total of %d files\n", IO.int[count]]; }; FreeLockedFile: PROC [file: Segments.FHandle] = { Segments.UnlockFile[file]; IF Segments.ReleasableFile[file] THEN Segments.ReleaseFile[file]; }; -- utility routines CallProc: PROC [self: VFTOps.Handle, proc: FileTryProc, token: STRING] = { SELECT proc[token] FROM none => self.log.PutF[" %s not found\n", IO.string[token]]; aborted => { self.log.PutF["... aborted.\n"]; ERROR UserAbort; }; ENDCASE; }; ParseFiles: PROC [self: VFTOps.Handle, tokens: STRING, proc: FileTryProc] = { index: CARDINAL _ 0; token: STRING = [100]; GetCharFromString: PROC RETURNS [c: CHAR] = { IF index >= tokens.length THEN RETURN [Ascii.NUL]; c _ tokens[index]; index _ index + 1; }; IF Storage.EmptyString[tokens] THEN RETURN; DO ENABLE UserAbort => GOTO done; GetToken[GetCharFromString, token]; IF token.length = 0 THEN EXIT; IF token[0] = '@ THEN ParseFromFile[self, token, proc] ELSE CallProc[self, proc, token]; ENDLOOP; EXITS done => NULL; }; ParseFromFile: PROC [self: VFTOps.Handle, file: STRING, proc: FileTryProc] = { OPEN Streams; token: STRING = [100]; stream: Streams.Handle _ NIL; GetCharFromFile: PROC RETURNS [c: CHAR] = {c _ GetByte[stream ! End[] => {c _ Ascii.NUL; CONTINUE}]}; { ENABLE UNWIND => Streams.Destroy[stream]; FOR i: CARDINAL IN [0..file.length-1) DO file[i] _ file[i+1] ENDLOOP; file.length _ file.length - 1; stream _ NewStream[file, Segments.Read ! Segments.FileNameProblem[] => CONTINUE]; IF stream = NIL THEN {VFTOps.PostComment[self, "Indirect file not found."]; RETURN}; DO GetToken[GetCharFromFile, token]; IF token.length = 0 THEN EXIT; IF token[0] = '@ THEN ParseFromFile[self, token, proc] ELSE CallProc[self, proc, token]; ENDLOOP; Streams.Destroy[stream]; }; -- of enable }; GetToken: PROC [get: PROC RETURNS [CHAR], s: STRING] = { OPEN String; c: CHAR; s.length _ 0; WHILE (c _ get[]) # Ascii.NUL DO SELECT c FROM Ascii.SP, Ascii.CR, Ascii.TAB => IF s.length # 0 THEN EXIT; '@ => IF s.length > 0 THEN NULL ELSE AppendChar[s, c ! StringBoundsFault => CONTINUE]; '^ => NULL; ENDCASE => AppendChar[s, c ! StringBoundsFault => CONTINUE]; ENDLOOP; RETURN }; GetRemoteTime: PROC [self: VFTOps.Handle, name: STRING] RETURNS [create: Time.Packed] = { GetDate: STP.ConfirmProcType = { info: STP.FileInfo = STP.GetFileInfo[self.user]; create _ Date.StringToPacked[info.create ! Time.Invalid => { create _ System.gmtEpoch; CONTINUE}]; RETURN[skip, NIL]; }; STP.Retrieve[self.user, name, GetDate]; }; PutNameInfo: PROC [self: VFTOps.Handle, name: STRING, info: STP.FileInfo, isDire: BOOL, pages: LONG INTEGER] = { noOptions: VFTOps.ListOptionsArray = ALL[FALSE]; IF isDire THEN { IF self.options = noOptions THEN self.log.PutChar['\n]; IF name # NIL THEN self.log.PutF["%s\n", IO.string[name]]; } ELSE { space: STRING _ " - - -"L; self.log.PutChar[' ]; IF self.options # noOptions THEN self.log.PutChar[' ]; self.log.PutF["%s", IO.string[name]]; IF self.options # noOptions THEN { SP: CHAR = Ascii.SP; THROUGH [0..25 - MIN[24, name.length]) DO self.log.PutChar[' ]; ENDLOOP; IF self.options[type] THEN self.log.PutF[SELECT info.type FROM text => "text ", binary => "binary ", unknown => "unknown ", ENDCASE => ERROR]; IF self.options[bytes] THEN self.log.PutF["%8d", IO.card[info.size]]; IF self.options[pages] AND pages >= 0 THEN self.log.PutF[" (%d)", IO.int[pages]]; -- PutF breaks on NIL !! (my bug too, sigh) IF self.options[create] THEN self.log.PutF[" %s", IO.string[IF info.create=NIL THEN space ELSE info.create]]; IF self.options[write] THEN self.log.PutF[" %s", IO.string[IF info.write=NIL THEN space ELSE info.write]]; IF self.options[read] THEN self.log.PutF[" %s", IO.string[IF info.read=NIL THEN space ELSE info.read]]; IF self.options[author] AND info.author # NIL THEN { self.log.PutF[" %s", IO.string[info.author]]; }; self.log.PutChar['\n]; }}}; -- background loop and process stuff CloseCommand: PUBLIC ENTRY PROC [self: VFTOps.Handle] = { ENABLE UNWIND => NULL; self.running _ TRUE; CloseConnection[self]; self.pleaseStop _ self.running _ FALSE; }; CloseConnection: INTERNAL PROC [self: VFTOps.Handle, giveMessage: BOOL _ TRUE] = { ok: BOOL _ TRUE; IF giveMessage THEN self.log.PutF["Closing connection ... "]; STP.Close[self.user ! STP.Error => { IF code # noConnection THEN { VFTOps.PostComment[self, ConvertUnsafe.ToRope[error]]; ok _ FALSE; }; CONTINUE}]; self.opened _ FALSE; IF ok AND giveMessage THEN self.log.PutF[" Connection now closed\n"]; }; OpenConnection: INTERNAL PROC [self: VFTOps.Handle, t: VFTOps.Task] RETURNS [BOOL] = { IF ~self.opened THEN { herald: STRING _ NIL; IF Storage.EmptyString[t.host] THEN { VFTOps.PostComment[self, "No host specified."]; RETURN[FALSE]}; self.log.PutF["Opening connection ... "]; herald _ STP.Open[self.user, t.host ! STP.Error => { IF code = alreadyAConnection THEN CONTINUE; VFTOps.PostComment[self, ConvertUnsafe.ToRope[error]]; GOTO failed; }]; STP.SetHost[self.user, t.host]; IF herald # NIL THEN { self.log.PutF["\n%s\n", IO.string[herald]]; Storage.FreeString[herald]; }; self.opened _ TRUE; EXITS failed => NULL; }; RETURN[self.opened] }; WaitForConfirm: INTERNAL PROC [self: VFTOps.Handle] RETURNS [c: VFTOps.ConfirmAbort] = { ENABLE UNWIND => NULL; IF --UserInput.userAbort OR-- self.pleaseStop OR self.pleaseStop THEN RETURN[stop]; IF ~self.verify THEN RETURN[confirm]; VFTOps.NeedConfirm[self]; WHILE self.confirmState = waiting AND ~self.pleaseStop AND ~self.pleaseStop DO WAIT self.haveConfirm; ENDLOOP; c _ self.confirmState; self.confirmState _ waiting; IF self.pleaseStop THEN c _ stop; }; GiveConfirm: PUBLIC ENTRY PROC[self: VFTOps.Handle, confirm: VFTOps.ConfirmAbort] = { ENABLE UNWIND => NULL; SELECT confirm FROM confirm => self.confirmState _ confirm; skip => self.confirmState _ skip; stop => self.confirmState _ stop; ENDCASE => ERROR; NOTIFY self.haveConfirm; }; GiveCommand: PUBLIC ENTRY PROC [self: VFTOps.Handle, command: VFTOps.FTPCommand] = {{ ENABLE UNWIND => TaskCleanup[self]; name, password: Rope.ROPE; needSTP: BOOL = SELECT command FROM store, remoteList, remoteDelete, retrieve => TRUE, ENDCASE => FALSE; self.running _ TRUE; [name: name, password: password] _ UserCredentials.GetUserCredentials[]; self.task.userName _ RopeToString[from: name, to: self.task.userName]; self.task.userPassword _ RopeToString[from: password, to: self.task.userPassword]; self.task.connectName _ RopeToString[from: self.connectNameRope, to: self.task.connectName]; self.task.connectPassword _ RopeToString[from: self.cPasswordRope, to: self.task.connectPassword]; self.task.host _ RopeToString[from: self.hostRope, to: self.task.host]; self.task.sourceFileName _ RopeToString[from: self.sourceRope, to: self.task.sourceFileName]; self.task.destFileName _ RopeToString[from: self.destRope, to: self.task.destFileName]; self.task.directory _ RopeToString[from: self.directoryRope, to: self.task.directory]; self.task.task _ command; IF self.pleaseStop THEN GOTO Cleanup; IF needSTP THEN { IF self.user = NIL THEN self.user _ STP.Create[]; IF ~OpenConnection[self, self.task] THEN { VFTOps.NeedCommand[self]; GOTO Cleanup; }; STP.Login[self.user, self.task.userName, self.task.userPassword]; STP.Connect[self.user, self.task.connectName, self.task.connectPassword]; IF self.user # NIL THEN STP.SetDirectory[self.user, self.task.directory] }; { ENABLE { PupStream.StreamClosing => { VFTOps.PostComment[self, ConvertUnsafe.ToRope[text]]; CONTINUE; }; STP.Error => { VFTOps.PostComment[self, ConvertUnsafe.ToRope[error]]; IF code IN STP.ConnectionErrors THEN CloseConnection[self, TRUE]; CONTINUE; }; Directory.Error => SELECT type FROM invalidFileName => { self.log.PutF[" ... illegal file name syntax.\n"]; CONTINUE; }; fileIsSD => { self.log.PutF[" ... file is subdirectory.\n"]; CONTINUE; }; ENDCASE; Segments.FileNameProblem[] => { self.log.PutF[" ... file name problem.\n"]; CONTINUE; }; Directory.VolumeError => { VFTOps.PostComment[self, "Volume error: cannot access other volumes."]; CONTINUE; }; }; SELECT self.task.task FROM store => StoreFiles[self: self, t: self.task]; remoteList => ListFiles[self: self, t: self.task]; remoteDelete => DeleteFiles[self: self, t: self.task]; retrieve => RetrieveFiles[self: self, t: self.task]; localDelete => DeleteLocal[self: self, t: self.task]; localList => ListLocal[self: self, t: self.task]; ENDCASE => ERROR; }; -- ENABLE EXITS Cleanup => NULL; }; TaskCleanup[self]; }; TaskCleanup: INTERNAL PROC [self: VFTOps.Handle] = { self.task _ FreeTask[self.task]; CloseConnection[self]; VFTOps.NeedCommand[self]; self.running _ FALSE; NOTIFY self.waitForHalt; }; FreeTask: PUBLIC PROC [t: VFTOps.Task] RETURNS [r: VFTOps.Task _ []] = { IF t.userName # NIL THEN Storage.FreeString[t.userName]; IF t.userPassword # NIL THEN Storage.FreeString[t.userPassword]; IF t.connectName # NIL THEN Storage.FreeString[t.connectName]; IF t.connectPassword # NIL THEN Storage.FreeString[t.connectPassword]; IF t.host # NIL THEN Storage.FreeString[t.host]; IF t.sourceFileName # NIL THEN Storage.FreeString[t.sourceFileName]; IF t.destFileName # NIL THEN Storage.FreeString[t.destFileName]; IF t.directory # NIL THEN Storage.FreeString[t.directory]; }; StopFTP: PUBLIC PROC [self: VFTOps.Handle] = { self.pleaseStop _ TRUE; StopFTPInternal[self]; self.pleaseStop _ FALSE; }; StopFTPInternal: ENTRY PROC [self: VFTOps.Handle] = { ENABLE UNWIND => NULL; WHILE self.running DO WAIT self.waitForHalt; ENDLOOP; }; -- main program END. Phil Karlton; 2-Mar-81 10:48:10 Mark; 12-Mar-81 20:31:21 16-Jan-82 15:56:15, Stewart, created from FileSupport.mesa March 27, 1982 9:28 pm, Stewart, NIL string problem June 7, 1982 7:31 pm, Stewart, pages option July 4, 1982 5:22 pm, Stewart, RTFiles September 16, 1982 3:23 pm, Stewart, always close connection November 4, 1982 10:16 am, Stewart, MakeTemporary ONLY if retrieve!