LANCommander/LANCommander.SDK/Clients/RedistributableClient.cs

530 lines
22 KiB
C#
Raw Permalink Normal View History

using LANCommander.SDK.Enums;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.Models;
using Microsoft.Extensions.Logging;
using SharpCompress.Common;
using SharpCompress.Readers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
using LANCommander.SDK.Abstractions;
using LANCommander.SDK.Exceptions;
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
using LANCommander.SDK.Factories;
namespace LANCommander.SDK.Services
{
public class RedistributableClient(
ILogger<RedistributableClient> logger,
ISettingsProvider settingsProvider,
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
ApiRequestFactory apiRequestFactory,
ScriptClient scriptClient,
ProfileClient profileClient)
{
public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e);
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress;
public delegate void OnArchiveExtractionProgressHandler(long position, long length);
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress;
public delegate void OnInstallProgressUpdateHandler(InstallProgress e);
public event OnInstallProgressUpdateHandler OnInstallProgressUpdate;
private InstallProgress _installProgress;
public async Task<SDK.Models.Manifest.Redistributable> GetManifestAsync(Guid id)
{
return await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
2026-03-20 01:09:29 -05:00
.UseRoute($"/api/Redistributables/{id}")
.GetAsync<SDK.Models.Manifest.Redistributable>();
}
public async Task<IEnumerable<Script>> GetScriptsAsync(Guid id)
{
return await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
2026-03-20 01:09:29 -05:00
.UseRoute($"/api/Redistributables/{id}/Scripts")
.GetAsync<IEnumerable<Script>>();
}
public async Task WriteScriptsAsync(Game game, Redistributable redistributable)
{
var scripts = await GetScriptsAsync(redistributable.Id);
if (scripts != null && scripts.Any())
{
logger?.LogTrace($"Saving scripts for redistributable {redistributable.Name} ({redistributable.Id}) into {game.InstallDirectory}");
foreach (var script in scripts)
2026-03-11 20:12:09 -05:00
await ScriptHelper.SaveScriptAsync(game, redistributable, script);
}
}
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
2026-06-10 21:51:12 -05:00
public async Task<bool> CheckForUpdateAsync(Guid id, string currentVersion)
{
return await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
.UseRoute($"/api/Redistributables/{id}/CheckForUpdate?version={currentVersion}")
.GetAsync<bool>();
}
public async Task<IEnumerable<Archive>> GetUpdatesAsync(Guid redistributableId, string version)
{
return await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
.UseRoute($"/api/Redistributables/{redistributableId}/Updates?version={version}")
.GetAsync<IEnumerable<Archive>>();
}
public async Task<Stream> Stream(Guid id)
{
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
return await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
2026-03-20 01:09:29 -05:00
.UseRoute($"/api/Redistributables/{id}/Download")
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
.StreamAsync();
}
2026-06-10 21:51:12 -05:00
private async Task<TrackableStream> StreamArchiveAsync(Guid archiveId)
{
return await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
.UseRoute($"/Download/Archive/{archiveId}")
.StreamAsync();
}
public async Task InstallAsync(Game game)
{
foreach (var redistributable in game.Redistributables)
{
await InstallAsync(redistributable, game);
}
}
public async Task InstallAsync(Redistributable redistributable, Game game, int maxAttempts = 10)
{
_installProgress = new InstallProgress();
_installProgress.Status = InstallStatus.Downloading;
_installProgress.Title = redistributable.Name;
_installProgress.Progress = 0;
_installProgress.TransferSpeed = 0;
_installProgress.TotalBytes = 0;
_installProgress.BytesTransferred = 0;
OnInstallProgressUpdate?.Invoke(_installProgress);
try
{
logger?.LogTrace("Saving manifest");
var manifest = await GetManifestAsync(redistributable.Id);
await ManifestHelper.WriteAsync(manifest, game.InstallDirectory);
logger?.LogTrace("Saving scripts");
await WriteScriptsAsync(game, redistributable);
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
var hasDetectInstallScript = redistributable.Scripts != null &&
redistributable.Scripts.Any(s => s.Type == Enums.ScriptType.DetectInstall);
var installed = hasDetectInstallScript &&
2026-02-10 17:34:35 -06:00
await scriptClient.Redistributable_RunDetectInstallScriptAsync(game.InstallDirectory, game.Id, redistributable.Id);
logger?.LogTrace("Redistributable install detection returned {Result} (hasDetectScript={HasDetectScript})", installed, hasDetectInstallScript);
if (!installed)
{
logger?.LogTrace("Redistributable {RedistributableName} not installed", redistributable.Name);
using (var fileTracker = new InstallDirectoryFileTracker(game.InstallDirectory))
{
if (redistributable.Archives?.Any() ?? false)
{
logger?.LogTrace("Archives for redistributable {RedistributableName} exist. Attempting to download...", redistributable.Name);
var result = await RetryHelper.RetryOnExceptionAsync(maxAttempts,
TimeSpan.FromMilliseconds(500), new ExtractionResult(),
async () =>
{
logger?.LogTrace("Attempting to download and extract redistributable");
return await DownloadAndExtractAsync(redistributable, game, CancellationToken.None);
});
if (!result.Success && !result.Canceled)
throw new InstallException("Could not extract the redistributable. Retry the install or check your connection");
2026-05-24 00:06:46 -05:00
if (result.Canceled)
throw new InstallCanceledException("Redistributable install canceled");
logger?.LogTrace("Extraction of redistributable successful. Extracted path is {Path}", result.Directory);
logger?.LogTrace("Running install script for redistributable {RedistributableName}", redistributable.Name);
await RunPostInstallScripts(game, redistributable);
}
else
{
logger?.LogTrace("No archives exist for redistributable {RedistributableName}. Running install script anyway...", redistributable.Name);
await RunPostInstallScripts(game, redistributable);
}
SaveTrackedFiles(game.InstallDirectory, redistributable.Id, fileTracker);
}
}
}
catch (Exception ex)
{
logger?.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name);
}
}
2026-06-10 21:51:12 -05:00
public async Task<bool> ApplyUpdateArchiveAsync(Guid archiveId, Guid redistributableId, Game game, CancellationToken cancellationToken = default)
{
_installProgress = new InstallProgress();
_installProgress.Status = InstallStatus.Downloading;
_installProgress.Title = game.Title;
_installProgress.Progress = 0;
OnInstallProgressUpdate?.Invoke(_installProgress);
var destination = Path.Combine(GameClient.GetMetadataDirectoryPath(game.InstallDirectory, redistributableId), "Files");
logger?.LogTrace("Downloading archive {ArchiveId} and extracting redistributable {RedistributableId} to path {Destination}", archiveId, redistributableId, destination);
try
{
Directory.CreateDirectory(destination);
using (var stream = await StreamArchiveAsync(archiveId))
{
var monitor = new FileTransferMonitor(stream.Length);
var progress = new Progress<ProgressReport>(report =>
{
if (cancellationToken.IsCancellationRequested)
return;
if (monitor.CanUpdate())
{
monitor.Update(stream.Position);
_installProgress.BytesTransferred = monitor.GetBytesTransferred();
_installProgress.TotalBytes = stream.Length;
_installProgress.TransferSpeed = monitor.GetSpeed();
_installProgress.TimeRemaining = monitor.GetTimeRemaining();
OnInstallProgressUpdate?.Invoke(_installProgress);
}
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
{
Progress = report,
});
});
await using var reader = await ReaderFactory.OpenAsyncReader(stream, new ReaderOptions { Progress = progress }, cancellationToken);
await reader.WriteAllToDirectoryAsync(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
}, cancellationToken);
}
logger?.LogTrace("Successfully applied update archive {ArchiveId} for redistributable {RedistributableId}", archiveId, redistributableId);
return true;
}
catch (Exception ex)
{
logger?.LogError(ex, "Could not apply update archive {ArchiveId} for redistributable {RedistributableId}", archiveId, redistributableId);
if (Directory.Exists(destination))
{
logger?.LogTrace("Cleaning up orphaned files after bad update");
Directory.Delete(destination, true);
}
throw new InstallException("The redistributable update archive could not be extracted. Please try again");
}
}
public async Task RefreshManifestAndScriptsAsync(string installDirectory, Redistributable redistributable)
{
logger?.LogTrace("Refreshing manifest and scripts for redistributable {RedistributableId} in {InstallDirectory}", redistributable.Id, installDirectory);
var manifest = await GetManifestAsync(redistributable.Id);
await ManifestHelper.WriteAsync(manifest, installDirectory);
var scripts = await GetScriptsAsync(redistributable.Id);
if (scripts != null && scripts.Any())
{
var game = new Game { InstallDirectory = installDirectory };
foreach (var script in scripts)
await ScriptHelper.SaveScriptAsync(game, redistributable, script);
}
}
private async Task RunPostInstallScripts(Game game, Redistributable redistributable)
{
if (redistributable.Scripts != null && redistributable.Scripts.Any())
{
//GameInstallProgress.Status = GameInstallStatus.RunningScripts;
// OnGameInstallProgressUpdate?.Invoke(GameInstallProgress);
try
{
2026-02-10 17:34:35 -06:00
await scriptClient.Redistributable_RunInstallScriptAsync(game.InstallDirectory, game.Id, redistributable.Id);
await scriptClient.Redistributable_RunNameChangeScriptAsync(game.InstallDirectory, game.Id, redistributable.Id, await profileClient.GetAliasAsync());
}
catch (Exception ex)
{
logger?.LogError(ex, "Scripts failed to execute for redistributable {RedistributableName} ({GameId})", redistributable.Name, redistributable.Id);
}
}
}
private async Task<ExtractionResult> DownloadAndExtractAsync(Redistributable redistributable, Game game, CancellationToken cancellationToken = default)
{
if (redistributable == null)
{
logger?.LogTrace("Redistributable failed to download! No redistributable was specified");
2025-08-18 03:02:49 -05:00
throw new ArgumentNullException(nameof(redistributable));
}
var destination = Path.Combine(GameClient.GetMetadataDirectoryPath(game.InstallDirectory, redistributable.Id), "Files");
var files = new List<ExtractionResult.FileEntry>();
logger?.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination);
try
{
Directory.CreateDirectory(destination);
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
using (var redistributableStream = await Stream(redistributable.Id))
{
var monitor = new FileTransferMonitor(redistributableStream.Length);
var seenEntries = new System.Collections.Generic.HashSet<string>(StringComparer.OrdinalIgnoreCase);
var progress = new Progress<ProgressReport>(report =>
{
if (!string.IsNullOrEmpty(report.EntryPath) && seenEntries.Add(report.EntryPath))
{
files.Add(new ExtractionResult.FileEntry
{
EntryPath = report.EntryPath,
LocalPath = Path.Combine(destination, report.EntryPath),
});
}
if (monitor.CanUpdate())
{
monitor.Update(redistributableStream.Position);
_installProgress.BytesTransferred = monitor.GetBytesTransferred();
_installProgress.TotalBytes = redistributableStream.Length;
_installProgress.TransferSpeed = monitor.GetSpeed();
_installProgress.TimeRemaining = monitor.GetTimeRemaining();
OnInstallProgressUpdate?.Invoke(_installProgress);
}
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
{
Progress = report,
});
});
await using var reader = await ReaderFactory.OpenAsyncReader(redistributableStream, new ReaderOptions { Progress = progress }, cancellationToken);
await reader.WriteAllToDirectoryAsync(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
}, cancellationToken);
}
}
catch (Exception ex)
{
logger?.LogError(ex, "Could not extract to path {Destination}", destination);
if (Directory.Exists(destination))
{
logger?.LogTrace("Cleaning up orphaned files after bad install");
Directory.Delete(destination, true);
}
throw new Exception("The redistributable archive could not be extracted, is it corrupted? Please try again");
}
var extractionResult = new ExtractionResult
{
Canceled = false
};
if (!extractionResult.Canceled)
{
extractionResult.Success = true;
extractionResult.Directory = destination;
extractionResult.Files = files;
logger?.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination);
}
return extractionResult;
}
public async Task ImportAsync(string archivePath)
{
using (var fs = new FileStream(archivePath, FileMode.Open, FileAccess.Read))
{
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
var objectKey = await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
.UploadInChunksAsync(settingsProvider.CurrentValue.Archives.UploadChunkSize, fs);
if (objectKey != Guid.Empty)
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
.UseRoute($"/api/Redistributables/Import/{objectKey}")
2025-10-01 18:45:30 -05:00
.PostAsync();
}
}
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
[Obsolete("Exporter no longer provides \"full\" exports")]
public async Task ExportAsync(string destinationPath, Guid redistributableId)
{
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
.UseRoute($"/Redistributables/{redistributableId}/Export/Full")
.DownloadAsync(destinationPath);
}
public async Task UploadArchiveAsync(string archivePath, Guid redistributableId, string version, string changelog = "")
{
using (var fs = new FileStream(archivePath, FileMode.Open, FileAccess.Read))
{
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
var objectKey = await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
.UploadInChunksAsync(settingsProvider.CurrentValue.Archives.UploadChunkSize, fs);
if (objectKey != Guid.Empty)
Implement SDK using dependency injection - API requests are now made through the ApiRequestBuilder with DI supplied via the ApiRequestFactory singleton - Reliance on RestSharp and WebClient has been removed in favor of HttpClient - Auth token is now being tracked by the ITokenProvider - Network information (MAC address, broadcast addresses, IP address) is now supplied with the INetworkInformationProvider singleton - Connection state is now being maintained by the ConnectionService, with a reliance on RPC (SignalR websocket) reporting actual connection state without relying on pings - All download streams are now provided as a TrackableStream - Singleton Client class has been removed. All usage of the SDK should happen via Dependency Injection This is just a base, non-functional refactor of the SDK to use DI. The following changes to the rest of the codebase need to be made: - Usage of the SDK client in the launcher needs to be replaced in favor of injecting SDK services - PowerShell cmdlets need to be able to have services injected. Most likely a separate scope will have to be opened up per PS runtime? - Usage of the SDK client in the server needs to be replaced in favor of injecting SDK services. This should be minimal and should actually provide benefit when it comes to executing client-like features (scripts mostly) without needing a fully configured client that maintains connection state. - Configuration of the client needs to be implemented using ILANCommanderConfiguration
2025-09-22 00:29:51 -05:00
await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
.UseRoute("/api/Redistributables/UploadArchive")
.AddBody(new UploadArchiveRequest
{
Id = redistributableId,
ObjectKey = objectKey,
Version = version,
Changelog = changelog,
})
2025-10-01 18:45:30 -05:00
.PostAsync();
}
}
private void SaveTrackedFiles(string installDirectory, Guid redistributableId, InstallDirectoryFileTracker fileTracker)
{
try
{
var relativePaths = fileTracker.GetCreatedFiles()
.Select(f => Path.GetRelativePath(installDirectory, f))
.OrderBy(f => f)
.ToList();
var fileListPath = GameClient.GetMetadataFilePath(installDirectory, redistributableId, "FileList.txt");
var directory = Path.GetDirectoryName(fileListPath);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
File.WriteAllText(fileListPath, string.Join(Environment.NewLine, relativePaths));
logger?.LogTrace("Tracked {Count} files installed by redistributable {RedistributableId}", relativePaths.Count, redistributableId);
}
catch (Exception ex)
{
logger?.LogWarning(ex, "Could not track files for redistributable {RedistributableId}", redistributableId);
}
}
private class InstallDirectoryFileTracker : IDisposable
{
private readonly FileSystemWatcher _watcher;
private readonly HashSet<string> _createdFiles = new(StringComparer.OrdinalIgnoreCase);
private readonly string _metadataPath;
private readonly object _lock = new();
public InstallDirectoryFileTracker(string installDirectory)
{
_metadataPath = Path.Combine(installDirectory, ".lancommander");
_watcher = new FileSystemWatcher(installDirectory)
{
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.FileName,
EnableRaisingEvents = true,
};
_watcher.Created += OnFileCreated;
_watcher.Renamed += OnFileRenamed;
}
private void OnFileCreated(object sender, FileSystemEventArgs e)
{
if (e.FullPath.StartsWith(_metadataPath, StringComparison.OrdinalIgnoreCase))
return;
lock (_lock)
_createdFiles.Add(e.FullPath);
}
private void OnFileRenamed(object sender, RenamedEventArgs e)
{
if (e.FullPath.StartsWith(_metadataPath, StringComparison.OrdinalIgnoreCase))
return;
lock (_lock)
_createdFiles.Add(e.FullPath);
}
public IEnumerable<string> GetCreatedFiles()
{
lock (_lock)
return _createdFiles.ToList();
}
public void Dispose()
{
_watcher.EnableRaisingEvents = false;
_watcher.Created -= OnFileCreated;
_watcher.Renamed -= OnFileRenamed;
_watcher.Dispose();
}
}
}
}