feat(native): Enhance native interop and client API
Some checks failed
Build / build (push) Failing after 1m18s
Some checks failed
Build / build (push) Failing after 1m18s
This commit introduces several enhancements to the native interop layer and the client API, focusing on improved data handling and memory management. - Modified `ListRange` and `HashGetAll` to return `StringArray` and `Dictionary` structures respectively, instead of newline-delimited strings. This change provides more structured data to native clients and avoids the need for manual parsing. - Added `ListLength` to retrieve the length of a list. - Introduced `NativeStringList` and `NativeDictionary` structures for interop, along with marshalling and unmarshalling methods (`MarshalStringList`, `MarshalDictionary`, `NativeFreeStringList`, `NativeFreeDictionary`) to facilitate data exchange between C# and native code. - Updated the `firefly.h` header file with C header definitions for the new structures and functions. - Changed access modifiers of several methods in `StringOperations.cs`, `HashOperations.cs`, `ListOperations.cs` and `FireflyClient.cs` to private. - Updated documentation to reflect the API changes. - Updated the command-line interface to use more modern C# syntax. These changes improve the usability and efficiency of the Firefly client for native applications, while also improving the internal code structure.
This commit is contained in:
parent
1d83563a07
commit
ab68dcc79b
13
.idea/.idea.FireflyClient/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.FireflyClient/.idea/.gitignore
generated
vendored
Normal file
@ -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
|
1
.idea/.idea.FireflyClient/.idea/.name
generated
Normal file
1
.idea/.idea.FireflyClient/.idea/.name
generated
Normal file
@ -0,0 +1 @@
|
||||
FireflyClient
|
4
.idea/.idea.FireflyClient/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.FireflyClient/.idea/encodings.xml
generated
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||
</project>
|
8
.idea/.idea.FireflyClient/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.FireflyClient/.idea/indexLayout.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
6
.idea/.idea.FireflyClient/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.FireflyClient/.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -161,6 +161,11 @@
|
||||
Removes and returns the last element of a list
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.ListLength(System.String)">
|
||||
<summary>
|
||||
Gets the length of a list
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.ListRange(System.String,System.Int32,System.Int32)">
|
||||
<summary>
|
||||
Gets a range of elements from a list
|
||||
@ -191,6 +196,61 @@
|
||||
Removes elements equal to the given value from a list
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:FireflyClient.FireflyClient.NativeStringList">
|
||||
<summary>
|
||||
String array structure for interop
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeStringList.Strings">
|
||||
<summary>
|
||||
Pointer to an array of string pointers
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeStringList.Count">
|
||||
<summary>
|
||||
Number of strings in the array
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:FireflyClient.FireflyClient.NativeKeyValuePair">
|
||||
<summary>
|
||||
Key-value pair structure for interop
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeKeyValuePair.Key">
|
||||
<summary>
|
||||
Pointer to the key string
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeKeyValuePair.Value">
|
||||
<summary>
|
||||
Pointer to the value string
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:FireflyClient.FireflyClient.NativeDictionary">
|
||||
<summary>
|
||||
Dictionary structure for interop
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeDictionary.Pairs">
|
||||
<summary>
|
||||
Pointer to an array of key-value pairs
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeDictionary.Count">
|
||||
<summary>
|
||||
Number of pairs in the array
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.MarshalStringList(System.Collections.Generic.List{System.String})">
|
||||
<summary>
|
||||
Marshals a List to a NativeStringList without string conversion
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.MarshalDictionary(System.Collections.Generic.Dictionary{System.String,System.String})">
|
||||
<summary>
|
||||
Marshals a Dictionary to a NativeDictionary without string conversion
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.CreateClient(System.IntPtr,System.Int32)">
|
||||
<summary>
|
||||
Creates a new FireflyClient instance for native interop
|
||||
@ -217,6 +277,16 @@
|
||||
Frees a string allocated by the native interop methods
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.NativeFreeStringList(FireflyClient.FireflyClient.NativeStringList)">
|
||||
<summary>
|
||||
Frees a NativeStringList allocated by the native interop methods
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.NativeFreeDictionary(FireflyClient.FireflyClient.NativeDictionary)">
|
||||
<summary>
|
||||
Frees a NativeDictionary allocated by the native interop methods
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.NativeStringSet(System.IntPtr,System.IntPtr,System.IntPtr)">
|
||||
<summary>
|
||||
Sets a string value for a given key (Native Interop).
|
||||
@ -277,6 +347,14 @@
|
||||
<param name="keyPtr">Pointer to a null-terminated UTF-8 string representing the list key.</param>
|
||||
<returns>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.</returns>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.NativeListLength(System.IntPtr,System.IntPtr)">
|
||||
<summary>
|
||||
Gets the length of a list (Native Interop).
|
||||
</summary>
|
||||
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
|
||||
<param name="keyPtr">Pointer to a null-terminated UTF-8 string representing the list key.</param>
|
||||
<returns>The length of the list, or 0 on error.</returns>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.NativeListRange(System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
|
||||
<summary>
|
||||
Gets a range of elements from a list (Native Interop).
|
||||
|
Binary file not shown.
Binary file not shown.
@ -161,6 +161,11 @@
|
||||
Removes and returns the last element of a list
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.ListLength(System.String)">
|
||||
<summary>
|
||||
Gets the length of a list
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.ListRange(System.String,System.Int32,System.Int32)">
|
||||
<summary>
|
||||
Gets a range of elements from a list
|
||||
@ -191,6 +196,61 @@
|
||||
Removes elements equal to the given value from a list
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:FireflyClient.FireflyClient.NativeStringList">
|
||||
<summary>
|
||||
String array structure for interop
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeStringList.Strings">
|
||||
<summary>
|
||||
Pointer to an array of string pointers
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeStringList.Count">
|
||||
<summary>
|
||||
Number of strings in the array
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:FireflyClient.FireflyClient.NativeKeyValuePair">
|
||||
<summary>
|
||||
Key-value pair structure for interop
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeKeyValuePair.Key">
|
||||
<summary>
|
||||
Pointer to the key string
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeKeyValuePair.Value">
|
||||
<summary>
|
||||
Pointer to the value string
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:FireflyClient.FireflyClient.NativeDictionary">
|
||||
<summary>
|
||||
Dictionary structure for interop
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeDictionary.Pairs">
|
||||
<summary>
|
||||
Pointer to an array of key-value pairs
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:FireflyClient.FireflyClient.NativeDictionary.Count">
|
||||
<summary>
|
||||
Number of pairs in the array
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.MarshalStringList(System.Collections.Generic.List{System.String})">
|
||||
<summary>
|
||||
Marshals a List to a NativeStringList without string conversion
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.MarshalDictionary(System.Collections.Generic.Dictionary{System.String,System.String})">
|
||||
<summary>
|
||||
Marshals a Dictionary to a NativeDictionary without string conversion
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.CreateClient(System.IntPtr,System.Int32)">
|
||||
<summary>
|
||||
Creates a new FireflyClient instance for native interop
|
||||
@ -217,6 +277,16 @@
|
||||
Frees a string allocated by the native interop methods
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.NativeFreeStringList(FireflyClient.FireflyClient.NativeStringList)">
|
||||
<summary>
|
||||
Frees a NativeStringList allocated by the native interop methods
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.NativeFreeDictionary(FireflyClient.FireflyClient.NativeDictionary)">
|
||||
<summary>
|
||||
Frees a NativeDictionary allocated by the native interop methods
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.NativeStringSet(System.IntPtr,System.IntPtr,System.IntPtr)">
|
||||
<summary>
|
||||
Sets a string value for a given key (Native Interop).
|
||||
@ -277,6 +347,14 @@
|
||||
<param name="keyPtr">Pointer to a null-terminated UTF-8 string representing the list key.</param>
|
||||
<returns>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.</returns>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.NativeListLength(System.IntPtr,System.IntPtr)">
|
||||
<summary>
|
||||
Gets the length of a list (Native Interop).
|
||||
</summary>
|
||||
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
|
||||
<param name="keyPtr">Pointer to a null-terminated UTF-8 string representing the list key.</param>
|
||||
<returns>The length of the list, or 0 on error.</returns>
|
||||
</member>
|
||||
<member name="M:FireflyClient.FireflyClient.NativeListRange(System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
|
||||
<summary>
|
||||
Gets a range of elements from a list (Native Interop).
|
||||
|
Binary file not shown.
Binary file not shown.
52
firefly.h
52
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 ---
|
||||
|
||||
/**
|
||||
|
@ -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<string> _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
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
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
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// Enables or disables pipeline mode
|
||||
/// </summary>
|
||||
public void SetPipelineMode(bool enabled)
|
||||
private void SetPipelineMode(bool enabled)
|
||||
{
|
||||
_isPipelineMode = enabled;
|
||||
if (!enabled)
|
||||
@ -152,7 +145,7 @@ namespace FireflyClient
|
||||
/// <summary>
|
||||
/// Flushes any queued commands in pipeline mode
|
||||
/// </summary>
|
||||
public string FlushPipeline()
|
||||
private string FlushPipeline()
|
||||
{
|
||||
return ProcessCommandQueue();
|
||||
}
|
||||
@ -160,7 +153,7 @@ namespace FireflyClient
|
||||
/// <summary>
|
||||
/// Sets the maximum number of commands to batch before sending
|
||||
/// </summary>
|
||||
public void SetBatchSize(int size)
|
||||
private void SetBatchSize(int size)
|
||||
{
|
||||
if (size <= 0)
|
||||
{
|
||||
@ -172,17 +165,17 @@ namespace FireflyClient
|
||||
/// <summary>
|
||||
/// Gets the current number of queued commands
|
||||
/// </summary>
|
||||
public int QueuedCommandCount => _commandQueue.Count;
|
||||
private int QueuedCommandCount => _commandQueue.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether pipeline mode is enabled
|
||||
/// </summary>
|
||||
public bool IsPipelineMode => _isPipelineMode;
|
||||
private bool IsPipelineMode => _isPipelineMode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum batch size
|
||||
/// </summary>
|
||||
public int MaxBatchSize => _maxBatchSize;
|
||||
private int MaxBatchSize => _maxBatchSize;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the client is connected to the server
|
||||
@ -199,18 +192,18 @@ namespace FireflyClient
|
||||
/// </summary>
|
||||
/// <param name="pattern">Pattern to match against keys. Use * for wildcard matches.</param>
|
||||
/// <returns>List of matching keys, or empty list if none found or on error</returns>
|
||||
public List<string> Keys(string pattern = "*")
|
||||
private List<string> 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
|
||||
/// <summary>
|
||||
/// Parses an array response from the server
|
||||
/// </summary>
|
||||
protected static List<string> ParseArrayResponse(string response)
|
||||
private static List<string> ParseArrayResponse(string response)
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
||||
@ -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('$')))
|
||||
{
|
||||
|
@ -7,53 +7,49 @@ namespace FireflyClient
|
||||
/// <summary>
|
||||
/// Sets a field in a hash
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a field from a hash
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a field from a hash
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a field exists in a hash
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all fields and values from a hash
|
||||
/// </summary>
|
||||
public Dictionary<string, string> HashGetAll(string key)
|
||||
private Dictionary<string, string> HashGetAll(string key)
|
||||
{
|
||||
string response = ExecuteCommand("HGETALL", key);
|
||||
List<string> items = ParseArrayResponse(response);
|
||||
var response = ExecuteCommand("HGETALL", key);
|
||||
var items = ParseArrayResponse(response);
|
||||
|
||||
var result = new Dictionary<string, string>();
|
||||
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
|
||||
/// <summary>
|
||||
/// Sets multiple fields in a hash at once
|
||||
/// </summary>
|
||||
public bool HashMultiSet(string key, Dictionary<string, string> fieldValues)
|
||||
private bool HashMultiSet(string key, Dictionary<string, string> fieldValues)
|
||||
{
|
||||
List<string> 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");
|
||||
}
|
||||
|
||||
|
@ -7,102 +7,88 @@ namespace FireflyClient
|
||||
/// <summary>
|
||||
/// Adds values to the beginning of a list
|
||||
/// </summary>
|
||||
public int ListLeftPush(string key, params string[] values)
|
||||
private int ListLeftPush(string key, params string[] values)
|
||||
{
|
||||
var args = new List<string> { 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds values to the end of a list
|
||||
/// </summary>
|
||||
public int ListRightPush(string key, params string[] values)
|
||||
private int ListRightPush(string key, params string[] values)
|
||||
{
|
||||
var args = new List<string> { 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes and returns the first element of a list
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes and returns the last element of a list
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of a list
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a range of elements from a list
|
||||
/// </summary>
|
||||
public List<string> ListRange(string key, int start, int stop)
|
||||
private List<string> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at the specified index in a list
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the element at the specified index in a list
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the first occurrence of an element in a list
|
||||
/// </summary>
|
||||
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<string> { 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trims a list to the specified range
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes elements equal to the given value from a list
|
||||
/// </summary>
|
||||
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
|
||||
|
@ -5,13 +5,66 @@ namespace FireflyClient
|
||||
{
|
||||
public partial class FireflyClient
|
||||
{
|
||||
// Native structure definitions for interop
|
||||
|
||||
/// <summary>
|
||||
/// String array structure for interop
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct NativeStringList
|
||||
{
|
||||
/// <summary>
|
||||
/// Pointer to an array of string pointers
|
||||
/// </summary>
|
||||
public IntPtr Strings;
|
||||
|
||||
/// <summary>
|
||||
/// Number of strings in the array
|
||||
/// </summary>
|
||||
public int Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key-value pair structure for interop
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct NativeKeyValuePair
|
||||
{
|
||||
/// <summary>
|
||||
/// Pointer to the key string
|
||||
/// </summary>
|
||||
public IntPtr Key;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the value string
|
||||
/// </summary>
|
||||
public IntPtr Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary structure for interop
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct NativeDictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// Pointer to an array of key-value pairs
|
||||
/// </summary>
|
||||
public IntPtr Pairs;
|
||||
|
||||
/// <summary>
|
||||
/// Number of pairs in the array
|
||||
/// </summary>
|
||||
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<string> result to IntPtr (newline-delimited)
|
||||
private static IntPtr MarshalStringListResult(List<string>? 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<string, string> result to IntPtr (newline-delimited field=value)
|
||||
private static IntPtr MarshalStringDictionaryResult(Dictionary<string, string>? result)
|
||||
|
||||
/// <summary>
|
||||
/// Marshals a List to a NativeStringList without string conversion
|
||||
/// </summary>
|
||||
private static NativeStringList MarshalStringList(List<string>? 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 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marshals a Dictionary to a NativeDictionary without string conversion
|
||||
/// </summary>
|
||||
private static NativeDictionary MarshalDictionary(Dictionary<string, string>? 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<NativeKeyValuePair>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Frees a NativeStringList allocated by the native interop methods
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees a NativeDictionary allocated by the native interop methods
|
||||
/// </summary>
|
||||
[UnmanagedCallersOnly(EntryPoint = "FreeDictionary")]
|
||||
public static void NativeFreeDictionary(NativeDictionary dict)
|
||||
{
|
||||
if (dict.Pairs != IntPtr.Zero && dict.Count > 0)
|
||||
{
|
||||
var structSize = Marshal.SizeOf<NativeKeyValuePair>();
|
||||
|
||||
for (var i = 0; i < dict.Count; i++)
|
||||
{
|
||||
var pairPtr = dict.Pairs + i * structSize;
|
||||
var pair = Marshal.PtrToStructure<NativeKeyValuePair>(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 ---
|
||||
|
||||
/// <summary>
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of a list (Native Interop).
|
||||
/// </summary>
|
||||
/// <param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
|
||||
/// <param name="keyPtr">Pointer to a null-terminated UTF-8 string representing the list key.</param>
|
||||
/// <returns>The length of the list, or 0 on error.</returns>
|
||||
[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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a range of elements from a list (Native Interop).
|
||||
/// </summary>
|
||||
@ -303,17 +469,19 @@ namespace FireflyClient
|
||||
/// <returns>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.</returns>
|
||||
/// <remarks>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.</remarks>
|
||||
[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<string>? result = client?.ListRange(key, start, stop);
|
||||
// Marshal List<string> 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<string>? result = client?.ListRange(key, start, stop);
|
||||
return MarshalStringList(result);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new NativeStringList { Strings = IntPtr.Zero, Count = 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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<string, string>();
|
||||
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
|
||||
/// <returns>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.</returns>
|
||||
/// <remarks>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.</remarks>
|
||||
[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<string, string>? result = client?.HashGetAll(key);
|
||||
// Marshal Dictionary<string, string> 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<string, string>? 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<string>? result = client?.Keys(pattern);
|
||||
return MarshalStringListResult(result);
|
||||
}
|
||||
|
124
src/Program.cs
124
src/Program.cs
@ -7,9 +7,9 @@ namespace FireflyClient
|
||||
/// <summary>
|
||||
/// Command-line interface for Firefly
|
||||
/// </summary>
|
||||
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 <hostname> 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<string>();
|
||||
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 ====");
|
||||
|
||||
|
@ -7,39 +7,29 @@ namespace FireflyClient
|
||||
/// <summary>
|
||||
/// Sets a key-value pair
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value by key
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a key from all stores (string, list, hash)
|
||||
/// </summary>
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user