DIRECTORY BasicTime USING [Now, Period], Commander USING [CommandProc, Register], CommandTool USING [ArgumentVector, Failed, Parse], IO USING [int, PutF, PutRope, PutChar, rope, STREAM], Jukebox USING [Handle, DeleteTune, Error, Info, OpenTune, ArchiveCloseTune, Tune], RecordingServiceRegister USING [database, haveJuke, jukebox], Rope USING [Equal, Fetch, Length, ROPE], TuneAccess USING [GetCreateDate, NextTuneNumber], VoiceRopeDB, VoiceCleanup; VoiceCleanupImpl: CEDAR PROGRAM IMPORTS BasicTime, Commander, CommandTool, IO, Jukebox, RecordingServiceRegister, Rope, TuneAccess, VoiceRopeDB EXPORTS VoiceCleanup ~ BEGIN ROPE: TYPE ~ Rope.ROPE; STREAM: TYPE ~ IO.STREAM; minimumTuneAge: INT _ 300; -- minimum age in seconds minimumVoiceRopeAge: INT _ 300; -- minimum age in seconds testing: BOOLEAN _ FALSE; -- when set, things don't actually get deleted aggressive: BOOLEAN _ TRUE; -- aggressive vertical garbage collection? RegisteredClass: TYPE ~ REF RegisteredClassRecord; RegisteredClassRecord: TYPE ~ RECORD[ class: ROPE, proc: VoiceCleanup.IsGarbageProc ]; classRegistry: LIST OF RegisteredClass _ NIL; CollectTunes: PUBLIC PROC [reports: STREAM _ NIL] RETURNS [] ~ TRUSTED { ENABLE Jukebox.Error => { IF reports#NIL THEN IO.PutF[reports, "Jukebox error: %g\n", IO.rope[rope]]; CONTINUE; }; before, after: INT; db: VoiceRopeDB.Handle _ RecordingServiceRegister.database; openJukebox: Jukebox.Handle _ RecordingServiceRegister.jukebox; tune: INT; IF NOT RecordingServiceRegister.haveJuke THEN { IF reports#NIL THEN IO.PutRope[reports, "Jukebox not available.\n"]; RETURN; }; IF db = NIL THEN { IF reports#NIL THEN IO.PutRope[reports, "Voice rope database not available.\n"]; RETURN; }; before _ Jukebox.Info[openJukebox].nTunes; IF reports # NIL THEN IO.PutRope[reports, "Collecting tunes"]; tune _ TuneAccess.NextTuneNumber[jukebox: openJukebox]; WHILE tune # -1 DO IF reports # NIL THEN IO.PutChar[reports, '.]; [] _ CollectTune[db, tune, reports]; tune _ TuneAccess.NextTuneNumber[jukebox: openJukebox, currentTuneID: tune]; ENDLOOP; after _ Jukebox.Info[openJukebox].nTunes; IF reports # NIL THEN IO.PutF[reports, "%g tunes collected.\n", IO.int[before - after]]; }; CollectTune: PROC [db: VoiceRopeDB.Handle, tune: INT, reports: STREAM] RETURNS [collected: BOOLEAN _ FALSE] ~ TRUSTED { ENABLE { VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; }; Jukebox.Error => { IF reports#NIL THEN IO.PutF[reports, "Jukebox error: %g\n", IO.rope[rope]]; CONTINUE; }; }; openTune: Jukebox.Tune; openJukebox: Jukebox.Handle _ RecordingServiceRegister.jukebox; IF VoiceRopeDB.VoiceRopeContainingTune[db, tune] = NIL THEN { openTune _ Jukebox.OpenTune[openJukebox, tune, FALSE]; IF BasicTime.Period[from: TuneAccess.GetCreateDate[openTune], to: BasicTime.Now[]] < minimumTuneAge THEN { Jukebox.ArchiveCloseTune[openJukebox, openTune]; RETURN;}; Jukebox.ArchiveCloseTune[openJukebox, openTune]; IF NOT testing THEN Jukebox.DeleteTune[openJukebox, tune]; IF reports # NIL THEN IO.PutF[reports, "\nTune garbage: %g\n", IO.int[tune]]; collected _ TRUE; }; }; CollectVoiceRopes: PUBLIC PROC [reports: STREAM _ NIL] RETURNS [] ~ { ENABLE VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; }; Internal: VoiceRopeDB.EnumProc ~ { IF CollectVoiceRope[db, info, reports] THEN count _ count + 1; IF reports # NIL THEN IO.PutChar[reports, '.]; }; count: INT _ 0; db: VoiceRopeDB.Handle _ RecordingServiceRegister.database; IF db = NIL THEN { IF reports#NIL THEN IO.PutRope[reports, "Voice rope database not available.\n"]; RETURN; }; IF reports # NIL THEN IO.PutRope[reports, "Collecting voice ropes"]; VoiceRopeDB.EnumerateVoiceRopes[handle: db, proc: Internal]; IF reports # NIL THEN IO.PutF[reports, "%g voice ropes collected.\n", IO.int[count]]; }; CollectVoiceRope: PROC [db: VoiceRopeDB.Handle, info: VoiceRopeDB.VoiceRopeInfo, reports: STREAM] RETURNS [collected: BOOLEAN _ FALSE] ~ { ENABLE VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; }; IF VoiceRopeDB.InterestInVoiceRope[db, info.vrID] = NIL AND BasicTime.Period[from: info.timestamp, to: BasicTime.Now[]] > minimumVoiceRopeAge THEN { IF NOT testing THEN VoiceRopeDB.DeleteVoiceRope[db, info.vrID]; IF reports # NIL THEN IO.PutF[reports, "\nVoice rope garbage: %g", IO.rope[info.vrID]]; IF aggressive THEN { tune: INT; struct: VoiceRopeDB.TuneList _ info.struct; UNTIL struct=NIL DO [tune: tune, rest: struct] _ VoiceRopeDB.NextTuneOnList[struct]; [] _ CollectTune[db, tune, reports]; ENDLOOP; }; IF reports # NIL THEN IO.PutChar[reports, '\n]; collected _ TRUE; } }; CollectInterests: PUBLIC PROC [class: ROPE _ NIL, reports: STREAM _ NIL] RETURNS [] ~ { ENABLE VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; }; Internal: VoiceRopeDB.InterestProc ~ { IF CollectInterest[db, info, classProc, reports] THEN count _ count + 1; IF reports # NIL THEN IO.PutChar[reports, '.]; }; count: INT _ 0; classProc: VoiceCleanup.IsGarbageProc _ NIL; db: VoiceRopeDB.Handle _ RecordingServiceRegister.database; IF db = NIL THEN { IF reports#NIL THEN IO.PutRope[reports, "Voice rope database not available.\n"]; RETURN; }; IF class # NIL THEN { -- collecting a single class so lookup garbage proc once classProc _ LookupClass[class]; IF classProc = NIL THEN { IF reports # NIL THEN IO.PutF[reports, "No registered garbage procedure for class %g.\n", IO.rope[class]]; RETURN; }; }; IF reports # NIL THEN IO.PutRope[reports, "Collecting interests"]; VoiceRopeDB.EnumerateInterestClass[handle: db, class: class, proc: Internal]; IF reports # NIL THEN IO.PutF[reports, "%g interests collected.\n", IO.int[count]]; }; CollectInterest: PROC [db: VoiceRopeDB.Handle, info: VoiceCleanup.InterestInfo, proc: VoiceCleanup.IsGarbageProc _ NIL, reports: STREAM] RETURNS [collected: BOOLEAN _ FALSE] ~ { ENABLE VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; }; classProc: VoiceCleanup.IsGarbageProc _ proc; IF proc = NIL THEN { -- lookup garbage proc for the particular class classProc _ LookupClass[info.class]; IF classProc = NIL THEN { IF reports # NIL THEN IO.PutF[reports, "No registered garbage procedure for class %g.\n", IO.rope[info.class]]; RETURN; }; }; IF classProc[info] THEN { -- returns TRUE if info is garbage IF NOT testing THEN VoiceRopeDB.DropInterest[db, info]; IF reports # NIL THEN IO.PutF[reports, "\nInterest garbage: vr=%g, ref=%g", IO.rope[info.vrID], IO.rope[info.refID]]; IF aggressive THEN { vrEntry: VoiceRopeDB.Header; vrInfo: VoiceRopeDB.VoiceRopeInfo; vrEntry _ VoiceRopeDB.ReadVoiceRope[handle: db, ropeID: info.vrID].header; IF vrEntry # NIL THEN { vrInfo _ VoiceRopeDB.UnpackHeader[vrEntry]; [] _ CollectVoiceRope[db, vrInfo, reports]; }; }; IF reports # NIL THEN IO.PutChar[reports, '\n]; collected _ TRUE; }; }; RegisterClass: PUBLIC PROC [class: ROPE, proc: VoiceCleanup.IsGarbageProc] RETURNS [] ~ { data: RegisteredClass; FOR l: LIST OF RegisteredClass _ classRegistry, l.rest WHILE l # NIL DO IF l.first.class = class THEN { -- new proc for a previously registered class l.first.proc _ proc; RETURN; } ENDLOOP; data _ NEW[RegisteredClassRecord _ [class, proc]]; classRegistry _ CONS[data, classRegistry]; }; LookupClass: PROC [class: ROPE] RETURNS [proc: VoiceCleanup.IsGarbageProc _ NIL] ~ { FOR l: LIST OF RegisteredClass _ classRegistry, l.rest WHILE l # NIL DO IF Rope.Equal[s1: l.first.class, s2: class, case: FALSE] THEN RETURN[l.first.proc]; ENDLOOP; }; silent: BOOLEAN _ FALSE; -- turns off reporting ParseSwitches: PROC [argv: CommandTool.ArgumentVector] RETURNS [] ~ { arg: ROPE; FOR i: NAT IN [1..argv.argc) DO arg _ argv[i]; IF Rope.Length[arg] = 0 THEN LOOP; IF Rope.Fetch[arg, 0] = '- THEN { FOR index: INT IN [1..Rope.Length[arg]) DO SELECT Rope.Fetch[arg, index] FROM 't, 'T => testing _ TRUE; -- testing mode 'g, 'G => testing _ FALSE; -- really collect bad entries 'r, 'R => silent _ FALSE; -- turn on reporting 's, 'S => silent _ TRUE; -- turn off reporting ENDCASE; ENDLOOP; }; ENDLOOP; }; NextNonSwitch: PROC [argv: CommandTool.ArgumentVector, start: NAT] RETURNS [arg: ROPE, p: NAT] ~ { FOR i: NAT IN [start..argv.argc) DO arg _ argv[i]; IF Rope.Length[arg] = 0 THEN LOOP; IF Rope.Fetch[arg, 0] # '- THEN RETURN[arg, i]; ENDLOOP; RETURN[NIL, argv.argc]; }; TuneProc: Commander.CommandProc = { argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; ParseSwitches[argv]; IF testing THEN IO.PutRope[cmd.out, "Test mode:\n"]; CollectTunes[reports: IF silent THEN NIL ELSE cmd.out]; IF testing THEN IO.PutRope[cmd.out, "No garbage actually collected.\n"]; EXITS failed => {result _ $Failure}; }; VoiceRopeProc: Commander.CommandProc = { argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; ParseSwitches[argv]; IF testing THEN IO.PutRope[cmd.out, "Test mode:\n"]; CollectVoiceRopes[reports: IF silent THEN NIL ELSE cmd.out]; IF testing THEN IO.PutRope[cmd.out, "No garbage actually collected.\n"]; EXITS failed => {result _ $Failure}; }; InterestProc: Commander.CommandProc = { class: ROPE; i: NAT; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; ParseSwitches[argv]; [class, i] _ NextNonSwitch[argv, 1]; IF testing THEN IO.PutRope[cmd.out, "Test mode:\n"]; CollectInterests[class: class, reports: IF silent THEN NIL ELSE cmd.out]; IF testing THEN IO.PutRope[cmd.out, "No garbage actually collected.\n"]; EXITS failed => {result _ $Failure}; }; Commander.Register[key: "CollectVoiceInterests", proc: InterestProc, doc: "Garbage collects interests in voice ropes.\n CollectVoiceInterests {-t | -g} "]; Commander.Register[key: "CollectVoiceRopes", proc: VoiceRopeProc, doc: "Garbage collects voice ropes that are no longer of interest.\n CollectVoiceRopes {-t | -g}"]; Commander.Register[key: "CollectTunes", proc: TuneProc, doc: "Garbage collects unused bluejay tunes.\n CollectTunes {-t | -g}"]; END. κVoiceCleanupImpl.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Doug Terry, November 18, 1986 4:30:43 pm PST Routines for collecting voice ropes that have no external references to them, i.e. are "garbage". This garbage collection is complicated by the fact that references to voice ropes may reside in arbitrary Tioga documents stored on any Cedar machine. Tunes This implementation of tune collection assumes that the code runs on the same machine as Bluejay, i.e. the Jukebox and TuneAccess interfaces are locally available. The Tune Collector enumerates the complete set of TuneIDs and calls CollectTune for each one. Queries the VoiceRope database to determine if any voice ropes make use of the tune. If not, then the tune is deleted. Voice ropes The Voice Rope Collector enumerates the VoiceRope database and calls CollectVoiceRope for each entry. Queries the VoiceInterest database to determine if an interest exists in the voice rope. If not, and the voice rope is older than some period of time, then the entry is deleted from the VoiceRope database. In addition, an aggressive implementation calls CollectTune for each tune component of the voice rope. Interests The Voice Interest Collector enumerates all entries of the specified class in the VoiceInterest database and calls the IsGarbageProc for each one. If this call returns TRUE, then the entry is deleted from the VoiceInterest database. In addition, an aggressive implementation calls CollectVoiceRope for the voice rope referenced in the deleted interest entry. Registers an IsGarbageProc to be used when garbage collecting interests of the given class. Returns the IsGarbageProc registered for the given class; NIL if none exists. CommandTool commands [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] Initialization Doug Terry, June 6, 1986 5:20:27 pm PDT Created to do garbage collection of voice ropes. changes to: DIRECTORY, VoiceCleanupImpl, IMPORTS, EXPORTS, ~, CollectTunes, CollectTune, CollectVoiceRopes, CollectVoiceRope, CollectInterests, CollectInterest, END Doug Terry, June 9, 1986 4:07:26 pm PDT changes to: DIRECTORY, ~, CollectTunes, CollectTune, NoVoiceRope, CollectVoiceRopes, Internal (local of CollectVoiceRopes), CollectVoiceRope, NoInterest, Internal (local of CollectInterests), CollectInterests Doug Terry, June 13, 1986 1:48:05 pm PDT Added class registration and feedback; upgraded to new VoiceRopeDB interface. changes to: ~, CollectTunes, NoVoiceRope, CollectVoiceRopes, CollectInterests, RegisterClass, Open, CollectTune, Internal (local of CollectVoiceRopes), CollectVoiceRope, Internal (local of CollectInterests), RegisterClass, LookupClass Doug Terry, June 15, 1986 10:12:06 pm PDT Added CommandTool commands. changes to: ~, RegisterClass, LookupClass, ParseSwitchesAndArg, TuneProc, VoiceRopeProc, InterestProc, Commander, Commander, Commander, DIRECTORY, IMPORTS, Internal (local of CollectInterests), CollectInterests, CollectInterest, Open, CollectVoiceRope, CollectTunes, CollectTune Doug Terry, June 16, 1986 4:15:17 pm PDT changes to: LookupClass, TuneProc, VoiceRopeProc, InterestProc, DIRECTORY, Open, CollectTune, Internal (local of CollectVoiceRopes), CollectVoiceRope, Internal (local of CollectInterests), CollectInterest Doug Terry, June 26, 1986 5:02:04 pm PDT changes to: Open, CollectTunes, CollectVoiceRopes, CollectInterests, DIRECTORY, IMPORTS Doug Terry, June 27, 1986 3:26:27 pm PDT Added argument to CommandTool commands for specifying a database name. changes to: ParseSwitches, NextNonSwitch, TuneProc, VoiceRopeProc, InterestProc, Commander, Commander, Commander, ParseSwitches Doug Terry, June 30, 1986 1:42:43 pm PDT changes to: CollectInterest Doug Terry, November 18, 1986 2:25:20 pm PST Uses RecordingServiceRegister to obtain open Jukebox and VoiceRopeDB handles. changes to: DIRECTORY, IMPORTS, ~, CollectTunes, CollectTune, Open Κ&˜codešœ™Kšœ Οmœ1™Jšœ7˜7šžœ ž˜Jšžœ žœžœ˜.Jšœ$˜$JšœL˜LJšžœ˜—Jšœ)˜)šžœ žœž˜Jšžœ(žœ˜B—J˜—J˜š‘ œžœ žœ žœžœ žœžœžœ˜wK™wšžœ˜Jš œžœ žœžœAžœ˜yJš œžœ žœžœžœ&žœ ž œ˜kJ˜—K•StartOfExpansion[from: GMT, to: GMT]šœ˜Jšœ?˜?šžœ1žœ˜>Kšœ/žœ˜6Kšžœbžœžœ0žœ˜₯Kšœ0˜0šžœžœ ž˜Kšœ&˜&—šžœ žœž˜Kšœ7˜7—Kšœ žœ˜K˜—K˜K˜——™ š ‘œžœžœ žœžœžœ˜EJšœe™eJš žœžœ žœžœAžœ˜€š‘œ˜"šžœ%ž˜+Jšœ˜—Jšžœ žœžœ˜.J˜—Jšœžœ˜Jšœ;˜;šžœžœžœ˜Jšžœ žœžœžœ:˜PJšžœ˜J˜—Jšžœ žœžœ/˜DJšœ<˜<šžœ žœž˜Jšžœ.žœ ˜?—J˜—J˜š ‘œžœDžœžœ žœžœ˜ŠKšœΆ™ΆJš žœžœ žœžœAžœ˜€–[from: GMT, to: GMT]šžœ2žœžœSžœ˜”šžœžœ ž˜Kšœ+˜+—šžœ žœž˜KšœA˜A—šžœ žœ˜Kšœžœ˜ Kšœ+˜+šžœžœž˜Kšœ@˜@Kšœ$˜$Kšžœ˜—K˜—Kšžœ žœžœ˜/Kšœ žœ˜K˜—K˜K˜——™ š‘œž œ žœžœ žœžœžœ˜WJšœ©žœ»™θJš žœžœ žœžœAžœ˜€š‘œ˜&šžœ/ž˜5Jšœ˜—Jšžœ žœžœ˜.J˜—Jšœžœ˜Jšœ(žœ˜,Jšœ;˜;šžœžœžœ˜Jšžœ žœžœžœ:˜PJšžœ˜J˜—šžœ žœžœ 8˜OJšœ˜šžœ žœžœ˜Jš žœ žœžœžœBžœ˜jJšžœ˜J˜—J˜—Jšžœ žœžœ-˜BJšœM˜Mšžœ žœž˜Jšžœ,žœ ˜=—J˜—J˜š‘œžœ^žœ žœžœ žœžœ˜±Jš žœžœ žœžœAžœ˜€Kšœ-˜-šžœžœžœ /˜EJšœ$˜$šžœ žœžœ˜Jš žœ žœžœžœBžœ˜oJšžœ˜J˜—J˜—šžœžœ "˜=šžœžœ ž˜Jšœ#˜#—šžœ žœž˜Kšœ_˜_—šžœ žœ˜Kšœ˜Kšœ"˜"KšœJ˜Jšžœ žœžœ˜Kšœ+˜+Kšœ+˜+K˜—K˜—Kšžœ žœžœ˜/Kšœ žœ˜K˜—K˜K˜—š‘ œž œ žœ$žœ˜YJšœ[™[Jšœ˜š žœžœžœ)žœžœž˜Gšžœžœ -˜NKšœ˜Kšžœ˜K˜—Kšžœ˜—Jšœžœ(˜2Jšœžœ˜*J˜—J˜š ‘ œžœ žœžœ%žœ˜TJšœM™Mš žœžœžœ)žœžœž˜GK–-[s1: ROPE, s2: ROPE, case: BOOL _ TRUE]šžœ0žœžœžœ˜SKšžœ˜—J˜—J˜—™Kšœžœžœ ˜0K˜š‘ œžœ$žœ˜EKšœžœ˜ šžœžœžœž˜Kšœ˜Kšžœžœžœ˜"šžœžœ˜!šžœžœžœž˜*šžœž˜"Kšœžœ ˜*Kšœžœ ˜9Kšœžœ ˜/Kšœžœ ˜/—Kšžœ˜—Kšžœ˜Kšœ˜—Kšžœ˜—K˜—J–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]˜š ‘ œžœ+žœžœžœžœ˜bšžœžœžœž˜#Kšœ˜Kšžœžœžœ˜"Kšžœžœžœ ˜/Kšžœ˜—Kšžœžœ ˜K˜K˜—š‘œ˜#JšΠckH™Hšœ=˜=Kšœ)žœžœ ˜8—Kšœ˜Kšžœ žœžœ"˜4Kš œžœžœžœžœ ˜7Kšžœ žœžœ6˜Hšž˜Kšœ˜—J˜J˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š‘ œ˜(Jš’H™Hšœ=˜=Kšœ)žœžœ ˜8—K˜Kšžœ žœžœ"˜4Kš œžœžœžœžœ ˜