DIRECTORY Basics USING [BITAND, BITNOT, BITOR, LowHalf], BasicTime USING [Now], FileNames USING [ConvertToSlashFormat, ResolveRelativePath], FS USING [BytesForPages, Close, Create, Delete, Error, ExpandName, nullOpenFile, Open, OpenFile, Read, SetByteCountAndCreatedTime, Write], IO USING [int, PutF, PutRope, STREAM], Jukebox, Rope USING [Equal, ROPE], VM USING [AddressForPageNumber, Allocate, Free, Interval, wordsPerPage]; JukeboxImpl: MONITOR LOCKS self.LOCK USING self: Jukebox.Handle IMPORTS Basics, BasicTime, FileNames, FS, IO, Rope, VM EXPORTS Jukebox SHARES Jukebox = BEGIN OPEN Jukebox; instances: LIST OF Jukebox.Handle _ NIL; FormatErrorMsg: Rope.ROPE = "Internal format error in jukebox."; OpenErrorMsg: Rope.ROPE = "No jukebox open."; BadTuneErrorMsg: Rope.ROPE = "Bad tune id."; BadTunePointerMsg: Rope.ROPE = "Bad tune pointer."; HeaderPageNumber: Jukebox.PageNumber = 1; FirstMapPageNumber: Jukebox.PageNumber = 2; Bits: ARRAY[0..15] OF CARDINAL = [1,2,4,10B,20B,40B,100B,200B,400B,1000B,2000B, 4000B,10000B,20000B,40000B,100000B]; BitsInUse: CARDINAL = 177777B; Error: PUBLIC ERROR[reason: ErrorCode, rope: Rope.ROPE] = CODE; MissingChirp: PUBLIC ERROR = CODE; EOF: PUBLIC ERROR = CODE; SHORT: PROC [l: LONG UNSPECIFIED] RETURNS [UNSPECIFIED] = { RETURN[Basics.LowHalf[l]]; }; CreateJukebox: PUBLIC PROC [name: Rope.ROPE, nPages: INT, nTunes: INT] = { space: VM.Interval; -- Holds jukebox disk block. locWindow: Jukebox.WindowOrigin; -- Used to initialize jukebox. h: LONG POINTER TO JukeboxHeader; m: LONG POINTER TO TuneBitMap; d: LONG POINTER TO TuneDescriptor; f: LONG POINTER TO FreeList; nChirps, nMaps: INT; firstChirp: INT; t1, t2: INTEGER; fullFName: Rope.ROPE; errorMsg: Rope.ROPE _ NIL; self: Jukebox.Handle _ NEW[Jukebox.JukeboxObject]; nMaps _ (nTunes + 16*bitMapSize - 1)/(16*bitMapSize); firstChirp _ 2 + nMaps + 2*nTunes; nChirps _ (nPages - firstChirp)/pagesPerChirp; IF nChirps < 1 THEN ERROR Error[TooManyTunes, "Too many tunes requested for jukebox size."]; name _ FileNames.ResolveRelativePath[name]; fullFName _ FS.ExpandName[name ! FS.Error => IF error.group = $user THEN { errorMsg _ error.explanation; CONTINUE; }].fullFName; IF errorMsg # NIL THEN ERROR Error[reason: NoJukebox, rope: errorMsg]; locWindow.file _ FS.nullOpenFile; locWindow.file _ FS.Open[name: fullFName, lock: $write, wDir: NIL ! FS.Error => { SELECT error.group FROM $lock => ERROR Error[reason: JukeboxOpen, rope: error.explanation]; ENDCASE => CONTINUE; }]; IF locWindow.file # FS.nullOpenFile THEN { FS.Close[locWindow.file]; ERROR Error[reason: JukeboxOpen, rope: "Cannot creat jukebox, it already exists"]; }; locWindow.file _ FS.Create[name: fullFName, pages: nPages, setKeep: TRUE, keep: 1, wDir: NIL]; FS.SetByteCountAndCreatedTime[file: locWindow.file, bytes: FS.BytesForPages[nPages], created: BasicTime.Now[]]; space _ VM.Allocate[count: 1]; h _ LOOPHOLE[VM.AddressForPageNumber[page: space.page]]; m _ LOOPHOLE[h]; d _ LOOPHOLE[h]; f _ LOOPHOLE[h]; h.magic _ magicJukeboxHeader; h.nTunes _ nTunes; h.nFreeChirps _ h.nChirps _ nChirps; h.freeListHead _ h.firstChirp _ firstChirp; locWindow.base _ HeaderPageNumber; CopyOut[space, locWindow]; FOR i: INTEGER IN [0..bitMapSize) DO m.bits[i] _ 0 ENDLOOP; m.magic _ magicTuneBitMap; FOR i: INT IN [0..nMaps) DO locWindow.base _ FirstMapPageNumber + i; CopyOut[space, locWindow]; ENDLOOP; t1 _ SHORT[nMaps*bitMapSize*16 - nTunes]; t2 _ bitMapSize - t1/16; t1 _ t1 MOD 16; FOR i: INTEGER IN [t2..bitMapSize) DO m.bits[i] _ BitsInUse ENDLOOP; FOR i: INTEGER IN [16-t1..15] DO m.bits[t2-1] _ Basics.BITOR[m.bits[t2-1], Bits[i]]; ENDLOOP; CopyOut[space, locWindow]; d.magic _ magicTuneHeader; d.state _ available; d.size _ 0; FOR i: INTEGER IN [0..nHdrChirps) DO d.chirps[i] _ 0 ENDLOOP; d.indirectChirp _ 0; d.deepChirp _ 0; d.openCount _ 0; FOR i: INT IN [0..nTunes) DO d.tuneId _ locWindow.base _ 2*i + 2 + nMaps; CopyOut[space, locWindow]; ENDLOOP; f.magic _ magicFreeList; f.nChirps _ freeListSize; FOR i: INTEGER IN [0..freeListSize) DO f.chirps[i] _ firstChirp + pagesPerChirp*i; ENDLOOP; f.next _ f.chirps[freeListSize-1] + pagesPerChirp; locWindow.base _ firstChirp; CopyOut[space, locWindow]; FOR i: INT IN [1..(nChirps + freeListSize - 1)/freeListSize) DO FOR j: INTEGER IN [0..freeListSize) DO f.chirps[j] _ f.chirps[j] + freeListSize*pagesPerChirp; ENDLOOP; f.next _ f.next + freeListSize*pagesPerChirp; locWindow.base _ firstChirp + i*freeListSize*pagesPerChirp; CopyOut[space, locWindow]; ENDLOOP; f.nChirps _ SHORT[nChirps MOD freeListSize]; f.next _ 0; IF f.nChirps = 0 THEN f.nChirps _ freeListSize; CopyOut[space, locWindow]; VM.Free[space]; FS.Close[locWindow.file]; locWindow.file _ FS.nullOpenFile; }; DeleteJukebox: PUBLIC PROC [name: Rope.ROPE] = { fullFName: Rope.ROPE; errorMsg: Rope.ROPE _ NIL; name _ FileNames.ResolveRelativePath[name]; fullFName _ FS.ExpandName[name ! FS.Error => IF error.group = $user THEN { errorMsg _ error.explanation; CONTINUE; }].fullFName; IF errorMsg # NIL THEN ERROR Error[reason: NoJukebox, rope: errorMsg]; IF FindJukebox[fullFName] # NIL THEN ERROR Error[reason: JukeboxOpen, rope: "Cannot delete an open jukebox"]; FS.Delete[name: fullFName, wDir: NIL]; }; FindJukebox: PUBLIC PROC [name: Rope.ROPE] RETURNS [Jukebox.Handle] = { fullFName: Rope.ROPE; errorMsg: Rope.ROPE _ NIL; name _ FileNames.ResolveRelativePath[name]; fullFName _ FS.ExpandName[name ! FS.Error => IF error.group = $user THEN { errorMsg _ error.explanation; CONTINUE; }].fullFName; IF errorMsg # NIL THEN ERROR Error[reason: NoJukebox, rope: errorMsg]; fullFName _ FileNames.ConvertToSlashFormat[fullFName]; FOR l: LIST OF Jukebox.Handle _ instances, l.rest WHILE l # NIL DO IF Rope.Equal[l.first.jukeboxName, fullFName, FALSE] THEN RETURN[l.first]; ENDLOOP; RETURN[NIL]; }; OpenJukebox: PUBLIC PROC [name: Rope.ROPE] RETURNS [Jukebox.Handle] = { fullFName: Rope.ROPE; errorMsg: Rope.ROPE _ NIL; self: Jukebox.Handle _ NEW[Jukebox.JukeboxObject]; name _ FileNames.ResolveRelativePath[name]; fullFName _ FS.ExpandName[name ! FS.Error => IF error.group = $user THEN { errorMsg _ error.explanation; CONTINUE; }].fullFName; IF errorMsg # NIL THEN ERROR Error[reason: NoJukebox, rope: errorMsg]; fullFName _ FileNames.ConvertToSlashFormat[fullFName]; IF FindJukebox[fullFName] # NIL THEN ERROR Error[reason: JukeboxOpen, rope: "Jukebox already open"]; self.window.file _ FS.Open[name: fullFName, lock: $write, wDir: NIL ! FS.Error => { SELECT error.group FROM $user => ERROR Error[reason: NoJukebox, rope: error.explanation]; $lock => ERROR Error[reason: JukeboxOpen, rope: error.explanation]; ENDCASE => REJECT; }]; self.jukeboxName _ fullFName; OpenLocked[self]; RETURN[self]; }; OpenLocked: ENTRY PROC [self: Jukebox.Handle] = { ourhdr: LONG POINTER TO JukeboxHeader _ NIL; ourfree: LONG POINTER TO FreeList _ NIL; { ENABLE UNWIND => { IF ourhdr # NIL THEN VM.Free[self.hdrSpace]; IF ourfree # NIL THEN VM.Free[self.freeSpace]; }; self.hdrSpace _ VM.Allocate[count: 1]; self.freeSpace _ VM.Allocate[count: 1]; ourhdr _ LOOPHOLE[VM.AddressForPageNumber[self.hdrSpace.page]]; ourfree _ LOOPHOLE[VM.AddressForPageNumber[self.freeSpace.page]]; self.window.base _ HeaderPageNumber; CopyIn[self.hdrSpace, self.window]; IF ourhdr.magic # magicJukeboxHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; self.currentMapPageNumber _ FirstMapPageNumber; self.window.base _ self.currentFreeChirpNumber _ ourhdr.freeListHead; IF self.currentFreeChirpNumber # 0 THEN { CopyIn[self.freeSpace, self.window]; IF ourfree.magic # magicFreeList THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; } ELSE { ourfree.magic _ magicFreeList; ourfree.nChirps _ 0; ourfree.next _ 0; }; self.maxMapPageNumber _ FirstMapPageNumber + (ourhdr.nTunes/(16*bitMapSize)); self.firstDesPageNumber _ self.maxMapPageNumber + 1; self.openTuneHeaderList _ NIL; ourhdr.freeListHead _ ourfree.next; ourhdr.nFreeChirps _ ourhdr.nFreeChirps - ourfree.nChirps; self.window.base _ HeaderPageNumber; CopyOut[self.hdrSpace, self.window]; self.hdr _ ourhdr; self.free _ ourfree; instances _ CONS[self, instances]; }}; Info: PUBLIC ENTRY PROC [self: Jukebox.Handle] RETURNS [name: Rope.ROPE, nPages, nTunes: INT] = { ENABLE UNWIND => NULL; loc: Rope.ROPE; loc _ self.jukeboxName; IF self.hdr = NIL THEN RETURN[name: NIL, nPages: 0, nTunes: 0]; RETURN [name: loc, nPages: self.hdr.nChirps*pagesPerChirp + self.hdr.firstChirp, nTunes: self.hdr.nTunes]; }; CloseJukebox: PUBLIC ENTRY PROC [self: Jukebox.Handle] RETURNS [Jukebox.Handle] = { ENABLE UNWIND => NULL; IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg]; WHILE self.openTuneHeaderList # NIL DO self.openTuneHeaderList.openCount _ 1; InternalCloseTune[self, self.openTuneHeaderList] ENDLOOP; self.hdr.nFreeChirps _ self.hdr.nFreeChirps + self.free.nChirps; IF self.free.nChirps # 0 THEN { self.free.next _ self.hdr.freeListHead; self.hdr.freeListHead _ self.window.base _ self.currentFreeChirpNumber; CopyOut[self.freeSpace, self.window]; }; self.window.base _ HeaderPageNumber; CopyOut[self.hdrSpace, self.window]; VM.Free[self.freeSpace]; self.free _ NIL; VM.Free[self.hdrSpace]; self.hdr _ NIL; FS.Close[self.window.file]; self.window.file _ FS.nullOpenFile; instances _ DRemove[ref: self, list: instances]; RETURN[NIL]; }; DRemove: INTERNAL PROC [ref: Jukebox.Handle, list: LIST OF Jukebox.Handle] RETURNS[LIST OF Jukebox.Handle] = { l, l1: LIST OF Jukebox.Handle _ NIL; l _ list; UNTIL l = NIL DO IF l.first = ref THEN { IF l1 = NIL THEN RETURN[l.rest]; -- ref was first object on list l1.rest _ l.rest; RETURN[list]; }; l1 _ l; l _ l.rest; ENDLOOP; RETURN [list]; }; -- of Dremove; CreateTune: PUBLIC ENTRY PROC [self: Jukebox.Handle, tuneId: INT _ -1] RETURNS [Tune] = { ENABLE UNWIND => NULL; tune: Tune _ NIL; word, bit: INTEGER; mapPage: INT; mapSpace: VM.Interval; map: LONG POINTER TO TuneBitMap _ NIL; tuneSpace: VM.Interval; {ENABLE UNWIND => { IF map # NIL THEN VM.Free[mapSpace]; IF tune # NIL THEN VM.Free[tuneSpace]; }; IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg]; mapSpace _ VM.Allocate[count: 1]; map _ LOOPHOLE[VM.AddressForPageNumber[mapSpace.page]]; tuneSpace _ VM.Allocate[count: 1]; tune _ LOOPHOLE[VM.AddressForPageNumber[tuneSpace.page]]; IF tuneId >= 0 THEN { IF tuneId >= self.hdr.nTunes THEN ERROR Error[BadTune, BadTuneErrorMsg]; IF searchTuneCache[self, tuneId] # NIL THEN ERROR Error[TuneLocked, "Tune is locked."]; self.window.base _ desPage[self, tuneId]; CopyIn[space: tuneSpace, window: self.window]; IF tune.magic # magicTuneHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; tune.tuneId _ tuneId; tune.space _ tuneSpace; IF tune.state = inUse THEN killTune[self: self, tune: tune, updateMap: FALSE] ELSE { self.window.base _ FirstMapPageNumber + tuneId/(bitMapSize*16); CopyIn[space: mapSpace, window: self.window]; IF map.magic # magicTuneBitMap THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; word _ SHORT[tuneId MOD (bitMapSize*16)]; bit _ word MOD 16; word _ word/16; map.bits[word] _ Basics.BITOR[map.bits[word], Bits[bit]]; CopyOut[mapSpace, self.window]; }; } ELSE { mapPage _ self.window.base _ self.currentMapPageNumber; CopyIn[mapSpace, self.window]; IF map.magic # magicTuneBitMap THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; WHILE TRUE DO FOR i: INTEGER IN [0..bitMapSize) DO IF map.bits[i] # 177777B THEN { FOR j: INTEGER IN [0..16) DO IF Basics.BITAND[map.bits[i], Bits[j]] = 0 THEN { map.bits[i] _ Basics.BITOR[map.bits[i], Bits[j]]; self.window.base _ mapPage; CopyOut[mapSpace, self.window]; tuneId _ (mapPage-FirstMapPageNumber)*16*bitMapSize + i*16 + j; self.window.base _ desPage[self, tuneId]; CopyIn[tuneSpace, self.window]; IF tune.magic # magicTuneHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; IF tune.state = available THEN GOTO gotTune; }; ENDLOOP; }; ENDLOOP; mapPage _ mapPage+1; IF mapPage > self.maxMapPageNumber THEN mapPage _ FirstMapPageNumber; IF mapPage = self.currentMapPageNumber THEN ERROR Error[NoTunesAvailable, "All tune headers in use."]; self.window.base _ mapPage; CopyIn[mapSpace, self.window]; IF map.magic # magicTuneBitMap THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; REPEAT gotTune => self.currentMapPageNumber _ mapPage; ENDLOOP; }; tune.state _ inUse; tune.createDate _ tune.appendDate _ tune.playDate _ BasicTime.Now[]; tune.size _ 0; FOR i: INTEGER IN [0..nHdrChirps) DO tune.chirps[i] _ 0; ENDLOOP; tune.indirectChirp _ tune.deepChirp _ 0; tune.tuneId _ tuneId; tune.openCount _ 1; tune.next _ self.openTuneHeaderList; tune.space _ tuneSpace; tune.writable _ TRUE; tune.kill _ FALSE; tune.deepCache _ tune.indirectCache _ NIL; self.window.base _ desPage[self, tuneId]; CopyOut[tuneSpace, self.window]; self.openTuneHeaderList _ tune; RETURN [tune]; }}; DeleteTune: PUBLIC ENTRY PROC [self: Jukebox.Handle, tuneId: INT] = { ENABLE UNWIND => NULL; tuneSpace: VM.Interval; tune: Tune _ NIL; {ENABLE UNWIND => { IF tune # NIL THEN VM.Free[tuneSpace]; }; IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg]; IF (tuneId >= self.hdr.nTunes) OR (tuneId < 0) THEN ERROR Error[BadTune, BadTuneErrorMsg]; tuneSpace _ VM.Allocate[count: 1]; tune _ searchTuneCache[self, tuneId]; IF tune = NIL THEN { tune _ LOOPHOLE[VM.AddressForPageNumber[tuneSpace.page]]; self.window.base _ desPage[self, tuneId]; CopyIn[tuneSpace, self.window]; tune.tuneId _ tuneId; tune.openCount _ 0; tune.next _ NIL; tune.space _ tuneSpace; tune.writable _ tune.kill _ FALSE; }; IF tune.magic # magicTuneHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; IF tune.state # inUse THEN ERROR Error[BadTune, BadTuneErrorMsg]; tune.kill _ TRUE; IF tune.openCount = 0 THEN killTune[self, tune]; VM.Free[tuneSpace]; }}; killTune: INTERNAL PROC [self: Jukebox.Handle, tune: Tune, updateMap: BOOL _ TRUE] = { mapSpace: VM.Interval; map: LONG POINTER TO TuneBitMap _ NIL; deepSpace: VM.Interval; deepList: LONG POINTER TO ChirpList _ NIL; word, bit: INTEGER; {ENABLE UNWIND => IF map # NIL THEN VM.Free[mapSpace]; tune.state _ available; tune.openCount _ 0; tune.kill _ FALSE; self.window.base _ desPage[self, tune.tuneId]; CopyOut[tune.space, self.window]; FOR i: INTEGER IN [0..nHdrChirps) DO IF tune.chirps[i] # 0 THEN FreeChirp[self, tune.chirps[i]]; ENDLOOP; IF tune.indirectChirp # 0 THEN FreeIndirect[self, tune.indirectChirp]; IF tune.deepChirp # 0 THEN { ENABLE UNWIND => IF deepList # NIL THEN VM.Free[deepSpace]; deepSpace _ VM.Allocate[count: pagesPerChirp]; deepList _ LOOPHOLE[VM.AddressForPageNumber[deepSpace.page]]; self.window.base _ tune.deepChirp; CopyIn[space: deepSpace, window: self.window]; IF deepList.magic # magicDeepList THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; FOR i: INTEGER IN [0..chirpListSize) DO IF deepList.chirps[i] # 0 THEN FreeIndirect[self, deepList.chirps[i]]; ENDLOOP; FreeChirp[self, tune.deepChirp]; VM.Free[deepSpace]; }; IF NOT updateMap THEN RETURN; mapSpace _ VM.Allocate[count: 1]; map _ LOOPHOLE[VM.AddressForPageNumber[mapSpace.page]]; self.window.base _ FirstMapPageNumber + tune.tuneId/(bitMapSize*16); CopyIn[mapSpace, self.window]; word _ SHORT[tune.tuneId MOD (bitMapSize*16)]; bit _ word MOD 16; word _ word/16; map.bits[word] _ Basics.BITAND[map.bits[word], Basics.BITNOT[Bits[bit]]]; CopyOut[mapSpace, self.window]; VM.Free[mapSpace]; }}; OpenTune: PUBLIC ENTRY PROC [self: Jukebox.Handle, tuneId: INT, write: BOOL] RETURNS [Tune] = { ENABLE UNWIND => NULL; tune: Tune _ NIL; tuneSpace: VM.Interval; {ENABLE UNWIND => { IF tune # NIL THEN VM.Free[tuneSpace]; }; IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg]; IF (tuneId >= self.hdr.nTunes) OR (tuneId < 0) THEN ERROR Error[BadTune, BadTuneErrorMsg]; tuneSpace _ VM.Allocate[count: 1]; tune _ searchTuneCache[self, tuneId]; IF tune # NIL THEN { IF tune.kill THEN ERROR Error[BadTune, BadTuneErrorMsg]; IF tune.writable OR write THEN ERROR Error[TuneLocked, "Tune is locked."]; tune.openCount _ tune.openCount + 1; VM.Free[tuneSpace]; RETURN [tune]; }; tune _ LOOPHOLE[VM.AddressForPageNumber[tuneSpace.page]]; self.window.base _ desPage[self, tuneId]; CopyIn[tuneSpace, self.window]; IF tune.magic # magicTuneHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; IF tune.state # inUse THEN ERROR Error[BadTune, BadTuneErrorMsg]; tune.tuneId _ tuneId; tune.openCount _ 1; tune.next _ self.openTuneHeaderList; tune.space _ tuneSpace; tune.writable _ write; tune.kill _ FALSE; tune.deepCache _ tune.indirectCache _ NIL; self.openTuneHeaderList _ tune; RETURN [tune]; }}; CloseTune: PUBLIC ENTRY PROC [self: Jukebox.Handle, tune: Tune] = { ENABLE UNWIND => NULL; InternalCloseTune[self, tune]; }; InternalCloseTune: INTERNAL PROC [self: Jukebox.Handle, tune: Tune] = { tuneptr: LONG POINTER TO Tune; tuneSpace: VM.Interval; IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg]; IF tune.magic # magicTuneHeader THEN ERROR Error[BadTunePointer, BadTunePointerMsg]; IF tune.magic # magicTuneHeader THEN ERROR Error[BadTune, BadTuneErrorMsg]; tune.openCount _ tune.openCount - 1; IF tune.openCount = 0 THEN { IF tune.writable THEN tune.appendDate _ BasicTime.Now[] ELSE tune.playDate _ BasicTime.Now[]; IF tune.deepCache # NIL THEN { IF tune.deepCache.dirty THEN { self.window.base _ tune.deepChirp; CopyOut[space: tune.deepSpace, window: self.window]; }; VM.Free[tune.deepSpace]; tune.deepCache _ NIL; }; IF tune.indirectCache # NIL THEN { IF tune.indirectCache.dirty THEN { self.window.base _ tune.indirectCache.page; CopyOut[space: tune.indirectSpace, window: self.window]; }; VM.Free[tune.indirectSpace]; tune.indirectCache _ NIL; }; tuneSpace _ tune.space; tuneptr _ @self.openTuneHeaderList; WHILE tuneptr^ # tune DO IF tuneptr^ = NIL THEN ERROR Error[Bug, "Unexpected tune list end."]; tuneptr _ @(tuneptr^.next); ENDLOOP; tuneptr^ _ tune.next; IF tune.kill THEN killTune[self, tune] ELSE { self.window.base _ desPage[self, tune.tuneId]; CopyOut[tuneSpace, self.window]; }; tune.magic _ 0; VM.Free[tuneSpace]; }; }; AllocChirp: ENTRY PROC [self: Jukebox.Handle] RETURNS [Jukebox.PageNumber] = { ENABLE UNWIND => NULL; chirp: Jukebox.PageNumber; WHILE self.free.nChirps = 0 DO IF self.free.next = 0 THEN ERROR Error[NoFreeChirps, "No free chirps left."]; self.window.base _ self.currentFreeChirpNumber _ self.free.next; CopyIn[space: self.freeSpace, window: self.window]; IF self.free.magic # magicFreeList THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; self.hdr.freeListHead _ self.free.next; self.hdr.nFreeChirps _ self.hdr.nFreeChirps - self.free.nChirps; self.window.base _ HeaderPageNumber; CopyOut[space: self.hdrSpace, window: self.window]; ENDLOOP; self.free.nChirps _ self.free.nChirps-1; chirp _ self.free.chirps[self.free.nChirps]; RETURN [chirp]; }; AllocateIndirect: PROC [self: Jukebox.Handle, magic: INTEGER] RETURNS [Jukebox.PageNumber] = { page: Jukebox.PageNumber; space: VM.Interval; chirpList: LONG POINTER TO ChirpList; {ENABLE UNWIND => IF chirpList # NIL THEN VM.Free[space]; page _ AllocChirp[self]; space _ VM.Allocate[count: pagesPerChirp]; chirpList _ LOOPHOLE[VM.AddressForPageNumber[space.page]]; chirpList.magic _ magic; FOR j: INTEGER IN [0..chirpListSize) DO chirpList.chirps[j] _ 0 ENDLOOP; chirpList.dirty _ FALSE; self.window.base _ page; CopyOut[space: space, window: self.window]; VM.Free[space]; RETURN [page]; }}; FreeChirp: PROC [self: Jukebox.Handle, chirp: Jukebox.PageNumber] = { ENABLE UNWIND => NULL; IF self.free.nChirps = freeListSize THEN { self.window.base _ self.currentFreeChirpNumber; CopyOut[space: self.freeSpace, window: self.window]; self.free.next _ self.currentFreeChirpNumber; self.free.nChirps _ 0; self.hdr.freeListHead _ self.free.next; self.hdr.nFreeChirps _ self.hdr.nFreeChirps + freeListSize; self.window.base _ HeaderPageNumber; CopyOut[space: self.hdrSpace, window: self.window]; }; IF self.free.nChirps = 0 THEN self.currentFreeChirpNumber _ chirp; self.free.chirps[self.free.nChirps] _ chirp; self.free.nChirps _ self.free.nChirps+1; }; FreeIndirect: PROC [self: Jukebox.Handle, page: Jukebox.PageNumber] = { indirectSpace: VM.Interval; chirpList: LONG POINTER TO ChirpList _ NIL; {ENABLE UNWIND => IF chirpList # NIL THEN VM.Free[indirectSpace]; indirectSpace _ VM.Allocate[count: pagesPerChirp]; chirpList _ LOOPHOLE[VM.AddressForPageNumber[indirectSpace.page]]; self.window.base _ page; CopyIn[space: indirectSpace, window: self.window]; IF chirpList.magic # magicChirpList THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; FOR i: INTEGER IN [0..chirpListSize) DO IF chirpList.chirps[i] # 0 THEN FreeChirp[self, chirpList.chirps[i]]; ENDLOOP; FreeChirp[self, page]; VM.Free[indirectSpace]; }}; FindChirp: PUBLIC PROC [self: Jukebox.Handle, tune: Tune, chirp: INT, signalMissingChirp: BOOL _ FALSE, signalEOF: BOOL _ FALSE] RETURNS [Jukebox.WindowOrigin] = { index, indirect: INTEGER; chirpWindow: Jukebox.WindowOrigin; IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg]; IF tune.magic # magicTuneHeader THEN ERROR Error[BadTunePointer, BadTunePointerMsg]; IF (tune.size <= chirp) THEN { IF signalEOF THEN ERROR EOF[]; tune.size _ chirp+1; }; chirpWindow.file _ self.window.file; IF chirp < nHdrChirps THEN { index _ SHORT[chirp]; IF tune.chirps[index] = 0 THEN { IF signalMissingChirp THEN ERROR MissingChirp[]; tune.chirps[index] _ AllocChirp[self]; }; chirpWindow.base _ tune.chirps[index]; RETURN [chirpWindow]; }; chirp _ chirp - nHdrChirps; index _ SHORT[chirp MOD chirpListSize]; chirp _ chirp/chirpListSize; IF chirp > chirpListSize THEN ERROR Error[TuneTooLarge, "Can't have tune that large."]; indirect _ SHORT[chirp]; IF (indirect # tune.indirect) OR (tune.indirectCache = NIL) THEN { IF tune.indirectCache = NIL THEN { tune.indirectSpace _ VM.Allocate[count: pagesPerChirp]; tune.indirectCache _ LOOPHOLE[VM.AddressForPageNumber[tune.indirectSpace.page]]; tune.indirectCache.dirty _ FALSE; tune.indirect _ -1; }; IF tune.indirectCache.dirty THEN { tune.indirectCache.dirty _ FALSE; self.window.base _ tune.indirectCache.page; CopyOut[space: tune.indirectSpace, window: self.window]; }; IF indirect = 0 THEN { IF tune.indirectChirp = 0 THEN { IF signalMissingChirp THEN ERROR MissingChirp[]; tune.indirectChirp _ AllocateIndirect[self, magicChirpList]; }; self.window.base _ tune.indirectChirp; } ELSE { IF (tune.deepCache = NIL) THEN { IF tune.deepChirp = 0 THEN { IF signalMissingChirp THEN ERROR MissingChirp[]; tune.deepChirp _ AllocateIndirect[self, magicDeepList]; }; self.window.base _ tune.deepChirp; tune.deepSpace _ VM.Allocate[count: pagesPerChirp]; tune.deepCache _ LOOPHOLE[VM.AddressForPageNumber[tune.deepSpace.page]]; CopyIn[space: tune.deepSpace, window: self.window]; }; IF tune.deepCache.magic # magicDeepList THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; IF tune.deepCache.chirps[indirect-1] = 0 THEN { IF signalMissingChirp THEN ERROR MissingChirp[]; tune.deepCache.chirps[indirect-1] _ AllocateIndirect[self, magicChirpList]; tune.deepCache.dirty _ TRUE; }; self.window.base _ tune.deepCache.chirps[indirect-1]; }; CopyIn[space: tune.indirectSpace, window: self.window]; tune.indirectCache.page _ self.window.base; tune.indirect _ indirect; }; IF tune.indirectCache.magic # magicChirpList THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; IF tune.indirectCache.chirps[index] = 0 THEN { IF signalMissingChirp THEN ERROR MissingChirp[]; tune.indirectCache.chirps[index] _ AllocChirp[self]; tune.indirectCache.dirty _ TRUE; }; chirpWindow.base _ tune.indirectCache.chirps[index]; RETURN [chirpWindow]; }; FindClientSpace: PUBLIC PROC [self: Jukebox.Handle, tuneId: INT] RETURNS [clientWindow: Jukebox.WindowOrigin] = { IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg]; IF (tuneId >= self.hdr.nTunes) OR (tuneId < 0) THEN ERROR Error[BadTune, BadTuneErrorMsg]; clientWindow.file _ self.window.file; clientWindow.base _ desPage[self, tuneId] + 1; }; desPage: PROC [self: Jukebox.Handle, tuneId: INT] RETURNS [INT] = INLINE { RETURN[self.firstDesPageNumber + 2*tuneId]; }; searchTuneCache: INTERNAL PROC [self: Jukebox.Handle, tuneId: INT] RETURNS [Tune] = { tune: Tune; tune _ self.openTuneHeaderList; WHILE tune # NIL DO IF tune.tuneId = tuneId THEN RETURN [tune]; tune _ tune.next; ENDLOOP; RETURN [NIL]; }; Scavenge: PUBLIC ENTRY PROC [self: Jukebox.Handle, stream: IO.STREAM, makeFixes: BOOL] RETURNS [nFree: INT, recovered: INT] = { ENABLE UNWIND => NULL; varArray: TYPE = ARRAY [0..0) OF INT; chirpMap: LONG POINTER TO varArray _ NIL; chirpSpace: VM.Interval; tuneId: INT; tuneSpace, indirectSpace, deepSpace: VM.Interval; tune: Tune _ NIL; indirect: LONG POINTER TO ChirpList _ NIL; deep: LONG POINTER TO ChirpList _ NIL; index: INT; oldNFree, newNFree: INT; badTunes: BOOL _ FALSE; unused: INT _ -1; gcIndex: PROC [chirp: INT] RETURNS [INT] = { result: INT; chirp _ chirp-self.hdr.firstChirp; result _ chirp/pagesPerChirp; IF (result >= self.hdr.nChirps) OR (result < 0) THEN ERROR Error[BadChirpPointer, "Bad chirp pointer."]; IF (result*pagesPerChirp) # chirp THEN ERROR Error[BadChirpPointer, "Bad chirp pointer."]; RETURN[result]; }; gcIndirect: PROC [tuneId: INT, chirp: INT, magic: INTEGER] = { index: INT; IF chirp = 0 THEN RETURN; self.window.base _ chirp; CopyIn[space: indirectSpace, window: self.window]; IF indirect.magic # magic THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; FOR i:INTEGER IN [0..chirpListSize) DO IF indirect.chirps[i] # 0 THEN { index _ gcIndex[indirect.chirps[i]]; IF chirpMap[index] = unused THEN chirpMap[index] _ tuneId ELSE { IO.PutRope[stream, "Indirect/deep pointer is duplicate:\n"]; IO.PutF[stream, " Chirp: %d, 1st tune: %d, 2nd tune: %d.\n", IO.int[indirect.chirps[i]], IO.int[chirpMap[index]], IO.int[tuneId]]; IF makeFixes THEN { IO.PutRope[stream, " Chirp deleted from 2nd tune.\n"]; indirect.chirps[i] _ 0; CopyOut[space: indirectSpace, window: self.window]; }; }; }; ENDLOOP; }; {ENABLE UNWIND => { IF indirect # NIL THEN VM.Free[indirectSpace]; IF deep # NIL THEN VM.Free[deepSpace]; IF tune # NIL THEN VM.Free[tuneSpace]; IF chirpMap # NIL THEN VM.Free[chirpSpace]; }; IF self.hdr = NIL THEN ERROR Error[NoJukebox, OpenErrorMsg]; IF self.openTuneHeaderList # NIL THEN ERROR Error[TunesOpen, "Can't scavenge: tune(s) open"]; chirpSpace _ VM.Allocate[count: ((self.hdr.nChirps * SIZE[INT]) / VM.wordsPerPage) + 1]; chirpMap _ LOOPHOLE[VM.AddressForPageNumber[chirpSpace.page]]; FOR i: INT IN [0..self.hdr.nChirps) DO chirpMap[i] _ unused ENDLOOP; tuneSpace _ VM.Allocate[count: 1]; tune _ LOOPHOLE[VM.AddressForPageNumber[tuneSpace.page]]; indirectSpace _ VM.Allocate[count: pagesPerChirp]; indirect _ LOOPHOLE[VM.AddressForPageNumber[indirectSpace.page]]; deepSpace _ VM.Allocate[count: pagesPerChirp]; deep _ LOOPHOLE[VM.AddressForPageNumber[deepSpace.page]]; FOR tuneId IN [0..self.hdr.nTunes) DO ENABLE { Error => { IO.PutRope[stream, rope]; IO.PutF[stream, " (in tune %d)\n", IO.int[tuneId]]; IF makeFixes THEN { IO.PutRope[stream, " Tune is being deleted"]; IO.PutRope[stream, " (some free chirps may not be recovered).\n"]; tune.magic _ magicTuneHeader; tune.state _ available; self.window.base _ desPage[self, tuneId]; CopyOut[space: tuneSpace, window: self.window]; } ELSE badTunes _ TRUE; CONTINUE; }; ANY => { IO.PutF[stream, "Unknown error occurred in scavenging tune %d.\n", IO.int[tuneId]]; IF makeFixes THEN { IO.PutRope[stream, " Tune is being deleted"]; IO.PutRope[stream, " (some free chirps may not be recovered).\n"]; tune.magic _ magicTuneHeader; tune.state _ available; self.window.base _ desPage[self, tuneId]; CopyOut[space: tuneSpace, window: self.window]; } ELSE badTunes _ TRUE; CONTINUE; }; }; self.window.base _ desPage[self, tuneId]; CopyIn[space: tuneSpace, window: self.window]; IF tune.magic # magicTuneHeader THEN ERROR Error[JukeboxFormat, FormatErrorMsg]; IF tune.state = available THEN LOOP; FOR i:INTEGER IN [0..nHdrChirps) DO IF tune.chirps[i] # 0 THEN { index _ gcIndex[tune.chirps[i]]; IF chirpMap[index] = unused THEN chirpMap[index] _ tuneId ELSE { IO.PutRope[stream, "Top level pointer is duplicate:\n"]; IO.PutF[stream, " Chirp: %d, 1st tune: %d, 2nd tune: %d.\n", IO.int[tune.chirps[i]], IO.int[chirpMap[index]], IO.int[tuneId]]; IF makeFixes THEN { IO.PutRope[stream, " Chirp deleted from 2nd tune.\n"]; tune.chirps[i] _ 0; CopyOut[space: tuneSpace, window: self.window]; }; }; }; ENDLOOP; IF tune.indirectChirp # 0 THEN { index _ gcIndex[tune.indirectChirp]; IF chirpMap[index] = unused THEN chirpMap[index] _ tuneId ELSE { IO.PutRope[stream, "Tune.indirectChirp is duplicate:\n"]; IO.PutF[stream, " Chirp: %d, 1st tune: %d, 2nd tune: %d.\n", IO.int[tune.indirectChirp], IO.int[chirpMap[index]], IO.int[tuneId]]; IF makeFixes THEN { IO.PutRope[stream, " Chirp deleted from 2nd tune.\n"]; tune.indirectChirp _ 0; self.window.base _ desPage[self, tuneId]; CopyOut[space: tuneSpace, window: self.window]; }; }; gcIndirect[tuneId, tune.indirectChirp, magicChirpList]; }; IF tune.deepChirp # 0 THEN { index _ gcIndex[tune.deepChirp]; IF chirpMap[index] = unused THEN chirpMap[index] _ tuneId ELSE { IO.PutRope[stream, "Tune.deepChirp is duplicate:\n"]; IO.PutF[stream, " Chirp: %d, 1st tune: %d, 2nd tune: %d.\n", IO.int[tune.deepChirp], IO.int[chirpMap[index]], IO.int[tuneId]]; IF makeFixes THEN { IO.PutRope[stream, " Chirp deleted from 2nd tune.\n"]; tune.deepChirp _ 0; self.window.base _ desPage[self, tuneId]; CopyOut[space: tuneSpace, window: self.window]; }; }; gcIndirect[tuneId, tune.deepChirp, magicDeepList]; self.window.base _ tune.deepChirp; CopyIn[space: deepSpace, window: self.window]; FOR i: INTEGER IN [0..chirpListSize) DO gcIndirect[tuneId, deep.chirps[i], magicChirpList] ENDLOOP; }; ENDLOOP; IF badTunes THEN { IO.PutRope[stream, "Bad tunes weren't deleted, so can't scavenge free list.\n"]; RETURN [nFree: self.hdr.nFreeChirps+self.free.nChirps, recovered: 0]; }; oldNFree _ 0; newNFree _ 0; DO FOR i:INTEGER IN [0..self.free.nChirps) DO oldNFree _ oldNFree + 1; index _ gcIndex[self.free.chirps[i]]; IF chirpMap[index] # unused THEN { IO.PutRope[stream, "Free list chirp is duplicate:\n"]; IO.PutF[stream, " Chirp: %d, Tune: %d.\n", IO.int[self.free.chirps[i]], IO.int[chirpMap[index]]]; }; ENDLOOP; IF self.free.next = 0 THEN EXIT; self.currentFreeChirpNumber _ self.window.base _ self.free.next; CopyIn[space: self.freeSpace, window: self.window]; ENDLOOP; self.free.next _ 0; self.free.nChirps _ 0; self.currentFreeChirpNumber _ 0; self.hdr.nFreeChirps _ 0; FOR i:INT IN [0..self.hdr.nChirps) DO IF chirpMap[i] = unused THEN { newNFree _ newNFree + 1; FreeChirp[self, self.hdr.firstChirp + i*pagesPerChirp]; }; ENDLOOP; VM.Free[tuneSpace]; VM.Free[indirectSpace]; VM.Free[deepSpace]; chirpMap _ NIL; VM.Free[chirpSpace]; RETURN[nFree: newNFree, recovered: newNFree - oldNFree]; }}; CopyIn: PROC [space: VM.Interval, window: Jukebox.WindowOrigin] = { FS.Read[file: window.file, from: window.base, nPages: space.count, to: VM.AddressForPageNumber[space.page]]; }; CopyOut: PROC [space: VM.Interval, window: Jukebox.WindowOrigin] = { FS.Write[file: window.file, to: window.base, nPages: space.count, from: VM.AddressForPageNumber[space.page]]; }; END. bFILE: JukeboxImpl.mesa Last Edited by: Stewart, December 30, 1983 11:38 am, Cedar5 Global variables Standard error messages. Standard page numbers: Bit patterns used to manipulate the tune header bit map: Signal definitions. Utility procedures Exported procedures Allocate a space and set pointers so we can initialize the various blocks of the jukebox. Initialize the header page. Initialize the tune header bit map. In the last map page, mark tune headers after the last valid one as in use. Initialize the tune header pages. Initialize the free list. Handle the last block of the free list specially, since it may not contain a full list of chirps. Allocate spaces to cache the header block, and a free list block. Read in the header block, first bit map block, and first free list block. Advance the header to reference the NEXT free list block, and write out the header. This way, if the system crashes we may lose track of a few free blocks but will never think a block is free when it isn't. Close all of the open tunes. Update the free list and the header block. Order of write-out is important here, if the jukebox is to survive after inopportune crashes. Clear out the data structures. This procedure creates a new tune. If tuneId is zero, then this routine selects a new tuneId. If tuneId is non-zero, then the indicated tune is used: if it had previously been allocated, then its old contents are discarded. Allocate a space for a map page and for the new tune. If an explicit tune has been specified, then validate it, read it in, and delete its contents. Note: if the tune is currently in the cache, then it is locked against us. The tune wasn't previously in use. Mark the bit map to show it being used. Scan through the tune bit map, looking for a free tune. Remember that the bit map is just a HINT: the final authority is the state field in the tune header. We seem to have a tune header that is free. Rewrite the map page to mark the tune as not available, then read in the tune descriptor to make sure it really is available. If not, just keep on trying. Now we REALLY have a tune. Initialize the header, write it out again, and link it into the main-memory list. Find and validate the tune. Check on the open list before going to disk. The tune isn't in memory. Get it from disk. We've got the tune. Make sure its create date matches and that its magic number hasn't gotten trashed. IF tune.createDate # createDateTHEN ERROR Error[BadTune, BadTuneErrorMsg]; Mark the tune to be killed. If no-one is using the tune, then deallocate it. This procedure will release all the chirp space in a tune, mark the disk copy of the header as available, and update the bit map. (The bit map is only updated if updateMap is TRUE) Mark the header as available and write it out. Release all of the blocks in the tune. Now update the tune bit map to show this tune as available. Convert the user tune id into a page number that we can use internally, and make sure that it's valid. First, go through the list of open tunes to see if this one is already open. If so, just use the open tune. Note that a tune is permitted to be writable by at most one client at a time. IF tune.createDate # createDate THEN ERROR Error[BadTune, BadTuneErrorMsg]; The tune isn't in memory. Check its number, and then get it from disk. IF tune.createDate # createDate THEN ERROR Error[BadTune. BadTuneErrorMsg]; First, make sure that the tune is valid. Decrement the open count. If no-one else is using the tune then flush it from the open list and deallocate it if necessary. Kill the tune as the very last thing, if it is necessary at all. Allocates a chirp from the free list. Allocates a chirp from the free list, initializes it as an empty indirect chirp, and saves it on disk again. Returns a chirp to the free list. Note: the caller must make sure that the monitor lock is held. Write out the current free list block if it is full. Update the jukebox header. Returns all of the chirps in an indirect chirp to the free list. Sets a window to correspond to the location of a chirp in a tune. If the chirp falls past the end of the tune and signalEOF is TRUE, then EOF is signalled. If the chirp hasn't been allocated in the file, then a new chirp is allocated (if signalMissingChirp is FALSE) or a MissingChirp is raised (if signalMissingChirp is TRUE). Check for EOF and update the tune's size if necessary. Alas, there isn't a cached indirect block to make everything simple. Read in an indirect block (this may also require a deep block to be read as well). Things couldn't be worse: we'll first have to read the deep indirect block, then an indirect block. This local procedure just computes the descriptor page for a tune based on its id. This local procedure checks to see if a given tune is in our memory cache. If so, then it returns a pointer to the cached copy. If not, then it returns NIL. This routine scans the jukebox to make sure that each chirp is present in at most one place. When a chirp is found both on the free list and in a tune, or if a chirp is found in two separate tunes, a message is output to stream, and one of the instances of the chirp is deleted (if makeFixes is TRUE). If some kind of format error is discovered, a message is output and the relevant tune is deleted (if makeFixes is TRUE). If makeFixes is FALSE then no corrections are made that would cause information to be lost from a tune. In any event, when the scan is finished, the free list is regenerated and reordered. This will recover chirps that got lost. The return values give information about the state of the free list. This local procedure merely computes a chirp index from a chirp number. If the chirp is out of range for the jukebox, generate an error. This local procedure checks all the chirps referenced in an indirect block. Main drag of scavenger: For starters, allocate space for the chirp map, based on the size of the jukebox. Scan all of the tunes in the jukebox. For each tune, scan all the descriptor information, and label each chirp with the tune id. If some error occurs underneath us here, output an error message, and discard the tune if that is allowed. If we can't discard the tune, then leave a note not to reconstruct the free list, since there may be valid chirps unseen in the tune. Scan top-level chirp pointers. Scan first-level indirect pointers. Scan deep pointers. If there are bad tunes right away, quit right now without looking at the free list. Now, scan the free list to make sure that all the blocks on it are currently free. Now we have the whole chirp map set up. Just rewrite the free list in order. Release storage and quit. Last Edited by: Swinehart, March 1, 1983 2:51 pm Last Edited by: Stewart, April 19, 1983 3:03 pm, volume stuff Last Edited by: Stewart, April 20, 1983 12:02 pm, remove use of Heap.systemZone Last Edited by: Stewart, May 22, 1983 6:32 pm, add FindClientSpace December 27, 1983 12:42 pm, Stewart, Cedar 5 Ê&Á˜Jšœ™Jšœ;™;J˜šÏk ˜ Jš œœœœœ ˜.Jšœ œ˜Jšœ œ-˜™>Jšœ#™#Jšœœœ˜J˜Jšœ4™4J˜šœ"œ˜*J˜/J˜4J˜-J˜J˜Jšœ™J˜J˜'J˜;J˜$J˜3J˜J˜—Jšœœ%˜BJ˜,J˜(J˜J˜—šž œœ5˜GJšœ@™@Jšœœ ˜Jš œ œœœ œ˜+J˜Jš œœœœ œœœ˜AJ˜Jšœœ ˜2Jšœ œœ+˜BJ˜J˜2Jšœ"œœ&˜Tšœœœ˜'Jšœœ&˜EJšœ˜—J˜Jšœ˜J˜J˜—šž œœœ+œœœ œœœ˜£J˜JšœÈ™ÈJ˜Jšœœ˜J˜"J˜Jšœ œœœ ˜˜JšœK™KJ˜Jšœœ˜ J˜Jšœ œœ˜J˜J˜2Jšœœœ&˜Jšœœœ˜&šœœ˜ J˜$Jšœœ˜9šœ˜Jšœ:˜<šœ:˜Jš œœœœœ˜DJ˜Jšœ™J˜Jšœ œ˜"Jšœœœ'˜9Jšœœ ˜2Jšœ œœ+˜AJšœ œ ˜.Jšœœœ'˜9Jšœœ˜%˜Jšœñ™ñJ˜šœ˜˜ Jšœ˜Jšœ!œ˜3šœ œ˜Jšœ+˜-Jšœ@˜BJ˜J˜J˜)J˜/J˜—Jšœ œ˜Jšœ˜ J˜J˜—šœ˜JšœAœ˜Sšœ œ˜Jšœ+˜-Jšœ@˜BJ˜J˜J˜)J˜/J˜—Jšœ œ˜Jšœ˜ J˜—J˜J˜—J˜)J˜.Jšœœœ&˜PJšœœœ˜$J˜Jšœ™J˜šœœœ˜#šœœ˜J˜ Jšœœ˜9šœ˜Jšœ6˜8šœ:˜—J˜—Jšœ˜J˜—JšœS™SJ˜šœ œ˜JšœN˜PJšœ?˜EJ˜J˜—JšœR™RJ˜J˜ J˜ š˜šœœœ˜*J˜J˜%šœœ˜"Jšœ4˜6šœ)œ˜GJšœ˜—J˜—Jšœ˜—Jšœœœ˜ J˜@J˜3Jšœ˜J˜—JšœM™MJ˜J˜J˜J˜ J˜!šœœœ˜%šœœ˜J˜J˜7J˜—Jšœ˜J˜—Jšœ™J˜Jšœ˜Jšœ˜Jšœ˜Jšœ œ˜Jšœ˜Jšœ2˜8J˜J˜—šžœœ œ,˜CJšœEœ#˜lJ˜J˜—šžœœ œ,˜DJšœFœ#˜mJ˜J˜—Jšœ˜Jšœ0™0Jšœ=™=JšœO™OJšœB™BJ™,J˜J˜J˜J˜—…—yú¼