diff --git a/.linting/roslynator.config b/.linting/roslynator.config
new file mode 100644
index 000000000..edcfc08ca
--- /dev/null
+++ b/.linting/roslynator.config
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.linting/roslynator.ruleset b/.linting/roslynator.ruleset
new file mode 100644
index 000000000..52a4ec943
--- /dev/null
+++ b/.linting/roslynator.ruleset
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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/.vscode/settings.json b/.vscode/settings.json
index ba38fd66b..0c3496fd9 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -7,5 +7,26 @@
"**/obj": true,
"**/Properties": true,
"**/TestResults": true
- }
+ },
+ "omnisharp.enableRoslynAnalyzers": true,
+ "cSpell.words": [
+ "Expando",
+ "Finalizers",
+ "Hass",
+ "Usings",
+ "Xunit",
+ "dayofweek",
+ "entityid",
+ "hassio",
+ "idguid",
+ "mulitinstance",
+ "mydomain",
+ "myevent",
+ "mylight",
+ "myscript",
+ "noexist",
+ "noquotes",
+ "parentidguid",
+ "useridguid"
+ ]
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs b/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs
index d6fb11f5d..018c6e47e 100644
--- a/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs
+++ b/src/App/NetDaemon.App/Common/AppRuntimeInfo.cs
@@ -33,12 +33,8 @@ public sealed class AppRuntimeInfo
/// in app switch
///
[JsonPropertyName("app_attributes")]
- public Dictionary AppAttributes { get; set; } = new();
-
-
+ public Dictionary AppAttributes { get; } = new();
}
-
-
}
diff --git a/src/App/NetDaemon.App/Common/Attributes.cs b/src/App/NetDaemon.App/Common/Attributes.cs
index 1fa7f4f4e..85de630bb 100644
--- a/src/App/NetDaemon.App/Common/Attributes.cs
+++ b/src/App/NetDaemon.App/Common/Attributes.cs
@@ -21,21 +21,23 @@ public sealed class DisableLogAttribute : System.Attribute
{
// See the attribute guidelines at
// http://go.microsoft.com/fwlink/?LinkId=85236
- private SupressLogType[] _logTypesToSupress;
+ private readonly SupressLogType[]? _logTypesToSupress;
///
/// Default constructor
///
/// List of logtypes to supress
- public DisableLogAttribute(params SupressLogType[] logTypes)
- {
- _logTypesToSupress = logTypes;
- }
+ public DisableLogAttribute(params SupressLogType[] logTypes) => _logTypesToSupress = logTypes;
///
/// Log types to supress
///
- public IEnumerable LogTypesToSupress => _logTypesToSupress;
+ public IEnumerable? LogTypesToSupress => _logTypesToSupress;
+
+ ///
+ /// Log tupes used
+ ///
+ public SupressLogType[]? LogTypes { get; }
}
///
@@ -50,16 +52,6 @@ public sealed class HomeAssistantServiceCallAttribute : System.Attribute { }
[System.AttributeUsage(System.AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
public sealed class HomeAssistantStateChangedAttribute : System.Attribute
{
- private readonly bool _allChanges;
-
- private readonly object? _from;
-
- private readonly object? _to;
-
- // See the attribute guidelines at
- // http://go.microsoft.com/fwlink/?LinkId=85236
- private string _entityId;
-
///
/// Default constructor
///
@@ -69,30 +61,30 @@ public sealed class HomeAssistantStateChangedAttribute : System.Attribute
/// Get all changes, ie also attribute changes
public HomeAssistantStateChangedAttribute(string entityId, object? to = null, object? from = null, bool allChanges = false)
{
- _entityId = entityId;
- _to = to;
- _from = from;
- _allChanges = allChanges;
+ EntityId = entityId;
+ To = to;
+ From = from;
+ AllChanges = allChanges;
}
///
/// Get all changes, even if only attribute changes
///
- public bool AllChanges => _allChanges;
+ public bool AllChanges { get; }
///
/// Unique id of the entity
///
- public string EntityId => _entityId;
+ public string EntityId { get; }
///
/// From state filter
///
- public object? From => _from;
+ public object? From { get; }
///
/// To state filter
///
- public object? To => _to;
+ public object? To { get; }
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs b/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs
index 918ee304d..463415acc 100644
--- a/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs
+++ b/src/App/NetDaemon.App/Common/Configuration/HomeAssistantSettings.cs
@@ -16,9 +16,9 @@ public class HomeAssistantSettings
///
/// Connect using ssl
///
- public bool Ssl { get; set; } = false;
+ public bool Ssl { get; set; }
///
- /// Token to authorize
+ /// Token to authorize
///
public string Token { get; set; } = string.Empty;
}
diff --git a/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs b/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs
index aa558a8b4..297851b50 100644
--- a/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs
+++ b/src/App/NetDaemon.App/Common/Configuration/NetDaemonSettings.cs
@@ -1,5 +1,7 @@
using System;
+using System.Globalization;
using System.IO;
+using NetDaemon.Common.Exceptions;
namespace NetDaemon.Common.Configuration
{
@@ -27,21 +29,22 @@ public class NetDaemonSettings
/// the apps to be in the file paths and tries to find
/// all apps recursivly
///
- public string? AppSource { get; set; } = null;
+ public string? AppSource { get; set; }
///
- /// Returns the directory path of AppSource
+ /// Returns the directory path of AppSource
///
public string GetAppSourceDirectory()
{
var source = AppSource?.Trim() ?? Directory.GetCurrentDirectory();
- if (source.EndsWith(".csproj") || source.EndsWith(".dll"))
+ if (source.EndsWith(".csproj", true, CultureInfo.InvariantCulture)
+ || source.EndsWith(".dll", true, CultureInfo.InvariantCulture))
{
source = Path.GetDirectoryName(source);
}
- return source ?? throw new NullReferenceException("Source cannot be null!");
+ return source ?? throw new NetDaemonNullReferenceException("Source cannot be null!");
}
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/DelayResult.cs b/src/App/NetDaemon.App/Common/DelayResult.cs
index fa92f28c6..46c4b3949 100644
--- a/src/App/NetDaemon.App/Common/DelayResult.cs
+++ b/src/App/NetDaemon.App/Common/DelayResult.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
@@ -13,7 +14,7 @@ public class DelayResult : IDelayResult
{
private readonly INetDaemonApp _daemonApp;
private readonly TaskCompletionSource _delayTaskCompletionSource;
- private bool _isCanceled = false;
+ private bool _isCanceled;
///
/// Constructor
@@ -51,7 +52,7 @@ public void Cancel()
#region IDisposable Support
- private bool disposedValue = false; // To detect redundant calls
+ private bool disposedValue; // To detect redundant calls
///
/// Disposes the object and cancel delay
@@ -60,6 +61,8 @@ public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
+ // Suppress finalization.
+ GC.SuppressFinalize(this);
}
///
diff --git a/src/App/NetDaemon.App/Common/Exceptions/Exceptions.cs b/src/App/NetDaemon.App/Common/Exceptions/Exceptions.cs
new file mode 100644
index 000000000..efa63350d
--- /dev/null
+++ b/src/App/NetDaemon.App/Common/Exceptions/Exceptions.cs
@@ -0,0 +1,66 @@
+using System;
+
+namespace NetDaemon.Common.Exceptions
+{
+ ///
+ public class NetDaemonNullReferenceException : NullReferenceException
+ {
+ ///
+ public NetDaemonNullReferenceException()
+ {
+ }
+
+ ///
+ public NetDaemonNullReferenceException(string? message) : base(message)
+ {
+ }
+
+ ///
+ public NetDaemonNullReferenceException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+ }
+
+ ///
+ public class NetDaemonException : Exception
+ {
+ ///
+ public NetDaemonException()
+ {
+ }
+
+ ///
+ public NetDaemonException(string? message) : base(message)
+ {
+ }
+
+ ///
+ public NetDaemonException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+ }
+
+ ///
+ public class NetDaemonArgumentNullException : ArgumentNullException
+ {
+ ///
+ public NetDaemonArgumentNullException()
+ {
+ }
+
+ ///
+ public NetDaemonArgumentNullException(string? paramName) : base(paramName)
+ {
+ }
+
+ ///
+ public NetDaemonArgumentNullException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+
+ ///
+ public NetDaemonArgumentNullException(string? paramName, string? message) : base(paramName, message)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/ExtensionMethods.cs b/src/App/NetDaemon.App/Common/ExtensionMethods.cs
index 626bc432a..39dab6826 100644
--- a/src/App/NetDaemon.App/Common/ExtensionMethods.cs
+++ b/src/App/NetDaemon.App/Common/ExtensionMethods.cs
@@ -4,13 +4,13 @@
using System.Globalization;
using System.Text;
using NetDaemon.Common.Fluent;
-
+using System.Diagnostics.CodeAnalysis;
namespace NetDaemon.Common
{
///
/// Useful extension methods used
///
- public static class Extensions
+ public static class NetDaemonExtensions
{
///
/// Converts a valuepair to dynamic object
@@ -20,30 +20,39 @@ public static dynamic ToDynamic(this (string name, object val)[] attributeNameVa
{
// Convert the tuple name/value pair to tuple that can be serialized dynamically
var attributes = new FluentExpandoObject(true, true);
- foreach (var (attribute, value) in attributeNameValuePair)
+ if (attributeNameValuePair is not null)
{
- ((IDictionary)attributes).Add(attribute, value);
+ foreach (var (attribute, value) in attributeNameValuePair)
+ {
+ if (value is not null)
+ {
+ // We only add non-null values since the FluentExpandoObject will
+ // return null on missing anyway
+ attributes.Add(attribute, value);
+ }
+ }
}
- dynamic result = attributes;
- return result;
+ return (dynamic)attributes;
}
///
/// Converts a anoumous type to expando object
///
///
- ///
public static ExpandoObject? ToExpandoObject(this object obj)
{
// Null-check
IDictionary expando = new ExpandoObject();
- foreach (PropertyDescriptor? property in TypeDescriptor.GetProperties(obj.GetType()))
+ if (obj is not null)
{
- if (property is not null)
- expando.Add(property.Name, property.GetValue(obj));
+ foreach (PropertyDescriptor? property in TypeDescriptor.GetProperties(obj.GetType()))
+ {
+ if (property is not null)
+ expando.Add(property.Name, property.GetValue(obj));
+ }
}
return (ExpandoObject)expando;
@@ -53,10 +62,14 @@ public static dynamic ToDynamic(this (string name, object val)[] attributeNameVa
/// Converts any unicode string to a safe Home Assistant name
///
/// The unicode string to convert
+ [SuppressMessage(category: "Microsoft.Globalization", checkId: "CA1308")]
public static string ToSafeHomeAssistantEntityId(this string str)
{
+ if (str is null)
+ return string.Empty;
+
string normalizedString = str.Normalize(NormalizationForm.FormD);
- StringBuilder stringBuilder = new StringBuilder(str.Length);
+ StringBuilder stringBuilder = new(str.Length);
foreach (char c in normalizedString)
{
diff --git a/src/App/NetDaemon.App/Common/ExternalEvents.cs b/src/App/NetDaemon.App/Common/ExternalEvents.cs
index b7b34819c..73efea5d5 100644
--- a/src/App/NetDaemon.App/Common/ExternalEvents.cs
+++ b/src/App/NetDaemon.App/Common/ExternalEvents.cs
@@ -5,13 +5,11 @@
namespace NetDaemon.Common
{
-
///
- /// Base class for all external events
+ /// Base class for all external events
///
public class ExternalEventBase
{
-
}
///
@@ -19,10 +17,8 @@ public class ExternalEventBase
///
public class AppsInformationEvent : ExternalEventBase
{
-
}
-
///
/// Information about the application
///
@@ -56,7 +52,6 @@ public class ApplicationInfo
/// Last known error message
///
public string? LastErrorMessage { get; set; }
-
}
///
@@ -72,6 +67,5 @@ public class ConfigInfo
/// Settings Home Assistant related
///
public HomeAssistantSettings? HomeAssistantSettings { get; set; }
-
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/Fluent/Context.cs b/src/App/NetDaemon.App/Common/Fluent/Context.cs
index df0d27a99..2d770ded7 100644
--- a/src/App/NetDaemon.App/Common/Fluent/Context.cs
+++ b/src/App/NetDaemon.App/Common/Fluent/Context.cs
@@ -1,7 +1,7 @@
namespace NetDaemon.Common.Fluent
{
///
- ///
+ /// Context
///
public class Context
{
@@ -10,13 +10,12 @@ public class Context
///
public string Id { get; set; } = "";
///
- ///
+ /// ParentId
///
public string? ParentId { get; set; }
///
/// The id of the user who is responsible for the connected item.
///
public string? UserId { get; set; }
-
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs b/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs
index 784e2183b..c7ae8ab72 100644
--- a/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs
+++ b/src/App/NetDaemon.App/Common/Fluent/EntityBase.cs
@@ -1,7 +1,7 @@
-using System;
-using System.Collections.Concurrent;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
+using NetDaemon.Common.Exceptions;
namespace NetDaemon.Common.Fluent
{
@@ -11,26 +11,26 @@ namespace NetDaemon.Common.Fluent
public class EntityBase //: EntityState
{
internal readonly ConcurrentQueue _actions =
- new ConcurrentQueue();
+ new();
internal FluentAction? _currentAction;
- internal StateChangedInfo _currentState = new StateChangedInfo();
+ internal StateChangedInfo _currentState = new();
///
/// The daemon used in the API
///
- protected readonly INetDaemonApp App;
+ protected INetDaemonApp App { get; }
///
/// The daemon used in the API
///
- protected readonly INetDaemon Daemon;
+ protected INetDaemon Daemon { get; }
///
/// The EntityIds used
///
- protected readonly IEnumerable EntityIds;
+ protected IEnumerable EntityIds { get; }
///
/// Constructor
@@ -48,9 +48,12 @@ public EntityBase(IEnumerable entityIds, INetDaemon daemon, INetDaemonAp
///
protected static string GetDomainFromEntity(string entity)
{
+ if (string.IsNullOrEmpty(entity))
+ throw new NetDaemonNullReferenceException(nameof(entity));
+
var entityParts = entity.Split('.');
if (entityParts.Length != 2)
- throw new ApplicationException($"entity_id is mal formatted {entity}");
+ throw new NetDaemonException($"entity_id is mal formatted {entity}");
return entityParts[0];
}
diff --git a/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs b/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs
index 71eefacf0..a720f5b37 100644
--- a/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs
+++ b/src/App/NetDaemon.App/Common/Fluent/EntityManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -9,7 +10,7 @@ namespace NetDaemon.Common.Fluent
///
/// Implements interface for managing entities in the fluent API
///
- public class EntityManager : EntityBase, IEntity, IAction,
+ public sealed class EntityManager : EntityBase, IEntity, IAction,
IStateEntity, IState, IStateAction, IScript
{
///
@@ -39,19 +40,18 @@ public IExecute Call(Func func)
///
IDelayResult IDelayStateChange.DelayUntilStateChange(object? to, object? from, bool allChanges)
{
- return App.DelayUntilStateChange(this.EntityIds, to, from, allChanges);
+ return App.DelayUntilStateChange(EntityIds, to, from, allChanges);
}
///
- IDelayResult IDelayStateChange.DelayUntilStateChange(Func stateFunc)
- {
- return App.DelayUntilStateChange(this.EntityIds, stateFunc);
- }
+ IDelayResult IDelayStateChange.DelayUntilStateChange(Func stateFunc) => App.DelayUntilStateChange(EntityIds, stateFunc);
///
+ [SuppressMessage("Microsoft.Design", "CA1031")]
public void Execute()
{
foreach (var entityId in EntityIds)
+ {
App.ListenState(entityId, async (entityIdInn, newState, oldState) =>
{
try
@@ -74,13 +74,13 @@ public void Execute()
}
else
{
- if (_currentState.To != null)
- if (_currentState.To != newState?.State)
- return;
+ if (_currentState.To != null && _currentState.To != newState?.State)
+ {
+ return;
+ }
- if (_currentState.From != null)
- if (_currentState.From != oldState?.State)
- return;
+ if (_currentState.From != null && _currentState.From != oldState?.State)
+ return;
// If we don´t accept all changes in the state change
// and we do not have a state change so return
@@ -104,7 +104,9 @@ public void Execute()
"State same {newState} during period of {time}, executing action!", $"{newState?.State}", _currentState.ForTimeSpan);
// The state has not changed during the time we waited
if (_currentState.FuncToCall == null)
+ {
await entityManager.ExecuteAsync(true).ConfigureAwait(false);
+ }
else
{
try
@@ -121,11 +123,10 @@ public void Execute()
}
else
{
- App.LogDebug(
- "State same {newState} but different state changed: {currentLastChanged}, expected {newLastChanged}",
- $"{newState?.State}",
- currentState?.LastChanged!,
- newState?.LastChanged!);
+ App.LogDebug("State same {newState} but different state changed: {currentLastChanged}, expected {newLastChanged}",
+ $"{newState?.State}",
+ currentState?.LastChanged ?? DateTime.MinValue,
+ newState?.LastChanged ?? DateTime.MinValue);
}
}
else
@@ -152,15 +153,23 @@ public void Execute()
}
catch (Exception e)
{
- App.LogError(e,
- "Call function error in App {appId}, EntityId: {entityId}, From: {newState} To: {oldState}",
- App.Id!, entityIdInn, $"{newState?.State}", $"{oldState?.State}");
+ App.LogError(
+ e,
+ "Call function error in App {appId}, EntityId: {entityId}, From: {newState} To: {oldState}",
+ App.Id!,
+ entityIdInn,
+ $"{newState?.State}",
+ $"{oldState?.State}");
}
}
else if (_currentState.ScriptToCall != null)
+ {
await Daemon.RunScript(App, _currentState.ScriptToCall).ExecuteAsync().ConfigureAwait(false);
+ }
else
+ {
await entityManager.ExecuteAsync(true).ConfigureAwait(false);
+ }
}
}
catch (OperationCanceledException)
@@ -172,6 +181,7 @@ public void Execute()
App.LogWarning(e, "Unhandled error in ListenState in App {appId}", App.Id!);
}
});
+ }
//}
}
@@ -189,8 +199,8 @@ async Task IScript.ExecuteAsync()
foreach (var scriptName in EntityIds)
{
var name = scriptName;
- if (scriptName.Contains('.'))
- name = scriptName[(scriptName.IndexOf('.') + 1)..];
+ if (scriptName.Contains('.', StringComparison.InvariantCulture))
+ name = scriptName[(scriptName.IndexOf('.', StringComparison.InvariantCulture) + 1)..];
var task = Daemon.CallServiceAsync("script", name);
taskList.Add(task);
}
@@ -209,15 +219,18 @@ async Task IScript.ExecuteAsync()
/// You want to keep the items when using this as part of an automation
/// that are kept over time. Not keeping when just doing a command
///
- ///
public async Task ExecuteAsync(bool keepItems)
{
if (keepItems)
+ {
foreach (var action in _actions)
await HandleAction(action).ConfigureAwait(false);
+ }
else
+ {
while (_actions.TryDequeue(out var fluentAction))
await HandleAction(fluentAction).ConfigureAwait(false);
+ }
}
///
@@ -230,8 +243,10 @@ public IExecute RunScript(params string[] entityIds)
///
IAction ISetState.SetState(dynamic state)
{
- _currentAction = new FluentAction(FluentActionType.SetState);
- _currentAction.State = state;
+ _currentAction = new FluentAction(FluentActionType.SetState)
+ {
+ State = state
+ };
_actions.Enqueue(_currentAction);
return this;
}
diff --git a/src/App/NetDaemon.App/Common/Fluent/Fluent.cs b/src/App/NetDaemon.App/Common/Fluent/Fluent.cs
index 476a22a7a..7f17bc4c0 100644
--- a/src/App/NetDaemon.App/Common/Fluent/Fluent.cs
+++ b/src/App/NetDaemon.App/Common/Fluent/Fluent.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace NetDaemon.Common.Fluent
@@ -42,6 +43,7 @@ public interface IDelayStateChange
/// The state change to, or null if any state
/// The state changed from or null if any state
/// Get all changed, even only attribute changes
+ [SuppressMessage("Microsoft.Naming", "CA1716")]
IDelayResult DelayUntilStateChange(object? to = null, object? from = null, bool allChanges = false);
///
@@ -95,6 +97,8 @@ public interface IScript
///
/// Represent state change actions
///
+ [SuppressMessage("Microsoft.Naming", "CA1716")]
+
public interface IState
{
///
@@ -150,6 +154,7 @@ public interface IStateAction : IExecute
///
/// When state change
///
+ [SuppressMessage("Microsoft.Naming", "CA1716")]
public interface IStateChanged
{
///
@@ -229,6 +234,7 @@ public interface ISpeak
/// Generic interface for stop
///
/// Return type of stop operation
+ [SuppressMessage("Microsoft.Naming", "CA1716")]
public interface IStop
{
///
diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentCamera.cs b/src/App/NetDaemon.App/Common/Fluent/FluentCamera.cs
index 6fad012d7..53c612f32 100644
--- a/src/App/NetDaemon.App/Common/Fluent/FluentCamera.cs
+++ b/src/App/NetDaemon.App/Common/Fluent/FluentCamera.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
+using NetDaemon.Common.Exceptions;
namespace NetDaemon.Common.Fluent
{
@@ -31,6 +33,7 @@ public interface IEnableMotionDetection
///
/// Generic interface for PlayStream
///
+ [SuppressMessage("Microsoft.Naming", "CA1711")]
public interface IPlayStream
{
///
@@ -79,9 +82,9 @@ public interface ICamera : IExecuteAsync, IDisableMotionDetection
///
/// Implements the fluent camera interface
///
- public class CameraManager : EntityBase, ICamera, IExecuteAsync
+ public class CameraManager : EntityBase, ICamera
{
- private (string, dynamic?)? _serviceCall = null;
+ private (string, dynamic?)? _serviceCall;
///
/// Constructor
@@ -122,14 +125,14 @@ public IExecuteAsync PlayStream(string mediaPlayerId, string? format = null)
}
///
- public IExecuteAsync Record(string fileName, int? duration = null, int? lookback = null)
+ public IExecuteAsync Record(string fileName, int? seconds = null, int? lookback = null)
{
dynamic serviceData = new FluentExpandoObject();
serviceData.filename = fileName;
- if (duration is not null)
- serviceData.duration = duration;
+ if (seconds is not null)
+ serviceData.duration = seconds;
if (lookback is not null)
serviceData.lookback = lookback;
@@ -165,7 +168,7 @@ public IExecuteAsync TurnOn()
///
public Task ExecuteAsync()
{
- (string service, dynamic? data) = _serviceCall ?? throw new NullReferenceException($"{nameof(_serviceCall)} is Null!");
+ (string service, dynamic? data) = _serviceCall ?? throw new NetDaemonNullReferenceException($"{nameof(_serviceCall)} is Null!");
return CallServiceOnAllEntities(service, data);
}
diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs b/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs
index 78072a2cc..2cb47cf1a 100644
--- a/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs
+++ b/src/App/NetDaemon.App/Common/Fluent/FluentEvent.cs
@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
+using NetDaemon.Common.Exceptions;
namespace NetDaemon.Common.Fluent
{
///
/// Handles events in fluent API
///
+ [SuppressMessage("Microsoft.Naming", "CA1716")]
public interface IFluentEvent
{
///
@@ -21,10 +24,10 @@ public interface IFluentEvent
///
public class FluentEventManager : IFluentEvent, IExecute
{
- private IEnumerable? _events;
- private INetDaemonApp _daemon;
+ private readonly IEnumerable? _events;
+ private readonly INetDaemonApp _daemon;
private Func? _functionToCall;
- private Func? _funcSelector;
+ private readonly Func? _funcSelector;
///
/// Constructor
@@ -52,7 +55,7 @@ public FluentEventManager(Func func, INetDaemonApp da
public IExecute Call(Func? func)
{
if (func == null)
- throw new NullReferenceException("Call function is null listening to event");
+ throw new NetDaemonNullReferenceException("Call function is null listening to event");
_functionToCall = func;
return this;
@@ -62,13 +65,17 @@ public IExecute Call(Func? func)
public void Execute()
{
if (_events == null && _funcSelector == null)
- throw new NullReferenceException($"Both {nameof(_events)} or {nameof(_events)} cant be null");
+ throw new NetDaemonNullReferenceException($"Both {nameof(_events)} or {nameof(_events)} cant be null");
if (_events != null)
+ {
foreach (var ev in _events)
_daemon.ListenEvent(ev, _functionToCall!);
+ }
else
+ {
_daemon.ListenEvent(_funcSelector!, _functionToCall!);
+ }
}
}
@@ -83,9 +90,8 @@ public class FluentEventProperty
public string EventId { get; set; } = "";
///
- ///
+ /// Data of the event
///
- ///
- public dynamic? Data { get; set; } = null;
+ public dynamic? Data { get; set; }
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs b/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs
index a5a79d148..593ae1caf 100644
--- a/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs
+++ b/src/App/NetDaemon.App/Common/Fluent/FluentExpandoObject.cs
@@ -1,10 +1,13 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
+using System.Globalization;
using System.Linq;
using System.Text.Json;
using JoySoftware.HomeAssistant.Client;
+using NetDaemon.Common.Exceptions;
namespace NetDaemon.Common.Fluent
{
@@ -15,6 +18,7 @@ namespace NetDaemon.Common.Fluent
/// Thanks to @lukevendediger for original code and inspiration
/// https://gist.github.com/lukevenediger/6327599
///
+ [SuppressMessage("Microsoft.Naming", "CA1710")]
public class FluentExpandoObject : DynamicObject, IDictionary
{
private readonly Dictionary _dict = new();
@@ -72,7 +76,6 @@ public bool Contains(KeyValuePair item)
///
public void CopyTo(KeyValuePair[] array, int arrayIndex)
{
- throw new NotImplementedException();
}
///
@@ -129,18 +132,22 @@ public object this[string key]
///
public override bool TrySetMember(SetMemberBinder binder, object? value)
{
+ if (binder is null)
+ throw new NetDaemonNullReferenceException(nameof(binder));
+
UpdateDictionary(binder.Name, value);
- if (_daemonApp != null)
- {
- // It is supposed to persist, this is the only reason _daemon is present
- _daemonApp.SaveAppState();
- }
+ // It is supposed to persist, this is the only reason _daemon is present
+ _daemonApp?.SaveAppState();
+
return true;
}
///
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object? value)
{
+ if (indexes is null)
+ throw new NetDaemonNullReferenceException(nameof(indexes));
+
if (!(indexes[0] is string)) return base.TrySetIndex(binder, indexes, value);
if (indexes[0] is string key) UpdateDictionary(NormalizePropertyName(key), value);
@@ -150,6 +157,9 @@ public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object
///
public override bool TryGetMember(GetMemberBinder binder, out object? result)
{
+ if (binder is null)
+ throw new NetDaemonNullReferenceException(nameof(binder));
+
var key = NormalizePropertyName(binder.Name);
if (_dict.ContainsKey(key))
@@ -173,6 +183,9 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result)
/// Existing properties are not overwritten.
public dynamic Augment(FluentExpandoObject obj)
{
+ if (obj is null)
+ throw new NetDaemonNullReferenceException(nameof(obj));
+
obj._dict
.Where(pair => !_dict.ContainsKey(NormalizePropertyName(pair.Key)))
.ToList()
@@ -186,6 +199,9 @@ public dynamic Augment(FluentExpandoObject obj)
/// The object to copy from
public dynamic CopyFrom(IDictionary obj)
{
+ if (obj is null)
+ throw new NetDaemonNullReferenceException(nameof(obj));
+
// Clear any items before copy
Clear();
@@ -212,7 +228,6 @@ public dynamic CopyFrom(IDictionary obj)
/// Combine two instances together to get a union.
///
/// the object to combine
- ///
public dynamic Augment(ExpandoObject obj)
{
obj
@@ -225,6 +240,9 @@ public dynamic Augment(ExpandoObject obj)
///
public T ValueOrDefault(string propertyName, T defaultValue)
{
+ if (propertyName is null)
+ throw new NetDaemonNullReferenceException(nameof(propertyName));
+
propertyName = NormalizePropertyName(propertyName);
return _dict.ContainsKey(propertyName)
? (T)_dict[propertyName]
@@ -237,6 +255,9 @@ public T ValueOrDefault(string propertyName, T defaultValue)
/// Respects the case sensitivity setting
public bool HasProperty(string name)
{
+ if (name is null)
+ throw new NetDaemonNullReferenceException(nameof(name));
+
return _dict.ContainsKey(NormalizePropertyName(name));
}
@@ -250,17 +271,15 @@ public override string ToString()
private void UpdateDictionary(string name, object? value)
{
- _ = value ?? throw new ArgumentNullException("value", "value cannot be null");
+ _ = value ?? throw new ArgumentNullException(nameof(value), "value cannot be null");
var key = NormalizePropertyName(name);
- if (_dict.ContainsKey(key))
- _dict[key] = value;
- else
- _dict.Add(key, value);
+ _dict[key] = value;
}
+ [SuppressMessage("", "CA1308")]
private string NormalizePropertyName(string propertyName)
{
- return _ignoreCase ? propertyName.ToLower() : propertyName;
+ return _ignoreCase ? propertyName.ToLower(CultureInfo.InvariantCulture) : propertyName;
}
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs b/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs
index a9cb745ce..ed3ffecfe 100644
--- a/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs
+++ b/src/App/NetDaemon.App/Common/Fluent/FluentInputSelect.cs
@@ -30,9 +30,8 @@ public interface IFluentSetOption
///
/// Set option value of the selected input selects
///
- ///
- ///
- T SetOption(string option);
+ ///
+ T SetOption(string selectOption);
}
///
@@ -41,8 +40,8 @@ public interface IFluentSetOption
public class InputSelectManager : IFluentInputSelect, IFluentExecuteAsync
{
private readonly IEnumerable _entityIds;
- private INetDaemon _daemon;
- private INetDaemonApp _app;
+ private readonly INetDaemon _daemon;
+ private readonly INetDaemonApp _app;
private string? _option;
///
@@ -75,9 +74,9 @@ public Task ExecuteAsync()
}
///
- public IFluentExecuteAsync SetOption(string option)
+ public IFluentExecuteAsync SetOption(string selectOption)
{
- _option = option;
+ _option = selectOption;
return this;
}
}
diff --git a/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs b/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs
index 9bcb1251b..122ddc1cc 100644
--- a/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs
+++ b/src/App/NetDaemon.App/Common/Fluent/FluentMediaPlayer.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using NetDaemon.Common.Exceptions;
namespace NetDaemon.Common.Fluent
{
@@ -63,8 +64,10 @@ public IMediaPlayerExecuteAsync PlayPause()
///
public IMediaPlayerExecuteAsync Speak(string message)
{
- _currentAction = new FluentAction(FluentActionType.Speak);
- _currentAction.MessageToSpeak = message;
+ _currentAction = new FluentAction(FluentActionType.Speak)
+ {
+ MessageToSpeak = message
+ };
return this;
}
@@ -78,7 +81,7 @@ public IMediaPlayerExecuteAsync Stop()
///
public async Task ExecuteAsync()
{
- _ = _currentAction ?? throw new NullReferenceException("Missing fluent action type!");
+ _ = _currentAction ?? throw new NetDaemonNullReferenceException("Missing fluent action type!");
var executeTask = _currentAction.ActionType switch
{
@@ -98,7 +101,7 @@ Task Speak()
foreach (var entityId in EntityIds)
{
var message = _currentAction?.MessageToSpeak ??
- throw new NullReferenceException("Message to speak is null or empty!");
+ throw new NetDaemonNullReferenceException("Message to speak is null or empty!");
Daemon.Speak(entityId, message);
}
diff --git a/src/App/NetDaemon.App/Common/IHttpHandler.cs b/src/App/NetDaemon.App/Common/IHttpHandler.cs
index cf8c69ea7..1e848c5d1 100644
--- a/src/App/NetDaemon.App/Common/IHttpHandler.cs
+++ b/src/App/NetDaemon.App/Common/IHttpHandler.cs
@@ -1,4 +1,5 @@
-using System.Net.Http;
+using System.Diagnostics.CodeAnalysis;
+using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
@@ -7,6 +8,7 @@ namespace NetDaemon.Common
///
/// Implements all Http features of NetDaemon
///
+ [SuppressMessage("", "CA1054")]
public interface IHttpHandler
{
///
diff --git a/src/App/NetDaemon.App/Common/INetDaemon.cs b/src/App/NetDaemon.App/Common/INetDaemon.cs
index 34e7550cb..315d09eab 100644
--- a/src/App/NetDaemon.App/Common/INetDaemon.cs
+++ b/src/App/NetDaemon.App/Common/INetDaemon.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NetDaemon.Common.Fluent;
@@ -103,8 +104,8 @@ public interface INetDaemon : INetDaemonCommon
///
/// Selects the input selects to do actions on using lambda
///
- /// The lambda expression selecting input select
/// The Daemon App calling fluent API
+ /// The lambda expression selecting input select
IFluentInputSelect InputSelects(INetDaemonApp app, Func func);
///
@@ -147,6 +148,7 @@ public interface INetDaemon : INetDaemonCommon
///
/// Base interface that all NetDaemon apps needs to implement
///
+ [SuppressMessage("", "CA1716")]
public interface INetDaemonApp : INetDaemonAppBase
{
///
@@ -326,6 +328,7 @@ void ListenEvent(Func funcSelector,
///
/// Shared features in both Reactive and async/await models
///
+ [SuppressMessage("", "CA1716")]
public interface INetDaemonAppBase :
INetDaemonInitialableApp, IDoLogging, IAsyncDisposable, IEquatable
{
@@ -361,7 +364,7 @@ public interface INetDaemonAppBase :
///
/// Gets or sets a flag indicating whether this app is enabled.
- /// This property property can be controlled from Home Assistant.
+ /// This property can be controlled from Home Assistant.
///
///
/// A disabled app will not be initialized during the discovery.
@@ -405,7 +408,6 @@ public interface INetDaemonAppBase :
void ListenServiceCall(string domain, string service,
Func action);
-
///
/// Returns different runtime information about an app
///
@@ -595,6 +597,7 @@ public interface INetDaemonCommon
/// exactly when it happens but it should be fine. The SetState function
/// updates the state before sending.
///
+ [SuppressMessage("", "CA1721")]
IEnumerable State { get; }
///
@@ -602,7 +605,7 @@ public interface INetDaemonCommon
///
/// Unique Id of the data
/// The data persistent or null if not exists
- ValueTask GetDataAsync(string id) where T : class;
+ Task GetDataAsync(string id) where T : class;
///
/// Gets current state for the entity
@@ -661,10 +664,11 @@ public interface INetDaemonInitialableApp
///
///
///
+ ///
/// Restores the state of the storage object.!--
/// Todo: in the future also the state of tagged properties
- ///
- /// It is implemented async so state will be lazy saved
+ ///
+ /// It is implemented async so state will be lazy saved
///
Task RestoreAppStateAsync();
@@ -672,10 +676,11 @@ public interface INetDaemonInitialableApp
/// Saves the app state
///
///
+ ///
/// Saves the state of the storage object.!--
/// Todo: in the future also the state of tagged properties
- ///
- /// It is implemented async so state will be lazy saved
+ ///
+ /// It is implemented async so state will be lazy saved
///
void SaveAppState();
diff --git a/src/App/NetDaemon.App/Common/IScheduler.cs b/src/App/NetDaemon.App/Common/IScheduler.cs
index 6ecdb4b83..3b1160b1b 100644
--- a/src/App/NetDaemon.App/Common/IScheduler.cs
+++ b/src/App/NetDaemon.App/Common/IScheduler.cs
@@ -22,8 +22,8 @@ public interface IScheduler : IAsyncDisposable
/// Run daily tasks
///
/// The time in the format HH:mm:ss
- /// The action to run
/// A list of days the scheduler will run on
+ /// The action to run
///
ISchedulerResult RunDaily(string time, IEnumerable? runOnDays, Func func);
diff --git a/src/App/NetDaemon.App/Common/Messages.cs b/src/App/NetDaemon.App/Common/Messages.cs
index c9c9ad0e1..b30b2a50e 100644
--- a/src/App/NetDaemon.App/Common/Messages.cs
+++ b/src/App/NetDaemon.App/Common/Messages.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
using System.Text.Json;
using NetDaemon.Common.Fluent;
@@ -14,52 +16,51 @@ public record Config
///
/// Components being installed and used
///
- public List? Components { get; init; } = null;
+ public IList? Components { get; }
///
/// Local config directory
///
- public string? ConfigDir { get; init; } = null;
+ public string? ConfigDir { get; }
///
/// Elevation of Home Assistant instance
///
- public int? Elevation { get; init; } = null;
+ public int? Elevation { get; }
///
/// Latitude of Home Assistant instance
///
- public float? Latitude { get; init; } = null;
+ public float? Latitude { get; }
///
/// The name of the location
///
- public string? LocationName { get; init; } = null;
+ public string? LocationName { get; }
///
/// Longitude of Home Assistant instance
///
- public float? Longitude { get; init; } = null;
+ public float? Longitude { get; }
///
/// The timezone
///
- public string? TimeZone { get; init; } = null;
-
+ public string? TimeZone { get; }
///
/// Unity system being configured
///
- public HassUnitSystem? UnitSystem { get; init; } = null;
+ public HassUnitSystem? UnitSystem { get; init; }
///
/// Current Home Assistant version
///
- public string? Version { get; init; } = null;
+ public string? Version { get; init; }
///
/// Whitelisted external directories
///
- public List? WhitelistExternalDirs { get; init; } = null;
+ public ReadOnlyCollection? WhitelistExternalDirs { get; }
}
///
@@ -105,7 +106,6 @@ public record EntityState : IEntityProperties
///
/// returns a pretty print of EntityState
///
- ///
public override string ToString()
{
string attributes = "";
@@ -115,7 +115,7 @@ public override string ToString()
{
if (key is not null)
{
- attributes = attributes + string.Format(" {0}:{1}", key, attr[key]);
+ attributes += string.Format(CultureInfo.InvariantCulture, " {0}:{1}", key, attr[key]);
}
}
}
@@ -131,7 +131,6 @@ public override string ToString()
}
}
-
///
/// Unit system parameters for Home Assistant
///
@@ -140,22 +139,22 @@ public record HassUnitSystem
///
/// Lenght unit
///
- public string? Length { get; init; } = null;
+ public string? Length { get; init; }
///
/// Mass unit
///
- public string? Mass { get; init; } = null;
+ public string? Mass { get; init; }
///
/// Temperature unit
///
- public string? Temperature { get; init; } = null;
+ public string? Temperature { get; init; }
///
/// Volume unit
///
- public string? Volume { get; init; } = null;
+ public string? Volume { get; init; }
}
///
@@ -176,7 +175,6 @@ public record ServiceEvent
///
/// Data being sent by the service
///
- ///
- public JsonElement? ServiceData { get; init; } = null;
+ public JsonElement? ServiceData { get; init; }
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/NetDaemonApp.cs b/src/App/NetDaemon.App/Common/NetDaemonApp.cs
index d1c6fcd9e..ab380b55a 100644
--- a/src/App/NetDaemon.App/Common/NetDaemonApp.cs
+++ b/src/App/NetDaemon.App/Common/NetDaemonApp.cs
@@ -1,9 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using NetDaemon.Common.Exceptions;
using NetDaemon.Common.Fluent;
[assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")]
@@ -13,73 +15,70 @@ namespace NetDaemon.Common
///
/// Base class för all NetDaemon apps
///
+ [SuppressMessage("", "CA1065"),
+ SuppressMessage("", "CA1721")
+ ]
public abstract class NetDaemonApp : NetDaemonAppBase, INetDaemonApp, INetDaemonCommon
{
- private readonly IList<(string pattern, Func action)> _eventCallbacks =
- new List<(string pattern, Func action)>();
-
- private readonly List<(Func, Func)> _eventFunctionSelectorCallbacks =
- new List<(Func, Func)>();
- private readonly ConcurrentDictionary action)> _stateCallbacks =
- new ConcurrentDictionary action)>();
-
///
/// All actions being performed for named events
///
- public IList<(string pattern, Func action)> EventCallbacks => _eventCallbacks;
+ public IList<(string pattern, Func action)> EventCallbacks { get; } =
+ new List<(string pattern, Func action)>();
///
/// All actions being performed for lambda selected events
///
- public List<(Func, Func)> EventFunctionCallbacks => _eventFunctionSelectorCallbacks;
+ public IList<(Func, Func)> EventFunctionCallbacks { get; } = new List<(Func, Func)>();
///
- public IScheduler Scheduler => _daemon?.Scheduler ??
- throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
+ public IScheduler Scheduler => Daemon?.Scheduler ??
+ throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
///
- public IEnumerable State => _daemon?.State ??
- throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
+ public IEnumerable State => Daemon?.State ??
+ throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
///
public ConcurrentDictionary action)>
- StateCallbacks => _stateCallbacks;
+ StateCallbacks
+ { get; } = new();
// Used for testing
- internal ConcurrentDictionary action)> InternalStateActions => _stateCallbacks;
+ internal ConcurrentDictionary action)> InternalStateActions => StateCallbacks;
///
public Task CallService(string domain, string service, dynamic? data = null, bool waitForResponse = false)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.CallServiceAsync(domain, service, data, waitForResponse);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.CallServiceAsync(domain, service, data, waitForResponse);
}
///
public ICamera Camera(params string[] entityIds)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.Camera(this, entityIds);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.Camera(this, entityIds);
}
///
public ICamera Cameras(IEnumerable entityIds)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.Cameras(this, entityIds);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.Cameras(this, entityIds);
}
///
public ICamera Cameras(Func func)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.Cameras(this, func);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.Cameras(this, func);
}
///
public void CancelListenState(string id)
{
// Remove and ignore if not exist
- _stateCallbacks.Remove(id, out _);
+ StateCallbacks.Remove(id, out _);
}
///
@@ -89,21 +88,26 @@ public IDelayResult DelayUntilStateChange(string entityId, object? to = null, ob
///
public IDelayResult DelayUntilStateChange(IEnumerable entityIds, object? to = null, object? from = null, bool allChanges = false)
{
+ _ = entityIds ??
+ throw new NetDaemonArgumentNullException(nameof(entityIds));
+
// Use TaskCompletionSource to simulate a task that we can control
var taskCompletionSource = new TaskCompletionSource();
var result = new DelayResult(taskCompletionSource, this);
foreach (var entityId in entityIds)
{
- result.StateSubscriptions.Add(ListenState(entityId, (entityIdInn, newState, oldState) =>
+ result.StateSubscriptions.Add(ListenState(entityId, (_, newState, oldState) =>
{
- if (to != null)
- if ((dynamic)to != newState?.State)
- return Task.CompletedTask;
+ if (to != null && (dynamic)to != newState?.State)
+ {
+ return Task.CompletedTask;
+ }
- if (from != null)
- if ((dynamic)from != oldState?.State)
- return Task.CompletedTask;
+ if (from != null && (dynamic)from != oldState?.State)
+ {
+ return Task.CompletedTask;
+ }
// If we don´t accept all changes in the state change
// and we do not have a state change so return
@@ -123,15 +127,19 @@ public IDelayResult DelayUntilStateChange(IEnumerable entityIds, object?
}
///
+ [SuppressMessage("", "CA1031")]
public IDelayResult DelayUntilStateChange(IEnumerable entityIds, Func stateFunc)
{
+ _ = entityIds ??
+ throw new NetDaemonArgumentNullException(nameof(entityIds));
+
// Use TaskCompletionSource to simulate a task that we can control
var taskCompletionSource = new TaskCompletionSource();
var result = new DelayResult(taskCompletionSource, this);
foreach (var entityId in entityIds)
{
- result.StateSubscriptions.Add(ListenState(entityId, (entityIdInn, newState, oldState) =>
+ result.StateSubscriptions.Add(ListenState(entityId, (_, newState, oldState) =>
{
try
{
@@ -161,31 +169,31 @@ public IDelayResult DelayUntilStateChange(IEnumerable entityIds, Func
public async override ValueTask DisposeAsync()
{
- _stateCallbacks.Clear();
- _eventCallbacks.Clear();
- _eventFunctionSelectorCallbacks.Clear();
+ StateCallbacks.Clear();
+ EventCallbacks.Clear();
+ EventFunctionCallbacks.Clear();
await base.DisposeAsync().ConfigureAwait(false);
}
///
public IEntity Entities(Func func)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.Entities(this, func);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.Entities(this, func);
}
///
- public IEntity Entities(IEnumerable entityIds)
+ public IEntity Entities(IEnumerable entityId)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.Entities(this, entityIds);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.Entities(this, entityId);
}
///
public IEntity Entity(params string[] entityId)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.Entity(this, entityId);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.Entity(this, entityId);
}
///
@@ -198,41 +206,41 @@ public IEntity Entity(params string[] entityId)
public IFluentEvent Events(IEnumerable eventParams) => new FluentEventManager(eventParams, this);
///
- public async ValueTask GetDataAsync(string id) where T : class
+ public async Task GetDataAsync(string id) where T : class
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return await _daemon!.GetDataAsync(id).ConfigureAwait(false);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return await Daemon!.GetDataAsync(id).ConfigureAwait(false);
}
///
- public EntityState? GetState(string entityId) => _daemon?.GetState(entityId);
+ public EntityState? GetState(string entityId) => Daemon?.GetState(entityId);
///
public IFluentInputSelect InputSelect(params string[] inputSelectParams)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.InputSelect(this, inputSelectParams);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.InputSelect(this, inputSelectParams);
}
///
public IFluentInputSelect InputSelects(IEnumerable inputSelectParams)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.InputSelects(this, inputSelectParams);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.InputSelects(this, inputSelectParams);
}
///
public IFluentInputSelect InputSelects(Func func)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.InputSelects(this, func);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.InputSelects(this, func);
}
///
- public void ListenEvent(string ev, Func action) => _eventCallbacks.Add((ev, action));
+ public void ListenEvent(string ev, Func action) => EventCallbacks.Add((ev, action));
///
- public void ListenEvent(Func funcSelector, Func func) => _eventFunctionSelectorCallbacks.Add((funcSelector, func));
+ public void ListenEvent(Func funcSelector, Func action) => EventFunctionCallbacks.Add((funcSelector, action));
///
public string? ListenState(string pattern,
@@ -241,57 +249,57 @@ public IFluentInputSelect InputSelects(Func func)
// Use guid as unique id but will externally use string so
// The design can change in-case guid won't cut it.
var uniqueId = Guid.NewGuid().ToString();
- _stateCallbacks[uniqueId] = (pattern, action);
+ StateCallbacks[uniqueId] = (pattern, action);
return uniqueId;
}
///
public IMediaPlayer MediaPlayer(params string[] entityIds)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.MediaPlayer(this, entityIds);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.MediaPlayer(this, entityIds);
}
///
public IMediaPlayer MediaPlayers(IEnumerable entityIds)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.MediaPlayers(this, entityIds);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.MediaPlayers(this, entityIds);
}
///
public IMediaPlayer MediaPlayers(Func func)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.MediaPlayers(this, func);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.MediaPlayers(this, func);
}
///
public IScript RunScript(params string[] entityIds)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.RunScript(this, entityIds);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.RunScript(this, entityIds);
}
///
public Task SaveDataAsync(string id, T data)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.SaveDataAsync(id, data);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.SaveDataAsync(id, data);
}
///
public async Task SendEvent(string eventId, dynamic? data = null)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return await _daemon!.SendEvent(eventId, data).ConfigureAwait(false);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return await Daemon!.SendEvent(eventId, data).ConfigureAwait(false);
}
///
public async Task SetStateAsync(string entityId, dynamic state, params (string name, object val)[] attributes)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return await _daemon!.SetStateAsync(entityId, state, attributes).ConfigureAwait(false);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return await Daemon!.SetStateAsync(entityId, state, attributes).ConfigureAwait(false);
}
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs b/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs
index c546776ca..7d801d038 100644
--- a/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs
+++ b/src/App/NetDaemon.App/Common/NetDaemonAppBase.cs
@@ -7,6 +7,7 @@
using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using NetDaemon.Common.Exceptions;
using NetDaemon.Common.Fluent;
namespace NetDaemon.Common
@@ -19,45 +20,33 @@ public abstract class NetDaemonAppBase : INetDaemonAppBase
///
/// A set of properties found in static analysis of code for each app
///
- public static Dictionary> CompileTimeProperties { get; set; } = new();
+ public static Dictionary> CompileTimeProperties { get; } = new();
- ///
- /// The NetDaemonHost instance
- ///
- protected INetDaemon? _daemon;
private Task? _manageRuntimeInformationUpdatesTask;
- ///
- /// Registered callbacks for service calls
- ///
- private readonly List<(string, string, Func)> _daemonCallBacksForServiceCalls
- = new List<(string, string, Func)>();
-
///
/// All actions being performed for service call events
///
- public List<(string, string, Func)> DaemonCallBacksForServiceCalls => _daemonCallBacksForServiceCalls;
-
+ public IList<(string, string, Func)> DaemonCallBacksForServiceCalls { get; }
+ = new List<(string, string, Func)>();
// This is declared as static since it will contain state shared globally
- private static ConcurrentDictionary _global = new();
-
- private readonly ConcurrentDictionary _attributes = new();
-
- // To handle state saves max once at a time, internal due to tests
- private readonly Channel _lazyStoreStateQueue =
- Channel.CreateBounded(1);
+ private static readonly ConcurrentDictionary _global = new();
private readonly Channel _updateRuntimeInfoChannel =
Channel.CreateBounded(5);
-
- // ///
- // /// The last error message logged och catched
- // ///
- // public string? RuntimeInfo.LastErrorMessage { get; set; } = null;
- private CancellationTokenSource _cancelSource = new CancellationTokenSource();
+ private readonly CancellationTokenSource _cancelSource;
+ private bool _isDisposed;
private Task? _lazyStoreStateTask;
- private FluentExpandoObject? _storageObject;
+
+ ///
+ /// Constructor
+ ///
+ protected NetDaemonAppBase()
+ {
+ _cancelSource = new();
+ _isDisposed = false;
+ }
///
/// Dependencies on other applications that will be initialized before this app
@@ -68,12 +57,13 @@ public abstract class NetDaemonAppBase : INetDaemonAppBase
public ConcurrentDictionary Global => _global;
///
+ [SuppressMessage("", "CA1065")]
public IHttpHandler Http
{
get
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.Http;
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.Http;
}
}
@@ -88,31 +78,29 @@ public string Description
{
get
{
- var app_key = this.GetType().FullName;
+ var app_key = GetType().FullName;
if (app_key is null)
return "";
- if (CompileTimeProperties.ContainsKey(app_key))
+ if (CompileTimeProperties.ContainsKey(app_key) && CompileTimeProperties[app_key].ContainsKey("description"))
{
- if (CompileTimeProperties[app_key].ContainsKey("description"))
- {
- return CompileTimeProperties[app_key]["description"];
- }
+ return CompileTimeProperties[app_key]["description"];
}
return "";
}
}
-
///
public ILogger? Logger { get; set; }
///
- public dynamic Storage => _storageObject ?? throw new NullReferenceException($"{nameof(_storageObject)} cant be null");
+ [SuppressMessage("", "CA1065")]
+ public dynamic Storage => InternalStorageObject ?? throw new NetDaemonNullReferenceException($"{nameof(InternalStorageObject)} cant be null");
- internal Channel InternalLazyStoreStateQueue => _lazyStoreStateQueue;
- internal FluentExpandoObject? InternalStorageObject { get { return _storageObject; } set { _storageObject = value; } }
+ internal Channel InternalLazyStoreStateQueue { get; } =
+ Channel.CreateBounded(1);
+ internal FluentExpandoObject? InternalStorageObject { get; set; }
///
/// Implements the IEqualit.Equals method
@@ -120,10 +108,7 @@ public string Description
/// The instance to compare
public bool Equals([AllowNull] INetDaemonAppBase other)
{
- if (other is not null && other.Id is not null && this.Id is not null && this.Id == other.Id)
- return true;
-
- return false;
+ return other is not null && other.Id is not null && Id is not null && Id == other.Id;
}
///
@@ -146,9 +131,9 @@ public virtual Task InitializeAsync()
///
public async Task RestoreAppStateAsync()
{
- _ = _daemon as INetDaemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
- var obj = await _daemon!.GetDataAsync>(GetUniqueIdForStorage()).ConfigureAwait(false);
+ var obj = await Daemon.GetDataAsync>(GetUniqueIdForStorage()).ConfigureAwait(false);
if (obj != null)
{
@@ -156,8 +141,8 @@ public async Task RestoreAppStateAsync()
expStore.CopyFrom(obj);
}
- bool isDisabled = this.Storage.__IsDisabled ?? false;
- var appInfo = _daemon!.State.FirstOrDefault(s => s.EntityId == EntityId);
+ bool isDisabled = Storage.__IsDisabled ?? false;
+ var appInfo = Daemon!.State.FirstOrDefault(s => s.EntityId == EntityId);
var appState = appInfo?.State as string;
if (isDisabled)
@@ -167,8 +152,7 @@ public async Task RestoreAppStateAsync()
{
dynamic serviceData = new FluentExpandoObject();
serviceData.entity_id = EntityId;
- await _daemon.SetStateAsync(EntityId, "off").ConfigureAwait(false);
- await _daemon.CallServiceAsync("switch", "turn_off", serviceData);
+ await Daemon.SetStateAsync(EntityId, "off").ConfigureAwait(false);
}
return;
}
@@ -179,8 +163,7 @@ public async Task RestoreAppStateAsync()
{
dynamic serviceData = new FluentExpandoObject();
serviceData.entity_id = EntityId;
- await _daemon.SetStateAsync(EntityId, "on").ConfigureAwait(false);
- await _daemon.CallServiceAsync("switch", "turn_on", serviceData);
+ await Daemon.SetStateAsync(EntityId, "on").ConfigureAwait(false);
}
return;
}
@@ -189,14 +172,18 @@ public async Task RestoreAppStateAsync()
///
public string EntityId => $"switch.netdaemon_{Id?.ToSafeHomeAssistantEntityId()}";
- AppRuntimeInfo _runtimeInfo = new AppRuntimeInfo { HasError = false };
-
///
- public AppRuntimeInfo RuntimeInfo => _runtimeInfo;
+ public AppRuntimeInfo RuntimeInfo { get; } = new AppRuntimeInfo { HasError = false };
///
- public IEnumerable EntityIds => _daemon?.State.Select(n => n.EntityId) ??
- throw new NullReferenceException("Deamon not expected to be null");
+ [SuppressMessage("", "CA1065")]
+ public IEnumerable EntityIds => Daemon?.State.Select(n => n.EntityId) ??
+ throw new NetDaemonNullReferenceException("Deamon not expected to be null");
+
+ ///
+ /// Instance to Daemon service
+ ///
+ protected INetDaemon? Daemon { get; set; }
///
public void SaveAppState()
@@ -204,30 +191,31 @@ public void SaveAppState()
// Intentionally ignores full queue since we know
// a state change already is in progress wich means
// this state will be saved
- var x = _lazyStoreStateQueue.Writer.TryWrite(true);
+ _ = InternalLazyStoreStateQueue.Writer.TryWrite(true);
}
///
public void Speak(string entityId, string message)
{
- _ = _daemon as INetDaemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- _daemon!.Speak(entityId, message);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ Daemon!.Speak(entityId, message);
}
///
public virtual Task StartUpAsync(INetDaemon daemon)
{
- _daemon = daemon;
+ _ = daemon ?? throw new NetDaemonArgumentNullException(nameof(daemon));
+
+ Daemon = daemon;
_manageRuntimeInformationUpdatesTask = ManageRuntimeInformationUpdates();
_lazyStoreStateTask = Task.Run(async () => await HandleLazyStorage().ConfigureAwait(false));
- _storageObject = new FluentExpandoObject(false, true, daemon: this);
+ InternalStorageObject = new FluentExpandoObject(false, true, daemon: this);
Logger = daemon.Logger;
Logger.LogDebug("Startup: {app}", GetUniqueIdForStorage());
- var appInfo = _daemon!.State.FirstOrDefault(s => s.EntityId == EntityId);
- var appState = appInfo?.State as string;
- if (appState == null || (appState != "on" && appState != "off"))
+ var appInfo = Daemon!.State.FirstOrDefault(s => s.EntityId == EntityId);
+ if (appInfo?.State is not string appState || (appState != "on" && appState != "off"))
{
IsEnabled = true;
}
@@ -242,22 +230,24 @@ public virtual Task StartUpAsync(INetDaemon daemon)
///
/// Returns unique Id for instance
///
- public string GetUniqueIdForStorage() => $"{this.GetType().Name}_{Id}".ToLowerInvariant();
+ [SuppressMessage("Microsoft.Globalization", "CA1308")]
+ public string GetUniqueIdForStorage() => $"{GetType().Name}_{Id}".ToLowerInvariant();
+ [SuppressMessage("Microsoft.Design", "CA1031")]
private async Task HandleLazyStorage()
{
- _ = _storageObject ??
- throw new NullReferenceException($"{nameof(_storageObject)} cant be null!");
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
+ _ = InternalStorageObject ??
+ throw new NetDaemonNullReferenceException($"{nameof(InternalStorageObject)} cant be null!");
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
while (!_cancelSource.IsCancellationRequested)
{
try
{
// Dont care about the result, just that it is time to store state
- _ = await _lazyStoreStateQueue.Reader.ReadAsync(_cancelSource.Token);
+ _ = await InternalLazyStoreStateQueue.Reader.ReadAsync(_cancelSource.Token).ConfigureAwait(false);
- await _daemon!.SaveDataAsync>(GetUniqueIdForStorage(), (IDictionary)Storage)
+ await Daemon!.SaveDataAsync(GetUniqueIdForStorage(), (IDictionary)Storage)
.ConfigureAwait(false);
}
catch (OperationCanceledException)
@@ -278,29 +268,37 @@ private async Task HandleLazyStorage()
///
public async virtual ValueTask DisposeAsync()
{
+ lock (_cancelSource)
+ {
+ if (_isDisposed)
+ return;
+ _isDisposed = true;
+ }
_cancelSource.Cancel();
if (_manageRuntimeInformationUpdatesTask is not null)
await _manageRuntimeInformationUpdatesTask.ConfigureAwait(false);
- _daemonCallBacksForServiceCalls.Clear();
+ DaemonCallBacksForServiceCalls.Clear();
- this.IsEnabled = false;
+ IsEnabled = false;
_lazyStoreStateTask = null;
- _storageObject = null;
- _daemon = null;
-
+ InternalStorageObject = null;
+ _cancelSource.Dispose();
+ Daemon = null;
}
///
public INetDaemonAppBase? GetApp(string appInstanceId)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon!.GetApp(appInstanceId);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon!.GetApp(appInstanceId);
}
///
+ [SuppressMessage("Microsoft.Design", "CA1062")]
+ [SuppressMessage("Microsoft.Design", "CA1308")]
public void ListenServiceCall(string domain, string service, Func action)
- => _daemonCallBacksForServiceCalls.Add((domain.ToLowerInvariant(), service.ToLowerInvariant(), action));
+ => DaemonCallBacksForServiceCalls.Add((domain.ToLowerInvariant(), service.ToLowerInvariant(), action));
///
public void Log(string message) => Log(LogLevel.Information, message);
@@ -311,7 +309,7 @@ public void ListenServiceCall(string domain, string service, Func
public void Log(LogLevel level, string message, params object[] param)
{
- if (param is not null && param.Length > 0)
+ if (param.Length > 0)
{
var result = param.Prepend(Id).ToArray();
Logger.Log(level, $" {{Id}}: {message}", result);
@@ -328,7 +326,7 @@ public void Log(LogLevel level, string message, params object[] param)
///
public void Log(LogLevel level, Exception exception, string message, params object[] param)
{
- if (param is not null && param.Length > 0)
+ if (param.Length > 0)
{
var result = param.Prepend(Id).ToArray();
Logger.Log(level, exception, $" {{Id}}: {message}", result);
@@ -462,7 +460,7 @@ private async Task ManageRuntimeInformationUpdates()
{
while (_updateRuntimeInfoChannel.Reader.TryRead(out _)) ;
- _ = await _updateRuntimeInfoChannel.Reader.ReadAsync(_cancelSource.Token);
+ _ = await _updateRuntimeInfoChannel.Reader.ReadAsync(_cancelSource.Token).ConfigureAwait(false);
// do the deed
await HandleUpdateRuntimeInformation().ConfigureAwait(false);
// make sure we never push more messages that 10 per second
@@ -478,16 +476,14 @@ private async Task ManageRuntimeInformationUpdates()
private async Task HandleUpdateRuntimeInformation()
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
if (RuntimeInfo.LastErrorMessage is not null)
{
RuntimeInfo.HasError = true;
}
- await _daemon!.SetStateAsync(EntityId, IsEnabled ? "on" : "off", ("runtime_info", RuntimeInfo)).ConfigureAwait(false);
+ await Daemon!.SetStateAsync(EntityId, IsEnabled ? "on" : "off", ("runtime_info", RuntimeInfo)).ConfigureAwait(false);
}
-
-
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs b/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs
index 24537d81b..3d3ede231 100644
--- a/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs
+++ b/src/App/NetDaemon.App/Common/Reactive/AppDaemonRxApp.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reactive.Concurrency;
@@ -8,6 +9,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using NetDaemon.Common.Exceptions;
using NetDaemon.Common.Fluent;
// For mocking
@@ -20,15 +22,24 @@ namespace NetDaemon.Common.Reactive
///
public abstract class NetDaemonRxApp : NetDaemonAppBase, INetDaemonReactive
{
- private CancellationTokenSource _cancelTimers = new CancellationTokenSource();
+ private readonly CancellationTokenSource _cancelTimers;
private EventObservable? _eventObservables;
- private ReactiveEvent? _reactiveEvent = null;
- private ReactiveState? _reactiveState = null;
private StateChangeObservable? _stateObservables;
+ private bool _isDisposed;
+
+ ///
+ /// Default constructor
+ ///
+ protected NetDaemonRxApp()
+ {
+ _cancelTimers = new();
+ StateAllChanges = new ReactiveState(this);
+ EventChanges = new ReactiveEvent(this);
+ _isDisposed = false;
+ }
///
- public IRxEvent EventChanges =>
- _reactiveEvent ?? throw new ApplicationException("Application not initialized correctly (EventChanges)");
+ public IRxEvent EventChanges { get; }
///
/// Returns the observables events implementation of AppDaemonRxApps
@@ -36,12 +47,10 @@ public abstract class NetDaemonRxApp : NetDaemonAppBase, INetDaemonReactive
public ObservableBase EventChangesObservable => _eventObservables!;
///
- public IObservable<(EntityState Old, EntityState New)> StateAllChanges =>
- _reactiveState ?? throw new ApplicationException("Application not initialized correctly (StateAllChanges>");
+ public IObservable<(EntityState Old, EntityState New)> StateAllChanges { get; }
///
- public IObservable<(EntityState Old, EntityState New)> StateChanges =>
- _reactiveState?.Where(e => e.New?.State != e.Old?.State) ?? throw new ApplicationException("Application not initialized correctly (StateAllChanges>");
+ public IObservable<(EntityState Old, EntityState New)> StateChanges => StateAllChanges.Where(e => e.New?.State != e.Old?.State);
///
/// Returns the observables states implementation of AppDaemonRxApps
@@ -50,13 +59,13 @@ public abstract class NetDaemonRxApp : NetDaemonAppBase, INetDaemonReactive
///
public IEnumerable States =>
- _daemon?.State ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
+ Daemon?.State ?? new List();
///
public void CallService(string domain, string service, dynamic? data)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- _daemon.CallService(domain, service, data);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ Daemon.CallService(domain, service, data);
}
///
@@ -64,8 +73,16 @@ public void CallService(string domain, string service, dynamic? data)
///
public async override ValueTask DisposeAsync()
{
+ lock (_cancelTimers)
+ {
+ if (_isDisposed)
+ return;
+ _isDisposed = true;
+ }
+
+ LogDebug("RxApp {app} is being Disposes", Id!);
// To end timers
- _cancelTimers.Cancel();
+ _cancelTimers?.Cancel();
if (_eventObservables is not null)
_eventObservables!.Clear();
@@ -73,10 +90,7 @@ public async override ValueTask DisposeAsync()
if (_stateObservables is not null)
_stateObservables!.Clear();
- // Make sure we release all references so the apps can be
- // unloaded correctly
- _reactiveEvent = null;
- _reactiveState = null;
+ _cancelTimers?.Dispose();
await base.DisposeAsync().ConfigureAwait(false);
LogDebug("RxApp {app} is Disposed", Id!);
@@ -85,17 +99,17 @@ public async override ValueTask DisposeAsync()
///
public RxEntity Entities(Func func)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
try
{
- IEnumerable x = _daemon.State.Where(func);
+ IEnumerable x = Daemon.State.Where(func);
return new RxEntity(this, x.Select(n => n.EntityId).ToArray());
}
catch (Exception e)
{
- _daemon.Logger.LogDebug(e, "Failed to select entities func in app {appId}", Id);
+ Daemon.Logger.LogDebug(e, "Failed to select entities func in app {appId}", Id);
throw;
}
}
@@ -106,30 +120,28 @@ public RxEntity Entities(Func func)
///
public RxEntity Entities(IEnumerable entityIds)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
return new RxEntity(this, entityIds);
}
///
public RxEntity Entity(string entityId)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
return new RxEntity(this, new string[] { entityId });
}
///
public T? GetData(string id) where T : class
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- return _daemon.GetDataAsync(id).Result;
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ return Daemon.GetDataAsync(id).Result;
}
///
public IDisposable RunDaily(string time, Action action)
{
- DateTime parsedTime;
-
- if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedTime))
+ if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedTime))
{
throw new FormatException($"{time} is not a valid time for the current locale");
}
@@ -144,8 +156,10 @@ public IDisposable RunDaily(string time, Action action)
);
if (now > timeOfDayToTrigger)
+ {
// It is not due until tomorrow
timeOfDayToTrigger = timeOfDayToTrigger.AddDays(1);
+ }
return CreateObservableTimer(timeOfDayToTrigger, TimeSpan.FromDays(1), action);
}
@@ -159,10 +173,9 @@ public IDisposable RunEvery(TimeSpan timespan, Action action)
///
public IDisposable RunEveryHour(string time, Action action)
{
- DateTime parsedTime;
time = $"{DateTime.Now.Hour:D2}:{time}";
- if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedTime))
+ if (!DateTime.TryParseExact(time, "HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedTime))
{
throw new FormatException($"{time} is not a valid time for the current locale");
}
@@ -178,8 +191,10 @@ public IDisposable RunEveryHour(string time, Action action)
);
if (now > timeOfDayToTrigger)
+ {
// It is not due until tomorrow
timeOfDayToTrigger = timeOfDayToTrigger.AddHours(1);
+ }
return CreateObservableTimer(timeOfDayToTrigger, TimeSpan.FromHours(1), action);
}
@@ -194,16 +209,17 @@ public IDisposable RunEveryMinute(short second, Action action)
}
///
+ [SuppressMessage("", "CA1031")] // In this case we want just to log the message
public IDisposable RunIn(TimeSpan timespan, Action action)
{
var result = new DisposableTimerResult(_cancelTimers.Token);
Observable.Timer(timespan, TaskPoolScheduler.Default)
.Subscribe(
- s =>
+ _ =>
{
try
{
- if (this.IsEnabled)
+ if (IsEnabled)
action();
}
catch (OperationCanceledException)
@@ -223,53 +239,52 @@ public IDisposable RunIn(TimeSpan timespan, Action action)
///
public void RunScript(params string[] script)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
foreach (var scriptName in script)
{
var name = scriptName;
- if (scriptName.Contains('.'))
- name = scriptName[(scriptName.IndexOf('.') + 1)..];
+ if (scriptName.Contains('.', StringComparison.InvariantCultureIgnoreCase))
+ name = scriptName[(scriptName.IndexOf('.', StringComparison.InvariantCultureIgnoreCase) + 1)..];
- _daemon.CallService("script", name);
+ Daemon.CallService("script", name);
}
}
///
public void SaveData(string id, T data)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- _daemon.SaveDataAsync(id, data).Wait();
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ Daemon.SaveDataAsync(id, data).Wait();
}
///
public void SetState(string entityId, dynamic state, dynamic? attributes = null)
{
- _ = _daemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- _daemon.SetState(entityId, state, attributes);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ Daemon.SetState(entityId, state, attributes);
}
///
public async override Task StartUpAsync(INetDaemon daemon)
{
- await base.StartUpAsync(daemon);
- _ = _daemon as INetDaemon ?? throw new NullReferenceException($"{nameof(_daemon)} cant be null!");
- _ = Logger ?? throw new NullReferenceException("Logger can not be null!");
+ await base.StartUpAsync(daemon).ConfigureAwait(false);
+ _ = Daemon ?? throw new NetDaemonNullReferenceException($"{nameof(Daemon)} cant be null!");
+ _ = Logger ?? throw new NetDaemonNullReferenceException("Logger can not be null!");
_eventObservables = new EventObservable(Logger, this);
_stateObservables = new StateChangeObservable(Logger, this);
- _reactiveState = new ReactiveState(this);
- _reactiveEvent = new ReactiveEvent(this);
}
///
- public EntityState? State(string entityId) => _daemon?.GetState(entityId);
+ public EntityState? State(string entityId) => Daemon?.GetState(entityId);
///
/// Creates an observable intervall
///
/// Time span for intervall
/// The action to call
+ [SuppressMessage("Microsoft.Design", "CA1031")]
internal virtual IDisposable CreateObservableIntervall(TimeSpan timespan, Action action)
{
var result = new DisposableTimerResult(_cancelTimers.Token);
@@ -278,11 +293,11 @@ internal virtual IDisposable CreateObservableIntervall(TimeSpan timespan, Action
Observable.Interval(timespan, TaskPoolScheduler.Default)
.Subscribe(
- s =>
+ _ =>
{
try
{
- if (this.IsEnabled)
+ if (IsEnabled)
{
action();
RuntimeInfo.NextScheduledEvent = DateTime.Now + timespan;
@@ -298,10 +313,7 @@ internal virtual IDisposable CreateObservableIntervall(TimeSpan timespan, Action
LogError(e, "Error, ObservableIntervall APP: {app}", Id ?? "unknown");
}
},
- ex =>
- {
- LogTrace("Exiting ObservableIntervall for app {app}, {trigger}:{span}", Id!, timespan);
- }
+ ex => LogTrace(ex, "Exiting ObservableIntervall for app {app}, {trigger}:{span}", Id!, timespan)
, result.Token);
return result;
@@ -313,7 +325,8 @@ internal virtual IDisposable CreateObservableIntervall(TimeSpan timespan, Action
/// When to start the timer
/// The intervall
/// Action to call each intervall
- ///
+ [SuppressMessage("Microsoft.Design", "CA1031")]
+
internal virtual IDisposable CreateObservableTimer(DateTime timeOfDayToTrigger, TimeSpan interval, Action action)
{
var result = new DisposableTimerResult(_cancelTimers.Token);
@@ -326,11 +339,11 @@ internal virtual IDisposable CreateObservableTimer(DateTime timeOfDayToTrigger,
interval,
TaskPoolScheduler.Default)
.Subscribe(
- s =>
+ _ =>
{
try
{
- if (this.IsEnabled)
+ if (IsEnabled)
{
action();
RuntimeInfo.NextScheduledEvent = DateTime.Now + interval;
@@ -349,7 +362,7 @@ internal virtual IDisposable CreateObservableTimer(DateTime timeOfDayToTrigger,
() => Log("Exiting timer for app {app}, {trigger}:{span}",
Id!, timeOfDayToTrigger, interval),
result.Token
- );
+ );
return result;
}
diff --git a/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs b/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs
index faa65eae9..4b9491374 100644
--- a/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs
+++ b/src/App/NetDaemon.App/Common/Reactive/DisposableTimerResult.cs
@@ -30,7 +30,8 @@ public DisposableTimerResult(CancellationToken token)
public void Dispose()
{
_internalToken.Cancel();
+ _combinedToken.Dispose();
+ _internalToken.Dispose();
}
}
-
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs b/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs
index fec144b97..5a9c59d11 100644
--- a/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs
+++ b/src/App/NetDaemon.App/Common/Reactive/ObservableBase.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
namespace NetDaemon.Common.Reactive
@@ -17,7 +18,7 @@ public class ObservableBase : IObservable
private readonly ILogger _logger;
private readonly ConcurrentDictionary, IObserver>
- _observersTuples = new ConcurrentDictionary, IObserver>();
+ _observersTuples = new();
///
/// Constructor
///
@@ -36,6 +37,8 @@ public ObservableBase(ILogger logger, INetDaemonAppBase app)
///
/// Clear all observers
///
+ [SuppressMessage("Microsoft.Design", "CA1031")]
+
public void Clear()
{
foreach (var eventObservable in _observersTuples)
@@ -75,8 +78,8 @@ private class UnsubscriberObservable : IDisposable
public UnsubscriberObservable(
ConcurrentDictionary, IObserver> observers, IObserver observer)
{
- this._observers = observers;
- this._observer = observer;
+ _observer = observer;
+ _observers = observers;
}
public void Dispose()
@@ -86,7 +89,6 @@ public void Dispose()
_observers.TryRemove(_observer, out _);
_observer.OnCompleted();
}
- // System.Console.WriteLine($"Subscribers:{_observers.Count}");
}
}
}
diff --git a/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs b/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs
index 938726c2b..d26a96e65 100644
--- a/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs
+++ b/src/App/NetDaemon.App/Common/Reactive/ObservableExtensionMethods.cs
@@ -13,7 +13,6 @@ public static class ObservableExtensionMethods
///
///
///
- ///
public static IObservable<(EntityState Old, EntityState New)> NDSameStateFor(this IObservable<(EntityState Old, EntityState New)> observable, TimeSpan span)
{
return observable.Throttle(span);
@@ -26,17 +25,17 @@ public static class ObservableExtensionMethods
/// Timeout waiting for state
public static IObservable<(EntityState Old, EntityState New)> NDWaitForState(this IObservable<(EntityState Old, EntityState New)> observable, TimeSpan timeout)
{
- return observable.Timeout(timeout, Observable.Return((new NetDaemon.Common.EntityState() { State = "TimeOut" }, new NetDaemon.Common.EntityState() { State = "TimeOut" }))).Take(1);
+ return observable
+ .Timeout(timeout,
+ Observable.Return((new EntityState() { State = "TimeOut" }, new EntityState() { State = "TimeOut" }))).Take(1);
}
///
/// Wait for state the default time
///
///
- public static IObservable<(EntityState Old, EntityState New)> NDWaitForState(this IObservable<(EntityState Old, EntityState New)> observable)
- {
- return observable.Timeout(TimeSpan.FromSeconds(5), Observable.Return((new NetDaemon.Common.EntityState() { State = "TimeOut" }, new NetDaemon.Common.EntityState() { State = "TimeOut" }))).Take(1);
- }
-
+ public static IObservable<(EntityState Old, EntityState New)> NDWaitForState(this IObservable<(EntityState Old, EntityState New)> observable) => observable
+ .Timeout(TimeSpan.FromSeconds(5),
+ Observable.Return((new EntityState() { State = "TimeOut" }, new EntityState() { State = "TimeOut" }))).Take(1);
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs b/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs
index 10118100b..cdc827b63 100644
--- a/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs
+++ b/src/App/NetDaemon.App/Common/Reactive/RxEntity.cs
@@ -3,6 +3,7 @@
using System.Dynamic;
using System.Linq;
using System.Reactive.Linq;
+using NetDaemon.Common.Exceptions;
using NetDaemon.Common.Fluent;
namespace NetDaemon.Common.Reactive
@@ -68,11 +69,11 @@ public class RxEntity : ICanTurnOnAndOff, ISetState, IObserve
///
/// The protected daemon app instance
///
- protected readonly INetDaemonReactive DaemonRxApp;
+ protected INetDaemonReactive DaemonRxApp { get; }
///
/// Entity ids being handled by the RxEntity
///
- protected readonly IEnumerable EntityIds;
+ protected IEnumerable EntityIds { get; }
///
/// Constructor
@@ -108,7 +109,6 @@ public void SetState(dynamic state, dynamic? attributes = null)
{
foreach (var entityId in EntityIds)
{
- var domain = GetDomainFromEntity(entityId);
DaemonRxApp.SetState(entityId, state, attributes);
}
}
@@ -126,7 +126,7 @@ internal static string GetDomainFromEntity(string entity)
{
var entityParts = entity.Split('.');
if (entityParts.Length != 2)
- throw new ApplicationException($"entity_id is mal formatted {entity}");
+ throw new NetDaemonException($"entity_id is mal formatted {entity}");
return entityParts[0];
}
@@ -138,7 +138,7 @@ internal static string GetDomainFromEntity(string entity)
/// Data to provide
public void CallService(string service, dynamic? data = null)
{
- if (EntityIds is null || EntityIds is not null && EntityIds.Count() == 0)
+ if (EntityIds?.Any() != true)
return;
foreach (var entityId in EntityIds!)
@@ -170,14 +170,14 @@ public void CallService(string service, dynamic? data = null)
private void CallServiceOnEntity(string service, dynamic? attributes = null)
{
- if (EntityIds is null || EntityIds is not null && EntityIds.Count() == 0)
+ if (EntityIds?.Any() != true)
return;
dynamic? data = null;
if (attributes is not null)
{
- if (attributes is IDictionary == false)
+ if (attributes is not IDictionary)
data = ((object)attributes).ToExpandoObject();
else
data = attributes;
diff --git a/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs b/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs
index 327a74b9c..7b2de97f7 100644
--- a/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs
+++ b/src/App/NetDaemon.App/Common/Reactive/RxEvent.cs
@@ -3,11 +3,9 @@
///
/// Represent an event from eventstream
///
- public struct RxEvent
+ public class RxEvent
{
- private readonly dynamic? _data;
private readonly string? _domain;
- private readonly string _eventName;
///
/// Constructor
@@ -17,15 +15,15 @@ public struct RxEvent
/// Data
public RxEvent(string eventName, string? domain, dynamic? data)
{
- _eventName = eventName;
+ Event = eventName;
_domain = domain;
- _data = data;
+ Data = data;
}
///
/// Data from event
///
- public dynamic? Data => _data;
+ public dynamic? Data { get; }
///
/// Domain (call service event)
@@ -35,6 +33,6 @@ public RxEvent(string eventName, string? domain, dynamic? data)
///
/// The event being sent
///
- public string Event => _eventName;
+ public string Event { get; }
}
}
\ No newline at end of file
diff --git a/src/App/NetDaemon.App/NetDaemon.App.csproj b/src/App/NetDaemon.App/NetDaemon.App.csproj
index 72f6946b4..927ae709e 100644
--- a/src/App/NetDaemon.App/NetDaemon.App.csproj
+++ b/src/App/NetDaemon.App/NetDaemon.App.csproj
@@ -24,7 +24,15 @@
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
-
+
+ ..\..\..\.linting\roslynator.ruleset
+ true
+ AllEnabledByDefault
+
diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs b/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs
index c6079cfa9..4c1615908 100644
--- a/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs
+++ b/src/Daemon/NetDaemon.Daemon/Daemon/CodeManager.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using NetDaemon.Common;
+using NetDaemon.Common.Exceptions;
using NetDaemon.Daemon.Config;
[assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")]
@@ -15,7 +17,7 @@ public sealed class CodeManager : IInstanceDaemonApp
{
private readonly ILogger _logger;
private readonly IYamlConfig _yamlConfig;
- private IEnumerable? _loadedDaemonApps;
+ private readonly IEnumerable? _loadedDaemonApps;
///
/// Constructor
@@ -30,7 +32,8 @@ public CodeManager(IEnumerable daemonAppTypes, ILogger logger, IYamlConfig
_yamlConfig = yamlConfig;
}
- public int Count => _loadedDaemonApps?.Count() ?? throw new NullReferenceException("_loadedDaemonApps cannot be null");
+ [SuppressMessage("", "CA1065")]
+ public int Count => _loadedDaemonApps?.Count() ?? throw new NetDaemonNullReferenceException("_loadedDaemonApps cannot be null");
// Internal for testing
public IEnumerable DaemonAppTypes => _loadedDaemonApps!;
@@ -40,7 +43,7 @@ public IEnumerable InstanceDaemonApps()
var result = new List(50);
// No loaded, just return an empty list
- if (_loadedDaemonApps is null || _loadedDaemonApps.Count() == 0)
+ if (_loadedDaemonApps?.Any() != true)
return result;
// Get all yaml config file paths
@@ -56,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)
{
@@ -67,7 +71,7 @@ public IEnumerable InstanceDaemonApps()
{
_logger.LogTrace(e, "Error instance the app from the file {file}", file);
_logger.LogError("Error instance the app from the file {file}, use trace flag for details", file);
- throw new ApplicationException($"Error instance the app from the file {file}", e);
+ throw new NetDaemonException($"Error instance the app from the file {file}", e);
}
}
return result;
diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/ConfigExtensions.cs
index 532e45605..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,21 +30,27 @@ 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)
{
if (char.IsUpper(c) && !isStart)
- build.Append("_");
+ 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,11 +62,10 @@ 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;
}
- var returnString = build.ToString();
return build.ToString();
}
diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs
index 1b72b1257..6cb10e8b2 100644
--- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs
+++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlAppConfig.cs
@@ -1,9 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using NetDaemon.Common;
+using NetDaemon.Common.Exceptions;
using YamlDotNet.RepresentationModel;
namespace NetDaemon.Daemon.Config
@@ -25,6 +27,8 @@ public YamlAppConfig(IEnumerable types, TextReader reader, IYamlConfig yam
_yamlFilePath = yamlFilePath;
}
+ [SuppressMessage("", "CA1508")]
+ [SuppressMessage("", "CA1065")]
public IEnumerable Instances
{
get
@@ -46,12 +50,11 @@ public IEnumerable Instances
// Get the class
string? appClass = GetTypeNameFromClassConfig((YamlMappingNode)app.Value);
- Type? appType = _types.Where(n => n.FullName?.ToLowerInvariant() == appClass)
- .FirstOrDefault();
+ Type? appType = _types.FirstOrDefault(n => n.FullName?.ToLowerInvariant() == appClass);
if (appType != null)
{
- var instance = InstanceAndSetPropertyConfig(appType, ((YamlMappingNode)app.Value), appId);
+ var instance = InstanceAndSetPropertyConfig(appType, (YamlMappingNode)app.Value, appId);
if (instance != null)
{
instance.Id = appId;
@@ -59,9 +62,9 @@ public IEnumerable Instances
}
}
}
- catch (System.Exception e)
+ catch (Exception e)
{
- throw new ApplicationException($"Error instancing application {appId}", e);
+ throw new NetDaemonException($"Error instancing application {appId}", e);
}
}
@@ -69,12 +72,15 @@ public IEnumerable Instances
}
}
- public INetDaemonAppBase? InstanceAndSetPropertyConfig(Type netDaemonAppType, YamlMappingNode appNode, string? appId)
+ public INetDaemonAppBase? InstanceAndSetPropertyConfig(
+ Type netDaemonAppType,
+ 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)
{
@@ -96,16 +102,16 @@ public IEnumerable Instances
}
catch (Exception e)
{
- throw new ApplicationException($"Failed to set value {scalarPropertyName}", e);
+ throw new NetDaemonException($"Failed to set value {scalarPropertyName} for app {appId}", e);
}
}
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)
{
var scalarNode = (YamlScalarNode)node;
@@ -117,16 +123,15 @@ public IEnumerable Instances
if (instanceType.IsGenericType && instanceType?.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
Type listType = instanceType?.GetGenericArguments()[0] ??
- throw new NullReferenceException($"The property {instanceType?.Name} of Class {parent?.GetType().Name} is not compatible with configuration");
+ throw new NetDaemonNullReferenceException($"The property {instanceType?.Name} of Class {parent?.GetType().Name} is not compatible with configuration");
IList list = listType.CreateListOfPropertyType() ??
- throw new NullReferenceException("Failed to create listtype, plese check {prop.Name} of Class {app.GetType().Name}");
+ throw new NetDaemonNullReferenceException("Failed to create listtype, plese check {prop.Name} of Class {app.GetType().Name}");
foreach (YamlNode item in ((YamlSequenceNode)node).Children)
{
-
var instance = InstanceProperty(null, listType, item) ??
- throw new NotSupportedException($"The class {parent?.GetType().Name} has wrong type in items");
+ throw new NotSupportedException($"The class {parent?.GetType().Name} has wrong type in items");
list.Add(instance);
}
@@ -162,7 +167,7 @@ public IEnumerable Instances
break;
case YamlNodeType.Mapping:
- var map = (YamlMappingNode)entry.Value;
+ // Maps are not currently supported (var map = (YamlMappingNode)entry.Value;)
break;
}
childProp.SetValue(instance, result);
@@ -179,13 +184,13 @@ private void ReplaceSecretIfExists(YamlScalarNode scalarNode)
var secretReplacement = _yamlConfig.GetSecretFromPath(scalarNode.Value!, Path.GetDirectoryName(_yamlFilePath)!);
- scalarNode.Value = secretReplacement ?? throw new ApplicationException($"{scalarNode.Value!} not found in secrets.yaml");
+ scalarNode.Value = secretReplacement ?? throw new NetDaemonException($"{scalarNode.Value!} not found in secrets.yaml");
}
- private string? GetTypeNameFromClassConfig(YamlMappingNode appNode)
+ private static string? GetTypeNameFromClassConfig(YamlMappingNode appNode)
{
- KeyValuePair classChild = appNode.Children.Where(n =>
- ((YamlScalarNode)n.Key)?.Value?.ToLowerInvariant() == "class").FirstOrDefault();
+ KeyValuePair classChild = appNode.Children.FirstOrDefault(n =>
+ ((YamlScalarNode)n.Key)?.Value?.ToLowerInvariant() == "class");
if (classChild.Key == null || classChild.Value == null)
{
@@ -196,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 97855d261..538137a71 100644
--- a/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs
+++ b/src/Daemon/NetDaemon.Daemon/Daemon/Config/YamlConfig.cs
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Options;
using NetDaemon.Common.Configuration;
+using NetDaemon.Common.Exceptions;
using YamlDotNet.RepresentationModel;
[assembly: InternalsVisibleTo("NetDaemon.Daemon.Tests")]
@@ -23,7 +25,11 @@ public class YamlConfig : IYamlConfig
public YamlConfig(IOptions netDaemonSettings)
{
+ _ = netDaemonSettings ??
+ throw new NetDaemonArgumentNullException(nameof(netDaemonSettings));
+
_configFolder = netDaemonSettings.Value.GetAppSourceDirectory();
+
_secrets = GetAllSecretsFromPath(_configFolder);
}
@@ -43,7 +49,7 @@ public IEnumerable GetAllConfigFilePaths()
}
if (configPath != _configFolder)
{
- var parentPath = Directory.GetParent(configPath)?.FullName ?? throw new ApplicationException("Parent folder of config path does not exist");
+ var parentPath = Directory.GetParent(configPath)?.FullName ?? throw new NetDaemonException("Parent folder of config path does not exist");
return GetSecretFromPath(secret, parentPath);
}
@@ -52,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();
@@ -93,8 +101,8 @@ internal static Dictionary> GetAllSecretsFrom
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 09b49f17a..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,19 +26,24 @@ public static class YamlExtensions
{
public static PropertyInfo? GetYamlProperty(this Type type, string propertyName)
{
- var prop = type.GetProperty(propertyName);
+ _ = type ??
+ throw new NetDaemonArgumentNullException(nameof(type));
- if (prop == null)
- {
- // Lets try convert from python style to CamelCase
- prop = type.GetProperty(propertyName.ToCamelCase());
- }
+ // 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 bde6b0c4d..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))
@@ -27,12 +35,12 @@ public static async Task HandleAttributeInitialization(this INetDaemonAppBase ne
{
switch (attr)
{
- case HomeAssistantServiceCallAttribute hasstServiceCallAttribute:
- await HandleServiceCallAttribute(_daemon, daemonApp, method, true).ConfigureAwait(false);
+ case HomeAssistantServiceCallAttribute:
+ await HandleServiceCallAttribute(daemon, daemonApp, method, true).ConfigureAwait(false);
break;
case HomeAssistantStateChangedAttribute hassStateChangedAttribute:
- HandleStateChangedAttribute(_daemon, hassStateChangedAttribute, daemonApp, method);
+ HandleStateChangedAttribute(daemon, hassStateChangedAttribute, daemonApp, method);
break;
}
}
@@ -40,8 +48,8 @@ public static async Task HandleAttributeInitialization(this INetDaemonAppBase ne
{
switch (attr)
{
- case HomeAssistantServiceCallAttribute hasstServiceCallAttribute:
- await HandleServiceCallAttribute(_daemon, daemonRxApp, method, false).ConfigureAwait(false);
+ case HomeAssistantServiceCallAttribute:
+ await HandleServiceCallAttribute(daemon, daemonRxApp, method, false).ConfigureAwait(false);
break;
}
}
@@ -56,13 +64,15 @@ private static (bool, string) CheckIfServiceCallSignatureIsOk(MethodInfo method,
var parameters = method.GetParameters();
- if (parameters == null || (parameters != null && parameters.Length != 1))
+ if (parameters == null || parameters.Length != 1)
return (false, $"{method.Name} has not correct number of parameters");
var dynParam = parameters![0];
if (dynParam.CustomAttributes.Count() == 1 &&
dynParam.CustomAttributes.First().AttributeType == typeof(DynamicAttribute))
+ {
return (true, string.Empty);
+ }
return (false, $"{method.Name} is not correct signature");
}
@@ -74,7 +84,7 @@ private static (bool, string) CheckIfStateChangedSignatureIsOk(MethodInfo method
var parameters = method.GetParameters();
- if (parameters == null || (parameters != null && parameters.Length != 3))
+ if (parameters == null || parameters.Length != 3)
return (false, $"{method.Name} has not correct number of parameters");
if (parameters![0].ParameterType != typeof(string))
@@ -89,7 +99,8 @@ private static (bool, string) CheckIfStateChangedSignatureIsOk(MethodInfo method
return (true, string.Empty);
}
- private static async Task HandleServiceCallAttribute(INetDaemon _daemon, NetDaemonAppBase netDaemonApp, MethodInfo method, bool async=true)
+ [SuppressMessage("", "CA1031")]
+ private static async Task HandleServiceCallAttribute(INetDaemon _daemon, NetDaemonAppBase netDaemonApp, MethodInfo method, bool async = true)
{
var (signatureOk, err) = CheckIfServiceCallSignatureIsOk(method, async);
if (!signatureOk)
@@ -118,8 +129,8 @@ private static async Task HandleServiceCallAttribute(INetDaemon _daemon, NetDaem
}
});
}
-
+ [SuppressMessage("", "CA1031")]
private static void HandleStateChangedAttribute(
INetDaemon _daemon,
HomeAssistantStateChangedAttribute hassStateChangedAttribute,
@@ -140,13 +151,15 @@ MethodInfo method
{
try
{
- if (hassStateChangedAttribute.To != null)
- if ((dynamic)hassStateChangedAttribute.To != to?.State)
- return;
+ if (hassStateChangedAttribute.To != null && (dynamic)hassStateChangedAttribute.To != to?.State)
+ {
+ return;
+ }
- if (hassStateChangedAttribute.From != null)
- if ((dynamic)hassStateChangedAttribute.From != from?.State)
- return;
+ if (hassStateChangedAttribute.From != null && (dynamic)hassStateChangedAttribute.From != from?.State)
+ {
+ return;
+ }
// If we don´t accept all changes in the state change
// and we do not have a state change so return
diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs b/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs
index 98a6a8a7c..1e0f01ecd 100644
--- a/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs
+++ b/src/Daemon/NetDaemon.Daemon/Daemon/HttpHandler.cs
@@ -4,6 +4,7 @@
using System.Text.Json;
using System.Threading.Tasks;
using NetDaemon.Common;
+using NetDaemon.Common.Exceptions;
namespace NetDaemon.Daemon
{
@@ -18,70 +19,74 @@ public HttpHandler(IHttpClientFactory? httpClientFactory)
public HttpClient CreateHttpClient(string? name = null)
{
- _ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!");
+ _ = _httpClientFactory ?? throw new NetDaemonNullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!");
return _httpClientFactory.CreateClient(name);
}
public async Task GetJson(string url, JsonSerializerOptions? options = null, params (string, object)[] headers)
{
- _ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!");
+ _ = _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)
- ?? throw new ApplicationException($"Unexpected, nothing returned from {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);
+ return await JsonSerializer.DeserializeAsync(await streamTask.ConfigureAwait(false), options).ConfigureAwait(false);
}
public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers)
{
- _ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!");
+ _ = _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));
+ var response = await httpClient.PostAsync(new Uri(url), content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var streamTask = response.Content.ReadAsStreamAsync();
- return await JsonSerializer.DeserializeAsync(await streamTask.ConfigureAwait(false));
+ return await JsonSerializer.DeserializeAsync(await streamTask.ConfigureAwait(false)).ConfigureAwait(false);
}
public async Task PostJson(string url, object request, JsonSerializerOptions? options = null, params (string, object)[] headers)
{
- _ = _httpClientFactory ?? throw new NullReferenceException("No IHttpClientFactory provided, please add AddHttpClient() in configure services!");
+ _ = _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));
+ var response = await httpClient.PostAsync(new Uri(url), content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
- private void AddHeaders(HttpClient httpClient, (string, object)[] headers)
+ private static void AddHeaders(HttpClient httpClient, (string, object)[] headers)
{
if (headers is not null && headers.Length > 0)
{
httpClient.DefaultRequestHeaders.Clear();
foreach (var (name, header) in headers)
{
- if (header is string)
- httpClient.DefaultRequestHeaders.Add(name, (string)header);
- else if (header is IEnumerable)
- httpClient.DefaultRequestHeaders.Add(name, (IEnumerable)header);
+ if (header is string headerStr)
+ httpClient.DefaultRequestHeaders.Add(name, headerStr);
+ else if (header is IEnumerable headerStrings)
+ httpClient.DefaultRequestHeaders.Add(name, headerStrings);
else
- throw new ApplicationException($"Unsupported header, expected string or IEnumerable for {name}");
+ throw new NetDaemonException($"Unsupported header, expected string or IEnumerable for {name}");
}
}
}
diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/IInstanceDaemonApp.cs b/src/Daemon/NetDaemon.Daemon/Daemon/IInstanceDaemonApp.cs
index 4c8271453..0de6fc692 100644
--- a/src/Daemon/NetDaemon.Daemon/Daemon/IInstanceDaemonApp.cs
+++ b/src/Daemon/NetDaemon.Daemon/Daemon/IInstanceDaemonApp.cs
@@ -19,7 +19,6 @@ public interface IInstanceDaemonApp
///
/// Returns a list of instanced daemonapps
///
- ///
IEnumerable InstanceDaemonApps();
}
}
\ No newline at end of file
diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/INetDaemonHost.cs b/src/Daemon/NetDaemon.Daemon/Daemon/INetDaemonHost.cs
index 09b8cf9aa..52a61f27f 100644
--- a/src/Daemon/NetDaemon.Daemon/Daemon/INetDaemonHost.cs
+++ b/src/Daemon/NetDaemon.Daemon/Daemon/INetDaemonHost.cs
@@ -8,7 +8,6 @@
namespace NetDaemon.Daemon
{
-
///
/// The interface that interacts with the daemon host main logic
///
diff --git a/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs b/src/Daemon/NetDaemon.Daemon/Daemon/NetDaemonHost.cs
index e23f0b119..10d391b6d 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;
@@ -11,6 +13,7 @@
using JoySoftware.HomeAssistant.Client;
using Microsoft.Extensions.Logging;
using NetDaemon.Common;
+using NetDaemon.Common.Exceptions;
using NetDaemon.Common.Fluent;
using NetDaemon.Common.Reactive;
using NetDaemon.Daemon.Storage;
@@ -24,17 +27,18 @@ namespace NetDaemon.Daemon
{
public class NetDaemonHost : INetDaemonHost, IAsyncDisposable
{
+ private static readonly ConcurrentBag> concurrentBag = new();
internal readonly ConcurrentBag> _externalEventCallSubscribers =
- new ConcurrentBag>();
+ concurrentBag;
internal readonly ConcurrentDictionary _hassAreas =
- new ConcurrentDictionary();
+ new();
// Internal for test
internal readonly ConcurrentDictionary _hassDevices =
- new ConcurrentDictionary();
+ new();
internal readonly ConcurrentDictionary _hassEntities =
- new ConcurrentDictionary();
+ new();
internal readonly Channel<(string, string, dynamic?)> _serviceCallMessageChannel =
Channel.CreateBounded<(string, string, dynamic?)>(200);
@@ -54,10 +58,10 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable
private IInstanceDaemonApp? _appInstanceManager;
// Internal token source for just cancel this objects activities
- private readonly CancellationTokenSource _cancelDaemon = new CancellationTokenSource();
+ private readonly CancellationTokenSource _cancelDaemon = new();
private readonly ConcurrentBag<(string, string, Func)> _daemonServiceCallFunctions
- = new ConcurrentBag<(string, string, Func)>();
+ = new();
///
/// Currently running tasks for handling new events from HomeAssistant
@@ -70,36 +74,18 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable
private readonly IDataRepository? _repository;
- private readonly ConcurrentDictionary _runningAppInstances =
- new ConcurrentDictionary();
- private readonly ConcurrentDictionary _allAppInstances =
- new ConcurrentDictionary();
-
///
/// Used for testing
///
- internal ConcurrentDictionary InternalAllAppInstances => _allAppInstances;
+ internal ConcurrentDictionary InternalAllAppInstances { get; } = new();
private readonly Scheduler _scheduler;
-
- private readonly List _supportedDomainsForTurnOnOff = new()
- {
- "light",
- "switch",
- "input_boolean",
- "automation",
- "input_boolean",
- "camera",
- "scene",
- "script",
- };
+ private bool _isDisposed;
// Following token source and token are set at RUN
private CancellationToken _cancelToken;
private CancellationTokenSource? _cancelTokenSource;
- private IDictionary _dataCache = new Dictionary();
-
///
/// Constructor
@@ -108,7 +94,6 @@ public class NetDaemonHost : INetDaemonHost, IAsyncDisposable
/// Repository to use
/// The loggerfactory
/// Http handler to use
- /// Handles instances of apps
public NetDaemonHost(
IHassClient? hassClient,
IDataRepository? repository,
@@ -119,9 +104,11 @@ public NetDaemonHost(
loggerFactory ??= DefaultLoggerFactory;
_httpHandler = httpHandler;
Logger = loggerFactory.CreateLogger();
- _hassClient = hassClient ?? throw new ArgumentNullException("HassClient can't be null!");
+ _hassClient = hassClient
+ ?? throw new ArgumentNullException(nameof(hassClient));
_scheduler = new Scheduler(loggerFactory: loggerFactory);
_repository = repository;
+ _isDisposed = false;
Logger.LogTrace("Instance NetDaemonHost");
}
@@ -131,23 +118,24 @@ public IHttpHandler Http
{
get
{
- _ = _httpHandler ?? throw new NullReferenceException("HttpHandler can not be null!");
+ _ = _httpHandler ?? throw new NetDaemonNullReferenceException("HttpHandler can not be null!");
return _httpHandler;
}
}
public ILogger Logger { get; }
- public IEnumerable RunningAppInstances => _runningAppInstances.Values;
+ public IEnumerable RunningAppInstances => InternalRunningAppInstances.Values;
- public IEnumerable AllAppInstances => _allAppInstances.Values;
+ public IEnumerable AllAppInstances => InternalAllAppInstances.Values;
public IScheduler Scheduler => _scheduler;
+ [SuppressMessage("", "CA1721")]
public IEnumerable State => InternalState.Select(n => n.Value);
// For testing
- internal ConcurrentDictionary InternalRunningAppInstances => _runningAppInstances;
+ internal ConcurrentDictionary InternalRunningAppInstances { get; } = new();
private static ILoggerFactory DefaultLoggerFactory => LoggerFactory.Create(builder =>
{
@@ -156,25 +144,27 @@ public IHttpHandler Http
.AddConsole();
});
+ public IDictionary DataCache { get; } = new Dictionary();
public async Task> GetAllServices()
{
- this._cancelToken.ThrowIfCancellationRequested();
+ _cancelToken.ThrowIfCancellationRequested();
- return await _hassClient.GetServices();
+ 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)) == false)
- throw new ApplicationException("Servicecall queue full!");
+ 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
{
@@ -189,21 +179,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);
@@ -219,22 +211,35 @@ public ICamera Cameras(INetDaemonApp app, Func func)
public async ValueTask DisposeAsync()
{
+ lock (_cancelDaemon)
+ {
+ if (_isDisposed)
+ return;
+ _isDisposed = true;
+ }
+
_cancelDaemon.Cancel();
await Stop().ConfigureAwait(false);
+ await _scheduler.DisposeAsync().ConfigureAwait(false);
+ _cancelDaemon.Dispose();
+ _cancelTokenSource?.Dispose();
+
Logger.LogTrace("Instance NetDaemonHost Disposed");
}
- public void EnableApplicationDiscoveryServiceAsync()
+ public void EnableApplicationDiscoveryService()
{
// For service call reload_apps we do just that... reload the fucking apps yay :)
- ListenCompanionServiceCall("reload_apps", async (_) => await ReloadAllApps());
+ ListenCompanionServiceCall("reload_apps", async (_) => await ReloadAllApps().ConfigureAwait(false));
RegisterAppSwitchesAndTheirStates();
}
public IEntity Entities(INetDaemonApp app, Func func)
{
- this._cancelToken.ThrowIfCancellationRequested();
+ _ = app ??
+ throw new NetDaemonArgumentNullException(nameof(app));
+ _cancelToken.ThrowIfCancellationRequested();
try
{
@@ -249,51 +254,52 @@ 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 _runningAppInstances.ContainsKey(appInstanceId) ?
- _runningAppInstances[appInstanceId] : null;
+ return InternalRunningAppInstances.ContainsKey(appInstanceId) ?
+ InternalRunningAppInstances[appInstanceId] : null;
}
- public async ValueTask GetDataAsync(string id) where T : class
+ public async Task GetDataAsync(string id) where T : class
{
- this._cancelToken.ThrowIfCancellationRequested();
-
- _ = _repository as IDataRepository ??
- throw new NullReferenceException($"{nameof(_repository)} can not be null!");
+ _cancelToken.ThrowIfCancellationRequested();
+ _ = _repository ??
+ throw new NetDaemonNullReferenceException($"{nameof(_repository)} can not be null!");
- if (_dataCache.ContainsKey(id))
+ if (DataCache.ContainsKey(id))
{
- return (T)_dataCache[id];
+ return (T)DataCache[id];
}
var data = await _repository!.Get(id).ConfigureAwait(false);
if (data != null)
- _dataCache[id] = data;
+ DataCache[id] = data;
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;
}
@@ -302,32 +308,32 @@ public IEntity Entity(INetDaemonApp app, params string[] entityIds)
public async Task Initialize(IInstanceDaemonApp appInstanceManager)
{
if (!Connected)
- throw new ApplicationException("NetDaemon is not connected, no use in initializing");
+ throw new NetDaemonException("NetDaemon is not connected, no use in initializing");
_appInstanceManager = appInstanceManager;
await LoadAllApps().ConfigureAwait(false);
- EnableApplicationDiscoveryServiceAsync();
+ EnableApplicationDiscoveryService();
}
///
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);
}
@@ -335,31 +341,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);
@@ -393,6 +409,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
@@ -405,7 +422,7 @@ public async Task Run(string host, short port, bool ssl, string token, Cancellat
if (_hassClient == null)
{
- throw new NullReferenceException("HassClient cant be null when running daemon, check constructor!");
+ throw new NetDaemonNullReferenceException("HassClient cant be null when running daemon, check constructor!");
}
try
@@ -434,14 +451,14 @@ public async Task Run(string host, short port, bool ssl, string token, Cancellat
if (hassConfig.State != "RUNNING")
{
Logger.LogInformation("Home Assistant is not ready yet, state: {state} ..", hassConfig.State);
- await _hassClient.CloseAsync();
+ await _hassClient.CloseAsync().ConfigureAwait(false);
return;
}
// Setup TTS
Task handleTextToSpeechMessagesTask = HandleTextToSpeechMessages(cancellationToken);
Task handleAsyncServiceCalls = HandleAsyncServiceCalls(cancellationToken);
- Task hanldeAsyncSetState = HandleAsyncSetState(cancellationToken);
+ Task handleAsyncSetState = HandleAsyncSetState(cancellationToken);
await RefreshInternalStatesAndSetArea().ConfigureAwait(false);
@@ -459,16 +476,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);
@@ -499,29 +513,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 ??
- throw new NullReferenceException($"{nameof(_repository)} can not be null!");
+ _ = _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;
+ 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;
@@ -539,11 +554,11 @@ 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",
- "Connected", // State will alawys be connected, otherwise state could not be set.
+ "Connected", // State will always be connected, otherwise state could not be set.
("number_of_loaded_apps", numberOfLoadedApps),
("number_of_running_apps", numberOfRunningApps),
("version", GetType().Assembly.GetName().Version?.ToString() ?? "N/A")).ConfigureAwait(false);
@@ -551,16 +566,16 @@ await SetStateAsync(
public void SetState(string entityId, dynamic state, dynamic? attributes = null)
{
- this._cancelToken.ThrowIfCancellationRequested();
+ _cancelToken.ThrowIfCancellationRequested();
- if (_setStateMessageChannel.Writer.TryWrite((entityId, state, attributes)) == false)
- throw new ApplicationException("Servicecall queue full!");
+ if (!_setStateMessageChannel.Writer.TryWrite((entityId, state, attributes)))
+ throw new NetDaemonException("Servicecall queue full!");
}
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
@@ -591,11 +606,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
@@ -606,9 +622,15 @@ public async Task Stop()
await _scheduler.Stop().ConfigureAwait(false);
-
InternalState.Clear();
-
+ InternalAllAppInstances.Clear();
+ InternalRunningAppInstances.Clear();
+ _hassAreas.Clear();
+ _hassDevices.Clear();
+ _hassEntities.Clear();
+ _daemonServiceCallFunctions.Clear();
+ _externalEventCallSubscribers.Clear();
+ _eventHandlerTasks.Clear();
Connected = false;
await _hassClient.CloseAsync().ConfigureAwait(false);
@@ -622,14 +644,23 @@ public async Task Stop()
}
///
+ [SuppressMessage("", "1031")]
public async Task UnloadAllApps()
{
- foreach (var app in _allAppInstances)
+ Logger.LogTrace("Unloading all apps ({instances}, {running})", InternalAllAppInstances.Count, InternalRunningAppInstances.Count);
+ foreach (var app in InternalAllAppInstances)
{
- await app.Value.DisposeAsync().ConfigureAwait(false);
+ try
+ {
+ await app.Value.DisposeAsync().ConfigureAwait(false);
+ }
+ catch (Exception e)
+ {
+ Logger.LogError(e, "Failed to unload apps, {app_id}", app.Value.Id);
+ }
}
- _allAppInstances.Clear();
- _runningAppInstances.Clear();
+ InternalAllAppInstances.Clear();
+ InternalRunningAppInstances.Clear();
}
///
@@ -638,7 +669,7 @@ public async Task UnloadAllApps()
///
/// The state data to be fixed
///
- /// If a sensor is unavailable that normally has a primtive value
+ /// If a sensor is unavailable that normally has a primitive value
/// it can be a string. The automations might expect a integer.
/// Another scenario is that a value of 10 is cast as long and
/// next time a value of 11.3 is cast to double. T
@@ -704,24 +735,15 @@ internal static bool FixStateTypes(HassStateChangedEventData stateData)
internal string? GetAreaForEntityId(string entityId)
{
- HassEntity? entity;
- if (_hassEntities.TryGetValue(entityId, out 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
- HassDevice? device;
- if (_hassDevices.TryGetValue(entity.DeviceId, out 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
- HassArea? area;
- if (_hassAreas.TryGetValue(device.AreaId, out area) && area is not null)
- {
- return area.Name;
- }
- }
+ return area.Name;
}
}
}
@@ -730,7 +752,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))
{
@@ -763,35 +785,38 @@ internal async Task RefreshInternalStatesAndSetArea()
}
}
- internal IList SortByDependency(IEnumerable unsortedList)
+ internal static IList SortByDependency(IEnumerable unsortedList)
{
- if (unsortedList.SelectMany(n => n.Dependencies).Count() > 0)
+ if (unsortedList.SelectMany(n => n.Dependencies).Any())
{
- // There are dependecies defined
+ // There are dependencies defined
var edges = new HashSet>();
foreach (var instance in unsortedList)
{
foreach (var dependency in instance.Dependencies)
{
- var dependentApp = unsortedList.Where(n => n.Id == dependency).FirstOrDefault();
+ var dependentApp = unsortedList.FirstOrDefault(n => n.Id == dependency);
if (dependentApp == null)
- throw new ApplicationException($"There is no app named {dependency}, please check dependencies or make sure you have not disabled the dependent app!");
+ throw new NetDaemonException($"There is no app named {dependency}, please check dependencies or make sure you have not disabled the dependent app!");
edges.Add(new Tuple(instance, dependentApp));
}
}
var sortedInstances = TopologicalSort(unsortedList.ToHashSet(), edges) ??
- throw new ApplicationException("Application dependencies is wrong, please check dependencies for circular dependencies!");
+ throw new NetDaemonException("Application dependencies is wrong, please check dependencies for circular dependencies!");
return sortedInstances;
}
return unsortedList.ToList();
}
+ [SuppressMessage("", "CA1031")]
protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationToken token)
{
_cancelToken.ThrowIfCancellationRequested();
+ _ = hassEvent ??
+ throw new NetDaemonArgumentNullException(nameof(hassEvent));
if (hassEvent.EventType == "state_changed")
{
@@ -801,7 +826,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
if (stateData is null)
{
- throw new NullReferenceException("StateData is null!");
+ throw new NetDaemonNullReferenceException("StateData is null!");
}
if (stateData.NewState is null || stateData.OldState is null)
@@ -812,16 +837,16 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
if (!FixStateTypes(stateData))
{
- if (stateData?.NewState?.State != stateData?.OldState?.State)
+ if (stateData.NewState?.State != stateData.OldState?.State)
{
var sb = new StringBuilder();
- sb.AppendLine($"Can not fix state typing for {stateData?.NewState?.EntityId}");
- sb.AppendLine($"NewStateObject: {stateData?.NewState}");
- sb.AppendLine($"OldStateObject: {stateData?.OldState}");
- sb.AppendLine($"NewState: {stateData?.NewState?.State}");
- sb.AppendLine($"OldState: {stateData?.OldState?.State}");
- sb.AppendLine($"NewState type: {stateData?.NewState?.State?.GetType().ToString() ?? "null"}");
- sb.AppendLine($"OldState type: {stateData?.OldState?.State?.GetType().ToString() ?? "null"}");
+ sb.Append("Can not fix state typing for ").AppendLine(stateData.NewState?.EntityId);
+ sb.Append("NewStateObject: ").Append(stateData.NewState).AppendLine();
+ sb.Append("OldStateObject: ").Append(stateData.OldState).AppendLine();
+ sb.Append("NewState: ").AppendLine(stateData.NewState?.State);
+ sb.Append("OldState: ").AppendLine(stateData.OldState?.State);
+ sb.Append("NewState type: ").AppendLine(stateData.NewState?.State?.GetType().ToString() ?? "null");
+ sb.Append("OldState type: ").AppendLine(stateData.OldState?.State?.GetType().ToString() ?? "null");
Logger.LogTrace(sb.ToString());
}
return;
@@ -835,7 +860,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
InternalState[stateData.EntityId] = newState;
var tasks = new List();
- foreach (var app in _runningAppInstances)
+ foreach (var app in InternalRunningAppInstances)
{
if (app.Value is NetDaemonApp netDaemonApp)
{
@@ -848,7 +873,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
oldState
));
}
- else if (stateData.EntityId.StartsWith(pattern))
+ else if (stateData.EntityId.StartsWith(pattern, StringComparison.InvariantCultureIgnoreCase))
{
tasks.Add(func(stateData.EntityId,
newState,
@@ -873,7 +898,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
observer.OnError(e);
netDaemonRxApp.LogError(e, $"Fail to OnNext on state change observer. {newState.EntityId}:{newState?.State}({oldState?.State})");
}
- }));
+ }, token));
}
}
}
@@ -882,7 +907,6 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
// Todo: Make it timeout! Maybe it should be handling in it's own task like scheduler
if (tasks.Count > 0)
{
-
await tasks.WhenAll(token).ConfigureAwait(false);
await tasks.WhenAll(token).ConfigureAwait(false);
@@ -901,10 +925,10 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
if (serviceCallData == null)
{
- throw new NullReferenceException("ServiceData is null! not expected");
+ throw new NetDaemonNullReferenceException("ServiceData is null! not expected");
}
var tasks = new List();
- foreach (var app in _runningAppInstances)
+ foreach (var app in InternalRunningAppInstances)
{
// Call any service call registered
if (app.Value is NetDaemonApp netDaemonApp)
@@ -952,7 +976,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
}
Logger.LogError(e, "Fail to OnNext on event observer (service_call)");
}
- }));
+ }, token));
}
}
}
@@ -999,7 +1023,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
hassEvent.Data = new FluentExpandoObject(true, true, exObject);
}
var tasks = new List();
- foreach (var app in _runningAppInstances)
+ foreach (var app in InternalRunningAppInstances)
{
if (app.Value is NetDaemonApp netDaemonApp)
{
@@ -1035,7 +1059,7 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
observer.OnError(e);
Logger.LogError(e, "Fail to OnNext on event observer (event)");
}
- }));
+ }, token));
}
}
}
@@ -1051,17 +1075,6 @@ protected virtual async Task HandleNewEvent(HassEvent hassEvent, CancellationTok
}
}
- private static string GetDomainFromEntity(string entity)
- {
- string[] entityParts = entity.Split('.');
- if (entityParts.Length != 2)
- {
- throw new ApplicationException($"entity_id is mal formatted {entity}");
- }
-
- return entityParts[0];
- }
-
///
/// Topological Sorting (Kahn's algorithm)
///
@@ -1076,10 +1089,10 @@ private static string GetDomainFromEntity(string entity)
var L = new List();
// Set of all nodes with no incoming edges
- var S = new HashSet(nodes.Where(n => edges.All(e => e.Item2.Equals(n) == false)));
+ var S = new HashSet(nodes.Where(n => edges.All(e => !e.Item2.Equals(n))));
// while S is non-empty do
- while (S.Any())
+ while (S.Count > 0)
{
// remove a node n from S
var n = S.First();
@@ -1097,7 +1110,7 @@ private static string GetDomainFromEntity(string entity)
edges.Remove(e);
// if m has no other incoming edges then
- if (edges.All(me => me.Item2.Equals(m) == false))
+ if (edges.All(me => !me.Item2.Equals(m)))
{
// insert m into S
S.Add(m);
@@ -1106,7 +1119,7 @@ private static string GetDomainFromEntity(string entity)
}
// if graph has edges then
- if (edges.Any())
+ if (edges.Count > 0)
{
// return error (graph has at least one cycle)
return null;
@@ -1118,10 +1131,10 @@ private static string GetDomainFromEntity(string entity)
return L;
}
}
-
+ [SuppressMessage("", "CA1031")]
private async Task HandleAsyncServiceCalls(CancellationToken cancellationToken)
{
- this._cancelToken.ThrowIfCancellationRequested();
+ _cancelToken.ThrowIfCancellationRequested();
bool hasLoggedError = false;
@@ -1133,7 +1146,7 @@ private async Task HandleAsyncServiceCalls(CancellationToken cancellationToken)
(string domain, string service, dynamic? data)
= await _serviceCallMessageChannel.Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
- await _hassClient.CallService(domain, service, data, false).ConfigureAwait(false); ;
+ await _hassClient.CallService(domain, service, data, false).ConfigureAwait(false);
hasLoggedError = false;
}
@@ -1143,17 +1156,18 @@ private async Task HandleAsyncServiceCalls(CancellationToken cancellationToken)
}
catch (Exception e)
{
- if (hasLoggedError == false)
+ if (!hasLoggedError)
Logger.LogDebug(e, "Failure sending call service");
hasLoggedError = true;
- await Task.Delay(100); // Do a delay to avoid loop
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false); // Do a delay to avoid loop
}
}
}
+ [SuppressMessage("", "CA1031")]
private async Task HandleAsyncSetState(CancellationToken cancellationToken)
{
- this._cancelToken.ThrowIfCancellationRequested();
+ _cancelToken.ThrowIfCancellationRequested();
bool hasLoggedError = false;
//_serviceCallMessageQueue
@@ -1174,17 +1188,18 @@ private async Task HandleAsyncSetState(CancellationToken cancellationToken)
}
catch (Exception e)
{
- if (hasLoggedError == false)
+ if (!hasLoggedError)
Logger.LogDebug(e, "Failure setting state");
hasLoggedError = true;
- await Task.Delay(100); // Do a delay to avoid loop
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false); // Do a delay to avoid loop
}
}
}
+ [SuppressMessage("", "CA1031")]
private async Task HandleTextToSpeechMessages(CancellationToken cancellationToken)
{
- this._cancelToken.ThrowIfCancellationRequested();
+ _cancelToken.ThrowIfCancellationRequested();
try
{
while (!cancellationToken.IsCancellationRequested)
@@ -1195,17 +1210,17 @@ private async Task HandleTextToSpeechMessages(CancellationToken cancellationToke
attributes.entity_id = entityId;
attributes.message = message;
await _hassClient.CallService("tts", "google_cloud_say", attributes, true).ConfigureAwait(false);
- await Task.Delay(InternalDelayTimeForTts).ConfigureAwait(false); // Wait 2 seconds to wait for status to complete
+ await Task.Delay(InternalDelayTimeForTts, cancellationToken).ConfigureAwait(false); // Wait 2 seconds to wait for status to complete
EntityState? currentPlayState = GetState(entityId);
- if (currentPlayState != null && currentPlayState.Attribute?.media_duration != null)
+ if (currentPlayState?.Attribute?.media_duration != null)
{
int delayInMilliSeconds = (int)Math.Round(currentPlayState?.Attribute?.media_duration * 1000) - InternalDelayTimeForTts;
if (delayInMilliSeconds > 0)
{
- await Task.Delay(delayInMilliSeconds).ConfigureAwait(false); // Wait remainder of text message
+ await Task.Delay(delayInMilliSeconds, cancellationToken).ConfigureAwait(false); // Wait remainder of text message
}
}
}
@@ -1223,29 +1238,31 @@ private async Task HandleTextToSpeechMessages(CancellationToken cancellationToke
///
private async Task LoadAllApps()
{
- _ = _appInstanceManager ?? throw new NullReferenceException(nameof(_appInstanceManager));
-
+ _ = _appInstanceManager ?? throw new NetDaemonNullReferenceException(nameof(_appInstanceManager));
+ Logger.LogTrace("Loading all apps ({instances}, {running})", InternalAllAppInstances.Count, InternalRunningAppInstances.Count);
// First unload any apps running
await UnloadAllApps().ConfigureAwait(false);
// Get all instances
var instancedApps = _appInstanceManager.InstanceDaemonApps();
- if (_runningAppInstances.Count() > 0)
- throw new ApplicationException("Did not expect running instances!");
+ if (!InternalRunningAppInstances.IsEmpty)
+ {
+ Logger.LogWarning("Old apps not unloaded correctly. {nr} apps still loaded.", InternalRunningAppInstances.Count);
+ InternalRunningAppInstances.Clear();
+ }
foreach (INetDaemonAppBase appInstance in instancedApps!)
{
- _allAppInstances[appInstance.Id!] = appInstance;
+ InternalAllAppInstances[appInstance.Id!] = appInstance;
if (await RestoreAppState(appInstance).ConfigureAwait(false))
{
- _runningAppInstances[appInstance.Id!] = appInstance;
+ InternalRunningAppInstances[appInstance.Id!] = appInstance;
}
-
}
// Now run initialize on all sorted by dependencies
- foreach (var sortedApp in SortByDependency(_runningAppInstances.Values))
+ foreach (var sortedApp in SortByDependency(InternalRunningAppInstances.Values))
{
// Init by calling the InitializeAsync
var taskInitAsync = sortedApp.InitializeAsync();
@@ -1260,24 +1277,19 @@ private async Task LoadAllApps()
Logger.LogWarning("Initialize of application {app} took longer that 5 seconds, make sure Initialize function is not blocking!", sortedApp.Id);
// Todo: refactor
- await sortedApp.HandleAttributeInitialization(this);
+ await sortedApp.HandleAttributeInitialization(this).ConfigureAwait(false);
Logger.LogInformation("Successfully loaded app {appId} ({class})", sortedApp.Id, sortedApp.GetType().Name);
}
- await SetDaemonStateAsync(_appInstanceManager.Count, _runningAppInstances.Count).ConfigureAwait(false);
+ await SetDaemonStateAsync(_appInstanceManager.Count, InternalRunningAppInstances.Count).ConfigureAwait(false);
}
+ [SuppressMessage("", "CA1031")]
private void RegisterAppSwitchesAndTheirStates()
{
- ListenServiceCall("switch", "turn_on", async (data) =>
- {
- await SetStateOnDaemonAppSwitch("on", data).ConfigureAwait(false);
- });
+ ListenServiceCall("switch", "turn_on", async (data) => await SetStateOnDaemonAppSwitch("on", data).ConfigureAwait(false));
- ListenServiceCall("switch", "turn_off", async (data) =>
- {
- await SetStateOnDaemonAppSwitch("off", data).ConfigureAwait(false);
- });
+ ListenServiceCall("switch", "turn_off", async (data) => await SetStateOnDaemonAppSwitch("off", data).ConfigureAwait(false));
ListenServiceCall("switch", "toggle", async (data) =>
{
@@ -1294,7 +1306,7 @@ private void RegisterAppSwitchesAndTheirStates()
else
await SetStateOnDaemonAppSwitch("on", data).ConfigureAwait(false);
}
- catch (System.Exception e)
+ catch (Exception e)
{
Logger.LogWarning(e, "Failed to set state from netdaemon switch");
}
@@ -1306,33 +1318,31 @@ 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);
- await ReloadAllApps();
+ await SetDependentState(entityId, state).ConfigureAwait(false);
+ await ReloadAllApps().ConfigureAwait(false);
- await PostExternalEvent(new AppsInformationEvent());
+ await PostExternalEvent(new AppsInformationEvent()).ConfigureAwait(false);
}
}
private async Task SetDependentState(string entityId, string state)
{
- var app = _allAppInstances.Values.Where(n => n.EntityId == entityId).FirstOrDefault();
+ var app = InternalAllAppInstances.Values.FirstOrDefault(n => n.EntityId == entityId);
if (app is not null)
{
if (state == "off")
{
// We need to turn off any dependent apps
- var depApps = _allAppInstances.Values.Where(n => n.Dependencies.Contains(app.Id));
-
- foreach (var depApp in depApps)
+ foreach (var depApp in InternalAllAppInstances.Values.Where(n => n.Dependencies.Contains(app.Id)))
{
await SetDependentState(depApp.EntityId, state).ConfigureAwait(false);
}
app.IsEnabled = false;
- await PersistAppStateAsync((NetDaemonAppBase)app);
+ await PersistAppStateAsync((NetDaemonAppBase)app).ConfigureAwait(false);
Logger.LogDebug("SET APP {app} state = disabled", app.Id);
}
else if (state == "on")
@@ -1341,14 +1351,13 @@ private async Task SetDependentState(string entityId, string state)
// Enable all apps that this app is dependent on
foreach (var depOnId in app.Dependencies)
{
- var depOnApp = _allAppInstances.Values.Where(n => n.Id == depOnId).FirstOrDefault();
+ var depOnApp = InternalAllAppInstances.Values.FirstOrDefault(n => n.Id == depOnId);
if (depOnApp is not null)
{
await SetDependentState(depOnApp.EntityId, state).ConfigureAwait(false);
-
}
}
- await PersistAppStateAsync((NetDaemonAppBase)app);
+ await PersistAppStateAsync((NetDaemonAppBase)app).ConfigureAwait(false);
Logger.LogDebug("SET APP {app} state = enabled", app.Id);
}
}
@@ -1364,20 +1373,19 @@ private async Task SetDependentState(string entityId, string state)
await SetStateAsync(entityId, state, attributes.ToArray()).ConfigureAwait(false);
else
await SetStateAsync(entityId, state).ConfigureAwait(false);
-
}
//TODO: Refactor this
private async Task PersistAppStateAsync(NetDaemonAppBase app)
{
-
var obj = await GetDataAsync>(app.GetUniqueIdForStorage()).ConfigureAwait(false) ??
new Dictionary();
obj["__IsDisabled"] = !app.IsEnabled;
- await SaveDataAsync>(app.GetUniqueIdForStorage(), obj);
+ await SaveDataAsync(app.GetUniqueIdForStorage(), obj).ConfigureAwait(false);
}
+ [SuppressMessage("", "CA1031")]
private async Task RestoreAppState(INetDaemonAppBase appInstance)
{
try
@@ -1412,7 +1420,7 @@ public void SubscribeToExternalEvents(Func