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

WIP: Initial dotnet 8 version and AOT #306

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore SharpCaster.sln
- name: tag
Expand Down
19 changes: 19 additions & 0 deletions SharpCaster.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// See https://aka.ms/new-console-template for more information
using Sharpcaster.Interfaces;
using Sharpcaster;
using Sharpcaster.Models.Media;

MdnsChromecastLocator locator = new();
var source = new CancellationTokenSource(TimeSpan.FromMilliseconds(1500));
var chromecasts = await locator.FindReceiversAsync(source.Token);

var chromecast = chromecasts.First();
var client = new ChromecastClient();
await client.ConnectChromecast(chromecast);
_ = await client.LaunchApplicationAsync("B3419EF5");

var media = new Media
{
ContentUrl = "https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/mp4/DesigningForGoogleCast.mp4"
};
_ = await client.MediaChannel.LoadAsync(media);
16 changes: 16 additions & 0 deletions SharpCaster.Console/SharpCaster.Console.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Sharpcaster\Sharpcaster.csproj" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions SharpCaster.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharpcaster", "Sharpcaster\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharpcaster.Test", "Sharpcaster.Test\Sharpcaster.Test.csproj", "{C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCaster.Console", "SharpCaster.Console\SharpCaster.Console.csproj", "{D31168F2-F70A-45A9-84B7-81A1B4956A2E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8C0A3A8-C6AC-4E1B-8D3B-E6764C45C35B}.Release|Any CPU.Build.0 = Release|Any CPU
{D31168F2-F70A-45A9-84B7-81A1B4956A2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D31168F2-F70A-45A9-84B7-81A1B4956A2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D31168F2-F70A-45A9-84B7-81A1B4956A2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D31168F2-F70A-45A9-84B7-81A1B4956A2E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 1 addition & 1 deletion Sharpcaster.Test/Sharpcaster.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions Sharpcaster/Channels/MediaChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private async Task<MediaStatus> SendAsync(IMessageWithId message, ChromecastAppl
}
catch (Exception ex)
{
Logger?.LogError($"Error sending message: {ex.Message}");
Logger?.LogError("Error sending message: {exceptionMessage}", ex.Message);
Status = null;
throw;
}
Expand All @@ -56,7 +56,7 @@ private async Task<MediaStatus> SendAsync(IMessageWithId message, ChromecastAppl
private async Task<MediaStatus> SendAsync(MediaSessionMessage message)
{
var chromecastStatus = Client.GetChromecastStatus();
message.MediaSessionId = Status?.First().MediaSessionId ?? throw new ArgumentNullException("MediaSessionId");
message.MediaSessionId = Status?.First().MediaSessionId ?? throw new ArgumentNullException(nameof(message), "MediaSessionID");
return await SendAsync(message, chromecastStatus.Applications[0]);
}

Expand Down
2 changes: 1 addition & 1 deletion Sharpcaster/Channels/ReceiverChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public async Task<ChromecastStatus> SetVolume(double level)
{
if (level < 0 || level > 1.0)
{
Logger?.LogError($"level must be between 0.0 and 1.0 - is {level}");
Logger?.LogError("level must be between 0.0 and 1.0 - is {level}", level);
throw new ArgumentException("level must be between 0.0 and 1.0", nameof(level));
}
return (await SendAsync<ReceiverStatusMessage>(new SetVolumeMessage() { Volume = new Models.Volume() { Level = level } })).Status;
Expand Down
5 changes: 5 additions & 0 deletions Sharpcaster/Channels/StatusChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,10 @@ protected virtual void OnStatusChanged()
{
StatusChanged?.Invoke(this, EventArgs.Empty);
}

public void ClearStatus()
{
Status = default;
}
}
}
104 changes: 58 additions & 46 deletions Sharpcaster/ChromeCastClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@
using Sharpcaster.Extensions;
using Sharpcaster.Interfaces;
using Sharpcaster.Messages;
using Sharpcaster.Messages.Connection;
using Sharpcaster.Messages.Heartbeat;
using Sharpcaster.Messages.Media;
using Sharpcaster.Messages.Multizone;
using Sharpcaster.Messages.Queue;
using Sharpcaster.Messages.Receiver;
using Sharpcaster.Models;
using Sharpcaster.Models.ChromecastStatus;
using Sharpcaster.Models.Media;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.Security;
Expand Down Expand Up @@ -44,13 +51,13 @@ public class ChromecastClient : IChromecastClient

private ILogger _logger = null;
private TcpClient _client;
private Stream _stream;
private SslStream _stream;
private TaskCompletionSource<bool> ReceiveTcs { get; set; }
private SemaphoreSlim SendSemaphoreSlim { get; } = new SemaphoreSlim(1, 1);

private IDictionary<string, Type> MessageTypes { get; set; }
private Dictionary<string, Type> MessageTypes { get; set; }
private IEnumerable<IChromecastChannel> Channels { get; set; }
private ConcurrentDictionary<int, object> WaitingTasks { get; } = new ConcurrentDictionary<int, object>();
private ConcurrentDictionary<int, SharpCasterTaskCompletionSource> WaitingTasks { get; } = new ConcurrentDictionary<int, SharpCasterTaskCompletionSource>();

public ChromecastClient(ILoggerFactory loggerFactory = null)
{
Expand All @@ -69,12 +76,20 @@ public ChromecastClient(ILoggerFactory loggerFactory = null)
serviceCollection.AddTransient<IChromecastChannel, MultiZoneChannel>();
serviceCollection.AddTransient<IChromecastChannel, SpotifyChannel>();
var messageInterfaceType = typeof(IMessage);
foreach (var type in (from t in typeof(IConnectionChannel).GetTypeInfo().Assembly.GetTypes()
where t.GetTypeInfo().IsClass && !t.GetTypeInfo().IsAbstract && messageInterfaceType.IsAssignableFrom(t) && t.GetTypeInfo().GetCustomAttribute<ReceptionMessageAttribute>() != null
select t))
{
serviceCollection.AddTransient(messageInterfaceType, type);
}
serviceCollection.AddTransient(messageInterfaceType, typeof(CloseMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(PingMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(ReceiverStatusMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(LoadFailedMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(LoadCancelledMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(MediaStatusMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(MultizoneStatusMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(DeviceUpdatedMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(QueueChangeMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(QueueItemsMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(QueueItemIdsMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(LaunchErrorMessage));
serviceCollection.AddTransient(messageInterfaceType, typeof(InvalidRequestMessage));

Init(serviceCollection);
}

Expand All @@ -97,8 +112,8 @@ private void Init(IServiceCollection serviceCollection)
Channels = channels;

_logger = serviceProvider.GetService<ILogger<ChromecastClient>>();
_logger?.LogDebug(MessageTypes.Keys.ToString(","));
_logger?.LogDebug(Channels.ToString(","));
_logger?.LogDebug("MessageTypes: {MessageTypes}", MessageTypes.Keys.ToString(","));
_logger?.LogDebug("Channels: {Channels}", Channels.ToString(","));

foreach (var channel in Channels)
{
Expand Down Expand Up @@ -160,22 +175,29 @@ private void Receive()
{
HeartbeatChannel.StopTimeoutTimer();
}
channel?.Logger?.LogTrace($"RECEIVED: {payload}");
channel?.Logger?.LogTrace("RECEIVED: {payload}", payload);

var message = JsonConvert.DeserializeObject<MessageWithId>(payload);
if (MessageTypes.TryGetValue(message.Type, out Type type))
{
object tcs = null;
try
{
var response = (IMessage)JsonConvert.DeserializeObject(payload, type);
await channel.OnMessageReceivedAsync(response);
TaskCompletionSourceInvoke(ref tcs, message, "SetResult", response);
if (message.HasRequestId)
{
WaitingTasks.TryRemove(message.RequestId, out SharpCasterTaskCompletionSource tcs);
tcs.SetResult(response as IMessageWithId);
}
}
catch (Exception ex)
{
_logger?.LogError($"Exception processing the Response: {ex.Message}");
TaskCompletionSourceInvoke(ref tcs, message, "SetException", ex, new Type[] { typeof(Exception) });
_logger?.LogError("Exception processing the Response: {Message}", ex.Message);
if (message.HasRequestId)
{
WaitingTasks.TryRemove(message.RequestId, out SharpCasterTaskCompletionSource tcs);
tcs.SetException(ex);
}
}
}
else
Expand All @@ -187,35 +209,19 @@ private void Receive()
}
else
{
_logger?.LogError($"Couldn't parse the channel from: {castMessage.Namespace} : {payload}");
_logger?.LogError("Couldn't parse the channel from: {NameSpace} : {Payload}", castMessage.Namespace, payload);
}
}
}
catch (Exception exception)
{
_logger?.LogError($"Error in receive loop: {exception.Message}");
_logger?.LogError("Error in receive loop: {Message}", exception.Message);
//await Dispose(false);
ReceiveTcs.SetResult(true);
}
});
}

private void TaskCompletionSourceInvoke(ref object tcs, MessageWithId message, string method, object parameter, Type[] types = null)
{
if (tcs == null)
{
if (message.HasRequestId && WaitingTasks.TryRemove(message.RequestId, out object newtcs))
{
tcs = newtcs;
}
}
if (tcs != null)
{
var tcsType = tcs.GetType();
(types == null ? tcsType.GetMethod(method) : tcsType.GetMethod(method, types)).Invoke(tcs, new object[] { parameter });
}
}

public async Task SendAsync(ILogger channelLogger, string ns, IMessage message, string destinationId)
{
var castMessage = CreateCastMessage(ns, destinationId);
Expand All @@ -228,11 +234,18 @@ private async Task SendAsync(ILogger channelLogger, CastMessage castMessage)
await SendSemaphoreSlim.WaitAsync();
try
{
(channelLogger ?? _logger)?.LogTrace($"SENT : {castMessage.DestinationId}: {castMessage.PayloadUtf8}");
(channelLogger ?? _logger)?.LogTrace("SENT: {DestinationId}: {PayloadUtf8}", castMessage.DestinationId, castMessage.PayloadUtf8);
#if NETSTANDARD2_0
byte[] message = castMessage.ToProto();
var networkStream = _stream;
await networkStream.WriteAsync(message, 0, message.Length);
await networkStream.FlushAsync();
#else
ReadOnlyMemory<byte> message = castMessage.ToProto();
#endif
#if NETSTANDARD2_0
await _stream.WriteAsync(message, 0, message.Length);
#else
await _stream.WriteAsync(message);
#endif
await _stream.FlushAsync();
}
finally
{
Expand All @@ -252,17 +265,17 @@ private CastMessage CreateCastMessage(string ns, string destinationId)

public async Task<TResponse> SendAsync<TResponse>(ILogger channelLogger, string ns, IMessageWithId message, string destinationId) where TResponse : IMessageWithId
{
var taskCompletionSource = new TaskCompletionSource<TResponse>();
var taskCompletionSource = new SharpCasterTaskCompletionSource();
WaitingTasks[message.RequestId] = taskCompletionSource;
await SendAsync(channelLogger, ns, message, destinationId);
return await taskCompletionSource.Task.TimeoutAfter(RECEIVE_TIMEOUT);
return (TResponse)await taskCompletionSource.Task.TimeoutAfter(RECEIVE_TIMEOUT);
}

public async Task DisconnectAsync()
{
foreach (var channel in GetStatusChannels())
{
channel.GetType().GetProperty("Status").SetValue(channel, null);
channel.ClearStatus();
}
HeartbeatChannel.StopTimeoutTimer();
HeartbeatChannel.StatusChanged -= HeartBeatTimedOut;
Expand Down Expand Up @@ -299,7 +312,7 @@ private void Dispose(IDisposable disposable, Action action)
}
catch (Exception ex)
{
_logger?.LogError($"Error on disposing. {ex.Message}");
_logger?.LogError("Error on disposing. {Message}", ex.Message);
}
finally
{
Expand Down Expand Up @@ -349,10 +362,9 @@ public async Task<ChromecastStatus> LaunchApplicationAsync(string applicationId,
return await ReceiverChannel.GetChromecastStatusAsync();
}

private IEnumerable<IChromecastChannel> GetStatusChannels()
private IEnumerable<IStatusChannel<object>> GetStatusChannels()
{
var statusChannelType = typeof(IStatusChannel<>);
return Channels.Where(c => c.GetType().GetInterfaces().Any(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == statusChannelType));
return Channels.OfType<IStatusChannel<object>>();
}

/// <summary>
Expand All @@ -361,7 +373,7 @@ private IEnumerable<IChromecastChannel> GetStatusChannels()
/// <returns>a dictionnary of namespace/status</returns>
public IDictionary<string, object> GetStatuses()
{
return GetStatusChannels().ToDictionary(c => c.Namespace, c => c.GetType().GetProperty("Status").GetValue(c));
return GetStatusChannels().ToDictionary(c => c.Namespace, c => c.Status);
}

public ChromecastStatus GetChromecastStatus()
Expand Down
13 changes: 8 additions & 5 deletions Sharpcaster/ChromecastLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ namespace Sharpcaster
public class MdnsChromecastLocator : IChromecastLocator
{
public event EventHandler<ChromecastReceiver> ChromecastReceivedFound;
private IList<ChromecastReceiver> DiscoveredDevices { get; }
private List<ChromecastReceiver> DiscoveredDevices { get; }
private readonly ServiceBrowser _serviceBrowser;
private SemaphoreSlim ServiceAddedSemaphoreSlim { get; } = new SemaphoreSlim(1, 1);

private static readonly string[] stringArray = new string[] { "", "" };

public MdnsChromecastLocator()
{
DiscoveredDevices = new List<ChromecastReceiver>();
Expand All @@ -43,16 +46,16 @@ private void OnServiceAdded(object sender, ServiceAnnouncementEventArgs e)
{
return i.Split('=');
}
return new string[] { "", "" };
return stringArray;
})
.ToDictionary(y => y[0], y => y[1]);
if (!txtValues.ContainsKey("fn")) return;
if (!txtValues.TryGetValue("fn", out string value)) return;
var ip = e.Announcement.Addresses[0];
Uri.TryCreate("https://" + ip, UriKind.Absolute, out Uri myUri);
var chromecast = new ChromecastReceiver
{
DeviceUri = myUri,
Name = txtValues["fn"],
Name = value,
Model = txtValues["md"],
Version = txtValues["ve"],
ExtraInformation = txtValues,
Expand Down Expand Up @@ -90,7 +93,7 @@ public async Task<IEnumerable<ChromecastReceiver>> FindReceiversAsync(Cancellati
_serviceBrowser.StartBrowse("_googlecast._tcp");
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(100);
await Task.Delay(100, cancellationToken);
}
_serviceBrowser.StopBrowse();
return DiscoveredDevices;
Expand Down
5 changes: 5 additions & 0 deletions Sharpcaster/Interfaces/IStatusChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,10 @@ public interface IStatusChannel<TStatus> : IChromecastChannel
/// Gets the status
/// </summary>
TStatus Status { get; }

/// <summary>
/// Clears the status
/// </summary>
void ClearStatus();
}
}
Loading
Loading