Home > Software design >  How to wait until a file is successfully copied from a network drive before reading from it?
How to wait until a file is successfully copied from a network drive before reading from it?

Time:10-19

I am currently facing a problem with an application which consists of multiple components. One component of the application periodically checks for new files on a network drive and copies them into a local folder. Another component of the application uses a FileSystemWatcher to watch for any new files in the local folder. If a new file is copied, the Created event of the FileSystemWatcher gets called and the application will then read the file contents and import the file into a database. To prevent the application from trying to read the file before it is fully copied into the local folder, it calls the following function periodically until it returns false:

private bool isFileLocked(string filePath)
{
    try
    {
        if (!File.Exists(filePath))
        {
            return false;
        }

        using (FileStream fs = File.OpenRead(filePath))
        {
        }

        return false;
    }
    catch (IOException)
    {
        return true;
    }
}

Unfortunately this does not seem to work in all cases. Sometimes, I noticed that the file is being read before it is completely written into the local folder. When this happens, the component which tries to copy the file gets the following error:

System.IO.IOException: The process cannot access the file '...' because it is being used by another process.

The component which copies the file is written in PowerShell and uses the following Cmdlet for copying:

Copy-Item $currentfile.FullName -Destination "$destfolder" –Force -ErrorAction Stop

The component which uses the FileSystemWatcher and imports the file is a C# based windows service. How can I prevent it from reading the file before it is fully copied into the local folder?

CodePudding user response:

If you don't worry about little delay - it may solve your trouble:

static void Main(string[] args)
{
    FileSystemWatcher fsw = new FileSystemWatcher("SomePathToFolder");
    fsw.EnableRaisingEvents = true;
    fsw.Created  = async (s, a) =>
    {
        while (FileIsLocked(a.FullPath))
        {
            Console.WriteLine($"File {a.Name} is locked!");
            await Task.Delay(TimeSpan.FromSeconds(5)); // 5 seconds delay between checks
        }
  
        Console.WriteLine($"File {a.Name} available!");
  
        // You can put here another delay to be 102% sure that file is free,
        // but I suppose this is too much.
        using (FileStream fs = File.OpenRead(a.FullPath))
        {
            Console.WriteLine($"File {a.Name} opened for reading.");
            // Do what you need
            await Task.Run(() => ImportFileToDatabase(fs));
        }

        Console.WriteLine($"File {a.Name} closed.");
    };


    Console.ReadKey();
}

static bool FileIsLocked(string filePath)
{
    if (!File.Exists(filePath))
        return false;

    try
    {
        using (FileStream fs = File.OpenRead(filePath)) { }
        return false;
    }
    catch { }

    return true;
}

CodePudding user response:

Some solutions are suggested here. I've had a similar problem using FileSystemWatcher. This is what I use:

async Task<FileStream> OpenWaitAsync(string path, TimeSpan interval, CancellationToken cancellationToken = default)
{
    const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020);
    while (true)
    {
        try
        {
            return File.OpenRead(path);
        }
        catch (IOException ioe) when (ioe.HResult == ERROR_SHARING_VIOLATION)
        {
            await Task.Delay(interval, cancellationToken);
        }
    }
}
  • Related