DIRECTORY BasicTime USING [GMT, Now, Period, Update], IO, MarkerControl USING [ClientProcs, ClientProcsHandle, MarkerStatus], MarkerControlImpl, MarkerControlInternal, PaperHandling USING [BannerMode, PrintOrder], PaperTypes USING [nullDimensions, Paper], PrintingState USING [Type], PrintingTypes USING [Option], PrintQueue USING [Empty, nilQueueObjectHandle, ObjectStatus, QueueObjectHandle, QueueStage, Requeue], Process USING [Detach, MsecToTicks, Pause, Priority, priorityBackground, SecondsToTicks, SetPriority, SetTimeout], PSAsyncMsg USING [PutString], BansheeCounter USING [ForceOut], BansheeEngine USING [GetPaperSize, GetStatus, GetPrintingState, Job, SetJob, SetPrintingState, WaitStatus, WaitPrintingState, JobHandle], BansheeMarkerControl, BansheeStatus USING [Type], Rope USING [ROPE, ToRefText]; BansheeMarkerControlImpl: CEDAR MONITOR LOCKS root IMPORTS root: MarkerControlImpl, --DecomposerControl, -- BansheeCounter, BansheeEngine, BasicTime, IO, MarkerControlInternal, PrintQueue, Process, PSAsyncMsg, Rope EXPORTS BansheeMarkerControl SHARES MarkerControlImpl = BEGIN ROPE: TYPE ~ Rope.ROPE; JobStatus: TYPE = {normal, paused, canceled, imagingError}; BansheeState: TYPE = RECORD [ jobStatus: JobStatus _ normal, job: BansheeEngine.JobHandle _ NIL, lastEngineStatus: BansheeStatus.Type _ okay, banner: PaperHandling.BannerMode _ oncePerJob, media: PaperTypes.Paper _ [knownSize: letter, otherSize: PaperTypes.nullDimensions]]; BansheeClientProcsHandle: TYPE = REF bansheeDl MarkerControl.ClientProcs; updateBansheeLog: CONDITION; engineStatusChange: CONDITION; jobStateChange: CONDITION; bansheeState: REF BansheeState _ NIL; bansheeClientProcs: BansheeClientProcsHandle _ NIL; lastActivityTimeStamp: BasicTime.GMT _ BasicTime.Now[]; --The time the last job completed marking. Startup: PUBLIC INTERNAL PROCEDURE [priority: Process.Priority] RETURNS [MarkerControl.ClientProcsHandle] = TRUSTED BEGIN bansheeState _ NEW[BansheeState]; bansheeState.job _ NEW[BansheeEngine.Job _ [document: NIL]]; bansheeClientProcs _ NEW[ bansheeDl MarkerControl.ClientProcs _ [ start: MarkerControlInternal.Start, stop: MarkerControlInternal.Stop, stopped: MarkerControlInternal.Stopped, pause: MarkerControlInternal.Pause, resume: MarkerControlInternal.Resume, status: Status, waitStatusChange: WaitStatusChange, setPrintOrder: MarkerControlInternal.SetPrintOrder, modifyTraceLevel: MarkerControlInternal.ModifyTraceLevel, engineSpecific: bansheeDl[ setBanner: SetBansheeBanner] ]]; bansheeState.lastEngineStatus _ BansheeEngine.GetStatus[]; Process.Detach[FORK StatusChangeWatcher[]]; Process.Detach[FORK StatusWatcher[]]; Process.Detach[FORK BansheeLogUpdater[]]; Process.Detach[FORK MarkingControl[priority]]; BansheeCounter.ForceOut[]; -- to start the logging; fixes AR 12573 RETURN[bansheeClientProcs] END; -- Startup Status: PUBLIC PROCEDURE RETURNS [MarkerControl.MarkerStatus] = BEGIN bansheeState.media _ BansheeEngine.GetPaperSize[]; RETURN[ [ activity: root.state.activity, engineFault: root.state.engineProblem, engine: [bansheeDl[ banshee: bansheeState.lastEngineStatus, paper: bansheeState.media]]]]; END; --Status SetBansheeBanner: PROCEDURE [banner: PaperHandling.BannerMode] = BEGIN bansheeState.banner _ banner; END; --SetBanner WaitStatusChange: ENTRY PROCEDURE RETURNS [MarkerControl.MarkerStatus] = BEGIN ENABLE UNWIND => NULL; WAIT engineStatusChange; bansheeState.lastEngineStatus _ BansheeEngine.GetStatus[]; RETURN[Status[]]; END; --WaitStatusChange UpdateBansheeLog: ENTRY PROCEDURE = INLINE BEGIN ENABLE UNWIND => NULL; NOTIFY updateBansheeLog; END; --UpdateBansheeLog BansheeLogUpdater: ENTRY PROCEDURE = TRUSTED BEGIN ENABLE UNWIND => NULL; Process.SetTimeout[@updateBansheeLog, Process.SecondsToTicks[60 * 30 --secs/min-- ]]; Process.SetPriority[Process.priorityBackground]; DO -- forever WAIT updateBansheeLog; IF NOT root.state.idle THEN LOOP; IF root.state.trace = verbose THEN PSAsyncMsg.PutString["MarkerBansheeCounter.ForceOut"]; BansheeCounter.ForceOut[]; ENDLOOP; END; --BansheeLogUpdater GetLatestPaperSize: PROCEDURE = BEGIN Process.Pause [Process.MsecToTicks[500]]; --Must pause before getting paper size, or correct size will not have been posted yet. bansheeState.media _ BansheeEngine.GetPaperSize[]; END; --GetLatestPaperSize StatusChangeWatcher: ENTRY PROCEDURE = BEGIN -- updates the Banshee display if necessary ENABLE UNWIND => NULL; engineStatus: BansheeStatus.Type; DO -- forever engineStatus _ BansheeEngine.GetStatus[]; SELECT engineStatus FROM imageFault1, imageFault2, sequenceFault => BEGIN root.state.engineProblem _ TRUE; -- Stop everything when an imaging error is detected... root.state.stopStatus _ restart; root.control.stop _ TRUE; root.control.start _ root.control.pause _ root.control.resume _ root.control.pausing _ root.control.resuming _ root.control.resumeWhenAppropriate _ FALSE; bansheeState.jobStatus _ imagingError; BansheeEngine.SetPrintingState[completed]; END; okay => TRUSTED BEGIN Process.Detach [FORK GetLatestPaperSize[]]; --This proc is forked because it pauses before getting the current paper size and we don't want to hang up the StatusChangeWatcher with a pause or it might miss the next status change. IF root.state.activity = markingButInterruptable THEN root.state.activity _ marking; root.state.engineProblem _ FALSE; IF root.state.activity = paused OR root.control.pause THEN BEGIN root.control.resume _ TRUE; IF root.state.trace = verbose THEN PSAsyncMsg.PutString["Marker.Resume Internal"]; BROADCAST root.event; END; END; ENDCASE => BEGIN root.state.engineProblem _ TRUE; IF root.state.activity = marking THEN BEGIN root.state.activity _ markingButInterruptable; root.control.pause _ TRUE; IF root.state.trace = verbose THEN PSAsyncMsg.PutString["Marker.Pause Internal"]; BROADCAST root.event; END; END; bansheeState.lastEngineStatus _ engineStatus; WAIT engineStatusChange; ENDLOOP; END; --StatusChangeWatcher StatusWatcher: PROCEDURE = BEGIN -- Forked process status: BansheeStatus.Type; DO -- forever status _ BansheeEngine.WaitStatus[]; IF status # bansheeState.lastEngineStatus OR status IN [imageFault1..sequenceFault] THEN TRUSTED BEGIN Process.Detach[FORK NotifyStatusChange[status]]; -- avoid Monitor locks IF root.state.trace # none THEN PSAsyncMsg.PutString["Banshee Status Change"]; END; ENDLOOP; END; --StatusWatcher NotifyStatusChange: ENTRY PROCEDURE [status: BansheeStatus.Type] = BEGIN ENABLE UNWIND => NULL; bansheeState.lastEngineStatus _ status; BROADCAST engineStatusChange; END; --NotifyStatusChange MarkingControl: ENTRY PROCEDURE [priority: Process.Priority] = BEGIN ENABLE UNWIND => NULL; WaitJobState: PROCEDURE [ targetState: PrintingState.Type, targetEqual: BOOLEAN] RETURNS [pState: PrintingState.Type] = BEGIN -- FORKed to avoid MLs NotifyJobStateChange: ENTRY PROC = BEGIN NOTIFY jobStateChange; END; pState _ BansheeEngine.WaitPrintingState[targetState, targetEqual]; NotifyJobStateChange[]; END; --WaitJobState Process.SetPriority[priority]; DO --forever SELECT TRUE FROM root.control.tpQueueEntry AND root.state.idle => BEGIN -- over-rides root.state.stopped root.state.tpJob _ TRUE; IF NOT MarkDocument[tpDecomposed] THEN BEGIN root.control.tpQueueEntry _ FALSE; LOOP; END; END; root.control.stop => SELECT TRUE FROM root.state.stopped => NULL; NOT root.state.idle AND NOT bansheeState.jobStatus = canceled AND NOT bansheeState.jobStatus = imagingError => BEGIN root.control.start _ root.control.pause _ root.control.resume _ root.control.pausing _ root.control.resuming _ root.control.resumeWhenAppropriate _ FALSE; bansheeState.jobStatus _ canceled; BansheeEngine.SetPrintingState[completed]; END; root.state.idle => BEGIN root.state.stopped _ TRUE; NOTIFY root.stopped; END; ENDCASE; root.control.start => BEGIN root.control.start _ root.state.stopped _ root.control.pause _ root.control.resume _ root.control.pausing _ root.control.resuming _ root.control.resumeWhenAppropriate _ FALSE; -- so we don't keep looking here LOOP; -- make another pass END; root.control.pause => BEGIN IF root.state.activity IN [marking..markingButInterruptable] THEN TRUSTED BEGIN waitJobState: PROCESS RETURNS [PrintingState.Type]; engineState: PrintingState.Type; BansheeEngine.SetPrintingState[stopped]; waitJobState _ FORK WaitJobState[started, FALSE]; -- avoid MLs WAIT jobStateChange; engineState _ JOIN waitJobState; IF engineState = stopped THEN root.state.activity _ paused; root.control.resumeWhenAppropriate _ FALSE; IF root.state.trace = verbose THEN PSAsyncMsg.PutString["Marking Paused"]; END; NOTIFY root.paused; root.control.pause _ FALSE; END; root.control.resume OR root.control.resumeWhenAppropriate => BEGIN root.control.resumeWhenAppropriate _ FALSE; IF root.state.activity = paused AND BansheeEngine.GetPrintingState[] = stopped AND root.state.clientPause = 0 THEN BEGIN IF BansheeEngine.GetStatus[] = okay THEN TRUSTED BEGIN waitJobState: PROCESS RETURNS [PrintingState.Type]; engineState: PrintingState.Type; BansheeEngine.SetPrintingState[started]; waitJobState _ FORK WaitJobState[stopped, FALSE]; -- avoid MLs WAIT jobStateChange; engineState _ JOIN waitJobState; IF engineState = started THEN root.state.activity _ marking; IF root.state.trace = verbose THEN PSAsyncMsg.PutString["Marking Resumed"]; END ELSE BEGIN root.control.resumeWhenAppropriate _ TRUE; IF root.state.trace = verbose THEN PSAsyncMsg.PutString["Marker.ResumeWhenAppropriate"]; END; END; root.control.resume _ FALSE; END; NOT root.state.idle => NULL; -- so we don't start multiple markers root.control.jobQueueEntry => BEGIN IF NOT MarkDocument[decomposed] THEN BEGIN root.control.jobQueueEntry _ FALSE; LOOP; END; END; ENDCASE; WAIT root.event; ENDLOOP; END; --MarkingControl MarkDocument: INTERNAL PROCEDURE [fromQ: PrintQueue.QueueStage, qOH: PrintQueue.QueueObjectHandle _ PrintQueue.nilQueueObjectHandle] RETURNS [okay: BOOLEAN] = TRUSTED BEGIN root.state.qOH _ PrintQueue.Requeue[qOH: qOH, fromQueue: fromQ, toQueue: marking]; IF root.state.qOH = PrintQueue.nilQueueObjectHandle THEN RETURN[FALSE]; root.state.idle _ FALSE; root.state.activity _ marking; Process.Detach[FORK DoMarking]; IF root.state.trace # none THEN BEGIN PSAsyncMsg.PutString[Rope.ToRefText[IO.PutFR["Marking File \"%g\"; Copy Count: %g", IO.text[root.state.qOH.fileName], IO.card[root.state.qOH.numberCopies]]]]; END; RETURN[TRUE]; END; -- MarkDocument DoMarking: PROCEDURE = BEGIN secondsToWait: LONG INTEGER _ 0; root.state.qOH.currentStatus _ marking; bansheeState.jobStatus _ normal; bansheeState.job.copyCount _ root.state.qOH.numberCopies; bansheeState.job.banner _ SELECT TRUE FROM root.state.tpJob => suppressed, root.state.qOH.bannerOnly => oncePerJob, ENDCASE => bansheeState.banner; BansheeEngine.SetJob[bansheeState.job]; IF root.state.waitBetweenJobs THEN BEGIN secondsToWait _ --last activity time plus wait time minus current time BasicTime.Period[ from: BasicTime.Update[lastActivityTimeStamp, root.state.betweenJobWaitTime], to: BasicTime.Now[]]; IF secondsToWait > 0 THEN BEGIN IF root.state.trace = verbose THEN BEGIN PSAsyncMsg.PutString[Rope.ToRefText[IO.PutFR["Wait %g seconds to allow spooling", IO.card[secondsToWait]]]]; END; Process.Pause[Process.SecondsToTicks[CARDINAL[secondsToWait]]]; --Wait a few seconds to allow spooling END; END; root.state.starting[]; root.state.startMarkDate _ BasicTime.Now[]; IF bansheeState.job.copyCount > 0 THEN BEGIN BansheeEngine.SetPrintingState[started]; [] _ BansheeEngine.WaitPrintingState[started, TRUE]; [] _ BansheeEngine.WaitPrintingState[completed, TRUE]; --returns when done END; root.state.finished[]; lastActivityTimeStamp _ BasicTime.Now[]; Queue[ SELECT bansheeState.jobStatus FROM normal => printed, imagingError => restart, ENDCASE => root.state.stopStatus]; MarkerControlInternal.SetActivity[available]; UpdateBansheeLog[]; END; -- DoMarking Queue: ENTRY PROCEDURE [status: PrintQueue.ObjectStatus] = BEGIN ENABLE UNWIND => NULL; nsDone: ROPE _ "Done Marking \"%g\"; seconds = %g"; nsFail: ROPE _ "Mark Failure on \"%g\"; seconds = %g"; p: CARD _ BasicTime.Period[from: lastActivityTimeStamp, to: root.state.startMarkDate]; IF p >= 0 THEN root.state.qOH.markTime _ p; root.state.qOH.currentStatus _ status; SELECT TRUE FROM status = printed OR root.state.tpJob --any tp failure-- => BEGIN IF root.state.trace # none THEN PSAsyncMsg.PutString[Rope.ToRefText[IO.PutFR[nsDone, IO.text[root.state.qOH.fileName], IO.card[root.state.qOH.markTime]]]]; [] _ PrintQueue.Requeue[ qOH: root.state.qOH, fromQueue: marking, toQueue: marked]; END; status = restart => BEGIN -- requeue document IF root.state.trace # none THEN PSAsyncMsg.PutString[Rope.ToRefText[IO.PutFR[nsDone, IO.text[root.state.qOH.fileName], IO.card[root.state.qOH.markTime]]]]; IF root.state.qOH.printObjectHandle # NIL THEN BEGIN root.state.qOH.printObjectHandle _ NIL; END; [] _ PrintQueue.Requeue[qOH: root.state.qOH, fromQueue: marking, toQueue: spooledNormal, position: front]; IF bansheeState.jobStatus = imagingError THEN TRUSTED BEGIN Process.Detach[FORK root.state.engineFailure[]]; END; END; status = canceledInMarker => BEGIN IF root.state.trace # none THEN PSAsyncMsg.PutString[Rope.ToRefText[IO.PutFR[nsDone, IO.text[root.state.qOH.fileName], IO.card[root.state.qOH.markTime]]]]; [] _ PrintQueue.Requeue[qOH: root.state.qOH, fromQueue: marking, toQueue: aborted]; IF root.state.canceledWhileMarking # NIL THEN root.state.canceledWhileMarking[root.state.qOH.fileName, status]; END; ENDCASE => BEGIN IF root.state.trace # none THEN PSAsyncMsg.PutString[Rope.ToRefText[IO.PutFR[nsFail, IO.text[root.state.qOH.fileName], IO.card[root.state.qOH.markTime]]]]; [] _ PrintQueue.Requeue[qOH: root.state.qOH, fromQueue: marking, toQueue: errors]; END; bansheeState.job.lastPage _ LAST[CARDINAL]; -- was modified by BansheeEngine?? root.state.tpJob _ FALSE; root.control.jobQueueEntry _ NOT PrintQueue.Empty[decomposed]; root.control.tpQueueEntry _ NOT PrintQueue.Empty[tpDecomposed]; BROADCAST root.event; -- in case there's something to do END; --Queue END. --BansheeMarkerControlImpl LOG when - who - what 19-Sep-84 13:52:32 - Strickberger - Created based on RavenMarkerControlImpl of 30-Aug-84 13:02:13. 8-Nov-84 16:52:08 - Jacks - Updated to second round of 9.0 interface changes; changed Stop parm from queue to status. 16-Jan-85 16:15:17 - Jacks - Put pause in "okay" arm of select stmt in StatusChangeWatcher to give time for paper size to be updated. 4-Feb-85 13:36:55 - Jacks - Moved SetActivity[available] after Queue[] at end of DoMarking. 17-Jun-85 16:08:32 - Jacks - Added copyright notice; updated to PS Euclid interfaces. 6-Aug-85 9:06:14 - Jacks - Updated to 10.0 DecomposerControl.FreeDecomposeObject. 23-Sep-85 15:22:18 - Jacks - String.AppendLongDecimal sCopies in MarkDocument to avoid Bounds Fault; only pause between jobs if client requests it. 4-Oct-85 9:33:49 - Jacks - Set printObjectHandle to NIL after calling DecomposerControl.FreeDecomposeObject. 21-Oct-85 17:12:44 - Jacks - Converted to new banshee paper def. 1-Nov-85 11:09:42 - Jacks - Check for copyCount=0 in DoMarking. 23-Apr-86 16:34:54 - Jacks - Added code for recognizing image faults in StatusChangeWatcher and dealing correctly with markFailure in Queue; requeue restart docs to FRONT of spooled queue. PBansheeMarkerControlImpl.mesa Copyright (C) 1986 by Xerox Corporation. All rights reserved. Ruseli Binsol: November 21, 1986 1:25:25 pm PST Tim Diebert: December 2, 1986 4:11:11 pm PST <> ******************************** PUBLIC PROCS: ******************************* ******************************** PRIVATE PROCS: ******************************** must process every image fault so printing gets stopped decomposedDocument: Decompose.Handle _ LOOPHOLE[root.state.qOH.printObjectHandle]; Initialize and set job record: bansheeState.job.document _ decomposedDocument.docTransferH; <> Check here for copyCount = 0 and don't pass those jobs to engine level... <> Must free decompose handle before requeuing doc. DecomposerControl.FreeDecomposeObject[root.state.qOH.uid]; Κ;˜codešœ™Kšœ>™>Kšœ0™0K™,K™—K™ZK˜šΟk ˜ Kšœ œœ˜+Kšœ˜Kšœœ0˜CK˜K˜Kšœœ˜-Kšœ œ˜)Kšœœ˜Kšœœ ˜Kšœ œV˜fKšœœe˜rKšœ œ ˜Kšœœ ˜ šœœK˜^K˜*—K˜Kšœœ˜Kšœœœ ˜—K˜šΟnœœœœ˜2š˜KšœΟcœ+œ>˜›—Kšœ˜Kšœ˜ K˜Kšœœœ˜Kšœ œ,˜;—˜šœœœ˜K˜Kšœœ˜#K˜,K˜.˜UK˜——šœœœ(˜LK˜—Kšœ œ˜Kšœ œ˜Kšœ œ˜—˜Kšœœœ˜%Kšœ/œ˜3—˜Kšœ!œŸ-˜eK˜™ Kšœœ™ —K™—˜šžœœœ œ˜?Kšœ&œ˜:Kšœœ˜!Kšœœ œ˜<šœœ˜˜'K˜EK˜KK˜5K˜#K˜3K˜9˜K˜——K˜——˜K˜:Kšœœ˜+Kšœœ˜%Kšœœ˜)Kšœœ˜.KšœŸ'˜CKšœ˜KšœŸ ˜——K˜˜š žœœ œœ œ˜FK˜2šœ˜K˜—˜!K˜'—˜˜*K˜——KšœŸ˜——K˜˜Kšœ ™ Kšœ™Kšœ ™ —˜šžœ œ%˜@Kšœ˜$KšœŸ ˜——K˜˜š žœœ œœ˜NKšœœœ˜Kšœ˜K˜:Kšœ ˜KšœŸ˜——K˜˜šžœœ œ˜*Kš œœœœœœŸ˜NK˜——˜š žœœ œœ˜2Kšœœœ˜KšœEŸ œ˜UK˜0šœŸ ˜Kšœ˜Kšœœœœ˜!Kšœœ7˜YK˜Kšœ˜—KšœŸ˜——˜šžœ œ˜&Kšœ*ŸV˜€K˜2KšœŸ˜K˜—K˜š žœœ œœŸ-˜ZKšœœœ˜K˜!K˜šœŸ ˜K˜)šœ˜šœ+˜0šœœŸ7˜XK˜ Kšœœ˜—˜?K˜.Kšœ%œ˜+—K˜&K˜*Kšœ˜—šœ ˜KšœœŸΈ˜δšœ/˜5K˜—Kšœœ˜!šœœ˜:Kš˜Kšœœ˜šœ˜"K˜/—Kš œ ˜Kšœ˜—Kšœ˜—šœ˜Kšœœ˜ šœ˜%Kš˜K˜.Kšœœ˜šœ˜"K˜.—Kš œ ˜Kšœ˜—Kšœ˜——K˜-Kšœ˜Kšœ˜—KšœŸ˜——K˜˜šž œ œœŸ˜2K˜šœŸ ˜K˜$šœ(œœ˜SKšœ7™7Kšœœ˜KšœœŸ˜HKšœœ/˜NKšœ˜—Kšœ˜—KšœŸ˜˜K˜—šžœœ œ ˜HKšœœœ˜K˜'Kš œ˜KšœŸ˜K˜———˜šžœœ œ ˜DKšœœœ˜šž œ œ˜Kšœ.œ˜6Kšœ œŸ˜CKš žœœœœœœ˜DK˜CK˜KšœŸ˜—K˜šœŸ ˜ šœœ˜šœœœŸ ˜XKšœœ˜šœœ˜&Kšœœœœ˜3—Kšœ˜—˜šœœ˜Kšœœ˜š œœœ#œ˜EKšœ)˜.K˜?K˜.Kšœ%œ˜+K˜"K˜*Kšœ˜—Kš œœœœœ˜MKšœ˜——šœœ˜K˜>K˜GKšœ(œŸ ˜PKšœŸ˜Kšœ˜—šœ˜Kšœœ$ œ˜OKšœœœ˜6K˜#K˜+KšœœœŸ ˜BKšœ˜Kšœœ˜#Kšœœ˜>Kšœ(œ˜.Kšœœ'˜MKšœ˜Kšœ ˜Kšœœ˜Kšœ˜—šœœ'˜BKšœ%œ˜+šœœ+œ ˜{šœ"˜6Kšœœœ˜3K˜ K˜(KšœœœŸ ˜?Kšœ˜Kšœœ˜ Kšœœ˜<šœ˜"K˜(—Kš˜—š ˜ Kšœ%œ˜*šœ˜"K˜5—Kšœ˜—Kšœ˜—Kšœœ˜Kšœ˜—KšœœŸ%˜Cšœ˜#šœœ˜$Kšœœœœ˜4—Kšœ˜—Kšœ˜—Kšœ ˜Kšœ˜—KšœŸ˜——K˜˜šž œœ œ˜?K˜JKšœœ˜.K˜RKšœ2œœœ˜GKšœœ˜K˜Kšœœ ˜šœ ˜%Kšœ$œ.œ œ&˜žKšœ˜—Kšœœ˜ KšœŸ˜——K˜˜šž œ œ˜Kšœœœ˜ Kšœ& œ#™R——˜K˜'K˜ —˜Kšœ™K™Kšœœ ˜?Kš œŸ"˜9KšœŸ˜ K˜—šœŸ˜ K˜—Kšœ˜˜bK˜u—˜…K˜[—šœ@œ˜UK˜R—˜“Kšœ5œ5˜m—˜@K˜?—Kšœ₯œ˜ΌK˜—…—<P£