Recent

Author Topic: Reading Directories  (Read 6275 times)

J-G

  • Hero Member
  • *****
  • Posts: 953
Reading Directories
« on: February 18, 2019, 05:51:30 pm »
I'm well aware that I can use FindFirst - FindNext - FindClose to determine what files exist on any Known Drive/Directory combination  but I now want to determine what directories there are on a Drive so that I can search a whole drive to determine ALL files with a specific extension, wherever they are located.

I've done some tests using the following :
Code: Pascal  [Select][+][-]
  1. procedure GetDirectories;
  2. var
  3.   n : word;
  4. begin
  5.   n := 2;
  6.   setLength(Dir_List,n);
  7.   if findfirst(DriveLetter+':/*.*',faDirectory,FileInfo) = 0 then
  8.     begin
  9.       Dir_List[1] := FileInfo.name;
  10.       inc(n);
  11.       Dir_Count := 1;
  12.     end;
  13.   if n > 2 then
  14.     begin
  15.       while findnext(FileInfo) = 0 do
  16.         begin
  17.           SetLength(Dir_List,n);
  18.           Dir_List[n-1] := FileInfo.name;
  19.           inc(n);
  20.         end;
  21.     end;
  22.   findclose(FileInfo);
  23. end;
  24.  

... but this doesn't only return Directory names, it also returns file-names which are in the root of the drive and I presume that it will also return file-names in sub-directories as well.

I could test for returned names having an extension and ignore them I'm sure but I would have hoped that using 'faDirectory' would have negated that.

Am I going about this in the wrong way?  ie. is there another procedure/function or even a component that I should be looking at?
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

Bart

  • Hero Member
  • *****
  • Posts: 5275
    • Bart en Mariska's Webstek
Re: Reading Directories
« Reply #1 on: February 18, 2019, 05:56:57 pm »
FindAllFiles() in unit FileUtil will do what you want.

Bart

furious programming

  • Hero Member
  • *****
  • Posts: 852
Re: Reading Directories
« Reply #2 on: February 18, 2019, 06:11:30 pm »
I will just add that if you need your own file search engine (the reason is not important), I suggest the following FindFirst, FindNext and FindClose constructs:

Code: Pascal  [Select][+][-]
  1. if FindFirst('*.ext', faAnyFile, SearchResult) = 0 then
  2. try
  3.   repeat
  4.     // use result
  5.   until FindNext(SearchResult) <> 0;
  6. finally
  7.   FindClose(SearchResult);
  8. end;

It is short, safe and does not need extra variables to work.
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

J-G

  • Hero Member
  • *****
  • Posts: 953
Re: Reading Directories
« Reply #3 on: February 18, 2019, 06:57:32 pm »
FindAllFiles() in unit FileUtil will do what you want.

Bart
Thanks for the quick response Bart.

Since I've never previously looked at TStringList I was amazed that my first attempt at formulating code to use it compiled and ran without error at the first attempt  :)

Code: Pascal  [Select][+][-]
  1. Dir_List := FindAllFiles(DriveLetter,'*.CDR',True,faDirectory);

Then I was not surprised that I couldn't see a list of filenames - especially since I used a DriveLetter where I know there are no files of that type :-[

After looking at the properties I thought to simply write the List to a file ...
Code: Pascal  [Select][+][-]
  1.   Dir_List.SaveToFile(DriveLetter+':/CDR_FileList.TXT');

The file was written but even though I used a drive where there are many files of that type the file was empty :(

I've tried a few other things such as creating the filename first and closing it afterward but can't yet see what I need to do to look at the list.

I have to be somewhere else in ½ an hour and it will take me 3/4 hour to get there!! so I won't see any response until late tonight  -  hopefully you (or someone) will provide a few pointers.  Thanks
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: Reading Directories
« Reply #4 on: February 18, 2019, 08:48:43 pm »
The following code is part of one my private utilities and I can assure you it works (I use it almost every day):

Code: Pascal  [Select][+][-]
  1. procedure TDiffApplication.AddToBakList(Mask: String);
  2. var
  3.   tmpList: TStringList;
  4. begin
  5.   {BakDir and BakList are globals}
  6.   tmpList := FindAllFiles(BakDir, Mask, False);
  7.   try
  8.     if Assigned(tmpList) and (tmpList.Count > 0) then begin
  9.       tmpList.Sort;
  10.       BakList.AddStrings(tmpList);
  11.     end;
  12.   finally
  13.     tmpList.Free;
  14.   end;
  15. end;

It's called throughtout my program with p.e.:
Code: Pascal  [Select][+][-]
  1.  { Get the list of backup files }
  2.   BakList.Clear;
  3.   AddToBakList(AFilename + '??');  { Get one-digit backups: filename;9  }
  4.   AddToBakList(AFilename + '???'); { Get two-digit backups: filename;99 }
  5.   AddToBakList(AFilename + '????'); { "  three-digit backups: filename;999 }

It's looking for Lazarus-style backup files and note one interesting caveat: you can't look for AFilename + ';??' because FindAllFiles() can search for multiple masks at once and uses the ";" as separator for them. Took me a while to find that!

Re. your original question, note that FileUtils also has FindAllDirectories() which returns a list of directories only.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

J-G

  • Hero Member
  • *****
  • Posts: 953
Re: Reading Directories
« Reply #5 on: February 19, 2019, 12:01:45 am »
I will just add that if you need your own file search engine (the reason is not important), I suggest the following FindFirst, FindNext and FindClose constructs:

Code: Pascal  [Select][+][-]
  1. if FindFirst('*.ext', faAnyFile, SearchResult) = 0 then
  2. try
  3.   repeat
  4.     // use result
  5.   until FindNext(SearchResult) <> 0;
  6. finally
  7.   FindClose(SearchResult);
  8. end;

It is short, safe and does not need extra variables to work.
That was my original idea of course and although your construct is more elegant than mine, it still doesn't answer my question  -  why doesn't 'faDirectory' only return 'Directories', and what does?

If I can get the 'FindAllFiles' option working I suspect that that will be easier to code.
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: Reading Directories
« Reply #6 on: February 19, 2019, 01:09:43 am »
why doesn't 'faDirectory' only return 'Directories', and what does?

Without any attribute, FindFirst(), FindNext() and cousins find only normal files. If you add attributes (faSystem, faDirectories, etc.) they find normal files plus those you asked for. This is the standard behaviour since time immemorial (actually, since Turbo Pascal 4.0, mimicking the DOS call).

Beyond backwards compatibility, It does so because there's no other way to search for "normal files plus something". Separating those others though is very easy:
Code: Pascal  [Select][+][-]
  1. if FindFirst('*.ext', faAnyFile, SearchResult) = 0 then
  2. try
  3.   repeat
  4.     if (faDirectory and SearchResult.Attr) <> 0 then
  5.       ProcessDirectory(SearchResult); {Whatever you want to do with directories}
  6.   until FindNext(SearchResult) <> 0;
  7. finally
  8.   FindClose(SearchResult);
  9. end;

BTW, this behaviour is well described in the documentation:
Quote
It is a common misconception that Attr specifies a set of attributes which must be matched in order for a file to be included in the list. This is not so: The value of Attr specifies additional attributes, this means that the returned files are either normal files or have an attribute which is present in Attr.
Specifically: specifying faDirectory as a value for Attr does not mean that only directories will be returned. Normal files and directories will be returned.
« Last Edit: February 19, 2019, 01:14:51 am by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

furious programming

  • Hero Member
  • *****
  • Posts: 852
Re: Reading Directories
« Reply #7 on: February 19, 2019, 01:59:06 pm »
Like @lucamar said. Sometimes you can find another way to detect directory:

Code: Pascal  [Select][+][-]
  1. if SearchResult.Attr and faDirectory = faDirectory then

and it gives the same effect.

But you just have to remember not to take into account the directories named '.' (dot) and '..' (double dot), because they are special folders for another purpose.
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

J-G

  • Hero Member
  • *****
  • Posts: 953
Re: Reading Directories
« Reply #8 on: February 19, 2019, 04:14:48 pm »
Success!

Well I have been able to display a list of all the files on a specified drive that have the same extension  -  ie. '.CDR'  - These are CorelDRAW! files and I have so many (47k + on one drive alone) in so many different places that it became a pain to remember where I'd put files that could be re-used and the search routine in Windows Explorer didn't really help that much.
 
You may now have realized that I don't actually need to find 'Directories' at all  -  that was simply the first step to writing a search engine but since I've got FindAllFiles working I don't need to bother with all the problems of searching down the directory tree at all.

My routine isn't perfect by any means and ultimately I'll make it more flexible than just searching for everything on one particular drive, but currently I save the list to a file and then read that back into an array and display it in a TMemo.

I'd be grateful for pointers to a more efficient method. This is my current code:

Code: Pascal  [Select][+][-]
  1. procedure GetDirectories;
  2. var
  3.   n,i : word;
  4.   s : string;
  5. begin
  6.   List_Name  := DriveLetter+':/CDR_FileList.TXT';
  7.  
  8.   File_List := FindAllFiles(DriveLetter+':\','*.CDR',True,faAnyFile);
  9.  
  10.   N := File_List.Count;
  11.   setLength(File_Array,N+1);
  12.  
  13.   File_List.SaveToFile(List_Name);
  14.  
  15.   assign(List,List_Name);
  16.   Reset(List);
  17.   for i := 0 to N do
  18.     begin
  19.       readLN(List,s);
  20.       File_Array[i] := s;
  21.       Form1.FoundFiles.Lines.Add(S);
  22.     end;
  23.   Close(List);
  24.  

I've left it as 'GetDirectories' even though that isn't really what it does

@Lucomar  -  I had seen FindAllDirectories in FileUtil but once I realized that I could go directly to find by specifying the file extension that became irrelevant.
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: Reading Directories
« Reply #9 on: February 19, 2019, 07:27:10 pm »
After the call to FindAllFiles you get the names loaded to File_List so there is no need later to use the list-file you saved. You should also add some guards against mishappenings
Code: Pascal  [Select][+][-]
  1. {Make it a method of the form.
  2. Alternatively, pass a TStrings reference for FoundFiles:
  3.   procedure GetDirectories(FoundFiles: TStrings);
  4. and call it as GetDirectories(MyMemo.Lines).
  5.  
  6. Whatever you do, *don't_hardcode_a_form_instance* like Form1}
  7. procedure TForm1.GetDirectories;
  8. var
  9.   N,  i : Integer;
  10. begin
  11.   List_Name  := DriveLetter+':/CDR_FileList.TXT';
  12.  
  13.   File_List := FindAllFiles(DriveLetter+':\','*.CDR',True,faAnyFile);
  14.  
  15.   if File_List.Count > 0 then begin
  16.     N := File_List.Count;
  17.     setLength(File_Array,N{why +1?});
  18.  
  19.     File_List.SaveToFile(List_Name);
  20.  
  21.     {File list is populated with the same info as the file, so use it!}
  22.  
  23.     FoundFiles.Lines.AddStrings(File_List);
  24.     {or FoundFiles.AddStrings(File_List) if you used the:
  25.        procedure GetDirectories(FoundFiles: TStrings)
  26.      variant.}
  27.  
  28.     for i := 0 to N-1 do
  29.       File_Array[i] := File_List[i];
  30.     end;
  31.   end; {if File_List.Count > 0}
  32. end;

Note that by the time you exit the procedure you have triplicated the list:
  • As File_List;
  • As FoundFiles.Lines; and
  • as File_Array
Are you sure you need all of them? What for?
« Last Edit: February 19, 2019, 07:31:57 pm by lucamar »
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

440bx

  • Hero Member
  • *****
  • Posts: 3944
Re: Reading Directories
« Reply #10 on: February 19, 2019, 07:42:10 pm »
<snip> and the search routine in Windows Explorer didn't really help that much.
 
Just in case you might not know about it and you might be interested, there is a free download "FileLocator" (lite version from mythicsoft) that is quite adept and full featured when it comes to finding files anywhere on your system.  To say it is vastly superior to what Windows offers is an understatement.

HTH.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Coldzer0

  • Jr. Member
  • **
  • Posts: 50
Re: Reading Directories
« Reply #11 on: February 19, 2019, 09:40:32 pm »

lainz

  • Hero Member
  • *****
  • Posts: 4460
    • https://lainz.github.io/
Re: Reading Directories
« Reply #12 on: February 20, 2019, 12:18:53 am »
Of course you can program your own file search, but you can try as well "voidtools everything", is the fastest one. Filter by cdr like *.cdr and it show the folder from where it comes as well.

J-G

  • Hero Member
  • *****
  • Posts: 953
Re: Reading Directories
« Reply #13 on: February 20, 2019, 04:41:02 am »
After the call to FindAllFiles you get the names loaded to File_List so there is no need later to use the list-file you saved. You should also add some guards against mishappenings
Agreed, errors will need to be accounted for but I initially wanted just a quick routine to find particular files.


Quote from: lucamar
Make it a method of the form.
Alternatively, pass a TStrings reference for FoundFiles:
  procedure GetDirectories(FoundFiles: TStrings);
and call it as GetDirectories(MyMemo.Lines).

Whatever you do, *don't_hardcode_a_form_instance* like Form1
Unfortunately, my feeble brain is unable to fathom how I might do that  :-[

I did simply add 'TForm1.' before GetDirectories but, as you are probably aware, it then refused to compile.

You are quite correct that I don't need three copies of the list. In fact I don't even need a file written to disk, I just want to display the list in the memo (for now at least), and the +1 was to circumvent a SIGVSEG error  -  I was multi-tasking (or probably not thinking)   and missed the N-1 in the loop -
in fact I originally tried 'for i := 1 to N'  :-[ :-[

Anyway, the new code is:
Code: Pascal  [Select][+][-]
  1. procedure FindFiles;
  2. var
  3.   n,i : word;
  4. begin
  5.   Target := '*'+Form1.Search.Caption+'*.CDR';
  6.  
  7.   File_List := FindAllFiles(DriveLetter+':/',Target,True,faAnyFile);
  8.  
  9.   N := File_List.Count;
  10.  
  11.   Form1.Found.Caption:=IntToStr(N);
  12.  
  13.   Form1.FoundFiles.Lines.AddStrings(File_List);
  14. end;
  15.  

You'll see that I've added a little more flexibility by creating a TEdit into which I can be specific about some part of the filename that I search for. So this has got me to a working solution for the original problem. I may go on to add error-trapping and even make the 'type' (extension) selectable from a drop-down list  - - - -  but not tonight (this morning!!)

Thanks for your input Lucamar.
« Last Edit: February 20, 2019, 04:49:50 am by J-G »
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: Reading Directories
« Reply #14 on: February 20, 2019, 01:19:09 pm »
I did simply add 'TForm1.' before GetDirectories but, as you are probably aware, it then refused to compile.

You've to add your method "FindFiles" to the TForm1 declaration too, so

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. interface
  4.  
  5. type
  6.   TForm1 = class(TForm)
  7.     {...all the controls, etc...}
  8.   private
  9.     procedure FindFiles;
  10.   end;
  11.  
  12. {... other interface declarations ...}
  13.  
  14. implementation
  15.  
  16. {$R *.lfm}
  17.  
  18. procedure TForm1.FindFiles;
  19. var
  20.   n,i : word;
  21. begin
  22.   Target := '*' + Search.Caption + '*.CDR';
  23.   File_List := FindAllFiles(DriveLetter+':/',Target,True,faAnyFile);
  24.   N := File_List.Count;
  25.   Found.Caption:=IntToStr(N);
  26.   FoundFiles.Lines.AddStrings(File_List);
  27. end;
  28.  
  29. {... The rest of your code ...}
  30.  
  31. end.

Do note that FoundFiles.Lines.AddStrings(File_List) will add the list to the current contents of the memo. If you want to replace them use:
  FoundFiles.Lines.Assign(File_List);
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

 

TinyPortal © 2005-2018