577 lines
19 KiB
C#
577 lines
19 KiB
C#
using LANCommander.SDK;
|
|
using LANCommander.SDK.Exceptions;
|
|
using LANCommander.SDK.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
using RestSharp;
|
|
using RestSharp.Interceptors;
|
|
using Semver;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Management.Automation.Internal;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace LANCommander.SDK
|
|
{
|
|
public class Client
|
|
{
|
|
private readonly ILogger Logger;
|
|
|
|
private RestClient ApiClient;
|
|
private AuthToken Token;
|
|
|
|
private bool Connected = false;
|
|
|
|
public Uri BaseUrl;
|
|
public string DefaultInstallDirectory;
|
|
|
|
public readonly GameService Games;
|
|
public readonly SaveService Saves;
|
|
public readonly RedistributableService Redistributables;
|
|
public readonly ActionService Actions;
|
|
public readonly ScriptService Scripts;
|
|
public readonly ProfileService Profile;
|
|
public readonly MediaService Media;
|
|
public readonly LauncherService Launcher;
|
|
public readonly IssueService Issues;
|
|
|
|
private Settings _Settings { get; set; }
|
|
public Settings Settings
|
|
{
|
|
get
|
|
{
|
|
if (_Settings == null)
|
|
_Settings = GetSettings();
|
|
|
|
return _Settings;
|
|
}
|
|
}
|
|
|
|
public Client(string baseUrl, string defaultInstallDirectory)
|
|
{
|
|
DefaultInstallDirectory = defaultInstallDirectory;
|
|
|
|
Games = new GameService(this, DefaultInstallDirectory);
|
|
Saves = new SaveService(this);
|
|
Redistributables = new RedistributableService(this);
|
|
Actions = new ActionService(this);
|
|
Scripts = new ScriptService(this);
|
|
Profile = new ProfileService(this);
|
|
Media = new MediaService(this);
|
|
Launcher = new LauncherService(this);
|
|
Issues = new IssueService(this);
|
|
|
|
ChangeServerAddress(baseUrl);
|
|
}
|
|
|
|
public Client(string baseUrl, string defaultInstallDirectory, ILogger logger)
|
|
{
|
|
ChangeServerAddress(baseUrl);
|
|
|
|
DefaultInstallDirectory = defaultInstallDirectory;
|
|
|
|
Games = new GameService(this, DefaultInstallDirectory, logger);
|
|
Saves = new SaveService(this, logger);
|
|
Redistributables = new RedistributableService(this, logger);
|
|
Actions = new ActionService(this);
|
|
Scripts = new ScriptService(this, logger);
|
|
Profile = new ProfileService(this, logger);
|
|
Media = new MediaService(this, logger);
|
|
Launcher = new LauncherService(this);
|
|
Issues = new IssueService(this);
|
|
|
|
Logger = logger;
|
|
}
|
|
|
|
public void ChangeServerAddress(string baseUrl)
|
|
{
|
|
if (!String.IsNullOrWhiteSpace(baseUrl))
|
|
{
|
|
BaseUrl = new Uri(baseUrl);
|
|
ApiClient = new RestClient(new RestClientOptions
|
|
{
|
|
BaseUrl = BaseUrl,
|
|
ThrowOnAnyError = true
|
|
});
|
|
}
|
|
}
|
|
|
|
public bool IsConnected()
|
|
{
|
|
return Connected;
|
|
}
|
|
|
|
public static SemVersion GetCurrentVersion()
|
|
{
|
|
return SemVersion.FromVersion(Assembly.GetExecutingAssembly().GetName().Version);
|
|
}
|
|
|
|
internal T PostRequest<T>(string route, object body, bool ignoreVersion = false)
|
|
{
|
|
if (Token == null)
|
|
return default;
|
|
|
|
var request = new RestRequest(route)
|
|
.AddJsonBody(body)
|
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}")
|
|
.AddHeader("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
var response = ApiClient.Post<T>(request);
|
|
|
|
return response;
|
|
}
|
|
|
|
internal T PostRequest<T>(string route, bool ignoreVersion = false)
|
|
{
|
|
if (Token == null)
|
|
return default;
|
|
|
|
var request = new RestRequest(route)
|
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}")
|
|
.AddHeader("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
var response = ApiClient.Post<T>(request);
|
|
|
|
return response;
|
|
}
|
|
|
|
internal async Task<T> PostRequestAsync<T>(string route, object body, bool ignoreVersion = false)
|
|
{
|
|
if (Token == null)
|
|
return default;
|
|
|
|
var request = new RestRequest(route)
|
|
.AddJsonBody(body)
|
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}")
|
|
.AddHeader("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
var response = await ApiClient.PostAsync<T>(request);
|
|
|
|
return response;
|
|
}
|
|
|
|
internal async Task<T> PostRequestAsync<T>(string route, bool ignoreVersion = false)
|
|
{
|
|
if (Token == null)
|
|
return default;
|
|
|
|
var request = new RestRequest(route)
|
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}")
|
|
.AddHeader("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
var response = await ApiClient.PostAsync<T>(request);
|
|
|
|
return response;
|
|
}
|
|
|
|
internal T GetRequest<T>(string route, bool ignoreVersion = false)
|
|
{
|
|
if (Token == null)
|
|
return default;
|
|
|
|
var request = new RestRequest(route)
|
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}")
|
|
.AddHeader("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
var response = ApiClient.Get<T>(request);
|
|
|
|
return response;
|
|
}
|
|
|
|
internal async Task<T> GetRequestAsync<T>(string route, bool ignoreVersion = false)
|
|
{
|
|
if (Token == null)
|
|
return default;
|
|
|
|
var request = new RestRequest(route)
|
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}")
|
|
.AddHeader("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
var response = await ApiClient.GetAsync<T>(request);
|
|
|
|
return response;
|
|
}
|
|
|
|
internal async Task<string> DownloadRequestAsync(string route, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
|
{
|
|
route = route.TrimStart('/');
|
|
|
|
var client = new WebClient();
|
|
var tempFile = Path.GetTempFileName();
|
|
|
|
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
|
|
client.Headers.Add("X-API-Version", GetCurrentVersion().ToString());
|
|
client.DownloadProgressChanged += (s, e) => progressHandler(e);
|
|
client.DownloadFileCompleted += (s, e) => completeHandler(e);
|
|
|
|
try
|
|
{
|
|
await client.DownloadFileTaskAsync(new Uri(BaseUrl, route), tempFile);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (File.Exists(tempFile))
|
|
File.Delete(tempFile);
|
|
|
|
tempFile = String.Empty;
|
|
}
|
|
|
|
return tempFile;
|
|
}
|
|
|
|
internal async Task<string> DownloadRequestAsync(string route, string destination)
|
|
{
|
|
route = route.TrimStart('/');
|
|
|
|
var client = new WebClient();
|
|
|
|
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
|
|
client.Headers.Add("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
try
|
|
{
|
|
await client.DownloadFileTaskAsync(new Uri(BaseUrl, route), destination);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (File.Exists(destination))
|
|
File.Delete(destination);
|
|
|
|
destination = String.Empty;
|
|
}
|
|
|
|
return destination;
|
|
}
|
|
|
|
internal TrackableStream StreamRequest(string route)
|
|
{
|
|
route = route.TrimStart('/');
|
|
|
|
var client = new WebClient();
|
|
|
|
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
|
|
client.Headers.Add("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
var ws = client.OpenRead(new Uri(BaseUrl, route));
|
|
|
|
return new TrackableStream(ws, true, Convert.ToInt64(client.ResponseHeaders["Content-Length"]));
|
|
}
|
|
|
|
internal T UploadRequest<T>(string route, string fileName, byte[] data, bool ignoreVersion = false)
|
|
{
|
|
var request = new RestRequest(route, Method.Post)
|
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}")
|
|
.AddHeader("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
request.AddFile(fileName, data, fileName);
|
|
|
|
var response = ApiClient.Post<T>(request);
|
|
|
|
return response;
|
|
}
|
|
|
|
internal async Task<T> UploadRequestAsync<T>(string route, string fileName, byte[] data, bool ignoreVersion = false)
|
|
{
|
|
var request = new RestRequest(route, Method.Post)
|
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}")
|
|
.AddHeader("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
request.AddFile(fileName, data, fileName);
|
|
|
|
var response = await ApiClient.PostAsync<T>(request);
|
|
|
|
return response;
|
|
}
|
|
|
|
internal async Task<Guid> ChunkedUploadRequestAsync(string fileName, Stream stream, bool ignoreVersion = false)
|
|
{
|
|
var maxChunkSize = 1024 * 1024 * 50;
|
|
var initResponse = await PostRequestAsync<UploadInitResponse>("/Upload/Init", ignoreVersion);
|
|
|
|
var buffer = new byte[maxChunkSize];
|
|
|
|
while (stream.Position < stream.Length)
|
|
{
|
|
var chunkRequest = new UploadChunkRequest();
|
|
|
|
chunkRequest.Start = stream.Position;
|
|
|
|
if (stream.Position + maxChunkSize > stream.Length)
|
|
{
|
|
var bytes = stream.Length - stream.Position;
|
|
|
|
buffer = new byte[bytes];
|
|
|
|
await stream.ReadAsync(buffer, 0, (int)(stream.Length - stream.Position));
|
|
}
|
|
else
|
|
await stream.ReadAsync(buffer, 0, maxChunkSize);
|
|
|
|
chunkRequest.End = stream.Position;
|
|
chunkRequest.Total = stream.Length;
|
|
chunkRequest.File = buffer;
|
|
chunkRequest.Key = initResponse.Key;
|
|
|
|
await PostRequestAsync<object>("/Upload/Chunk", chunkRequest, ignoreVersion);
|
|
}
|
|
|
|
return initResponse.Key;
|
|
}
|
|
|
|
public async Task<AuthToken> AuthenticateAsync(string username, string password, bool ignoreVersion = false)
|
|
{
|
|
var request = new RestRequest("/api/Auth", Method.Post);
|
|
|
|
request.AddJsonBody(new AuthRequest()
|
|
{
|
|
UserName = username,
|
|
Password = password
|
|
});
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
var response = await ApiClient.ExecuteAsync<AuthToken>(request);
|
|
|
|
if (response.ErrorException != null)
|
|
throw response.ErrorException;
|
|
|
|
switch (response.StatusCode)
|
|
{
|
|
case HttpStatusCode.OK:
|
|
var token = new AuthToken
|
|
{
|
|
AccessToken = response.Data.AccessToken,
|
|
RefreshToken = response.Data.RefreshToken,
|
|
Expiration = response.Data.Expiration
|
|
};
|
|
|
|
UseToken(token);
|
|
|
|
Connected = true;
|
|
|
|
return token;
|
|
|
|
case HttpStatusCode.Forbidden:
|
|
case HttpStatusCode.BadRequest:
|
|
case HttpStatusCode.Unauthorized:
|
|
Connected = false;
|
|
throw new WebException("Invalid username or password");
|
|
|
|
default:
|
|
Connected = false;
|
|
throw new WebException("Could not communicate with the server");
|
|
}
|
|
}
|
|
|
|
public async Task LogoutAsync()
|
|
{
|
|
await ApiClient.ExecuteAsync(new RestRequest("/api/Auth/Logout", Method.Post));
|
|
|
|
Connected = false;
|
|
Token = null;
|
|
}
|
|
|
|
public async Task<AuthToken> RegisterAsync(string username, string password)
|
|
{
|
|
var response = await ApiClient.ExecuteAsync<AuthResponse>(new RestRequest("/api/auth/register", Method.Post).AddJsonBody(new AuthRequest()
|
|
{
|
|
UserName = username,
|
|
Password = password
|
|
}));
|
|
|
|
switch (response.StatusCode)
|
|
{
|
|
case HttpStatusCode.OK:
|
|
Token = new AuthToken
|
|
{
|
|
AccessToken = response.Data.AccessToken,
|
|
RefreshToken = response.Data.RefreshToken,
|
|
Expiration = response.Data.Expiration
|
|
};
|
|
|
|
Connected = true;
|
|
|
|
return Token;
|
|
|
|
case HttpStatusCode.BadRequest:
|
|
case HttpStatusCode.Forbidden:
|
|
case HttpStatusCode.Unauthorized:
|
|
Connected = false;
|
|
throw new WebException(response.Data.Message);
|
|
|
|
default:
|
|
Connected = false;
|
|
throw new WebException("Could not communicate with the server");
|
|
}
|
|
}
|
|
|
|
public async Task<bool> PingAsync()
|
|
{
|
|
var response = await ApiClient.ExecuteAsync(new RestRequest("/api/Ping", Method.Get));
|
|
|
|
return response.StatusCode == HttpStatusCode.OK;
|
|
}
|
|
|
|
public bool ValidateToken()
|
|
{
|
|
return ValidateToken(Token);
|
|
}
|
|
|
|
public bool ValidateToken(AuthToken token, bool ignoreVersion = false)
|
|
{
|
|
Logger?.LogTrace("Validating token...");
|
|
|
|
if (token == null)
|
|
{
|
|
Logger?.LogTrace("Token is null!");
|
|
return false;
|
|
}
|
|
|
|
var request = new RestRequest("/api/Auth/Validate")
|
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}")
|
|
.AddHeader("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
if (String.IsNullOrEmpty(token.AccessToken) || String.IsNullOrEmpty(token.RefreshToken))
|
|
{
|
|
Logger?.LogTrace("Token is empty!");
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
var response = ApiClient.Post(request);
|
|
|
|
var valid = response.StatusCode == HttpStatusCode.OK;
|
|
|
|
if (valid)
|
|
Logger?.LogTrace("Token is valid!");
|
|
else
|
|
Logger?.LogTrace("Token is invalid!");
|
|
|
|
Connected = valid;
|
|
|
|
return response.StatusCode == HttpStatusCode.OK;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger?.LogTrace(ex, "Token could not be retrieved");
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> ValidateTokenAsync()
|
|
{
|
|
return await ValidateTokenAsync(Token);
|
|
}
|
|
|
|
public async Task<bool> ValidateTokenAsync(AuthToken token, bool ignoreVersion = false)
|
|
{
|
|
Logger?.LogTrace("Validating token...");
|
|
|
|
if (token == null)
|
|
{
|
|
Logger?.LogTrace("Token is null!");
|
|
return false;
|
|
}
|
|
|
|
var request = new RestRequest("/api/Auth/Validate")
|
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}")
|
|
.AddHeader("X-API-Version", GetCurrentVersion().ToString());
|
|
|
|
if (!ignoreVersion)
|
|
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
|
|
|
if (String.IsNullOrEmpty(token.AccessToken) || String.IsNullOrEmpty(token.RefreshToken))
|
|
{
|
|
Logger?.LogTrace("Token is empty!");
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
var response = await ApiClient.PostAsync<object>(request);
|
|
|
|
Logger?.LogTrace("Token is valid!");
|
|
|
|
Connected = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger?.LogTrace(ex, "Could not validate token");
|
|
|
|
Connected = false;
|
|
}
|
|
|
|
return Connected;
|
|
}
|
|
|
|
public void UseToken(AuthToken token)
|
|
{
|
|
Token = token;
|
|
}
|
|
|
|
public void UseServerAddress(string address)
|
|
{
|
|
BaseUrl = new Uri(address);
|
|
ApiClient = new RestClient(BaseUrl);
|
|
}
|
|
|
|
public string GetServerAddress()
|
|
{
|
|
return BaseUrl.ToString();
|
|
}
|
|
|
|
public Settings GetSettings()
|
|
{
|
|
return GetRequest<Settings>($"/api/Settings");
|
|
}
|
|
|
|
internal string GetMacAddress()
|
|
{
|
|
return NetworkInterface.GetAllNetworkInterfaces()
|
|
.Where(nic => nic.OperationalStatus == OperationalStatus.Up && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
|
.Select(nic => nic.GetPhysicalAddress().ToString())
|
|
.FirstOrDefault();
|
|
}
|
|
|
|
internal string GetIpAddress()
|
|
{
|
|
return Dns.GetHostEntry(Dns.GetHostName()).AddressList[0].ToString();
|
|
}
|
|
}
|
|
}
|