DIRECTORY Atom USING[ GetPName ], BasicTime USING[ FromPupTime ], Buttons USING[ Button, ButtonProc, Create, Destroy, SetDisplayStyle ], Commander USING[ CommandProc, Register ], Containers USING[ ChildXBound, ChildYBound, Create ], GVBasics USING[ GVString, MakeKey, oldestTime, Password, RName, Timestamp ], GVNames USING[ CheckStamp, GetEntry, GetEntryInfo, GetEntryList, GetMembers, MemberInfo, NameType, Outcome, RListHandle, ReporterProc, RName, RSOperation, SetServer, SetServerInfo, Update], IO USING[ Put, PutChar, PutF, PutFR, PutRope, STREAM, Value ], Labels USING[ Create ], Rope USING[ Cat, Equal, Fetch, Find, Length, Match, ROPE, SkipOver, SkipTo, Substr ], Rules USING[ Create ], TypeScript USING[ Create ], UserCredentials USING[ Get ], ViewerClasses USING[ Viewer ], ViewerIO USING[ CreateViewerStreams ], ViewerOps USING[ ComputeColumn, DestroyViewer, MoveViewer, SetOpenHeight ], ViewerTools USING[ GetContents, MakeNewTextViewer, SetContents, SetSelection]; Maintain: CEDAR MONITOR LOCKS d USING d: MyData IMPORTS Atom, BasicTime, Buttons, Commander, Containers, GVBasics, GVNames, IO, Labels, Rope, Rules, TypeScript, UserCredentials, ViewerIO, ViewerOps, ViewerTools SHARES GVNames--Update-- = { ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Viewer: TYPE = ViewerClasses.Viewer; -- ******** Enquiry operations ******** -- Amount: TYPE = {members, finks, summary, details, names}; TypeGroupMatchesProc: Buttons.ButtonProc = { TypeGroup[clientData, names] }; TypeGroupMembersProc: Buttons.ButtonProc = { TypeGroup[clientData, members] }; TypeGroupSummaryProc: Buttons.ButtonProc = { TypeGroup[clientData, summary] }; TypeGroupDetailsProc: Buttons.ButtonProc = { TypeGroup[clientData, details] }; TypeGroupFinksProc: Buttons.ButtonProc = { TypeGroup[clientData, finks] }; TypeIndividualMatchesProc: Buttons.ButtonProc = { TypeIndividual[clientData, names] }; TypeIndividualSummaryProc: Buttons.ButtonProc = { TypeIndividual[clientData, summary] }; TypeIndividualDetailsProc: Buttons.ButtonProc = { TypeIndividual[clientData, details] }; TypeDeadMatchesProc: Buttons.ButtonProc = { TypeDead[clientData, names] }; TypeDeadDetailsProc: Buttons.ButtonProc = { TypeDead[clientData, details] }; TypeGroup: PROC [clientData: REF, which: Amount] = { d: MyData = NARROW[clientData]; name: ROPE = ViewerTools.GetContents[d.groupT]; IF ~CheckForm[clientData, name, "Group"] THEN RETURN; DoEnumerate[d, name, "Groups", which]; }; TypeIndividual: PROC [clientData: REF, which: Amount] = { d: MyData = NARROW[clientData]; name: ROPE = ViewerTools.GetContents[d.indivT]; IF ~CheckForm[clientData, name, "Individual"] THEN RETURN; DoEnumerate[d, name, "Individuals", which]; }; TypeDead: PROC [clientData: REF, which: Amount] = { d: MyData = NARROW[clientData]; name: ROPE = ViewerTools.GetContents[d.deadT]; IF ~CheckForm[clientData, name, "Dead"] THEN RETURN; DoEnumerate[d, name, "Dead", which]; }; CallForItems: PROC [d: MyData, names: ROPE, proc: PROC [ROPE], evenIfNull: BOOL _ FALSE] = { len: INT = Rope.Length[names]; pos: INT _ 0; IF Quote[d] THEN { IF len # 0 OR evenIfNull THEN proc[names]; RETURN; }; DO startPos: INT _ Rope.SkipOver[names, pos, ","]; startPos _ Rope.SkipOver[names, startPos, " "]; IF startPos = len THEN EXIT; pos _ MIN[Rope.SkipTo[names, startPos, ","], Rope.SkipTo[names, startPos, " "]]; evenIfNull _ FALSE; proc[Rope.Substr[names, startPos, pos-startPos]]; ENDLOOP; IF evenIfNull THEN proc[NIL]; }; DoEnumerate: ENTRY PROC [d: MyData, pattern, type: ROPE, which: Amount] = { ENABLE UNWIND => NULL; EnumerateOne: INTERNAL PROC [pattern: ROPE] = { IF Rope.Find[pattern, "*"] >= 0 THEN { lastDotPos: INT _ -1; DO thisDot: INT = Rope.Find[pattern, ".", lastDotPos+1]; IF thisDot < 0 THEN EXIT; lastDotPos _ thisDot; ENDLOOP; d.out.PutF["Enumerate%g[%g] ... ", [rope[type]], [rope[pattern]] ]; IF lastDotPos < 0 THEN { d.out.PutRope["the pattern must have an explicit registry\n"]; RETURN }; IF Rope.Find[pattern, "*", lastDotPos+1] > 0 THEN { d.out.PutRope["the registry must not contain \"*\"\n"]; RETURN }; { registry: ROPE = Rope.Substr[pattern, lastDotPos+1]; enumName: ROPE = Rope.Cat[type, ".", registry]; info: GVNames.MemberInfo = GVNames.GetMembers[enumName]; TRUSTED { WITH i: info SELECT FROM notFound => d.out.PutF["\"%g\" is not a registry\n", [rope[registry]] ]; noChange, individual => d.out.PutRope["internal error - consult a wizard\n"]; allDown => d.out.PutF["no R-Server for registry \"%g\" is available\n", [rope[registry]] ]; group => { first: BOOL _ TRUE; FOR l: GVNames.RListHandle _ i.members, l.rest UNTIL l = NIL DO IF Rope.Match[pattern, l.first, FALSE] THEN { IF which = names THEN { IF NOT first THEN d.out.PutRope[", "]; d.out.PutRope[l.first] } ELSE DoEnquiry[d, l.first, which]; first _ FALSE; }; ENDLOOP; IF first THEN d.out.PutRope["no matches\n"] ELSE IF which = names THEN d.out.PutChar['\n]; }; ENDCASE => ERROR; }; }; } ELSE { IF which = names THEN which _ summary; DoEnquiry[d, pattern, which] }; }; d.stop _ FALSE; CallForItems[d, pattern, EnumerateOne, FALSE]; IO.PutRope[d.out, "\n"]; }; DoEnquiry: INTERNAL PROC [d: MyData, name: ROPE, which: Amount[members..details]] = { Reporter: PROC [report: ROPE] = { d.out.PutF["%g ... ", [rope[report]] ]; }; info: REF GVNames.GetEntryInfo; rc: GVNames.NameType[group..allDown]; d.out.PutF["%g[%g] ... ", [rope[SELECT which FROM members => "TypeMembers", finks => "TypeFinks", summary => "TypeSummary", details => "TypeDetails", ENDCASE => ERROR]], [rope[name]] ]; [rc, info] _ GVNames.GetEntry[name, Reporter]; IF info.type = dead THEN d.out.PutRope["recently deleted name"]; IF info.type # notFound AND which = details THEN d.out.PutF["\nPrefix: %g, %g, %g", [rope[info.name]], [rope[SELECT info.type FROM individual => "individual", group => "group", dead => "dead", ENDCASE => ERROR]], Stamp[info.stamp] ]; TRUSTED { WITH i: info SELECT FROM individual => { IF which = details THEN d.out.PutF["\nPassword: %b %b %b %b, %g", [cardinal[i.password[0]]], [cardinal[i.password[1]]], [cardinal[i.password[2]]], [cardinal[i.password[3]]], Stamp[i.passwordStamp] ]; d.out.PutF["\nConnect: \"%g\"", [rope[i.connect]] ]; IF which = details THEN d.out.PutF[", %g", Stamp[i.connectStamp]]; IF which = details OR i.forward.current # NIL THEN TypeEntryList[d, i.forward, "Forwarding", IF which = details THEN details ELSE members]; TypeEntryList[d, i.sites, "Mailboxes", IF which = details THEN details ELSE members]; d.out.PutChar['\n]; }; group => { IF which # members AND which # finks THEN d.out.PutF["\nRemark: \"%g\"", [rope[i.remark]] ]; IF which = details THEN d.out.PutF[", %g", Stamp[i.remarkStamp]]; IF which = finks THEN TypeFinks[d, i.members.current] ELSE TypeEntryList[d, i.members, "Members", which]; IF which # members AND which # finks THEN { TypeEntryList[d, i.owners, "Owners", IF which = details THEN details ELSE members]; TypeEntryList[d, i.friends, "Friends", IF which = details THEN details ELSE members]; }; d.out.PutChar['\n]; }; dead => d.out.PutChar['\n]; ENDCASE => TypeRC[d, rc, ReadEntry, name, NIL]; }; IF d.stop THEN { IO.PutRope[d.out, "Stopping.\n"]; ERROR ABORTED; }; }; Stamp: PROC [stamp: GVBasics.Timestamp] RETURNS [IO.Value] = { time: ROPE = IO.PutFR[NIL, [time[BasicTime.FromPupTime[stamp.time]]] ]; RETURN[ [rope[IO.PutFR["[%b#%b,%g]", [cardinal[stamp.net]], [cardinal[stamp.host]], [rope[Rope.Substr[time, 0, Rope.Length[time]-4]]]]]]] }; TypeEntryList: PROC [d: MyData, list: GVNames.GetEntryList, text: ROPE, which: Amount] = { TypeEntrySublist[d, list.current, list.currentStamps, text, which]; IF which = details THEN TypeEntrySublist[d, list.deleted, list.deletedStamps, Rope.Cat["Del", text], which]; }; TypeEntrySublist: PROC [d: MyData, names: GVNames.RListHandle, stamps: LIST OF GVBasics.Timestamp, text: ROPE, which: Amount] = { count: INT _ 0; stampList: LIST OF GVBasics.Timestamp _ stamps; d.out.PutF["\n%g: ", [rope[text]] ]; FOR c: GVNames.RListHandle _ names, c.rest UNTIL c = NIL DO IF which # summary AND count # 0 THEN d.out.PutRope[", "]; count _ count+1; IF which # summary THEN d.out.PutRope[c.first]; IF which = details THEN d.out.Put[Stamp[stampList.first]]; stampList _ stampList.rest; ENDLOOP; IF which = summary THEN d.out.Put[[integer[count]]] ELSE { IF count = 0 THEN d.out.PutRope["none"] }; }; TypeFinks: PROC [d: MyData, names: GVNames.RListHandle] = { members, finks: INT _ 0; badGuys, last: GVNames.RListHandle _ NIL; FOR c: GVNames.RListHandle _ names, c.rest UNTIL c = NIL DO who: GVNames.RName _ c.first; rc: GVNames.NameType[group..allDown]; info: REF GVNames.GetEntryInfo; members _ members + 1; [rc, info] _ GVNames.GetEntry[who]; TRUSTED { WITH i: info SELECT FROM group => LOOP; individual => { temp: GVNames.RListHandle; forwarding: INT _ 0; IF i.forward.current = NIL THEN LOOP; temp _ CONS[who, NIL]; IF badGuys = NIL THEN badGuys _ temp ELSE last.rest _ temp; last _ temp; d.out.PutF["\n%G => is forwarded to ", [rope[who]] ]; FOR c: GVNames.RListHandle _ i.forward.current, c.rest UNTIL c = NIL DO IF forwarding # 0 THEN d.out.PutRope[", "]; forwarding _ forwarding+1; d.out.PutRope[c.first]; ENDLOOP; }; dead => d.out.PutF["\n%G => recently deleted", [rope[who]] ]; notFound => d.out.PutF["\n%G => invalid", [rope[who]] ]; ENDCASE => d.out.PutF["\n%G => ????", [rope[who]] ]; finks _ finks+1; }; ENDLOOP; d.out.PutF["\nFinks: "]; FOR c: GVNames.RListHandle _ badGuys, c.rest UNTIL c = NIL DO IF c # badGuys THEN d.out.PutRope[", "]; d.out.PutRope[c.first]; ENDLOOP; d.out.PutF["\n%G members, %G finks.", [integer[members]], [integer[finks]] ]; }; -- ******** Update operations ******** -- UpdateOp: TYPE = REF UpdateOpRec; UpdateOpRec: TYPE = RECORD[ d: MyData, op: GVNames.RSOperation ]; UpdateGroup: Buttons.ButtonProc = { rec: UpdateOp = NARROW[clientData]; name: ROPE = ViewerTools.GetContents[rec.d.groupT]; data: ROPE = IF rec.d.dataGT = NIL THEN NIL ELSE ViewerTools.GetContents[rec.d.dataGT]; IF ~CheckForm[rec.d, name, "Group"] THEN RETURN; SELECT rec.op FROM CreateGroup, DeleteGroup, AddSelf, DeleteSelf => NULL; ENDCASE => IF ~CheckForm[rec.d, data, "Argument"] THEN RETURN; DoUpdate[rec.d, group, rec.op, name, data]; }; UpdateIndividual: Buttons.ButtonProc = { rec: UpdateOp = NARROW[clientData]; name: ROPE = ViewerTools.GetContents[rec.d.indivT]; data: ROPE = ViewerTools.GetContents[rec.d.dataIT]; IF ~CheckForm[rec.d, name, "Individual"] THEN RETURN; SELECT rec.op FROM DeleteIndividual => NULL; ENDCASE => IF ~CheckForm[rec.d, data, "Argument"] THEN RETURN; DoUpdate[rec.d, individual, rec.op, name, data]; }; DoUpdate: ENTRY PROC [d: MyData, expected: GVNames.NameType, op: GVNames.RSOperation, target: GVBasics.RName, value: GVBasics.GVString] = { ENABLE UNWIND => NULL; EnumerateValue: PROC [rope1: ROPE] = { AddDeleteOne: PROC [rope2: ROPE] = { DoOneUpdate[d, expected, op, rope1, rope2]; }; CallForItems[d, value, AddDeleteOne, TRUE]; }; UpdateSelf: PROC [rope: ROPE] = { DoOneUpdate[d, expected, op, rope, NIL]; }; d.stop _ FALSE; SELECT op FROM AddSelf, DeleteSelf => CallForItems[d, target, UpdateSelf, TRUE]; CreateIndividual, DeleteIndividual, ChangeConnect, ChangePassword => DoOneUpdate[d, expected, op, target, value]; CreateGroup, DeleteGroup, ChangeRemark, NewName => DoOneUpdate[d, expected, op, target, value]; AddMember, AddMailBox, AddForward, AddOwner, AddFriend, DeleteMember, DeleteMailBox, DeleteForward, DeleteOwner, DeleteFriend => CallForItems[d, target, EnumerateValue, TRUE]; ENDCASE => ERROR; IO.PutRope[d.out, "\n"]; }; DoOneUpdate: PROC [d: MyData, expected: GVNames.NameType, op: GVNames.RSOperation, target: GVBasics.RName, value: GVBasics.GVString] = { outcome: GVNames.Outcome; Reporter: SAFE PROC [report: ROPE] = { d.out.PutF["%g ... ", [rope[report]] ]; }; user, password: ROPE; [name: user, password: password] _ UserCredentials.Get[]; -- Log command -- IO.PutF[d.out, "%g%g[%g,%g] ... ", [rope[SELECT op FROM CreateIndividual, CreateGroup => "Create", DeleteIndividual, DeleteGroup => "Delete", AddMember, AddOwner, AddFriend, AddMailBox, AddForward, AddSelf => "Add", DeleteMember, DeleteOwner, DeleteFriend, DeleteMailBox, DeleteForward, DeleteSelf => "Remove", ChangeRemark, ChangePassword, ChangeConnect => "Set", ENDCASE => ""]], [rope[SELECT op FROM CreateIndividual, DeleteIndividual => "Individual", CreateGroup, DeleteGroup => "Group", NewName => "NewName", AddMember, DeleteMember => "Member", AddOwner, DeleteOwner => "Owner", AddFriend, DeleteFriend => "Friend", AddMailBox, DeleteMailBox => "Mailbox", AddForward, DeleteForward => "Forwarding", AddSelf, DeleteSelf => "Self", ChangeRemark => "Remark", ChangePassword => "Password", ChangeConnect => "Connect", ENDCASE => "UnknownOp"]], [rope[target]], [rope[SELECT op FROM CreateIndividual, CreateGroup, ChangePassword, DeleteIndividual, DeleteGroup, AddSelf, DeleteSelf => "", ENDCASE => value]] ]; -- validate argument -- IF (SELECT op FROM AddMember, AddOwner, AddFriend, AddForward => CheckName[d, value, Reporter], AddMailBox => CheckInbox[d, value, Reporter], ChangePassword => CheckPwd[d, user, target, value], ENDCASE => TRUE) AND NotGVMS[d, target] THEN { newPwd: GVBasics.Password; SELECT op FROM CreateIndividual, ChangePassword => newPwd _ GVBasics.MakeKey[value]; ENDCASE => NULL; SELECT op FROM -- patch because of inadequate return codes from Update -- CreateIndividual, CreateGroup, NewName => { outcome _ GVNames.CheckStamp[target, GVBasics.oldestTime--, Reporter--]; IF outcome # notFound THEN GOTO Bad; }; DeleteIndividual, DeleteGroup => { outcome _ GVNames.CheckStamp[target, GVBasics.oldestTime--, Reporter--]; IF outcome = notFound THEN GOTO Bad; expected _ notFound; }; ENDCASE => NULL; outcome _ GVNames.Update[user: user, password: GVBasics.MakeKey[password], op: op, target: target, value: value, newPwd: newPwd, reporter: Reporter]; IF outcome # expected THEN GOTO Bad; IO.PutRope[d.out, "ok\n"] EXITS Bad => TypeRC[d, outcome, op, target, value]; }; IF d.stop THEN { IO.PutRope[d.out, "Stopping.\n"]; ERROR ABORTED; }; }; verifyOn: ATOM = $on; Verify: PROC [d: MyData] RETURNS [BOOL] = { IF d.verify = NIL THEN RETURN[TRUE]; IF d.verify^ = verifyOn THEN RETURN[TRUE]; RETURN[FALSE]; }; quoteOff: ATOM = $off; Quote: PROC [d: MyData] RETURNS [BOOL] = { IF d.quote = NIL THEN RETURN[FALSE]; IF d.quote^ = quoteOff THEN RETURN[FALSE]; RETURN[TRUE]; }; CheckName: PROC [d: MyData, name: GVBasics.RName, reporter: GVNames.ReporterProc] RETURNS [BOOL] = { length: INT = Rope.Length[name]; IF length = 0 THEN GOTO Bad; IF length = 1 AND Rope.Fetch[name, 0] = '* THEN RETURN[TRUE]; IF length > 1 AND Rope.Fetch[name, 0] = '* AND Rope.Fetch[name, 1] = '. THEN RETURN[TRUE]; IF GVNames.CheckStamp[name, GVBasics.oldestTime--, reporter--] = notFound THEN { FOR i: INT DECREASING IN [0..length) DO IF Rope.Fetch[name, i] = '. THEN { f: GVBasics.RName = Rope.Cat[Rope.Substr[name, i+1, length-i], ".foreign"]; IF ~CheckIndividual[f] THEN GOTO Bad; IO.PutRope[d.out, "(foreign) ... "]; RETURN[TRUE]; }; REPEAT FINISHED => GOTO Bad ENDLOOP }; RETURN[TRUE] EXITS Bad => { IF d.verify # NIL AND d.verify^ # verifyOn THEN { IO.PutRope[d.out, "(invalid) ... "]; RETURN[TRUE] }; IO.PutF[d.out, "\"%g\" is not a valid name\n", [rope[name]] ]; RETURN[FALSE] } }; CheckIndividual: PROC [name: GVBasics.RName] RETURNS [BOOL] = { IF GVNames.CheckStamp[name, GVBasics.oldestTime] # individual THEN RETURN[FALSE]; RETURN[TRUE]; }; CheckInbox: PROC [d: MyData, name: GVBasics.RName, reporter: GVNames.ReporterProc] RETURNS [BOOL] = { IF ~EndsWith[name, ".ms"] OR ~CheckIndividual[name] THEN { IF ~Verify[d] THEN { IO.PutRope[d.out, "(invalid) ... "]; RETURN[TRUE] } ELSE { IO.PutF[d.out, "\"%g\" is not a mail server\n", [rope[name]] ]; RETURN[FALSE]; }; }; RETURN[TRUE]; }; CheckPwd: PROC [d: MyData, user, name, pwd: GVBasics.RName] RETURNS [BOOL] = { IF Rope.Length[pwd] # 0 THEN RETURN[TRUE]; IF ~Rope.Equal[user, name, FALSE] THEN RETURN[TRUE]; IO.PutRope[d.out, " You don't want an empty password.\n"]; RETURN[FALSE]; }; gvmsAllowed: ATOM = $yes; NotGVMS: PROC [d: MyData, a: ROPE] RETURNS [BOOL] = { IF d.gvms # NIL AND d.gvms^ = gvmsAllowed THEN RETURN[TRUE]; IF ~EndsWith[a, ".gv"] AND ~EndsWith[a, ".ms"] THEN RETURN[TRUE]; IO.PutRope[d.out, " Use wizard's GV/MS switch first.\n"]; RETURN[FALSE]; }; EndsWith: PROC [a, b: ROPE] RETURNS [BOOL] = { bLength: INT = Rope.Length[b]; aLength: INT = Rope.Length[a]; IF aLength < bLength THEN RETURN[FALSE]; RETURN[Rope.Equal[Rope.Substr[a, aLength - bLength, bLength], b, FALSE]]; }; -- ******** SetServer operation ******** -- SetServerProc: Buttons.ButtonProc = { d: MyData = NARROW[clientData]; name: ROPE = ViewerTools.GetContents[d.serverT]; IF ~CheckForm[clientData, name, "Host"] THEN RETURN; DoSetServer[d, name]; }; DoSetServer: ENTRY PROC [d: MyData, name: ROPE] = { ENABLE UNWIND => NULL; info: GVNames.SetServerInfo; d.out.PutF["\nSetServer[%g] ... ", [rope[name]] ]; info _ GVNames.SetServer[name]; d.out.PutRope[SELECT info FROM ok => "ok\n", allDown => "can't look up name - allDown\n", noRoute => "no route to that host at present\n", badName => "bad server name\n", ENDCASE => ERROR]; }; -- ******** Miscellaneous sub-routines for the operations ******** -- CheckForm: SAFE PROC [clientData: REF, value, label: ROPE] RETURNS[ BOOL ] = { d: MyData = NARROW[clientData]; IF Rope.Length[value] > 0 AND Rope.Fetch[value, 0] = ' THEN { IO.PutF[d.out, "\nPlease fill in the \"%g\" field\n", [rope[label]] ]; RETURN[FALSE] } ELSE RETURN[TRUE] }; TypeRC: PROC [d: MyData, info: GVNames.Outcome, op: GVNames.RSOperation, name: ROPE, value: ROPE] = { create: BOOL = SELECT op FROM CreateIndividual, CreateGroup => TRUE, ENDCASE => FALSE; SELECT info FROM group, individual => IO.PutF[d.out, "\"%g\" is %ga %g", [rope[name]], [rope[IF create THEN "already " ELSE NIL]], [rope[IF info = group THEN "group" ELSE "individual"]] ]; notFound => IO.PutF[d.out, "\"%g\" %g", [rope[name]], [rope[SELECT TRUE FROM Rope.Find[name, "."] < 0 => "needs an explicit registry", create => "has an invalid registry or is recently deleted" ENDCASE => "doesn't exist"]] ]; protocolError => IO.PutF[d.out, "protocol error - consult expert." ]; wrongServer => IO.PutF[d.out, "wrong server - consult expert." ]; allDown => IO.PutF[d.out, "couldn't contact needed server. Try later." ]; badPwd => IO.PutF[d.out, "your password is now incorrect. Please login again." ]; outOfDate => IO.PutF[d.out, "there's newer information in the database - consult expert." ]; notAllowed => IO.PutF[d.out, "you're not allowed to do that - ask an administrator to help you." ]; noChange => { IsFooOfBaz: PROC [s: ROPE] = { IO.PutF[d.out, "\"%g\" is %g of \"%g\"", [rope[value]], [rope[s]], [rope[name]] ] }; SELECT op FROM CreateIndividual, CreateGroup => IO.PutF[d.out, "\"%g\" already exists", [rope[name]] ]; AddMember => IsFooOfBaz["already a member"]; AddMailBox => IsFooOfBaz["already a mailbox-site"]; AddForward => IsFooOfBaz["already a forwardee"]; AddOwner => IsFooOfBaz["already an owner"]; AddFriend => IsFooOfBaz["already a friend"]; DeleteMember => IsFooOfBaz["not a member"]; DeleteMailBox => IsFooOfBaz["not a mailbox-site"]; DeleteForward => IsFooOfBaz["not a forwardee"]; DeleteOwner => IsFooOfBaz["not an owner"]; DeleteFriend => IsFooOfBaz["not a friend"]; AddSelf => IO.PutF[d.out, "you're already a member of \"%g\"", [rope[name]]]; DeleteSelf => IO.PutF[d.out, "you're not a member of \"%g\"", [rope[name]]]; ENDCASE => IO.PutF[d.out, "no-change: consult LaurelSupport.pa."]; }; ENDCASE => IO.PutF[d.out, "unknown return code!"]; IO.PutRope[d.out, "\n"]; }; -- ******** Viewer management ******** -- MyData: TYPE = REF MyDataObject; MyDataObject: TYPE = MONITORED RECORD[ in: STREAM, out: STREAM, level: { normal, owner, admin, wizard } _ normal, verify: REF ATOM _ NIL, quote: REF ATOM _ NIL, gvms: REF ATOM _ NIL, topChild, kids, groupT, dataGT, indivT, dataIT, deadT, serverT, script: Viewer _ NIL, maxW: INTEGER, buttH: INTEGER, stop: BOOL _ FALSE ]; Create: Commander.CommandProc = { d: MyData = NEW[MyDataObject]; v: Viewer = Containers.Create[ info: [name: "Maintain", column: right, scrollable: FALSE, iconic: TRUE]]; { -- kludge to find max button size! -- temp: Buttons.Button = Buttons.Create[ info: [name: "Forwarding", parent: v, border: FALSE, wx: 0, wy: 0], proc: NIL, clientData: d, fork: FALSE, paint: FALSE]; d.maxW _ temp.ww; d.buttH _ temp.wh; Buttons.Destroy[temp]; }; d.topChild _ CreateSelector[name: "Level:", values: LIST[$Normal, $Owner, $Administrator, $Wizard], change: ChangeLevel, clientData: d, viewer: v, x: 2, y: 1].child; d.topChild _ Buttons.Create[ info: [name: "Stop", parent: v, border: TRUE, wy: d.topChild.wy, wx: d.topChild.wx + d.topChild.ww + 20], proc: Stop, clientData: d, fork: TRUE, paint: TRUE]; d.script _ TypeScript.Create[ info: [parent: v, wh: v.ch - (d.topChild.wy + d.topChild.wh + 2), ww: v.cw, border: FALSE, wy: d.topChild.wy + d.topChild.wh + 2, wx: 0] ]; Containers.ChildXBound[v, d.script]; Containers.ChildYBound[v, d.script]; [in: d.in, out: d.out] _ ViewerIO.CreateViewerStreams[ name: "Maintain", viewer: d.script, backingFile: "///Temp/Maintain.log", editedStream: FALSE]; CreateButtons[d, v]; }; ChangeLevel: PROC [parent: Viewer, clientData: REF, value: ATOM] = { d: MyData = NARROW[clientData]; SELECT value FROM $Normal => d.level _ normal; $Owner => d.level _ owner; $Administrator => d.level _ admin; $Wizard => d.level _ wizard; ENDCASE => ERROR; CreateButtons[d, parent] }; Stop: Buttons.ButtonProc = { d: MyData = NARROW[clientData]; d.stop _ TRUE; }; CreateButtons: ENTRY PROC [d: MyData, parent: Viewer] = { ENABLE UNWIND => NULL; child: Viewer _ NIL; EnquiryButton: PROC [name: ROPE, proc: Buttons.ButtonProc] = { child _ Buttons.Create[ info: [name: name, parent: kids, border: TRUE, wy: child.wy, wx: child.wx + d.maxW - 1, ww: d.maxW], proc: proc, clientData: d, fork: TRUE, paint: FALSE]; }; updateProc: Buttons.ButtonProc _ UpdateGroup; UpdateButton: PROC [name: ROPE, op: GVNames.RSOperation, guarded: BOOL _ FALSE] = { child _ Buttons.Create[ info: [name: name, parent: kids, border: TRUE, wy: child.wy, wx: child.wx + d.maxW - 1, ww: d.maxW], proc: updateProc, clientData: NEW[UpdateOpRec _ [d,op]], fork: TRUE, guarded: guarded, paint: FALSE]; }; LabelText: PROC [name, data: ROPE, prev: Viewer, newline: BOOL _ TRUE] RETURNS[Viewer] = { x: INTEGER = IF newline THEN 2 ELSE child.wx + d.maxW - 1; y: INTEGER = IF newline THEN child.wy + child.wh + 1 ELSE child.wy; child _ ViewerTools.MakeNewTextViewer[ info: [parent: kids, wh: d.buttH, ww: 999, scrollable: TRUE, data: IF prev = NIL THEN data ELSE ViewerTools.GetContents[prev], border: FALSE, wx: x + d.maxW + 2, wy: y], paint: FALSE ]; Containers.ChildXBound[kids, child]; [] _ Buttons.Create[ info: [name: name, parent: kids, wh: d.buttH, border: FALSE, wx: x, wy: y], proc: TextLabelProc, clientData: child, fork: FALSE, paint: FALSE]; RETURN[child] }; Label: PROC [name: ROPE] = { child _ Labels.Create[ info: [name: name, parent: kids, border: FALSE, wy: child.wy + child.wh + (IF child.class.flavor = $Button THEN -1 ELSE 2), wx: 2], paint: FALSE ]; }; Rule: PROC = { child _ Rules.Create[ info: [parent: kids, border: FALSE, wy: IF child = NIL THEN 0 ELSE child.wy + child.wh + 2, wx: 0, ww: kids.ww, wh: 1], paint: FALSE ]; Containers.ChildXBound[kids, child]; }; kids: Viewer = Containers.Create[ info: [parent: parent, border: FALSE, scrollable: FALSE, wx: 0, wy: -9999, ww: 9999, wh: 0] ]; Containers.ChildXBound[parent, kids]; Rule[]; d.groupT _ LabelText[ name: "Group:", data: "group name or pattern", prev: d.groupT ]; IF d.level IN [owner..wizard] THEN d.dataGT _ LabelText[ name: "Argument:", data: "remark string, or member/owner/friend name", prev: d.dataGT ] ELSE d.dataGT _ NIL; Label["Type: "]; EnquiryButton[name: "Matches", proc: TypeGroupMatchesProc]; EnquiryButton[name: "Members", proc: TypeGroupMembersProc]; EnquiryButton[name: "Summary", proc: TypeGroupSummaryProc]; IF d.level IN [admin..wizard] THEN EnquiryButton[name: "Details", proc: TypeGroupDetailsProc]; EnquiryButton[name: "Finks", proc: TypeGroupFinksProc]; IF d.level IN [owner..wizard] THEN { Label["Set:"]; UpdateButton[name: "Remark", op: ChangeRemark]; }; Label["Add:"]; UpdateButton[name: "Self", op: AddSelf]; IF d.level IN [owner..wizard] THEN { UpdateButton[name: "Member", op: AddMember]; UpdateButton[name: "Owner", op: AddOwner]; UpdateButton[name: "Friend", op: AddFriend]; }; Label["Remove:"]; UpdateButton[name: "Self", op: DeleteSelf]; IF d.level IN [owner..wizard] THEN { UpdateButton[name: "Member", op: DeleteMember]; UpdateButton[name: "Owner", op: DeleteOwner]; UpdateButton[name: "Friend", op: DeleteFriend]; }; IF d.level IN [admin..wizard] THEN { Label[""]; UpdateButton[name: "Create", op: CreateGroup, guarded: FALSE]; UpdateButton[name: "Delete", op: DeleteGroup, guarded: TRUE]; UpdateButton[name: "NewName", op: NewName, guarded: FALSE]; }; Rule[]; updateProc _ UpdateIndividual; d.indivT _ LabelText[ name: "Individual:", data: UserCredentials.Get[].name, prev: d.indivT ]; d.dataIT _ LabelText[ name: "Argument:", data: "password, connect-site, etc.", prev: d.dataIT ]; Label["Type:"]; EnquiryButton[name: "Matches", proc: TypeIndividualMatchesProc]; EnquiryButton[name: "Summary", proc: TypeIndividualSummaryProc]; IF d.level IN [admin..wizard] THEN EnquiryButton[name: "Details", proc: TypeIndividualDetailsProc]; Label["Set:"]; UpdateButton[name: "Password", op: ChangePassword]; UpdateButton[name: "Connect", op: ChangeConnect]; IF d.level IN [admin..wizard] THEN { Label["Add: "]; UpdateButton[name: "Mailbox", op: AddMailBox, guarded: FALSE]; UpdateButton[name: "Forwarding", op: AddForward, guarded: FALSE]; Label["Remove: "]; UpdateButton[name: "Mailbox", op: DeleteMailBox, guarded: FALSE]; UpdateButton[name: "Forwarding", op: DeleteForward, guarded: FALSE]; Label[""]; UpdateButton[name: "Create", op: CreateIndividual, guarded: FALSE]; UpdateButton[name: "Delete", op: DeleteIndividual, guarded: TRUE]; }; Rule[]; IF d.level = wizard THEN { d.deadT _ LabelText[ name: "Dead:", data: "dead name or pattern", prev: d.deadT]; Label["Type:"]; EnquiryButton[name: "Matches", proc: TypeDeadMatchesProc]; EnquiryButton[name: "Details", proc: TypeDeadDetailsProc]; Rule[]; [child, d.verify] _ CreateSelector[ name: "Verify:", values: LIST[verifyOn, $off], init: d.verify, viewer: kids, x: 2, y: child.wy + child.wh + 2]; [child, d.quote] _ CreateSelector[ name: "Quote:", values: LIST[quoteOff, $on], init: d.quote, viewer: kids, x: child.wx + child.ww + 10, y: child.wy]; [child, d.gvms] _ CreateSelector[ name: "GV/MS updates:", values: LIST[$no, gvmsAllowed], init: d.gvms, viewer: kids, x: child.wx + child.ww + 10, y: child.wy]; child _ Buttons.Create[ info: [name: "SetServer", parent: kids, wh: d.buttH, border: TRUE, wx: 2, wy: child.wy + child.wh + 2], proc: SetServerProc, clientData: d, fork: TRUE, paint: FALSE]; d.serverT _ LabelText[ name: " Host:", data: "RName, NLS name, or Pup address", prev: d.serverT, newline: FALSE ]; Rule[]; } ELSE { d.serverT _ d.deadT _ NIL }; { kidsY: INTEGER = d.topChild.wy + d.topChild.wh + 2; kidsH: INTEGER = child.wy + child.wh + 2; IF d.kids # NIL THEN ViewerOps.DestroyViewer[d.kids, FALSE]; d.kids _ kids; ViewerOps.MoveViewer[viewer: d.script, x: 0, y: kidsY + kidsH, w: d.script.ww, h: parent.ch - (kids.wy + kidsH), paint: FALSE]; ViewerOps.SetOpenHeight[parent, kidsY + kidsH + 8 * d.buttH]; IF NOT parent.iconic THEN ViewerOps.ComputeColumn[parent.column]; ViewerOps.MoveViewer[viewer: kids, x: kids.wx, y: kidsY, w: kids.ww, h: kidsH]; }; }; TextLabelProc: Buttons.ButtonProc = { text: Viewer = NARROW[clientData]; SELECT mouseButton FROM red => ViewerTools.SetSelection[text, NIL]; blue => { ViewerTools.SetContents[text, NIL]; ViewerTools.SetSelection[text, NIL] }; yellow => NULL; ENDCASE => ERROR; }; 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 ]; CreateSelector: PROC [name: ROPE, values: LIST OF ATOM, init: REF ATOM _ NIL, change: PROC [parent: Viewer, clientData: REF, value: ATOM] _ NIL, clientData: REF _ NIL, viewer: Viewer, x, y: INTEGER] RETURNS [child: Viewer, value: REF ATOM] = { 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; value _ selector.value; child _ Labels.Create[info: [name: name, parent: viewer, 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: viewer, border: TRUE, wy: child.wy, wx: child.wx + child.ww + 2], proc: SelectorProc, 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; }; SelectorProc: 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; }; -- ******** Main program ******** -- Commander.Register[key: "Maintain", proc: Create, doc: "Performs enquiries and updates to the Grapevine database"]; }. €Maintain.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Andrew Birrell August 25, 1983 11:10 am Russ Atkinson, August 29, 1984 7:18:41 pm PDT Hal Murray May 23, 1985 6:12:46 pm PDT Bob Hagmann January 29, 1986 8:22:18 am PST Hal Murray, March 20, 1986 0:45:38 am PST 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 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 [cmd: Handle] 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 Bob Hagmann January 29, 1986 8:21:42 am PST Moved the "log" file from the current directory to ///Temp/ changes to: Create Κ ¦˜šœ ™ Jšœ Οmœ1™˜LKšœžœ°˜½Jšžœžœ&žœ ˜>Jšœžœ ˜Jšœžœ*žœ˜UJšœžœ ˜Jšœ žœ ˜Jšœžœ˜Jšœžœ ˜Jšœ žœ˜&Jšœ žœ<˜KJšœ žœ=˜N—J˜š œ žœžœžœžœ ˜/KšžœEžœT˜’JšžœΟc œ˜—J˜Jšœ˜˜Jšžœžœžœ˜Jšžœžœžœžœ˜Jšœžœ˜$—J˜JšŸ*˜*˜Jšœžœ-˜9J˜˜,JšœL™LJ˜—J˜˜,JšœL™LJ˜!—J˜˜,JšœL™LJ˜!—J˜˜,JšœL™LJ˜!J˜—˜*JšœL™LJšœ˜—J˜˜1JšœL™LJ˜$—J˜˜1JšœL™LJ˜&—J˜˜1JšœL™LJ˜&—J˜˜+JšœL™LJ˜—J˜˜+JšœL™LJ˜ —J˜šΟn œžœ žœ˜4Jšœ žœ ˜Jšœžœ%˜/Jšžœ'žœžœ˜5Jšœ&˜&Jšœ˜—J˜š œžœ žœ˜9Jšœ žœ ˜Jšœžœ%˜/Jšžœ+žœžœ˜:Jšœ+˜+Jšœ˜—J˜š œžœ žœ˜3Jšœ žœ ˜Jšœžœ$˜.Jšžœ&žœžœ˜4Jšœ$˜$Jšœ˜J˜—š  œžœžœžœžœžœžœ˜\Jšœžœ˜Jšœžœ˜ Jš žœ žœžœ žœ žœžœ˜Hšž˜Jšœ žœ"˜/Jšœ/˜/Jšžœžœžœ˜JšœžœG˜PJšœ žœ˜Jšœ1˜1Jšžœ˜—Jšžœ žœžœ˜J˜—J˜š  œžœžœžœ˜KJšžœžœžœ˜š  œžœžœ žœ˜/šžœžœ˜&Jšœ žœ˜šž˜Jšœ žœ)˜5Jšžœ žœžœ˜J˜Jšžœ˜—J˜Cšžœžœ˜Jšœ?žœ˜H—šžœ*žœ˜3Jšœ8žœ˜A—šœ˜Jšœ žœ&˜4Jšœ žœ!˜/J˜8Jšžœ˜ šžœ žœž˜˜ J˜<—˜J˜5—˜ J˜P—šœ ˜ Jšœžœžœ˜šžœ,žœž˜?šžœžœžœ˜-šžœžœ˜Jšžœžœžœ.˜?—Jšžœ˜"Jšœžœ˜—Jšžœ˜—Jšžœžœ˜+Jšžœžœžœ˜.Jšœ˜—Jšžœžœ˜—Jšœ˜—Jšœ˜Jšžœžœžœ1˜M—J˜—Jšœ žœ˜Jšœ'žœ˜.Jšžœ˜Jšœ˜—J˜š  œžœžœžœ&˜Uš œžœ žœ˜!Jšœ'˜'Jšœ˜—Jšœžœ˜J˜%˜šœžœž˜J˜J˜J˜J˜Jšžœžœ˜J˜——J˜.Jšžœžœ(˜@šžœžœž˜0šœ"˜"J˜šœžœ ž˜J˜J˜J˜Jšžœžœ˜—J˜——Jšžœ˜ šžœ žœž˜šœ˜Jšžœ˜šžœ*˜.J˜J˜J˜J˜J˜—J˜4Jšžœžœ+˜BJšžœžœž˜-šžœ*˜.Jšžœžœ žœ ˜.—Jšœ'žœžœ žœ ˜UJ˜Jšœ˜—šœ ˜ Jšžœžœžœ3˜\Jšžœžœ*˜AJšžœžœ ˜5Jšžœ/˜3šžœžœžœ˜+Jšœ%žœžœ žœ ˜SJšœ'žœžœ žœ ˜UJšœ˜—J˜Jšœ˜—J˜Jšžœ#žœ˜2—Jš žœžœžœžœžœ˜DJšœ˜—J˜š œžœžœžœ ˜>Jšœžœžœžœ.˜Gšžœžœ˜$J˜J˜J˜5—Jšœ˜—J˜š  œžœ.žœ˜ZJ˜Cšžœž˜JšœT˜T—Jšœ˜—J˜š  œžœ1žœžœžœ˜Jšœžœ˜Jšœ žœžœ˜/J˜$šžœ(žœž˜;Jšžœžœ žœ˜:J˜Jšžœžœ˜/Jšžœžœ#˜:J˜Jšžœ˜—Jšžœžœ˜3Jšžœžœ žœ˜2Jšœ˜J˜—š  œžœ,˜;Jšœžœ˜Jšœ%žœ˜)šžœ(žœž˜;Jšœ˜J˜%Jšœžœ˜J˜Jšœ#˜#Jšž ˜ šžœ žœž˜Jšœžœ˜šœ˜Jšœ˜Jšœ žœ˜Jšžœžœžœžœ˜%Jšœžœžœ˜Jšžœ žœžœžœ˜;J˜ Jšœ5˜5šžœ4žœžœž˜GJšžœžœ˜+Jšœ˜Jšœ˜Jšžœ˜ ——Jšœ=˜=Jšœ8˜8Jšžœ-˜4—Jšœ˜Jšžœ˜—J˜šžœ*žœž˜=Jšžœ žœ˜(Jšœ˜Jšžœ˜—JšœM˜MJšœ˜—J˜J˜—JšŸ)˜)˜Jšœ žœžœ ˜!J˜šœ žœžœ˜J˜ J˜—J˜˜#JšœL™LJšœžœ ˜#Jšœžœ)˜3Jš œžœžœžœžœžœžœ'˜WJšžœ"žœžœ˜0šžœž˜Jšœ1žœ˜6Jšžœžœ%žœžœ˜>—Jšœ+˜+Jšœ˜—J˜˜(JšœL™LJšœžœ ˜#Jšœžœ)˜3Jšœžœ)˜3Jšžœ'žœžœ˜5šžœž˜Jšœžœ˜Jšžœžœ%žœžœ˜>—Jšœ0˜0Jšœ˜—J˜š œžœžœw˜‹Jšžœžœžœ˜š œžœ žœ˜&š  œžœ žœ˜$Jšœ+˜+Jšœ˜—Jšœ%žœ˜+Jšœ˜—š  œžœžœ˜!Jšœ#žœ˜(Jšœ˜—Jšœ žœ˜šžœž˜Jšœ;žœ˜AšœD˜DJšœ,˜,—šœ2˜2Jšœ,˜,—Jš œ žœ žœ žœžœ ˜7š œ žœ žœ žœ žœ˜HJšœ(žœ˜.—Jšžœžœ˜—Jšžœ˜Jšœ˜—J˜š  œžœw˜ˆJ˜š œž œ žœ˜&Jšœ'˜'Jšœ˜—Jšœžœ˜J˜9JšŸ˜šžœ ˜"šœžœž˜J˜*J˜*J˜J˜)J˜(J˜5J˜5Jšžœ ˜—šœžœž˜J˜3J˜$Jšœ˜J˜$J˜!J˜$J˜'J˜*J˜J˜J˜J˜Jšžœ˜—J˜šœžœž˜J˜.J˜J˜Jšžœ˜——JšŸ˜šžœžœž˜J˜LJ˜-J˜3Jšžœžœ˜Jšžœžœ˜J˜šžœž˜J˜E—Jšžœžœ˜šžœžœŸ:˜Išœ+˜+J˜HJšžœžœžœ˜$Jšœ˜—šœ"˜"J˜HJšžœžœžœ˜$J˜Jšœ˜——Jšžœžœ˜˜JJšœJ˜J—Jšžœžœžœ˜$Jšžœ˜Jšžœ.˜3Jšœ˜—Jš žœžœžœžœžœ˜DJšœ˜J˜—Jšœ žœ˜J˜š  œžœ žœžœž˜+Jš žœ žœžœžœžœ˜$Jšžœžœžœžœ˜*Jšžœžœ˜Jšžœ˜—J˜Jšœ žœ˜š  œžœ žœžœž˜*Jš žœ žœžœžœžœ˜$Jšžœžœžœžœ˜*Jšžœžœ˜ Jšžœ˜—J˜š  œžœA˜QJšžœžœ˜Jšœžœ˜ Jšžœ žœžœ˜Jš žœ žœžœžœžœ˜=Jš žœ žœžœžœžœžœ˜ZJšžœG˜Išžœ˜Jšžœžœž œžœ ˜$šžœžœ˜šžœ˜J˜KJšžœžœžœ˜%Jšžœ"˜$Jšžœžœ˜ Jšœ˜——Jšžœžœžœ˜Jšž˜Jšœ˜—Jšžœžœ˜ šžœ˜ šœžœ žœžœ˜,Jšžœžœ#žœžœ˜;Jšžœ<˜>Jšžœžœ˜——Jšœ˜—J˜š  œžœžœžœž˜?Jšžœ;žœžœžœ˜QJšžœžœ˜ Jšžœ˜—J˜š  œžœA˜RJšžœžœ˜šžœžœžœ˜:Jš žœ žœžœ#žœžœ˜Hšžœ˜Jšžœ=˜?Jšžœžœ˜——Jšžœžœ˜ Jšœ˜—J˜š œžœ,˜;Jšžœžœ˜Jšžœžœžœžœ˜*Jš žœžœžœžœžœ˜4Jšžœ8˜:Jšžœžœ˜Jšœ˜—J˜Jšœ žœ˜J˜š  œžœžœžœžœ˜5Jš žœ žœžœžœžœ˜JšžœD˜FJšžœžœ˜—Jšžœžœžœ˜Jšœ˜—J˜š œž˜ JšœBžœ žœ˜Xšœžœžœž˜Jšœ!žœ˜&Jšžœžœ˜—šžœž˜˜šžœ ˜"J˜ Jš œžœžœ žœžœ˜+Jšœžœžœ žœ˜9——˜ šžœ˜J˜ šœžœžœž˜J˜9J˜:Jšžœ˜———˜Jšžœ2˜4—˜Jšžœ0˜2—˜ Jšžœ=˜?—˜ JšžœF˜H—˜ JšžœM˜O—˜ JšžœS˜U—šœ ˜ š  œžœžœ˜šœžœ&˜*J˜+——šžœž˜˜ Jšžœ5˜7—J˜,J˜3J˜0J˜+J˜,J˜+J˜2J˜/J˜*J˜+˜ Jšžœ@˜B—˜ Jšžœ<˜>——šžœ˜ Jšžœ5˜7—Jšœ˜—šžœ˜ Jšžœ%˜'——Jšžœ˜Jšœ˜—J˜J˜J˜—JšŸ)˜)˜Jšœžœžœ˜ J˜šœžœž œžœ˜&Jšœžœ˜ Jšœžœ˜ J˜1Jšœžœžœžœ˜Jšœžœžœžœ˜Jšœžœžœžœ˜J˜ J˜J˜J˜J˜J˜J˜J˜Jšœžœ˜Jšœžœ˜Jšœž˜Jšœžœžœ˜—J˜˜!Jšœ ™ Jšœ žœ˜šœ˜Jšœ4žœ žœ˜J—šœ˜JšŸ%˜%˜&šœ.žœ˜4J˜—Jšœžœžœ žœ˜5—J˜J˜J˜Jšœ˜—˜+Jšœžœ+˜7J˜J˜J˜ J˜—šœ˜Kšœ(žœ=˜iJšœ!žœ žœ˜4—˜˜KJšœžœ˜J˜0——J˜$J˜$šœ6˜6JšœWžœ˜^—J˜Jšœ˜—J˜š  œžœžœ žœ˜DJšœ žœ ˜šžœž˜J˜J˜J˜"J˜—Jšžœžœ˜J˜Jšœ˜—J˜š œ˜JšœL™LJšœ žœ ˜Jšœ žœ˜Jšœ˜—J˜š  œžœžœ˜9Jšžœžœžœ˜Jšœžœ˜š  œžœžœ˜Jšœ7žœ˜=Jšœ4žœ˜;Jšœ˜—J˜J˜˜J˜J˜!J˜—˜J˜J˜'J˜—J˜J˜@J˜@šžœ žœž˜"Jšœ@˜@—J˜J˜3J˜1šžœ žœžœ˜$J˜Jšœ7žœ˜>Jšœ:žœ˜AJ˜Jšœ:žœ˜AJšœ=žœ˜DJ˜ Jšœ<žœ˜CJšœ<žœ˜BJšœ˜—J˜šžœ˜šžœ˜˜J˜J˜J˜—J˜J˜:J˜:J˜˜#J˜Jšœžœ˜J˜J˜ J˜"—šœ"˜"J˜Jšœžœ˜Jšœ˜J˜ J˜*—˜!J˜Jšœžœ˜J˜ J˜ J˜*—˜˜4Jšœžœ&˜2—Jšœ*žœ žœ˜>—˜J˜J˜*J˜Jšœ žœ˜—J˜Jšœ˜—Jšžœžœ˜$—šœ˜Jšœžœ%˜3Jšœžœ˜)Jšžœ žœžœ!žœ˜šžœžœ˜J˜Jšžœžœžœ<˜YJ˜6Jšœ˜—Jšžœ7˜;J˜Jšžœ˜—Jšœ˜—J˜—JšŸ$˜$˜˜1J˜A—J˜—Jšœ˜J˜™+K™;Kšœ Οr™—K™—…—t€›Κ