Skip to content
Open
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
78 changes: 60 additions & 18 deletions DivAcerManagerMax/DAMXClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;

namespace DivAcerManagerMax;
Expand All @@ -25,6 +26,7 @@ public class DAMXClient : IDisposable
private const int MaxRetryAttempts = 3;

private const int RetryDelayMs = 500;
private readonly SemaphoreSlim _commandLock = new(1, 1);

// Cache of available features
private HashSet<string> _availableFeatures = new();
Expand Down Expand Up @@ -149,15 +151,21 @@ public void Disconnect()
public async Task<JsonDocument> SendCommandAsync(string command, Dictionary<string, object> parameters = null)
{
var attempt = 0;

while (attempt < MaxRetryAttempts)
try
{
if (!IsConnected)
{
await ConnectAsync();

if (!IsConnected)
{
await ConnectAsync();
if (!IsConnected) throw new InvalidOperationException("Not connected to daemon");
}
throw new InvalidOperationException("Not connected to daemon");
}

await _commandLock.WaitAsync();

try
{
var request = new
{
command,
Expand All @@ -167,39 +175,50 @@ public async Task<JsonDocument> SendCommandAsync(string command, Dictionary<stri
var requestJson = JsonSerializer.Serialize(request);
var requestBytes = Encoding.UTF8.GetBytes(requestJson);

// Send request
await _socket.SendAsync(requestBytes, SocketFlags.None);

// Receive response
var buffer = new byte[4096];
var received = await _socket.ReceiveAsync(buffer, SocketFlags.None);

if (received > 0)
if (received <= 0)
{
var responseJson = Encoding.UTF8.GetString(buffer, 0, received);
return JsonDocument.Parse(responseJson);
ResetConnection();
attempt++;
await Task.Delay(RetryDelayMs);
continue;
}

// If we got here, we received 0 bytes - connection was closed
IsConnected = false;
var responseJson = Encoding.UTF8.GetString(buffer, 0, received);
return JsonDocument.Parse(responseJson);
}
catch (SocketException ex) when (
ex.SocketErrorCode == SocketError.ConnectionReset ||
ex.SocketErrorCode == SocketError.Shutdown ||
ex.SocketErrorCode == SocketError.ConnectionAborted)
{
ResetConnection();
attempt++;
await Task.Delay(RetryDelayMs);
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset ||
ex.SocketErrorCode == SocketError.Shutdown ||
ex.SocketErrorCode == SocketError.ConnectionAborted)
catch (JsonException ex)
{
// Connection was reset - try to reconnect
IsConnected = false;
Console.WriteLine($"Invalid JSON response from daemon: {ex.Message}");

ResetConnection();
attempt++;
await Task.Delay(RetryDelayMs);
}
catch (Exception ex)
{
Console.WriteLine($"Error communicating with daemon: {ex.Message}");
IsConnected = false;
ResetConnection();
throw;
}
finally
{
_commandLock.Release();
}
}

throw new IOException($"Failed to communicate with daemon after {MaxRetryAttempts} attempts");
}
Expand Down Expand Up @@ -484,6 +503,29 @@ protected virtual void Dispose(bool disposing)

_disposed = true;
}

private void ResetConnection()
{
IsConnected = false;

try
{
_socket?.Shutdown(SocketShutdown.Both);
}
catch
{
// Socket may already be closed.
}

try
{
_socket?.Dispose();
}
catch
{
// Ignore cleanup failure.
}
}
}

/// <summary>
Expand Down
24 changes: 12 additions & 12 deletions DivAcerManagerMax/DivAcerManagerMax.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.0"/>
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.3.0"/>
<PackageReference Include="Avalonia.Desktop" Version="11.3.0"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.0"/>
<PackageReference Include="Avalonia" Version="12.0.3"/>
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="12.0.3"/>
<PackageReference Include="Avalonia.Desktop" Version="12.0.3"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.3"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.3"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.0">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.15">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">none</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">all</PrivateAssets>
</PackageReference>
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" Version="2.0.0-rc5.4"/>
<PackageReference Include="Material.Icons" Version="3.0.0-preview1.1"/>
<PackageReference Include="Material.Icons.Avalonia" Version="2.4.1"/>
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" Version="2.1.0-dev-570"/>
<PackageReference Include="Material.Icons" Version="3.0.2"/>
<PackageReference Include="Material.Icons.Avalonia" Version="3.0.2"/>
<PackageReference Include="MaterialDesign.Icons" Version="1.1.0"/>
<PackageReference Include="MessageBox.Avalonia" Version="3.2.0"/>
<PackageReference Include="MessageBox.Avalonia" Version="12.0.0"/>
</ItemGroup>

<ItemGroup>
Expand Down
Loading