DIRECTORY AlpFile, AlpineEnvironment, AlpineFile, AlpDirectory, AlpineOwnerRpcControl, AlpInstance, AlpTransaction, Basics, BasicTime, Buttons, IO, List, Real, Rope, ViewerClasses, ViewerTools, YodelData; YodelAdministratorImpl: CEDAR PROGRAM IMPORTS AlpFile, AlpInstance, AlpDirectory, AlpineOwnerRpcControl, AlpTransaction, Buttons, IO, List, Real, Rope, ViewerTools, YodelData EXPORTS YodelData = BEGIN OPEN YodelData; ROPE: TYPE = Rope.ROPE; OwnerPropertyValuePairList: TYPE = LIST OF AlpineEnvironment.OwnerPropertyValuePair; CompareProc: SAFE PROC [ref1, ref2: REF ANY] RETURNS [Basics.Comparison] = CHECKED { RETURN [Rope.Compare[NARROW[ref1], NARROW[ref2], FALSE]]; }; trimWedges: PROC [inRope: ROPE] RETURNS [outRope: ROPE _NIL] = { len: INT _ inRope.InlineLength[]; IF len < 2 OR inRope.Fetch[0] ~= '< OR inRope.Fetch[len-1] ~= '> THEN ERROR; RETURN [ inRope.Substr[start: 1, len: len-2]]; }; ChangeAssertWheel: PUBLIC Buttons.ButtonProc = { d: MyData = NARROW[clientData]; p: ViewerClasses.Viewer = NARROW[parent]; d.assertWheel _ NOT d.assertWheel ; IF d.assertWheel THEN Buttons.SetDisplayStyle[p, $WhiteOnBlack] ELSE Buttons.SetDisplayStyle[p, $BlackOnWhite]; }; ChangeBreakLocks: PUBLIC Buttons.ButtonProc = { d: MyData = NARROW[clientData]; p: ViewerClasses.Viewer = NARROW[parent]; d.breakLocks _ NOT d.breakLocks ; IF d.breakLocks THEN Buttons.SetDisplayStyle[p, $WhiteOnBlack] ELSE Buttons.SetDisplayStyle[p, $BlackOnWhite]; }; createOwner: PROC [trans: AlpTransaction.Handle, server: ROPE, directory: ROPE, user: ROPE _ NIL, password: ROPE _ NIL , properties: OwnerPropertyValuePairList, d: MyData] RETURNS [resultList: LIST OF REF ANY] = { spaceLeftOnVolumeGroup: INT; owner: ROPE _ trimWedges[directory]; IF trans = NIL THEN RETURN [CONS[NARROW["Bad Server Name", ROPE], NIL]]; trans.AssertAlpineWheel[TRUE]; [spaceLeftOnVolumeGroup: spaceLeftOnVolumeGroup] _ trans.CreateOwner[ volumeGroupID: trans.GetNextVolumeGroup[previousGroup: AlpineEnvironment.nullVolumeGroupID, lock: [none, wait]], owner: owner, properties: properties]; AlpDirectory.CreateDirectory[ volume: server, owner: owner, transHandle: trans ]; IF (trans.Finish[requestedOutcome: commit, continue: FALSE] # commit) THEN { resultList _ CONS[ first: NARROW["Alpine transaction aborted -- owner NOT created", ROPE], rest: NIL]; } ELSE resultList _ CONS[ first: IO.PutFR["Owner added; space left on volume group is %g", IO.int[spaceLeftOnVolumeGroup]], rest: NIL]; }; CreateOwnerProc: PUBLIC Buttons.ButtonProc = { resultList: LIST OF REF ANY _ NIL; d: MyData = NARROW[clientData]; server, user, password: ROPE; directory: ROPE; properties: OwnerPropertyValuePairList ; pageLimit: INT; newPageLimitRope: ROPE; scratchStream: IO.STREAM _ NIL; file: ROPE; parseError: BOOL; errorExplanation: ROPE; callCreate: YodelData.PerformProc = { RETURN[createOwner[trans, server, directory, user, password, properties, d]]; }; typeParseError: PROC [] = { d.out.PutF["\nBad name in Create Owner because %g\n", IO.rope[errorExplanation]]; }; d.stopFlag _ FALSE; [user, password, server, directory, file, parseError, errorExplanation] _ ParseSArgs[d]; IF parseError THEN { typeParseError[]; GOTO badParse;}; newPageLimitRope _ ViewerTools.GetContents[viewer: d.oQuota]; IF newPageLimitRope.IsEmpty[] THEN { d.out.PutF["\nCreate Owner has no quota specified\n"]; } ELSE { IF directory.Size[] <= 2 OR hasPattern[directory] THEN { d.out.PutF["\nCreate Owner has an invalid owner name to create. Name is %g\n", IO.rope[directory]]; } ELSE { scratchStream _ IO.RIS[newPageLimitRope, scratchStream]; pageLimit _ IO.GetInt[scratchStream]; properties _ LIST[[quota[pageLimit]]]; d.out.PutF["\nCreate Owner of [%g]%g for %g pages\n", IO.rope[server], IO.rope[directory], IO.int[pageLimit]]; resultList _ PerformOp[performProc: callCreate, server: server, user: user, password: password]; DO nowRope: ROPE _ NARROW[IF resultList = NIL THEN NIL ELSE resultList.first]; IF resultList = NIL THEN EXIT; resultList _ resultList.rest; d.out.PutF[" %g\n", IO.rope[nowRope]]; ENDLOOP; }; }; EXITS badParse => {}; }; destroyOwner: PROC [trans: AlpTransaction.Handle, server: ROPE, directory: ROPE, user: ROPE _ NIL, password: ROPE _ NIL , d: MyData] RETURNS [resultList: LIST OF REF ANY] = { owner: ROPE _ trimWedges[directory]; thereIsAFile: BOOL _ FALSE; properties: LIST OF AlpineEnvironment.OwnerPropertyValuePair; rootFile: AlpineEnvironment.UniversalFile _ AlpineEnvironment.nullUniversalFile ; IF trans = NIL THEN RETURN [CONS[NARROW["Bad Server Name", ROPE], NIL]]; trans.AssertAlpineWheel[TRUE]; TRUSTED { userdirectory: ROPE _ Rope.Concat["[",Rope.Concat[server,Rope.Concat["]",directory]]]; IF AlpDirectory.Enumerate[ transHandle: trans, pattern: userdirectory.Concat["*!H"], previousFile: userdirectory.Concat["$$$.btree"]].file # AlpineEnvironment.nullUniversalFile THEN RETURN [ CONS[ NARROW["Delete all files in the directory before destroying the owner",ROPE] ,NIL]]; }; properties _ AlpTransaction.ReadOwnerProperties[ handle: trans, volumeGroupID: AlpTransaction.GetNextVolumeGroup[handle: trans, previousGroup: AlpineEnvironment.nullVolumeGroupID, lock: [none, fail]], owner: owner]; UNTIL properties = NIL DO -- because ReadOwnerProperties does not sort them yet ... WITH properties.first SELECT FROM q: AlpineEnvironment.OwnerPropertyValuePair.createAccessList => { }; s: AlpineEnvironment.OwnerPropertyValuePair.modifyAccessList => { }; r: AlpineEnvironment.OwnerPropertyValuePair.rootFile => { rootFile _ r.rootFile ; }; t: AlpineEnvironment.OwnerPropertyValuePair.quota => {}; u: AlpineEnvironment.OwnerPropertyValuePair.spaceInUse => {}; ENDCASE ; properties _ properties.rest; ENDLOOP; IF rootFile # AlpineEnvironment.nullUniversalFile THEN { fileHandle: AlpFile.Handle _ NIL; [fileHandle] _ AlpFile.Open[transHandle: trans, universalFile: rootFile, access: readWrite, lock: [write, fail] ! AlpInstance.Unknown => CONTINUE; ]; IF fileHandle # NIL THEN AlpFile.Delete[fileHandle ! AlpInstance.Unknown => CONTINUE;]; }; [] _ trans.DestroyOwner[ volumeGroupID: trans.GetNextVolumeGroup[ previousGroup: AlpineEnvironment.nullVolumeGroupID, lock: [none, wait]], owner: owner]; IF (trans.Finish[requestedOutcome: commit, continue: FALSE] # commit) THEN { resultList _ CONS[ first: NARROW["Alpine transaction aborted -- owner NOT destroyed", ROPE], rest: NIL]; } ELSE resultList _ CONS[ first: NARROW["Owner destroyed", ROPE], rest: NIL]; }; DestroyOwnerProc: PUBLIC Buttons.ButtonProc = { resultList: LIST OF REF ANY _ NIL; d: MyData = NARROW[clientData]; server, user, file, password: ROPE; directory: ROPE; parseError: BOOL; errorExplanation: ROPE; callDestroy: YodelData.PerformProc = { RETURN[destroyOwner[trans, server, directory, user, password, d]]; }; typeParseError: PROC [] = { d.out.PutF["\nBad pattern in Destroy Owner because %g\n", IO.rope[errorExplanation]]; }; d.stopFlag _ FALSE; [user, password, server, directory, file, parseError, errorExplanation] _ ParseSArgs[d]; IF parseError THEN { typeParseError[]; GOTO badParse;}; d.out.PutF["\nDestroy Owner of [%g]%g \n", IO.rope[server], IO.rope[directory]]; resultList _ PerformOp[performProc: callDestroy, server: server, user: user, password: password]; DO nowRope: ROPE _ NARROW[IF resultList = NIL THEN NIL ELSE resultList.first]; IF resultList = NIL THEN EXIT; resultList _ resultList.rest; d.out.PutF[" %g\n", IO.rope[nowRope]]; ENDLOOP; EXITS badParse => {}; }; writeQuota: PROC [trans: AlpTransaction.Handle, server: ROPE, directory: ROPE, user: ROPE _ NIL, password: ROPE _ NIL , properties: OwnerPropertyValuePairList, d: MyData] RETURNS [resultList: LIST OF REF ANY] = { owner: ROPE _ trimWedges[directory]; IF trans = NIL THEN RETURN [CONS[NARROW["Bad Server Name", ROPE], NIL]]; trans.AssertAlpineWheel[TRUE]; [] _ trans.WriteOwnerProperties[ volumeGroupID: trans.GetNextVolumeGroup[previousGroup: AlpineEnvironment.nullVolumeGroupID, lock: [none, wait]], owner: owner, properties: properties]; IF (trans.Finish[requestedOutcome: commit, continue: FALSE] # commit) THEN { resultList _ CONS[ first: NARROW["Alpine transaction aborted -- quota NOT changed", ROPE], rest: NIL]; } ELSE resultList _ CONS[ first: NARROW["Quota changed", ROPE], rest: NIL]; }; WriteQuotaProc: PUBLIC Buttons.ButtonProc = { resultList: LIST OF REF ANY _ NIL; d: MyData = NARROW[clientData]; server, user, password: ROPE; directory: ROPE; properties: OwnerPropertyValuePairList ; pageLimit: INT; newPageLimitRope: ROPE; scratchStream: IO.STREAM _ NIL; file: ROPE; parseError: BOOL; errorExplanation: ROPE; callWriteQuota: YodelData.PerformProc = { RETURN[writeQuota[trans, server, directory, user, password, properties, d]]; }; typeParseError: PROC [] = { d.out.PutF["\nBad name in Write Quota because %g\n", IO.rope[errorExplanation]]; }; { d.stopFlag _ FALSE; [user, password, server, directory, file, parseError, errorExplanation] _ ParseSArgs[d]; IF parseError THEN { typeParseError[]; GOTO badParse;}; newPageLimitRope _ ViewerTools.GetContents[viewer: d.oQuota]; scratchStream _ IO.RIS[newPageLimitRope, scratchStream]; pageLimit _ IO.GetInt[scratchStream ! IO.EndOfStream, IO.Error => GOTO badInt]; properties _ LIST[[quota[pageLimit]]]; d.out.PutF["\nWrite quota for Owner of [%g]%g to %g pages\n", IO.rope[server], IO.rope[directory], IO.int[pageLimit]]; resultList _ PerformOp[performProc: callWriteQuota, server: server, user: user, password: password]; DO nowRope: ROPE _ NARROW[ IF resultList = NIL THEN NIL ELSE resultList.first]; IF resultList = NIL THEN EXIT; resultList _ resultList.rest; d.out.PutF[" %g\n", IO.rope[nowRope]]; ENDLOOP; EXITS badInt => { d.out.PutF["\nWrite quota: bad quota specified\n"]; }; badParse => {}; }; }; listOwners: PROC [trans: AlpTransaction.Handle] RETURNS [resultList: LIST OF REF ANY] = { owner: ROPE _ NIL ; properties: LIST OF AlpineEnvironment.OwnerPropertyValuePair; result: ROPE; totalQuota: INT _ 0 ; totalSpaceInUse: INT _ 0 ; IF trans = NIL THEN RETURN [CONS[NARROW["Bad Server Name", ROPE], NIL]]; trans.AssertAlpineWheel[TRUE]; DO quota: INT _ 0; spaceInUse: INT _ 0; [owner, properties] _ trans.ReadNextOwner[ volumeGroupID: trans.GetNextVolumeGroup[previousGroup: AlpineEnvironment.nullVolumeGroupID, lock: [none, wait]], previousOwner: owner, desiredProperties: ALL [TRUE]]; IF owner = NIL THEN EXIT; result _ IO.PutFR["%g\t\t", IO.rope[owner]]; UNTIL properties = NIL DO property: AlpineEnvironment.OwnerPropertyValuePair _ properties.first ; junk: REF AlpineEnvironment.OwnerPropertyValuePair _ NEW[AlpineEnvironment.OwnerPropertyValuePair]; -- black magic inserted by compiler wizard to prevent fatal error in pass 4. properties _ properties.rest; SELECT property.property FROM quota => { quota _ NARROW[property, AlpineEnvironment.OwnerPropertyValuePair.quota].quota; totalQuota _ totalQuota + quota ; result _ Rope.Concat[result, IO.PutFR["quota %5g ", IO.int[quota]]]; }; spaceInUse => { spaceInUse _ NARROW[property, AlpineEnvironment.OwnerPropertyValuePair.spaceInUse].spaceInUse; totalSpaceInUse _ totalSpaceInUse + spaceInUse ; result _ Rope.Concat[result, IO.PutFR["spaceInUse %5g ", IO.int[spaceInUse]]]; }; ENDCASE; ENDLOOP; IF quota > 0 THEN { percent: INT _ Real.Fix[(spaceInUse * 100.0)/quota]; result _ Rope.Concat[result, IO.PutFR[" (%2g%%) ", IO.int[percent]]]; IF percent > 90 THEN result _ Rope.Concat[result, " <-----"]; }; resultList _ CONS[ first: result, rest: resultList]; ENDLOOP; resultList _ CONS[ first: IO.PutFR[" totals\t\tspaceInUse %5g\tquota %5g\n", IO.int[totalSpaceInUse], IO.int[totalQuota] ], rest: resultList]; }; ListOwnersProc: PUBLIC Buttons.ButtonProc = { callListOwners: YodelData.PerformProc = { RETURN[listOwners[trans]]; }; typeParseError: PROC [] = { d.out.PutF["\nBad pattern in List Owners because %g\n", IO.rope[errorExplanation]]; }; resultList: LIST OF REF ANY _ NIL; d: MyData = NARROW[clientData]; server, user, password: ROPE; parseError: BOOL; errorExplanation: ROPE; d.stopFlag _ FALSE; [user: user, password: password, srcServer: server, parseError: parseError, errorExplanation: errorExplanation] _ ParseSArgs[d]; IF parseError THEN { typeParseError[]; GOTO badParse;}; d.out.PutF["\nList Owners for server %g\n", IO.rope[server]]; resultList _ PerformOp[performProc: callListOwners, server: server, user: user, password: password]; resultList _ List.Sort[resultList, CompareProc]; DO nowRope: ROPE _ NARROW[IF resultList = NIL THEN NIL ELSE resultList.first]; IF resultList = NIL THEN EXIT; resultList _ resultList.rest; d.out.PutF[" %g\n", IO.rope[nowRope]]; ENDLOOP; EXITS badParse => {}; }; readDBProperties: PROC [trans: AlpTransaction.Handle] RETURNS [resultList: LIST OF REF ANY] = { nOwners, nEntriesUsed, nEntries: NAT; totalQuota, totalSpaceInUse, volumeGroupSize: AlpineEnvironment.PageCount; IF trans = NIL THEN RETURN [CONS[NARROW["Bad Server Name", ROPE], NIL]]; trans.AssertAlpineWheel[TRUE]; TRUSTED { [nOwners, nEntriesUsed, nEntries, totalQuota, totalSpaceInUse, volumeGroupSize] _ trans.inst.owner.ReadDBProperties[conversation: trans.inst.conversation, transID: trans.transID, volumeGroupID: trans.GetNextVolumeGroup[previousGroup: AlpineEnvironment.nullVolumeGroupID, lock: [none, wait]]]; }; resultList _ CONS[ first: IO.PutFR[" owners %g, entries used %g, entries %g", IO.int[nOwners], IO.int[nEntriesUsed], IO.int[nEntries]], rest: CONS[ IO.PutFR[" total quota %g, total space in use %g, volume group size %g", IO.int[totalQuota], IO.int[totalSpaceInUse], IO.int[volumeGroupSize]], NIL] ]; }; ReadDBPropertiesProc: PUBLIC Buttons.ButtonProc = { callReadDBPropertiesProc: YodelData.PerformProc = { RETURN[readDBProperties[trans]]; }; typeParseError: PROC [] = { d.out.PutF["\nBad pattern in Read Statistics because %g\n", IO.rope[errorExplanation]]; }; resultList: LIST OF REF ANY _ NIL; d: MyData = NARROW[clientData]; server, user, password: ROPE; parseError: BOOL; errorExplanation: ROPE; d.stopFlag _ FALSE; [user: user, password: password, srcServer: server, parseError: parseError, errorExplanation: errorExplanation] _ ParseSArgs[d]; IF parseError THEN { typeParseError[]; GOTO badParse;}; d.out.PutF["\nRead Statistics for server %g\n", IO.rope[server]]; resultList _ PerformOp[performProc: callReadDBPropertiesProc, server: server, user: user, password: password]; resultList _ List.Sort[resultList, CompareProc]; DO nowRope: ROPE _ NARROW[IF resultList = NIL THEN NIL ELSE resultList.first]; IF resultList = NIL THEN EXIT; resultList _ resultList.rest; d.out.PutF[" %g\n", IO.rope[nowRope]]; ENDLOOP EXITS badParse => {}; }; END. òYodelAdministratorImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: Carl Hauser, February 13, 1986 1:33:56 pm PST Hauser, April 12, 1985 10:17:48 am PST Bob Hagmann June 11, 1985 11:38:12 am PDT filler to use all the variants so the compiler won't blow up (this is the cedar version of the "don't delete this line" comment December 14, 1984 Converting from AlpineInterimDirectory to AlpineDirectory. Bob Hagmann June 10, 1985 4:55:48 pm PDT reformatted Ê]˜šœ™Icodešœ Ïmœ1™<—šœ™K™-Jšœ&™&K™)—J˜šÏk ˜ J˜J˜J˜ J˜ J˜J˜ J˜J˜J˜ J˜Jšžœ˜J˜J˜J˜J˜J˜ J˜ —J˜Jšœžœž˜%J˜JšžœUžœ+˜‰J˜Jšžœ ž˜J˜Jšžœ ˜J˜Jšžœžœžœ˜J˜Jšœžœžœžœ*˜TJ˜š Ïn œžœžœžœžœ˜,Jšžœžœ˜'Jšžœžœžœžœ˜9J˜—J˜š Ïb œžœ žœžœ žœžœ˜@Jšœžœ˜!Jš žœ žœžœžœžœ˜LJšžœ(˜.J˜—J˜J˜š œžœ˜0Jšœ žœ ˜Jšœžœ ˜)Jšœžœ˜#Jšžœ˜Jšœžœ*˜/Jšœžœ+˜0J˜—J˜š œžœ˜/Jšœ žœ ˜Jšœžœ ˜)Jšœžœ˜!Jšžœ ˜Jšœžœ*˜/Jšœžœ+˜0J˜—J˜š  œžœ(žœ žœžœžœ žœžœ6žœžœžœžœžœ˜ÕJšœžœ˜Jšœžœ˜$Jšžœ žœžœžœžœžœžœžœ˜HJšœžœ˜˜EJ˜pJ˜ J˜—J˜Qšžœ3žœ žœ˜Lšœ žœ˜Jšœžœ4žœ˜GJšœžœ˜ ——šœžœžœ˜Jšœžœ8žœ˜aJšœžœ˜ —J˜J˜—š œžœ˜.Jš œ žœžœžœžœžœ˜"Jšœ žœ ˜Jšœžœ˜Jšœ žœ˜J˜(Jšœ žœ˜Jšœžœ˜Jšœžœžœžœ˜Jšœžœ˜ Jšœ žœ˜Jšœžœ˜J˜š  œ˜%JšžœG˜MJ˜—š œžœ˜Jšœ6žœ˜QJ˜—Jšœ žœ˜JšœX˜XJšžœ žœžœ ˜7J˜=šžœžœ˜$J˜6J˜—šœžœ˜šžœžœžœ˜8JšœPžœ˜dJ˜—šœžœ˜Jšœžœžœ"˜8Jšœ žœ˜%Jšœ žœ˜&J˜Jšœ6žœžœžœ˜nJ˜`šž˜Jšœ žœžœžœžœžœžœžœ˜KJšžœžœžœžœ˜J˜Jšœžœ˜'Jšžœ˜—J˜—J˜—šž˜Jšœ˜—J˜—J˜š  œžœ(žœ žœžœžœ žœžœžœžœžœžœžœ˜®Jšœžœ˜$Jšœžœžœ˜Jšœ žœžœ*˜=J˜QJšžœ žœžœžœžœžœžœžœ˜HJšœžœ˜Jšž˜˜JšœžœC˜Všžœ¯ž˜µšžœžœ˜JšžœAžœ˜LJšœžœ˜——J˜—˜0J˜J˜‰J˜—šžœžœžœÏc9˜Sšžœžœž˜!˜AJ˜—˜AJ˜—Jšœ<™žœžœžœ˜vJ˜dšž˜Jšœ žœžœžœžœžœžœžœ˜LJšžœžœžœžœ˜J˜Jšœžœ˜'Jšžœ˜—šž˜˜ J˜3J˜—Jšœ˜—J˜—J˜—J˜š  œžœ žœžœžœžœžœ˜YJšœžœžœ˜Jšœ žœžœ*˜=Jšœžœ˜ Jšœ žœ˜Jšœžœ˜J˜Jšžœ žœžœžœžœžœžœžœ˜HJšœžœ˜šž˜Jšœžœ˜Jšœ žœ˜˜*J˜pJ˜Jšœžœžœ˜—Jšžœ žœžœžœ˜Jšœ žœžœ˜,šžœžœž˜J˜GKšœžœ,žœ,¡M˜±J˜šžœž˜˜ šœžœ ˜J˜6—J˜!˜Jšžœžœ˜'—J˜—˜šœ žœ ˜J˜@—J˜0˜Jšžœžœ˜1—J˜—Jšžœ˜—Jšžœ˜—šžœ žœ˜Jšœ žœ(˜4˜Jšžœžœ˜(—Jšžœžœ)˜=J˜—Jšœ žœ#˜4—Jšžœ˜šœ žœ˜Jšœžœ1žœžœ˜hJ˜—J˜—J˜š œžœ˜-š œ˜)Jšžœ˜J˜—š œžœ˜Jšœ8žœ˜SJ˜—Jš œ žœžœžœžœžœ˜"Jšœ žœ ˜Jšœžœ˜Jšœ žœ˜Jšœžœ˜J˜šœ žœ˜J˜—Jšœ€˜€Jšžœ žœžœ ˜7Jšœ,žœ˜=J˜dJ˜0šž˜Jšœ žœžœžœžœžœžœžœ˜KJšžœžœžœžœ˜J˜Jšœžœ˜'Jšžœ˜—šž˜Jšœ˜—J˜—J˜š œžœ žœžœžœžœžœ˜_Jšœ!žœ˜%J˜JJšžœ žœžœžœžœžœžœžœ˜HJšœžœ˜šžœ˜ J˜é˜:J˜——šœ žœ˜Jš œžœ4žœžœžœ˜všœžœ˜ JšžœHžœžœžœ˜Jšžœ˜—J˜—J˜—J˜š œžœ˜3š œ˜3Jšžœ˜ J˜—š œžœ˜Jšœ<žœ˜WJ˜—J˜Jš œ žœžœžœžœžœ˜"Jšœ žœ ˜Jšœžœ˜Jšœ žœ˜Jšœžœ˜J˜šœ žœ˜J˜—Jšœ€˜€Jšžœ žœžœ ˜7Jšœ0žœ˜AJ˜nJ˜0šž˜Jšœ žœžœžœžœžœžœžœ˜KJšžœžœžœžœ˜J˜Jšœžœ˜'Jšž˜—šž˜Jšœ˜—Jšœ˜J˜—Jšžœ˜J˜J˜JšœL™L™(K™ —K™K™—…—8îK=