-- Transport mechanism: Maintain: high-level maintenance of groups

-- [Indigo]<Grapevine>Maintain>MaintainGroups.mesa

-- Andrew Birrell  13-Jan-82 16:19:46
-- Philip Karlton  15-May-81 17:17:48

DIRECTORY
Ascii		USING[ CR, SP ],
BodyDefs	USING[ maxRemarkLength, maxRNameLength, Remark, RName ],
GlassDefs	USING[ Handle ],
MaintainPrivate	USING[ BadName, CheckMailName, CopyName, Del,
		       Failed, Handle, Operate, ReadWord ],
NameInfoDefs	USING[ Close, Enumerate, GetMembers, IsMemberDirect,
		       MemberInfo, Membership ],
ProtocolDefs	USING[ Handle, SendCount, SendRName ],
Segments	USING[ FileNameProblem ],
Streams		USING[ Destroy, Ended, GetChar, NewStream, Read, Handle, SetIndex ],
String		USING[ AppendString, EquivalentString ];

MaintainGroups: PROGRAM
   IMPORTS MaintainPrivate, NameInfoDefs, ProtocolDefs, Segments,
           Streams, String
   EXPORTS MaintainPrivate =

BEGIN

OPEN MaintainPrivate;

VerifyGroup: PUBLIC PROCEDURE[handle: MaintainPrivate.Handle] =
   BEGIN
   OPEN handle;
   ReadWord[glass, ": "L, group];
   CopyName[from: group, to: dName];
   glass.WriteString[" ... "L]; glass.SendNow[];
   [] ← VerifySingleGroup[glass, group ! HaveIDoneThis => RESUME[FALSE]];
   END;

AddListOfMembers: PUBLIC PROCEDURE[handle: MaintainPrivate.Handle] =
   BEGIN
   OPEN handle;
   file: STRING = [40];
   ReadWord[glass, " from file: "L, file];
   ReadWord[glass, " to group: "L, group]; 
   CopyName[from: group, to: dName];
   AddList[handle: handle, file: file, name: group];
   END;

RemoveAllMemberships: PUBLIC PROCEDURE[handle: MaintainPrivate.Handle] =
   BEGIN
   OPEN handle;
   registry: STRING = [64];
   ReadWord[glass, " in registry: "L, registry];
   ReadWord[glass, " for R-Name: "L, dName];
   AllGroups[handle, registry, dName, [delete[]] ];
   END;

TypeAllGroups: PUBLIC PROCEDURE[handle: MaintainPrivate.Handle] =
   BEGIN
   OPEN handle;
   registry: STRING = [64];
   ReadWord[glass, " in registry: "L, registry];
   ReadWord[glass, " containing R-Name: "L, dName];
   AllGroups[handle, registry, dName, [type[]] ];
   END;

ModifyAllOccurrences: PUBLIC PROCEDURE[handle: MaintainPrivate.Handle] =
   BEGIN
   OPEN handle;
   registry: STRING = [64];
   ReadWord[glass, " of R-Name: "L, name];
   MaintainPrivate.CheckMailName[name];
   ReadWord[glass, " in groups in registry: "L, registry];
   ReadWord[glass, " to be R-Name: ", dName];
   MaintainPrivate.CheckMailName[dName];
   IF String.EquivalentString[name, dName]
   THEN glass.WriteString[" ... silly!"L]
   ELSE AllGroups[handle, registry, name, [replace[dName]] ];
   END;

VerifyAllGroups: PUBLIC PROCEDURE[handle: MaintainPrivate.Handle] =
   BEGIN
   OPEN handle;
   registry: STRING = [64];
   ReadWord[glass, " in registry "L, registry];
   EnumerateGroups[glass, registry, VerifySingleGroup !
                   HaveIDoneThis => RESUME[TRUE] ];
   END;

CompareRNames: PROC[n1, n2: BodyDefs.RName]
                  RETURNS [{less, equal, greater}] =
   BEGIN
   length: CARDINAL = MIN [n1.length, n2.length];
   i: CARDINAL;
   FOR i IN [0 .. length)
   DO BEGIN
      ch1: CHARACTER = IF n1[i] IN CHARACTER['A .. 'Z] 
                       THEN (n1[i] - 'A) + 'a ELSE n1[i];
      ch2: CHARACTER = IF n2[i] IN CHARACTER['A .. 'Z] 
                       THEN (n2[i] - 'A) + 'a ELSE n2[i];
      IF ch1 < ch2 THEN RETURN [less];
      IF ch1 > ch2 THEN RETURN [greater];
      END
   ENDLOOP;
   IF n1.length < n2.length
   THEN RETURN [less]
   ELSE IF n1.length = n2.length
        THEN RETURN [equal]
        ELSE RETURN [greater];
   END;

EnumerateGroups: PROC[glass: GlassDefs.Handle, registry: STRING,
        work: PROC[glass: GlassDefs.Handle, group: BodyDefs.RName]RETURNS[done:BOOLEAN] ] =
   BEGIN
   OPEN glass;
   groupName: BodyDefs.RName = [BodyDefs.maxRNameLength];
   groupInfo: NameInfoDefs.MemberInfo;
   String.AppendString[groupName, "Groups."L];
   String.AppendString[groupName, registry];
   WriteString[" ... finding groups ... "L]; glass.SendNow[];
   groupInfo ← NameInfoDefs.GetMembers[groupName];
   WITH groupInfo SELECT FROM
     notFound =>
       BEGIN
       WriteChar['"];
       WriteString[registry];
       WriteString[""" is not a registry"L];
       ERROR MaintainPrivate.Failed[];
       END;
     allDown =>
       BEGIN
       WriteString["all servers for that registry are down"L];
       ERROR MaintainPrivate.Failed[];
       END;
     group =>
       BEGIN
       Enumeratee: PROC[member: BodyDefs.RName]RETURNS[done:BOOLEAN] =
          { done ← work[glass, member] };
       WriteString["enumerating them ... "L]; glass.SendNow[];
       NameInfoDefs.Enumerate[members, Enumeratee !
          UNWIND => NameInfoDefs.Close[members] ];
       NameInfoDefs.Close[members];
       END;
   ENDCASE => ERROR;
   END;

AllGroups: PROC[handle: MaintainPrivate.Handle,
                registry: STRING, name: BodyDefs.RName,
                action: RECORD[ var: SELECT type:* FROM
                                  type, delete => NULL,
                                  replace => [new: BodyDefs.RName],
                                ENDCASE] ] =
   BEGIN
   CheckForMember: PROC[glass: GlassDefs.Handle, group: BodyDefs.RName]
                RETURNS[done: BOOLEAN] =
      BEGIN
      OPEN glass;
      info: NameInfoDefs.Membership =
         NameInfoDefs.IsMemberDirect[group, name];
      done ← FALSE;
      IF DelTyped[] THEN ERROR MaintainPrivate.Del[];
      SELECT info FROM
        allDown => { WriteString["all down"L]; ERROR MaintainPrivate.Failed };
        notGroup => NULL --strange--;
        no => NULL;
        yes =>
          BEGIN
          WriteString[group];
          WITH action SELECT FROM
            replace =>
               BEGIN
               WriteString[": adding"L];
               [] ← MaintainPrivate.Operate[handle: handle,
                                         op: AddMember, name: group,
                                         value: new];
               END;
            type, delete => NULL;
          ENDCASE => ERROR;
          WITH action SELECT FROM
            type => NULL;
            delete, replace =>
               BEGIN
               WriteString[": removing"L];
               [] ← MaintainPrivate.Operate[handle: handle,
                                         op: DeleteMember, name: group,
                                         value: name];
               END;
          ENDCASE => ERROR;
          WriteString[";  "L]; SendNow[];
          END;
      ENDCASE => ERROR;
      END;
   EnumerateGroups[handle.glass, registry, CheckForMember];
   END;


HaveIDoneThis: SIGNAL[new: BodyDefs.RName] RETURNS[BOOLEAN] = CODE;

VerifySingleGroup: PROC[glass: GlassDefs.Handle,
                        group: BodyDefs.RName]
                RETURNS[done: BOOLEAN] =
   BEGIN
   OPEN glass;
   info: NameInfoDefs.MemberInfo = NameInfoDefs.GetMembers[group];
   done ← FALSE;
   WITH info SELECT FROM
     notFound =>
       BEGIN
       WriteString["group """L]; WriteString[group];
       WriteString[""" doesn't exist ... "L];
       END;
     allDown =>
       BEGIN
       WriteString["all servers for """L];
       WriteString[group];
       WriteString[""" are down"L]; done ← TRUE;
       ERROR MaintainPrivate.Failed[];
       END;
     individual =>
       BEGIN
       WriteString[group]; WriteString[" is an individual! ... "L];
       END;
     group =>
       BEGIN
       CheckMember: PROC[member: BodyDefs.RName]RETURNS[done: BOOLEAN] =
         BEGIN
         done ← FALSE;
         IF DelTyped[] THEN ERROR MaintainPrivate.Del[];
         MaintainPrivate.CheckMailName[member ! MaintainPrivate.BadName =>
            BEGIN
            WriteChar[Ascii.CR];
            WriteString["Bad name """L];
            WriteString[member];
            WriteString[""" in group "L];
            WriteString[group];
            WriteString[" ... "L];
            GOTO bad
            END ];
         FOR index: CARDINAL DECREASING IN [0..member.length)
         DO IF member[index] = '↑
            THEN BEGIN
                 IF NOT (SIGNAL HaveIDoneThis[member])
                 THEN done ← VerifySingleGroup[glass, member];
                 EXIT
                 END;
         ENDLOOP;
         EXITS bad => NULL;
         END;
       NameInfoDefs.Enumerate[members, CheckMember !
          HaveIDoneThis =>
             IF String.EquivalentString[new, group]
             THEN RESUME[TRUE];
          UNWIND => NameInfoDefs.Close[members] ];
       NameInfoDefs.Close[members];
       END;
   ENDCASE => ERROR;
   END;

AddList: PROC[handle: MaintainPrivate.Handle,
              file: STRING, name: BodyDefs.RName] =
   BEGIN
   stream: Streams.Handle = Streams.NewStream[
      name: file, access: Streams.Read ! Segments.FileNameProblem[] =>
      GOTO badFile ];
   ParseFile[handle, stream, name ! UNWIND => Streams.Destroy[stream] ];
   Streams.Destroy[stream];
   EXITS badFile =>
      { handle.glass.WriteString[" ... """L];
        handle.glass.WriteString[file];
        handle.glass.WriteString[""" doesn't exist"L];
        ERROR MaintainPrivate.Failed[] }
   END;

ParseFile: PROC[handle: MaintainPrivate.Handle,
                stream: Streams.Handle, name: BodyDefs.RName] =
   BEGIN
   OPEN handle.glass;
   count: CARDINAL ← 0;
   memberLength: CARDINAL ← 0;
   ch: CHARACTER;
   Get: PROC RETURNS[CHARACTER] = INLINE
     { RETURN[ ch ← IF Streams.Ended[stream] THEN '; ELSE Streams.GetChar[stream] ] };
   orderOK: BOOLEAN ← TRUE;
   badFound: BOOLEAN ← FALSE;
   remark: BodyDefs.Remark = [BodyDefs.maxRemarkLength];
   remarkExists: BOOLEAN ← FALSE;
   colonPos: CARDINAL ← 0;
   FOR index: CARDINAL IN [0..LAST[CARDINAL])
   DO IF Streams.Ended[stream] THEN EXIT;
      IF Get[] = ': THEN { remarkExists ← TRUE; colonPos ← index; EXIT };
   ENDLOOP;

   -- parse members and read remark --
   BEGIN
      prevMember: BodyDefs.RName = [BodyDefs.maxRNameLength];
      FirstWork: PROC[member: BodyDefs.RName] =
         BEGIN
         IF orderOK AND NOT badFound
         AND CompareRNames[prevMember,member] # less
         THEN BEGIN
              WriteString["name """L]; WriteString[member];
              WriteString[""" isn't in alphabetical order"L];
              orderOK ← FALSE;
              END
         ELSE BEGIN
              prevMember.length ← 0;
              String.AppendString[prevMember, member]
              END;
         memberLength ← memberLength + SIZE[StringBody[member.length]];
         IF orderOK
         THEN MaintainPrivate.CheckMailName[member ! MaintainPrivate.BadName =>
                 BEGIN
                 IF NOT badFound
                 THEN { badFound ← TRUE; WriteString["bad name(s): "L]; }
                 ELSE WriteString[", "L];
                 WriteString[member];
                 CONTINUE
                 END ];
         END;
      WriteString[" ... checking members ... "L];
      SingleParseOfFile[stream, name, remarkExists, colonPos,
                        remark, FirstWork];
   END;
   IF orderOK AND NOT badFound AND remarkExists
   THEN BEGIN
        WriteString["setting remark"L];
        [] ← MaintainPrivate.Operate[handle: handle,
                                  op: ChangeRemark, name: name,
                                  remark: remark];
        END;
   -- add members --
   IF orderOK AND NOT badFound
   THEN BEGIN
        AddListWork: PROC[str: ProtocolDefs.Handle] =
           BEGIN
           SecondWork: PROC[member: BodyDefs.RName] =
              BEGIN
              ProtocolDefs.SendRName[str, member];
              count ← count + 1;
              END;
           ProtocolDefs.SendCount[str, memberLength];
           count ← 0;
           SingleParseOfFile[stream, name, remarkExists, colonPos,
                             remark, SecondWork];
           END;
        WriteString["adding members"L];
        [] ← MaintainPrivate.Operate[handle: handle,
                                  op: AddListOfMembers,
                                  name: name,
                                  sendRList: AddListWork];
        END
   ELSE ERROR MaintainPrivate.Failed[];
   WriteString[".  "L]; WriteDecimal[count]; WriteString[" members"L];
   END;


SingleParseOfFile: PROC[stream: Streams.Handle,
                        name: BodyDefs.RName,
                        remarkExists: BOOLEAN, colonPos: CARDINAL,
                        remark: BodyDefs.Remark,
                        memberWork: PROC[BodyDefs.RName] ] =
   BEGIN
   ch: CHARACTER;
   Get: PROC RETURNS[CHARACTER] = INLINE
     { RETURN[ ch ← IF Streams.Ended[stream] THEN '; ELSE Streams.GetChar[stream] ] };
   Streams.SetIndex[stream, 0];
   IF remarkExists
   THEN BEGIN
        remark.length ← 0;
        FOR index: CARDINAL IN [0..colonPos)
        DO IF remark.length < remark.maxlength
           THEN { remark[index] ← Get[]; remark.length ← remark.length+1 }
           ELSE [] ← Get[];
        ENDLOOP;
        [] ← Get[] -- skip the colon --;
        END;
   [] ← Get[];
   DO member: BodyDefs.RName = [BodyDefs.maxRNameLength];
      DO SELECT ch FROM Ascii.SP, Ascii.CR => NULL; ENDCASE => EXIT;
         [] ← Get[];
      ENDLOOP;
      IF ch = '; THEN EXIT;
      DO SELECT ch FROM
           ';, ',, Ascii.SP, Ascii.CR => EXIT;
         ENDCASE => NULL;
         IF member.length < member.maxlength
         THEN { member[member.length] ← ch; member.length←member.length+1 };
         [] ← Get[];
      ENDLOOP;
      IF member.length > 0
      THEN BEGIN
           memberWork[member];
           member.length ← 0;
           END;
      IF ch = ', THEN [] ← Get[];
   ENDLOOP;
   END;

END.