-- 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!