Detect disconnection from server, automatically go into offline mode
This commit is contained in:
parent
38e95c9102
commit
cf7e81ba54
16 changed files with 844 additions and 437 deletions
7
LANCommander.Launcher.Models/ConnectionState.cs
Normal file
7
LANCommander.Launcher.Models/ConnectionState.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace LANCommander.Launcher.Models;
|
||||
|
||||
public class ConnectionState
|
||||
{
|
||||
public bool IsConnected { get; set; }
|
||||
public bool OfflineModeEnabled { get; set; }
|
||||
}
|
||||
|
|
@ -9,11 +9,24 @@ namespace LANCommander.Launcher.Services;
|
|||
|
||||
public class AuthenticationService : BaseService
|
||||
{
|
||||
private readonly Settings Settings = SettingService.GetSettings();
|
||||
private readonly ProfileService ProfileService;
|
||||
|
||||
private Settings Settings;
|
||||
|
||||
public event EventHandler OnLogin;
|
||||
public event EventHandler OnLogout;
|
||||
public event EventHandler OnRegister;
|
||||
|
||||
public delegate void OnOfflineModeChangedHandler(bool state);
|
||||
public event OnOfflineModeChangedHandler OnOfflineModeChanged;
|
||||
|
||||
public AuthenticationService(
|
||||
Client client,
|
||||
ILogger<AuthenticationService> logger) : base(client, logger)
|
||||
ILogger<AuthenticationService> logger,
|
||||
ProfileService profileService) : base(client, logger)
|
||||
{
|
||||
Settings = SettingService.GetSettings();
|
||||
ProfileService = profileService;
|
||||
}
|
||||
|
||||
public bool IsConnected()
|
||||
|
|
@ -32,6 +45,109 @@ public class AuthenticationService : BaseService
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Login()
|
||||
{
|
||||
await Login(Settings.Authentication.ServerAddress, new SDK.Models.AuthToken
|
||||
{
|
||||
AccessToken = Settings.Authentication.AccessToken,
|
||||
RefreshToken = Settings.Authentication.RefreshToken,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task Login(string serverAddress, string username, string password)
|
||||
{
|
||||
Client.ChangeServerAddress(serverAddress);
|
||||
|
||||
var token = await Client.AuthenticateAsync(username, password);
|
||||
|
||||
await Login(serverAddress, token);
|
||||
}
|
||||
|
||||
public async Task Login(string serverAddress, SDK.Models.AuthToken token)
|
||||
{
|
||||
Client.ChangeServerAddress(serverAddress);
|
||||
|
||||
Settings = SettingService.GetSettings();
|
||||
|
||||
Settings.Authentication.ServerAddress = serverAddress;
|
||||
Settings.Authentication.AccessToken = token.AccessToken;
|
||||
Settings.Authentication.RefreshToken = token.RefreshToken;
|
||||
|
||||
Client.UseToken(token);
|
||||
|
||||
if (await Client.ValidateTokenAsync())
|
||||
{
|
||||
SetOfflineMode(false);
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
|
||||
OnLogin?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
await ProfileService.DownloadProfileInfoAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Register(string serverAddress, string username, string password, string passwordConfirmation)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(serverAddress))
|
||||
throw new Exception("Server address cannot be blank");
|
||||
|
||||
if (String.IsNullOrWhiteSpace(username))
|
||||
throw new Exception("Username cannot be blank");
|
||||
|
||||
if (String.IsNullOrWhiteSpace(password))
|
||||
throw new Exception("Password cannot be blank");
|
||||
|
||||
if (password != passwordConfirmation)
|
||||
throw new Exception("Passwords do not match");
|
||||
|
||||
Client.ChangeServerAddress(serverAddress);
|
||||
|
||||
var token = await Client.RegisterAsync(username, password, passwordConfirmation);
|
||||
|
||||
Client.UseToken(token);
|
||||
|
||||
Settings = SettingService.GetSettings();
|
||||
|
||||
Settings.Authentication.ServerAddress = serverAddress;
|
||||
Settings.Authentication.AccessToken = token.AccessToken;
|
||||
Settings.Authentication.RefreshToken = token.RefreshToken;
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
|
||||
OnRegister?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
await ProfileService.DownloadProfileInfoAsync();
|
||||
}
|
||||
|
||||
public void SetOfflineMode(bool state)
|
||||
{
|
||||
Settings = SettingService.GetSettings();
|
||||
|
||||
Settings.Authentication.OfflineMode = state;
|
||||
|
||||
if (!state)
|
||||
Client.Disconnect();
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
|
||||
OnOfflineModeChanged?.Invoke(state);
|
||||
}
|
||||
|
||||
public async Task Logout()
|
||||
{
|
||||
await Client.LogoutAsync();
|
||||
|
||||
Settings = SettingService.GetSettings();
|
||||
|
||||
Settings.Profile = new ProfileSettings();
|
||||
Settings.Authentication = new AuthenticationSettings();
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
|
||||
OnLogout?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public Guid GetUserId()
|
||||
{
|
||||
|
|
@ -75,6 +191,6 @@ public class AuthenticationService : BaseService
|
|||
|
||||
public async Task<bool> OfflineModeAvailableAsync()
|
||||
{
|
||||
return !IsConnected() && !(await IsServerOnlineAsync()) && HasStoredCredentials();
|
||||
return !(await IsServerOnlineAsync()) && !IsConnected() && HasStoredCredentials();
|
||||
}
|
||||
}
|
||||
|
|
@ -21,10 +21,11 @@ namespace LANCommander.Launcher.Services.Extensions
|
|||
using (var scope = serviceProvider.CreateScope())
|
||||
{
|
||||
var logger = scope.ServiceProvider.GetService<ILogger>();
|
||||
var profileService = scope.ServiceProvider.GetService<ProfileService>();
|
||||
var authenticationService = scope.ServiceProvider.GetService<AuthenticationService>();
|
||||
var keepAliveService = scope.ServiceProvider.GetService<KeepAliveService>();
|
||||
|
||||
#region Sign in
|
||||
profileService.Login().Wait();
|
||||
authenticationService.Login().Wait();
|
||||
#endregion
|
||||
|
||||
#region Scaffold Required Directories
|
||||
|
|
|
|||
|
|
@ -55,9 +55,10 @@ namespace LANCommander.Launcher.Services.Extensions
|
|||
|
||||
services.AddSingleton(client);
|
||||
services.AddSingleton<MessageBusService>();
|
||||
services.AddSingleton<AuthenticationService>();
|
||||
services.AddSingleton<KeepAliveService>();
|
||||
#endregion
|
||||
|
||||
services.AddScoped<AuthenticationService>();
|
||||
|
||||
services.AddScoped<CollectionService>();
|
||||
services.AddScoped<CommandLineService>();
|
||||
services.AddScoped<CompanyService>();
|
||||
|
|
|
|||
120
LANCommander.Launcher.Services/KeepAliveService.cs
Normal file
120
LANCommander.Launcher.Services/KeepAliveService.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using System.Management.Automation.Language;
|
||||
using LANCommander.SDK;
|
||||
using System.Timers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace LANCommander.Launcher.Services;
|
||||
|
||||
public class KeepAliveService : BaseService
|
||||
{
|
||||
private readonly AuthenticationService AuthenticationService;
|
||||
|
||||
private Timer CheckConnectionTimer;
|
||||
private Timer RetryConnectionTimer;
|
||||
|
||||
private int PingInterval = 2000;
|
||||
private int RetryInterval = 1000;
|
||||
private int RetryCount;
|
||||
private int MaxRetries = 10;
|
||||
private bool ConnectionLost = false;
|
||||
|
||||
public event EventHandler ConnectionSevered;
|
||||
public event EventHandler ConnectionLostPermanently;
|
||||
public event EventHandler ConnectionEstablished;
|
||||
|
||||
public KeepAliveService(
|
||||
Client client,
|
||||
ILogger<KeepAliveService> logger,
|
||||
AuthenticationService authenticationService) : base(client, logger)
|
||||
{
|
||||
AuthenticationService = authenticationService;
|
||||
|
||||
AuthenticationService.OnLogin += (sender, args) => StartMonitoring();
|
||||
AuthenticationService.OnLogout += (sender, args) => StopMonitoring();
|
||||
AuthenticationService.OnRegister += (sender, args) => StartMonitoring();
|
||||
AuthenticationService.OnOfflineModeChanged += (state) =>
|
||||
{
|
||||
if (state)
|
||||
StopMonitoring();
|
||||
else
|
||||
StartMonitoring();
|
||||
};
|
||||
|
||||
ConnectionEstablished += (sender, args) => StartMonitoring();
|
||||
}
|
||||
|
||||
public void StartMonitoring()
|
||||
{
|
||||
RetryCount = 0;
|
||||
|
||||
CheckConnectionTimer?.Stop();
|
||||
CheckConnectionTimer?.Dispose();
|
||||
|
||||
CheckConnectionTimer = new Timer(PingInterval);
|
||||
CheckConnectionTimer.Elapsed += CheckConnection;
|
||||
CheckConnectionTimer.AutoReset = true;
|
||||
CheckConnectionTimer.Start();
|
||||
}
|
||||
|
||||
public void StopMonitoring()
|
||||
{
|
||||
RetryCount = 0;
|
||||
CheckConnectionTimer?.Stop();
|
||||
CheckConnectionTimer?.Dispose();
|
||||
RetryConnectionTimer?.Stop();
|
||||
RetryConnectionTimer?.Dispose();
|
||||
}
|
||||
|
||||
private async void CheckConnection(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
var serverOnline = await AuthenticationService.IsServerOnlineAsync();
|
||||
|
||||
if (!serverOnline && !ConnectionLost)
|
||||
{
|
||||
CheckConnectionTimer.Stop();
|
||||
CheckConnectionTimer.Dispose();
|
||||
CheckConnectionTimer = null;
|
||||
|
||||
ConnectionLost = true;
|
||||
|
||||
ConnectionSevered?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
RetryConnectionTimer = new Timer(RetryInterval);
|
||||
RetryConnectionTimer.Elapsed += RetryConnection;
|
||||
RetryConnectionTimer.AutoReset = true;
|
||||
RetryConnectionTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private async void RetryConnection(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
var serverOnline = await AuthenticationService.IsServerOnlineAsync();
|
||||
|
||||
if (serverOnline && ConnectionLost)
|
||||
{
|
||||
ConnectionLost = false;
|
||||
|
||||
RetryConnectionTimer.Stop();
|
||||
RetryConnectionTimer.Elapsed -= RetryConnection;
|
||||
|
||||
ConnectionEstablished?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
AuthenticationService.SetOfflineMode(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
RetryCount++;
|
||||
|
||||
if (RetryCount == MaxRetries)
|
||||
{
|
||||
ConnectionLostPermanently?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
RetryConnectionTimer.Stop();
|
||||
RetryConnectionTimer.Elapsed -= RetryConnection;
|
||||
|
||||
AuthenticationService.SetOfflineMode(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,105 +26,19 @@ namespace LANCommander.Launcher.Services
|
|||
Settings = SettingService.GetSettings();
|
||||
}
|
||||
|
||||
public async Task Login()
|
||||
public async Task ChangeAlias(string newName)
|
||||
{
|
||||
await Login(Settings.Authentication.ServerAddress, new SDK.Models.AuthToken
|
||||
{
|
||||
AccessToken = Settings.Authentication.AccessToken,
|
||||
RefreshToken = Settings.Authentication.RefreshToken,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task Login(string serverAddress, SDK.Models.AuthToken token)
|
||||
{
|
||||
Client.ChangeServerAddress(serverAddress);
|
||||
await Client.Profile.ChangeAliasAsync(newName);
|
||||
|
||||
Settings = SettingService.GetSettings();
|
||||
|
||||
Settings.Authentication.ServerAddress = serverAddress;
|
||||
Settings.Authentication.AccessToken = token.AccessToken;
|
||||
Settings.Authentication.RefreshToken = token.RefreshToken;
|
||||
|
||||
Client.UseToken(token);
|
||||
|
||||
if (await Client.ValidateTokenAsync())
|
||||
{
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
|
||||
var remoteProfile = await Client.Profile.GetAsync();
|
||||
|
||||
Settings.Profile.Id = remoteProfile.Id;
|
||||
Settings.Profile.Alias = String.IsNullOrWhiteSpace(remoteProfile.Alias) ? remoteProfile.UserName : remoteProfile.Alias;
|
||||
|
||||
try
|
||||
{
|
||||
var tempAvatarPath = await Client.Profile.DownloadAvatar();
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(tempAvatarPath))
|
||||
{
|
||||
var media = new Media
|
||||
{
|
||||
FileId = Guid.NewGuid(),
|
||||
Type = SDK.Enums.MediaType.Avatar,
|
||||
MimeType = MediaTypeNames.Image.Png,
|
||||
Crc32 = SDK.Services.MediaService.CalculateChecksum(tempAvatarPath),
|
||||
};
|
||||
|
||||
media = await MediaService.Add(media);
|
||||
|
||||
var localPath = MediaService.GetImagePath(media);
|
||||
|
||||
if (File.Exists(tempAvatarPath))
|
||||
File.Move(tempAvatarPath, localPath);
|
||||
|
||||
Settings.Profile.AvatarId = media.Id;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "Could not download avatar");
|
||||
}
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Login(string serverAddress, string username, string password)
|
||||
{
|
||||
Client.ChangeServerAddress(serverAddress);
|
||||
|
||||
var token = await Client.AuthenticateAsync(username, password);
|
||||
|
||||
await Login(serverAddress, token);
|
||||
}
|
||||
|
||||
public async Task Register(string serverAddress, string username, string password, string passwordConfirmation)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(serverAddress))
|
||||
throw new Exception("Server address cannot be blank");
|
||||
|
||||
if (String.IsNullOrWhiteSpace(username))
|
||||
throw new Exception("Username cannot be blank");
|
||||
|
||||
if (String.IsNullOrWhiteSpace(password))
|
||||
throw new Exception("Password cannot be blank");
|
||||
|
||||
if (password != passwordConfirmation)
|
||||
throw new Exception("Passwords do not match");
|
||||
|
||||
Client.ChangeServerAddress(serverAddress);
|
||||
|
||||
var token = await Client.RegisterAsync(username, password, passwordConfirmation);
|
||||
|
||||
Settings = SettingService.GetSettings();
|
||||
|
||||
Settings.Authentication.ServerAddress = serverAddress;
|
||||
Settings.Authentication.AccessToken = token.AccessToken;
|
||||
Settings.Authentication.RefreshToken = token.RefreshToken;
|
||||
Settings.Profile.Alias = newName;
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
}
|
||||
|
||||
public async Task DownloadProfileInfoAsync()
|
||||
{
|
||||
var remoteProfile = await Client.Profile.GetAsync();
|
||||
|
||||
Settings.Profile.Id = remoteProfile.Id;
|
||||
|
|
@ -162,38 +76,6 @@ namespace LANCommander.Launcher.Services
|
|||
SettingService.SaveSettings(Settings);
|
||||
}
|
||||
|
||||
public void SetOfflineMode(bool state)
|
||||
{
|
||||
Settings = SettingService.GetSettings();
|
||||
|
||||
Settings.Authentication.OfflineMode = false;
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
}
|
||||
|
||||
public async Task Logout()
|
||||
{
|
||||
await Client.LogoutAsync();
|
||||
|
||||
Settings = SettingService.GetSettings();
|
||||
|
||||
Settings.Profile = new ProfileSettings();
|
||||
Settings.Authentication = new AuthenticationSettings();
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
}
|
||||
|
||||
public async Task ChangeAlias(string newName)
|
||||
{
|
||||
await Client.Profile.ChangeAliasAsync(newName);
|
||||
|
||||
Settings = SettingService.GetSettings();
|
||||
|
||||
Settings.Profile.Alias = newName;
|
||||
|
||||
SettingService.SaveSettings(Settings);
|
||||
}
|
||||
|
||||
public bool IsAuthenticated()
|
||||
{
|
||||
return !String.IsNullOrWhiteSpace(Settings.Authentication.AccessToken);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@using LANCommander.SDK.Models
|
||||
@inject ProfileService ProfileService
|
||||
@inject AuthenticationService AuthenticationService
|
||||
@inject SDK.Client Client
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IMessageService MessageService
|
||||
|
|
@ -105,7 +105,7 @@
|
|||
|
||||
try
|
||||
{
|
||||
await ProfileService.Login(Client.GetServerAddress(), Model.UserName, Model.Password);
|
||||
await AuthenticationService.Login(Client.GetServerAddress(), Model.UserName, Model.Password);
|
||||
|
||||
MainLayout.Import();
|
||||
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
|
||||
try
|
||||
{
|
||||
await ProfileService.Login(ServerAddress, token);
|
||||
await AuthenticationService.Login(ServerAddress, token);
|
||||
|
||||
MainLayout.Import();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@using LANCommander.SDK.Models
|
||||
@inject ProfileService ProfileService
|
||||
@inject AuthenticationService AuthenticationService
|
||||
@inject SDK.Client Client
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IMessageService MessageService
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
try
|
||||
{
|
||||
await ProfileService.Register(Client.GetServerAddress(), Model.UserName, Model.Password, Model.PasswordConfirmation);
|
||||
await AuthenticationService.Register(Client.GetServerAddress(), Model.UserName, Model.Password, Model.PasswordConfirmation);
|
||||
|
||||
MainLayout.Import();
|
||||
|
||||
|
|
@ -78,7 +78,7 @@
|
|||
|
||||
try
|
||||
{
|
||||
await ProfileService.Login(ServerAddress, token);
|
||||
await AuthenticationService.Login(ServerAddress, token);
|
||||
|
||||
MainLayout.Import();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
@if (!ConnectionState.IsConnected || ConnectionState.OfflineModeEnabled)
|
||||
{
|
||||
@Offline
|
||||
}
|
||||
else
|
||||
{
|
||||
@Online
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment Online { get; set; }
|
||||
[Parameter] public RenderFragment Offline { get; set; }
|
||||
|
||||
[CascadingParameter] public Models.ConnectionState ConnectionState { get; set; }
|
||||
}
|
||||
|
|
@ -12,16 +12,16 @@
|
|||
}
|
||||
else
|
||||
{
|
||||
if (Client.IsConnected())
|
||||
{
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.AppstoreAdd" OnClick="@(() => NavigationManager.NavigateTo("/Depot"))">Depot</Button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Tooltip Title="You are currently offline">
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.AppstoreAdd" Disabled>Depot</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
<ConnectionStateView>
|
||||
<Online>
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.AppstoreAdd" OnClick="@(() => NavigationManager.NavigateTo("/Depot"))">Depot</Button>
|
||||
</Online>
|
||||
<Offline>
|
||||
<Tooltip Title="You are currently offline" Placement="Placement.TopLeft">
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.AppstoreAdd" Disabled>Depot</Button>
|
||||
</Tooltip>
|
||||
</Offline>
|
||||
</ConnectionStateView>
|
||||
}
|
||||
|
||||
@if (InstallService.Queue.Any(qi => qi.State))
|
||||
|
|
@ -35,12 +35,16 @@
|
|||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Download" OnClick="() => ShowDownloadQueue()">Downloads</Button>
|
||||
}
|
||||
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Team">Friends</Button>
|
||||
<Tooltip Title="Coming soon!" Placement="Placement.TopRight">
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Team" Disabled>Friends</Button>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
|
||||
<DownloadQueue @ref="DownloadQueue" />
|
||||
|
||||
@code {
|
||||
[CascadingParameter] public bool Connected { get; set; }
|
||||
|
||||
bool DownloadQueueVisible = false;
|
||||
|
||||
DownloadQueue DownloadQueue;
|
||||
|
|
|
|||
60
LANCommander.Launcher/UI/Components/KeepAliveContainer.razor
Normal file
60
LANCommander.Launcher/UI/Components/KeepAliveContainer.razor
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
@implements IDisposable
|
||||
@inject KeepAliveService KeepAliveService
|
||||
@inject IMessageService MessageService
|
||||
|
||||
@code {
|
||||
string ConnectionMessageKey = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
KeepAliveService.ConnectionSevered += OnConnectionSevered;
|
||||
KeepAliveService.ConnectionLostPermanently += OnConnectionLostPermanently;
|
||||
KeepAliveService.ConnectionEstablished += OnConnectionEstablished;
|
||||
}
|
||||
|
||||
void OnConnectionSevered(object? sender, EventArgs e)
|
||||
{
|
||||
var messageConfig = new MessageConfig
|
||||
{
|
||||
Key = ConnectionMessageKey,
|
||||
Type = MessageType.Loading,
|
||||
Content = "Lost connection, retrying...",
|
||||
Duration = 0,
|
||||
};
|
||||
|
||||
MessageService.Open(messageConfig);
|
||||
}
|
||||
|
||||
void OnConnectionLostPermanently(object? sender, EventArgs e)
|
||||
{
|
||||
var messageConfig = new MessageConfig
|
||||
{
|
||||
Key = ConnectionMessageKey,
|
||||
Type = MessageType.Error,
|
||||
Content = "Server unavailable, enabling offline mode",
|
||||
Duration = 2.5,
|
||||
};
|
||||
|
||||
MessageService.Open(messageConfig);
|
||||
}
|
||||
|
||||
void OnConnectionEstablished(object? sender, EventArgs e)
|
||||
{
|
||||
var messageConfig = new MessageConfig
|
||||
{
|
||||
Key = ConnectionMessageKey,
|
||||
Type = MessageType.Success,
|
||||
Content = "Connection established!",
|
||||
Duration = 2.5,
|
||||
};
|
||||
|
||||
MessageService.Open(messageConfig);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
KeepAliveService.ConnectionSevered -= OnConnectionSevered;
|
||||
KeepAliveService.ConnectionLostPermanently -= OnConnectionLostPermanently;
|
||||
KeepAliveService.ConnectionEstablished -= OnConnectionEstablished;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@
|
|||
[Parameter] public Guid Id { get; set; }
|
||||
[Parameter] public string Class { get; set; }
|
||||
[Parameter] public string Key { get; set; }
|
||||
[CascadingParameter] public bool Connected { get; set; }
|
||||
[CascadingParameter] public bool OfflineMode { get; set; }
|
||||
|
||||
string MediaUrl { get; set; }
|
||||
string HiddenClass { get; set; }
|
||||
|
|
@ -28,7 +30,7 @@
|
|||
try {
|
||||
var media = await MediaService.Get(Id);
|
||||
|
||||
if (media == null && Client.IsConnected())
|
||||
if (media == null && (Connected || OfflineMode))
|
||||
media = await ImportService.ImportMediaAsync(Id);
|
||||
|
||||
UpdateMediaUrl(media);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
@inject ProfileService ProfileService
|
||||
@using LANCommander.Launcher.Models
|
||||
@inject ProfileService ProfileService
|
||||
@inject AuthenticationService AuthenticationService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ModalService ModalService
|
||||
|
||||
<Dropdown>
|
||||
<Overlay>
|
||||
<Menu>
|
||||
<MenuItem OnClick="ChangeAlias" Disabled="Settings.Authentication.OfflineMode">
|
||||
<MenuItem OnClick="ChangeAlias" Disabled="ConnectionState.OfflineModeEnabled">
|
||||
Change Name
|
||||
</MenuItem>
|
||||
<MenuItem OnClick="@(() => NavigationManager.NavigateTo("/Settings"))">
|
||||
Settings
|
||||
</MenuItem>
|
||||
|
||||
@if (!Settings.Authentication.OfflineMode)
|
||||
@if (!ConnectionState.OfflineModeEnabled)
|
||||
{
|
||||
<MenuItem OnClick="Logout">
|
||||
Logout
|
||||
|
|
@ -34,6 +36,7 @@
|
|||
</Dropdown>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] public ConnectionState ConnectionState { get; set; }
|
||||
Models.Settings Settings = null;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
|
|
@ -68,7 +71,7 @@
|
|||
|
||||
async Task Logout()
|
||||
{
|
||||
await ProfileService.Logout();
|
||||
await AuthenticationService.Logout();
|
||||
|
||||
NavigationManager.NavigateTo("/Authenticate");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,16 @@
|
|||
}
|
||||
else if (SelectedItem.State == ListItemState.NotInstalled)
|
||||
{
|
||||
<Button Type="ButtonType.Primary" Size="ButtonSize.Large" Icon="@IconType.Outline.Download" OnClick="() => Install(SelectedItem)">Install</Button>
|
||||
<ConnectionStateView>
|
||||
<Online>
|
||||
<Button Type="ButtonType.Primary" Size="ButtonSize.Large" Icon="@IconType.Outline.Download" OnClick="() => Install(SelectedItem)">Install</Button>
|
||||
</Online>
|
||||
<Offline>
|
||||
<Tooltip Title="You are currently offline">
|
||||
<Button Type="ButtonType.Primary" Size="ButtonSize.Large" Icon="@IconType.Outline.Download" Disabled>Install</Button>
|
||||
</Tooltip>
|
||||
</Offline>
|
||||
</ConnectionStateView>
|
||||
}
|
||||
else if (SelectedItem.State == ListItemState.Queued)
|
||||
{
|
||||
|
|
@ -56,7 +65,16 @@
|
|||
}
|
||||
else if (SelectedItem.State == ListItemState.UpdateAvailable)
|
||||
{
|
||||
<Button Type="ButtonType.Primary" Size="ButtonSize.Large" Icon="@IconType.Outline.Download">Update</Button>
|
||||
<ConnectionStateView>
|
||||
<Online>
|
||||
<Button Type="ButtonType.Primary" Size="ButtonSize.Large" Icon="@IconType.Outline.Download" OnClick="() => Update(SelectedItem)">Update</Button>
|
||||
</Online>
|
||||
<Offline>
|
||||
<Tooltip Title="You are currently offline">
|
||||
<Button Type="ButtonType.Primary" Size="ButtonSize.Large" Icon="@IconType.Outline.Download" Disabled>Update</Button>
|
||||
</Tooltip>
|
||||
</Offline>
|
||||
</ConnectionStateView>
|
||||
}
|
||||
else if (SelectedItem.State == ListItemState.Installing)
|
||||
{
|
||||
|
|
@ -64,11 +82,15 @@
|
|||
}
|
||||
</SpaceItem>
|
||||
|
||||
@if (SelectedItem.State == ListItemState.NotInstalled && Client.IsConnected())
|
||||
@if (SelectedItem.State == ListItemState.NotInstalled && Connected)
|
||||
{
|
||||
<SpaceItem>
|
||||
<Statistic Title="Download Size" Value="@ByteSizeLib.ByteSize.FromBytes(GetDownloadSize()).ToString()" />
|
||||
</SpaceItem>
|
||||
<ConnectionStateView>
|
||||
<Online>
|
||||
<SpaceItem>
|
||||
<Statistic Title="Download Size" Value="@ByteSizeLib.ByteSize.FromBytes(GetDownloadSize()).ToString()"/>
|
||||
</SpaceItem>
|
||||
</Online>
|
||||
</ConnectionStateView>
|
||||
}
|
||||
|
||||
<SpaceItem>
|
||||
|
|
@ -148,6 +170,8 @@
|
|||
|
||||
@code {
|
||||
[Parameter] public Guid Id { get; set; }
|
||||
[CascadingParameter] public bool Connected { get; set; }
|
||||
[CascadingParameter] public bool OfflineMode { get; set; }
|
||||
|
||||
Models.ListItem SelectedItem { get; set; }
|
||||
|
||||
|
|
@ -172,7 +196,7 @@
|
|||
{
|
||||
await OnLibraryItemSelected(await LibraryService.GetItemAsync(Id));
|
||||
|
||||
if (SelectedGame != null && Client.IsConnected())
|
||||
if (SelectedGame != null && (Connected && !OfflineMode))
|
||||
RemoteGame = await Client.Games.GetAsync(SelectedGame.Id);
|
||||
}
|
||||
|
||||
|
|
@ -229,6 +253,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
async Task Update(Models.ListItem item)
|
||||
{
|
||||
var game = item.DataItem as Game;
|
||||
|
||||
await InstallService.Add(game);
|
||||
}
|
||||
|
||||
async Task OnQueueChanged()
|
||||
{
|
||||
var queueItem = InstallService.Queue.FirstOrDefault(i => SelectedItem != null && i.Id == SelectedItem.Key);
|
||||
|
|
|
|||
|
|
@ -1,85 +1,94 @@
|
|||
@using LANCommander.Launcher.Models
|
||||
@using System.Management.Automation.Remoting
|
||||
@using LANCommander.Launcher.Models
|
||||
@using LANCommander.SDK
|
||||
@using Photino.Blazor.CustomWindow.Components
|
||||
@using ConnectionState = LANCommander.Launcher.Models.ConnectionState
|
||||
@inherits LayoutComponentBase
|
||||
@inject ImportService ImportService
|
||||
@inject ProfileService ProfileService
|
||||
@inject AuthenticationService AuthenticationService
|
||||
@inject IMessageService MessageService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ModalService ModalService
|
||||
@inject LANCommander.SDK.Client LANCommander
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<CustomWindow HeaderHeight="37">
|
||||
<HeaderExtraControlsLayout>
|
||||
<Space Direction="SpaceDirection.Horizontal">
|
||||
@if (Settings != null && Settings.Profile != null && ProfileService.IsAuthenticated())
|
||||
{
|
||||
@if (!Settings.Authentication.OfflineMode)
|
||||
<CascadingValue Value="ConnectionState">
|
||||
<CustomWindow HeaderHeight="37">
|
||||
<HeaderExtraControlsLayout>
|
||||
<Space Direction="SpaceDirection.Horizontal">
|
||||
<ConnectionStateView>
|
||||
<Online>
|
||||
<SpaceItem>
|
||||
<Popover Placement="Placement.BottomRight" IsButton OnClick="Import" Trigger="new[] { Trigger.Hover }">
|
||||
<ChildContent>
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Sync" Loading="@Importing"/>
|
||||
</ChildContent>
|
||||
<ContentTemplate>
|
||||
<Progress Percent="@((ImportStatusIndex / (float)ImportStatusTotal) * 100)" Steps="@ImportStatusTotal"/>
|
||||
</ContentTemplate>
|
||||
</Popover>
|
||||
</SpaceItem>
|
||||
</Online>
|
||||
<Offline>
|
||||
<SpaceItem>
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.CloudSync" OnClick="Connect" Loading="@Connecting" Danger/>
|
||||
</SpaceItem>
|
||||
</Offline>
|
||||
</ConnectionStateView>
|
||||
|
||||
@if (Settings != null && Settings.Profile != null && ProfileService.IsAuthenticated())
|
||||
{
|
||||
<SpaceItem>
|
||||
<Popover Placement="Placement.BottomRight" IsButton OnClick="Import" Trigger="new [] { Trigger.Hover }">
|
||||
<ChildContent>
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Sync" Loading="@Importing"/>
|
||||
</ChildContent>
|
||||
<ContentTemplate>
|
||||
<Progress Percent="@((ImportStatusIndex / (float)ImportStatusTotal) * 100)" Steps="@ImportStatusTotal" />
|
||||
</ContentTemplate>
|
||||
</Popover>
|
||||
</SpaceItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
<SpaceItem>
|
||||
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.CloudSync" OnClick="Connect" Loading="@Connecting" Danger />
|
||||
<ProfileButton />
|
||||
</SpaceItem>
|
||||
}
|
||||
</Space>
|
||||
</HeaderExtraControlsLayout>
|
||||
<WindowContent>
|
||||
<ErrorBoundary>
|
||||
<ChildContent>
|
||||
@Body
|
||||
|
||||
<SpaceItem>
|
||||
<ProfileButton />
|
||||
</SpaceItem>
|
||||
}
|
||||
</Space>
|
||||
</HeaderExtraControlsLayout>
|
||||
<WindowContent>
|
||||
<ErrorBoundary>
|
||||
<ChildContent>
|
||||
@Body
|
||||
@if (Settings.Debug.EnableScriptDebugging)
|
||||
{
|
||||
<PowerShellConsole />
|
||||
}
|
||||
|
||||
@if (Settings.Debug.EnableScriptDebugging)
|
||||
{
|
||||
<PowerShellConsole/>
|
||||
}
|
||||
|
||||
<UpdateChecker/>
|
||||
</ChildContent>
|
||||
<ErrorContent>
|
||||
<Result Status="ResultStatus.Error"
|
||||
Title="Launcher Crashed"
|
||||
SubTitle="@RandomQuip"
|
||||
Class="crash-error">
|
||||
<Extra>
|
||||
<Button Type="ButtonType.Primary" OnClick="@(() => NavigationManager.NavigateTo("/", true))">View Library</Button>
|
||||
<Button OnClick="() => CopyError(context)">Copy Error</Button>
|
||||
</Extra>
|
||||
<ChildContent>
|
||||
<code>
|
||||
@context.StackTrace
|
||||
</code>
|
||||
</ChildContent>
|
||||
</Result>
|
||||
</ErrorContent>
|
||||
</ErrorBoundary>
|
||||
|
||||
<AntContainer/>
|
||||
<RedirectToLogin/>
|
||||
</WindowContent>
|
||||
</CustomWindow>
|
||||
<UpdateChecker />
|
||||
<AntContainer />
|
||||
<KeepAliveContainer />
|
||||
<RedirectToLogin />
|
||||
</ChildContent>
|
||||
<ErrorContent>
|
||||
<Result Status="ResultStatus.Error"
|
||||
Title="Launcher Crashed"
|
||||
SubTitle="@RandomQuip"
|
||||
Class="crash-error">
|
||||
<Extra>
|
||||
<Button Type="ButtonType.Primary" OnClick="@(() => NavigationManager.NavigateTo("/", true))">View Library</Button>
|
||||
<Button OnClick="() => CopyError(context)">Copy Error</Button>
|
||||
</Extra>
|
||||
<ChildContent>
|
||||
<code>
|
||||
@context.StackTrace
|
||||
</code>
|
||||
</ChildContent>
|
||||
</Result>
|
||||
</ErrorContent>
|
||||
</ErrorBoundary>
|
||||
</WindowContent>
|
||||
</CustomWindow>
|
||||
</CascadingValue>
|
||||
|
||||
@code {
|
||||
Models.Settings Settings = null;
|
||||
|
||||
public bool Importing = false;
|
||||
public bool Connecting = false;
|
||||
|
||||
public ConnectionState ConnectionState = new();
|
||||
|
||||
public IMessageService Messages { get; set; }
|
||||
|
||||
public static MainLayout _MainLayout { get; set; }
|
||||
|
|
@ -129,12 +138,17 @@
|
|||
|
||||
ImportService.OnImportComplete += OnImportComplete;
|
||||
ImportService.OnImportUpdated += OnImportUpdated;
|
||||
|
||||
AuthenticationService.OnOfflineModeChanged += OnOfflineModeChanged;
|
||||
|
||||
var randIndex = new Random().Next(0, CrashQuips.Length - 1);
|
||||
|
||||
RandomQuip = CrashQuips[randIndex];
|
||||
|
||||
if (!(await LANCommander.ValidateTokenAsync()) && !Settings.Authentication.OfflineMode)
|
||||
ConnectionState.IsConnected = await LANCommander.ValidateTokenAsync();
|
||||
ConnectionState.OfflineModeEnabled = Settings.Authentication.OfflineMode;
|
||||
|
||||
if (!ConnectionState.IsConnected && !ConnectionState.OfflineModeEnabled)
|
||||
NavigationManager.NavigateTo("/Authenticate");
|
||||
}
|
||||
|
||||
|
|
@ -148,6 +162,13 @@
|
|||
MessageService.Info($"Importing {status.CurrentItem.Name}");
|
||||
}
|
||||
|
||||
async void OnOfflineModeChanged(bool state)
|
||||
{
|
||||
_MainLayout.ConnectionState.OfflineModeEnabled = state;
|
||||
_MainLayout.ConnectionState.IsConnected = LANCommander.IsConnected();
|
||||
await InvokeAsync(_MainLayout.StateHasChanged);
|
||||
}
|
||||
|
||||
async Task CopyError(Exception ex)
|
||||
{
|
||||
await JS.InvokeVoidAsync("navigator.clipboard.writeText", ex.Message + "\n" + ex.StackTrace);
|
||||
|
|
@ -169,14 +190,19 @@
|
|||
|
||||
if (await LANCommander.ValidateTokenAsync(token))
|
||||
{
|
||||
ProfileService.SetOfflineMode(false);
|
||||
await AuthenticationService.Login();
|
||||
|
||||
MessageService.Success("Back Online!");
|
||||
|
||||
ConnectionState.IsConnected = true;
|
||||
ConnectionState.OfflineModeEnabled = false;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await LANCommander.PingAsync())
|
||||
{
|
||||
ProfileService.SetOfflineMode(false);
|
||||
await Logout();
|
||||
}
|
||||
else
|
||||
|
|
@ -185,7 +211,7 @@
|
|||
{
|
||||
Title = "Could Not Reconnect!",
|
||||
Icon = @<Icon Type="@IconType.Outline.ExclamationCircle"></Icon>,
|
||||
Content = "The LANCommander server could not be reached. Click stay offline and try later, or logout and fix your credentials.",
|
||||
Content = "The LANCommander server could not be reached. Click stay offline and try later, or logout and enter your credentials.",
|
||||
OkText = "Logout",
|
||||
CancelText = "Stay Offline",
|
||||
Centered = true,
|
||||
|
|
@ -203,7 +229,10 @@
|
|||
|
||||
async Task Logout()
|
||||
{
|
||||
await ProfileService.Logout();
|
||||
await AuthenticationService.Logout();
|
||||
|
||||
ConnectionState.IsConnected = false;
|
||||
ConnectionState.OfflineModeEnabled = false;
|
||||
|
||||
NavigationManager.NavigateTo("/Authenticate");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@ namespace LANCommander.SDK
|
|||
}
|
||||
}
|
||||
|
||||
public EventHandler<Exception> OnError;
|
||||
|
||||
public Client(string baseUrl, string defaultInstallDirectory)
|
||||
{
|
||||
DefaultInstallDirectory = defaultInstallDirectory;
|
||||
|
|
@ -129,195 +131,286 @@ namespace LANCommander.SDK
|
|||
|
||||
internal T PostRequest<T>(string route, object body, bool ignoreVersion = false)
|
||||
{
|
||||
if (Token == null)
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
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)
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
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)
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
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)
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
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 async Task<T> PutRequestAsync<T>(string route, object body, bool ignoreVersion = false)
|
||||
{
|
||||
if (Token == null)
|
||||
try
|
||||
{
|
||||
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.PutAsync<T>(request);
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
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.PutAsync<T>(request);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
internal T GetRequest<T>(string route, bool ignoreVersion = false)
|
||||
{
|
||||
if (Token == null)
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
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)
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
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<T> DeleteRequestAsync<T>(string route, bool ignoreVersion = false)
|
||||
{
|
||||
if (Token == null)
|
||||
try
|
||||
{
|
||||
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.DeleteAsync<T>(request);
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
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.DeleteAsync<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);
|
||||
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)
|
||||
{
|
||||
Logger?.LogError(ex, "An unknown error occurred while downloading from the server at route {Route}", route);
|
||||
|
||||
if (File.Exists(tempFile))
|
||||
File.Delete(tempFile);
|
||||
|
||||
tempFile = String.Empty;
|
||||
}
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "An unknown error occurred while downloading from the server at route {Route}", route);
|
||||
|
||||
if (File.Exists(tempFile))
|
||||
File.Delete(tempFile);
|
||||
|
||||
tempFile = String.Empty;
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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);
|
||||
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)
|
||||
{
|
||||
Logger?.LogError(ex, "An unknown error occurred while downloading from the server at route {Route}",
|
||||
route);
|
||||
|
||||
if (File.Exists(destination))
|
||||
File.Delete(destination);
|
||||
|
||||
destination = String.Empty;
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "An unknown error occurred while downloading from the server at route {Route}", route);
|
||||
|
||||
if (File.Exists(destination))
|
||||
File.Delete(destination);
|
||||
|
||||
destination = String.Empty;
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
internal TrackableStream StreamRequest(string route)
|
||||
|
|
@ -336,123 +429,164 @@ namespace LANCommander.SDK
|
|||
|
||||
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());
|
||||
try
|
||||
{
|
||||
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() };
|
||||
if (!ignoreVersion)
|
||||
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
||||
|
||||
request.AddFile(fileName, data, fileName);
|
||||
request.AddFile(fileName, data, fileName);
|
||||
|
||||
var response = ApiClient.Post<T>(request);
|
||||
var response = ApiClient.Post<T>(request);
|
||||
|
||||
return response;
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
try
|
||||
{
|
||||
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() };
|
||||
if (!ignoreVersion)
|
||||
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
||||
|
||||
request.AddFile(fileName, data, fileName);
|
||||
request.AddFile(fileName, data, fileName);
|
||||
|
||||
var response = await ApiClient.PostAsync<T>(request);
|
||||
var response = await ApiClient.PostAsync<T>(request);
|
||||
|
||||
return response;
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
try
|
||||
{
|
||||
var chunkRequest = new UploadChunkRequest();
|
||||
var maxChunkSize = 1024 * 1024 * 50;
|
||||
var initResponse = await PostRequestAsync<UploadInitResponse>("/Upload/Init", ignoreVersion);
|
||||
|
||||
chunkRequest.Start = stream.Position;
|
||||
var buffer = new byte[maxChunkSize];
|
||||
|
||||
if (stream.Position + maxChunkSize > stream.Length)
|
||||
while (stream.Position < stream.Length)
|
||||
{
|
||||
var bytes = stream.Length - stream.Position;
|
||||
var chunkRequest = new UploadChunkRequest();
|
||||
|
||||
buffer = new byte[bytes];
|
||||
chunkRequest.Start = stream.Position;
|
||||
|
||||
await stream.ReadAsync(buffer, 0, (int)(stream.Length - 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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
return initResponse.Key;
|
||||
}
|
||||
|
||||
public async Task<AuthToken> AuthenticateAsync(string username, string password, bool ignoreVersion = false)
|
||||
{
|
||||
var request = new RestRequest("/api/Auth/Login", Method.Post);
|
||||
|
||||
request.AddJsonBody(new AuthRequest()
|
||||
try
|
||||
{
|
||||
UserName = username,
|
||||
Password = password
|
||||
});
|
||||
var request = new RestRequest("/api/Auth/Login", Method.Post);
|
||||
|
||||
if (!ignoreVersion)
|
||||
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
||||
request.AddJsonBody(new AuthRequest()
|
||||
{
|
||||
UserName = username,
|
||||
Password = password
|
||||
});
|
||||
|
||||
var response = await ApiClient.ExecuteAsync<AuthToken>(request);
|
||||
if (!ignoreVersion)
|
||||
request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
|
||||
|
||||
if (response.ErrorException != null)
|
||||
{
|
||||
Logger?.LogError(response.ErrorException, "Authentication failed for user {UserName}", username);
|
||||
var response = await ApiClient.ExecuteAsync<AuthToken>(request);
|
||||
|
||||
throw response.ErrorException;
|
||||
if (response.ErrorException != null)
|
||||
{
|
||||
Logger?.LogError(response.ErrorException, "Authentication failed for user {UserName}", username);
|
||||
|
||||
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;
|
||||
Logger?.LogError("Authentication failed for user {UserName}: invalid username or password", username);
|
||||
throw new WebException("Invalid username or password");
|
||||
|
||||
default:
|
||||
Connected = false;
|
||||
Logger?.LogError("Authentication failed for user {UserName}: could not communicate with the server", username);
|
||||
throw new WebException("Could not communicate with the server");
|
||||
}
|
||||
}
|
||||
|
||||
switch (response.StatusCode)
|
||||
catch (Exception ex)
|
||||
{
|
||||
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;
|
||||
Logger?.LogError("Authentication failed for user {UserName}: invalid username or password", username);
|
||||
throw new WebException("Invalid username or password");
|
||||
|
||||
default:
|
||||
Connected = false;
|
||||
Logger?.LogError("Authentication failed for user {UserName}: could not communicate with the server", username);
|
||||
throw new WebException("Could not communicate with the server");
|
||||
OnError?.Invoke(this, ex);
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
Connected = false;
|
||||
}
|
||||
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
await ApiClient.ExecuteAsync(new RestRequest("/api/Auth/Logout", Method.Post));
|
||||
|
|
@ -476,6 +610,8 @@ namespace LANCommander.SDK
|
|||
if (!String.IsNullOrWhiteSpace(response?.Data?.Message))
|
||||
{
|
||||
Logger?.LogError(response.Data.Message);
|
||||
|
||||
OnError?.Invoke(this, response.ErrorException);
|
||||
|
||||
throw new Exception(response.Data.Message);
|
||||
}
|
||||
|
|
@ -521,7 +657,7 @@ namespace LANCommander.SDK
|
|||
|
||||
public async Task<bool> PingAsync()
|
||||
{
|
||||
var response = await ApiClient.ExecuteAsync(new RestRequest("/api/Ping", Method.Get));
|
||||
var response = await ApiClient.ExecuteAsync(new RestRequest("/api/Ping", Method.Head));
|
||||
|
||||
return response.StatusCode == HttpStatusCode.OK;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue