Author Topic: Reading Directories and Files without SHELLing out  (Read 5810 times)

0 Members and 1 Guest are viewing this topic.

This topic contains a post which is marked as Best Answer. Press here if you would like to see it.

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Reading Directories and Files without SHELLing out
« on: September 14, 2019, 03:35:15 am »
So I'm trying to add the ability to save (or load) game files from different directories for some odds and ends I am working on. Last time I did this I used DIRECTQBs routines. And I'm finding QB64 lacks any inbuilt way of loading Directories. and _DIR$ is sadly all but useless.(BTW why is that?)  I did however find a little gem by none other than Steve.

But I found a problem Steve,
it only recognizes  '.' and '..' as directories and treats everything else as files. it comes from a post on your forum which I found searching this forum for ways of obtaining a directory list.
Code: QB64: [Select]
  1.  FUNCTION load_dir& (s AS STRING)
  2.  FUNCTION has_next_entry& ()
  3.  SUB close_dir ()
  4.  SUB get_next_entry (s AS STRING, flags AS LONG, file_size AS LONG)
  5.  
  6. REDIM Dir(0) AS STRING, File(0) AS STRING
  7.  
  8. 'D$ = _CWD$
  9. D$ = "E:\SMB64\"
  10. GetFileList D$, Dir(), File()
  11.  
  12. PRINT "SUBDIRECTORIES"; UBOUND(dir)
  13. FOR i = 1 TO UBOUND(dir)
  14.  PRINT Dir(i),
  15.  
  16. PRINT "FILES"; UBOUND(file): PRINT: PRINT
  17. FOR i = 1 TO UBOUND(file)
  18.  PRINT File(i),
  19.  
  20. SUB GetFileList (SearchDirectory AS STRING, DirList() AS STRING, FileList() AS STRING)
  21.  CONST IS_DIR = 1
  22.  CONST IS_FILE = 2
  23.  DIM flags AS LONG, file_size AS LONG
  24.  
  25.  REDIM _PRESERVE DirList(100), FileList(100)
  26.  DirCount = 0: FileCount = 0
  27.  
  28.  IF load_dir(SearchDirectory) THEN
  29.   DO
  30.    length = has_next_entry
  31.    IF length > -1 THEN
  32.     nam$ = SPACE$(length)
  33.     get_next_entry nam$, flags, file_size
  34.     PRINT flags;
  35.     IF flags AND IS_DIR THEN
  36.      DirCount = DirCount + 1
  37.      IF DirCount > UBOUND(DirList) THEN REDIM _PRESERVE DirList(UBOUND(DirList) + 100)
  38.      DirList(DirCount) = nam$
  39.     ELSEIF flags AND IS_FILE THEN
  40.      FileCount = FileCount + 1
  41.      IF FileCount > UBOUND(filelist) THEN REDIM _PRESERVE FileList(UBOUND(filelist) + 100)
  42.      FileList(FileCount) = nam$
  43.     END IF
  44.    END IF
  45.   LOOP UNTIL length = -1
  46.   close_dir
  47.  REDIM _PRESERVE DirList(DirCount)
  48.  REDIM _PRESERVE FileList(FileCount)
  49.  

I added a couple of lines to see what was going on, printing the UBOUNDs for DIR() and FILE() as well as the FLAGS variable in the sub.

I've attached the output I get from my Super Mario directory, as you can see it only says there are 2 directories, yet anything listed in the file section without an extension is a directory too. This is true only cause I always make sure to have an extension on my files and never in my directories. Not everybody does so I couldn't use that as a sorting method.

I've also attached the header file needed to run the program in case anybody else wanted to take a look. as I can't remember the link to the topic its under on your forum right off this late. If nobody beats me to it I'll find it and post the link when I wake up. as for now I'm going to sleep.
Granted after becoming radioactive I only have a half-life!

Offline doppler

  • Forum Regular
  • Posts: 241
    • View Profile
Re: Reading Directories and Files without SHELLing out
« Reply #1 on: September 14, 2019, 09:06:24 am »
Usable now, but when fully fleshed out.  Will become another tool bag trick for me.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Reading Directories and Files without SHELLing out
« Reply #2 on: September 14, 2019, 09:40:23 am »
What is drive D for you?  I've never ran into any trouble with the routine sorting the files and directories for me, so I'm curious why it's failing  for you.  These are just simple little POSIX routines, so they're generally very reliable, and, so far, I don't think I've  ever gotten the results you're having.

Is D a USB drive?  An old drive, perhaps in a format it might not recognize?  (FAT16 or some such?)  Are you using an old OS which might not have the fully functional POSIX functions?  There's a lot of little things which it could be, but I don't have a clue as to why it's not sorting properly for you.  Test it out for me, if you can, on a few different drives/machines and let me know which ones it does and doesn't work for.  Once I know exactly what architecture it fails to sort on, then I might be able to figure out a way to patch the issue so we can work around it without any issues.

When it does fail to autosort, the easy solution is to just place them all into a single list and then use _FILEEXISTS and _DIREXISTS to sort them for file/directory usage.

EDIT:  In fact, since it's a simple enough change, I'll just add it directly into the routine itself as a secondary means of sorting/testing for us.  Try this and see if it sorts on your system for you:

Code: QB64: [Select]
  1.     FUNCTION load_dir& (s AS STRING)
  2.     FUNCTION has_next_entry& ()
  3.     SUB close_dir ()
  4.     SUB get_next_entry (s AS STRING, flags AS LONG, file_size AS LONG)
  5.  
  6. REDIM Dir(0) AS STRING, File(0) AS STRING
  7.  
  8. 'D$ = _CWD$
  9. D$ = _CWD$
  10. GetFileList D$, Dir(), File()
  11.  
  12. PRINT "SUBDIRECTORIES"; UBOUND(dir)
  13. FOR i = 1 TO UBOUND(dir)
  14.     PRINT Dir(i),
  15.  
  16. PRINT "FILES"; UBOUND(file): PRINT: PRINT
  17. FOR i = 1 TO UBOUND(file)
  18.     PRINT File(i),
  19.  
  20. SUB GetFileList (SearchDirectory AS STRING, DirList() AS STRING, FileList() AS STRING)
  21.     CONST IS_DIR = 1
  22.     CONST IS_FILE = 2
  23.     DIM flags AS LONG, file_size AS LONG
  24.  
  25.     REDIM _PRESERVE DirList(100), FileList(100)
  26.     DirCount = 0: FileCount = 0
  27.  
  28.     IF load_dir(SearchDirectory) THEN
  29.         DO
  30.             length = has_next_entry
  31.             IF length > -1 THEN
  32.                 nam$ = SPACE$(length)
  33.                 get_next_entry nam$, flags, file_size
  34.                 PRINT flags;
  35.                 IF flags AND IS_DIR OR _DIREXISTS(nam$) THEN
  36.                     DirCount = DirCount + 1
  37.                     IF DirCount > UBOUND(DirList) THEN REDIM _PRESERVE DirList(UBOUND(DirList) + 100)
  38.                     DirList(DirCount) = nam$
  39.                 ELSEIF flags AND IS_FILE OR _FILEEXISTS(nam$) THEN
  40.                     FileCount = FileCount + 1
  41.                     IF FileCount > UBOUND(filelist) THEN REDIM _PRESERVE FileList(UBOUND(filelist) + 100)
  42.                     FileList(FileCount) = nam$
  43.                 END IF
  44.             END IF
  45.         LOOP UNTIL length = -1
  46.         close_dir
  47.     ELSE
  48.     END IF
  49.     REDIM _PRESERVE DirList(DirCount)
  50.     REDIM _PRESERVE FileList(FileCount)
« Last Edit: September 14, 2019, 09:42:06 am by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Re: Reading Directories and Files without SHELLing out
« Reply #3 on: September 14, 2019, 07:20:49 pm »
If you mean drive E, its a partition on my laptop, on the main hard disk. Does the same thing on my desktop when trying to read the structure of a different specified location.

However, it does seem to work when using D$ = _CWD$ and reading the execution directory. Though that is not very helpful if a user wants to change directories when saving or loading.
Granted after becoming radioactive I only have a half-life!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Reading Directories and Files without SHELLing out
« Reply #4 on: September 14, 2019, 07:27:17 pm »
Does it work with the change above?  If not, I’ll need to do a lot of digging to see what the difference is. 
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Re: Reading Directories and Files without SHELLing out
« Reply #5 on: September 14, 2019, 09:00:05 pm »
Only if your reading the Execution directory, as soon as you pick a new directory to read it fails to separate the directories from the files again.

what does yours do if you have it try to read D$="C:\" instead of D$= _CWD$ ?
Granted after becoming radioactive I only have a half-life!

Marked as best answer by Cobalt on September 15, 2019, 07:33:18 am

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Reading Directories and Files without SHELLing out
« Reply #6 on: September 14, 2019, 09:16:43 pm »
Only if your reading the Execution directory, as soon as you pick a new directory to read it fails to separate the directories from the files again.

what does yours do if you have it try to read D$="C:\" instead of D$= _CWD$ ?

You're right.  The issue is relative paths for us.   _DIREXISTS("my folder\") isn't the same thing as _DIREXISTS("C:\my folder\").  We strip those paths out so all we get back is the file structure for the specified folder, and not the WHOLE path + file/directory listing each time.

Try this patch and see how it performs for you:
Code: QB64: [Select]
  1.     FUNCTION load_dir& (s AS STRING)
  2.     FUNCTION has_next_entry& ()
  3.     SUB close_dir ()
  4.     SUB get_next_entry (s AS STRING, flags AS LONG, file_size AS LONG)
  5.  
  6. REDIM Dir(0) AS STRING, File(0) AS STRING
  7.  
  8. 'D$ = _CWD$
  9. D$ = "C:\"
  10. GetFileList D$, Dir(), File()
  11.  
  12. PRINT "SUBDIRECTORIES"; UBOUND(dir)
  13. FOR i = 1 TO UBOUND(dir)
  14.     PRINT Dir(i),
  15.  
  16. PRINT "FILES"; UBOUND(file): PRINT: PRINT
  17. FOR i = 1 TO UBOUND(file)
  18.     PRINT File(i),
  19.  
  20. SUB GetFileList (SearchDirectory AS STRING, DirList() AS STRING, FileList() AS STRING)
  21.     CONST IS_DIR = 1
  22.     CONST IS_FILE = 2
  23.     DIM flags AS LONG, file_size AS LONG
  24.  
  25.     REDIM _PRESERVE DirList(100), FileList(100)
  26.     DirCount = 0: FileCount = 0
  27.  
  28.     IF load_dir(SearchDirectory) THEN
  29.         DO
  30.             length = has_next_entry
  31.             IF length > -1 THEN
  32.                 nam$ = SPACE$(length)
  33.                 get_next_entry nam$, flags, file_size
  34.                 IF (flags AND IS_DIR) OR _DIREXISTS(SearchDirectory + nam$) THEN
  35.                     DirCount = DirCount + 1
  36.                     IF DirCount > UBOUND(DirList) THEN REDIM _PRESERVE DirList(UBOUND(DirList) + 100)
  37.                     DirList(DirCount) = nam$
  38.                 ELSEIF (flags AND IS_FILE) OR _FILEEXISTS(SearchDirectory + nam$) THEN
  39.                     FileCount = FileCount + 1
  40.                     IF FileCount > UBOUND(filelist) THEN REDIM _PRESERVE FileList(UBOUND(filelist) + 100)
  41.                     FileList(FileCount) = nam$
  42.                 END IF
  43.             END IF
  44.         LOOP UNTIL length = -1
  45.         close_dir
  46.     ELSE
  47.     END IF
  48.     REDIM _PRESERVE DirList(DirCount)
  49.     REDIM _PRESERVE FileList(FileCount)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Re: Reading Directories and Files without SHELLing out
« Reply #7 on: September 14, 2019, 10:17:49 pm »
That seems to do the trick. Thanks Steve.

Odd that something that should be really simple takes so much effort anymore.
Granted after becoming radioactive I only have a half-life!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Reading Directories and Files without SHELLing out
« Reply #8 on: September 15, 2019, 02:03:23 am »
I am back to . and .. with all the sub-directories as files when I change D$ = "C:\" to my users folder "C:\users\marka"

Am I misunderstanding how to use this? Should SearchDirectory really mean search drive?

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Reading Directories and Files without SHELLing out
« Reply #9 on: September 15, 2019, 03:46:45 am »
Hi. I think, this very small SUB solved all...
PLEASE TEST IT, if it works for you.


Code: QB64: [Select]
  1.     FUNCTION load_dir& (s AS STRING)
  2.     FUNCTION has_next_entry& ()
  3.     SUB close_dir ()
  4.     SUB get_next_entry (s AS STRING, flags AS LONG, file_size AS LONG)
  5.  
  6. REDIM Dir(0) AS STRING, File(0) AS STRING
  7.  
  8. D$ = _CWD$
  9. 'D$ = "" 'INSERT PATH HERE
  10.  
  11.  
  12.  
  13. SetPath D$
  14. GetFileList D$, Dir(), File()
  15.  
  16. PRINT "SUBDIRECTORIES"; UBOUND(dir)
  17. FOR i = 1 TO UBOUND(dir)
  18.     PRINT Dir(i),
  19.  
  20. PRINT "FILES"; UBOUND(file): PRINT: PRINT
  21. FOR i = 1 TO UBOUND(file)
  22.     PRINT File(i),
  23.  
  24.  
  25. SUB SetPath (p$)
  26.     L$ = RIGHT$(p$, 1)
  27.     $IF WIN THEN
  28.         IF L$ <> "\" THEN p$ = p$ + "\"
  29.     $ELSEIF LIN THEN
  30.         if L$ <> "/" then p$ = p$ + "/"
  31.     $END IF
  32.  
  33.  
  34.  
  35. SUB GetFileList (SearchDirectory AS STRING, DirList() AS STRING, FileList() AS STRING)
  36.     CONST IS_DIR = 1
  37.     CONST IS_FILE = 2
  38.     DIM flags AS LONG, file_size AS LONG
  39.  
  40.     REDIM _PRESERVE DirList(100), FileList(100)
  41.     DirCount = 0: FileCount = 0
  42.  
  43.     IF load_dir(SearchDirectory) THEN
  44.         DO
  45.             length = has_next_entry
  46.             IF length > -1 THEN
  47.                 nam$ = SPACE$(length)
  48.                 get_next_entry nam$, flags, file_size
  49.                 IF (flags AND IS_DIR) OR _DIREXISTS(SearchDirectory + nam$) THEN
  50.                     DirCount = DirCount + 1
  51.                     IF DirCount > UBOUND(DirList) THEN REDIM _PRESERVE DirList(UBOUND(DirList) + 100)
  52.                     DirList(DirCount) = nam$
  53.                 ELSEIF (flags AND IS_FILE) OR _FILEEXISTS(SearchDirectory + nam$) THEN
  54.                     FileCount = FileCount + 1
  55.                     IF FileCount > UBOUND(filelist) THEN REDIM _PRESERVE FileList(UBOUND(filelist) + 100)
  56.                     FileList(FileCount) = nam$
  57.                 END IF
  58.             END IF
  59.         LOOP UNTIL length = -1
  60.         close_dir
  61.     ELSE
  62.     END IF
  63.     REDIM _PRESERVE DirList(DirCount)
  64.     REDIM _PRESERVE FileList(FileCount)
  65.  
  66.  

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Reading Directories and Files without SHELLing out
« Reply #10 on: September 15, 2019, 09:42:31 am »
Thanks Petr,

I guess I was using it incorrectly, I needed a slash at the end for the SearchDirectory variable.

Well this should work nicely in my Tiny Navigator which could then work in Linux?

For Windows, either slash direction works, you can even mix and match :)

Offline doppler

  • Forum Regular
  • Posts: 241
    • View Profile
Re: Reading Directories and Files without SHELLing out
« Reply #11 on: September 15, 2019, 09:54:52 am »
Thank-you, Thank-you, Thank-you

I have been using shell and capture output to mange files and directories for so long.

Using less shells and this code is so much more efficient.

Why anyone would want to use the "FILES" command is beyond me at this point now.
Why look at files listed, when you can't do a damn thing with them.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: Reading Directories and Files without SHELLing out
« Reply #12 on: September 15, 2019, 10:06:26 am »
Hi BPlus, yeah, direntry.h is compatible with Linux, so your file browser will also work in Linux. You just have to be careful of the characters size in the names of folders and files, that's what Linux is sensitive to.
But there is also an option in Linux such as DIR to a file in windows, so you do this in Linux -   with command "ls > file.txt".

Hi Doppler. FILES is a prehistory maintained for compatibility with older Stone Age ...  :)

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Re: Reading Directories and Files without SHELLing out
« Reply #13 on: September 15, 2019, 11:59:18 am »
Glad so many people are happy I brought this up, As I stated in my first post it is a little Gem of a routine.

Won't actually return file size, but you could always add a simple OPEN:LOF:CLOSE to get file sizes as it reads in the files(I mean put the code in the ELSEIF area). I do not need that functionality in what I'm working on, but something like Bplus is talking about you might what to show file size as well.

Wonder why they made the Linux OS so picky? Maybe its just me but I can't see the need for case sensitivity in file names. Be like having a variable named ABCD and having 15 variations on it simply by using upper and lower case, that would be horrible to keep track of I would think! The forward-backward slash thing I don't know, I tend to use either one at random which may be bad practice in reality, but I still feel its rather nit picky to be arbitrarily enforcing one or the other.
Granted after becoming radioactive I only have a half-life!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Reading Directories and Files without SHELLing out
« Reply #14 on: September 15, 2019, 12:41:30 pm »
Glad so many people are happy I brought this up, As I stated in my first post it is a little Gem of a routine.

Won't actually return file size, but you could always add a simple OPEN:LOF:CLOSE to get file sizes as it reads in the files(I mean put the code in the ELSEIF area). I do not need that functionality in what I'm working on, but something like Bplus is talking about you might what to show file size as well.

It *does* get the file_size for us; we just never return it anywhere to make use of it:
Code: [Select]
get_next_entry nam$, flags, file_size
If you need file size, change the routine just a bit and make your file listing an array to hold that information, like so:
Code: QB64: [Select]
  1.     FUNCTION load_dir& (s AS STRING)
  2.     FUNCTION has_next_entry& ()
  3.     SUB close_dir ()
  4.     SUB get_next_entry (s AS STRING, flags AS LONG, file_size AS LONG)
  5.  
  6. TYPE File_Type
  7.     Size AS _INTEGER64
  8.     Name AS STRING
  9.  
  10.  
  11. REDIM Dir(0) AS STRING, File(0) AS File_Type
  12.  
  13. D$ = _CWD$
  14. D$ = "C:\"
  15. GetFileList D$, Dir(), File()
  16.  
  17. PRINT "SUBDIRECTORIES"; UBOUND(dir)
  18. FOR i = 1 TO UBOUND(dir)
  19.     PRINT Dir(i)
  20.  
  21. PRINT "FILES"; UBOUND(file): PRINT: PRINT
  22. FOR i = 1 TO UBOUND(file)
  23.     PRINT File(i).Name, File(i).Size
  24.  
  25. SUB GetFileList (SearchDirectory AS STRING, DirList() AS STRING, FileList() AS File_Type)
  26.     CurrDir$ = _CWD$
  27.     CHDIR SearchDirectory
  28.     CONST IS_DIR = 1
  29.     CONST IS_FILE = 2
  30.     DIM flags AS LONG, file_size AS LONG
  31.  
  32.     REDIM _PRESERVE DirList(100), FileList(100) AS File_Type
  33.     DirCount = 0: FileCount = 0
  34.  
  35.     IF load_dir(_CWD$) THEN
  36.         DO
  37.             length = has_next_entry
  38.             IF length > -1 THEN
  39.                 nam$ = SPACE$(length)
  40.                 get_next_entry nam$, flags, file_size
  41.                 IF flags AND IS_DIR THEN
  42.                     DirCount = DirCount + 1
  43.                     IF DirCount > UBOUND(DirList) THEN REDIM _PRESERVE DirList(UBOUND(DirList) + 100)
  44.                     DirList(DirCount) = nam$
  45.                 ELSEIF flags AND IS_FILE THEN
  46.                     FileCount = FileCount + 1
  47.                     IF FileCount > UBOUND(filelist) THEN REDIM _PRESERVE FileList(UBOUND(filelist) + 100) AS File_Type
  48.                     FileList(FileCount).Name = nam$
  49.                     FileList(FileCount).Size = file_size
  50.                 END IF
  51.             END IF
  52.         LOOP UNTIL length = -1
  53.         close_dir
  54.     ELSE
  55.     END IF
  56.     REDIM _PRESERVE DirList(DirCount)
  57.     REDIM _PRESERVE FileList(FileCount) AS File_Type
  58.     CHDIR CurrDir$
  59.  

(And, since this also temporarily changes the path for us, it corrects the issue you were experiencing before, where relative path vs absolute path caused issues.  No worries though; it changes it back once it's finished, so your program won't have any pathing problems when you plug it in.)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!