diff --git a/AaxDecrypter/AaxcDownloadConverter.cs b/AaxDecrypter/AaxcDownloadConverter.cs index 86794123..f9490ecc 100644 --- a/AaxDecrypter/AaxcDownloadConverter.cs +++ b/AaxDecrypter/AaxcDownloadConverter.cs @@ -112,7 +112,6 @@ namespace AaxDecrypter { nfsPersister = NewNetworkFilePersister(); } - nfsPersister.NetworkFileStream.BeginDownloading(); aaxFile = new AaxFile(nfsPersister.NetworkFileStream); diff --git a/AaxDecrypter/NetworkFileStream.cs b/AaxDecrypter/NetworkFileStream.cs index d3849992..eae48ccd 100644 --- a/AaxDecrypter/NetworkFileStream.cs +++ b/AaxDecrypter/NetworkFileStream.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Net; using System.Threading; -using System.Threading.Tasks; namespace AaxDecrypter { @@ -27,7 +26,7 @@ namespace AaxDecrypter public CookieCollection GetCookies() { - return base.GetCookies(Uri); + return GetCookies(Uri); } } @@ -79,15 +78,14 @@ namespace AaxDecrypter #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; } private bool isCancelled { get; set; } - private bool finishedDownloading { get; set; } - private Action downloadThreadCompleteCallback { get; set; } + private EventWaitHandle downloadEnded { get; set; } + private EventWaitHandle downloadedPiece { get; set; } #endregion @@ -147,7 +145,7 @@ namespace AaxDecrypter private void Update() { RequestHeaders = HttpRequest.Headers; - Updated?.Invoke(this, new EventArgs()); + Updated?.Invoke(this, EventArgs.Empty); } /// @@ -160,8 +158,8 @@ 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 && !finishedDownloading) - throw new Exception("Cannot change Uri during a download operation."); + if (hasBegunDownloading) + throw new InvalidOperationException("Cannot change Uri after download has started."); Uri = uriToSameFile; HttpRequest = WebRequest.CreateHttp(Uri); @@ -176,25 +174,27 @@ namespace AaxDecrypter /// /// Begins downloading to in a background thread. /// - public void BeginDownloading() + private void BeginDownloading() { + downloadEnded = new EventWaitHandle(false, EventResetMode.ManualReset); + if (ContentLength != 0 && WritePosition == ContentLength) { hasBegunDownloading = true; - finishedDownloading = true; + downloadEnded.Set(); return; } if (ContentLength != 0 && WritePosition > ContentLength) - throw new Exception($"Specified write position (0x{WritePosition:X10}) is larger than the file size."); + throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10})."); var response = HttpRequest.GetResponse() as HttpWebResponse; if (response.StatusCode != HttpStatusCode.PartialContent) - throw new Exception($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}."); + throw new WebException($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}."); if (response.Headers.GetValues("Accept-Ranges").FirstOrDefault(r => r.EqualsInsensitive("bytes")) is null) - throw new Exception($"Server at {Uri.Host} does not support Http ranges"); + throw new WebException($"Server at {Uri.Host} does not support Http ranges"); //Content length is the length of the range request, and it is only equal //to the complete file length if requesting Range: bytes=0- @@ -202,10 +202,12 @@ namespace AaxDecrypter ContentLength = response.ContentLength; _networkStream = response.GetResponseStream(); + downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset); //Download the file in the background. - Thread downloadThread = new Thread(() => DownloadFile()) { IsBackground = true }; - downloadThread.Start(); + new Thread(() => DownloadFile()) + { IsBackground = true } + .Start(); hasBegunDownloading = true; return; @@ -216,13 +218,13 @@ namespace AaxDecrypter /// private void DownloadFile() { - long downloadPosition = WritePosition; - long nextFlush = downloadPosition + DATA_FLUSH_SZ; + var downloadPosition = WritePosition; + var nextFlush = downloadPosition + DATA_FLUSH_SZ; - byte[] buff = new byte[DOWNLOAD_BUFF_SZ]; + var buff = new byte[DOWNLOAD_BUFF_SZ]; do { - int bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ); + var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ); _writeFile.Write(buff, 0, bytesRead); downloadPosition += bytesRead; @@ -233,6 +235,7 @@ namespace AaxDecrypter WritePosition = downloadPosition; Update(); nextFlush = downloadPosition + DATA_FLUSH_SZ; + downloadedPiece.Set(); } } while (downloadPosition < ContentLength && !isCancelled); @@ -243,13 +246,12 @@ namespace AaxDecrypter _networkStream.Close(); if (!isCancelled && WritePosition < ContentLength) - throw new Exception("File download ended before finishing."); + throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10})."); if (WritePosition > ContentLength) - throw new Exception("Downloaded file is larger than expected."); + throw new WebException($"Downloaded size (0x{WritePosition:X10}) is greater than {nameof(ContentLength)} (0x{ContentLength:X10})."); - finishedDownloading = true; - downloadThreadCompleteCallback?.Invoke(); + downloadEnded.Set(); } #endregion @@ -330,9 +332,7 @@ namespace AaxDecrypter var result = new WebHeaderCollection(); foreach (var kvp in jObj) - { result.Add(kvp.Key, kvp.Value.Value()); - } return result; } @@ -341,8 +341,8 @@ namespace AaxDecrypter public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - JObject jObj = new JObject(); - Type type = value.GetType(); + 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); @@ -364,13 +364,21 @@ namespace AaxDecrypter public override bool CanWrite => false; [JsonIgnore] - public override long Length => ContentLength; + public override long Length + { + get + { + if (!hasBegunDownloading) + BeginDownloading(); + return ContentLength; + } + } [JsonIgnore] public override long Position { get => _readFile.Position; set => Seek(value, SeekOrigin.Begin); } [JsonIgnore] - public override bool CanTimeout => base.CanTimeout; + public override bool CanTimeout => false; [JsonIgnore] public override int ReadTimeout { get => base.ReadTimeout; set => base.ReadTimeout = value; } @@ -387,69 +395,45 @@ namespace AaxDecrypter if (!hasBegunDownloading) BeginDownloading(); - long toRead = Math.Min(count, Length - Position); - long requiredPosition = Position + toRead; - - //read operation will block until file contains enough data - //to fulfil the request, or until cancelled. - while (requiredPosition > WritePosition && !isCancelled) - Thread.Sleep(2); - + var toRead = Math.Min(count, Length - Position); + WaitToPosition(Position + toRead); return _readFile.Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { - long newPosition; + var newPosition = origin switch + { + SeekOrigin.Current => Position + offset, + SeekOrigin.End => ContentLength + offset, + _ => offset, + }; - switch (origin) - { - case SeekOrigin.Current: - newPosition = Position + offset; - break; - case SeekOrigin.End: - newPosition = ContentLength + offset; - break; - default: - newPosition = offset; - break; - } - ReadToPosition(newPosition); - - _readFile.Position = newPosition; - return newPosition; + WaitToPosition(newPosition); + return _readFile.Position = newPosition; } /// - /// Ensures that the file has downloaded to at least , then returns. + /// Blocks until the file has downloaded to at least , then returns. /// - /// The minimum required data length in . - private void ReadToPosition(long neededPosition) - { - byte[] buff = new byte[DOWNLOAD_BUFF_SZ]; - do - { - Read(buff, 0, DOWNLOAD_BUFF_SZ); - } while (neededPosition > WritePosition); + /// The minimum required flished data length in . + private void WaitToPosition(long requiredPosition) + { + while (requiredPosition > WritePosition && !isCancelled && hasBegunDownloading && !downloadedPiece.WaitOne(1000)) ; } + public override void Close() { isCancelled = true; - downloadThreadCompleteCallback = CloseAction; - //ensure that close will run even if called after callback was fired. - if (finishedDownloading) - CloseAction(); + while (downloadEnded is not null && !downloadEnded.WaitOne(1000)) ; - } - private void CloseAction() - { _readFile.Close(); _writeFile.Close(); _networkStream?.Close(); Update(); } - + #endregion } }