Initial commit
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
using RobotNet10.CustomConfiguration.Models;
|
||||
using RobotNet10.CustomConfiguration.Services;
|
||||
|
||||
namespace RobotNet10.FleetManager.Services.ConfigManager;
|
||||
|
||||
/// <summary>
|
||||
/// Service implementation for managing ACSTraffic configurations
|
||||
/// </summary>
|
||||
public class ACSTrafficConfig : IACSTrafficConfig
|
||||
{
|
||||
private readonly IConfigManager _configManager;
|
||||
private readonly Logger<ACSTrafficConfig> _logger;
|
||||
|
||||
private readonly Lock _lockObject = new();
|
||||
private bool _configLoaded = false;
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when ACSTraffic configuration is changed/reloaded
|
||||
/// </summary>
|
||||
public event EventHandler? ConfigChanged;
|
||||
|
||||
private bool _trafficEnable = false;
|
||||
private int _trafficInterval = 1000;
|
||||
private string _trafficURL = string.Empty;
|
||||
private Dictionary<string, string> _acsZoneMaping = [];
|
||||
private Dictionary<string, string> _acsOutMaping = [];
|
||||
private bool _publishEnable = false;
|
||||
private string _publishURL = string.Empty;
|
||||
private int _publishInterval = 1000;
|
||||
|
||||
private const string ACS_TRAFFIC_CONFIG_TYPE = "ACSTrafficConfig";
|
||||
|
||||
public ACSTrafficConfig(IConfigManager configManager, Logger<ACSTrafficConfig> logger)
|
||||
{
|
||||
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
|
||||
_logger = logger;
|
||||
|
||||
// Subscribe to config changes
|
||||
_configManager.ConfigChanged += OnConfigChanged;
|
||||
}
|
||||
|
||||
public bool TrafficEnable
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigLoaded();
|
||||
return _trafficEnable;
|
||||
}
|
||||
}
|
||||
|
||||
public int TrafficInterval
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigLoaded();
|
||||
return _trafficInterval;
|
||||
}
|
||||
}
|
||||
|
||||
public string TrafficURL
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigLoaded();
|
||||
return _trafficURL;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, string> ACSZoneMaping
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigLoaded();
|
||||
return _acsZoneMaping;
|
||||
}
|
||||
}
|
||||
|
||||
public bool PublishEnable
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigLoaded();
|
||||
return _publishEnable;
|
||||
}
|
||||
}
|
||||
|
||||
public string PublishURL
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigLoaded();
|
||||
return _publishURL;
|
||||
}
|
||||
}
|
||||
|
||||
public int PublishInterval
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigLoaded();
|
||||
return _publishInterval;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, string> ACSOutMaping
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigLoaded();
|
||||
return _acsOutMaping;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureConfigLoaded()
|
||||
{
|
||||
if (!_configLoaded)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (!_configLoaded)
|
||||
{
|
||||
LoadACSTrafficConfigAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadACSTrafficConfigAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var configFile = await _configManager.GetConfigByTypeAsync(ACS_TRAFFIC_CONFIG_TYPE);
|
||||
|
||||
if (configFile == null)
|
||||
{
|
||||
_logger.Warning("ACSTraffic configuration not found, using defaults");
|
||||
_trafficEnable = false;
|
||||
_trafficInterval = 1000;
|
||||
_trafficURL = string.Empty;
|
||||
_acsZoneMaping = [];
|
||||
_acsOutMaping = [];
|
||||
_publishEnable = false;
|
||||
_publishURL = string.Empty;
|
||||
_publishInterval = 1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
MapConfigVariablesToProperties(configFile.Variables);
|
||||
_logger.Info("ACSTraffic configuration loaded successfully");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Error loading ACSTraffic configuration, using defaults: {ex.Message}");
|
||||
_trafficEnable = false;
|
||||
_trafficInterval = 1000;
|
||||
_trafficURL = string.Empty;
|
||||
_acsZoneMaping = [];
|
||||
_acsOutMaping = [];
|
||||
_publishEnable = false;
|
||||
_publishURL = string.Empty;
|
||||
_publishInterval = 1000;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_configLoaded = true;
|
||||
// Trigger ConfigChanged event after config is loaded/reloaded
|
||||
OnConfigChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConfigChanged(object? sender, RobotNet10.CustomConfiguration.Events.ConfigChangedEventArgs e)
|
||||
{
|
||||
// Reload config if our config type changed
|
||||
if (e.ConfigType == ACS_TRAFFIC_CONFIG_TYPE)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
_configLoaded = false;
|
||||
}
|
||||
_logger.Info($"ACSTraffic configuration changed ({e.ConfigType}), will reload on next access");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger ConfigChanged event to notify subscribers that config has been reloaded
|
||||
/// </summary>
|
||||
private void OnConfigChanged()
|
||||
{
|
||||
ConfigChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void MapConfigVariablesToProperties(List<ConfigVariable> variables)
|
||||
{
|
||||
foreach (var variable in variables)
|
||||
{
|
||||
if (variable.Value == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
switch (variable.Name)
|
||||
{
|
||||
case nameof(TrafficEnable):
|
||||
_trafficEnable = (bool)ConvertValue(variable.Value, typeof(bool))!;
|
||||
break;
|
||||
case nameof(TrafficInterval):
|
||||
_trafficInterval = (int)ConvertValue(variable.Value, typeof(int))!;
|
||||
break;
|
||||
case nameof(TrafficURL):
|
||||
_trafficURL = (string)ConvertValue(variable.Value, typeof(string))!;
|
||||
break;
|
||||
case nameof(ACSZoneMaping):
|
||||
_acsZoneMaping = (Dictionary<string, string>)ConvertValue(variable.Value, typeof(Dictionary<string, string>))!;
|
||||
break;
|
||||
case nameof(ACSOutMaping):
|
||||
_acsOutMaping = (Dictionary<string, string>)ConvertValue(variable.Value, typeof(Dictionary<string, string>))!;
|
||||
break;
|
||||
case nameof(PublishEnable):
|
||||
_publishEnable = (bool)ConvertValue(variable.Value, typeof(bool))!;
|
||||
break;
|
||||
case nameof(PublishURL):
|
||||
_publishURL = (string)ConvertValue(variable.Value, typeof(string))!;
|
||||
break;
|
||||
case nameof(PublishInterval):
|
||||
_publishInterval = (int)ConvertValue(variable.Value, typeof(int))!;
|
||||
break;
|
||||
default:
|
||||
_logger.Warning($"Unknown variable name: {variable.Name}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"Failed to set property {variable.Name} from variable: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object? ConvertValue(object? value, Type targetType)
|
||||
{
|
||||
if (value == null)
|
||||
return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;
|
||||
|
||||
// If value is already of the correct type, return it
|
||||
if (targetType.IsInstanceOfType(value))
|
||||
return value;
|
||||
|
||||
try
|
||||
{
|
||||
// Handle nullable types
|
||||
var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;
|
||||
|
||||
// Convert based on target type
|
||||
if (underlyingType == typeof(string))
|
||||
{
|
||||
return value.ToString() ?? string.Empty;
|
||||
}
|
||||
else if (underlyingType == typeof(int))
|
||||
{
|
||||
if (value is int i) return i;
|
||||
if (int.TryParse(value.ToString(), out var parsed)) return parsed;
|
||||
if (value is double d) return (int)d;
|
||||
throw new InvalidCastException($"Cannot convert {value.GetType()} to int");
|
||||
}
|
||||
else if (underlyingType == typeof(double))
|
||||
{
|
||||
if (value is double d) return d;
|
||||
if (double.TryParse(value.ToString(), out var parsed)) return parsed;
|
||||
if (value is int i) return i;
|
||||
throw new InvalidCastException($"Cannot convert {value.GetType()} to double");
|
||||
}
|
||||
else if (underlyingType == typeof(bool))
|
||||
{
|
||||
if (value is bool b) return b;
|
||||
if (bool.TryParse(value.ToString(), out var parsed)) return parsed;
|
||||
// Handle numeric values: 0/1, "0"/"1", etc.
|
||||
if (value.ToString()?.Trim() == "0" || value.ToString()?.Trim().ToLower() == "false") return false;
|
||||
if (value.ToString()?.Trim() == "1" || value.ToString()?.Trim().ToLower() == "true") return true;
|
||||
throw new InvalidCastException($"Cannot convert {value.GetType()} to bool");
|
||||
}
|
||||
else if (underlyingType.IsEnum)
|
||||
{
|
||||
var enumValue = Enum.Parse(underlyingType, value.ToString() ?? "", true);
|
||||
return enumValue;
|
||||
}
|
||||
else if (underlyingType.IsGenericType && underlyingType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
{
|
||||
// Handle Dictionary types - try to parse from JSON string
|
||||
return ConvertDictionary(value, underlyingType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try standard conversion
|
||||
return Convert.ChangeType(value, underlyingType);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"Error converting value {value} of type {value.GetType()} to {targetType}: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert value to Dictionary type (supports Dictionary<string, string>)
|
||||
/// </summary>
|
||||
private object ConvertDictionary(object value, Type dictionaryType)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get key and value types
|
||||
var genericArgs = dictionaryType.GetGenericArguments();
|
||||
var keyType = genericArgs[0];
|
||||
var valueType = genericArgs[1];
|
||||
|
||||
// If value is already the correct Dictionary type, return it
|
||||
if (dictionaryType.IsInstanceOfType(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
// If value is Dictionary<string, object>, try to convert
|
||||
if (value is Dictionary<string, object> stringDict)
|
||||
{
|
||||
var result = Activator.CreateInstance(dictionaryType);
|
||||
var addMethod = dictionaryType.GetMethod("Add");
|
||||
|
||||
if (addMethod != null && result != null)
|
||||
{
|
||||
foreach (var kvp in stringDict)
|
||||
{
|
||||
var key = ConvertValue(kvp.Key, keyType);
|
||||
var val = ConvertValue(kvp.Value, valueType);
|
||||
addMethod.Invoke(result, [key, val]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Try parse as JSON string
|
||||
var jsonString = value.ToString();
|
||||
if (!string.IsNullOrEmpty(jsonString))
|
||||
{
|
||||
try
|
||||
{
|
||||
var doc = System.Text.Json.JsonDocument.Parse(jsonString);
|
||||
if (doc.RootElement.ValueKind == System.Text.Json.JsonValueKind.Object)
|
||||
{
|
||||
var result = Activator.CreateInstance(dictionaryType);
|
||||
var addMethod = dictionaryType.GetMethod("Add");
|
||||
|
||||
if (addMethod != null && result != null)
|
||||
{
|
||||
foreach (var prop in doc.RootElement.EnumerateObject())
|
||||
{
|
||||
// Convert key
|
||||
var key = ConvertValue(prop.Name, keyType);
|
||||
|
||||
// Convert value
|
||||
var val = ConvertValue(prop.Value.GetString() ?? prop.Value.GetRawText(), valueType);
|
||||
|
||||
addMethod.Invoke(result, [key, val]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"Failed to parse Dictionary from JSON string: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// If all else fails, return default (empty dictionary)
|
||||
_logger.Warning($"Cannot convert {value.GetType()} to {dictionaryType}, using default (empty dictionary)");
|
||||
return Activator.CreateInstance(dictionaryType) ?? throw new InvalidOperationException($"Cannot create instance of {dictionaryType}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"Error converting Dictionary: {ex.Message}");
|
||||
// Return default (empty dictionary)
|
||||
return Activator.CreateInstance(dictionaryType) ?? throw new InvalidOperationException($"Cannot create instance of {dictionaryType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user