DIRECTORY
Atom USING [GetPName],
Basics USING [bytesPerWord],
BasicTime USING [GMT, MonthOfYear, Now, Unpack, Unpacked],
Buttons USING [Button, ButtonProc, Create, SetDisplayStyle],
Checksum USING [ComputeChecksum],
Commander USING [CommandProc, Register],
Containers USING [ChildXBound, ChildYBound, Create],
Convert USING [Error, IntFromRope, RopeFromInt],
FS USING [EnumerateForInfo, Error, InfoProc, StreamOpen],
IO USING [Close, CreateStream, CreateStreamProcs, GetBlock, Flush, PutChar, PutF, RopeFromROS, ROS, STREAM, StreamProcs],
Labels USING [Create],
Loader USING [BCDBuildTime],
Process USING [Detach],
RefText USING [ObtainScratch, ReleaseScratch],
Rope USING [Length, ROPE],
Rules USING [Create],
STP USING [CompletionProcType, ConfirmProcType, Close, Create, DesiredProperties, Error, GetProperty, Handle, Login, Open, Retrieve, SetDesiredProperties],
TypeScript USING [ChangeLooks, Create],
UserCredentials USING [Get],
VFonts USING [FontHeight, StringWidth],
ViewerClasses USING [Viewer],
ViewerEvents USING [EventProc, RegisterEventProc],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [AddProp, ComputeColumn, CreateViewer, FetchProp, MoveViewer, OpenIcon, SetOpenHeight],
ViewerTools USING [GetContents, MakeNewTextViewer, SetContents, SetSelection];
Compare:
CEDAR
MONITOR
IMPORTS
Atom, BasicTime, Buttons, Checksum, Commander, Containers, Convert, FS, IO, Labels, Loader, Process, RefText, Rope, Rules, STP, TypeScript, UserCredentials, VFonts, ViewerEvents, ViewerIO, ViewerOps, ViewerTools =
BEGIN
BYTE: TYPE = [0..100H);
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Viewer: TYPE = ViewerClasses.Viewer;
Viewer layout parameters
buttonHeight: INT ← VFonts.FontHeight[] + 3;
buttonWidth: INT ← VFonts.StringWidth["Checksum"] + 2*3;
ClientData: TYPE = REF ClientDataRep;
ClientDataRep:
TYPE =
RECORD [
log: STREAM ← NIL,
in: STREAM ← NIL,
pleaseStop: BOOLEAN ← FALSE,
user: PROCESS ← NIL,
host1, file1: Viewer ← NIL,
host2, file2: Viewer ← NIL ];
global: ClientData ← NIL; -- debugging
Create: Commander.CommandProc = {
viewer, buttons, log: Viewer ← NIL;
data: ClientData ← NEW[ClientDataRep ← []];
global ← data;
viewer ← ViewerOps.CreateViewer [
flavor: $Container,
info: [name: "Compare", column: left, iconic: TRUE, scrollable: FALSE]];
[] ← ViewerEvents.RegisterEventProc[Poof, destroy, viewer, TRUE];
ViewerOps.AddProp[viewer, $Compare, data];
log ← TypeScript.Create[
[name: "Compare.log", wy: 27+4, parent: viewer, border: FALSE], FALSE];
[data.in, data.log] ← ViewerIO.CreateViewerStreams [
name: "Compare.log", backingFile: "Compare.log", viewer: log, editedStream: FALSE];
Containers.ChildXBound[viewer, log];
Containers.ChildYBound[viewer, log];
CreateButtons[data, viewer, log];
TypeScript.ChangeLooks[log, 'f];
IO.PutF[data.log, "Compare of %G.\n\n", [time[Loader.BCDBuildTime[Create]]]];
ViewerOps.OpenIcon[viewer]; };
CreateButtons:
ENTRY
PROC [data: ClientData, parent, log: Viewer] = {
child: Viewer ← NIL;
kids: Viewer = Containers.Create[
info: [parent: parent, border: FALSE, scrollable: FALSE, wx: 0, wy: -9999, ww: 9999, wh: 0] ];
Containers.ChildXBound[parent, kids];
child ← MakeRule[kids, child];
child ← MakeLabel[kids, child, "File 1: "];
child ← data.host1 ← MakeLabeledText[
parent: kids,
sibling: child,
name: "Host:",
data: "Host (empty for local)",
width: VFonts.StringWidth["BunkerHill-xxx"],
prev: data.host1,
newline: FALSE ];
child ← data.file1 ← MakeLabeledText[
parent: kids,
sibling: child,
name: "Name:",
data: "File",
width: VFonts.StringWidth["Big long file name ..........................."],
prev: data.file1,
newline: FALSE ];
Containers.ChildXBound[parent, child];
child ← MakeLabel[kids, child, "File 2: "];
child ← data.host2 ← MakeLabeledText[
parent: kids,
sibling: child,
name: "Host:",
data: "Host (empty for local)",
width: VFonts.StringWidth["BunkerHill-xxx"],
prev: data.host2,
newline: FALSE ];
child ← data.file2 ← MakeLabeledText[
parent: kids,
sibling: child,
name: "Name:",
data: "File",
width: VFonts.StringWidth["Big long file name ..........................."],
prev: data.file2,
newline: FALSE ];
Containers.ChildXBound[parent, child];
child ← MakeRule[kids, child];
child ← MakeLabel[kids, child, "What: "];
child ← MakeButton[kids, child, data, "Stop", StopProc];
child ← MakeButton[kids, child, data, "Checksum", ChecksumProc];
child ← MakeButton[kids, child, data, "Compare", CompareProc];
child ← MakeRule[kids, child];
{
kidsY: INTEGER = 2;
kidsH: INTEGER = child.wy + child.wh + 2;
ViewerOps.MoveViewer[viewer: log, x: 0, y: kidsY + kidsH, w: log.ww, h: parent.ch - (kids.wy + kidsH), paint: FALSE];
ViewerOps.SetOpenHeight[parent, kidsY + kidsH + 12 * buttonHeight];
IF ~parent.iconic THEN ViewerOps.ComputeColumn[parent.column];
ViewerOps.MoveViewer[viewer: kids, x: kids.wx, y: kidsY, w: kids.ww, h: kidsH]; };
};
Poof: ViewerEvents.EventProc = {
[viewer: ViewerClasses.Viewer, event: ViewerEvent, before: BOOL]
RETURNS[abort: BOOL ← FALSE]
data: ClientData ← NARROW[ViewerOps.FetchProp[viewer, $Compare]];
IF event # destroy OR before # TRUE THEN ERROR;
Stop[data];
IO.Close[data.log];
IO.Close[data.in];
};
StopProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
data: ClientData ← NARROW[clientData];
Stop[data]; };
Stop:
PROC [data: ClientData] =
TRUSTED {
data.pleaseStop ← TRUE;
IF data.user # NIL THEN JOIN data.user;
data.user ← NIL; };
ChecksumProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
data: ClientData ← NARROW[clientData];
data.pleaseStop ← FALSE;
ChecksumIt[data]; };
CompareProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
data: ClientData ← NARROW[clientData];
data.pleaseStop ← FALSE;
CompareIt[data]; };
streamProcs:
REF
IO.StreamProcs =
IO.CreateStreamProcs[
variety: $output,
class: $Compare,
putBlock: PutBlock,
close: Close ];
StreamData: TYPE = REF StreamDataRep;
StreamDataRep:
TYPE =
RECORD [
data: ClientData,
host: ROPE ← NIL,
fileName: ROPE ← NIL,
sizeRope: ROPE ← NIL,
createRope: ROPE ← NIL,
size: INT ← 0,
bytes: INT ← 0,
checksum: WORD ← 0,
stream: STREAM ← NIL,
index: INT ← 0,
block: REF READONLY TEXT ← NIL,
length: INT ← 0,
done: BOOL ← FALSE ];
waiting: CONDITION;
PushLocalBits:
PROC [data: ClientData, me: StreamData, files:
ROPE] = {
buffer: REF TEXT = RefText.ObtainScratch[512]; -- Hack, must be same size as STP uses
PutBlock:
ENTRY
PROC = {
IF me.block # NIL THEN ERROR;
me.block ← buffer;
me.length ← buffer.length;
BROADCAST waiting;
UNTIL me.block = NIL DO WAIT waiting; ENDLOOP;
};
EachFile:
FS.InfoProc = {
checksumRope: ROPE;
stream: STREAM ← FS.StreamOpen[fileName: fullFName, streamBufferParms: [128, 4]];
me.size ← bytes;
me.bytes ← 0;
me.checksum ← 0;
me.index ← 0;
DO
nBytes: NAT = IO.GetBlock[stream, buffer, 0];
IF nBytes = 0 THEN EXIT;
PutBlock[];
ENDLOOP;
IO.Close[stream];
checksumRope ← Convert.RopeFromInt[me.checksum, 16, FALSE];
IO.PutF[
data.log,
"%8G %4G %G %G.\n",
[integer[me.bytes]],
[rope[checksumRope]],
[rope[FTPStyleDate[created]]],
[rope[fullFName]] ];
IF me.size # me.bytes
THEN
IO.PutF[
data.log,
"***** Length mixup. Expected %g bytes, got %G.\n",
[integer[me.size]],
[integer[me.bytes]] ];
RETURN[~data.pleaseStop];
};
FS.EnumerateForInfo[files, EachFile !
FS.Error => {
IO.PutF[data.log, " FS troubles: %G.\n", [rope[error.explanation]] ]; CONTINUE; } ];
RefText.ReleaseScratch[buffer];
me.done ← TRUE;
PokeWaiting[];
};
PushTheBits:
PROC [data: ClientData, me: StreamData, host, files:
ROPE] = {
Open:
STP.ConfirmProcType = {
me.fileName ← file;
me.sizeRope ← STP.GetProperty[stp, size];
me.createRope ← STP.GetProperty[stp, createDate];
me.size ← 0;
me.bytes ← 0;
me.checksum ← 0;
me.index ← 0;
IF data.pleaseStop THEN RETURN[abort, NIL];
me.size ← Convert.IntFromRope[me.sizeRope ! Convert.Error => CONTINUE];
RETURN[do, me.stream];
};
Close:
ENTRY
STP.CompletionProcType = {
SELECT what
FROM
ok => NULL;
error => IO.PutF[data.log, "***** STP Says error: %G while processing %G.\n", [rope[fileOrError]], [rope[me.fileName]] ];
ENDCASE => ERROR;
};
herald, name, password: ROPE;
failed: BOOL ← FALSE;
stp: STP.Handle ← STP.Create[];
desiredProperties: STP.DesiredProperties ← ALL[FALSE];
desiredProperties[size] ← TRUE;
desiredProperties[createDate] ← TRUE;
desiredProperties[directory] ← TRUE;
desiredProperties[nameBody] ← TRUE;
desiredProperties[version] ← TRUE;
herald ←
STP.Open[stp, host !
STP.Error => {
IO.PutF[data.log, " STP Open failed: %G.\n", [rope[error]] ];
herald ← NIL;
failed ← TRUE;
CONTINUE }];
IF herald # NIL THEN IO.PutF[data.log, "%G\n", [rope[herald]] ];
IF failed THEN { me.done ← TRUE; PokeWaiting[]; RETURN; };
me.stream ← IO.CreateStream[streamProcs: streamProcs, streamData: me];
me.host ← host;
[name, password] ← UserCredentials.Get[];
STP.Login[stp, name, password];
STP.SetDesiredProperties[stp, desiredProperties];
STP.Retrieve[stp, files, Open, Close !
STP.Error => {
IO.PutF[data.log, " STP Retrieve failed: %G.\n", [rope[error]] ];
CONTINUE }];
STP.Close[stp ! STP.Error => CONTINUE ];
me.done ← TRUE;
PokeWaiting[];
};
PokeWaiting:
ENTRY PROC = {
BROADCAST waiting;
};
Words:
PROC [bytes:
NAT]
RETURNS [words:
NAT] = {
RETURN[(bytes+Basics.bytesPerWord-1)/Basics.bytesPerWord];
};
PutBlock:
ENTRY
PROC [self:
STREAM, block:
REF
READONLY
TEXT, startIndex:
NAT, count:
NAT] = {
me: StreamData = NARROW[self.streamData];
IF startIndex # 0 THEN ERROR;
IF me.block # NIL THEN ERROR;
me.block ← block;
me.length ← MIN[count, block.length];
BROADCAST waiting;
UNTIL me.block = NIL DO WAIT waiting; ENDLOOP;
};
Close:
ENTRY
PROC [self:
STREAM, abort:
BOOL] = {
me: StreamData = NARROW[self.streamData];
data: ClientData ← me.data;
checksumRope: ROPE ← Convert.RopeFromInt[me.checksum, 16, FALSE];
IO.PutF[
data.log,
"%8G %4G %G [%G]%G.\n",
[integer[me.size]],
[rope[checksumRope]],
[rope[me.createRope]],
[rope[me.host]],
[rope[me.fileName]] ];
IF me.size # me.bytes
THEN
IO.PutF[
data.log,
"***** Length mixup. Expected %g bytes, got %G.\n",
[integer[me.size]],
[integer[me.bytes]] ];
};
ChecksumIt:
ENTRY
PROC [data: ClientData] = {
me: StreamData ← NEW[StreamDataRep ← [data: data]];
host: ROPE = ViewerTools.GetContents[data.host1];
files: ROPE = ViewerTools.GetContents[data.file1];
IO.PutF[
data.log,
"\n%G Scanning [%G]%G.\n\n",
[time[BasicTime.Now[]]],
[rope[host]],
[rope[files]] ];
TRUSTED {
IF Rope.Length[host] = 0 THEN Process.Detach[FORK PushLocalBits[data, me, files]]
ELSE Process.Detach[FORK PushTheBits[data, me, host, files]]; };
UNTIL me.done
DO
base: LONG POINTER;
bytes: LONG POINTER TO PACKED ARRAY [0..NAT.LAST] OF CHAR;
UNTIL me.block # NIL OR me.done DO WAIT waiting; ENDLOOP;
IF me.done THEN EXIT;
base ← LOOPHOLE[me.block, LONG POINTER]+TEXT[0].SIZE;
bytes ← LOOPHOLE[base];
me.bytes ← me.bytes + me.length;
IF (me.length MOD 2) # 0 THEN TRUSTED { bytes[me.length] ← 0C; };
TRUSTED {
me.checksum ← Checksum.ComputeChecksum[me.checksum, Words[me.length], base]; };
me.index ← me.index + me.length;
me.block ← NIL;
BROADCAST waiting;
ENDLOOP;
IO.Flush[data.log];
};
CompareIt:
ENTRY
PROC [data: ClientData] = {
me1: StreamData ← NEW[StreamDataRep ← [data: data]];
me2: StreamData ← NEW[StreamDataRep ← [data: data]];
host1: ROPE = ViewerTools.GetContents[data.host1];
file1: ROPE = ViewerTools.GetContents[data.file1];
host2: ROPE = ViewerTools.GetContents[data.host2];
file2: ROPE = ViewerTools.GetContents[data.file2];
IO.PutF[
data.log,
"\n%G\nComparing [%G]%G with [%G]%G.\n\n",
[time[BasicTime.Now[]]],
[rope[host1]],
[rope[file1]],
[rope[host2]],
[rope[file2]] ];
TRUSTED {
IF Rope.Length[host1] = 0 THEN Process.Detach[FORK PushLocalBits[data, me1, file1]]
ELSE Process.Detach[FORK PushTheBits[data, me1, host1, file1]];
IF Rope.Length[host2] = 0 THEN Process.Detach[FORK PushLocalBits[data, me2, file2]]
ELSE Process.Detach[FORK PushTheBits[data, me2, host2, file2]]; };
DO
UNTIL me1.block # NIL OR me1.done DO WAIT waiting; ENDLOOP;
IF me1.done THEN EXIT;
UNTIL me2.block # NIL OR me2.done DO WAIT waiting; ENDLOOP;
IF me2.done THEN EXIT;
me1.bytes ← me1.bytes + me1.length;
me2.bytes ← me2.bytes + me2.length;
IF me1.length # me2.length
THEN {
IO.PutF[
data.log,
"Ugh: Can't yet process different size blocks: %G vs %G.\n",
[integer[me1.length]],
[integer[me2.length]] ];
EXIT; }
ELSE {
words: NAT ← Words[me1.length];
base1: LONG POINTER ← LOOPHOLE[me1.block, LONG POINTER]+TEXT[0].SIZE;
bytes1: LONG POINTER TO PACKED ARRAY [0..NAT.LAST] OF CHAR ← LOOPHOLE[base1];
base2: LONG POINTER ← LOOPHOLE[me2.block, LONG POINTER]+TEXT[0].SIZE;
bytes2: LONG POINTER TO PACKED ARRAY [0..NAT.LAST] OF CHAR ← LOOPHOLE[base2];
IF (me1.length MOD 2) # 0 THEN TRUSTED { bytes1[me1.length] ← 0C; };
IF (me2.length MOD 2) # 0 THEN TRUSTED { bytes2[me2.length] ← 0C; };
TRUSTED {
me1.checksum ← Checksum.ComputeChecksum[me1.checksum, words, base1];
me2.checksum ← Checksum.ComputeChecksum[me2.checksum, words, base2]; };
FOR i:
INT
IN [0..words)
UNTIL data.pleaseStop
DO
word1, word2: WORD;
TRUSTED { word1 ← base1^; word2 ← base2^; };
IF word1 # word2
THEN
IO.PutF[data.log,
"%8B/ %4G %4G\n",
[integer[me1.index+i*Basics.bytesPerWord]],
[rope[Convert.RopeFromInt[word1, 16, FALSE]]],
[rope[Convert.RopeFromInt[word2, 16, FALSE]]] ];
base1 ← base1 + 1;
base2 ← base2 + 1;
ENDLOOP; };
me1.index ← me1.index + me1.length;
me2.index ← me2.index + me2.length;
me1.block ← NIL;
me2.block ← NIL;
BROADCAST waiting;
ENDLOOP;
data.pleaseStop ← TRUE;
UNTIL me1.done
DO
-- Skip rest of me1
UNTIL me1.block # NIL OR me1.done DO WAIT waiting; ENDLOOP;
IF me1.done THEN EXIT;
me1.bytes ← me1.bytes + me1.length;
me1.index ← me1.index + me1.length;
me1.block ← NIL;
BROADCAST waiting;
ENDLOOP;
UNTIL me2.done
DO
-- Skip rest of me2
UNTIL me2.block # NIL OR me2.done DO WAIT waiting; ENDLOOP;
IF me2.done THEN EXIT;
me2.bytes ← me2.bytes + me2.length;
me2.index ← me1.index + me2.length;
me2.block ← NIL;
BROADCAST waiting;
ENDLOOP;
IO.Flush[data.log];
};
MakeRule:
PROC [parent, sibling: Viewer]
RETURNS [child: Viewer] = {
child ← Rules.Create[
info: [parent: parent, border:
FALSE,
wy: IF sibling = NIL THEN 0 ELSE sibling.wy + sibling.wh + 2, wx: 0, ww: parent.ww, wh: 1],
paint: FALSE ];
Containers.ChildXBound[parent, child];
};
MakeButton:
PROC [parent, sibling: Viewer, data:
REF
ANY, name:
ROPE, proc: Buttons.ButtonProc]
RETURNS[child: Viewer] = {
child ← Buttons.Create[
info: [name: name, parent: parent, border:
TRUE,
wy: sibling.wy, wx: sibling.wx + sibling.ww - 1, ww: buttonWidth],
proc: proc,
clientData: data,
fork: TRUE,
paint: FALSE];
};
SelectorProc: TYPE = PROC [parent: Viewer, clientData: REF, value: ATOM];
Selector: TYPE = REF SelectorRec;
SelectorRec:
TYPE =
RECORD [
value: REF ATOM,
change: PROC [parent: Viewer, clientData: REF, value: ATOM],
clientData: REF,
buttons: LIST OF Buttons.Button,
values: LIST OF ATOM ];
MakeSelector:
PROC
[name: ROPE, values: LIST OF ATOM, init: REF ATOM ← NIL, change: SelectorProc ← NIL, clientData: REF ← NIL, parent: Viewer, x, y: INTEGER]
RETURNS [child: Viewer] = {
selector: Selector ←
NEW [SelectorRec ← [
value: IF init # NIL THEN init ELSE NEW [ATOM ← values.first],
change: change,
clientData: clientData,
buttons: NIL,
values: values ] ];
last: LIST OF Buttons.Button ← NIL;
child ← Labels.Create[info: [name: name, parent: parent, border: FALSE, wx: x, wy: y] ];
FOR a:
LIST
OF
ATOM ← values, a.rest
UNTIL a =
NIL
DO
child ← Buttons.Create[
info: [name: Atom.GetPName[a.first], parent: parent, border: TRUE, wx: child.wx + child.ww + 2, wy: child.wy],
proc: SelectorHelper, clientData: selector, fork: TRUE, paint: TRUE];
IF last = NIL THEN last ← selector.buttons ← CONS[first: child, rest: NIL]
ELSE { last.rest ← CONS[first: child, rest: NIL]; last ← last.rest };
IF a.first = selector.value^ THEN Buttons.SetDisplayStyle[child, $WhiteOnBlack];
ENDLOOP; };
SelectorHelper: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
self: Buttons.Button = NARROW[parent];
selector: Selector = NARROW[clientData];
buttons: LIST OF Buttons.Button ← selector.buttons;
FOR a:
LIST
OF
ATOM ← selector.values, a.rest
UNTIL a =
NIL
DO
IF self = buttons.first
THEN {
selector.value^ ← a.first;
IF selector.change # NIL THEN selector.change[self.parent, selector.clientData, a.first];
Buttons.SetDisplayStyle[buttons.first, $WhiteOnBlack]; }
ELSE Buttons.SetDisplayStyle[buttons.first, $BlackOnWhite];
buttons ← buttons.rest;
ENDLOOP; };
BoolProc: TYPE = PROC [parent: Viewer, clientData: REF, value: BOOL];
Bool: TYPE = REF BoolRec;
BoolRec:
TYPE =
RECORD [
value: REF BOOL,
change: BoolProc,
clientData: REF,
button: Viewer ];
MakeBool:
PROC
[name: ROPE, init: REF BOOL, change: BoolProc ← NIL, clientData: REF ← NIL, parent: Viewer, x, y: INTEGER]
RETURNS [child: Viewer] = {
bool: Bool ←
NEW [BoolRec ← [
value: IF init # NIL THEN init ELSE NEW [BOOL ← TRUE],
change: change,
clientData: clientData,
button: NIL ] ];
child ← Buttons.Create[
info: [name: name, parent: parent, border: TRUE, wx: x, wy: y],
proc: BoolHelper, clientData: bool, fork: TRUE, paint: TRUE];
bool.button ← child;
IF bool.value^ THEN Buttons.SetDisplayStyle[child, $WhiteOnBlack]; };
BoolHelper: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
self: Buttons.Button = NARROW[parent];
bool: Bool = NARROW[clientData];
bool.value^ ← ~bool.value^;
IF bool.value^ THEN Buttons.SetDisplayStyle[bool.button, $WhiteOnBlack]
ELSE Buttons.SetDisplayStyle[bool.button, $BlackOnWhite];
IF bool.change # NIL THEN bool.change[self.parent, bool.clientData, bool.value^]; };
MakeLabel:
PROC [parent, sibling: Viewer, name:
ROPE]
RETURNS [child: Viewer] = {
child ← Labels.Create[
info: [name: name, parent: parent, border:
FALSE,
wy: sibling.wy + sibling.wh + (IF sibling.class.flavor = $Button THEN -1 ELSE 2),
wx: 2,
ww: VFonts.StringWidth[name] + 2*3 + 2],
paint: FALSE ]; };
MakeLabeledText:
PROC [
parent, sibling: Viewer, name, data: ROPE, prev: Viewer, width: INT, newline: BOOL ← TRUE] RETURNS [child: Viewer] = {
buttonWidth: INT ← VFonts.StringWidth[name] + 2*3;
x: INTEGER = IF newline THEN 2 ELSE sibling.wx + sibling.ww + 10;
y: INTEGER = IF newline THEN sibling.wy + sibling.wh + 1 ELSE sibling.wy;
child ← ViewerTools.MakeNewTextViewer[
info: [
parent: parent, wh: buttonHeight, ww: width+10,
data: IF prev = NIL THEN data ELSE ViewerTools.GetContents[prev],
border: FALSE,
wx: x + buttonWidth + 2, wy: y,
scrollable: FALSE ],
paint: FALSE ];
[] ← Buttons.Create[
info: [name: name, parent: parent, wh: buttonHeight, border: FALSE, wx: x, wy: y],
proc: LabeledTextProc, clientData: child, fork: FALSE, paint: FALSE];
RETURN[child]; };
LabeledTextProc: Buttons.ButtonProc = {
parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL
text: Viewer = NARROW[clientData];
SELECT mouseButton
FROM
red => ViewerTools.SetSelection[text, NIL];
yellow => NULL;
blue => { ViewerTools.SetContents[text, NIL]; ViewerTools.SetSelection[text, NIL] };
ENDCASE => ERROR; };
FTPStyleDate:
PROC [date: BasicTime.
GMT]
RETURNS [rope:
ROPE] = {
month:
ARRAY BasicTime.MonthOfYear
OF Rope.
ROPE = [
January: "Jan", February: "Feb", March: "Mar", April: "Apr", May: "May", June: "Jun", July: "Jul", August: "Aug", September: "Sep", October: "Oct", November: "Nov", December: "Dec" ];
zoneIndex: TYPE = [4 .. 10];
zoneChars: ARRAY zoneIndex OF CHAR = ['A, 'E, 'C, 'M, 'P, 'Y, 'H];
unpack: BasicTime.Unpacked = BasicTime.Unpack[date];
absZone: INT ← ABS[IF unpack.dst = yes THEN unpack.zone - 60 ELSE unpack.zone];
Desired syntax is: "dd-mmm-yy hh:mm:ss zzz"
str: IO.STREAM = IO.ROS[];
IO.PutF[str, "%02d-%g-%02d", [integer[unpack.day]], [rope[month[unpack.month]]], [integer[unpack.year MOD 100]] ];
IO.PutF[str, " %02d:%02d:%02d ", [integer[unpack.hour]], [integer[unpack.minute]], [integer[unpack.second]] ];
IF (unpack.zone / 60
IN zoneIndex)
AND absZone
MOD 60 = 0
THEN {
IO.PutChar[str, zoneChars[unpack.zone / 60]];
IO.PutChar[str, IF unpack.dst = yes THEN 'D ELSE 'S];
IO.PutChar[str, 'T]; }
ELSE
IO.PutF[str, "%g%g:%02d", [character[
IF unpack.zone < 0
THEN '-
ELSE '+]],
[integer[absZone/60]], [integer[absZone MOD 60]] ];
RETURN[IO.RopeFromROS[str]];
};
Commander.Register["Compare", Create, "Compare a pair of remote files."];
END.