xsharp.eu • find files pitfall
Page 1 of 1

find files pitfall

Posted: Thu Sep 23, 2021 4:42 pm
by Karl-Heinz
Guys,

In the German Xbase++ forum there´s been a discussion about the Directory() behaviour with the search pattern "862*.jpg".

https://www.xbaseforum.de/viewtopic.php ... 00#p139600

The problem can be reproduced with these two (long name ! ) files:

86220200630.JPG
86720210702.JPG

Because of the search pattern, only this file should be found.

86220200630.JPG

but both files are found !

Since VO and X# give the same result I took a closer look. The problem occurs when long filename are used and the
DOS 8.3 names feature is enabled on the search drive. You can check the current state of your drives when you open
a CMD box with admin rights and run the fsutil.exe.

fsutil 8dot3name query <Drive:>

These are the results of my "C:" and "D:" drives

8dot3 name creation is enabled on C:
8dot3 name creation is disabled on D:

When i search in my C:Test dir both files are found and only the search in my D:test directory shows one file. The
problem is that in the C:Test dir a comparison takes place with the short filenames and the long filenames. If the
pattern is found in the short filename it is interpreted as a hit.

That´s the content of my C:Test dir. As you can see both short file names start with "862"

Code: Select all

C:Test>dir/X 862*.jpg
 Volume in Laufwerk C: hat keine Bezeichnung.
 Volumeseriennummer: DA00-F3C0

 Verzeichnis von C:Test

23.09.2021  18:00                 5 862202~1.JPG 86220200630.JPG
23.09.2021  18:00                 5 8624E7~1.JPG 86720210702.JPG
               2 Datei(en),             10 Bytes
               0 Verzeichnis(se), 119.755.788.288 Bytes frei
From what i´ve found so far, the 8dot3name feature is enabled by default for the C drive only. That's the reason why
the search works in my D:test dir. It seems the only way to fix this behaviour is to disable the 8dot3name feature, or
to implement a own pattern search ?

That's my test code:

Code: Select all

FUNCTION SearchFiles()
LOCAL aFiles, aDir AS ARRAY
LOCAL i AS DWORD 
LOCAL hFile, hFind AS PTR 
LOCAL cDir, cFile AS STRING
LOCAL struFindData IS _winWIN32_FIND_DATA 


	cDir := "c:Test"	
//	cDir := "d:Test"

	aFiles := {} 
	
	// long filenames 
	AAdd(aFiles,"86220200630.JPG")
	AAdd(aFiles,"86720210702.JPG")
   
	FOR i := 1 TO ALen(aFiles)
		IF ! File( cDir + aFiles [i])
			IF ( hFile := FCreate( cDir + aFiles[i], FC_NORMAL ) ) != F_ERROR
				FWrite( hFile, "Jimmy" )
				FClose( hFile )
			ENDIF 	
		ENDIF	       
	NEXT

	aDir := Directory( cDir + "862*.JPG") 
    
	?
	? "Directory() results"
	?
   
	FOR i := 1 UPTO ALen ( ADir ) 
		? aDir [ i , F_NAME ] 
	NEXT
	
	?
	? "FindxxxFile results"
	?
	
	cFile := cDir + "862*.JPG" 

	IF ( hFind := FindFirstFile( String2Psz ( cFile ) , @struFindData) )  != INVALID_HANDLE_VALUE

		// note: Psz2String ( @struFindData.cAlternateFileName ) shows the 8.3 filename if any
		
		? Psz2String ( @struFindData.cFileName ) , Psz2String ( @struFindData.cAlternateFileName )
		
		WHILE FindNextFile(hFind, @struFindData)
			? Psz2String ( @struFindData.cFileName ) , Psz2String ( @struFindData.cAlternateFileName )
		ENDDO 	           
 			
 		FindClose ( hFind ) 	

	ENDIF    		
   

#ifdef __XSHARP__ 

	?
	? "System.IO.Directory.GetFiles results"
	?
        
	VAR files := System.IO.Directory.GetFiles(cDir, "862*.JPG" ) 
	
	FOREACH VAR f IN files 
		? System.IO.Path.GetFileName ( f )
	NEXT 	
	
  
   
#endif   

   
RETURN TRUE
regards
Karl-Heinz

find files pitfall

Posted: Thu Sep 23, 2021 8:42 pm
by robert
Karl-Heinz,
Maybe the Directory function should (internally) double check to see if the found file matches the mask?
We have a _Like() function that could be used for that.

Robert

find files pitfall

Posted: Fri Sep 24, 2021 11:28 am
by ic2
Hello Robert, Karl-Heinz,

Reading this amazed me. What is the use of DOS 8.3 filenames anyway? If not already disabled, I think every sensible system administrator should do that (I think it's fsutil behavior set disable8dot3 3, correct?)

If 86720210702.JPG becomes 8624E7~1.JPG , a totally random name change, then you could expect to find anything. This of course again comes from Microsoft. Logically this file should be called 867202~1.jpg I'd say. I often like older techniques to remain available but who wants to see a >8+3 filename converted in 2021 to a 8+3 file name which was from before 1994?

Dick

find files pitfall

Posted: Sun Oct 03, 2021 10:37 am
by Karl-Heinz
Guys,

the advice of Robert to use the _Like() function seems the way to go. Here´s a modified test function:

Code: Select all

FUNCTION SearchFiles2()
LOCAL cDir, sWildCard  AS STRING 	
LOCAL aFiles AS ARRAY 
LOCAL files AS STRING[]
LOCAL i AS DWORD 
LOCAL hFile AS PTR 

	cDir := "c:Test"	// 8dot3name enabled 
//	cDir := "d:Test"

	aFiles := {}  
	
	AAdd(aFiles,"86220200630.JPG")
	AAdd(aFiles,"86720210702.JPG")
	AAdd(aFiles,"86220200630.Txt")
	AAdd(aFiles,"86720210702.Txt")
	AAdd(aFiles,"Test1.htm")
	AAdd(aFiles,"Test1.html")	
	AAdd(aFiles,"TEst2.html")		
	
//	sWildCard := "*.???" 	
//	sWildCard := "*.*" 
	sWildCard := "862*.jpg" 
//	sWildCard := "86220200630.JPG"	
//	sWildCard := "862*.txt" 	
//	sWildCard := "*.h*"
//	sWildCard := "*.htm"	 
//	sWildCard := "*.html" 
	
	? "Current search mask: " + sWildCard

	FOR i := 1 TO ALen(aFiles)
		IF ! File( cDir + aFiles [i])
			IF ( hFile := FCreate( cDir + aFiles[i], FC_NORMAL ) ) != F_ERROR
				FWrite( hFile, "Jimmy" )
				FClose( hFile )
			ENDIF 	
		ENDIF	       
	NEXT

	

	
	?
	? "System.IO.Directory.GetFiles results"
	?
	
 
	files := System.IO.Directory.GetFiles(cDir, sWildCard ) 

	FOREACH cFile AS STRING IN files 
		
		VAR oFile := System.IO.FileInfo{cFile}	
		
		? _Like ( sWildCard:ToUpper() , oFile:Name:ToUpper() ) , oFile:Name
		
		/* 
			Only if _Like() returns true, the file should be added inside  		
		 	the Directory() function.			

		*/

	NEXT 
	
	?
	? PathMatchSpec ( "Test.htm" , "*.htm" ) 	// ok, true 
	? PathMatchSpec ( "Test.html" , "*.htm" ) 	// ok, false 
	? PathMatchSpec ( "Test.html" , "*.html" ) 	// ok, true 
	
RETURN TRUE 	

[DllImport("shlwapi.dll", CharSet := CharSet.Auto)] ;
FUNCTION PathMatchSpec( pszFileParam AS STRING , pszSpec AS STRING ) AS LOGIC


A side effect is that a search with a pattern like "*.htm" no longer includes "*.html" files. Just activate the line

sWildCard := "*.htm"

and you´ll see that the - imo correct - results are now:

.T. Test1.htm
.F. Test1.html
.F. TEst2.html

BTW I. Another option to filter out files according the file extension is to use the API function PathMatchSpec() - see the PathMatchSpec() samples.

BTW II. Without looking directly at the created short file names you´ll never know how such names look like. Simply use the pattern "862*.txt" instead of "862*.jpg" and you´ll see that now only the file "86220200630.Txt" will be found ...

regards
Karl-Heinz

find files pitfall

Posted: Tue Oct 05, 2021 6:29 am
by robert
Karl Heinz,
I will add the check to the runtime.
Btw if you use Like() instead of _Like() then you do not have to uppercase the strings in your code. Like() is case insensitive.

Robert

find files pitfall

Posted: Wed Oct 06, 2021 4:17 pm
by Karl-Heinz
Robert,

my intention to use _Like() instead of Like() was that Like() behaves differently when the Fox dialect is used.

Code: Select all

FUNCTION Like(sWildCard AS STRING, sSource AS STRING) AS LOGIC
    IF XSharp.RuntimeState.Dialect == XSharpDialect.FoxPro
        RETURN _Like(sWildCard, sSource)
    ENDIF
    RETURN _Like(Upper(sWildCard), Upper(sSource))
But in the meantime I found out that the VFP Directory() works completely different than the VO Directory() function.

@all

When I look at the result of the X#/VO Directory() function, i see that the content of the array returned has been expanded. The new array constants are marked with // new

Code: Select all

CONST EXPORT F_NAME := 1 AS INT
CONST EXPORT F_SIZE := 2 AS INT
CONST EXPORT F_DATE := 3 AS INT
CONST EXPORT F_WRITE_DATE := 3 AS INT  // New
CONST EXPORT F_TIME := 4 AS INT
CONST EXPORT F_WRITE_TIME := 4 AS INT // New
CONST EXPORT F_ATTR := 5 AS INT
CONST EXPORT F_EA_SIZE := 6 AS INT  // New: 
CONST EXPORT F_CREATION_DATE := 7 AS INT // New
CONST EXPORT F_CREATION_TIME := 8 AS INT // New
CONST EXPORT F_ACCESS_DATE := 9 AS INT  // New
CONST EXPORT F_ACCESS_TIME := 10 AS INT // New

Now, showing a X#/VO Directory() result like

Code: Select all

? ShowArray ( aDirectoryResult )

results in:

Code: Select all

a[1] = {[0000000010]0x0141B42A} (A)
a[1][1] = 86220200630.Txt (C)
a[1][2] = 5 (N)
a[1][3] = 29.09.2021 (D)
a[1][4] = 08:30:11 (C)
a[1][5] = A (C)
a[1][6] = 32 (N)
a[1][7] = 27.09.2021 (D)
a[1][8] = 07:39:26 (C)
a[1][9] = 29.09.2021 (D)
a[1][10] = 08:30:11 (C)
regards
Karl-Heinz