0

OK, so I'm working on a game launcher. It checks for an update, if it exists it gets downloaded and unzipped. After unzipping is finished, new files are copied where they are needed and then the zip and the unzipped files are deleted.

The problem is this: if the user closes the launcher while unzipping, the next time they launch it, I get an error while unzipping - the file already exists.

So what I would like to do is delete the Patch folder when exiting. However, if the background worker is running, the resource cannot be deleted as it is being used by another process.

The downloader class:

static void downloader_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;

        if (!Directory.Exists(Path.Combine(BASE_DIR, "Patch")))
            Directory.CreateDirectory(Path.Combine(BASE_DIR, "Patch"));

        string sFilePathToWriteFileTo = Path.Combine(BASE_DIR, "Patch", "patch.zip").ToString();

        // first, we need to get the exact size (in bytes) of the file we are downloading
        Uri url = new Uri(sUrlToReadFileFrom);
        System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
        System.Net.HttpWebResponse response = (System.Net.HttpWebResponse)request.GetResponse();
        response.Close();
        // gets the size of the file in bytes
        Int64 iSize = response.ContentLength;

        // keeps track of the total bytes downloaded so we can update the progress bar
        Int64 iRunningByteTotal = 0;

        // use the webclient object to download the file
        using (System.Net.WebClient client = new System.Net.WebClient())
        {
            // open the file at the remote URL for reading
            using (System.IO.Stream streamRemote = client.OpenRead(new Uri(sUrlToReadFileFrom)))
            {
                // using the FileStream object, we can write the downloaded bytes to the file system
                using (Stream streamLocal = new FileStream(sFilePathToWriteFileTo, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    // loop the stream and get the file into the byte buffer
                    int iByteSize = 0;
                    byte[] byteBuffer = new byte[1024];
                    double dTotal = (double)iSize;
                    while ((iByteSize = streamRemote.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
                    {
                        // write the bytes to the file system at the file path specified
                        streamLocal.Write(byteBuffer, 0, iByteSize);
                        iRunningByteTotal += iByteSize;

                        // calculate the progress out of a base "100"
                        double dIndex = (double)(iRunningByteTotal);
                        double dProgressPercentage = (dIndex / dTotal);
                        int iProgressPercentage = (int)(dProgressPercentage * 100);

                        // update the progress bar
                        worker.ReportProgress(iProgressPercentage);
                    }

                    // clean up the file stream
                    streamLocal.Close();
                }
                // close the connection to the remote server
                streamRemote.Close();
            }
        }

    }

and then the unzipper:

private void decompresser_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        #region Unzip files

        if (!Directory.Exists(decompressPath))
            Directory.CreateDirectory(decompressPath);

        using (ZipArchive archive = ZipFile.OpenRead(archiveName))
        {
            int iTotal = archive.Entries.Count();
            int curr = 0;
            foreach (ZipArchiveEntry entry in archive.Entries)
            {
                if (entry.FullName != entry.Name)
                {
                    if (entry.Name == string.Empty)
                    {
                        //create folder here
                        Directory.CreateDirectory(Path.Combine(decompressPath, entry.FullName));
                    }
                    else
                    {
                        //create folder and extract file into it
                        string dirToCreate = entry.FullName.Replace(entry.Name, "");
                        Directory.CreateDirectory(Path.Combine(decompressPath, dirToCreate));
                        entry.ExtractToFile(Path.Combine(decompressPath, entry.FullName));
                    }
                }
                else
                {
                    //just extract file
                    Console.WriteLine(Path.Combine(decompressPath, entry.FullName));
                    entry.ExtractToFile(Path.Combine(decompressPath, entry.FullName));
                }

                curr++;
                var progress = ((double)curr / (double)iTotal) * 60.0;
                worker.ReportProgress((int)progress);
            }
        }

        //delete zip file
        File.Delete(Path.Combine(BASE_DIR, "Patch", "patch.zip"));
        #endregion

        #region Copy files
        string sourceDirName = Path.Combine(BASE_DIR, "Patch");
        string destDirName = BASE_DIR;
        DirectoryInfo dir = new DirectoryInfo(sourceDirName);
        long maxbytes = 0;
        List<FileInfo> files = new List<FileInfo>();
        FileInfo[] folder = dir.GetFiles("*.*", SearchOption.AllDirectories);
        foreach (FileInfo file in folder)
        {
            if ((file.Attributes & FileAttributes.Directory) != 0) continue;
            files.Add(file);
            maxbytes += file.Length;
        }

        // Copy files
        long bytes = 0;
        foreach (FileInfo file in files)
        {
            try
            {
                //where to copy
                string copyPath = file.FullName.Replace("Patch\\", "").Replace(file.Name, "");
                if (!Directory.Exists(copyPath))
                    Directory.CreateDirectory(copyPath);
                File.Copy(file.FullName, Path.Combine(copyPath, file.Name), true);
                var progress = 60 + ((double)bytes / (double)maxbytes) * 30.0;
                worker.ReportProgress((int)progress);
            }
            catch (Exception ex)
            {
            }
            bytes += file.Length;
        }

        #endregion

        #region Clean Up

        foreach (FileInfo file in files)
        {
            try
            {
                var progress = 90 + ((double)(maxbytes - file.Length) / (double)maxbytes) * 9;

                file.Delete();
                worker.ReportProgress((int)progress);
            }
            catch (Exception ex) { }
        }
        try
        {
            string delPath = Path.Combine(BASE_DIR, "Patch");
            Directory.Delete(delPath, true);
        }
        catch (Exception ex) { }

        worker.ReportProgress(100);


        #endregion
    }

I terminate the application by calling the App.Current.Shutdown(); method.

m0rgul
  • 21
  • 3

5 Answers5

0

If I am not missing anything there is a very simple solution. You can use the Application On Exit event to stop all the background workers by calling workerObject.RequestStop(); (note it only signals the code in worker that a stop has been requested you need to handle this in the worker's do work loop to stop the work). And then you can clean up the patch folder.

Community
  • 1
  • 1
Karthik
  • 990
  • 10
  • 19
0

As a dodge to the issue, could you not also delete the patch folder upon launching the app, but before starting the download?

GWLlosa
  • 23,995
  • 17
  • 79
  • 116
0

@GWLlosa - i did think about deleting the files upon launch, if needed, but i was going to save that as a last resort if i couldn't find anything better

i tried something like this

    protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            updater.CancelAsync();
            while (updater.IsBusy)
            {
                Thread.Sleep(100);
            }
//run code to delete the files after the worked has stopped
}

but this only got me to an infinite loop :)

m0rgul
  • 21
  • 3
0

If I do, I would to find and kill immediately download process after launching.

Process[] p = Process.GetProcessesByName("processname");
foreach (var proc in p)
{
     proc.Kill();
}
HungDL
  • 493
  • 2
  • 11
0

I have 2 solutions for your problem.

1) Don't try to delete patch folder on exit, just fire & forget the BackgroundWorker, if user cancels patch and quits, then just exit app. But always delete patch folder when user starts your app, specially right before you calling unzip.

2) If you still want to delete patch folder on exit, then cancel your BackgroundWorker with CancelAsync() function. For example

private void btnStart_Click(object sender, EventArgs e)
{
    if (btnStart.Text == "Start")
    {
        ...
    }
    else
    {
        m_bgWorker.CancelAsync();
    }
}


private void SearchFiles(string strPath, string strPattern)
{
    string strMessage = "Parsing directory " + strPath;
    m_bgWorker.ReportProgress(0, strMessage);
    foreach (string strFileName in Directory.GetFiles(strPath, strPattern))
    {
        if (m_bgWorker.CancellationPending)
        {
            return;
        }
        else
        {
            m_lstFiles.Add(strFileName);
        }
    }

    foreach (string strDirName in Directory.GetDirectories(strPath))
    {
        if (m_bgWorker.CancellationPending)
        {
            return;
        }
        else
        {
            SearchFiles(strDirName, strPattern);
        }
    }
}

I got the code form this post. You can read more from that example to see how author cancels his BackgroundWorker.

T N
  • 396
  • 5
  • 13
  • i wrote a comment earlier explaining that i already did try that :) i captured the shutdown event, stopped it, tried to cancelAsync but it't not working. as far as I can figure out, the problem is the using{} blocks, because they do not release their assets until the operation is done. – m0rgul Apr 01 '15 at 08:41
  • How do you forward CancelAsync() to your decompresser BackgroundWorker? It's weird if using block causes problem. Because if you CancelAsync() the unzip action, the using block (ZipArchive archive = ZipFile.OpenRead(archiveName)) is now out of scope therefore the ZipArchive object should be also disposed and all unzip process should be terminated too. – T N Apr 01 '15 at 09:25
  • apparently that does not happen. I actually used some console.writeline here and there, and apparently, inside the "using" block, the worker cancel event is not received. so my guess is that this block creates some sort of "separate environment" therefore changing variables outside this scope does not impact the using block. so, having bashed my head against this issue for some time, i decided to go for a much easier one...delete files on start :) – m0rgul Apr 06 '15 at 08:49