diff --git a/.idea/.idea.FireflyClient/.idea/.gitignore b/.idea/.idea.FireflyClient/.idea/.gitignore new file mode 100644 index 0000000..4ae9bca --- /dev/null +++ b/.idea/.idea.FireflyClient/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/projectSettingsUpdater.xml +/.idea.FireflyClient.iml +/contentModel.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.FireflyClient/.idea/.name b/.idea/.idea.FireflyClient/.idea/.name new file mode 100644 index 0000000..ec3e592 --- /dev/null +++ b/.idea/.idea.FireflyClient/.idea/.name @@ -0,0 +1 @@ +FireflyClient \ No newline at end of file diff --git a/.idea/.idea.FireflyClient/.idea/encodings.xml b/.idea/.idea.FireflyClient/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.FireflyClient/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.FireflyClient/.idea/indexLayout.xml b/.idea/.idea.FireflyClient/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.FireflyClient/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.FireflyClient/.idea/vcs.xml b/.idea/.idea.FireflyClient/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/.idea.FireflyClient/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/artifacts/exe/FireflyClient b/artifacts/exe/FireflyClient index a1bf1eb..64b668f 100644 Binary files a/artifacts/exe/FireflyClient and b/artifacts/exe/FireflyClient differ diff --git a/artifacts/exe/FireflyClient.dbg b/artifacts/exe/FireflyClient.dbg index 25c0e5c..2e96188 100644 Binary files a/artifacts/exe/FireflyClient.dbg and b/artifacts/exe/FireflyClient.dbg differ diff --git a/artifacts/exe/FireflyClient.exe b/artifacts/exe/FireflyClient.exe index d280d11..f112e19 100644 Binary files a/artifacts/exe/FireflyClient.exe and b/artifacts/exe/FireflyClient.exe differ diff --git a/artifacts/exe/FireflyClient.pdb b/artifacts/exe/FireflyClient.pdb index c349a4b..287cbef 100644 Binary files a/artifacts/exe/FireflyClient.pdb and b/artifacts/exe/FireflyClient.pdb differ diff --git a/artifacts/exe/FireflyClient.xml b/artifacts/exe/FireflyClient.xml index 5f487b8..eaa7895 100644 --- a/artifacts/exe/FireflyClient.xml +++ b/artifacts/exe/FireflyClient.xml @@ -161,6 +161,11 @@ Removes and returns the last element of a list + + + Gets the length of a list + + Gets a range of elements from a list @@ -191,6 +196,61 @@ Removes elements equal to the given value from a list + + + String array structure for interop + + + + + Pointer to an array of string pointers + + + + + Number of strings in the array + + + + + Key-value pair structure for interop + + + + + Pointer to the key string + + + + + Pointer to the value string + + + + + Dictionary structure for interop + + + + + Pointer to an array of key-value pairs + + + + + Number of pairs in the array + + + + + Marshals a List to a NativeStringList without string conversion + + + + + Marshals a Dictionary to a NativeDictionary without string conversion + + Creates a new FireflyClient instance for native interop @@ -217,6 +277,16 @@ Frees a string allocated by the native interop methods + + + Frees a NativeStringList allocated by the native interop methods + + + + + Frees a NativeDictionary allocated by the native interop methods + + Sets a string value for a given key (Native Interop). @@ -277,6 +347,14 @@ Pointer to a null-terminated UTF-8 string representing the list key. Pointer to a null-terminated UTF-8 string result allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error or if list is empty. + + + Gets the length of a list (Native Interop). + + The GCHandle (as IntPtr) representing the client instance. + Pointer to a null-terminated UTF-8 string representing the list key. + The length of the list, or 0 on error. + Gets a range of elements from a list (Native Interop). diff --git a/artifacts/native/FireflyClient.pdb b/artifacts/native/FireflyClient.pdb index c3bcbbf..72dc6e7 100644 Binary files a/artifacts/native/FireflyClient.pdb and b/artifacts/native/FireflyClient.pdb differ diff --git a/artifacts/native/FireflyClient.so.dbg b/artifacts/native/FireflyClient.so.dbg index d8c3807..216807b 100644 Binary files a/artifacts/native/FireflyClient.so.dbg and b/artifacts/native/FireflyClient.so.dbg differ diff --git a/artifacts/native/FireflyClient.xml b/artifacts/native/FireflyClient.xml index 5f487b8..eaa7895 100644 --- a/artifacts/native/FireflyClient.xml +++ b/artifacts/native/FireflyClient.xml @@ -161,6 +161,11 @@ Removes and returns the last element of a list + + + Gets the length of a list + + Gets a range of elements from a list @@ -191,6 +196,61 @@ Removes elements equal to the given value from a list + + + String array structure for interop + + + + + Pointer to an array of string pointers + + + + + Number of strings in the array + + + + + Key-value pair structure for interop + + + + + Pointer to the key string + + + + + Pointer to the value string + + + + + Dictionary structure for interop + + + + + Pointer to an array of key-value pairs + + + + + Number of pairs in the array + + + + + Marshals a List to a NativeStringList without string conversion + + + + + Marshals a Dictionary to a NativeDictionary without string conversion + + Creates a new FireflyClient instance for native interop @@ -217,6 +277,16 @@ Frees a string allocated by the native interop methods + + + Frees a NativeStringList allocated by the native interop methods + + + + + Frees a NativeDictionary allocated by the native interop methods + + Sets a string value for a given key (Native Interop). @@ -277,6 +347,14 @@ Pointer to a null-terminated UTF-8 string representing the list key. Pointer to a null-terminated UTF-8 string result allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error or if list is empty. + + + Gets the length of a list (Native Interop). + + The GCHandle (as IntPtr) representing the client instance. + Pointer to a null-terminated UTF-8 string representing the list key. + The length of the list, or 0 on error. + Gets a range of elements from a list (Native Interop). diff --git a/artifacts/native/libFireflyClient.dll b/artifacts/native/libFireflyClient.dll index 6364094..d36b935 100644 Binary files a/artifacts/native/libFireflyClient.dll and b/artifacts/native/libFireflyClient.dll differ diff --git a/artifacts/native/libFireflyClient.so b/artifacts/native/libFireflyClient.so index 4f8273b..c08bbd8 100644 Binary files a/artifacts/native/libFireflyClient.so and b/artifacts/native/libFireflyClient.so differ diff --git a/firefly.h b/firefly.h index f221e30..969c90c 100644 --- a/firefly.h +++ b/firefly.h @@ -7,6 +7,22 @@ extern "C" { #endif +// C header definitions +typedef struct { + const char** strings; + int count; +} StringArray; + +typedef struct { + const char* key; + const char* value; +} KeyValuePair; + +typedef struct { + KeyValuePair* pairs; + int count; +} Dictionary; + // --- Client Management --- /** @@ -113,12 +129,11 @@ char* ListRightPop(void* handle, const char* key); * @param key Null-terminated UTF-8 string for the list key. * @param start The start index (0-based). * @param stop The stop index (0-based, inclusive). Use -1 to specify the end of the list. - * @return A pointer to a null-terminated UTF-8 string containing the list elements, separated by newline characters (\n). - * NOTE: This is a single string containing all elements. The caller is responsible for parsing this string (e.g., splitting by '\n'). - * This string is allocated by the library and MUST be freed by the caller using FreeString(). + * @return A StringArray structure containing the elements in the specified range. + * The caller is responsible for freeing the memory allocated for the StringArray structure using FreeStringArray(). * Returns NULL if the key does not exist or an error occurs. */ -char* ListRange(void* handle, const char* key, int start, int stop); +StringArray ListRange(void* handle, const char* key, int start, int stop); /** * @brief Gets the element at the specified index in a list. @@ -175,6 +190,20 @@ bool ListTrim(void* handle, const char* key, int start, int stop); */ int ListRemove(void* handle, const char* key, int count, const char* element); +/** + * @brief Gets the length of a list. + * @param handle The client handle. + * @param key Null-terminated UTF-8 string for the list key. + * @return The length of the list, or 0 on error. + */ +int ListLength(void* handle, const char* key); + +/** + * @brief Frees a StringArray structure and its contents. + * @param array Pointer to the StringArray structure to be freed. + */ +void FreeStringList(StringArray array); + // --- Hash Operations --- /** @@ -220,14 +249,11 @@ bool HashFieldExists(void* handle, const char* key, const char* field); * @brief Gets all fields and values from a hash. * @param handle The client handle. * @param key Null-terminated UTF-8 string for the hash key. - * @return A pointer to a null-terminated UTF-8 string containing the hash fields and values. - * Each field/value pair is represented as "field=value", separated by newline characters (\n). - * NOTE: This is a single string containing all field-value pairs. The caller is responsible for parsing this string (e.g., splitting by '\n' then by '='). - * Example: "field1=value1\nfield2=value2\nfield3=value3" - * This string is allocated by the library and MUST be freed by the caller using FreeString(). + * @return A Dictionary structure containing all fields and values in the hash. + * The caller is responsible for freeing the memory allocated for the Dictionary structure using FreeDictionary(). * Returns NULL if the key does not exist or an error occurs. */ -char* HashGetAll(void* handle, const char* key); +Dictionary HashGetAll(void* handle, const char* key); /** * @brief Sets multiple fields and values in a hash. @@ -240,6 +266,12 @@ char* HashGetAll(void* handle, const char* key); */ bool HashMultiSet(void* handle, const char* key, const char* fieldValuePairs); +/** + * @brief Frees the memory allocated for a Dictionary structure. + * @param dict Pointer to the Dictionary structure to be freed. + */ +void FreeDictionary(Dictionary dict); + // --- Raw Command Execution --- /** diff --git a/src/FireflyClient.cs b/src/FireflyClient.cs index ee387d3..4245679 100644 --- a/src/FireflyClient.cs +++ b/src/FireflyClient.cs @@ -11,15 +11,12 @@ namespace FireflyClient { private readonly TcpClient _client; private readonly NetworkStream _stream; - private readonly string _host; - private readonly int _port; - private readonly string _password; private readonly byte[] _responseBuffer = new byte[1024 * 1024]; // 1MB buffer for responses private int _maxBatchSize = 1000; private readonly int _maxPipelineSize = 10000; private readonly Queue _commandQueue = new(); - private bool _isPipelineMode = false; - private bool _isAuthenticated = false; + private bool _isPipelineMode; + private bool _isAuthenticated; // Static instance for native interop // private static FireflyClient? _instance; @@ -29,11 +26,8 @@ namespace FireflyClient /// public FireflyClient(string host = "127.0.0.1", int port = 6379) { - _host = host; - _port = port; - _password = string.Empty; _client = new TcpClient(); - _client.Connect(_host, _port); + _client.Connect(host, port); _stream = _client.GetStream(); } @@ -42,10 +36,9 @@ namespace FireflyClient /// public FireflyClient(string host, int port, string password) : this(host, port) { - _password = password; - if (!string.IsNullOrEmpty(_password)) + if (!string.IsNullOrEmpty(password)) { - Authenticate(_password); + Authenticate(password); } } @@ -54,7 +47,7 @@ namespace FireflyClient /// public bool Authenticate(string password) { - string response = ExecuteCommand("AUTH", password); + var response = ExecuteCommand("AUTH", password); _isAuthenticated = response.StartsWith("+OK"); return _isAuthenticated; } @@ -70,7 +63,7 @@ namespace FireflyClient } // Build the command string - string fullCommand = $"{command.ToUpperInvariant()}{string.Join("", args.Select(arg => $" {QuoteIfNeeded(arg)}"))}"; + var fullCommand = $"{command.ToUpperInvariant()}{string.Join("", args.Select(arg => $" {QuoteIfNeeded(arg)}"))}"; // Handle special commands that should not be pipelined if (command.Equals("AUTH", StringComparison.OrdinalIgnoreCase) || @@ -118,11 +111,11 @@ namespace FireflyClient command += "\r\n"; } - byte[] commandBytes = Encoding.UTF8.GetBytes(command); + var commandBytes = Encoding.UTF8.GetBytes(command); _stream.Write(commandBytes, 0, commandBytes.Length); // Read response - int bytesRead = _stream.Read(_responseBuffer, 0, _responseBuffer.Length); + var bytesRead = _stream.Read(_responseBuffer, 0, _responseBuffer.Length); return bytesRead > 0 ? Encoding.UTF8.GetString(_responseBuffer, 0, bytesRead) : string.Empty; } @@ -131,7 +124,7 @@ namespace FireflyClient if (_commandQueue.Count == 0) return string.Empty; // Build the pipeline command string - string pipelineCommand = string.Join("\r\n", _commandQueue) + "\r\n"; + var pipelineCommand = string.Join("\r\n", _commandQueue) + "\r\n"; _commandQueue.Clear(); return SendCommandInternal(pipelineCommand); @@ -140,7 +133,7 @@ namespace FireflyClient /// /// Enables or disables pipeline mode /// - public void SetPipelineMode(bool enabled) + private void SetPipelineMode(bool enabled) { _isPipelineMode = enabled; if (!enabled) @@ -152,7 +145,7 @@ namespace FireflyClient /// /// Flushes any queued commands in pipeline mode /// - public string FlushPipeline() + private string FlushPipeline() { return ProcessCommandQueue(); } @@ -160,7 +153,7 @@ namespace FireflyClient /// /// Sets the maximum number of commands to batch before sending /// - public void SetBatchSize(int size) + private void SetBatchSize(int size) { if (size <= 0) { @@ -172,17 +165,17 @@ namespace FireflyClient /// /// Gets the current number of queued commands /// - public int QueuedCommandCount => _commandQueue.Count; + private int QueuedCommandCount => _commandQueue.Count; /// /// Gets whether pipeline mode is enabled /// - public bool IsPipelineMode => _isPipelineMode; + private bool IsPipelineMode => _isPipelineMode; /// /// Gets the maximum batch size /// - public int MaxBatchSize => _maxBatchSize; + private int MaxBatchSize => _maxBatchSize; /// /// Gets whether the client is connected to the server @@ -199,18 +192,18 @@ namespace FireflyClient /// /// Pattern to match against keys. Use * for wildcard matches. /// List of matching keys, or empty list if none found or on error - public List Keys(string pattern = "*") + private List Keys(string pattern = "*") { try { - string response = ExecuteCommand("KEYS", pattern); + var response = ExecuteCommand("KEYS", pattern); if (string.IsNullOrEmpty(response) || !response.StartsWith('+')) { return []; } // Remove the '+' prefix and split by newlines - string keysStr = response[1..].Trim(); + var keysStr = response[1..].Trim(); return string.IsNullOrEmpty(keysStr) ? [] : [.. keysStr.Split('\n')]; @@ -224,7 +217,7 @@ namespace FireflyClient /// /// Parses an array response from the server /// - protected static List ParseArrayResponse(string response) + private static List ParseArrayResponse(string response) { var result = new List(); @@ -237,7 +230,7 @@ namespace FireflyClient string[] parts = response.Split("\r\n"); // Skip the first line which just contains the * prefix - for (int i = 1; i < parts.Length; i++) + for (var i = 1; i < parts.Length; i++) { if (!string.IsNullOrEmpty(parts[i]) && (parts[i].StartsWith('+') || parts[i].StartsWith('$'))) { diff --git a/src/HashOperations.cs b/src/HashOperations.cs index e551b47..92b97c5 100644 --- a/src/HashOperations.cs +++ b/src/HashOperations.cs @@ -7,53 +7,49 @@ namespace FireflyClient /// /// Sets a field in a hash /// - public bool HashSet(string key, string field, string value) + private bool HashSet(string key, string field, string value) { - string response = ExecuteCommand("HSET", key, field, value); + var response = ExecuteCommand("HSET", key, field, value); return response.StartsWith(":1"); } /// /// Gets a field from a hash /// - public string HashGet(string key, string field) + private string HashGet(string key, string field) { - string response = ExecuteCommand("HGET", key, field); - if (response.StartsWith('+')) - { - return response[1..].TrimEnd('\r', '\n'); - } - return string.Empty; + var response = ExecuteCommand("HGET", key, field); + return response.StartsWith('+') ? response[1..].TrimEnd('\r', '\n') : string.Empty; } /// /// Deletes a field from a hash /// - public bool HashDelete(string key, string field) + private bool HashDelete(string key, string field) { - string response = ExecuteCommand("HDEL", key, field); + var response = ExecuteCommand("HDEL", key, field); return response.StartsWith(":1"); } /// /// Checks if a field exists in a hash /// - public bool HashFieldExists(string key, string field) + private bool HashFieldExists(string key, string field) { - string response = ExecuteCommand("HEXISTS", key, field); + var response = ExecuteCommand("HEXISTS", key, field); return response.StartsWith(":1"); } /// /// Gets all fields and values from a hash /// - public Dictionary HashGetAll(string key) + private Dictionary HashGetAll(string key) { - string response = ExecuteCommand("HGETALL", key); - List items = ParseArrayResponse(response); + var response = ExecuteCommand("HGETALL", key); + var items = ParseArrayResponse(response); var result = new Dictionary(); - for (int i = 0; i < items.Count; i += 2) + for (var i = 0; i < items.Count; i += 2) { if (i + 1 < items.Count) { @@ -67,7 +63,7 @@ namespace FireflyClient /// /// Sets multiple fields in a hash at once /// - public bool HashMultiSet(string key, Dictionary fieldValues) + private bool HashMultiSet(string key, Dictionary fieldValues) { List args = [key]; @@ -77,7 +73,7 @@ namespace FireflyClient args.Add(kvp.Value); } - string response = ExecuteCommand("HMSET", [.. args]); + var response = ExecuteCommand("HMSET", [.. args]); return response.StartsWith("+OK"); } diff --git a/src/ListOperations.cs b/src/ListOperations.cs index 41a8dd3..632e9f4 100644 --- a/src/ListOperations.cs +++ b/src/ListOperations.cs @@ -7,102 +7,88 @@ namespace FireflyClient /// /// Adds values to the beginning of a list /// - public int ListLeftPush(string key, params string[] values) + private int ListLeftPush(string key, params string[] values) { var args = new List { key }; args.AddRange(values); - string response = ExecuteCommand("LPUSH", [.. args]); - if (response.StartsWith(':')) - { - if (int.TryParse(response[1..].TrimEnd('\r', '\n'), out int result)) - { - return result; - } - } - return 0; + var response = ExecuteCommand("LPUSH", [.. args]); + if (!response.StartsWith(':')) return 0; + return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : 0; } /// /// Adds values to the end of a list /// - public int ListRightPush(string key, params string[] values) + private int ListRightPush(string key, params string[] values) { var args = new List { key }; args.AddRange(values); - string response = ExecuteCommand("RPUSH", [.. args]); - if (response.StartsWith(':')) - { - if (int.TryParse(response[1..].TrimEnd('\r', '\n'), out int result)) - { - return result; - } - } - return 0; + var response = ExecuteCommand("RPUSH", [.. args]); + if (!response.StartsWith(':')) return 0; + return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : 0; } /// /// Removes and returns the first element of a list /// - public string ListLeftPop(string key) + private string ListLeftPop(string key) { - string response = ExecuteCommand("LPOP", key); - if (response.StartsWith('+')) - { - return response[1..].TrimEnd('\r', '\n'); - } - return string.Empty; + var response = ExecuteCommand("LPOP", key); + return response.StartsWith('+') ? response[1..].TrimEnd('\r', '\n') : string.Empty; } /// /// Removes and returns the last element of a list /// - public string ListRightPop(string key) + private string ListRightPop(string key) { - string response = ExecuteCommand("RPOP", key); - if (response.StartsWith('+')) - { - return response[1..].TrimEnd('\r', '\n'); - } - return string.Empty; + var response = ExecuteCommand("RPOP", key); + return response.StartsWith('+') ? response[1..].TrimEnd('\r', '\n') : string.Empty; + } + + /// + /// Gets the length of a list + /// + private int ListLength(string key) + { + var response = ExecuteCommand("LLEN", key); + if (!response.StartsWith(':')) return 0; + return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : 0; } /// /// Gets a range of elements from a list /// - public List ListRange(string key, int start, int stop) + private List ListRange(string key, int start, int stop) { - string response = ExecuteCommand("LRANGE", key, start.ToString(), stop.ToString()); + var response = ExecuteCommand("LRANGE", key, start.ToString(), stop.ToString()); return ParseArrayResponse(response); } /// /// Gets the element at the specified index in a list /// - public string ListIndex(string key, int index) + private string ListIndex(string key, int index) { - string response = ExecuteCommand("LINDEX", key, index.ToString()); - if (response.StartsWith('+')) - { - return response[1..].TrimEnd('\r', '\n'); - } - return string.Empty; + var response = ExecuteCommand("LINDEX", key, index.ToString()); + return response.StartsWith('+') ? response[1..].TrimEnd('\r', '\n') : string.Empty; } /// /// Sets the element at the specified index in a list /// - public bool ListSet(string key, int index, string value) + private bool ListSet(string key, int index, string value) { - string response = ExecuteCommand("LSET", key, index.ToString(), value); + var response = ExecuteCommand("LSET", key, index.ToString(), value); return response.StartsWith("+OK"); } /// /// Returns the index of the first occurrence of an element in a list /// - public int ListPosition(string key, string element, int rank = 1, int maxlen = 0) + private int ListPosition(string key, string element, int rank = 1, int maxlen = 0) { var args = new List { key, element }; @@ -118,40 +104,28 @@ namespace FireflyClient args.Add(maxlen.ToString()); } - string response = ExecuteCommand("LPOS", [.. args]); - if (response.StartsWith(':')) - { - if (int.TryParse(response[1..].TrimEnd('\r', '\n'), out int result)) - { - return result; - } - } - return -1; + var response = ExecuteCommand("LPOS", [.. args]); + if (!response.StartsWith(':')) return -1; + return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : -1; } /// /// Trims a list to the specified range /// - public bool ListTrim(string key, int start, int stop) + private bool ListTrim(string key, int start, int stop) { - string response = ExecuteCommand("LTRIM", key, start.ToString(), stop.ToString()); + var response = ExecuteCommand("LTRIM", key, start.ToString(), stop.ToString()); return response.StartsWith("+OK"); } /// /// Removes elements equal to the given value from a list /// - public int ListRemove(string key, int count, string element) + private int ListRemove(string key, int count, string element) { - string response = ExecuteCommand("LREM", key, count.ToString(), element); - if (response.StartsWith(':')) - { - if (int.TryParse(response[1..].TrimEnd('\r', '\n'), out int result)) - { - return result; - } - } - return 0; + var response = ExecuteCommand("LREM", key, count.ToString(), element); + if (!response.StartsWith(':')) return 0; + return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : 0; } #endregion diff --git a/src/NativeInterop.cs b/src/NativeInterop.cs index 6f8c2a5..698e57c 100644 --- a/src/NativeInterop.cs +++ b/src/NativeInterop.cs @@ -5,13 +5,66 @@ namespace FireflyClient { public partial class FireflyClient { + // Native structure definitions for interop + + /// + /// String array structure for interop + /// + [StructLayout(LayoutKind.Sequential)] + public struct NativeStringList + { + /// + /// Pointer to an array of string pointers + /// + public IntPtr Strings; + + /// + /// Number of strings in the array + /// + public int Count; + } + + /// + /// Key-value pair structure for interop + /// + [StructLayout(LayoutKind.Sequential)] + public struct NativeKeyValuePair + { + /// + /// Pointer to the key string + /// + public IntPtr Key; + + /// + /// Pointer to the value string + /// + public IntPtr Value; + } + + /// + /// Dictionary structure for interop + /// + [StructLayout(LayoutKind.Sequential)] + public struct NativeDictionary + { + /// + /// Pointer to an array of key-value pairs + /// + public IntPtr Pairs; + + /// + /// Number of pairs in the array + /// + public int Count; + } + // Helper to safely get client from handle private static FireflyClient? GetClientFromHandle(IntPtr handle) { if (handle == IntPtr.Zero) return null; try { - GCHandle gch = (GCHandle)handle; + var gch = (GCHandle)handle; return gch.Target as FireflyClient; } catch @@ -21,31 +74,80 @@ namespace FireflyClient } // Helper to marshal string result to IntPtr - private static IntPtr MarshalStringResult(string? result) + private static IntPtr MarshalString(string? result) { if (result == null) return IntPtr.Zero; - byte[] bytes = Encoding.UTF8.GetBytes(result); - IntPtr ptr = Marshal.AllocHGlobal(bytes.Length + 1); + var bytes = Encoding.UTF8.GetBytes(result); + var ptr = Marshal.AllocHGlobal(bytes.Length + 1); Marshal.Copy(bytes, 0, ptr, bytes.Length); Marshal.WriteByte(ptr + bytes.Length, 0); // Null terminator return ptr; } - + // Helper to marshal List result to IntPtr (newline-delimited) private static IntPtr MarshalStringListResult(List? result) { if (result == null || result.Count == 0) return IntPtr.Zero; - string joinedResult = string.Join("\n", result); // Using \n as delimiter - return MarshalStringResult(joinedResult); + var joinedResult = string.Join("\n", result); // Using \n as delimiter + return MarshalString(joinedResult); } - - // Helper to marshal Dictionary result to IntPtr (newline-delimited field=value) - private static IntPtr MarshalStringDictionaryResult(Dictionary? result) + + /// + /// Marshals a List to a NativeStringList without string conversion + /// + private static NativeStringList MarshalStringList(List? list) { - if (result == null || result.Count == 0) return IntPtr.Zero; - string joinedResult = string.Join("\n", result.Select(kvp => $"{kvp.Key}={kvp.Value}")); - return MarshalStringResult(joinedResult); + if (list == null || list.Count == 0) + { + return new NativeStringList { Strings = IntPtr.Zero, Count = 0 }; + } + + // Allocate memory for the array of strings + var arrayPtr = Marshal.AllocHGlobal(list.Count * IntPtr.Size); + + // Copy each string to the allocated memory + for (var i = 0; i < list.Count; i++) + { + var stringPtr = MarshalString(list[i]); + Marshal.WriteIntPtr(arrayPtr + i * IntPtr.Size, stringPtr); + } + + return new NativeStringList { Strings = arrayPtr, Count = list.Count }; + } + + /// + /// Marshals a Dictionary to a NativeDictionary without string conversion + /// + private static NativeDictionary MarshalDictionary(Dictionary? dict) + { + if (dict == null || dict.Count == 0) + { + return new NativeDictionary { Pairs = IntPtr.Zero, Count = 0 }; + } + + // Allocate memory for the array of key-value pairs + var structSize = Marshal.SizeOf(); + var pairsPtr = Marshal.AllocHGlobal(dict.Count * structSize); + + // For each key-value pair, create a NativeKeyValuePair + var index = 0; + foreach (var kvp in dict) + { + var keyPtr = MarshalString(kvp.Key); + var valuePtr = MarshalString(kvp.Value); + + // Create a NativeKeyValuePair and write it to the allocated memory + var pair = new NativeKeyValuePair + { + Key = keyPtr, + Value = valuePtr + }; + Marshal.StructureToPtr(pair, pairsPtr + index * structSize, false); + index++; + } + + return new NativeDictionary { Pairs = pairsPtr, Count = dict.Count }; } #region Native Interop Methods @@ -59,7 +161,7 @@ namespace FireflyClient try { // Use UTF8 for host string - string host = Marshal.PtrToStringUTF8(hostPtr) ?? "127.0.0.1"; + var host = Marshal.PtrToStringUTF8(hostPtr) ?? "127.0.0.1"; var client = new FireflyClient(host, port); var handle = GCHandle.Alloc(client); return GCHandle.ToIntPtr(handle); @@ -80,7 +182,7 @@ namespace FireflyClient { try { - GCHandle gch = GCHandle.FromIntPtr(handle); + var gch = GCHandle.FromIntPtr(handle); if (gch.Target is FireflyClient client) { client.Dispose(); @@ -104,7 +206,7 @@ namespace FireflyClient try { // Use UTF8 for password string - string password = Marshal.PtrToStringUTF8(passwordPtr) ?? string.Empty; + var password = Marshal.PtrToStringUTF8(passwordPtr) ?? string.Empty; var client = GetClientFromHandle(handle); return client?.Authenticate(password) ?? false; } @@ -124,13 +226,13 @@ namespace FireflyClient try { // Use UTF8 for command and args strings - string command = Marshal.PtrToStringUTF8(commandPtr) ?? string.Empty; - string args = Marshal.PtrToStringUTF8(argsPtr) ?? string.Empty; + var command = Marshal.PtrToStringUTF8(commandPtr) ?? string.Empty; + var args = Marshal.PtrToStringUTF8(argsPtr) ?? string.Empty; var client = GetClientFromHandle(handle); // WARNING: Basic split, unreliable if args have spaces string[] argArray = args.Split(' ', StringSplitOptions.RemoveEmptyEntries); - string? result = client?.ExecuteCommand(command, argArray); - return MarshalStringResult(result); + var result = client?.ExecuteCommand(command, argArray); + return MarshalString(result); } catch { @@ -149,7 +251,52 @@ namespace FireflyClient Marshal.FreeHGlobal(ptr); } } - + + /// + /// Frees a NativeStringList allocated by the native interop methods + /// + [UnmanagedCallersOnly(EntryPoint = "FreeStringList")] + public static void NativeFreeStringList(NativeStringList list) + { + if (list.Strings != IntPtr.Zero && list.Count > 0) + { + for (var i = 0; i < list.Count; i++) + { + var stringPtr = Marshal.ReadIntPtr(list.Strings + i * IntPtr.Size); + if (stringPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(stringPtr); + } + } + Marshal.FreeHGlobal(list.Strings); + } + } + + /// + /// Frees a NativeDictionary allocated by the native interop methods + /// + [UnmanagedCallersOnly(EntryPoint = "FreeDictionary")] + public static void NativeFreeDictionary(NativeDictionary dict) + { + if (dict.Pairs != IntPtr.Zero && dict.Count > 0) + { + var structSize = Marshal.SizeOf(); + + for (var i = 0; i < dict.Count; i++) + { + var pairPtr = dict.Pairs + i * structSize; + var pair = Marshal.PtrToStructure(pairPtr); + + if (pair.Key != IntPtr.Zero) + Marshal.FreeHGlobal(pair.Key); + + if (pair.Value != IntPtr.Zero) + Marshal.FreeHGlobal(pair.Value); + } + Marshal.FreeHGlobal(dict.Pairs); + } + } + // --- String Operations --- /// @@ -165,8 +312,8 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty; return client?.StringSet(key, value) ?? false; } catch { return false; } @@ -184,9 +331,9 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string? result = client?.StringGet(key); - return MarshalStringResult(result); + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var result = client?.StringGet(key); + return MarshalString(result); } catch { return IntPtr.Zero; } } @@ -204,7 +351,7 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; // Assuming Delete returns the number of keys deleted (usually 1 or 0) return client?.Delete(key) ?? 0; } @@ -226,8 +373,8 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty; // Assuming LPUSH returns the new length of the list return client?.ListLeftPush(key, value) ?? 0; } @@ -247,8 +394,8 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty; // Assuming RPUSH returns the new length of the list return client?.ListRightPush(key, value) ?? 0; } @@ -267,9 +414,9 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string? result = client?.ListLeftPop(key); - return MarshalStringResult(result); + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var result = client?.ListLeftPop(key); + return MarshalString(result); } catch { return IntPtr.Zero; } } @@ -286,13 +433,32 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string? result = client?.ListRightPop(key); - return MarshalStringResult(result); + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var result = client?.ListRightPop(key); + return MarshalString(result); } catch { return IntPtr.Zero; } } + /// + /// Gets the length of a list (Native Interop). + /// + /// The GCHandle (as IntPtr) representing the client instance. + /// Pointer to a null-terminated UTF-8 string representing the list key. + /// The length of the list, or 0 on error. + [UnmanagedCallersOnly(EntryPoint = "ListLength")] + public static int NativeListLength(IntPtr handle, IntPtr keyPtr) + { + try + { + var client = GetClientFromHandle(handle); + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var result = client?.ListLength(key) ?? 0; + return result; + } + catch { return 0; } + } + /// /// Gets a range of elements from a list (Native Interop). /// @@ -303,17 +469,19 @@ namespace FireflyClient /// Pointer to a null-terminated UTF-8 string containing newline-delimited list elements, allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error. /// The returned IntPtr points to a single string. The native caller must parse this string (e.g., split by '\n') and free the pointer using NativeFreeString. [UnmanagedCallersOnly(EntryPoint = "ListRange")] - public static IntPtr NativeListRange(IntPtr handle, IntPtr keyPtr, int start, int stop) + public static NativeStringList NativeListRange(IntPtr handle, IntPtr keyPtr, int start, int stop) { - try - { - var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - List? result = client?.ListRange(key, start, stop); - // Marshal List as a single newline-delimited string - return MarshalStringListResult(result); - } - catch { return IntPtr.Zero; } + try + { + var client = GetClientFromHandle(handle); + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + List? result = client?.ListRange(key, start, stop); + return MarshalStringList(result); + } + catch + { + return new NativeStringList { Strings = IntPtr.Zero, Count = 0 }; + } } /// @@ -329,9 +497,9 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string? result = client?.ListIndex(key, index); - return MarshalStringResult(result); + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var result = client?.ListIndex(key, index); + return MarshalString(result); } catch { return IntPtr.Zero; } } @@ -350,8 +518,8 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty; return client?.ListSet(key, index, value) ?? false; } catch { return false; } @@ -373,8 +541,8 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string element = Marshal.PtrToStringUTF8(elementPtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var element = Marshal.PtrToStringUTF8(elementPtr) ?? string.Empty; // Note: C# ListPosition returns the first index or -1 var position = client?.ListPosition(key, element, rank, maxlen); return position ?? -1; @@ -396,7 +564,7 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; return client?.ListTrim(key, start, stop) ?? false; } catch { return false; } @@ -416,8 +584,8 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string element = Marshal.PtrToStringUTF8(elementPtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var element = Marshal.PtrToStringUTF8(elementPtr) ?? string.Empty; return client?.ListRemove(key, count, element) ?? 0; } catch { return 0; } @@ -439,9 +607,9 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty; - string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty; + var value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty; // Assuming HSET returns 1 if field is new, 0 if updated return client?.HashSet(key, field, value) ?? false; // Adapt if C# returns differently } @@ -461,10 +629,10 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty; - string? result = client?.HashGet(key, field); - return MarshalStringResult(result); + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty; + var result = client?.HashGet(key, field); + return MarshalString(result); } catch { return IntPtr.Zero; } } @@ -482,8 +650,8 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty; // Assuming HDEL returns true if field was deleted, false otherwise return client?.HashDelete(key, field) ?? false; } @@ -503,8 +671,8 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty; return client?.HashFieldExists(key, field) ?? false; } catch { return false; } @@ -524,8 +692,8 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - string fieldValuePairs = Marshal.PtrToStringUTF8(fieldValuePairsPtr) ?? string.Empty; + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + var fieldValuePairs = Marshal.PtrToStringUTF8(fieldValuePairsPtr) ?? string.Empty; string[] pairs = fieldValuePairs.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (pairs.Length % 2 != 0) @@ -535,7 +703,7 @@ namespace FireflyClient } var dict = new Dictionary(); - for (int i = 0; i < pairs.Length; i += 2) + for (var i = 0; i < pairs.Length; i += 2) { dict[pairs[i]] = pairs[i + 1]; } @@ -553,17 +721,19 @@ namespace FireflyClient /// Pointer to a null-terminated UTF-8 string containing newline-delimited "field=value" pairs, allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error or if hash not found. /// The returned IntPtr points to a single string. The native caller must parse this string (e.g., split by '\n', then by '=') and free the pointer using NativeFreeString. [UnmanagedCallersOnly(EntryPoint = "HashGetAll")] - public static IntPtr NativeHashGetAll(IntPtr handle, IntPtr keyPtr) + public static NativeDictionary NativeHashGetAll(IntPtr handle, IntPtr keyPtr) { - try - { - var client = GetClientFromHandle(handle); - string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; - Dictionary? result = client?.HashGetAll(key); - // Marshal Dictionary as a single newline-delimited string - return MarshalStringDictionaryResult(result); - } - catch { return IntPtr.Zero; } + try + { + var client = GetClientFromHandle(handle); + var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty; + Dictionary? result = client?.HashGetAll(key); + return MarshalDictionary(result); + } + catch + { + return new NativeDictionary { Pairs = IntPtr.Zero, Count = 0 }; + } } // --- Pipeline Operations (Already Implemented) --- @@ -625,8 +795,8 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string? response = client?.FlushPipeline(); - return MarshalStringResult(response); + var response = client?.FlushPipeline(); + return MarshalString(response); } catch { @@ -703,7 +873,7 @@ namespace FireflyClient try { var client = GetClientFromHandle(handle); - string pattern = Marshal.PtrToStringUTF8(patternPtr) ?? "*"; + var pattern = Marshal.PtrToStringUTF8(patternPtr) ?? "*"; List? result = client?.Keys(pattern); return MarshalStringListResult(result); } diff --git a/src/Program.cs b/src/Program.cs index e8c0534..b5213a1 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -7,9 +7,9 @@ namespace FireflyClient /// /// Command-line interface for Firefly /// - public class Program + public abstract class Program { - static async Task Main(string[] args) + private static async Task Main(string[] args) { Console.WriteLine("Firefly Client"); Console.WriteLine("Enter commands (type EXIT to quit)"); @@ -17,29 +17,28 @@ namespace FireflyClient Console.WriteLine("Type HELP for basic commands or HELP EMAIL for email examples"); // Parse command line arguments - string host = "127.0.0.1"; - int port = 6379; - string password = string.Empty; + var host = "127.0.0.1"; + var port = 6379; + var password = string.Empty; - for (int i = 0; i < args.Length; i++) + for (var i = 0; i < args.Length; i++) { - if ((args[i] == "--host" || args[i] == "-h") && i + 1 < args.Length) + switch (args[i]) { - host = args[++i]; - } - else if ((args[i] == "--port" || args[i] == "-p") && i + 1 < args.Length && int.TryParse(args[i + 1], out int parsedPort)) - { - port = parsedPort; - i++; - } - else if ((args[i] == "--password" || args[i] == "--pass") && i + 1 < args.Length) - { - password = args[++i]; - } - else if (args[i] == "--help" || args[i] == "-?") - { - PrintClientHelp(); - return; + case "--host" or "-h" when i + 1 < args.Length: + host = args[++i]; + break; + case "--port" or "-p" when i + 1 < args.Length && int.TryParse(args[i + 1], out var parsedPort): + port = parsedPort; + i++; + break; + case "--password" or "--pass" when i + 1 < args.Length: + password = args[++i]; + break; + case "--help": + case "-?": + PrintClientHelp(); + return; } } @@ -53,7 +52,7 @@ namespace FireflyClient } } - static void PrintClientHelp() + private static void PrintClientHelp() { Console.WriteLine("\nFirefly Client Usage:"); Console.WriteLine(" --host, -h Server hostname or IP (default: 127.0.0.1)"); @@ -63,7 +62,7 @@ namespace FireflyClient Console.WriteLine("Example: FireflyClient --host localhost --port 6380 --password secret123\n"); } - static Task RunInteractiveClientAsync(string host, int port, string password) + private static Task RunInteractiveClientAsync(string host, int port, string password) { using var client = new FireflyClient(host, port); Console.WriteLine($"Connecting to server at {host}:{port}..."); @@ -79,7 +78,7 @@ namespace FireflyClient // Authenticate if password was provided if (!string.IsNullOrEmpty(password)) { - bool success = client.Authenticate(password); + var success = client.Authenticate(password); // Check if authentication failed if (!success) @@ -93,15 +92,15 @@ namespace FireflyClient { // Get command from user Console.Write("> "); - string input = Console.ReadLine() ?? string.Empty; + var input = Console.ReadLine() ?? string.Empty; if (string.IsNullOrEmpty(input) || input.Equals("EXIT", StringComparison.OrdinalIgnoreCase)) { // Send QUIT command before exiting try { - string response = client.ExecuteCommand("QUIT"); - Console.WriteLine($"Server: {response.TrimEnd('\r', '\n')}"); + var response = client.ExecuteCommand("QUIT"); + Console.WriteLine($"{response.TrimEnd('\r', '\n')}"); } catch (Exception ex) { @@ -127,14 +126,14 @@ namespace FireflyClient try { // Parse the command - string[] parts = SplitCommandLine(input); + var parts = SplitCommandLine(input); if (parts.Length == 0) continue; - string command = parts[0]; + var command = parts[0]; string[] args = [.. parts.Skip(1)]; // Execute the command - string response = client.ExecuteCommand(command, args); + var response = client.ExecuteCommand(command, args); FormatAndPrintResponse(response); } catch (Exception ex) @@ -148,33 +147,31 @@ namespace FireflyClient } // Helper method to split command line respecting quotes - static string[] SplitCommandLine(string commandLine) + private static string[] SplitCommandLine(string commandLine) { var result = new List(); - bool inQuotes = false; + var inQuotes = false; StringBuilder currentArg = new(); - for (int i = 0; i < commandLine.Length; i++) + foreach (var c in commandLine) { - char c = commandLine[i]; - - if (c == '"') + switch (c) { - inQuotes = !inQuotes; - // Don't include the quote character - } - else if (c == ' ' && !inQuotes) - { - // End of argument - if (currentArg.Length > 0) + case '"': + inQuotes = !inQuotes; + // Don't include the quote character + break; + case ' ' when !inQuotes: { + // End of argument + if (currentArg.Length <= 0) continue; result.Add(currentArg.ToString()); currentArg.Clear(); + break; } - } - else - { - currentArg.Append(c); + default: + currentArg.Append(c); + break; } } @@ -186,8 +183,8 @@ namespace FireflyClient return [.. result]; } - - static void FormatAndPrintResponse(string response) + + private static void FormatAndPrintResponse(string response) { // Remove trailing whitespace response = response.TrimEnd(); @@ -195,23 +192,20 @@ namespace FireflyClient if (response.StartsWith('*') && response.Contains("\r\n")) { // Format array responses for better readability - string[] parts = response.Split("\r\n"); + var parts = response.Split("\r\n"); if (parts.Length > 1) { - Console.WriteLine("Server: Array response:"); - for (int i = 1; i < parts.Length; i++) + for (var i = 1; i < parts.Length; i++) { - if (!string.IsNullOrEmpty(parts[i])) + if (string.IsNullOrEmpty(parts[i])) continue; + // For simple string and bulk string responses in an array + if (parts[i].StartsWith('+') || parts[i].StartsWith('$')) { - // For simple string and bulk string responses in an array - if (parts[i].StartsWith('+') || parts[i].StartsWith('$')) - { - Console.WriteLine($"{i}) {parts[i][1..]}"); - } - else if (!parts[i].StartsWith('*')) // Skip the array count line - { - Console.WriteLine($"{i}) {parts[i]}"); - } + Console.WriteLine($"{i}) {parts[i][1..]}"); + } + else if (!parts[i].StartsWith('*')) // Skip the array count line + { + Console.WriteLine($"{i}) {parts[i]}"); } } return; @@ -219,11 +213,11 @@ namespace FireflyClient } // Default formatting - Console.WriteLine($"Server: {response}"); + Console.WriteLine($"{response}"); } // Helper method: Print basic commands - static void PrintBasicCommands() + private static void PrintBasicCommands() { Console.WriteLine("\n==== Basic Firefly Commands ===="); diff --git a/src/StringOperations.cs b/src/StringOperations.cs index de56e3c..54d08ed 100644 --- a/src/StringOperations.cs +++ b/src/StringOperations.cs @@ -7,39 +7,29 @@ namespace FireflyClient /// /// Sets a key-value pair /// - public bool StringSet(string key, string value) + private bool StringSet(string key, string value) { - string response = ExecuteCommand("SET", key, value); + var response = ExecuteCommand("SET", key, value); return response.StartsWith("+OK"); } /// /// Gets a value by key /// - public string StringGet(string key) + private string StringGet(string key) { - string response = ExecuteCommand("GET", key); - if (response.StartsWith('+')) - { - return response[1..].TrimEnd('\r', '\n'); - } - return string.Empty; + var response = ExecuteCommand("GET", key); + return response.StartsWith('+') ? response[1..].TrimEnd('\r', '\n') : string.Empty; } /// /// Deletes a key from all stores (string, list, hash) /// - public int Delete(string key) + private int Delete(string key) { - string response = ExecuteCommand("DEL", key); - if (response.StartsWith(':')) - { - if (int.TryParse(response[1..].TrimEnd('\r', '\n'), out int result)) - { - return result; - } - } - return 0; + var response = ExecuteCommand("DEL", key); + if (!response.StartsWith(':')) return 0; + return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : 0; } #endregion