DIRECTORY FS, IO, MessageWindow, Process, Protocols, PupDefs, PupStream, PupTypes, Rope, UserCredentials; PupTelnet: CEDAR MONITOR LOCKS pc USING pc: PupConversation IMPORTS FS, IO, MessageWindow, Process, Protocols, PupStream, Rope, UserCredentials = {OPEN Protocols; pupTelnet: Protocol _ NEW [ProtocolRep _ [ name: "PupTelnet", Instantiate: Instantiate, StartConnect: StartConnect, StartDisconnect: StartDisconnect, GiveUp: GiveUp, SetOnLine: SetOnLine ]]; PupConversation: TYPE = REF PupConversationRep; PupConversationRep: TYPE = MONITORED RECORD [ fromClient, toClient, serverStream, logStream: IO.STREAM _ NIL, charsSentSinceFlush: NAT _ 0, changer: PROCESS _ NIL, change: CONDITION, procs: ARRAY Who OF PROCESS _ ALL[NIL], phase: Phase _ running, serverToUsering: BOOL _ TRUE, gettingClientChar: BOOL _ FALSE]; Phase: TYPE = {running, destroying, destroyed}; Who: TYPE = {uToS, sToU}; whoBar: ARRAY Who OF Who _ [uToS: sToU, sToU: uToS]; MarkByte: TYPE = [0..256); setLineWidth: MarkByte = 2; setPageLength: MarkByte = 3; timingMark: MarkByte = 5; timingMarkReply: MarkByte = 6; doSetLineWidth: BOOL _ TRUE; minLogKeep: CARDINAL _ 10; Instantiate: PROC [client: Client, logFileName: ROPE _ NIL] RETURNS [c: Conversant] = { pc: PupConversation _ NEW [PupConversationRep _ [ fromClient: client.fromClient, toClient: client.toClient ]]; IF logFileName # NIL THEN SetLog[pc, logFileName]; TRUSTED { Process.InitializeCondition[@pc.change, Process.SecondsToTicks[60]]; Process.EnableAborts[@pc.change]; }; c _ NEW [ConversantPrivate _ [ protocol: pupTelnet, client: client, conversantData: pc ]]; TRUSTED { Process.Detach[pc.procs[uToS] _ FORK UserToServer[c, pc]]; Process.Detach[pc.procs[sToU] _ FORK ServerToUser[c, pc]]; }; }; SetLog: PROC [pc: PupConversation, logFileName: ROPE] = { oldKeep: CARDINAL _ 1; pc.logStream _ FS.StreamOpen[fileName: logFileName, accessOptions: create]; oldKeep _ FS.FileInfo[logFileName !FS.Error => CONTINUE].keep; IF oldKeep < minLogKeep THEN FS.SetKeep[logFileName, minLogKeep !FS.Error => CONTINUE]; }; NoteDestruction: ENTRY PROC [c: Conversant, pc: PupConversation] = { ENABLE UNWIND => {}; InternalNoteDestruction[c, pc]; }; InternalNoteDestruction: INTERNAL PROC [c: Conversant, pc: PupConversation] = { SELECT pc.phase FROM running => pc.phase _ destroying; destroying, destroyed => NULL; ENDCASE => ERROR; BROADCAST pc.change; SELECT c.state.connectivity FROM connected => InternalReallyStartDisconnect[c, pc, NIL, FALSE]; connecting, disconnecting => InternalGiveUp[c, pc, NIL]; disconnected => NULL; ENDCASE => ERROR; pc.phase _ destroyed; BROADCAST pc.change; }; StartConnect : PROC [ c: Conversant, serverName: ROPE, login: BOOL _ FALSE] = { pc: PupConversation = NARROW[c.conversantData]; Really: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; IF pc.phase >= destroying THEN { Scream["Can't connect a destroyed conversant!"]; RETURN}; SELECT c.state.connectivity FROM disconnected => { c.serverName _ serverName; c.state.connectivity _ connecting; c.state.onLine _ TRUE; BROADCAST pc.change; c.client.type.NoteState[c ! UNWIND => { c.state.connectivity _ disconnected; BROADCAST pc.change; }; ]; TRUSTED {Process.Detach[ pc.changer _ FORK Connect[c, pc, serverName, login] ]}; }; connecting, connected, disconnecting => { Scream["Already connected to %g!", [rope[c.serverName]]]; c.client.type.NoteState[c]; }; ENDCASE => ERROR; }; Really[pc]; }; SetConnectivity: ENTRY PROC [c: Conversant, pc: PupConversation, cy: Connectivity, cond: UNSAFE PROCESS _ NIL, serverStream: IO.STREAM _ NIL] RETURNS [ok: BOOL] = { IF ok _ (cond = NIL OR cond = pc.changer) THEN { c.state.connectivity _ cy; c.state.onLine _ cy = connected; c.fromServer _ pc.serverStream _ serverStream; BROADCAST pc.change; c.client.type.NoteState[c]; }; }; Connect: PROC [c: Conversant, pc: PupConversation, serverName: ROPE, login: BOOL] = { self: UNSAFE PROCESS = Process.GetCurrent[]; toClient: IO.STREAM = c.client.toClient; { ENABLE { IO.Error => IF ec = StreamClosed AND stream = toClient THEN GOTO Destroyed; IO.EndOfStream => IF stream = toClient THEN GOTO Destroyed; UNWIND => [] _ SetConnectivity[c, pc, disconnected, self]; }; addr: PupDefs.PupAddress; serverStream: IO.STREAM _ NIL; toClient.PutF["\015\012Opening connection to %g ... ", IO.rope[serverName]]; addr _ PupStream.GetPupAddress[PupTypes.telnetSoc, serverName ! PupStream.PupNameTrouble => { Process.CheckForAbort[]; toClient.PutRope["PUP name error"]; FinishStringWithErrorMsg[toClient, e]; GOTO Fail; } ]; Process.CheckForAbort[]; serverStream _ PupStream.PupByteStreamCreate[addr, PupStream.SecondsToTocks[1] ! PupStream.StreamClosing => { Process.CheckForAbort[]; toClient.PutRope["Can't connect"]; FinishStringWithErrorMsg[toClient, text]; GOTO Fail; } ]; Process.CheckForAbort[]; toClient.PutF["open.\015\012"]; IF doSetLineWidth THEN { PupStream.SendMark[serverStream, setLineWidth]; serverStream.PutChar[0C]; serverStream.Flush[]; }; IF login THEN { name, password: Rope.ROPE; [name: name, password: password] _ UserCredentials.Get[]; serverStream.PutRope["Login "]; serverStream.PutRope[name]; IF Rope.Find[s1: name, s2: "."] = -1 THEN serverStream.PutRope[".PA"]; serverStream.PutRope[" "]; serverStream.PutRope[password]; serverStream.PutRope[" \n"]; serverStream.Flush[]; }; IF NOT SetConnectivity[c, pc, connected, self, serverStream] THEN serverStream.Close[TRUE !IO.Error => CONTINUE]; EXITS Fail => { [] _ SetConnectivity[c, pc, disconnected, self]; }; Destroyed => NoteDestruction[c, pc]; }}; FinishStringWithErrorMsg: PROC [toClient: IO.STREAM, errorMsg: ROPE, ending: ROPE _ ".\015\012"] = { IF errorMsg # NIL THEN toClient.PutF[":\t%s", IO.rope[errorMsg]]; toClient.PutRope[ending]; }; StartDisconnect: PROC [c: Conversant, verbosely: BOOL _ TRUE] = { ReallyStartDisconnect[c, IO.PutFR["Closing connection to %g", [rope[c.serverName]]], verbosely]; }; ReallyStartDisconnect: PROC [c: Conversant, msg: ROPE, verbosely: BOOL _ TRUE] = { pc: PupConversation = NARROW[c.conversantData]; Really: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; InternalReallyStartDisconnect[c, pc, msg, verbosely]; }; Really[pc]; }; InternalReallyStartDisconnect: INTERNAL PROC [c: Conversant, pc: PupConversation, msg: ROPE, verbosely: BOOL] = { IF pc.logStream # NIL THEN pc.logStream.Flush[!FS.Error => CONTINUE]; SELECT c.state.connectivity FROM disconnected => { Scream["Already disconnected ?%g?", [rope[msg]]]; c.client.type.NoteState[c]; }; connecting => { Scream["Can't disconnect because still connecting ?%g?", [rope[msg]]]; c.client.type.NoteState[c]; }; connected => { c.state.connectivity _ disconnecting; c.state.onLine _ FALSE; BROADCAST pc.change; c.client.type.NoteState[c ! UNWIND => { c.state.connectivity _ disconnected; BROADCAST pc.change; }; ]; TRUSTED {Process.Detach[ pc.changer _ FORK Disconnect[c, pc, msg, verbosely] ]}; }; disconnecting => { Scream["Already disconnecting from %g!", [rope[c.serverName]]]; c.client.type.NoteState[c]; }; ENDCASE => ERROR; }; Disconnect: PROC [c: Conversant, pc: PupConversation, msg: ROPE, verbosely: BOOL] = { self: UNSAFE PROCESS = Process.GetCurrent[]; { ENABLE { IO.Error => IF ec = StreamClosed AND stream = c.client.toClient THEN GOTO Destroyed; IO.EndOfStream => IF stream = c.client.toClient THEN GOTO Destroyed; }; ok: BOOL; serverStream: IO.STREAM = pc.serverStream; IF verbosely THEN c.client.toClient.PutF["\015\012%g ... ", [rope[msg]]]; serverStream.Close[TRUE !IO.Error => CONTINUE]; ok _ SetConnectivity[c, pc, disconnected, self, NIL]; IF ok AND verbosely THEN c.client.toClient.PutRope[" ... closed\015\012"]; EXITS Destroyed => NoteDestruction[c, pc]; }}; GiveUp: PROC [c: Conversant, verbosely: BOOL _ TRUE] = { pc: PupConversation = NARROW[c.conversantData]; Really: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; InternalGiveUp[c, pc, IF verbosely THEN "... gave up!\015\012" ELSE NIL]; }; Really[pc]; }; InternalGiveUp: INTERNAL PROC [c: Conversant, pc: PupConversation, msg: ROPE _ NIL] = { SELECT c.state.connectivity FROM connecting, disconnecting => { oldChanger: PROCESS _ pc.changer; pc.changer _ NIL; IF c.state.connectivity = connecting THEN TRUSTED {Process.Abort[oldChanger]}; c.state.connectivity _ disconnected; c.state.onLine _ FALSE; BROADCAST pc.change; c.client.type.NoteState[c]; IF msg # NIL THEN c.client.toClient.PutRope[msg ! IO.Error => IF ec = StreamClosed AND stream = c.client.toClient THEN {InternalNoteDestruction[c, pc]; CONTINUE}; ]; }; disconnected, connected => { Scream["Not connecting or disconnecting!"]; c.client.type.NoteState[c]; }; ENDCASE => ERROR; }; SetOnLine: PROC [c: Conversant, onLine: BOOL] = { pc: PupConversation = NARROW[c.conversantData]; Really: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; c.state.onLine _ onLine; BROADCAST pc.change; c.client.type.NoteState[c]; }; Really[pc]; }; flushPeriod: NAT _ 50; UserToServer: PROC [c: Conversant, pc: PupConversation] = { PreChar: ENTRY PROC [pc: PupConversation] RETURNS [do: {proceed, exit}] = { IF pc.phase >= destroying THEN RETURN [exit]; pc.gettingClientChar _ TRUE; do _ proceed; }; Stuff: ENTRY PROC [pc: PupConversation] RETURNS [do: {disconnect, proceed, exit}] = { ENABLE UNWIND => {}; pc.gettingClientChar _ FALSE; UNTIL c.state.connectivity=connected OR (NOT c.state.onLine) OR pc.phase >= destroying DO WAIT pc.change ENDLOOP; do _ proceed; SELECT TRUE FROM pc.phase >= destroying => do _ exit; NOT c.state.onLine => pc.toClient.PutChar[char]; ENDCASE => { ENABLE PupStream.StreamClosing => SELECT c.state.connectivity FROM connected => { do _ disconnect; whyDisconnect _ text; CONTINUE}; disconnected, connecting, disconnecting => { }; ENDCASE => ERROR; pc.serverStream.PutChar[char]; IF pc.charsSentSinceFlush >= flushPeriod OR pc.fromClient.CharsAvail[] = 0 THEN { pc.serverStream.Flush[]; pc.charsSentSinceFlush _ 0; } ELSE pc.charsSentSinceFlush _ pc.charsSentSinceFlush + 1; }; }; char: CHAR; whyDisconnect: ROPE _ NIL; DO ENABLE IO.EndOfStream, IO.Error, ABORTED => EXIT; SELECT PreChar[pc] FROM proceed => NULL; exit => EXIT; ENDCASE => ERROR; char _ pc.fromClient.GetChar[! IO.Error => IF ec = StreamClosed AND stream = pc.fromClient THEN GOTO Destroyed; ABORTED => RETRY ]; DO SELECT Stuff[pc] FROM exit => GOTO Exit; proceed => EXIT; disconnect => ReallyStartDisconnect[ c, IO.PutFR[ "Connection being closed by %s %g", [rope[c.serverName]], [rope[whyDisconnect]]], TRUE ]; ENDCASE => ERROR; ENDLOOP; ENDLOOP; EXITS Exit => NULL; Destroyed => NoteDestruction[c, pc]; }; ServerToUser: PROC [c: Conversant, pc: PupConversation] = { serverStream: IO.STREAM _ NIL; serverName: ROPE _ "??"; char: CHAR; Stuff: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; IF pc.logStream # NIL THEN pc.logStream.PutChar[char]; UNTIL (c.state.connectivity=connected AND c.state.onLine) OR pc.phase >= destroying DO WAIT pc.change ENDLOOP; IF pc.phase >= destroying THEN RETURN; pc.toClient.PutChar[char ! IO.Error => IF ec = StreamClosed AND stream = pc.toClient THEN {InternalNoteDestruction[c, pc]; CONTINUE}; ]; serverStream _ pc.serverStream; serverName _ c.serverName; }; Acquire: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; UNTIL c.state.connectivity=connected OR pc.phase >= destroying DO WAIT pc.change ENDLOOP; serverStream _ pc.serverStream; serverName _ c.serverName; }; NeedToClose: ENTRY PROC [pc: PupConversation] RETURNS [need: BOOL] = { need _ SELECT c.state.connectivity FROM connected => TRUE, disconnected, disconnecting, connecting => FALSE, ENDCASE => ERROR; }; StopServing: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; pc.serverToUsering _ FALSE; BROADCAST pc.change; }; Acquire[pc]; {ENABLE UNWIND => StopServing[pc]; DO whyDisconnect: ROPE _ NIL; { ENABLE { PupStream.StreamClosing => {whyDisconnect _ text; GOTO Disc}; IO.EndOfStream, IO.Error, ABORTED => EXIT; UNWIND => { IF pc.logStream # NIL THEN pc.logStream.Close[!FS.Error => CONTINUE]; }; }; IF pc.phase >= destroying THEN EXIT; IF serverStream.EndOf[] THEN { mySST: MarkByte; mySST _ PupStream.ConsumeMark[serverStream]; IF pc.phase >= destroying THEN EXIT; IF mySST = timingMark THEN PupStream.SendMark[serverStream, timingMarkReply]; }; IF pc.phase >= destroying THEN EXIT; char _ serverStream.GetChar[! PupStream.TimeOut, IO.EndOfStream => GOTO ReAcquire; ]; Stuff[pc]; EXITS Disc => { IF NeedToClose[pc] THEN ReallyStartDisconnect[c, IO.PutFR["Connection being closed by %s %g", [rope[serverName]], [rope[whyDisconnect]]], TRUE]; Acquire[pc]; }; ReAcquire => Acquire[pc]; }ENDLOOP; }; StopServing[pc]; IF pc.logStream # NIL THEN pc.logStream.Close[!FS.Error => CONTINUE]; }; Scream: PROC [fmt: ROPE, v1, v2: IO.Value _ [null[]]] = { MessageWindow.Append[ message: IO.PutFR[fmt, v1, v2], clearFirst: TRUE ]; MessageWindow.Blink[]; }; Protocols.RegProtocol[pupTelnet]; }. ŠPupTelnet.Mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last Edited by: Spreitzer, February 19, 1986 9:51:20 pm PST Locking Order: PupConversation, then display stuff (i.e. Viewers & Tioga). Destroy: PROC [c: Conversant] = { pc: PupConversation = NARROW[c.conversantData]; Start: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; SELECT pc.phase FROM running => pc.phase _ destroying; destroying, destroyed => ERROR; ENDCASE => ERROR; BROADCAST pc.change; WHILE pc.serverToUsering DO WAIT pc.change ENDLOOP; }; Really: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; pc.phase _ destroyed; BROADCAST pc.change; IF pc.gettingClientChar THEN TRUSTED {Process.Abort[pc.procs[uToS]]}; }; Start[pc]; SELECT c.state.connectivity FROM connected => StartDisconnect[c, FALSE]; connecting, disconnecting => GiveUp[c]; disconnected => NULL; ENDCASE => ERROR; Really[pc]; }; Κή– "cedar" style˜code™Kšœ Οmœ1™šžœ˜Kšžœžœ"žœ žœ˜?—K˜—K˜š œžœžœ)˜DKšžœžœ˜Kšœ˜Kšœ˜—K˜š œžœžœ)˜Ošžœ ž˜K˜!Kšœžœ˜Kšžœžœ˜—Kšž œ ˜šžœž˜ Kšœ2žœžœ˜>Kšœ3žœ˜8Kšœžœ˜Kšžœžœ˜—Kšœ˜Kšž œ ˜K˜—K˜š œžœ™!Kšœžœ™/š œžœžœ™+Kšžœžœ™šžœ ž™K™!Kšœžœ™Kšžœžœ™—Kšž œ ™Kšžœžœžœ žœ™3K™—š œžœžœ™,Kšžœžœ™Kšœ™Kšž œ ™Kšžœžœžœ!™EK™—Kšœ ™ šžœž™ Kšœ žœ™'Kšœ'™'Kšœžœ™Kšžœžœ™—K™ K™—K˜š  ˜ šœžœ˜K˜Kšœ žœ˜Kšœžœžœ˜—šœ˜Kšœžœ˜/š œžœžœ˜,Kšžœžœ˜šžœžœ˜ K˜0Kšžœ˜—šžœž˜ ˜K˜Kšœ"˜"Kšœžœ˜Kšž œ ˜šœ˜šžœ˜ Kšœ$˜$Kšž œ ˜Kšœ˜—Kšœ˜—šžœ˜Kšœ žœ"˜3Kšœ˜—K˜—˜)K˜9Kšœ˜K˜—Kšžœžœ˜—K˜—K˜ K˜——K˜š œžœžœ>žœžœžœžœžœžœžœžœ˜€šžœžœžœžœ˜0Kšœ˜Kšœ ˜ Kšœ.˜.Kšž œ ˜K˜K˜—K˜—K˜š œžœ2žœ žœ˜UKšœžœžœ˜,Kšœ žœžœ˜(K˜šžœž˜Kš žœ žœžœžœžœ ˜KKšžœžœžœžœ ˜;Kšžœ4˜:Kšœ˜—K˜Kšœžœžœžœ˜Kšœ7žœ˜L˜=˜˜K˜Kšœ#˜#Kšœ&˜&Kšžœ˜ —K˜—K˜—K˜˜N˜˜K˜Kšœ"˜"Kšœ)˜)Kšžœ˜ —K˜—K˜—K˜Kšœ˜šžœžœ˜Kšœ0˜0Kšœ˜Kšœ˜K˜—šžœžœ˜Kšœžœ˜J˜9Jšœ˜Jšœ˜Jšžœ#žœ˜FJšœ˜Jšœ˜Jšœ˜Jšœ˜J˜—Kš žœžœ7žœžœžœ žœ˜qšž˜šœ ˜ Kšœ0˜0Kšœ˜—Kšœ$˜$—K˜—K˜š  œžœ žœžœ žœ žœ˜dKšžœ žœžœžœ˜AKšœ˜K˜—K˜š œžœžœžœ˜AKšœžœE˜`K˜—K˜š  œžœžœ žœžœ˜RKšœžœ˜/š œžœžœ˜,Kšžœžœ˜Kšœ5˜5K˜—K˜ Kšœ˜—K˜š  œžœžœ+žœ žœ˜qKš žœžœžœžœ žœ˜Ešžœž˜ ˜K˜1K˜K˜—˜K˜FK˜K˜—˜Kšœ%˜%Kšœžœ˜Kšž œ ˜šœ˜šžœ˜ Kšœ$˜$Kšž œ ˜Kšœ˜—Kšœ˜—šžœ˜Kšœ žœ"˜3Kšœ˜—K˜—šœ˜Kšœ?˜?Kšœ˜K˜—Kšžœžœ˜—K˜—K˜š  œžœ+žœ žœ˜UKšœžœžœ˜,K˜šžœž˜Kš žœ žœžœžœžœ ˜TKšžœžœžœžœ ˜DKšœ˜—Kšœžœ˜ Kšœžœžœ˜*Kšžœ žœ8˜IKšœžœžœ žœ˜/Kšœ0žœ˜5Kšžœžœ žœ2˜Jšž˜Kšœ%˜%—K˜—K˜š œžœžœžœ˜8Kšœžœ˜/š œžœžœ˜,Kšžœžœ˜Kš œžœ žœžœžœ˜IK˜—K˜ K˜—K˜š  œžœžœ+žœžœ˜Wšžœž˜ ˜Kšœ žœ˜!Kšœ žœ˜Kšžœ#žœžœ˜NK˜$Kšœžœ˜Kšž œ ˜K˜šžœžœžœ ˜1Kš žœ žœžœžœ"žœ˜pKšœ˜—K˜—˜K˜+K˜K˜—Kšžœžœ˜—K˜—K˜š  œžœžœ˜1Kšœžœ˜/š œžœžœ˜,Kšžœžœ˜K˜Kšž œ ˜K˜K˜—K˜ K˜—K˜Kšœ žœ˜K˜š  œžœ)˜;š œžœžœžœ˜KKšžœžœžœ˜-Kšœžœ˜K˜ K˜—š œžœžœžœ&˜UKšžœžœ˜Kšœžœ˜Kšžœ žœžœžœžœžœ žœ˜qK˜ šžœžœž˜Kšœ$˜$Kšžœ-˜0šžœ˜ šžœžœž˜Bšœ˜Kšœ˜Kšœ˜Kšžœ˜ —šœ,˜,Kšœ˜—Kšžœžœ˜—Kšœ˜šžœ'žœ žœ˜QK˜Kšœ˜K˜—Kšžœ5˜9Kšœ˜——K˜—Kšœžœ˜ Kšœžœžœ˜šž˜Kš žœžœžœžœžœ˜1šžœ ž˜Kšœ žœ˜Kšœžœ˜ Kšžœžœ˜—šœ˜Kš žœ žœžœžœžœ ˜PKšžœž˜Kšœ˜—šž˜šžœ ž˜Kšœžœ˜Kšœ žœ˜šœ$˜$Kšœ˜šžœ˜ Kšœ#˜#Kšœ˜Kšœ˜—Kšž˜Kšœ˜—Kšžœžœ˜—Kšžœ˜—Kšžœ˜—šž˜Kšœžœ˜ Kšœ$˜$—K˜—K˜š  œžœ)˜;Kšœžœžœžœ˜Kšœ žœ˜Kšœžœ˜ š œžœžœ˜+Kšžœžœ˜Kšžœžœžœ˜6Kš žœ!žœžœžœžœ žœ˜nKšžœžœžœ˜&˜Kš žœ žœžœžœ"žœ˜jK˜—Kšœ˜Kšœ˜K˜—š œžœžœ˜-Kšžœžœ˜Kš žœ žœžœžœ žœ˜YKšœ˜Kšœ˜K˜—š   œžœžœžœžœ˜Fšœžœž˜'Kšœ žœ˜Kšœ+žœ˜1Kšžœžœ˜—K˜—š  œžœžœ˜1Kšžœžœ˜Kšœžœ˜Kšž œ ˜K˜—˜ Kšœžœžœ˜"šž˜Kšœžœžœ˜K˜šžœ˜Kšœ2žœ˜=Kšžœžœžœžœ˜*šžœ˜ Kš žœžœžœžœ žœ˜EKšœ˜—K˜—Kšžœžœžœ˜$šžœžœ˜K˜K˜,Kšžœžœžœ˜$Kšžœžœ3˜MK˜—Kšžœžœžœ˜$˜Kšœžœžœ ˜4Kšœ˜—K˜ šž˜šœ ˜ KšžœžœžœWžœ˜Kšœ ˜ K˜—Kšœ˜—Kšœžœ˜ —Kšœ˜—Kšœ˜Kš žœžœžœžœ žœ˜EK˜—K˜š œžœžœ žœ˜9˜Kšœ žœ˜Kšœ ž˜K˜—Kšœ˜K˜—K˜K˜!K˜K˜——…—24Gœ