9
\$\begingroup\$

Any suggestions, corrections and advice would be very nice and much appreciated.

public class Download
{
    public event EventHandler<DownloadStatusChangedEventArgs> DownloadStatusChanged;
    public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;
    public event EventHandler DownloadCompleted;

    public bool stop = true; // by default stop is true

    public void DownloadFile(string DownloadLink, string Path)
    {
        stop = false; // always set this bool to false, everytime this method is called

        long ExistingLength = 0;
        FileStream saveFileStream;

        if (File.Exists(Path))
        {
            FileInfo fileInfo = new FileInfo(Path);
            ExistingLength = fileInfo.Length;
        }

        if (ExistingLength > 0)
        {
            saveFileStream = new FileStream(Path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
        }
        else
        {
            saveFileStream = new FileStream(Path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
        }

        var request = (HttpWebRequest)HttpWebRequest.Create(DownloadLink);
        request.Proxy = null;
        request.AddRange(ExistingLength);

        try
        {
            using (var response = (HttpWebResponse)request.GetResponse())
            {
                long FileSize = ExistingLength + response.ContentLength; //response.ContentLength gives me the size that is remaining to be downloaded
                bool downloadResumable; // need it for sending empty progress

                if ((int)response.StatusCode == 206)
                {
                    //Console.WriteLine("Resumable");
                    var downloadStatusArgs = new DownloadStatusChangedEventArgs();
                    downloadResumable = true;
                    downloadStatusArgs.ResumeSupported = downloadResumable;
                    OnDownloadStatusChanged(downloadStatusArgs);
                }
                else // sometimes a server that supports partial content will lose its ability to send partial content(weird behavior) and thus the download will lose its resumability
                {
                    //Console.WriteLine("Resume Not Supported");
                    ExistingLength = 0;
                    var downloadStatusArgs = new DownloadStatusChangedEventArgs();
                    downloadResumable = false;
                    downloadStatusArgs.ResumeSupported = downloadResumable;
                    OnDownloadStatusChanged(downloadStatusArgs);
                    // restart downloading the file from the beginning because it isn't resumable
                    // if this isn't done, the method downloads the file from the beginning and starts writing it after the previously half downloaded file, thus increasing the filesize and corrupting the downloaded file
                    saveFileStream.Dispose(); // dispose object to free it for the next operation
                    File.WriteAllText(Path, string.Empty); // clear the contents of the half downloaded file that can't be resumed
                    saveFileStream = new FileStream(Path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); // reopen it for writing
                }

                using (var stream = response.GetResponseStream())
                {
                    byte[] downBuffer = new byte[4096];
                    int byteSize = 0;
                    long totalReceived = byteSize + ExistingLength;
                    var sw = new Stopwatch();
                    sw.Start();
                    while ((byteSize = stream.Read(downBuffer, 0, downBuffer.Length)) > 0)
                    {
                        saveFileStream.Write(downBuffer, 0, byteSize);
                        totalReceived += byteSize;

                        var args = new DownloadProgressChangedEventArgs();
                        args.BytesReceived = totalReceived;
                        args.TotalBytesToReceive = FileSize;
                        float currentSpeed = totalReceived / (float)sw.Elapsed.TotalSeconds;
                        args.CurrentSpeed = currentSpeed;
                        if (downloadResumable == true)
                        {
                            args.ProgressPercentage = ((float)totalReceived / (float)FileSize) * 100;
                            long bytesRemainingtoBeReceived = FileSize - totalReceived;
                            args.TimeLeft = (long)(bytesRemainingtoBeReceived / currentSpeed);
                        }
                        else
                        {
                            //args.ProgressPercentage = Unknown;
                            //args.TimeLeft = Unknown;
                        }
                        OnDownloadProgressChanged(args);

                        if (stop == true) 
                            return;
                    }
                    sw.Stop();
                }
            }
            var completedArgs = new EventArgs();
            OnDownloadCompleted(completedArgs);
        }
        catch (WebException e)
        {
            string filename = System.IO.Path.GetFileName(Path);
            Console.WriteLine(e.Message);
        }
        finally
        {
            saveFileStream.Dispose();
        }
    }

    public void StopDownload()
    {
        stop = true;
    }

    protected virtual void OnDownloadStatusChanged(DownloadStatusChangedEventArgs e)
    {
        EventHandler<DownloadStatusChangedEventArgs> handler = DownloadStatusChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    protected virtual void OnDownloadProgressChanged(DownloadProgressChangedEventArgs e)
    {
        EventHandler<DownloadProgressChangedEventArgs> handler = DownloadProgressChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    protected virtual void OnDownloadCompleted(EventArgs e)
    {
        EventHandler handler = DownloadCompleted;
        if (handler != null)
        {
            handler(this, e);
        }
    }
}

public class DownloadStatusChangedEventArgs : EventArgs
{
    public bool ResumeSupported { get; set; }
}

public class DownloadProgressChangedEventArgs : EventArgs
{
    public long BytesReceived { get; set; }
    public long TotalBytesToReceive { get; set; }
    public float ProgressPercentage { get; set; }
    public float CurrentSpeed { get; set; } // in bytes
    public long TimeLeft { get; set; } // in seconds
}
\$\endgroup\$
12
\$\begingroup\$

I would not directly write to the console.

You are best off building an ILogger interface, then create a ConsoleLogger class that is passed as an ILogger to the constructor.

public interface ILogger
{
    void LogMessage(string message, params Object[] args);
}

public class ConsoleLogger : ILogger
{
    public void LogMessage(string message, params Object[] args)
    {
        Console.WriteLine(string.Format(message, args));
    }
}

This allows you to break responsibilities away, and if in the future you wish to log messages directly to the Event Log instead, this allows you to make a class:

public class EventLogger : ILogger
{
    // ...
}

In the class handling all your work above (no class name is supplied) you would add:

private ILogger _Logger;

public ClassName(ILogger logger)
{
    _Logger = logger;
}

And modify all the Console.WriteLine calls to:

logger.LogMessage(/* ... */);

This is much more extensible, and allows you to unit test easier as well.

Additionally, if you wish to support a lack of a ILogger:

if (logger != null) { logger.LogMessage(/* ... */); }

I know this seems like an irrelevant suggestion, but simple changes like this can go a long way for future maintainability.

\$\endgroup\$
  • \$\begingroup\$ That was just the debug code. But thanks for the advice. \$\endgroup\$ – Expenzor Jul 24 '15 at 0:30
  • 1
    \$\begingroup\$ @Expenzor It's always good to get in the habit of writing debug code as if it were live code. :) \$\endgroup\$ – 410_Gone Jul 24 '15 at 0:31
10
\$\begingroup\$

Your FileStream looks like it is an IDisposable as well, you should put it into a using statement as well, then you don't have to worry about missing a chance to dispose it.


Right here

    if (ExistingLength > 0)
        saveFileStream = new FileStream(Path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
    else
        saveFileStream = new FileStream(Path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);

In my opinion you should use curly braces, I almost always use curly braces.


MSDN Using Statement

One of the reasons to use a using statement is because, no matter what happens the object that you opened with the statement is always closed, no matter what happens. It's like throwing the dispose call into the Finally statement of a try catch block.


You should remove the saveFileStream.Dispose and replace it with saveFileStream.Flush() (Clears buffers for this stream and causes any buffered data to be written to the file.) which will allow you to get rid of this line as well because that is essentially what it was doing in the first place

File.WriteAllText(Path, string.Empty); // clear the contents of the half downloaded file that can't be resumed

Edit: We are going to use a Try/Catch and re-New the filestream each time, instead of try to bunch it into a using statement block.

I also realized that you do not need this

saveFileStream = new FileStream(Path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);

since we are not disposing the filestream, we are just flushing the stream, so the stream still has the same path, mode, and access. so that line is unneeded.


I would one line this

if (stop == true) 
    return;

like this

if (stop == true) return;

Because if I am going to create an if statement and not use curly braces I should be able to one line it and leave and extra line after it, so that I can tell there is nothing else inside that if statement.

and because stop is a boolean already

if (stop) return;

just because...


Don't let your Comments get too long, multi line them if you need to.

And get rid of comments that don't need to be there as well.


Your else statement

  else
  {   
      //args.ProgressPercentage = Unknown;
      //args.TimeLeft = Unknown;
  }

doesn't do anything, so get rid of it.


I left a lot of Comments in here that I would have probably just documented in my documentation. I am sure that someone maintaining this would know exactly what most of this is doing. the comments aren't horrible though, so leaving them there is probably okay, just keep in mind that too many make the code hard to read.


Here is what I came up with.

public void DownloadFile(string DownloadLink, string Path)
{
    stop = false; 

    long ExistingLength = 0;
    //using (FileStream saveFileStream = new FileStream()
    try
    {
        if (File.Exists(Path))
        {
            FileInfo fileInfo = new FileInfo(Path);
            ExistingLength = fileInfo.Length;
        }

        if (ExistingLength > 0)
        {
            saveFileStream = new FileStream(Path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
        }
        else
        {
            saveFileStream = new FileStream(Path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
        }

        var request = (HttpWebRequest)HttpWebRequest.Create(DownloadLink);
        request.Proxy = null;
        request.AddRange(ExistingLength);

        try
        {
            using (var response = (HttpWebResponse)request.GetResponse())
            {
                long FileSize = ExistingLength + response.ContentLength; //response.ContentLength gives me the size that is remaining to be downloaded
                bool downloadResumable; // need it for sending empty progress

                if ((int)response.StatusCode == 206)
                {
                    //Console.WriteLine("Resumable");
                    var downloadStatusArgs = new DownloadStatusChangedEventArgs();
                    downloadResumable = true;
                    downloadStatusArgs.ResumeSupported = downloadResumable;
                    OnDownloadStatusChanged(downloadStatusArgs);
                }
                else // sometimes a server that supports partial content will lose its ability to send partial content(weird behavior) and thus the download will lose its resumability
                {
                    //Console.WriteLine("Resume Not Supported");
                    ExistingLength = 0;
                    var downloadStatusArgs = new DownloadStatusChangedEventArgs();
                    downloadResumable = false;
                    downloadStatusArgs.ResumeSupported = downloadResumable;
                    OnDownloadStatusChanged(downloadStatusArgs);
                    // restart downloading the file from the beginning because it isn't resumable
                    // if this isn't done, the method downloads the file from the beginning and starts writing it after the 
                    // previously half downloaded file, thus increasing the filesize and corrupting the downloaded file
                    saveFileStream.Flush();// clear the contents of the half downloaded file that can't be resumed
                }

                using (var stream = response.GetResponseStream())
                {
                    byte[] downBuffer = new byte[4096];
                    int byteSize = 0;
                    long totalReceived = byteSize + ExistingLength;
                    var sw = new Stopwatch();
                    sw.Start();
                    while ((byteSize = stream.Read(downBuffer, 0, downBuffer.Length)) > 0)
                    {
                        saveFileStream.Write(downBuffer, 0, byteSize);
                        totalReceived += byteSize;

                        var args = new DownloadProgressChangedEventArgs();
                        args.BytesReceived = totalReceived;
                        args.TotalBytesToReceive = FileSize;
                        float currentSpeed = totalReceived / (float)sw.Elapsed.TotalSeconds;
                        args.CurrentSpeed = currentSpeed;
                        if (downloadResumable == true)
                        {
                            args.ProgressPercentage = ((float)totalReceived / (float)FileSize) * 100;
                            long bytesRemainingtoBeReceived = FileSize - totalReceived;
                            args.TimeLeft = (long)(bytesRemainingtoBeReceived / currentSpeed);
                        }

                        OnDownloadProgressChanged(args);

                        if (stop == true) return;

                    }
                    sw.Stop();
                }
            }
            var completedArgs = new EventArgs();
            OnDownloadCompleted(completedArgs);
        }
        catch (WebException e)
        {
            string filename = System.IO.Path.GetFileName(Path);
            Console.WriteLine(e.Message);
        }
    }
    finally
    {
        saveFileStream.Close()
        saveFileStream.Dispose()
    }
}
\$\endgroup\$
  • \$\begingroup\$ I need to dispose FileStream in between, if the download is not resumable. That's why I haven't used the using statement. The second change is extremely doable. Any other suggestions? \$\endgroup\$ – Expenzor Jul 23 '15 at 22:17
  • \$\begingroup\$ That is the point of the using statement, no matter what the object is disposed when the scope leaves the using statement. \$\endgroup\$ – Malachi Jul 24 '15 at 0:10
  • \$\begingroup\$ I didn't know you could dispose an object in between the scope without throwing an exception. \$\endgroup\$ – Expenzor Jul 24 '15 at 0:31
  • \$\begingroup\$ I am not sure what you mean by "in between the scope"? You could also use a finally statement after the catch and dispose the stream there and there only, and no matter what the stream will be disposed as well. I am on my phone otherwise I am sure I could find some link to explain try/catch/finally and how it compares to a using statement. \$\endgroup\$ – Malachi Jul 24 '15 at 0:35
  • 1
    \$\begingroup\$ It's fine :) Future people will not be confused now. \$\endgroup\$ – Expenzor Jul 24 '15 at 3:09
5
\$\begingroup\$
  • use a logger as EBrown said
  • FileInfo will shorten some expressions
  • EventArgs should be readonly (Except return parameter which are not present)
  • use EventaArgs.Empty instead of newing up an instance every time
  • EventArgs can calculate other Properties so the download method will get shorter
  • the 3 lines which create the filestream can be reduced to 1 when it is delayed to the actual use
  • the using will take care of Disposing and stream Dispose calls Flush
    • FileMode.Create will Take care of removing the outdated file in case of non resumable download
  • IMO Prefixing the events is not nessesary because the class name and the varable name it contains should indicate its a downloader.complete (instead of duplicate downloader.DownloadComplete)
  • Stopwatch.StartNew() is shorter than new Stopwatch() and Start()

complete refactored code

public interface ILogger
{
    void Log(string format, params object[] args);
    void Log(object message);
}

public class Downloader
{
    public Downloader(ILogger logger)
    {
        _logger = logger;
    }

    public event EventHandler<DownloadStatusChangedEventArgs> ResumablityChanged;
    public event EventHandler<DownloadProgressChangedEventArgs> ProgressChanged;
    public event EventHandler Completed;

    public bool stop = true; // by default stop is true
    private ILogger _logger;

    public void DownloadFile(string downloadLink, string path)
    {
        stop = false; // always set this bool to false, everytime this method is called

        var fileInfo = new FileInfo(path);
        long existingLength = 0;
        if (fileInfo.Exists)
            existingLength = fileInfo.Length;

        var request = (HttpWebRequest)HttpWebRequest.Create(downloadLink);
        request.Proxy = null;
        request.AddRange(existingLength);

        try
        {
            using (var response = (HttpWebResponse)request.GetResponse())
            {
                long fileSize = existingLength + response.ContentLength; //response.ContentLength gives me the size that is remaining to be downloaded
                bool downloadResumable; // need it for sending empty progress

                if (response.StatusCode == HttpStatusCode.PartialContent)
                {
                    downloadResumable = true;
                }
                else // sometimes a server that supports partial content will lose its ability to send partial content(weird behavior) and thus the download will lose its resumability
                {
                    _logger.Log("Resume Not Supported");
                    existingLength = 0;
                    downloadResumable = false;
                }
                OnResumabilityChanged(new DownloadStatusChangedEventArgs(downloadResumable));

                using (var saveFileStream = fileInfo.Open(downloadResumable ? FileMode.Append : FileMode.Create, FileAccess.Write))
                using (var stream = response.GetResponseStream())
                {
                    byte[] downBuffer = new byte[4096];
                    int byteSize = 0;
                    long totalReceived = byteSize + existingLength;
                    var sw = Stopwatch.StartNew();
                    while (!stop && (byteSize = stream.Read(downBuffer, 0, downBuffer.Length)) > 0)
                    {
                        saveFileStream.Write(downBuffer, 0, byteSize);
                        totalReceived += byteSize;

                        var currentSpeed = totalReceived / sw.Elapsed.TotalSeconds;
                        OnProgressChanged(new DownloadProgressChangedEventArgs(totalReceived, fileSize, (long)currentSpeed));
                    }
                    sw.Stop();
                }
            }
            if (!stop)
                OnCompleted(EventArgs.Empty);
        }
        catch (WebException e)
        {
            _logger.Log(e);
        }
    }

    public void StopDownload()
    {
        stop = true;
    }

    protected virtual void OnResumabilityChanged(DownloadStatusChangedEventArgs e)
    {
        var handler = ResumablityChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    protected virtual void OnProgressChanged(DownloadProgressChangedEventArgs e)
    {
        var handler = ProgressChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    protected virtual void OnCompleted(EventArgs e)
    {
        var handler = Completed;
        if (handler != null)
        {
            handler(this, e);
        }
    }
}

public class DownloadStatusChangedEventArgs : EventArgs
{
    public DownloadStatusChangedEventArgs(bool canResume)
    {
        ResumeSupported = canResume;
    }
    public bool ResumeSupported { get; private set; }
}

public class DownloadProgressChangedEventArgs : EventArgs
{
    public DownloadProgressChangedEventArgs(long totalReceived, long fileSize, long currentSpeed)
    {
        BytesReceived = totalReceived;
        TotalBytesToReceive = fileSize;
        CurrentSpeed = currentSpeed;
    }
    public long BytesReceived { get; private set; }
    public long TotalBytesToReceive { get; private set; }
    public float ProgressPercentage { get { return ((float)BytesReceived / (float)TotalBytesToReceive) * 100; } }
    /// <summary>in Bytes</summary>
    public long CurrentSpeed { get; private set; }
    public TimeSpan TimeLeft
    {
        get
        {
            var bytesRemainingtoBeReceived = TotalBytesToReceive - BytesReceived;
            return TimeSpan.FromSeconds(bytesRemainingtoBeReceived / CurrentSpeed);
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ Great suggestions :) \$\endgroup\$ – Expenzor Jul 24 '15 at 16:51
1
\$\begingroup\$

With the given suggestions implemented and some extra bits.

Pure version:

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;

namespace Downloader
{
    public interface ILogger
    {
        void LogMessage(string message, params Object[] args);
    }

    public class ConsoleLogger : ILogger
    {
        public void LogMessage(string message, params Object[] args)
        {
            Console.WriteLine(string.Format(message, args));
        }
    }

    public class Download
    {
        private ILogger _Logger;

        public CallingClass(ILogger logger)
        {
            _Logger = logger;
        }

        public event EventHandler<DownloadStatusChangedEventArgs> ResumablityChanged;
        public event EventHandler<DownloadProgressChangedEventArgs> ProgressChanged;
        public event EventHandler Completed;

        public bool stop = true; // by default stop is true
        public bool paused = false;
        SemaphoreSlim pauseLock = new SemaphoreSlim(1);

        string filename;

        public void DownloadFile(string DownloadLink, string Path)
        {
            filename = System.IO.Path.GetFileName(Path);

            stop = false; // always set this bool to false, everytime this method is called

            var fileInfo = new FileInfo(Path);
            long existingLength = 0;
            if (fileInfo.Exists)
                existingLength = fileInfo.Length;

            var request = (HttpWebRequest)HttpWebRequest.Create(DownloadLink);
            request.Proxy = null;
            request.AddRange(existingLength);

            try
            {
                using (var response = (HttpWebResponse)request.GetResponse())
                {
                    long fileSize = existingLength + response.ContentLength; //response.ContentLength gives me the size that is remaining to be downloaded
                    bool downloadResumable; // need it for not sending any progress

                    if ((int)response.StatusCode == 206) //same as: response.StatusCode == HttpStatusCode.PartialContent
                    {
                        logger.LogMessage("Resumable");
                        downloadResumable = true;
                    }
                    else // sometimes a server that supports partial content will lose its ability to send partial content(weird behavior) and thus the download will lose its resumability
                    {
                        logger.LogMessage("Not Resumable");
                        existingLength = 0;
                        downloadResumable = false;
                    }
                    OnResumabilityChanged(new DownloadStatusChangedEventArgs(downloadResumable));

                    using (var saveFileStream = fileInfo.Open(downloadResumable ? FileMode.Append : FileMode.Create, FileAccess.Write))
                    using (var stream = response.GetResponseStream())
                    {
                        byte[] downBuffer = new byte[4096];
                        int byteSize = 0;
                        long totalReceived = byteSize + existingLength;
                        var sw = Stopwatch.StartNew();
                        while (!stop && (byteSize = stream.Read(downBuffer, 0, downBuffer.Length)) > 0)
                        {
                            saveFileStream.Write(downBuffer, 0, byteSize);
                            totalReceived += byteSize;

                            float currentSpeed = totalReceived / (float)sw.Elapsed.TotalSeconds;
                            OnProgressChanged(new DownloadProgressChangedEventArgs(totalReceived, fileSize, (long)currentSpeed));

                            pauseLock.Wait();
                            pauseLock.Release();
                        }
                        sw.Stop();
                    }
                }
                if (!stop) OnCompleted(EventArgs.Empty);
            }
            catch (WebException e)
            {
                logger.LogMessage(e);
            }
        }

        public void pause()
        {
            if (!paused)
            {
                paused = true;
                // Note this cannot block for more than a moment
                // since the download thread doesn't keep the lock held
                pauseLock.Wait();
            }
        }

        public void unpause()
        {
            if (paused)
            {
                paused = false;
                pauseLock.Release();
            }
        }

        public void StopDownload()
        {
            stop = true;
            this.unpause();  // stop waiting on lock if needed
        }

        protected virtual void OnResumabilityChanged(DownloadStatusChangedEventArgs e)
        {
            var handler = ResumablityChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnProgressChanged(DownloadProgressChangedEventArgs e)
        {
            var handler = ProgressChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnCompleted(EventArgs e)
        {
            var handler = Completed;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }

    public class DownloadStatusChangedEventArgs : EventArgs
    {
        public DownloadStatusChangedEventArgs(bool canResume)
        {
            ResumeSupported = canResume;
        }
        public bool ResumeSupported { get; private set; }
    }

    public class DownloadProgressChangedEventArgs : EventArgs
    {
        public DownloadProgressChangedEventArgs(long totalReceived, long fileSize, long currentSpeed)
        {
            BytesReceived = totalReceived;
            TotalBytesToReceive = fileSize;
            CurrentSpeed = currentSpeed;
        }

        public long BytesReceived { get; private set; }
        public long TotalBytesToReceive { get; private set; }
        public float ProgressPercentage 
        { 
            get 
            { 
                return ((float)BytesReceived / (float)TotalBytesToReceive) * 100; 
            } 
        }
        public float CurrentSpeed { get; private set; } // in bytes
        public TimeSpan TimeLeft
        {
            get
            {
                var bytesRemainingtoBeReceived = TotalBytesToReceive - BytesReceived;
                return TimeSpan.FromSeconds(bytesRemainingtoBeReceived / CurrentSpeed);
            }
        }
    }
}

GUI version:

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;

namespace Downloader
{
    public interface ILogger
    {
        void LogMessage(string message, params Object[] args);
    }

    public class ConsoleLogger : ILogger
    {
        public void LogMessage(string message, params Object[] args)
        {
            Console.WriteLine(string.Format(message, args));
        }
    }

    public class Download
    {
        private ILogger _Logger;

        public CallingClass(ILogger logger)
        {
            _Logger = logger;
        }

        public event EventHandler<DownloadStatusChangedEventArgs> ResumablityChanged;
        public event EventHandler<DownloadProgressChangedEventArgs> ProgressChanged;
        public event EventHandler Completed;

        public bool stop = true; // by default stop is true
        public bool paused = false;
        SemaphoreSlim pauseLock = new SemaphoreSlim(1);

        string filename;

        public void DownloadFile(string DownloadLink, string Path)
        {
            filename = System.IO.Path.GetFileName(Path);

            stop = false; // always set this bool to false, everytime this method is called

            var fileInfo = new FileInfo(Path);
            long existingLength = 0;
            if (fileInfo.Exists)
                existingLength = fileInfo.Length;

            var request = (HttpWebRequest)HttpWebRequest.Create(DownloadLink);
            request.Proxy = null;
            request.AddRange(existingLength);

            try
            {
                using (var response = (HttpWebResponse)request.GetResponse())
                {
                    long fileSize = existingLength + response.ContentLength; //response.ContentLength gives me the size that is remaining to be downloaded
                    bool downloadResumable; // need it for not sending any progress

                    if ((int)response.StatusCode == 206) //same as: response.StatusCode == HttpStatusCode.PartialContent
                    {
                        logger.LogMessage("Resumable");
                        downloadResumable = true;
                    }
                    else // sometimes a server that supports partial content will lose its ability to send partial content(weird behavior) and thus the download will lose its resumability
                    {
                        logger.LogMessage("Not Resumable");
                        if (existingLength > 0)
                        {
                            if (ResumeUnsupportedWarning() == false) // warn and ask for confirmation to continue if the half downloaded file is unresumable
                            {
                                return;
                            }
                        }
                        existingLength = 0;
                        downloadResumable = false;
                    }
                    OnResumabilityChanged(new DownloadStatusChangedEventArgs(downloadResumable));

                    using (var saveFileStream = fileInfo.Open(downloadResumable ? FileMode.Append : FileMode.Create, FileAccess.Write))
                    using (var stream = response.GetResponseStream())
                    {
                        byte[] downBuffer = new byte[4096];
                        int byteSize = 0;
                        long totalReceived = byteSize + existingLength;
                        var sw = Stopwatch.StartNew();
                        while (!stop && (byteSize = stream.Read(downBuffer, 0, downBuffer.Length)) > 0)
                        {
                            saveFileStream.Write(downBuffer, 0, byteSize);
                            totalReceived += byteSize;

                            float currentSpeed = totalReceived / (float)sw.Elapsed.TotalSeconds;
                            OnProgressChanged(new DownloadProgressChangedEventArgs(totalReceived, fileSize, (long)currentSpeed));

                            pauseLock.Wait();
                            pauseLock.Release();
                        }
                        sw.Stop();
                    }
                }
                if (!stop) OnCompleted(EventArgs.Empty);
            }
            catch (WebException e)
            {
                System.Windows.MessageBox.Show(e.Message, filename);
            }
        }

        public void pause()
        {
            if (!paused)
            {
                paused = true;
                // Note this cannot block for more than a moment
                // since the download thread doesn't keep the lock held
                pauseLock.Wait();
            }
        }

        public void unpause()
        {
            if (paused)
            {
                paused = false;
                pauseLock.Release();
            }
        }

        public void StopDownload()
        {
            stop = true;
            this.unpause();  // stop waiting on lock if needed
        }

        public bool ResumeUnsupportedWarning()
        {
            var messageBoxResult = System.Windows.MessageBox.Show("When trying to resume the download , Mackerel got a response from the server that it doesn't support resuming the download. It's possible that it's a temporary error of the server, and you will be able to resume the file at a later time, but at this time Mackerel can download this file from the beginning.\n\nDo you want to download this file from the beginning?", filename, System.Windows.MessageBoxButton.YesNo);
            if (messageBoxResult == System.Windows.MessageBoxResult.Yes)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        protected virtual void OnResumabilityChanged(DownloadStatusChangedEventArgs e)
        {
            var handler = ResumablityChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnProgressChanged(DownloadProgressChangedEventArgs e)
        {
            var handler = ProgressChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnCompleted(EventArgs e)
        {
            var handler = Completed;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }

    public class DownloadStatusChangedEventArgs : EventArgs
    {
        public DownloadStatusChangedEventArgs(bool canResume)
        {
            ResumeSupported = canResume;
        }
        public bool ResumeSupported { get; private set; }
    }

    public class DownloadProgressChangedEventArgs : EventArgs
    {
        public DownloadProgressChangedEventArgs(long totalReceived, long fileSize, long currentSpeed)
        {
            BytesReceived = totalReceived;
            TotalBytesToReceive = fileSize;
            CurrentSpeed = currentSpeed;
        }

        public long BytesReceived { get; private set; }
        public long TotalBytesToReceive { get; private set; }
        public float ProgressPercentage 
        { 
            get 
            { 
                return ((float)BytesReceived / (float)TotalBytesToReceive) * 100; 
            } 
        }
        public float CurrentSpeed { get; private set; } // in bytes
        public TimeSpan TimeLeft
        {
            get
            {
                var bytesRemainingtoBeReceived = TotalBytesToReceive - BytesReceived;
                return TimeSpan.FromSeconds(bytesRemainingtoBeReceived / CurrentSpeed);
            }
        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ You can write another question and see if there is more that can be done to make the code more readable or more efficient. \$\endgroup\$ – Malachi Jul 26 '15 at 13:31
  • \$\begingroup\$ Is that okay? Won't that be considered a duplicate? \$\endgroup\$ – Expenzor Jul 26 '15 at 17:22
  • 1
    \$\begingroup\$ Posts are almost never closed as duplicates on CR. It's just if you post exactly the same code. If you made changes, post it as a follow-up. Follow-ups are regular here. \$\endgroup\$ – jacwah Jul 26 '15 at 18:14
0
\$\begingroup\$

I know this is quite an old post but for resumable download C# searches it comes up pretty much top.

I found this code really useful and have pretty much implemented it like for like into my project. My goal was to resume a download on loss of network connectivity.

The default timeout on the stream reader is 30 minutes and therefore found loss of network connectivity wasn't noticed for that long.

Inside this using statement

using (var stream = response.GetResponseStream())

you can do stream.Readtimeout = 60000

to reduce it

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.