DIRECTORY Atom USING [GetPName], Basics USING [bytesPerWord, LowByte, LowHalf, RawBytes, UnsafeBlock], BasicTime USING [GetClockPulses, Pulses, PulsesToSeconds], Buttons USING [Button, ButtonProc, Create, SetDisplayStyle], Commander USING [CommandProc, Register], Containers USING [ChildXBound, ChildYBound, Create], Convert USING [IntFromRope], CountedVM USING [Free, Handle, SimpleAllocate], FS USING [Error, StreamBufferParms, StreamOpen], IO USING [Close, Flush, GetBlock, GetLength, PutBlock, PutF, PutRope, STREAM, UnsafeGetBlock, UnsafePutBlock, Value], Labels USING [Create], Loader USING [BCDBuildTime], Process USING [Pause, priorityBackground, SecondsToTicks, SetPriority], Pup USING [Address, nullAddress], PupHop USING [GetRouting, RoutingTableEntry], PupName USING [AddressToRope, Error, HisName, NameLookup], PupStream USING [AllocateSocket, Create, CreateListener, DestroyListener, Listener, LocalAddress, Push, Sockets, StreamClosing, Timeout, WaitForRendezvous], PupWKS USING [bspSink], Rope USING [ROPE], Rules USING [Create], STP USING [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]; PupBSPTool: CEDAR MONITOR IMPORTS Atom, Basics, BasicTime, Buttons, Commander, Containers, Convert, CountedVM, FS, IO, Labels, Loader, Process, PupHop, PupName, PupStream, 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; buttonHeight: INT _ VFonts.FontHeight[] + 3; buttonWidth: INT _ VFonts.StringWidth["WrDsk"] + 2*3; serverLog: STREAM _ NIL; listener: PupStream.Listener _ NIL; ClientData: TYPE = REF ClientDataRep; ClientDataRep: TYPE = RECORD [ log: STREAM _ NIL, in: STREAM _ NIL, pleaseStop: BOOLEAN _ FALSE, loop: BOOL _ FALSE, small: BOOL _ FALSE, user: PROCESS _ NIL, where: Pup.Address _ Pup.nullAddress, target: 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: "PupBSPTool", column: right, iconic: TRUE, scrollable: FALSE]]; [] _ ViewerEvents.RegisterEventProc[Poof, destroy, viewer, TRUE]; ViewerOps.AddProp[viewer, $PupBSPTool, data]; log _ TypeScript.Create[ [name: "PupBSPTool.log", wy: 27+4, parent: viewer, border: FALSE], FALSE]; [data.in, data.log] _ ViewerIO.CreateViewerStreams [ name: "PupBSPTool.log", backingFile: "PupBSPTool.log", viewer: log, editedStream: FALSE]; IF serverLog = NIL THEN serverLog _ data.log; Containers.ChildXBound[viewer, log]; Containers.ChildYBound[viewer, log]; CreateButtons[data, viewer, log]; TypeScript.ChangeLooks[log, 'f]; IO.PutF[data.log, "PupBSPTool of %G.\n\n", [time[Loader.BCDBuildTime[Create]]]]; ViewerOps.OpenIcon[viewer]; }; CreateButtons: 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 _ data.target _ MakeLabeledText[ parent: kids, sibling: child, name: "Target:", data: "Target", width: VFonts.StringWidth["Big long name ................................."], prev: data.target ]; child _ MakeRule[kids, child]; child _ MakeLabel[kids, child, "What: "]; child _ MakeButton[kids, child, data, "Stop", StopProc]; child _ MakeButton[kids, child, data, "Push", PushProc]; child _ MakeButton[kids, child, data, "Clump", ClumpProc]; child _ MakeButton[kids, child, data, "Flush", FlushProc]; child _ MakeButton[kids, child, data, "RdDsk", ReadDiskProc]; child _ MakeButton[kids, child, data, "WrDsk", WriteDiskProc]; child _ MakeButton[kids, child, data, "Snarf", SnarfProc]; child _ MakeButton[kids, child, data, "Rendezvous", RendezvousProc]; child _ MakeButton[kids, child, data, "BufferTest", BufferTestProc]; 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 = { data: ClientData _ NARROW[ViewerOps.FetchProp[viewer, $PupBSPTool]]; IF event # destroy OR before # TRUE THEN ERROR; Stop[data]; SmashServerLog[data]; IO.Close[data.log]; IO.Close[data.in]; }; SmashServerLog: ENTRY PROC [data: ClientData] = { IF data.log = serverLog THEN serverLog _ NIL; }; StartServer: PROC = { IF listener # NIL THEN RETURN; listener _ PupStream.CreateListener[ local: PupWKS.bspSink, worker: Server, getTimeout: 60000, putTimeout: 0]; }; StopServer: PROC = { IF listener = NIL THEN RETURN; PupStream.DestroyListener[listener]; listener _ NIL; }; Server: PROC [stream: STREAM, clientData: REF ANY, remote: Pup.Address] = { start: BasicTime.Pulses _ BasicTime.GetClockPulses[]; stop: BasicTime.Pulses; bytesReceived: INT _ 0; temp: REF TEXT _ NEW[TEXT[1024]]; error: ROPE _ NIL; DO bytes: INT _ IO.GetBlock[stream, temp, 0 ! PupStream.StreamClosing => { IF why # remoteClose THEN error _ text; EXIT; }; PupStream.Timeout => { error _ "Timeout"; EXIT; }]; IF bytes = 0 THEN EXIT; bytesReceived _ bytesReceived + bytes; ENDLOOP; IO.Close[stream]; stop _ BasicTime.GetClockPulses[]; ServerPrintout[remote, bytesReceived, start, stop, error]; }; StopProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; Stop[data]; }; Start: PROC [data: ClientData, proc: PROC [ClientData]] = { p: PROCESS; data.pleaseStop _ FALSE; p _ FORK proc[data]; p _ StartInner[data, p]; IF p # NIL THEN TRUSTED { JOIN p; }; -- User pounding on the button }; StartInner: ENTRY PROC [data: ClientData, p: PROCESS] RETURNS [q: PROCESS] = { q _ data.user; data.user _ p; }; Stop: PROC [data: ClientData] = { p: PROCESS _ StopInner[data]; data.pleaseStop _ TRUE; IF p # NIL THEN TRUSTED { JOIN p; }; }; StopInner: ENTRY PROC [data: ClientData] RETURNS [p: PROCESS] = { p _ data.user; data.user _ NIL; }; PushProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; data.small _ control; Push[data]; }; Push: PROC [data: ClientData] = { target: ROPE = ViewerTools.GetContents[data.target]; Stop[data]; IO.PutF[data.log, "\nSending to %G", [rope[target]]]; IF ~FindPath[data, target] THEN RETURN; Start[data, Pusher]; }; ClumpProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; data.loop _ shift; data.small _ control; Clump[data]; }; Clump: PROC [data: ClientData] = { target: ROPE = ViewerTools.GetContents[data.target]; Stop[data]; IO.PutF[data.log, "\nSending a clump to %G", [rope[target]]]; IF ~FindPath[data, target] THEN RETURN; Process.Pause[5]; Start[data, Clumper]; }; FlushProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; Flush[data]; }; Flush: PROC [data: ClientData] = { target: ROPE = ViewerTools.GetContents[data.target]; Stop[data]; IO.PutF[data.log, "\nFlushing to %G", [rope[target]]]; IF ~FindPath[data, target] THEN RETURN; Start[data, Flusher]; }; RendezvousProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; Rendezvous[data]; }; Rendezvous: PROC [data: ClientData] = { target: ROPE = ViewerTools.GetContents[data.target]; Stop[data]; IO.PutF[data.log, "\nTesting Rendezvous with self"]; IF ~FindPath[data, "ME"] THEN RETURN; Start[data, Rendezvouser]; }; BufferTestProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; BufferTest[data]; }; BufferTest: PROC [data: ClientData] = { target: ROPE = ViewerTools.GetContents[data.target]; Stop[data]; IO.PutF[data.log, "\nTesting huge buffer cases"]; IF ~FindPath[data, "ME"] THEN RETURN; Process.SetPriority[Process.priorityBackground]; Start[data, BufferTester]; }; ReadDiskProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; ReadDisk[data]; }; WriteDiskProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; scale: NAT; SELECT TRUE FROM shift AND control => scale _ 1; control => scale _ 2; shift => scale _ 4; ENDCASE => scale _ 8; WriteDisk[data, scale]; }; SnarfProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; Snarf[data]; }; Pusher: PROC [data: ClientData] = { stream: STREAM; start: BasicTime.Pulses _ BasicTime.GetClockPulses[]; open: BasicTime.Pulses; flush: BasicTime.Pulses; close: BasicTime.Pulses; stop: BasicTime.Pulses; bytesSent: INT _ 0; error: ROPE _ NIL; temp: REF TEXT _ NEW[TEXT[1024]]; temp.length _ temp.maxLength; IF data.small THEN temp.length _ 32; stream _ PupStream.Create[data.where, 0, 30000 ! PupStream.StreamClosing => { IO.PutF[data.log, "Open failed: %G.\n\n", [rope[text]] ]; CONTINUE }]; IF stream = NIL THEN RETURN; open _ BasicTime.GetClockPulses[]; UNTIL data.pleaseStop DO IO.PutBlock[stream, temp ! PupStream.StreamClosing => { error _ text; EXIT; }; PupStream.Timeout => { error _ "Timeout"; EXIT; }]; bytesSent _ bytesSent + temp.length; IF data.small THEN PupStream.Push[stream]; ENDLOOP; flush _ BasicTime.GetClockPulses[]; IO.Flush[stream ! PupStream.StreamClosing => { IF error = NIL THEN error _ text; CONTINUE; }; PupStream.Timeout => { IF error = NIL THEN error _ "Timeout"; CONTINUE; }]; close _ BasicTime.GetClockPulses[]; IO.Close[stream]; stop _ BasicTime.GetClockPulses[]; ClientPrintout[data, bytesSent, start, open, flush, close, stop, error]; }; clumpSize: NAT _ 32; -- Quarter megabit Clumper: PROC [data: ClientData] = { clumpsToSend: INT _ clumpSize; stream: STREAM; start: BasicTime.Pulses; open: BasicTime.Pulses; flush: BasicTime.Pulses; close: BasicTime.Pulses; stop: BasicTime.Pulses; temp: REF TEXT _ NEW[TEXT[1024]]; temp.length _ temp.maxLength; IF data.small THEN { temp.length _ 32; clumpsToSend _ clumpsToSend * temp.maxLength / temp.length; }; UNTIL data.pleaseStop DO error: ROPE _ NIL; bytesSent: INT _ 0; start _ BasicTime.GetClockPulses[]; stream _ PupStream.Create[data.where, 0, 30000 ! PupStream.StreamClosing => { IO.PutF[data.log, "Open failed: %G.\n\n", [rope[text]] ]; CONTINUE }]; IF stream = NIL THEN RETURN; open _ BasicTime.GetClockPulses[]; FOR i: INT IN [0..clumpsToSend) UNTIL data.pleaseStop DO IO.PutBlock[stream, temp ! PupStream.StreamClosing => { error _ text; EXIT; }; PupStream.Timeout => { error _ "Timeout"; EXIT; }]; bytesSent _ bytesSent + temp.length; IF data.small THEN PupStream.Push[stream]; ENDLOOP; flush _ BasicTime.GetClockPulses[]; IO.Flush[stream ! PupStream.StreamClosing => { IF error = NIL THEN error _ text; CONTINUE; }; PupStream.Timeout => { IF error = NIL THEN error _ "Timeout"; CONTINUE; }]; close _ BasicTime.GetClockPulses[]; IO.Close[stream]; stop _ BasicTime.GetClockPulses[]; ClientPrintout[data, bytesSent, start, open, flush, close, stop, error]; IF error # NIL THEN EXIT; IF ~data.loop THEN EXIT; Process.Pause[Process.SecondsToTicks[10]]; ENDLOOP; }; flushClumpSize: INT _ 10; Flusher: PROC [data: ClientData] = { stream: STREAM; start: BasicTime.Pulses _ BasicTime.GetClockPulses[]; open: BasicTime.Pulses; close: BasicTime.Pulses; stop: BasicTime.Pulses; error: ROPE _ NIL; bytesSent: INT _ 0; temp: REF TEXT _ NEW[TEXT[1024]]; temp.length _ temp.maxLength; stream _ PupStream.Create[data.where, 0, 30000 ! PupStream.StreamClosing => { IO.PutF[data.log, "Open failed: %G.\n\n", [rope[text]] ]; CONTINUE }]; IF stream = NIL THEN RETURN; open _ BasicTime.GetClockPulses[]; UNTIL data.pleaseStop OR error # NIL DO FOR i: INT IN [0..flushClumpSize) DO IO.PutBlock[stream, temp ! PupStream.StreamClosing => { error _ text; EXIT; }; PupStream.Timeout => { error _ "Timeout"; EXIT; }]; bytesSent _ bytesSent + temp.length; ENDLOOP; IO.Flush[stream ! PupStream.StreamClosing => { error _ text; EXIT; }; PupStream.Timeout => { error _ "Timeout"; EXIT; }]; IO.Flush[stream ! -- Again to test the empty case PupStream.StreamClosing => { error _ text; EXIT; }; PupStream.Timeout => { error _ "Timeout"; EXIT; }]; ENDLOOP; close _ BasicTime.GetClockPulses[]; IO.Close[stream]; stop _ BasicTime.GetClockPulses[]; ClientPrintout[data, bytesSent, start, open, close, close, stop, error]; }; Rendezvouser: PROC [data: ClientData] = { sockets: PupStream.Sockets _ PupStream.AllocateSocket[data.where]; start: BasicTime.Pulses _ BasicTime.GetClockPulses[]; stop: BasicTime.Pulses; bytesReceived: INT _ 0; stream: IO.STREAM _ NIL; temp: REF TEXT _ NEW[TEXT[1024]]; error: ROPE _ NIL; helper: PROCESS; data.where _ PupStream.LocalAddress[sockets]; helper _ FORK Clumper[data]; stream _ PupStream.WaitForRendezvous[sockets, 60000, 0, 20000 ! PupStream.StreamClosing => { error _ text; CONTINUE; } ]; DO bytes: INT _ IO.GetBlock[stream, temp, 0 ! PupStream.StreamClosing => { IF why # remoteClose THEN error _ text; EXIT; }; PupStream.Timeout => { error _ "Timeout"; EXIT; }]; IF bytes = 0 THEN EXIT; bytesReceived _ bytesReceived + bytes; ENDLOOP; IO.Close[stream]; stop _ BasicTime.GetClockPulses[]; ServerPrintout[data.where, bytesReceived, start, stop, error]; TRUSTED { JOIN helper[]; }; }; bigBufferWords: INT _ 250000; -- several times 64K maxBigBufferCycles: INT = 10; bigBufferCycles: [0..maxBigBufferCycles) _ 5; BogusData: SIGNAL = CODE; BufferTester: PROC [data: ClientData] = { sockets: PupStream.Sockets _ PupStream.AllocateSocket[data.where]; start: BasicTime.Pulses _ BasicTime.GetClockPulses[]; stop: BasicTime.Pulses; bytesReceived: INT _ 0; stream: IO.STREAM _ NIL; countedVM: CountedVM.Handle _ CountedVM.SimpleAllocate[words: bigBufferWords+maxBigBufferCycles]; bigBlock: Basics.UnsafeBlock _ [base: NIL, startIndex: 0, count: bigBufferWords * Basics.bytesPerWord]; error: ROPE _ NIL; helper: PROCESS; finger: INT _ 0; TRUSTED { bigBlock.base _ countedVM.pointer; }; data.where _ PupStream.LocalAddress[sockets]; helper _ FORK BufferPusher[data]; stream _ PupStream.WaitForRendezvous[sockets, 600000, 0, 20000 ! PupStream.StreamClosing => { error _ text; CONTINUE; } ]; FOR i: INT IN [0..9999) DO bytes: INT; bigBlock.startIndex _ i MOD bigBufferCycles; -- Test various offset cases too TRUSTED { bytes _ IO.UnsafeGetBlock[stream, bigBlock ! PupStream.StreamClosing => { IF why # remoteClose THEN error _ text; EXIT; }; PupStream.Timeout => { error _ "Timeout"; EXIT; }]; }; IF bytes = 0 THEN EXIT; FOR i: INT IN [0..bytes) DO expected: BYTE _ IndexToByte[finger]; found: BYTE _ LoadByte[bigBlock, i]; IF found # expected THEN SIGNAL BogusData; finger _ SUCC[finger]; ENDLOOP; bytesReceived _ bytesReceived + bytes; ENDLOOP; IO.Close[stream]; stop _ BasicTime.GetClockPulses[]; ServerPrintout[data.where, bytesReceived, start, stop, error]; TRUSTED { JOIN helper[]; CountedVM.Free[countedVM]; }; }; BufferPusher: PROC [data: ClientData] = { stream: STREAM; start: BasicTime.Pulses; open: BasicTime.Pulses; flush: BasicTime.Pulses; close: BasicTime.Pulses; stop: BasicTime.Pulses; countedVM: CountedVM.Handle _ CountedVM.SimpleAllocate[words: bigBufferWords+maxBigBufferCycles]; bigBlock: Basics.UnsafeBlock _ [base: NIL, startIndex: 0, count: bigBufferWords * Basics.bytesPerWord]; error: ROPE _ NIL; bytesSent: INT _ 0; finger: INT _ 0; TRUSTED { bigBlock.base _ countedVM.pointer; }; start _ BasicTime.GetClockPulses[]; stream _ PupStream.Create[data.where, 0, 600000 ! PupStream.StreamClosing => { IO.PutF[data.log, "Open failed: %G.\n\n", [rope[text]] ]; CONTINUE }]; IF stream = NIL THEN RETURN; open _ BasicTime.GetClockPulses[]; FOR i: INT IN [0..bigBufferCycles) UNTIL data.pleaseStop DO bigBlock.startIndex _ i; -- Test various offset cases too FOR i: INT IN [0..bigBlock.count) DO expected: BYTE _ IndexToByte[finger]; StoreByte[bigBlock, i, expected]; finger _ SUCC[finger]; ENDLOOP; IO.UnsafePutBlock[stream, bigBlock ! PupStream.StreamClosing => { error _ text; EXIT; }; PupStream.Timeout => { error _ "Timeout"; EXIT; }]; bytesSent _ bytesSent + bigBlock.count; ENDLOOP; flush _ BasicTime.GetClockPulses[]; IO.Flush[stream ! PupStream.StreamClosing => { IF error = NIL THEN error _ text; CONTINUE; }; PupStream.Timeout => { IF error = NIL THEN error _ "Timeout"; CONTINUE; }]; close _ BasicTime.GetClockPulses[]; IO.Close[stream]; stop _ BasicTime.GetClockPulses[]; ClientPrintout[data, bytesSent, start, open, flush, close, stop, error]; TRUSTED { CountedVM.Free[countedVM]; }; }; IndexToByte: PROC [index: INT] RETURNS [BYTE] = INLINE { RETURN[Basics.LowByte[Basics.LowHalf[(index+1234) MOD 256]]]; }; LoadByte: PROC [block: Basics.UnsafeBlock, offset: INT] RETURNS [data: BYTE] = INLINE { index: INT = block.startIndex + offset; wordShift: INT = index / Basics.bytesPerWord; byteShift: INT = wordShift * Basics.bytesPerWord; leftovers: INT = index - byteShift; base: LONG POINTER TO Basics.RawBytes = block.base + wordShift; TRUSTED { RETURN[base[leftovers]]; }; }; StoreByte: PROC [block: Basics.UnsafeBlock, offset: INT, data: BYTE] = INLINE { index: INT = block.startIndex + offset; wordShift: INT = index / Basics.bytesPerWord; byteShift: INT = wordShift * Basics.bytesPerWord; leftovers: INT = index - byteShift; base: LONG POINTER TO Basics.RawBytes = block.base + wordShift; TRUSTED { base[leftovers] _ data; }; }; KrockeryToSeeWhatTheCompilerGenerates: PROC = { i: INT; base: LONG POINTER TO PACKED ARRAY [0..0) OF BYTE; TRUSTED { base[123] _ 6; }; TRUSTED { base[i] _ 6; }; }; filename: ROPE = "///Foo"; remoteFilename: ROPE = "Foo"; readBuffers: FS.StreamBufferParms _ [vmPagesPerBuffer: 127, nBuffers: 2]; ReadDisk: ENTRY PROC [data: ClientData] = { stream: STREAM; start: BasicTime.Pulses _ BasicTime.GetClockPulses[]; open: BasicTime.Pulses; close: BasicTime.Pulses; stop: BasicTime.Pulses; bytes: INT _ 0; temp: REF TEXT _ NEW[TEXT[1024]]; stream _ FS.StreamOpen[ fileName: filename, accessOptions: $read, streamBufferParms: readBuffers ! FS.Error => { IO.PutF[data.log, "Open for read failed: %G.\n\n", [rope[error.explanation]] ]; CONTINUE }]; IF stream = NIL THEN RETURN; open _ BasicTime.GetClockPulses[]; DO clump: INT _ IO.GetBlock[stream, temp, 0]; bytes _ bytes + clump; IF clump = 0 THEN EXIT; ENDLOOP; close _ BasicTime.GetClockPulses[]; IO.Close[stream]; stop _ BasicTime.GetClockPulses[]; DiskPrintout[data.log, "read", bytes, start, open, close, stop]; }; writeBuffers: FS.StreamBufferParms _ [vmPagesPerBuffer: 127, nBuffers: 2]; WriteDisk: ENTRY PROC [data: ClientData, scale: NAT] = { stream: STREAM; start: BasicTime.Pulses _ BasicTime.GetClockPulses[]; open: BasicTime.Pulses; close: BasicTime.Pulses; stop: BasicTime.Pulses; bytes: INT _ 0; temp: REF TEXT _ NEW[TEXT[1024]]; temp.length _ temp.maxLength; stream _ FS.StreamOpen[ fileName: filename, accessOptions: $create, createByteCount: scale*1048576, streamBufferParms: writeBuffers ! FS.Error => { IO.PutF[data.log, "Open for write failed: %G.\n\n", [rope[error.explanation]] ]; CONTINUE }]; IF stream = NIL THEN RETURN; open _ BasicTime.GetClockPulses[]; FOR i: NAT IN [0..scale*1024) DO -- n Megabytes (big M) IO.PutBlock[stream, temp, 0]; bytes _ bytes + temp.length; ENDLOOP; close _ BasicTime.GetClockPulses[]; IO.Close[stream]; stop _ BasicTime.GetClockPulses[]; DiskPrintout[data.log, "written", bytes, start, open, close, stop]; }; stpBuffers: FS.StreamBufferParms _ [vmPagesPerBuffer: 127, nBuffers: 2]; Snarf: ENTRY PROC [data: ClientData] = { Confirm: STP.ConfirmProcType = { size: ROPE _ STP.GetProperty[stp, size]; bytes: INT _ 1048576; IF size # NIL THEN bytes _ Convert.IntFromRope[size]; stream _ FS.StreamOpen[ fileName: filename, accessOptions: $create, createByteCount: bytes, streamBufferParms: stpBuffers ! FS.Error => { IO.PutF[data.log, "Open for write failed: %G.\n\n", [rope[error.explanation]] ]; CONTINUE }]; IF stream = NIL THEN RETURN[abort, NIL]; open _ BasicTime.GetClockPulses[]; RETURN[do, stream]; }; start: BasicTime.Pulses _ BasicTime.GetClockPulses[]; open: BasicTime.Pulses; close: BasicTime.Pulses; stop: BasicTime.Pulses; target: ROPE = ViewerTools.GetContents[data.target]; stp: STP.Handle _ STP.Create[]; herald, name, password: ROPE; failed: BOOL _ FALSE; stream: STREAM; bytes: INT _ 0; desiredProperties: STP.DesiredProperties _ ALL[FALSE]; desiredProperties[size] _ TRUE; herald _ STP.Open[stp, target ! STP.Error => { IO.PutF[data.log, "STP Open failed: %G.\n\n", [rope[error]] ]; failed _ TRUE; CONTINUE }]; IF failed THEN RETURN; [name, password] _ UserCredentials.Get[]; STP.Login[stp, name, password]; STP.SetDesiredProperties[stp, desiredProperties]; STP.Retrieve[stp, remoteFilename, Confirm, NIL]; close _ BasicTime.GetClockPulses[]; bytes _ IO.GetLength[stream]; IO.Close[stream]; STP.Close[stp]; stop _ BasicTime.GetClockPulses[]; DiskPrintout[data.log, "copied", bytes, start, open, close, stop]; }; FindPath: PROC [data: ClientData, target: ROPE] RETURNS [BOOLEAN] = { rte: PupHop.RoutingTableEntry; data.where _ PupName.NameLookup[target, PupWKS.bspSink ! PupName.Error => { IO.PutF[data.log, " Oops: %G.\n", [rope[text]]]; GOTO Trouble; }]; IO.PutF[data.log, " = %G", [rope[PupName.AddressToRope[data.where]]]]; rte _ PupHop.GetRouting[[data.where.net]]; IF rte.hop # 0 THEN IO.PutF[data.log, " which is %G hops via %G", [integer[rte.hop]], [rope[PupName.AddressToRope[rte.immediate]]] ]; IO.PutRope[data.log, ".\n"]; RETURN[TRUE]; EXITS Trouble => RETURN[FALSE]; }; ClientPrintout: ENTRY PROC [ data: ClientData, bytesSent: INT, start, open, flush, close, stop: BasicTime.Pulses, error: ROPE] = { who: ROPE _ PupName.HisName[data.where]; bits: INT _ 8*bytesSent; seconds: REAL _ BasicTime.PulsesToSeconds[stop-start]; bitsPerSecond: REAL _ bits/seconds; IF error # NIL THEN IO.PutF[data.log, "*** Trouble: %G.\n", [rope[error]] ]; IF bitsPerSecond > 100000 THEN IO.PutF[data.log, "%GK bits sent to %G in %3.2F seconds = %1.0FK bits/second.\n", [integer[bits/1000]], [rope[who]], [real[seconds]], [real[bitsPerSecond/1000]] ] ELSE IO.PutF[data.log, "%GK bits sent to %G in %3.2F seconds = %1.0F bits/second.\n", [integer[bits/1000]], [rope[who]], [real[seconds]], [real[bitsPerSecond]] ]; seconds _ BasicTime.PulsesToSeconds[open-start]; IO.PutF[data.log, "It took %3.2F seconds to open the connection.\n", [real[seconds]] ]; seconds _ BasicTime.PulsesToSeconds[close-flush]; IF close # flush THEN IO.PutF[data.log, "It took %3.2F seconds to flush the connection.\n", [real[seconds]] ]; seconds _ BasicTime.PulsesToSeconds[stop-close]; IO.PutF[data.log, "It took %3.2F seconds to close the connection.\n", [real[seconds]] ]; IO.PutRope[data.log, "\n"]; IO.Flush[data.log]; }; DiskPrintout: INTERNAL PROC [ log: STREAM, what: ROPE, bytes: INT, start, open, close, stop: BasicTime.Pulses] = { bits: INT _ 8*bytes; seconds: REAL _ BasicTime.PulsesToSeconds[close-open]; IO.PutF[log, "%GK bits %G in %3.2F seconds = %1.0FK bits/second.\n", [integer[bits/1000]], [rope[what]], [real[seconds]], [real[bits/seconds/1000]] ]; seconds _ BasicTime.PulsesToSeconds[open-start]; IO.PutF[log, "It took %3.2F more seconds to open the file.\n", [real[seconds]] ]; seconds _ BasicTime.PulsesToSeconds[stop-close]; IO.PutF[log, "It took %3.2F more seconds to close the file.\n", [real[seconds]] ]; IO.PutRope[log, "\n"]; }; ServerPrintout: ENTRY PROC [ remote: Pup.Address, bytes: INT, start, stop: BasicTime.Pulses, error: ROPE] = { who: ROPE _ PupName.HisName[remote]; bits: INT _ 8*bytes; seconds: REAL _ BasicTime.PulsesToSeconds[stop-start]; bitsPerSecond: REAL _ bits/seconds; IF serverLog = NIL THEN RETURN; IF error # NIL THEN IO.PutF[serverLog, "*** Trouble: %G.*** \n", [rope[error]] ]; IF bitsPerSecond > 100000 THEN IO.PutF[serverLog, "%GK bits received from %G in %3.2F seconds = %1.0FK bits/second.\n", [integer[bits/1000]], [rope[who]], [real[seconds]], [real[bitsPerSecond/1000]] ] ELSE IO.PutF[serverLog, "%GK bits received from %G in %3.2F seconds = %1.0F bits/second.\n", [integer[bits/1000]], [rope[who]], [real[seconds]], [real[bitsPerSecond]] ]; IO.PutRope[serverLog, "\n"]; IO.Flush[serverLog]; }; 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 = { 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 = { 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 = { 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; }; Commander.Register["PupBSPTool", Create, "Test Pup Streams to another machine."]; StartServer[]; END. τPupBSPTool.mesa Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. Hal Murray, December 13, 1986 4:34:03 pm PST The Monitor is used to protect 3 things: 1) Access to data.user - only one task can run at a time. 2) Access to ///Foo (on the disk). 3) Interleaving printout at the end of each task. Viewer layout parameters Server data (global) [viewer: ViewerClasses.Viewer, event: ViewerEvent, before: BOOL] RETURNS[abort: BOOL _ FALSE] parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL Super barfo. The Compiler can't hack PACKED ARRAYS OF BYTE > 64K. It generates code that gets a BoundsCheck (just before a WriteStringLong). parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL Κ#!˜šœ™Icodešœ Οmœ7™BK™,—J˜šΟk ˜ Jšœžœ ˜Jšœžœ9˜EJšœ žœ+˜:Jšœžœ/˜žœ)˜uJšœžœ ˜Jšœžœ˜Jšœžœ:˜GJšœžœ˜!Jšœžœ!˜-Jšœžœ-˜:Jšœ žœ˜œJšœžœ ˜Jšœžœžœ˜Jšœžœ ˜Jšžœžœ~˜‡Jšœ žœ˜'Jšœžœ˜Jšœžœ˜'Jšœžœ ˜Jšœ žœ ˜2Jšœ žœ˜%Jšœ žœX˜gJšœ žœ=˜NJ˜—šœ žœž˜šž˜JšœMžœžœ>žœW˜λ—Jšž˜J˜šœ(™(J™9J™"J™1—J˜Jšžœžœ ˜Jšžœžœžœ˜Jšžœžœžœžœ˜Jšœžœ˜%™Jšœžœ˜,Jšœ žœ%˜5—J˜™Jšœ žœžœ˜Jšœžœ˜#—J˜Jšœ žœžœ˜%šœžœžœ˜Jšœžœžœ˜Jšœžœžœ˜Jšœ žœžœ˜Jšœž œ˜Jšœž œ˜Jšœžœžœ˜Jšœ%˜%Jšœžœ˜—J˜JšœžœΟc ˜'šΟbœ˜!Jšœžœ˜#Jšœžœ˜+J˜šœ!˜!J˜Jšœ2žœžœ˜M—Jšœ;žœ˜AJšœ-˜-šœ˜Jšœ;žœžœ˜J—šœ4˜4JšœRžœ˜Y—Jšžœ žœžœ˜-Jšœ$˜$Jšœ$˜$Jšœ!˜!Jšœ ˜ JšžœN˜PJšœ˜J˜—šΟn œžœ+˜?Jšœžœ˜šœ!˜!Jšœžœžœ'˜^—J˜%J˜šœ&˜&J˜ J˜J˜J˜JšœM˜MJšœ˜—J˜J˜)Jšœ8˜8Jšœ8˜8Jšœ:˜:Jšœ:˜:Jšœ=˜=Jšœ>˜>Jšœ:˜:JšœD˜DJšœD˜DJ˜šœ˜Jšœžœ˜Jšœžœ˜)Kšœnžœ˜uJ˜CJšžœžœ(˜>JšœR˜R—J˜—šΠbnœ˜ Jšœ;žœ™@Jšžœžœžœ™Jšœžœ+˜DJš žœžœ žœžœžœ˜/Jšœ ˜ Jšœ˜Jšžœ˜Jšžœ˜J˜J˜—š‘œžœžœ˜1Jšžœžœ žœ˜-J˜J˜—š‘ œžœ˜Jšžœ žœžœžœ˜šœ$˜$JšœI˜I—Jšœ˜J˜—š‘ œžœ˜Jšžœ žœžœžœ˜Jšœ$˜$Jšœ žœ˜Jšœ˜J˜—š ‘œžœ žœžœžœ˜KJšœ5˜5Jšœ˜Jšœžœ˜Jš œžœžœžœžœ˜!Jšœžœžœ˜šž˜šœžœžœ˜*Jšœžœžœžœ˜MJšœ*žœ˜3—Jšžœ žœžœ˜Jšœ&˜&Jšžœ˜—Jšžœ˜Jšœ"˜"Jšœ:˜:Jšœ˜J˜—š œ˜ JšœL™LJšœžœ ˜&Jšœ ˜ Jšœ˜J˜—š‘œžœžœ˜;Jšœžœ˜ Jšœžœ˜Jšœžœ ˜J˜Jš žœžœžœžœžœŸ˜CJšœ˜J˜—š ‘ œžœžœžœžœžœ˜NJšœ˜Jšœ˜Jšœ˜J˜—š‘œžœ˜!Jšœžœ˜Jšœžœ˜Jš žœžœžœžœžœ˜$Jšœ˜J˜—š ‘ œžœžœžœžœ˜AJšœ˜Jšœ žœ˜Jšœ˜J˜—š œ˜ JšœL™LJšœžœ ˜&Jšœ˜Jšœ ˜ Jšœ˜J˜—š‘œžœ˜!Jšœžœ(˜4Jšœ ˜ Jšžœ3˜5Jšžœžœžœ˜'Jšœ˜Jšœ˜J˜—š  œ˜!JšœL™LJšœžœ ˜&Jšœ˜Jšœ˜Jšœ ˜ Jšœ˜J˜—š‘œžœ˜"Jšœžœ(˜4Jšœ ˜ Jšžœ;˜=Jšžœžœžœ˜'Jšœ˜Jšœ˜Jšœ˜J˜—š  œ˜!JšœL™LJšœžœ ˜&Jšœ ˜ Jšœ˜J˜—š‘œžœ˜"Jšœžœ(˜4Jšœ ˜ Jšžœ4˜6Jšžœžœžœ˜'Jšœ˜Jšœ˜J˜—š‘ ’œ˜&JšœL™LJšœžœ ˜&Jšœ˜Jšœ˜J˜—š‘ œžœ˜'Jšœžœ(˜4Jšœ ˜ Jšžœ2˜4Jšžœžœžœ˜%Jšœ˜Jšœ˜J˜—š‘ ’œ˜&JšœL™LJšœžœ ˜&Jšœ˜Jšœ˜J˜—š‘ œžœ˜'Jšœžœ(˜4Jšœ ˜ Jšžœ/˜1Jšžœžœžœ˜%Jšœ0˜0Jšœ˜Jšœ˜J˜—š  œ˜$JšœL™LJšœžœ ˜&Jšœ˜J˜—š  œ˜%JšœL™LJšœžœ ˜&Jšœžœ˜ šžœžœž˜Jšœžœ˜Jšœ˜Jšœ˜Jšžœ˜—Jšœ˜J˜—š  œ˜!JšœL™LJšœžœ ˜&Jšœ˜J˜—š‘œžœ˜#Jšœžœ˜Jšœ5˜5Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ žœ˜Jšœžœžœ˜Jš œžœžœžœžœ˜!Jšœ˜Jšžœ žœ˜$šœM˜MJšžœ8žœ˜F—Jšžœ žœžœžœ˜Jšœ"˜"šžœž˜šžœ˜Jšœ+žœ˜3Jšœ*žœ˜3—Jšœ$˜$Jšžœ žœ˜*Jšžœ˜—Jšœ#˜#šžœ˜Jš œžœ žœžœžœ˜KJš œžœ žœžœžœ˜K—Jšœ#˜#Jšžœ˜Jšœ"˜"JšœH˜HJšœ˜J˜—Jšœ žœŸ˜'š‘œžœ˜$Jšœžœ ˜Jšœžœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jš œžœžœžœžœ˜!šœ˜Jšžœ žœS˜e—šžœž˜Jšœžœžœ˜Jšœ žœ˜Jšœ#˜#šœM˜MJšžœ8žœ˜F—Jšžœ žœžœžœ˜Jšœ"˜"š žœžœžœžœž˜8šžœ˜Jšœ+žœ˜3Jšœ*žœ˜3—Jšœ$˜$Jšžœ žœ˜*Jšžœ˜—Jšœ#˜#šžœ˜Jš œžœ žœžœžœ˜KJš œžœ žœžœžœ˜K—Jšœ#˜#Jšžœ˜Jšœ"˜"JšœH˜HJšžœ žœžœžœ˜Jšžœ žœžœ˜Jšœ*˜*Jšžœ˜—Jšœ˜J˜—Jšœžœ˜š‘œžœ˜$Jšœžœ˜Jšœ5˜5Jšœ˜Jšœ˜Jšœ˜Jšœžœžœ˜Jšœ žœ˜Jš œžœžœžœžœ˜!Jšœ˜šœM˜MJšžœ8žœ˜F—Jšžœ žœžœžœ˜Jšœ"˜"šžœžœ žœž˜'šžœžœžœž˜$šžœ˜Jšœ+žœ˜3Jšœ*žœ˜3—Jšœ$˜$Jšžœ˜—šžœ˜Jšœ+žœ˜3Jšœ*žœ˜3—šžœŸ˜1Jšœ+žœ˜3Jšœ*žœ˜3—Jšžœ˜—Jšœ#˜#Jšžœ˜Jšœ"˜"JšœH˜HJšœ˜J˜—š‘ œžœ˜)K˜BJšœ5˜5Jšœ˜Jšœžœ˜Jšœžœžœžœ˜Jš œžœžœžœžœ˜!Jšœžœžœ˜Jšœžœ˜Jšœ-˜-Jšœ žœ˜šœ?˜?Jšœ+žœ˜9—šž˜šœžœžœ˜*Jšœžœžœžœ˜MJšœ*žœ˜3—Jšžœ žœžœ˜Jšœ&˜&Jšžœ˜—Jšžœ˜Jšœ"˜"Jšœ>˜>Jšžœžœ ˜Jšœ˜J˜—Jšœžœ Ÿ˜2Jšœžœ˜Jšœ-˜-Jš‘ œžœžœ˜J˜š‘ œžœ˜)K˜BJšœ5˜5Jšœ˜Jšœžœ˜Jšœžœžœžœ˜Jšœa˜aJšœ&žœ>˜gJšœžœžœ˜Jšœžœ˜Jšœžœ˜Jšžœ(˜/Jšœ-˜-Jšœ žœ˜!šœ@˜@Jšœ+žœ˜9—šžœžœžœ ž˜Jšœžœ˜ JšœžœŸ ˜Mšžœ˜ šœžœ"˜,Jšœžœžœžœ˜MJšœ*žœ˜6——Jšžœ žœžœ˜šžœžœžœ ž˜Jšœ žœž˜%Jšœžœ˜$Jšžœžœžœ ˜*Jšœ žœ ˜Jšžœ˜—Jšœ&˜&Jšžœ˜—Jšžœ˜Jšœ"˜"Jšœ>˜>šžœ˜ Jšžœ ˜Jšœ˜—Jšœ˜J˜—š‘ œžœ˜)Jšœžœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœa˜aJšœ&žœ>˜gJšœžœžœ˜Jšœ žœ˜Jšœžœ˜Jšžœ(˜/Jšœ#˜#šœN˜NJšžœ8žœ˜F—Jšžœ žœžœžœ˜Jšœ"˜"š žœžœžœžœž˜;JšœŸ ˜9šžœžœžœž˜$Jšœ žœ˜%Jšœ"˜"Jšœ žœ ˜Jšžœ˜—šžœ"˜$Jšœ+žœ˜3Jšœ*žœ˜3—Jšœ'˜'—Jšžœ˜Jšœ#˜#šžœ˜Jš œžœ žœžœžœ˜KJš œžœ žœžœžœ˜K—Jšœ#˜#Jšžœ˜Jšœ"˜"JšœH˜HJšžœ ˜'Jšœ˜J˜—š ‘ œžœ žœžœžœžœ˜8Jšžœ,žœ ˜@—Jš œ%žœžœžœžœR™Œš ‘œžœ%žœžœžœžœ˜WJšœžœ˜'Jšœ žœ˜-Jšœ žœ#˜1Jšœ žœ˜#Jšœžœžœžœ*˜?Jšžœžœ˜(—š ‘ œžœ%žœžœžœ˜OJšœžœ˜'Jšœ žœ˜-Jšœ žœ#˜1Jšœ žœ˜#Jšœžœžœžœ*˜?Jšžœ ˜'—š‘%œžœ˜/Jšœžœ˜Jšœžœžœžœžœžœžœžœ˜2Jšžœ˜Jšžœ˜—J˜Jšœ žœ ˜šœžœ ˜J˜—Jšœ žœ:˜Iš‘œžœžœ˜+Jšœžœ˜Jšœ5˜5Jšœ˜Jšœ˜Jšœ˜Jšœžœ˜Jš œžœžœžœžœ˜!šœ žœ ˜Jšœ˜Jšœ˜Jšœ˜šœžœ ˜JšžœNžœ˜\——Jšžœ žœžœžœ˜Jšœ"˜"šž˜Jšœžœžœ˜*Jšœ˜Jšžœ žœžœ˜Jšžœ˜—Jšœ#˜#Jšžœ˜Jšœ"˜"Jšœ@˜@Jšœ˜J˜—Jšœžœ:˜Jš‘ œžœžœžœ˜8Jšœžœ˜Jšœ5˜5Jšœ˜Jšœ˜Jšœ˜Jšœžœ˜Jš œžœžœžœžœ˜!Jšœ˜šœ žœ ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜šœžœ ˜JšžœOžœ˜]——Jšžœ žœžœžœ˜Jšœ"˜"š žœžœžœžœŸ˜7Jšžœ˜Jšœ˜Jšžœ˜—Jšœ#˜#Jšžœ˜Jšœ"˜"JšœC˜CJšœ˜J˜—Jšœ žœ:˜Hš‘œžœžœ˜(š œžœ˜ Jšœžœžœ˜(Jšœžœ ˜Jšžœžœžœ#˜5šœ žœ ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜šœžœ ˜JšžœOžœ˜]——Jš žœ žœžœžœžœ˜(Jšœ"˜"Jšžœ˜—Jšœ5˜5Jšœ˜Jšœ˜Jšœ˜Jšœžœ(˜4Jšœžœ žœ ˜Jšœžœ˜Jšœžœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœžœžœ˜6Jšœžœ˜šœ žœžœ ˜.Jšžœ<˜>Jšœ žœ˜Jšžœ˜ —Jšžœžœžœ˜Jšœ)˜)Jšžœ˜Jšžœ.˜1Jšžœ(žœ˜0Jšœ#˜#Jšœžœ˜Jšžœ˜Jšžœ ˜Jšœ"˜"JšœB˜BJšœ˜J˜—š ‘œžœžœžœžœ˜EJšœ˜šœ8˜8Jš œžœžœ/žœ žœ˜U—JšžœD˜FJšœ*˜*šžœ ž˜šžœ+˜-Jšœ˜Jšœ/˜/——Jšžœ˜Jšžœžœ˜ Jšžœ žœžœ˜Jšœ˜J˜—š‘œžœžœ˜Jšœžœ<žœ˜eJšœžœ˜(Jšœžœ˜Jšœ žœ)˜6Jšœžœ˜#Jšžœ žœžœžœ6˜Lšžœž˜šžœO˜QJšœ˜Jšœ ˜ Jšœ˜Jšœ˜——šž˜šžœN˜PJšœ˜Jšœ ˜ Jšœ˜Jšœ˜——Jšœ0˜0JšžœU˜WJšœ1˜1šžœž˜JšžœV˜X—Jšœ0˜0JšžœV˜XJšžœ˜Jšžœ˜Jšœ˜J˜—š‘ œžœžœ˜Jšœžœžœ žœ1˜TJšœžœ ˜Jšœ žœ)˜6šžœB˜DJšœ˜Jšœ ˜ Jšœ˜Jšœ˜—Jšœ0˜0JšžœO˜QJšœ0˜0JšžœP˜RJšžœ˜Jšœ˜J˜—š‘œžœžœ˜Jšœžœ(žœ˜PJšœžœ˜$Jšœžœ ˜Jšœ žœ)˜6Jšœžœ˜#Jšžœ žœžœžœ˜Jšžœ žœžœžœ;˜Qšžœž˜šžœV˜XJšœ˜Jšœ ˜ Jšœ˜Jšœ˜——šž˜šžœU˜WJšœ˜Jšœ ˜ Jšœ˜Jšœ˜——Jšžœ˜Jšžœ˜Jšœ˜J˜—š‘œžœžœ˜D˜šœžœ˜%Jš œžœ žœžœžœ;˜[—Jšœžœ˜—Jšœ&˜&Jšœ˜J˜—š ‘ œžœ!žœžœžœžœ˜z˜šœ+žœ˜0J˜B—J˜ J˜Jšœžœ˜ Jšœžœ˜—Jšœ˜J˜—Jš ‘ œžœžœžœ žœ˜IJšœ žœžœ ˜!šœ žœžœ˜Jšœžœžœ˜Jšœžœžœ žœ˜J˜J˜Jšœ žœ˜ J˜—Jšœžœžœžœ˜#JšœAžœ˜Xš žœžœžœžœžœžœž˜5šœ˜Kšœ=žœ-˜nJšœ2žœ žœ˜E—Jš žœžœžœžœžœ˜JJšžœžœžœ˜EJšžœžœ/˜PJšžœ˜ ——J˜š œ˜&JšœL™LJšœžœ ˜&Jšœžœ ˜(Jšœ žœžœ#˜3š žœžœžœžœžœžœž˜>šžœžœ˜J˜Jšžœžœžœ<˜YJšœ8˜8—Jšžœ7˜;J˜Jšžœ˜ ——J˜Jš ‘œžœžœžœ žœ˜EJšœžœžœ ˜šœ žœžœ˜Jšœžœžœ˜Jšœ˜Jšœ žœ˜Jšœ˜—J˜š‘œž˜Kšœžœžœžœžœžœžœžœ˜jKšžœ˜šœ žœ ˜Jšœžœžœžœžœžœžœžœ˜6J˜Jšœ˜Jšœžœ˜—šœ˜Kšœ+žœ˜?Jšœ*žœ žœ˜=—Jšœ˜Jšžœ žœ2˜E—J˜š  œ˜"JšœL™LJšœžœ ˜&Jšœ žœ ˜ Jšœ˜Jšžœ žœ4˜GJšžœ5˜9Jšžœžœžœ<˜U—š‘ œžœ!žœžœ˜Q˜šœ+žœ˜1Jšœžœ žœžœ˜QJ˜Jšœ(˜(—Jšœžœ˜——J˜š‘œžœ˜Jš œ%žœžœ žœžœžœ˜vJšœ žœ"˜2Jš œžœžœ žœžœ˜AJš œžœžœ žœžœ ˜I˜&šœ˜Jšœ/˜/Jš œžœžœžœžœ˜AJšœžœ˜J˜Jšœ žœ˜—Jšœžœ˜—˜Jšœ=žœ˜RJšœ0žœ žœ˜E—Jšžœ ˜J˜—š œ˜'JšœL™LJšœžœ ˜"šžœ ž˜Jšœ&žœ˜+Jšœ žœ˜Jšœ(žœ"žœ˜TJšžœžœ˜——J˜JšœQ˜Qšœ˜J˜—Jšžœ˜J˜J˜J˜——…—rθ›ύ