DownloadUtility.cs 4.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. using System;
  2. using System.IO;
  3. using System.Net;
  4. using System.Net.Http;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using SingularityGroup.HotReload.Editor.Cli;
  8. namespace SingularityGroup.HotReload.Editor {
  9. static class DownloadUtility {
  10. const string baseUrl = "https://cdn.hotreload.net";
  11. public static async Task<DownloadResult> DownloadFile(string url, string targetFilePath, IProgress<float> progress, CancellationToken cancellationToken) {
  12. var tmpDir = Path.GetDirectoryName(targetFilePath);
  13. Directory.CreateDirectory(tmpDir);
  14. using(var client = HttpClientUtils.CreateHttpClient()) {
  15. client.Timeout = TimeSpan.FromMinutes(10);
  16. return await client.DownloadAsync(url, targetFilePath, progress, cancellationToken).ConfigureAwait(false);
  17. }
  18. }
  19. public static string GetPackagePrefix(string version) {
  20. if (PackageConst.IsAssetStoreBuild) {
  21. return $"releases/asset-store/{version.Replace('.', '-')}";
  22. }
  23. return $"releases/{version.Replace('.', '-')}";
  24. }
  25. public static string GetDownloadUrl(string key) {
  26. return $"{baseUrl}/{key}";
  27. }
  28. public static async Task<DownloadResult> DownloadAsync(this HttpClient client, string requestUri, string destinationFilePath, IProgress<float> progress, CancellationToken cancellationToken = default(CancellationToken)) {
  29. // Get the http headers first to examine the content length
  30. using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false)) {
  31. if (response.StatusCode != HttpStatusCode.OK) {
  32. throw new DownloadException($"Download failed with status code {response.StatusCode} and reason {response.ReasonPhrase}");
  33. }
  34. var contentLength = response.Content.Headers.ContentLength;
  35. if (!contentLength.HasValue) {
  36. throw new DownloadException("Download failed: Content length unknown");
  37. }
  38. using (var fs = new FileStream(destinationFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
  39. using (var download = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) {
  40. // Ignore progress reporting when no progress reporter was
  41. if (progress == null) {
  42. await download.CopyToAsync(fs).ConfigureAwait(false);
  43. } else {
  44. // Convert absolute progress (bytes downloaded) into relative progress (0% - 99.9%)
  45. var relativeProgress = new Progress<long>(totalBytes => progress.Report(Math.Min(99.9f, (float)totalBytes / contentLength.Value)));
  46. // Use extension method to report progress while downloading
  47. await download.CopyToAsync(fs, 81920, relativeProgress, cancellationToken).ConfigureAwait(false);
  48. }
  49. await fs.FlushAsync().ConfigureAwait(false);
  50. if (fs.Length != contentLength.Value) {
  51. throw new DownloadException("Download failed: download file is corrupted");
  52. }
  53. return new DownloadResult(HttpStatusCode.OK, null);
  54. }
  55. }
  56. }
  57. static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress, CancellationToken cancellationToken) {
  58. if (source == null)
  59. throw new ArgumentNullException(nameof(source));
  60. if (!source.CanRead)
  61. throw new ArgumentException("Has to be readable", nameof(source));
  62. if (destination == null)
  63. throw new ArgumentNullException(nameof(destination));
  64. if (!destination.CanWrite)
  65. throw new ArgumentException("Has to be writable", nameof(destination));
  66. if (bufferSize < 0)
  67. throw new ArgumentOutOfRangeException(nameof(bufferSize));
  68. var buffer = new byte[bufferSize];
  69. long totalBytesRead = 0;
  70. int bytesRead;
  71. while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) {
  72. await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
  73. totalBytesRead += bytesRead;
  74. progress?.Report(totalBytesRead);
  75. }
  76. }
  77. [Serializable]
  78. public class DownloadException : ApplicationException {
  79. public DownloadException(string message)
  80. : base(message) {
  81. }
  82. public DownloadException(string message, Exception innerException)
  83. : base(message, innerException) {
  84. }
  85. }
  86. }
  87. }