DIRECTORY BasicTime USING [Now, Period], BluejayUtils USING [DeleteTune, TunesInJukebox], Commander USING [CommandProc, Register], CommandTool USING [ArgumentVector, Failed, Parse], IO USING [int, PutF, PutRope, PutChar, rope, STREAM], Jukebox USING [Handle, OpenJukebox, CloseJukebox], Rope USING [Cat, Equal, Fetch, Length, ROPE], TuneAccess USING [NextTuneNumber], UserProfile USING [Token], VoiceUtils USING [RnameToRspec], VoiceRopeDB, VoiceCleanup; VoiceCleanupImpl: CEDAR PROGRAM IMPORTS BasicTime, BluejayUtils, Commander, CommandTool, IO, Jukebox, Rope, TuneAccess, UserProfile, VoiceUtils, 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 _ TRUE; -- when set, things don't actually get deleted aggressive: BOOLEAN _ TRUE; -- aggressive vertical garbage collection? jukeboxName: ROPE _ "SomeJukeboxName"; RegisteredClass: TYPE ~ REF RegisteredClassRecord; RegisteredClassRecord: TYPE ~ RECORD[ class: ROPE, proc: VoiceCleanup.IsGarbageProc ]; classRegistry: LIST OF RegisteredClass _ NIL; Open: PROC[dbName: ROPE _ NIL, reports: STREAM _ NIL] RETURNS [handle: VoiceRopeDB.Handle] = { server: Rope.ROPE _ UserProfile.Token["ThrushClientServerInstance", "Strowger.Lark"]; IF dbName = NIL THEN dbName _ Rope.Cat["/",server,"//", VoiceUtils.RnameToRspec[server].simpleName, "/VoiceRopeDB.df"]; handle _ VoiceRopeDB.Open[dbName ! VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Trouble opening voice rope database: %g\n", IO.rope[explanation]]; handle _ NIL; }]; }; CollectTunes: PUBLIC PROC [dbName: ROPE _ NIL, reports: STREAM _ NIL] RETURNS [] ~ TRUSTED { before, after: INT; db: VoiceRopeDB.Handle; openJukebox: Jukebox.Handle; tune: INT; db _ Open[dbName, reports]; IF db = NIL THEN RETURN; openJukebox _ Jukebox.OpenJukebox[name: jukeboxName]; IF openJukebox = NIL THEN { IF reports # NIL THEN IO.PutF[reports, "Can't open Jukebox: %g.\n", IO.rope[jukeboxName]]; RETURN; }; before _ BluejayUtils.TunesInJukebox[]; 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 _ BluejayUtils.TunesInJukebox[]; openJukebox _ Jukebox.CloseJukebox[openJukebox]; 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] ~ { ENABLE VoiceRopeDB.Error => { IF reports # NIL THEN IO.PutF[reports, "Database error: %g\n", IO.rope[explanation]]; CONTINUE; }; IF VoiceRopeDB.VoiceRopeContainingTune[db, tune] = NIL THEN { IF NOT testing THEN [] _ BluejayUtils.DeleteTune[tune]; IF reports # NIL THEN IO.PutF[reports, "\nTune garbage: %g\n", IO.int[tune]]; collected _ TRUE; }; }; CollectVoiceRopes: PUBLIC PROC [dbName: 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.EnumProc ~ { IF CollectVoiceRope[db, info, reports] THEN count _ count + 1; IF reports # NIL THEN IO.PutChar[reports, '.]; }; count: INT _ 0; db: VoiceRopeDB.Handle _ Open[dbName, reports]; IF db = NIL THEN 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 [dbName: ROPE _ NIL, 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 _ Open[dbName, reports]; IF db = NIL THEN 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[dbName: NextNonSwitch[argv, 1].arg, 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[dbName: NextNonSwitch[argv, 1].arg, 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[dbName: NextNonSwitch[argv, i+1].arg, 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, June 30, 1986 2:47:38 pm PDT 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. Database names If dbName is omitted, a default based on the user profile choice of Thrush Server is invented. default parameters set up handle 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. IF BasicTime.Period[from: TuneAccess.GetCreateDate[tune], to: BasicTime.Now[]] < minimumTuneAge THEN RETURN; 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 Κ˜codešœ™Kšœ Οmœ1™Jšœ7˜7šžœ ž˜Jšžœ žœžœ˜.Jšœ$˜$JšœL˜LJšžœ˜—Jšœ&˜&Jšœ0˜0šžœ žœž˜Jšžœ(žœ˜B—J˜—J˜š‘ œžœ žœ žœžœ žœžœ˜oK™wJš žœžœ žœžœAžœ˜€•StartOfExpansion[from: GMT, to: GMT]šžœ1žœ˜>Kšžœ^žœžœ™lšžœžœ ž˜Kšœ#˜#—šžœ žœž˜Kšœ7˜7—Kšœ žœ˜K˜—K˜K˜——™ š‘œžœžœ žœžœ žœžœžœ˜YJšœe™eJš žœžœ žœžœAžœ˜€š‘œ˜"šžœ%ž˜+Jšœ˜—Jšžœ žœžœ˜.J˜—Jšœžœ˜Jšœ/˜/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˜——™ š‘œž œ žœžœ žœžœ žœžœžœ˜kJšœ©žœ»™θJš žœžœ žœžœAžœ˜€š‘œ˜&šžœ/ž˜5Jšœ˜—Jšžœ žœžœ˜.J˜—Jšœžœ˜Jšœ(žœ˜,Jšœ/˜/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š œ:žœžœžœžœ ˜[Kšžœ žœžœ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š œ?žœžœžœžœ ˜`Kšžœ žœžœ6˜Hšž˜Kšœ˜—J˜J˜—–L -- [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]š‘ œ˜'Jš’H™HKšœžœ˜ Kšœžœ˜šœ=˜=Kšœ)žœžœ ˜8—K˜Kšœ$˜$Kšžœ žœžœ"˜4Kš œNžœžœžœžœ ˜oKšžœ žœžœ6˜Hšž˜Kšœ˜—J˜J˜——™K–x[key: ROPE, proc: Commander.CommandProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL, interpreted: BOOL _ TRUE]šœ­˜­K–x[key: ROPE, proc: Commander.CommandProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL, interpreted: BOOL _ TRUE]šœ°˜°K–x[key: ROPE, proc: Commander.CommandProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL, interpreted: BOOL _ TRUE]šœ‹˜‹J˜—Kšžœ˜K™™'K™0Kšœ Οr˜™€—™'Kšœ £Qœ£(œ£™Π—™(K™MKšœ £mœ£œ£™κ—™)K™Kšœ £˜œ£V™–—™(Kšœ £Zœ£œ£™Μ—™(Kšœ £K™W—™(K™FKšœ £s™—K™™(Kšœ £™—K™—…—'ΪH