Skip to content

Instantly share code, notes, and snippets.

@oferei
Last active July 25, 2022 14:05
Show Gist options
  • Save oferei/5069c726ba7764d743e5e34580533560 to your computer and use it in GitHub Desktop.
Save oferei/5069c726ba7764d743e5e34580533560 to your computer and use it in GitHub Desktop.
A patch for "Resumable File Downloader" package
A patch for Unity package "Resumable File Downloader"
https://www.assetstore.unity3d.com/en/#!/content/88831
Fixed a few issues:
1. If a partial file exists, download resumes instead of restarting
2. Added option to abort immediately (useful for application quit)
using System.Net;
using System;
using System.IO;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.IO.Compression;
namespace ribit
{
namespace Utils
{
//Creating an enum for different Dowmload Modes.
public enum DownloadMode
{
Resumable, NonResumable,
}
//Creating an enum for different Connection Types.
public enum ConnectionType
{
WIFI, MOBILENET, NOTREACHABLE
}
//This is the Download CLient Class Used for Downloading Files.
public class DownloadClient
{
private bool debug = false;
//Defining Different Variables for using it in the DownloadClient Class.
private long BytesDownloaded;
private long TotalBytesToDownload;
private long TotalBytesThisSession;
int BytesRead;
private Uri _url;
private string _downloadLocation;
private DownloadMode _mode;
private byte[] _Buffer;
bool _Cancelled;
bool _isbusy = false;
public bool Paused = false;
public bool _DownloadAnyway;
//Creating Constructor for DownloadCLient Class.
public DownloadClient(string url, string DownloadLocation, DownloadMode mode,bool DownloadAnyway = true)
{
_url = new Uri(url);
_downloadLocation = DownloadLocation;
_mode = mode;
_Buffer = new byte[1024 * 4];
_DownloadAnyway = DownloadAnyway;
}
public DownloadClient(string url, string DownloadLocation, DownloadMode mode, byte[] Buffer, bool DownloadAnyway = true)
{
_url = new Uri(url);
_downloadLocation = DownloadLocation;
_mode = mode;
_Buffer = Buffer;
_DownloadAnyway = DownloadAnyway;
}
//Defining the Thread Here.
Thread AsyncThread;
//Creating few Delegate Callbacks and event to report progress etc.
public delegate void DownloadProgressChanged(OnProgressChangedEvent e);
public delegate void DownloadCompleted(OnDownloadCompletedEvent e);
public event DownloadProgressChanged ProgressChangedEvent;
public event DownloadCompleted DownloadCompletedEvent;
//Start Download if the giving Client is not busy.
public void StartDownloadAsync()
{
//checking if we are not busy and then starting file download.
if (!_isbusy)
{
AsyncThread = new Thread(new ThreadStart(StartAsyncDownloadThread));
AsyncThread.Start();
}
}
//Cancels the given download.
public void CancelAsync()
{
_Cancelled = true;
}
//Cancels the given download instantly
public void CancelDownloadImmediately() {
AsyncThread.Abort();
}
//Pause/Resume functionality for the download
public void SwitchPause()
{
if (Paused)
{
Paused = false;
}
else
{
Paused = true;
}
}
//Starts the background thread for file download and reports the update to the delegates created above.
private void StartAsyncDownloadThread()
{
//trying and catching exception for creating download request.
try
{
//creating a webrequest and defining its method as GET to download file.
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url);
FileStream DownloadFile;
request.Method = "GET";
//Getting Total Size of the file to download
TotalBytesToDownload = request.GetResponse().ContentLength;
FileInfo FileToDownload = new FileInfo(_downloadLocation);
//Switching cases for resumable and non resumable as per the value passed in the constructor.
if (_mode == DownloadMode.NonResumable)
{
if (File.Exists(_downloadLocation))
{
if (FileToDownload.Length > 0 && File.Open(_downloadLocation, FileMode.Open).Length < FileToDownload.Length)
{
if (_DownloadAnyway)
{
FileToDownload.Delete();
}
else
{
if (debug)
UnityEngine.Debug.Log("File Already Exsists");
AsyncThread.Join();
return;
}
}
}
}
else if (_mode == DownloadMode.Resumable)
{
if (File.Exists(_downloadLocation))
{
if (debug)
UnityEngine.Debug.Log("File exists: size=" + FileToDownload.Length + " TotalBytesToDownload=" + TotalBytesToDownload);
if (_DownloadAnyway && FileToDownload.Length < TotalBytesToDownload)
{
request = (HttpWebRequest)WebRequest.Create(_url);
request.Method = "GET";
request.AddRange((int)FileToDownload.Length);
if (debug)
UnityEngine.Debug.Log("Request Headers: " + request.Headers.ToString());
}
else
{
if (debug)
UnityEngine.Debug.Log("File Already Exsists");
if (DownloadCompletedEvent != null)
DownloadCompletedEvent(new OnDownloadCompletedEvent(null, false));
AsyncThread.Join();
return;
}
}
}
//Getting response from the server.
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
//checking if the file exists and it exists and server supports partial content the append the incomming data of else create new file of 0B.
if (File.Exists(_downloadLocation))
{
if (response.StatusCode == HttpStatusCode.PartialContent)
{
DownloadFile = new FileStream(_downloadLocation, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
}
else
{
UnityEngine.Debug.LogWarning("Server does not support resumable download");
DownloadFile = new FileStream(_downloadLocation, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
DownloadFile.SetLength(0);
}
}
else {
DownloadFile = new FileStream(_downloadLocation, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
}
BytesDownloaded = DownloadFile.Length;
TotalBytesThisSession = 0;
if (debug) {
UnityEngine.Debug.Log("Response Headers: " + response.Headers);
UnityEngine.Debug.Log("Response Status: " + response.StatusDescription);
}
Stream ResponseStream = response.GetResponseStream();
//Writing Bytes to files
BytesRead = ResponseStream.Read(_Buffer, 0, _Buffer.Length);
while (BytesRead > 0 && !_Cancelled)
{
if (!Paused)
{
_isbusy = true;
DownloadFile.Write(_Buffer, 0, BytesRead);
BytesDownloaded += BytesRead;
TotalBytesThisSession += BytesRead;
BytesRead = ResponseStream.Read(_Buffer, 0, _Buffer.Length);
//Reporting progress if the given event is registered.
if (ProgressChangedEvent != null)
{
ProgressChangedEvent(new OnProgressChangedEvent(BytesDownloaded, TotalBytesToDownload, TotalBytesThisSession, Paused));
}
}
}
//Report the download completion at the end of while loop.
if (DownloadCompletedEvent != null)
{
if (!_Cancelled)
{
DownloadCompletedEvent(new OnDownloadCompletedEvent(null, false));
}
else
{
DownloadCompletedEvent(new OnDownloadCompletedEvent(null, true));
}
}
DownloadFile.Flush();
DownloadFile.Close();
response.Close();
AsyncThread.Join();
if (AsyncThread.ThreadState == ThreadState.Aborted)
{
_isbusy = false;
}
}
catch (Exception ex)
{
_isbusy = false;
if (DownloadCompletedEvent != null)
{
DownloadCompletedEvent(new OnDownloadCompletedEvent(ex, false));
}
}
}
}
//event class created for reporting progress.
public class OnProgressChangedEvent
{
public readonly long BytesReceived;
public readonly long TotalBytesToReceive;
public readonly long TotalBytesThisSession;
public readonly int Progress;
public readonly bool Paused;
public OnProgressChangedEvent(long bytesReceived, long totalBytesToReceive, long totalBytesThisSession, bool paused)
{
BytesReceived = bytesReceived;
TotalBytesToReceive = totalBytesToReceive;
TotalBytesThisSession = totalBytesThisSession;
Paused = paused;
if (Progress <= 100)
{
Progress = (int)((float)bytesReceived / totalBytesToReceive * 100);
}
else
{
Progress = 100;
}
}
}
public class OnDownloadCompletedEvent
{
public readonly Exception error;
public readonly bool Cancelled;
public OnDownloadCompletedEvent(Exception Error, bool cancelled)
{
error = Error;
Cancelled = cancelled;
}
}
//This class is used for checking internet connection and conection type.
public class ConnectionChecker
{
/// <summary>
/// This does not check connection exactly this is just to determine connection type.
/// </summary>
/// <returns></returns>
public static ConnectionType GetConnectionType()
{
if (UnityEngine.Application.internetReachability == UnityEngine.NetworkReachability.ReachableViaLocalAreaNetwork)
{
return ConnectionType.WIFI;
}
else if (UnityEngine.Application.internetReachability == UnityEngine.NetworkReachability.ReachableViaCarrierDataNetwork)
{
return ConnectionType.MOBILENET;
}
else
{
return ConnectionType.NOTREACHABLE;
}
}
/// <summary>
/// This can Check Internet Availability.
/// </summary>
/// <returns></returns>
public static bool GetConnectionStatus()
{
try
{
WebClient client = new WebClient();
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; });
client.OpenRead("https://www.google.com");
return true;
}
catch (Exception ex)
{
UnityEngine.Debug.LogError(ex);
return false;
}
}
}
}
}
using System.Net;
using System.ComponentModel;
using System;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Threading;
using ribit.Utils;
public class DownloadManager
{
#region Private Variables
DownloadClient Client;
private float DownloadSpeed;
private float DataDownloaded;
private float FileSize;
private float RemainingTime;
Stopwatch SW = new Stopwatch();
private string LogMessages;
private float Progress;
float LastBytesReceived;
string FileS;
private string DownloadFileName;
private bool IsDownloadComplete = false;
private bool IsDownloadCancelled = false;
private bool IsDownloadFailed = false;
private bool Paused;
#endregion
/// <summary>
/// By pass Ssl Certificate
/// </summary>
public enum RemainingTimeFormat
{
Format1,Format2,Format3,
}
/// <summary>
/// Start Downloading the file without disturbing the Ongoing Thread i.e asynchronously.
/// </summary>
/// <param name="URL">url of the file to be downloaded</param>
/// <param name="FileLocation">Loaction where to store the file after Download</param>
public void DownloadFileAsync(string URL, string FileLocation,DownloadMode Mode,string FileName = "",bool DownloadAnyway = true)
{
//creating a URI
Uri url = new Uri(URL);
//Getting DownloadFile name from the server.
DownloadFileName = System.IO.Path.GetFileName(url.LocalPath);
//Setting File Location and File name.
if (FileName != "")
{
Client = new DownloadClient(URL, FileLocation+"/"+FileName, Mode,DownloadAnyway);
}
else
{
Client = new DownloadClient(URL, FileLocation + "/" + DownloadFileName, Mode,DownloadAnyway);
}
//Registering to the events on DownloadClient Class.
Client.ProgressChangedEvent += OnProgressChanged;
Client.DownloadCompletedEvent += OnDownloadCompleted;
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; });
DownloadSpeed = 0; DataDownloaded = 0; FileSize = 0;
SW.Reset();
SW.Start();
Client.StartDownloadAsync();
IsDownloadCancelled = false;
}
/// <summary>
/// Called when there is change in Download Progress
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void OnProgressChanged(OnProgressChangedEvent e)
{
if (IsDownloadComplete == false)
{
DataDownloaded = e.BytesReceived;
FileSize = e.TotalBytesToReceive;
DownloadSpeed = (int)(e.TotalBytesThisSession / SW.Elapsed.TotalSeconds);
RemainingTime = (e.TotalBytesToReceive - e.BytesReceived) / DownloadSpeed;
Progress = e.Progress;
Paused = e.Paused;
if (e.Progress >= 100)
{
IsDownloadComplete = true;
}
if (e.Paused)
{
LogMessages = "Download Paused";
}
else
{
LogMessages = "Downloading " + GetDownloadFileName();
}
}
else
{
DataDownloaded = FileSize;
DownloadSpeed = 0;
RemainingTime = 0;
Progress = 100;
}
}
/// <summary>
/// Called When Download Completes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void OnDownloadCompleted(OnDownloadCompletedEvent e)
{
if(e.error != null)
{
if (e.error.GetType() != typeof(ThreadAbortException))
UnityEngine.Debug.LogError(e.error);
LogMessages = e.error.ToString();
IsDownloadFailed = true;
}else if (e.Cancelled)
{
LogMessages = "Download was cancelled by user";
IsDownloadCancelled = true;
}
else
{
LogMessages = "Download Complete";
IsDownloadComplete = true;
}
}
//Cancel the download.
public void CancleDownload()
{
Client.CancelAsync();
}
public void CancelDownloadImmediately() {
Client.CancelDownloadImmediately();
}
//Pause or Resume Download.
public void SwitchPause()
{
Client.SwitchPause();
}
//Amount of files downloaded in various units.
/// <summary>
/// Amount of file downloaded in Bytes(B)
/// </summary>
/// <returns></returns>
public float GetFileDownloadedInBytes()
{
return DataDownloaded;
}
/// <summary>
/// Amount of file downloaded in KiloBytes(KB)
/// </summary>
/// <returns>Data Downloaded in KB</returns>
public float GetFileDownloadedInKiloBytes()
{
return DataDownloaded / 1024;
}
/// <summary>
/// Amount of file downloaded in MegaBytes(MB)
/// </summary>
/// <returns>Data Downloaded in MB</returns>
public float GetFileDownloadedInMegaBytes()
{
return DataDownloaded / (1024 * 1024);
}
/// <summary>
/// Amount of file downloaded in GigaBytes(GB)
/// </summary>
/// <returns>Data Downloaded in GB</returns>
public float GetFileDownloadedInGigaBytes()
{
return DataDownloaded / (1024 * 1024 * 1024);
}
//File Size in Different Units
/// <summary>
/// Amount of File in Bytes(B)
/// </summary>
/// <returns></returns>
public float GetFileSizeInKiloBytes()
{
return FileSize;
}
/// <summary>
/// File Size in KiloBytes(KB)
/// </summary>
/// <returns></returns>
public float GetFileSizeInBytes()
{
return FileSize / 1024;
}
/// <summary>
/// File Size in MegaBytes(MB)
/// </summary>
/// <returns></returns>
public float GetFileSizeInMegaBytes()
{
return FileSize / (1024 * 1024);
}
/// <summary>
/// File Size in GigaBytes(GB)
/// </summary>
/// <returns></returns>
public float GetFileSizeInGigaBytes()
{
return FileSize / (1024 * 1024 * 1024);
}
//Download Speed in Different Units
/// <summary>
/// Download Speed in B/s
/// </summary>
/// <returns></returns>
public float GetDownloadSpeedInBytesPerSecond()
{
return DownloadSpeed;
}
/// <summary>
/// DownloadSpeed in KB/s
/// </summary>
/// <returns></returns>
public float GetDownloadSpeedInKiloBytesPerSecond()
{
return DownloadSpeed / 1024;
}
/// <summary>
/// Download Speed in MB/s
/// </summary>
/// <returns></returns>
public float GetDownloadSpeedInMegaBytesPerSecond()
{
return DownloadSpeed / (1024 * 1024);
}
//TimeRemaining
/// <summary>
/// Estimated Download Time in Seconds
/// </summary>
/// <returns></returns>
public float GetEstimatedTimeInSceconds()
{
return RemainingTime;
}
/// <summary>
/// Estimated Download Time in Minutes
/// </summary>
/// <returns></returns>
public float GetEstimatedTimeInMunites()
{
return RemainingTime / 60;
}
/// <summary>
/// Estimated Download Time in Hours
/// </summary>
/// <returns></returns>
public float GetEstimatedTimeInHours()
{
return RemainingTime / 360;
}
/// <summary>
/// Estimated Download Time in Days
/// </summary>
/// <returns></returns>
public float GetEstimatedTimeInDays()
{
return RemainingTime / (360 * 24);
}
//Progress
/// <summary>
/// Current Download Progress
/// </summary>
/// <returns></returns>
public float GetCurrentProgress()
{
return Progress;
}
public string GetLogMessages()
{
return LogMessages;
}
//Formated Strings
/// <summary>
/// returns a well formated string of download speed.
/// </summary>
/// <returns></returns>
public string GetDownloadSpeedStringFormated()
{
if (DownloadSpeed > 1024 && DownloadSpeed <= (1024*1024))
{
return string.Format("{0}KB/s", GetDownloadSpeedInKiloBytesPerSecond().ToString("0.0"));
}
else if (DownloadSpeed > (1024 * 1024) )
{
return string.Format("{0}MB/s", GetDownloadSpeedInMegaBytesPerSecond().ToString("0.0"));
}
else
{
return string.Format("{0}B/s", GetDownloadSpeedInBytesPerSecond().ToString("0.0"));
}
}
/// <summary>
/// returns a well formated string of remaining download time.
/// </summary>
/// <returns></returns>
public string GetRemainingTimeFormatedString(RemainingTimeFormat Format = RemainingTimeFormat.Format1)
{
int Seconds = ((int)GetEstimatedTimeInSceconds() % 60);
int Minutes = ((int)GetEstimatedTimeInSceconds()/60 ) % 60;
int Hours = ((int)GetEstimatedTimeInSceconds()/360) % 60;
int Days = ((int)GetEstimatedTimeInSceconds()/360*24)% 24;
if (Format == RemainingTimeFormat.Format1)
{
if (GetEstimatedTimeInSceconds() > (60 * 60 * 24))
{
return string.Format("{0} Days {1}Hours {2}Minutes {3}Seconds Left", Days, Hours, Minutes, Seconds);
}
else if (GetEstimatedTimeInSceconds() > 360)
{
return string.Format("{0}Hours {1}Minutes {2}Seconds Left", Hours, Minutes, Seconds);
}
else if (GetEstimatedTimeInSceconds() > 60)
{
return string.Format("{0}Minutes {1}Seconds Left", Minutes, Seconds);
}
else
{
return string.Format("{0}Seconds Left", Seconds);
}
}
else if(Format == RemainingTimeFormat.Format2)
{
if (GetEstimatedTimeInSceconds() > (60 * 60 * 24))
{
return string.Format("{0:0}:{1:00} Days Left", Days, Hours, Minutes, Seconds);
}
else if (GetEstimatedTimeInSceconds() > 360)
{
return string.Format("{0:0}:{1:00} hours Left", Hours, Minutes, Seconds);
}
else if (GetEstimatedTimeInSceconds() > 60)
{
return string.Format("{0:0}:{1:00} minutes Left", Minutes, Seconds);
}
else
{
return string.Format("{0}Seconds Left", Seconds);
}
}
else if (Format == RemainingTimeFormat.Format3)
{
if (GetEstimatedTimeInSceconds() > (60 * 60 * 24))
{
return string.Format("{0} Days Left", Days);
}
else if (GetEstimatedTimeInSceconds() > 360)
{
return string.Format("{0}Hours Left", Hours);
}
else if (GetEstimatedTimeInSceconds() > 60)
{
return string.Format("{0}Minutes Left", Minutes);
}
else
{
return string.Format("{0}Seconds Left", Seconds);
}
}
else
{
return null;
}
}
/// <summary>
/// returns well formated downloadprogress string
/// </summary>
/// <returns></returns>
public string GetFormatedDownloadProgress()
{
if (FileSize > 1024 && FileSize <= (1024*1024))
{
FileS = (FileSize / (1024)).ToString("0") + "KB";
}
else if (FileSize > (1024 * 1024) && FileSize <= (1024*1024*1024))
{
FileS = (FileSize / (1024 * 1024)).ToString("0") + "MB";
}
else if(FileSize > (1024 * 1024 * 1024))
{
FileS = (FileSize / (1024 * 1024 * 1024)).ToString("0") + "GB";
}
else
{
FileS = ((int)FileSize).ToString("0") + "B";
}
if (GetFileDownloadedInBytes() > 1024 && GetFileDownloadedInBytes() <= (1024 * 1024))
{
string s = GetFileDownloadedInKiloBytes().ToString("0") + "KB" +"/" + FileS;
return s;
}
else if (GetFileDownloadedInBytes() > (1024 * 1024) && GetFileDownloadedInBytes() <= (1024 * 1024 * 1024))
{
string s = GetFileDownloadedInMegaBytes().ToString("0") + "MB" + "/" + FileS;
return s;
}
else if (GetFileDownloadedInBytes() > (1024 * 1024 * 1024))
{
string s = GetFileDownloadedInGigaBytes().ToString("0") + "GB" + "/" + FileS;
return s;
}
else
{
string s = GetFileDownloadedInBytes().ToString("0") + "B" + "/" + FileS;
return s;
}
}
/// <summary>
/// Name of the file you are currently downloading
/// </summary>
/// <returns></returns>
public string GetDownloadFileName()
{
return DownloadFileName;
}
/// <summary>
/// returns true when download is complete
/// </summary>
/// <returns></returns>
public bool GetDownloadCompletionsStatus()
{
return IsDownloadComplete;
}
/// <summary>
/// returns true if user cancels the download.
/// </summary>
/// <returns></returns>
public bool GetCancellationStatus()
{
return IsDownloadCancelled;
}
public bool GetDownloadFailureStatus() {
return IsDownloadFailed;
}
/// <summary>
/// returns true is download is paused.
/// </summary>
/// <returns></returns>
public bool GetpauseStatus()
{
return Paused;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment