-- STPSubrImpl.Mesa, last edit March 8, 1983 12:37 pm
-- Pilot 6.0/ Mesa 7.0
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 [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],
String: TYPE USING [AppendChar],
Subr: TYPE USING [AbortMyself, 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, String, Subr, UserCredentialsUnsafe, UserTerminal
EXPORTS STPSubr
SHARES File = {
-- MDS USAGE !!!
useCIFS: BOOL _ TRUE;
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 STRING _ NIL
];
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 STRING _ NIL;
skipRest, tryAgain: BOOL;
shortFilePattern: STRING _ [100];
desiredProperties: STP.DesiredProperties _ ALL[FALSE];
Cleanup: PROC = {
IF remoteName ~= NIL THEN Heap.systemZone.FREE[@remoteName];
IF remoteStream ~= NIL THEN Stream.Delete[remoteStream];
remoteStream _ NIL;
};
-- nested procedure needed to handle retrys of entire enumerates
TryIt: PROC = {
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[];
};
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;
};
-- if not found, raises Subr.FileError, for both remote and local files
-- filename looks like [host]
name
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] = {
host: STRING _ [30];
sfn: STRING _ [100];
tryAgain: BOOL;
desiredProperties: STP.DesiredProperties _ ALL[FALSE];
sh _ NIL;
-- local case
IF filename[0] ~= '[ AND filename[0] ~= '< THEN {
sh _ Subr.NewStream[filename, access];
RETURN[sh, NIL];
};
-- remote file name
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;
};
-- just like GeneralOpen but the filename can be a pattern, e.g. with * in it
PatternGeneralOpen: PUBLIC PROC[filepattern: STRING, proc: STPSubr.RetrieveProcType,
h: Subr.TTYProcs] = {
host: STRING _ [30];
sfn: STRING _ [100];
-- 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;
};
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;
};
-- may raise Subr.FileError
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] = {
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] = {
localOnly: BOOL _ host = NIL OR host.length = 0;
localcreatetime: LONG CARDINAL _ 0;
fileNotRetrieved: BOOL _ FALSE;
cap _ File.nullCapability;
{
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[cap];
EXITS
notonlocal => NULL;
};
-- try with $$ on end
IF tryDollars THEN {
sDollar: STRING _ [100];
CWF.SWF1[sDollar, "%s$$"L, shortname];
cap _ Directory.Lookup[fileName: sDollar, permissions: Directory.ignore
! Directory.Error => GOTO notonlocaldollar];
localcreatetime _ Subr.GetCreateDate[cap];
IF localcreatetime = wantcreatetime THEN RETURN[cap];
EXITS
notonlocaldollar => NULL;
};
-- look on remote server
IF NOT localOnly THEN {
targetFileName: STRING _ [125];
remoteVersion: CARDINAL;
remoteCreateTime: LONG CARDINAL;
OneFile: STPSubr.RetrieveProcType = {
info: STP.FileInfo _ STP.GetFileInfo[stp];
localname: STRING _ [100];
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 {
dollar: STRING _ [100];
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;
};
{
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;
sfn: STRING _ [100];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
IF localcreatetime = remoteCreateTime THEN 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;
}};
};
CheckForOverwrite: PROC[localname: LONG STRING, localcreate: LONG CARDINAL,
remotename: LONG STRING, remotecreate: LONG CARDINAL, h: Subr.TTYProcs]
RETURNS[answer: {all, yes, no, substitute}] = {
ch: CHAR;
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]];
ch _ h.Confirm[h.in, h.out, h.data, "", 'y];
IF ch = 'q THEN
SIGNAL Subr.AbortMyself
ELSE IF ch = 'y THEN
answer _ yes
ELSE IF ch = 'l THEN
answer _ substitute
ELSE IF ch = 'a THEN
answer _ all
ELSE
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: BOOL _ FALSE;
space: Space.Handle _ Space.nullHandle;
localsh: Stream.Handle _ NIL;
buffer: LONG POINTER _ NIL;
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;
remName: STRING _ [100];
localStream, remoteStream: Stream.Handle _ NIL;
len: LONG CARDINAL;
lstr: STRING _ [30];
Cleanup: PROC = {
IF localStream ~= NIL THEN Stream.Delete[localStream];
localStream _ NIL;
IF remoteStream ~= NIL THEN Stream.Delete[remoteStream];
remoteStream _ NIL;
};
{
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];
};
Cleanup[];
};
WriteDiskToStream: PROC[localsh, remotesh: Stream.Handle,
h: Subr.TTYProcs] RETURNS[nbytes: LONG CARDINAL] = {
nxfer: CARDINAL;
stopit, flip: BOOL _ FALSE;
space: Space.Handle _ Space.nullHandle;
buffer: LONG POINTER _ NIL;
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[];
};
-- takes sfn, strips off the host, puts the host in "host"
-- leaves the remainder in sfn
StripHost: PROC[host, sfn: STRING] = {
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
String.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: LONG STRING;
shorthost: STRING _ [30];
user: STRING _ [40];
password: STRING _ [40];
ntries: CARDINAL _ 0;
IF host = NIL OR host.length = 0 THEN {
CWF.WF0["Error: remote host has not been specified.\n"L];
RETURN[NIL];
};
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];
};
STPLogin: PROC[stphandle: STP.Handle] = {
-- STP.Login[stphandle, NameAndPasswordOps.userName, NameAndPasswordOps.userPassword];
n: STRING _ [50];
p: STRING _ [50];
UserCredentialsUnsafe.GetUserCredentials[name: n, password: p];
STP.Login[stphandle, n, p];
};
-- can be called by programs that are not using the connection table in this module
HandleSTPError: PUBLIC PROC[stphandle: STP.Handle, stpError: STP.ErrorCode,
message: LONG STRING, h: Subr.TTYProcs] RETURNS[retryit: BOOL] ={
IF stpError = illegalUserPassword OR
stpError = illegalUserName OR
stpError = illegalUserAccount OR
-- misspelled: credentailsMissing
stpError = credentailsMissing THEN {
SetPass[stphandle, h, message];
RETURN[TRUE];
}
ELSE IF stpError = illegalConnectName OR
stpError = illegalConnectPassword OR
stpError = accessDenied
THEN {
SetOtherPass[stphandle, h, message];
RETURN[TRUE];
};
RETURN[FALSE];
};
-- can be called by programs that are not using the connection table in this module
SetPass: PROC[stphandle: STP.Handle, h: Subr.TTYProcs, error: LONG STRING] = {
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];
};
-- can be called by programs that are not using the connection table in this module
SetOtherPass: PROC[stphandle: STP.Handle, h: Subr.TTYProcs, error: LONG STRING] = {
user2: STRING _ [40];
password2: STRING _ [40];
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];
};
};
-- can be called by programs that are not using the connection table in this module
AddUserName: PUBLIC PROC[sto: LONG STRING, spat: STRING, h: Subr.TTYProcs] = {
-- u: STRING;
-- p: STRING _ NameAndPasswordOps.userPassword;
u: STRING _ [50];
p: STRING _ [50];
lengthzero: BOOL;
UserCredentialsUnsafe.GetUserCredentials[name: NIL, password: p];
lengthzero _ p = NIL OR p.length = 0;
DO
-- u _ NameAndPasswordOps.userName;
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];
};
GetFileType: PROC[filename: LONG STRING]
RETURNS[filetype: STP.Type] = {
name: STRING _ [100];
Subr.strcpy[name, filename];
FOR i: CARDINAL IN [0..name.length) DO
IF name[i] = '! OR name[i] = '; THEN {
name.length _ i;
EXIT;
};
ENDLOOP;
filetype _ unknown;
IF Subr.EndsIn[name,".mesa"L] OR Subr.EndsIn[name,".bravo"L]
OR Subr.EndsIn[name,".config"L] OR Subr.EndsIn[name,".cm"L]
OR Subr.EndsIn[name, ".mail"L] OR Subr.EndsIn[name, ".df"L] THEN
filetype _ text;
IF Subr.EndsIn[name,".bcd"L] OR Subr.EndsIn[name,".press"L]
OR Subr.EndsIn[name,".run"L] THEN filetype _ binary;
};
}.