using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Text.Json; using System.Threading.Tasks; using CommandLine; using RedisManager.Commands; namespace RedisManager { /// /// TCP server that provides Redis command execution as a service. /// Accepts client connections and executes Redis commands on their behalf. /// Clients send JSON-formatted requests and receive JSON responses. /// public class RedisManagerService { private TcpListener _listener; private readonly ConcurrentDictionary _clients = new(); private Config _config; private readonly object _configLock = new(); // Track Valkey/Redis server processes started by the daemon private readonly ConcurrentDictionary _instanceProcesses = new(); private bool _isRunning = false; private readonly int _port = 6380; // Port for service communication /// /// Initializes a new instance of the RedisManagerService. /// Loads configuration and prepares the service for operation. /// public RedisManagerService() { _config = ConfigManager.LoadConfig(); } /// /// Starts the TCP server and begins accepting client connections. /// The server runs indefinitely until StopAsync is called. /// /// A task that represents the asynchronous start operation public Task StartAsync() { if (_isRunning) return Task.CompletedTask; try { _listener = new TcpListener(IPAddress.Any, _port); _listener.Start(); _isRunning = true; } catch (SocketException ex) { Console.WriteLine($"Error starting service on port {_port}: {ex.Message}"); Console.WriteLine("Another instance of the service may be running, or the port is in use."); _isRunning = false; return Task.CompletedTask; // Exit if we can't start the listener } Console.WriteLine($"RedisManager Service started on port {_port}"); // Start Valkey/Redis servers for all configured instances at startup Task.Run(async () => { bool anySuccess = false; List instancesSnapshot; lock (_configLock) { instancesSnapshot = new List(_config.Instances); } foreach (var instance in instancesSnapshot) { var result = await StartValkeyServerIfNeeded(instance); if (result.Success) { Console.WriteLine($"[Daemon] Started Valkey/Redis for instance '{instance.Name}' on port {instance.Port}."); anySuccess = true; } else { Console.WriteLine($"[Daemon] Failed to start Valkey/Redis for instance '{instance.Name}' on port {instance.Port}: {result.Output}"); } } if (!anySuccess) { Console.WriteLine("[Daemon] FATAL: Could not start any Valkey/Redis server. Check your ServerBinaryPath configuration and ensure the binary exists and is executable."); Environment.Exit(1); } }); // Run the client accepting loop in the background _ = AcceptClientsAsync(); return Task.CompletedTask; } private async Task AcceptClientsAsync() { while (_isRunning) { try { var client = await _listener.AcceptTcpClientAsync(); _ = Task.Run(() => HandleClientAsync(client)); } catch (Exception ex) when (!_isRunning) { // Expected when stopping break; } catch (Exception ex) { Console.WriteLine($"[Daemon] Error accepting client: {ex}"); // Continue loop; do not exit } } } /// /// Stops the TCP server and closes all client connections. /// /// A task that represents the asynchronous stop operation public async Task StopAsync() { if (!_isRunning) return; _isRunning = false; _listener?.Stop(); Console.WriteLine("RedisManager Service stopped"); } /// /// Handles a single client connection asynchronously. /// Processes JSON-formatted command requests and sends back responses. /// /// The TCP client connection to handle /// A task that represents the asynchronous client handling operation private async Task HandleClientAsync(TcpClient client) { var clientId = Guid.NewGuid().ToString(); _clients[clientId] = DateTime.UtcNow; Console.WriteLine($"[Daemon] Client connected: {clientId}"); try { using var stream = client.GetStream(); using var reader = new StreamReader(stream, new UTF8Encoding(false)); using var writer = new StreamWriter(stream, new UTF8Encoding(false)) { AutoFlush = true }; while (client.Connected) { try { var line = await reader.ReadLineAsync(); if (line == null) break; Console.WriteLine($"[Daemon] Received command: {line}"); var response = await ExecuteCommandAsync(line); var responseJson = JsonSerializer.Serialize(response, new JsonSerializerOptions { WriteIndented = false }); await writer.WriteLineAsync(responseJson); } catch (Exception ex) { Console.WriteLine($"[Daemon] Error processing client command: {ex}"); } } } catch (Exception ex) { Console.WriteLine($"[Daemon] Error handling client: {ex}"); } finally { _clients.TryRemove(clientId, out _); Console.WriteLine($"[Daemon] Client disconnected: {clientId}"); client.Close(); } } /// /// Executes a Redis command from a JSON-formatted request. /// /// JSON string containing the command request /// A ServiceResponse object with the command execution results private async Task ExecuteCommandAsync(string commandJson) { try { var request = JsonSerializer.Deserialize(commandJson); if (request == null) { return new ServiceResponse { Success = false, Data = null, Error = "Invalid request format", ErrorCode = "INVALID_REQUEST", ErrorDetails = new { RawInput = commandJson } }; } // Special handling for reload-config if (request.Arguments != null && request.Arguments.Length > 0 && string.Equals(request.Arguments[0], "reload-config", StringComparison.OrdinalIgnoreCase)) { if (request.Arguments.Length > 1) { return new ServiceResponse { Success = false, Data = null, Error = "'reload-config' takes no arguments", ErrorCode = "ARGUMENT_ERROR", ErrorDetails = new { Command = "reload-config", Args = request.Arguments } }; } try { var newConfig = ConfigManager.LoadConfig(); lock (_configLock) { _config = newConfig; } Console.WriteLine($"[Daemon] Config reloaded from disk at {DateTime.Now:O}."); return new ServiceResponse { Success = true, Data = "Configuration reloaded successfully.", Error = null, ErrorCode = null, ErrorDetails = null }; } catch (Exception ex) { Console.WriteLine($"[Daemon] ERROR: Failed to reload config: {ex.Message}"); return new ServiceResponse { Success = false, Data = null, Error = $"Failed to reload configuration: {ex.Message}", ErrorCode = "CONFIG_RELOAD_FAILED", ErrorDetails = new { ExceptionType = ex.GetType().Name, ExceptionMessage = ex.Message } }; } } var (success, output) = await ExecuteRedisCommandAsync(request.Arguments); if (!success) { return new ServiceResponse { Success = false, Data = null, Error = output, ErrorCode = "COMMAND_EXECUTION_FAILED", ErrorDetails = new { Arguments = request.Arguments } }; } return new ServiceResponse { Success = true, Data = output, Error = null, ErrorCode = null, ErrorDetails = null }; } catch (Exception ex) { return new ServiceResponse { Success = false, Data = null, Error = ex.Message, ErrorCode = "EXCEPTION", ErrorDetails = new { ExceptionType = ex.GetType().Name, ExceptionMessage = ex.Message } }; } } // Helper: Check if a TCP port is open private bool IsPortOpen(string host, int port) { try { using var client = new TcpClient(); var result = client.BeginConnect(host, port, null, null); bool success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(300)); return success && client.Connected; } catch { return false; } } // Helper: Check if a Redis/Valkey server is running on host:port private bool IsRedisServer(string host, int port) { try { using var client = new TcpClient(host, port); using var stream = client.GetStream(); var ping = Encoding.ASCII.GetBytes("*1\r\n$4\r\nPING\r\n"); stream.Write(ping, 0, ping.Length); stream.Flush(); var buffer = new byte[64]; int read = stream.Read(buffer, 0, buffer.Length); var response = Encoding.ASCII.GetString(buffer, 0, read); return response.Contains("PONG"); } catch { return false; } } // Helper: Start Valkey/Redis server for an instance if not running private async Task<(bool Success, string Output)> StartValkeyServerIfNeeded(InstanceConfig instance) { // Already started by us? if (_instanceProcesses.TryGetValue(instance.Name, out var existingProc) && !existingProc.HasExited) return (true, ""); // Is port open? if (IsPortOpen(instance.Host, instance.Port)) { if (IsRedisServer(instance.Host, instance.Port)) return (true, ""); else return (false, $"Port {instance.Port} is in use but is not a Redis/Valkey server."); } // Determine binary path string binaryPath = instance.ServerBinaryPath ?? _config.ServerBinaryPath; if (string.IsNullOrWhiteSpace(binaryPath) || !File.Exists(binaryPath)) return (false, $"Redis/Valkey server binary not found at '{binaryPath}'. Please set 'ServerBinaryPath' in redismanager.json to the correct path for your Valkey or Redis server binary."); // Start the server var psi = new System.Diagnostics.ProcessStartInfo { FileName = binaryPath, Arguments = $"--port {instance.Port}", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; try { var proc = System.Diagnostics.Process.Start(psi); if (proc == null) return (false, $"Failed to start Valkey/Redis server for instance '{instance.Name}'."); _instanceProcesses[instance.Name] = proc; // Wait briefly for the server to start await Task.Delay(500); if (!IsPortOpen(instance.Host, instance.Port)) return (false, $"Valkey/Redis server did not start on port {instance.Port}."); // Apply custom config options after server is confirmed up await CustomConfigApplier.ApplyCustomConfigAsync(instance); return (true, $"Started Valkey/Redis server for instance '{instance.Name}'."); } catch (Exception ex) { return (false, $"Exception starting Valkey/Redis server: {ex.Message}"); } } /// /// Executes Redis commands by parsing arguments and routing to appropriate command handlers. /// Captures console output and returns it as a string. /// /// Command line arguments to execute /// The captured console output from command execution private async Task<(bool Success, string Output, string ErrorCode, object ErrorDetails)> ValidateArguments(string[] args) { // Special validation for reload-config if (args.Length > 0 && string.Equals(args[0], "reload-config", StringComparison.OrdinalIgnoreCase)) { if (args.Length == 1) return (true, null, null, null); else return (false, "'reload-config' takes no arguments", "ARGUMENT_ERROR", new { Command = "reload-config", Args = args }); } if (args == null || args.Length == 0) { return (false, "No command arguments provided.", "ARGUMENT_ERROR", new { Args = args }); } // Instance flag validation string instanceName = null; var filteredArgs = new List(); for (int i = 0; i < args.Length; i++) { if ((args[i] == "-i" || args[i] == "--instance") && i + 1 < args.Length && !string.IsNullOrWhiteSpace(args[i + 1])) { instanceName = args[i + 1]; i++; // Skip instance name } else { filteredArgs.Add(args[i]); } } // Whitelist of commands that do NOT require instance var noInstanceRequired = new HashSet(StringComparer.OrdinalIgnoreCase) { "list-instances", "add-instance", "update-instance", "delete-instance", "reload-config", "help", "version" }; string commandName = filteredArgs.Count > 0 ? filteredArgs[0].ToLowerInvariant() : null; bool needsInstance = !noInstanceRequired.Contains(commandName); if (needsInstance && string.IsNullOrEmpty(instanceName)) { return (false, "Instance name must be specified with --instance or -i.", "INSTANCE_REQUIRED", new { Command = commandName, Args = args }); } if (!string.IsNullOrEmpty(instanceName)) { var instanceConfig = _config.Instances.Find(x => x.Name == instanceName); if (instanceConfig == null) { return (false, $"Instance '{instanceName}' not found in config.", "ARGUMENT_ERROR", new { Instance = instanceName }); } } // Command-specific validation if (filteredArgs.Count > 0) { string cmd = filteredArgs[0].ToLowerInvariant(); switch (cmd) { case "get": if (filteredArgs.Count != 2) return (false, "'get' command requires exactly 1 argument: get ", "ARGUMENT_ERROR", new { Command = "get", Args = filteredArgs }); break; case "set": if (filteredArgs.Count < 3) return (false, "'set' command requires at least 2 arguments: set [EX seconds] [PX milliseconds] [NX|XX]", "ARGUMENT_ERROR", new { Command = "set", Args = filteredArgs }); // Validate optional flags for set var setFlags = new HashSet(StringComparer.OrdinalIgnoreCase) { "EX", "PX", "NX", "XX" }; bool seenNX = false, seenXX = false; int iSet = 3; while (iSet < filteredArgs.Count) { string flag = filteredArgs[iSet]; if (flag.Equals("EX", StringComparison.OrdinalIgnoreCase)) { if (iSet + 1 >= filteredArgs.Count || !int.TryParse(filteredArgs[iSet + 1], out _)) return (false, "'set' EX flag must be followed by an integer (seconds)", "ARGUMENT_ERROR", new { Command = "set", Args = filteredArgs }); iSet += 2; } else if (flag.Equals("PX", StringComparison.OrdinalIgnoreCase)) { if (iSet + 1 >= filteredArgs.Count || !int.TryParse(filteredArgs[iSet + 1], out _)) return (false, "'set' PX flag must be followed by an integer (milliseconds)", "ARGUMENT_ERROR", new { Command = "set", Args = filteredArgs }); iSet += 2; } else if (flag.Equals("NX", StringComparison.OrdinalIgnoreCase)) { if (seenXX) return (false, "'set' cannot have both NX and XX flags", "ARGUMENT_ERROR", new { Command = "set", Args = filteredArgs }); seenNX = true; iSet++; } else if (flag.Equals("XX", StringComparison.OrdinalIgnoreCase)) { if (seenNX) return (false, "'set' cannot have both NX and XX flags", "ARGUMENT_ERROR", new { Command = "set", Args = filteredArgs }); seenXX = true; iSet++; } else { return (false, $"Unknown or misplaced flag '{flag}' in 'set' command", "ARGUMENT_ERROR", new { Command = "set", Args = filteredArgs }); } } break; case "del": if (filteredArgs.Count < 2) return (false, "'del' command requires at least 1 argument: del [key2 ...]", "ARGUMENT_ERROR", new { Command = "del", Args = filteredArgs }); break; case "hget": if (filteredArgs.Count != 3) return (false, "'hget' command requires exactly 2 arguments: hget ", "ARGUMENT_ERROR", new { Command = "hget", Args = filteredArgs }); break; case "hset": if (filteredArgs.Count != 4) return (false, "'hset' command requires exactly 3 arguments: hset ", "ARGUMENT_ERROR", new { Command = "hset", Args = filteredArgs }); break; case "mget": if (filteredArgs.Count < 2) return (false, "'mget' command requires at least 1 argument: mget [key2 ...]", "ARGUMENT_ERROR", new { Command = "mget", Args = filteredArgs }); break; case "mset": if (filteredArgs.Count < 3 || filteredArgs.Count % 2 != 1) return (false, "'mset' command requires an even number of arguments ≥ 2: mset [key2 value2 ...]", "ARGUMENT_ERROR", new { Command = "mset", Args = filteredArgs }); break; case "lpush": case "rpush": if (filteredArgs.Count < 3) return (false, $"'{cmd}' command requires at least 2 arguments: {cmd} [value2 ...]", "ARGUMENT_ERROR", new { Command = cmd, Args = filteredArgs }); break; case "lrange": if (filteredArgs.Count != 4) return (false, "'lrange' command requires exactly 3 arguments: lrange ", "ARGUMENT_ERROR", new { Command = "lrange", Args = filteredArgs }); break; case "zadd": if (filteredArgs.Count < 4 || (filteredArgs.Count - 2) % 2 != 0) return (false, "'zadd' command requires at least 3 arguments and pairs: zadd [score2 member2 ...]", "ARGUMENT_ERROR", new { Command = "zadd", Args = filteredArgs }); // Validate that all scores are valid numbers for (int i = 2; i < filteredArgs.Count; i += 2) { if (!double.TryParse(filteredArgs[i], out _)) return (false, $"'zadd' score argument at position {i} must be a valid number", "ARGUMENT_ERROR", new { Command = "zadd", Args = filteredArgs }); } break; case "sadd": if (filteredArgs.Count < 3) return (false, "'sadd' command requires at least 2 arguments: sadd [member2 ...]", "ARGUMENT_ERROR", new { Command = "sadd", Args = filteredArgs }); break; case "exists": if (filteredArgs.Count < 2) return (false, "'exists' command requires at least 1 argument: exists [key2 ...]", "ARGUMENT_ERROR", new { Command = "exists", Args = filteredArgs }); break; case "expire": if (filteredArgs.Count != 3) return (false, "'expire' command requires exactly 2 arguments: expire ", "ARGUMENT_ERROR", new { Command = "expire", Args = filteredArgs }); if (!int.TryParse(filteredArgs[2], out _)) return (false, "'expire' seconds argument must be a valid integer", "ARGUMENT_ERROR", new { Command = "expire", Args = filteredArgs }); break; case "rename": if (filteredArgs.Count != 3) return (false, "'rename' command requires exactly 2 arguments: rename ", "ARGUMENT_ERROR", new { Command = "rename", Args = filteredArgs }); break; case "select": if (filteredArgs.Count != 2) return (false, "'select' command requires exactly 1 argument: select ", "ARGUMENT_ERROR", new { Command = "select", Args = filteredArgs }); if (!int.TryParse(filteredArgs[1], out int dbidx) || dbidx < 0) return (false, "'select' dbindex must be a non-negative integer", "ARGUMENT_ERROR", new { Command = "select", Args = filteredArgs }); break; case "flushdb": case "flushall": case "dbsize": case "info": case "ping": case "quit": if (filteredArgs.Count != 1) return (false, $"'{cmd}' command takes no arguments", "ARGUMENT_ERROR", new { Command = cmd, Args = filteredArgs }); break; case "auth": if (filteredArgs.Count != 2) return (false, "'auth' command requires exactly 1 argument: auth ", "ARGUMENT_ERROR", new { Command = "auth", Args = filteredArgs }); break; case "keys": if (filteredArgs.Count != 2) return (false, "'keys' command requires exactly 1 argument: keys ", "ARGUMENT_ERROR", new { Command = "keys", Args = filteredArgs }); break; case "ttl": if (filteredArgs.Count != 2) return (false, "'ttl' command requires exactly 1 argument: ttl ", "ARGUMENT_ERROR", new { Command = "ttl", Args = filteredArgs }); break; case "type": if (filteredArgs.Count != 2) return (false, "'type' command requires exactly 1 argument: type ", "ARGUMENT_ERROR", new { Command = "type", Args = filteredArgs }); break; case "persist": if (filteredArgs.Count != 2) return (false, "'persist' command requires exactly 1 argument: persist ", "ARGUMENT_ERROR", new { Command = "persist", Args = filteredArgs }); break; case "scard": if (filteredArgs.Count != 2) return (false, "'scard' command requires exactly 1 argument: scard ", "ARGUMENT_ERROR", new { Command = "scard", Args = filteredArgs }); break; case "smembers": if (filteredArgs.Count != 2) return (false, "'smembers' command requires exactly 1 argument: smembers ", "ARGUMENT_ERROR", new { Command = "smembers", Args = filteredArgs }); break; case "sismember": if (filteredArgs.Count != 3) return (false, "'sismember' command requires exactly 2 arguments: sismember ", "ARGUMENT_ERROR", new { Command = "sismember", Args = filteredArgs }); break; case "srem": if (filteredArgs.Count < 3) return (false, "'srem' command requires at least 2 arguments: srem [member2 ...]", "ARGUMENT_ERROR", new { Command = "srem", Args = filteredArgs }); break; case "zcard": if (filteredArgs.Count != 2) return (false, "'zcard' command requires exactly 1 argument: zcard ", "ARGUMENT_ERROR", new { Command = "zcard", Args = filteredArgs }); break; case "zrange": case "zrevrange": if (filteredArgs.Count != 4 && filteredArgs.Count != 5) return (false, $"'{cmd}' command requires 3 or 4 arguments: {cmd} [WITHSCORES]", "ARGUMENT_ERROR", new { Command = cmd, Args = filteredArgs }); if (filteredArgs.Count == 5 && !filteredArgs[4].Equals("WITHSCORES", StringComparison.OrdinalIgnoreCase)) return (false, $"'{cmd}' 5th argument must be 'WITHSCORES' if present.", "ARGUMENT_ERROR", new { Command = cmd, Args = filteredArgs }); break; case "zscore": if (filteredArgs.Count != 3) return (false, "'zscore' command requires exactly 2 arguments: zscore ", "ARGUMENT_ERROR", new { Command = "zscore", Args = filteredArgs }); break; case "zrem": if (filteredArgs.Count < 3) return (false, "'zrem' command requires at least 2 arguments: zrem [member2 ...]", "ARGUMENT_ERROR", new { Command = "zrem", Args = filteredArgs }); break; case "zcount": if (filteredArgs.Count != 4) return (false, "'zcount' command requires exactly 3 arguments: zcount ", "ARGUMENT_ERROR", new { Command = "zcount", Args = filteredArgs }); break; case "zrank": case "zrevrank": if (filteredArgs.Count != 3) return (false, $"'{cmd}' command requires exactly 2 arguments: {cmd} ", "ARGUMENT_ERROR", new { Command = cmd, Args = filteredArgs }); break; case "hincrby": if (filteredArgs.Count != 4) return (false, "'hincrby' command requires exactly 3 arguments: hincrby ", "ARGUMENT_ERROR", new { Command = "hincrby", Args = filteredArgs }); if (!int.TryParse(filteredArgs[3], out _)) return (false, "'hincrby' increment must be an integer", "ARGUMENT_ERROR", new { Command = "hincrby", Args = filteredArgs }); break; case "hincrbyfloat": if (filteredArgs.Count != 4) return (false, "'hincrbyfloat' command requires exactly 3 arguments: hincrbyfloat ", "ARGUMENT_ERROR", new { Command = "hincrbyfloat", Args = filteredArgs }); if (!double.TryParse(filteredArgs[3], out _)) return (false, "'hincrbyfloat' increment must be a number", "ARGUMENT_ERROR", new { Command = "hincrbyfloat", Args = filteredArgs }); break; case "hdel": if (filteredArgs.Count < 3) return (false, "'hdel' command requires at least 2 arguments: hdel [field2 ...]", "ARGUMENT_ERROR", new { Command = "hdel", Args = filteredArgs }); break; case "hmget": if (filteredArgs.Count < 3) return (false, "'hmget' command requires at least 2 arguments: hmget [field2 ...]", "ARGUMENT_ERROR", new { Command = "hmget", Args = filteredArgs }); break; case "hmset": if (filteredArgs.Count < 4 || (filteredArgs.Count - 2) % 2 != 0) return (false, "'hmset' command requires at least 2 field-value pairs: hmset [field2 value2 ...]", "ARGUMENT_ERROR", new { Command = "hmset", Args = filteredArgs }); break; case "hkeys": case "hvals": case "hlen": if (filteredArgs.Count != 2) return (false, $"'{cmd}' command requires exactly 1 argument: {cmd} ", "ARGUMENT_ERROR", new { Command = cmd, Args = filteredArgs }); break; case "move": if (filteredArgs.Count != 3) return (false, "'move' command requires exactly 2 arguments: move ", "ARGUMENT_ERROR", new { Command = "move", Args = filteredArgs }); break; case "randomkey": if (filteredArgs.Count != 1) return (false, "'randomkey' command takes no arguments", "ARGUMENT_ERROR", new { Command = "randomkey", Args = filteredArgs }); break; case "echo": if (filteredArgs.Count != 2) return (false, "'echo' command requires exactly 1 argument: echo ", "ARGUMENT_ERROR", new { Command = "echo", Args = filteredArgs }); break; } } return (true, null, null, null); } private async Task<(bool Success, string Output)> ExecuteRedisCommandAsync(string[] args) { // (reload-config now handled in ExecuteCommandAsync) // Validate input arguments var validation = await ValidateArguments(args); if (!validation.Success) { return (false, validation.Output); } // Extract instance name if present in args (-i or --instance) string instanceName = null; for (int i = 0; i < args.Length - 1; i++) { if ((args[i] == "-i" || args[i] == "--instance") && !string.IsNullOrWhiteSpace(args[i + 1])) { instanceName = args[i + 1]; break; } } if (!string.IsNullOrEmpty(instanceName)) { var instanceConfig = _config.Instances.Find(x => x.Name == instanceName); if (instanceConfig == null) return (false, $"Instance '{instanceName}' not found in config."); // Check/start Valkey server if needed var startResult = await StartValkeyServerIfNeeded(instanceConfig); if (!startResult.Success) return (false, startResult.Output); } try { // Capture console output var originalOut = Console.Out; var stringWriter = new StringWriter(); Console.SetOut(stringWriter); // Always use the full in-memory config for all command handlers Config config = _config; bool commandRan = false; bool parseError = false; var parserResult = Parser.Default.ParseArguments(args, GetCommandTypes()); parserResult .WithParsed(o => { commandRan = true; switch (o) { case StatusOptions statusOpts: StatusCommand.RunStatus(statusOpts, config); break; case ListInstancesOptions listInstancesOpts: InstanceCommands.RunListInstances(config); break; case AddInstanceOptions addInstanceOpts: InstanceCommands.RunAddInstance(addInstanceOpts, config); break; case UpdateInstanceOptions updateInstanceOpts: InstanceCommands.RunUpdateInstance(updateInstanceOpts, config); break; case DeleteInstanceOptions deleteInstanceOpts: InstanceCommands.RunDeleteInstance(deleteInstanceOpts, config); break; case GetOptions getOpts: StringCommands.RunGet(getOpts, config); break; case SetOptions setOpts: StringCommands.RunSet(setOpts, config); break; case DelOptions delOpts: StringCommands.RunDel(delOpts, config); break; case HGetOptions hgetOpts: HashCommands.RunHGet(hgetOpts, config); break; case HSetOptions hsetOpts: HashCommands.RunHSet(hsetOpts, config); break; case HDelOptions hdelOpts: HashCommands.RunHDel(hdelOpts, config); break; case HGetAllOptions hgetAllOpts: HashCommands.RunHGetAll(hgetAllOpts, config); break; case HKeysOptions hkeysOpts: HashCommands.RunHKeys(hkeysOpts, config); break; case HValsOptions hvalsOpts: HashCommands.RunHVals(hvalsOpts, config); break; case HLenOptions hlenOpts: HashCommands.RunHLen(hlenOpts, config); break; case HExistsOptions hexistsOpts: HashCommands.RunHExists(hexistsOpts, config); break; case HIncrByOptions hincrByOpts: HashCommands.RunHIncrBy(hincrByOpts, config); break; case HIncrByFloatOptions hincrByFloatOpts: HashCommands.RunHIncrByFloat(hincrByFloatOpts, config); break; case HMSetOptions hmsetOpts: HashCommands.RunHMSet(hmsetOpts, config); break; case HMGetOptions hmgetOpts: HashCommands.RunHMGet(hmgetOpts, config); break; case HSetNxOptions hsetnxOpts: HashCommands.RunHSetNx(hsetnxOpts, config); break; case HStrLenOptions hstrlenOpts: HashCommands.RunHStrLen(hstrlenOpts, config); break; case HScanOptions hscanOpts: HashCommands.RunHScan(hscanOpts, config); break; case PFAddOptions pfaddOpts: HyperLogLogCommands.RunPFAdd(pfaddOpts, config); break; case PFCountOptions pfcountOpts: HyperLogLogCommands.RunPFCount(pfcountOpts, config); break; case PFMergeOptions pfmergeOpts: HyperLogLogCommands.RunPFMerge(pfmergeOpts, config); break; case GeoAddOptions geoaddOpts: GeoCommands.RunGeoAdd(geoaddOpts, config); break; case GeoDistOptions geodistOpts: GeoCommands.RunGeoDist(geodistOpts, config); break; case GeoHashOptions geohashOpts: GeoCommands.RunGeoHash(geohashOpts, config); break; case GeoPosOptions geoposOpts: GeoCommands.RunGeoPos(geoposOpts, config); break; case GeoRadiusOptions georadiusOpts: GeoCommands.RunGeoRadius(georadiusOpts, config); break; case BitCountOptions bitcountOpts: BitCommands.RunBitCount(bitcountOpts, config); break; case BitFieldOptions bitfieldOpts: BitCommands.RunBitField(bitfieldOpts, config); break; case BitOpOptions bitopOpts: BitCommands.RunBitOp(bitopOpts, config); break; case BitPosOptions bitposOpts: BitCommands.RunBitPos(bitposOpts, config); break; case GetBitOptions getbitOpts: BitCommands.RunGetBit(getbitOpts, config); break; case SetBitOptions setbitOpts: BitCommands.RunSetBit(setbitOpts, config); break; case ModuleListOptions moduleListOpts: ModuleCommands.RunModuleList(moduleListOpts, config); break; case ModuleLoadOptions moduleLoadOpts: ModuleCommands.RunModuleLoad(moduleLoadOpts, config); break; case ModuleUnloadOptions moduleUnloadOpts: ModuleCommands.RunModuleUnload(moduleUnloadOpts, config); break; case LPushOptions lpushOpts: ListCommands.RunLPush(lpushOpts, config); break; case RPushOptions rpushOpts: ListCommands.RunRPush(rpushOpts, config); break; case LLenOptions llenOpts: ListCommands.RunLLen(llenOpts, config); break; case LRangeOptions lrangeOpts: ListCommands.RunLRange(lrangeOpts, config); break; case LIndexOptions lindexOpts: ListCommands.RunLIndex(lindexOpts, config); break; case LSetOptions lsetOpts: ListCommands.RunLSet(lsetOpts, config); break; case LInsertOptions linsertOpts: ListCommands.RunLInsert(linsertOpts, config); break; case LRemOptions lremOpts: ListCommands.RunLRem(lremOpts, config); break; case LTrimOptions ltrimOpts: ListCommands.RunLTrim(ltrimOpts, config); break; case LPopOptions lpopOpts: ListCommands.RunLPop(lpopOpts, config); break; case RPopOptions rpopOpts: ListCommands.RunRPop(rpopOpts, config); break; case BLPopOptions blpopOpts: ListCommands.RunBLPop(blpopOpts, config); break; case BRPopOptions brpopOpts: ListCommands.RunBRPop(brpopOpts, config); break; case RPopLPushOptions rpoplpushOpts: ListCommands.RunRPopLPush(rpoplpushOpts, config); break; case SAddOptions saddOpts: SetCommands.RunSAdd(saddOpts, config); break; case SMembersOptions smembersOpts: SetCommands.RunSMembers(smembersOpts, config); break; case SIsMemberOptions sismemberOpts: SetCommands.RunSIsMember(sismemberOpts, config); break; case SCardOptions scardOpts: SetCommands.RunSCard(scardOpts, config); break; case SPopOptions spopOpts: SetCommands.RunSPop(spopOpts, config); break; case SRandMemberOptions srandmemberOpts: SetCommands.RunSRandMember(srandmemberOpts, config); break; case SRemOptions sremOpts: SetCommands.RunSRem(sremOpts, config); break; case SInterOptions sinterOpts: SetCommands.RunSInter(sinterOpts, config); break; case SUnionOptions sunionOpts: SetCommands.RunSUnion(sunionOpts, config); break; case SDiffOptions sdiffOpts: SetCommands.RunSDiff(sdiffOpts, config); break; case SInterStoreOptions sinterstoreOpts: SetCommands.RunSInterStore(sinterstoreOpts, config); break; case SUnionStoreOptions sunionstoreOpts: SetCommands.RunSUnionStore(sunionstoreOpts, config); break; case SDiffStoreOptions sdiffstoreOpts: SetCommands.RunSDiffStore(sdiffstoreOpts, config); break; case SScanOptions sscanOpts: SetCommands.RunSScan(sscanOpts, config); break; case SMoveOptions smoveOpts: SetCommands.RunSMove(smoveOpts, config); break; case ZAddOptions zaddOpts: SortedSetCommands.RunZAdd(zaddOpts, config); break; case ZRemOptions zremOpts: SortedSetCommands.RunZRem(zremOpts, config); break; case ZRangeOptions zrangeOpts: SortedSetCommands.RunZRange(zrangeOpts, config); break; case ZRevRangeOptions zrevrangeOpts: SortedSetCommands.RunZRevRange(zrevrangeOpts, config); break; case ZRangeByScoreOptions zrangebyscoreOpts: SortedSetCommands.RunZRangeByScore(zrangebyscoreOpts, config); break; case ZCardOptions zcardOpts: SortedSetCommands.RunZCard(zcardOpts, config); break; case ZScoreOptions zscoreOpts: SortedSetCommands.RunZScore(zscoreOpts, config); break; case ZRankOptions zrankOpts: SortedSetCommands.RunZRank(zrankOpts, config); break; case ZRevRankOptions zrevrankOpts: SortedSetCommands.RunZRevRank(zrevrankOpts, config); break; case ZIncrByOptions zincrbyOpts: SortedSetCommands.RunZIncrBy(zincrbyOpts, config); break; case ZRevRangeByScoreOptions zrevrangebyscoreOpts: SortedSetCommands.RunZRevRangeByScore(zrevrangebyscoreOpts, config); break; case ZCountOptions zcountOpts: SortedSetCommands.RunZCount(zcountOpts, config); break; case ZUnionStoreOptions zunionstoreOpts: SortedSetCommands.RunZUnionStore(zunionstoreOpts, config); break; case ZInterStoreOptions zinterstoreOpts: SortedSetCommands.RunZInterStore(zinterstoreOpts, config); break; case ZScanOptions zscanOpts: SortedSetCommands.RunZScan(zscanOpts, config); break; case ZPopMaxOptions zpopmaxOpts: SortedSetCommands.RunZPopMax(zpopmaxOpts, config); break; case ZPopMinOptions zpopminOpts: SortedSetCommands.RunZPopMin(zpopminOpts, config); break; case ZRemRangeByRankOptions zremrangebyrankOpts: SortedSetCommands.RunZRemRangeByRank(zremrangebyrankOpts, config); break; case ZRemRangeByScoreOptions zremrangebyscoreOpts: SortedSetCommands.RunZRemRangeByScore(zremrangebyscoreOpts, config); break; case FlushDbOptions flushdbOpts: DatabaseCommands.RunFlushDb(flushdbOpts, config); break; case DbSizeOptions dbsizeOpts: DatabaseCommands.RunDbSize(dbsizeOpts, config); break; case SelectOptions selectOpts: DatabaseCommands.RunSelect(selectOpts, config); break; case FlushAllOptions flushallOpts: DatabaseCommands.RunFlushAll(flushallOpts, config); break; case ScanOptions scanOpts: KeyCommands.RunScan(scanOpts, config); break; case KeysOptions keysOpts: KeyCommands.RunKeys(keysOpts, config); break; case ExistsOptions existsOpts: KeyCommands.RunExists(existsOpts, config); break; case TypeOptions typeOpts: KeyCommands.RunType(typeOpts, config); break; case TtlOptions ttlOpts: KeyCommands.RunTtl(ttlOpts, config); break; case ExpireOptions expireOpts: KeyCommands.RunExpire(expireOpts, config); break; case PersistOptions persistOpts: KeyCommands.RunPersist(persistOpts, config); break; case RenameOptions renameOpts: KeyCommands.RunRename(renameOpts, config); break; case ConfigOptions configOpts: ServerCommands.RunConfig(configOpts, config); break; case AuthOptions authOpts: ConnectionCommands.RunAuth(authOpts, config); break; case QuitOptions quitOpts: ConnectionCommands.RunQuit(quitOpts, config); break; case ClientListOptions clientListOpts: ConnectionCommands.RunClientList(clientListOpts, config); break; case ClientKillOptions clientKillOpts: ConnectionCommands.RunClientKill(clientKillOpts, config); break; case AppendOptions appendOpts: AdvancedStringCommands.RunAppend(appendOpts, config); break; case IncrOptions incrOpts: AdvancedStringCommands.RunIncr(incrOpts, config); break; case DecrOptions decrOpts: AdvancedStringCommands.RunDecr(decrOpts, config); break; case IncrByOptions incrbyOpts: AdvancedStringCommands.RunIncrBy(incrbyOpts, config); break; case DecrByOptions decrbyOpts: AdvancedStringCommands.RunDecrBy(decrbyOpts, config); break; case IncrByFloatOptions incrbyfloatOpts: AdvancedStringCommands.RunIncrByFloat(incrbyfloatOpts, config); break; case GetRangeOptions getrangeOpts: AdvancedStringCommands.RunGetRange(getrangeOpts, config); break; case SetRangeOptions setrangeOpts: AdvancedStringCommands.RunSetRange(setrangeOpts, config); break; case StrLenOptions strlenOpts: AdvancedStringCommands.RunStrLen(strlenOpts, config); break; case MGetOptions mgetOpts: AdvancedStringCommands.RunMGet(mgetOpts, config); break; case MSetOptions msetOpts: AdvancedStringCommands.RunMSet(msetOpts, config); break; case XAddOptions xaddOpts: StreamCommands.RunXAdd(xaddOpts, config); break; case XRangeOptions xrangeOpts: StreamCommands.RunXRange(xrangeOpts, config); break; case XLenOptions xlenOpts: StreamCommands.RunXLen(xlenOpts, config); break; case XDelOptions xdelOpts: StreamCommands.RunXDel(xdelOpts, config); break; case MultiOptions multiOpts: TransactionCommands.RunMulti(multiOpts, config); break; default: break; } }); parserResult .WithNotParsed(errors => { parseError = true; }); Console.SetOut(originalOut); var output = stringWriter.ToString(); if (!commandRan || parseError) { string attemptedCommand = args.Length > 0 ? args[0] : ""; return (false, $"Unknown or invalid command: '{attemptedCommand}'. Arguments: [{string.Join(", ", args)}]"); } return (true, output); } catch (Exception ex) { return (false, $"[Daemon] Exception: {ex}"); } } /// /// Returns an array of all command option types supported by the service. /// This array is used by the command line parser to determine which commands are available. /// /// Array of Type objects representing all supported command options private Type[] GetCommandTypes() { return new Type[] { typeof(StatusOptions), typeof(ListInstancesOptions), typeof(AddInstanceOptions), typeof(UpdateInstanceOptions), typeof(DeleteInstanceOptions), typeof(GetOptions), typeof(SetOptions), typeof(DelOptions), typeof(HGetOptions), typeof(HSetOptions), typeof(HDelOptions), typeof(HGetAllOptions), typeof(HKeysOptions), typeof(HValsOptions), typeof(HLenOptions), typeof(HExistsOptions), typeof(HIncrByOptions), typeof(HIncrByFloatOptions), typeof(HMSetOptions), typeof(HMGetOptions), typeof(HSetNxOptions), typeof(HStrLenOptions), typeof(HScanOptions), typeof(PFAddOptions), typeof(PFCountOptions), typeof(PFMergeOptions), typeof(GeoAddOptions), typeof(GeoDistOptions), typeof(GeoHashOptions), typeof(GeoPosOptions), typeof(GeoRadiusOptions), typeof(BitCountOptions), typeof(BitFieldOptions), typeof(BitOpOptions), typeof(BitPosOptions), typeof(GetBitOptions), typeof(SetBitOptions), typeof(ModuleListOptions), typeof(ModuleLoadOptions), typeof(ModuleUnloadOptions), typeof(LPushOptions), typeof(RPushOptions), typeof(LLenOptions), typeof(LRangeOptions), typeof(LIndexOptions), typeof(LSetOptions), typeof(LInsertOptions), typeof(LRemOptions), typeof(LTrimOptions), typeof(LPopOptions), typeof(RPopOptions), typeof(BLPopOptions), typeof(BRPopOptions), typeof(RPopLPushOptions), typeof(SAddOptions), typeof(SMembersOptions), typeof(SIsMemberOptions), typeof(SCardOptions), typeof(SPopOptions), typeof(SRandMemberOptions), typeof(SRemOptions), typeof(SInterOptions), typeof(SUnionOptions), typeof(SDiffOptions), typeof(SInterStoreOptions), typeof(SUnionStoreOptions), typeof(SDiffStoreOptions), typeof(SScanOptions), typeof(SMoveOptions), typeof(ZAddOptions), typeof(ZRemOptions), typeof(ZRangeOptions), typeof(ZRevRangeOptions), typeof(ZRangeByScoreOptions), typeof(ZCardOptions), typeof(ZScoreOptions), typeof(ZRankOptions), typeof(ZRevRankOptions), typeof(ZIncrByOptions), typeof(ZRevRangeByScoreOptions), typeof(ZCountOptions), typeof(ZUnionStoreOptions), typeof(ZInterStoreOptions), typeof(ZScanOptions), typeof(ZPopMaxOptions), typeof(ZPopMinOptions), typeof(ZRemRangeByRankOptions), typeof(ZRemRangeByScoreOptions), typeof(FlushDbOptions), typeof(DbSizeOptions), typeof(SelectOptions), typeof(FlushAllOptions), typeof(ScanOptions), typeof(KeysOptions), typeof(ExistsOptions), typeof(TypeOptions), typeof(TtlOptions), typeof(ExpireOptions), typeof(PersistOptions), typeof(RenameOptions), typeof(ConfigOptions), typeof(AuthOptions), typeof(QuitOptions), typeof(ClientListOptions), typeof(ClientKillOptions), typeof(AppendOptions), typeof(IncrOptions), typeof(DecrOptions), typeof(IncrByOptions), typeof(DecrByOptions), typeof(IncrByFloatOptions), typeof(GetRangeOptions), typeof(SetRangeOptions), typeof(StrLenOptions), typeof(MGetOptions), typeof(MSetOptions), typeof(XAddOptions), typeof(XRangeOptions), typeof(XLenOptions), typeof(XDelOptions), typeof(MultiOptions) }; } } /// /// Represents a command request sent from a client to the RedisManager service. /// Contains the command name and arguments to be executed. /// public class ServiceRequest { /// /// Gets or sets the name of the command to execute. /// public string Command { get; set; } /// /// Gets or sets the array of arguments for the command. /// public string[] Arguments { get; set; } } /// /// Represents a response from the RedisManager service to a client request. /// Contains the success status, result data, and any error information. /// public class ServiceResponse { /// /// Gets or sets whether the command execution was successful. /// public bool Success { get; set; } /// /// Gets or sets the result data from the command execution. /// Contains the output when Success is true. /// public string Data { get; set; } /// /// Gets or sets the error message if the command execution failed. /// Contains the error details when Success is false. /// public string Error { get; set; } /// /// Gets or sets the error code (e.g., INVALID_REQUEST, REDIS_ERROR, INTERNAL_ERROR). /// public string ErrorCode { get; set; } /// /// Gets or sets additional error details (optional, can be any object). /// public object ErrorDetails { get; set; } } }