Announcement

Collapse
No announcement yet.

Folder Sorting Possible?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Folder Sorting Possible?

    I could read a list of files in a folder and then sort the list by date.

    But I wondered, is there any way to tell Windows to sort the folder content by date so that Dir$(*.*) gets the files in date order to start with?

    I tried it manually, but even with File Explorer showing the files in date order, Dir$() does not return them in the same order as they are displayed in File Explorer.

    #2
    Nope, Dir$() will be using FindFirstFile()/FindNextFile under the hood.

    "The order in which the search returns the files, such as alphabetical order, is not guaranteed, and is dependent on the file system. If the data must be sorted, the application must do the ordering after obtaining all the results."

    You could do a CLI Dir /OD /B or similar to get a list in date order.

    PipeToString may be useful in that case: https://forum.powerbasic.com/forum/u...ing#post824717


    =========================
    https://camcopng.com
    =========================

    Comment


      #3
      Howdy, Stuart!

      Thanks for the comments!

      But, the files are stored somewhere in the bowels of the Windows File System. Surely, someone has played with moving file information around in that system? Or not! I didn't find any good reference to the possibility in my internet search.

      It's only a handful of lines to read files into an array and then sort by date. I just wondered it there was another way.

      Your CLI suggestion works too.

      Comment


        #4
        Which date? File Explorer defaults to last modified date. If DIR$ is different, and not alphabetical, then it is by creation date.

        With View | Current View | Sort By - File Explorer can be set to creation date.

        cheers,
        Dale

        Comment


          #5
          Having seen Stuart's post I read
          FindFirstFileA function (fileapi.h) - Win32 apps | Microsoft Learn

          Creation date would be closer, but looks possible that a later created directory could be in lower sector number, making it found first.

          Not what you wanted, but add code to sort results of DIR$.

          Cheers,
          Dale

          Comment


            #6
            Originally posted by Gary Beene View Post
            But, the files are stored somewhere in the bowels of the Windows File System. Surely, someone has played with moving file information around in that system? Or not! I didn't find any good reference to the possibility in my internet search.
            Which "file system" ?
            How file information is stored depends on the Filesystem. NTFS, Fat32, ExFAT all use different methods.(not to mention SMB, NFS etc )

            Anyone who "plays around with moving file information around in the system" is asking for an unreadable disk.






            =========================
            https://camcopng.com
            =========================

            Comment


              #7
              Originally posted by Dale Yarker View Post
              Having seen Stuart's post I read
              FindFirstFileA function (fileapi.h) - Win32 apps | Microsoft Learn

              Creation date would be closer, but looks possible that a later created directory could be in lower sector number, making it found first.

              Not what you wanted, but add code to sort results of DIR$.

              Cheers,
              FindNext is more relevant to the question: https://learn.microsoft.com/en-us/wi...-findnextfilea
              That's where the quote in Post#2 comes from.

              And it goes on to say:
              "The order in which this function returns the file names is dependent on the file system type. With the NTFS file system and CDFS file systems, the names are usually returned in alphabetical order. With FAT file systems, the names are usually returned in the order the files were written to the disk, which may or may not be in alphabetical order. However, as stated previously, these behaviors are not guaranteed."
              =========================
              https://camcopng.com
              =========================

              Comment


                #8
                With the NTFS file system and CDFS file systems, the names are usually returned in alphabetical order.
                So, assume nothing about order of DIR$. Sort your self (code), by a member of DIRDATA.

                Cheers,
                Dale

                Comment


                  #9
                  Originally posted by Dale Yarker View Post
                  So, assume nothing about order of DIR$. Sort your self (code), by a member of DIRDATA.
                  Cheers,
                  Yep, as MS says, if you need a sorted directory list, the only reliable way is to get the full list and then sort it yourself.

                  "the application must do the ordering after obtaining all the results​"

                  I'd probably iterate through DIR$() populating an array of DIRDATAs and then do a custom sort on whatever member(s) I needed at the time.
                  =========================
                  https://camcopng.com
                  =========================

                  Comment


                    #10
                    Yes, I'm back to post 1, where I acknowledged I could do it myself.

                    Side observation ... I was playing with CMD Dir /od and noticed that the output did not match the order of Date sort in File Explorer. File Explorer, in my folder of interest, says Date Modified. I couldn't find a clear answer on which date type CMD Dir /od uses (Creation/Modified/Accessed).

                    Comment


                      #11


                      /T:W Last Written (default)

                      <b>George W. Bleck</b>
                      <img src='http://www.blecktech.com/myemail.gif'>

                      Comment


                        #12
                        Well, that ended up taking an hour or two to get it all working
                        '
                        Code:
                        'SortDirList.inc
                        'Get a sorted directory list as a single dimensioned, One based array
                        'Pass an empty WSTRINGZ * 350 array to GetDir
                        'It needs to be a WSTRINGZ to use Array Custom sort
                        'It needs to be that large (roughly) to allow for maximum filename length and file size string length
                        'Each directory entry is returned as a pipe delimited string "|"  ("|" is not a valid filename character!)
                        'fields can be extracted with sField = PARSE$(sRecord,"|",x) where x is 1 to 6 (see below)
                        'The fields in each record are text strings:
                        '1 = Filename
                        '2 = File size (use Val to convert back to a quad if required as a number)
                        '3 = Created Time - as an ISO string "YYYY-MM-DDTHH:NN:SS" for ease of sorting
                        '4 = Last Write time - ditto
                        '5 = Last Accessed Time - ditto
                        '6 = Hex representation of attributes below &H10000. see https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants  (Note: currently not sortable )
                        
                        FUNCTION GetDir(wsarr() AS WSTRINGZ *350, dirSpec AS WSTRING, OPT BYVAL Attr AS LONG) AS LONG
                            LOCAL ws,ws AS WSTRING
                            LOCAL lEntries AS LONG
                            LOCAL ddat AS DIRDATA
                            LOCAL sb AS ISTRINGBUILDERW
                            LOCAL pt AS IPOWERTIME
                            pt = CLASS "PowerTime"
                            sb = CLASS "StringbuilderW"
                            ws = DIR$(DirSpec, attr TO ddat)
                            WHILE ws > ""
                                INCR lEntries
                                wsTemp= ddat.filename
                                sb.add wsTemp
                                sb.add "|"
                                sb.add DEC$(4294967296 * ddat.filesizehigh + ddat.filesizelow)
                                sb.add "|"
                                sb.add ISODateFromFT(ddat.creationtime)
                                sb.add "|"
                                sb.add ISODateFromFT(ddat.lastwritetime)
                                sb.add "|"
                                sb.add ISODateFromFT(ddat.lastaccesstime)
                                sb.add "|"
                                sb.add HEX$(LO(WORD,ddat.fileattributes),4)
                                sb.add $CRLF
                                ws = DIR$(NEXT TO ddat)
                            LOOP
                            ws = sb.string
                            ws = RTRIM$(ws,$CRLF)
                            REDIM wsArr(1 TO lENtries)
                            PARSE ws,wsArr(),$CRLF
                            FUNCTION = lENtries
                        END FUNCTION
                        
                        FUNCTION ISODateFromFT (ft AS QUAD) AS WSTRING
                            LOCAL pt AS IPOWERTIME
                            pt = CLASS "POWERTIME"
                            pt.filetime = ft
                            FUNCTION = USING$("*0*0*0*0_T*0*0*0" ,pt.year,pt.month,pt.day,pt.hour,pt.minute,pt.second)
                        END FUNCTION
                        
                        FUNCTION SortDir(wsarr() AS WSTRINGZ * 350,Fld AS LONG,Direction AS STRING) AS LONG
                            LOCAL d AS STRING
                            d = UCASE$(LEFT$(Direction,1))
                            IF d = "A" THEN
                                SELECT CASE fld
                                    CASE 1: ARRAY SORT wsArr() , CALL SortNameA
                                    CASE 2:  ARRAY SORT wsArr() ,CALL SortSizeA
                                     CASE 3: ARRAY SORT wsArr() ,CALL SortCreateA
                                     CASE 4: ARRAY SORT wsArr() ,CALL SortwriteA
                                     CASE 5: ARRAY SORT wsArr() ,CALL SortaccessA
                                END SELECT
                            ELSE
                               SELECT CASE fld
                                    CASE 1: ARRAY SORT wsArr() ,CALL SortNameD
                                    CASE 2:  ARRAY SORT wsArr() ,CALL SortSizeD
                                    CASE 3: ARRAY SORT wsArr() ,CALL SortCreateD
                                    CASE 4: ARRAY SORT wsArr() ,CALL SortwriteD
                                    CASE 5: ARRAY SORT wsArr() ,CALL SortaccessD
                                END SELECT
                            END IF
                        END FUNCTION
                        
                        FUNCTION sortNameA(p1 AS WSTRINGZ *350, p2 AS WSTRINGZ *350) AS LONG
                            IF PARSE$(p1,"|",1) < PARSE$(p2,"|",1) THEN FUNCTION = -1:EXIT FUNCTION
                            IF PARSE$(p1,"|",1) > PARSE$(p2,"|",1) THEN FUNCTION = 1:EXIT FUNCTION
                        END FUNCTION
                        
                        FUNCTION sortNameD(p1 AS WSTRINGZ * 350, p2 AS WSTRINGZ *350) AS LONG
                            IF PARSE$(p1,"|",1) < PARSE$(p2,"|",1) THEN FUNCTION =  1:EXIT FUNCTION
                            IF PARSE$(p1,"|",1) > PARSE$(p2,"|",1) THEN FUNCTION = -1:EXIT FUNCTION
                        END FUNCTION
                        FUNCTION sortSizeA(p1 AS WSTRINGZ *350, p2 AS WSTRINGZ *350) AS LONG
                            IF VAL(PARSE$(p1,"|",2)) < VAL(PARSE$(p2,"|",2)) THEN FUNCTION = -1:EXIT FUNCTION
                            IF VAL(PARSE$(p1,"|",2)) > VAL(PARSE$(p2,"|",2)) THEN FUNCTION = 1:EXIT FUNCTION
                        END FUNCTION
                        
                        FUNCTION sortSizeD(p1 AS WSTRINGZ * 350, p2 AS WSTRINGZ *350) AS LONG
                            IF VAL(PARSE$(p1,"|",2)) < VAL(PARSE$(p2,"|",2)) THEN FUNCTION =  1:EXIT FUNCTION
                            IF VAL(PARSE$(p1,"|",2)) > VAL(PARSE$(p2,"|",2)) THEN FUNCTION = -1:EXIT FUNCTION
                        END FUNCTION
                        
                        FUNCTION sortCreateA(p1 AS WSTRINGZ *350, p2 AS WSTRINGZ *350) AS LONG
                            IF PARSE$(p1,"|",3) < PARSE$(p2,"|",3) THEN FUNCTION = -1:EXIT FUNCTION
                            IF PARSE$(p1,"|",3) > PARSE$(p2,"|",3) THEN FUNCTION = 1:EXIT FUNCTION
                        END FUNCTION
                        
                        FUNCTION sortCreateD(p1 AS WSTRINGZ * 350, p2 AS WSTRINGZ *350) AS LONG
                            IF PARSE$(p1,"|",3) < PARSE$(p2,"|",3) THEN FUNCTION =  1:EXIT FUNCTION
                            IF PARSE$(p1,"|",3) > PARSE$(p2,"|",3) THEN FUNCTION = -1:EXIT FUNCTION
                        END FUNCTION
                        
                        FUNCTION sortWriteA(p1 AS WSTRINGZ *350, p2 AS WSTRINGZ *350) AS LONG
                            IF PARSE$(p1,"|",4) < PARSE$(p2,"|",4) THEN FUNCTION = -1:EXIT FUNCTION
                            IF PARSE$(p1,"|",4) > PARSE$(p2,"|",4) THEN FUNCTION = 1:EXIT FUNCTION
                        END FUNCTION
                        
                        FUNCTION sortWriteD(p1 AS WSTRINGZ * 350, p2 AS WSTRINGZ *350) AS LONG
                            IF PARSE$(p1,"|",4) < PARSE$(p2,"|",4) THEN FUNCTION =  1:EXIT FUNCTION
                            IF PARSE$(p1,"|",4) > PARSE$(p2,"|",4) THEN FUNCTION = -1:EXIT FUNCTION
                        END FUNCTION
                        
                        FUNCTION sortAccessA(p1 AS WSTRINGZ *350, p2 AS WSTRINGZ *350) AS LONG
                            IF PARSE$(p1,"|",5) < PARSE$(p2,"|",5) THEN FUNCTION = -1:EXIT FUNCTION
                            IF PARSE$(p1,"|",5) > PARSE$(p2,"|",5) THEN FUNCTION = 1:EXIT FUNCTION
                        END FUNCTION
                        
                        FUNCTION sortAccessD(p1 AS WSTRINGZ * 350, p2 AS WSTRINGZ *350) AS LONG
                            IF PARSE$(p1,"|",5) < PARSE$(p2,"|",5) THEN FUNCTION =  1:EXIT FUNCTION
                            IF PARSE$(p1,"|",5) > PARSE$(p2,"|",5) THEN FUNCTION = -1:EXIT FUNCTION
                        END FUNCTION
                        '


                        and a simple demo:
                        '
                        Code:
                        #COMPILE EXE "SortDirList"
                        #DIM ALL
                        #DEBUG ERROR ON
                        #DEBUG DISPLAY ON
                        #INCLUDE "SortDirList.inc"
                        
                        FUNCTION PBMAIN() AS LONG
                            LOCAL lDebug AS LONG: TXT.WINDOW EXE.FULL$, 10,10,45,90 TO lDebug
                            LOCAL wsArrDirEntries() AS WSTRINGZ * 350
                            LOCAL i,j, lENtries AS LONG
                        
                            lEntries = GetDir(wsArrDirEntries(),"*.*",0) ')
                            j = MIN(12,lEntries) ' keep it to a single txt window
                        
                            TXT.PRINT "Directory entries: ",lEntries
                            IF lEntries  THEN
                                TXT.PRINT "Sorted ascending by name
                                SortDir(wsArrDirENtries(),1,"A") '1 = name, 2 = size, 3 = created, 4 = written, 5 = accessed
                                FOR i =  1 TO j
                                    TXT.PRINT FORMAT$(i); TAB(6) wsArrDirEntries(i)
                                NEXT
                                TXT.PRINT
                                TXT.PRINT "Sorted descending by Size"
                                SortDir(wsArrDirENtries(),2,"D") '1 = name, 2 = size, 3 = created, 4 = written, 5 = accessed
                                FOR i =  1 TO j
                                    TXT.PRINT FORMAT$(i); TAB(6) wsArrDirEntries(i)
                                NEXT
                           ELSE
                                TXT.PRINT "No matching entries"
                            END IF
                        
                        'Finalise
                            TXT.COLOR = %RGB_BLUE
                            TXT.PRINT
                            TXT.PRINT "  ....Press any key to exit": TXT.WAITKEY$: TXT.END
                        END FUNCTION
                        '


                        (Takes less than 0.1 seconds to get a 1000+ entry directory list and sort it twice.)
                        Last edited by Stuart McLachlan; 13 Sep 2023, 12:04 PM.
                        =========================
                        https://camcopng.com
                        =========================

                        Comment


                          #13
                          Just for the heck of it. Done with a collection. Maybe someone in the future needs a Collection demo.
                          Code:
                          'read all files in the current directory
                          'set the PowerKey to LastAccessTime in the order to be sorted YYYYMMDDHHMM
                          'reverse sort the collection to get most recent file
                          'display the sorted list
                          
                          'change *.* to what files to include
                          'change T.FileTime to what date to use for sorting
                          'change PowerKey to what to use for sorting
                          'change Sort(1) to Sort(0) for ascending sort
                          
                          'remember that PowerKey can never be the same. A good way to
                          'insure a unique PowerKey is to tag on the C.Count to the end.
                          
                          #COMPILER PBWIN
                          #COMPILE EXE
                          #DIM ALL
                          
                          %Unicode = 1
                          
                          #INCLUDE "Win32API.Inc"
                          
                          '********************************************************************************
                          FUNCTION PBMAIN() AS LONG
                          '********************************************************************************
                            LOCAL lDebug AS LONG: TXT.WINDOW EXE.FULL$, 10,10,50,75 TO lDebug 'Thanks Dave!
                          
                            LOCAL C AS IPOWERCOLLECTION: C = CLASS "PowerCollection"
                            LOCAL T AS IPOWERTIME: T = CLASS "PowerTime"
                            LOCAL PowerKey AS WSTRING, PowerItem AS VARIANT, PowerIndex AS LONG
                            LOCAL D AS DIRDATA
                            LOCAL FName AS WSTRING
                          
                            'read in the directory, set up PowerKey
                            FName = DIR$("*.*" TO D)
                            WHILE LEN(FName) > 0
                              T.FileTime = D.LastAccessTime
                              PowerKey = DEC$(T.Year,4) & _
                                         DEC$(T.Month,2) & _
                                         DEC$(T.Day,2) & _
                                         DEC$(T.Hour,2) & _
                                         DEC$(T.Minute,2) & " " & _
                                         DEC$(C.Count,6)
                              PowerItem = D AS STRING
                              C.Add(PowerKey, PowerItem)
                              IF OBJRESULT <> %S_OK THEN TXT.PRINT "Could not add " & PowerKey & ", Code: " & HEX$(OBJRESULT)
                              FName = DIR$(NEXT TO D)
                            WEND
                            DIR$ CLOSE
                          
                            'sort the directory if necessary
                            IF C.Count > 1 THEN C.Sort(1)
                          
                            'list the first 45 enteries
                            FOR PowerIndex = 1 TO MIN(C.Count, 45)
                              C.Entry(PowerIndex, PowerKey, PowerItem)
                              D = VARIANT$(BYTE, PowerItem)
                              TXT.PRINT PowerKey & " - " & D.FileName
                            NEXT PowerIndex
                          
                            TXT.PRINT
                            TXT.PRINT "Total Files = " & DEC$(C.Count)
                            C.Clear
                          
                            TXT.PRINT "Press any key to exit (just like the old days!)": TXT.WAITKEY$: TXT.END
                          END FUNCTION

                          Comment


                            #14
                            Well, that ended up taking an hour or two to get it all working
                            It would have been a lot easier, Stuart, had you remembered a FILETIME is actually a QUAD integer and you could sort by that; although here it might have been really expeditious to create your own DIRDATA UDT structure, but calling the desired FILETIME a QUAD and using a variable defined that way that as the "Dirdata" in a DIR$ function. .

                            Damn, I'm being practical again. My bad.
                            Michael Mattias
                            Tal Systems (retired)
                            Port Washington WI USA
                            [email protected]
                            http://www.talsystems.com

                            Comment


                              #15
                              You could change PowerKey to use .LastAccessTime in the form of a string as Michael suggests...
                              Code:
                                  PowerKey = DEC$(D.LastAccessTime) & " " & DEC$(C.Count, 6)

                              Comment


                                #16
                                Originally posted by Michael Mattias View Post
                                It would have been a lot easier, Stuart, had you remembered a FILETIME is actually a QUAD integer and you could sort by that; although here it might have been really expeditious to create your own DIRDATA UDT structure, but calling the desired FILETIME a QUAD and using a variable defined that way that as the "Dirdata" in a DIR$ function. .

                                Damn, I'm being practical again. My bad.
                                You think I wasn't aware of that initially?
                                The PB DIRDATA type actually defines it as a quad, so no need for a different UDT if that were the only issue.

                                Easier at first glance, but not so practical on reflection.

                                First pass - array of DIRDATAs (with QUAD filetimes)
                                Second pass UDT with FilesizeHigh and FilesizeLow converted to a single quad when read initially

                                I then realised that it was a "Bad Idea"™ to have to convert those filetimes to a meaningful format every where one was displayed.

                                Third pass converted Filetimes to ISO strings as they were read in so that they were still sortable, but also meaningful when displayed with no further data manipulation,
                                Then fourth pass went with pipe delimited strings instead of UDTs - can't remember exactly why now, but I definitely had a good reason when I did it
                                =========================
                                https://camcopng.com
                                =========================

                                Comment


                                  #17
                                  Originally posted by Frank Rogers View Post
                                  You could change PowerKey to use .LastAccessTime in the form of a string as Michael suggests...
                                  Code:
                                  PowerKey = DEC$(D.LastAccessTime) & " " & DEC$(C.Count, 6)
                                  OK for initial sorting, but not good for display of the date/time without having to process it every time it is used for a meaning output.
                                  =========================
                                  https://camcopng.com
                                  =========================

                                  Comment


                                    #18
                                    That is just for sorting. When displaying use the fields in the UDT

                                    Comment


                                      #19
                                      ????????????????????????????????????
                                      The times in type DIRDATA are already QUADs in PB Help.
                                      ???????????????????????????????
                                      Where did Michael suggest string.?
                                      He suggested QUAD vs FileTime.
                                      Dale

                                      Comment


                                        #20
                                        Yes, Michael suggest using a Quad. I converted the Quad to a WString because that is all that PowerCollections use.

                                        Comment

                                        Working...
                                        X
                                        😀
                                        🥰
                                        🤢
                                        😎
                                        😡
                                        👍
                                        👎