This commit is contained in:
commit
4063fe6bd8
48
.gitea/workflows/build.yml
Normal file
48
.gitea/workflows/build.yml
Normal 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
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.editorconfig
|
||||
nuget.config
|
||||
*.7z
|
||||
bin
|
||||
obj
|
||||
.venv
|
||||
.vs
|
||||
.vscode
|
67
FireflyClient.csproj
Normal file
67
FireflyClient.csproj
Normal 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>
|
6
FireflyClient.csproj.user
Normal file
6
FireflyClient.csproj.user
Normal 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
25
FireflyClient.sln
Normal 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
|
17
Properties/PublishProfiles/FolderProfile.pubxml
Normal file
17
Properties/PublishProfiles/FolderProfile.pubxml
Normal 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>
|
8
Properties/PublishProfiles/FolderProfile.pubxml.user
Normal file
8
Properties/PublishProfiles/FolderProfile.pubxml.user
Normal 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
83
Properties/Resources.Designer.cs
generated
Normal 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
127
Properties/Resources.resx
Normal 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
622
README.md
Normal 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
BIN
artifacts/exe/FireflyClient
Normal file
Binary file not shown.
BIN
artifacts/exe/FireflyClient.dbg
Normal file
BIN
artifacts/exe/FireflyClient.dbg
Normal file
Binary file not shown.
BIN
artifacts/exe/FireflyClient.exe
Normal file
BIN
artifacts/exe/FireflyClient.exe
Normal file
Binary file not shown.
BIN
artifacts/exe/FireflyClient.pdb
Normal file
BIN
artifacts/exe/FireflyClient.pdb
Normal file
Binary file not shown.
471
artifacts/exe/FireflyClient.xml
Normal file
471
artifacts/exe/FireflyClient.xml
Normal 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>
|
BIN
artifacts/native/FireflyClient.pdb
Normal file
BIN
artifacts/native/FireflyClient.pdb
Normal file
Binary file not shown.
BIN
artifacts/native/FireflyClient.so.dbg
Normal file
BIN
artifacts/native/FireflyClient.so.dbg
Normal file
Binary file not shown.
471
artifacts/native/FireflyClient.xml
Normal file
471
artifacts/native/FireflyClient.xml
Normal 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>
|
BIN
artifacts/native/libFireflyClient.dll
Normal file
BIN
artifacts/native/libFireflyClient.dll
Normal file
Binary file not shown.
BIN
artifacts/native/libFireflyClient.so
Normal file
BIN
artifacts/native/libFireflyClient.so
Normal file
Binary file not shown.
52
build-all.ps1
Normal file
52
build-all.ps1
Normal 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
71
build-all.sh
Normal 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
176
examples/example.cpp
Normal 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
1438
examples/example.py
Normal file
File diff suppressed because it is too large
Load Diff
178
examples/firefly_debug.log
Normal file
178
examples/firefly_debug.log
Normal 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
BIN
examples/libfirefly.dll
Normal file
Binary file not shown.
318
firefly.h
Normal file
318
firefly.h
Normal 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
resources/icon.ico
Normal file
BIN
resources/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
BIN
resources/icon.png
Normal file
BIN
resources/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 261 KiB |
278
src/FireflyClient.cs
Normal file
278
src/FireflyClient.cs
Normal 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
86
src/HashOperations.cs
Normal 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
159
src/ListOperations.cs
Normal 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
718
src/NativeInterop.cs
Normal 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
269
src/Program.cs
Normal 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
47
src/StringOperations.cs
Normal 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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user