Compare commits
No commits in common. "master" and "v1.0.0" have entirely different histories.
@ -31,7 +31,7 @@ jobs:
|
|||||||
./build-all.sh
|
./build-all.sh
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: firefly-artifacts
|
name: firefly-artifacts
|
||||||
path: |
|
path: |
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
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: Create Release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
name: FireflyClient v1.0.0 - Initial Release
|
|
||||||
body: |
|
|
||||||
# FireflyClient v1.0.0 - Initial Release
|
|
||||||
|
|
||||||
We're excited to announce the initial release of FireflyClient! This version includes the core functionality of our application.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
- Native library support for enhanced performance
|
|
||||||
- Self-contained executable builds
|
|
||||||
- Cross-platform compatibility (Linux, macOS)
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
Choose the appropriate build for your platform from the assets below.
|
|
||||||
|
|
||||||
## System Requirements
|
|
||||||
- .NET 9.0 Runtime (for non-self-contained builds)
|
|
||||||
- Linux x64, macOS x64, or Windows x64
|
|
||||||
|
|
||||||
## Known Issues
|
|
||||||
- None reported
|
|
||||||
|
|
||||||
## What's Next
|
|
||||||
- Additional platform support
|
|
||||||
- Performance optimizations
|
|
||||||
- Enhanced documentation
|
|
||||||
|
|
||||||
## Feedback
|
|
||||||
We welcome your feedback and bug reports. Please use the issue tracker to report any problems.
|
|
||||||
files: |
|
|
||||||
artifacts/exe/*
|
|
||||||
artifacts/native/*
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
13
.idea/.idea.FireflyClient/.idea/.gitignore
generated
vendored
13
.idea/.idea.FireflyClient/.idea/.gitignore
generated
vendored
@ -1,13 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Rider ignored files
|
|
||||||
/modules.xml
|
|
||||||
/projectSettingsUpdater.xml
|
|
||||||
/.idea.FireflyClient.iml
|
|
||||||
/contentModel.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
1
.idea/.idea.FireflyClient/.idea/.name
generated
1
.idea/.idea.FireflyClient/.idea/.name
generated
@ -1 +0,0 @@
|
|||||||
FireflyClient
|
|
4
.idea/.idea.FireflyClient/.idea/encodings.xml
generated
4
.idea/.idea.FireflyClient/.idea/encodings.xml
generated
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
|
||||||
</project>
|
|
8
.idea/.idea.FireflyClient/.idea/indexLayout.xml
generated
8
.idea/.idea.FireflyClient/.idea/indexLayout.xml
generated
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="UserContentModel">
|
|
||||||
<attachedFolders />
|
|
||||||
<explicitIncludes />
|
|
||||||
<explicitExcludes />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
6
.idea/.idea.FireflyClient/.idea/vcs.xml
generated
6
.idea/.idea.FireflyClient/.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
14
README.md
14
README.md
@ -373,6 +373,20 @@ class FireflyClientPy:
|
|||||||
self.lib.ExecuteCommand.argtypes = [c_void_p, c_char_p, c_char_p]
|
self.lib.ExecuteCommand.argtypes = [c_void_p, c_char_p, c_char_p]
|
||||||
self.lib.ExecuteCommand.restype = 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):
|
def close(self):
|
||||||
if self.client_handle:
|
if self.client_handle:
|
||||||
self.lib.DestroyClient(self.client_handle)
|
self.lib.DestroyClient(self.client_handle)
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -161,11 +161,6 @@
|
|||||||
Removes and returns the last element of a list
|
Removes and returns the last element of a list
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:FireflyClient.FireflyClient.ListLength(System.String)">
|
|
||||||
<summary>
|
|
||||||
Gets the length of a list
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.ListRange(System.String,System.Int32,System.Int32)">
|
<member name="M:FireflyClient.FireflyClient.ListRange(System.String,System.Int32,System.Int32)">
|
||||||
<summary>
|
<summary>
|
||||||
Gets a range of elements from a list
|
Gets a range of elements from a list
|
||||||
@ -196,61 +191,6 @@
|
|||||||
Removes elements equal to the given value from a list
|
Removes elements equal to the given value from a list
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="T:FireflyClient.FireflyClient.NativeStringList">
|
|
||||||
<summary>
|
|
||||||
String array structure for interop
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeStringList.Strings">
|
|
||||||
<summary>
|
|
||||||
Pointer to an array of string pointers
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeStringList.Count">
|
|
||||||
<summary>
|
|
||||||
Number of strings in the array
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="T:FireflyClient.FireflyClient.NativeKeyValuePair">
|
|
||||||
<summary>
|
|
||||||
Key-value pair structure for interop
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeKeyValuePair.Key">
|
|
||||||
<summary>
|
|
||||||
Pointer to the key string
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeKeyValuePair.Value">
|
|
||||||
<summary>
|
|
||||||
Pointer to the value string
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="T:FireflyClient.FireflyClient.NativeDictionary">
|
|
||||||
<summary>
|
|
||||||
Dictionary structure for interop
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeDictionary.Pairs">
|
|
||||||
<summary>
|
|
||||||
Pointer to an array of key-value pairs
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeDictionary.Count">
|
|
||||||
<summary>
|
|
||||||
Number of pairs in the array
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.MarshalStringList(System.Collections.Generic.List{System.String})">
|
|
||||||
<summary>
|
|
||||||
Marshals a List to a NativeStringList without string conversion
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.MarshalDictionary(System.Collections.Generic.Dictionary{System.String,System.String})">
|
|
||||||
<summary>
|
|
||||||
Marshals a Dictionary to a NativeDictionary without string conversion
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.CreateClient(System.IntPtr,System.Int32)">
|
<member name="M:FireflyClient.FireflyClient.CreateClient(System.IntPtr,System.Int32)">
|
||||||
<summary>
|
<summary>
|
||||||
Creates a new FireflyClient instance for native interop
|
Creates a new FireflyClient instance for native interop
|
||||||
@ -277,16 +217,6 @@
|
|||||||
Frees a string allocated by the native interop methods
|
Frees a string allocated by the native interop methods
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:FireflyClient.FireflyClient.NativeFreeStringList(FireflyClient.FireflyClient.NativeStringList)">
|
|
||||||
<summary>
|
|
||||||
Frees a NativeStringList allocated by the native interop methods
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.NativeFreeDictionary(FireflyClient.FireflyClient.NativeDictionary)">
|
|
||||||
<summary>
|
|
||||||
Frees a NativeDictionary allocated by the native interop methods
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.NativeStringSet(System.IntPtr,System.IntPtr,System.IntPtr)">
|
<member name="M:FireflyClient.FireflyClient.NativeStringSet(System.IntPtr,System.IntPtr,System.IntPtr)">
|
||||||
<summary>
|
<summary>
|
||||||
Sets a string value for a given key (Native Interop).
|
Sets a string value for a given key (Native Interop).
|
||||||
@ -347,14 +277,6 @@
|
|||||||
<param name="keyPtr">Pointer to a null-terminated UTF-8 string representing the list key.</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>
|
<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>
|
||||||
<member name="M:FireflyClient.FireflyClient.NativeListLength(System.IntPtr,System.IntPtr)">
|
|
||||||
<summary>
|
|
||||||
Gets the length of a list (Native Interop).
|
|
||||||
</summary>
|
|
||||||
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
|
|
||||||
<param name="keyPtr">Pointer to a null-terminated UTF-8 string representing the list key.</param>
|
|
||||||
<returns>The length of the list, or 0 on error.</returns>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.NativeListRange(System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
|
<member name="M:FireflyClient.FireflyClient.NativeListRange(System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
|
||||||
<summary>
|
<summary>
|
||||||
Gets a range of elements from a list (Native Interop).
|
Gets a range of elements from a list (Native Interop).
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -161,11 +161,6 @@
|
|||||||
Removes and returns the last element of a list
|
Removes and returns the last element of a list
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:FireflyClient.FireflyClient.ListLength(System.String)">
|
|
||||||
<summary>
|
|
||||||
Gets the length of a list
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.ListRange(System.String,System.Int32,System.Int32)">
|
<member name="M:FireflyClient.FireflyClient.ListRange(System.String,System.Int32,System.Int32)">
|
||||||
<summary>
|
<summary>
|
||||||
Gets a range of elements from a list
|
Gets a range of elements from a list
|
||||||
@ -196,61 +191,6 @@
|
|||||||
Removes elements equal to the given value from a list
|
Removes elements equal to the given value from a list
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="T:FireflyClient.FireflyClient.NativeStringList">
|
|
||||||
<summary>
|
|
||||||
String array structure for interop
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeStringList.Strings">
|
|
||||||
<summary>
|
|
||||||
Pointer to an array of string pointers
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeStringList.Count">
|
|
||||||
<summary>
|
|
||||||
Number of strings in the array
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="T:FireflyClient.FireflyClient.NativeKeyValuePair">
|
|
||||||
<summary>
|
|
||||||
Key-value pair structure for interop
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeKeyValuePair.Key">
|
|
||||||
<summary>
|
|
||||||
Pointer to the key string
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeKeyValuePair.Value">
|
|
||||||
<summary>
|
|
||||||
Pointer to the value string
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="T:FireflyClient.FireflyClient.NativeDictionary">
|
|
||||||
<summary>
|
|
||||||
Dictionary structure for interop
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeDictionary.Pairs">
|
|
||||||
<summary>
|
|
||||||
Pointer to an array of key-value pairs
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="F:FireflyClient.FireflyClient.NativeDictionary.Count">
|
|
||||||
<summary>
|
|
||||||
Number of pairs in the array
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.MarshalStringList(System.Collections.Generic.List{System.String})">
|
|
||||||
<summary>
|
|
||||||
Marshals a List to a NativeStringList without string conversion
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.MarshalDictionary(System.Collections.Generic.Dictionary{System.String,System.String})">
|
|
||||||
<summary>
|
|
||||||
Marshals a Dictionary to a NativeDictionary without string conversion
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.CreateClient(System.IntPtr,System.Int32)">
|
<member name="M:FireflyClient.FireflyClient.CreateClient(System.IntPtr,System.Int32)">
|
||||||
<summary>
|
<summary>
|
||||||
Creates a new FireflyClient instance for native interop
|
Creates a new FireflyClient instance for native interop
|
||||||
@ -277,16 +217,6 @@
|
|||||||
Frees a string allocated by the native interop methods
|
Frees a string allocated by the native interop methods
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:FireflyClient.FireflyClient.NativeFreeStringList(FireflyClient.FireflyClient.NativeStringList)">
|
|
||||||
<summary>
|
|
||||||
Frees a NativeStringList allocated by the native interop methods
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.NativeFreeDictionary(FireflyClient.FireflyClient.NativeDictionary)">
|
|
||||||
<summary>
|
|
||||||
Frees a NativeDictionary allocated by the native interop methods
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.NativeStringSet(System.IntPtr,System.IntPtr,System.IntPtr)">
|
<member name="M:FireflyClient.FireflyClient.NativeStringSet(System.IntPtr,System.IntPtr,System.IntPtr)">
|
||||||
<summary>
|
<summary>
|
||||||
Sets a string value for a given key (Native Interop).
|
Sets a string value for a given key (Native Interop).
|
||||||
@ -347,14 +277,6 @@
|
|||||||
<param name="keyPtr">Pointer to a null-terminated UTF-8 string representing the list key.</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>
|
<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>
|
||||||
<member name="M:FireflyClient.FireflyClient.NativeListLength(System.IntPtr,System.IntPtr)">
|
|
||||||
<summary>
|
|
||||||
Gets the length of a list (Native Interop).
|
|
||||||
</summary>
|
|
||||||
<param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
|
|
||||||
<param name="keyPtr">Pointer to a null-terminated UTF-8 string representing the list key.</param>
|
|
||||||
<returns>The length of the list, or 0 on error.</returns>
|
|
||||||
</member>
|
|
||||||
<member name="M:FireflyClient.FireflyClient.NativeListRange(System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
|
<member name="M:FireflyClient.FireflyClient.NativeListRange(System.IntPtr,System.IntPtr,System.Int32,System.Int32)">
|
||||||
<summary>
|
<summary>
|
||||||
Gets a range of elements from a list (Native Interop).
|
Gets a range of elements from a list (Native Interop).
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -1,127 +1,176 @@
|
|||||||
#include "../src/firefly.h"
|
#include "../src/firefly.h" // Adjusted path to header in src directory
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
void demonstrateStringOperations(void* client) {
|
// Helper function to split string (basic)
|
||||||
std::cout << "\n=== String Operations ===" << std::endl;
|
std::vector<std::string> split(const std::string& s, char delimiter) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
// Set a string value
|
std::string token;
|
||||||
if (StringSet(client, "greeting", "Hello, Firefly!")) {
|
std::istringstream tokenStream(s);
|
||||||
std::cout << "String set successfully" << std::endl;
|
while (std::getline(tokenStream, token, delimiter)) {
|
||||||
}
|
tokens.push_back(token);
|
||||||
|
|
||||||
// Get the string value
|
|
||||||
if (char* value = StringGet(client, "greeting")) {
|
|
||||||
std::cout << "Retrieved string: " << value << std::endl;
|
|
||||||
FreeString(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
Delete(client, "greeting");
|
|
||||||
}
|
|
||||||
|
|
||||||
void demonstrateListOperations(void* client) {
|
|
||||||
std::cout << "\n=== List Operations ===" << std::endl;
|
|
||||||
const char* listKey = "todo_list";
|
|
||||||
|
|
||||||
// Add items to list
|
|
||||||
ListRightPush(client, listKey, "Buy groceries");
|
|
||||||
ListRightPush(client, listKey, "Write code");
|
|
||||||
ListRightPush(client, listKey, "Exercise");
|
|
||||||
|
|
||||||
// Get all items
|
|
||||||
std::cout << "Todo list items:" << std::endl;
|
|
||||||
if (char* items = ListRange(client, listKey, 0, -1)) {
|
|
||||||
std::cout << items;
|
|
||||||
FreeString(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove and get first item
|
|
||||||
if (char* completed = ListLeftPop(client, listKey)) {
|
|
||||||
std::cout << "Completed task: " << completed << std::endl;
|
|
||||||
FreeString(completed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
Delete(client, listKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
void demonstrateHashOperations(void* client) {
|
|
||||||
std::cout << "\n=== Hash Operations ===" << std::endl;
|
|
||||||
const char* userKey = "user:123";
|
|
||||||
|
|
||||||
// Store user profile
|
|
||||||
HashSet(client, userKey, "name", "Alice");
|
|
||||||
HashSet(client, userKey, "email", "alice@example.com");
|
|
||||||
HashMultiSet(client, userKey, "city SanFrancisco country USA");
|
|
||||||
|
|
||||||
// Get all user data
|
|
||||||
std::cout << "User profile:" << std::endl;
|
|
||||||
if (char* userData = HashGetAll(client, userKey)) {
|
|
||||||
std::cout << userData;
|
|
||||||
FreeString(userData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get specific field
|
|
||||||
if (char* name = HashGet(client, userKey, "name")) {
|
|
||||||
std::cout << "Name: " << name << std::endl;
|
|
||||||
FreeString(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
Delete(client, userKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
void demonstratePipelineOperations(void* client) {
|
|
||||||
std::cout << "\n=== Pipeline Operations ===" << std::endl;
|
|
||||||
|
|
||||||
// Enable pipeline mode
|
|
||||||
if (SetPipelineMode(client, true)) {
|
|
||||||
std::cout << "Pipeline mode enabled" << std::endl;
|
|
||||||
SetBatchSize(client, 3);
|
|
||||||
|
|
||||||
// Queue multiple commands
|
|
||||||
StringSet(client, "counter:1", "100");
|
|
||||||
StringSet(client, "counter:2", "200");
|
|
||||||
StringSet(client, "counter:3", "300");
|
|
||||||
|
|
||||||
// Show queue status and execute
|
|
||||||
std::cout << "Queued commands: " << GetQueuedCommandCount(client) << std::endl;
|
|
||||||
if (char* results = FlushPipeline(client)) {
|
|
||||||
std::cout << "Pipeline results:\n" << results << std::endl;
|
|
||||||
FreeString(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetPipelineMode(client, false);
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
for (int i = 1; i <= 3; i++) {
|
|
||||||
char key[20];
|
|
||||||
sprintf(key, "counter:%d", i);
|
|
||||||
Delete(client, key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
// Create client
|
// Create client
|
||||||
|
// Assumes the compiled library is accessible (e.g., in PATH or same dir)
|
||||||
void* client = CreateClient("localhost", 6379);
|
void* client = CreateClient("localhost", 6379);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
std::cerr << "Failed to create client" << std::endl;
|
std::cerr << "Failed to create client" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
std::cout << "Connected to Firefly database" << std::endl;
|
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;
|
||||||
|
|
||||||
// Run demonstrations
|
char* popped = ListLeftPop(client, "cpp:mylist");
|
||||||
demonstrateStringOperations(client);
|
if (popped) {
|
||||||
demonstrateListOperations(client);
|
std::cout << "ListLeftPop: " << popped << std::endl;
|
||||||
demonstrateHashOperations(client);
|
FreeString(popped); // IMPORTANT: Free
|
||||||
demonstratePipelineOperations(client);
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup
|
// 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);
|
DestroyClient(client);
|
||||||
std::cout << "\nExample completed successfully" << std::endl;
|
std::cout << "Client destroyed." << std::endl;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
1532
examples/example.py
1532
examples/example.py
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.
52
firefly.h
52
firefly.h
@ -7,22 +7,6 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// C header definitions
|
|
||||||
typedef struct {
|
|
||||||
const char** strings;
|
|
||||||
int count;
|
|
||||||
} StringArray;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const char* key;
|
|
||||||
const char* value;
|
|
||||||
} KeyValuePair;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
KeyValuePair* pairs;
|
|
||||||
int count;
|
|
||||||
} Dictionary;
|
|
||||||
|
|
||||||
// --- Client Management ---
|
// --- Client Management ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,11 +113,12 @@ char* ListRightPop(void* handle, const char* key);
|
|||||||
* @param key Null-terminated UTF-8 string for the list key.
|
* @param key Null-terminated UTF-8 string for the list key.
|
||||||
* @param start The start index (0-based).
|
* @param start The start index (0-based).
|
||||||
* @param stop The stop index (0-based, inclusive). Use -1 to specify the end of the list.
|
* @param stop The stop index (0-based, inclusive). Use -1 to specify the end of the list.
|
||||||
* @return A StringArray structure containing the elements in the specified range.
|
* @return A pointer to a null-terminated UTF-8 string containing the list elements, separated by newline characters (\n).
|
||||||
* The caller is responsible for freeing the memory allocated for the StringArray structure using FreeStringArray().
|
* 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.
|
* Returns NULL if the key does not exist or an error occurs.
|
||||||
*/
|
*/
|
||||||
StringArray ListRange(void* handle, const char* key, int start, int stop);
|
char* ListRange(void* handle, const char* key, int start, int stop);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gets the element at the specified index in a list.
|
* @brief Gets the element at the specified index in a list.
|
||||||
@ -190,20 +175,6 @@ bool ListTrim(void* handle, const char* key, int start, int stop);
|
|||||||
*/
|
*/
|
||||||
int ListRemove(void* handle, const char* key, int count, const char* element);
|
int ListRemove(void* handle, const char* key, int count, const char* element);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Gets the length of a list.
|
|
||||||
* @param handle The client handle.
|
|
||||||
* @param key Null-terminated UTF-8 string for the list key.
|
|
||||||
* @return The length of the list, or 0 on error.
|
|
||||||
*/
|
|
||||||
int ListLength(void* handle, const char* key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Frees a StringArray structure and its contents.
|
|
||||||
* @param array Pointer to the StringArray structure to be freed.
|
|
||||||
*/
|
|
||||||
void FreeStringList(StringArray array);
|
|
||||||
|
|
||||||
// --- Hash Operations ---
|
// --- Hash Operations ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,11 +220,14 @@ bool HashFieldExists(void* handle, const char* key, const char* field);
|
|||||||
* @brief Gets all fields and values from a hash.
|
* @brief Gets all fields and values from a hash.
|
||||||
* @param handle The client handle.
|
* @param handle The client handle.
|
||||||
* @param key Null-terminated UTF-8 string for the hash key.
|
* @param key Null-terminated UTF-8 string for the hash key.
|
||||||
* @return A Dictionary structure containing all fields and values in the hash.
|
* @return A pointer to a null-terminated UTF-8 string containing the hash fields and values.
|
||||||
* The caller is responsible for freeing the memory allocated for the Dictionary structure using FreeDictionary().
|
* 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.
|
* Returns NULL if the key does not exist or an error occurs.
|
||||||
*/
|
*/
|
||||||
Dictionary HashGetAll(void* handle, const char* key);
|
char* HashGetAll(void* handle, const char* key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sets multiple fields and values in a hash.
|
* @brief Sets multiple fields and values in a hash.
|
||||||
@ -266,12 +240,6 @@ Dictionary HashGetAll(void* handle, const char* key);
|
|||||||
*/
|
*/
|
||||||
bool HashMultiSet(void* handle, const char* key, const char* fieldValuePairs);
|
bool HashMultiSet(void* handle, const char* key, const char* fieldValuePairs);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Frees the memory allocated for a Dictionary structure.
|
|
||||||
* @param dict Pointer to the Dictionary structure to be freed.
|
|
||||||
*/
|
|
||||||
void FreeDictionary(Dictionary dict);
|
|
||||||
|
|
||||||
// --- Raw Command Execution ---
|
// --- Raw Command Execution ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,12 +11,15 @@ namespace FireflyClient
|
|||||||
{
|
{
|
||||||
private readonly TcpClient _client;
|
private readonly TcpClient _client;
|
||||||
private readonly NetworkStream _stream;
|
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 readonly byte[] _responseBuffer = new byte[1024 * 1024]; // 1MB buffer for responses
|
||||||
private int _maxBatchSize = 1000;
|
private int _maxBatchSize = 1000;
|
||||||
private readonly int _maxPipelineSize = 10000;
|
private readonly int _maxPipelineSize = 10000;
|
||||||
private readonly Queue<string> _commandQueue = new();
|
private readonly Queue<string> _commandQueue = new();
|
||||||
private bool _isPipelineMode;
|
private bool _isPipelineMode = false;
|
||||||
private bool _isAuthenticated;
|
private bool _isAuthenticated = false;
|
||||||
|
|
||||||
// Static instance for native interop
|
// Static instance for native interop
|
||||||
// private static FireflyClient? _instance;
|
// private static FireflyClient? _instance;
|
||||||
@ -26,8 +29,11 @@ namespace FireflyClient
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public FireflyClient(string host = "127.0.0.1", int port = 6379)
|
public FireflyClient(string host = "127.0.0.1", int port = 6379)
|
||||||
{
|
{
|
||||||
|
_host = host;
|
||||||
|
_port = port;
|
||||||
|
_password = string.Empty;
|
||||||
_client = new TcpClient();
|
_client = new TcpClient();
|
||||||
_client.Connect(host, port);
|
_client.Connect(_host, _port);
|
||||||
_stream = _client.GetStream();
|
_stream = _client.GetStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,9 +42,10 @@ namespace FireflyClient
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public FireflyClient(string host, int port, string password) : this(host, port)
|
public FireflyClient(string host, int port, string password) : this(host, port)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(password))
|
_password = password;
|
||||||
|
if (!string.IsNullOrEmpty(_password))
|
||||||
{
|
{
|
||||||
Authenticate(password);
|
Authenticate(_password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +54,7 @@ namespace FireflyClient
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Authenticate(string password)
|
public bool Authenticate(string password)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("AUTH", password);
|
string response = ExecuteCommand("AUTH", password);
|
||||||
_isAuthenticated = response.StartsWith("+OK");
|
_isAuthenticated = response.StartsWith("+OK");
|
||||||
return _isAuthenticated;
|
return _isAuthenticated;
|
||||||
}
|
}
|
||||||
@ -63,7 +70,7 @@ namespace FireflyClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build the command string
|
// Build the command string
|
||||||
var fullCommand = $"{command.ToUpperInvariant()}{string.Join("", args.Select(arg => $" {QuoteIfNeeded(arg)}"))}";
|
string fullCommand = $"{command.ToUpperInvariant()}{string.Join("", args.Select(arg => $" {QuoteIfNeeded(arg)}"))}";
|
||||||
|
|
||||||
// Handle special commands that should not be pipelined
|
// Handle special commands that should not be pipelined
|
||||||
if (command.Equals("AUTH", StringComparison.OrdinalIgnoreCase) ||
|
if (command.Equals("AUTH", StringComparison.OrdinalIgnoreCase) ||
|
||||||
@ -111,11 +118,11 @@ namespace FireflyClient
|
|||||||
command += "\r\n";
|
command += "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandBytes = Encoding.UTF8.GetBytes(command);
|
byte[] commandBytes = Encoding.UTF8.GetBytes(command);
|
||||||
_stream.Write(commandBytes, 0, commandBytes.Length);
|
_stream.Write(commandBytes, 0, commandBytes.Length);
|
||||||
|
|
||||||
// Read response
|
// Read response
|
||||||
var bytesRead = _stream.Read(_responseBuffer, 0, _responseBuffer.Length);
|
int bytesRead = _stream.Read(_responseBuffer, 0, _responseBuffer.Length);
|
||||||
return bytesRead > 0 ? Encoding.UTF8.GetString(_responseBuffer, 0, bytesRead) : string.Empty;
|
return bytesRead > 0 ? Encoding.UTF8.GetString(_responseBuffer, 0, bytesRead) : string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +131,7 @@ namespace FireflyClient
|
|||||||
if (_commandQueue.Count == 0) return string.Empty;
|
if (_commandQueue.Count == 0) return string.Empty;
|
||||||
|
|
||||||
// Build the pipeline command string
|
// Build the pipeline command string
|
||||||
var pipelineCommand = string.Join("\r\n", _commandQueue) + "\r\n";
|
string pipelineCommand = string.Join("\r\n", _commandQueue) + "\r\n";
|
||||||
_commandQueue.Clear();
|
_commandQueue.Clear();
|
||||||
|
|
||||||
return SendCommandInternal(pipelineCommand);
|
return SendCommandInternal(pipelineCommand);
|
||||||
@ -133,7 +140,7 @@ namespace FireflyClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables pipeline mode
|
/// Enables or disables pipeline mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SetPipelineMode(bool enabled)
|
public void SetPipelineMode(bool enabled)
|
||||||
{
|
{
|
||||||
_isPipelineMode = enabled;
|
_isPipelineMode = enabled;
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
@ -145,7 +152,7 @@ namespace FireflyClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Flushes any queued commands in pipeline mode
|
/// Flushes any queued commands in pipeline mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string FlushPipeline()
|
public string FlushPipeline()
|
||||||
{
|
{
|
||||||
return ProcessCommandQueue();
|
return ProcessCommandQueue();
|
||||||
}
|
}
|
||||||
@ -153,7 +160,7 @@ namespace FireflyClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the maximum number of commands to batch before sending
|
/// Sets the maximum number of commands to batch before sending
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SetBatchSize(int size)
|
public void SetBatchSize(int size)
|
||||||
{
|
{
|
||||||
if (size <= 0)
|
if (size <= 0)
|
||||||
{
|
{
|
||||||
@ -165,17 +172,17 @@ namespace FireflyClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current number of queued commands
|
/// Gets the current number of queued commands
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int QueuedCommandCount => _commandQueue.Count;
|
public int QueuedCommandCount => _commandQueue.Count;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether pipeline mode is enabled
|
/// Gets whether pipeline mode is enabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool IsPipelineMode => _isPipelineMode;
|
public bool IsPipelineMode => _isPipelineMode;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the maximum batch size
|
/// Gets the maximum batch size
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int MaxBatchSize => _maxBatchSize;
|
public int MaxBatchSize => _maxBatchSize;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether the client is connected to the server
|
/// Gets whether the client is connected to the server
|
||||||
@ -192,18 +199,18 @@ namespace FireflyClient
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pattern">Pattern to match against keys. Use * for wildcard matches.</param>
|
/// <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>
|
/// <returns>List of matching keys, or empty list if none found or on error</returns>
|
||||||
private List<string> Keys(string pattern = "*")
|
public List<string> Keys(string pattern = "*")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("KEYS", pattern);
|
string response = ExecuteCommand("KEYS", pattern);
|
||||||
if (string.IsNullOrEmpty(response) || !response.StartsWith('+'))
|
if (string.IsNullOrEmpty(response) || !response.StartsWith('+'))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the '+' prefix and split by newlines
|
// Remove the '+' prefix and split by newlines
|
||||||
var keysStr = response[1..].Trim();
|
string keysStr = response[1..].Trim();
|
||||||
return string.IsNullOrEmpty(keysStr)
|
return string.IsNullOrEmpty(keysStr)
|
||||||
? []
|
? []
|
||||||
: [.. keysStr.Split('\n')];
|
: [.. keysStr.Split('\n')];
|
||||||
@ -217,7 +224,7 @@ namespace FireflyClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parses an array response from the server
|
/// Parses an array response from the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static List<string> ParseArrayResponse(string response)
|
protected static List<string> ParseArrayResponse(string response)
|
||||||
{
|
{
|
||||||
var result = new List<string>();
|
var result = new List<string>();
|
||||||
|
|
||||||
@ -230,7 +237,7 @@ namespace FireflyClient
|
|||||||
string[] parts = response.Split("\r\n");
|
string[] parts = response.Split("\r\n");
|
||||||
|
|
||||||
// Skip the first line which just contains the * prefix
|
// Skip the first line which just contains the * prefix
|
||||||
for (var i = 1; i < parts.Length; i++)
|
for (int i = 1; i < parts.Length; i++)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(parts[i]) && (parts[i].StartsWith('+') || parts[i].StartsWith('$')))
|
if (!string.IsNullOrEmpty(parts[i]) && (parts[i].StartsWith('+') || parts[i].StartsWith('$')))
|
||||||
{
|
{
|
||||||
|
@ -7,49 +7,53 @@ namespace FireflyClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a field in a hash
|
/// Sets a field in a hash
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool HashSet(string key, string field, string value)
|
public bool HashSet(string key, string field, string value)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("HSET", key, field, value);
|
string response = ExecuteCommand("HSET", key, field, value);
|
||||||
return response.StartsWith(":1");
|
return response.StartsWith(":1");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a field from a hash
|
/// Gets a field from a hash
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string HashGet(string key, string field)
|
public string HashGet(string key, string field)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("HGET", key, field);
|
string response = ExecuteCommand("HGET", key, field);
|
||||||
return response.StartsWith('+') ? response[1..].TrimEnd('\r', '\n') : string.Empty;
|
if (response.StartsWith('+'))
|
||||||
|
{
|
||||||
|
return response[1..].TrimEnd('\r', '\n');
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes a field from a hash
|
/// Deletes a field from a hash
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool HashDelete(string key, string field)
|
public bool HashDelete(string key, string field)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("HDEL", key, field);
|
string response = ExecuteCommand("HDEL", key, field);
|
||||||
return response.StartsWith(":1");
|
return response.StartsWith(":1");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if a field exists in a hash
|
/// Checks if a field exists in a hash
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool HashFieldExists(string key, string field)
|
public bool HashFieldExists(string key, string field)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("HEXISTS", key, field);
|
string response = ExecuteCommand("HEXISTS", key, field);
|
||||||
return response.StartsWith(":1");
|
return response.StartsWith(":1");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all fields and values from a hash
|
/// Gets all fields and values from a hash
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Dictionary<string, string> HashGetAll(string key)
|
public Dictionary<string, string> HashGetAll(string key)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("HGETALL", key);
|
string response = ExecuteCommand("HGETALL", key);
|
||||||
var items = ParseArrayResponse(response);
|
List<string> items = ParseArrayResponse(response);
|
||||||
|
|
||||||
var result = new Dictionary<string, string>();
|
var result = new Dictionary<string, string>();
|
||||||
for (var i = 0; i < items.Count; i += 2)
|
for (int i = 0; i < items.Count; i += 2)
|
||||||
{
|
{
|
||||||
if (i + 1 < items.Count)
|
if (i + 1 < items.Count)
|
||||||
{
|
{
|
||||||
@ -63,7 +67,7 @@ namespace FireflyClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets multiple fields in a hash at once
|
/// Sets multiple fields in a hash at once
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool HashMultiSet(string key, Dictionary<string, string> fieldValues)
|
public bool HashMultiSet(string key, Dictionary<string, string> fieldValues)
|
||||||
{
|
{
|
||||||
List<string> args = [key];
|
List<string> args = [key];
|
||||||
|
|
||||||
@ -73,7 +77,7 @@ namespace FireflyClient
|
|||||||
args.Add(kvp.Value);
|
args.Add(kvp.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = ExecuteCommand("HMSET", [.. args]);
|
string response = ExecuteCommand("HMSET", [.. args]);
|
||||||
return response.StartsWith("+OK");
|
return response.StartsWith("+OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,88 +7,102 @@ namespace FireflyClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds values to the beginning of a list
|
/// Adds values to the beginning of a list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int ListLeftPush(string key, params string[] values)
|
public int ListLeftPush(string key, params string[] values)
|
||||||
{
|
{
|
||||||
var args = new List<string> { key };
|
var args = new List<string> { key };
|
||||||
args.AddRange(values);
|
args.AddRange(values);
|
||||||
|
|
||||||
var response = ExecuteCommand("LPUSH", [.. args]);
|
string response = ExecuteCommand("LPUSH", [.. args]);
|
||||||
if (!response.StartsWith(':')) return 0;
|
if (response.StartsWith(':'))
|
||||||
return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : 0;
|
{
|
||||||
|
if (int.TryParse(response[1..].TrimEnd('\r', '\n'), out int result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds values to the end of a list
|
/// Adds values to the end of a list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int ListRightPush(string key, params string[] values)
|
public int ListRightPush(string key, params string[] values)
|
||||||
{
|
{
|
||||||
var args = new List<string> { key };
|
var args = new List<string> { key };
|
||||||
args.AddRange(values);
|
args.AddRange(values);
|
||||||
|
|
||||||
var response = ExecuteCommand("RPUSH", [.. args]);
|
string response = ExecuteCommand("RPUSH", [.. args]);
|
||||||
if (!response.StartsWith(':')) return 0;
|
if (response.StartsWith(':'))
|
||||||
return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : 0;
|
{
|
||||||
|
if (int.TryParse(response[1..].TrimEnd('\r', '\n'), out int result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes and returns the first element of a list
|
/// Removes and returns the first element of a list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string ListLeftPop(string key)
|
public string ListLeftPop(string key)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("LPOP", key);
|
string response = ExecuteCommand("LPOP", key);
|
||||||
return response.StartsWith('+') ? response[1..].TrimEnd('\r', '\n') : string.Empty;
|
if (response.StartsWith('+'))
|
||||||
|
{
|
||||||
|
return response[1..].TrimEnd('\r', '\n');
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes and returns the last element of a list
|
/// Removes and returns the last element of a list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string ListRightPop(string key)
|
public string ListRightPop(string key)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("RPOP", key);
|
string response = ExecuteCommand("RPOP", key);
|
||||||
return response.StartsWith('+') ? response[1..].TrimEnd('\r', '\n') : string.Empty;
|
if (response.StartsWith('+'))
|
||||||
}
|
{
|
||||||
|
return response[1..].TrimEnd('\r', '\n');
|
||||||
/// <summary>
|
}
|
||||||
/// Gets the length of a list
|
return string.Empty;
|
||||||
/// </summary>
|
|
||||||
private int ListLength(string key)
|
|
||||||
{
|
|
||||||
var response = ExecuteCommand("LLEN", key);
|
|
||||||
if (!response.StartsWith(':')) return 0;
|
|
||||||
return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a range of elements from a list
|
/// Gets a range of elements from a list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<string> ListRange(string key, int start, int stop)
|
public List<string> ListRange(string key, int start, int stop)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("LRANGE", key, start.ToString(), stop.ToString());
|
string response = ExecuteCommand("LRANGE", key, start.ToString(), stop.ToString());
|
||||||
return ParseArrayResponse(response);
|
return ParseArrayResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the element at the specified index in a list
|
/// Gets the element at the specified index in a list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string ListIndex(string key, int index)
|
public string ListIndex(string key, int index)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("LINDEX", key, index.ToString());
|
string response = ExecuteCommand("LINDEX", key, index.ToString());
|
||||||
return response.StartsWith('+') ? response[1..].TrimEnd('\r', '\n') : string.Empty;
|
if (response.StartsWith('+'))
|
||||||
|
{
|
||||||
|
return response[1..].TrimEnd('\r', '\n');
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the element at the specified index in a list
|
/// Sets the element at the specified index in a list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool ListSet(string key, int index, string value)
|
public bool ListSet(string key, int index, string value)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("LSET", key, index.ToString(), value);
|
string response = ExecuteCommand("LSET", key, index.ToString(), value);
|
||||||
return response.StartsWith("+OK");
|
return response.StartsWith("+OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the index of the first occurrence of an element in a list
|
/// Returns the index of the first occurrence of an element in a list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int ListPosition(string key, string element, int rank = 1, int maxlen = 0)
|
public int ListPosition(string key, string element, int rank = 1, int maxlen = 0)
|
||||||
{
|
{
|
||||||
var args = new List<string> { key, element };
|
var args = new List<string> { key, element };
|
||||||
|
|
||||||
@ -104,28 +118,40 @@ namespace FireflyClient
|
|||||||
args.Add(maxlen.ToString());
|
args.Add(maxlen.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = ExecuteCommand("LPOS", [.. args]);
|
string response = ExecuteCommand("LPOS", [.. args]);
|
||||||
if (!response.StartsWith(':')) return -1;
|
if (response.StartsWith(':'))
|
||||||
return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : -1;
|
{
|
||||||
|
if (int.TryParse(response[1..].TrimEnd('\r', '\n'), out int result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Trims a list to the specified range
|
/// Trims a list to the specified range
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool ListTrim(string key, int start, int stop)
|
public bool ListTrim(string key, int start, int stop)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("LTRIM", key, start.ToString(), stop.ToString());
|
string response = ExecuteCommand("LTRIM", key, start.ToString(), stop.ToString());
|
||||||
return response.StartsWith("+OK");
|
return response.StartsWith("+OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes elements equal to the given value from a list
|
/// Removes elements equal to the given value from a list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int ListRemove(string key, int count, string element)
|
public int ListRemove(string key, int count, string element)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("LREM", key, count.ToString(), element);
|
string response = ExecuteCommand("LREM", key, count.ToString(), element);
|
||||||
if (!response.StartsWith(':')) return 0;
|
if (response.StartsWith(':'))
|
||||||
return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : 0;
|
{
|
||||||
|
if (int.TryParse(response[1..].TrimEnd('\r', '\n'), out int result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -5,66 +5,13 @@ namespace FireflyClient
|
|||||||
{
|
{
|
||||||
public partial class FireflyClient
|
public partial class FireflyClient
|
||||||
{
|
{
|
||||||
// Native structure definitions for interop
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// String array structure for interop
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct NativeStringList
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Pointer to an array of string pointers
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Strings;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Number of strings in the array
|
|
||||||
/// </summary>
|
|
||||||
public int Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Key-value pair structure for interop
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct NativeKeyValuePair
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Pointer to the key string
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Key;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pointer to the value string
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dictionary structure for interop
|
|
||||||
/// </summary>
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct NativeDictionary
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Pointer to an array of key-value pairs
|
|
||||||
/// </summary>
|
|
||||||
public IntPtr Pairs;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Number of pairs in the array
|
|
||||||
/// </summary>
|
|
||||||
public int Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to safely get client from handle
|
// Helper to safely get client from handle
|
||||||
private static FireflyClient? GetClientFromHandle(IntPtr handle)
|
private static FireflyClient? GetClientFromHandle(IntPtr handle)
|
||||||
{
|
{
|
||||||
if (handle == IntPtr.Zero) return null;
|
if (handle == IntPtr.Zero) return null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var gch = (GCHandle)handle;
|
GCHandle gch = (GCHandle)handle;
|
||||||
return gch.Target as FireflyClient;
|
return gch.Target as FireflyClient;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@ -74,80 +21,31 @@ namespace FireflyClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper to marshal string result to IntPtr
|
// Helper to marshal string result to IntPtr
|
||||||
private static IntPtr MarshalString(string? result)
|
private static IntPtr MarshalStringResult(string? result)
|
||||||
{
|
{
|
||||||
if (result == null) return IntPtr.Zero;
|
if (result == null) return IntPtr.Zero;
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(result);
|
byte[] bytes = Encoding.UTF8.GetBytes(result);
|
||||||
var ptr = Marshal.AllocHGlobal(bytes.Length + 1);
|
IntPtr ptr = Marshal.AllocHGlobal(bytes.Length + 1);
|
||||||
Marshal.Copy(bytes, 0, ptr, bytes.Length);
|
Marshal.Copy(bytes, 0, ptr, bytes.Length);
|
||||||
Marshal.WriteByte(ptr + bytes.Length, 0); // Null terminator
|
Marshal.WriteByte(ptr + bytes.Length, 0); // Null terminator
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to marshal List<string> result to IntPtr (newline-delimited)
|
// Helper to marshal List<string> result to IntPtr (newline-delimited)
|
||||||
private static IntPtr MarshalStringListResult(List<string>? result)
|
private static IntPtr MarshalStringListResult(List<string>? result)
|
||||||
{
|
{
|
||||||
if (result == null || result.Count == 0) return IntPtr.Zero;
|
if (result == null || result.Count == 0) return IntPtr.Zero;
|
||||||
var joinedResult = string.Join("\n", result); // Using \n as delimiter
|
string joinedResult = string.Join("\n", result); // Using \n as delimiter
|
||||||
return MarshalString(joinedResult);
|
return MarshalStringResult(joinedResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
// Helper to marshal Dictionary<string, string> result to IntPtr (newline-delimited field=value)
|
||||||
/// Marshals a List to a NativeStringList without string conversion
|
private static IntPtr MarshalStringDictionaryResult(Dictionary<string, string>? result)
|
||||||
/// </summary>
|
|
||||||
private static NativeStringList MarshalStringList(List<string>? list)
|
|
||||||
{
|
{
|
||||||
if (list == null || list.Count == 0)
|
if (result == null || result.Count == 0) return IntPtr.Zero;
|
||||||
{
|
string joinedResult = string.Join("\n", result.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||||
return new NativeStringList { Strings = IntPtr.Zero, Count = 0 };
|
return MarshalStringResult(joinedResult);
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate memory for the array of strings
|
|
||||||
var arrayPtr = Marshal.AllocHGlobal(list.Count * IntPtr.Size);
|
|
||||||
|
|
||||||
// Copy each string to the allocated memory
|
|
||||||
for (var i = 0; i < list.Count; i++)
|
|
||||||
{
|
|
||||||
var stringPtr = MarshalString(list[i]);
|
|
||||||
Marshal.WriteIntPtr(arrayPtr + i * IntPtr.Size, stringPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NativeStringList { Strings = arrayPtr, Count = list.Count };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Marshals a Dictionary to a NativeDictionary without string conversion
|
|
||||||
/// </summary>
|
|
||||||
private static NativeDictionary MarshalDictionary(Dictionary<string, string>? dict)
|
|
||||||
{
|
|
||||||
if (dict == null || dict.Count == 0)
|
|
||||||
{
|
|
||||||
return new NativeDictionary { Pairs = IntPtr.Zero, Count = 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate memory for the array of key-value pairs
|
|
||||||
var structSize = Marshal.SizeOf<NativeKeyValuePair>();
|
|
||||||
var pairsPtr = Marshal.AllocHGlobal(dict.Count * structSize);
|
|
||||||
|
|
||||||
// For each key-value pair, create a NativeKeyValuePair
|
|
||||||
var index = 0;
|
|
||||||
foreach (var kvp in dict)
|
|
||||||
{
|
|
||||||
var keyPtr = MarshalString(kvp.Key);
|
|
||||||
var valuePtr = MarshalString(kvp.Value);
|
|
||||||
|
|
||||||
// Create a NativeKeyValuePair and write it to the allocated memory
|
|
||||||
var pair = new NativeKeyValuePair
|
|
||||||
{
|
|
||||||
Key = keyPtr,
|
|
||||||
Value = valuePtr
|
|
||||||
};
|
|
||||||
Marshal.StructureToPtr(pair, pairsPtr + index * structSize, false);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NativeDictionary { Pairs = pairsPtr, Count = dict.Count };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Native Interop Methods
|
#region Native Interop Methods
|
||||||
@ -161,7 +59,7 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Use UTF8 for host string
|
// Use UTF8 for host string
|
||||||
var host = Marshal.PtrToStringUTF8(hostPtr) ?? "127.0.0.1";
|
string host = Marshal.PtrToStringUTF8(hostPtr) ?? "127.0.0.1";
|
||||||
var client = new FireflyClient(host, port);
|
var client = new FireflyClient(host, port);
|
||||||
var handle = GCHandle.Alloc(client);
|
var handle = GCHandle.Alloc(client);
|
||||||
return GCHandle.ToIntPtr(handle);
|
return GCHandle.ToIntPtr(handle);
|
||||||
@ -182,7 +80,7 @@ namespace FireflyClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var gch = GCHandle.FromIntPtr(handle);
|
GCHandle gch = GCHandle.FromIntPtr(handle);
|
||||||
if (gch.Target is FireflyClient client)
|
if (gch.Target is FireflyClient client)
|
||||||
{
|
{
|
||||||
client.Dispose();
|
client.Dispose();
|
||||||
@ -206,7 +104,7 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Use UTF8 for password string
|
// Use UTF8 for password string
|
||||||
var password = Marshal.PtrToStringUTF8(passwordPtr) ?? string.Empty;
|
string password = Marshal.PtrToStringUTF8(passwordPtr) ?? string.Empty;
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
return client?.Authenticate(password) ?? false;
|
return client?.Authenticate(password) ?? false;
|
||||||
}
|
}
|
||||||
@ -226,13 +124,13 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Use UTF8 for command and args strings
|
// Use UTF8 for command and args strings
|
||||||
var command = Marshal.PtrToStringUTF8(commandPtr) ?? string.Empty;
|
string command = Marshal.PtrToStringUTF8(commandPtr) ?? string.Empty;
|
||||||
var args = Marshal.PtrToStringUTF8(argsPtr) ?? string.Empty;
|
string args = Marshal.PtrToStringUTF8(argsPtr) ?? string.Empty;
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
// WARNING: Basic split, unreliable if args have spaces
|
// WARNING: Basic split, unreliable if args have spaces
|
||||||
string[] argArray = args.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
string[] argArray = args.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
var result = client?.ExecuteCommand(command, argArray);
|
string? result = client?.ExecuteCommand(command, argArray);
|
||||||
return MarshalString(result);
|
return MarshalStringResult(result);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -251,52 +149,7 @@ namespace FireflyClient
|
|||||||
Marshal.FreeHGlobal(ptr);
|
Marshal.FreeHGlobal(ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Frees a NativeStringList allocated by the native interop methods
|
|
||||||
/// </summary>
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "FreeStringList")]
|
|
||||||
public static void NativeFreeStringList(NativeStringList list)
|
|
||||||
{
|
|
||||||
if (list.Strings != IntPtr.Zero && list.Count > 0)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < list.Count; i++)
|
|
||||||
{
|
|
||||||
var stringPtr = Marshal.ReadIntPtr(list.Strings + i * IntPtr.Size);
|
|
||||||
if (stringPtr != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(stringPtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Marshal.FreeHGlobal(list.Strings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Frees a NativeDictionary allocated by the native interop methods
|
|
||||||
/// </summary>
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "FreeDictionary")]
|
|
||||||
public static void NativeFreeDictionary(NativeDictionary dict)
|
|
||||||
{
|
|
||||||
if (dict.Pairs != IntPtr.Zero && dict.Count > 0)
|
|
||||||
{
|
|
||||||
var structSize = Marshal.SizeOf<NativeKeyValuePair>();
|
|
||||||
|
|
||||||
for (var i = 0; i < dict.Count; i++)
|
|
||||||
{
|
|
||||||
var pairPtr = dict.Pairs + i * structSize;
|
|
||||||
var pair = Marshal.PtrToStructure<NativeKeyValuePair>(pairPtr);
|
|
||||||
|
|
||||||
if (pair.Key != IntPtr.Zero)
|
|
||||||
Marshal.FreeHGlobal(pair.Key);
|
|
||||||
|
|
||||||
if (pair.Value != IntPtr.Zero)
|
|
||||||
Marshal.FreeHGlobal(pair.Value);
|
|
||||||
}
|
|
||||||
Marshal.FreeHGlobal(dict.Pairs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- String Operations ---
|
// --- String Operations ---
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -312,8 +165,8 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
|
string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
|
||||||
return client?.StringSet(key, value) ?? false;
|
return client?.StringSet(key, value) ?? false;
|
||||||
}
|
}
|
||||||
catch { return false; }
|
catch { return false; }
|
||||||
@ -331,9 +184,9 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var result = client?.StringGet(key);
|
string? result = client?.StringGet(key);
|
||||||
return MarshalString(result);
|
return MarshalStringResult(result);
|
||||||
}
|
}
|
||||||
catch { return IntPtr.Zero; }
|
catch { return IntPtr.Zero; }
|
||||||
}
|
}
|
||||||
@ -351,7 +204,7 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
// Assuming Delete returns the number of keys deleted (usually 1 or 0)
|
// Assuming Delete returns the number of keys deleted (usually 1 or 0)
|
||||||
return client?.Delete(key) ?? 0;
|
return client?.Delete(key) ?? 0;
|
||||||
}
|
}
|
||||||
@ -373,8 +226,8 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
|
string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
|
||||||
// Assuming LPUSH returns the new length of the list
|
// Assuming LPUSH returns the new length of the list
|
||||||
return client?.ListLeftPush(key, value) ?? 0;
|
return client?.ListLeftPush(key, value) ?? 0;
|
||||||
}
|
}
|
||||||
@ -394,8 +247,8 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
|
string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
|
||||||
// Assuming RPUSH returns the new length of the list
|
// Assuming RPUSH returns the new length of the list
|
||||||
return client?.ListRightPush(key, value) ?? 0;
|
return client?.ListRightPush(key, value) ?? 0;
|
||||||
}
|
}
|
||||||
@ -414,9 +267,9 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var result = client?.ListLeftPop(key);
|
string? result = client?.ListLeftPop(key);
|
||||||
return MarshalString(result);
|
return MarshalStringResult(result);
|
||||||
}
|
}
|
||||||
catch { return IntPtr.Zero; }
|
catch { return IntPtr.Zero; }
|
||||||
}
|
}
|
||||||
@ -433,32 +286,13 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var result = client?.ListRightPop(key);
|
string? result = client?.ListRightPop(key);
|
||||||
return MarshalString(result);
|
return MarshalStringResult(result);
|
||||||
}
|
}
|
||||||
catch { return IntPtr.Zero; }
|
catch { return IntPtr.Zero; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the length of a list (Native Interop).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handle">The GCHandle (as IntPtr) representing the client instance.</param>
|
|
||||||
/// <param name="keyPtr">Pointer to a null-terminated UTF-8 string representing the list key.</param>
|
|
||||||
/// <returns>The length of the list, or 0 on error.</returns>
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "ListLength")]
|
|
||||||
public static int NativeListLength(IntPtr handle, IntPtr keyPtr)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var client = GetClientFromHandle(handle);
|
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
|
||||||
var result = client?.ListLength(key) ?? 0;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch { return 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a range of elements from a list (Native Interop).
|
/// Gets a range of elements from a list (Native Interop).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -469,19 +303,17 @@ namespace FireflyClient
|
|||||||
/// <returns>Pointer to a null-terminated UTF-8 string containing newline-delimited list elements, allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error.</returns>
|
/// <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>
|
/// <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")]
|
[UnmanagedCallersOnly(EntryPoint = "ListRange")]
|
||||||
public static NativeStringList NativeListRange(IntPtr handle, IntPtr keyPtr, int start, int stop)
|
public static IntPtr NativeListRange(IntPtr handle, IntPtr keyPtr, int start, int stop)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
List<string>? result = client?.ListRange(key, start, stop);
|
List<string>? result = client?.ListRange(key, start, stop);
|
||||||
return MarshalStringList(result);
|
// Marshal List<string> as a single newline-delimited string
|
||||||
}
|
return MarshalStringListResult(result);
|
||||||
catch
|
}
|
||||||
{
|
catch { return IntPtr.Zero; }
|
||||||
return new NativeStringList { Strings = IntPtr.Zero, Count = 0 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -497,9 +329,9 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var result = client?.ListIndex(key, index);
|
string? result = client?.ListIndex(key, index);
|
||||||
return MarshalString(result);
|
return MarshalStringResult(result);
|
||||||
}
|
}
|
||||||
catch { return IntPtr.Zero; }
|
catch { return IntPtr.Zero; }
|
||||||
}
|
}
|
||||||
@ -518,8 +350,8 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
|
string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
|
||||||
return client?.ListSet(key, index, value) ?? false;
|
return client?.ListSet(key, index, value) ?? false;
|
||||||
}
|
}
|
||||||
catch { return false; }
|
catch { return false; }
|
||||||
@ -541,8 +373,8 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var element = Marshal.PtrToStringUTF8(elementPtr) ?? string.Empty;
|
string element = Marshal.PtrToStringUTF8(elementPtr) ?? string.Empty;
|
||||||
// Note: C# ListPosition returns the first index or -1
|
// Note: C# ListPosition returns the first index or -1
|
||||||
var position = client?.ListPosition(key, element, rank, maxlen);
|
var position = client?.ListPosition(key, element, rank, maxlen);
|
||||||
return position ?? -1;
|
return position ?? -1;
|
||||||
@ -564,7 +396,7 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
return client?.ListTrim(key, start, stop) ?? false;
|
return client?.ListTrim(key, start, stop) ?? false;
|
||||||
}
|
}
|
||||||
catch { return false; }
|
catch { return false; }
|
||||||
@ -584,8 +416,8 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var element = Marshal.PtrToStringUTF8(elementPtr) ?? string.Empty;
|
string element = Marshal.PtrToStringUTF8(elementPtr) ?? string.Empty;
|
||||||
return client?.ListRemove(key, count, element) ?? 0;
|
return client?.ListRemove(key, count, element) ?? 0;
|
||||||
}
|
}
|
||||||
catch { return 0; }
|
catch { return 0; }
|
||||||
@ -607,9 +439,9 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty;
|
string field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty;
|
||||||
var value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
|
string value = Marshal.PtrToStringUTF8(valuePtr) ?? string.Empty;
|
||||||
// Assuming HSET returns 1 if field is new, 0 if updated
|
// Assuming HSET returns 1 if field is new, 0 if updated
|
||||||
return client?.HashSet(key, field, value) ?? false; // Adapt if C# returns differently
|
return client?.HashSet(key, field, value) ?? false; // Adapt if C# returns differently
|
||||||
}
|
}
|
||||||
@ -629,10 +461,10 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty;
|
string field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty;
|
||||||
var result = client?.HashGet(key, field);
|
string? result = client?.HashGet(key, field);
|
||||||
return MarshalString(result);
|
return MarshalStringResult(result);
|
||||||
}
|
}
|
||||||
catch { return IntPtr.Zero; }
|
catch { return IntPtr.Zero; }
|
||||||
}
|
}
|
||||||
@ -650,8 +482,8 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty;
|
string field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty;
|
||||||
// Assuming HDEL returns true if field was deleted, false otherwise
|
// Assuming HDEL returns true if field was deleted, false otherwise
|
||||||
return client?.HashDelete(key, field) ?? false;
|
return client?.HashDelete(key, field) ?? false;
|
||||||
}
|
}
|
||||||
@ -671,8 +503,8 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty;
|
string field = Marshal.PtrToStringUTF8(fieldPtr) ?? string.Empty;
|
||||||
return client?.HashFieldExists(key, field) ?? false;
|
return client?.HashFieldExists(key, field) ?? false;
|
||||||
}
|
}
|
||||||
catch { return false; }
|
catch { return false; }
|
||||||
@ -692,8 +524,8 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
var fieldValuePairs = Marshal.PtrToStringUTF8(fieldValuePairsPtr) ?? string.Empty;
|
string fieldValuePairs = Marshal.PtrToStringUTF8(fieldValuePairsPtr) ?? string.Empty;
|
||||||
|
|
||||||
string[] pairs = fieldValuePairs.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
string[] pairs = fieldValuePairs.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (pairs.Length % 2 != 0)
|
if (pairs.Length % 2 != 0)
|
||||||
@ -703,7 +535,7 @@ namespace FireflyClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dict = new Dictionary<string, string>();
|
var dict = new Dictionary<string, string>();
|
||||||
for (var i = 0; i < pairs.Length; i += 2)
|
for (int i = 0; i < pairs.Length; i += 2)
|
||||||
{
|
{
|
||||||
dict[pairs[i]] = pairs[i + 1];
|
dict[pairs[i]] = pairs[i + 1];
|
||||||
}
|
}
|
||||||
@ -721,19 +553,17 @@ namespace FireflyClient
|
|||||||
/// <returns>Pointer to a null-terminated UTF-8 string containing newline-delimited "field=value" pairs, allocated via AllocHGlobal (caller must free with FreeString), or IntPtr.Zero on error or if hash not found.</returns>
|
/// <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>
|
/// <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")]
|
[UnmanagedCallersOnly(EntryPoint = "HashGetAll")]
|
||||||
public static NativeDictionary NativeHashGetAll(IntPtr handle, IntPtr keyPtr)
|
public static IntPtr NativeHashGetAll(IntPtr handle, IntPtr keyPtr)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
string key = Marshal.PtrToStringUTF8(keyPtr) ?? string.Empty;
|
||||||
Dictionary<string, string>? result = client?.HashGetAll(key);
|
Dictionary<string, string>? result = client?.HashGetAll(key);
|
||||||
return MarshalDictionary(result);
|
// Marshal Dictionary<string, string> as a single newline-delimited string
|
||||||
}
|
return MarshalStringDictionaryResult(result);
|
||||||
catch
|
}
|
||||||
{
|
catch { return IntPtr.Zero; }
|
||||||
return new NativeDictionary { Pairs = IntPtr.Zero, Count = 0 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Pipeline Operations (Already Implemented) ---
|
// --- Pipeline Operations (Already Implemented) ---
|
||||||
@ -795,8 +625,8 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var response = client?.FlushPipeline();
|
string? response = client?.FlushPipeline();
|
||||||
return MarshalString(response);
|
return MarshalStringResult(response);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -873,7 +703,7 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = GetClientFromHandle(handle);
|
var client = GetClientFromHandle(handle);
|
||||||
var pattern = Marshal.PtrToStringUTF8(patternPtr) ?? "*";
|
string pattern = Marshal.PtrToStringUTF8(patternPtr) ?? "*";
|
||||||
List<string>? result = client?.Keys(pattern);
|
List<string>? result = client?.Keys(pattern);
|
||||||
return MarshalStringListResult(result);
|
return MarshalStringListResult(result);
|
||||||
}
|
}
|
||||||
|
124
src/Program.cs
124
src/Program.cs
@ -7,9 +7,9 @@ namespace FireflyClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Command-line interface for Firefly
|
/// Command-line interface for Firefly
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Program
|
public class Program
|
||||||
{
|
{
|
||||||
private static async Task Main(string[] args)
|
static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Firefly Client");
|
Console.WriteLine("Firefly Client");
|
||||||
Console.WriteLine("Enter commands (type EXIT to quit)");
|
Console.WriteLine("Enter commands (type EXIT to quit)");
|
||||||
@ -17,28 +17,29 @@ namespace FireflyClient
|
|||||||
Console.WriteLine("Type HELP for basic commands or HELP EMAIL for email examples");
|
Console.WriteLine("Type HELP for basic commands or HELP EMAIL for email examples");
|
||||||
|
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
var host = "127.0.0.1";
|
string host = "127.0.0.1";
|
||||||
var port = 6379;
|
int port = 6379;
|
||||||
var password = string.Empty;
|
string password = string.Empty;
|
||||||
|
|
||||||
for (var i = 0; i < args.Length; i++)
|
for (int i = 0; i < args.Length; i++)
|
||||||
{
|
{
|
||||||
switch (args[i])
|
if ((args[i] == "--host" || args[i] == "-h") && i + 1 < args.Length)
|
||||||
{
|
{
|
||||||
case "--host" or "-h" when i + 1 < args.Length:
|
host = args[++i];
|
||||||
host = args[++i];
|
}
|
||||||
break;
|
else if ((args[i] == "--port" || args[i] == "-p") && i + 1 < args.Length && int.TryParse(args[i + 1], out int parsedPort))
|
||||||
case "--port" or "-p" when i + 1 < args.Length && int.TryParse(args[i + 1], out var parsedPort):
|
{
|
||||||
port = parsedPort;
|
port = parsedPort;
|
||||||
i++;
|
i++;
|
||||||
break;
|
}
|
||||||
case "--password" or "--pass" when i + 1 < args.Length:
|
else if ((args[i] == "--password" || args[i] == "--pass") && i + 1 < args.Length)
|
||||||
password = args[++i];
|
{
|
||||||
break;
|
password = args[++i];
|
||||||
case "--help":
|
}
|
||||||
case "-?":
|
else if (args[i] == "--help" || args[i] == "-?")
|
||||||
PrintClientHelp();
|
{
|
||||||
return;
|
PrintClientHelp();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ namespace FireflyClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintClientHelp()
|
static void PrintClientHelp()
|
||||||
{
|
{
|
||||||
Console.WriteLine("\nFirefly Client Usage:");
|
Console.WriteLine("\nFirefly Client Usage:");
|
||||||
Console.WriteLine(" --host, -h <hostname> Server hostname or IP (default: 127.0.0.1)");
|
Console.WriteLine(" --host, -h <hostname> Server hostname or IP (default: 127.0.0.1)");
|
||||||
@ -62,7 +63,7 @@ namespace FireflyClient
|
|||||||
Console.WriteLine("Example: FireflyClient --host localhost --port 6380 --password secret123\n");
|
Console.WriteLine("Example: FireflyClient --host localhost --port 6380 --password secret123\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task RunInteractiveClientAsync(string host, int port, string password)
|
static Task RunInteractiveClientAsync(string host, int port, string password)
|
||||||
{
|
{
|
||||||
using var client = new FireflyClient(host, port);
|
using var client = new FireflyClient(host, port);
|
||||||
Console.WriteLine($"Connecting to server at {host}:{port}...");
|
Console.WriteLine($"Connecting to server at {host}:{port}...");
|
||||||
@ -78,7 +79,7 @@ namespace FireflyClient
|
|||||||
// Authenticate if password was provided
|
// Authenticate if password was provided
|
||||||
if (!string.IsNullOrEmpty(password))
|
if (!string.IsNullOrEmpty(password))
|
||||||
{
|
{
|
||||||
var success = client.Authenticate(password);
|
bool success = client.Authenticate(password);
|
||||||
|
|
||||||
// Check if authentication failed
|
// Check if authentication failed
|
||||||
if (!success)
|
if (!success)
|
||||||
@ -92,15 +93,15 @@ namespace FireflyClient
|
|||||||
{
|
{
|
||||||
// Get command from user
|
// Get command from user
|
||||||
Console.Write("> ");
|
Console.Write("> ");
|
||||||
var input = Console.ReadLine() ?? string.Empty;
|
string input = Console.ReadLine() ?? string.Empty;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(input) || input.Equals("EXIT", StringComparison.OrdinalIgnoreCase))
|
if (string.IsNullOrEmpty(input) || input.Equals("EXIT", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Send QUIT command before exiting
|
// Send QUIT command before exiting
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = client.ExecuteCommand("QUIT");
|
string response = client.ExecuteCommand("QUIT");
|
||||||
Console.WriteLine($"{response.TrimEnd('\r', '\n')}");
|
Console.WriteLine($"Server: {response.TrimEnd('\r', '\n')}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -126,14 +127,14 @@ namespace FireflyClient
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Parse the command
|
// Parse the command
|
||||||
var parts = SplitCommandLine(input);
|
string[] parts = SplitCommandLine(input);
|
||||||
if (parts.Length == 0) continue;
|
if (parts.Length == 0) continue;
|
||||||
|
|
||||||
var command = parts[0];
|
string command = parts[0];
|
||||||
string[] args = [.. parts.Skip(1)];
|
string[] args = [.. parts.Skip(1)];
|
||||||
|
|
||||||
// Execute the command
|
// Execute the command
|
||||||
var response = client.ExecuteCommand(command, args);
|
string response = client.ExecuteCommand(command, args);
|
||||||
FormatAndPrintResponse(response);
|
FormatAndPrintResponse(response);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -147,31 +148,33 @@ namespace FireflyClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to split command line respecting quotes
|
// Helper method to split command line respecting quotes
|
||||||
private static string[] SplitCommandLine(string commandLine)
|
static string[] SplitCommandLine(string commandLine)
|
||||||
{
|
{
|
||||||
var result = new List<string>();
|
var result = new List<string>();
|
||||||
var inQuotes = false;
|
bool inQuotes = false;
|
||||||
StringBuilder currentArg = new();
|
StringBuilder currentArg = new();
|
||||||
|
|
||||||
foreach (var c in commandLine)
|
for (int i = 0; i < commandLine.Length; i++)
|
||||||
{
|
{
|
||||||
switch (c)
|
char c = commandLine[i];
|
||||||
|
|
||||||
|
if (c == '"')
|
||||||
{
|
{
|
||||||
case '"':
|
inQuotes = !inQuotes;
|
||||||
inQuotes = !inQuotes;
|
// Don't include the quote character
|
||||||
// Don't include the quote character
|
}
|
||||||
break;
|
else if (c == ' ' && !inQuotes)
|
||||||
case ' ' when !inQuotes:
|
{
|
||||||
|
// End of argument
|
||||||
|
if (currentArg.Length > 0)
|
||||||
{
|
{
|
||||||
// End of argument
|
|
||||||
if (currentArg.Length <= 0) continue;
|
|
||||||
result.Add(currentArg.ToString());
|
result.Add(currentArg.ToString());
|
||||||
currentArg.Clear();
|
currentArg.Clear();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
default:
|
}
|
||||||
currentArg.Append(c);
|
else
|
||||||
break;
|
{
|
||||||
|
currentArg.Append(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,8 +186,8 @@ namespace FireflyClient
|
|||||||
|
|
||||||
return [.. result];
|
return [.. result];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FormatAndPrintResponse(string response)
|
static void FormatAndPrintResponse(string response)
|
||||||
{
|
{
|
||||||
// Remove trailing whitespace
|
// Remove trailing whitespace
|
||||||
response = response.TrimEnd();
|
response = response.TrimEnd();
|
||||||
@ -192,20 +195,23 @@ namespace FireflyClient
|
|||||||
if (response.StartsWith('*') && response.Contains("\r\n"))
|
if (response.StartsWith('*') && response.Contains("\r\n"))
|
||||||
{
|
{
|
||||||
// Format array responses for better readability
|
// Format array responses for better readability
|
||||||
var parts = response.Split("\r\n");
|
string[] parts = response.Split("\r\n");
|
||||||
if (parts.Length > 1)
|
if (parts.Length > 1)
|
||||||
{
|
{
|
||||||
for (var i = 1; i < parts.Length; i++)
|
Console.WriteLine("Server: Array response:");
|
||||||
|
for (int i = 1; i < parts.Length; i++)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(parts[i])) continue;
|
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..]}");
|
// For simple string and bulk string responses in an array
|
||||||
}
|
if (parts[i].StartsWith('+') || parts[i].StartsWith('$'))
|
||||||
else if (!parts[i].StartsWith('*')) // Skip the array count line
|
{
|
||||||
{
|
Console.WriteLine($"{i}) {parts[i][1..]}");
|
||||||
Console.WriteLine($"{i}) {parts[i]}");
|
}
|
||||||
|
else if (!parts[i].StartsWith('*')) // Skip the array count line
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{i}) {parts[i]}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -213,11 +219,11 @@ namespace FireflyClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default formatting
|
// Default formatting
|
||||||
Console.WriteLine($"{response}");
|
Console.WriteLine($"Server: {response}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method: Print basic commands
|
// Helper method: Print basic commands
|
||||||
private static void PrintBasicCommands()
|
static void PrintBasicCommands()
|
||||||
{
|
{
|
||||||
Console.WriteLine("\n==== Basic Firefly Commands ====");
|
Console.WriteLine("\n==== Basic Firefly Commands ====");
|
||||||
|
|
||||||
|
@ -7,29 +7,39 @@ namespace FireflyClient
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets a key-value pair
|
/// Sets a key-value pair
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool StringSet(string key, string value)
|
public bool StringSet(string key, string value)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("SET", key, value);
|
string response = ExecuteCommand("SET", key, value);
|
||||||
return response.StartsWith("+OK");
|
return response.StartsWith("+OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value by key
|
/// Gets a value by key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string StringGet(string key)
|
public string StringGet(string key)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("GET", key);
|
string response = ExecuteCommand("GET", key);
|
||||||
return response.StartsWith('+') ? response[1..].TrimEnd('\r', '\n') : string.Empty;
|
if (response.StartsWith('+'))
|
||||||
|
{
|
||||||
|
return response[1..].TrimEnd('\r', '\n');
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes a key from all stores (string, list, hash)
|
/// Deletes a key from all stores (string, list, hash)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int Delete(string key)
|
public int Delete(string key)
|
||||||
{
|
{
|
||||||
var response = ExecuteCommand("DEL", key);
|
string response = ExecuteCommand("DEL", key);
|
||||||
if (!response.StartsWith(':')) return 0;
|
if (response.StartsWith(':'))
|
||||||
return int.TryParse(response[1..].TrimEnd('\r', '\n'), out var result) ? result : 0;
|
{
|
||||||
|
if (int.TryParse(response[1..].TrimEnd('\r', '\n'), out int result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
Loading…
x
Reference in New Issue
Block a user