Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep track of reservation time client-side and add debug settings to aid development #12

Merged
merged 1 commit into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-installer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: Test
run: dotnet test --no-restore --verbosity normal Lanpartyseating.Desktop.Tests --configuration Release
- name: Build
run: dotnet build LanpartySeating.Desktop.Installer/LanpartySeating.Desktop.Installer.wixproj --configuration Release --property:Version=1.0.21 /p:InstallerPlatform=${{ matrix.arch }}
run: dotnet build LanpartySeating.Desktop.Installer/LanpartySeating.Desktop.Installer.wixproj --configuration Release --property:Version=1.0.22 /p:InstallerPlatform=${{ matrix.arch }}
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,12 @@
<DirectoryRef Id="CONFIGFOLDER">
<!-- Create config folder to store appsettings.json -->
<!-- Make sure regular users can't read the config file because it contains credentials -->
<Component Id="ConfigPermissions" Guid="16b25c34-404e-47d5-9fd5-b88eb507216d">
<Component Id="ConfigPermissions" Bitness="always64" Guid="16b25c34-404e-47d5-9fd5-b88eb507216d">
<CreateFolder>
<!-- Deny read access to Everyone -->
<util:PermissionEx User="Users"
GenericRead="no"
Read="no"
GenericExecute="no"
Domain="[LOCAL_MACHINE]" />

<util:PermissionEx User="Administrators"
GenericAll="yes"
GenericWrite="yes"
Delete="yes"
Domain="[LOCAL_MACHINE]" />

<util:PermissionEx User="LocalService"
GenericRead="yes"
GenericWrite="yes"
Delete="yes"
Domain="[LOCAL_MACHINE]" />
<util:PermissionEx User="Administrators" GenericAll="yes" />
<util:PermissionEx User="LocalService" GenericRead="yes" GenericWrite="yes" Delete="yes" />
<!-- Explicitly deny all common permissions for the "Users" group -->
<util:PermissionEx User="Users" GenericRead="no" GenericWrite="no" Delete="no" />
</CreateFolder>
</Component>
</DirectoryRef>
Expand Down
25 changes: 23 additions & 2 deletions Lanpartyseating.Desktop.Tests/UtilsTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Lanpartyseating.Desktop.Business;
using Lanpartyseating.Desktop.Config;
using Microsoft.Extensions.Options;

namespace Lanpartyseating.Desktop.Tests;

public class UtilsTests
{
private readonly Utils _utils = new();

private readonly Utils _utils = new(Options.Create(new DebugOptions()));
[Theory]
[InlineData("LAN-GAMING-01", 1, true)]
[InlineData("LAN-GAMING-01", 2, false)]
Expand All @@ -19,4 +21,23 @@ public void Test_When_ForThisStationCalled_Then_MatchingStationNumberAndHostname
// Assert
Assert.Equal(expectedResult, result);
}

[Theory]
[InlineData("LAN-GAMING-01", 1, true)]
[InlineData("LAN-GAMING-01", 2, true)]
[InlineData("LAN-GAMING-1", 1, true)]
[InlineData("LAN-GAMING-70", 70, true)]
public void Test_When_ForThisStationCalled_And_ReactToAllStationsEnabled_Then_ReturnTrue(string hostname, int stationNumber, bool expectedResult)
{
// Create servicecollection with debug options true
var utils = new Utils(Options.Create(new DebugOptions { ReactToAllStations = true }));

// Act
var result = utils.ForThisStation(stationNumber, hostname);

// Assert
Assert.Equal(expectedResult, result);
}


}
9 changes: 2 additions & 7 deletions Lanpartyseating.Desktop.sln.DotSettings.user
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=10ba09a3_002D1640_002D4bef_002Db3c4_002D8eaf8e690b9b/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Test_When_ForThisStationCalled_Then_MatchingStationNumberAndHostnameReturnTrue" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=3c5c5451_002Dbe2a_002D4e87_002Da7ba_002D85cf30564cd6/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="UtilsTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;xUnit::8B549C98-F78C-422A-8C26-E1D7A0881CAD::net8.0-windows::Lanpartyseating.Desktop.Tests.UtilsTests.Test_When_ForThisStationCalled_Then_MatchingStationNumberAndHostnameReturnTrue&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=27b22507_002D9043_002D43f5_002D9688_002Df4a293a50701/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="Test_When_ForThisStationCalled_Then_MatchingStationNumberAndHostnameReturnTrue #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;xUnit::8B549C98-F78C-422A-8C26-E1D7A0881CAD::net8.0-windows::Lanpartyseating.Desktop.Tests.UtilsTests.Test_When_ForThisStationCalled_Then_MatchingStationNumberAndHostnameReturnTrue&lt;/TestId&gt;&#xD;
&lt;TestId&gt;xUnit::8B549C98-F78C-422A-8C26-E1D7A0881CAD::net8.0-windows::Lanpartyseating.Desktop.Tests.UtilsTests&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>
40 changes: 25 additions & 15 deletions Lanpartyseating.Desktop/Business/Callbacks.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using Lanpartyseating.Desktop.Config;
using Lanpartyseating.Desktop.Contracts;
using Lanpartyseating.Desktop.Contracts;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Win32;
using Message = Phoenix.Message;

namespace Lanpartyseating.Desktop.Business;
Expand All @@ -13,23 +8,28 @@ public class Callbacks
{
private readonly ILogger<Callbacks> _logger;
private readonly Utils _utils;
private readonly SeatingOptions _options;
private readonly ISessionManager _sessionManager;
private readonly Timekeeper _timekeeper;

public Callbacks(ILogger<Callbacks> logger, IOptions<SeatingOptions> options, Utils utils)
public Callbacks(ILogger<Callbacks> logger,
Utils utils,
ISessionManager sessionManager,
Timekeeper timekeeper)
{
_logger = logger;
_utils = utils;
_options = options.Value;
_sessionManager = sessionManager;
_timekeeper = timekeeper;
}

public void NewReservation(Message payload)
{
var payloadObject = payload.Payload.Unbox<NewReservation>();
var newReservation = payload.Payload.Unbox<NewReservation>();

_logger.LogInformation($"New reservation created for station #{payloadObject.StationNumber}");
if (!_utils.ForThisStation(payloadObject.StationNumber, Environment.MachineName)) return;
_logger.LogInformation($"New reservation created for station #{newReservation.StationNumber}");
if (!_utils.ForThisStation(newReservation.StationNumber, Environment.MachineName)) return;

_utils.LoginInteractiveSession(_options.GamerAccountUsername, _options.GamerAccountPassword);
_timekeeper.StartSession(newReservation.Start, newReservation.End);
}

public void TournamentStart(Message payload)
Expand All @@ -39,7 +39,7 @@ public void TournamentStart(Message payload)
_logger.LogInformation($"Tournament started for station #{payloadObject.StationNumber}");
if (!_utils.ForThisStation(payloadObject.StationNumber, Environment.MachineName)) return;

_utils.LoginInteractiveSession(_options.TournamentAccountUsername, _options.TournamentAccountPassword);
_sessionManager.SignInTournamentAccount();
}

public void CancelReservation(Message payload)
Expand All @@ -49,6 +49,16 @@ public void CancelReservation(Message payload)
_logger.LogInformation($"Reservation cancelled for station #{payloadObject.StationNumber}");
if (!_utils.ForThisStation(payloadObject.StationNumber, Environment.MachineName)) return;

_utils.LogoffInteractiveSession();
_timekeeper.EndSession();
}

public void ExtendReservation(Message payload)
{
var extendReservation = payload.Payload.Unbox<ExtendReservation>();

_logger.LogInformation($"Reservation extended for station #{extendReservation.StationNumber}");
if (!_utils.ForThisStation(extendReservation.StationNumber, Environment.MachineName)) return;

_timekeeper.ExtendSession(extendReservation.End);
}
}
29 changes: 29 additions & 0 deletions Lanpartyseating.Desktop/Business/DummySessionManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.Extensions.Logging;

namespace Lanpartyseating.Desktop.Business;

public class DummySessionManager : ISessionManager
{
private readonly ILogger _logger;

public DummySessionManager(ILogger<DummySessionManager> logger)
{
_logger = logger;
_logger.LogInformation("The dummy session manager is in use");
}

public void SignInGamerAccount()
{
_logger.LogInformation("The client would have logged in an interactive session for the gamer account now");
}

public void SignInTournamentAccount()
{
_logger.LogInformation("The client would have logged in an interactive session for the tournament account now");
}

public void SignOut()
{
_logger.LogInformation("The client would have logged out an the current interactive session now");
}
}
8 changes: 8 additions & 0 deletions Lanpartyseating.Desktop/Business/ISessionManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Lanpartyseating.Desktop.Business;

public interface ISessionManager
{
public void SignInGamerAccount();
public void SignInTournamentAccount();
public void SignOut();
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public PhoenixChannelReactorService(IOptions<SeatingOptions> options, Callbacks
_desktopChannel.On("new_reservation", callbacks.NewReservation);
_desktopChannel.On("cancel_reservation", callbacks.CancelReservation);
_desktopChannel.On("tournament_start", callbacks.TournamentStart);
_desktopChannel.On("extend_reservation", callbacks.ExtendReservation);
}

public void Connect()
Expand Down
99 changes: 99 additions & 0 deletions Lanpartyseating.Desktop/Business/Timekeeper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using Microsoft.Extensions.Logging;

namespace Lanpartyseating.Desktop.Business;

using System;
using System.Threading;

public class Timekeeper : IDisposable
{
private readonly ILogger _logger;
private readonly ISessionManager _sessionManager;
private Timer _timer;
private DateTimeOffset _sessionEndTime;
private readonly object _lock = new();

public Timekeeper(ILogger<Timekeeper> logger, ISessionManager sessionManager)
{
_logger = logger;
_sessionManager = sessionManager;
_timer = new Timer(SessionEnded, null, Timeout.Infinite, Timeout.Infinite);

Check warning on line 20 in Lanpartyseating.Desktop/Business/Timekeeper.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Nullability of reference types in type of parameter 'state' of 'void Timekeeper.SessionEnded(object state)' doesn't match the target delegate 'TimerCallback' (possibly because of nullability attributes).

Check warning on line 20 in Lanpartyseating.Desktop/Business/Timekeeper.cs

View workflow job for this annotation

GitHub Actions / build (x64)

Nullability of reference types in type of parameter 'state' of 'void Timekeeper.SessionEnded(object state)' doesn't match the target delegate 'TimerCallback' (possibly because of nullability attributes).

Check warning on line 20 in Lanpartyseating.Desktop/Business/Timekeeper.cs

View workflow job for this annotation

GitHub Actions / build (arm64)

Nullability of reference types in type of parameter 'state' of 'void Timekeeper.SessionEnded(object state)' doesn't match the target delegate 'TimerCallback' (possibly because of nullability attributes).

Check warning on line 20 in Lanpartyseating.Desktop/Business/Timekeeper.cs

View workflow job for this annotation

GitHub Actions / build (arm64)

Nullability of reference types in type of parameter 'state' of 'void Timekeeper.SessionEnded(object state)' doesn't match the target delegate 'TimerCallback' (possibly because of nullability attributes).
}

public void StartSession(DateTimeOffset startTime, DateTimeOffset endTime)
{
lock (_lock)
{
if (endTime <= startTime)
{
throw new ArgumentException("End time must be later than start time.");
}

if (endTime <= DateTimeOffset.UtcNow)
{
throw new ArgumentException("End time must be in the future.");
}

_sessionEndTime = endTime;
var duration = endTime - DateTimeOffset.UtcNow;

// If the start time is in the future, delay the timer start
if (startTime > DateTimeOffset.UtcNow)
{
_timer.Change(startTime - DateTimeOffset.UtcNow, Timeout.InfiniteTimeSpan);
}
else
{
_timer.Change(duration, Timeout.InfiniteTimeSpan);
}

_logger.LogInformation($"Session started. Will end at {endTime}.");
_sessionManager.SignInGamerAccount();
}
}

public void ExtendSession(DateTimeOffset newEndTime)
{
lock (_lock)
{
if (newEndTime <= DateTimeOffset.UtcNow)
{
throw new ArgumentException("New end time must be in the future.");
}

if (newEndTime > _sessionEndTime)
{
_sessionEndTime = newEndTime;
var duration = newEndTime - DateTimeOffset.UtcNow;
_timer.Change(duration, Timeout.InfiniteTimeSpan);
_logger.LogInformation($"Session extended. New end time: {newEndTime}.");
}
else
{
_logger.LogInformation("New end time must be later than the current end time.");
}
}
}

public void EndSession()
{
lock (_lock)
{
_timer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
_sessionEndTime = DateTimeOffset.MinValue;
_logger.LogInformation("Session forcibly ended.");
_sessionManager.SignOut();
}
}

private void SessionEnded(object state)
{
_logger.LogInformation("Session ended.");
_sessionManager.SignOut();
}

public void Dispose()
{
_timer?.Dispose();
}
}
47 changes: 10 additions & 37 deletions Lanpartyseating.Desktop/Business/Utils.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,29 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Lanpartyseating.Desktop.Config;
using Microsoft.Extensions.Options;
using Microsoft.Win32;

namespace Lanpartyseating.Desktop.Business;

public class Utils
{

[DllImport("wtsapi32.dll", SetLastError = true)]
[return:MarshalAs(UnmanagedType.Bool)]
private static extern bool WTSLogoffSession(IntPtr hServer, int sessionId, bool bWait);
private readonly IOptions<DebugOptions> _debugOptions;

[DllImport("Kernel32.dll", SetLastError = true)]
[return:MarshalAs(UnmanagedType.U4)]
private static extern int WTSGetActiveConsoleSessionId();

public void LoginInteractiveSession(string username, string password)
public Utils(IOptions<DebugOptions> debugOptions)
{
var winlogonRegPath = @"Software\Microsoft\Windows NT\CurrentVersion\Winlogon";

// Enable autologon
Registry.SetValue($@"HKEY_LOCAL_MACHINE\{winlogonRegPath}", "AutoAdminLogon", 1, RegistryValueKind.DWord);

// Don't autologon as soon as the session is logged out
Registry.SetValue($@"HKEY_LOCAL_MACHINE\{winlogonRegPath}", "ForceAutoLogon", 0, RegistryValueKind.DWord);

// Set autologon username
Registry.SetValue($@"HKEY_LOCAL_MACHINE\{winlogonRegPath}", "DefaultUserName", username, RegistryValueKind.String);

// Set autologon password
Registry.SetValue($@"HKEY_LOCAL_MACHINE\{winlogonRegPath}", "DefaultPassword", password, RegistryValueKind.String);

// Trigger autologon on next winlogon start
Registry.LocalMachine.DeleteSubKeyTree($@"{winlogonRegPath}\AutoLogonChecked", false);

// Kill winlogon
var processes = Process.GetProcessesByName("winlogon");
foreach (var process in processes)
{
process.Kill();
}
_debugOptions = debugOptions;
}

public void LogoffInteractiveSession()
{
var sessionId = WTSGetActiveConsoleSessionId();
WTSLogoffSession(IntPtr.Zero, sessionId, false);
}

public bool ForThisStation(int stationNumber, string hostname)
{
if (_debugOptions.Value.ReactToAllStations)
{
return true;
}

var regex = new Regex(@"^LAN-GAMING-(\d+)$");
var match = regex.Match(hostname);
if (!match.Success)
Expand Down
Loading
Loading