Initial Repo Setup
Some checks failed
Build / build (push) Has been cancelled

This commit is contained in:
Jacob Schmidt 2025-04-10 21:49:46 -05:00
commit 4063fe6bd8
37 changed files with 5743 additions and 0 deletions

View File

@ -0,0 +1,48 @@
name: Build
on:
push:
branches: [ master ]
tags:
- 'v*'
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '9.0.x'
- name: Restore dependencies
run: dotnet restore FireflyClient.sln
- name: Build
run: |
chmod +x build-all.sh
./build-all.sh
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: firefly-artifacts
path: |
artifacts/exe/*
artifacts/native/*
- name: Create Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v1
with:
files: |
artifacts/exe/*
artifacts/native/*
generate_release_notes: true

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.editorconfig
nuget.config
*.7z
bin
obj
.venv
.vs
.vscode

67
FireflyClient.csproj Normal file
View File

@ -0,0 +1,67 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Common settings -->
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OptimizationPreference>Speed</OptimizationPreference>
<ApplicationIcon>icon.ico</ApplicationIcon>
<PackageIcon>icon.png</PackageIcon>
<SelfContained>true</SelfContained>
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<InvariantGlobalization>true</InvariantGlobalization>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<DebugType>embedded</DebugType>
<!-- Package information -->
<AssemblyName>FireflyClient</AssemblyName>
<RootNamespace>FireflyClient</RootNamespace>
<Title>Firefly Client</Title>
<Description>A client library for interacting with Firefly Redis-compatible server</Description>
<PackageId>FireflyClient</PackageId>
<Version>1.0.0</Version>
<Authors>IDSolutions</Authors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<IsPackable>true</IsPackable>
</PropertyGroup>
<!-- Executable configuration -->
<PropertyGroup Condition="'$(BuildType)' != 'lib'">
<OutputType>Exe</OutputType>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
<StripSymbols>true</StripSymbols>
<PublishAot>true</PublishAot>
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
</PropertyGroup>
<!-- Library configuration -->
<PropertyGroup Condition="'$(BuildType)' == 'lib'">
<OutputType>Library</OutputType>
<EnableDynamicLoading>true</EnableDynamicLoading>
<NativeLib>Shared</NativeLib>
<PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
<StripSymbols>true</StripSymbols>
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
</PropertyGroup>
<ItemGroup>
<Content Include="icon.png" />
<Content Include="icon.ico" />
<None Include="resources\icon.png" Pack="true" Visible="false" PackagePath="\" />
<None Include="resources\icon.ico" Pack="true" Visible="false" PackagePath="\" />
</ItemGroup>
<!-- Include header file in the package -->
<ItemGroup>
<None Include="firefly.h" Pack="true" Visible="false" PackagePath="include" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_LastSelectedProfileId>G:\forge\firefly\extension\FireflyClient\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
</PropertyGroup>
</Project>

25
FireflyClient.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35919.96 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FireflyClient", "FireflyClient.csproj", "{2AC8541A-E7A5-9E29-C21E-529DB87DC28F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2AC8541A-E7A5-9E29-C21E-529DB87DC28F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2AC8541A-E7A5-9E29-C21E-529DB87DC28F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AC8541A-E7A5-9E29-C21E-529DB87DC28F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AC8541A-E7A5-9E29-C21E-529DB87DC28F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BD4E2CA4-BE5E-4D45-B35E-09A818648961}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net9.0\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net9.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<History>True|2025-04-07T00:26:35.2561447Z||;True|2025-04-06T18:20:46.8599175-05:00||;True|2025-04-06T17:37:25.8290243-05:00||;False|2025-04-06T17:37:09.9452190-05:00||;True|2025-04-06T17:30:00.9176527-05:00||;True|2025-04-06T17:23:09.5889784-05:00||;True|2025-04-06T17:00:26.8780282-05:00||;True|2025-04-06T16:53:46.4057024-05:00||;True|2025-04-06T16:29:10.3736900-05:00||;True|2025-04-06T16:24:55.0062926-05:00||;True|2025-04-06T16:18:23.5491794-05:00||;True|2025-04-06T16:06:28.3996665-05:00||;True|2025-04-06T15:28:33.7325402-05:00||;True|2025-04-06T13:14:44.0043568-05:00||;True|2025-04-06T12:35:46.4550298-05:00||;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>

83
Properties/Resources.Designer.cs generated Normal file
View File

@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FireflyClient.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FireflyClient.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] icon {
get {
object obj = ResourceManager.GetObject("icon", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Byte[].
/// </summary>
internal static byte[] icon1 {
get {
object obj = ResourceManager.GetObject("icon1", resourceCulture);
return ((byte[])(obj));
}
}
}
}

127
Properties/Resources.resx Normal file
View File

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="icon1" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\resources\icon.ico;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
<data name="icon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\resources\icon.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

622
README.md Normal file
View File

@ -0,0 +1,622 @@
# 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
# Pipeline Ops
self.lib.SetPipelineMode.argtypes = [c_void_p, c_bool]
self.lib.SetPipelineMode.restype = c_bool
self.lib.SetBatchSize.argtypes = [c_void_p, c_int]
self.lib.SetBatchSize.restype = c_bool
self.lib.FlushPipeline.argtypes = [c_void_p]
self.lib.FlushPipeline.restype = c_char_p
self.lib.GetQueuedCommandCount.argtypes = [c_void_p]
self.lib.GetQueuedCommandCount.restype = c_int
self.lib.IsPipelineMode.argtypes = [c_void_p]
self.lib.IsPipelineMode.restype = c_bool
self.lib.GetBatchSize.argtypes = [c_void_p]
self.lib.GetBatchSize.restype = c_int
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.

BIN
artifacts/exe/FireflyClient Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,471 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>FireflyClient</name>
</assembly>
<members>
<member name="T:FireflyClient.Properties.Resources">
<summary>
A strongly-typed resource class, for looking up localized strings, etc.
</summary>
</member>
<member name="P:FireflyClient.Properties.Resources.ResourceManager">
<summary>
Returns the cached ResourceManager instance used by this class.
</summary>
</member>
<member name="P:FireflyClient.Properties.Resources.Culture">
<summary>
Overrides the current thread's CurrentUICulture property for all
resource lookups using this strongly typed resource class.
</summary>
</member>
<member name="P:FireflyClient.Properties.Resources.icon">
<summary>
Looks up a localized resource of type System.Byte[].
</summary>
</member>
<member name="P:FireflyClient.Properties.Resources.icon1">
<summary>
Looks up a localized resource of type System.Byte[].
</summary>
</member>
<member name="T:FireflyClient.FireflyClient">
<summary>
FireflyClient provides a clean API for interacting with the Firefly server.
This can be used as a library for integrating with other applications.
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.#ctor(System.String,System.Int32)">
<summary>
Creates a new FireflyClient and connects to the specified server
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.#ctor(System.String,System.Int32,System.String)">
<summary>
Creates a new FireflyClient, connects to the server and authenticates
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.Authenticate(System.String)">
<summary>
Authenticates with the server using the provided password
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ExecuteCommand(System.String,System.String[])">
<summary>
Executes a raw command with any number of arguments
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.SetPipelineMode(System.Boolean)">
<summary>
Enables or disables pipeline mode
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.FlushPipeline">
<summary>
Flushes any queued commands in pipeline mode
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.SetBatchSize(System.Int32)">
<summary>
Sets the maximum number of commands to batch before sending
</summary>
</member>
<member name="P:FireflyClient.FireflyClient.QueuedCommandCount">
<summary>
Gets the current number of queued commands
</summary>
</member>
<member name="P:FireflyClient.FireflyClient.IsPipelineMode">
<summary>
Gets whether pipeline mode is enabled
</summary>
</member>
<member name="P:FireflyClient.FireflyClient.MaxBatchSize">
<summary>
Gets the maximum batch size
</summary>
</member>
<member name="P:FireflyClient.FireflyClient.IsConnected">
<summary>
Gets whether the client is connected to the server
</summary>
</member>
<member name="P:FireflyClient.FireflyClient.IsAuthenticated">
<summary>
Gets whether the client is authenticated
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.Keys(System.String)">
<summary>
Gets all keys matching the specified pattern
</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>
</member>
<member name="M:FireflyClient.FireflyClient.ParseArrayResponse(System.String)">
<summary>
Parses an array response from the server
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.Dispose">
<inheritdoc/>
</member>
<member name="M:FireflyClient.FireflyClient.HashSet(System.String,System.String,System.String)">
<summary>
Sets a field in a hash
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.HashGet(System.String,System.String)">
<summary>
Gets a field from a hash
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.HashDelete(System.String,System.String)">
<summary>
Deletes a field from a hash
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.HashFieldExists(System.String,System.String)">
<summary>
Checks if a field exists in a hash
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.HashGetAll(System.String)">
<summary>
Gets all fields and values from a hash
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.HashMultiSet(System.String,System.Collections.Generic.Dictionary{System.String,System.String})">
<summary>
Sets multiple fields in a hash at once
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListLeftPush(System.String,System.String[])">
<summary>
Adds values to the beginning of a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListRightPush(System.String,System.String[])">
<summary>
Adds values to the end of a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListLeftPop(System.String)">
<summary>
Removes and returns the first element of a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListRightPop(System.String)">
<summary>
Removes and returns the last element 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
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListIndex(System.String,System.Int32)">
<summary>
Gets the element at the specified index in a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListSet(System.String,System.Int32,System.String)">
<summary>
Sets the element at the specified index in a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListPosition(System.String,System.String,System.Int32,System.Int32)">
<summary>
Returns the index of the first occurrence of an element in a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListTrim(System.String,System.Int32,System.Int32)">
<summary>
Trims a list to the specified range
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListRemove(System.String,System.Int32,System.String)">
<summary>
Removes elements equal to the given value from a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.CreateClient(System.IntPtr,System.Int32)">
<summary>
Creates a new FireflyClient instance for native interop
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.DestroyClient(System.IntPtr)">
<summary>
Destroys a FireflyClient instance created for native interop
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.NativeAuthenticate(System.IntPtr,System.IntPtr)">
<summary>
Authenticates with the server using the provided password
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.NativeExecuteCommand(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Executes a raw command. Note: Argument parsing is basic.
Consider using specific functions instead for reliability.
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.NativeFreeString(System.IntPtr)">
<summary>
Frees a string 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).
</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 key.</param>
<param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value.</param>
<returns>True if the command was successful (e.g., server replied OK), false otherwise.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeStringGet(System.IntPtr,System.IntPtr)">
<summary>
Gets a string value for a given key (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 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 key not found.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeDelete(System.IntPtr,System.IntPtr)">
<summary>
Deletes one or more keys (Native Interop).
Note: Currently only supports deleting a single key via the C# Delete method.
</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 key to delete.</param>
<returns>The number of keys that were removed (typically 1 or 0), or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListLeftPush(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Adds a value to the beginning 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>
<param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value to add.</param>
<returns>The length of the list after the push operation, or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListRightPush(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Adds a value to the end 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>
<param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value to add.</param>
<returns>The length of the list after the push operation, or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListLeftPop(System.IntPtr,System.IntPtr)">
<summary>
Removes and returns the first element 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>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.NativeListRightPop(System.IntPtr,System.IntPtr)">
<summary>
Removes and returns the last element 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>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.NativeListRange(System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
<summary>
Gets a range of elements from 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>
<param name="start">The start index (0-based).</param>
<param name="stop">The stop index (inclusive, use -1 for end).</param>
<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>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListIndex(System.IntPtr,System.IntPtr,System.Int32)">
<summary>
Gets an element from a list by its index (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>
<param name="index">The index of the element (0-based).</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 index is out of range.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListSet(System.IntPtr,System.IntPtr,System.Int32,System.IntPtr)">
<summary>
Sets the value of an element in a list by its index (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>
<param name="index">The index of the element to set (0-based).</param>
<param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the new value.</param>
<returns>True if the command was successful, false otherwise (e.g., index out of range).</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListPosition(System.IntPtr,System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
<summary>
Returns the index of the first occurrence of an element in a list (Native Interop).
Rank and MaxLen parameters are currently ignored.
</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>
<param name="elementPtr">Pointer to a null-terminated UTF-8 string representing the element to find.</param>
<param name="rank">Optional rank (ignored).</param>
<param name="maxlen">Optional max length (ignored).</param>
<returns>The 0-based index of the element, or -1 if not found or on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListTrim(System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
<summary>
Trims a list to contain only the specified range of elements (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>
<param name="start">The start index (0-based).</param>
<param name="stop">The stop index (inclusive, use -1 for end).</param>
<returns>True if the command was successful, false otherwise.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListRemove(System.IntPtr,System.IntPtr,System.Int32,System.IntPtr)">
<summary>
Removes occurrences of elements from 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>
<param name="count">Number of occurrences to remove (see C# ListRemove docs).</param>
<param name="elementPtr">Pointer to a null-terminated UTF-8 string representing the element to remove.</param>
<returns>The number of elements removed, or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashSet(System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Sets the value of a field within a hash (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 hash key.</param>
<param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field name.</param>
<param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value to set.</param>
<returns>True if the field was new or updated successfully, false on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashGet(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Gets the value of a field within a hash (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 hash key.</param>
<param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field name.</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 field/key not found.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashDelete(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Deletes a field from a hash (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 hash key.</param>
<param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field to delete.</param>
<returns>True if the field was deleted, false otherwise (e.g., field/key not found).</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashFieldExists(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Checks if a field exists within a hash (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 hash key.</param>
<param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field name.</param>
<returns>True if the field exists, false otherwise.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashMultiSet(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Sets multiple fields and values in a hash (Native Interop).
Parses a space-separated string of field-value pairs.
</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 hash key.</param>
<param name="fieldValuePairsPtr">Pointer to a null-terminated UTF-8 string of space-separated field-value pairs.</param>
<returns>True if successful, false on error (e.g., odd number of pairs).</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashGetAll(System.IntPtr,System.IntPtr)">
<summary>
Gets all fields and values from a hash (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 hash key.</param>
<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>
</member>
<member name="M:FireflyClient.FireflyClient.NativeSetPipelineMode(System.IntPtr,System.Boolean)">
<summary>
Enables or disables pipeline mode for the client (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<param name="enabled">True to enable pipeline mode, false to disable.</param>
<returns>True if the mode was set successfully, false on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeSetBatchSize(System.IntPtr,System.Int32)">
<summary>
Sets the maximum number of commands to batch in pipeline mode (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<param name="size">The maximum number of commands to queue before sending.</param>
<returns>True if the batch size was set successfully, false on error (e.g., invalid size).</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeFlushPipeline(System.IntPtr)">
<summary>
Sends all queued commands to the server immediately (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<returns>Pointer to a null-terminated UTF-8 string containing the server's combined response, allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error or if queue was empty.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeGetQueuedCommandCount(System.IntPtr)">
<summary>
Gets the number of commands currently waiting in the pipeline queue (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<returns>The number of queued commands, or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeIsPipelineMode(System.IntPtr)">
<summary>
Checks if pipeline mode is currently enabled for the client (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<returns>True if pipeline mode is enabled, false otherwise or on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeGetBatchSize(System.IntPtr)">
<summary>
Gets the current maximum batch size configured for pipeline mode (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<returns>The maximum batch size, or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeKeys(System.IntPtr,System.IntPtr)">
<summary>
Gets all keys matching the specified pattern (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<param name="patternPtr">Pointer to a null-terminated UTF-8 string representing the pattern to match against keys.</param>
<returns>Pointer to a null-terminated UTF-8 string containing newline-delimited keys, allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.StringSet(System.String,System.String)">
<summary>
Sets a key-value pair
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.StringGet(System.String)">
<summary>
Gets a value by key
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.Delete(System.String)">
<summary>
Deletes a key from all stores (string, list, hash)
</summary>
</member>
<member name="T:FireflyClient.Program">
<summary>
Command-line interface for Firefly
</summary>
</member>
</members>
</doc>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,471 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>FireflyClient</name>
</assembly>
<members>
<member name="T:FireflyClient.Properties.Resources">
<summary>
A strongly-typed resource class, for looking up localized strings, etc.
</summary>
</member>
<member name="P:FireflyClient.Properties.Resources.ResourceManager">
<summary>
Returns the cached ResourceManager instance used by this class.
</summary>
</member>
<member name="P:FireflyClient.Properties.Resources.Culture">
<summary>
Overrides the current thread's CurrentUICulture property for all
resource lookups using this strongly typed resource class.
</summary>
</member>
<member name="P:FireflyClient.Properties.Resources.icon">
<summary>
Looks up a localized resource of type System.Byte[].
</summary>
</member>
<member name="P:FireflyClient.Properties.Resources.icon1">
<summary>
Looks up a localized resource of type System.Byte[].
</summary>
</member>
<member name="T:FireflyClient.FireflyClient">
<summary>
FireflyClient provides a clean API for interacting with the Firefly server.
This can be used as a library for integrating with other applications.
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.#ctor(System.String,System.Int32)">
<summary>
Creates a new FireflyClient and connects to the specified server
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.#ctor(System.String,System.Int32,System.String)">
<summary>
Creates a new FireflyClient, connects to the server and authenticates
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.Authenticate(System.String)">
<summary>
Authenticates with the server using the provided password
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ExecuteCommand(System.String,System.String[])">
<summary>
Executes a raw command with any number of arguments
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.SetPipelineMode(System.Boolean)">
<summary>
Enables or disables pipeline mode
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.FlushPipeline">
<summary>
Flushes any queued commands in pipeline mode
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.SetBatchSize(System.Int32)">
<summary>
Sets the maximum number of commands to batch before sending
</summary>
</member>
<member name="P:FireflyClient.FireflyClient.QueuedCommandCount">
<summary>
Gets the current number of queued commands
</summary>
</member>
<member name="P:FireflyClient.FireflyClient.IsPipelineMode">
<summary>
Gets whether pipeline mode is enabled
</summary>
</member>
<member name="P:FireflyClient.FireflyClient.MaxBatchSize">
<summary>
Gets the maximum batch size
</summary>
</member>
<member name="P:FireflyClient.FireflyClient.IsConnected">
<summary>
Gets whether the client is connected to the server
</summary>
</member>
<member name="P:FireflyClient.FireflyClient.IsAuthenticated">
<summary>
Gets whether the client is authenticated
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.Keys(System.String)">
<summary>
Gets all keys matching the specified pattern
</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>
</member>
<member name="M:FireflyClient.FireflyClient.ParseArrayResponse(System.String)">
<summary>
Parses an array response from the server
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.Dispose">
<inheritdoc/>
</member>
<member name="M:FireflyClient.FireflyClient.HashSet(System.String,System.String,System.String)">
<summary>
Sets a field in a hash
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.HashGet(System.String,System.String)">
<summary>
Gets a field from a hash
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.HashDelete(System.String,System.String)">
<summary>
Deletes a field from a hash
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.HashFieldExists(System.String,System.String)">
<summary>
Checks if a field exists in a hash
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.HashGetAll(System.String)">
<summary>
Gets all fields and values from a hash
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.HashMultiSet(System.String,System.Collections.Generic.Dictionary{System.String,System.String})">
<summary>
Sets multiple fields in a hash at once
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListLeftPush(System.String,System.String[])">
<summary>
Adds values to the beginning of a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListRightPush(System.String,System.String[])">
<summary>
Adds values to the end of a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListLeftPop(System.String)">
<summary>
Removes and returns the first element of a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListRightPop(System.String)">
<summary>
Removes and returns the last element 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
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListIndex(System.String,System.Int32)">
<summary>
Gets the element at the specified index in a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListSet(System.String,System.Int32,System.String)">
<summary>
Sets the element at the specified index in a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListPosition(System.String,System.String,System.Int32,System.Int32)">
<summary>
Returns the index of the first occurrence of an element in a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListTrim(System.String,System.Int32,System.Int32)">
<summary>
Trims a list to the specified range
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.ListRemove(System.String,System.Int32,System.String)">
<summary>
Removes elements equal to the given value from a list
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.CreateClient(System.IntPtr,System.Int32)">
<summary>
Creates a new FireflyClient instance for native interop
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.DestroyClient(System.IntPtr)">
<summary>
Destroys a FireflyClient instance created for native interop
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.NativeAuthenticate(System.IntPtr,System.IntPtr)">
<summary>
Authenticates with the server using the provided password
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.NativeExecuteCommand(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Executes a raw command. Note: Argument parsing is basic.
Consider using specific functions instead for reliability.
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.NativeFreeString(System.IntPtr)">
<summary>
Frees a string 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).
</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 key.</param>
<param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value.</param>
<returns>True if the command was successful (e.g., server replied OK), false otherwise.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeStringGet(System.IntPtr,System.IntPtr)">
<summary>
Gets a string value for a given key (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 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 key not found.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeDelete(System.IntPtr,System.IntPtr)">
<summary>
Deletes one or more keys (Native Interop).
Note: Currently only supports deleting a single key via the C# Delete method.
</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 key to delete.</param>
<returns>The number of keys that were removed (typically 1 or 0), or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListLeftPush(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Adds a value to the beginning 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>
<param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value to add.</param>
<returns>The length of the list after the push operation, or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListRightPush(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Adds a value to the end 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>
<param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value to add.</param>
<returns>The length of the list after the push operation, or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListLeftPop(System.IntPtr,System.IntPtr)">
<summary>
Removes and returns the first element 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>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.NativeListRightPop(System.IntPtr,System.IntPtr)">
<summary>
Removes and returns the last element 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>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.NativeListRange(System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
<summary>
Gets a range of elements from 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>
<param name="start">The start index (0-based).</param>
<param name="stop">The stop index (inclusive, use -1 for end).</param>
<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>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListIndex(System.IntPtr,System.IntPtr,System.Int32)">
<summary>
Gets an element from a list by its index (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>
<param name="index">The index of the element (0-based).</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 index is out of range.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListSet(System.IntPtr,System.IntPtr,System.Int32,System.IntPtr)">
<summary>
Sets the value of an element in a list by its index (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>
<param name="index">The index of the element to set (0-based).</param>
<param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the new value.</param>
<returns>True if the command was successful, false otherwise (e.g., index out of range).</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListPosition(System.IntPtr,System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
<summary>
Returns the index of the first occurrence of an element in a list (Native Interop).
Rank and MaxLen parameters are currently ignored.
</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>
<param name="elementPtr">Pointer to a null-terminated UTF-8 string representing the element to find.</param>
<param name="rank">Optional rank (ignored).</param>
<param name="maxlen">Optional max length (ignored).</param>
<returns>The 0-based index of the element, or -1 if not found or on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListTrim(System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
<summary>
Trims a list to contain only the specified range of elements (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>
<param name="start">The start index (0-based).</param>
<param name="stop">The stop index (inclusive, use -1 for end).</param>
<returns>True if the command was successful, false otherwise.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeListRemove(System.IntPtr,System.IntPtr,System.Int32,System.IntPtr)">
<summary>
Removes occurrences of elements from 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>
<param name="count">Number of occurrences to remove (see C# ListRemove docs).</param>
<param name="elementPtr">Pointer to a null-terminated UTF-8 string representing the element to remove.</param>
<returns>The number of elements removed, or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashSet(System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Sets the value of a field within a hash (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 hash key.</param>
<param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field name.</param>
<param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value to set.</param>
<returns>True if the field was new or updated successfully, false on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashGet(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Gets the value of a field within a hash (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 hash key.</param>
<param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field name.</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 field/key not found.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashDelete(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Deletes a field from a hash (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 hash key.</param>
<param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field to delete.</param>
<returns>True if the field was deleted, false otherwise (e.g., field/key not found).</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashFieldExists(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Checks if a field exists within a hash (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 hash key.</param>
<param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field name.</param>
<returns>True if the field exists, false otherwise.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashMultiSet(System.IntPtr,System.IntPtr,System.IntPtr)">
<summary>
Sets multiple fields and values in a hash (Native Interop).
Parses a space-separated string of field-value pairs.
</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 hash key.</param>
<param name="fieldValuePairsPtr">Pointer to a null-terminated UTF-8 string of space-separated field-value pairs.</param>
<returns>True if successful, false on error (e.g., odd number of pairs).</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeHashGetAll(System.IntPtr,System.IntPtr)">
<summary>
Gets all fields and values from a hash (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 hash key.</param>
<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>
</member>
<member name="M:FireflyClient.FireflyClient.NativeSetPipelineMode(System.IntPtr,System.Boolean)">
<summary>
Enables or disables pipeline mode for the client (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<param name="enabled">True to enable pipeline mode, false to disable.</param>
<returns>True if the mode was set successfully, false on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeSetBatchSize(System.IntPtr,System.Int32)">
<summary>
Sets the maximum number of commands to batch in pipeline mode (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<param name="size">The maximum number of commands to queue before sending.</param>
<returns>True if the batch size was set successfully, false on error (e.g., invalid size).</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeFlushPipeline(System.IntPtr)">
<summary>
Sends all queued commands to the server immediately (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<returns>Pointer to a null-terminated UTF-8 string containing the server's combined response, allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error or if queue was empty.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeGetQueuedCommandCount(System.IntPtr)">
<summary>
Gets the number of commands currently waiting in the pipeline queue (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<returns>The number of queued commands, or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeIsPipelineMode(System.IntPtr)">
<summary>
Checks if pipeline mode is currently enabled for the client (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<returns>True if pipeline mode is enabled, false otherwise or on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeGetBatchSize(System.IntPtr)">
<summary>
Gets the current maximum batch size configured for pipeline mode (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<returns>The maximum batch size, or 0 on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.NativeKeys(System.IntPtr,System.IntPtr)">
<summary>
Gets all keys matching the specified pattern (Native Interop).
</summary>
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
<param name="patternPtr">Pointer to a null-terminated UTF-8 string representing the pattern to match against keys.</param>
<returns>Pointer to a null-terminated UTF-8 string containing newline-delimited keys, allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error.</returns>
</member>
<member name="M:FireflyClient.FireflyClient.StringSet(System.String,System.String)">
<summary>
Sets a key-value pair
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.StringGet(System.String)">
<summary>
Gets a value by key
</summary>
</member>
<member name="M:FireflyClient.FireflyClient.Delete(System.String)">
<summary>
Deletes a key from all stores (string, list, hash)
</summary>
</member>
<member name="T:FireflyClient.Program">
<summary>
Command-line interface for Firefly
</summary>
</member>
</members>
</doc>

Binary file not shown.

Binary file not shown.

52
build-all.ps1 Normal file
View File

@ -0,0 +1,52 @@
# Build configuration
$configuration = "Release"
$baseOutputPath = ".\artifacts"
# Determine current OS and platform
$currentPlatform = "win-x64" # Since we're running PowerShell, we're on Windows
Write-Host "Building for current platform: $currentPlatform"
# Create output directories
$exeOutputPath = "$baseOutputPath\exe"
$nativeOutputPath = "$baseOutputPath\native"
New-Item -ItemType Directory -Force -Path $exeOutputPath | Out-Null
New-Item -ItemType Directory -Force -Path $nativeOutputPath | Out-Null
# Build executable
Write-Host "Building executable..."
dotnet publish `
-c $configuration `
-r $currentPlatform `
--self-contained true `
-p:PublishDir=$exeOutputPath
if ($LASTEXITCODE -ne 0) {
Write-Host "Executable build failed. Check the error messages above."
exit 1
}
# Build native shared library
Write-Host "Building native shared library..."
dotnet publish `
-c $configuration `
-r $currentPlatform `
--self-contained true `
-p:BuildType=lib `
-p:PublishDir=$nativeOutputPath
if ($LASTEXITCODE -ne 0) {
Write-Host "Library build failed. Check the error messages above."
exit 1
}
# Rename the native library to the expected name
$dllPath = Join-Path $nativeOutputPath "FireflyClient.dll"
$libDllPath = Join-Path $nativeOutputPath "libFireflyClient.dll"
if (Test-Path $dllPath) {
Write-Host "Renaming native library to libFireflyClient.dll..."
Move-Item -Path $dllPath -Destination $libDllPath -Force
}
Write-Host "Build complete. Outputs available at:"
Write-Host "Executable: $exeOutputPath"
Write-Host "Native library: $nativeOutputPath"

71
build-all.sh Normal file
View File

@ -0,0 +1,71 @@
#!/bin/bash
configuration="Release"
baseOutputPath="./artifacts"
# Detect platform
if [[ "$OSTYPE" == "darwin"* ]]; then
currentPlatform="osx-x64"
else
currentPlatform="linux-x64"
fi
echo "Building for current platform: $currentPlatform"
# Create output directories
exeOutputPath="$baseOutputPath/exe"
nativeOutputPath="$baseOutputPath/native"
mkdir -p "$exeOutputPath"
mkdir -p "$nativeOutputPath"
# Build executable
echo "Building executable..."
dotnet publish \
-c $configuration \
-r $currentPlatform \
--self-contained true \
-p:PublishDir=$exeOutputPath
if [ $? -ne 0 ]; then
echo "Executable build failed. Check the error messages above."
exit 1
fi
# Build native shared library
echo "Building native shared library..."
dotnet publish \
-c $configuration \
-r $currentPlatform \
--self-contained true \
-p:BuildType=lib \
-p:PublishDir=$nativeOutputPath
if [ $? -ne 0 ]; then
echo "Library build failed. Check the error messages above."
exit 1
fi
# Rename the native library to the expected name
dllPath="$nativeOutputPath/FireflyClient.so"
libDllPath="$nativeOutputPath/libFireflyClient.so"
if [ -f "$dllPath" ]; then
echo "Renaming native library to libFireflyClient.so..."
mv "$dllPath" "$libDllPath"
fi
echo "Build complete. Outputs available at:"
echo "Executable: $exeOutputPath"
echo "Native library: $nativeOutputPath"
# Make the outputs executable
if [[ "$currentPlatform" == "linux-x64" ]]; then
chmod +x "$exeOutputPath/FireflyClient"
if [ -f "$libDllPath" ]; then
chmod +x "$libDllPath"
else
echo "Warning: Native library not found at $libDllPath"
fi
elif [[ "$currentPlatform" == "osx-x64" ]]; then
chmod +x "$exeOutputPath/FireflyClient"
chmod +x "$nativeOutputPath/libFireflyClient.dylib"
fi

176
examples/example.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "../src/firefly.h" // Adjusted path to header in src directory
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
// Helper function to split string (basic)
std::vector<std::string> split(const std::string& s, char delimiter) {
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter)) {
tokens.push_back(token);
}
return tokens;
}
int main() {
// Create client
// Assumes the compiled library is accessible (e.g., in PATH or same dir)
void* client = CreateClient("localhost", 6379);
if (!client) {
std::cerr << "Failed to create client" << std::endl;
return 1;
}
std::cout << "Client created." << std::endl;
// Authenticate if needed (replace with your password or remove if none)
// if (!Authenticate(client, "your_password")) {
// std::cerr << "Authentication failed" << std::endl;
// DestroyClient(client);
// return 1;
// }
// std::cout << "Authentication successful." << std::endl;
// String operations
std::cout << "\n--- String Ops ---" << std::endl;
if (StringSet(client, "cpp:test", "hello from c++")) {
std::cout << "StringSet OK" << std::endl;
}
char* value = StringGet(client, "cpp:test");
if (value) {
std::cout << "StringGet: " << value << std::endl;
FreeString(value); // IMPORTANT: Free the returned string
}
// List operations
std::cout << "\n--- List Ops ---" << std::endl;
ListLeftPush(client, "cpp:mylist", "item1");
ListLeftPush(client, "cpp:mylist", "item2");
ListRightPush(client, "cpp:mylist", "item3");
std::cout << "LPUSH/RPUSH OK" << std::endl;
char* popped = ListLeftPop(client, "cpp:mylist");
if (popped) {
std::cout << "ListLeftPop: " << popped << std::endl;
FreeString(popped); // IMPORTANT: Free
}
// Get a range of elements (Updated for char* return)
std::cout << "ListRange:" << std::endl;
char* rangeStr = ListRange(client, "cpp:mylist", 0, -1);
if (rangeStr) {
std::string rangeResult(rangeStr);
FreeString(rangeStr); // IMPORTANT: Free
// Parse the newline-delimited string
std::vector<std::string> range = split(rangeResult, '\n');
for (size_t i = 0; i < range.size(); ++i) {
std::cout << " Element " << i << ": " << range[i] << std::endl;
}
} else {
std::cout << " (empty or error)" << std::endl;
}
// ListPosition
int pos = ListPosition(client, "cpp:mylist", "item1", 0, 0);
std::cout << "ListPosition of 'item1': " << pos << std::endl;
// ListSet
if (ListSet(client, "cpp:mylist", 0, "item1_updated")) {
std::cout << "ListSet OK" << std::endl;
}
// ListRemove
int removed = ListRemove(client, "cpp:mylist", 1, "item3");
std::cout << "ListRemove ('item3', count 1): Removed " << removed << " elements" << std::endl;
// ListTrim
if (ListTrim(client, "cpp:mylist", 0, 0)) { // Trim to keep only the first element
std::cout << "ListTrim OK" << std::endl;
}
// Verify trim
char* finalRangeStr = ListRange(client, "cpp:mylist", 0, -1);
if (finalRangeStr) {
std::cout << "List after Trim: " << finalRangeStr << std::endl;
FreeString(finalRangeStr);
}
// Hash operations
std::cout << "\n--- Hash Ops ---" << std::endl;
HashSet(client, "cpp:user:1", "name", "John C.");
HashSet(client, "cpp:user:1", "email", "john.c@example.com");
std::cout << "HashSet OK" << std::endl;
char* name = HashGet(client, "cpp:user:1", "name");
if (name) {
std::cout << "HashGet 'name': " << name << std::endl;
FreeString(name); // IMPORTANT: Free
}
// HashMultiSet
if(HashMultiSet(client, "cpp:user:1", "city NewYork country USA")) {
std::cout << "HashMultiSet OK" << std::endl;
}
// Get all hash fields (Updated for char* return)
std::cout << "HashGetAll:" << std::endl;
char* hashDataStr = HashGetAll(client, "cpp:user:1");
if (hashDataStr) {
std::string hashResult(hashDataStr);
FreeString(hashDataStr); // IMPORTANT: Free
// Parse the newline-delimited "field=value" string
std::vector<std::string> pairs = split(hashResult, '\n');
for (const auto& pair : pairs) {
size_t eqPos = pair.find('=');
if (eqPos != std::string::npos) {
std::cout << " " << pair.substr(0, eqPos) << ": " << pair.substr(eqPos + 1) << std::endl;
}
}
} else {
std::cout << " (empty or error)" << std::endl;
}
// Execute a raw command
std::cout << "\n--- Raw Command ---" << std::endl;
char* result = ExecuteCommand(client, "PING", "");
if (result) {
std::cout << "PING result: " << result; // Result likely includes \r\n
FreeString(result); // IMPORTANT: Free
}
// Pipeline operations (example)
std::cout << "\n--- Pipeline Ops ---" << std::endl;
if (SetPipelineMode(client, true)) {
std::cout << "Pipeline mode enabled." << std::endl;
SetBatchSize(client, 5);
StringSet(client, "pipe:1", "a");
StringSet(client, "pipe:2", "b");
std::cout << " Queued: " << GetQueuedCommandCount(client) << std::endl;
char* flushResult = FlushPipeline(client);
if(flushResult) {
std::cout << " Flushed Result: " << flushResult << std::endl;
FreeString(flushResult);
}
SetPipelineMode(client, false);
std::cout << "Pipeline mode disabled." << std::endl;
}
// Clean up string/list/hash keys used
Delete(client, "cpp:test");
Delete(client, "cpp:mylist");
Delete(client, "cpp:user:1");
Delete(client, "pipe:1");
Delete(client, "pipe:2");
// Destroy client
std::cout << "\nDestroying client..." << std::endl;
DestroyClient(client);
std::cout << "Client destroyed." << std::endl;
return 0;
}

1438
examples/example.py Normal file

File diff suppressed because it is too large Load Diff

178
examples/firefly_debug.log Normal file
View File

@ -0,0 +1,178 @@
2025-04-09 18:14:40,895 - FireflyDB - INFO - Script starting...
2025-04-09 18:14:40,895 - FireflyDB - INFO - Starting FireflyDatabase test...
2025-04-09 18:14:40,896 - FireflyDB - INFO - Creating FireflyDatabase instance...
2025-04-09 18:14:40,940 - FireflyDB - DEBUG - Firefly library loaded from: G:\firefly\client\examples\libFirefly.dll
2025-04-09 18:14:40,940 - FireflyDB - DEBUG - Connecting to 135.134.202.221:6379
2025-04-09 18:14:40,944 - FireflyDB - DEBUG - Client created successfully
2025-04-09 18:14:40,944 - FireflyDB - DEBUG - Authenticating...
2025-04-09 18:14:40,964 - FireflyDB - DEBUG - Authentication successful
2025-04-09 18:14:40,964 - FireflyDB - INFO - Connected to Firefly server
2025-04-09 18:14:40,964 - FireflyDB - INFO - Performing operations...
2025-04-09 18:14:40,965 - FireflyDB - INFO - About to call ping()...
2025-04-09 18:14:40,965 - FireflyDB - DEBUG - Sending PING command
2025-04-09 18:14:40,965 - FireflyDB - DEBUG - Executing command: PING with args:
2025-04-09 18:14:40,981 - FireflyDB - DEBUG - ExecuteCommand result pointer: b'+PONG\r\n'
2025-04-09 18:14:40,982 - FireflyDB - DEBUG - Result is already a Python bytes object, no need to free
2025-04-09 18:14:40,982 - FireflyDB - DEBUG - Received response: +PONG
2025-04-09 18:14:40,982 - FireflyDB - DEBUG - Raw ping response: '+PONG
'
2025-04-09 18:14:40,983 - FireflyDB - DEBUG - Response type: <class 'str'>, value: '+PONG
'
2025-04-09 18:14:40,983 - FireflyDB - DEBUG - Normalized response: 'PONG'
2025-04-09 18:14:40,983 - FireflyDB - DEBUG - PONG found in normalized response - ping successful
2025-04-09 18:14:40,983 - FireflyDB - INFO - Ping completed with result: True
2025-04-09 18:14:40,983 - FireflyDB - INFO - After ping attempt
2025-04-09 18:14:40,983 - FireflyDB - INFO - Testing Database
2025-04-09 18:14:40,996 - FireflyDB - DEBUG - StringSet result for key 'test': True
2025-04-09 18:14:41,015 - FireflyDB - DEBUG - StringGet raw result pointer: b'"hello world"'
2025-04-09 18:14:41,015 - FireflyDB - DEBUG - Result is already a Python bytes object, no need to free
2025-04-09 18:14:41,016 - FireflyDB - DEBUG - StringGet for key 'test': "hello world"
2025-04-09 18:14:41,016 - FireflyDB - INFO - Value: "hello world"
2025-04-09 18:14:41,018 - FireflyDB - DEBUG - ListRightPush on key 'mylist' with value 'item1'. New length: 4
2025-04-09 18:14:41,033 - FireflyDB - DEBUG - ListRightPush on key 'mylist' with value 'item2'. New length: 5
2025-04-09 18:14:41,036 - FireflyDB - DEBUG - ListRightPush on key 'mylist' with value 'item3'. New length: 6
2025-04-09 18:14:41,045 - FireflyDB - DEBUG - ListRange raw result: b'item1\nitem2\nitem3\nitem1\nitem2\nitem3'
2025-04-09 18:14:41,046 - FireflyDB - DEBUG - ListRange on key 'mylist' from 0 to -1. Values: ['item1', 'item2', 'item3', 'item1', 'item2', 'item3']
2025-04-09 18:14:41,046 - FireflyDB - INFO - List items:
2025-04-09 18:14:41,046 - FireflyDB - INFO - - item1
2025-04-09 18:14:41,047 - FireflyDB - INFO - - item2
2025-04-09 18:14:41,047 - FireflyDB - INFO - - item3
2025-04-09 18:14:41,047 - FireflyDB - INFO - - item1
2025-04-09 18:14:41,047 - FireflyDB - INFO - - item2
2025-04-09 18:14:41,047 - FireflyDB - INFO - - item3
2025-04-09 18:14:41,061 - FireflyDB - DEBUG - HashSet on key 'user:1', field 'name': False
2025-04-09 18:14:41,071 - FireflyDB - DEBUG - HashSet on key 'user:1', field 'email': False
2025-04-09 18:14:41,076 - FireflyDB - DEBUG - HashGet raw result: b'John'
2025-04-09 18:14:41,076 - FireflyDB - DEBUG - HashGet on key 'user:1', field 'name': John
2025-04-09 18:14:41,077 - FireflyDB - INFO - Name: John
2025-04-09 18:14:41,089 - FireflyDB - DEBUG - HashGetAll raw result: b'email=john@example.com\nname=John'
2025-04-09 18:14:41,089 - FireflyDB - DEBUG - HashGetAll on key 'user:1': {'email': 'john@example.com', 'name': 'John'}
2025-04-09 18:14:41,090 - FireflyDB - INFO - User data:
2025-04-09 18:14:41,090 - FireflyDB - INFO - email: john@example.com
2025-04-09 18:14:41,090 - FireflyDB - INFO - name: John
2025-04-09 18:14:41,090 - FireflyDB - INFO -
Pipeline Operations:
2025-04-09 18:14:41,090 - FireflyDB - DEBUG - Setting pipeline mode to True
2025-04-09 18:14:41,090 - FireflyDB - DEBUG - SetPipelineMode to True: True
2025-04-09 18:14:41,091 - FireflyDB - INFO - Note: When in pipeline mode, operations return 'QUEUED' instead of actual values
2025-04-09 18:14:41,091 - FireflyDB - INFO - Values aren't actually set until flush_pipeline() is called
2025-04-09 18:14:41,091 - FireflyDB - DEBUG - Setting batch size to 100
2025-04-09 18:14:41,091 - FireflyDB - DEBUG - SetBatchSize to 100: True
2025-04-09 18:14:41,091 - FireflyDB - INFO - Adding dummy entries for each data type to handle Redis pipeline shifting
2025-04-09 18:14:41,092 - FireflyDB - DEBUG - StringSet result for key 'pipeline:string:0': False
2025-04-09 18:14:41,092 - FireflyDB - DEBUG - ListRightPush on key 'pipeline:list:0' with value 'dummy-list-item'. New length: 0
2025-04-09 18:14:41,092 - FireflyDB - DEBUG - HashSet on key 'pipeline:hash', field 'dummy-field': False
2025-04-09 18:14:41,092 - FireflyDB - DEBUG - StringSet result for key 'pipeline:string:1': False
2025-04-09 18:14:41,092 - FireflyDB - DEBUG - ListRightPush on key 'pipeline:list' with value 'list-item-1'. New length: 0
2025-04-09 18:14:41,093 - FireflyDB - DEBUG - HashSet on key 'pipeline:hash', field 'field-1': False
2025-04-09 18:14:41,093 - FireflyDB - DEBUG - StringSet result for key 'pipeline:string:2': False
2025-04-09 18:14:41,093 - FireflyDB - DEBUG - ListRightPush on key 'pipeline:list' with value 'list-item-2'. New length: 0
2025-04-09 18:14:41,093 - FireflyDB - DEBUG - HashSet on key 'pipeline:hash', field 'field-2': False
2025-04-09 18:14:41,094 - FireflyDB - DEBUG - StringSet result for key 'pipeline:string:3': False
2025-04-09 18:14:41,094 - FireflyDB - DEBUG - ListRightPush on key 'pipeline:list' with value 'list-item-3'. New length: 0
2025-04-09 18:14:41,094 - FireflyDB - DEBUG - HashSet on key 'pipeline:hash', field 'field-3': False
2025-04-09 18:14:41,094 - FireflyDB - DEBUG - StringSet result for key 'pipeline:string:4': False
2025-04-09 18:14:41,094 - FireflyDB - DEBUG - ListRightPush on key 'pipeline:list' with value 'list-item-4'. New length: 0
2025-04-09 18:14:41,095 - FireflyDB - DEBUG - HashSet on key 'pipeline:hash', field 'field-4': False
2025-04-09 18:14:41,095 - FireflyDB - DEBUG - StringSet result for key 'pipeline:string:5': False
2025-04-09 18:14:41,095 - FireflyDB - DEBUG - ListRightPush on key 'pipeline:list' with value 'list-item-5'. New length: 0
2025-04-09 18:14:41,095 - FireflyDB - DEBUG - HashSet on key 'pipeline:hash', field 'field-5': False
2025-04-09 18:14:41,095 - FireflyDB - DEBUG - GetQueuedCommandCount: 18
2025-04-09 18:14:41,095 - FireflyDB - INFO - Queued commands: 18
2025-04-09 18:14:41,096 - FireflyDB - INFO - Results should be obtained after flushing the pipeline
2025-04-09 18:14:41,103 - FireflyDB - DEBUG - Flushed pipeline. Response: +OK
2025-04-09 18:14:41,103 - FireflyDB - INFO - Pipeline flush result: +OK
2025-04-09 18:14:41,103 - FireflyDB - INFO - After pipeline flush, we need to exit pipeline mode to get actual values
2025-04-09 18:14:41,103 - FireflyDB - DEBUG - Setting pipeline mode to False
2025-04-09 18:14:41,103 - FireflyDB - DEBUG - SetPipelineMode to False: True
2025-04-09 18:14:41,103 - FireflyDB - INFO - Pipeline mode disabled
2025-04-09 18:14:41,103 - FireflyDB - INFO -
Verifying results (note: Redis pipeline responses might not match input order):
2025-04-09 18:14:41,105 - FireflyDB - DEBUG - StringGet raw result pointer: b''
2025-04-09 18:14:41,105 - FireflyDB - DEBUG - StringGet for key 'pipeline:string:0': Key not found
2025-04-09 18:14:41,106 - FireflyDB - INFO - String key:0 = None
2025-04-09 18:14:41,107 - FireflyDB - DEBUG - StringGet raw result pointer: b'dummy-string'
2025-04-09 18:14:41,107 - FireflyDB - DEBUG - Result is already a Python bytes object, no need to free
2025-04-09 18:14:41,108 - FireflyDB - DEBUG - StringGet for key 'pipeline:string:1': dummy-string
2025-04-09 18:14:41,108 - FireflyDB - INFO - String key:1 = dummy-string
2025-04-09 18:14:41,116 - FireflyDB - DEBUG - StringGet raw result pointer: b'string-value-1'
2025-04-09 18:14:41,116 - FireflyDB - DEBUG - Result is already a Python bytes object, no need to free
2025-04-09 18:14:41,116 - FireflyDB - DEBUG - StringGet for key 'pipeline:string:2': string-value-1
2025-04-09 18:14:41,116 - FireflyDB - INFO - String key:2 = string-value-1
2025-04-09 18:14:41,118 - FireflyDB - DEBUG - StringGet raw result pointer: b'string-value-2'
2025-04-09 18:14:41,118 - FireflyDB - DEBUG - Result is already a Python bytes object, no need to free
2025-04-09 18:14:41,118 - FireflyDB - DEBUG - StringGet for key 'pipeline:string:3': string-value-2
2025-04-09 18:14:41,118 - FireflyDB - INFO - String key:3 = string-value-2
2025-04-09 18:14:41,129 - FireflyDB - DEBUG - StringGet raw result pointer: b'string-value-3'
2025-04-09 18:14:41,129 - FireflyDB - DEBUG - Result is already a Python bytes object, no need to free
2025-04-09 18:14:41,129 - FireflyDB - DEBUG - StringGet for key 'pipeline:string:4': string-value-3
2025-04-09 18:14:41,129 - FireflyDB - INFO - String key:4 = string-value-3
2025-04-09 18:14:41,131 - FireflyDB - DEBUG - StringGet raw result pointer: b'string-value-4'
2025-04-09 18:14:41,131 - FireflyDB - DEBUG - Result is already a Python bytes object, no need to free
2025-04-09 18:14:41,132 - FireflyDB - DEBUG - StringGet for key 'pipeline:string:5': string-value-4
2025-04-09 18:14:41,132 - FireflyDB - INFO - String key:5 = string-value-4
2025-04-09 18:14:41,151 - FireflyDB - DEBUG - ListRange raw result: None
2025-04-09 18:14:41,151 - FireflyDB - DEBUG - ListRange on key 'pipeline:list' from 0 to -1. Empty list
2025-04-09 18:14:41,152 - FireflyDB - INFO - List items: []
2025-04-09 18:14:41,153 - FireflyDB - DEBUG - ListRange raw result: b'list-item-1\nlist-item-2\nlist-item-3\nlist-item-4\nlist-item-5\nlist-item-1\nlist-item-2\nlist-item-3\nlist-item-4\nlist-item-5'
2025-04-09 18:14:41,153 - FireflyDB - DEBUG - ListRange on key 'pipeline:list' from 0 to -1. Values: ['list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5', 'list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5']
2025-04-09 18:14:41,154 - FireflyDB - INFO - List items: ['list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5', 'list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5']
2025-04-09 18:14:41,168 - FireflyDB - DEBUG - ListRange raw result: b'list-item-1\nlist-item-2\nlist-item-3\nlist-item-4\nlist-item-5\nlist-item-1\nlist-item-2\nlist-item-3\nlist-item-4\nlist-item-5'
2025-04-09 18:14:41,168 - FireflyDB - DEBUG - ListRange on key 'pipeline:list' from 0 to -1. Values: ['list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5', 'list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5']
2025-04-09 18:14:41,168 - FireflyDB - INFO - List items: ['list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5', 'list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5']
2025-04-09 18:14:41,170 - FireflyDB - DEBUG - ListRange raw result: b'list-item-1\nlist-item-2\nlist-item-3\nlist-item-4\nlist-item-5\nlist-item-1\nlist-item-2\nlist-item-3\nlist-item-4\nlist-item-5'
2025-04-09 18:14:41,170 - FireflyDB - DEBUG - ListRange on key 'pipeline:list' from 0 to -1. Values: ['list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5', 'list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5']
2025-04-09 18:14:41,170 - FireflyDB - INFO - List items: ['list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5', 'list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5']
2025-04-09 18:14:41,183 - FireflyDB - DEBUG - ListRange raw result: b'list-item-1\nlist-item-2\nlist-item-3\nlist-item-4\nlist-item-5\nlist-item-1\nlist-item-2\nlist-item-3\nlist-item-4\nlist-item-5'
2025-04-09 18:14:41,183 - FireflyDB - DEBUG - ListRange on key 'pipeline:list' from 0 to -1. Values: ['list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5', 'list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5']
2025-04-09 18:14:41,183 - FireflyDB - INFO - List items: ['list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5', 'list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5']
2025-04-09 18:14:41,185 - FireflyDB - DEBUG - ListRange raw result: b'list-item-1\nlist-item-2\nlist-item-3\nlist-item-4\nlist-item-5\nlist-item-1\nlist-item-2\nlist-item-3\nlist-item-4\nlist-item-5'
2025-04-09 18:14:41,185 - FireflyDB - DEBUG - ListRange on key 'pipeline:list' from 0 to -1. Values: ['list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5', 'list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5']
2025-04-09 18:14:41,185 - FireflyDB - INFO - List items: ['list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5', 'list-item-1', 'list-item-2', 'list-item-3', 'list-item-4', 'list-item-5']
2025-04-09 18:14:41,196 - FireflyDB - DEBUG - HashGetAll raw result: b'list-item-1=list-item-2\nlist-item-3=list-item-4\nlist-item-5=list-item-1\nlist-item-2=list-item-3\nlist-item-4=list-item-5'
2025-04-09 18:14:41,196 - FireflyDB - DEBUG - HashGetAll on key 'pipeline:hash': {'list-item-1': 'list-item-2', 'list-item-3': 'list-item-4', 'list-item-5': 'list-item-1', 'list-item-2': 'list-item-3', 'list-item-4': 'list-item-5'}
2025-04-09 18:14:41,196 - FireflyDB - INFO - Hash fields:
2025-04-09 18:14:41,196 - FireflyDB - INFO - list-item-1: list-item-2
2025-04-09 18:14:41,197 - FireflyDB - INFO - list-item-3: list-item-4
2025-04-09 18:14:41,197 - FireflyDB - INFO - list-item-5: list-item-1
2025-04-09 18:14:41,197 - FireflyDB - INFO - list-item-2: list-item-3
2025-04-09 18:14:41,197 - FireflyDB - INFO - list-item-4: list-item-5
2025-04-09 18:14:41,199 - FireflyDB - DEBUG - Delete result: b'*12\r\n+field-5\r\n+hash-value-5\r\n+field-4\r\n+hash-value-4\r\n+field-3\r\n+hash-value-3\r\n+field-2\r\n+hash-value-2\r\n+field-1\r\n+hash-value-1\r\n+dummy-field\r\n+dummy-value\r\n'
2025-04-09 18:14:41,199 - FireflyDB - DEBUG - Complex pipeline response for DEL on key 'pipeline:string:0', assuming success
2025-04-09 18:14:41,209 - FireflyDB - DEBUG - Delete result: b':1\r\n'
2025-04-09 18:14:41,209 - FireflyDB - DEBUG - Deleted key 'pipeline:list:0'. Count: 1
2025-04-09 18:14:41,211 - FireflyDB - DEBUG - Delete result: b':1\r\n'
2025-04-09 18:14:41,211 - FireflyDB - DEBUG - Deleted key 'pipeline:string:1'. Count: 1
2025-04-09 18:14:41,222 - FireflyDB - DEBUG - Delete result: b':1\r\n'
2025-04-09 18:14:41,223 - FireflyDB - DEBUG - Deleted key 'pipeline:list:1'. Count: 1
2025-04-09 18:14:41,225 - FireflyDB - DEBUG - Delete result: b':0\r\n'
2025-04-09 18:14:41,225 - FireflyDB - DEBUG - Deleted key 'pipeline:string:2'. Count: 0
2025-04-09 18:14:41,231 - FireflyDB - DEBUG - Delete result: b':1\r\n'
2025-04-09 18:14:41,231 - FireflyDB - DEBUG - Deleted key 'pipeline:list:2'. Count: 1
2025-04-09 18:14:41,233 - FireflyDB - DEBUG - Delete result: b':0\r\n'
2025-04-09 18:14:41,233 - FireflyDB - DEBUG - Deleted key 'pipeline:string:3'. Count: 0
2025-04-09 18:14:41,246 - FireflyDB - DEBUG - Delete result: b':1\r\n'
2025-04-09 18:14:41,247 - FireflyDB - DEBUG - Deleted key 'pipeline:list:3'. Count: 1
2025-04-09 18:14:41,248 - FireflyDB - DEBUG - Delete result: b':0\r\n'
2025-04-09 18:14:41,248 - FireflyDB - DEBUG - Deleted key 'pipeline:string:4'. Count: 0
2025-04-09 18:14:41,258 - FireflyDB - DEBUG - Delete result: b':1\r\n'
2025-04-09 18:14:41,258 - FireflyDB - DEBUG - Deleted key 'pipeline:list:4'. Count: 1
2025-04-09 18:14:41,260 - FireflyDB - DEBUG - Delete result: b':0\r\n'
2025-04-09 18:14:41,260 - FireflyDB - DEBUG - Deleted key 'pipeline:string:5'. Count: 0
2025-04-09 18:14:41,273 - FireflyDB - DEBUG - Delete result: b':1\r\n'
2025-04-09 18:14:41,273 - FireflyDB - DEBUG - Deleted key 'pipeline:list:5'. Count: 1
2025-04-09 18:14:41,275 - FireflyDB - DEBUG - Delete result: b':0\r\n'
2025-04-09 18:14:41,275 - FireflyDB - DEBUG - Deleted key 'pipeline:hash'. Count: 0
2025-04-09 18:14:41,276 - FireflyDB - INFO - Cleanup complete
2025-04-09 18:14:41,276 - FireflyDB - DEBUG - Destroying client connection
2025-04-09 18:14:41,284 - FireflyDB - DEBUG - Client connection destroyed
2025-04-09 18:14:41,285 - FireflyDB - INFO - Test completed
2025-04-09 18:14:41,285 - FireflyDB - INFO - Script completed successfully.

BIN
examples/libfirefly.dll Normal file

Binary file not shown.

318
firefly.h Normal file
View File

@ -0,0 +1,318 @@
#pragma once
#include <stdbool.h> // For bool type
#include <stddef.h> // For size_t (though not directly used, common practice)
#ifdef __cplusplus
extern "C" {
#endif
// --- Client Management ---
/**
* @brief Creates a new Firefly client instance and connects to the server.
* @param host Null-terminated UTF-8 string for the server hostname or IP.
* @param port The server port number.
* @return A handle (void*) representing the client instance, or NULL on failure.
* This handle must be passed to other functions and eventually freed with DestroyClient.
*/
void* CreateClient(const char* host, int port);
/**
* @brief Disposes of the Firefly client instance and frees associated resources.
* @param handle The client handle obtained from CreateClient.
*/
void DestroyClient(void* handle);
/**
* @brief Authenticates the client connection with the server.
* @param handle The client handle.
* @param password Null-terminated UTF-8 string for the password.
* @return true if authentication was successful, false otherwise.
*/
bool Authenticate(void* handle, const char* password);
// --- String Operations ---
/**
* @brief Sets a string value for a given key.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the key.
* @param value Null-terminated UTF-8 string for the value.
* @return true if the operation was successful, false otherwise.
*/
bool StringSet(void* handle, const char* key, const char* value);
/**
* @brief Gets the string value for a given key.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the key.
* @return A pointer to a null-terminated UTF-8 string containing the value.
* This string is allocated by the library and MUST be freed by the caller using FreeString().
* Returns NULL if the key does not exist or an error occurs.
*/
char* StringGet(void* handle, const char* key);
/**
* @brief Deletes a key.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the key to delete.
* @return The number of keys deleted (usually 1 if the key existed, 0 otherwise), or 0 on error.
*/
int Delete(void* handle, const char* key);
/**
* @brief Frees memory allocated by the library for returned strings (e.g., from StringGet).
* @param str Pointer to the string allocated by the library.
*/
void FreeString(char* str);
// --- List Operations ---
/**
* @brief Adds a value to the beginning (left side) of a list.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the list key.
* @param value Null-terminated UTF-8 string for the value to add.
* @return The length of the list after the push operation, or 0 on error.
*/
int ListLeftPush(void* handle, const char* key, const char* value);
/**
* @brief Adds a value to the end (right side) of a list.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the list key.
* @param value Null-terminated UTF-8 string for the value to add.
* @return The length of the list after the push operation, or 0 on error.
*/
int ListRightPush(void* handle, const char* key, const char* value);
/**
* @brief Removes and returns the first element (left side) of a list.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the list key.
* @return A pointer to a null-terminated UTF-8 string containing the popped value.
* This string is allocated by the library and MUST be freed by the caller using FreeString().
* Returns NULL if the list is empty or an error occurs.
*/
char* ListLeftPop(void* handle, const char* key);
/**
* @brief Removes and returns the last element (right side) of a list.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the list key.
* @return A pointer to a null-terminated UTF-8 string containing the popped value.
* This string is allocated by the library and MUST be freed by the caller using FreeString().
* Returns NULL if the list is empty or an error occurs.
*/
char* ListRightPop(void* handle, const char* key);
/**
* @brief Gets a range of elements from a list.
* @param handle The client handle.
* @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().
* Returns NULL if the key does not exist or an error occurs.
*/
char* ListRange(void* handle, const char* key, int start, int stop);
/**
* @brief Gets the element at the specified index in a list.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the list key.
* @param index The 0-based index of the element.
* @return A pointer to a null-terminated UTF-8 string containing the element.
* This string is allocated by the library and MUST be freed by the caller using FreeString().
* Returns NULL if the index is out of range or an error occurs.
*/
char* ListIndex(void* handle, const char* key, int index);
/**
* @brief Sets the list element at the specified index.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the list key.
* @param index The 0-based index of the element to set.
* @param value Null-terminated UTF-8 string for the new value.
* @return true if the operation was successful, false if the index is out of range or an error occurs.
*/
bool ListSet(void* handle, const char* key, int index, const char* value);
/**
* @brief Returns the index of the first occurrence of an element in a list.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the list key.
* @param element Null-terminated UTF-8 string for the element to find.
* @param rank Optional rank for finding duplicates (not typically used/supported).
* @param maxlen Optional max length to search (not typically used/supported).
* @return The 0-based index of the element, or -1 if not found or on error.
*/
int ListPosition(void* handle, const char* key, const char* element, int rank, int maxlen);
/**
* @brief Trims a list to contain only the specified range of elements.
* @param handle The client handle.
* @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 for end.
* @return true if the operation was successful, false otherwise.
*/
bool ListTrim(void* handle, const char* key, int start, int stop);
/**
* @brief Removes occurrences of elements from a list.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the list key.
* @param count Number of occurrences to remove:
* count > 0: Remove elements equal to 'element' moving from head to tail.
* count < 0: Remove elements equal to 'element' moving from tail to head.
* count = 0: Remove all elements equal to 'element'.
* @param element Null-terminated UTF-8 string for the element to remove.
* @return The number of elements removed, or 0 on error.
*/
int ListRemove(void* handle, const char* key, int count, const char* element);
// --- Hash Operations ---
/**
* @brief Sets the value of a field within a hash.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the hash key.
* @param field Null-terminated UTF-8 string for the field name.
* @param value Null-terminated UTF-8 string for the value.
* @return true if the operation was successful (field created or updated), false on error.
*/
bool HashSet(void* handle, const char* key, const char* field, const char* value);
/**
* @brief Gets the value of a field within a hash.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the hash key.
* @param field Null-terminated UTF-8 string for the field name.
* @return A pointer to a null-terminated UTF-8 string containing the field's value.
* This string is allocated by the library and MUST be freed by the caller using FreeString().
* Returns NULL if the key or field does not exist or an error occurs.
*/
char* HashGet(void* handle, const char* key, const char* field);
/**
* @brief Deletes a field from a hash.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the hash key.
* @param field Null-terminated UTF-8 string for the field to delete.
* @return true if the field was deleted, false if the field or key did not exist or on error.
*/
bool HashDelete(void* handle, const char* key, const char* field);
/**
* @brief Checks if a field exists within a hash.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the hash key.
* @param field Null-terminated UTF-8 string for the field name.
* @return true if the field exists, false otherwise or on error.
*/
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().
* Returns NULL if the key does not exist or an error occurs.
*/
char* HashGetAll(void* handle, const char* key);
/**
* @brief Sets multiple fields and values in a hash.
* @param handle The client handle.
* @param key Null-terminated UTF-8 string for the hash key.
* @param fieldValuePairs Null-terminated UTF-8 string containing space-separated field-value pairs.
* Example: "field1 value1 field2 value2 field3 value3"
* @warning Values containing spaces are not properly handled by the basic parsing.
* @return true if the operation was successful, false on error (e.g., odd number of items in pairs string).
*/
bool HashMultiSet(void* handle, const char* key, const char* fieldValuePairs);
// --- Raw Command Execution ---
/**
* @brief Executes a raw command string with optional arguments.
* @warning The argument parsing is basic (simple space split) and may not handle arguments with spaces correctly.
* Prefer using the specific typed functions (StringSet, ListLeftPush, etc.) for reliability.
* @param handle The client handle.
* @param command Null-terminated UTF-8 string for the command (e.g., "PING").
* @param args Null-terminated UTF-8 string containing space-separated arguments (e.g., "mykey myvalue").
* @return A pointer to a null-terminated UTF-8 string containing the raw server response.
* This string is allocated by the library and MUST be freed by the caller using FreeString().
* Returns NULL on error.
*/
char* ExecuteCommand(void* handle, const char* command, const char* args);
/**
* @brief Gets all keys matching the specified pattern.
* @param handle The client handle.
* @param pattern Null-terminated UTF-8 string for the pattern to match against keys (e.g., "*" for all keys).
* @return A pointer to a null-terminated UTF-8 string containing newline-delimited keys.
* This string is allocated by the library and MUST be freed by the caller using FreeString().
* Returns NULL if no keys are found or on error.
*/
char* Keys(void* handle, const char* pattern);
// --- Pipeline Operations ---
/**
* @brief Enables or disables pipeline mode for the client.
* @param handle The client handle.
* @param enabled true to enable pipeline mode, false to disable.
* @return true if the mode was set successfully, false on error.
*/
bool SetPipelineMode(void* handle, bool enabled);
/**
* @brief Sets the maximum number of commands to batch in pipeline mode.
* @param handle The client handle.
* @param size The maximum number of commands to queue before sending.
* @return true if the batch size was set successfully, false on error (e.g., invalid size).
*/
bool SetBatchSize(void* handle, int size);
/**
* @brief Sends all queued commands to the server immediately.
* @param handle The client handle.
* @return A pointer to a null-terminated UTF-8 string containing the raw combined server response for all flushed commands.
* This string is allocated by the library and MUST be freed by the caller using FreeString().
* Returns NULL if the queue was empty or an error occurs.
*/
char* FlushPipeline(void* handle);
/**
* @brief Gets the number of commands currently waiting in the pipeline queue.
* @param handle The client handle.
* @return The number of queued commands, or 0 on error.
*/
int GetQueuedCommandCount(void* handle);
/**
* @brief Checks if pipeline mode is currently enabled for the client.
* @param handle The client handle.
* @return true if pipeline mode is enabled, false otherwise or on error.
*/
bool IsPipelineMode(void* handle);
/**
* @brief Gets the current maximum batch size configured for pipeline mode.
* @param handle The client handle.
* @return The maximum batch size, or 0 on error.
*/
int GetBatchSize(void* handle);
#ifdef __cplusplus
}
#endif

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

BIN
resources/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

278
src/FireflyClient.cs Normal file
View File

@ -0,0 +1,278 @@
using System.Net.Sockets;
using System.Text;
namespace FireflyClient
{
/// <summary>
/// FireflyClient provides a clean API for interacting with the Firefly server.
/// This can be used as a library for integrating with other applications.
/// </summary>
public partial class FireflyClient : IDisposable
{
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;
// Static instance for native interop
// private static FireflyClient? _instance;
/// <summary>
/// Creates a new FireflyClient and connects to the specified server
/// </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);
_stream = _client.GetStream();
}
/// <summary>
/// Creates a new FireflyClient, connects to the server and authenticates
/// </summary>
public FireflyClient(string host, int port, string password) : this(host, port)
{
_password = password;
if (!string.IsNullOrEmpty(_password))
{
Authenticate(_password);
}
}
/// <summary>
/// Authenticates with the server using the provided password
/// </summary>
public bool Authenticate(string password)
{
string response = ExecuteCommand("AUTH", password);
_isAuthenticated = response.StartsWith("+OK");
return _isAuthenticated;
}
/// <summary>
/// Executes a raw command with any number of arguments
/// </summary>
public string ExecuteCommand(string command, params string[] args)
{
if (!_client.Connected)
{
throw new InvalidOperationException("Not connected to server");
}
// Build the command string
string fullCommand = $"{command.ToUpperInvariant()}{string.Join("", args.Select(arg => $" {QuoteIfNeeded(arg)}"))}";
// Handle special commands that should not be pipelined
if (command.Equals("AUTH", StringComparison.OrdinalIgnoreCase) ||
command.Equals("PING", StringComparison.OrdinalIgnoreCase) ||
command.Equals("QUIT", StringComparison.OrdinalIgnoreCase))
{
return SendCommandInternal(fullCommand);
}
// Add command to queue
_commandQueue.Enqueue(fullCommand);
// Process queue if we've reached batch size or if pipeline mode is disabled
if (_commandQueue.Count >= _maxBatchSize || !_isPipelineMode)
{
return ProcessCommandQueue();
}
return "+QUEUED\r\n";
}
// Helper method to quote a string if needed
private static string QuoteIfNeeded(string input)
{
if (input.Contains(' ') || input.Contains('\''))
{
// If the input already has quotes, don't add more
if (input.StartsWith('"') && input.EndsWith('"'))
{
return input;
}
// Escape any quotes in the input
return $"\"{input.Replace("\"", "\\\"")}\"";
}
return input;
}
// Private method to send a command to the server
private string SendCommandInternal(string command)
{
// Add CRLF if not present
if (!command.EndsWith("\r\n"))
{
command += "\r\n";
}
byte[] commandBytes = Encoding.UTF8.GetBytes(command);
_stream.Write(commandBytes, 0, commandBytes.Length);
// Read response
int bytesRead = _stream.Read(_responseBuffer, 0, _responseBuffer.Length);
return bytesRead > 0 ? Encoding.UTF8.GetString(_responseBuffer, 0, bytesRead) : string.Empty;
}
private string ProcessCommandQueue()
{
if (_commandQueue.Count == 0) return string.Empty;
// Build the pipeline command string
string pipelineCommand = string.Join("\r\n", _commandQueue) + "\r\n";
_commandQueue.Clear();
return SendCommandInternal(pipelineCommand);
}
/// <summary>
/// Enables or disables pipeline mode
/// </summary>
public void SetPipelineMode(bool enabled)
{
_isPipelineMode = enabled;
if (!enabled)
{
ProcessCommandQueue();
}
}
/// <summary>
/// Flushes any queued commands in pipeline mode
/// </summary>
public string FlushPipeline()
{
return ProcessCommandQueue();
}
/// <summary>
/// Sets the maximum number of commands to batch before sending
/// </summary>
public void SetBatchSize(int size)
{
if (size <= 0)
{
throw new ArgumentException("Batch size must be greater than 0");
}
_maxBatchSize = Math.Min(size, _maxPipelineSize);
}
/// <summary>
/// Gets the current number of queued commands
/// </summary>
public int QueuedCommandCount => _commandQueue.Count;
/// <summary>
/// Gets whether pipeline mode is enabled
/// </summary>
public bool IsPipelineMode => _isPipelineMode;
/// <summary>
/// Gets the maximum batch size
/// </summary>
public int MaxBatchSize => _maxBatchSize;
/// <summary>
/// Gets whether the client is connected to the server
/// </summary>
public bool IsConnected => _client.Connected;
/// <summary>
/// Gets whether the client is authenticated
/// </summary>
public bool IsAuthenticated => _isAuthenticated;
/// <summary>
/// Gets all keys matching the specified pattern
/// </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 = "*")
{
try
{
string response = ExecuteCommand("KEYS", pattern);
if (string.IsNullOrEmpty(response) || !response.StartsWith('+'))
{
return [];
}
// Remove the '+' prefix and split by newlines
string keysStr = response[1..].Trim();
return string.IsNullOrEmpty(keysStr)
? []
: [.. keysStr.Split('\n')];
}
catch
{
return [];
}
}
/// <summary>
/// Parses an array response from the server
/// </summary>
protected static List<string> ParseArrayResponse(string response)
{
var result = new List<string>();
// If not an array response, return empty list
if (!response.StartsWith('*'))
{
return result;
}
string[] parts = response.Split("\r\n");
// Skip the first line which just contains the * prefix
for (int i = 1; i < parts.Length; i++)
{
if (!string.IsNullOrEmpty(parts[i]) && (parts[i].StartsWith('+') || parts[i].StartsWith('$')))
{
// Extract the value part (after the + or $ prefix)
result.Add(parts[i][1..]);
}
}
return result;
}
/// <inheritdoc/>
public void Dispose()
{
try
{
// Try to gracefully disconnect by sending QUIT command
if (_client.Connected)
{
try
{
ExecuteCommand("QUIT");
}
catch
{
// Ignore exceptions during QUIT command
}
}
}
finally
{
_stream.Dispose();
_client.Dispose();
GC.SuppressFinalize(this);
}
}
}
}

86
src/HashOperations.cs Normal file
View File

@ -0,0 +1,86 @@
namespace FireflyClient
{
public partial class FireflyClient
{
#region Hash Operations
/// <summary>
/// Sets a field in a hash
/// </summary>
public bool HashSet(string key, string field, string value)
{
string 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)
{
string response = ExecuteCommand("HGET", key, field);
if (response.StartsWith('+'))
{
return response[1..].TrimEnd('\r', '\n');
}
return string.Empty;
}
/// <summary>
/// Deletes a field from a hash
/// </summary>
public bool HashDelete(string key, string field)
{
string 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)
{
string 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)
{
string response = ExecuteCommand("HGETALL", key);
List<string> items = ParseArrayResponse(response);
var result = new Dictionary<string, string>();
for (int i = 0; i < items.Count; i += 2)
{
if (i + 1 < items.Count)
{
result[items[i]] = items[i + 1];
}
}
return result;
}
/// <summary>
/// Sets multiple fields in a hash at once
/// </summary>
public bool HashMultiSet(string key, Dictionary<string, string> fieldValues)
{
List<string> args = [key];
foreach (var kvp in fieldValues)
{
args.Add(kvp.Key);
args.Add(kvp.Value);
}
string response = ExecuteCommand("HMSET", [.. args]);
return response.StartsWith("+OK");
}
#endregion
}
}

159
src/ListOperations.cs Normal file
View File

@ -0,0 +1,159 @@
namespace FireflyClient
{
public partial class FireflyClient
{
#region List Operations
/// <summary>
/// Adds values to the beginning of a list
/// </summary>
public 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;
}
/// <summary>
/// Adds values to the end of a list
/// </summary>
public 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;
}
/// <summary>
/// Removes and returns the first element of a list
/// </summary>
public string ListLeftPop(string key)
{
string response = ExecuteCommand("LPOP", key);
if (response.StartsWith('+'))
{
return response[1..].TrimEnd('\r', '\n');
}
return string.Empty;
}
/// <summary>
/// Removes and returns the last element of a list
/// </summary>
public string ListRightPop(string key)
{
string response = ExecuteCommand("RPOP", key);
if (response.StartsWith('+'))
{
return response[1..].TrimEnd('\r', '\n');
}
return string.Empty;
}
/// <summary>
/// Gets a range of elements from a list
/// </summary>
public List<string> ListRange(string key, int start, int stop)
{
string 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)
{
string response = ExecuteCommand("LINDEX", key, index.ToString());
if (response.StartsWith('+'))
{
return response[1..].TrimEnd('\r', '\n');
}
return string.Empty;
}
/// <summary>
/// Sets the element at the specified index in a list
/// </summary>
public bool ListSet(string key, int index, string value)
{
string 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)
{
var args = new List<string> { key, element };
if (rank != 1)
{
args.Add("RANK");
args.Add(rank.ToString());
}
if (maxlen > 0)
{
args.Add("MAXLEN");
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;
}
/// <summary>
/// Trims a list to the specified range
/// </summary>
public bool ListTrim(string key, int start, int stop)
{
string 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)
{
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;
}
#endregion
}
}

718
src/NativeInterop.cs Normal file
View File

@ -0,0 +1,718 @@
using System.Runtime.InteropServices;
using System.Text;
namespace FireflyClient
{
public partial class FireflyClient
{
// Helper to safely get client from handle
private static FireflyClient? GetClientFromHandle(IntPtr handle)
{
if (handle == IntPtr.Zero) return null;
try
{
GCHandle gch = (GCHandle)handle;
return gch.Target as FireflyClient;
}
catch
{
return null; // Invalid handle
}
}
// Helper to marshal string result to IntPtr
private static IntPtr MarshalStringResult(string? result)
{
if (result == null) return IntPtr.Zero;
byte[] bytes = Encoding.UTF8.GetBytes(result);
IntPtr 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);
}
// Helper to marshal Dictionary<string, string> result to IntPtr (newline-delimited field=value)
private static IntPtr MarshalStringDictionaryResult(Dictionary<string, string>? result)
{
if (result == null || result.Count == 0) return IntPtr.Zero;
string joinedResult = string.Join("\n", result.Select(kvp => $"{kvp.Key}={kvp.Value}"));
return MarshalStringResult(joinedResult);
}
#region Native Interop Methods
/// <summary>
/// Creates a new FireflyClient instance for native interop
/// </summary>
[UnmanagedCallersOnly(EntryPoint = "CreateClient")]
public static IntPtr CreateClient(IntPtr hostPtr, int port)
{
try
{
// Use UTF8 for host string
string host = Marshal.PtrToStringUTF8(hostPtr) ?? "127.0.0.1";
var client = new FireflyClient(host, port);
var handle = GCHandle.Alloc(client);
return GCHandle.ToIntPtr(handle);
}
catch
{
return IntPtr.Zero;
}
}
/// <summary>
/// Destroys a FireflyClient instance created for native interop
/// </summary>
[UnmanagedCallersOnly(EntryPoint = "DestroyClient")]
public static void DestroyClient(IntPtr handle)
{
if (handle != IntPtr.Zero)
{
try
{
GCHandle gch = GCHandle.FromIntPtr(handle);
if (gch.Target is FireflyClient client)
{
client.Dispose();
}
// Check if handle is still allocated before freeing
if (gch.IsAllocated)
{
gch.Free();
}
}
catch { /* Handle potential errors with invalid handles */ }
}
}
/// <summary>
/// Authenticates with the server using the provided password
/// </summary>
[UnmanagedCallersOnly(EntryPoint = "Authenticate")]
public static bool NativeAuthenticate(IntPtr handle, IntPtr passwordPtr)
{
try
{
// Use UTF8 for password string
string password = Marshal.PtrToStringUTF8(passwordPtr) ?? string.Empty;
var client = GetClientFromHandle(handle);
return client?.Authenticate(password) ?? false;
}
catch
{
return false;
}
}
/// <summary>
/// Executes a raw command. Note: Argument parsing is basic.
/// Consider using specific functions instead for reliability.
/// </summary>
[UnmanagedCallersOnly(EntryPoint = "ExecuteCommand")]
public static IntPtr NativeExecuteCommand(IntPtr handle, IntPtr commandPtr, IntPtr argsPtr)
{
try
{
// Use UTF8 for command and args strings
string command = Marshal.PtrToStringUTF8(commandPtr) ?? string.Empty;
string 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);
}
catch
{
return IntPtr.Zero;
}
}
/// <summary>
/// Frees a string allocated by the native interop methods
/// </summary>
[UnmanagedCallersOnly(EntryPoint = "FreeString")]
public static void NativeFreeString(IntPtr ptr)
{
if (ptr != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptr);
}
}
// --- String Operations ---
/// <summary>
/// Sets a string value for a given key (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 key.</param>
/// <param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value.</param>
/// <returns>True if the command was successful (e.g., server replied OK), false otherwise.</returns>
[UnmanagedCallersOnly(EntryPoint = "StringSet")]
public static bool NativeStringSet(IntPtr handle, IntPtr keyPtr, IntPtr valuePtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
return client?.StringSet(key, value) ?? false;
}
catch { return false; }
}
/// <summary>
/// Gets a string value for a given key (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 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 key not found.</returns>
[UnmanagedCallersOnly(EntryPoint = "StringGet")]
public static IntPtr NativeStringGet(IntPtr handle, IntPtr keyPtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string? result = client?.StringGet(key);
return MarshalStringResult(result);
}
catch { return IntPtr.Zero; }
}
/// <summary>
/// Deletes one or more keys (Native Interop).
/// Note: Currently only supports deleting a single key via the C# Delete method.
/// </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 key to delete.</param>
/// <returns>The number of keys that were removed (typically 1 or 0), or 0 on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "Delete")]
public static int NativeDelete(IntPtr handle, IntPtr keyPtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
// Assuming Delete returns the number of keys deleted (usually 1 or 0)
return client?.Delete(key) ?? 0;
}
catch { return 0; }
}
// --- List Operations ---
/// <summary>
/// Adds a value to the beginning 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>
/// <param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value to add.</param>
/// <returns>The length of the list after the push operation, or 0 on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "ListLeftPush")]
public static int NativeListLeftPush(IntPtr handle, IntPtr keyPtr, IntPtr valuePtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
// Assuming LPUSH returns the new length of the list
return client?.ListLeftPush(key, value) ?? 0;
}
catch { return 0; }
}
/// <summary>
/// Adds a value to the end 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>
/// <param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value to add.</param>
/// <returns>The length of the list after the push operation, or 0 on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "ListRightPush")]
public static int NativeListRightPush(IntPtr handle, IntPtr keyPtr, IntPtr valuePtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
// Assuming RPUSH returns the new length of the list
return client?.ListRightPush(key, value) ?? 0;
}
catch { return 0; }
}
/// <summary>
/// Removes and returns the first element 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>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>
[UnmanagedCallersOnly(EntryPoint = "ListLeftPop")]
public static IntPtr NativeListLeftPop(IntPtr handle, IntPtr keyPtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string? result = client?.ListLeftPop(key);
return MarshalStringResult(result);
}
catch { return IntPtr.Zero; }
}
/// <summary>
/// Removes and returns the last element 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>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>
[UnmanagedCallersOnly(EntryPoint = "ListRightPop")]
public static IntPtr NativeListRightPop(IntPtr handle, IntPtr keyPtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string? result = client?.ListRightPop(key);
return MarshalStringResult(result);
}
catch { return IntPtr.Zero; }
}
/// <summary>
/// Gets a range of elements from 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>
/// <param name="start">The start index (0-based).</param>
/// <param name="stop">The stop index (inclusive, use -1 for end).</param>
/// <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)
{
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; }
}
/// <summary>
/// Gets an element from a list by its index (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>
/// <param name="index">The index of the element (0-based).</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 index is out of range.</returns>
[UnmanagedCallersOnly(EntryPoint = "ListIndex")]
public static IntPtr NativeListIndex(IntPtr handle, IntPtr keyPtr, int index)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string? result = client?.ListIndex(key, index);
return MarshalStringResult(result);
}
catch { return IntPtr.Zero; }
}
/// <summary>
/// Sets the value of an element in a list by its index (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>
/// <param name="index">The index of the element to set (0-based).</param>
/// <param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the new value.</param>
/// <returns>True if the command was successful, false otherwise (e.g., index out of range).</returns>
[UnmanagedCallersOnly(EntryPoint = "ListSet")]
public static bool NativeListSet(IntPtr handle, IntPtr keyPtr, int index, IntPtr valuePtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
return client?.ListSet(key, index, value) ?? false;
}
catch { return false; }
}
/// <summary>
/// Returns the index of the first occurrence of an element in a list (Native Interop).
/// Rank and MaxLen parameters are currently ignored.
/// </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>
/// <param name="elementPtr">Pointer to a null-terminated UTF-8 string representing the element to find.</param>
/// <param name="rank">Optional rank (ignored).</param>
/// <param name="maxlen">Optional max length (ignored).</param>
/// <returns>The 0-based index of the element, or -1 if not found or on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "ListPosition")]
public static int NativeListPosition(IntPtr handle, IntPtr keyPtr, IntPtr elementPtr, int rank, int maxlen)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string 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;
}
catch { return -1; }
}
/// <summary>
/// Trims a list to contain only the specified range of elements (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>
/// <param name="start">The start index (0-based).</param>
/// <param name="stop">The stop index (inclusive, use -1 for end).</param>
/// <returns>True if the command was successful, false otherwise.</returns>
[UnmanagedCallersOnly(EntryPoint = "ListTrim")]
public static bool NativeListTrim(IntPtr handle, IntPtr keyPtr, int start, int stop)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
return client?.ListTrim(key, start, stop) ?? false;
}
catch { return false; }
}
/// <summary>
/// Removes occurrences of elements from 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>
/// <param name="count">Number of occurrences to remove (see C# ListRemove docs).</param>
/// <param name="elementPtr">Pointer to a null-terminated UTF-8 string representing the element to remove.</param>
/// <returns>The number of elements removed, or 0 on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "ListRemove")]
public static int NativeListRemove(IntPtr handle, IntPtr keyPtr, int count, IntPtr elementPtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string element = Marshal.PtrToStringUTF8(elementPtr) ?? string.Empty;
return client?.ListRemove(key, count, element) ?? 0;
}
catch { return 0; }
}
// --- Hash Operations ---
/// <summary>
/// Sets the value of a field within a hash (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 hash key.</param>
/// <param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field name.</param>
/// <param name="valuePtr">Pointer to a null-terminated UTF-8 string representing the value to set.</param>
/// <returns>True if the field was new or updated successfully, false on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "HashSet")]
public static bool NativeHashSet(IntPtr handle, IntPtr keyPtr, IntPtr fieldPtr, IntPtr valuePtr)
{
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;
// Assuming HSET returns 1 if field is new, 0 if updated
return client?.HashSet(key, field, value) ?? false; // Adapt if C# returns differently
}
catch { return false; }
}
/// <summary>
/// Gets the value of a field within a hash (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 hash key.</param>
/// <param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field name.</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 field/key not found.</returns>
[UnmanagedCallersOnly(EntryPoint = "HashGet")]
public static IntPtr NativeHashGet(IntPtr handle, IntPtr keyPtr, IntPtr fieldPtr)
{
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);
}
catch { return IntPtr.Zero; }
}
/// <summary>
/// Deletes a field from a hash (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 hash key.</param>
/// <param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field to delete.</param>
/// <returns>True if the field was deleted, false otherwise (e.g., field/key not found).</returns>
[UnmanagedCallersOnly(EntryPoint = "HashDelete")]
public static bool NativeHashDelete(IntPtr handle, IntPtr keyPtr, IntPtr fieldPtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty;
// Assuming HDEL returns true if field was deleted, false otherwise
return client?.HashDelete(key, field) ?? false;
}
catch { return false; }
}
/// <summary>
/// Checks if a field exists within a hash (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 hash key.</param>
/// <param name="fieldPtr">Pointer to a null-terminated UTF-8 string representing the field name.</param>
/// <returns>True if the field exists, false otherwise.</returns>
[UnmanagedCallersOnly(EntryPoint = "HashFieldExists")]
public static bool NativeHashFieldExists(IntPtr handle, IntPtr keyPtr, IntPtr fieldPtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty;
return client?.HashFieldExists(key, field) ?? false;
}
catch { return false; }
}
/// <summary>
/// Sets multiple fields and values in a hash (Native Interop).
/// Parses a space-separated string of field-value pairs.
/// </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 hash key.</param>
/// <param name="fieldValuePairsPtr">Pointer to a null-terminated UTF-8 string of space-separated field-value pairs.</param>
/// <returns>True if successful, false on error (e.g., odd number of pairs).</returns>
[UnmanagedCallersOnly(EntryPoint = "HashMultiSet")]
public static bool NativeHashMultiSet(IntPtr handle, IntPtr keyPtr, IntPtr fieldValuePairsPtr)
{
try
{
var client = GetClientFromHandle(handle);
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
string fieldValuePairs = Marshal.PtrToStringUTF8(fieldValuePairsPtr) ?? string.Empty;
string[] pairs = fieldValuePairs.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (pairs.Length % 2 != 0)
{
// Invalid input: odd number of elements
return false;
}
var dict = new Dictionary<string, string>();
for (int i = 0; i < pairs.Length; i += 2)
{
dict[pairs[i]] = pairs[i + 1];
}
return client?.HashMultiSet(key, dict) ?? false;
}
catch { return false; }
}
/// <summary>
/// Gets all fields and values from a hash (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 hash key.</param>
/// <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)
{
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; }
}
// --- Pipeline Operations (Already Implemented) ---
/// <summary>
/// Enables or disables pipeline mode for the client (Native Interop).
/// </summary>
/// <param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
/// <param name="enabled">True to enable pipeline mode, false to disable.</param>
/// <returns>True if the mode was set successfully, false on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "SetPipelineMode")]
public static bool NativeSetPipelineMode(IntPtr handle, bool enabled)
{
try
{
var client = GetClientFromHandle(handle);
if (client == null) return false;
client.SetPipelineMode(enabled);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Sets the maximum number of commands to batch in pipeline mode (Native Interop).
/// </summary>
/// <param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
/// <param name="size">The maximum number of commands to queue before sending.</param>
/// <returns>True if the batch size was set successfully, false on error (e.g., invalid size).</returns>
[UnmanagedCallersOnly(EntryPoint = "SetBatchSize")]
public static bool NativeSetBatchSize(IntPtr handle, int size)
{
try
{
var client = GetClientFromHandle(handle);
if (client == null) return false;
client.SetBatchSize(size);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Sends all queued commands to the server immediately (Native Interop).
/// </summary>
/// <param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
/// <returns>Pointer to a null-terminated UTF-8 string containing the server's combined response, allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error or if queue was empty.</returns>
[UnmanagedCallersOnly(EntryPoint = "FlushPipeline")]
public static IntPtr NativeFlushPipeline(IntPtr handle)
{
try
{
var client = GetClientFromHandle(handle);
string? response = client?.FlushPipeline();
return MarshalStringResult(response);
}
catch
{
return IntPtr.Zero;
}
}
/// <summary>
/// Gets the number of commands currently waiting in the pipeline queue (Native Interop).
/// </summary>
/// <param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
/// <returns>The number of queued commands, or 0 on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "GetQueuedCommandCount")]
public static int NativeGetQueuedCommandCount(IntPtr handle)
{
try
{
var client = GetClientFromHandle(handle);
return client?.QueuedCommandCount ?? 0;
}
catch
{
return 0;
}
}
/// <summary>
/// Checks if pipeline mode is currently enabled for the client (Native Interop).
/// </summary>
/// <param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
/// <returns>True if pipeline mode is enabled, false otherwise or on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "IsPipelineMode")]
public static bool NativeIsPipelineMode(IntPtr handle)
{
try
{
var client = GetClientFromHandle(handle);
return client?.IsPipelineMode ?? false;
}
catch
{
return false;
}
}
/// <summary>
/// Gets the current maximum batch size configured for pipeline mode (Native Interop).
/// </summary>
/// <param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
/// <returns>The maximum batch size, or 0 on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "GetBatchSize")]
public static int NativeGetBatchSize(IntPtr handle)
{
try
{
var client = GetClientFromHandle(handle);
return client?.MaxBatchSize ?? 0;
}
catch
{
return 0;
}
}
/// <summary>
/// Gets all keys matching the specified pattern (Native Interop).
/// </summary>
/// <param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
/// <param name="patternPtr">Pointer to a null-terminated UTF-8 string representing the pattern to match against keys.</param>
/// <returns>Pointer to a null-terminated UTF-8 string containing newline-delimited keys, allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error.</returns>
[UnmanagedCallersOnly(EntryPoint = "Keys")]
public static IntPtr NativeKeys(IntPtr handle, IntPtr patternPtr)
{
try
{
var client = GetClientFromHandle(handle);
string pattern = Marshal.PtrToStringUTF8(patternPtr) ?? "*";
List<string>? result = client?.Keys(pattern);
return MarshalStringListResult(result);
}
catch
{
return IntPtr.Zero;
}
}
#endregion
}
}

269
src/Program.cs Normal file
View File

@ -0,0 +1,269 @@
using System.Text;
#pragma warning disable IDE0130 // Namespace does not match folder structure
namespace FireflyClient
#pragma warning restore IDE0130 // Namespace does not match folder structure
{
/// <summary>
/// Command-line interface for Firefly
/// </summary>
public class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Firefly Client");
Console.WriteLine("Enter commands (type EXIT to quit)");
Console.WriteLine("Use double quotes for values with spaces: HSET email:1 subject \"Hello World\"");
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;
for (int i = 0; i < args.Length; i++)
{
if ((args[i] == "--host" || args[i] == "-h") && i + 1 < args.Length)
{
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;
}
}
try
{
await RunInteractiveClientAsync(host, port, password);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
static void PrintClientHelp()
{
Console.WriteLine("\nFirefly Client Usage:");
Console.WriteLine(" --host, -h <hostname> Server hostname or IP (default: 127.0.0.1)");
Console.WriteLine(" --port, -p <port> Server port (default: 6379)");
Console.WriteLine(" --password, --pass <pwd> Server password");
Console.WriteLine(" --help, -? Show this help message\n");
Console.WriteLine("Example: FireflyClient --host localhost --port 6380 --password secret123\n");
}
static Task RunInteractiveClientAsync(string host, int port, string password)
{
using var client = new FireflyClient(host, port);
Console.WriteLine($"Connecting to server at {host}:{port}...");
Console.WriteLine("Connected to server!");
// If password wasn't provided as a command line argument, prompt for it
if (string.IsNullOrEmpty(password))
{
Console.Write("Enter password (leave empty if no password required): ");
password = Console.ReadLine() ?? string.Empty;
}
// Authenticate if password was provided
if (!string.IsNullOrEmpty(password))
{
bool success = client.Authenticate(password);
// Check if authentication failed
if (!success)
{
Console.WriteLine("Authentication failed. Disconnecting.");
return Task.CompletedTask;
}
}
while (true)
{
// Get command from user
Console.Write("> ");
string 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')}");
}
catch (Exception ex)
{
Console.WriteLine($"Error while quitting: {ex.Message}");
}
break;
}
// Check for help commands
if (input.Equals("HELP", StringComparison.OrdinalIgnoreCase))
{
PrintBasicCommands();
continue;
}
// Handle clear screen command
else if (input.Equals("CLS", StringComparison.OrdinalIgnoreCase) ||
input.Equals("CLEAR", StringComparison.OrdinalIgnoreCase))
{
Console.Clear();
continue;
}
try
{
// Parse the command
string[] parts = SplitCommandLine(input);
if (parts.Length == 0) continue;
string command = parts[0];
string[] args = [.. parts.Skip(1)];
// Execute the command
string response = client.ExecuteCommand(command, args);
FormatAndPrintResponse(response);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
Console.WriteLine("Disconnected from server.");
return Task.CompletedTask;
}
// Helper method to split command line respecting quotes
static string[] SplitCommandLine(string commandLine)
{
var result = new List<string>();
bool inQuotes = false;
StringBuilder currentArg = new();
for (int i = 0; i < commandLine.Length; i++)
{
char c = commandLine[i];
if (c == '"')
{
inQuotes = !inQuotes;
// Don't include the quote character
}
else if (c == ' ' && !inQuotes)
{
// End of argument
if (currentArg.Length > 0)
{
result.Add(currentArg.ToString());
currentArg.Clear();
}
}
else
{
currentArg.Append(c);
}
}
// Add the last argument if any
if (currentArg.Length > 0)
{
result.Add(currentArg.ToString());
}
return [.. result];
}
static void FormatAndPrintResponse(string response)
{
// Remove trailing whitespace
response = response.TrimEnd();
if (response.StartsWith('*') && response.Contains("\r\n"))
{
// Format array responses for better readability
string[] parts = response.Split("\r\n");
if (parts.Length > 1)
{
Console.WriteLine("Server: Array response:");
for (int i = 1; i < parts.Length; i++)
{
if (!string.IsNullOrEmpty(parts[i]))
{
// 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]}");
}
}
}
return;
}
}
// Default formatting
Console.WriteLine($"Server: {response}");
}
// Helper method: Print basic commands
static void PrintBasicCommands()
{
Console.WriteLine("\n==== Basic Firefly Commands ====");
Console.WriteLine("\n## String Operations ##");
Console.WriteLine("SET key value # Set a string value");
Console.WriteLine("GET key # Get a string value");
Console.WriteLine("DEL key # Delete a key");
Console.WriteLine("SEXISTS key # Check if string key exists");
Console.WriteLine("\n## List Operations ##");
Console.WriteLine("LPUSH key value # Add value to the beginning of a list");
Console.WriteLine("RPUSH key value # Add value to the end of a list");
Console.WriteLine("LPOP key # Remove and return the first element");
Console.WriteLine("RPOP key # Remove and return the last element");
Console.WriteLine("LRANGE key start end # Get a range of elements (0 -1 for all)");
Console.WriteLine("LINDEX key index # Get element at specific position");
Console.WriteLine("LGET key [index] # Get entire list or element at index");
Console.WriteLine("LEXISTS key # Check if list key exists");
Console.WriteLine("\n## Hash Operations ##");
Console.WriteLine("HSET key field value # Set a field in a hash");
Console.WriteLine("HGET key field # Get a field from a hash");
Console.WriteLine("HDEL key field # Delete a field from a hash");
Console.WriteLine("HEXISTS key field # Check if field exists in hash");
Console.WriteLine("HGETALL key # Get all fields and values in a hash");
Console.WriteLine("HMSET key field1 val1... # Set multiple fields at once");
Console.WriteLine("HASKEY key # Check if hash key exists");
Console.WriteLine("\n## Server Operations ##");
Console.WriteLine("PING # Test connection");
Console.WriteLine("ECHO message # Echo a message");
Console.WriteLine("SAVE # Save database to disk");
Console.WriteLine("BGSAVE # Save database in background");
Console.WriteLine("QUIT # Gracefully disconnect from server");
Console.WriteLine("\n## Help Commands ##");
Console.WriteLine("HELP # Show this help message");
Console.WriteLine("HELP EMAIL # Show email storage examples");
Console.WriteLine("CLS, CLEAR # Clear the console screen");
Console.WriteLine("EXIT # Exit the client");
}
}
}

47
src/StringOperations.cs Normal file
View File

@ -0,0 +1,47 @@
namespace FireflyClient
{
public partial class FireflyClient
{
#region String Operations
/// <summary>
/// Sets a key-value pair
/// </summary>
public bool StringSet(string key, string value)
{
string response = ExecuteCommand("SET", key, value);
return response.StartsWith("+OK");
}
/// <summary>
/// Gets a value by key
/// </summary>
public string StringGet(string key)
{
string response = ExecuteCommand("GET", key);
if (response.StartsWith('+'))
{
return response[1..].TrimEnd('\r', '\n');
}
return string.Empty;
}
/// <summary>
/// Deletes a key from all stores (string, list, hash)
/// </summary>
public 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;
}
#endregion
}
}