STPSubrImpl.Mesa
last edit March 8, 1983 12:37 pm
last edit May 23, 1983 5:59 pm, Russ Atkinson
reduced use of STRING in favor of LONG STRING
DIRECTORY
CIFS: TYPE USING[Close, create, GetFC, Open, OpenFile, replace, write],
ConvertUnsafe: TYPE USING[ToRope],
CWF: TYPE USING [SWF1, SWF4, WF0, WF1, WF2, WFCR],
DCSFileTypes: TYPE USING [tLeaderPage],
Directory: TYPE USING [CreateFile, DeleteFile, Error, Handle, ignore, Lookup, Rename],
Environment: TYPE USING [bytesPerPage],
File: TYPE USING [Capability, nullCapability, Permissions, read, SetSize],
FileStream: TYPE USING [Create, GetLength, SetLeaderPropertiesForCapability],
FQ: TYPE USING[FileQuery, Result],
Heap: TYPE USING[systemZone],
Inline: TYPE USING [BITNOT, LowHalf],
IO: TYPE USING[card, PutF, string, UserAbort],
LongString: TYPE USING [AppendChar, EquivalentString],
Process: TYPE USING [Pause, SecondsToTicks],
Space: TYPE USING [Create, Delete, Handle, LongPointer, Map, nullHandle, virtualMemory],
UnsafeSTP: TYPE USING [Connect, Create, CreateRemoteStream, DesiredProperties, Destroy,
Error, ErrorCode, FileInfo, GetFileInfo, Handle,
Login, NextFileName, Open, SetDesiredProperties, Type],
UnsafeSTPOps: TYPE USING [FindFileType, Handle, SetPListItem],
STPSubr: TYPE USING [RetrieveProcType, StpState],
Stream: TYPE USING [Delete, EndOfStream, GetBlock, Handle, PutBlock],
Subr: TYPE USING [AbortMyself, AllocateString, Any, CopyString, CursorInWindow,
EndsIn, errorflg, FileError, FreeString, GetCreateDate, GetNameandPassword,
LongZone, NewStream, Prefix, Read, SetRemoteFilenameProp, strcpy,
SubStrCopy, TTYProcs, Write],
UserCredentialsUnsafe: TYPE USING[GetUserCredentials],
UserTerminal: TYPE USING [cursor, CursorArray, GetCursorPattern, SetCursorPattern];
STPSubrImpl: PROGRAM
IMPORTS CIFS, ConvertUnsafe, CWF, Directory, File, FileStream, FQ, Heap,
Inline, IO, LongString, Process, Space, STP: UnsafeSTP,
STPOps: UnsafeSTPOps, Stream, Subr, UserCredentialsUnsafe, UserTerminal
EXPORTS STPSubr
SHARES File = {
MDS USAGE !!!
useCIFS: BOOLTRUE;
connseq: LONG POINTER TO ConnSeqRecord ← NIL;
maxNumberOfTries: CARDINAL ← 10; -- # tries to get connected if rejecting
endof MDS USAGE !!!
connection table
MAXCONNS: CARDINAL = 8;
we use the username and password from Profile.userName, userPassword
ConnSeqRecord: TYPE = RECORD[
size: CARDINAL ← 0, -- # of connections
body: SEQUENCE maxsize: CARDINAL OF ConnTable
];
ConnTable: TYPE = RECORD[
stphandle: STP.Handle ← NIL,
host: LONG STRINGNIL
];
SetUseOfCIFS: PUBLIC PROC[shouldUseCIFS: BOOL] = {
useCIFS ← shouldUseCIFS; -- in a procedure to make sure the module is started
};
EnumerateForRetrieve: PUBLIC PROC[host, filePattern: LONG STRING,
enumProc: STPSubr.RetrieveProcType, h: Subr.TTYProcs, onlyOne: BOOL] ={
stp: STP.Handle;
remoteStream: Stream.Handle ← NIL;
remoteName: LONG STRINGNIL;
skipRest, tryAgain: BOOL;
desiredProperties: STP.DesiredProperties ← ALL[FALSE];
shortFilePattern: LONG STRING ← Subr.AllocateString[100];
Cleanup: PROC = {
IF remoteName ~= NIL THEN Heap.systemZone.FREE[@remoteName];
IF remoteStream ~= NIL THEN Stream.Delete[remoteStream];
remoteStream ← NIL;
};
TryIt: PROC = {
nested procedure needed to handle retrys of entire enumerates
skipRest ← FALSE;
remoteName ← NIL;
remoteStream ← STP.CreateRemoteStream[stp, shortFilePattern, read
! STP.Error =>
IF HandleSTPError[stp, code, error, h] THEN RETRY
];
DO
ENABLE UNWIND => Cleanup[];
IF remoteName ~= NIL THEN Heap.systemZone.FREE[@remoteName];
may generate STP.Error!
remoteName ← STP.NextFileName[remoteStream];
IF remoteName = NIL THEN EXIT;
IF skipRest THEN LOOP;
skipRest ← enumProc[remoteName, stp, remoteStream];
ENDLOOP;
Cleanup[];
};
{ENABLE UNWIND => Subr.FreeString[shortFilePattern];
Subr.strcpy[shortFilePattern, filePattern];
desiredProperties[directory] ← TRUE;
desiredProperties[nameBody] ← TRUE;
desiredProperties[version] ← TRUE;
desiredProperties[createDate] ← TRUE;
desiredProperties[size] ← TRUE;
DO
tryAgain ← FALSE;
stp ← Connect[host: host, h: h, onlyOne: onlyOne];
STP.SetDesiredProperties[stp, desiredProperties];
TryIt[ ! STP.Error =>
IF HandleSTPError[stp, code, error, h] THEN {
tryAgain ← TRUE;
CONTINUE;
}
ELSE IF code = connectionClosed THEN {
CWF.WF1["Connection to %s timed out.\n"L, host];
tryAgain ← TRUE;
CONTINUE;
};
];
IF NOT tryAgain THEN EXIT;
stp ← ForceClosed[stp];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
ENDLOOP;
}; -- of ENABLE UNWIND
Subr.FreeString[shortFilePattern];
};
GeneralOpen: PUBLIC PROC[filename: LONG STRING, h: Subr.TTYProcs,
access: File.Permissions, fileType: STP.Type, -- FileType -- createtime: LONG CARDINAL]
RETURNS[sh: Stream.Handle, stphandle: STP.Handle] = {
if not found, raises Subr.FileError, for both remote and local files
filename looks like [host]<dir>name
tryAgain: BOOL;
desiredProperties: STP.DesiredProperties ← ALL[FALSE];
host, sfn: LONG STRINGNIL;
sh ← NIL;
local case
IF filename[0] ~= '[ AND filename[0] ~= '< THEN {
sh ← Subr.NewStream[filename, access];
RETURN[sh, NIL];
};
remote file name
host ← Subr.AllocateString[40];
sfn ← Subr.AllocateString[100];
{ENABLE UNWIND => {Subr.FreeString[host]; Subr.FreeString[sfn]};
Subr.strcpy[sfn, filename];
StripHost[host, sfn];
StartSTP[];
DO
tryAgain ← FALSE;
stphandle ← Connect[host, h, FALSE];
desiredProperties[directory] ← TRUE;
desiredProperties[nameBody] ← TRUE;
desiredProperties[version] ← TRUE;
desiredProperties[createDate] ← TRUE;
desiredProperties[size] ← TRUE;
CWF.WF1["%s ... "L, filename];
STP.SetDesiredProperties[stphandle, desiredProperties];
sh ← STP.CreateRemoteStream[stp: stphandle,
file: sfn,
access: IF access = Subr.Read THEN read ELSE write,
fileType: IF access = Subr.Read THEN unknown ELSE
(IF fileType = unknown THEN GetFileType[sfn] ELSE fileType),
creation: LOOPHOLE[createtime]
! STP.Error =>
IF code = noSuchFile THEN {
CWF.WF2["Error - %s: %s.\n\n"L, filename, error];
ERROR Subr.FileError[notFound];
}
ELSE IF code = connectionClosed THEN {
CWF.WF1["Connection to %s timed out.\n"L, host];
tryAgain ← TRUE;
CONTINUE;
}
ELSE IF HandleSTPError[stphandle,code,error,h] THEN RETRY];
IF NOT tryAgain THEN EXIT;
stphandle ← ForceClosed[stphandle];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
ENDLOOP;
}; -- of ENABLE UNWIND
Subr.FreeString[host]; Subr.FreeString[sfn];
};
PatternGeneralOpen: PUBLIC PROC
[filepattern: LONG STRING, proc: STPSubr.RetrieveProcType, h: Subr.TTYProcs] = {
just like GeneralOpen but the filename can be a pattern, e.g. with * in it
host, sfn: LONG STRINGNIL;
local case
IF filepattern[0] ~= '[ AND filepattern[0] ~= '< THEN {
sh: Stream.Handle;
sh ← Subr.NewStream[filepattern, Subr.Read
! Subr.FileError => GOTO err];
[] ← proc[filepattern, NIL, sh];
Stream.Delete[sh];
RETURN;
EXITS err => Subr.errorflg ← TRUE;
};
host ← Subr.AllocateString[40];
sfn ← Subr.AllocateString[100];
{ENABLE UNWIND => {Subr.FreeString[host]; Subr.FreeString[sfn]};
Subr.strcpy[sfn, filepattern];
StripHost[host, sfn];
EnumerateForRetrieve[host, sfn, proc, h, TRUE
! STP.Error =>
IF code = noSuchFile THEN {
CWF.WF2["Error - %s: %s.\n\n"L, filepattern, error];
GOTO err;
}
];
EXITS err => Subr.errorflg ← TRUE}; -- of ENABLE UNWIND
Subr.FreeString[host]; Subr.FreeString[sfn];
};
CachedOpen: PUBLIC PROC[host, directory, shortname: LONG STRING,
version: CARDINAL, wantcreatetime: LONG CARDINAL, h: Subr.TTYProcs,
stpState: STPSubr.StpState, wantExplicitVersion, onlyOne, tryDollars: BOOL]
RETURNS[sh: Stream.Handle, cap: File.Capability] = {
may raise Subr.FileError
cap ← CachedRetrieve[host, directory, shortname, version, wantcreatetime, h,
stpState, wantExplicitVersion, onlyOne, tryDollars];
sh ← NIL;
IF cap ~= File.nullCapability
THEN sh ← FileStream.Create[[cap.fID, File.read]]
ELSE
this case can only happen if the user said no to the retrieval
in which case the existing file is used
sh ← Subr.NewStream[shortname, Subr.Read];
RETURN[sh, cap];
};
CachedRetrieve: PUBLIC PROC[host, directory, shortname: LONG STRING,
version: CARDINAL, wantcreatetime: LONG CARDINAL, h: Subr.TTYProcs,
stpState: STPSubr.StpState, wantExplicitVersion, onlyOne, tryDollars: BOOL]
RETURNS[cap: File.Capability ← File.nullCapability] = {
localOnly: BOOL ← host = NIL OR host.length = 0;
localcreatetime: LONG CARDINAL ← 0;
fileNotRetrieved: BOOLFALSE;
{
cap ← Directory.Lookup[fileName: shortname, permissions: Directory.ignore
! Directory.Error => GOTO notonlocal];
localcreatetime ← Subr.GetCreateDate[cap];
IF localcreatetime = wantcreatetime
OR (wantcreatetime = 0 AND localOnly) THEN RETURN;
EXITS notonlocal => NULL;
};
try with $$ on end
IF tryDollars THEN {
sDollar: LONG STRING ← Subr.AllocateString[100];
checkLocalDate: BOOLTRUE;
{ENABLE UNWIND => Subr.FreeString[sDollar];
CWF.SWF1[sDollar, "%s$$"L, shortname];
cap ← Directory.Lookup[fileName: sDollar, permissions: Directory.ignore
! Directory.Error => {checkLocalDate ← FALSE; CONTINUE}];
};
Subr.FreeString[sDollar];
IF checkLocalDate THEN {
localcreatetime ← Subr.GetCreateDate[cap];
IF localcreatetime = wantcreatetime THEN RETURN;
};
};
look on remote server
IF NOT localOnly THEN {
targetFileName: LONG STRING ← Subr.AllocateString[125];
localname: LONG STRING ← Subr.AllocateString[100];
dollar: LONG STRING ← Subr.AllocateString[100];
sfn: LONG STRING ← Subr.AllocateString[100];
remoteVersion: CARDINAL;
remoteCreateTime: LONG CARDINAL;
OneFile: STPSubr.RetrieveProcType = {
info: STP.FileInfo ← STP.GetFileInfo[stp];
skipRest ← TRUE;
IF tryDollars AND localcreatetime ~= 0 THEN
CWF.SWF1[localname, "%s$$"L, shortname]
ELSE {
IF stpState.checkForOverwrite
AND localcreatetime ~= 0
THEN
SELECT CheckForOverwrite[shortname, localcreatetime,
targetFileName, remoteCreateTime, h] FROM
no => {
fileNotRetrieved ← TRUE;
RETURN;
};
substitute => {
cap ← Directory.Lookup[fileName: shortname, permissions: Directory.ignore
! Directory.Error => CONTINUE];
the file on the local disk is used
RETURN;
};
yes => NULL;
all => stpState.checkForOverwrite ← FALSE;
ENDCASE => ERROR;
Subr.strcpy[localname, shortname];
};
CWF.WF1["Retrieving %s "L, targetFileName];
IF tryDollars AND localcreatetime ~= 0 THEN
CWF.WF1["(as %s)"L, localname]
ELSE IF remoteCreateTime < localcreatetime THEN {
CWF.SWF1[dollar, "%s$$"L, shortname];
Directory.DeleteFile[dollar ! Directory.Error => CONTINUE];
Directory.Rename[oldName: shortname, newName: dollar];
CWF.WF1["(local version renamed to %s)"L, dollar];
};
CWF.WF0[" ... "L];
[cap,] ← WriteStreamToDisk[remoteStream,
localname, info.size, h];
Subr.SetRemoteFilenameProp[cap, targetFileName];
FileStream.SetLeaderPropertiesForCapability[cap: cap,
create: LOOPHOLE[remoteCreateTime]];
CWF.WF1["%lu bytes.\n"L, @info.size];
RETURN;
};
{ENABLE
UNWIND => {
Subr.FreeString[targetFileName]; Subr.FreeString[localname];
Subr.FreeString[dollar]; Subr.FreeString[sfn];
};
fres: FQ.Result;
[fres: fres, remoteVersion: remoteVersion, remoteCreateTime: remoteCreateTime]
FQ.FileQuery[host, directory, shortname,
version, wantcreatetime, wantExplicitVersion, h, targetFileName];
SELECT fres FROM
foundCorrectVersion => {
vstring: STRING;
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
IF localcreatetime = remoteCreateTime THEN GO TO return; -- already on disk
vstring ← IF Subr.Prefix[host, "maxc"L] THEN ";"L ELSE "!"L;
CWF.SWF4[sfn, "<%s>%s%s%u"L, directory, shortname,
vstring, @remoteVersion];
EnumerateForRetrieve[host, sfn, OneFile, h, onlyOne];
IF fileNotRetrieved THEN
cap ← File.nullCapability;
};
foundWrongVersion => ERROR Subr.FileError[wrongVersion];
notFound => ERROR Subr.FileError[notFound];
ENDCASE => ERROR;
EXITS return => {}}; -- of ENABLE UNWIND
Subr.FreeString[targetFileName]; Subr.FreeString[localname];
Subr.FreeString[dollar]; Subr.FreeString[sfn];
}; -- of IF NOT localOnly
};
CheckForOverwrite: PROC
[localname: LONG STRING, localcreate: LONG CARDINAL, remotename: LONG STRING, remotecreate: LONG CARDINAL, h: Subr.TTYProcs]
RETURNS[answer: {all, yes, no, substitute}] = {
h.out.PutF["Ok to retrieve %s, dated %t,\n\tand overwrite %s of %t ",
IO.string[remotename], IO.card[remotecreate], IO.string[localname], IO.card[localcreate]];
SELECT h.Confirm[h.in, h.out, h.data, "", 'y] FROM
'q => SIGNAL Subr.AbortMyself;
'y => answer ← yes;
'l => answer ← substitute;
'a => answer ← all;
ENDCASE => answer ← no;
};
StartSTP: PROC = {
longzone: UNCOUNTED ZONE;
IF connseq ~= NIL THEN RETURN;
longzone ← Subr.LongZone[];
connseq ← longzone.NEW[ConnSeqRecord[MAXCONNS]];
};
StopSTP: PUBLIC PROC = {
ENABLE UNWIND => connseq ← NIL;
longzone: UNCOUNTED ZONE;
IF connseq = NIL THEN RETURN;
longzone ← Subr.LongZone[];
FOR i: CARDINAL IN [0..connseq.size) DO
IF connseq[i].stphandle ~= NIL THEN CloseAndFree[i];
ENDLOOP;
longzone.FREE[@connseq];
};
Connect: PUBLIC PROC[host: LONG STRING, h: Subr.TTYProcs, onlyOne: BOOL]
RETURNS[stphandle: STP.Handle] = {
free: CARDINAL;
StartSTP[];
IF onlyOne THEN {
IF connseq.size > 0
AND connseq[0].host ~= NIL
AND NOT LongString.EquivalentString[host, connseq[0].host] THEN {
CloseAndFree[0];
IF connseq.size = 1 THEN connseq.size ← 0;
};
};
free ← connseq.size;
{
FOR i: CARDINAL IN [0..connseq.size) DO
IF connseq[i].host ~= NIL
AND connseq[i].stphandle ~= NIL
AND LongString.EquivalentString[host,connseq[i].host]
AND LOOPHOLE[connseq[i].stphandle, STPOps.Handle].remoteStream = NIL THEN {
free ← i;
GOTO leave;
};
IF connseq[i].host = NIL THEN free ← i;
ENDLOOP;
IF free = connseq.size THEN {
IF connseq.size >= connseq.maxsize THEN {
CWF.WF0["Error - to many conns\n"L];
RETURN[NIL];
};
connseq.size ← connseq.size + 1;
};
connseq[free] ← [NIL, NIL]; -- in case MakeSTPHandle signals
connseq[free].stphandle ← MakeSTPHandle[host, h];
connseq[free].host ← Subr.CopyString[host];
EXITS
leave => NULL;
};
STP.SetDesiredProperties[connseq[free].stphandle, ALL[FALSE]];
RETURN[connseq[free].stphandle];
};
ForceClosed: PUBLIC PROC[stphandle: STP.Handle] RETURNS[STP.Handle] = {
FOR i: CARDINAL IN [0 .. connseq.size) DO
IF stphandle = connseq[i].stphandle THEN
CloseAndFree[i];
ENDLOOP;
RETURN[NIL];
};
CloseAndFree: PROC[i: CARDINAL] = {
CWF.WF1["Closing connection to %s ... "L, connseq[i].host];
connseq[i].stphandle ← STP.Destroy[connseq[i].stphandle
! STP.Error => CONTINUE];
CWF.WF0["closed.\n"L];
Subr.FreeString[connseq[i].host];
connseq[i].host ← NIL;
};
CheckStarted: PROC = {
IF connseq = NIL THEN ERROR;
};
the procedures below can be called without using the Connect[] stphandles
NPAGEBUFFER: CARDINAL = 6;
may raise CIFS.Error[fileBusy]
WriteStreamToDisk: PUBLIC PROC[remotesh: Stream.Handle,
localfilename: LONG STRING, byteLengthHint: LONG CARDINAL,
h: Subr.TTYProcs]
RETURNS[cap: File.Capability, nbytes: LONG CARDINAL] = {
nxfer, npages: CARDINAL;
stopit, flip: BOOLFALSE;
space: Space.Handle ← Space.nullHandle;
localsh: Stream.Handle ← NIL;
buffer: LONG POINTERNIL;
ca: UserTerminal.CursorArray ← ALL[0];
cursorX, cursorY: INTEGER;
openFile: CIFS.OpenFile;
ftp: UserTerminal.CursorArray ← [
177400B, 177400B, 177400B, 177400B,
177400B, 177400B, 177400B, 177400B,
000377B, 000377B, 000377B, 000377B,
000377B, 000377B, 000377B, 000377B];
Cleanup: PROC = {
IF space ~= Space.nullHandle THEN Space.Delete[space];
space ← Space.nullHandle;
IF localsh ~= NIL THEN Stream.Delete[localsh];
localsh ← NIL;
IF flip THEN UserTerminal.SetCursorPattern[ca];
IF useCIFS AND openFile ~= NIL THEN CIFS.Close[openFile];
openFile ← NIL;
};
npages ← Inline.LowHalf[byteLengthHint/Environment.bytesPerPage] + 2;
nbytes ← 0;
cap ← File.nullCapability;
IF useCIFS THEN {
openFile ← CIFS.Open[ConvertUnsafe.ToRope[localfilename],
CIFS.replace+CIFS.create+CIFS.write];
cap ← CIFS.GetFC[openFile];
}
ELSE {
cap ← Directory.CreateFile[localfilename, DCSFileTypes.tLeaderPage, npages
! Directory.Error => {
IF type = fileAlreadyExists THEN {
cap ← Directory.Lookup[fileName: localfilename,
permissions: Directory.ignore];
IF npages > 2 THEN File.SetSize[cap, npages];
}
ELSE ERROR Subr.FileError[notFound];
CONTINUE
}];
};
localsh ← FileStream.Create[[cap.fID, Subr.Write]];
space ← Space.Create[NPAGEBUFFER, Space.virtualMemory];
Space.Map[space];
buffer ← Space.LongPointer[space];
IF Subr.CursorInWindow[h] THEN {
[cursorX, cursorY] ← UserTerminal.cursor^;
ca ← UserTerminal.GetCursorPattern[];
UserTerminal.SetCursorPattern[ftp];
flip ← TRUE;
};
WHILE NOT stopit DO
ENABLE UNWIND => Cleanup[];
[bytesTransferred: nxfer] ← Stream.GetBlock[remotesh,
[buffer, 0, NPAGEBUFFER*Environment.bytesPerPage]
! STP.Error => IF code = noSuchFile THEN {
CWF.WF1["\n\tError - %s not found.\n"L, localfilename];
EXIT;
};
Stream.EndOfStream => {
stopit ← TRUE;
nxfer ← nextIndex;
CONTINUE
}
];
IF nxfer = 0 THEN EXIT;
Stream.PutBlock[localsh, [buffer, 0, nxfer]];
nbytes ← nbytes + nxfer;
only flips if not moved
IF flip AND cursorX = UserTerminal.cursor^.x
AND cursorY = UserTerminal.cursor^.y THEN {
code from Cursor.Invert
bits: UserTerminal.CursorArray ← UserTerminal.GetCursorPattern[];
FOR i: CARDINAL IN [0..16) DO
bits[i] ← Inline.BITNOT[bits[i]];
ENDLOOP;
UserTerminal.SetCursorPattern[bits];
};
ENDLOOP;
Cleanup[];
};
Store: PUBLIC PROC
[stphandle: STP.Handle, remoteName: LONG STRING, localCap: File.Capability, createDate: LONG CARDINAL, h: Subr.TTYProcs]
RETURNS[nbytes: LONG CARDINAL] = {
ft: STP.Type;
localStream, remoteStream: Stream.Handle ← NIL;
len: LONG CARDINAL;
lstr: LONG STRING ← Subr.AllocateString[40];
remName: LONG STRING ← Subr.AllocateString[100];
Cleanup: PROC = {
IF localStream ~= NIL THEN Stream.Delete[localStream];
localStream ← NIL;
IF remoteStream ~= NIL THEN Stream.Delete[remoteStream];
remoteStream ← NIL;
Subr.FreeString[lstr]; Subr.FreeString[remName]
};
{ENABLE UNWIND => Cleanup[];
desiredProperties: STP.DesiredProperties ← ALL[FALSE];
nbytes ← 0;
Subr.strcpy[remName, remoteName];
ft ← GetFileType[remoteName];
localStream ← FileStream.Create[[localCap.fID, Subr.Read]];
this will read the entire local file to check for 8-th bit
IF ft = unknown THEN ft ← STPOps.FindFileType[localStream];
desiredProperties[directory] ← TRUE;
desiredProperties[nameBody] ← TRUE;
desiredProperties[version] ← TRUE;
STP.SetDesiredProperties[stphandle, desiredProperties];
remoteStream ← STP.CreateRemoteStream[stp: stphandle, file: remName,
access: write, fileType: ft, creation: LOOPHOLE[createDate]
! STP.Error => IF HandleSTPError[stphandle, code,
error, h] THEN RETRY];
len ← FileStream.GetLength[localStream];
CWF.SWF1[lstr, "%lu"L, @len];
STPOps.SetPListItem[LOOPHOLE[stphandle, STPOps.Handle].plist, "Size"L, lstr];
nbytes ← WriteDiskToStream[localStream, remoteStream, h];
}; -- of ENABLE UNWIND
Cleanup[];
};
WriteDiskToStream: PROC
[localsh, remotesh: Stream.Handle, h: Subr.TTYProcs] RETURNS[nbytes: LONG CARDINAL] = {
nxfer: CARDINAL;
stopit, flip: BOOLFALSE;
space: Space.Handle ← Space.nullHandle;
buffer: LONG POINTERNIL;
cursorX, cursorY: INTEGER;
ca: UserTerminal.CursorArray ← ALL[0];
ftp: UserTerminal.CursorArray ← [
177400B, 177400B, 177400B, 177400B,
177400B, 177400B, 177400B, 177400B,
000377B, 000377B, 000377B, 000377B,
000377B, 000377B, 000377B, 000377B];
Cleanup: PROC = {
IF space ~= Space.nullHandle THEN Space.Delete[space];
space ← Space.nullHandle;
IF flip THEN UserTerminal.SetCursorPattern[ca];
};
nbytes ← 0;
space ← Space.Create[NPAGEBUFFER, Space.virtualMemory];
Space.Map[space];
buffer ← Space.LongPointer[space];
IF Subr.CursorInWindow[h] THEN {
[cursorX, cursorY] ← UserTerminal.cursor^;
ca ← UserTerminal.GetCursorPattern[];
UserTerminal.SetCursorPattern[ftp];
flip ← TRUE;
};
WHILE NOT stopit DO
ENABLE UNWIND => Cleanup[];
[bytesTransferred: nxfer] ← Stream.GetBlock[localsh,
[buffer, 0, NPAGEBUFFER*Environment.bytesPerPage]
! Stream.EndOfStream => {
stopit ← TRUE;
nxfer ← nextIndex;
CONTINUE
}
];
IF nxfer = 0 THEN EXIT;
Stream.PutBlock[remotesh, [buffer, 0, nxfer]];
nbytes ← nbytes + nxfer;
IF flip AND cursorX = UserTerminal.cursor^.x
AND cursorY = UserTerminal.cursor^.y THEN {
code from Cursor.Invert
bits: UserTerminal.CursorArray ← UserTerminal.GetCursorPattern[];
FOR i: CARDINAL IN [0..16) DO
bits[i] ← Inline.BITNOT[bits[i]];
ENDLOOP;
UserTerminal.SetCursorPattern[bits];
};
ENDLOOP;
Cleanup[];
};
StripHost: PROC[host, sfn: LONG STRING] = {
takes sfn, strips off the host, puts the host in "host"
leaves the remainder in sfn
IF sfn[0] ~= '[ THEN {
Subr.strcpy[host, "Ivy"L];
RETURN;
};
host.length ← 0;
FOR i: CARDINAL IN [1..sfn.length) DO
IF sfn[i] = '] THEN {
FOR j: CARDINAL IN [1 .. i-1] DO
LongString.AppendChar[host, sfn[j]];
ENDLOOP;
Subr.SubStrCopy[sfn, sfn, i+1];
RETURN;
};
ENDLOOP;
CWF.WF1["Error - %s is not a valid remote file name.\n"L, sfn];
};
NSECPAUSE: CARDINAL = 10; -- # seconds to wait
SetNumberOfConnectTries: PUBLIC PROC[nTries: CARDINAL] = {
maxNumberOfTries ← nTries;
};
MakeSTPHandle: PUBLIC PROC
[host: LONG STRING, h: Subr.TTYProcs] RETURNS[stphandle: STP.Handle] = {
herald, shorthost: LONG STRING ← NIL;
ntries: CARDINAL ← 0;
IF host = NIL OR host.length = 0 THEN {
CWF.WF0["Error: remote host has not been specified.\n"L];
RETURN[NIL];
};
shorthost ← Subr.AllocateString[40];
{ENABLE UNWIND => Subr.FreeString[shorthost];
stphandle ← STP.Create[];
CWF.WF1["Opening connection to %s.\n"L, host];
Subr.strcpy[shorthost, host];
herald ← STP.Open[stphandle, shorthost
! STP.Error => IF code = connectionRejected THEN {
CWF.WF1["Connection rejected by %s.\n"L, host];
ntries ← ntries + 1;
IF ntries < maxNumberOfTries THEN {
CWF.WF0["Will pause and try again. (Type control-DEL to abort.)\n"L];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
Process.Pause[Process.SecondsToTicks[NSECPAUSE]];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
RETRY;
}
ELSE CWF.WF0["Connection rejected too many times.\n"L];
}];
CWF.WF1["%s\n"L,herald];
Heap.systemZone.FREE[@herald];
userName and userPassword may be NIL at this point
STPLogin[stphandle];
}; -- of ENABLE UNWIND
Subr.FreeString[shorthost];
};
STPLogin: PROC[stphandle: STP.Handle] = {
n: LONG STRING ← Subr.AllocateString[50];
p: LONG STRING ← Subr.AllocateString[50];
{ENABLE UNWIND => {Subr.FreeString[n]; Subr.FreeString[p]};
UserCredentialsUnsafe.GetUserCredentials[name: n, password: p];
STP.Login[stphandle, n, p];
}; -- of ENABLE UNWIND
Subr.FreeString[n]; Subr.FreeString[p];
};
HandleSTPError: PUBLIC PROC
[stphandle: STP.Handle, stpError: STP.ErrorCode, message: LONG STRING, h: Subr.TTYProcs]
RETURNS [retryit: BOOL] ={
can be called by programs that are not using the connection table in this module
SELECT stpError FROM
illegalUserPassword, illegalUserName, illegalUserAccount, credentailsMissing =>
SetPass[stphandle, h, message];
illegalConnectName, illegalConnectPassword, accessDenied =>
SetOtherPass[stphandle, h, message];
ENDCASE => RETURN [FALSE];
RETURN[TRUE];
};
SetPass: PROC[stphandle: STP.Handle, h: Subr.TTYProcs, error: LONG STRING] = {
can be called by programs that are not using the connection table in this module
IF error ~= NIL THEN CWF.WF1["Error - %s\n"L,error];
CWF.WF0["Enter "L];
Subr.GetNameandPassword[login, NIL, NIL, h];
CWF.WFCR[];
IF stphandle ~= NIL THEN STPLogin[stphandle];
};
SetOtherPass: PROC[stphandle: STP.Handle, h: Subr.TTYProcs, error: LONG STRING] = {
can be called by programs that are not using the connection table in this module
user2: LONG STRING ← Subr.AllocateString[40]; -- must remain short for STP, dammitt!
password2: LONG STRING ← Subr.AllocateString[40]; -- must remain short for STP, dammitt!
{ENABLE UNWIND => {Subr.FreeString[user2]; Subr.FreeString[password2]};
IF error ~= NIL THEN CWF.WF1["Error - %s\n"L,error];
CWF.WF0["Enter Connect "L];
Subr.GetNameandPassword[connect, user2, password2, h];
IF stphandle ~= NIL AND user2.length > 0 THEN
STP.Connect[stphandle, user2,
IF password2.length = 0 THEN NIL ELSE password2];
by convention, if not connect name and no password is supplied then we abort
IF user2.length = 0 AND password2.length = 0 THEN {
IF h.Confirm[h.in, h.out, h.data,
"\nType y to retry with different login name, n to abort ", 'n] = 'n THEN
SIGNAL Subr.AbortMyself;
CWF.WF0["Enter "L];
Subr.GetNameandPassword[login, NIL, NIL, h];
CWF.WFCR[];
STPLogin[stphandle];
};
}; -- of ENABLE UNWIND
Subr.FreeString[user2]; Subr.FreeString[password2];
};
AddUserName: PUBLIC PROC[sto: LONG STRING, spat: LONG STRING, h: Subr.TTYProcs] = {
can be called by programs that are not using the connection table in this module
u: LONG STRING ← Subr.AllocateString[40]; -- must remain short for STP, dammitt!
p: LONG STRING ← Subr.AllocateString[40]; -- must remain short for STP, dammitt!
lengthzero: BOOL;
{ENABLE UNWIND => {Subr.FreeString[u]; Subr.FreeString[p]};
UserCredentialsUnsafe.GetUserCredentials[name: NIL, password: p];
lengthzero ← p = NIL OR p.length = 0;
DO
UserCredentialsUnsafe.GetUserCredentials[name: u, password: NIL];
IF u = NIL
OR u.length = 0
OR Subr.Any[u,' ]
OR Subr.Prefix[u, "CedarUser"L]
OR lengthzero
THEN {
lengthzero ← FALSE;
CWF.WF0["Enter "L];
Subr.GetNameandPassword[login, NIL, NIL, h];
CWF.WFCR[];
}
ELSE EXIT;
ENDLOOP;
IF sto ~= NIL THEN CWF.SWF1[sto,spat,u];
}; -- of ENABLE UNWIND
Subr.FreeString[u]; Subr.FreeString[p];
};
GetFileType: PROC[filename: LONG STRING] RETURNS[filetype: STP.Type] = {
note: temporarily alters the length of the string to avoid copying
old: CARDINAL ← filename.length;
FOR i: CARDINAL IN [0..old) DO
IF filename[i] = '! OR filename[i] = '; THEN {
filename.length ← i;
EXIT;
};
ENDLOOP;
filetype ← unknown;
IF Subr.EndsIn[filename,".mesa"L] OR Subr.EndsIn[filename,".bravo"L]
OR Subr.EndsIn[filename,".config"L] OR Subr.EndsIn[filename,".cm"L]
OR Subr.EndsIn[filename, ".mail"L] OR Subr.EndsIn[filename, ".df"L] THEN
filetype ← text;
IF Subr.EndsIn[filename,".bcd"L] OR Subr.EndsIn[filename,".press"L]
OR Subr.EndsIn[filename,".run"L] THEN filetype ← binary;
filename.length ← old;
};
}.