From 3d7159f65ac04b1703650511f6217937f2370caa Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Sat, 4 Aug 2018 18:26:01 -0500 Subject: [PATCH] continued the insanity of getting unity to behave --- IPA.Tests/updater_test.json | 2 +- IllusionInjector/IllusionInjector.csproj | 1 + .../Updating/ModsaberML/Updater.cs | 101 ++++++++++++------ IllusionInjector/Utilities/BlockingStream.cs | 39 +++++-- IllusionInjector/Utilities/EchoStream.cs | 40 +++++++ ...ionInjector.csproj.CoreCompileInputs.cache | 2 +- 6 files changed, 146 insertions(+), 39 deletions(-) create mode 100644 IllusionInjector/Utilities/EchoStream.cs diff --git a/IPA.Tests/updater_test.json b/IPA.Tests/updater_test.json index dda03aec..36fca4a0 100644 --- a/IPA.Tests/updater_test.json +++ b/IPA.Tests/updater_test.json @@ -40,7 +40,7 @@ "Mono.Cecil.dll": "762fb07e4d81722f0a766460289c6114cd4b7dae", "Plugins/SongLoaderPlugin.dll": "f4ab080fbcc5abc6005a868cd965920bac46ddaf" }, - "url": "https://www.modsaber.ml/cdn/song-loader/4.2.2-default.zip" + "url": "file://Z:/Users/aaron/Source/Repos/IPA-Reloaded-BeatSaber/IPA/bin/Debug/Debug.zip" } }, "weight": 7, diff --git a/IllusionInjector/IllusionInjector.csproj b/IllusionInjector/IllusionInjector.csproj index 9e6b3c5d..be93c80d 100644 --- a/IllusionInjector/IllusionInjector.csproj +++ b/IllusionInjector/IllusionInjector.csproj @@ -81,6 +81,7 @@ + diff --git a/IllusionInjector/Updating/ModsaberML/Updater.cs b/IllusionInjector/Updating/ModsaberML/Updater.cs index 90709fe1..b430a22d 100644 --- a/IllusionInjector/Updating/ModsaberML/Updater.cs +++ b/IllusionInjector/Updating/ModsaberML/Updater.cs @@ -151,49 +151,76 @@ namespace IllusionInjector.Updating.ModsaberML } } - class StreamDownloadHandler : DownloadHandlerScript + public class StreamDownloadHandler : DownloadHandlerScript { - public MemoryStream Stream { get; set; } + public BlockingStream Stream { get; set; } + + public StreamDownloadHandler(BlockingStream stream) + { + Stream = stream; + } protected void ReceiveContentLength(long contentLength) { - Stream.Capacity = (int)contentLength; + //(Stream.BaseStream as MemoryStream).Capacity = (int)contentLength; Logger.log.Debug($"Got content length: {contentLength}"); } protected void OnContentComplete() { + Stream.Open = false; Logger.log.Debug("Download complete"); } protected bool ReceiveData(byte[] data, long dataLength) { + Logger.log.Debug("ReceiveData"); + if (data == null || data.Length < 1) + { + Logger.log.Debug("CustomWebRequest :: ReceiveData - received a null/empty buffer"); + return false; + } + Stream.Write(data, 0, (int)dataLength); return true; } + + protected override byte[] GetData() { return null; } + + protected override float GetProgress() + { + return 0f; + } + + public override string ToString() + { + return $"{base.ToString()} ({Stream?.ToString()})"; + } + } - IEnumerator UpdateModCoroutine(string tempdir, UpdateStruct item) + private void DownloadPluginAsync(BlockingStream stream, UpdateStruct item, string tempdir) { - void DownloadPluginAsync(MemoryStream stream) - { // embedded because i don't think unity likes it in the top level - Logger.log.Debug($"Getting ZIP file for {item.plugin.Plugin.Name}"); - //var stream = await httpClient.GetStreamAsync(url); + Logger.log.Debug($"Getting ZIP file for {item.plugin.Plugin.Name}"); + //var stream = await httpClient.GetStreamAsync(url); - using (var zipFile = new ZipInputStream(new BlockingStream(stream))) + using (var zipFile = new ZipInputStream(stream)) + { + Logger.log.Debug("Streams opened"); + ZipEntry entry; + while ((entry = zipFile.GetNextEntry()) != null) { - Logger.log.Debug("Streams opened"); - ZipEntry entry; - while ((entry = zipFile.GetNextEntry()) != null) - { - Logger.log.Debug(entry?.FileName); - } + Logger.log.Debug(entry?.FileName ?? "NULL"); } - - Logger.log.Debug("Downloader exited"); } + Logger.log.Debug("Downloader exited"); + } + + IEnumerator UpdateModCoroutine(string tempdir, UpdateStruct item) + { + string url; if (SteamCheck.IsAvailable || item.externInfo.OculusFile == null) url = item.externInfo.SteamFile; @@ -201,33 +228,45 @@ namespace IllusionInjector.Updating.ModsaberML url = item.externInfo.OculusFile; Logger.log.Debug($"URL = {url}"); - - using (var req = UnityWebRequest.Get(url)) - using (var memStream = new MemoryStream()) + + using (var memStream = new EchoStream()) + using (var stream = new BlockingStream(memStream)) + using (var request = UnityWebRequest.Get(url)) + using (var taskTokenSource = new CancellationTokenSource()) { - req.downloadHandler = new StreamDownloadHandler - { - Stream = memStream - }; + var dlh = new StreamDownloadHandler(stream); + request.downloadHandler = dlh; var downloadTask = Task.Run(() => { // use slightly more multithreaded approach than coroutines - DownloadPluginAsync(memStream); - }); + DownloadPluginAsync(stream, item, tempdir); + }, taskTokenSource.Token); Logger.log.Debug("Sending request"); - yield return req.SendWebRequest(); + Logger.log.Debug(request?.downloadHandler?.ToString() ?? "DLH==NULL"); + yield return request.SendWebRequest(); + Logger.log.Debug("Download finished"); + + if (stream.Open) + { // anti-hang + Logger.log.Warn("Downloader failed to call DownloadHandler"); + stream.Open = false; // no more writing + stream.BaseStream.Write(new byte[] { 0 }, 0, 1); + } - if (req.isNetworkError) + if (request.isNetworkError) { Logger.log.Error("Network error while trying to update mod"); - Logger.log.Error(req.error); + Logger.log.Error(request.error); + taskTokenSource.Cancel(); yield break; } - if (req.isHttpError) + if (request.isHttpError) { Logger.log.Error($"Server returned an error code while trying to update mod"); - Logger.log.Error(req.error); + Logger.log.Error(request.error); + taskTokenSource.Cancel(); + yield break; } downloadTask.Wait(); // wait for the damn thing to finish diff --git a/IllusionInjector/Utilities/BlockingStream.cs b/IllusionInjector/Utilities/BlockingStream.cs index 45e4fb41..e23811bb 100644 --- a/IllusionInjector/Utilities/BlockingStream.cs +++ b/IllusionInjector/Utilities/BlockingStream.cs @@ -1,5 +1,7 @@ -using System; +using IllusionInjector.Logging; +using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -16,11 +18,27 @@ namespace IllusionInjector.Utilities public Stream BaseStream { get; set; } - public override bool CanRead => BaseStream.CanRead; + private bool _open = true; + public bool Open { + get + { + return CanWrite; + } + set + { + if (!_open) + throw new InvalidOperationException("Blocking stream has already been closed!"); + else + _open = value; + } + } + + private bool canReadOverride = true; + public override bool CanRead => BaseStream.CanRead && canReadOverride; public override bool CanSeek => BaseStream.CanSeek; - public override bool CanWrite => BaseStream.CanWrite; + public override bool CanWrite => BaseStream.CanWrite && _open; public override long Length => BaseStream.Length; @@ -34,13 +52,17 @@ namespace IllusionInjector.Utilities public override int Read(byte[] buffer, int offset, int count) { var read = 0; - do + while (read < count && Open) { read += BaseStream.Read(buffer, read, count-read); } - while (read < count); - return count; + if (read == 0) + { + canReadOverride = false; + } + + return read; } public override long Seek(long offset, SeekOrigin origin) @@ -57,5 +79,10 @@ namespace IllusionInjector.Utilities { BaseStream.Write(buffer, offset, count); } + + public override string ToString() + { + return $"{base.ToString()} ({BaseStream?.ToString()})"; + } } } diff --git a/IllusionInjector/Utilities/EchoStream.cs b/IllusionInjector/Utilities/EchoStream.cs new file mode 100644 index 00000000..6d1a3ca0 --- /dev/null +++ b/IllusionInjector/Utilities/EchoStream.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace IllusionInjector.Utilities +{ + public class EchoStream : MemoryStream + { + private ManualResetEvent m_dataReady = new ManualResetEvent(false); + private byte[] m_buffer; + private int m_offset; + private int m_count; + + public override void Write(byte[] buffer, int offset, int count) + { + m_buffer = buffer; + m_offset = offset; + m_count = count; + m_dataReady.Set(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (m_buffer == null) + { + // Block until the stream has some more data. + m_dataReady.Reset(); + m_dataReady.WaitOne(); + } + + Buffer.BlockCopy(m_buffer, m_offset, buffer, offset, (count < m_count) ? count : m_count); + m_buffer = null; + return (count < m_count) ? count : m_count; + } + } +} diff --git a/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache b/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache index ef1f81e3..c8be9064 100644 --- a/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache +++ b/IllusionInjector/obj/Debug/IllusionInjector.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -28cceae1ca85bfd009d7109ef2bb2d8a1412f4b3 +c0ee92dfbaba38e571471f2b468aeb2a80b0f76b