diff --git a/Source/AaxDecrypter/AudiobookDownloadBase.cs b/Source/AaxDecrypter/AudiobookDownloadBase.cs
index d6529b39..f2347e94 100644
--- a/Source/AaxDecrypter/AudiobookDownloadBase.cs
+++ b/Source/AaxDecrypter/AudiobookDownloadBase.cs
@@ -52,7 +52,7 @@ namespace AaxDecrypter
// delete file after validation is complete
FileUtility.SaferDelete(OutputFileName);
- }
+ }
public abstract Task CancelAsync();
@@ -72,7 +72,7 @@ namespace AaxDecrypter
=> RetrievedNarrators?.Invoke(this, narrators);
protected void OnRetrievedCoverArt(byte[] coverArt)
=> RetrievedCoverArt?.Invoke(this, coverArt);
- protected void OnDecryptProgressUpdate(DownloadProgress downloadProgress)
+ protected void OnDecryptProgressUpdate(DownloadProgress downloadProgress)
=> DecryptProgressUpdate?.Invoke(this, downloadProgress);
protected void OnDecryptTimeRemaining(TimeSpan timeRemaining)
=> DecryptTimeRemaining?.Invoke(this, timeRemaining);
@@ -111,8 +111,8 @@ namespace AaxDecrypter
{
FileUtility.SaferDelete(jsonDownloadState);
- if (DownloadOptions.AudibleKey is not null &&
- DownloadOptions.AudibleIV is not null &&
+ if (DownloadOptions.AudibleKey is not null &&
+ DownloadOptions.AudibleIV is not null &&
DownloadOptions.RetainEncryptedFile)
{
string aaxPath = Path.ChangeExtension(TempFilePath, ".aax");
@@ -156,12 +156,7 @@ namespace AaxDecrypter
private NetworkFileStreamPersister NewNetworkFilePersister()
{
- var headers = new System.Net.WebHeaderCollection
- {
- { "User-Agent", DownloadOptions.UserAgent }
- };
-
- var networkFileStream = new NetworkFileStream(TempFilePath, new Uri(DownloadOptions.DownloadUrl), 0, headers);
+ var networkFileStream = new NetworkFileStream(TempFilePath, new Uri(DownloadOptions.DownloadUrl), 0, new() { { "User-Agent", DownloadOptions.UserAgent } });
return new NetworkFileStreamPersister(networkFileStream, jsonDownloadState);
}
}
diff --git a/Source/AaxDecrypter/NetworkFileStream.cs b/Source/AaxDecrypter/NetworkFileStream.cs
index d03e44f8..0b7b1d79 100644
--- a/Source/AaxDecrypter/NetworkFileStream.cs
+++ b/Source/AaxDecrypter/NetworkFileStream.cs
@@ -1,35 +1,16 @@
using Dinah.Core;
using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Threading;
+using System.Threading.Tasks;
namespace AaxDecrypter
{
- ///
- /// A for a single Uri.
- ///
- public class SingleUriCookieContainer : CookieContainer
- {
- private Uri baseAddress;
- public Uri Uri
- {
- get => baseAddress;
- set
- {
- baseAddress = new UriBuilder(value.Scheme, value.Host).Uri;
- }
- }
-
- public CookieCollection GetCookies()
- {
- return GetCookies(Uri);
- }
- }
-
///
/// A resumable, simultaneous file downloader and reader.
///
@@ -51,17 +32,11 @@ namespace AaxDecrypter
[JsonProperty(Required = Required.Always)]
public Uri Uri { get; private set; }
- ///
- /// All cookies set by caller or by the remote server.
- ///
- [JsonProperty(Required = Required.Always)]
- public SingleUriCookieContainer CookieContainer { get; }
-
///
/// Http headers to be sent to the server with the request.
///
[JsonProperty(Required = Required.Always)]
- public WebHeaderCollection RequestHeaders { get; private set; }
+ public Dictionary RequestHeaders { get; private set; }
///
/// The position in that has been written and flushed to disk.
@@ -75,17 +50,16 @@ namespace AaxDecrypter
[JsonProperty(Required = Required.Always)]
public long ContentLength { get; private set; }
+ [JsonIgnore]
+ public bool IsCancelled { get; private set; }
+
#endregion
#region Private Properties
- private HttpWebRequest HttpRequest { get; set; }
private FileStream _writeFile { get; }
private FileStream _readFile { get; }
- private Stream _networkStream { get; set; }
- private bool hasBegunDownloading { get; set; }
- public bool IsCancelled { get; private set; }
- private EventWaitHandle downloadEnded { get; set; }
- private EventWaitHandle downloadedPiece { get; set; }
+ private EventWaitHandle _downloadedPiece { get; set; }
+ private Task _backgroundDownloadTask { get; set; }
#endregion
@@ -110,7 +84,7 @@ namespace AaxDecrypter
/// The position in to begin downloading.
/// Http headers to be sent to the server with the .
/// A with cookies to send with the . It will also be populated with any cookies set by the server.
- public NetworkFileStream(string saveFilePath, Uri uri, long writePosition = 0, WebHeaderCollection requestHeaders = null, SingleUriCookieContainer cookies = null)
+ public NetworkFileStream(string saveFilePath, Uri uri, long writePosition = 0, Dictionary requestHeaders = null)
{
ArgumentValidator.EnsureNotNullOrWhiteSpace(saveFilePath, nameof(saveFilePath));
ArgumentValidator.EnsureNotNullOrWhiteSpace(uri?.AbsoluteUri, nameof(uri));
@@ -122,8 +96,7 @@ namespace AaxDecrypter
SaveFilePath = saveFilePath;
Uri = uri;
WritePosition = writePosition;
- RequestHeaders = requestHeaders ?? new WebHeaderCollection();
- CookieContainer = cookies ?? new SingleUriCookieContainer { Uri = uri };
+ RequestHeaders = requestHeaders ?? new();
_writeFile = new FileStream(SaveFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)
{
@@ -144,7 +117,7 @@ namespace AaxDecrypter
///
private void Update()
{
- RequestHeaders = HttpRequest.Headers;
+ RequestHeaders["Range"] = $"bytes={WritePosition}-";
try
{
Updated?.Invoke(this, EventArgs.Empty);
@@ -165,37 +138,31 @@ namespace AaxDecrypter
if (uriToSameFile.Host != Uri.Host)
throw new ArgumentException($"New uri to the same file must have the same host.\r\n Old Host :{Uri.Host}\r\nNew Host: {uriToSameFile.Host}");
- if (hasBegunDownloading)
+ if (_backgroundDownloadTask is not null)
throw new InvalidOperationException("Cannot change Uri after download has started.");
Uri = uriToSameFile;
- HttpRequest = WebRequest.CreateHttp(Uri);
-
- HttpRequest.CookieContainer = CookieContainer;
- HttpRequest.Headers = RequestHeaders;
- //If NetworkFileStream is resuming, Header will already contain a range.
- HttpRequest.Headers.Remove("Range");
- HttpRequest.AddRange(WritePosition);
+ RequestHeaders["Range"] = $"bytes={WritePosition}-";
}
///
/// Begins downloading to in a background thread.
///
- private void BeginDownloading()
+ private Task BeginDownloading()
{
- downloadEnded = new EventWaitHandle(false, EventResetMode.ManualReset);
-
if (ContentLength != 0 && WritePosition == ContentLength)
- {
- hasBegunDownloading = true;
- downloadEnded.Set();
- return;
- }
+ return Task.CompletedTask;
if (ContentLength != 0 && WritePosition > ContentLength)
throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10}).");
- var response = HttpRequest.GetResponse() as HttpWebResponse;
+
+ var request = new HttpRequestMessage(HttpMethod.Get, Uri);
+
+ foreach (var header in RequestHeaders)
+ request.Headers.Add(header.Key, header.Value);
+
+ var response = new HttpClient().Send(request, HttpCompletionOption.ResponseHeadersRead);
if (response.StatusCode != HttpStatusCode.PartialContent)
throw new WebException($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}.");
@@ -203,24 +170,19 @@ namespace AaxDecrypter
//Content length is the length of the range request, and it is only equal
//to the complete file length if requesting Range: bytes=0-
if (WritePosition == 0)
- ContentLength = response.ContentLength;
+ ContentLength = response.Content.Headers.ContentLength.GetValueOrDefault();
- _networkStream = response.GetResponseStream();
- downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset);
+ var networkStream = response.Content.ReadAsStream();
+ _downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset);
//Download the file in the background.
- new Thread(() => DownloadFile())
- { IsBackground = true }
- .Start();
-
- hasBegunDownloading = true;
- return;
+ return Task.Run(() => DownloadFile(networkStream));
}
///
/// Download to .
///
- private void DownloadFile()
+ private void DownloadFile(Stream networkStream)
{
var downloadPosition = WritePosition;
var nextFlush = downloadPosition + DATA_FLUSH_SZ;
@@ -231,7 +193,7 @@ namespace AaxDecrypter
int bytesRead;
do
{
- bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ);
+ bytesRead = networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ);
_writeFile.Write(buff, 0, bytesRead);
downloadPosition += bytesRead;
@@ -242,15 +204,12 @@ namespace AaxDecrypter
WritePosition = downloadPosition;
Update();
nextFlush = downloadPosition + DATA_FLUSH_SZ;
- downloadedPiece.Set();
+ _downloadedPiece.Set();
}
} while (downloadPosition < ContentLength && !IsCancelled && bytesRead > 0);
- _writeFile.Close();
- _networkStream.Close();
WritePosition = downloadPosition;
- Update();
if (!IsCancelled && WritePosition < ContentLength)
throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10}).");
@@ -264,8 +223,10 @@ namespace AaxDecrypter
}
finally
{
- downloadedPiece.Set();
- downloadEnded.Set();
+ networkStream.Close();
+ _writeFile.Close();
+ _downloadedPiece.Set();
+ Update();
}
}
@@ -274,96 +235,7 @@ namespace AaxDecrypter
#region Json Connverters
public static JsonSerializerSettings GetJsonSerializerSettings()
- {
- var settings = new JsonSerializerSettings();
- settings.Converters.Add(new CookieContainerConverter());
- settings.Converters.Add(new WebHeaderCollectionConverter());
- return settings;
- }
-
- internal class CookieContainerConverter : JsonConverter
- {
- public override bool CanConvert(Type objectType)
- => objectType == typeof(SingleUriCookieContainer);
-
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
- {
- var jObj = JObject.Load(reader);
-
- var result = new SingleUriCookieContainer()
- {
- Uri = new Uri(jObj["Uri"].Value()),
- Capacity = jObj["Capacity"].Value(),
- MaxCookieSize = jObj["MaxCookieSize"].Value(),
- PerDomainCapacity = jObj["PerDomainCapacity"].Value()
- };
-
- var cookieList = jObj["Cookies"].ToList();
-
- foreach (var cookie in cookieList)
- {
- result.Add(
- new Cookie
- {
- Comment = cookie["Comment"].Value(),
- HttpOnly = cookie["HttpOnly"].Value(),
- Discard = cookie["Discard"].Value(),
- Domain = cookie["Domain"].Value(),
- Expired = cookie["Expired"].Value(),
- Expires = cookie["Expires"].Value(),
- Name = cookie["Name"].Value(),
- Path = cookie["Path"].Value(),
- Port = cookie["Port"].Value(),
- Secure = cookie["Secure"].Value(),
- Value = cookie["Value"].Value(),
- Version = cookie["Version"].Value(),
- });
- }
-
- return result;
- }
-
- public override bool CanWrite => true;
-
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
- {
- var cookies = value as SingleUriCookieContainer;
- var obj = (JObject)JToken.FromObject(value);
- var container = cookies.GetCookies();
- var propertyNames = container.Select(c => JToken.FromObject(c));
- obj.AddFirst(new JProperty("Cookies", new JArray(propertyNames)));
- obj.WriteTo(writer);
- }
- }
-
- internal class WebHeaderCollectionConverter : JsonConverter
- {
- public override bool CanConvert(Type objectType)
- => objectType == typeof(WebHeaderCollection);
-
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
- {
- var jObj = JObject.Load(reader);
- var result = new WebHeaderCollection();
-
- foreach (var kvp in jObj)
- result.Add(kvp.Key, kvp.Value.Value());
-
- return result;
- }
-
- public override bool CanWrite => true;
-
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
- {
- var jObj = new JObject();
- var type = value.GetType();
- var headers = value as WebHeaderCollection;
- var jHeaders = headers.AllKeys.Select(k => new JProperty(k, headers[k]));
- jObj.Add(jHeaders);
- jObj.WriteTo(writer);
- }
- }
+ => new JsonSerializerSettings();
#endregion
@@ -383,8 +255,7 @@ namespace AaxDecrypter
{
get
{
- if (!hasBegunDownloading)
- BeginDownloading();
+ _backgroundDownloadTask ??= BeginDownloading();
return ContentLength;
}
}
@@ -401,15 +272,14 @@ namespace AaxDecrypter
[JsonIgnore]
public override int WriteTimeout { get => base.WriteTimeout; set => base.WriteTimeout = value; }
- public override void Flush() => throw new NotImplementedException();
- public override void SetLength(long value) => throw new NotImplementedException();
- public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
+ public override void Flush() => throw new InvalidOperationException();
+ public override void SetLength(long value) => throw new InvalidOperationException();
+ public override void Write(byte[] buffer, int offset, int count) => throw new InvalidOperationException();
public override int Read(byte[] buffer, int offset, int count)
{
- if (!hasBegunDownloading)
- BeginDownloading();
-
+ _backgroundDownloadTask ??= BeginDownloading();
+
var toRead = Math.Min(count, Length - Position);
WaitToPosition(Position + toRead);
return _readFile.Read(buffer, offset, count);
@@ -435,31 +305,27 @@ namespace AaxDecrypter
private void WaitToPosition(long requiredPosition)
{
while (WritePosition < requiredPosition
- && hasBegunDownloading
- && !IsCancelled
- && !downloadEnded.WaitOne(0))
+ && _backgroundDownloadTask?.IsCompleted is false
+ && !IsCancelled)
{
- downloadedPiece.WaitOne(100);
+ _downloadedPiece.WaitOne(100);
}
}
public override void Close()
{
IsCancelled = true;
-
- while (downloadEnded is not null && !downloadEnded.WaitOne(100)) ;
+ _backgroundDownloadTask?.Wait();
_readFile.Close();
_writeFile.Close();
- _networkStream?.Close();
Update();
}
#endregion
~NetworkFileStream()
{
- downloadEnded?.Close();
- downloadedPiece?.Close();
+ _downloadedPiece?.Close();
}
}
}