-- FQImpl.Mesa, last edit February 8, 1983 4:31 pm

	DIRECTORY
	  ConvertUnsafe: TYPE USING[ToRope],
	  CWF: TYPE USING [SWF2, SWF3, SWF4],
	  DateAndTimeUnsafe: TYPE USING [Parse, Unintelligible],
	  FileLookup: TYPE USING[LookupFile, Result],
	  FQ: TYPE USING[Result],
	  LongString: TYPE USING[StringToDecimal],
	  Rope: TYPE USING[ROPE],
	  UnsafeSTP: TYPE USING [DesiredProperties, Enumerate, Error, FileInfo, GetFileInfo, Handle,
	  	NoteFileProcType, SetDesiredProperties],
	  STPSubr: TYPE USING [Connect, ForceClosed, HandleSTPError],
	  Subr: TYPE USING [EndsIn, Prefix, strcpy, TTYProcs];


FQImpl: PROGRAM 
IMPORTS ConvertUnsafe, CWF, DateAndTimeUnsafe, FileLookup, LongString, 
	STP: UnsafeSTP, STPSubr, Subr 
EXPORTS FQ = {


-- be careful about useRopes: ropes are not available for BringOver in the boot file

-- if createtime ~= 0 then
--		looks for a file with that create time
--		uses version number as a hint
--		result is foundCorrectVersion if the file is found
--			foundWrongVersion if a file is found but not that one
--			notFound  if the file or server does not exist at all
-- if createtime = 0 and wantExplicitVersion THEN
--		looks for a file with that version number
--		result is foundCorrectVersion if the file is found
--			notFound  if the file or server does not exist at all
-- if createtime = 0 and NOT wantExplicitVersion THEN
--		looks for any file of that name (!H)
--		result is foundCorrectVersion if the file is found
--			notFound  if the file or server does not exist at all

-- foundCorrectVersion returns correct version and time
--			targetFileName has a full filename including version number
-- foundWrongVersion returns highest version and time
--			targetFileName has a full filename without version number
-- notFound returns no version and no time
--			targetFileName has a full filename without version number
--				unless wantExplicitVersion = TRUE, in which case it will have one

FileQuery: PUBLIC PROC [host, directory, shortname: LONG STRING, 
		version: CARDINAL, createtime: LONG CARDINAL, wantExplicitVersion: BOOL,
		h: Subr.TTYProcs, targetFileName: LONG STRING, useRopes: BOOL] 
	RETURNS [fres: FQ.Result, remoteVersion: CARDINAL, 
		remoteCreateTime, remoteByteLength: LONG CARDINAL] = {
	sfn: STRING ← [100];
	result: FileLookup.Result;
	prefix: STRING ← [100];
	tryBangH: BOOL ← FALSE;
	server, file: Rope.ROPE;
	
	CWF.SWF3[prefix, "[%s]<%s>%s"L, host, directory, shortname];
	{
	IF NOT useRopes THEN 
		GOTO skipThis;
	IF version > 0 AND (createtime ~= 0 OR wantExplicitVersion) THEN
		CWF.SWF3[sfn, "<%s>%s!%u"L, directory, shortname, @version]
	ELSE {
		tryBangH ← TRUE; 
		CWF.SWF2[sfn, "<%s>%s!H"L, directory, shortname];
		};
	server ← ConvertUnsafe.ToRope[host];
	file ← ConvertUnsafe.ToRope[sfn];
	[result: result, version: remoteVersion, create: remoteCreateTime, 
	  count: remoteByteLength] ← FileLookup.LookupFile[server: server, file: file];
	server ← file ← NIL;	-- free as soon as possible
	SELECT result FROM
	ok => {
		IF createtime = 0 OR createtime = remoteCreateTime THEN {
			-- the file found has the right create date, or there was no create time but we found it
			CWF.SWF2[targetFileName, "%s!%d"L, prefix, @remoteVersion];
			RETURN[foundCorrectVersion, remoteVersion, remoteCreateTime, remoteByteLength];
			};
		-- otherwise do the FTP search
		};
	noSuchName => {
		-- this file does not exist
		IF tryBangH OR wantExplicitVersion THEN {
			-- we probed with !H, there is no file
			-- we probed with explicit version, there is no file
			FillInErrorTargetFile[targetFileName, prefix, wantExplicitVersion, version];
			RETURN[notFound, 0, 0, 0];
			};
		-- if we probed with a version hint, do the FTP search
		};
	noResponse, noSuchPort =>{
		-- no file lookup server or it does not reply, do the FTP search
		};
	ENDCASE => ERROR;
	EXITS
	skipThis => NULL;
	};
	[fres, remoteVersion, remoteCreateTime, remoteByteLength] ← 
		EnumerateWithSTP[host, directory, shortname,
			version, createtime, wantExplicitVersion, h, prefix, targetFileName];
	};
	
EnumerateWithSTP: PROC[host, directory, shortname: LONG STRING, 
		version: CARDINAL, createtime: LONG CARDINAL, wantExplicitVersion: BOOL,
		h: Subr.TTYProcs, prefix, targetFileName: LONG STRING] 
	RETURNS[fres: FQ.Result, remoteVersion: CARDINAL, 
		remoteCreateTime, remoteByteLength: LONG CARDINAL] = {
	stphandle: STP.Handle;
	found: BOOL;
	highdate: LONG CARDINAL ← 0;
	highversion: CARDINAL ← 0;
	foundFileName: STRING  ← [100];
	desiredProperties: STP.DesiredProperties ← ALL[FALSE];
	
	-- NOTE: Desired properties have been set
	-- only called once unless createtime ~= 0
	-- stphandle is passed in
	-- NoteFileProcType: TYPE = PROC [file: STRING] RETURNS [continue: Continue];
	EnumProcessFile: STP.NoteFileProcType = {
		vers: CARDINAL ← 0;
		date: LONG CARDINAL;
		info: STP.FileInfo ← STP.GetFileInfo[stphandle];
		continue ← yes;
		IF info.version ~= NIL AND info.version.length > 0 THEN
			vers ← LongString.StringToDecimal[info.version];
		date ← DateAndTimeUnsafe.Parse[info.create
			! DateAndTimeUnsafe.Unintelligible => {	-- this can actually happen e.g. time = 0 (or 1937)
				date ← 0;
				CONTINUE;
				};
			].dt;
		IF vers > highversion THEN {
			highversion ← vers;
			highdate ← date;
			};
		IF found THEN RETURN[yes];
		-- if the date in the df file agrees with date on remote server
		IF createtime > 0 AND createtime = date THEN {
			remoteVersion ← vers;
			remoteCreateTime ← date;
			remoteByteLength ← info.size;
			fres ← foundCorrectVersion;
			found ← TRUE;
			Subr.strcpy[foundFileName, file];
			RETURN;
			}
		-- if there is no date in the df file
		ELSE IF createtime = 0 THEN {
			remoteVersion ← vers;
			remoteCreateTime ← date;
			remoteByteLength ← info.size;
			found ← TRUE;
			Subr.strcpy[foundFileName, file];
			RETURN;
			};
		-- if we were given a date but it did not agree, keep looking
		};
		
	{
	smashstp: STP.Handle ← NIL;
	vstring: STRING ← IF Subr.Prefix[host, "maxc"L] THEN ";"L ELSE "!"L;
	sfn: STRING ← [100];
	found ← FALSE;
	remoteCreateTime ← remoteByteLength ← remoteVersion ← 0;
	IF version > 0 AND createtime = 0 AND wantExplicitVersion THEN
		CWF.SWF4[sfn, "<%s>%s%s%u"L, directory, shortname, vstring, @version]
	ELSE IF createtime = 0 THEN -- take highest version
		CWF.SWF3[sfn, "<%s>%s%sH"L, directory, shortname, vstring]
	ELSE -- search for all
		CWF.SWF3[sfn, "<%s>%s%s**"L, directory, shortname, vstring];
	desiredProperties[directory] ← TRUE;
	desiredProperties[nameBody] ← TRUE;
	desiredProperties[version] ← TRUE;
	desiredProperties[createDate] ← TRUE;
	desiredProperties[size] ← TRUE;
	DO
		stphandle ← STPSubr.Connect[host: host, onlyOne: TRUE, h: h];
		STP.SetDesiredProperties[stphandle, desiredProperties];
		STP.Enumerate[stphandle, sfn, EnumProcessFile
		! STP.Error => 
			IF code = noSuchFile OR code = illegalFileName THEN {
				GOTO notFound
				}
			ELSE IF code = connectionClosed THEN {
				-- CWF.WF1["Connection to %s timed out.\n"L, df.host];
				smashstp ← stphandle;
				CONTINUE;
				}
			ELSE IF STPSubr.HandleSTPError[stphandle, code, error, h] THEN RETRY
			];
		IF smashstp ~= NIL THEN 
			smashstp ← STPSubr.ForceClosed[smashstp]
		ELSE 
			EXIT;
		ENDLOOP;
	EXITS
	notFound => {
		FillInErrorTargetFile[targetFileName, prefix, wantExplicitVersion, version];
		RETURN[notFound, 0, 0, 0];	-- no such file, did not enumerate
		};
	};
	IF found THEN {
		CWF.SWF2[targetFileName, "[%s]%s"L, host, foundFileName]; 
		RETURN[foundCorrectVersion, remoteVersion, remoteCreateTime, remoteByteLength];
		};
	FillInErrorTargetFile[targetFileName, prefix, wantExplicitVersion, version];
	RETURN[foundWrongVersion, highversion, highdate, 0];	-- enumerated but didn't find it
	};


FillInErrorTargetFile: PROC[targetFileName, prefix: LONG STRING, wantExplicitVersion: BOOL,
	version: CARDINAL] = {
	IF wantExplicitVersion THEN
		CWF.SWF2[targetFileName, "%s!%d"L, prefix, @version]
	ELSE
		Subr.strcpy[targetFileName, prefix];
	};

-- just check the highest version

-- if createtime > 0 then looks for !h in that version
--			foundCorrectVersion if found
--			foundWrongVersion if not found in that date
--			notFound if not there at all
-- if createtime = 0 then look for any !h
--			foundCorrectVersion if found
--			notFound if not there at all
-- foundCorrectVersion returns correct version and time
-- foundWrongVersion returns highest version and time
-- notFound returns no version and no time

FileQueryBangH: PUBLIC PROC [host, directory, shortname: LONG STRING, 
		createtime: LONG CARDINAL, h: Subr.TTYProcs, useRopes: BOOL] 
	RETURNS [fres: FQ.Result, remoteVersion: CARDINAL, 
		remoteCreateTime, remoteByteLength: LONG CARDINAL] = {
	sfn: STRING ← [100];
	result: FileLookup.Result;
	IF NOT useRopes THEN {
		[fres, remoteVersion, remoteCreateTime, remoteByteLength] ← 
			EnumerateWithSTPBangH[host, directory, shortname, createtime, h];
		RETURN;	-- can't use FileLookup because of Ropes
		};
	CWF.SWF2[sfn, "<%s>%s!H"L, directory, shortname];
	[result: result, version: remoteVersion, create: remoteCreateTime, 
	  count: remoteByteLength] ←
		FileLookup.LookupFile[server: ConvertUnsafe.ToRope[host],
			file: ConvertUnsafe.ToRope[sfn]];
	SELECT result FROM
	ok => {
		IF createtime ~= 0 AND createtime = remoteCreateTime THEN {
			-- the file found has the right create date
			RETURN[foundCorrectVersion, remoteVersion, remoteCreateTime, remoteByteLength];
			}
		ELSE IF createtime = 0 THEN {
			-- there was no create time but we found it
			RETURN[foundCorrectVersion, remoteVersion, remoteCreateTime, remoteByteLength];
			};
		-- otherwise the file we found was the wrong version
		RETURN[foundWrongVersion, remoteVersion, remoteCreateTime, remoteByteLength];
		};
	noSuchName => {
		-- this file does not exist
		RETURN[notFound, 0, 0, 0];
		};
	noResponse, noSuchPort =>{
		-- no file lookup server or it does not reply, do the FTP search
		[fres, remoteVersion, remoteCreateTime, remoteByteLength] ← 
			EnumerateWithSTPBangH[host, directory, shortname, createtime, h];
	};
	ENDCASE => ERROR;
	};
	
EnumerateWithSTPBangH: PROC[host, directory, shortname: LONG STRING, 
		createtime: LONG CARDINAL, h: Subr.TTYProcs] 
	RETURNS[fres: FQ.Result, remoteVersion: CARDINAL, 
		remoteCreateTime, remoteByteLength: LONG CARDINAL] = {
	stphandle: STP.Handle;
	desiredProperties: STP.DesiredProperties ← ALL[FALSE];

	-- NOTE: Desired properties have been set
	-- only called once
	-- stphandle is passed in
	-- NoteFileProcType: TYPE = PROC [file: STRING] RETURNS [continue: Continue];
	EnumProcessFile: STP.NoteFileProcType = {
		vers: CARDINAL ← 0;
		date: LONG CARDINAL;
		info: STP.FileInfo ← STP.GetFileInfo[stphandle];
		continue ← yes;
		IF info.version ~= NIL AND info.version.length > 0 THEN
			vers ← LongString.StringToDecimal[info.version];
		date ← DateAndTimeUnsafe.Parse[info.create
			! DateAndTimeUnsafe.Unintelligible => {	-- this can actually happen e.g. time = 0 (or 1937)
				date ← 0;
				CONTINUE;
				};
			].dt;
		remoteCreateTime ← date;
		remoteByteLength ← info.size;
		remoteVersion ← vers;
		-- if the date in the df file agrees with date on remote server
		IF createtime > 0 AND createtime = date THEN 
			fres ← foundCorrectVersion
		ELSE IF createtime = 0 THEN -- if there is no date in the df file
			fres ← foundCorrectVersion
		ELSE -- if we were given a date but it did not agree
			fres ← foundWrongVersion;
		};
		
	{
	smashstp: STP.Handle ← NIL;
	vstring: STRING ← IF Subr.Prefix[host, "maxc"L] THEN ";"L ELSE "!"L;
	sfn: STRING ← [100];
	fres ← notFound;
	remoteCreateTime ← remoteByteLength ← remoteVersion ← 0;
	CWF.SWF3[sfn, "<%s>%s%sH"L, directory, shortname, vstring];
	desiredProperties[version] ← TRUE;
	desiredProperties[createDate] ← TRUE;
	desiredProperties[size] ← TRUE;
	DO
		stphandle ← STPSubr.Connect[host: host, onlyOne: TRUE, h: h];
		STP.SetDesiredProperties[stphandle, desiredProperties];
		STP.Enumerate[stphandle, sfn, EnumProcessFile
		! STP.Error => 
			IF code = noSuchFile THEN GOTO notFound
			ELSE IF code = illegalFileName THEN {
				-- see if the file name works without the !* or ;*
				IF Subr.EndsIn[sfn, "!H"L] THEN {
					sfn.length ← sfn.length - 2;
					LOOP;	-- try again without it (this is for Twinkle)
					}
				ELSE CONTINUE	-- second try failed
				}
			ELSE IF code = connectionClosed THEN {
				-- CWF.WF1["Connection to %s timed out.\n"L, df.host];
				smashstp ← stphandle;
				CONTINUE;
				}
			ELSE IF STPSubr.HandleSTPError[stphandle, code, error, h] THEN RETRY
			];
		IF smashstp ~= NIL THEN 
			smashstp ← STPSubr.ForceClosed[smashstp]
		ELSE 
			EXIT;
		ENDLOOP;
	EXITS
	notFound => RETURN[notFound, 0, 0, 0];	-- no such file, did not enumerate
	};
	RETURN[fres, remoteVersion, remoteCreateTime, remoteByteLength];
	};

}.