-- CatalogImpl.Mesa -- Gifford, July 16, 1982 10:42 am -- Schroeder, September 20, 1982 1:00 pm DIRECTORY DirMan USING [Dir, Open, Close, Insert, SetFlushMode], CatalogComm, CIFS USING [AddSearchRule, DeleteContext, Enumerate, EProc, Error, GetSearchRules, GetWDir, SetWDir], ConvertUnsafe USING [ToRope], Environment USING [Comparison], FileIO USING [Open, OpenFailed], IO USING [char, Close, Handle, int, PutChar, PutF, PutFR, rope, string], List USING [CompareProc, Sort], LongString USING [EquivalentStrings], Process USING [Yield], Rope USING [Cat, Compare, Equal, Fetch, Find, FromRefText, Length, Lower, Replace, ROPE, Substr, Upper], Storage USING [StringLength], STP USING [Error, FileInfo, FileInfoObject], UECP, UserExec USING [CommandProc, GetNameAndPassword, RegisterCommand, GetStreams]; CatalogImpl: PROGRAM IMPORTS DirMan, CatalogComm, CIFS, ConvertUnsafe, FileIO, IO, List, LongString, Process, Rope, Storage, STP, UECP, UserExec EXPORTS CatalogComm = { -- MDS Usage. ch: CatalogComm.Handle; -- MDS Usage. CatalogError: PUBLIC ERROR[e: CatalogComm.CatalogErrorType] = CODE; NoneFound: PUBLIC SIGNAL = CODE; -- Why things are STRINGs instead of Rope.ROPEs: -- STP calls use STRING in their interfaces. Main: PROC [h: CatalogComm.Handle] = { -- Cleanup is called both before and after doing work. Cleanup: PROC = { h.fCount _ 0; h.locEnumFileName _ NIL; h.directory _ NIL; h.filesystem _ NIL; h.current _ NIL; CatalogComm.FreeWorld[h]; }; { ENABLE { UNWIND => Cleanup[]; ABORTED => { h.out.PutF["Catalog: Aborted!\n"]; GOTO leave; }; STP.Error => { h.out.PutF["Catalog STP Error: %s, Code: %s\n", IO.string[error], IO.char[reply]]; IF h.reportSignals THEN REJECT ELSE GOTO leave; }; CIFS.Error => CHECKED { h.out.PutF["Catalog CIFS Error: %s, Code: %s\n", IO.rope[error], IO.char[reply]]; IF h.reportSignals THEN REJECT ELSE GOTO leave; }; CatalogComm.CatalogError => { IF e=catalogAbort THEN { h.out.PutF["Catalog: Aborted!\n"]; GOTO leave; }; h.out.PutF["Catalog CatalogError: %s\n", IO.rope[CatalogComm.CatalogErrorString[e]]]; IF h.reportSignals THEN REJECT ELSE GOTO leave; }; ANY => { IF h.reportSignals THEN REJECT ELSE h.out.PutF["Catalog: Unknown error\n"]; GOTO leave; }; }; h.cmdindex _ 0; h.oldWorkingDir _ CIFS.GetWDir[]; h.oldSearchRules _ CIFS.GetSearchRules[]; h.argv _ UECP.Parse[h.he.commandLine]; CIFS.DeleteContext[]; RestoreDefaults[h]; -- Main loop, parse command and do work DO h.cmdindex _ h.cmdindex+1; Cleanup[]; IF h.cmdindex>=h.argv.argc THEN GOTO leave; IF h.argv[h.cmdindex].Length[]=0 THEN GOTO leave; IF Rope.Equal[h.argv[h.cmdindex], "Catalog", FALSE] THEN LOOP; IF h.argv[h.cmdindex].Fetch[0]='- THEN { FOR i: INT _ 1, i+1 WHILE i { h.saveLinksComments _ FALSE; }; 'n => { h.doStoreRemote _ FALSE; }; 'l => { h.listLocal _ TRUE; }; 's => { h.reportSignals _ TRUE; }; 'r => { h.readLocal _ TRUE; }; 'z => { h.listDirsOnly _ TRUE; }; 'v => { h.verbose _ TRUE; }; ENDCASE => GOTO usage; ENDLOOP; LOOP; -- Get next command line token }; IF h.argv[h.cmdindex].Fetch[0]#'/ THEN GOTO usage; h.filesystem _ CatalogComm.SubStringCopy[s: h.argv[h.cmdindex], start: 1, stop: CatalogComm.FindFirst[s: h.argv[h.cmdindex], c: '/, start: 2]-1 ! NoneFound => GOTO usage]; h.directory _ CatalogComm.SubStringCopy[s: h.argv[h.cmdindex], start: CatalogComm.FindFirst[s: h.argv[h.cmdindex], c: '/, start: 2]+1, stop: IF h.argv[h.cmdindex].Fetch[h.argv[h.cmdindex].Length[]-1] = '/ THEN h.argv[h.cmdindex].Length[]-2 ELSE h.argv[h.cmdindex].Length[]-1]; h.out.PutF["Cataloging /%s/%s/\n", IO.rope[h.filesystem], IO.rope[h.directory]]; h.locEnumFileName _ IO.PutFR["%s-%s.enum", IO.rope[h.filesystem], IO.rope[h.directory]]; FOR i: INT _ 0, i+1 WHILE i NULL; usage => h.out.PutF["usage: Catalog [-switches] /hostname/directory/names/ (multilevel ok)\n"]; }; Cleanup[]; CIFS.SetWDir[h.oldWorkingDir]; h.oldWorkingDir _ NIL; WHILE h.oldSearchRules#NIL DO CIFS.AddSearchRule[path: h.oldSearchRules.first, before: FALSE]; h.oldSearchRules _ h.oldSearchRules.rest; ENDLOOP; h.out.PutF["Catalog -- Goodbye\n"]; }; EnumFromLocalFile: PUBLIC PROC [h: CatalogComm.Handle] = { aFileName: STRING _ [100]; aFile: STP.FileInfoObject; localDirectory: STRING _ [100]; localVersion: STRING _ [20]; localName: STRING _ [100]; sdir, edir, bang: NAT; locEnumStream: IO.Handle; aFile.directory _ localDirectory; aFile.body _ localName; aFile.version _ localVersion; locEnumStream _ FileIO.Open[h.locEnumFileName, read ! FileIO.OpenFailed => CHECKED { h.out.PutF["Catalog: Can't find local file!\n"]; ERROR CatalogComm.CatalogError[cantEnumerate]; }]; h.out.PutF["Catalog: Building directories from local enumeration file %s\n", IO.rope[h.locEnumFileName]]; WHILE CatalogComm.GetLine[h, locEnumStream, aFileName] DO { Process.Yield[]; IF CatalogComm.CheckForAbort[h] THEN { locEnumStream.Close[]; ERROR CatalogComm.CatalogError[catalogAbort]; }; aFile.directory.length _ aFile.body.length _ 0; { ever: NAT; bang _ CatalogComm.LFindLast[aFileName, '! ! NoneFound => GOTO NoVersion]; ever _ bang + 1; WHILE aFileName[ever] IN ['0..'9] AND ever < aFileName.length DO ever _ SUCC[ever]; ENDLOOP; localVersion.length _ 0; aFile.version _ localVersion; CatalogComm.LSubStringAppend[from: aFileName, to: aFile.version, start: bang+1, stop: ever-1]; EXITS NoVersion => {bang _ aFileName.length; aFile.version _ NIL; }; }; edir _ CatalogComm.LFindLast[aFileName, '>]; CatalogComm.LSubStringAppend[from: aFileName, to: aFile.body, start: edir+1, stop: bang-1]; sdir _ CatalogComm.LFindFirst[aFileName, '<, 0]; CatalogComm.LSubStringAppend[from: aFileName, to: aFile.directory, start: sdir+1, stop: edir-1]; ParseFile[h: h, vf: @aFile]; }; ENDLOOP; locEnumStream.Close[]; }; MakeCurrent: PROC [h: CatalogComm.Handle, key: Rope.ROPE, createIfNotFound: BOOL] = { p: LIST OF REF CatalogComm.Directory; -- cache most recently found directory as h.current. IF h.current#NIL AND Rope.Compare[s1: key, s2: h.current.name, case: FALSE]=equal THEN RETURN; FOR p _ h.root, p.rest WHILE p#NIL DO Process.Yield[]; IF Rope.Compare[s1: p.first.name, s2: key, case: FALSE]=equal THEN EXIT; ENDLOOP; IF p = NIL THEN { IF NOT createIfNotFound THEN { h.current _ NIL; RETURN; }; h.current _ NEW[CatalogComm.Directory]; h.root _ CONS[h.current, h.root]; h.current.name _ key; h.current.chain _ NIL; } ELSE h.current _ p.first; }; ParseFile: PUBLIC PROC [h: CatalogComm.Handle, vf: STP.FileInfo] = { t: REF CatalogComm.Node; Process.Yield[]; IF h.fCount=0 THEN h.out.PutChar['.]; h.fCount _ h.fCount + 1; IF h.fCount>=10 THEN h.fCount _ 0; IF Storage.StringLength[vf.directory]=0 THEN ERROR CatalogComm.CatalogError[nilDirectory]; IF Storage.StringLength[vf.body]=0 THEN ERROR CatalogComm.CatalogError[nilFilename]; -- When making new directories, note old ones but don't make a node. FOR i: NAT IN [0..vf.directory.length) DO IF vf.directory[i] = '> THEN vf.directory[i] _ '/; ENDLOOP; IF h.current=NIL OR NOT AlreadyCurrent[s: vf.directory, r: h.current.name] THEN MakeCurrent[h: h, key: ConvertUnsafe.ToRope[vf.directory], createIfNotFound: TRUE]; IF LongString.EquivalentStrings[vf.body, "dir.bt"] THEN { h.current.oldDirExists _ TRUE; } ELSE { t _ NEW[CatalogComm.Node]; t.what _ file; t.directory _ NIL; FOR i: NAT IN [0..vf.body.length) DO IF vf.body[i] = '> THEN vf.body[i] _ '/; ENDLOOP; IF Storage.StringLength[vf.version]#0 THEN t.name _ IO.PutFR["%s!%s", IO.string[vf.body], IO.string[vf.version]] ELSE t.name _ ConvertUnsafe.ToRope[vf.body]; h.current.fileCount _ SUCC[h.current.fileCount]; h.current.chain _ CONS[t, h.current.chain]; }; }; AlreadyCurrent: PROC [s: LONG STRING, r: Rope.ROPE] RETURNS [BOOL] = { slen: NAT _ IF s = NIL THEN 0 ELSE s.length; IF slen # r.Length[] THEN RETURN [FALSE]; FOR i: NAT IN [0..slen) DO IF s[i]#r.Fetch[i] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; EnterDirectories: PROC [h: CatalogComm.Handle] = { topLevelFound: BOOL _ FALSE; parent: Rope.ROPE _ NIL; entry: Rope.ROPE _ NIL; lastGR: INT; madeDirectory: BOOL; t: REF CatalogComm.Node; p: LIST OF REF CatalogComm.Directory; -- For each directory on the list, if there is a parent listed, make an entry in it. DO madeDirectory _ FALSE; FOR p _ h.root, p.rest WHILE p # NIL DO Process.Yield[]; IF p.first.entryMade THEN LOOP; -- We've already handled this one. -- we can't make an entry for the top level directory in it's parent directory, so skip it. -- This stuff happens if the enumerated directory has a single component name. lastGR _ CatalogComm.FindLast[s: p.first.name, c: '/ ! NoneFound => { IF Rope.Compare[s1: p.first.name, s2: h.directory, case: FALSE]=equal THEN { -- found the one we expect IF topLevelFound THEN ERROR CatalogComm.CatalogError[topLevelDirectoryFoundTwice]; topLevelFound _ TRUE; p.first.entryMade _ TRUE; LOOP; } ELSE ERROR CatalogComm.CatalogError[unknownTopLevelDirectory]; } ]; parent _ CatalogComm.SubStringCopy[s: p.first.name, start: 0, stop: lastGR-1]; entry _ CatalogComm.SubStringCopy[s: p.first.name, start: lastGR+1, stop: p.first.name.Length[]-1]; MakeCurrent[h, parent, FALSE]; -- This case happens when the enumerated directory has a multiple component name. -- Or when a directory contains only other directories. IF h.current = NIL THEN { -- Top level dir, probably, should be only one IF Rope.Compare[s1: p.first.name, s2: h.directory, case: FALSE]=equal THEN { IF topLevelFound THEN ERROR CatalogComm.CatalogError[topLevelDirectoryFoundTwice]; topLevelFound _ TRUE; p.first.entryMade _ TRUE; LOOP; } ELSE { madeDirectory _ TRUE; MakeCurrent[h, parent, TRUE]; -- create an intervening directory }; }; t _ NEW[CatalogComm.Node]; t.what _ directory; t.directory _ p.first.name; -- full path name, needed for lister t.name _ Rope.Cat[entry, "/"]; h.current.chain _ CONS[t, h.current.chain]; h.current.directoryCount _ SUCC[h.current.directoryCount]; p.first.entryMade _ TRUE; ENDLOOP; IF NOT madeDirectory THEN EXIT; ENDLOOP; }; SortDirectories: PROC [h: CatalogComm.Handle] = { fc, dc, ad, lc, cc: INT _ 0; p: REF CatalogComm.Directory; pp: LIST OF REF CatalogComm.Directory; -- Count up the files and directories FOR pp _ h.root, pp.rest WHILE pp # NIL DO Process.Yield[]; h.out.PutChar['.]; p _ pp.first; fc _ fc + p.fileCount; dc _ dc + p.directoryCount; lc _ lc + p.linkCount; cc _ cc + p.commentCount; ad _ SUCC[ad]; p.chain _ List.Sort[p.chain, LessThan]; ENDLOOP; h.out.PutF["\nCatalog: %d files, %d directories, %d links, and %d comments in %d directories.\n", IO.int[fc], IO.int[dc], IO.int[lc], IO.int[cc], IO.int[ad]]; }; -- Sort by directory over name, then by name, then decreasing by version. LessThan: List.CompareProc = CHECKED { p: REF CatalogComm.Node _ NARROW[ref1]; q: REF CatalogComm.Node _ NARROW[ref2]; i: Environment.Comparison _ Rope.Compare[s1: p.name, s2: q.name, case: FALSE]; IF i=equal THEN SELECT TRUE FROM p.what=directory AND q.what=file=> i _ less; p.what=file AND q.what=directory => i _ greater; ENDCASE => NULL; RETURN[i] }; StoreRemote : PROC [h: CatalogComm.Handle] = { t: REF CatalogComm.Node; tt: LIST OF REF; p: REF CatalogComm.Directory; pp: LIST OF REF CatalogComm.Directory; dFH: DirMan.Dir _ NIL; {ENABLE UNWIND => { IF dFH#NIL THEN dFH.Close[]; }; FOR pp _ h.root, pp.rest WHILE pp # NIL DO Process.Yield[]; p _ pp.first; IF h.verbose THEN h.out.PutF[" %s", IO.rope[p.name]] ELSE h.out.PutChar['.]; dFH _ DirMan.Open[name: Rope.Cat["/", ch.filesystem, "/", p.name], erase: TRUE]; dFH.SetFlushMode[flush: FALSE]; FOR tt _ p.chain, tt.rest WHILE tt#NIL DO t _ NARROW[tt.first]; dFH.Insert[name: t.name, link: t.target, comment: t.comment]; ENDLOOP; dFH.Close[]; ENDLOOP; h.out.PutChar['\n]; }; }; EnterLinksAndOrComments: PROC [h: CatalogComm.Handle] = { tt: LIST OF REF CatalogComm.Directory; t: REF CatalogComm.Directory; FOR tt _ h.root, tt.rest WHILE tt#NIL DO t _ tt.first; IF NOT t.oldDirExists THEN LOOP; -- There exists a previous dir.bt file IF h.verbose THEN h.out.PutF["%s ", IO.rope[t.name]] ELSE h.out.PutChar['.]; EnterLCInternal[h: h, dir: t]; ENDLOOP; h.out.PutChar['\n]; }; EqualTextRope: SAFE PROC [t: REF READONLY TEXT, r: Rope.ROPE, case: BOOL] RETURNS [BOOL] = CHECKED { tlen: NAT _ IF t = NIL THEN 0 ELSE t.length; IF tlen # r.Length[] THEN RETURN [FALSE]; IF case THEN FOR i: NAT IN [0..tlen) DO IF t[i]#r.Fetch[i] THEN RETURN[FALSE]; ENDLOOP ELSE FOR i: NAT IN [0..tlen) DO IF Rope.Upper[t[i]]#Rope.Upper[r.Fetch[i]] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; EnterLCInternal: PROC [h: CatalogComm.Handle, dir: REF CatalogComm.Directory] = CHECKED { dirName: Rope.ROPE; MyEP: CIFS.EProc = { pp: LIST OF REF; p: REF CatalogComm.Node; directory: BOOL _ name[name.length-1]='/; ll: NAT _ IF link=NIL THEN 0 ELSE link.length; cl: NAT _ IF comment=NIL THEN 0 ELSE comment.length; IF ll=0 AND cl=0 THEN RETURN[stop: FALSE]; FOR pp _ dir.chain, pp.rest WHILE pp#NIL DO p _ NARROW[pp.first]; IF directory # (p.what=directory) THEN LOOP; -- names must match exactly or up to version number of actual file -- If old dir.dir entry has a version, then must match exactly. -- If old dir.dir did not have a version, then must match any version. IF EqualTextRope[t: name, r: p.name, case: FALSE] OR EqualTextRope[ t: name, r: Rope.Substr[base: p.name, start: 0, len: MAX[Rope.Find[s1: p.name, s2: "!"], 0]], case: FALSE] THEN { -- Discard an old link for something that is now real IF cl#0 THEN { p.comment _ Rope.FromRefText[comment]; dir.commentCount _ dir.commentCount + 1; }; }; REPEAT FINISHED => { -- If it's not a link, then discard it because it's a comment for a deleted item IF ll#0 THEN { p _ NEW[CatalogComm.Node]; p.what _ link; p.directory _ dir.name; p.name _ Rope.FromRefText[name]; p.target _ Rope.FromRefText[link]; p.comment _ Rope.FromRefText[comment]; dir.chain _ CONS[p, dir.chain]; dir.linkCount _ dir.linkCount + 1; IF cl#0 THEN dir.commentCount _ dir.commentCount + 1; }; }; ENDLOOP; RETURN[stop: FALSE]; }; dirName _ IO.PutFR["/%s/%s/", IO.rope[h.filesystem], IO.rope[dir.name]]; CIFS.Enumerate[dir: dirName, pattern: "*", p: MyEP ! CIFS.Error => CONTINUE]; }; RestoreDefaults: PROC [h: CatalogComm.Handle] = { h.reportSignals _ FALSE; h.doStoreRemote _ TRUE; h.listLocal _ FALSE; h.readLocal _ FALSE; h.listDirsOnly _ FALSE; h.saveLinksComments _ TRUE; h.verbose _ FALSE; }; LoopMain: UserExec.CommandProc = TRUSTED { ch _ NEW[CatalogComm.CatalogObject]; [ch.in, ch.out] _ exec.GetStreams[]; ch.eh _ exec; ch.he _ event; [name: ch.name, password: ch.password] _ UserExec.GetNameAndPassword[]; Main[ch]; }; Init: PROC = { UserExec.RegisterCommand["Catalog.~", LoopMain, "Construct CIFS directories", docstring]; }; docstring: Rope.ROPE = "Command format: Catalog accepts commands of the form Catalog {[-switches] remotePathName} where a remote path name has the form /HostName/Directory/. Directory can include '/ for subdirectories. The braces indicate repeated commands are OK. Switches must be repeated (or changed) for repeated commands. The switches are (default values in parentheses): i Ignore exisiting comments and links (FALSE) n Don't store new directories. (FALSE) l Produce nested listing of enumeration. (FALSE) r Read local enumeration file. (FALSE) s Report SIGNALs to debugger (FALSE) z Produce nested listing of enumeration, directories only. (FALSE) v List names of directories as they are handled. (FALSE) Catalog will create a local enumeration file. Reading it (with -r) is faster than a second remote enumeration if the need arises to do it twice.\n"; -- main program Init[]; }. 25-Jul-81 22:50:58, Stewart, modified from MTypeImpl.mesa 26-Jul-81 0:54:22, Stewart, stuff added from dfdiffimpl.mesa 30-Jul-81 10:18:37, Stewart, added arrays of files 30-Jul-81 10:51:18, Stewart, EquivalentString used! 8-Aug-81 17:38:25, Stewart, Read from Disk instead of FTP Enumerate 8-Aug-81 18:42:22, Stewart, create directory files without dir links 9-Aug-81 21:35:39, Stewart, make dir entries for dirs 11-Aug-81 21:31:01, Stewart, modify LessThan 12-Aug-81 23:18:55, Stewart, BubbleSort, recursive print procedure 15-Aug-81 16:39:46, Stewart, command line switches, remote files ignore dir.dir files 15-Aug-81 19:12:21, Stewart, combined Enumeration 16-Aug-81 19:20:36, Stewart, General cleanup 24-Aug-81 18:57:08, Stewart, Cedarize 27-Aug-81 1:04:21, Stewart, More Cedarize 29-Aug-81 23:31:47, Stewart, PF 30-Aug-81 0:39:18, Stewart, Split into two parts with interface 2-Sep-81 0:14:36, Stewart, creates intervening directories if needed. 2-Sep-81 1:43:08, Stewart, directory listings only 2-Sep-81 1:54:52, Stewart, added limit to length of local file name since Pilot Directory breaks 3-Sep-81 0:29:51, Stewart, FREE all the objects 3-Sep-81 1:14:07, Stewart, Use CatalogObject 4-Sep-81 21:09:34, Stewart, numbered local files plus command file 5-Sep-81 16:08:41, Stewart, Yields added 17-Oct-81 22:38:38, Stewart, New Cedar, STP replaces FTP 1-Nov-81 17:37:04, Stewart, Add space after names in dir.dir files, for future comments and links 9-Feb-82 23:14:41, Stewart, Cedar 2.3, IO, comments and links March 21, 1982 3:08 pm, Stewart, Cedar 2.5.1, Viewers, CIFS 25-Mar-82 15:58:48, Stewart, remove Subr, STPSubr March 26, 1982 5:38 pm, Stewart, bulletproofing June 16, 1982 9:58 am, Gifford, new BTree directory system Questions: It shouldn't be necessary to store a copy of the directory name in EACH Node.directory of type file, because that is stored in the Directory.name. However, the Node.directory IS needed for Nodes of type directory. It is uded by the lister. Problems: STP doesn't connect to a directory when needed Ź)˜JšœėjĻkœ…:˜€„—…—R‚R±