(* Script:  Find file pairs for files by exclusion string.
	Locate and display files which match other files *excluding* a specified string in the filename.
	i.e.   locate files matching xxxxx_hdr  where '_hdr' is exclued  (z1_hdr.jpg ->  z1.jpg, z1.rw2, z1.tif).  
	The string to exclude in the matching is provided by the user.
	NB: The extension is not included in the matching. 
	
	By: Will H,  4-jul-21   (some code copied based other scripts)  https://repository.idiimager.com
	
	Version 2.0
	
	Use: 
	
	1. Select thumnails to work with  (wouLD typically be the images which contain the string you want to exclude in the matching).
	2. You can select any number of images, and only those containing the exclusion string would be used for matching (e.g. you could select all images for a month or year, only those including the string would be used)
	3. Invoke script,
	4. provide the string for exclusion (eg. z1_hdr.jpg - provide '_hdr') in the dialogue box.
	5. Optional check boxes:  'Constrain Results to common extension': only results with matching extensions will be returned (i.e. z1_hdr.jpg -> z1.jpg), 'Ignore Case in exclsuion string': disregard the case of the exclusion string when matching.
	
	Results:
	1. Tab containing images which match each source image excluding the 'exclusion string' and with any extension
	2. Tab containing source images which have at least one matched files (without the exclusion string) in the results tab #1
	
	Other notes:
	1. Currently if there is an instance where two separate filenames with same name and the same exclusion string but different extension,
	a note will be displayed advising of possible duplicate return.  Duplicate results are notincluded in the output.  (eg. z1_hdr.png and z1_hdr.jpg could give dupicate results)
	2. The path names are not examined for the exclusion string
	3. Only those files in the same path will be matched.
	
	Possible uses:
	Search for duplicate photos z1(001).jpg,  z2(002).jpg etc.
	locate source files for processed files with tail (e.g.  z1_hdr.jpg)
	locate related files for source image (z1_hdr.jpg -> z1.jpg, z1.raw, z1.tif)  - perhaps when needing to update keywords, or portfolios.
*)

const
	cGUIDFileWOExclStr   = 'wphA5DDD2EA8E5A47E598F270326903C21A';  //data set of files *without* the exclusion string
	cGUIDFileInclExclStr = 'wph7440A20D2E26437A80091F8420FE3B38'; //data set of files with exclusion string which match against cGUIDFileWOExclStr
	cExclStr = '(001)';   // default exclusion string - change this to suit your practice
	rfReplaceAll = 1;  // constanbt for stringreplace function
	rfIgnoreCase = 2;  // costant for stringreplace function

var
	ATextBox: TvxTextBox;  // response from form with extension
	ACheckBoxExtension: TvxCheckBox; // yes to constrain matching on extension
	ACheckBoxIgnoreCase: TvxCheckBox; // Yes to ignore case
	AForm: TvxSceneForm;   // extension form
	ExclStr: WideString;    // Exclusion string
	bExtension: Boolean;    // If 'match extension' is true, only the which have the same extension as the search file are returned
	bIgnoreCase: Boolean;  // ignore case on the search
	rFlags: Byte //options for replace function

function WideQuotedStr(AStr: WideString): WideString;  //returns wide string with single quotes
begin
	result := UTF8Decode(QuotedStr(UTF8Encode(AStr)));
end;

function CreateForm: TvxSceneForm;
var
	ARoot: TvxVisualObject;
	ALabel: TvxLabel;

begin
	result := TvxSceneForm.Create(nil);
	result.Caption := UTF8Encode('Exclusion String For Search');
	result.Position := poScreenCenter;
	result.Height := 200 * result.DPIFactor;
	
	ARoot := result.Root;
	
	ALabel := TvxLabel.Create(ARoot);
	ALabel.Text := 'Enter Exclusion String: (default: '+cExclStr+')';
	ALabel.TextAlign := vgTextAlignNear;
	ALabel.Padding.Left := 10;
	ALabel.Padding.Top := 10;
	ALabel.Align := vaTop;
	ARoot.AddObject(ALabel);
	
	ATextBox := TvxTextBox.Create(ARoot);
	ATextBox.Padding.Left := 10;
	ATextBox.Padding.Right := 10;
	ATextBox.Padding.Top := 10;
	ATextBox.Align := vaTop;
	ARoot.AddObject(ATextBox);
	
	ACheckBoxExtension := TvxCheckBox.Create(ARoot);
    ACheckBoxExtension.Text:= 'Constrain Results to common extension';
	ACheckBoxExtension.IsChecked:= 0;
	ACheckBoxExtension.Padding.Left := 10;
	ACheckBoxExtension.Padding.Right := 10;
	ACheckBoxExtension.Padding.Top := 10;
	ACheckBoxExtension.Align := vaTop;
	ARoot.AddObject(ACheckBoxExtension);
	
	ACheckBoxIgnoreCase := TvxCheckBox.Create(ARoot);
	ACheckBoxIgnoreCase.Text := 'Ignore Case in exclsuion string';
	ACheckBoxIgnoreCase.IsChecked := 1;
	ACheckBoxIgnoreCase.Padding.Left := 10;
	ACheckBoxIgnoreCase.Padding.Right := 10;
	ACheckBoxIgnoreCase.Padding.Top := 10;
	ACheckBoxIgnoreCase.Align := vaTop;
	ARoot.AddObject(ACheckBoxIgnoreCase);	
	
end;

procedure CleanIDTemp(myGUID: String);  // remove entries from idTempList for provided GUIDs
Var
	ADs: TDBXOMClientDataSet;

Begin
	ADs := PublicCatalog.NewDataSet;
	try  // clean up templist - remove my results from the list
		ADs.CommandText := 'delete from idTempList where ClassGUID=''' + myGUID + '''';
		ADs.Run;
	finally
		PublicCatalog.FreeDataSet (ADs);
	end;
end;

procedure FindFileExcl(ExclStr: WideString); 
var
	i: Integer;
	ADs: TDBXOMClientDataSet;
	AItems: TCatalogItems;
	FileExcl: WideString;
	FileExclWildCard: WideString;
	FileExclPath: WideString;
	FileNameSel : WideString;
	FileExclCount: LongInt;
	FileSQL: WideString;
	bHasExclStr: Boolean;

begin
	// scrub idTemplList
	CleanIDTemp(cGUIDFileWOExclStr)
	CleanIDTemp(cGUIDFileInclExclStr)
	
	for i := 0 to Selected.Count - 1 do  // for each selected image
	begin
		if bIgnoreCase then  // check that exclusion string exists in selected image
			bHasExclStr:=WideTextPos(WideUpperCase(ExclStr), WideUpperCase(Selected.Items[i].FileName)) >0
		else
			bHasExclStr:=WidePos(ExclStr, Selected.Items[i].FileName)>0;	
		if bHasExclStr then
		begin
			FileExclPath := WideExtractFilePath(Selected.Items[i].FileName);   // path only
			FileNameSel := WideExtractFileName(Selected.Items[i].FileName);  // filename only
			FileExcl := StringReplace(WideChangeFileExt(FileNameSel,''), ExclStr,'',(rfReplaceAll or iif(bIgnoreCase,rfIgnoreCase,0)));  // filename withpout exclusion string
			if bExtension then //check whether to match against extension also
				FileExclWildCard := FileExcl + WideExtractFileExt(Selected.Items[i].FileName)
			else
				FileExclWildCard :=  WideChangeFileExt(FileExcl,'.%');  // wild card for SQL
			FileSQL := ' select distinct ''' + cGUIDFileWOExclStr + ''', idCatalogItem.GUID ' +
			'  from   idCatalogItem ' +
			'  inner  join idCache_FilePath on idCatalogItem.PathGUID = idCache_FilePath.FilePathGUID ' +
			'  where  (idCatalogItem.FileName Like ' + WideQuotedStr(FileExclWildCard) + ') ' +
			'  and    (idCache_FilePath.FilePath = ' + WideQuotedStr(FileExclPath) + ') ' +
			iif(IsSQLite, 'collate nocase', '') +
			'';
			// check to see if there at least one matching image
			try
				ADs := PublicCatalog.NewDataSet;
				ADs.CommandText := FileSQL;
				ADs.OpenSet;
				try
					FileExclCount := ADs.RecordsInSet;
				finally
					ADs.CloseSet;
				end;
			finally
				PublicCatalog.FreeDataSet(ADs);
			end;
			// if at least one matching image then process
			if FileExclCount then
			begin
				ADs := PublicCatalog.NewDataSet;
				// add matching images to idTempList
				try
					ADs.CommandText := 'insert into idTempList (ClassGUID, GUID) ' + FileSQL               
					try
						ADs.Run;
					except
						say ('Possible duplicate result for: ' + Selected.Items[i].FileName);
					end;
				finally
					PublicCatalog.FreeDataSet(ADs);
				end;
				
				// add source images which have at least one matching image into idTempList
				ADs := PublicCatalog.NewDataSet;
				ADs.CommandText := 'insert into idTempList (ClassGUID, GUID) ' +
				'  select distinct ''' + cGUIDFileInclExclStr + ''', idCatalogItem.GUID ' +
				'  from   idCatalogItem ' +
				'  inner  join idCache_FilePath on idCatalogItem.PathGUID = idCache_FilePath.FilePathGUID ' +
				'  where  (idCatalogItem.FileName = ' + WideQuotedStr(FileNameSel) +') ' +
				'  and    (idCache_FilePath.FilePath = ' + WideQuotedStr(FileExclPath) + ') ' +
				'';
				try
					ADs.Run;
				finally
					PublicCatalog.FreeDataSet(ADs);
				end;
			end;
		end;
	end;
	
	// Display matching images in new tab
	
	ADs := PublicCatalog.NewDataSet;  
	try
		ADs.CommandText := 'select ' + PublicCatalog.FullColumnList('v.') + ' ' +
		'from   v_CatalogItem v, idTempList t ' +
		'where  t.ClassGUID = ''' + cGUIDFileWOExclStr + ''' ' +
		'and    v.GUID = t.GUID ' +
		'union all ' +
		'select ' + PublicCatalog.FullColumnList('v.') + ' ' +
		'from   v_CatalogItemVersion v, idTempList t ' +
		'where  t.ClassGUID = ''' + cGUIDFileWOExclStr + ''' ' +
		'and    v.GUID = t.GUID ' +
		'order by FullFileName ' +
		'';
		//CopyTextToClipboard(ADs.CommandText);
		ADs.OpenSet;
		AItems := TCatalogItems.Create(TCatalogItem, '');  // results
		
		try  // display results in new tab
			AItems.Name := 'Matches excl. ' + WideQuotedStr(ExclStr);
			AItems.AddDataSet (ADs, True);
			PublicBroadCast(nil, 'OpenNewTab', nil);
			PublicBroadcastObject(nil, 'ShowImageResults', AItems, nil, nil);
		finally
			AItems.Free;
		end;
		ADs.CloseSet;
	finally
		PublicCatalog.FreeDataSet (ADs);
end;

CleanIDTemp(cGUIDFileWOExclStr);

// display source images with at least one matching image in a new tab

ADs := PublicCatalog.NewDataSet;  // create the resultant dataset for display
try
	ADs.CommandText := 'select ' + PublicCatalog.FullColumnList('v.') + ' ' +
	'from   v_CatalogItem v, idTempList t ' +
	'where  t.ClassGUID = ''' + cGUIDFileInclExclStr + ''' ' +
	'and    v.GUID = t.GUID ' +
	'union all ' +
	'select ' + PublicCatalog.FullColumnList('v.') + ' ' +
	'from   v_CatalogItemVersion v, idTempList t ' +
	'where  t.ClassGUID = ''' + cGUIDFileInclExclStr + ''' ' +
	'and    v.GUID = t.GUID ' +
	'order by FullFileName ' +
	'';
	ADs.OpenSet;
	AItems := TCatalogItems.Create(TCatalogItem, '');  // results
	try  // display results in new tab
		AItems.Name := 'Source incl. ' + WideQuotedStr(ExclStr);
		AItems.AddDataSet (ADs, True);
		PublicBroadCast(nil, 'OpenNewTab', nil);
		PublicBroadcastObject(nil, 'ShowImageResults', AItems, nil, nil);
	finally
		AItems.Free;
	end;
	ADs.CloseSet;
finally
	PublicCatalog.FreeDataSet (ADs);
end;
CleanIDTemp(cGUIDFileInclExclStr);
Say ('Done. Found ' + ExclStr + ' files are placed in a new Tab');
end;

// ----  main - get exclusion string for matching
begin
	if Selected.Count = 0 then
	begin
		Say('No image selected');
		exit;
	end;
	
	AForm := CreateForm; 
	try
		ATextBox.Text := cExclStr; // default exclusion string
		AForm.PrepareModal([mbOK, mbCancel]);
		AForm.ShowModal;
		if AForm.ModalResult = mrOk then
		begin
			if Length(ATextBox.Text) <> 0 then // double check for blank extension entered
			begin		
				bExtension := ACheckBoxExtension.IsChecked = 1;
				bIgnoreCase := ACheckBoxIgnoreCase.IsChecked = 1;
				ExclStr := ATextBox.Text;
				FindFileExcl(ExclStr); // go look
			end;
		end;
	finally
		AForm.Free;
	end;
end;



