-- BringOverImpl.Mesa
-- last edit February 4, 1983 2:42 pm
-- last edit May 22, 1983 3:49 pm, Russ Atkinson
 -- changed short STRING into LONG STRING
-- Pilot 6.0/ Mesa 7.0

-- Switch Meaning
-- /a  do NOT confirm any transfers!!!
-- /o file retrieve only file "file"
-- /p  retrieve only the Public files
-- /v  verify mode - simply confirm that df file has no errors
--  (e.g. file not found, etc.) Does not transfer anything.

-- default is to retrieve both Public and non-Public files

-- note that calling procedures in BringOverInterface will cause a start trap,
-- which will run Exec.AddCommand [BringOver]
-- this BringOver.~ can be run without any problems

DIRECTORY
BareBringOver: TYPE USING[State],
CIFS: TYPE USING[Error],
CWF: TYPE USING [SWF1, SWF2, SWF3, SWF4, WF0, WF1,
 WF2, WF3, WF4, WFC, WFCR],
DFSubr: TYPE USING [AllocateDFSeq, CopyUsing, Criterion,
DF, DFEntryProcType, DFSeq, FreeDFSeq, FreeUsingSeq,
  InterestingNestedDFProcType, IntersectUsing, NextDF, ParseStream, ReadInDir,
  UsingEmpty, UsingSeq, WriteOut],
Directory: TYPE USING[DeleteFile, Error, Lookup, ignore, Rename, RemoveFile],
File: TYPE USING[Capability, nullCapability, Unknown],
FileStream: TYPE USING [SetLeaderPropertiesForCapability],
FQ: TYPE USING[FileQuery, FileQueryBangH, Result],
IO: TYPE USING[UserAbort],
KernelFile: TYPE USING[MakeTemporary],
LongString: TYPE USING [EquivalentString],
Runtime: TYPE USING[GetBcdTime, GetBuildTime],
Space: TYPE USING [Handle],
UnsafeSTP: TYPE USING [FileInfo, GetFileInfo, Handle],
STPSubr: TYPE USING [EnumerateForRetrieve, RetrieveProcType,
 WriteStreamToDisk],
Stream: TYPE USING [Delete, Handle],
Subr: TYPE USING [AbortMyself, AllocateString, CheckForModify, CopyString, debugflg,
 EndsIn, FreeString, GetCreateDateWithSpace,
 NewStream, numberofleaders, Prefix, Read,
 SetRemoteFilenameProp, strcpy, TTYProcs,
 Write];

-- should really be a monitor on BringOverState

BringOverImpl: PROGRAM
IMPORTS CIFS, CWF, DFSubr, Directory, File, FileStream, FQ,
 KernelFile, IO, LongString, Runtime,
STP: UnsafeSTP, STPSubr, Stream, Subr
EXPORTS BareBringOver = {

MAXFILES: CARDINAL = 500;

-- no mds usage

-- exported to BareBringOver
CheckThisFile: PUBLIC SIGNAL[checkfilename: LONG STRING] = CODE;
RecursiveLoop: PUBLIC SIGNAL[loopfilename: LONG STRING] = CODE;

BringOverDF: PUBLIC PROC[dfseq: DFSubr.DFSeq, state: BareBringOver.State, ldspace: Space.Handle,
 h: Subr.TTYProcs] = {
 nretrieved, nRenamed: CARDINAL ← 0;

  NestedDFFileProc: DFSubr.InterestingNestedDFProcType = {
  sh: Stream.Handle ← NIL;
  dfseq, dfseqInner: DFSubr.DFSeq ← NIL;
  newUsing: DFSubr.UsingSeq ← NIL;
  {
  ENABLE UNWIND => {
   IF sh ~= NIL THEN Stream.Delete[sh];
   sh ← NIL;
   IF newUsing ~= NIL AND innerUsingSeq ~= NIL THEN
    DFSubr.FreeUsingSeq[newUsing];
   DFSubr.FreeDFSeq[@dfseqInner];
   DFSubr.FreeDFSeq[@dfseq];
   };
  -- don't continue if q typed, df not on disk,
  -- (wrong version on disk and there is a remote host)
  IF state.quit THEN RETURN;
  IF dfEntry ~= NIL AND
   (NOT dfEntry.presentonlocaldisk
    OR (dfEntry.need AND NOT EmptyString[dfEntry.host])) THEN {
   CWF.WF1["Warning - unable to analyze contents of %s\n"L, dfEntry.shortname];
   RETURN;
   };
  -- using list processing
  IF driverUsingSeq ~= NIL AND innerUsingSeq ~= NIL THEN {
   -- must do intersection
   newUsing ← DFSubr.IntersectUsing[driverUsingSeq, innerUsingSeq];
   IF newUsing = NIL THEN RETURN; -- no intersection
   -- newUsing should be freed
   }
  ELSE IF innerUsingSeq ~= NIL THEN { -- driverUsingSeq = NIL
   -- this is an imported DF file with a using list
   newUsing ← DFSubr.CopyUsing[innerUsingSeq];
   -- allows first level to be non-public as well
   publicOnly ← FALSE;
   -- newUsing should be freed
   }
  ELSE IF driverUsingSeq ~= NIL THEN { -- innerUsingSeq = NIL
   IF DFSubr.UsingEmpty[driverUsingSeq] THEN RETURN; -- already empty
   newUsing ← driverUsingSeq;
   -- newUsing should NOT be freed
   };
  SIGNAL CheckThisFile[shortname];
  CWF.WF1["\nBringOver of %s"L, shortname];
  IF newUsing ~= NIL THEN CWF.WF0[" (with Using list)"L]
  ELSE IF publicOnly THEN CWF.WF0[" (Exports only)"L]
  ELSE IF state.verify THEN CWF.WF0[" (Verify)"L];
  CWF.WFCR[];
  IF dfEntry = NIL THEN {
   -- this calls RetrieveDF for those DF files that appear in no DFseq
   dfseqInner ← BuildFakeDFSeq[host, directory, shortname, version,
    createtime, criterion];
   dfEntry ← @dfseqInner[0];
   DFEntryProc[dfEntry];
   };
  -- don't continue if q typed, df not on disk, wrong version on disk
  IF state.quit THEN RETURN;
  IF dfEntry ~= NIL AND
   (NOT dfEntry.presentonlocaldisk
   OR (dfEntry.need AND NOT EmptyString[dfEntry.host])) THEN {
   CWF.WF1["Warning - unable to analyze contents of %s\n"L, dfEntry.shortname];
   RETURN;
   };
  sh ← Subr.NewStream[dfEntry.shortname, Subr.Read];
  dfseq ← DFSubr.AllocateDFSeq[maxEntries: MAXFILES, zoneType: shared];
  DFSubr.ParseStream[sh: sh, dfseq: dfseq, dffilename: shortname,
   using: newUsing, noremoteerrors: FALSE,
   forceReadonly: entryIsReadonly,
   omitNonPublic: publicOnly,
   h: h,
   interestingNestedDF: IF state.verify THEN NIL ELSE NestedDFFileProc,
   dfEntryProc: IF state.verify THEN NIL ELSE DFEntryProc,
   nLevel: nLevel + 1,
   ancestor: IF ancestor = NIL THEN shortname ELSE ancestor
   ! CheckThisFile =>
    IF LongString.EquivalentString[shortname, checkfilename] THEN
     ERROR RecursiveLoop[checkfilename]
   ];
  Stream.Delete[sh];
  sh ← NIL;
  IF state.verify THEN {
   ProcessVerify[dfseq, h, ldspace, state];
   GOTO leave;
   };
  IF dfseq.trailingcomment ~= NIL AND newUsing = NIL THEN {
   -- only print if non-blank
   FOR i: CARDINAL IN [0 .. dfseq.trailingcomment.length) DO
    IF dfseq.trailingcomment[i] ~= '\n AND dfseq.trailingcomment[i] ~= ' THEN {
     CWF.WF1["%s"L, dfseq.trailingcomment];
     EXIT;
     };
    ENDLOOP;
   };
  -- now append in command stream any @file.cm files in the DF file
  -- IF NOT state.quit AND newUsing = NIL THEN {
   -- stemp: STRING ← [100];
   -- FOR i: CARDINAL IN [0 .. dfseq.size) DO
    -- df: DFSubr.DF ← @dfseq[i];
    -- IF NOT df.atsign OR df.need OR NOT df.presentonlocaldisk THEN LOOP;
    -- IF NOT Subr.EndsIn[df.shortname, "cm"L] THEN LOOP;
    -- CWF.SWF1[stemp, "@%s\n"L, df.shortname];
    -- Exec.AppendCommands[stemp];
    -- ENDLOOP;
   -- };
  IF Subr.debugflg THEN
   CWF.WF2["%u leaders read, dfseq.size = %u.\n"L,
    @Subr.numberofleaders, @dfseq.size];
  IF newUsing ~= NIL AND innerUsingSeq ~= NIL THEN {
   FreeUsingSeqWithError[newUsing];
   };
  EXITS
  leave => NULL;
  };
  CWF.WF1["End of BringOver of %s\n"L, shortname];
  DFSubr.FreeDFSeq[@dfseqInner];
  DFSubr.FreeDFSeq[@dfseq];
  };

  -- derived files: .bcd, .signals, .boot, .press
  -- source files are anything else
  DFEntryProc: DFSubr.DFEntryProcType = {
  broughtover, renamed: BOOL;
  IF state.quit THEN RETURN;
  -- called for every entry in the sequence, including @ df files
  -- these @ df files cause NestedDFFileProc to be called ALSO
  IF state.justReadOnlys
  AND NOT dfEntry.readonly
  AND NOT dfEntry.atsign THEN
   RETURN;
  IF state.justNonReadOnlys AND dfEntry.readonly THEN
   RETURN;
  IF state.justSources
  AND (Subr.EndsIn[dfEntry.shortname, ".Bcd"L]
   OR Subr.EndsIn[dfEntry.shortname, ".Signals"L]
   OR Subr.EndsIn[dfEntry.shortname, ".Boot"L]
   OR Subr.EndsIn[dfEntry.shortname, ".Press"L]) THEN
    RETURN;
  IF state.justObjects
  AND NOT dfEntry.atsign
  AND NOT (Subr.EndsIn[dfEntry.shortname, ".Bcd"L]
   OR Subr.EndsIn[dfEntry.shortname, ".Signals"L]
   OR Subr.EndsIn[dfEntry.shortname, ".Boot"L]
   OR Subr.EndsIn[dfEntry.shortname, ".Press"L]) THEN
    RETURN;
  [broughtover, renamed] ← RetrieveDF[dfEntry, ldspace, h, state];
  IF broughtover THEN nretrieved ← nretrieved + 1;
  IF renamed THEN nRenamed ← nRenamed + 1;
  };
  
 {
 df: DFSubr.DF;
-- the main body
FOR i: CARDINAL IN [0 .. dfseq.size) DO
  df ← @dfseq[i];
  DFEntryProc[df];
  IF df.presentonlocaldisk AND df.atsign THEN {
   NestedDFFileProc[host: df.host, directory: df.directory,
    shortname: df.shortname,
    ancestor: NIL, immediateParent: NIL, version: df.version, nLevel: 0,
    createtime: 0, driverUsingSeq: df.using, innerUsingSeq: NIL,
    dfEntry: df, entryIsReadonly: FALSE, publicOnly: state.publicOnly,
    criterion: none];
   IF df.using ~= NIL THEN FreeUsingSeqWithError[df.using];
   df.using ← NIL;
   };
  ENDLOOP;
IF nretrieved > 0 THEN {
  CWF.WF1["%u files retrieved.*n"L, @nretrieved];
  }
ELSE CWF.WF0["No files retrieved.*n"L];
IF nRenamed > 0 THEN
  CWF.WF1["%u files had '$$' appended to their names.\n"L, @nRenamed];
 }};

EmptyString: PROC[s: LONG STRING] RETURNS[empty: BOOL] = {
RETURN[s = NIL OR s.length = 0];
 };

FreeUsingSeqWithError: PUBLIC PROC[newUsing: DFSubr.UsingSeq] = {
IF NOT DFSubr.UsingEmpty[newUsing] THEN {
  CWF.WF0["Error - cannot find "L];
  FOR i: CARDINAL IN [0 .. newUsing.size) DO
   IF newUsing[i] = NIL THEN LOOP;
   CWF.WF1["%s "L, newUsing[i]];
   ENDLOOP;
  CWF.WF0["in any nested DF file.\n"L];
  };
 DFSubr.FreeUsingSeq[newUsing];
 };

BuildFakeDFSeq: PROC[host, directory, shortname: LONG STRING, version: CARDINAL,
  createtime: LONG CARDINAL, criterion: DFSubr.Criterion]
  RETURNS[dfseq: DFSubr.DFSeq] = {
 df: DFSubr.DF;
 dfseq ← DFSubr.AllocateDFSeq[maxEntries: 1, zoneType: shared];
 df ← DFSubr.NextDF[dfseq];
 df.host ← IF host.length = 0 THEN NIL ELSE Subr.CopyString[host, dfseq.dfzone];
 df.directory ← IF directory.length = 0 THEN NIL ELSE Subr.CopyString[directory, dfseq.dfzone];
 df.shortname ← Subr.CopyString[shortname, dfseq.dfzone];
 df.version ← version;
 df.createtime ← createtime;
 df.criterion ← criterion;
 df.atsign ← TRUE;
 };

-- df.need will be TRUE if the file is not found on the remote server or the user
-- declines to bring it over
RetrieveDF: PROC[df: DFSubr.DF, ldspace: Space.Handle, h: Subr.TTYProcs, state: BareBringOver.State]
  RETURNS[broughtover, renamed: BOOL] = {
 refused: BOOLFALSE;
 localCreateTime: LONG CARDINAL ← 0;
 remoteCreateTime: LONG CARDINAL ← 0;
 remoteVersion: CARDINAL;
 fres: FQ.Result;
 targetFileName: LONG STRING ← Subr.AllocateString[125];
 sfn: LONG STRING ← Subr.AllocateString[100];
 {ENABLE {
  File.Unknown => {
   CWF.WF1["ERROR File.Unknown - problem reading %s,\n"L, df.shortname];
   GOTO out;
   };
  UNWIND => {Subr.FreeString[targetFileName]; Subr.FreeString[sfn]};
  };
 broughtover ← renamed ← FALSE;
 df.presentonlocaldisk ← TRUE;
 df.cap ← Directory.Lookup[fileName: df.shortname, permissions: Directory.ignore
  ! Directory.Error => {
   df.presentonlocaldisk ← FALSE;
   IF EmptyString[df.host] THEN
    CWF.WF1["Error - cannot open %s.\n"L, df.shortname];
   CONTINUE;
   }];
IF state.updateOnly AND NOT df.atsign AND NOT df.presentonlocaldisk THEN
  RETURN; -- not on local disk
IF state.forceRetrieval THEN
  df.need ← TRUE
ELSE
  [df.need, localCreateTime] ← ComputeNeed[df, ldspace];
IF NOT df.need OR EmptyString[df.host] THEN GO TO out;
 [fres: fres, remoteVersion: remoteVersion, remoteCreateTime: remoteCreateTime]
   ← FQ.FileQuery[df.host, df.directory, df.shortname, df.version, df.createtime,
     df.criterion = none AND df.createtime = 0, h,
     targetFileName, state.useCIFS];
SELECT fres FROM
 foundCorrectVersion => {
  CWF.SWF4[sfn, "<%s>%s%s%u"L, df.directory, df.shortname,
   IF Subr.Prefix[df.host, "maxc"L] THEN ";"L ELSE "!"L, @remoteVersion];
  [broughtover, refused, renamed] ← RetrieveIfWanted[df, sfn,
   remoteCreateTime, localCreateTime, h, ldspace, state];
  df.need ← refused;
  };
 foundWrongVersion => {
  CWF.WF2["\nError - %s (%lt) not available -- will offer the latest."L,
   df.shortname, @df.createtime];
  CWF.SWF3[sfn, "<%s>%s%sH"L, df.directory, df.shortname,
   IF Subr.Prefix[df.host, "maxc"L] THEN ";"L ELSE "!"L];
  [broughtover, refused, renamed] ← RetrieveIfWanted[df, sfn,
   remoteCreateTime, localCreateTime, h, ldspace, state];
  df.need ← refused;
  };
 notFound => CWF.WF1["Error - %s: file not found.\n"L, targetFileName];
ENDCASE => ERROR;
EXITS out => NULL}; -- of ENABLE UNWIND
 Subr.FreeString[targetFileName]; Subr.FreeString[sfn];
 };

RetrieveIfWanted: PROC[df: DFSubr.DF, remoteName: LONG STRING,
 remoteCreateTime, localCreateTime: LONG CARDINAL,
 h: Subr.TTYProcs, ldspace: Space.Handle, state: BareBringOver.State]
RETURNS[broughtover, refused, renamed: BOOL] = {
 stemp: LONG STRING ← Subr.AllocateString[100];
 fullname: LONG STRING ← Subr.AllocateString[125];
 {ENABLE UNWIND => {Subr.FreeString[stemp]; Subr.FreeString[fullname]};
-- this is only called once
 OneFile: STPSubr.RetrieveProcType = {
  skipRest ← TRUE;
  renamed ← ProceedWithBringOver[df, STP.GetFileInfo[stp],
   h, remoteCreateTime, localCreateTime, remoteStream, fullname, state];
  broughtover ← TRUE;
  };
  
 broughtover ← refused ← renamed ← FALSE;
IF df.presentonlocaldisk THEN {
  IF localCreateTime = 0 THEN
   [create: localCreateTime] ← Subr.GetCreateDateWithSpace[df.cap, ldspace];
  CWF.SWF1[stemp, "%lt"L, @localCreateTime]
  }
ELSE {
  Subr.strcpy[stemp, "New"L];
  localCreateTime ← 0;
  };
CWF.SWF2[fullname, "[%s]%s"L, df.host, remoteName];
CWF.WF2["\n%s (%lt)\n"L, fullname, @remoteCreateTime];
CWF.WF2[" to local file %s (%s)"L, df.shortname, stemp];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
IF NOT state.forceRetrieval
AND ((df.criterion = update AND localCreateTime >= remoteCreateTime) -- not newer version
OR localCreateTime = remoteCreateTime     -- already here
OR (df.newOnly AND df.presentonlocaldisk)) THEN { -- new only, already here
  CWF.WF0[" ... not retrieved.\n"L];
  GO TO return;
  };
IF df.createtime > 0 AND remoteCreateTime ~= df.createtime THEN
  CWF.WF1["\n Error - you wanted (%lt) Retrieve it?"L,
   @df.createtime];
IF state.mustconfirm THEN {
  ch: CHAR;
  ch ← h.Confirm[h.in, h.out, h.data, " ", 'y];
  IF ch = 'q OR ch = 'Q THEN {
   state.quit ← TRUE;
   GO TO return;
   };
  IF ch = 'a THEN
   state.mustconfirm ← FALSE
  ELSE IF ch ~= 'y THEN {
   refused ← TRUE;
   GO TO return;
   };
  };
-- this will retrieve it
 STPSubr.EnumerateForRetrieve[df.host, remoteName, OneFile, h];
EXITS return => {}}; -- of ENABLE UNWIND
 Subr.FreeString[stemp]; Subr.FreeString[fullname];
 };
  
ProceedWithBringOver: PROC[df: DFSubr.DF, info: STP.FileInfo,
  h: Subr.TTYProcs, remoteCreateTime, localCreateDate: LONG CARDINAL,
  remotestream: Stream.Handle, fullname: LONG STRING, state: BareBringOver.State]
  RETURNS[renamed: BOOL] = {

 renamed ← FALSE;
CWF.WF0[" ... "L];
IF df.presentonlocaldisk
AND NOT Subr.CheckForModify[df.shortname, h] THEN {
  CWF.WF1[" ... %s not transferred (can't be modified).\n"L,
   df.shortname];
  RETURN;
  };
-- the runtime calls are a hack to tell if this is BringOver
-- being run from the boot file, in which case the new version
-- cannot modify the BringOver in the boot file
IF LongString.EquivalentString[df.shortname, "BringOver.Bcd"L]
AND Runtime.GetBcdTime[] ~= Runtime.GetBuildTime[] THEN
  HandleBringOverSpecialCase[df];
-- check to see if local file is newer version
-- don't rename if the file is a bcd file
IF df.presentonlocaldisk AND localCreateDate > remoteCreateTime
AND NOT Subr.EndsIn[df.shortname, ".bcd"L] THEN {
  RenameSpecialCase[df];
  renamed ← TRUE;
  };
IF state.useCIFS THEN
  [df.cap,] ← STPSubr.WriteStreamToDisk[remotestream, df.shortname, info.size, h
   ! CIFS.Error => TRUSTED {
    IF code = fileBusy THEN {
    CWF.WF1[" ... %s not transferred (CIFS says file is busy).\n"L,
     df.shortname];
    GOTO out
    }}]
ELSE
  [df.cap,] ← STPSubr.WriteStreamToDisk[remotestream, df.shortname, info.size, h];
 df.presentonlocaldisk ← TRUE;
 Subr.SetRemoteFilenameProp[df.cap, fullname];
 FileStream.SetLeaderPropertiesForCapability[cap: df.cap, create: LOOPHOLE[remoteCreateTime]];
CWF.WF1["%lu bytes.\n"L, @info.size];
EXITS
 out => NULL;
 };

-- called whenever there is a local version that is newer than
-- the one being retrieved
RenameSpecialCase: PROC[df: DFSubr.DF] = {
 dollar: LONG STRING ← Subr.AllocateString[100];
 {ENABLE UNWIND => {Subr.FreeString[dollar]};
  CWF.SWF1[dollar, "%s$$"L, df.shortname];
  Directory.DeleteFile[dollar ! Directory.Error => CONTINUE];
  Directory.Rename[oldName: df.shortname, newName: dollar];
  CWF.WF1["(local version renamed to %s)"L, dollar];
  df.cap ← File.nullCapability;
  }; -- of ENABLE UNWIND
 Subr.FreeString[dollar];
 };

-- this means BringOver is about to replace the BringOver.Bcd on the local disk
-- by a different version. This kludge is required to avoid overwriting
-- the backing file for the code segment for the loaded BringOver.Bcd
HandleBringOverSpecialCase: PROC[df: DFSubr.DF] = {
-- Directory.Error here indicates BringOver.Bcd was renamed or a messed up directory
 cap: File.Capability;
 cap ← Directory.Lookup[fileName: "BringOver.Bcd"L, permissions: Directory.ignore
    ! Directory.Error => GOTO out];
 Directory.RemoveFile[fileName: "BringOver.Bcd"L, file: cap];
-- this begins a critical section where, if the system crashes, BringOver.Bcd is not on the disk
 KernelFile.MakeTemporary[cap];
 df.presentonlocaldisk ← FALSE; -- just in case
 df.cap ← File.nullCapability;
CWF.WF0["Warning - you have just retrieved a new version of BringOver.Bcd.\n"L];
CWF.WF0[" If you want to use the new version, you must either (1) type the\n"L];
CWF.WF0[" command 'Run BringOver.Bcd;' to the Executive, or\n"L];
CWF.WF0[" (2) boot this volume.\n"L];
EXITS
 out => NULL;
 };

ProcessVerify: PROC[dfseq: DFSubr.DFSeq, h: Subr.TTYProcs, ldspace: Space.Handle,
 state: BareBringOver.State] = {
 localdiskdate, remoteCreateTime: LONG CARDINAL;
 newerversions, fillinvers: BOOL;
 df: DFSubr.DF;
 fres: FQ.Result;
 remoteVersion: CARDINAL;
 targetFileName: LONG STRING ← Subr.AllocateString[125];
 {ENABLE UNWIND => {Subr.FreeString[targetFileName]};
 fillinvers ← newerversions ← FALSE;
 DFSubr.ReadInDir[dfseq];
FOR i: CARDINAL IN [0 .. dfseq.size) DO
  df ← @dfseq[i];
  IF EmptyString[df.host] THEN {
   CWF.WF1["Warning - unable to verify %s on remote server.\n"L, df.shortname];
   LOOP;
   };
  IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
  CWF.WFC['+];
  [fres: fres, remoteVersion: remoteVersion, remoteCreateTime: remoteCreateTime]
   ← FQ.FileQuery[df.host, df.directory, df.shortname, df.version, df.createtime,
      df.criterion = none AND df.createtime = 0, h,
      targetFileName, state.useCIFS];
  SELECT fres FROM
  foundCorrectVersion => {
   IF df.createtime > 0 AND df.version > 0 AND df.version ~= remoteVersion THEN
    CWF.WF4["%s Warning: Version !%u has date %lt, but DF file says !%u.\n"L,
      targetFileName, @remoteVersion, @remoteCreateTime, @df.version];
   IF df.criterion = none AND (df.version = 0 OR df.version ~= remoteVersion) THEN {
    df.version ← remoteVersion;
    fillinvers ← TRUE;
    };
   -- fill in for those with no create time
   IF df.createtime = 0 AND df.criterion = none AND df.version = 0 THEN {
    df.createtime ← remoteCreateTime;
    df.version ← remoteVersion;
    fillinvers ← TRUE;
    };
   };
  foundWrongVersion =>
   CWF.WF2["\n%s of %lt not found.\n"L, targetFileName, @df.createtime];
  notFound =>
   CWF.WF1["\n%s not found.\n"L, targetFileName];
  ENDCASE => ERROR;
  IF fres ~= notFound THEN {
   highfres: FQ.Result;
   highdate: LONG CARDINAL;
   highversion: CARDINAL;
   -- find out the highest version, and complain
   [fres: highfres, remoteVersion: highversion, remoteCreateTime: highdate] ←
    FQ.FileQueryBangH[df.host, df.directory, df.shortname, df.createtime,
     h, state.useCIFS];
   IF fres = foundWrongVersion THEN {
    CWF.WF2["%s: highest version is %lt"L,
     targetFileName, @highdate];
    df.createtime ← highdate;
    df.version ← highversion;
    newerversions ← TRUE;
    }
   ELSE IF highversion > remoteVersion THEN {
    CWF.WF3["%s: newer version !%u\n\tof %lt is available"L,
     targetFileName, @highversion, @highdate];
    df.createtime ← highdate;
    df.version ← highversion;
    newerversions ← TRUE;
    };
   };
  IF df.createtime = 0 AND df.criterion ~= none THEN
   df.version ← 0; -- these are confusing to users
  IF df.presentonlocaldisk THEN
   [create: localdiskdate] ← Subr.GetCreateDateWithSpace[df.cap, ldspace];
  IF df.presentonlocaldisk AND localdiskdate = remoteCreateTime
  AND fres = foundCorrectVersion THEN
   Subr.SetRemoteFilenameProp[df.cap, targetFileName];
  ENDLOOP;
IF newerversions OR fillinvers THEN {
  CWF.WF0["Storing new dffile with the newest versions \n"L];
  CWF.WF0["and/ or filled in version numbers as 'NewDFFile.DF' ... "L];
  IF Subr.CheckForModify["NewDFFile.DF"L, h] THEN {
   sh: Stream.Handle;
   sh ← Subr.NewStream["NewDFFile.DF"L, Subr.Write];
   DFSubr.WriteOut[dfseq, NIL, sh, FALSE];
   Stream.Delete[sh];
   CWF.WF0["NewDFFile.DF created.\n"L];
   };
  };
 }; -- of ENABLE UNWIND
 Subr.FreeString[targetFileName];
 };


ComputeNeed: PROC[df: DFSubr.DF, ldspace: Space.Handle]
  RETURNS[need: BOOL, localCreateDate: LONG CARDINAL] = {
IF df.createtime = 0
OR NOT df.presentonlocaldisk
OR df.criterion = update THEN
  RETURN[TRUE, 0];
 [create: localCreateDate] ← Subr.GetCreateDateWithSpace[df.cap, ldspace];
RETURN[localCreateDate ~= df.createtime, localCreateDate];
 };

}.