-- Copyright (C) 1981, 1982, 1984, 1985  by Xerox Corporation. All rights reserved. 
-- NetDirBuilderInterlocks.mesa, HGM, 25-Jun-85  8:37:24
-- From AccessIO of Oct 9, 1980 7:53 PM (Alto version)
-- From AccessIO of 15-Apr-81 12:00:24 (Pilot 6.0? version)
-- From AccessIO of 12-Jan-82 11:58:35 (Pilot 8.0h version)
-- From AccessIO of 23-Dec-82 11:37:12 (Pilot 10.0d version)
-- From AccessIO of 13-Aug-84 14:25:10 (Pilot 11.1 version)


DIRECTORY
  Ascii USING [CR],
  Cursor USING [Object, Fetch, Set, Store],
  Heap USING [systemZone],
  Inline USING [LongNumber],
  FileName USING [
    AllocVFN, FreeVFN, PackFilename, ResetVFN, UnpackFilename,
    VirtualFilename, VirtualFilenameObject],
  FileTransfer USING [
    Connection, Copy, Create, Error, FileInfo, SetPrimaryCredentials, VetoProc],
  PupLibrarian USING [
    AllFromHereID, OpenLibrarianConnection, CreatePropertyList, CredentialsObject,
    DestroyPropertyList, Error, FileServerObject, FullLibjectID,
    FullLibjectIDHandle, GetPropertyString, GetPropertyTwoWord, Handle,
    Activate, Checkin, Checkout, FindID,
    FindVersion, Query, LibjectID, PropertyList, PropertyPair,
    SnapShotHandle],
  PupLibrarianPN USING [
    ContentsFile, localnamePN, CheckedInReason, CheckedOutBy, CheckedOutReason,
    CheckedOutTOD, CheckedInTOD, UserName],
  MFile USING [Acquire, Error, Handle, Release],
  MStream USING [Create, Error, Handle],
  Process USING [Pause, SecondsToTicks, Ticks],
  Profile USING [GetUser],
  Put USING [Char, Line, Text],
  Stream USING [Handle],
  String USING [AppendString, AppendStringAndGrow, Copy, CopyToNewString, Empty, Equal, Equivalent],
  Time USING [Append, Current, Packed, Unpack],
  UserInput USING [UserAbort],
  PupDefs USING [AppendMyName, PupPackageDestroy, PupPackageMake],
  NetDirBuilderOps USING [log];

NetDirBuilderInterlocks: PROGRAM
  IMPORTS
    Cursor, Heap, PupLibrarian, MFile, MStream, Process, Profile, Put,
    String, FileName, FileTransfer, Time, UserInput, PupDefs, NetDirBuilderOps
  EXPORTS NetDirBuilderOps =
  BEGIN

  OPEN PupLibrarian, PupLibrarianPN;

  z: UNCOUNTED ZONE = Heap.systemZone;

  -- global variable declarations

  VirtualFilename: TYPE = FileName.VirtualFilename;
  VirtualFilenameObject: TYPE = FileName.VirtualFilenameObject;
  ActivateMode: TYPE = {noActivate, activate, activateAndWait};
  ErrorLevel: TYPE = {info, warning, error};

  -- User and User.cm stuff

  currentFid: FullLibjectID;
  person: LONG STRING ← [20];
  pwd: LONG STRING ← [20];
  connect: LONG STRING ← [40];
  connectPwd: LONG STRING ← [20];
  primary: PupLibrarian.CredentialsObject ← [person, pwd];
  secondary: PupLibrarian.CredentialsObject ← [connect, connectPwd];

  reason: LONG STRING ← [40];
  errors, warnings: CARDINAL;

  Switch: TYPE = PACKED ARRAY CHARACTER ['a..'z] OF BOOLEAN;
  switch: Switch ← ALL[FALSE];

  conn: FileTransfer.Connection ← NIL;
  cmdStream: Stream.Handle ← NIL;  --used by GetToken

  libject: LONG STRING = "<Pup>Pup-Network.txt";
  server: PupLibrarian.Handle = PupLibrarian.OpenLibrarianConnection["Marion", pup];

  Lock: PUBLIC PROCEDURE RETURNS [BOOLEAN] =
    BEGIN switch['s] ← TRUE; RETURN[PokePupLibrarian[Retrieve]]; END;

  Abort: PUBLIC PROCEDURE RETURNS [BOOLEAN] =
    BEGIN switch['s] ← FALSE; RETURN[PokePupLibrarian[Store]]; END;

  Unlock: PUBLIC PROCEDURE RETURNS [BOOLEAN] =
    BEGIN switch['s] ← TRUE; RETURN[PokePupLibrarian[Store]]; END;

  PokePupLibrarian: PROCEDURE [proc: PROCEDURE [LONG STRING, BOOLEAN]] RETURNS [BOOLEAN] =
    BEGIN
    SaveUserInfo: PROCEDURE [name, password: LONG STRING] =
      BEGIN
      String.Copy[person, name];
      String.Copy[pwd, password];
      END;
    Profile.GetUser[SaveUserInfo, registry];
    errors ← warnings ← 0;
    [] ← PupDefs.PupPackageMake[];
    reason.length ← 0;
    PupDefs.AppendMyName[reason];
    IF conn = NIL THEN
      BEGIN
      conn ← FileTransfer.Create[];
      FileTransfer.SetPrimaryCredentials[
        conn: conn, user: primary.directory, password: primary.password];
      END;
    proc[
      libject, TRUE !
      PupLibrarian.Error => {PostMessage[libject, message, error]; CONTINUE}];
    PupDefs.PupPackageDestroy[];
    RETURN[(errors + warnings) = 0];
    END;

  Aborted: SIGNAL = CODE;

  Retrieve: PROCEDURE [name: LONG STRING, checkOut: BOOLEAN] =
    BEGIN
    fid: FullLibjectIDHandle ← GetCurrentFID[name, activateAndWait];
    plist: PropertyList ← GetLibjectInOutInfo[fid, FALSE];
    outBy: LONG STRING = GetPropertyString[plist, CheckedOutBy].s;
    {
    IF checkOut THEN {
      IF String.Empty[reason] THEN {
        PostMessage[name, "no CheckOut reason supplied"L, error]; GOTO alldone};
      Checkout[
        server, fid, reason !
        PupLibrarian.Error =>
          IF code = checkedOut THEN {
            string: LONG STRING = [80];
            outTime: LONG CARDINAL = GetPropertyTwoWord[
              plist, CheckedOutTOD].ln.lc;
            String.AppendString[string, "Already checked out by "L];
            String.AppendString[string, outBy];
            String.AppendString[string, " at "L];
            Time.Append[string, Time.Unpack[[outTime]]];
            PostMessage[name, string, error];
            GOTO alldone}; UNWIND => GOTO alldone];
      PostMessage[name, "Checked Out"L, info]}
    ELSE {WriteChar[Ascii.CR]};
    IF switch['s] THEN RetrieveFiles[name, plist];
    GOTO alldone;
    EXITS alldone => DestroyPropertyList[plist]};
    END;

  BadNameProperty: PROCEDURE [
    libject, expandedName: LONG STRING, type: {local, remote}] RETURNS [bad: BOOLEAN] =
    BEGIN
    IF (bad ← String.Empty[expandedName]) THEN
      BEGIN
      msg: LONG STRING ← z.NEW[StringBody[100]];  -- do not use frame as it is allocated on ALL calls
      String.AppendString[msg, "has empty "L];
      String.AppendString[msg, IF type = local THEN "local"L ELSE "remote"L];
      String.AppendString[msg, " name property"L];
      PostMessage[libject, msg, warning];
      z.FREE[@msg];
      END;
    END;

  CopyNameProperty: PROCEDURE [plist: PropertyList, type: {local, remote}]
    RETURNS [name: LONG STRING] =
    BEGIN
    name ← String.CopyToNewString[
      GetPropertyString[
      plist, IF type = local THEN localnamePN ELSE ContentsFile].s, z, 20];
    END;

  RetrieveFiles: PROCEDURE [libject: LONG STRING, plist: PropertyList] =
    BEGIN
    remoteName: LONG STRING ← CopyNameProperty[plist, remote];
    localName: LONG STRING ← CopyNameProperty[plist, local];
    rec: VirtualFilenameObject ← [NIL, NIL, NIL, NIL];
    extension: LONG STRING ← [20];
    errors: CARDINAL ← 0;
    IF BadNameProperty[libject, localName, local] THEN RETURN;
    IF BadNameProperty[libject, remoteName, remote] THEN RETURN;
    FileName.UnpackFilename[remoteName, @rec];
    IF switch['s] THEN {
      IF String.Empty[rec.version] THEN {
        PostMessage[remoteName, "Retrieving default version"L, warning]};
      RetrieveOneFile[@rec, libject, localName, NIL]};
    FileName.ResetVFN[@rec, TRUE, TRUE, TRUE, TRUE];
    z.FREE[@localName];
    z.FREE[@remoteName];
    END;

  RetrieveOneFile: PROC [rec: VirtualFilename, libject, localName, ext: LONG STRING] =
    BEGIN
    targetName: LONG STRING ← NIL;
    localVFN: VirtualFilename;
    retrievedVersion: LONG STRING ← [6];
    origVersion: LONG STRING = rec.version;
    checkVersion: BOOLEAN = ~String.Empty[origVersion]
      AND ~String.Equivalent[origVersion, "h"L];
    gotIt: BOOLEAN;

    RetrieveThisOne: PROC [checkVersion: BOOLEAN] RETURNS [BOOLEAN] =
      BEGIN
      localVFN ← FileName.AllocVFN[localName];
      SetTargetName[];
      WriteString["    Retrieving: "L];
      WriteLine[targetName];
      FileTransfer.Copy[
        sourceFile: rec, destFile: localVFN, sourceConn: conn,
        veto: IF checkVersion THEN GetVersion ELSE NIL !
        FileTransfer.Error => {
          SELECT TRUE FROM
            code = skipOperation => REJECT;
            code = cantModify => {
              PostMessage[localName, "can't be modified!"L, error]; GOTO fail};
            ENDCASE => {
              PostMessage[localName, "retrieve failure"L, error]; GOTO fail}}];
      PostMessage["    Retrieved"L, targetName, info];
      RETURN[TRUE];
      EXITS fail => RETURN[FALSE]
      END;

    GetVersion: FileTransfer.VetoProc = {
      remoteInfo: FileTransfer.FileInfo = info[conn].source;
      retrievedVersion.length ← 0;
      String.AppendString[retrievedVersion, remoteInfo.version];
      rec.version ← retrievedVersion;
      SetTargetName[];
      RETURN[do, TRUE]};

    SetTargetName: PROCEDURE = {
      z.FREE[@targetName];
      targetName ← FileName.PackFilename[rec, TRUE, TRUE, TRUE, TRUE]};
    IF ext # NIL THEN {
      String.AppendStringAndGrow[@rec.name, ext, z]; String.AppendString[localName, ext]};
    rec.version ← NIL;
    gotIt ← RetrieveThisOne[TRUE];
    rec.version ← origVersion;
    IF gotIt AND checkVersion
      AND ~String.Equal[origVersion, retrievedVersion] THEN {
      PostMessage[
        libject, "no CheckIn corresponding to most recent store"L, warning];
      [] ← RetrieveThisOne[FALSE]};
    FileName.FreeVFN[localVFN];
    z.FREE[@targetName];
    END;


  Store: PROCEDURE [name: LONG STRING, checkIn: BOOLEAN] =
    BEGIN
    dh: MStream.Handle ← NIL;
    fh: MFile.Handle ← NIL;
    localName: LONG STRING ← NIL;
    remoteName: LONG STRING ← NIL;
    firstStoreError: BOOLEAN ← TRUE;
    fid: FullLibjectIDHandle ← GetCurrentFID[name, activateAndWait];
    plist: PropertyList ← GetLibjectInOutInfo[fid, checkIn];
    reasonPP: LONG POINTER TO String PropertyPair;
    storeSource: BOOLEAN ← switch['s];
    DoNotAttemptStores: PROCEDURE = INLINE {storeSource ← FALSE};
    localName ← CopyNameProperty[plist, local];
    IF storeSource THEN
      BEGIN
      IF BadNameProperty[name, localName, local] THEN {DoNotAttemptStores[]}
      ELSE
        IF storeSource THEN {
          fh ← MFile.Acquire[localName, readOnly, [] ! MFile.Error => CONTINUE];
          IF fh # NIL THEN {
            IF checkIn AND storeSource THEN
              BEGIN
              dh ← MStream.Create[
                fh, [] !
                MStream.Error =>
                  PostMessage[localName, "cannot be opened!"L, error]];
              fh ← NIL;
              END}
          ELSE {
            WriteChar[Ascii.CR];
            PostMessage[localName, "NOT on local disk!"L, error];
            checkIn ← FALSE;
            DoNotAttemptStores[]}};
      END;
    IF checkIn THEN
      BEGIN
      fsh: PupLibrarian.FileServerObject ← [NIL, primary, secondary];
      reasonPP ← GetPropertyString[plist, CheckedInReason].pp;
      z.FREE[@reasonPP.string];
      reasonPP.string ← String.CopyToNewString[reason, z];
      remoteName ← Checkin[
        server, fid, modification, plist, dh, @fsh !
        PupLibrarian.Error =>
          IF code = communicationError OR code = fatalTransferError THEN
            BEGIN
            IF firstStoreError THEN
              PostMessage[localName, "store failure"L, warning];
            firstStoreError ← FALSE;
            IF code = communicationError THEN {WaitForRetry[message, 10]; RESUME };
            END;  --dh destroyed in PupLibrariansB
        UNWIND => DestroyPropertyList[plist]];
      PostMessage[name, "Checked In"L, info];
      IF switch['s] THEN PostMessage["    Stored"L, remoteName, info];
      storeSource ← FALSE;
      END
    ELSE
      IF storeSource THEN {
        outBy: LONG STRING = GetPropertyString[plist, CheckedOutBy].s;
        IF storeSource AND switch['m]
          AND ~String.Equivalent[person, outBy] THEN {
          PostMessage[name, "skipped, since not checked out to you."L, info];
          switch['d] ← FALSE;
          DoNotAttemptStores[]}
        ELSE {remoteName ← CopyNameProperty[plist, remote]; WriteChar[Ascii.CR]}};
    IF storeSource THEN
      BEGIN
      IF BadNameProperty[name, remoteName, remote] THEN storeSource ← FALSE
      ELSE StoreFiles[name, localName, remoteName, ~checkIn AND storeSource];
      END;
    IF fh # NIL THEN BEGIN MFile.Release[fh ! MFile.Error => CONTINUE]; END;
    DestroyPropertyList[plist];
    z.FREE[@remoteName];
    z.FREE[@localName];
    END;

  StoreFiles: PROCEDURE [
    libject, localName, remoteName: LONG STRING, storeOrig: BOOLEAN] =
    BEGIN
    targetFH: MFile.Handle ← NIL;
    remoteVFN: VirtualFilenameObject ← [NIL, NIL, NIL, NIL];
    extension: LONG STRING ← [20];
    FileName.UnpackFilename[remoteName, @remoteVFN];
    FileName.ResetVFN[@remoteVFN, FALSE, FALSE, FALSE, TRUE];
    IF storeOrig THEN
      StoreOneFile[@remoteVFN, libject, localName, NIL, FALSE, FALSE];
    FileName.ResetVFN[@remoteVFN, TRUE, TRUE, TRUE, TRUE];
    END;

  StoreOneFile: PROCEDURE [
    rec: VirtualFilename, libject, localName, ext: LONG STRING, missingOK: BOOLEAN,
    checkFile: BOOLEAN] =
    BEGIN
    targetFH: MFile.Handle ← NIL;
    targetRemoteName: LONG STRING;
    storedIt: BOOLEAN ← TRUE;
    doingRetry: BOOLEAN ← FALSE;
    localVFN: VirtualFilenameObject ← [NIL, NIL, NIL, NIL];
    IF ext # NIL THEN
      BEGIN
      String.AppendStringAndGrow[@rec.name, ext, z];
      String.AppendString[localName, ext];
      END;
    IF checkFile THEN
      BEGIN
      -- librarian maps ftp error to something unrecognizable; check if local file exists 
      targetFH ← MFile.Acquire[localName, readOnly, [] ! MFile.Error => CONTINUE];
      IF targetFH = NIL THEN {
        IF ~missingOK THEN PostMessage[localName, "NOT on local disk!"L, warning];
        RETURN};
      -- don't forget to release handle
      END;
    FileName.UnpackFilename[localName, @localVFN];
    FileTransfer.Copy[
      destFile: rec, sourceFile: @localVFN, destConn: conn !
      FileTransfer.Error => {
        IF code # skipOperation THEN REJECT;
        PostMessage[localName, "store failure"L, error];
        storedIt ← FALSE;
        CONTINUE}];
    IF storedIt THEN {
      targetRemoteName ← FileName.PackFilename[rec, TRUE, TRUE, TRUE, TRUE];
      PostMessage["    Stored"L, targetRemoteName, info]};
    z.FREE[@targetRemoteName];
    FileName.ResetVFN[@localVFN, TRUE, TRUE, TRUE, TRUE];
    END;

  -- Library Support routines

  GetCurrentFID: PROCEDURE [name: LONG STRING, mode: ActivateMode]
    RETURNS [fid: FullLibjectIDHandle] =
    BEGIN
    id: LibjectID;
    active: BOOLEAN;
    snapFid: FullLibjectID;
    snapshot: SnapShotHandle ← DESCRIPTOR[@snapFid, 1];
    [id, active] ← FindID[
      server: server, s: name, activate: mode # noActivate, wait: FALSE];
    IF ~active AND mode = activateAndWait THEN {
      WriteString[" (activating)"L];
      Activate[server: server, id: id, wait: TRUE]};
    snapFid ← FullLibjectID[
      id: AllFromHereID,
      version: [
      type: timeAndDate,
      body: timeAndDate[
      pad: -- Must be default until Service > 5.1! -- , tod: Time.Current[]]]];
    currentFid ← FindVersion[server, id, snapshot]↑;
    fid ← @currentFid;
    END;

  GetLibjectInOutInfo: PROCEDURE [
    fid: FullLibjectIDHandle, checkInReason: BOOLEAN]
    RETURNS [plist: PropertyList] =
    BEGIN
    plist ← CreatePropertyList[IF checkInReason THEN 6 ELSE 5];
    plist[0] ← PropertyPair[FALSE, localnamePN.prefix, String[NIL]];
    plist[1] ← PropertyPair[FALSE, ContentsFile.prefix, String[NIL]];
    plist[2] ← PropertyPair[FALSE, CheckedOutBy.prefix, String[NIL]];
    plist[3] ← PropertyPair[FALSE, CheckedOutTOD.prefix, TwoWord[Inline.LongNumber[lc[0]]]];
    plist[4] ← PropertyPair[FALSE, CheckedOutReason.prefix, String[NIL]];
    IF checkInReason THEN
      plist[5] ← PropertyPair[FALSE, CheckedInReason.prefix, String[NIL]];
    Query[server, fid, plist];
    END;

  GetLibjectQueryInfo: PROCEDURE [fid: FullLibjectIDHandle]
    RETURNS [plist: PropertyList] =
    BEGIN
    plist ← CreatePropertyList[6];
    plist[0] ← PropertyPair[FALSE, CheckedOutBy.prefix, String[NIL]];
    plist[1] ← PropertyPair[FALSE, CheckedOutReason.prefix, String[NIL]];
    plist[2] ← PropertyPair[FALSE, CheckedInReason.prefix, String[NIL]];
    plist[3] ← PropertyPair[FALSE, CheckedInTOD.prefix, TwoWord[Inline.LongNumber[lc[0]]]];
    plist[4] ← PropertyPair[FALSE, CheckedOutTOD.prefix, TwoWord[Inline.LongNumber[lc[0]]]];
    plist[5] ← PropertyPair[FALSE, UserName.prefix, String[NIL]];
    Query[server, fid, plist];
    END;

  WaitForRetry: PROCEDURE [msg: LONG STRING, seconds: CARDINAL] =
    BEGIN
    oldCursor: Cursor.Object;
    ticks: Process.Ticks = Process.SecondsToTicks[1];
    Cursor.Fetch[@oldCursor];
    Cursor.Set[retry];
    WriteString[msg];
    WriteString[".  Will retry."L];
    THROUGH [0..seconds) DO
      Process.Pause[ticks];
      IF UserInput.UserAbort[NetDirBuilderOps.log] THEN
        BEGIN
        PostMessage["  Aborted!"L, NIL, warning];
        Cursor.Store[@oldCursor];
        ERROR Aborted
        END;
      ENDLOOP;
    WriteChar[Ascii.CR];
    Cursor.Store[@oldCursor];
    END;

  -- String Support routines

  PostMessage: PROCEDURE [name, message: LONG STRING, err: ErrorLevel ← info] =
    BEGIN OPEN String;
    SELECT err FROM
      info => NULL;
      warning => {WriteString["Warning, "L]; warnings ← warnings + 1};
      error => {WriteString["Error, "L]; errors ← errors + 1};
      ENDCASE;
    IF name # NIL THEN WriteString[name];
    IF message # NIL THEN {WriteString[": "L]; WriteString[message]};
    WriteChar[Ascii.CR];
    END;

  -- IODefs still lives

  WriteChar: PROCEDURE [c: CHARACTER] =
    BEGIN Put.Char[NetDirBuilderOps.log, c]; END;

  WriteString: PROCEDURE [s: LONG STRING] =
    BEGIN Put.Text[NetDirBuilderOps.log, s]; END;

  WriteLine: PROCEDURE [s: LONG STRING] =
    BEGIN Put.Line[NetDirBuilderOps.log, s]; END;

  END.