Merge pull request #19 from DanTheMan827/direct-extract

Extract directly from the download stream
This commit is contained in:
Pat Hartl 2023-04-09 19:28:24 -05:00 committed by GitHub
commit ac8263aa0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 493 additions and 170 deletions

View file

@ -1,23 +1,17 @@
using Playnite.SDK; using LANCommander.PlaynitePlugin.Helpers;
using Playnite.SDK.Models;
using Playnite.SDK.Plugins;
using LANCommander.SDK.Enums; using LANCommander.SDK.Enums;
using LANCommander.SDK.Extensions; using LANCommander.SDK.Extensions;
using LANCommander.SDK.Models;
using Playnite.SDK;
using Playnite.SDK.Models;
using Playnite.SDK.Plugins;
using SharpCompress.Common;
using SharpCompress.Readers;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Core;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NamingConventions;
using LANCommander.SDK.Models;
using System.Collections.ObjectModel;
using System.Web.Caching;
using LANCommander.PlaynitePlugin.Helpers;
namespace LANCommander.PlaynitePlugin namespace LANCommander.PlaynitePlugin
{ {
@ -45,19 +39,9 @@ namespace LANCommander.PlaynitePlugin
var gameId = Guid.Parse(Game.GameId); var gameId = Guid.Parse(Game.GameId);
var game = Plugin.LANCommander.GetGame(gameId); var game = Plugin.LANCommander.GetGame(gameId);
string tempDownloadLocation;
if (Plugin.DownloadCache.ContainsKey(gameId))
tempDownloadLocation = Plugin.DownloadCache[gameId];
else
{
tempDownloadLocation = Download(game);
Plugin.DownloadCache[gameId] = tempDownloadLocation;
}
var installDirectory = RetryHelper.RetryOnException(10, TimeSpan.FromMilliseconds(500), "", () => var installDirectory = RetryHelper.RetryOnException(10, TimeSpan.FromMilliseconds(500), "", () =>
{ {
return Extract(game, tempDownloadLocation); return DownloadAndExtract(game);
}); });
if (installDirectory == "") if (installDirectory == "")
@ -103,11 +87,61 @@ namespace LANCommander.PlaynitePlugin
Plugin.UpdateGame(manifest, gameId); Plugin.UpdateGame(manifest, gameId);
Plugin.DownloadCache.Remove(gameId); Plugin.DownloadCache.Remove(gameId);
File.Delete(tempDownloadLocation);
InvokeOnInstalled(new GameInstalledEventArgs(installInfo)); InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
} }
private string DownloadAndExtract(LANCommander.SDK.Models.Game game)
{
if (game == null)
{
throw new Exception("Game failed to download!");
}
var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename());
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
{
try
{
Directory.CreateDirectory(destination);
progress.ProgressMaxValue = 100;
progress.CurrentProgressValue = 0;
using (var gameStream = Plugin.LANCommander.StreamGame(game.Id))
using (var reader = ReaderFactory.Open(gameStream))
{
progress.ProgressMaxValue = gameStream.Length;
gameStream.OnProgress += (pos, len) =>
{
progress.CurrentProgressValue = pos;
};
reader.WriteAllToDirectory(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
catch (Exception ex)
{
if (Directory.Exists(destination))
{
Directory.Delete(destination, true);
}
}
},
new GlobalProgressOptions($"Downloading {game.Title}...")
{
IsIndeterminate = false,
Cancelable = false,
});
return destination;
}
private string Download(LANCommander.SDK.Models.Game game) private string Download(LANCommander.SDK.Models.Game game)
{ {
string tempFile = String.Empty; string tempFile = String.Empty;
@ -153,45 +187,23 @@ namespace LANCommander.PlaynitePlugin
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress => Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
{ {
ZipFile file = null; Directory.CreateDirectory(destination);
try using (var fs = File.OpenRead(archivePath))
using (var ts = new TrackableStream(fs))
using (var reader = ReaderFactory.Open(ts))
{ {
FileStream fs = File.OpenRead(archivePath); progress.ProgressMaxValue = ts.Length;
ts.OnProgress += (pos, len) =>
file = new ZipFile(fs);
progress.ProgressMaxValue = file.Count;
foreach (ZipEntry entry in file)
{ {
if (!entry.IsFile) progress.CurrentProgressValue = pos;
continue; };
byte[] buffer = new byte[4096]; reader.WriteAllToDirectory(destination, new ExtractionOptions()
var zipStream = file.GetInputStream(entry);
var entryDestination = Path.Combine(destination, entry.Name);
var entryDirectory = Path.GetDirectoryName(entryDestination);
if (!String.IsNullOrWhiteSpace(entryDirectory))
Directory.CreateDirectory(entryDirectory);
using (FileStream streamWriter = File.Create(entryDestination))
{
StreamUtils.Copy(zipStream, streamWriter, buffer);
}
progress.CurrentProgressValue = entry.ZipFileIndex;
}
}
finally
{
if (file != null)
{ {
file.IsStreamOwner = true; ExtractFullPath = true,
file.Close(); Overwrite = true
} });
} }
}, },
new GlobalProgressOptions($"Extracting {game.Title}...") new GlobalProgressOptions($"Extracting {game.Title}...")

View file

@ -34,9 +34,6 @@
<Reference Include="BeaconLib, Version=1.0.2.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="BeaconLib, Version=1.0.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\rix0rrr.BeaconLib.1.0.2\lib\net40\BeaconLib.dll</HintPath> <HintPath>..\packages\rix0rrr.BeaconLib.1.0.2\lib\net40\BeaconLib.dll</HintPath>
</Reference> </Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=1.4.2.13, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\packages\SharpZipLib.1.4.2\lib\netstandard2.0\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath> <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference> </Reference>
@ -48,6 +45,9 @@
<Reference Include="RestSharp, Version=106.15.0.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL"> <Reference Include="RestSharp, Version=106.15.0.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.106.15.0\lib\net452\RestSharp.dll</HintPath> <HintPath>..\packages\RestSharp.106.15.0\lib\net452\RestSharp.dll</HintPath>
</Reference> </Reference>
<Reference Include="SharpCompress, Version=0.33.0.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
<HintPath>..\packages\SharpCompress.0.33.0\lib\net462\SharpCompress.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath> <HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
@ -64,8 +64,11 @@
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath> <HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll</HintPath> <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding.CodePages, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encoding.CodePages.7.0.0\lib\net462\System.Text.Encoding.CodePages.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Text.Encodings.Web, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> <Reference Include="System.Text.Encodings.Web, Version=5.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encodings.Web.5.0.0\lib\net461\System.Text.Encodings.Web.dll</HintPath> <HintPath>..\packages\System.Text.Encodings.Web.5.0.0\lib\net461\System.Text.Encodings.Web.dll</HintPath>
@ -93,6 +96,7 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="TrackableStream.cs" />
<Compile Include="Extensions\MultiplayerInfoExtensions.cs" /> <Compile Include="Extensions\MultiplayerInfoExtensions.cs" />
<Compile Include="Helpers\RetryHelper.cs" /> <Compile Include="Helpers\RetryHelper.cs" />
<Compile Include="PowerShellRuntime.cs" /> <Compile Include="PowerShellRuntime.cs" />

View file

@ -1,7 +1,6 @@
using LANCommander.SDK; using LANCommander.SDK;
using LANCommander.SDK.Models; using LANCommander.SDK.Models;
using RestSharp; using RestSharp;
using RestSharp.Extensions;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
@ -9,10 +8,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Media.Converters;
using YamlDotNet.Core;
namespace LANCommander.PlaynitePlugin namespace LANCommander.PlaynitePlugin
{ {
@ -64,6 +60,20 @@ namespace LANCommander.PlaynitePlugin
return tempFile; return tempFile;
} }
private TrackableStream StreamRequest(string route)
{
route = route.TrimStart('/');
var client = new WebClient();
var tempFile = Path.GetTempFileName();
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
var ws = client.OpenRead(new Uri($"{Client.BaseUrl}{route}"));
return new TrackableStream(ws, true, Convert.ToInt64(client.ResponseHeaders["Content-Length"]));
}
public async Task<AuthResponse> AuthenticateAsync(string username, string password) public async Task<AuthResponse> AuthenticateAsync(string username, string password)
{ {
var response = await Client.ExecuteAsync<AuthResponse>(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest() var response = await Client.ExecuteAsync<AuthResponse>(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest()
@ -166,6 +176,11 @@ namespace LANCommander.PlaynitePlugin
return DownloadRequest($"/api/Games/{id}/Download", progressHandler, completeHandler); return DownloadRequest($"/api/Games/{id}/Download", progressHandler, completeHandler);
} }
public TrackableStream StreamGame(Guid id)
{
return StreamRequest($"/api/Games/{id}/Download");
}
public string DownloadArchive(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler) public string DownloadArchive(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
{ {
return DownloadRequest($"/api/Archives/Download/{id}", progressHandler, completeHandler); return DownloadRequest($"/api/Archives/Download/{id}", progressHandler, completeHandler);

View file

@ -1,28 +1,17 @@
using ICSharpCode.SharpZipLib.Core; using LANCommander.PlaynitePlugin.Extensions;
using ICSharpCode.SharpZipLib.Zip;
using LANCommander.PlaynitePlugin.Extensions;
using LANCommander.PlaynitePlugin.Services; using LANCommander.PlaynitePlugin.Services;
using LANCommander.SDK;
using Playnite.SDK; using Playnite.SDK;
using Playnite.SDK.Events; using Playnite.SDK.Events;
using Playnite.SDK.Models; using Playnite.SDK.Models;
using Playnite.SDK.Plugins; using Playnite.SDK.Plugins;
using System; using System;
using System.CodeDom.Compiler;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media; using System.Windows.Media;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using PN = Playnite; using PN = Playnite;
namespace LANCommander.PlaynitePlugin namespace LANCommander.PlaynitePlugin
@ -296,7 +285,8 @@ namespace LANCommander.PlaynitePlugin
Padding = new Thickness(10, 0, 10, 0), Padding = new Thickness(10, 0, 10, 0),
}, },
Activated = () => { Activated = () =>
{
ShowNameChangeWindow(); ShowNameChangeWindow();
} }
}; };

View file

@ -1,16 +1,17 @@
using ICSharpCode.SharpZipLib.Zip; using LANCommander.SDK;
using LANCommander.SDK; using Playnite.SDK;
using Playnite.SDK.Models; using Playnite.SDK.Models;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Readers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using ICSharpCode.SharpZipLib.Core; using YamlDotNet.Serialization.NamingConventions;
using Playnite.SDK;
namespace LANCommander.PlaynitePlugin.Services namespace LANCommander.PlaynitePlugin.Services
{ {
@ -20,7 +21,8 @@ namespace LANCommander.PlaynitePlugin.Services
private readonly IPlayniteAPI PlayniteApi; private readonly IPlayniteAPI PlayniteApi;
private readonly PowerShellRuntime PowerShellRuntime; private readonly PowerShellRuntime PowerShellRuntime;
internal GameSaveService(LANCommanderClient lanCommander, IPlayniteAPI playniteApi, PowerShellRuntime powerShellRuntime) { internal GameSaveService(LANCommanderClient lanCommander, IPlayniteAPI playniteApi, PowerShellRuntime powerShellRuntime)
{
LANCommander = lanCommander; LANCommander = lanCommander;
PlayniteApi = playniteApi; PlayniteApi = playniteApi;
PowerShellRuntime = powerShellRuntime; PowerShellRuntime = powerShellRuntime;
@ -166,9 +168,9 @@ namespace LANCommander.PlaynitePlugin.Services
var manifest = deserializer.Deserialize<GameManifest>(File.ReadAllText(manifestPath)); var manifest = deserializer.Deserialize<GameManifest>(File.ReadAllText(manifestPath));
var temp = Path.GetTempFileName(); var temp = Path.GetTempFileName();
using (ZipOutputStream zipStream = new ZipOutputStream(File.Create(temp))) using (var archive = ZipArchive.Create())
{ {
zipStream.SetLevel(5); archive.DeflateCompressionLevel = SharpCompress.Compressors.Deflate.CompressionLevel.BestCompression;
#region Add files from defined paths #region Add files from defined paths
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File")) foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
@ -177,18 +179,27 @@ namespace LANCommander.PlaynitePlugin.Services
if (Directory.Exists(localPath)) if (Directory.Exists(localPath))
{ {
AddDirectoryToZip(zipStream, localPath, localPath, savePath.Id); AddDirectoryToZip(archive, localPath, localPath, savePath.Id);
} }
else if (File.Exists(localPath)) else if (File.Exists(localPath))
{ {
var entry = new ZipEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", ""))); archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath);
}
}
#endregion
zipStream.PutNextEntry(entry); #region Add files from defined paths
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
{
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
byte[] buffer = File.ReadAllBytes(localPath); if (Directory.Exists(localPath))
{
zipStream.Write(buffer, 0, buffer.Length); AddDirectoryToZip(archive, localPath, localPath, savePath.Id);
zipStream.CloseEntry(); }
else if (File.Exists(localPath))
{
archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath);
} }
} }
#endregion #endregion
@ -218,47 +229,32 @@ namespace LANCommander.PlaynitePlugin.Services
File.Delete(tempRegFile); File.Delete(tempRegFile);
} }
zipStream.PutNextEntry(new ZipEntry("_registry.reg")); archive.AddEntry("_registry.reg", new MemoryStream(Encoding.UTF8.GetBytes(exportFile.ToString())), true);
byte[] regBuffer = Encoding.UTF8.GetBytes(exportFile.ToString());
zipStream.Write(regBuffer, 0, regBuffer.Length);
zipStream.CloseEntry();
} }
#endregion #endregion
var manifestEntry = new ZipEntry("_manifest.yml"); archive.AddEntry("_manifest.yml", manifestPath);
zipStream.PutNextEntry(manifestEntry); using (var ms = new MemoryStream())
{
archive.SaveTo(ms);
byte[] manifestBuffer = File.ReadAllBytes(manifestPath); ms.Seek(0, SeekOrigin.Begin);
zipStream.Write(manifestBuffer, 0, manifestBuffer.Length); var save = LANCommander.UploadSave(game.GameId, ms.ToArray());
zipStream.CloseEntry(); }
} }
var save = LANCommander.UploadSave(game.GameId, File.ReadAllBytes(temp));
File.Delete(temp);
} }
} }
private void AddDirectoryToZip(ZipOutputStream zipStream, string path, string workingDirectory, Guid pathId) private void AddDirectoryToZip(ZipArchive zipArchive, string path, string workingDirectory, Guid pathId)
{ {
foreach (var file in Directory.GetFiles(path)) foreach (var file in Directory.GetFiles(path))
{ {
// Oh man is this a hack. We should be removing only the working directory from the start, // Oh man is this a hack. We should be removing only the working directory from the start,
// but we're making the assumption that the working dir put in actually prefixes the path. // but we're making the assumption that the working dir put in actually prefixes the path.
// Also wtf, that Path.Combine is stripping the pathId out? // Also wtf, that Path.Combine is stripping the pathId out?
var entry = new ZipEntry(Path.Combine(pathId.ToString(), path.Substring(workingDirectory.Length), Path.GetFileName(file))); zipArchive.AddEntry(Path.Combine(pathId.ToString(), path.Substring(workingDirectory.Length), Path.GetFileName(file)), file);
zipStream.PutNextEntry(entry);
byte[] buffer = File.ReadAllBytes(file);
zipStream.Write(buffer, 0, buffer.Length);
zipStream.CloseEntry();
} }
foreach (var child in Directory.GetDirectories(path)) foreach (var child in Directory.GetDirectories(path))
@ -269,47 +265,21 @@ namespace LANCommander.PlaynitePlugin.Services
//zipStream.PutNextEntry(entry); //zipStream.PutNextEntry(entry);
//zipStream.CloseEntry(); //zipStream.CloseEntry();
AddDirectoryToZip(zipStream, child, workingDirectory, pathId); AddDirectoryToZip(zipArchive, child, workingDirectory, pathId);
} }
} }
private void ExtractFilesFromZip(string zipPath, string destination) private void ExtractFilesFromZip(string zipPath, string destination)
{ {
ZipFile file = null; using (var fs = File.OpenRead(zipPath))
using (var ts = new TrackableStream(fs))
try using (var reader = ReaderFactory.Open(ts))
{ {
FileStream fs = File.OpenRead(zipPath); reader.WriteAllToDirectory(destination, new ExtractionOptions()
file = new ZipFile(fs);
foreach (ZipEntry entry in file)
{ {
if (!entry.IsFile) ExtractFullPath = true,
continue; Overwrite = true
});
byte[] buffer = new byte[4096];
var zipStream = file.GetInputStream(entry);
var entryDestination = Path.Combine(destination, entry.Name);
var entryDirectory = Path.GetDirectoryName(entryDestination);
if (!String.IsNullOrWhiteSpace(entryDirectory))
Directory.CreateDirectory(entryDirectory);
using (FileStream streamWriter = File.Create(entryDestination))
{
StreamUtils.Copy(zipStream, streamWriter, buffer);
}
}
}
finally
{
if (file != null)
{
file.IsStreamOwner = true;
file.Close();
}
} }
} }
} }

View file

@ -0,0 +1,340 @@
using System;
using System.IO;
namespace LANCommander.PlaynitePlugin
{
internal class TrackableStream : MemoryStream, IDisposable
{
public delegate void OnProgressDelegate(long Position, long Length);
public event OnProgressDelegate OnProgress = delegate { };
private long internalStreamProgress = 0;
private Stream internalStream;
private bool disposeStream = false;
private long? streamLength;
//
// Summary:
// Initializes a new instance of the TrackableStream class with an expandable
// capacity initialized to zero.
public TrackableStream() : base() { }
//
// Summary:
// Initializes a new instance of the TrackableStream class with the contents of stream.
// capacity initialized to zero.
public TrackableStream(Stream stream, bool disposeStream = false, long? streamLength = null) : base()
{
internalStream = stream;
this.disposeStream = disposeStream;
this.streamLength = streamLength;
}
//
// Summary:
// Initializes a new instance of the TrackableStream class with an expandable
// capacity initialized as specified.
//
// Parameters:
// capacity:
// The initial size of the internal array in bytes.
//
// Exceptions:
// T:System.ArgumentOutOfRangeException:
// capacity is negative.
public TrackableStream(int capacity) : base(capacity) { }
//
// Summary:
// Initializes a new non-resizable instance of the TrackableStream class
// based on the specified byte array.
//
// Parameters:
// buffer:
// The array of unsigned bytes from which to create the current stream.
//
// Exceptions:
// T:System.ArgumentNullException:
// buffer is null.
public TrackableStream(byte[] buffer) : base(buffer) { }
//
// Summary:
// Initializes a new non-resizable instance of the TrackableStream class
// based on the specified byte array with the TrackableStream.CanWrite property
// set as specified.
//
// Parameters:
// buffer:
// The array of unsigned bytes from which to create this stream.
//
// writable:
// The setting of the TrackableStream.CanWrite property, which determines
// whether the stream supports writing.
//
// Exceptions:
// T:System.ArgumentNullException:
// buffer is null.
public TrackableStream(byte[] buffer, bool writable) : base(buffer, writable) { }
//
// Summary:
// Initializes a new non-resizable instance of the TrackableStream class
// based on the specified region (index) of a byte array.
//
// Parameters:
// buffer:
// The array of unsigned bytes from which to create this stream.
//
// index:
// The index into buffer at which the stream begins.
//
// count:
// The length of the stream in bytes.
//
// Exceptions:
// T:System.ArgumentNullException:
// buffer is null.
//
// T:System.ArgumentOutOfRangeException:
// index or count is less than zero.
//
// T:System.ArgumentException:
// The buffer length minus index is less than count.
public TrackableStream(byte[] buffer, int index, int count) : base(buffer, index, count) { }
//
// Summary:
// Initializes a new non-resizable instance of the TrackableStream class
// based on the specified region of a byte array, with the TrackableStream.CanWrite
// property set as specified.
//
// Parameters:
// buffer:
// The array of unsigned bytes from which to create this stream.
//
// index:
// The index in buffer at which the stream begins.
//
// count:
// The length of the stream in bytes.
//
// writable:
// The setting of the TrackableStream.CanWrite property, which determines
// whether the stream supports writing.
//
// Exceptions:
// T:System.ArgumentNullException:
// buffer is null.
//
// T:System.ArgumentOutOfRangeException:
// index or count are negative.
//
// T:System.ArgumentException:
// The buffer length minus index is less than count.
public TrackableStream(byte[] buffer, int index, int count, bool writable) : base(buffer, index, count, writable) { }
//
// Summary:
// Initializes a new instance of the TrackableStream class based on the specified
// region of a byte array, with the TrackableStream.CanWrite property set
// as specified, and the ability to call TrackableStream.GetBuffer set as
// specified.
//
// Parameters:
// buffer:
// The array of unsigned bytes from which to create this stream.
//
// index:
// The index into buffer at which the stream begins.
//
// count:
// The length of the stream in bytes.
//
// writable:
// The setting of the TrackableStream.CanWrite property, which determines
// whether the stream supports writing.
//
// publiclyVisible:
// true to enable TrackableStream.GetBuffer, which returns the unsigned byte
// array from which the stream was created; otherwise, false.
//
// Exceptions:
// T:System.ArgumentNullException:
// buffer is null.
//
// T:System.ArgumentOutOfRangeException:
// index or count is negative.
//
// T:System.ArgumentException:
// The buffer length minus index is less than count.
public TrackableStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible) : base(buffer, index, count, writable, publiclyVisible) { }
//
// Summary:
// Writes a block of bytes to the current stream using data read from a buffer.
//
// Parameters:
// buffer:
// The buffer to write data from.
//
// offset:
// The zero-based byte offset in buffer at which to begin copying bytes to the current
// stream.
//
// count:
// The maximum number of bytes to write.
//
// Exceptions:
// T:System.ArgumentNullException:
// buffer is null.
//
// T:System.NotSupportedException:
// The stream does not support writing. For additional information see System.IO.Stream.CanWrite.-or-
// The current position is closer than count bytes to the end of the stream, and
// the capacity cannot be modified.
//
// T:System.ArgumentException:
// offset subtracted from the buffer length is less than count.
//
// T:System.ArgumentOutOfRangeException:
// offset or count are negative.
//
// T:System.IO.IOException:
// An I/O error occurs.
//
// T:System.ObjectDisposedException:
// The current stream instance is closed.
public override void Write(byte[] array, int offset, int count)
{
if (internalStream is Stream)
{
internalStream.Write(array, offset, count);
OnProgress(internalStream.Position, internalStream.Length);
}
else
{
base.Write(array, offset, count);
OnProgress(this.Position, this.Length);
}
}
//
// Summary:
// Writes a byte to the current stream at the current position.
//
// Parameters:
// value:
// The byte to write.
//
// Exceptions:
// T:System.NotSupportedException:
// The stream does not support writing. For additional information see System.IO.Stream.CanWrite.-or-
// The current position is at the end of the stream, and the capacity cannot be
// modified.
//
// T:System.ObjectDisposedException:
// The current stream is closed.
public override void WriteByte(byte value)
{
if (internalStream is Stream)
{
internalStream.WriteByte(value);
OnProgress(internalStream.Position, internalStream.Length);
}
else
{
base.WriteByte(value);
OnProgress(this.Position, this.Length);
}
}
//
// Summary:
// Reads a block of bytes from the current stream and writes the data to a buffer.
//
// Parameters:
// buffer:
// When this method returns, contains the specified byte array with the values between
// offset and (offset + count - 1) replaced by the characters read from the current
// stream.
//
// offset:
// The zero-based byte offset in buffer at which to begin storing data from the
// current stream.
//
// count:
// The maximum number of bytes to read.
//
// Returns:
// The total number of bytes written into the buffer. This can be less than the
// number of bytes requested if that number of bytes are not currently available,
// or zero if the end of the stream is reached before any bytes are read.
//
// Exceptions:
// T:System.ArgumentNullException:
// buffer is null.
//
// T:System.ArgumentOutOfRangeException:
// offset or count is negative.
//
// T:System.ArgumentException:
// offset subtracted from the buffer length is less than count.
//
// T:System.ObjectDisposedException:
// The current stream instance is closed.
public override int Read(byte[] array, int offset, int count)
{
int r;
if (internalStream is Stream)
{
r = internalStream.Read(array, offset, count);
internalStreamProgress += r;
OnProgress(internalStreamProgress, this.Length);
}
else
{
r = base.Read(array, offset, count);
OnProgress(this.Position, this.Length);
}
return r;
}
//
// Summary:
// Reads a byte from the current stream.
//
// Returns:
// The byte cast to a System.Int32, or -1 if the end of the stream has been reached.
//
// Exceptions:
// T:System.ObjectDisposedException:
// The current stream instance is closed.
public override int ReadByte()
{
int r;
if (internalStream is Stream)
{
r = internalStream.ReadByte();
internalStreamProgress += r;
OnProgress(internalStreamProgress, this.Length);
}
else
{
r = base.ReadByte();
OnProgress(this.Position, this.Length);
}
return r;
}
public void Dispose()
{
if (disposeStream)
{
internalStream.Dispose();
}
}
public long Length => streamLength ?? internalStream.Length;
}
}

View file

@ -1,17 +1,8 @@
using Playnite.SDK; using LANCommander.SDK.Enums;
using Playnite.SDK.Models; using Playnite.SDK.Models;
using Playnite.SDK.Plugins; using Playnite.SDK.Plugins;
using LANCommander.SDK.Extensions;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Core;
using LANCommander.SDK.Enums;
namespace LANCommander.PlaynitePlugin namespace LANCommander.PlaynitePlugin
{ {

View file

@ -4,7 +4,7 @@
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" /> <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /> <assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />

View file

@ -6,11 +6,12 @@
<package id="PowerShellStandard.Library" version="5.1.1" targetFramework="net462" /> <package id="PowerShellStandard.Library" version="5.1.1" targetFramework="net462" />
<package id="RestSharp" version="106.15.0" targetFramework="net462" /> <package id="RestSharp" version="106.15.0" targetFramework="net462" />
<package id="rix0rrr.BeaconLib" version="1.0.2" targetFramework="net462" /> <package id="rix0rrr.BeaconLib" version="1.0.2" targetFramework="net462" />
<package id="SharpZipLib" version="1.4.2" targetFramework="net462" /> <package id="SharpCompress" version="0.33.0" targetFramework="net462" />
<package id="System.Buffers" version="4.5.1" targetFramework="net462" /> <package id="System.Buffers" version="4.5.1" targetFramework="net462" />
<package id="System.Memory" version="4.5.5" targetFramework="net462" /> <package id="System.Memory" version="4.5.5" targetFramework="net462" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net462" /> <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net462" />
<package id="System.Runtime.CompilerServices.Unsafe" version="5.0.0" targetFramework="net462" /> <package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net462" />
<package id="System.Text.Encoding.CodePages" version="7.0.0" targetFramework="net462" />
<package id="System.Text.Encodings.Web" version="5.0.0" targetFramework="net462" /> <package id="System.Text.Encodings.Web" version="5.0.0" targetFramework="net462" />
<package id="System.Text.Json" version="5.0.1" targetFramework="net462" /> <package id="System.Text.Json" version="5.0.1" targetFramework="net462" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net462" /> <package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net462" />