<> <> <> <> < IOMisc.AskUser)>> <> <<1 verify consistency of all DF files:>> <> <> <<2 verify sufficiency: run VerifyDF on all DF files>> <<3 transfer files to new homes, produce new DF files>> <> <> DIRECTORY BTreeDefs: TYPE USING [BTreeHandle, CreateAndInitializeBTree, Desc, Insert, KeyNotFound, Lookup,ReleaseBTree, TestKeys], BTreeSupportDefs: TYPE USING[FileHandle--, SetLength--], BTreeSupportExtraDefs: TYPE USING[CloseFile, OpenFile], Buttons: TYPE USING [Button, ButtonProc, Create], Containers: TYPE USING [ChildXBound, Container, Create], CWF: TYPE USING [SetWriteProcedure, SWF1, SWF2, SWF3, WF0, WF1, WF2, WF3, WF4, WFC, WFCR], DateAndTime: TYPE USING [Parse], Directory: TYPE USING[Error, Lookup, ignore], DFSubr: TYPE USING [AllocateDFSeq, DF, DFSeq, FlattenDF, FreeDFSeq, SortByFileName, StripLongName, TooManyEntries], File: TYPE USING [Capability, nullCapability], FileIO: TYPE USING [minimumStreamBufferParms, Open], FQ: TYPE USING [FileQuery, Result], IFSFile: TYPE USING [CantOpen, Error, UnableToLogin], Inline: TYPE USING [BITOR], IO: TYPE USING [card, Close, CreateDribbleStream, Flush, Handle, PutChar, PutF, PutRope, ResetUserAbort, SetUserAbort, STREAM, string, UserAbort, UserAborted], IOMisc: TYPE USING[AskUser], Labels: TYPE USING [Create, Label, Set], LeafSubr: TYPE USING [PrintLeafAccessFailure, PrintLeafProblem, StopLeaf], LongString: TYPE USING [EquivalentString], Menus: TYPE USING [CreateEntry, CreateMenu, InsertMenuEntry, Menu, MenuProc], MessageWindow: TYPE USING[Append, Blink], Process: TYPE USING [Detach, Priority, priorityBackground, priorityForeground, priorityNormal, SetPriority], ReleaseSupport: TYPE USING [TransferFiles, VerifySufficiency], Rope: TYPE USING[ROPE, Text], RopeInline: TYPE USING[InlineFlatten], UnsafeSTP: TYPE USING [Error], STPSubr: TYPE USING [SetNumberOfConnectTries, StopSTP], Stream: TYPE USING [Delete, Handle], Subr: TYPE USING [AbortMyself, AllocateString, Any, CopyString, debugflg, EndsIn, errorflg, FileError, FreeHugeZone, FreeString, GetLine, LongZone, MakeTTYProcs, NewFile, NewStream, PagesUsedInHugeZone, Prefix, Read, ReadWrite, strcpy, SubStrCopy, SubrInit, SubrStop, TTYProcs], Time: TYPE USING [Current], TypeScript: TYPE USING[Create, Destroy, TS], ViewerClasses: TYPE USING [Viewer], ViewerEvents: TYPE USING[EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc], ViewerOps: TYPE USING [PaintViewer, SetMenu, SetOpenHeight], ViewerIO: TYPE USING[CreateViewerStreams], ViewerTools: TYPE USING [MakeNewTextViewer, GetContents, SetSelection]; ReleaseImpl: MONITOR IMPORTS BTreeDefs, BTreeSupportExtraDefs, Buttons, Containers, CWF, DateAndTime, DFSubr, Directory, FileIO, FQ, IFSFile, Inline, IO, IOMisc, Labels, LeafSubr, LongString, Menus, MessageWindow, Process, ReleaseSupport, RopeInline, STP: UnsafeSTP, STPSubr, Stream, Subr, Time, TypeScript, ViewerEvents, ViewerOps, ViewerIO, ViewerTools = { LC: TYPE = LONG CARDINAL; -- try to not overflow poor Pass3 LS: TYPE = LONG STRING; -- try to not overflow poor Pass3 ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; <> MAXALLFILES: CARDINAL = 6000; entryHeight: CARDINAL = 15; entryVSpace: CARDINAL = 10; entryHSpace: CARDINAL = 10; <> <<(all page counts are in IFS pages, not Pilot pages)>> bytesPerIFSPage: CARDINAL = 2048; <<>> <> InitialNumberOfPhase1BTreePages: CARDINAL = 1080; EPhase: TYPE = {One, Two, Three, OneThree}; <> Global: TYPE = RECORD[ <> container: Containers.Container _ NIL, typeScript: TypeScript.TS _ NIL, tty: Subr.TTYProcs, -- for typeScript in: STREAM _ NIL, -- to the dribble stream out: STREAM _ NIL, -- to the dribble stream logFile: STREAM _ NIL, -- to the log file <> fileNameButton: Buttons.Button _ NIL, fileNameViewer: ViewerClasses.Viewer _ NIL, phaseLabel: Labels.Label _ NIL, phaseButton: Buttons.Button _ NIL, priorityLabel: Labels.Label _ NIL, priorityButton: Buttons.Button _ NIL, useOldPhase1FileLabel: Labels.Label _ NIL, useOldPhase1FileButton: Buttons.Button _ NIL, useOldPhase3FileLabel: Labels.Label _ NIL, useOldPhase3FileButton: Buttons.Button _ NIL, updateBTreeLabel: Labels.Label _ NIL, updateBTreeButton: Buttons.Button _ NIL, verboseLabel: Labels.Label _ NIL, verboseButton: Buttons.Button _ NIL, <> busy: BOOL _ FALSE, phase: EPhase _ One, priority: Process.Priority _ Process.priorityNormal, checkOverwrite: BOOL _ FALSE, verbose: REF BOOL _ NIL, nProbablePages: LC _ 0, nProbableFiles: CARDINAL _ 0, dfseqall: DFSubr.DFSeq _ NIL, useOldPhase1FileCache: BOOL _ TRUE, useOldPhase3FileCache: BOOL _ TRUE, updateBTree: BOOL _ TRUE, oldPhase1FileCacheExists: BOOL _ FALSE, oldestBcdDate: LC _ 0, <> bTreeCap: File.Capability _ File.nullCapability, bTreeHandle: BTreeDefs.BTreeHandle _ NULL ]; NEXCEPTIONS: CARDINAL = 100; BVal: TYPE = RECORD[ version: CARDINAL _ 0, nIfsPages: CARDINAL _ 0 ]; EXSeq: TYPE = LONG POINTER TO EXSeqRecord; EXSeqRecord: TYPE = RECORD[ size: CARDINAL _ 0, body: SEQUENCE maxsize: CARDINAL OF EXItem ]; EXItem: TYPE = RECORD[ host: LS _ NIL, -- "Indigo" directory: LS _ NIL, -- "Cedar>Pilot" shortname: LS _ NIL -- "FileName.Mesa" ]; <> g: REF Global; destroyEventRegistration: ViewerEvents.EventRegistration; <> Go: Menus.MenuProc = TRUSTED { p: PROCESS; IF g.busy THEN { CWF.WF0["Still busy - try later.\n"L]; RETURN; }; p _ FORK ChooseAmong[g.phase]; Process.Detach[p]; }; ChooseAmong: ENTRY PROC [phase: EPhase] = { ENABLE UNWIND => g.busy _ FALSE; topdffilename: LS _ Subr.AllocateString[100]; dfFileRef: Rope.Text; starttime: LC _ Time.Current[]; DoPhase: PROC[phase: EPhase] = { time: LC _ Time.Current[]; Process.SetPriority[g.priority]; SELECT phase FROM One => CWF.WF0["Phase One: Verify consistency of all the DF files:\nStart by reading all DF files.\n"L]; Two => CWF.WF0["Phase Two: Verify sufficiency by invoking VerifyDF on all the DF files:\n"L]; Three => CWF.WF0["Phase Three: Transfer remote files and fix up DF files to match.\n"L]; ENDCASE => ERROR; CWF.WF1["Phase started at %lt.\n"L, @time]; Subr.errorflg _ Subr.debugflg _ FALSE; SELECT phase FROM One => VerifyConsistency[topdffilename]; Two => ReleaseSupport.VerifySufficiency[topdffilename, g.tty, g.out, g.checkOverwrite]; Three => ReleaseSupport.TransferFiles[ topdffilename, g.dfseqall, g.tty, g.in, g.out, g.logFile, g.checkOverwrite, g.useOldPhase3FileCache, g.updateBTree, g.verbose]; ENDCASE => ERROR; TemporaryStop[]; time _ Time.Current[]; CWF.WF1["Phase ended at %lt.\n"L, @time]; }; {ENABLE { UNWIND => {Subr.FreeString[topdffilename]; TemporaryStop[]}; IFSFile.Error, IFSFile.UnableToLogin => { LeafSubr.PrintLeafProblem[reason]; GOTO leave; }; IFSFile.CantOpen => { LeafSubr.PrintLeafAccessFailure[reason]; GOTO leave; }; ABORTED, Subr.AbortMyself, IO.UserAborted => { g.in.ResetUserAbort[]; CWF.WF0["\nRelease Aborted.\n"L]; GOTO leave; }; STP.Error => { CWF.WF0["FTP Error. "L]; IF error ~= NIL THEN CWF.WF2["message: %s, code %u in Stp.Mesa\n"L, error, @code]; Subr.errorflg _ TRUE; GOTO leave; }; }; STPSubr.SetNumberOfConnectTries[64000]; -- infinite number g.in.ResetUserAbort[]; g.busy _ TRUE; [] _ CWF.SetWriteProcedure[ToolTTYProc]; dfFileRef _ RopeInline.InlineFlatten[ViewerTools.GetContents[g.fileNameViewer]]; IF dfFileRef = NIL THEN { CWF.WF0["Error - must specify a df file.\n"L]; GOTO leave; }; IF NOT Subr.Any[LOOPHOLE[dfFileRef], '.] THEN CWF.SWF1[topdffilename, "%s.DF"L, LOOPHOLE[dfFileRef]] ELSE Subr.strcpy[topdffilename, LOOPHOLE[dfFileRef]]; IF NOT Subr.EndsIn[topdffilename, ".df"L] THEN { CWF.WF1["Error - %s must be a DF file name.\n"L, topdffilename]; GOTO leave; }; IF phase = OneThree THEN { DoPhase[One]; CWF.WFCR[]; PrintSeparator[]; DoPhase[Three]; CWF.WFCR[]; } ELSE DoPhase[phase]; EXITS leave => NULL; }; Subr.FreeString[topdffilename]; TemporaryStop[]; starttime _ Time.Current[] - starttime; CWF.WF1["\nTotal elapsed time for ReleaseTool %lr."L,@starttime]; IF Subr.errorflg THEN CWF.WF0["\tErrors logged."L]; CWF.WFCR[]; PrintSeparator[]; g.busy _ FALSE; }; VerifyConsistency: PROC[topdffilename: LS] = { <> nconflicts, nmissing, nentries, npages: CARDINAL; CardOut[g.out, "Remember: max entries in flat DF files = %d\n", MAXALLFILES]; g.out.PutRope["(Do not Destroy this window. Destroy the upper one only.)\n"]; <> IF g.useOldPhase1FileCache OR g.updateBTree THEN MakeBTree[]; g.nProbablePages _ 0; g.nProbableFiles _ 0; IF g.dfseqall ~= NIL THEN { hz: UNCOUNTED ZONE _ g.dfseqall.dfzone; <> FreeDFSeqAll[]; hz _ Subr.FreeHugeZone[hz]; -- we know it is a huge zone }; g.dfseqall _ ReadDF[topdffilename]; IF g.dfseqall.size = 0 THEN { FreeDFSeqAll[]; RETURN; }; npages _ Subr.PagesUsedInHugeZone[g.dfseqall.dfzone]; CWF.WF1["%u pages used in HugeZone.\n"L, @npages]; Flush[]; CWF.WF0["Sorting the flattened DF files.\n"L]; <> DFSubr.SortByFileName[g.dfseqall, 0, g.dfseqall.size-1, TRUE]; <> Flush[]; VerifySpanningTree[g.dfseqall, topdffilename]; <> Flush[]; CheckExceptions[g.dfseqall]; Flush[]; CheckReleaseAs[g.dfseqall]; Flush[]; [nmissing, nentries] _ CheckForExistence[g.dfseqall]; CWF.WF0["Sorting the flattened DF files by shortname.\n"L]; <> DFSubr.SortByFileName[g.dfseqall, 0, g.dfseqall.size-1, TRUE]; Flush[]; CheckBcdDates[g.dfseqall]; Flush[]; nconflicts _ CheckConflicts[g.dfseqall]; SetDFFileCreateTimes[g.dfseqall, topdffilename]; CWF.WF2[ "Summary for Phase 1: %u conflicting versions, %u files/versions not found.\n"L, @nconflicts, @nmissing]; CWF.WF4[ "Total %u entries, %u unique entries\n\t%lu probable pages (2048 byte pgs) in release of %u probable files.\n"L, @g.dfseqall.size, @nentries, @g.nProbablePages, @g.nProbableFiles]; npages _ Subr.PagesUsedInHugeZone[g.dfseqall.dfzone]; CWF.WF1["%u pages used in HugeZone.\n"L, @npages]; Flush[]; }; ReadDF: PROC [topdffilename: LS] RETURNS [dfseq: DFSubr.DFSeq] = { time: LC _ Time.Current[]; dfseq _ DFSubr.AllocateDFSeq[maxEntries: MAXALLFILES, zoneType: huge]; IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; <> DFSubr.FlattenDF[dfseq: dfseq, dffilename: topdffilename, h: g.tty, checkForOverwrite: g.checkOverwrite, setRecorder: TRUE, printStatus: g.verbose^, allowForceReadOnly: FALSE, skipCameFrom: TRUE, tryDollars: FALSE ! DFSubr.TooManyEntries => { CWF.WF0[ "Error - too many entries for the one data structure that holds every DF entry.\n"L]; CWF.WF0["Increase its size and try again.\n"L]; CONTINUE; } ]; time _ Time.Current[] - time; Flush[]; CWF.WF1["Time to read in all the DF files: %lr.\n"L, @time]; }; VerifySpanningTree: PROC[dfseq: DFSubr.DFSeq, topdffilename: LS] = { <> df, dfj: DFSubr.DF; natsign, nreleaseas: CARDINAL; out: STREAM _ FileIO.Open["SpanningTree.List$", overwrite]; out.PutRope["Verify spanning tree rule: each DF file has exactly one ReleaseAs clause. (skipping CameFroms)\n"]; FOR i: CARDINAL IN [0 .. dfseq.size) DO dfseq[i].eval _ FALSE; ENDLOOP; FOR i: CARDINAL IN [0 .. dfseq.size) DO j: CARDINAL; df _ @dfseq[i]; IF df.eval OR NOT Subr.EndsIn[df.shortname, ".df"L] OR df.parentCameFrom OR df.cameFrom THEN LOOP; natsign _ nreleaseas _ 0; <> Flush[]; StrungOut[out, "\nFile %s referenced by:\n", df.shortname]; j _ i; DO IF j >= dfseq.size THEN EXIT; dfj _ @dfseq[j]; IF dfj.shortname.length = df.shortname.length AND LongString.EquivalentString[dfj.shortname, df.shortname] THEN { IF dfj.atsign AND NOT dfj.readonly THEN natsign _ natsign + 1; IF dfj.releaseDirectory ~= NIL AND NOT dfj.cameFrom AND NOT dfj.readonly AND NOT LongString.EquivalentString[dfj.shortname, dfj.recorder] THEN nreleaseas _ nreleaseas + 1; dfj.eval _ TRUE; StrungOut[out, " %s", dfj.recorder]; IF dfj.readonly THEN out.PutRope[" (readonly)"]; out.PutRope[","]; } ELSE EXIT; j _ j + 1; ENDLOOP; out.PutRope["\n"]; IF natsign = 0 AND NOT LongString.EquivalentString[df.shortname, topdffilename] THEN StrungOut[out, "\tWarning - no Includes for %s anywhere.\n", df.shortname]; IF nreleaseas ~= 1 THEN { CardOut[out, "\tError - %d ReleaseAs statements for ", nreleaseas]; StrungOut[out, "%s (not counting self references), exactly one required.\n", df.shortname]; }; ENDLOOP; out.Close[]; }; CheckForExistence: PROC[dfseq: DFSubr.DFSeq] RETURNS[nmissing, nentries: CARDINAL] = { <> <> df: DFSubr.DF; inCache: BOOL; nIfsPages, remoteVersion, i: CARDINAL _ 0; remoteCreateTime: LC _ 0; fres: FQ.Result; starttime: LC _ Time.Current[]; <> nmissing _ nentries _ 0; <> CWF.WF0["Now make sure all those files are really out there.\n"L]; IF NOT g.oldPhase1FileCacheExists THEN { CWF.WF0["(Running priorityForeground.)\n"L]; Process.SetPriority[Process.priorityForeground]; }; i _ 0; WHILE i < dfseq.size DO df _ @dfseq[i]; <>> IF i > 0 AND ((df.createtime > 0 AND dfseq[i-1].createtime = df.createtime) OR (df.criterion ~= none AND dfseq[i-1].criterion ~= none)) AND LongString.EquivalentString[dfseq[i-1].shortname, df.shortname] AND LongString.EquivalentString[dfseq[i-1].directory, df.directory] AND LongString.EquivalentString[dfseq[i-1].host, df.host] THEN { df.version _ dfseq[i-1].version; i _ i + 1; -- skip this one, go to next; LOOP; }; nentries _ nentries + 1; IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; IF g.verbose^ THEN CWF.WF4[ "Check [%s]<%s>%s in %s ... "L, df.host, df.directory, df.shortname, df.recorder] ELSE CWF.WFC['&]; nIfsPages _ 0; remoteCreateTime _ 0; remoteVersion _ 0; inCache _ FALSE; fres _ notFound; IF g.oldPhase1FileCacheExists AND g.useOldPhase1FileCache AND df.createtime ~= 0 THEN { [inCache, nIfsPages] _ LookupInOldFileCache[df]; fres _ foundCorrectVersion; -- highversion and highdate have no value }; IF NOT inCache THEN [fres, nIfsPages, remoteVersion, remoteCreateTime] _ CheckFile[df]; SELECT fres FROM foundCorrectVersion => { IF g.verbose^ THEN CWF.WF1["ok. %s\n"L, IF inCache THEN "(in cache)"L ELSE ""L]; IF df.releaseDirectory ~= NIL AND NOT df.cameFrom THEN { g.nProbableFiles _ g.nProbableFiles + 1; g.nProbablePages _ g.nProbablePages + nIfsPages; }; IF NOT inCache AND g.updateBTree THEN InsertIntoCache[df, nIfsPages]; }; foundWrongVersion => { <> <> df.createtime _ remoteCreateTime; df.version _ remoteVersion; }; notFound => nmissing _ nmissing + 1; ENDCASE => ERROR; i _ i + 1; ENDLOOP; IF NOT g.oldPhase1FileCacheExists THEN { Process.SetPriority[g.priority]; CWF.WF0["\n(Priority reset.)\n"L]; }; starttime _ Time.Current[] - starttime; CWF.WF1["Total time for CheckFiles %lr.\n"L,@starttime]; }; LookupInOldFileCache: PROC [df: DFSubr.DF] RETURNS[inCache: BOOL, nIfsPages: CARDINAL] = { <> bval: BVal _ []; len: CARDINAL; specialPrefix: STRING _ "[Indigo]"L; file, sfn: LS _ NIL; inCache _ FALSE; nIfsPages _ 0; IF df.createtime = 0 OR NOT g.oldPhase1FileCacheExists OR NOT g.useOldPhase1FileCache THEN RETURN; file _ Subr.AllocateString[100]; sfn _ Subr.AllocateString[100]; {ENABLE UNWIND => {Subr.FreeString[file]; Subr.FreeString[sfn]}; CWF.SWF3[file, "[%s]<%s>%s"L, df.host, df.directory, df.shortname]; IF Subr.Prefix[file, specialPrefix] THEN Subr.SubStrCopy[file, file, specialPrefix.length]; CWF.SWF2[sfn, "%lu\000%s"L, @df.createtime, file]; len _ BTreeDefs.Lookup[ g.bTreeHandle, MakeBTreeDesc[sfn], DESCRIPTOR[@bval, SIZE[BVal]]]; }; -- of ENABLE UNWIND Subr.FreeString[file]; Subr.FreeString[sfn]; IF len = BTreeDefs.KeyNotFound THEN RETURN; df.version _ bval.version; nIfsPages _ bval.nIfsPages; RETURN[TRUE, nIfsPages]; }; InsertIntoCache: PROC[df: DFSubr.DF, nIfsPages: CARDINAL] = { <> bval: BVal _ [version: df.version, nIfsPages: nIfsPages]; specialPrefix: STRING _ "[Indigo]"L; sfn: LS _ Subr.AllocateString[100]; file: LS _ Subr.AllocateString[100]; {ENABLE UNWIND => {Subr.FreeString[file]; Subr.FreeString[sfn]}; IF df.version = 0 THEN ERROR; CWF.SWF3[file, "[%s]<%s>%s"L, df.host, df.directory, df.shortname]; IF Subr.Prefix[file, specialPrefix] THEN Subr.SubStrCopy[file, file, specialPrefix.length]; CWF.SWF2[sfn, "%lu\000%s"L, @df.createtime, file]; BTreeDefs.Insert[g.bTreeHandle, MakeBTreeDesc[sfn], DESCRIPTOR[@bval, SIZE[BVal]]]; }; -- of ENABLE UNWIND Subr.FreeString[file]; Subr.FreeString[sfn]; }; CheckFile: PROC [df: DFSubr.DF] RETURNS[fres: FQ.Result, nIfsPages, remoteVersion: CARDINAL, remoteCreateTime: LC] = { remoteByteLength: LC; targetFileName: LS _ Subr.AllocateString[100]; {ENABLE UNWIND => {Subr.FreeString[targetFileName]}; nIfsPages _ 0; [fres: fres, remoteVersion: remoteVersion, remoteCreateTime: remoteCreateTime, remoteByteLength: remoteByteLength] _ FQ.FileQuery[df.host, df.directory, df.shortname, df.version, df.createtime, df.criterion = none AND df.createtime = 0, g.tty, targetFileName]; SELECT fres FROM foundCorrectVersion => { IF df.createtime > 0 AND df.version > 0 AND df.version ~= remoteVersion THEN { CWF.WF1["\n(Warning %s: "L, df.shortname]; CWF.WF4["Create date %lt has version !%u, but %s refers to !%u)\n"L, @remoteCreateTime, @remoteVersion, df.recorder, @df.version]; }; df.version _ remoteVersion; nIfsPages _ (remoteByteLength/bytesPerIFSPage) + 2; }; foundWrongVersion => { StrungOut[g.out, "\nError - %s of ", targetFileName]; CardOut[g.out, "%t not found.\n", df.createtime]; }; notFound => { StrungOut[g.out, "\nError - %s not found.\n", targetFileName]; }; ENDCASE => ERROR; IF df.createtime = 0 AND remoteCreateTime > 0 THEN { <> df.createtime _ remoteCreateTime; df.version _ remoteVersion; }; }; -- of ENABLE UNWIND Subr.FreeString[targetFileName]; }; CheckConflicts: PROC[dfseq: DFSubr.DFSeq] RETURNS[nconflicts: CARDINAL] = { df, dflast: DFSubr.DF; nconflicts _ 0; CWF.WF0["Check consistency of DF files.\n"L]; FOR i: CARDINAL IN [1 .. dfseq.size) DO dflast _ @dfseq[i-1]; df _ @dfseq[i]; IF dflast.createtime ~= df.createtime AND dflast.createtime > 0 AND df.createtime > 0 AND LongString.EquivalentString[dflast.shortname, df.shortname] THEN { Flush[]; CWF.WF2["%s: Conflict- %s "L, dflast.shortname, dflast.recorder]; CWF.WF3["has an entry for [%s]<%s>%s"L, dflast.host, dflast.directory, dflast.shortname]; CWF.WF4[ " dated %lt,\n\tbut %s has an entry for [%s]<%s>"L, @dflast.createtime, df.recorder, df.host, df.directory]; CWF.WF2["%s dated %lt.\n"L, df.shortname, @df.createtime]; nconflicts _ nconflicts + 1; }; ENDLOOP; IF nconflicts > 0 THEN CWF.WF1["Warning - there were %u conflicts.\n"L, @nconflicts] ELSE CWF.WF0["Success - there were no conflicts among the files.\n"L]; }; SetDFFileCreateTimes: PROC[dfseq: DFSubr.DFSeq, topdffilename: LS] = { <> time: LC _ Time.Current[]; df: DFSubr.DF; FOR i: CARDINAL IN [0..dfseq.size) DO df _ @dfseq[i]; IF df.atsign AND df.releaseDirectory ~= NIL AND NOT df.cameFrom AND NOT df.readonly AND Subr.EndsIn[df.shortname, ".df"L] AND (NOT LongString.EquivalentString[df.shortname, df.recorder] OR LongString.EquivalentString[df.shortname, topdffilename]) THEN { df.createtime _ time; df.version _ 0; time _ time + 1; FOR j: CARDINAL IN [0 .. dfseq.size) DO IF i ~= j AND dfseq[j].shortname.length = df.shortname.length AND LongString.EquivalentString[dfseq[j].shortname, df.shortname] THEN { IF LongString.EquivalentString[dfseq[j].host, df.host] AND LongString.EquivalentString[dfseq[j].directory, df.directory] THEN { dfseq[j].createtime _ df.createtime; dfseq[j].version _ 0; } ELSE CWF.WF4[ "Warning: %s refers to a version of [%s]<%s>%s that is not being released.\n"L, dfseq[j].recorder, dfseq[j].host, dfseq[j].directory, dfseq[j].shortname]; }; ENDLOOP; }; ENDLOOP; }; ParseExceptionList: PROC[filename: LS] RETURNS[exseq: EXSeq] = { sh: Stream.Handle; line: LS _ Subr.AllocateString[100]; host: LS _ Subr.AllocateString[100]; directory: LS _ Subr.AllocateString[100]; shortname: LS _ Subr.AllocateString[100]; longzone: UNCOUNTED ZONE _ Subr.LongZone[]; Cleanup: PROC = { Subr.FreeString[line]; Subr.FreeString[host]; Subr.FreeString[directory]; Subr.FreeString[shortname]; }; exseq _ longzone.NEW[EXSeqRecord[NEXCEPTIONS]]; {ENABLE UNWIND => Cleanup[]; sh _ Subr.NewStream[filename, Subr.Read ! Subr.FileError => { CWF.WF1["Warning - Cannot open %s.\n"L, filename]; GOTO return }]; WHILE Subr.GetLine[sh, line] DO IF line.length = 0 OR Subr.Prefix[line, "//"L] OR Subr.Prefix[line, "--"L] THEN LOOP; [] _ DFSubr.StripLongName[line, host, directory, shortname, FALSE]; IF exseq.size > exseq.maxsize THEN { CWF.WF1["Error - too many exception list entries in %s.\n"L, filename]; EXIT; }; exseq[exseq.size] _ [host: Subr.CopyString[host, longzone], directory: Subr.CopyString[directory, longzone], shortname: IF shortname.length > 0 THEN Subr.CopyString[shortname, longzone] ELSE NIL]; exseq.size _ exseq.size + 1; ENDLOOP; Stream.Delete[sh]; EXITS return => {}; }; -- of ENABLE UNWIND Cleanup[]; }; EXPrefix: PROC[dfHost, dfDir, exHost, exDir: LS] RETURNS[BOOL] = { RETURN[ LongString.EquivalentString[dfHost, exHost] AND Subr.Prefix[dfDir, exDir] AND ( dfDir.length=exDir.length OR (dfDir.length>exDir.length AND dfDir[exDir.length] = '>) ) ]; }; CheckExceptions: PROC[dfseq: DFSubr.DFSeq] = { <> exseq: EXSeq _ NIL; i, j: CARDINAL; dfcur, dfinner: DFSubr.DF; nrelease, ncamefrom: CARDINAL; skip: BOOL; i _ 0; WHILE i < dfseq.size DO IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; dfcur _ @dfseq[i]; nrelease _ ncamefrom _ 0; j _ i; skip _ FALSE; WHILE j < dfseq.size AND LongString.EquivalentString[dfcur.shortname, dfseq[j].shortname] AND LongString.EquivalentString[dfcur.directory, dfseq[j].directory] AND LongString.EquivalentString[dfcur.host, dfseq[j].host] DO dfinner _ @dfseq[j]; IF dfinner.readonly THEN { IF dfinner.releaseDirectory ~= NIL AND NOT dfinner.cameFrom THEN CWF.WF3["Warning- %s in %s is ReadOnly but also has a %s statement.\n"L, dfinner.shortname, dfinner.recorder, IF dfinner.cameFrom THEN "CameFrom"L ELSE "ReleaseAs"L]; }; IF dfinner.releaseDirectory ~= NIL AND NOT dfinner.cameFrom THEN { IF LongString.EquivalentString[dfseq[j].directory, dfseq[j].releaseDirectory] AND LongString.EquivalentString[dfseq[j].host, dfseq[j].releaseHost] THEN CWF.WF2["Warning - %s in %s has directory and release directory that are identical.\n"L, dfseq[j].shortname, dfseq[j].recorder]; nrelease _ nrelease + 1; }; IF NOT dfinner.readonly AND dfinner.releaseDirectory = NIL THEN { Flush[]; CWF.WF2["Error - %s in %s is not ReadOnly and has no release directory.\n"L, dfinner.shortname, dfinner.recorder]; skip _ TRUE; }; IF dfinner.cameFrom THEN ncamefrom _ ncamefrom + 1; j _ j + 1; ENDLOOP; <> IF nrelease = 0 AND NOT skip AND ncamefrom = 0 THEN { ex: BOOL _ FALSE; IF exseq = NIL THEN { CWF.WF0["Reading exception list from 'Release.ExceptionList'.\n"L]; exseq _ ParseExceptionList["Release.ExceptionList"L]; }; FOR i: CARDINAL IN [0 .. exseq.size) DO IF EXPrefix[dfcur.host, dfcur.directory, exseq[i].host, exseq[i].directory] AND (exseq[i].shortname = NIL OR LongString.EquivalentString[dfcur.shortname, exseq[i].shortname]) THEN { ex _ TRUE; EXIT; }; ENDLOOP; IF NOT ex THEN { Flush[]; CWF.WF3[ "Error - no ReleaseAs statements for [%s]<%s>%s.\n\t(referenced in"L, dfcur.host, dfcur.directory, dfcur.shortname]; FOR k: CARDINAL IN [i .. j) DO CWF.WF1[" %s"L, dfseq[k].recorder]; ENDLOOP; CWF.WF0[").\n"L]; }; }; i _ j; ENDLOOP; IF exseq ~= NIL THEN FreeExceptionList[@exseq]; }; CheckReleaseAs: PROC[dfseq: DFSubr.DFSeq] = { <> destSeq: EXSeq _ ParseExceptionList["Release.Destinations"L]; exceptSeq: EXSeq _ NIL; IF destSeq = NIL OR destSeq.size = 0 THEN { CWF.WF0["No destination list found on 'Release.Destinations'.\n"L]; RETURN; }; FOR i: CARDINAL IN [0 .. dfseq.size) DO df: DFSubr.DF _ @dfseq[i]; IF df.cameFrom THEN { IF df.createtime = 0 THEN CWF.WF3["Warning - %s contains an entry for %s in a CameFrom Directory, but it has no create time for %s.\n"L, df.recorder, df.shortname, df.shortname]; <> <> FOR j: CARDINAL IN [0 .. destSeq.size) DO dest: EXItem = destSeq[j]; IF EXPrefix[df.host, df.directory, dest.host, dest.directory] THEN EXIT; REPEAT FINISHED => { <> IF exceptSeq = NIL THEN exceptSeq _ ParseExceptionList["Release.ExceptionList"L]; FOR k: CARDINAL IN [0 .. exceptSeq.size) DO ex: EXItem = exceptSeq[k]; IF EXPrefix[df.host, df.directory, ex.host, ex.directory] THEN EXIT; REPEAT FINISHED => CWF.WF4[ "CameFrom Warning - %s in %s is a CameFrom, but is on [%s]<%s>, not the official release directory.\n"L, df.shortname, df.recorder, df.host, df.directory]; ENDLOOP; }; ENDLOOP; }; IF df.releaseDirectory ~= NIL AND NOT df.cameFrom THEN { FOR j: CARDINAL IN [0 .. destSeq.size) DO dest: EXItem = destSeq[j]; IF EXPrefix[df.releaseHost, df.releaseDirectory, dest.host, dest.directory] THEN EXIT; REPEAT FINISHED => CWF.WF4["Warning - %s in %s is released onto [%s]<%s>, not the official release directory.\n"L, df.shortname, df.recorder, df.releaseHost, df.releaseDirectory]; ENDLOOP; <Top> directories>> IF NOT Subr.EndsIn[df.shortname, ".df"L] AND NOT Subr.EndsIn[df.shortname, ".boot"L] AND Subr.EndsIn[df.releaseDirectory, ">Top"L] THEN CWF.WF2[ "Warning - %s is released onto %s but is not a DF file or a boot file.\n"L, df.shortname, df.releaseDirectory]; }; ENDLOOP; FreeExceptionList[@destSeq]; IF exceptSeq#NIL THEN FreeExceptionList[@exceptSeq]; }; CheckBcdDates: PROC[dfseq: DFSubr.DFSeq] = { <> f: BOOL _ TRUE; df: DFSubr.DF; FOR i: CARDINAL IN [0 .. dfseq.size) DO df _ @dfseq[i]; IF Subr.EndsIn[df.shortname, ".bcd"L] AND df.createtime < g.oldestBcdDate THEN { IF f THEN { CardOut[ g.out, "These files were created before the earliest Bcd date (%t):\n", g.oldestBcdDate]; f _ FALSE; }; StrungOut[g.out, "\t%s of ", df.shortname]; CardOut[g.out, "%t in", df.createtime]; StrungOut[g.out, "%s\n", df.recorder]; }; ENDLOOP; }; FreeExceptionList: PROC[pexseq: POINTER TO EXSeq] = { FOR i: CARDINAL IN [0 .. pexseq.size) DO Subr.FreeString[pexseq[i].host]; Subr.FreeString[pexseq[i].directory]; Subr.FreeString[pexseq[i].shortname]; ENDLOOP; Subr.LongZone[].FREE[pexseq]; }; PrintSeparator: PROC = { CWF.WF0["===============================\n"L]; }; MakeBTree: PROC = { fileHandle: BTreeSupportDefs.FileHandle; g.oldPhase1FileCacheExists _ TRUE; g.bTreeCap _ Directory.Lookup[ fileName: "ReleaseTool.Phase1BTreeFile$"L, permissions: Directory.ignore ! Directory.Error => { g.oldPhase1FileCacheExists _ FALSE; CONTINUE; }]; IF NOT g.oldPhase1FileCacheExists THEN g.bTreeCap _ Subr.NewFile[ "ReleaseTool.Phase1BTreeFile$"L, Subr.ReadWrite, InitialNumberOfPhase1BTreePages]; fileHandle _ BTreeSupportExtraDefs.OpenFile[g.bTreeCap]; g.bTreeHandle _ BTreeDefs.CreateAndInitializeBTree[ fileH: fileHandle, initializeFile: NOT g.oldPhase1FileCacheExists, isFirstGreaterOrEqual: IsFirstGEQ, areTheyEqual: AreTheyEQ]; }; CleanupBTree: PROC = { IF g.bTreeCap ~= File.nullCapability THEN { BTreeSupportExtraDefs.CloseFile[BTreeDefs.ReleaseBTree[g.bTreeHandle]]; g.bTreeCap _ File.nullCapability; g.oldPhase1FileCacheExists _ FALSE; }; }; MakeBTreeDesc: PROC [s: LS] RETURNS [d: BTreeDefs.Desc] = { RETURN[DESCRIPTOR[LOOPHOLE[s, LONG POINTER], (s.length + 1)/2 + 2]]}; IsFirstGEQ: BTreeDefs.TestKeys = { aS: LS = LOOPHOLE[BASE[a]]; bS: LS = LOOPHOLE[BASE[b]]; FOR i: CARDINAL IN [0..MIN[aS.length, bS.length]) DO aC: CHAR = Inline.BITOR[aS[i], 40B]; bC: CHAR = Inline.BITOR[bS[i], 40B]; SELECT aC FROM > bC => RETURN [TRUE]; < bC => RETURN [FALSE]; ENDCASE; ENDLOOP; RETURN [aS.length >= bS.length] }; AreTheyEQ: BTreeDefs.TestKeys = { aS: LS = LOOPHOLE[BASE[a]]; bS: LS = LOOPHOLE[BASE[b]]; IF aS.length ~= bS.length THEN RETURN [FALSE]; FOR i: CARDINAL IN [0..aS.length) DO IF Inline.BITOR[aS[i], 40B] ~= Inline.BITOR[bS[i], 40B] THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE] }; BuildOuter: PROC = { <> menu: Menus.Menu _ Menus.CreateMenu[]; g _ NEW[Global _ []]; g.verbose _ NEW[BOOL _ TRUE]; <> [dt: g.oldestBcdDate] _ DateAndTime.Parse["4-Aug-82 8:00:00 PDT"]; g.container _ Containers.Create[info: [name: "ReleaseTool", iconic: FALSE, scrollable: FALSE]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry[" Go", Go]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Abort", MyAbort]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Reset", MyReset]]; ViewerOps.SetMenu[g.container, menu]; <> BuildUserInput[]; ViewerOps.PaintViewer[g.container, all]; <> SetupTypescriptPart[]; [g.in, g.out, g.logFile] _ SetUpLogStreams[g.typeScript]; g.tty _ Subr.MakeTTYProcs[g.in, g.out, g.typeScript, MyConfirm]; [] _ CWF.SetWriteProcedure[ToolTTYProc]; destroyEventRegistration _ ViewerEvents.RegisterEventProc[MyDestroy, destroy]; }; BuildUserInput: PROC = { heightSoFar: CARDINAL; l: ViewerClasses.Viewer; CreateButton: PROC[bname, lname: Rope.Text, newLine: BOOL] RETURNS[button: Buttons.Button, label: Labels.Label] = { x: CARDINAL; IF newLine THEN { IF l = NIL THEN heightSoFar _ entryVSpace/2 ELSE heightSoFar _ heightSoFar + entryVSpace + l.wh; x _ 0; } ELSE x _ l.wx + l.ww + entryHSpace; l _ button _ Buttons.Create[ info: [name: bname, parent: g.container, border: FALSE, wx: x, wy: heightSoFar], proc: PushButton]; IF lname ~= NIL THEN l _ label _ Labels.Create[info: [ name: lname, parent: g.container, wx: button.wx + button.ww + entryHSpace, wy: heightSoFar, border: TRUE]]; }; <> [g.phaseButton, g.phaseLabel] _ CreateButton["Phase:", "OneThree", TRUE]; IF g.phase = One THEN Labels.Set[g.phaseLabel, "One"]; [g.useOldPhase1FileButton, g.useOldPhase1FileLabel] _ CreateButton["UseOldPhase1BTree:", "FALSE", FALSE]; IF g.useOldPhase1FileCache THEN Labels.Set[g.useOldPhase1FileLabel, "TRUE"]; [g.useOldPhase3FileButton, g.useOldPhase3FileLabel] _ CreateButton["UseOldPhase3BTree:", "FALSE", FALSE]; IF g.useOldPhase3FileCache THEN Labels.Set[g.useOldPhase3FileLabel, "TRUE"]; [g.updateBTreeButton, g.updateBTreeLabel] _ CreateButton["UpdateBTrees:", "FALSE", FALSE]; IF g.updateBTree THEN Labels.Set[g.updateBTreeLabel, "TRUE"]; <> [g.priorityButton, g.priorityLabel] _ CreateButton["Priority:", "Background", TRUE]; IF g.priority = Process.priorityNormal THEN Labels.Set[g.priorityLabel, "Normal"]; [g.verboseButton, g.verboseLabel] _ CreateButton["Verbose:", "FALSE", FALSE]; IF g.verbose^ THEN Labels.Set[g.verboseLabel, "TRUE"]; [g.fileNameButton,] _ CreateButton["DFFileName:", NIL, FALSE]; l _ g.fileNameViewer _ ViewerTools.MakeNewTextViewer[info: [parent: g.container, wx: l.wx+l.ww+entryHSpace, wy: heightSoFar, ww: 150, wh: entryHeight, data: NIL, scrollable: FALSE, border: FALSE], paint: FALSE]; Containers.ChildXBound[g.container, g.fileNameViewer]; heightSoFar _ heightSoFar+entryVSpace+l.wh; ViewerOps.SetOpenHeight[g.container, heightSoFar]; }; SetupTypescriptPart: PROC = { <> <> <> <> <> <> <> <> <> <> <> <<>> <> g.typeScript _ TypeScript.Create[info: [name: "Release Tool TypeScript", iconic: FALSE]]; }; PushButton: Buttons.ButtonProc = CHECKED { SELECT NARROW[parent, ViewerClasses.Viewer] FROM g.phaseButton => { phaseString: ARRAY EPhase OF Rope.Text _ ["One", "Two", "Three", "OneThree"]; g.phase _ IF g.phase = OneThree THEN One ELSE SUCC[g.phase]; Labels.Set[g.phaseLabel, phaseString[g.phase]]; }; g.priorityButton => { g.priority _ IF g.priority = Process.priorityNormal THEN Process.priorityBackground ELSE Process.priorityNormal; Labels.Set[ g.priorityLabel, IF g.priority = Process.priorityNormal THEN "Normal" ELSE "Background"]; }; g.fileNameButton => { ViewerTools.SetSelection[g.fileNameViewer, NIL]; }; g.verboseButton => { g.verbose^ _ NOT g.verbose^; Labels.Set[g.verboseLabel, IF g.verbose^ THEN "TRUE" ELSE "FALSE"]; }; g.useOldPhase1FileButton => { g.useOldPhase1FileCache _ NOT g.useOldPhase1FileCache; Labels.Set[ g.useOldPhase1FileLabel, IF g.useOldPhase1FileCache THEN "TRUE" ELSE "FALSE"]; }; g.useOldPhase3FileButton => { g.useOldPhase3FileCache _ NOT g.useOldPhase3FileCache; Labels.Set[ g.useOldPhase3FileLabel, IF g.useOldPhase3FileCache THEN "TRUE" ELSE "FALSE"]; }; g.updateBTreeButton => { g.updateBTree _ NOT g.updateBTree; Labels.Set[ g.updateBTreeLabel, IF g.updateBTree THEN "TRUE" ELSE "FALSE"]; }; ENDCASE => ERROR; }; SetUpLogStreams: PROC[ts: TypeScript.TS] RETURNS[in, out, file: STREAM] = { <> file _ FileIO.Open[ fileName: "ReleaseTool.Log", accessOptions: overwrite, streamBufferParms: FileIO.minimumStreamBufferParms]; [in, out] _ ViewerIO.CreateViewerStreams[viewer: ts, name: "Release Tool TypeScript", editedStream: FALSE]; out _ IO.CreateDribbleStream[out, file]; }; MyConfirm: SAFE PROC [in, out: STREAM, data: REF ANY, msg: ROPE, dch: CHAR] RETURNS [CHAR] = CHECKED { value: ATOM; value _ IOMisc.AskUser[ msg: msg, in: in, out: out, keyList: LIST[$Yes, $No, $All, $Local, $Quit]]; -- order is important SELECT value FROM $All => RETURN['a]; $Local => RETURN['l]; $No => RETURN['n]; $Quit => RETURN['q]; $Yes => RETURN['y]; ENDCASE => ERROR; }; ToolTTYProc: PROC[ch: CHAR] = { g.out.PutChar[ch]; }; Flush: PROC = { g.out.Flush[]; }; StrungOut: PROC [out: STREAM, msg: ROPE, string: LS] = { <> out.PutF[msg, IO.string[string]]; }; CardOut: PROC [out: STREAM, msg: ROPE, card: LC] = { <> out.PutF[msg, IO.card[card]]; }; FreeDFSeqAll: PROC = { dfseq: DFSubr.DFSeq _ g.dfseqall; DFSubr.FreeDFSeq[@dfseq]; g.dfseqall _ NIL; }; MyAbort: Menus.MenuProc = CHECKED { g.in.SetUserAbort[]; }; TemporaryStop: PROC = { <> CleanupBTree[]; STPSubr.StopSTP[]; LeafSubr.StopLeaf[]; Flush[]; }; NullTTYProc: PROC[ch: CHAR] = {}; -- prints nothing MyDestroy: ViewerEvents.EventProc = TRUSTED { <> p: PROCESS; IF g = NIL OR event ~= destroy OR viewer ~= g.container THEN RETURN; IF g.busy THEN { MessageWindow.Append[message: "ReleaseTool still busy, try later.", clearFirst: TRUE]; MessageWindow.Blink[]; RETURN; }; [] _ CWF.SetWriteProcedure[NullTTYProc]; -- turn off printing g.busy _ TRUE; CWF.WF0["Destroying ReleaseTool.\n"L]; TemporaryStop[]; -- does flush FreeDFSeqAll[]; Subr.SubrStop[]; p _ FORK DestroyTypeScript[g.typeScript]; -- separate process for monitor lock Process.Detach[p]; g.logFile.Close[]; g^ _ []; g _ NIL; ViewerEvents.UnRegisterEventProc[destroyEventRegistration, destroy]; }; DestroyTypeScript: PROC[ts: TypeScript.TS] = { TypeScript.Destroy[ts]; -- destroys the typescript window }; MyReset: Menus.MenuProc = TRUSTED { ENABLE UNWIND => g.busy _ FALSE; IF g.busy THEN { MessageWindow.Append[message: "ReleaseTool still busy, try later.", clearFirst: TRUE]; MessageWindow.Blink[]; RETURN; }; g.busy _ TRUE; CWF.WF0["Resetting memory... "L]; TemporaryStop[]; FreeDFSeqAll[]; Subr.SubrStop[]; [] _ Subr.SubrInit[256]; CWF.WF0["ReleaseTool memory reset.\n"L]; PrintSeparator[]; g.busy _ FALSE; }; BuildOuter[]; }.