James Curran

For an application, I need to create a large number of files. I had planned on naming them in the form: "YYYY-MM-DD--NNN.txt" where "YYYY-MM-DD" is todays date, and "NNN" is just a sequential index (eg "001", 002" etc).

Since I do not wish to overwrite an exist file, I need a way to quickly determine what is the next available filename in that sequence.

The only way that I can think of is to call Directory.GetFiles(), sort the list, take the last element of the array, and step from there.

The problem with that is that it's a bit slow (and gets increasing slower as more files are created), and since there will be a delay between identifying a filename, and using it, a seaprate thread could come up with the same name.

Any suggestions




Re: Visual C# General Creating Sequental filename.

Peter Ritchie

If uniqueness is your goal, I would suggest PInvoking the GetTempFileName function. It lets you specify a prefix to use to generate a unique filename. The PInvoke signature would look something like:

Code Snippet
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
internal static extern uint GetTempFileName(string tmpPath, string prefix, uint uniqueIdOrZero, StringBuilder tmpFileName);







Re: Visual C# General Creating Sequental filename.

sirjis

You could also consider appending the time, either as HH:mm: ss or as something like the number of seconds since midnight. If you're making the files faster than that, you could add more precision. Before creating a file, you would check whether it already exists (just in case you make two very quickly, or the system clock changes), and if so, change the filename (by adding 1 to the # of seconds, or appending a letter or something).

Edit:

After looking at GetTempFileName, it seems that it does something similar, at least based on the current time.





Re: Visual C# General Creating Sequental filename.

Derek Smyth

Hi,

Store the next number in a file within the directory where the files are written. If someone accidently deletes the file then have your application crash with a rude error message, no if the file is missing then do your idea.


If your worried about another thread causing problems then mark the code as a critical section using Thread.BeginCriticalRegion and Thread.EndCriticalRegion. This will ensure the thread completes the task of saving the file.






Re: Visual C# General Creating Sequental filename.

OmegaMan

James Curran wrote:

"YYYY-MM-DD--NNN.txt" where "YYYY-MM-DD" is todays date, and "NNN" is just a sequential index (eg "001", 002" etc).

Since I do not wish to overwrite an exist file, I need a way to quickly determine what is the next available filename in that sequence.



Code Snippet

public static string DetermineFilename(string prefix, string extension, int startNumber)
{
string target;
do
{
target = string.Format("{0}{1:000}.{2}", prefix, startNumber++, extension);

} while (File.Exists(target) == true);

return target;
}







Re: Visual C# General Creating Sequental filename.

JohnWein

I must be missing something here. You are naming the files sequentially over the course of one day. You know the number of the last file you created. Wouldn't the next file number be the last file number plus one



Re: Visual C# General Creating Sequental filename.

sirjis

JohnWein wrote:
You know the number of the last file you created.

The issue is that you have to list all of the files in order to determine the last one in the series (after restarting the program, at least), which can be slow if there are tons of files. I think Derek has a good solution with an additional file with a known name that keeps track of the most recent number.





Re: Visual C# General Creating Sequental filename.

James Curran

You know the number of the last file you created.
That assumes that I have some place to store that number (which might be used by several different application which might be running at the same or different times over the course of the day)






Re: Visual C# General Creating Sequental filename.

James Curran

If uniqueness is your goal, I would suggest PInvoking the GetTempFileName function.

I was hoping for a somewhat more readable filename (and one where I had control over the extension), but I'll keep it in mind if what I come up with proves unworkable.






Re: Visual C# General Creating Sequental filename.

James Curran

OmegaMan wrote:

Code Snippet

public static string DetermineFilename(string prefix, string extension, int startNumber)


Not quite that simple:

Code Snippet

string file1 = DetermineFilename("IM", "jpg", 1);

string file2 = DetermineFilename("IM", "jpg", 2);

using (FileStream fs1 = new FileStream(file1))

{

using (FileStream fs2 = new FileStream(file2))

{ // etc

Now, if there's already an IM1.jpg, then both calls will return "IM2.jpg".






Re: Visual C# General Creating Sequental filename.

James Curran

Thanks for the suggestions. In the end, here's what I came up with:

    /// <summary>
    /// Creates a sequential filename. (a file with that name is created)
    /// </summary>
    /// <param name="folder">The folder.</param>
    /// <returns></returns>
    static string CreateSequentialFilename(string folder)
    {
      return CreateSequentialFilename(folder, false);
    }
 
    /// <summary>
    /// Creates a sequential filename. (a file with that name is created)
    /// </summary>
    /// <param name="folder">The folder.</param>
    /// <param name="useThreadId">if set to <c>true</c> the thread id is used in the filename.</param>
    /// <returns>The filename (with path)</returns>
    /// <remarks>This probably should be expanded to allow passing in a prefix and the extension</remarks>
    static string CreateSequentialFilename(string folder, bool useThreadId)
    {
      string filePattern = "";
      if (useThreadId)
      {
        // This pattern adds the thread ID in the middle. Use if multiple threads will be creating files at the same time.
        filePattern = String.Format("{0:yyyy-MM-dd.HH-mm-ss}({1:X4}){{0:000}}.jpg", DateTime.Now, GetCurrentThreadId());
      }
      else
      {
        // This pattern has the date (dot) time (dot) sequential index number
        filePattern = String.Format("{0:yyyy-MM-dd.HH-mm-ss}.{{0:000}}.jpg", DateTime.Now);
      }
 
      int n = 0;
      do
      {
        string filename = Path.Combine(folder, string.Format(filePattern, ++n));
        // Cycle through numbers until one is found which does not exist.
        if (!File.Exists(filename))
        {
          // Try/catch is used for the case where between checking for the file's existence above, and
          // creating it below, another process creates a file with the same name. In this case (which should never
          // occur if the filePattern with the threadid is used above), File.Open will throw a IOException, inwhich case,
          // we keep looking.
          try
          {
            using (FileStream fs = File.Open(filename, FileMode.CreateNew))
            {
              // A file must be created, so the next time this is called, it will create a different name.
            }
 
            return filename;
          }
          catch (IOException )
          { /* just swallow it */ }
 
        }
      } while (true);
    }
 
    [DllImport("KERNEL32")]
    public static extern int GetCurrentThreadId();





Re: Visual C# General Creating Sequental filename.

OmegaMan

I was remiss in missing the threading requirement...my bad. Here is a sample which addresses these issues
  1. Works with multiple threads of any number.
  2. Avoids the overhead of checking the hard drive for already checked files.
  3. Reserves names to allow for the consumer to create file at own pace without affecting other thread(s).


public static Dictionary<string, int> _Reserved = new Dictionary<string, int>();

public static string DetermineFilename(string prefix, string extension, int startNumber)
{
string target;

lock (_Reserved)
{
bool searching = true;

do
{
target = string.Format("{0}{1:000}.{2}", prefix, startNumber++, extension);

// Ignore any already discovered or reserved filename.
if (_Reserved.ContainsKey(target) == false)
{

searching = File.Exists(target);

// Add to reserved regardless of whether
// it exsits at this point or not.
_Reserved.Add(target, 0);

}

} while (searching);

}

return target;
}

The obvious limitation is the 999 limit...but I know you can work around that. <g>