
Some checks failed
Build / build (push) Failing after 1m14s
This commit removes the pipeline-related function definitions from the `FireflyClientPy` class in the `README.md` file. These functions are no longer needed in the Python client as pipeline operations are handled differently. The removed functions include: - `SetPipelineMode` - `SetBatchSize` - `FlushPipeline` - `GetQueuedCommandCount` - `IsPipelineMode` - `GetBatchSize`
608 lines
24 KiB
Markdown
608 lines
24 KiB
Markdown
# Firefly Client Library
|
|
|
|
A C# client library for interacting with the Firefly Redis-compatible server. This library provides a clean API for storing and retrieving data from Firefly servers, handling all the protocol details for you.
|
|
|
|
## Features
|
|
|
|
- Simple API for common Redis/Firefly operations (strings, lists, hashes)
|
|
- Automatic handling of quotes and special characters in commands
|
|
- Built-in connection management and authentication
|
|
- Optional Native Interop library for use from C, C++, Python, etc.
|
|
- Designed with thread-safety in mind (see Thread Safety section)
|
|
|
|
## Usage
|
|
|
|
### As a Console Application
|
|
|
|
Simply run the FireflyClient executable (if built as an executable) to connect to a Firefly server and execute commands interactively:
|
|
|
|
```bash
|
|
# Example (replace FireflyClient.exe with actual executable name/path)
|
|
./FireflyClient --host 127.0.0.1 --port 6379 --password yourpassword
|
|
```
|
|
|
|
### As a C# Library
|
|
|
|
#### Installation
|
|
|
|
Reference the FireflyClient library in your .NET project:
|
|
|
|
1. Add a reference to the compiled `FireflyClient.dll`.
|
|
2. Or add a project reference if working in the same solution.
|
|
|
|
#### Basic Usage
|
|
|
|
```csharp
|
|
using FireflyClient; // Namespace
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
// Create a client within a using statement for proper disposal
|
|
// Connects to localhost:6379 by default
|
|
using (var client = new FireflyClient("127.0.0.1", 6379))
|
|
{
|
|
try
|
|
{
|
|
// Authenticate if needed
|
|
// bool authResult = client.Authenticate("yourpassword");
|
|
// if (!authResult) {
|
|
// Console.WriteLine("Authentication failed.");
|
|
// return;
|
|
// }
|
|
|
|
// Store a string
|
|
client.StringSet("mykey", "Hello C# world!");
|
|
|
|
// Retrieve a string
|
|
string value = client.StringGet("mykey");
|
|
Console.WriteLine($"GET mykey: {value}");
|
|
|
|
// Work with lists
|
|
client.ListRightPush("mylist", "item1"); // Can take multiple values
|
|
client.ListRightPush("mylist", "item2", "item3");
|
|
List<string> items = client.ListRange("mylist", 0, -1);
|
|
Console.WriteLine($"LRANGE mylist: {string.Join(", ", items)}");
|
|
|
|
// Work with hashes
|
|
client.HashSet("user:1", "name", "Alice");
|
|
client.HashSet("user:1", "email", "alice@example.com");
|
|
Dictionary<string, string> userData = client.HashGetAll("user:1");
|
|
Console.WriteLine("HGETALL user:1:");
|
|
foreach(var kvp in userData)
|
|
{
|
|
Console.WriteLine($" {kvp.Key}: {kvp.Value}");
|
|
}
|
|
|
|
// Use HMSET
|
|
var moreData = new Dictionary<string, string> { { "city", "Csharpville" }, { "zip", "98765" } };
|
|
client.HashMultiSet("user:1", moreData);
|
|
Console.WriteLine($"HMSET city/zip for user:1");
|
|
|
|
string city = client.HashGet("user:1", "city");
|
|
Console.WriteLine($"HGET user:1 city: {city}");
|
|
|
|
// Delete keys
|
|
client.Delete("mykey");
|
|
client.Delete("mylist");
|
|
client.Delete("user:1");
|
|
Console.WriteLine("Deleted keys.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"An error occurred: {ex.Message}");
|
|
}
|
|
} // client.Dispose() is automatically called here
|
|
```
|
|
|
|
#### Error Handling
|
|
|
|
Operations can throw exceptions (e.g., `SocketException` for connection issues, `InvalidOperationException` for protocol errors). Always use try-catch blocks for robust applications.
|
|
|
|
## C# API Reference (Partial)
|
|
|
|
This assumes the `FireflyClient` object is named `client`.
|
|
|
|
### Client Management
|
|
- `new FireflyClient(host, port)`: Constructor to create and connect.
|
|
- `new FireflyClient(host, port, password)`: Constructor to create, connect, and authenticate.
|
|
- `client.Dispose()`: Disconnects and releases resources (called automatically by `using`).
|
|
- `client.Authenticate(password)`: Authenticates an existing connection.
|
|
- `client.IsConnected`: Property (bool) to check connection status.
|
|
- `client.IsAuthenticated`: Property (bool) to check authentication status.
|
|
|
|
### String Operations
|
|
- `client.StringSet(key, value)`: Sets a string value.
|
|
- `client.StringGet(key)`: Gets a string value.
|
|
- `client.Delete(key)`: Deletes a key (works for any type).
|
|
|
|
### List Operations
|
|
- `client.ListLeftPush(key, value, ...)`: Inserts value(s) at the head.
|
|
- `client.ListRightPush(key, value, ...)`: Appends value(s) to the tail.
|
|
- `client.ListLeftPop(key)`: Removes and returns the first element.
|
|
- `client.ListRightPop(key)`: Removes and returns the last element.
|
|
- `client.ListRange(key, start, stop)`: Gets a range of elements.
|
|
- `client.ListIndex(key, index)`: Gets element at index.
|
|
- `client.ListSet(key, index, value)`: Sets element at index.
|
|
- `client.ListPosition(key, element, rank, maxlen)`: Finds position of element.
|
|
- `client.ListTrim(key, start, stop)`: Trims list to specified range.
|
|
- `client.ListRemove(key, count, element)`: Removes elements from list.
|
|
|
|
### Hash Operations
|
|
- `client.HashSet(key, field, value)`: Sets a field in a hash.
|
|
- `client.HashGet(key, field)`: Gets a field from a hash.
|
|
- `client.HashDelete(key, field)`: Deletes a field from a hash.
|
|
- `client.HashFieldExists(key, field)`: Checks if a field exists.
|
|
- `client.HashMultiSet(key, dictionary)`: Sets multiple fields from a Dictionary.
|
|
- `client.HashGetAll(key)`: Gets all fields and values as a Dictionary.
|
|
|
|
### Pipeline Operations
|
|
- `client.SetPipelineMode(enabled)`: Enables/disables pipeline mode.
|
|
- `client.SetBatchSize(size)`: Sets the command batch size for pipelining.
|
|
- `client.FlushPipeline()`: Sends all queued commands.
|
|
- `client.QueuedCommandCount`: Property (int) to get queued command count.
|
|
- `client.IsPipelineMode`: Property (bool) to check pipeline mode.
|
|
- `client.MaxBatchSize`: Property (int) to get max batch size.
|
|
|
|
### Server Operations (Example - verify actual methods)
|
|
- `client.ExecuteCommand(command, args)`: Executes a raw command.
|
|
- `client.Ping()`: Checks server responsiveness.
|
|
- `client.Save()`: Requests a synchronous save.
|
|
- `client.BackgroundSave()`: Requests an asynchronous save.
|
|
|
|
*(Note: This API list is based on common patterns and native functions. Verify actual C# method names and signatures.)*
|
|
|
|
## Native Interop Library (`firefly.h` API)
|
|
|
|
A native C API is provided for using the client from non-.NET languages.
|
|
|
|
### Prerequisites
|
|
|
|
- **Library Build:** You need the compiled shared library (`libFireflyClient.dll` on Windows, `libFireflyClient.so` on Linux/macOS).
|
|
- **Header:** You need the `firefly.h` header file.
|
|
- **Runtime:** The target machine needs the appropriate .NET Runtime installed (e.g., .NET 9.0 as specified during build).
|
|
|
|
### Building the Native Library
|
|
|
|
Use `dotnet publish` with the appropriate Runtime Identifier (RID) and configuration. Example publish profiles might be provided (`NativeLinux`, `NativeWin`).
|
|
|
|
```bash
|
|
# Example Linux Build
|
|
dotnet publish -c Release -r linux-x64 --self-contained false # Requires .NET Runtime
|
|
# Or self-contained (larger size):
|
|
# dotnet publish -c Release -r linux-x64 --self-contained true
|
|
|
|
# Example Windows Build
|
|
dotnet publish -c Release -r win-x64 --self-contained false
|
|
```
|
|
*(Adjust RID and configuration as needed. Output will be in `bin/Release/net9.0/<RID>/publish`)*
|
|
|
|
### Usage (C/C++)
|
|
|
|
```c
|
|
#include "firefly.h" // Make sure this path is correct
|
|
#include <stdio.h>
|
|
#include <stdlib.h> // For exit
|
|
#include <string.h> // For string processing
|
|
|
|
// Simple helper to split string by newline
|
|
void process_multiline_string(const char* str, const char* prefix) {
|
|
if (!str) return;
|
|
char* buffer = strdup(str);
|
|
if (!buffer) return;
|
|
char* line = strtok(buffer, "\n");
|
|
while (line != NULL) {
|
|
printf("%s%s\n", prefix, line);
|
|
line = strtok(NULL, "\n");
|
|
}
|
|
free(buffer);
|
|
}
|
|
|
|
// Simple helper to parse field=value string
|
|
void process_hash_string(const char* str, const char* prefix) {
|
|
if (!str) return;
|
|
char* buffer = strdup(str);
|
|
if (!buffer) return;
|
|
char* line = strtok(buffer, "\n");
|
|
while (line != NULL) {
|
|
char* eq = strchr(line, '=');
|
|
if (eq) {
|
|
*eq = '\0'; // Split string at '='
|
|
printf("%s%s: %s\n", prefix, line, eq + 1);
|
|
}
|
|
line = strtok(NULL, "\n");
|
|
}
|
|
free(buffer);
|
|
}
|
|
|
|
int main() {
|
|
// Initialize client
|
|
void* client = CreateClient("127.0.0.1", 6379);
|
|
if (!client) {
|
|
fprintf(stderr, "Failed to create client\n");
|
|
return 1;
|
|
}
|
|
printf("Client created.\n");
|
|
|
|
// Authenticate (optional)
|
|
// if (!Authenticate(client, "your_password")) { ... }
|
|
|
|
// String operations
|
|
StringSet(client, "mykey", "Hello C world!");
|
|
char* value = StringGet(client, "mykey");
|
|
if (value) {
|
|
printf("StringGet: %s\n", value);
|
|
FreeString(value); // Free the returned string
|
|
}
|
|
|
|
// List operations
|
|
ListRightPush(client, "mylist", "item1");
|
|
ListRightPush(client, "mylist", "item2");
|
|
|
|
// Get list range (char* return)
|
|
char* range = ListRange(client, "mylist", 0, -1);
|
|
if (range) {
|
|
printf("ListRange:\n");
|
|
process_multiline_string(range, " - ");
|
|
FreeString(range); // Free the returned string
|
|
}
|
|
|
|
// Use other list ops...
|
|
ListSet(client, "mylist", 0, "item1_updated");
|
|
ListRemove(client, "mylist", 1, "item2");
|
|
|
|
// Hash operations
|
|
HashSet(client, "user:1", "name", "Alice C");
|
|
HashMultiSet(client, "user:1", "email alice.c@example.com city C-Town");
|
|
|
|
char* name = HashGet(client, "user:1", "name");
|
|
if (name) {
|
|
printf("HashGet Name: %s\n", name);
|
|
FreeString(name); // Free the returned string
|
|
}
|
|
|
|
// Get all hash fields (char* return)
|
|
char* hashData = HashGetAll(client, "user:1");
|
|
if (hashData) {
|
|
printf("HashGetAll:\n");
|
|
process_hash_string(hashData, " ");
|
|
FreeString(hashData); // Free the returned string
|
|
}
|
|
|
|
// Cleanup keys
|
|
Delete(client, "mykey");
|
|
Delete(client, "mylist");
|
|
Delete(client, "user:1");
|
|
|
|
// Cleanup client
|
|
printf("Destroying client.\n");
|
|
DestroyClient(client);
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
### Usage (Python)
|
|
|
|
```python
|
|
import ctypes
|
|
import os
|
|
import platform
|
|
from ctypes import c_char_p, c_int, c_void_p, c_bool # Use c_bool
|
|
|
|
# --- Define FireflyClient Python Wrapper Class ---
|
|
class FireflyClientPy:
|
|
def __init__(self, library_path=None, host="127.0.0.1", port=6379, password=None):
|
|
if library_path is None:
|
|
# Simple auto-detect based on OS (adjust path as needed)
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
if platform.system() == "Windows":
|
|
library_path = os.path.join(script_dir, "libFireflyClient.dll") # Assumes DLL is here
|
|
else:
|
|
library_path = os.path.join(script_dir, "libFireflyClient.so") # Assumes SO is here
|
|
|
|
if not os.path.exists(library_path):
|
|
raise FileNotFoundError(f"Shared library not found: {library_path}")
|
|
|
|
self.lib = ctypes.CDLL(library_path)
|
|
self._define_signatures()
|
|
|
|
self.client_handle = self.lib.CreateClient(host.encode('utf-8'), port)
|
|
if not self.client_handle:
|
|
raise ConnectionError(f"Failed to create client for {host}:{port}")
|
|
|
|
if password:
|
|
if not self.lib.Authenticate(self.client_handle, password.encode('utf-8')):
|
|
self.lib.DestroyClient(self.client_handle)
|
|
raise ConnectionError("Authentication failed")
|
|
|
|
def _define_signatures(self):
|
|
# Client Management
|
|
self.lib.CreateClient.argtypes = [c_char_p, c_int]
|
|
self.lib.CreateClient.restype = c_void_p
|
|
self.lib.DestroyClient.argtypes = [c_void_p]
|
|
self.lib.DestroyClient.restype = None
|
|
self.lib.Authenticate.argtypes = [c_void_p, c_char_p]
|
|
self.lib.Authenticate.restype = c_bool
|
|
|
|
# String Ops
|
|
self.lib.StringSet.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
self.lib.StringSet.restype = c_bool
|
|
self.lib.StringGet.argtypes = [c_void_p, c_char_p]
|
|
self.lib.StringGet.restype = c_char_p
|
|
self.lib.Delete.argtypes = [c_void_p, c_char_p]
|
|
self.lib.Delete.restype = c_int
|
|
self.lib.FreeString.argtypes = [c_char_p] # Keep c_char_p, ctypes handles IntPtr
|
|
self.lib.FreeString.restype = None
|
|
|
|
# List Ops
|
|
self.lib.ListLeftPush.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
self.lib.ListLeftPush.restype = c_int
|
|
self.lib.ListRightPush.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
self.lib.ListRightPush.restype = c_int
|
|
self.lib.ListLeftPop.argtypes = [c_void_p, c_char_p]
|
|
self.lib.ListLeftPop.restype = c_char_p
|
|
self.lib.ListRightPop.argtypes = [c_void_p, c_char_p]
|
|
self.lib.ListRightPop.restype = c_char_p
|
|
self.lib.ListRange.argtypes = [c_void_p, c_char_p, c_int, c_int]
|
|
self.lib.ListRange.restype = c_char_p
|
|
self.lib.ListIndex.argtypes = [c_void_p, c_char_p, c_int]
|
|
self.lib.ListIndex.restype = c_char_p
|
|
self.lib.ListSet.argtypes = [c_void_p, c_char_p, c_int, c_char_p]
|
|
self.lib.ListSet.restype = c_bool
|
|
self.lib.ListPosition.argtypes = [c_void_p, c_char_p, c_char_p, c_int, c_int]
|
|
self.lib.ListPosition.restype = c_int
|
|
self.lib.ListTrim.argtypes = [c_void_p, c_char_p, c_int, c_int]
|
|
self.lib.ListTrim.restype = c_bool
|
|
self.lib.ListRemove.argtypes = [c_void_p, c_char_p, c_int, c_char_p]
|
|
self.lib.ListRemove.restype = c_int
|
|
|
|
# Hash Ops
|
|
self.lib.HashSet.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p]
|
|
self.lib.HashSet.restype = c_bool
|
|
self.lib.HashGet.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
self.lib.HashGet.restype = c_char_p
|
|
self.lib.HashDelete.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
self.lib.HashDelete.restype = c_bool
|
|
self.lib.HashFieldExists.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
self.lib.HashFieldExists.restype = c_bool
|
|
self.lib.HashMultiSet.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
self.lib.HashMultiSet.restype = c_bool
|
|
self.lib.HashGetAll.argtypes = [c_void_p, c_char_p]
|
|
self.lib.HashGetAll.restype = c_char_p
|
|
|
|
# Raw Command
|
|
self.lib.ExecuteCommand.argtypes = [c_void_p, c_char_p, c_char_p]
|
|
self.lib.ExecuteCommand.restype = c_char_p
|
|
|
|
def close(self):
|
|
if self.client_handle:
|
|
self.lib.DestroyClient(self.client_handle)
|
|
self.client_handle = None
|
|
|
|
def _call_get_string(self, func, key):
|
|
key_b = key.encode('utf-8')
|
|
result_ptr = func(self.client_handle, key_b)
|
|
if result_ptr:
|
|
value = ctypes.cast(result_ptr, c_char_p).value.decode('utf-8')
|
|
self.lib.FreeString(result_ptr)
|
|
return value
|
|
return None
|
|
|
|
def _parse_and_free_list(self, result_ptr):
|
|
if result_ptr:
|
|
full_string = ctypes.cast(result_ptr, c_char_p).value.decode('utf-8')
|
|
self.lib.FreeString(result_ptr)
|
|
return full_string.split('\n') if full_string else []
|
|
return []
|
|
|
|
def _parse_and_free_hash(self, result_ptr):
|
|
data = {}
|
|
if result_ptr:
|
|
full_string = ctypes.cast(result_ptr, c_char_p).value.decode('utf-8')
|
|
self.lib.FreeString(result_ptr)
|
|
if full_string:
|
|
pairs = full_string.split('\n')
|
|
for pair in pairs:
|
|
if '=' in pair:
|
|
field, value = pair.split('=', 1)
|
|
data[field] = value
|
|
return data
|
|
|
|
# --- Public Methods ---
|
|
def string_set(self, key, value):
|
|
return self.lib.StringSet(self.client_handle, key.encode('utf-8'), value.encode('utf-8'))
|
|
|
|
def string_get(self, key):
|
|
return self._call_get_string(self.lib.StringGet, key)
|
|
|
|
def list_right_push(self, key, value):
|
|
return self.lib.ListRightPush(self.client_handle, key.encode('utf-8'), value.encode('utf-8'))
|
|
|
|
def list_range(self, key, start, stop):
|
|
result_ptr = self.lib.ListRange(self.client_handle, key.encode('utf-8'), start, stop)
|
|
return self._parse_and_free_list(result_ptr)
|
|
|
|
def hash_set(self, key, field, value):
|
|
return self.lib.HashSet(self.client_handle, key.encode('utf-8'), field.encode('utf-8'), value.encode('utf-8'))
|
|
|
|
def hash_get_all(self, key):
|
|
result_ptr = self.lib.HashGetAll(self.client_handle, key.encode('utf-8'))
|
|
return self._parse_and_free_hash(result_ptr)
|
|
|
|
# Add other methods here following the pattern...
|
|
def list_left_pop(self, key):
|
|
return self._call_get_string(self.lib.ListLeftPop, key)
|
|
|
|
def hash_get(self, key, field):
|
|
key_b = key.encode('utf-8')
|
|
field_b = field.encode('utf-8')
|
|
result_ptr = self.lib.HashGet(self.client_handle, key_b, field_b)
|
|
if result_ptr:
|
|
value = ctypes.cast(result_ptr, c_char_p).value.decode('utf-8')
|
|
self.lib.FreeString(result_ptr)
|
|
return value
|
|
return None
|
|
|
|
def hash_multi_set(self, key, field_value_dict):
|
|
pairs_str = " ".join([f"{k} {v}" for k, v in field_value_dict.items()])
|
|
return self.lib.HashMultiSet(self.client_handle, key.encode('utf-8'), pairs_str.encode('utf-8'))
|
|
|
|
def delete(self, key):
|
|
return self.lib.Delete(self.client_handle, key.encode('utf-8'))
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
self.close()
|
|
|
|
# --- Example Usage ---
|
|
if __name__ == "__main__":
|
|
try:
|
|
# Assumes library is in the same directory or accessible via path
|
|
# Use context manager for cleanup
|
|
with FireflyClientPy(host="127.0.0.1", port=6379) as client:
|
|
print("Python client created.")
|
|
# Authenticate if needed: client.lib.Authenticate(...) after creation
|
|
|
|
# String operations
|
|
client.string_set("py:mykey", "Hello Python world!")
|
|
value = client.string_get("py:mykey")
|
|
print(f"StringGet: {value}")
|
|
|
|
# List operations
|
|
client.list_right_push("py:mylist", "item1")
|
|
client.list_right_push("py:mylist", "item2")
|
|
|
|
# Get list range (updated parsing)
|
|
items = client.list_range("py:mylist", 0, -1)
|
|
print(f"ListRange: {items}")
|
|
|
|
# Use other list ops...
|
|
left_popped = client.list_left_pop("py:mylist")
|
|
print(f"ListLeftPop: {left_popped}")
|
|
|
|
# Hash operations
|
|
client.hash_set("py:user:1", "language", "Python")
|
|
client.hash_multi_set("py:user:1", {"version": "3.10", "framework": "None"})
|
|
|
|
lang = client.hash_get("py:user:1", "language")
|
|
print(f"HashGet language: {lang}")
|
|
|
|
# Get all hash fields (updated parsing)
|
|
hash_data = client.hash_get_all("py:user:1")
|
|
print(f"HashGetAll: {hash_data}")
|
|
|
|
# Cleanup
|
|
client.delete("py:mykey")
|
|
client.delete("py:mylist")
|
|
client.delete("py:user:1")
|
|
print("Cleaned up keys.")
|
|
|
|
except (FileNotFoundError, ConnectionError, Exception) as e:
|
|
print(f"Error: {e}")
|
|
|
|
print("Python example finished.")
|
|
|
|
```
|
|
|
|
## Native API Reference
|
|
|
|
This lists the functions exported by the native library (defined in `firefly.h`).
|
|
|
|
### Client Management
|
|
- `void* CreateClient(const char* host, int port)`
|
|
- `void DestroyClient(void* handle)`
|
|
- `bool Authenticate(void* handle, const char* password)`
|
|
|
|
### String Operations
|
|
- `bool StringSet(void* handle, const char* key, const char* value)`
|
|
- `char* StringGet(void* handle, const char* key)`
|
|
- `int Delete(void* handle, const char* key)`
|
|
|
|
### List Operations
|
|
- `int ListLeftPush(void* handle, const char* key, const char* value)`
|
|
- `int ListRightPush(void* handle, const char* key, const char* value)`
|
|
- `char* ListLeftPop(void* handle, const char* key)`
|
|
- `char* ListRightPop(void* handle, const char* key)`
|
|
- `char* ListRange(void* handle, const char* key, int start, int stop)`
|
|
- `char* ListIndex(void* handle, const char* key, int index)`
|
|
- `bool ListSet(void* handle, const char* key, int index, const char* value)`
|
|
- `int ListPosition(void* handle, const char* key, const char* element, int rank, int maxlen)`
|
|
- `bool ListTrim(void* handle, const char* key, int start, int stop)`
|
|
- `int ListRemove(void* handle, const char* key, int count, const char* element)`
|
|
|
|
### Hash Operations
|
|
- `bool HashSet(void* handle, const char* key, const char* field, const char* value)`
|
|
- `char* HashGet(void* handle, const char* key, const char* field)`
|
|
- `bool HashDelete(void* handle, const char* key, const char* field)`
|
|
- `bool HashFieldExists(void* handle, const char* key, const char* field)`
|
|
- `bool HashMultiSet(void* handle, const char* key, const char* fieldValuePairs)`
|
|
- `char* HashGetAll(void* handle, const char* key)`
|
|
|
|
### Raw Command Execution
|
|
- `char* ExecuteCommand(void* handle, const char* command, const char* args)`
|
|
|
|
### Pipeline Operations
|
|
- `bool SetPipelineMode(void* handle, bool enabled)`
|
|
- `bool SetBatchSize(void* handle, int size)`
|
|
- `char* FlushPipeline(void* handle)`
|
|
- `int GetQueuedCommandCount(void* handle)`
|
|
- `bool IsPipelineMode(void* handle)`
|
|
- `int GetBatchSize(void* handle)`
|
|
|
|
### Memory Management
|
|
- `void FreeString(char* str)`: **Crucial** - Must be called by the user to free the memory for any `char*` returned by the library functions (StringGet, ListLeftPop, ListRightPop, ListRange, ListIndex, HashGet, HashGetAll, ExecuteCommand, FlushPipeline).
|
|
|
|
### Error Handling
|
|
Most functions return `NULL` (for `char*`) or `false` / `0` / `-1` (for `bool`/`int`) on failure. The native caller should check these return values.
|
|
|
|
## Thread Safety
|
|
|
|
The library's native API is designed with thread safety in mind, relying on the underlying thread-safe mechanisms implemented in the C# `FireflyClient`.
|
|
|
|
- **Instance Handles (`void*`)**: Each handle returned by `CreateClient` represents an independent connection and state. Operations on different handles do not interfere.
|
|
- **Internal Locking**: The C# implementation is assumed to use appropriate locking (e.g., per-key locks for hashes/lists, connection locks) to allow safe concurrent calls on the *same* handle from multiple native threads.
|
|
- **Caller Responsibility**: The caller must ensure that a single handle is not concurrently used in conflicting ways if the underlying operation requires it (though many operations like reads on different keys should be safe).
|
|
|
|
*(Verify the specific locking strategies implemented in the C# partial classes for detailed guarantees.)*
|
|
|
|
## Performance Considerations
|
|
|
|
- **Interop Overhead**: There is inherent overhead in calling between native code and the .NET runtime.
|
|
- **Pipelining**: Use the pipeline operations (`SetPipelineMode`, queue commands, `FlushPipeline`) for significantly better performance when sending multiple commands.
|
|
- **Memory Management**: Frequent allocation/deallocation of strings across the interop boundary can impact performance. Ensure `FreeString` is called promptly.
|
|
|
|
## Best Practices
|
|
|
|
1. **Memory Management**: Always free returned strings.
|
|
```c
|
|
char* value = StringGet(client, "mykey");
|
|
if (value) {
|
|
// Use value
|
|
printf("Value: %s\n", value);
|
|
FreeString(value); // Essential!
|
|
}
|
|
```
|
|
|
|
2. **Error Handling**: Check return values.
|
|
```c
|
|
if (!ListSet(client, key, index, value)) { // Check boolean return
|
|
fprintf(stderr, "Error setting list value!\n");
|
|
}
|
|
char* item = ListIndex(client, key, index);
|
|
if (!item) { // Check pointer return
|
|
fprintf(stderr, "Error getting list item or index out of range!\n");
|
|
} else {
|
|
// Use item
|
|
FreeString(item);
|
|
}
|
|
```
|
|
|
|
3. **Resource Cleanup**: Always destroy the client.
|
|
```c
|
|
void* client = CreateClient(...);
|
|
// ... Use client ...
|
|
DestroyClient(client); // Ensure cleanup
|
|
```
|
|
|
|
4. **Encoding**: Assume all `char*` parameters and return values use UTF-8 encoding. |