diff --git a/.linting/roslynator.ruleset b/.linting/roslynator.ruleset index 967ecd465..52a4ec943 100644 --- a/.linting/roslynator.ruleset +++ b/.linting/roslynator.ruleset @@ -25,7 +25,14 @@ Just add ruleset file to a solution and open it. + + + + + + + diff --git a/.vscode/daemon.code-snippets b/.vscode/daemon.code-snippets index 4918506db..f2c7e35cb 100644 --- a/.vscode/daemon.code-snippets +++ b/.vscode/daemon.code-snippets @@ -12,6 +12,14 @@ // "description": "Log output to console" // } { + "nullcheck": { + "scope": "csharp", + "prefix": "nullcheck", + "body": [ + "_ = $1 ??", + " throw new NetDaemonArgumentNullException(nameof($1));" + ] + }, "fact": { "scope": "csharp", "prefix": "fact", diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs b/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs index 1c5139237..4c1615908 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -31,6 +32,7 @@ public CodeManager(IEnumerable daemonAppTypes, ILogger logger, IYamlConfig _yamlConfig = yamlConfig; } + [SuppressMessage("", "CA1065")] public int Count => _loadedDaemonApps?.Count() ?? throw new NetDaemonNullReferenceException("_loadedDaemonApps cannot be null"); // Internal for testing @@ -57,7 +59,8 @@ public IEnumerable InstanceDaemonApps() { try { - var yamlAppConfig = new YamlAppConfig(_loadedDaemonApps, File.OpenText(file), _yamlConfig, file); + using var fileReader = File.OpenText(file); + var yamlAppConfig = new YamlAppConfig(_loadedDaemonApps, fileReader, _yamlConfig, file); foreach (var appInstance in yamlAppConfig.Instances) { diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs index 9f668ef94..f413e67c6 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs @@ -1,7 +1,9 @@ -using System.Reflection; +using System.Globalization; +using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using NetDaemon.Common.Exceptions; [assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")] @@ -15,6 +17,9 @@ public static class TaskExtensions { public static async Task InvokeAsync(this MethodInfo mi, object? obj, params object?[]? parameters) { + _ = mi ?? + throw new NetDaemonArgumentNullException(nameof(mi)); + dynamic? awaitable = mi.Invoke(obj, parameters); if (awaitable != null) await awaitable.ConfigureAwait(false); @@ -25,6 +30,9 @@ public static class ConfigStringExtensions { public static string ToPythonStyle(this string str) { + _ = str ?? + throw new NetDaemonArgumentNullException(nameof(str)); + var build = new StringBuilder(str.Length); bool isStart = true; foreach (char c in str) @@ -33,13 +41,16 @@ public static string ToPythonStyle(this string str) build.Append('_'); else isStart = false; - build.Append(char.ToLower(c)); + build.Append(char.ToLower(c, CultureInfo.InvariantCulture)); } return build.ToString(); } public static string ToCamelCase(this string str) { + _ = str ?? + throw new NetDaemonArgumentNullException(nameof(str)); + var build = new StringBuilder(); bool nextIsUpper = false; bool isFirstCharacter = true; @@ -51,7 +62,7 @@ public static string ToCamelCase(this string str) continue; } - build.Append(nextIsUpper || isFirstCharacter ? char.ToUpper(c) : c); + build.Append(nextIsUpper || isFirstCharacter ? char.ToUpper(c, CultureInfo.InvariantCulture) : c); nextIsUpper = false; isFirstCharacter = false; } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs index 8b0c3604b..6cb10e8b2 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using NetDaemon.Common; @@ -26,6 +27,8 @@ public YamlAppConfig(IEnumerable types, TextReader reader, IYamlConfig yam _yamlFilePath = yamlFilePath; } + [SuppressMessage("", "CA1508")] + [SuppressMessage("", "CA1065")] public IEnumerable Instances { get @@ -59,7 +62,7 @@ public IEnumerable Instances } } } - catch (System.Exception e) + catch (Exception e) { throw new NetDaemonException($"Error instancing application {appId}", e); } @@ -74,10 +77,10 @@ public IEnumerable Instances YamlMappingNode appNode, string? appId) { - var netDaemonApp = (INetDaemonAppBase?)Activator.CreateInstance(netDaemonAppType); + _ = appNode ?? + throw new NetDaemonArgumentNullException(nameof(appNode)); - if (netDaemonApp == null) - return null; + var netDaemonApp = (INetDaemonAppBase?)Activator.CreateInstance(netDaemonAppType); foreach (KeyValuePair entry in appNode.Children) { @@ -106,7 +109,8 @@ public IEnumerable Instances return netDaemonApp; } - private object? InstanceProperty(Object? parent, Type instanceType, YamlNode node) + [SuppressMessage("", "CA1508")] // Weird bug that this should not warn! + private object? InstanceProperty(object? parent, Type instanceType, YamlNode node) { if (node.NodeType == YamlNodeType.Scalar) { @@ -197,7 +201,8 @@ private void ReplaceSecretIfExists(YamlScalarNode scalarNode) { return null; } - return ((YamlScalarNode)classChild.Value)?.Value?.ToLowerInvariant(); + var scalarNode = (YamlScalarNode)classChild.Value; + return scalarNode.Value?.ToLowerInvariant(); } } } \ No newline at end of file diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs index d7965edaf..538137a71 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.CompilerServices; using Microsoft.Extensions.Options; @@ -24,7 +25,11 @@ public class YamlConfig : IYamlConfig public YamlConfig(IOptions netDaemonSettings) { + _ = netDaemonSettings ?? + throw new NetDaemonArgumentNullException(nameof(netDaemonSettings)); + _configFolder = netDaemonSettings.Value.GetAppSourceDirectory(); + _secrets = GetAllSecretsFromPath(_configFolder); } @@ -53,9 +58,11 @@ public IEnumerable GetAllConfigFilePaths() internal static Dictionary GetSecretsFromSecretsYaml(string file) { - return GetSecretsFromSecretsYaml(File.OpenText(file)); + using var fileReader = File.OpenText(file); + return GetSecretsFromSecretsYaml(fileReader); } + [SuppressMessage("", "CA1508")] // TODO: Need to refactor this internal static Dictionary GetSecretsFromSecretsYaml(TextReader reader) { var result = new Dictionary(); @@ -94,8 +101,8 @@ public IEnumerable GetAllConfigFilePaths() if (!result.ContainsKey(fileDirectory)) { - var secretsFromFile = GetSecretsFromSecretsYaml(file); - result[fileDirectory] = secretsFromFile; + result[fileDirectory] = (Dictionary?)GetSecretsFromSecretsYaml(file) ?? + new Dictionary(); } } return result; diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs index 9d121c82e..0b13fe40f 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlExtensions.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; +using NetDaemon.Common.Exceptions; using YamlDotNet.RepresentationModel; [assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")] @@ -25,14 +26,24 @@ public static class YamlExtensions { public static PropertyInfo? GetYamlProperty(this Type type, string propertyName) { + _ = type ?? + throw new NetDaemonArgumentNullException(nameof(type)); + // Lets try convert from python style to CamelCase + var prop = type.GetProperty(propertyName) ?? type.GetProperty(propertyName.ToCamelCase()); return prop; } public static object? ToObject(this YamlScalarNode node, Type valueType) { + _ = valueType ?? + throw new NetDaemonArgumentNullException(nameof(valueType)); + _ = node ?? + throw new NetDaemonArgumentNullException(nameof(node)); + Type? underlyingNullableType = Nullable.GetUnderlyingType(valueType); + if (underlyingNullableType != null) { // It is nullable type diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs b/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs index 985c9f2e3..3a0c0e36e 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/DaemonAppExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; using System.Linq; using System.Reflection; @@ -6,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; using NetDaemon.Common.Fluent; using NetDaemon.Common.Reactive; using NetDaemon.Daemon.Config; @@ -16,9 +18,15 @@ namespace NetDaemon.Daemon { public static class DaemonAppExtensions { - public static async Task HandleAttributeInitialization(this INetDaemonAppBase netDaemonApp, INetDaemon _daemon) + public static async Task HandleAttributeInitialization(this INetDaemonAppBase netDaemonApp, INetDaemon daemon) { + _ = daemon ?? + throw new NetDaemonArgumentNullException(nameof(daemon)); + _ = netDaemonApp ?? + throw new NetDaemonArgumentNullException(nameof(netDaemonApp)); + var netDaemonAppType = netDaemonApp.GetType(); + foreach (var method in netDaemonAppType.GetMethods()) { foreach (var attr in method.GetCustomAttributes(false)) @@ -28,11 +36,11 @@ public static async Task HandleAttributeInitialization(this INetDaemonAppBase ne switch (attr) { case HomeAssistantServiceCallAttribute: - await HandleServiceCallAttribute(_daemon, daemonApp, method, true).ConfigureAwait(false); + await HandleServiceCallAttribute(daemon, daemonApp, method, true).ConfigureAwait(false); break; case HomeAssistantStateChangedAttribute hassStateChangedAttribute: - HandleStateChangedAttribute(_daemon, hassStateChangedAttribute, daemonApp, method); + HandleStateChangedAttribute(daemon, hassStateChangedAttribute, daemonApp, method); break; } } @@ -41,7 +49,7 @@ public static async Task HandleAttributeInitialization(this INetDaemonAppBase ne switch (attr) { case HomeAssistantServiceCallAttribute: - await HandleServiceCallAttribute(_daemon, daemonRxApp, method, false).ConfigureAwait(false); + await HandleServiceCallAttribute(daemon, daemonRxApp, method, false).ConfigureAwait(false); break; } } @@ -91,6 +99,7 @@ private static (bool, string) CheckIfStateChangedSignatureIsOk(MethodInfo method return (true, string.Empty); } + [SuppressMessage("", "CA1031")] private static async Task HandleServiceCallAttribute(INetDaemon _daemon, NetDaemonAppBase netDaemonApp, MethodInfo method, bool async = true) { var (signatureOk, err) = CheckIfServiceCallSignatureIsOk(method, async); @@ -121,6 +130,7 @@ private static async Task HandleServiceCallAttribute(INetDaemon _daemon, NetDaem }); } + [SuppressMessage("", "CA1031")] private static void HandleStateChangedAttribute( INetDaemon _daemon, HomeAssistantStateChangedAttribute hassStateChangedAttribute, @@ -141,16 +151,14 @@ MethodInfo method { try { - if (hassStateChangedAttribute.To != null) + if (hassStateChangedAttribute.To != null && (dynamic)hassStateChangedAttribute.To != to?.State) { - if ((dynamic)hassStateChangedAttribute.To != to?.State) - return; + return; } - if (hassStateChangedAttribute.From != null) + if (hassStateChangedAttribute.From != null && (dynamic)hassStateChangedAttribute.From != from?.State) { - if ((dynamic)hassStateChangedAttribute.From != from?.State) - return; + return; } // If we don´t accept all changes in the state change diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs index 165e665f8..1e0f01ecd 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs @@ -27,11 +27,11 @@ public HttpClient CreateHttpClient(string? name = null) { _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); - var httpClient = _httpClientFactory.CreateClient(); + using var httpClient = _httpClientFactory.CreateClient(); AddHeaders(httpClient, headers); - var streamTask = httpClient.GetStreamAsync(url) + var streamTask = httpClient.GetStreamAsync(new Uri(url)) ?? throw new NetDaemonException($"Unexpected, nothing returned from {url}"); return await JsonSerializer.DeserializeAsync(await streamTask.ConfigureAwait(false), options).ConfigureAwait(false); @@ -40,14 +40,16 @@ public HttpClient CreateHttpClient(string? name = null) public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers) { _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); + _ = request ?? throw new NetDaemonArgumentNullException(nameof(request)); - var httpClient = _httpClientFactory.CreateClient(); + using var httpClient = _httpClientFactory.CreateClient(); AddHeaders(httpClient, headers); var bytesToPost = JsonSerializer.SerializeToUtf8Bytes(request, request.GetType(), options); + using var content = new ByteArrayContent(bytesToPost); - var response = await httpClient.PostAsync(url, new ByteArrayContent(bytesToPost)).ConfigureAwait(false); + var response = await httpClient.PostAsync(new Uri(url), content).ConfigureAwait(false); response.EnsureSuccessStatusCode(); @@ -58,14 +60,16 @@ public HttpClient CreateHttpClient(string? name = null) public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers) { _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!"); + _ = request ?? throw new NetDaemonArgumentNullException(nameof(request)); - var httpClient = _httpClientFactory.CreateClient(); + using var httpClient = _httpClientFactory.CreateClient(); AddHeaders(httpClient, headers); var bytesToPost = JsonSerializer.SerializeToUtf8Bytes(request, request.GetType(), options); + using var content = new ByteArrayContent(bytesToPost); - var response = await httpClient.PostAsync(url, new ByteArrayContent(bytesToPost)).ConfigureAwait(false); + var response = await httpClient.PostAsync(new Uri(url), content).ConfigureAwait(false); response.EnsureSuccessStatusCode(); } diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs index 2c7d1f162..ef565a1fe 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; +using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Text; @@ -128,6 +130,7 @@ public IHttpHandler Http public IScheduler Scheduler => _scheduler; + [SuppressMessage("", "CA1721")] public IEnumerable State => InternalState.Select(n => n.Value); // For testing @@ -144,22 +147,23 @@ public IHttpHandler Http public async Task> GetAllServices() { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return await _hassClient.GetServices().ConfigureAwait(false); } public void CallService(string domain, string service, dynamic? data = null) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); if (!_serviceCallMessageChannel.Writer.TryWrite((domain, service, data))) throw new NetDaemonException("Servicecall queue full!"); } + [SuppressMessage("", "CA1031")] public async Task CallServiceAsync(string domain, string service, dynamic? data = null, bool waitForResponse = false) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); try { @@ -174,21 +178,23 @@ public async Task CallServiceAsync(string domain, string service, dynamic? data /// public ICamera Camera(INetDaemonApp app, params string[] entityIds) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new CameraManager(entityIds, this, app); } /// public ICamera Cameras(INetDaemonApp app, IEnumerable entityIds) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new CameraManager(entityIds, this, app); } /// public ICamera Cameras(INetDaemonApp app, Func func) { - this._cancelToken.ThrowIfCancellationRequested(); + _ = app ?? + throw new NetDaemonArgumentNullException(nameof(app)); + _cancelToken.ThrowIfCancellationRequested(); try { IEnumerable x = State.Where(func); @@ -206,6 +212,9 @@ public async ValueTask DisposeAsync() { _cancelDaemon.Cancel(); await Stop().ConfigureAwait(false); + _cancelDaemon.Dispose(); + await _scheduler.DisposeAsync().ConfigureAwait(false); + _cancelTokenSource?.Dispose(); Logger.LogTrace("Instance NetDaemonHost Disposed"); } @@ -219,7 +228,9 @@ public void EnableApplicationDiscoveryService() public IEntity Entities(INetDaemonApp app, Func func) { - this._cancelToken.ThrowIfCancellationRequested(); + _ = app ?? + throw new NetDaemonArgumentNullException(nameof(app)); + _cancelToken.ThrowIfCancellationRequested(); try { @@ -234,22 +245,22 @@ public IEntity Entities(INetDaemonApp app, Func func) } } - public IEntity Entities(INetDaemonApp app, IEnumerable entityIds) + public IEntity Entities(INetDaemonApp app, IEnumerable entityId) { - this._cancelToken.ThrowIfCancellationRequested(); - return new EntityManager(entityIds, this, app); + _cancelToken.ThrowIfCancellationRequested(); + return new EntityManager(entityId, this, app); } - public IEntity Entity(INetDaemonApp app, params string[] entityIds) + public IEntity Entity(INetDaemonApp app, params string[] entityId) { - this._cancelToken.ThrowIfCancellationRequested(); - return new EntityManager(entityIds, this, app); + _cancelToken.ThrowIfCancellationRequested(); + return new EntityManager(entityId, this, app); } /// public INetDaemonAppBase? GetApp(string appInstanceId) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return InternalRunningAppInstances.ContainsKey(appInstanceId) ? InternalRunningAppInstances[appInstanceId] : null; @@ -257,7 +268,7 @@ public IEntity Entity(INetDaemonApp app, params string[] entityIds) public async Task GetDataAsync(string id) where T : class { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); _ = _repository as IDataRepository ?? throw new NetDaemonNullReferenceException($"{nameof(_repository)} can not be null!"); @@ -274,11 +285,13 @@ public IEntity Entity(INetDaemonApp app, params string[] entityIds) return data; } - public EntityState? GetState(string entity) + public EntityState? GetState(string entityId) { - this._cancelToken.ThrowIfCancellationRequested(); + _ = entityId ?? + throw new NetDaemonArgumentNullException(nameof(entityId)); + _cancelToken.ThrowIfCancellationRequested(); - return InternalState.TryGetValue(entity, out EntityState? returnValue) + return InternalState.TryGetValue(entityId, out EntityState? returnValue) ? returnValue : null; } @@ -298,21 +311,21 @@ public async Task Initialize(IInstanceDaemonApp appInstanceManager) /// public IFluentInputSelect InputSelect(INetDaemonApp app, params string[] inputSelectParams) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new InputSelectManager(inputSelectParams, this, app); } /// public IFluentInputSelect InputSelects(INetDaemonApp app, IEnumerable inputSelectParams) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new InputSelectManager(inputSelectParams, this, app); } /// public IFluentInputSelect InputSelects(INetDaemonApp app, Func func) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); IEnumerable x = State.Where(func).Select(n => n.EntityId); return new InputSelectManager(x, this, app); } @@ -320,31 +333,41 @@ public IFluentInputSelect InputSelects(INetDaemonApp app, Func public void ListenCompanionServiceCall(string service, Func action) { - this._cancelToken.ThrowIfCancellationRequested(); + _ = service ?? + throw new NetDaemonArgumentNullException(nameof(service)); + _cancelToken.ThrowIfCancellationRequested(); _daemonServiceCallFunctions.Add(("netdaemon", service.ToLowerInvariant(), action)); } public void ListenServiceCall(string domain, string service, Func action) - => _daemonServiceCallFunctions.Add((domain.ToLowerInvariant(), service.ToLowerInvariant(), action)); + { + _ = service ?? + throw new NetDaemonArgumentNullException(nameof(service)); + _ = domain ?? + throw new NetDaemonArgumentNullException(nameof(domain)); + _daemonServiceCallFunctions.Add((domain.ToLowerInvariant(), service.ToLowerInvariant(), action)); + } /// public IMediaPlayer MediaPlayer(INetDaemonApp app, params string[] entityIds) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new MediaPlayerManager(entityIds, this, app); } /// public IMediaPlayer MediaPlayers(INetDaemonApp app, IEnumerable entityIds) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); return new MediaPlayerManager(entityIds, this, app); } /// public IMediaPlayer MediaPlayers(INetDaemonApp app, Func func) { - this._cancelToken.ThrowIfCancellationRequested(); + _ = app ?? + throw new NetDaemonArgumentNullException(nameof(app)); + _cancelToken.ThrowIfCancellationRequested(); try { IEnumerable x = State.Where(func); @@ -378,6 +401,7 @@ public async Task ReloadAllApps() /// /// /// + [SuppressMessage("", "CA1031")] public async Task Run(string host, short port, bool ssl, string token, CancellationToken cancellationToken) { // Create combine cancellation token @@ -444,16 +468,13 @@ public async Task Run(string host, short port, bool ssl, string token, Cancellat HassEvent changedEvent = await _hassClient.ReadEventAsync(cancellationToken).ConfigureAwait(false); if (changedEvent != null) { - if (changedEvent.Data is HassServiceEventData hseData) - { - if (hseData.Domain == "homeassistant" && + if (changedEvent.Data is HassServiceEventData hseData && hseData.Domain == "homeassistant" && (hseData.Service == "stop" || hseData.Service == "restart")) - { - // The user stopped HA so just stop processing messages - Logger.LogInformation("User {action} Home Assistant, will try to reconnect...", - hseData.Service == "stop" ? "stopping" : "restarting"); - return; - } + { + // The user stopped HA so just stop processing messages + Logger.LogInformation("User {action} Home Assistant, will try to reconnect...", + hseData.Service == "stop" ? "stopping" : "restarting"); + return; } // Remove all completed Tasks _eventHandlerTasks.RemoveAll(x => x.IsCompleted); @@ -484,29 +505,30 @@ public async Task Run(string host, short port, bool ssl, string token, Cancellat } } - public IScript RunScript(INetDaemonApp app, params string[] entityId) + public IScript RunScript(INetDaemonApp app, params string[] entityIds) { - this._cancelToken.ThrowIfCancellationRequested(); - return new EntityManager(entityId, this, app); + _cancelToken.ThrowIfCancellationRequested(); + return new EntityManager(entityIds, this, app); } public Task SaveDataAsync(string id, T data) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); - _ = _repository as IDataRepository ?? + _ = _repository ?? throw new NetDaemonNullReferenceException($"{nameof(_repository)} can not be null!"); if (data == null) - throw new ArgumentNullException(nameof(data)); + throw new NetDaemonArgumentNullException(nameof(data)); DataCache[id] = data; return _repository!.Save(id, data); } + [SuppressMessage("", "CA1031")] public async Task SendEvent(string eventId, dynamic? data = null) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); if (!Connected) return false; @@ -524,7 +546,7 @@ public async Task SendEvent(string eventId, dynamic? data = null) /// public async Task SetDaemonStateAsync(int numberOfLoadedApps, int numberOfRunningApps) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); await SetStateAsync( "netdaemon.status", @@ -536,7 +558,7 @@ public async Task SetDaemonStateAsync(int numberOfLoadedApps, int numberOfRunnin public void SetState(string entityId, dynamic state, dynamic? attributes = null) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); if (!_setStateMessageChannel.Writer.TryWrite((entityId, state, attributes))) throw new NetDaemonException("Servicecall queue full!"); @@ -545,7 +567,7 @@ public void SetState(string entityId, dynamic state, dynamic? attributes = null) public async Task SetStateAsync(string entityId, dynamic state, params (string name, object val)[] attributes) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); try { // Use expando object as all other methods @@ -576,11 +598,12 @@ public void SetState(string entityId, dynamic state, dynamic? attributes = null) public void Speak(string entityId, string message) { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); _ttsMessageChannel.Writer.TryWrite((entityId, message)); } + [SuppressMessage("", "CA1031")] public async Task Stop() { try @@ -687,21 +710,15 @@ internal static bool FixStateTypes(HassStateChangedEventData stateData) internal string? GetAreaForEntityId(string entityId) { - if (_hassEntities.TryGetValue(entityId, out HassEntity? entity) && entity is not null) + if (_hassEntities.TryGetValue(entityId, out HassEntity? entity) && entity is not null && entity.DeviceId is not null) { - if (entity.DeviceId is not null) + // The entity is on a device + if (_hassDevices.TryGetValue(entity.DeviceId, out HassDevice? device) && device is not null && device.AreaId is not null) { - // The entity is on a device - if (_hassDevices.TryGetValue(entity.DeviceId, out HassDevice? device) && device is not null) + // This device is in an area + if (_hassAreas.TryGetValue(device.AreaId, out HassArea? area) && area is not null) { - if (device.AreaId is not null) - { - // This device is in an area - if (_hassAreas.TryGetValue(device.AreaId, out HassArea? area) && area is not null) - { - return area.Name; - } - } + return area.Name; } } } @@ -710,7 +727,7 @@ internal static bool FixStateTypes(HassStateChangedEventData stateData) internal async Task RefreshInternalStatesAndSetArea() { - this._cancelToken.ThrowIfCancellationRequested(); + _cancelToken.ThrowIfCancellationRequested(); foreach (var device in await _hassClient.GetDevices().ConfigureAwait(false)) { @@ -769,9 +786,12 @@ internal static IList SortByDependency(IEnumerable await SetStateOnDaemonAppSwitch("on", data).ConfigureAwait(false)); @@ -1267,7 +1290,7 @@ async Task SetStateOnDaemonAppSwitch(string state, dynamic? data) if (entityId is null) return; - if (!entityId.StartsWith("switch.netdaemon_")) + if (!entityId.StartsWith("switch.netdaemon_", true, CultureInfo.InvariantCulture)) return; // We only want app switches await SetDependentState(entityId, state).ConfigureAwait(false); @@ -1336,6 +1359,7 @@ private async Task PersistAppStateAsync(NetDaemonAppBase app) await SaveDataAsync>(app.GetUniqueIdForStorage(), obj).ConfigureAwait(false); } + [SuppressMessage("", "CA1031")] private async Task RestoreAppState(INetDaemonAppBase appInstance) { try diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs index d2ea08cd4..2e42a12c4 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Scheduler.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Threading; @@ -62,6 +63,9 @@ public ISchedulerResult RunEvery(int millisecondsDelay, Func func) /// public ISchedulerResult RunEvery(TimeSpan timeSpan, Func func) { + _ = func ?? + throw new NetDaemonArgumentNullException(nameof(func)); + var cancelSource = new CancellationTokenSource(); var task = RunEveryInternalAsync(timeSpan, func, cancelSource.Token); @@ -70,6 +74,7 @@ public ISchedulerResult RunEvery(TimeSpan timeSpan, Func func) return new SchedulerResult(task, cancelSource); } + [SuppressMessage("", "CA1031")] private async Task RunEveryInternalAsync(TimeSpan timeSpan, Func func, CancellationToken token) { using CancellationTokenSource linkedCts = @@ -131,6 +136,9 @@ internal TimeSpan CalculateEveryMinuteTimeBetweenNowAndTargetTime(short second) /// public ISchedulerResult RunDaily(string time, IEnumerable? runOnDays, Func func) { + _ = func ?? + throw new NetDaemonArgumentNullException(nameof(func)); + var cancelSource = new CancellationTokenSource(); if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime timeOfDayToTrigger)) @@ -144,6 +152,7 @@ public ISchedulerResult RunDaily(string time, IEnumerable? runOnDays, return new SchedulerResult(task, cancelSource); } + [SuppressMessage("", "CA1031")] private async Task RunDailyInternalAsync(DateTime timeOfDayToTrigger, IEnumerable? runOnDays, Func func, CancellationToken token) { using CancellationTokenSource linkedCts = @@ -193,6 +202,8 @@ private async Task RunDailyInternalAsync(DateTime timeOfDayToTrigger, IEnumerabl /// public ISchedulerResult RunEveryMinute(short second, Func func) { + _ = func ?? + throw new NetDaemonArgumentNullException(nameof(func)); var cancelSource = new CancellationTokenSource(); var task = RunEveryMinuteInternalAsync(second, func, cancelSource.Token); @@ -201,6 +212,7 @@ public ISchedulerResult RunEveryMinute(short second, Func func) return new SchedulerResult(task, cancelSource); } + [SuppressMessage("", "CA1031")] private async Task RunEveryMinuteInternalAsync(short second, Func func, CancellationToken token) { using CancellationTokenSource linkedCts = @@ -223,11 +235,15 @@ private async Task RunEveryMinuteInternalAsync(short second, Func func, Ca } /// + [SuppressMessage("", "CA1031")] public ISchedulerResult RunIn(int millisecondsDelay, Func func) => RunIn(TimeSpan.FromMilliseconds(millisecondsDelay), func); /// public ISchedulerResult RunIn(TimeSpan timeSpan, Func func) { + _ = func ?? + throw new NetDaemonArgumentNullException(nameof(func)); + var cancelSource = new CancellationTokenSource(); var task = InternalRunInAsync(timeSpan, func, cancelSource.Token); ScheduleTask(task); @@ -235,6 +251,7 @@ public ISchedulerResult RunIn(TimeSpan timeSpan, Func func) return new SchedulerResult(task, cancelSource); } + [SuppressMessage("", "CA1031")] private async Task InternalRunInAsync(TimeSpan timeSpan, Func func, CancellationToken token) { using CancellationTokenSource linkedCts = @@ -265,6 +282,8 @@ public async Task Stop() var taskResult = await Task.WhenAny( Task.WhenAll(_scheduledTasks.Values.ToArray()), Task.Delay(1000)).ConfigureAwait(false); + _cancelSource.Dispose(); + if (_scheduledTasks.Values.Any(n => !n.IsCompleted)) { // Todo: Some kind of logging have to be done here to tell user which task caused timeout @@ -316,6 +335,7 @@ private void ScheduleTask(Task addedTask) _scheduledTasks[addedTask.Id] = addedTask; } + [SuppressMessage("", "CA1031")] public async ValueTask DisposeAsync() { try @@ -337,7 +357,6 @@ public class TimeManager : IManageTime /// /// Returns current local time /// - /// public DateTime Current => DateTime.Now; /// diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs index c784bc044..8ffb496e6 100644 --- a/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs +++ b/src/Daemon/NetDaemon.Daemon/Daemon/Storage/DataRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Text.Json; @@ -23,6 +24,7 @@ public DataRepository(string dataStoragePath) } /// + [SuppressMessage("", "CA1031")] public async ValueTask Get(string id) where T : class { try diff --git a/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs b/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs index 409a77be9..5ca4ca720 100644 --- a/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs +++ b/src/Daemon/NetDaemon.Daemon/Mapping/EntityStateMapper.cs @@ -4,6 +4,7 @@ using JoySoftware.HomeAssistant.Client; using NetDaemon.Infrastructure.Extensions; using NetDaemon.Common; +using NetDaemon.Common.Exceptions; namespace NetDaemon.Mapping { @@ -13,9 +14,10 @@ public static class EntityStateMapper /// Converts HassState to EntityState /// /// - /// public static EntityState Map(this HassState hassState) { + _ = hassState ?? + throw new NetDaemonArgumentNullException(nameof(hassState)); var entityState = new EntityState { EntityId = hassState.EntityId, diff --git a/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj b/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj index 667c9b43a..8faf00c9a 100644 --- a/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj +++ b/src/Daemon/NetDaemon.Daemon/NetDaemon.Daemon.csproj @@ -25,6 +25,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + @@ -33,4 +37,9 @@ ..\..\..\.linting\roslynator.ruleset + + ..\..\..\.linting\roslynator.ruleset + true + AllEnabledByDefault + \ No newline at end of file diff --git a/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs b/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs index caad4c270..31286a7f2 100644 --- a/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs +++ b/tests/NetDaemon.Daemon.Tests/Daemon/DataRepositoryTests.cs @@ -16,7 +16,7 @@ public class DataRepositoryTests : DaemonHostTestBase public static readonly string DataRepositoryPath = Path.Combine(AppContext.BaseDirectory, "datarepository"); - public DataRepositoryTests() : base() + public DataRepositoryTests() { }