Compare.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Hal Murray, September 26, 1986 2:18:16 am PDT
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: STREAMNIL,
in: STREAMNIL,
pleaseStop: BOOLEANFALSE,
user: PROCESSNIL,
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: BOOLFALSE]
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: ROPENIL,
fileName: ROPENIL,
sizeRope: ROPENIL,
createRope: ROPENIL,
size: INT ← 0,
bytes: INT ← 0,
checksum: WORD ← 0,
stream: STREAMNIL,
index: INT ← 0,
block: REF READONLY TEXTNIL,
length: INT ← 0,
done: BOOLFALSE ];
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: STREAMFS.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: BOOLFALSE;
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.lengthMIN[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 ATOMNIL, change: SelectorProc ← NIL, clientData: REFNIL, 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: REFNIL, parent: Viewer, x, y: INTEGER]
RETURNS [child: Viewer] = {
bool: Bool ← NEW [BoolRec ← [
value: IF init # NIL THEN init ELSE NEW [BOOLTRUE],
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: BOOLTRUE] 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: INTABS[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.