-- File: ClientFileTool.mesa -- Last edit by Russ Atkinson: May 13, 1982 3:14 pm -- Schmidt: 25-Jul-81 12:09:21 -- NOTE: change the create date message when changing this beast DIRECTORY Ascii USING [CR], Directory USING [CreateFile, DeleteFile, Error, GetDefaultContext, GetNext, GetProps, GetWD, Handle, Lookup, ModifyContext], DirectoryExtras USING [ForgetVolumes], Event USING[Notify, Reason], File USING [ Capability, GetAttributes, GetSize, grow, nullCapability, PageCount, Permissions, read, SetSize, shrink, write], FormSW USING [ AllocateItemDescriptor, ClientItemsProcType, CommandItem, line0, line1, line2, nextPlace, ProcType, StringItem], HeapString USING [AppendChar, AppendString], Inline USING [LowHalf], Put USING [Char, Blanks, Date, LongDecimal, Text], Space USING [ CopyOut, Create, Delete, Handle, Map, Unmap, virtualMemory], Storage USING [FreeString, String], String USING [AppendLongDecimal, AppendString, EquivalentString], Time USING [Current, Packed], Tool USING [Create, MakeFormSW, MakeFileSW, MakeSWsProc], ToolDriver USING [Address, NoteSWs], Volume USING [ Close, GetAttributes, GetLabelString, GetNext, GetType, ID, maxNameLength, nullID, PageCount, Type, TypeSet, systemID], VolumeExtras USING [OpenVolume], Window USING [Handle], TTY USING [ResetUserAbort, UserAbort]; ClientFileTool: PROGRAM IMPORTS Directory, DirectoryExtras, Event, File, FormSW, HeapString, Inline, Put, Space, Storage, String, Time, Tool, ToolDriver, TTY, Volume, VolumeExtras = BEGIN OPEN HeapString; Bool: TYPE = BOOLEAN; Char: TYPE = CHARACTER; CommandDesc: TYPE = RECORD [cpName, localName: STRING _ NIL]; CommandType: TYPE = {get, list, delete, name, getboth}; copier: CommandDesc _ CommandDesc[NIL, NIL]; clientVolume: Volume.ID _ Volume.nullID; volumeOpened: BOOLEAN _ FALSE; -- some slight protection here window, cpMsgSW, cpFormSW: Window.Handle _ NIL; -- Executive Commands Invoked from Menu GetFileFromClientVolume: FormSW.ProcType = { DoCommand[get]}; DeleteFilesFromClientVolume: FormSW.ProcType = { DoCommand[delete]}; ListFilesOnClientVolume: FormSW.ProcType = { DoCommand[list]}; NameFilesOnClientVolume: FormSW.ProcType = { DoCommand[name]}; GetBothFilesOnClientVolume: FormSW.ProcType = { DoCommand[getboth]}; DoCommand: PROC [command: CommandType] = { OPEN HeapString; volumeLabel: STRING _ "Client"L; fileName, pathName, localName, workingDir, scratch: STRING _ NIL; oldContext: Directory.Handle _ Directory.GetDefaultContext[]; fileCount, pageCount: LONG CARDINAL _ 0; useLocal: Bool _ FALSE; IF volumeOpened THEN {DisplayMsg["Client volume already opened!"L]; RETURN}; IF clientVolume = Volume.nullID THEN clientVolume _ MapClientVolumeNameToID[volumeLabel]; IF clientVolume = Volume.nullID THEN {DisplayMsg["Client volume not found!"L]; RETURN}; fileName _ Storage.String[60]; pathName _ Storage.String[60]; localName _ Storage.String[60]; workingDir _ Storage.String[60]; scratch _ Storage.String[60]; Directory.GetWD[oldContext, workingDir]; AppendChar[@pathName, '<]; AppendString[@pathName, volumeLabel]; AppendString[@pathName, ">SysDir>"L]; IF copier.cpName # NIL THEN AppendString[@fileName, copier.cpName]; IF command = get AND copier.localName # NIL AND copier.localName.length > 0 THEN {AppendString[@localName, copier.localName]; useLocal _ TRUE}; {single: Bool _ SingleFilePattern[fileName]; nextName: STRING _ [80]; pathNameLen: NAT _ pathName.length; triedBcd: BOOL _ FALSE; triedMesa: BOOL _ FALSE; IF useLocal THEN {IF NOT single THEN {DisplayMsg["Sorry, client patterns require blank local name."L]; GO TO out}; IF NOT SingleFilePattern[localName] THEN {DisplayMsg["Sorry, patterns not allowed in local name."L]; GO TO out}}; VolumeExtras.OpenVolume [volume: clientVolume, readOnly: command # delete ! ANY => {DisplayMsg["Client volume could not be opened!"L]; GO TO out}]; volumeOpened _ TRUE; Directory.ModifyContext[oldContext, pathName]; DisplayMsg[""L]; {root: File.Capability; volSize,freePages: Volume.PageCount; [volSize, freePages, root] _ Volume.GetAttributes[clientVolume]; DisplayNums ["-- Client volume -- #total pages: "L, volSize, ", #free pages: "L, freePages]}; IF fileName.length = 0 THEN {DisplayMsg["No client file name."L]; GO TO out}; DO localFile: File.Capability _ File.nullCapability; pages: File.PageCount _ 0; cap: File.Capability _ File.nullCapability; lagName: STRING _ [80]; IF TTY.UserAbort[] THEN { TTY.ResetUserAbort[]; DisplayMsg["!!Aborted!!"]; GO TO out; }; IF command = delete THEN String.AppendString[lagName, nextName]; IF single THEN {nextName.length _ 0; String.AppendString[nextName, pathName]; String.AppendString[nextName, fileName]; SELECT FALSE FROM command = getboth => {}; triedBcd => {String.AppendString[nextName, ".bcd"]; triedBcd _ TRUE}; triedMesa => {String.AppendString[nextName, ".mesa"]; triedMesa _ TRUE}; ENDCASE => EXIT; cap _ Directory.Lookup [fileName: nextName, permissions: File.read ! Directory.Error --[type]-- => {IF type # fileNotFound THEN DisplayMsg[nextName, " has a problem!"L] ELSE DisplayMsg[nextName, " could not be found!"L]; EXIT}]} ELSE cap _ Directory.GetNext [pathName, nextName, nextName ! ANY => CONTINUE]; IF nextName.length = 0 OR cap = File.nullCapability THEN EXIT; IF NOT Match[fileName, 0, nextName, pathNameLen, command = getboth] THEN LOOP; pages _ File.GetSize[cap]; SELECT command FROM get, getboth => {scratch.length _ 0; AppendString[@scratch, workingDir]; IF pages > 8191 THEN {DisplayMsg[nextName, " is too big!"L]; LOOP}; IF NOT useLocal THEN {-- skip over path name to get local name pos: NAT _ pathNameLen; localName.length _ 0; FOR pos: NAT IN [pathNameLen..nextName.length) DO AppendChar[@localName, nextName[pos]]; ENDLOOP}; AppendString[@scratch, localName]; localFile _ Directory.Lookup [fileName: scratch, permissions: File.read+File.write+File.grow+File.shrink ! Directory.Error --[type]-- => {IF type # fileNotFound THEN {DisplayMsg[localName, " has a problem"L]; LOOP}; CONTINUE}]; IF localFile = File.nullCapability THEN localFile _ Directory.CreateFile [fileName: scratch, fileType: File.GetAttributes[cap].type, size: pages ! ANY => {DisplayMsg[fileName, " can't be created locally"L]; LOOP}] ELSE File.SetSize[localFile, pages]; DisplayMsg["Copy "L, nextName]; {chunk: CARDINAL _ Inline.LowHalf[pages]; space: Space.Handle; base: File.PageCount _ 0; IF chunk > 64 THEN chunk _ 64; IF chunk > 0 THEN space _ Space.Create [chunk, Space.virtualMemory ! ANY => {DisplayMsg ["Could not create VM space for "L, nextName]; LOOP}]; WHILE base < pages DO -- loop to transfer file (avoid LARGE spaces) Space.Map[space, [cap, base]]; Space.CopyOut[space, [localFile, base]]; Space.Unmap[space]; base _ base + chunk; ENDLOOP; Space.Delete[space]}; DisplayMsg[" to "L, scratch]; Event.Notify[Event.Reason[newFiles]]; }; delete => {Directory.DeleteFile [nextName ! ANY => {DisplayMsg[nextName, " could not be deleted!"L]; LOOP}]; DisplayMsg[nextName, " deleted."L]; nextName.length _ 0; String.AppendString[nextName, lagName]; }; list => {readDate, writeDate, createDate: Time.Packed; byteLength: LONG CARDINAL; parent: File.Capability; [readDate, writeDate, createDate, byteLength, parent] _ Directory.GetProps [cap, lagName ! ANY => {DisplayMsg[nextName, " ???"L]; LOOP}]; DisplayMsg[lagName, " "L]; {size: STRING _ [12]; lim: NAT _ lagName.length + 1; String.AppendLongDecimal[size, byteLength]; lim _ lim + size.length; IF lim < 36 THEN Put.Blanks[cpMsgSW, 36-lim]; Put.Text[cpMsgSW, size]; size.length _ 0; String.AppendLongDecimal[size, pages]; IF size.length < 5 THEN Put.Blanks[cpMsgSW, 5-size.length]; Put.Text[cpMsgSW, size]; }; Put.Text[cpMsgSW, "p "L]; Put.Date[cpMsgSW, createDate]; }; name => {IF fileCount = 0 THEN Put.Char[cpMsgSW, Ascii.CR]; Put.Char[cpMsgSW, ' ]; FOR i: NAT IN [pathNameLen..nextName.length) DO Put.Char[cpMsgSW, nextName[i]]; ENDLOOP; }; ENDCASE => ERROR; fileCount _ fileCount + 1; pageCount _ pageCount + pages; IF single AND command # getboth THEN EXIT; ENDLOOP; EXITS out => {}}; IF volumeOpened THEN { IF workingDir # NIL AND workingDir.length > 0 THEN { Directory.ModifyContext[oldContext, workingDir]}; DirectoryExtras.ForgetVolumes[]; Volume.Close[clientVolume]; volumeOpened _ FALSE}; Storage.FreeString[fileName]; Storage.FreeString[pathName]; Storage.FreeString[localName]; Storage.FreeString[workingDir]; Storage.FreeString[scratch]; {lead: STRING _ SELECT command FROM get, getboth => "-- Copied #files: "L, delete => "-- Deleted #files: "L, list => "-- Listed #files: "L, name => "-- Named #files: "L, ENDCASE => ERROR; DisplayNums[lead, fileCount, ", #pages: "L, pageCount]}; }; DisplayNums: PROC [s1: STRING _ NIL, d1: LONG CARDINAL _ 0, s2: STRING _ NIL, d2: LONG CARDINAL _ 0] = { Put.Char[cpMsgSW, Ascii.CR]; IF s1 # NIL THEN {Put.Text[cpMsgSW, s1]; Put.LongDecimal[cpMsgSW, d1]}; IF s2 # NIL THEN {Put.Text[cpMsgSW, s2]; Put.LongDecimal[cpMsgSW, d2]}; }; SingleFilePattern: PROC [pattern: STRING] RETURNS [BOOLEAN] = { patlen: NAT = IF pattern = NIL THEN 0 ELSE pattern.length; pos: NAT _ 0; FOR pos IN [pos..patlen) DO c: Char = pattern[pos]; IF c = '* OR c = '# OR c = ' THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE]}; Match: PROC [pattern: STRING, patpos: NAT, object: STRING, objpos: NAT, isGetBoth: Bool] RETURNS [Bool] = { -- return TRUE if the object matches the pattern, -- FALSE if it does not match. The pattern may contain -- * characters which will match 0 or more characters -- in the object. Case does not matter. -- If isGetBoth, then .bcd and .mesa extensions also match. -- Blanks in the pattern separate alternate matches submatch: PROC [i1: NAT, len1: INTEGER, i2: NAT, len2: INTEGER, isGetBoth: BOOLEAN] RETURNS [BOOLEAN] = { DO c1: Char _ IF len1 > 0 THEN Lower[pattern[i1]] ELSE ' ; IF c1 = ' THEN {IF len2 = 0 THEN RETURN [TRUE]; IF NOT isGetBoth THEN RETURN [FALSE]; RETURN [Match[".mesa"L, 0, object, i2, FALSE] OR Match[".bcd"L, 0, object, i2, FALSE]]}; IF c1 = '* THEN { -- quick kill for * at end of pattern IF len1 = 1 THEN RETURN [TRUE]; -- else must take all combinations {j1: NAT _ i1 + 1; nlen1: INTEGER _ len1 - 1; j2: NAT _ i2; nlen2: INTEGER _ len2; WHILE nlen2 >= 0 DO IF submatch[j1, nlen1, j2, nlen2, isGetBoth] THEN RETURN [TRUE]; j2 _ j2 + 1; nlen2 _ nlen2 - 1; ENDLOOP}; RETURN [FALSE]; }; IF len2 <= 0 THEN RETURN [FALSE]; -- at this point demand an exact match in both strings {c2: Char _ Lower[object[i2]]; IF c1 # c2 AND c1 # '# THEN -- mismatch, so reject this alternative RETURN [FALSE]; }; i1 _ i1 + 1; len1 _ len1 - 1; i2 _ i2 + 1; len2 _ len2 - 1; ENDLOOP; }; -- submatch patlen: CARDINAL = IF pattern = NIL THEN 0 ELSE pattern.length; objlen: CARDINAL = IF object = NIL THEN 0 ELSE object.length; DO -- cycle through all of the alternatives in the pattern WHILE patpos < patlen AND pattern[patpos] = ' DO -- skip over blanks patpos _ patpos + 1; ENDLOOP; IF patpos >= patlen THEN RETURN [FALSE]; IF submatch[patpos, patlen-patpos, objpos, objlen-objpos, isGetBoth] THEN RETURN [TRUE]; WHILE patpos < patlen AND pattern[patpos] # ' DO -- skip over non-blanks patpos _ patpos + 1; ENDLOOP; ENDLOOP; }; Lower: PROC [c: Char] RETURNS [Char] = INLINE { IF c IN ['A..'Z] THEN RETURN[LOOPHOLE[LOOPHOLE[c,CARDINAL]+40B]] ELSE RETURN[c]}; -- Error Reporting Procedures DisplayMsg: PROCEDURE [s1, s2: STRING _ NIL] = { Put.Char[cpMsgSW, Ascii.CR]; IF s1 # NIL THEN Put.Text[cpMsgSW, s1]; IF s2 # NIL THEN Put.Text[cpMsgSW, s2]}; -- Initialization and Window Management MakeSWs: Tool.MakeSWsProc = { OPEN Tool; addresses: ARRAY [0..2) OF ToolDriver.Address; cpFormSW _ MakeFormSW[window: window, formProc: MakeForm]; cpMsgSW _ MakeFileSW[window: window, name: "ClientFileTool.log"L, h: 360]; addresses _ [["cpFormSW"L, cpFormSW], ["cpMsgSW"L, cpMsgSW]]; ToolDriver.NoteSWs["Executive"L, DESCRIPTOR[addresses]]}; MakeForm: FormSW.ClientItemsProcType = { OPEN FormSW; items _ AllocateItemDescriptor[7]; items[0] _ CommandItem [tag: "Get"L, place: [20, line0], proc: GetFileFromClientVolume]; items[1] _ CommandItem [tag: "GetBoth"L, place: [-30, line0], proc: GetBothFilesOnClientVolume]; items[2] _ CommandItem [tag: "List"L, place: [-30, line0], proc: ListFilesOnClientVolume]; items[3] _ CommandItem [tag: "Names"L, place: [-30, line0], proc: NameFilesOnClientVolume]; items[4] _ CommandItem [tag: "DELETE!"L, place: [-50, line0], proc: DeleteFilesFromClientVolume]; items[5] _ StringItem[ tag: "ClientFile"L, place: [0,line1], string: @copier.cpName, inHeap: TRUE]; items[6] _ StringItem[ tag: "LocalFile"L, place: [0,line2], string: @copier.localName, inHeap: TRUE]; RETURN[items: items, freeDesc: TRUE]; }; -- copied for now (will get exported from CascadeExec) MapClientVolumeNameToID: PROCEDURE [name: STRING] RETURNS [volumeID: Volume.ID] = { myType: Volume.Type = Volume.GetType[Volume.systemID]; debuggers: Volume.TypeSet _ []; volumeID _ Volume.nullID; IF myType = FIRST[Volume.Type] THEN RETURN; debuggers[PRED[myType]] _ TRUE; UNTIL (volumeID _ Volume.GetNext[volumeID, debuggers]) = Volume.nullID DO volumeLabel: STRING _ [Volume.maxNameLength]; Volume.GetLabelString[volumeID, volumeLabel]; IF String.EquivalentString[volumeLabel, name] THEN EXIT; ENDLOOP}; -- Initialization Init: PROC = { window _ Tool.Create [name: "ClientFile Tool of May 13, 1982"L, makeSWsProc: MakeSWs, initialBox: [[300,100],[400,360]]]; Put.Text[cpMsgSW, "ClientFileTool started at "L]; Put.Date[cpMsgSW, Time.Current[]]; Put.Char[cpMsgSW, Ascii.CR]; }; Init[]; END.