using Dapper; using Sonex.Data.Database; using System.Text; namespace Sonex.Data.Records; public sealed class ServiceRecord { private const string SelectColumns = """ SELECT caption, description, name, status, started, start_mode, accept_pause, accept_stop, display_name, error_control, exit_code, path_name, service_specific_exit_code, start_name, state, delayed_auto_start, process_id FROM sonex.services_get() """; public string? Caption { get; set; } public string? Description { get; set; } public string Name { get; set; } = string.Empty; public string? Status { get; set; } public bool? Started { get; set; } public string? StartMode { get; set; } public bool? AcceptPause { get; set; } public bool? AcceptStop { get; set; } public string? DisplayName { get; set; } public string? ErrorControl { get; set; } public int? ExitCode { get; set; } public string? PathName { get; set; } public int? ServiceSpecificExitCode { get; set; } public string? StartName { get; set; } public string? State { get; set; } public bool? DelayedAutoStart { get; set; } public int? ProcessId { get; set; } public static Task> Get( string name, CancellationToken ct = default) { return DB.QuerySingleAsync( $""" {SelectColumns} WHERE name = @name LIMIT 1; """, new { name }, ct: ct); } public static Task> GetByServiceName( string serviceName, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(serviceName)) { return Task.FromResult(new DB.SingleResult { Success = false, ErrorMessage = "Service name cannot be empty." }); } return DB.QuerySingleAsync( """ SELECT caption, description, name, status, started, start_mode, accept_pause, accept_stop, display_name, error_control, exit_code, path_name, service_specific_exit_code, start_name, state, delayed_auto_start, process_id FROM sonex.get_service_by_name(@serviceName) LIMIT 1; """, new { serviceName = serviceName.Trim() }, ct: ct); } public static Task> GetAll( int limit, CancellationToken ct = default) { return GetAll(limit, null, null, ct); } public static Task> GetAll( int limit, string? whereSql, IReadOnlyDictionary? whereParameters, CancellationToken ct = default) { var sql = new StringBuilder(); sql.AppendLine(SelectColumns); if (!string.IsNullOrWhiteSpace(whereSql)) { sql.AppendLine(); sql.Append("WHERE "); sql.AppendLine(whereSql.Trim()); } sql.AppendLine( """ ORDER BY display_name ASC NULLS LAST, name ASC NULLS LAST LIMIT @limit; """); var parameters = new DynamicParameters(); parameters.Add("limit", limit); if (whereParameters != null) { foreach (var item in whereParameters) { parameters.Add(item.Key, item.Value); } } return DB.QueryListAsync( sql.ToString(), parameters, ct: ct); } public static Task> StartByServiceName( string serviceName, CancellationToken ct = default) { return ExecuteServiceAction("sonex.start_service_by_name", serviceName, ct); } public static Task> ContinueByServiceName( string serviceName, CancellationToken ct = default) { return ExecuteServiceAction("sonex.continue_service_by_name", serviceName, ct); } public static Task> PauseByServiceName( string serviceName, CancellationToken ct = default) { return ExecuteServiceAction("sonex.pause_service_by_name", serviceName, ct); } public static Task> StopByServiceName( string serviceName, CancellationToken ct = default) { return ExecuteServiceAction("sonex.stop_service_by_name", serviceName, ct); } public static Task> SetStartWithSystemByServiceName( string serviceName, bool enabled, CancellationToken ct = default) { string startMode = enabled ? "auto" : "demand"; return ExecuteServiceStartModeAction( "sonex.set_service_start_mode_by_name", serviceName, startMode, ct); } private static async Task> ExecuteServiceAction( string functionName, string serviceName, CancellationToken ct) { if (string.IsNullOrWhiteSpace(serviceName)) { return new DB.SingleResult { Success = false, Item = false, ErrorMessage = "Service name cannot be empty." }; } var actionResult = await DB.QuerySingleAsync( $""" SELECT success, output FROM {functionName}(@serviceName) LIMIT 1; """, new { serviceName = serviceName.Trim() }, ct: ct).ConfigureAwait(false); if (!actionResult.Success) { return new DB.SingleResult { Success = false, Item = false, ErrorMessage = actionResult.ErrorMessage, ErrorType = actionResult.ErrorType, ErrorStackTrace = actionResult.ErrorStackTrace, ErrorData = actionResult.ErrorData }; } ServiceActionResult? item = actionResult.Item; if (item is null) { return new DB.SingleResult { Success = false, Item = false, ErrorMessage = "Service control function returned no result." }; } return new DB.SingleResult { Success = item.Success, Item = item.Success, ErrorMessage = item.Success ? null : item.Output }; } private static async Task> ExecuteServiceStartModeAction( string functionName, string serviceName, string startMode, CancellationToken ct) { if (string.IsNullOrWhiteSpace(serviceName)) { return new DB.SingleResult { Success = false, Item = false, ErrorMessage = "Service name cannot be empty." }; } if (string.IsNullOrWhiteSpace(startMode)) { return new DB.SingleResult { Success = false, Item = false, ErrorMessage = "Start mode cannot be empty." }; } var actionResult = await DB.QuerySingleAsync( $""" SELECT success, output FROM {functionName}(@serviceName, @startMode) LIMIT 1; """, new { serviceName = serviceName.Trim(), startMode = startMode.Trim().ToLowerInvariant() }, ct: ct).ConfigureAwait(false); if (!actionResult.Success) { return new DB.SingleResult { Success = false, Item = false, ErrorMessage = actionResult.ErrorMessage, ErrorType = actionResult.ErrorType, ErrorStackTrace = actionResult.ErrorStackTrace, ErrorData = actionResult.ErrorData }; } ServiceActionResult? item = actionResult.Item; if (item is null) { return new DB.SingleResult { Success = false, Item = false, ErrorMessage = "Service start mode function returned no result." }; } return new DB.SingleResult { Success = item.Success, Item = item.Success, ErrorMessage = item.Success ? null : item.Output }; } private sealed class ServiceActionResult { public bool Success { get; set; } public string Output { get; set; } = string.Empty; } }