Announcement

Collapse
No announcement yet.

Next File Name

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

  • Next File Name

    When I create a new file, I might use something like this, where the LastFileNumber is a static variable whose value is incremented before using it to assign a new file name.

    Code:
    NewName$ = "New " + Format$(LastFileNumber+1,"###")
    But, if the user can delete a file number whose name incorporates a value less than the static variable, then a different approach is needed where all existing file names in the folder are looked at to find the first available number, like this, where the existing file names are found in Files().


    ... removed previously posted code ... see post #3 below.

  • #2
    Not sure your use case but I tend to use GUIDTXT$( GUID$ ) & ".tmp" for "temp-ish" file names. Can always sort by timestamp to get the true order.

    Alternately can use an ISO8601 formatted time (with or without milliseconds) for a file name. No need to keep track of previous file number auto sorts in order of creation as the file name is in string-sortable format.

    Code:
    FUNCTION fn_SystemTimeToISO8601( BYREF v_udtST AS SYSTEMTIME ) AS STRING
        FUNCTION = _
                    FORMAT$( v_udtST.wYear, "0000" ) & _
                    FORMAT$( v_udtST.wMonth, "00" ) & _
                    FORMAT$( v_udtST.wDay, "00" ) & _
                    "T" & _ _
                    FORMAT$( v_udtST.wHour, "00" ) & _
                    FORMAT$( v_udtST.wMinute, "00" ) & _
                    FORMAT$( v_udtST.wSecond, "00" ) & _
                    "Z"
    END FUNCTION
    
    '----------------------------------------------------------------------------(')
    
    FUNCTION fn_GetCurrentTimeISO8601UTC( ) AS STRING
        LOCAL v_udtST AS SYSTEMTIME
        LOCAL v_strTimpstamp AS STRING
        GETSYSTEMTIME v_udtST
        FUNCTION = fn_SystemTimeToISO8601( v_udtST )
    END FUNCTION
    <b>George W. Bleck</b>
    <img src='http://www.blecktech.com/myemail.gif'>

    Comment


    • #3
      Howdy, George!

      Yep, that would work.

      I was wanting to keep the file name simple, as "New 1", "New 2", etc. GUID and TimeStamps are ok, just more characters than I want.

      Something along the lines of this was what I had in mind ...

      Code:
      Function NextFileNumber() As Long
         Local i As Long
         Do
            Incr i
         While Len(Dir$("New " + Format$(i,"###") + *.txt"))
         Function = i
      End Function
      I had posted another version but quickly removed it because it was limited to only file numbers 1-9. This one above has no limit. It works for *.txt files but you can edit it to cover whatever file types are of interest.

      Comment


      • #4
        Can also increase your pool by switching from base 10 to another base. Base 10 only gives you 0000-9999 But something like Base16 (hex) gives you 0000-FFFF (65536 file names). Obviously you can easily craft other bases as well like base 26 (letters of a single case), base 36 (letters of a single case and numbers), base 62 (letters of both cases and numbers), etc.
        <b>George W. Bleck</b>
        <img src='http://www.blecktech.com/myemail.gif'>

        Comment


        • #5
          Howdy, George!

          That's a good thought for keeping the character count down! I chuckled when I saw your post, thinking about what my user would exclaim if their new file name was something like "FFCA.txt".

          I might have to include a "bad word" filter with that approach, although I'm not sure what unacceptable words you can make with those limited characters. I'm sure someone can imagine something that might be offensive!

          Comment


          • #6
            I have used the following which always gives a higher number, and can be prepended with an identifying term.
            Code:
            date=RIGHT$(DATE$,4)+"-"+LEFT$(DATE$,5)  'changes MM-DD-YYYY to YYYY-MM-DD
            datel=REMOVE$(date,"-")                  'changes YYYY-MM-DD to YYYYMMDD
            dattim= datel+REMOVE$(TIME$,":")         'creates a YYYYMMDDHHMMSS string
            Rod
            In some future era, dark matter and dark energy will only be found in Astronomy's Dark Ages.

            Comment


            • #7
              Originally posted by Gary Beene View Post
              Howdy, George!

              Yep, that would work.

              I was wanting to keep the file name simple, as "New 1", "New 2", etc. GUID and TimeStamps are ok, just more characters than I want.

              Something along the lines of this was what I had in mind ...

              Code:
              Function NextFileNumber() As Long
              Local i As Long
              Do
              Incr i
              While Len(Dir$("New " + Format$(i,"###") + *.txt"))
              Function = i
              End Function
              I had posted another version but quickly removed it because it was limited to only file numbers 1-9. This one above has no limit. It works for *.txt files but you can edit it to cover whatever file types are of interest.
              So they don't need to named sequentially in order of creation?
              i.e. if you have File001.txt, File002.txt, File004.txt, you want the next one to be File003.txt ?

              (BTW, spaces in file names are a real PITA that can bite you in the future)

              Comment


              • #8
                Another ISO function for current local date/time
                '
                Code:
                FUNCTION ISONow() AS STRING
                    FUNCTION = REMOVE$(RIGHT$(DATE$,4) & LEFT$(DATE$,5) & "T" & TIME$,ANY "-:")
                END FUNCTION
                '

                Comment


                • #9
                  Gary,
                  This does not help with numbering, but prevents users from deleting your files.

                  Files or folders permissions can be changed using cacls or icacls so users can't delete folders or files.
                  MakeSureDirectoryPathExists could also be used to easily create a long or nested subfolder so threw it into the mix.
                  Be sure to UnlockFolder if you want others to delete your files or folders.
                  Code:
                  DECLARE FUNCTION MakeSureDirectoryPathExists LIB "IMAGEHLP.DLL" ALIAS "MakeSureDirectoryPathExists" (DirPath AS ASCIIZ) AS LONG
                  
                  FUNCTION PBMAIN AS LONG
                   LOCAL sFolder AS STRING
                   sFolder = "c:\sql\bin\1"
                   MakeSure sFolder
                   LockFolder sFolder 
                   UnlockFolder sFolder
                  END FUNCTION
                  
                  FUNCTION MakeSure(BYVAL sFolder AS STRING) AS LONG
                   LOCAL result AS LONG
                   IF RIGHT$(sFolder,1) <> "\" THEN sFolder+="\"
                   FUNCTION = MakeSureDirectoryPathExists(BYCOPY sFolder)
                  END FUNCTION
                  
                  SUB UnlockFolder(BYVAL sFolder AS STRING)
                   IF RIGHT$(sFolder,1) = "\" THEN sFolder = LEFT$(sFolder,-1)
                   SHELL ENVIRON$("COMSPEC") + " /C echo y| cacls.exe " + sFolder + " /P everyone:f",0
                  END SUB
                  
                  SUB LockFolder(BYVAL sFolder AS STRING)
                   IF RIGHT$(sFolder,1) = "\" THEN sFolder = LEFT$(sFolder,-1)
                   SHELL ENVIRON$("COMSPEC") + " /C echo y| cacls.exe " + sFolder + " /P everyone:n",0
                  END SUB

                  Comment


                  • #10
                    File cannot be created if folder is locked, ERROR 70.
                    ISFILE does not correctly work if folder is locked so used GETATTR.

                    I thought this was too easy.
                    Need to look again at cacls.exe and icacls.exe to use correct permissons instead of this:
                    /P everyone:f",0 'unlock
                    /P everyone:n",0 'lock


                    Code:
                    DECLARE FUNCTION MakeSureDirectoryPathExists LIB "IMAGEHLP.DLL" ALIAS "MakeSureDirectoryPathExists" (DirPath AS ASCIIZ) AS LONG
                    
                    FUNCTION PBMAIN AS LONG
                     LOCAL sFile   AS STRING
                     LOCAL sFolder AS STRING
                     LOCAL sData   AS STRING
                     LOCAL FileExists AS LONG
                     LOCAL result  AS LONG
                     sFile   = "test.txt"
                     sFolder = "c:\sql\bin\1\"
                     MakeSure  sFolder
                     LockFolder sFolder
                     'IF ISFILE(sfolder + sFile) THEN  'fails if folder locked
                     IF GETATTR(sFolder + sFile) THEN  'this works
                      ? sFolder + sFile + " exists"
                      OPEN sFolder + sFile FOR BINARY AS #1
                      IF ERR THEN ? ERROR$,,"Open error":EXIT FUNCTION
                      GET$ #1,LOF(1),sData
                      IF ERR THEN ? ERROR$,,"GET$ error":CLOSE #1:EXIT FUNCTION
                      ? sData,%MB_SYSTEMMODAL,"LOF" + STR$(LOF(1))
                     ELSE
                      ? "File did not exist " + sFolder + sFile
                      ERRCLEAR
                      OPEN sFolder + sFile FOR OUTPUT AS #1
                      IF ERR THEN ? ERROR$,,"OPEN error"+STR$(ERRCLEAR)
                      PRINT #1, TIME$
                      IF ERR THEN ? ERROR$,,"PRINT #1 error" ELSE ? "PRINT #1 success"
                     END IF
                    
                     CLOSE #1
                     UnlockFolder sFolder
                    END FUNCTION
                    
                    FUNCTION MakeSure(BYVAL sFolder AS STRING) AS LONG
                     LOCAL result AS LONG
                     IF RIGHT$(sFolder,1) <> "\" THEN sFolder+="\"
                     FUNCTION = MakeSureDirectoryPathExists(BYCOPY sFolder)
                    END FUNCTION
                    
                    SUB UnlockFolder(BYVAL sFolder AS STRING)
                     IF RIGHT$(sFolder,1) = "\" THEN sFolder = LEFT$(sFolder,-1)
                     SHELL ENVIRON$("COMSPEC") + " /C echo y| cacls.exe " + sFolder + " /P everyone:f",0
                    END SUB
                    
                    SUB LockFolder(BYVAL sFolder AS STRING)
                     IF RIGHT$(sFolder,1) = "\" THEN sFolder = LEFT$(sFolder,-1)
                     SHELL ENVIRON$("COMSPEC") + " /C echo y| cacls.exe " + sFolder + " /P everyone:n",0
                    END SUB

                    Comment


                    • #11
                      Have you considered GetTempFileName API?

                      The GetTempFileName function creates a name for a temporary file. The filename is the concatenation of specified path and prefix strings, a hexadecimal string formed from a specified integer, and the .TMP extension.

                      The specified integer can be nonzero, in which case, the function creates the filename but does not create the file. If you specify zero for the integer, the function creates a unique filename and creates the file in the specified directory.

                      UINT GetTempFileName(
                      LPCTSTR lpPathName, // address of directory name for temporary file
                      LPCTSTR lpPrefixString, // address of filename prefix
                      UINT uUnique, // number used to create temporary filename
                      LPTSTR lpTempFileName // address of buffer that receives the new filename
                      );

                      Comment


                      • #12
                        for example...
                        Code:
                        #COMPILER PBWIN
                        #COMPILE EXE
                        #DIM ALL
                        
                        #INCLUDE "Win32API.Inc"
                        
                        '********************************************************************************
                        FUNCTION PBMAIN() AS LONG
                        '********************************************************************************
                          LOCAL lDebug AS LONG: TXT.WINDOW "Debug", 10,10,50,75 TO lDebug 'Thanks Dave!
                        
                          LOCAL lResult AS DWORD
                          LOCAL TempPath AS ASCIIZ * %MAX_PATH
                          LOCAL TempFileName AS ASCIIZ * %MAX_PATH
                        
                          lResult = GetTempPath(SIZEOF(TempPath), TempPath)
                          TXT.PRINT "       Length = " & DEC$(lResult)
                        
                          lResult = GetTempFileName(TempPath, "Tmp", 0, TempFileName)
                        
                          TXT.PRINT "Unique Result = " & HEX$(lResult)
                          TXT.PRINT "     TempPath = " & TempPath
                          TXT.PRINT " TempFileName = " & TempFileName
                        
                          lResult = DeleteFile(TempFileName)
                          TXT.PRINT "       Delete = " & CHOOSE$(lResult-1, "Fail" ELSE "Success")
                          TXT.PRINT
                        
                          TXT.PRINT "Press any key to exit (just like the old days!)": TXT.WAITKEY$: TXT.END
                        END FUNCTION
                        Last edited by Frank Rogers; 9 Oct 2021, 09:50 AM. Reason: Added DeleteFile to clean up.

                        Comment


                        • #13
                          Do you need to keep your data in a separate physical file? Maybe you can store ALL data in the same physical file. Its "unique id" would be the record number. And the user cannot delete a single record. (you'd need some kind of housekeeping function to remove unused records but there's no such thing as a free lunch).

                          It's a thought.

                          Application not described.
                          Michael Mattias
                          Tal Systems (retired)
                          Port Washington WI USA
                          [email protected]
                          http://www.talsystems.com

                          Comment


                          • #14
                            Gary -- if the objective is to find the next file sequence even after human involvement, I would probably try this:
                            ** untested code follows **

                            Code:
                            ' Change mask to expected max number of digits
                            $NumMask = "0000"
                            
                            ' create file
                            NewName$ = "New " + Format$(LastFileNumber+1,$Nummask)
                            Code:
                            ' Presumes all files begin with the same text
                            ' Returns the last file number used
                            
                            FUNCTION LastFileNumber() AS LONG
                              LOCAL i, j AS LONG
                              LOCAL tFile AS STRING
                              i = -1
                              tFile = DIR$("New*.*")
                              WHILE LEN(tFile) > 0
                                j = VAL(LEFT$(tFile, LEN($NumMask))
                                IF j > i THEN i = j
                                tFile = DIR$()
                              WEND
                            
                              FUNCTION = i
                            END FUNCTION
                            Using "000" as the format string will produce a file "New001", etc
                            Edited: debug. I said it was untested.
                            Real programmers use a magnetized needle and a steady hand

                            Comment


                            • #15
                              If the challenge is to get the next serial number you could store "last number" in a file or database table. When needed, you read and use this number (maybe checking for unique file name), then add one and save.

                              (If you already use an INI file, you could keep it right in there.This I know I have done several times).


                              Michael Mattias
                              Tal Systems (retired)
                              Port Washington WI USA
                              [email protected]
                              http://www.talsystems.com

                              Comment


                              • #16
                                Or rather than an INI, in the registry (user or machine based on app personalization / storage location sharing capabilities)
                                <b>George W. Bleck</b>
                                <img src='http://www.blecktech.com/myemail.gif'>

                                Comment


                                • #17
                                  Catching up ... thanks for all the suggestions ...

                                  Howdy, Stuart!

                                  Correct, if 002 goes missing I want the new file to re-use the missing name. The intent is simply to keep the number as small as possible. There's no technical reason for it - just a preference on my part. I typically don't use 002 either, just 2.

                                  Howdy, Frank!
                                  I had not tried that API. It has the drawback of being a fairly long name rather than a simpler "New 1" kind of name.

                                  Howdy MCM!
                                  Yes, I have used that approach in the past. By not keeping a number in the INI, I can delete the INI occasionally and not worry about losing the max number. I've found myself deleting the iNI file a lot - particularly during a development phase. I love INI files, just not for this particular usage.

                                  Howdy George!
                                  I like to be able to remove my apps by simply deleting the installation folder and everything within it. An INI supports that whereas using the Registry does not.

                                  Howdy, Bud!
                                  The code I posted in #3 gives the same results, doesn't it? Or have you done something I didn't catch?

                                  Howdy Frank!
                                  I definitely would go with an approach like I posted in #3 in order to get a much shorter name. Sometimes in my apps for low vision users, the large fonts I use quickly use up the width of a ListView column, hence my preference for shorter names.

                                  Howdy, Mike!
                                  In my case, the files belong to the user and I expect them to eventually rename or delete the files. So protection is not needed.

                                  Howdy Rodney!
                                  I usually avoid time/date strings in my file names but only because of the length. There's not an iron-clad rule for me

                                  Comment


                                  • #18
                                    Originally posted by Gary Beene View Post
                                    [B]Catching up ... thanks for all the suggestions ...
                                    Looks like your Post#3 already solved your problem. I can't think of a simpler way to achieve your stated requirements.

                                    Comment


                                    • #19
                                      Originally posted by Gary Beene View Post
                                      Howdy, Bud!
                                      The code I posted in #3 gives the same results, doesn't it? Or have you done something I didn't catch?
                                      #3 does solve the problem in fewer lines. I was thinking of the human element. Open the folder in explorer and sort by file name, you'll get
                                      Code:
                                      NEW1.TXT
                                      NEW10.TXT
                                      NEW100.TXT
                                      NEW11.TXT
                                      (etc)
                                      Real programmers use a magnetized needle and a steady hand

                                      Comment


                                      • #20
                                        Originally posted by Bud Durland View Post

                                        #3 does solve the problem in fewer lines. I was thinking of the human element. Open the folder in explorer and sort by file name, you'll get
                                        Code:
                                        NEW1.TXT
                                        NEW10.TXT
                                        NEW100.TXT
                                        NEW11.TXT
                                        (etc)
                                        Worse, he'll get file names with spaces in them
                                        NEW 1.TXT
                                        NEW 10.TXT
                                        etc
                                        which is often a PITA

                                        Comment

                                        Working...
                                        X