-- File: PupDirServer.mesa,  Last Edit: HGM  March 8, 1981  1:25 AM

DIRECTORY
  Ascii USING [CR],
  Process USING [Detach, SetTimeout, MsecToTicks, Pause, Yield],
  String USING [EquivalentString, AppendChar, AppendString, AppendDecimal],
  Time USING [AppendCurrent],

  Event USING [Item, Reason, AddNotifier],
  Put USING [Line],
  Window USING [Handle],

  File USING [Capability],
  MiscServerDefs USING [
    PupMiscServerOn, PupMiscServerOff, IgnoreThisPacket, SetDirectoryServer],
  Slosh USING [
    AddProcs, RecvStatus, RecvFile, RejectThisTrash, RetransmissionInterval,
    SendFile, Why],
  NameServerDefs USING [
    lockDirRequest, lockDirReply, unlockDirRequest, unlockDirReply,
    CloseDirectoryFile, CheckDirectoryFile, FlushWholeCache,
    GetDirectoryFile, GetDirectoryVersion, OpenDirectoryFile, ResetDirectoryFile],
  StatsDefs USING [StatCounterIndex, StatIncr],
  PupDefs USING [
    GetFreePupBuffer, AppendHostName, PupAddress, PupBuffer,
    PupRouterBroadcastThis, PupSocket, PupSocketDestroy, PupSocketID,
    PupSocketMake, ReturnFreePupBuffer, SecondsToTocks, SetPupContentsWords,
    ReturnPup, SendPup, UniqueLocalPupAddress, UniqueLocalPupSocketID],
  PupTypes USING [fillInPupAddress, miscSrvSoc];

PupDirServer: MONITOR
  IMPORTS
    Process, String, Time, Event, Put, Slosh, MiscServerDefs, NameServerDefs,
    StatsDefs, PupDefs
  EXPORTS NameServerDefs =
  BEGIN OPEN StatsDefs, NameServerDefs, PupDefs;

  msg: PUBLIC Window.Handle ← NIL;
  eventItem: Event.Item ← [eventMask: 177777B, eventProc: Broom];
  dirRunning, probing, announcing, sending: PUBLIC BOOLEAN ← FALSE;
  useCount: CARDINAL ← 0;
  pleaseStop: BOOLEAN ← FALSE;
  probePeriod: CARDINAL ← 60; -- in minutes
  tries: CARDINAL ← 0;
  delay: CONDITION;
  lock: BOOLEAN ← FALSE;
  verbose: BOOLEAN = TRUE;

  currentVersion: CARDINAL ← 0;
  directoryName: STRING = "Pup-Network.Directory";

  statVers, statSend: PUBLIC StatCounterIndex;

  PupDirServerOn: PUBLIC PROCEDURE =
    BEGIN
    IF (useCount ← useCount + 1) = 1 THEN BEGIN dirRunning ← TRUE; Starter[]; END;
    --UpdatePicture[];
    END;

  Starter: PROCEDURE =
    BEGIN
    pleaseStop ← FALSE;
    MiscServerDefs.PupMiscServerOn[];
    OpenDirectoryFile[];
    currentVersion ← GetDirectoryVersion[];
    MiscServerDefs.SetDirectoryServer[PupDirServer];
    Process.Detach[FORK ProbeGovenor[]];
    END;

  PupDirServerOff: PUBLIC PROCEDURE =
    BEGIN
    IF useCount # 0 AND (useCount ← useCount - 1) = 0 THEN
      BEGIN dirRunning ← FALSE; Stopper[]; END;
    --UpdatePicture[];
    END;

  Stopper: PROCEDURE =
    BEGIN
    StopperLocked: ENTRY PROCEDURE = BEGIN NOTIFY delay; END;
    MiscServerDefs.SetDirectoryServer[MiscServerDefs.IgnoreThisPacket];
    pleaseStop ← TRUE;
    StopperLocked[];
    WHILE probing OR announcing OR sending DO Process.Yield[]; ENDLOOP;
    CloseDirectoryFile[];
    MiscServerDefs.PupMiscServerOff[];
    END;

  CountTries: ENTRY PROCEDURE = BEGIN tries ← tries + 1; END;

  KickProber: ENTRY PROCEDURE =
    BEGIN IF tries > 3 THEN RETURN; StartProbing[]; END;

  StartProbingForDirectory: PUBLIC ENTRY PROCEDURE = BEGIN StartProbing[]; END;

  StartProbing: INTERNAL PROCEDURE =
    BEGIN
    IF pleaseStop OR probing THEN RETURN;
    probing ← TRUE;
    Process.Detach[FORK Probe[]];
    END;

  ProbeGovenor: ENTRY PROCEDURE =
    BEGIN
    n: CARDINAL ← 1; -- probe 1 min after startup
    Process.SetTimeout[@delay, Process.MsecToTicks[60000]];
    UNTIL pleaseStop DO
      WAIT delay; -- one minute
      IF (n ← n - 1) = 0 THEN
	BEGIN
	tries ← 0;
	StartProbing[];
	WHILE probing DO WAIT delay; ENDLOOP;
	n ← probePeriod;
	END;
      ENDLOOP;
    END;

  Probe: PROCEDURE =
    BEGIN
    b: PupBuffer;
    from: PupSocketID ← UniqueLocalPupSocketID[];
    socket: PupSocket;
    bestSoFar: CARDINAL ← currentVersion;
    where: PupAddress;
    socket ← PupSocketMake[from, PupTypes.fillInPupAddress, SecondsToTocks[1]];
    THROUGH [0..5) UNTIL bestSoFar > currentVersion DO
      b ← GetFreePupBuffer[];
      b.source.socket ← from;
      b.dest.socket ← PupTypes.miscSrvSoc;
      b.pupType ← netDirVersion;
      b.pupWords[0] ← currentVersion;
      SetPupContentsWords[b, 1];
      PupRouterBroadcastThis[b];
      UNTIL b = NIL DO
	b ← socket.get[];
	IF b # NIL THEN
	  BEGIN
	  IF b.pupType = netDirVersion AND b.pupWords[0] > bestSoFar THEN
	    BEGIN bestSoFar ← b.pupWords[0]; where ← b.source; END;
	  ReturnFreePupBuffer[b];
	  END;
	ENDLOOP;
      ENDLOOP;
    PupSocketDestroy[socket];
    IF bestSoFar > currentVersion THEN
      BEGIN
      IF verbose THEN
	BEGIN OPEN String;
	text: STRING = [100];
	Time.AppendCurrent[text];
	AppendString[text, "  Found Pup-Network.Directory!"L];
	AppendDecimal[text, bestSoFar];
	AppendString[text, " on "L];
	AppendHostName[text, where];
	AppendChar[text, '.];
	LogString[text];
	END;
      Process.Detach[FORK GetNewDirectory[where]];
      RETURN;
      END;
    probing ← FALSE;
    END;

  Announce: PROCEDURE =
    BEGIN
    b: PupBuffer;
    THROUGH [0..5) UNTIL pleaseStop DO
      Process.Pause[1000];
      b ← GetFreePupBuffer[];
      b.source.socket ← b.dest.socket ← PupTypes.miscSrvSoc;
      b.pupType ← netDirVersion;
      b.pupWords[0] ← currentVersion;
      SetPupContentsWords[b, 1];
      PupRouterBroadcastThis[b];
      ENDLOOP;
    announcing ← FALSE;
    END;

  GetNewDirectory: PROCEDURE [where: PupAddress] =
    BEGIN
    from: PupAddress ← UniqueLocalPupAddress[@where];
    status: Slosh.RecvStatus;
    Ask: PROCEDURE =
      BEGIN
      b: PupBuffer ← GetFreePupBuffer[];
      b.source ← from;
      b.dest ← where;
      b.address ← from;
      SendPup[b, sendNetDir, 2*SIZE[PupAddress]];
      END;
    CountTries[];
    status ← Slosh.RecvFile[
      msg, directoryName, "Pup-Network.scratch$"L, GetDirectoryFile[], from, Ask];
    IF status # statusStoreOk THEN
      BEGIN
      n: CARDINAL ← Slosh.RetransmissionInterval[];
      IF status = statusDiskFull THEN {
	CountTries[]; CountTries[]; CountTries[]; };
      THROUGH [0..n) UNTIL pleaseStop DO
	Process.Pause[Process.MsecToTicks[1000]]; ENDLOOP;
      END;
    probing ← FALSE;
    IF status # statusStoreOk THEN KickProber[];
    END;


  Checker: PROCEDURE [why: Slosh.Why, fileName: STRING, temp: File.Capability] =
    BEGIN
    IF ~String.EquivalentString[fileName, directoryName] THEN RETURN;
    SELECT why FROM
      check => IF ~CheckDirectoryFile[temp] THEN ERROR Slosh.RejectThisTrash[NIL];
      release => CloseDirectoryFile[];
      arrived, failed =>
	BEGIN
	currentVersion ← ResetDirectoryFile[];
	FlushWholeCache[];
	announcing ← TRUE;
	Process.Detach[FORK Announce[]];
	END;
      ENDCASE => ERROR
    END;

  SendDirectory: ENTRY PROCEDURE [where: PupAddress] =
    BEGIN
    IF verbose THEN
      BEGIN
      text: STRING = [100];
      Time.AppendCurrent[text];
      String.AppendString[text, "  Pup-Network.Directory wanted by "L];
      AppendHostName[text, where];
      String.AppendChar[text, '.];
      LogString[text];
      END;
    IF Slosh.SendFile[msg, directoryName, GetDirectoryFile[], where] = ok THEN
      StatIncr[statSend];
    sending ← FALSE;
    END;

  PupDirServer: PUBLIC PROCEDURE [b: PupBuffer] =
    BEGIN
    IF ~(lock OR pleaseStop) OR b.pupType = unlockDirRequest THEN
      SELECT b.pupType FROM
	netDirVersion =>
	  BEGIN
	  StatIncr[statVers];
	  SELECT b.pupWords[0] FROM
	    = currentVersion => NULL; -- we have the same ones

	    > currentVersion => KickProber[]; -- he has a newer one

	    < currentVersion => -- tell him about our newer one
	      BEGIN
	      b.pupWords[0] ← currentVersion;
	      ReturnPup[b, netDirVersion, 2];
	      RETURN;
	      END;
	    ENDCASE => ERROR;
	  END;
	sendNetDir =>
	  BEGIN
	  IF ~sending THEN
	    BEGIN
	    sending ← TRUE;
	    Process.Detach[FORK SendDirectory[b.address]];
	    END;
	  END;
	lockDirRequest =>
	  BEGIN
	  lock ← TRUE;
	  ReturnPup[b, lockDirReply, 0];
	  CloseDirectoryFile[];
	  FlushWholeCache[];
	  currentVersion ← 0;
	  RETURN;
	  END;
	unlockDirRequest =>
	  BEGIN
	  lock ← FALSE;
	  ReturnPup[b, unlockDirReply, 0];
	  IF currentVersion = 0 THEN currentVersion ← ResetDirectoryFile[];
	  tries ← 0;
	  KickProber[];
	  RETURN;
	  END;
	ENDCASE;
    ReturnFreePupBuffer[b];
    END;

  LogString: PROCEDURE [text: STRING] =
    BEGIN IF msg # NIL THEN Put.Line[msg, text]; Put.Line[NIL, text]; END;

  Broom: PROCEDURE [why: Event.Reason] =
    BEGIN
    IF useCount = 0 THEN RETURN;
    SELECT why FROM
      makeImage, makeCheck, stopMesa => IF dirRunning THEN Stopper[];
      startImage, restartCheck, continueCheck => IF dirRunning THEN Starter[];
      ENDCASE => NULL;
    END;

  -- initialization

  Event.AddNotifier[@eventItem];
  Slosh.AddProcs[Checker];
  END.