codeg
codeg

Reputation: 333

How to check if a zip file is not in use by another process before downloading

I'm writing a Windows Service that collects a zip file from a Uri path on a server. The problem I am running into is the zip file is still being written to on the server when my service attempts to collect it. If I put a Thread.Sleep(15000) before I make the call to download the file, it works fine. That delay is unacceptable when processing video alarm. If possible I would prefer to add a check in a while loop to see if the file is ready to download from the server URI before calling the download function. Is this possible?

I'm using an asynchronous WebClient to download the zip file:

internal void DownloadZippedFile(string eventId)
{
    try
    {
        Logger.LogInformation("Extracing Video for event: " + eventId);

        using (WebClient webClient = new WebClient())
        {
            string urlPath = sureViewZipHost + "/archiveevent?user=" + sureViewUserId + "&password=" + sureViewPassword + "&event=" + eventId;
            string tempPath = Path.GetTempPath();

            long lReceived = 0;
            long lTotal = 0;

            webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted);
            webClient.DownloadProgressChanged += delegate(object sender, DownloadProgressChangedEventArgs e)
            {
                Logger.LogInformation("downloaded {0} of {1} bytes. {2}% complete", e.BytesReceived, e.TotalBytesToReceive, e.ProgressPercentage);

                // Assign to outer variables to allow busy-wait to check.
                lReceived = e.BytesReceived;
                lTotal = e.TotalBytesToReceive;
            };
    
            webClient.DownloadFileAsync(new Uri(urlPath), tempPath + "//" + eventId + ".zip",eventId);
        }
    }
    catch (Exception ex)
    {
        Logger.LogError("Error extracting Video " + ex.Message);
    }
}

An exception never gets thrown using the code above. I found the issue by opening the partially downloaded zip, it contains:

400 BadRequest

System.IO.IOException: The process cannot access the file 'C:\FileStore\G98\E16884\R6507679\JpegBackup022620132102384484.zip' because it is being used by another process.

Upvotes: 0

Views: 1705

Answers (3)

Bobson
Bobson

Reputation: 13706

I don't think there's any way to check whether a process on the server is using a file before trying to access it. What you can do is specifically catch that 400 error, check the error message for "used by another process", and then sleep briefly before retrying.

try
{
  int retrysLeft = 10;
  while (retrysLeft >= 0)
  {
     try 
     {
        // attempt to download
        retrysLeft = -1;
     }
     catch (WebException ex)
     {
        if (ex.InnerException.Message.Contains("used by another process"))
        {
           retrysLeft--;
           Thread.Sleep(1000);
        }
        else throw;
     }
   }
 }
 catch (Exception ex)
 {
    // Regular error handling
 }

Upvotes: 0

Zaid Amir
Zaid Amir

Reputation: 4775

You can't, basically - you have to attempt to open the file for writing, and if you get an exception you can't write to it :(

Even if you could do a separate check first, the result would be out of date before you could start writing - you could still end up with the exception later.

You could try something like this:

public class FileManager
{
    private string _fileName;

    private int _numberOfTries;

    private int _timeIntervalBetweenTries;

    private FileStream GetStream(FileAccess fileAccess)
    {
        var tries = 0;
        while (true)
        {
            try
            {
                return File.Open(_fileName, FileMode.Open, fileAccess, Fileshare.None); 
            }
            catch (IOException e)
            {
                if (!IsFileLocked(e))
                    throw;
                if (++tries > _numberOfTries)
                    throw new MyCustomException("The file is locked too long: " + e.Message, e);
                Thread.Sleep(_timeIntervalBetweenTries);
            }
        }
    }

    private static bool IsFileLocked(IOException exception)
    {
        int errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
        return errorCode == 32 || errorCode == 33;
    }

    // other code

}

This is what i use in my code to overcome such an issue..

Upvotes: 1

RQDQ
RQDQ

Reputation: 15569

One way to attack this would be to retry the operation every n seconds (with a max number of retries specified).

Upvotes: 0

Related Questions