-- BringOverImpl.Mesa, last edit February 4, 1983 2:42 pm
-- 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, CheckForModify, CopyString, debugflg,
  	EndsIn, 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] = {
	ENABLE File.Unknown => {
		CWF.WF1["ERROR File.Unknown - problem reading %s,\n"L, df.shortname];
		GOTO out;
		};
	refused: BOOL ← FALSE;
	localCreateTime: LONG CARDINAL ← 0;
	remoteCreateTime: LONG CARDINAL ← 0;
	remoteVersion: CARDINAL;
	fres: FQ.Result;
	targetFileName: STRING ← [125];
	
	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 RETURN;
	[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 => {
		sfn: STRING ← [100];
		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 => {
		sfn: STRING ← [100];
		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;
	};
	
RetrieveIfWanted: PROC[df: DFSubr.DF, remoteName: STRING,
	remoteCreateTime, localCreateTime: LONG CARDINAL,
	h: Subr.TTYProcs, ldspace: Space.Handle, state: BareBringOver.State] 
	RETURNS[broughtover, refused, renamed: BOOL] = {
	stemp: STRING ← [100];
	fullname: STRING ← [125];
	
	-- 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];
		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;
			RETURN;	
			};
		IF ch = 'a THEN 
			state.mustconfirm ← FALSE
		ELSE IF ch ~= 'y THEN {
			refused ← TRUE;
			RETURN;	
			};
		};
	-- this will retrieve it
	STPSubr.EnumerateForRetrieve[df.host, remoteName, OneFile, h];
	};
		
ProceedWithBringOver: PROC[df: DFSubr.DF, info: STP.FileInfo, 
		h: Subr.TTYProcs, remoteCreateTime, localCreateDate: LONG CARDINAL,
		remotestream: Stream.Handle, fullname: 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: STRING ← [100];
		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;
		};
	
-- 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;
	targetFileName: STRING ← [125];
	remoteVersion: CARDINAL;
	
	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];
			};
		};
	};
	
	
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];
	};
	
}.